@hina-ui/vue 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +4326 -2130
- package/dist/index.js.map +1 -1
- package/dist/types/components/alert-dialog/AlertDialog.vue.d.ts +2 -2
- package/dist/types/components/alert-dialog/AlertDialog.vue.d.ts.map +1 -1
- package/dist/types/components/dialog/Dialog.vue.d.ts.map +1 -1
- package/dist/types/components/dialog/ModalContent.vue.d.ts +21 -0
- package/dist/types/components/dialog/ModalContent.vue.d.ts.map +1 -0
- package/dist/types/components/dropdown-menu/DropdownMenu.vue.d.ts.map +1 -1
- package/dist/types/components/file-upload/FileUpload.vue.d.ts +1 -1
- package/dist/types/components/file-upload/FileUpload.vue.d.ts.map +1 -1
- package/dist/types/components/hover-card/HoverCard.vue.d.ts +7 -7
- package/dist/types/components/hover-card/HoverCard.vue.d.ts.map +1 -1
- package/dist/types/components/hover-card/HoverCardAnchor.vue.d.ts +27 -0
- package/dist/types/components/hover-card/HoverCardAnchor.vue.d.ts.map +1 -0
- package/dist/types/components/hover-card/composables/useHoverCardAnchor.d.ts +17 -0
- package/dist/types/components/hover-card/composables/useHoverCardAnchor.d.ts.map +1 -0
- package/dist/types/components/image/Image.vue.d.ts +8 -3
- package/dist/types/components/image/Image.vue.d.ts.map +1 -1
- package/dist/types/components/image/composables/useImage.d.ts.map +1 -1
- package/dist/types/components/image/composables/useImageGroupState.d.ts.map +1 -1
- package/dist/types/components/image/image.variants.d.ts +23 -1
- package/dist/types/components/image/image.variants.d.ts.map +1 -1
- package/dist/types/components/lightbox/Lightbox.vue.d.ts.map +1 -1
- package/dist/types/components/lightbox/LightboxChrome.vue.d.ts +4 -0
- package/dist/types/components/lightbox/LightboxChrome.vue.d.ts.map +1 -1
- package/dist/types/components/lightbox/LightboxToolbar.vue.d.ts +4 -0
- package/dist/types/components/lightbox/LightboxToolbar.vue.d.ts.map +1 -1
- package/dist/types/components/lightbox/composables/useLightboxFrames.d.ts +4 -1
- package/dist/types/components/lightbox/composables/useLightboxFrames.d.ts.map +1 -1
- package/dist/types/components/lightbox/composables/useLightboxLarge.d.ts +1 -0
- package/dist/types/components/lightbox/composables/useLightboxLarge.d.ts.map +1 -1
- package/dist/types/components/lightbox/composables/useLightboxLayout.d.ts +17 -0
- package/dist/types/components/lightbox/composables/useLightboxLayout.d.ts.map +1 -0
- package/dist/types/components/lightbox/composables/useLightboxMotion.d.ts.map +1 -1
- package/dist/types/components/lightbox/composables/useLightboxPhase.d.ts +1 -1
- package/dist/types/components/lightbox/composables/useLightboxPhase.d.ts.map +1 -1
- package/dist/types/components/lightbox/composables/useLightboxPose.d.ts.map +1 -1
- package/dist/types/components/lightbox/composables/useLightboxRotation.d.ts +5 -1
- package/dist/types/components/lightbox/composables/useLightboxRotation.d.ts.map +1 -1
- package/dist/types/components/lightbox/composables/useLightboxZoom.d.ts +10 -1
- package/dist/types/components/lightbox/composables/useLightboxZoom.d.ts.map +1 -1
- package/dist/types/components/lightbox/lightbox.variants.d.ts +2 -0
- package/dist/types/components/lightbox/lightbox.variants.d.ts.map +1 -0
- package/dist/types/components/lightbox/types.d.ts +7 -1
- package/dist/types/components/lightbox/types.d.ts.map +1 -1
- package/dist/types/components/lightbox/utils/clip.d.ts +16 -0
- package/dist/types/components/lightbox/utils/clip.d.ts.map +1 -0
- package/dist/types/components/lightbox/utils/pose.d.ts +4 -4
- package/dist/types/components/lightbox/utils/pose.d.ts.map +1 -1
- package/dist/types/components/lightbox/utils/source.d.ts +4 -0
- package/dist/types/components/lightbox/utils/source.d.ts.map +1 -0
- package/dist/types/components/lightbox/utils/zoom.d.ts +8 -4
- package/dist/types/components/lightbox/utils/zoom.d.ts.map +1 -1
- package/dist/types/components/pagination/Pagination.vue.d.ts +59 -0
- package/dist/types/components/pagination/Pagination.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/PaginationContent.vue.d.ts +23 -0
- package/dist/types/components/pagination/PaginationContent.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/PaginationControl.vue.d.ts +10 -0
- package/dist/types/components/pagination/PaginationControl.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/PaginationEllipsis.vue.d.ts +23 -0
- package/dist/types/components/pagination/PaginationEllipsis.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/PaginationInfo.vue.d.ts +24 -0
- package/dist/types/components/pagination/PaginationInfo.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/PaginationJump.vue.d.ts +7 -0
- package/dist/types/components/pagination/PaginationJump.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/PaginationPage.vue.d.ts +20 -0
- package/dist/types/components/pagination/PaginationPage.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/PaginationPages.vue.d.ts +27 -0
- package/dist/types/components/pagination/PaginationPages.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/PaginationPopup.vue.d.ts +8 -0
- package/dist/types/components/pagination/PaginationPopup.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/PaginationSize.vue.d.ts +7 -0
- package/dist/types/components/pagination/PaginationSize.vue.d.ts.map +1 -0
- package/dist/types/components/pagination/composables/usePagination.d.ts +25 -0
- package/dist/types/components/pagination/composables/usePagination.d.ts.map +1 -0
- package/dist/types/components/pagination/composables/usePaginationEllipsis.d.ts +34 -0
- package/dist/types/components/pagination/composables/usePaginationEllipsis.d.ts.map +1 -0
- package/dist/types/components/pagination/composables/usePaginationJump.d.ts +8 -0
- package/dist/types/components/pagination/composables/usePaginationJump.d.ts.map +1 -0
- package/dist/types/components/pagination/composables/usePaginationPageTooltip.d.ts +3 -0
- package/dist/types/components/pagination/composables/usePaginationPageTooltip.d.ts.map +1 -0
- package/dist/types/components/pagination/composables/usePaginationWindow.d.ts +188 -0
- package/dist/types/components/pagination/composables/usePaginationWindow.d.ts.map +1 -0
- package/dist/types/components/pagination/context.d.ts +19 -0
- package/dist/types/components/pagination/context.d.ts.map +1 -0
- package/dist/types/components/pagination/pagination.variants.d.ts +68 -0
- package/dist/types/components/pagination/pagination.variants.d.ts.map +1 -0
- package/dist/types/components/pagination/types.d.ts +23 -0
- package/dist/types/components/pagination/types.d.ts.map +1 -0
- package/dist/types/components/pagination/utils/ranges.d.ts +3 -0
- package/dist/types/components/pagination/utils/ranges.d.ts.map +1 -0
- package/dist/types/components/popover/Popover.vue.d.ts.map +1 -1
- package/dist/types/components/range-slider/RangeSlider.vue.d.ts +1 -0
- package/dist/types/components/range-slider/RangeSlider.vue.d.ts.map +1 -1
- package/dist/types/components/scroll-area/composables/useLayerLock.d.ts.map +1 -1
- package/dist/types/components/slider/Slider.vue.d.ts +1 -0
- package/dist/types/components/slider/Slider.vue.d.ts.map +1 -1
- package/dist/types/components/slider/slider.variants.d.ts.map +1 -1
- package/dist/types/components/tooltip/Tooltip.vue.d.ts +4 -15
- package/dist/types/components/tooltip/Tooltip.vue.d.ts.map +1 -1
- package/dist/types/components/tooltip/TooltipBubble.vue.d.ts +20 -0
- package/dist/types/components/tooltip/TooltipBubble.vue.d.ts.map +1 -0
- package/dist/types/components/tooltip/TooltipDirectiveRoot.d.ts +33 -0
- package/dist/types/components/tooltip/TooltipDirectiveRoot.d.ts.map +1 -0
- package/dist/types/components/tooltip/composables/useTooltipTarget.d.ts +2 -0
- package/dist/types/components/tooltip/composables/useTooltipTarget.d.ts.map +1 -0
- package/dist/types/components/tooltip/directive.d.ts +4 -0
- package/dist/types/components/tooltip/directive.d.ts.map +1 -0
- package/dist/types/components/tooltip/types.d.ts +14 -0
- package/dist/types/components/tooltip/types.d.ts.map +1 -0
- package/dist/types/components/tooltip/utils/directiveContext.d.ts +10 -0
- package/dist/types/components/tooltip/utils/directiveContext.d.ts.map +1 -0
- package/dist/types/components/tree-select/TreeSelect.vue.d.ts +6 -1
- package/dist/types/components/tree-select/TreeSelect.vue.d.ts.map +1 -1
- package/dist/types/components/tree-select/TreeSelectSearch.vue.d.ts +22 -0
- package/dist/types/components/tree-select/TreeSelectSearch.vue.d.ts.map +1 -0
- package/dist/types/components/tree-select/composables/useTreeSelect.d.ts +35 -0
- package/dist/types/components/tree-select/composables/useTreeSelect.d.ts.map +1 -0
- package/dist/types/components/tree-select/tree-select.variants.d.ts +1 -1
- package/dist/types/components/tree-select/tree-select.variants.d.ts.map +1 -1
- package/dist/types/components/tree-select/utils/filterTree.d.ts +7 -0
- package/dist/types/components/tree-select/utils/filterTree.d.ts.map +1 -0
- package/dist/types/index.d.ts +10 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/lib/anchored-overlay.d.ts +3 -7
- package/dist/types/lib/anchored-overlay.d.ts.map +1 -1
- package/dist/types/lib/dialog-focus.d.ts +2 -0
- package/dist/types/lib/dialog-focus.d.ts.map +1 -0
- package/dist/types/lib/overlay-portal.d.ts +6 -0
- package/dist/types/lib/overlay-portal.d.ts.map +1 -0
- package/dist/types/lib/overlay-positioner.d.ts +3 -0
- package/dist/types/lib/overlay-positioner.d.ts.map +1 -0
- package/dist/types/lib/pointer-corridor.d.ts +7 -0
- package/dist/types/lib/pointer-corridor.d.ts.map +1 -0
- package/dist/types/lib/useDirection.d.ts +7 -0
- package/dist/types/lib/useDirection.d.ts.map +1 -0
- package/dist/types/locale/en-US.d.ts.map +1 -1
- package/dist/types/locale/index.d.ts.map +1 -1
- package/dist/types/locale/types.d.ts +13 -0
- package/dist/types/locale/types.d.ts.map +1 -1
- package/dist/types/locale/zh-CN.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/alert-dialog/AlertDialog.vue +7 -4
- package/src/components/badge/badge.variants.ts +1 -1
- package/src/components/dialog/Dialog.vue +7 -4
- package/src/components/dialog/ModalContent.vue +19 -0
- package/src/components/dropdown-menu/DropdownMenu.vue +1 -1
- package/src/components/hover-card/HoverCard.vue +34 -16
- package/src/components/hover-card/HoverCardAnchor.vue +16 -0
- package/src/components/hover-card/composables/useHoverCardAnchor.ts +160 -0
- package/src/components/image/Image.vue +11 -12
- package/src/components/image/composables/useImage.ts +6 -3
- package/src/components/image/composables/useImageGroupState.ts +9 -1
- package/src/components/image/image.variants.ts +13 -1
- package/src/components/lightbox/Lightbox.vue +23 -17
- package/src/components/lightbox/LightboxChrome.vue +7 -1
- package/src/components/lightbox/LightboxToolbar.vue +16 -2
- package/src/components/lightbox/composables/useLightboxFrames.ts +108 -12
- package/src/components/lightbox/composables/useLightboxLarge.ts +4 -1
- package/src/components/lightbox/composables/useLightboxLayout.ts +44 -0
- package/src/components/lightbox/composables/useLightboxMotion.ts +3 -1
- package/src/components/lightbox/composables/useLightboxPhase.ts +12 -4
- package/src/components/lightbox/composables/useLightboxPose.ts +20 -19
- package/src/components/lightbox/composables/useLightboxRotation.ts +14 -4
- package/src/components/lightbox/composables/useLightboxZoom.ts +51 -9
- package/src/components/lightbox/lightbox.variants.ts +5 -0
- package/src/components/lightbox/types.ts +5 -1
- package/src/components/lightbox/utils/clip.ts +117 -0
- package/src/components/lightbox/utils/pose.ts +36 -19
- package/src/components/lightbox/utils/source.ts +17 -0
- package/src/components/lightbox/utils/zoom.ts +21 -7
- package/src/components/pagination/Pagination.vue +118 -0
- package/src/components/pagination/PaginationContent.vue +29 -0
- package/src/components/pagination/PaginationControl.vue +42 -0
- package/src/components/pagination/PaginationEllipsis.vue +67 -0
- package/src/components/pagination/PaginationInfo.vue +18 -0
- package/src/components/pagination/PaginationJump.vue +32 -0
- package/src/components/pagination/PaginationPage.vue +34 -0
- package/src/components/pagination/PaginationPages.vue +43 -0
- package/src/components/pagination/PaginationPopup.vue +92 -0
- package/src/components/pagination/PaginationSize.vue +30 -0
- package/src/components/pagination/composables/usePagination.ts +76 -0
- package/src/components/pagination/composables/usePaginationEllipsis.ts +269 -0
- package/src/components/pagination/composables/usePaginationJump.ts +23 -0
- package/src/components/pagination/composables/usePaginationPageTooltip.ts +23 -0
- package/src/components/pagination/composables/usePaginationWindow.ts +116 -0
- package/src/components/pagination/context.ts +23 -0
- package/src/components/pagination/pagination.variants.ts +32 -0
- package/src/components/pagination/types.ts +19 -0
- package/src/components/pagination/utils/ranges.ts +12 -0
- package/src/components/popover/Popover.vue +6 -2
- package/src/components/range-slider/RangeSlider.vue +6 -0
- package/src/components/scroll-area/composables/useLayerLock.ts +8 -9
- package/src/components/scroll-area/composables/useWheelRedirect.ts +1 -1
- package/src/components/skeleton/LICENSE +24 -0
- package/src/components/slider/Slider.vue +6 -0
- package/src/components/slider/slider.variants.ts +1 -0
- package/src/components/tooltip/Tooltip.vue +10 -33
- package/src/components/tooltip/TooltipBubble.vue +36 -0
- package/src/components/tooltip/TooltipDirectiveRoot.ts +61 -0
- package/src/components/tooltip/composables/useTooltipTarget.ts +56 -0
- package/src/components/tooltip/directive.ts +54 -0
- package/src/components/tooltip/types.ts +15 -0
- package/src/components/tooltip/utils/directiveContext.ts +32 -0
- package/src/components/tree-select/TreeSelect.vue +90 -61
- package/src/components/tree-select/TreeSelectSearch.vue +48 -0
- package/src/components/tree-select/composables/useTreeSelect.ts +130 -0
- package/src/components/tree-select/tree-select.variants.ts +2 -2
- package/src/components/tree-select/utils/filterTree.ts +19 -0
- package/src/index.ts +10 -0
- package/src/lib/anchored-overlay.ts +2 -5
- package/src/lib/dialog-focus.ts +13 -0
- package/src/lib/overlay-portal.ts +8 -0
- package/src/lib/overlay-positioner.ts +25 -0
- package/src/lib/pointer-corridor.ts +26 -0
- package/src/lib/useDirection.ts +31 -0
- package/src/locale/en-US.ts +15 -1
- package/src/locale/index.ts +1 -0
- package/src/locale/types.ts +13 -0
- package/src/locale/zh-CN.ts +15 -1
- package/src/styles/interaction.css +26 -3
- package/src/styles/motion.css +14 -2
- package/src/styles/semantic.css +5 -1
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { h, render, type AppContext, type DirectiveBinding, type ObjectDirective } from 'vue'
|
|
2
|
+
import TooltipDirectiveRoot from './TooltipDirectiveRoot'
|
|
3
|
+
import { directiveContext } from './utils/directiveContext'
|
|
4
|
+
import type { TooltipDirectiveOptions, TooltipDirectiveValue } from './types'
|
|
5
|
+
|
|
6
|
+
interface TooltipMount {
|
|
7
|
+
container: HTMLElement
|
|
8
|
+
context: AppContext
|
|
9
|
+
revision: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const mounts = new WeakMap<HTMLElement, TooltipMount>()
|
|
13
|
+
let nextId = 0
|
|
14
|
+
|
|
15
|
+
function options(value: TooltipDirectiveValue): TooltipDirectiveOptions {
|
|
16
|
+
return typeof value === 'string' ? { content: value } : value ? { ...value } : { content: '' }
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function update(target: HTMLElement, binding: DirectiveBinding<TooltipDirectiveValue>) {
|
|
20
|
+
const mount = mounts.get(target)
|
|
21
|
+
if (!mount) return
|
|
22
|
+
const vnode = h(TooltipDirectiveRoot, {
|
|
23
|
+
target,
|
|
24
|
+
options: options(binding.value),
|
|
25
|
+
revision: ++mount.revision,
|
|
26
|
+
})
|
|
27
|
+
vnode.appContext = mount.context
|
|
28
|
+
render(vnode, mount.container)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const vTooltip: ObjectDirective<HTMLElement, TooltipDirectiveValue> = {
|
|
32
|
+
deep: true,
|
|
33
|
+
mounted(target, binding) {
|
|
34
|
+
const owner = binding.instance?.$
|
|
35
|
+
if (!owner) return
|
|
36
|
+
const context = directiveContext(owner, target)
|
|
37
|
+
context.config = Object.create(context.config) as AppContext['config']
|
|
38
|
+
context.config.idPrefix = 'hn-tooltip-' + ++nextId
|
|
39
|
+
mounts.set(target, {
|
|
40
|
+
container: target.ownerDocument.createElement('div'),
|
|
41
|
+
context,
|
|
42
|
+
revision: 0,
|
|
43
|
+
})
|
|
44
|
+
update(target, binding)
|
|
45
|
+
},
|
|
46
|
+
updated: update,
|
|
47
|
+
beforeUnmount(target) {
|
|
48
|
+
const mount = mounts.get(target)
|
|
49
|
+
if (!mount) return
|
|
50
|
+
render(null, mount.container)
|
|
51
|
+
mounts.delete(target)
|
|
52
|
+
},
|
|
53
|
+
getSSRProps: () => ({}),
|
|
54
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface TooltipProps {
|
|
2
|
+
content?: string
|
|
3
|
+
side?: 'top' | 'right' | 'bottom' | 'left'
|
|
4
|
+
align?: 'start' | 'center' | 'end'
|
|
5
|
+
sideOffset?: number
|
|
6
|
+
open?: boolean
|
|
7
|
+
disabled?: boolean
|
|
8
|
+
class?: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type TooltipDirectiveOptions = Omit<TooltipProps, 'open' | 'content'> & {
|
|
12
|
+
content: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type TooltipDirectiveValue = string | TooltipDirectiveOptions | null | undefined | false
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { isVNode, type AppContext, type ComponentInternalInstance, type VNode } from 'vue'
|
|
2
|
+
|
|
3
|
+
type ContextOwner = ComponentInternalInstance & { provides: AppContext['provides'] }
|
|
4
|
+
|
|
5
|
+
function findOwner(
|
|
6
|
+
node: VNode,
|
|
7
|
+
target: HTMLElement,
|
|
8
|
+
owner: ComponentInternalInstance,
|
|
9
|
+
): ComponentInternalInstance | undefined {
|
|
10
|
+
if (node.component) {
|
|
11
|
+
return findOwner(node.component.subTree, target, node.component)
|
|
12
|
+
}
|
|
13
|
+
if (node.suspense) {
|
|
14
|
+
for (const branch of [node.suspense.activeBranch, node.suspense.pendingBranch]) {
|
|
15
|
+
const match = branch && findOwner(branch, target, owner)
|
|
16
|
+
if (match) return match
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (node.el === target) return owner
|
|
20
|
+
if (Array.isArray(node.children)) {
|
|
21
|
+
for (const child of node.children) {
|
|
22
|
+
if (!isVNode(child)) continue
|
|
23
|
+
const match = findOwner(child, target, owner)
|
|
24
|
+
if (match) return match
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function directiveContext(owner: ComponentInternalInstance, target: HTMLElement) {
|
|
30
|
+
const parent = (findOwner(owner.subTree, target, owner) ?? owner) as ContextOwner
|
|
31
|
+
return { ...parent.appContext, provides: parent.provides }
|
|
32
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import {
|
|
2
|
+
import { useId } from 'vue'
|
|
3
3
|
import { Check } from '@lucide/vue'
|
|
4
4
|
import {
|
|
5
5
|
PopoverContent,
|
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
treeSelectRow,
|
|
30
30
|
treeSelectToggle,
|
|
31
31
|
} from './tree-select.variants'
|
|
32
|
-
import {
|
|
32
|
+
import type { TreeSelectNode } from './types'
|
|
33
|
+
import TreeSelectSearch from './TreeSelectSearch.vue'
|
|
34
|
+
import { useTreeSelect } from './composables/useTreeSelect'
|
|
33
35
|
|
|
34
36
|
defineOptions({ name: 'HnTreeSelect', inheritAttrs: false })
|
|
35
37
|
|
|
@@ -37,6 +39,8 @@
|
|
|
37
39
|
defineProps<{
|
|
38
40
|
items: TreeSelectNode[]
|
|
39
41
|
placeholder?: string
|
|
42
|
+
searchable?: boolean
|
|
43
|
+
searchPlaceholder?: string
|
|
40
44
|
defaultExpanded?: Array<string | number>
|
|
41
45
|
variant?: InputVariants['variant']
|
|
42
46
|
size?: InputVariants['size']
|
|
@@ -49,6 +53,9 @@
|
|
|
49
53
|
|
|
50
54
|
const model = defineModel<string | number | null>()
|
|
51
55
|
const open = defineModel<boolean>('open', { default: false })
|
|
56
|
+
const search = defineModel<string>('search', { default: '' })
|
|
57
|
+
const treeId = useId()
|
|
58
|
+
const triggerId = useId()
|
|
52
59
|
|
|
53
60
|
defineSlots<{ node(props: { node: TreeSelectNode }): unknown }>()
|
|
54
61
|
|
|
@@ -60,82 +67,102 @@
|
|
|
60
67
|
invalid,
|
|
61
68
|
disabled,
|
|
62
69
|
describedBy,
|
|
70
|
+
labelledBy,
|
|
63
71
|
} = useFieldControl({
|
|
64
72
|
invalid: () => props.invalid || !!group?.invalid.value,
|
|
65
73
|
disabled: () => props.disabled || !!group?.disabled.value,
|
|
66
74
|
})
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
return String(node.value)
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function choose(node: TreeSelectNode | undefined) {
|
|
87
|
-
if (!node || node.disabled) return
|
|
88
|
-
model.value = node.value
|
|
89
|
-
open.value = false
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function keepRowClick(event: CustomEvent<{ originalEvent: Event }>) {
|
|
93
|
-
if (event.detail.originalEvent.type === 'click') event.preventDefault()
|
|
94
|
-
}
|
|
75
|
+
const {
|
|
76
|
+
selected,
|
|
77
|
+
items,
|
|
78
|
+
expanded,
|
|
79
|
+
input,
|
|
80
|
+
tree,
|
|
81
|
+
getChildren,
|
|
82
|
+
key,
|
|
83
|
+
choose,
|
|
84
|
+
keepRowClick,
|
|
85
|
+
focusSearch,
|
|
86
|
+
clearSearch,
|
|
87
|
+
onEscape,
|
|
88
|
+
onSearchKeydown,
|
|
89
|
+
onTreeKeydown,
|
|
90
|
+
} = useTreeSelect(props, model, open, search)
|
|
95
91
|
</script>
|
|
96
92
|
|
|
97
93
|
<template>
|
|
98
94
|
<PopoverRoot v-model:open="open" modal>
|
|
99
|
-
<PopoverTrigger
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
95
|
+
<PopoverTrigger as-child>
|
|
96
|
+
<button
|
|
97
|
+
type="button"
|
|
98
|
+
v-bind="$attrs"
|
|
99
|
+
:id="fieldId ?? triggerId"
|
|
100
|
+
:aria-describedby="describedBy"
|
|
101
|
+
data-hn-tree-select
|
|
102
|
+
role="combobox"
|
|
103
|
+
:aria-expanded="open"
|
|
104
|
+
:disabled="disabled"
|
|
105
|
+
:data-placeholder="selected ? undefined : ''"
|
|
106
|
+
:data-invalid="invalid ? '' : undefined"
|
|
107
|
+
:data-disabled="disabled ? '' : undefined"
|
|
108
|
+
:aria-invalid="invalid || undefined"
|
|
109
|
+
:class="
|
|
110
|
+
cn(
|
|
111
|
+
group ? inputEmbedded() : inputHost({ variant: props.variant, size: props.size }),
|
|
112
|
+
selectTrigger(),
|
|
113
|
+
props.class,
|
|
114
|
+
)
|
|
115
|
+
"
|
|
116
|
+
>
|
|
117
|
+
<span class="min-w-0 flex-1 truncate">
|
|
118
|
+
{{ selected ? selected.label : (props.placeholder ?? t.select.placeholder) }}
|
|
119
|
+
</span>
|
|
120
|
+
<span :class="inputAdornment()">
|
|
121
|
+
<DisclosureIcon />
|
|
122
|
+
</span>
|
|
123
|
+
</button>
|
|
125
124
|
</PopoverTrigger>
|
|
126
125
|
<PopoverPortal>
|
|
127
|
-
<PopoverContent
|
|
128
|
-
|
|
126
|
+
<PopoverContent
|
|
127
|
+
as-child
|
|
128
|
+
align="start"
|
|
129
|
+
:side-offset="8"
|
|
130
|
+
@open-auto-focus="focusSearch"
|
|
131
|
+
@escape-key-down="onEscape"
|
|
132
|
+
>
|
|
133
|
+
<Card
|
|
134
|
+
:padded="false"
|
|
135
|
+
data-hn-tree-select-content
|
|
136
|
+
:aria-label="$attrs['aria-label'] as string | undefined"
|
|
137
|
+
:aria-labelledby="
|
|
138
|
+
labelledBy ?? ($attrs['aria-label'] ? undefined : (fieldId ?? triggerId))
|
|
139
|
+
"
|
|
140
|
+
:class="treeSelectContent()"
|
|
141
|
+
>
|
|
142
|
+
<TreeSelectSearch
|
|
143
|
+
v-if="props.searchable"
|
|
144
|
+
ref="input"
|
|
145
|
+
v-model="search"
|
|
146
|
+
:placeholder="props.searchPlaceholder ?? t.treeSelect.search"
|
|
147
|
+
:controls="treeId"
|
|
148
|
+
@clear="clearSearch"
|
|
149
|
+
@keydown="onSearchKeydown"
|
|
150
|
+
/>
|
|
129
151
|
<ScrollArea :class="treeSelectList()">
|
|
130
152
|
<TreeRoot
|
|
153
|
+
:id="treeId"
|
|
154
|
+
ref="tree"
|
|
131
155
|
v-slot="{ flattenItems }"
|
|
132
156
|
v-model:expanded="expanded"
|
|
133
|
-
:items="
|
|
157
|
+
:items="items"
|
|
134
158
|
:get-key="key"
|
|
135
|
-
:get-children="
|
|
159
|
+
:get-children="getChildren"
|
|
136
160
|
:model-value="selected"
|
|
161
|
+
:aria-label="$attrs['aria-label'] as string | undefined"
|
|
162
|
+
:aria-labelledby="labelledBy"
|
|
137
163
|
class="flex flex-col p-1 outline-none"
|
|
138
164
|
@update:model-value="choose"
|
|
165
|
+
@keydown.capture="onTreeKeydown"
|
|
139
166
|
>
|
|
140
167
|
<TreeItem
|
|
141
168
|
v-for="item in flattenItems"
|
|
@@ -169,7 +196,9 @@
|
|
|
169
196
|
<Check v-if="isSelected" />
|
|
170
197
|
</span>
|
|
171
198
|
</TreeItem>
|
|
172
|
-
<
|
|
199
|
+
<li v-if="items.length === 0" role="none" :class="selectEmpty()">
|
|
200
|
+
<span role="status">{{ t.select.empty }}</span>
|
|
201
|
+
</li>
|
|
173
202
|
</TreeRoot>
|
|
174
203
|
</ScrollArea>
|
|
175
204
|
</Card>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { shallowRef } from 'vue'
|
|
3
|
+
import { Search, X } from '@lucide/vue'
|
|
4
|
+
import { useUiLocale } from '../../locale'
|
|
5
|
+
import InputAction from '../input/InputAction.vue'
|
|
6
|
+
import { inputAdornment, inputControl, textInputHost } from '../input/input.variants'
|
|
7
|
+
|
|
8
|
+
defineOptions({ name: 'HnTreeSelectSearch', inheritAttrs: false })
|
|
9
|
+
|
|
10
|
+
const props = defineProps<{ placeholder: string; controls: string }>()
|
|
11
|
+
const emit = defineEmits<{ clear: []; keydown: [event: KeyboardEvent] }>()
|
|
12
|
+
const model = defineModel<string>({ required: true })
|
|
13
|
+
const t = useUiLocale()
|
|
14
|
+
const input = shallowRef<HTMLInputElement | null>(null)
|
|
15
|
+
|
|
16
|
+
defineExpose({ focus: () => input.value?.focus() })
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<template>
|
|
20
|
+
<div class="border-line shrink-0 border-b px-1">
|
|
21
|
+
<div :class="textInputHost({ variant: 'bare', size: 'sm' })">
|
|
22
|
+
<span :class="inputAdornment()"><Search /></span>
|
|
23
|
+
<input
|
|
24
|
+
ref="input"
|
|
25
|
+
v-model="model"
|
|
26
|
+
data-hn-tree-select-search
|
|
27
|
+
type="text"
|
|
28
|
+
role="searchbox"
|
|
29
|
+
autocomplete="off"
|
|
30
|
+
:aria-label="props.placeholder"
|
|
31
|
+
:aria-controls="props.controls"
|
|
32
|
+
:placeholder="props.placeholder"
|
|
33
|
+
:class="inputControl({ leading: true, trailing: !!model })"
|
|
34
|
+
@keydown="emit('keydown', $event)"
|
|
35
|
+
/>
|
|
36
|
+
<Transition
|
|
37
|
+
enter-active-class="hn-transition-base"
|
|
38
|
+
enter-from-class="scale-90 opacity-0"
|
|
39
|
+
leave-active-class="hn-transition"
|
|
40
|
+
leave-to-class="scale-90 opacity-0"
|
|
41
|
+
>
|
|
42
|
+
<InputAction v-if="model" :label="t.common.clear" @click="emit('clear')">
|
|
43
|
+
<X />
|
|
44
|
+
</InputAction>
|
|
45
|
+
</Transition>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</template>
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { computed, ref, shallowRef, watch, type Ref } from 'vue'
|
|
2
|
+
import { useFilter } from 'reka-ui'
|
|
3
|
+
import { findNode, pathTo, type TreeSelectNode } from '../types'
|
|
4
|
+
import { filterTree } from '../utils/filterTree'
|
|
5
|
+
|
|
6
|
+
interface TreeSelectOptions {
|
|
7
|
+
items: TreeSelectNode[]
|
|
8
|
+
defaultExpanded: Array<string | number>
|
|
9
|
+
searchable?: boolean
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useTreeSelect(
|
|
13
|
+
props: TreeSelectOptions,
|
|
14
|
+
model: Ref<string | number | null | undefined>,
|
|
15
|
+
open: Ref<boolean>,
|
|
16
|
+
search: Ref<string>,
|
|
17
|
+
) {
|
|
18
|
+
const selected = computed(() => findNode(props.items, model.value))
|
|
19
|
+
const normalExpanded = ref<string[]>([])
|
|
20
|
+
const searchExpanded = ref<string[]>([])
|
|
21
|
+
const input = shallowRef<{ focus: () => void } | null>(null)
|
|
22
|
+
const tree = shallowRef<{ $el: HTMLElement } | null>(null)
|
|
23
|
+
const { contains } = useFilter({ sensitivity: 'base' })
|
|
24
|
+
const query = computed(() => (props.searchable ? search.value.trim() : ''))
|
|
25
|
+
const filtered = computed(() =>
|
|
26
|
+
query.value ? filterTree(props.items, node => contains(node.label, query.value)) : null,
|
|
27
|
+
)
|
|
28
|
+
const items = computed(() => filtered.value?.items ?? props.items)
|
|
29
|
+
const expanded = computed({
|
|
30
|
+
get: () => (filtered.value ? searchExpanded.value : normalExpanded.value),
|
|
31
|
+
set: value => {
|
|
32
|
+
if (filtered.value) searchExpanded.value = value
|
|
33
|
+
else normalExpanded.value = value
|
|
34
|
+
},
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
watch(
|
|
38
|
+
filtered,
|
|
39
|
+
value => {
|
|
40
|
+
searchExpanded.value = value?.expanded ?? []
|
|
41
|
+
},
|
|
42
|
+
{ immediate: true },
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
watch(
|
|
46
|
+
open,
|
|
47
|
+
(isOpen, wasOpen) => {
|
|
48
|
+
if (!isOpen) {
|
|
49
|
+
if (wasOpen) search.value = ''
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
const path = pathTo(props.items, model.value) ?? []
|
|
53
|
+
normalExpanded.value = Array.from(new Set([...props.defaultExpanded, ...path].map(String)))
|
|
54
|
+
},
|
|
55
|
+
{ immediate: true },
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
function getChildren(node: TreeSelectNode) {
|
|
59
|
+
return filtered.value ? filtered.value.children.get(node) : node.children
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function key(node: TreeSelectNode) {
|
|
63
|
+
return String(node.value)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function choose(node: TreeSelectNode | undefined) {
|
|
67
|
+
if (!node || node.disabled) return
|
|
68
|
+
model.value = node.value
|
|
69
|
+
open.value = false
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function keepRowClick(event: CustomEvent<{ originalEvent: Event }>) {
|
|
73
|
+
if (event.detail.originalEvent.type === 'click') event.preventDefault()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function focusSearch(event: Event) {
|
|
77
|
+
if (!props.searchable) return
|
|
78
|
+
event.preventDefault()
|
|
79
|
+
input.value?.focus()
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function clearSearch() {
|
|
83
|
+
search.value = ''
|
|
84
|
+
input.value?.focus()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function onEscape(event: KeyboardEvent) {
|
|
88
|
+
if (!props.searchable || !search.value) return
|
|
89
|
+
event.preventDefault()
|
|
90
|
+
clearSearch()
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function enabledRows() {
|
|
94
|
+
return Array.from(
|
|
95
|
+
tree.value?.$el.querySelectorAll<HTMLElement>('[role="treeitem"]:not([data-disabled])') ?? [],
|
|
96
|
+
)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function onSearchKeydown(event: KeyboardEvent) {
|
|
100
|
+
if (event.isComposing || !['ArrowDown', 'ArrowUp'].includes(event.key)) return
|
|
101
|
+
event.preventDefault()
|
|
102
|
+
const rows = enabledRows()
|
|
103
|
+
const target = event.key === 'ArrowDown' ? rows[0] : rows.at(-1)
|
|
104
|
+
target?.focus()
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function onTreeKeydown(event: KeyboardEvent) {
|
|
108
|
+
if (!props.searchable || event.key !== 'ArrowUp' || event.target !== enabledRows()[0]) return
|
|
109
|
+
event.preventDefault()
|
|
110
|
+
event.stopPropagation()
|
|
111
|
+
input.value?.focus()
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
selected,
|
|
116
|
+
items,
|
|
117
|
+
expanded,
|
|
118
|
+
input,
|
|
119
|
+
tree,
|
|
120
|
+
getChildren,
|
|
121
|
+
key,
|
|
122
|
+
choose,
|
|
123
|
+
keepRowClick,
|
|
124
|
+
focusSearch,
|
|
125
|
+
clearSearch,
|
|
126
|
+
onEscape,
|
|
127
|
+
onSearchKeydown,
|
|
128
|
+
onTreeKeydown,
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -2,12 +2,12 @@ import { tv } from '../../lib/tv'
|
|
|
2
2
|
|
|
3
3
|
export const treeSelectContent = tv({
|
|
4
4
|
base: [
|
|
5
|
-
'hn-anim-pop z-(--hn-z-overlay) flex w-[var(--reka-popover-trigger-width)] min-w-40 flex-col overflow-hidden shadow-md outline-none',
|
|
5
|
+
'hn-anim-pop z-(--hn-z-overlay) flex w-[var(--reka-popover-trigger-width)] min-w-40 max-h-[var(--reka-popover-content-available-height)] flex-col overflow-hidden shadow-md outline-none',
|
|
6
6
|
],
|
|
7
7
|
})
|
|
8
8
|
|
|
9
9
|
export const treeSelectList = tv({
|
|
10
|
-
base: '
|
|
10
|
+
base: 'min-h-0 max-h-80',
|
|
11
11
|
})
|
|
12
12
|
|
|
13
13
|
export const treeSelectRow = tv({
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { TreeSelectNode } from '../types'
|
|
2
|
+
|
|
3
|
+
export function filterTree(nodes: TreeSelectNode[], matches: (node: TreeSelectNode) => boolean) {
|
|
4
|
+
const children = new Map<TreeSelectNode, TreeSelectNode[]>()
|
|
5
|
+
const expanded: string[] = []
|
|
6
|
+
|
|
7
|
+
function visit(items: TreeSelectNode[]): TreeSelectNode[] {
|
|
8
|
+
return items.filter(node => {
|
|
9
|
+
const descendants = visit(node.children ?? [])
|
|
10
|
+
if (descendants.length) {
|
|
11
|
+
children.set(node, descendants)
|
|
12
|
+
expanded.push(String(node.value))
|
|
13
|
+
}
|
|
14
|
+
return descendants.length > 0 || matches(node)
|
|
15
|
+
})
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
return { items: visit(nodes), children, expanded }
|
|
19
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -25,6 +25,12 @@ export { default as Badge } from './components/badge/Badge.vue'
|
|
|
25
25
|
export { default as Indicator } from './components/indicator/Indicator.vue'
|
|
26
26
|
export { default as Callout } from './components/callout/Callout.vue'
|
|
27
27
|
export { default as Alert } from './components/alert/Alert.vue'
|
|
28
|
+
export { default as Pagination } from './components/pagination/Pagination.vue'
|
|
29
|
+
export { default as PaginationContent } from './components/pagination/PaginationContent.vue'
|
|
30
|
+
export { default as PaginationInfo } from './components/pagination/PaginationInfo.vue'
|
|
31
|
+
export { default as PaginationSize } from './components/pagination/PaginationSize.vue'
|
|
32
|
+
export { default as PaginationJump } from './components/pagination/PaginationJump.vue'
|
|
33
|
+
export type { PaginationChange, PaginationState } from './components/pagination/types'
|
|
28
34
|
export { default as PrevNext } from './components/prev-next/PrevNext.vue'
|
|
29
35
|
export { default as PrevNextLink } from './components/prev-next/PrevNextLink.vue'
|
|
30
36
|
export { default as Table } from './components/table/Table.vue'
|
|
@@ -206,6 +212,8 @@ export { default as BreadcrumbItem } from './components/breadcrumb/BreadcrumbIte
|
|
|
206
212
|
export { default as BreadcrumbSeparator } from './components/breadcrumb/BreadcrumbSeparator.vue'
|
|
207
213
|
export { default as TooltipProvider } from './components/tooltip/TooltipProvider.vue'
|
|
208
214
|
export { default as Tooltip } from './components/tooltip/Tooltip.vue'
|
|
215
|
+
export { vTooltip } from './components/tooltip/directive'
|
|
216
|
+
export type { TooltipDirectiveOptions, TooltipDirectiveValue } from './components/tooltip/types'
|
|
209
217
|
export { default as Popover } from './components/popover/Popover.vue'
|
|
210
218
|
export { default as DropdownMenu } from './components/dropdown-menu/DropdownMenu.vue'
|
|
211
219
|
export { default as DropdownMenuItem } from './components/dropdown-menu/DropdownMenuItem.vue'
|
|
@@ -227,6 +235,8 @@ export { default as Spoiler } from './components/spoiler/Spoiler.vue'
|
|
|
227
235
|
export { default as Skeleton } from './components/skeleton/Skeleton.vue'
|
|
228
236
|
export { default as Image } from './components/image/Image.vue'
|
|
229
237
|
export { default as ImageGroup } from './components/image/ImageGroup.vue'
|
|
238
|
+
export { default as Lightbox } from './components/lightbox/Lightbox.vue'
|
|
239
|
+
export type { LightboxItem, LightboxSource } from './components/lightbox/types'
|
|
230
240
|
export { provideImageResolver, useImageResolver } from './components/image/resolver'
|
|
231
241
|
export type { ImageResolver } from './components/image/resolver'
|
|
232
242
|
export type { ImageVariants } from './components/image/image.variants'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { computed, nextTick, shallowRef, watch, type Ref } from 'vue'
|
|
2
|
-
import type
|
|
2
|
+
import { useForwardExpose, type DropdownMenuContentEmits } from 'reka-ui'
|
|
3
3
|
|
|
4
4
|
export function useAnchoredOverlay(
|
|
5
5
|
props: { anchor?: HTMLElement | null; modal: boolean },
|
|
@@ -10,10 +10,7 @@ export function useAnchoredOverlay(
|
|
|
10
10
|
) => void,
|
|
11
11
|
openAutoFocus?: (event: Event) => void,
|
|
12
12
|
) {
|
|
13
|
-
const
|
|
14
|
-
const triggerElement = computed(() =>
|
|
15
|
-
trigger.value?.$el?.nodeType === 1 ? trigger.value.$el : undefined,
|
|
16
|
-
)
|
|
13
|
+
const { forwardRef: trigger, currentElement: triggerElement } = useForwardExpose()
|
|
17
14
|
const retainedAnchor = shallowRef<HTMLElement>()
|
|
18
15
|
const reference = computed(
|
|
19
16
|
() =>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { injectDialogRootContext } from 'reka-ui'
|
|
2
|
+
|
|
3
|
+
export function useDialogCloseFocus(onCloseAutoFocus: (event: Event) => void) {
|
|
4
|
+
const context = injectDialogRootContext()
|
|
5
|
+
|
|
6
|
+
return (event: Event) => {
|
|
7
|
+
onCloseAutoFocus(event)
|
|
8
|
+
if (event.defaultPrevented) return
|
|
9
|
+
event.preventDefault()
|
|
10
|
+
const target = context.triggerElement.value
|
|
11
|
+
if (target?.isConnected) target.focus({ preventScroll: true })
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { computed, shallowRef, type ComponentPublicInstance, type Ref } from 'vue'
|
|
2
|
+
|
|
3
|
+
export function useOverlayPortal(open: Ref<boolean | undefined>) {
|
|
4
|
+
const content = shallowRef<ComponentPublicInstance | null>(null)
|
|
5
|
+
const present = computed(() => !!open.value || !!content.value)
|
|
6
|
+
|
|
7
|
+
return { content, present }
|
|
8
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {
|
|
2
|
+
toValue,
|
|
3
|
+
watchEffect,
|
|
4
|
+
type ComponentPublicInstance,
|
|
5
|
+
type MaybeRefOrGetter,
|
|
6
|
+
type Ref,
|
|
7
|
+
} from 'vue'
|
|
8
|
+
|
|
9
|
+
export function useOverlayPositionerClass(
|
|
10
|
+
content: Ref<ComponentPublicInstance | null>,
|
|
11
|
+
className: MaybeRefOrGetter<string | undefined>,
|
|
12
|
+
) {
|
|
13
|
+
watchEffect(
|
|
14
|
+
onCleanup => {
|
|
15
|
+
const element = content.value?.$el as HTMLElement | undefined
|
|
16
|
+
const positioner = element?.parentElement
|
|
17
|
+
const classes = toValue(className)?.split(/\s+/).filter(Boolean) ?? []
|
|
18
|
+
if (!positioner?.hasAttribute('data-reka-popper-content-wrapper') || !classes.length) return
|
|
19
|
+
const added = classes.filter(name => !positioner.classList.contains(name))
|
|
20
|
+
positioner.classList.add(...added)
|
|
21
|
+
onCleanup(() => positioner.classList.remove(...added))
|
|
22
|
+
},
|
|
23
|
+
{ flush: 'post' },
|
|
24
|
+
)
|
|
25
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
type Point = { x: number; y: number }
|
|
2
|
+
const cross = (a: Point, b: Point, c: Point) =>
|
|
3
|
+
(b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)
|
|
4
|
+
|
|
5
|
+
export function inPointerCorridor(point: Point, first: DOMRect, second: DOMRect) {
|
|
6
|
+
const points = [first, second]
|
|
7
|
+
.flatMap(rect => [
|
|
8
|
+
{ x: rect.left - 6, y: rect.top - 6 },
|
|
9
|
+
{ x: rect.right + 6, y: rect.top - 6 },
|
|
10
|
+
{ x: rect.left - 6, y: rect.bottom + 6 },
|
|
11
|
+
{ x: rect.right + 6, y: rect.bottom + 6 },
|
|
12
|
+
])
|
|
13
|
+
.sort((a, b) => a.x - b.x || a.y - b.y)
|
|
14
|
+
const half = (values: Point[]) => {
|
|
15
|
+
const hull: Point[] = []
|
|
16
|
+
for (const value of values) {
|
|
17
|
+
while (hull.length > 1 && cross(hull[hull.length - 2]!, hull[hull.length - 1]!, value) <= 0)
|
|
18
|
+
hull.pop()
|
|
19
|
+
hull.push(value)
|
|
20
|
+
}
|
|
21
|
+
hull.pop()
|
|
22
|
+
return hull
|
|
23
|
+
}
|
|
24
|
+
const hull = [...half(points), ...half([...points].reverse())]
|
|
25
|
+
return hull.every((a, i) => cross(a, hull[(i + 1) % hull.length]!, point) >= 0)
|
|
26
|
+
}
|