mirror of
https://github.com/ArthurDanjou/ui.git
synced 2026-01-14 20:19:34 +01:00
chore: improve popper handling
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
</slot>
|
||||
</MenuButton>
|
||||
|
||||
<div v-if="open" ref="container" :class="containerClass" @mouseover="onMouseOver">
|
||||
<div v-if="open" ref="container" :class="[containerClass, widthClass]" @mouseover="onMouseOver">
|
||||
<transition appear v-bind="transitionClass">
|
||||
<MenuItems :class="baseClass" static>
|
||||
<div v-for="(subItems, index) of items" :key="index" class="py-1">
|
||||
@@ -44,8 +44,10 @@ import { ref, onMounted } from 'vue'
|
||||
import NuxtLink from '#app/components/nuxt-link'
|
||||
import Icon from '../elements/Icon.vue'
|
||||
import Avatar from '../elements/Avatar.vue'
|
||||
import { classNames, usePopper } from '../../utils'
|
||||
import { classNames } from '../../utils'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import type { Avatar as AvatarType } from '../../types/avatar'
|
||||
import type { PopperOptions } from './../types'
|
||||
import $ui from '#build/ui'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -64,20 +66,6 @@ const props = defineProps({
|
||||
}[][]>,
|
||||
default: () => []
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottom-end',
|
||||
validator: (value: string) => {
|
||||
return ['auto', 'auto-start', 'auto-end', 'top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end'].includes(value)
|
||||
}
|
||||
},
|
||||
strategy: {
|
||||
type: String,
|
||||
default: 'fixed',
|
||||
validator: (value: string) => {
|
||||
return ['absolute', 'fixed'].includes(value)
|
||||
}
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'click',
|
||||
@@ -93,6 +81,10 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: () => $ui.dropdown.container
|
||||
},
|
||||
widthClass: {
|
||||
type: String,
|
||||
default: () => $ui.dropdown.width
|
||||
},
|
||||
baseClass: {
|
||||
type: String,
|
||||
default: () => $ui.dropdown.base
|
||||
@@ -128,32 +120,17 @@ const props = defineProps({
|
||||
itemShortcutsClass: {
|
||||
type: String,
|
||||
default: () => $ui.dropdown.item.shortcuts
|
||||
},
|
||||
popperOptions: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => ({
|
||||
placement: 'bottom-end',
|
||||
strategy: 'fixed'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const [trigger, container] = usePopper({
|
||||
placement: props.placement,
|
||||
strategy: props.strategy,
|
||||
modifiers: [{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'computeStyles',
|
||||
options: {
|
||||
gpuAcceleration: false,
|
||||
adaptive: false
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
padding: 8
|
||||
}
|
||||
}]
|
||||
})
|
||||
const [trigger, container] = usePopper(props.popperOptions)
|
||||
|
||||
function resolveItemClass ({ active, disabled }: { active: boolean, disabled: boolean }) {
|
||||
return classNames(
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
</slot>
|
||||
</ComboboxButton>
|
||||
|
||||
<div v-if="open" ref="container" :class="listContainerClass">
|
||||
<div v-if="open" ref="container" :class="[listContainerClass, listWidthClass]">
|
||||
<transition appear v-bind="listTransitionClass">
|
||||
<ComboboxOptions static :class="listBaseClass">
|
||||
<ComboboxInput
|
||||
@@ -93,7 +93,9 @@ import {
|
||||
ComboboxInput
|
||||
} from '@headlessui/vue'
|
||||
import Icon from '../elements/Icon.vue'
|
||||
import { classNames, usePopper } from '../../utils'
|
||||
import { classNames } from '../../utils'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import type { PopperOptions } from './../types'
|
||||
import $ui from '#build/ui'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -105,20 +107,6 @@ const props = defineProps({
|
||||
type: Array as PropType<{ disabled?: boolean }[]>,
|
||||
default: () => []
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottom-end',
|
||||
validator: (value: string) => {
|
||||
return ['auto', 'auto-start', 'auto-end', 'top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end'].includes(value)
|
||||
}
|
||||
},
|
||||
strategy: {
|
||||
type: String,
|
||||
default: 'absolute',
|
||||
validator: (value: string) => {
|
||||
return ['absolute', 'fixed'].includes(value)
|
||||
}
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@@ -189,6 +177,10 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: () => $ui.selectCustom.list.container
|
||||
},
|
||||
listWidthClass: {
|
||||
type: String,
|
||||
default: () => $ui.selectCustom.list.width
|
||||
},
|
||||
listInputClass: {
|
||||
type: String,
|
||||
default: () => $ui.selectCustom.list.input
|
||||
@@ -256,34 +248,18 @@ const props = defineProps({
|
||||
searchAttributes: {
|
||||
type: Array,
|
||||
default: null
|
||||
},
|
||||
popperOptions: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => ({
|
||||
placement: 'bottom-end'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const [trigger, container] = usePopper({
|
||||
placement: props.placement,
|
||||
strategy: props.strategy,
|
||||
modifiers: [{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'computeStyles',
|
||||
options: {
|
||||
gpuAcceleration: false,
|
||||
adaptive: false
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
padding: 8
|
||||
}
|
||||
}]
|
||||
})
|
||||
const [trigger, container] = usePopper(props.popperOptions)
|
||||
|
||||
const query = ref('')
|
||||
const searchInput = ref<ComponentPublicInstance<HTMLElement>>()
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</slot>
|
||||
</PopoverButton>
|
||||
|
||||
<div v-if="open" ref="container" :class="containerClass" @mouseover="onMouseOver">
|
||||
<div v-if="open" ref="container" :class="[containerClass, widthClass]" @mouseover="onMouseOver">
|
||||
<transition appear v-bind="transitionClass">
|
||||
<PopoverPanel :class="baseClass" static>
|
||||
<slot name="panel" :open="open" :close="close" />
|
||||
@@ -17,21 +17,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Ref } from 'vue'
|
||||
import type { Ref, PropType } from 'vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue'
|
||||
import { usePopper } from '../../utils'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import type { PopperOptions } from './../types'
|
||||
import $ui from '#build/ui'
|
||||
|
||||
const props = defineProps({
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottom'
|
||||
},
|
||||
strategy: {
|
||||
type: String,
|
||||
default: 'fixed'
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
default: 'click',
|
||||
@@ -47,6 +40,10 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: () => $ui.popover.container
|
||||
},
|
||||
widthClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.width
|
||||
},
|
||||
baseClass: {
|
||||
type: String,
|
||||
default: () => $ui.popover.base
|
||||
@@ -54,32 +51,16 @@ const props = defineProps({
|
||||
transitionClass: {
|
||||
type: Object,
|
||||
default: () => $ui.popover.transition
|
||||
},
|
||||
popperOptions: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => ({
|
||||
strategy: 'fixed'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const [trigger, container] = usePopper({
|
||||
placement: props.placement,
|
||||
strategy: props.strategy,
|
||||
modifiers: [{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'computeStyles',
|
||||
options: {
|
||||
gpuAcceleration: false,
|
||||
adaptive: false
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
padding: 8
|
||||
}
|
||||
}]
|
||||
})
|
||||
const [trigger, container] = usePopper(props.popperOptions)
|
||||
|
||||
const popoverApi: Ref<any> = ref(null)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
Hover me
|
||||
</slot>
|
||||
|
||||
<div v-if="open" ref="container" :class="containerClass">
|
||||
<div v-if="open" ref="container" :class="[containerClass, widthClass]">
|
||||
<transition appear v-bind="transitionClass">
|
||||
<div :class="baseClass">
|
||||
<slot name="text">
|
||||
@@ -17,8 +17,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { PropType } from 'vue'
|
||||
import { ref } from 'vue'
|
||||
import { usePopper } from '../../utils'
|
||||
import { usePopper } from '../../composables/usePopper'
|
||||
import type { PopperOptions } from './../types'
|
||||
import $ui from '#build/ui'
|
||||
|
||||
const props = defineProps({
|
||||
@@ -26,24 +28,6 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
placement: {
|
||||
type: String,
|
||||
default: 'bottom',
|
||||
validator: (value: string) => {
|
||||
return ['auto', 'auto-start', 'auto-end', 'top', 'top-start', 'top-end', 'bottom', 'bottom-start', 'bottom-end', 'right', 'right-start', 'right-end', 'left', 'left-start', 'left-end'].includes(value)
|
||||
}
|
||||
},
|
||||
strategy: {
|
||||
type: String,
|
||||
default: 'fixed',
|
||||
validator: (value: string) => {
|
||||
return ['absolute', 'fixed'].includes(value)
|
||||
}
|
||||
},
|
||||
flip: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
wrapperClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.wrapper
|
||||
@@ -52,6 +36,10 @@ const props = defineProps({
|
||||
type: String,
|
||||
default: () => $ui.tooltip.container
|
||||
},
|
||||
widthClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.width
|
||||
},
|
||||
baseClass: {
|
||||
type: String,
|
||||
default: () => $ui.tooltip.base
|
||||
@@ -59,36 +47,17 @@ const props = defineProps({
|
||||
transitionClass: {
|
||||
type: Object,
|
||||
default: () => $ui.tooltip.transition
|
||||
},
|
||||
popperOptions: {
|
||||
type: Object as PropType<PopperOptions>,
|
||||
default: () => ({
|
||||
strategy: 'fixed'
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const open = ref(false)
|
||||
const [trigger, container] = usePopper({
|
||||
placement: props.placement,
|
||||
strategy: props.strategy,
|
||||
modifiers: [{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'computeStyles',
|
||||
options: {
|
||||
gpuAcceleration: false,
|
||||
adaptive: false
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
padding: 8
|
||||
}
|
||||
}, {
|
||||
name: 'flip',
|
||||
enabled: props.flip
|
||||
}]
|
||||
})
|
||||
const [trigger, container] = usePopper(props.popperOptions)
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
|
||||
58
src/runtime/composables/usePopper.ts
Normal file
58
src/runtime/composables/usePopper.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { ref, onMounted, watchEffect } from 'vue'
|
||||
import { popperGenerator, defaultModifiers } from '@popperjs/core/lib/popper-lite'
|
||||
import { omitBy, isUndefined } from 'lodash-es'
|
||||
import flip from '@popperjs/core/lib/modifiers/flip'
|
||||
import offset from '@popperjs/core/lib/modifiers/offset'
|
||||
import preventOverflow from '@popperjs/core/lib/modifiers/preventOverflow'
|
||||
|
||||
const createPopper = popperGenerator({
|
||||
defaultModifiers: [...defaultModifiers, offset, flip, preventOverflow]
|
||||
})
|
||||
|
||||
export function usePopper ({
|
||||
locked = false,
|
||||
overflowPadding = 8,
|
||||
offsetDistance = 8,
|
||||
offsetSkid = 0,
|
||||
placement,
|
||||
strategy
|
||||
}) {
|
||||
const reference = ref(null)
|
||||
const popper = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
watchEffect((onInvalidate) => {
|
||||
if (!popper.value) { return }
|
||||
if (!reference.value) { return }
|
||||
|
||||
const popperEl = popper.value.$el || popper.value
|
||||
const referenceEl = reference.value.$el || reference.value
|
||||
|
||||
if (!(referenceEl instanceof HTMLElement)) { return }
|
||||
if (!(popperEl instanceof HTMLElement)) { return }
|
||||
|
||||
const { destroy } = createPopper(referenceEl, popperEl, omitBy({
|
||||
placement,
|
||||
strategy,
|
||||
modifiers: [{
|
||||
name: 'flip',
|
||||
enabled: !locked
|
||||
}, {
|
||||
name: 'preventOverflow',
|
||||
options: {
|
||||
padding: overflowPadding
|
||||
}
|
||||
}, {
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [offsetSkid, offsetDistance]
|
||||
}
|
||||
}]
|
||||
}, isUndefined))
|
||||
|
||||
onInvalidate(destroy)
|
||||
})
|
||||
})
|
||||
|
||||
return [reference, popper]
|
||||
}
|
||||
@@ -195,7 +195,8 @@ export default (variantColors: string[]) => {
|
||||
...select.icon
|
||||
},
|
||||
list: {
|
||||
container: 'z-10 w-full py-1',
|
||||
container: 'z-20',
|
||||
width: 'w-full',
|
||||
base: 'u-bg-white shadow-lg rounded-md ring-1 u-ring-gray-200 focus:outline-none overflow-y-auto py-1 max-h-60',
|
||||
input: 'relative block w-full focus:ring-transparent text-sm px-4 py-2 u-text-gray-700 border-l-0 u-bg-white border-t-0 border-r-0 u-border-gray-200 focus:u-border-gray-200',
|
||||
option: {
|
||||
@@ -335,7 +336,8 @@ export default (variantColors: string[]) => {
|
||||
|
||||
const dropdown = {
|
||||
wrapper: 'relative inline-flex text-left',
|
||||
container: 'w-48 z-20 py-2',
|
||||
container: 'z-20',
|
||||
width: 'w-48',
|
||||
base: 'u-bg-white divide-y u-divide-gray-100 rounded-md ring-1 u-ring-gray-200 shadow-lg',
|
||||
item: {
|
||||
base: 'group flex items-center gap-3 px-4 py-2 text-sm w-full',
|
||||
@@ -473,7 +475,8 @@ export default (variantColors: string[]) => {
|
||||
|
||||
const tooltip = {
|
||||
wrapper: 'relative inline-flex',
|
||||
container: 'z-10 py-2',
|
||||
container: 'z-20',
|
||||
width: '',
|
||||
base: 'flex items-center justify-center invisible w-auto h-6 max-w-xs px-2 space-x-1 truncate rounded shadow lg:visible u-bg-gray-800 truncate u-text-gray-50 text-xs',
|
||||
transition: {
|
||||
enterActiveClass: 'transition ease-out duration-200',
|
||||
@@ -487,7 +490,8 @@ export default (variantColors: string[]) => {
|
||||
|
||||
const popover = {
|
||||
wrapper: 'relative',
|
||||
container: 'z-10 py-2',
|
||||
container: 'z-20',
|
||||
width: '',
|
||||
base: '',
|
||||
transition: {
|
||||
enterActiveClass: 'transition ease-out duration-200',
|
||||
|
||||
1
src/runtime/types/index.d.ts
vendored
1
src/runtime/types/index.d.ts
vendored
@@ -1,2 +1,3 @@
|
||||
export * from './clipboard'
|
||||
export * from './toast'
|
||||
export * from './popper'
|
||||
|
||||
10
src/runtime/types/popper.d.ts
vendored
Normal file
10
src/runtime/types/popper.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
import type { Placement, PositioningStrategy } from '@popperjs/core'
|
||||
|
||||
export interface PopperOptions {
|
||||
locked?: boolean
|
||||
overflowPadding?: number
|
||||
offsetDistance?: number
|
||||
offsetSkid?: number
|
||||
placement?: Placement
|
||||
strategy?: PositioningStrategy
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
export * from './popper'
|
||||
|
||||
export function classNames (...classes: any[string]) {
|
||||
return classes.filter(Boolean).join(' ')
|
||||
}
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
import { ref, onMounted, watchEffect } from 'vue'
|
||||
import { createPopper } from '@popperjs/core'
|
||||
|
||||
export function usePopper (options: object) {
|
||||
const reference = ref(null)
|
||||
const popper = ref(null)
|
||||
|
||||
onMounted(() => {
|
||||
watchEffect((onInvalidate) => {
|
||||
if (!popper.value) { return }
|
||||
if (!reference.value) { return }
|
||||
|
||||
const popperEl = popper.value.$el || popper.value
|
||||
const referenceEl = reference.value.$el || reference.value
|
||||
|
||||
if (!(referenceEl instanceof HTMLElement)) { return }
|
||||
if (!(popperEl instanceof HTMLElement)) { return }
|
||||
|
||||
const { destroy } = createPopper(referenceEl, popperEl, options)
|
||||
|
||||
onInvalidate(destroy)
|
||||
})
|
||||
})
|
||||
|
||||
return [reference, popper]
|
||||
}
|
||||
Reference in New Issue
Block a user