diff --git a/.vscode/settings.json b/.vscode/settings.json index 57ddcdd5..d9940c6a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -81,7 +81,8 @@ "cSpell.words": [ "composables", "Vite", - "vitepress" + "vitepress", + "Vuetify's" ], // Extension: UnoCSS "unocss.root": "packages/documentation", diff --git a/netlify.toml b/netlify.toml index 9076f3ef..8f6e09f0 100644 --- a/netlify.toml +++ b/netlify.toml @@ -12,7 +12,7 @@ # a base has not been set. This sample publishes the directory # located at the absolute path "root/project/build-output" - publish = "packages/documentation/docs/.vitepress/dist" + publish = "docs/.vitepress/dist" command = "npx pnpm i --store=node_modules/.pnpm-store && npm run docs:build" # Default build command. diff --git a/packages/anu-vue/package.json b/packages/anu-vue/package.json index e170e103..ee0bcf4f 100644 --- a/packages/anu-vue/package.json +++ b/packages/anu-vue/package.json @@ -66,7 +66,7 @@ }, "peerDependencies": { "@unocss/reset": "^0.41.1", - "@vueuse/core": "^8.7.5", + "@vueuse/core": "^9.6.0", "vue-router": "4" } } diff --git a/packages/anu-vue/src/components/dialog/ADialog.tsx b/packages/anu-vue/src/components/dialog/ADialog.tsx index a8101fc7..571b625b 100644 --- a/packages/anu-vue/src/components/dialog/ADialog.tsx +++ b/packages/anu-vue/src/components/dialog/ADialog.tsx @@ -1,7 +1,8 @@ -import { onClickOutside } from '@vueuse/core' +import { onClickOutside, useMounted } from '@vueuse/core' import { Teleport, Transition, defineComponent, ref, toRef } from 'vue' import { ACard, useCardProps } from '@/components/card' import { useDOMScrollLock } from '@/composables/useDOMScrollLock' +import { useTeleport } from '@/composables/useTeleport' export const ADialog = defineComponent({ name: 'ADialog', @@ -27,6 +28,9 @@ export const ADialog = defineComponent({ }, emits: ['update:modelValue'], setup(props, { slots, emit, attrs }) { + const { teleportTarget } = useTeleport() + const isMounted = useMounted() + const refCard = ref() if (!props.persistent) { onClickOutside(refCard, () => { @@ -41,26 +45,28 @@ export const ADialog = defineComponent({ // Lock DOM scroll when modelValue is `true` useDOMScrollLock(toRef(props, 'modelValue')) - return () => - -
- - - {{ ...slots }} - - -
-
-
+ return () => isMounted.value + ? + +
+ + + {{ ...slots }} + + +
+
+
+ : null }, }) diff --git a/packages/anu-vue/src/components/drawer/ADrawer.tsx b/packages/anu-vue/src/components/drawer/ADrawer.tsx index 01ffbd64..bd688b9f 100644 --- a/packages/anu-vue/src/components/drawer/ADrawer.tsx +++ b/packages/anu-vue/src/components/drawer/ADrawer.tsx @@ -1,8 +1,9 @@ -import { onClickOutside } from '@vueuse/core' +import { onClickOutside, useMounted } from '@vueuse/core' import type { PropType } from 'vue' import { Teleport, Transition, defineComponent, ref, toRef } from 'vue' -import { useDOMScrollLock } from '@/composables/useDOMScrollLock' import { ACard, useCardProps } from '@/components/card' +import { useDOMScrollLock } from '@/composables/useDOMScrollLock' +import { useTeleport } from '@/composables/useTeleport' export const ADrawer = defineComponent({ name: 'ADrawer', @@ -35,6 +36,9 @@ export const ADrawer = defineComponent({ }, emits: ['update:modelValue'], setup(props, { slots, emit, attrs }) { + const { teleportTarget } = useTeleport() + const isMounted = useMounted() + const refCard = ref() if (!props.persistent) { onClickOutside(refCard, () => { @@ -49,11 +53,12 @@ export const ADrawer = defineComponent({ // Lock DOM scroll when modelValue is `true` useDOMScrollLock(toRef(props, 'modelValue')) - return () => - -
isMounted.value + ? + +
- - - {{ ...slots }} - - -
-
-
+ ]} + v-show={props.modelValue} + > + + + {{ ...slots }} + + +
+
+
+ : null }, }) diff --git a/packages/anu-vue/src/components/menu/AMenu.tsx b/packages/anu-vue/src/components/menu/AMenu.tsx index 89f6155d..36650995 100644 --- a/packages/anu-vue/src/components/menu/AMenu.tsx +++ b/packages/anu-vue/src/components/menu/AMenu.tsx @@ -1,9 +1,10 @@ import type { Middleware, Placement, Strategy } from '@floating-ui/dom' import { autoUpdate, computePosition, flip, shift } from '@floating-ui/dom' -import { onClickOutside, useEventListener } from '@vueuse/core' +import { onClickOutside, useEventListener, useMounted } from '@vueuse/core' import type { PropType } from 'vue' -import { Teleport, Transition, defineComponent, getCurrentInstance, onBeforeUnmount, onMounted, ref, watch } from 'vue' +import { Teleport, Transition, defineComponent, getCurrentInstance, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue' import { sameWidth as sameWidthMiddleware } from './middlewares' +import { useTeleport } from '@/composables/useTeleport' import { ACard } from '@/components' export const AMenu = defineComponent({ @@ -71,6 +72,9 @@ export const AMenu = defineComponent({ }, }, setup(props, { slots }) { + const { teleportTarget } = useTeleport() + const isMounted = useMounted() + const isMenuVisible = ref(props.modelValue ?? false) // Template refs @@ -110,15 +114,22 @@ export const AMenu = defineComponent({ } let floatingUiCleanup: Function + onMounted(() => { const vm = getCurrentInstance() refReference.value = vm?.proxy?.$el?.parentNode - - // Recalculate position if placement changes at runtime - watch(() => props.placement, () => { - floatingUiCleanup = autoUpdate(refReference.value, refFloating.value.$el, calculateFloatingPosition) - }, { immediate: true }) }) + + // Recalculate position if placement changes at runtime + watch( + [isMounted, () => props.placement], + () => { + console.log('watch triggered') + nextTick(() => { + floatingUiCleanup = autoUpdate(refReference.value, refFloating.value.$el, calculateFloatingPosition) + }) + }, + ) onBeforeUnmount(() => floatingUiCleanup()) // 👉 Event listeners @@ -155,18 +166,20 @@ export const AMenu = defineComponent({ } } - return () => - {/* ℹ️ Transition component don't accept null as value of name prop so we need `props.transition || undefined` */} - - - {slots.default?.()} - - - + return () => isMounted.value + ? + {/* ℹ️ Transition component don't accept null as value of name prop so we need `props.transition || undefined` */} + + + {slots.default?.()} + + + + : null }, }) diff --git a/packages/anu-vue/src/components/select/ASelect.tsx b/packages/anu-vue/src/components/select/ASelect.tsx index 15a0636d..721b552e 100644 --- a/packages/anu-vue/src/components/select/ASelect.tsx +++ b/packages/anu-vue/src/components/select/ASelect.tsx @@ -1,8 +1,9 @@ import { autoUpdate, computePosition, flip, offset, shift } from '@floating-ui/dom' -import { onClickOutside } from '@vueuse/core' +import { onClickOutside, useMounted } from '@vueuse/core' import type { PropType } from 'vue' -import { Teleport, computed, defineComponent, onBeforeUnmount, onMounted, ref } from 'vue' +import { Teleport, computed, defineComponent, nextTick, onBeforeUnmount, onMounted, ref } from 'vue' import { isObject } from '@/utils/helpers' +import { useTeleport } from '@/composables/useTeleport' import { ABaseInput, useBaseInputProp } from '@/components/base-input' interface ObjectOption { label: string; value: string | number } @@ -51,6 +52,9 @@ export const ASelect = defineComponent({ }, emits: ['input', 'update:modelValue'], setup(props, { slots, emit, attrs }) { + const { teleportTarget } = useTeleport() + const isMounted = useMounted() + // SECTION Floating // Template refs const refReference = ref() @@ -87,7 +91,9 @@ export const ASelect = defineComponent({ let floatingUiCleanup: Function = () => { } onMounted(() => { - floatingUiCleanup = autoUpdate(refReference.value.refInputContainer, refFloating.value, calculateFloatingPosition) + nextTick(() => { + floatingUiCleanup = autoUpdate(refReference.value.refInputContainer, refFloating.value, calculateFloatingPosition) + }) }) onBeforeUnmount(() => floatingUiCleanup()) @@ -157,19 +163,21 @@ export const ASelect = defineComponent({ />, }} - - + + : null + } }, }) diff --git a/packages/anu-vue/src/composables/useTeleport.ts b/packages/anu-vue/src/composables/useTeleport.ts new file mode 100644 index 00000000..4891b7cd --- /dev/null +++ b/packages/anu-vue/src/composables/useTeleport.ts @@ -0,0 +1,38 @@ +// ℹ️ Inspired from Vuetify's useTeleport composable + +import type { Ref } from 'vue' +import { computed, unref } from 'vue' + +export function useTeleport(target?: Ref) { + const teleportTarget = computed(() => { + const _target = unref(target) + + if (typeof window === 'undefined') + return undefined + + const targetElement + = _target === undefined + ? document.body + : typeof _target === 'string' + ? document.querySelector(_target) + : _target + + if (targetElement == null) { + console.warn(`Unable to locate target ${_target}`) + + return undefined + } + + if (!useTeleport.cache.has(targetElement)) { + const el = document.createElement('div') + el.id = 'a-teleport-target' + targetElement.appendChild(el) + useTeleport.cache.set(targetElement, el) + } + + return useTeleport.cache.get(targetElement) + }) + + return { teleportTarget } +} +useTeleport.cache = new WeakMap() diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7d41587..72307577 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1463,13 +1463,13 @@ packages: resolution: {integrity: sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==} dependencies: '@types/eslint': 8.4.10 - '@types/estree': 0.0.51 + '@types/estree': 1.0.0 dev: true /@types/eslint/8.4.10: resolution: {integrity: sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==} dependencies: - '@types/estree': 0.0.51 + '@types/estree': 1.0.0 '@types/json-schema': 7.0.11 dev: true