base-ui-vue 0.3.0 → 0.5.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/arrow/TooltipArrow.cjs +609 -0
- package/dist/arrow/TooltipArrow.cjs.map +1 -0
- package/dist/arrow/TooltipArrow.js +478 -0
- package/dist/arrow/TooltipArrow.js.map +1 -0
- package/dist/button/Button.cjs +5 -317
- package/dist/button/Button.cjs.map +1 -1
- package/dist/button/Button.js +3 -249
- package/dist/button/Button.js.map +1 -1
- package/dist/button/ToolbarButton.cjs +19 -6
- package/dist/button/ToolbarButton.cjs.map +1 -1
- package/dist/button/ToolbarButton.js +3 -2
- package/dist/button/ToolbarButton.js.map +1 -1
- package/dist/checkbox/index.cjs +20 -9
- package/dist/checkbox/index.cjs.map +1 -1
- package/dist/checkbox/index.js +7 -2
- package/dist/checkbox/index.js.map +1 -1
- package/dist/checkbox-group/CheckboxGroup.cjs +14 -13
- package/dist/checkbox-group/CheckboxGroup.cjs.map +1 -1
- package/dist/checkbox-group/CheckboxGroup.js +2 -1
- package/dist/checkbox-group/CheckboxGroup.js.map +1 -1
- package/dist/components/FloatingPortal.cjs +66 -0
- package/dist/components/FloatingPortal.cjs.map +1 -0
- package/dist/components/FloatingPortal.js +55 -0
- package/dist/components/FloatingPortal.js.map +1 -0
- package/dist/components/FloatingRootStore.cjs +101 -0
- package/dist/components/FloatingRootStore.cjs.map +1 -0
- package/dist/components/FloatingRootStore.js +84 -0
- package/dist/components/FloatingRootStore.js.map +1 -0
- package/dist/components/FloatingTree.cjs +33 -0
- package/dist/components/FloatingTree.cjs.map +1 -0
- package/dist/components/FloatingTree.js +22 -0
- package/dist/components/FloatingTree.js.map +1 -0
- package/dist/composite/composite.cjs +6 -0
- package/dist/composite/composite.js +1 -1
- package/dist/content/ScrollAreaContent.cjs +3 -3
- package/dist/content/ScrollAreaContent.cjs.map +1 -1
- package/dist/content/ScrollAreaContent.js +1 -1
- package/dist/control/FieldControl.cjs +3 -3
- package/dist/control/FieldControl.cjs.map +1 -1
- package/dist/control/FieldControl.js +1 -1
- package/dist/control/SliderControl.cjs +3 -3
- package/dist/control/SliderControl.cjs.map +1 -1
- package/dist/control/SliderControl.js +3 -3
- package/dist/corner/ScrollAreaCorner.cjs +3 -3
- package/dist/corner/ScrollAreaCorner.cjs.map +1 -1
- package/dist/corner/ScrollAreaCorner.js +1 -1
- package/dist/decrement/NumberFieldDecrement.cjs +868 -0
- package/dist/decrement/NumberFieldDecrement.cjs.map +1 -0
- package/dist/decrement/NumberFieldDecrement.js +701 -0
- package/dist/decrement/NumberFieldDecrement.js.map +1 -0
- package/dist/description/FieldDescription.cjs +2 -2
- package/dist/description/FieldDescription.cjs.map +1 -1
- package/dist/description/FieldDescription.js +1 -1
- package/dist/error/FieldError.cjs +2 -2
- package/dist/error/FieldError.cjs.map +1 -1
- package/dist/error/FieldError.js +1 -1
- package/dist/fallback/AvatarFallback.cjs +4 -48
- package/dist/fallback/AvatarFallback.cjs.map +1 -1
- package/dist/fallback/AvatarFallback.js +4 -42
- package/dist/fallback/AvatarFallback.js.map +1 -1
- package/dist/floating-ui-vue/index.js +638 -0
- package/dist/floating-ui-vue/index.js.map +1 -0
- package/dist/form/Form.cjs +3 -3
- package/dist/form/Form.cjs.map +1 -1
- package/dist/form/Form.js +1 -1
- package/dist/group/NumberFieldGroup.cjs +72 -0
- package/dist/group/NumberFieldGroup.cjs.map +1 -0
- package/dist/group/NumberFieldGroup.js +67 -0
- package/dist/group/NumberFieldGroup.js.map +1 -0
- package/dist/group/ToolbarGroup.cjs +2 -2
- package/dist/group/ToolbarGroup.cjs.map +1 -1
- package/dist/group/ToolbarGroup.js +1 -1
- package/dist/header/AccordionHeader.cjs +2 -2
- package/dist/header/AccordionHeader.cjs.map +1 -1
- package/dist/header/AccordionHeader.js +1 -1
- package/dist/hooks/useDismiss.cjs +502 -0
- package/dist/hooks/useDismiss.cjs.map +1 -0
- package/dist/hooks/useHoverFloatingInteraction.cjs +233 -0
- package/dist/hooks/useHoverFloatingInteraction.cjs.map +1 -0
- package/dist/image/AvatarImage.cjs +4 -4
- package/dist/image/AvatarImage.cjs.map +1 -1
- package/dist/image/AvatarImage.js +1 -1
- package/dist/increment/NumberFieldIncrement.cjs +113 -0
- package/dist/increment/NumberFieldIncrement.cjs.map +1 -0
- package/dist/increment/NumberFieldIncrement.js +108 -0
- package/dist/increment/NumberFieldIncrement.js.map +1 -0
- package/dist/index.cjs +80 -8
- package/dist/index.d.cts +4328 -1600
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +4328 -1600
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -4
- package/dist/index2.cjs +9435 -3236
- package/dist/index2.cjs.map +1 -1
- package/dist/index2.js +8905 -3049
- package/dist/index2.js.map +1 -1
- package/dist/rolldown-runtime.js +36 -0
- package/package.json +1 -1
- package/src/floating-ui-vue/components/FloatingPortal.ts +171 -0
- package/src/floating-ui-vue/components/FloatingPortal.vue +259 -0
- package/src/floating-ui-vue/components/FloatingRootStore.ts +83 -0
- package/src/floating-ui-vue/components/FloatingTree.ts +64 -0
- package/src/floating-ui-vue/components/FloatingTreeStore.ts +21 -0
- package/src/floating-ui-vue/hooks/useDismiss.ts +689 -0
- package/src/floating-ui-vue/hooks/useHoverFloatingInteraction.ts +244 -0
- package/src/floating-ui-vue/hooks/useHoverInteractionSharedState.ts +125 -0
- package/src/floating-ui-vue/hooks/useHoverShared.ts +53 -0
- package/src/floating-ui-vue/index.ts +48 -0
- package/src/floating-ui-vue/middleware/arrow.ts +114 -0
- package/src/floating-ui-vue/types.ts +117 -0
- package/src/floating-ui-vue/utils/composite.ts +20 -0
- package/src/floating-ui-vue/utils/constants.ts +2 -0
- package/src/floating-ui-vue/utils/createAttribute.ts +3 -0
- package/src/floating-ui-vue/utils/createEventEmitter.ts +20 -0
- package/src/floating-ui-vue/utils/element.ts +59 -0
- package/src/floating-ui-vue/utils/event.ts +15 -0
- package/src/floating-ui-vue/utils/nodes.ts +14 -0
- package/src/floating-ui-vue/utils/tabbable.ts +309 -0
- package/src/index.ts +7 -1
- package/src/number-field/decrement/NumberFieldDecrement.vue +109 -0
- package/src/number-field/group/NumberFieldGroup.vue +47 -0
- package/src/number-field/increment/NumberFieldIncrement.vue +109 -0
- package/src/number-field/index.ts +42 -0
- package/src/number-field/input/NumberFieldInput.vue +455 -0
- package/src/number-field/root/NumberFieldRoot.vue +626 -0
- package/src/number-field/root/NumberFieldRootContext.ts +94 -0
- package/src/number-field/root/useNumberFieldButton.ts +171 -0
- package/src/number-field/scrub-area/NumberFieldScrubArea.vue +359 -0
- package/src/number-field/scrub-area/NumberFieldScrubAreaContext.ts +26 -0
- package/src/number-field/scrub-area-cursor/NumberFieldScrubAreaCursor.vue +75 -0
- package/src/number-field/utils/constants.ts +4 -0
- package/src/number-field/utils/getViewportRect.ts +34 -0
- package/src/number-field/utils/parse.ts +248 -0
- package/src/number-field/utils/stateAttributesMapping.ts +9 -0
- package/src/number-field/utils/subscribeToVisualViewportResize.ts +27 -0
- package/src/number-field/utils/types.ts +24 -0
- package/src/number-field/utils/validate.ts +120 -0
- package/src/otp-field/index.ts +22 -0
- package/src/otp-field/input/OtpFieldInput.vue +336 -0
- package/src/otp-field/root/OtpFieldRoot.vue +583 -0
- package/src/otp-field/root/OtpFieldRootContext.ts +81 -0
- package/src/otp-field/utils/otp.ts +135 -0
- package/src/otp-field/utils/stateAttributesMapping.ts +16 -0
- package/src/radio/index.ts +28 -0
- package/src/radio/indicator/RadioIndicator.vue +90 -0
- package/src/radio/indicator/RadioIndicatorDataAttributes.ts +54 -0
- package/src/radio/root/RadioRoot.vue +544 -0
- package/src/radio/root/RadioRootContext.ts +18 -0
- package/src/radio/root/RadioRootDataAttributes.ts +46 -0
- package/src/radio/utils/stateAttributesMapping.ts +29 -0
- package/src/radio-group/RadioGroup.vue +381 -0
- package/src/radio-group/RadioGroupContext.ts +42 -0
- package/src/radio-group/RadioGroupDataAttributes.ts +6 -0
- package/src/radio-group/index.ts +10 -0
- package/src/tabs/index.ts +38 -0
- package/src/tabs/indicator/TabsIndicator.vue +201 -0
- package/src/tabs/indicator/TabsIndicatorCssVars.ts +26 -0
- package/src/tabs/indicator/TabsIndicatorDataAttributes.ts +12 -0
- package/src/tabs/indicator/prehydrationScript.min.ts +5 -0
- package/src/tabs/indicator/prehydrationScript.template.js +87 -0
- package/src/tabs/list/TabsList.vue +201 -0
- package/src/tabs/list/TabsListContext.ts +29 -0
- package/src/tabs/list/TabsListDataAttributes.ts +12 -0
- package/src/tabs/panel/TabsPanel.vue +167 -0
- package/src/tabs/panel/TabsPanelDataAttributes.ts +28 -0
- package/src/tabs/root/TabsRoot.vue +468 -0
- package/src/tabs/root/TabsRootContext.ts +56 -0
- package/src/tabs/root/TabsRootDataAttributes.ts +12 -0
- package/src/tabs/root/stateAttributesMapping.ts +9 -0
- package/src/tabs/tab/TabsTab.vue +274 -0
- package/src/tabs/tab/TabsTabDataAttributes.ts +20 -0
- package/src/tabs/utils/areTabValuesEqual.ts +6 -0
- package/src/tooltip/arrow/TooltipArrow.vue +116 -0
- package/src/tooltip/arrow/TooltipArrowDataAttributes.ts +31 -0
- package/src/tooltip/index.ts +54 -0
- package/src/tooltip/popup/TooltipPopup.vue +142 -0
- package/src/tooltip/popup/TooltipPopupDataAttributes.ts +35 -0
- package/src/tooltip/portal/TooltipPortal.vue +83 -0
- package/src/tooltip/portal/TooltipPortalContext.ts +19 -0
- package/src/tooltip/positioner/TooltipPositioner.vue +221 -0
- package/src/tooltip/positioner/TooltipPositionerContext.ts +23 -0
- package/src/tooltip/positioner/TooltipPositionerCssVars.ts +27 -0
- package/src/tooltip/positioner/TooltipPositionerDataAttributes.ts +26 -0
- package/src/tooltip/provider/TooltipProvider.vue +78 -0
- package/src/tooltip/provider/TooltipProviderContext.ts +19 -0
- package/src/tooltip/root/TooltipRoot.vue +401 -0
- package/src/tooltip/root/TooltipRootContext.ts +71 -0
- package/src/tooltip/store/TooltipHandle.ts +215 -0
- package/src/tooltip/trigger/TooltipTrigger.vue +343 -0
- package/src/tooltip/trigger/TooltipTriggerDataAttributes.ts +12 -0
- package/src/tooltip/utils/constants.ts +2 -0
- package/src/tooltip/viewport/TooltipViewport.vue +149 -0
- package/src/tooltip/viewport/TooltipViewportCssVars.ts +14 -0
- package/src/tooltip/viewport/TooltipViewportDataAttributes.ts +26 -0
- package/src/utils/FloatingPortalLite.vue +66 -0
- package/src/utils/FocusGuard.vue +36 -0
- package/src/utils/adaptiveOriginMiddleware.ts +79 -0
- package/src/utils/addEventListener.ts +16 -0
- package/src/utils/constants.ts +11 -0
- package/src/utils/createBaseUIEventDetails.ts +2 -0
- package/src/utils/detectBrowser.ts +15 -0
- package/src/utils/formatNumber.ts +35 -2
- package/src/utils/getCssDimensions.ts +19 -0
- package/src/utils/getDisabledMountTransitionStyles.ts +9 -0
- package/src/utils/hideMiddleware.ts +19 -0
- package/src/utils/mergeCleanups.ts +7 -0
- package/src/utils/platform.ts +18 -0
- package/src/utils/popupStateMapping.ts +103 -0
- package/src/utils/reasons.ts +4 -0
- package/src/utils/serializeValue.ts +17 -0
- package/src/utils/useAnchorPositioning.ts +644 -0
- package/src/utils/useInterval.ts +45 -0
- package/src/utils/usePopupAutoResize.ts +254 -0
- package/src/utils/usePopupViewport.ts +398 -0
- package/src/utils/usePositioner.ts +61 -0
- package/src/utils/usePressAndHold.ts +260 -0
- package/src/utils/useRenderElement.ts +3 -0
- package/src/utils/useValueChanged.ts +21 -0
|
@@ -3,6 +3,7 @@ import type { Ref } from 'vue'
|
|
|
3
3
|
import type { Dimensions } from '../types'
|
|
4
4
|
|
|
5
5
|
import { floor } from '@floating-ui/utils'
|
|
6
|
+
import { getComputedStyle } from '@floating-ui/utils/dom'
|
|
6
7
|
import { ARROW_DOWN, ARROW_LEFT, ARROW_RIGHT, ARROW_UP } from './constants'
|
|
7
8
|
import { stopEvent } from './event'
|
|
8
9
|
|
|
@@ -473,3 +474,22 @@ export function isListIndexDisabled(
|
|
|
473
474
|
|| element.getAttribute('aria-disabled') === 'true'
|
|
474
475
|
)
|
|
475
476
|
}
|
|
477
|
+
|
|
478
|
+
export function isHiddenByStyles(styles: CSSStyleDeclaration) {
|
|
479
|
+
return styles.visibility === 'hidden' || styles.visibility === 'collapse'
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
export function isElementVisible(
|
|
483
|
+
element: Element | null,
|
|
484
|
+
styles: CSSStyleDeclaration | null = element ? getComputedStyle(element) : null,
|
|
485
|
+
) {
|
|
486
|
+
if (!element || !element.isConnected || !styles || isHiddenByStyles(styles)) {
|
|
487
|
+
return false
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
if (typeof element.checkVisibility === 'function') {
|
|
491
|
+
return element.checkVisibility()
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return styles.display !== 'none' && styles.display !== 'contents'
|
|
495
|
+
}
|
|
@@ -2,3 +2,5 @@ export const ARROW_LEFT = 'ArrowLeft'
|
|
|
2
2
|
export const ARROW_RIGHT = 'ArrowRight'
|
|
3
3
|
export const ARROW_UP = 'ArrowUp'
|
|
4
4
|
export const ARROW_DOWN = 'ArrowDown'
|
|
5
|
+
|
|
6
|
+
export const TYPEABLE_SELECTOR = 'input:not([type="hidden"]):not([disabled]),[contenteditable]:not([contenteditable="false"]),textarea:not([disabled])'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { FloatingEvents } from '../types'
|
|
2
|
+
|
|
3
|
+
export function createEventEmitter(): FloatingEvents {
|
|
4
|
+
const map = new Map<string, Set<(data: any) => void>>()
|
|
5
|
+
|
|
6
|
+
return {
|
|
7
|
+
emit(event: string, data?: any) {
|
|
8
|
+
map.get(event)?.forEach(listener => listener(data))
|
|
9
|
+
},
|
|
10
|
+
on(event: string, listener: (data: any) => void) {
|
|
11
|
+
if (!map.has(event)) {
|
|
12
|
+
map.set(event, new Set())
|
|
13
|
+
}
|
|
14
|
+
map.get(event)!.add(listener)
|
|
15
|
+
},
|
|
16
|
+
off(event: string, listener: (data: any) => void) {
|
|
17
|
+
map.get(event)?.delete(listener)
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -1,3 +1,62 @@
|
|
|
1
|
+
import type { FloatingTriggerMap } from '../types'
|
|
2
|
+
import { isElement, isHTMLElement } from '@floating-ui/utils/dom'
|
|
3
|
+
import { TYPEABLE_SELECTOR } from './constants'
|
|
4
|
+
import { contains, getTarget } from './shadowDom'
|
|
5
|
+
|
|
6
|
+
export { contains, getTarget }
|
|
7
|
+
|
|
8
|
+
export function isTargetInsideEnabledTrigger(
|
|
9
|
+
target: EventTarget | null,
|
|
10
|
+
triggerElements: FloatingTriggerMap,
|
|
11
|
+
) {
|
|
12
|
+
if (!isElement(target)) {
|
|
13
|
+
return false
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const targetElement = target
|
|
17
|
+
|
|
18
|
+
if (triggerElements.hasElement(targetElement)) {
|
|
19
|
+
return !targetElement.hasAttribute('data-trigger-disabled')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (const [, trigger] of triggerElements.entries()) {
|
|
23
|
+
if (contains(trigger, targetElement)) {
|
|
24
|
+
return !trigger.hasAttribute('data-trigger-disabled')
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function isEventTargetWithin(event: Event, node: Node | null | undefined) {
|
|
32
|
+
if (node == null) {
|
|
33
|
+
return false
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if ('composedPath' in event) {
|
|
37
|
+
return event.composedPath().includes(node)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const eventAgain = event as Event
|
|
41
|
+
return eventAgain.target != null && node.contains(eventAgain.target as Node)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function isRootElement(element: Element): boolean {
|
|
45
|
+
return element.matches('html,body')
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function isTypeableElement(element: unknown): boolean {
|
|
49
|
+
return isHTMLElement(element) && element.matches(TYPEABLE_SELECTOR)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function isInteractiveElement(element: Element | null) {
|
|
53
|
+
return (
|
|
54
|
+
element?.closest(
|
|
55
|
+
`button,a[href],[role="button"],select,[tabindex]:not([tabindex="-1"]),${TYPEABLE_SELECTOR}`,
|
|
56
|
+
) != null
|
|
57
|
+
)
|
|
58
|
+
}
|
|
59
|
+
|
|
1
60
|
export function matchesFocusVisible(element: Element | null) {
|
|
2
61
|
if (!element) {
|
|
3
62
|
return true
|
|
@@ -2,3 +2,18 @@ export function stopEvent(event: Event) {
|
|
|
2
2
|
event.preventDefault()
|
|
3
3
|
event.stopPropagation()
|
|
4
4
|
}
|
|
5
|
+
|
|
6
|
+
export function isMouseLikePointerType(pointerType: string | undefined, strict?: boolean) {
|
|
7
|
+
const values: Array<string | undefined> = ['mouse', 'pen']
|
|
8
|
+
|
|
9
|
+
if (!strict) {
|
|
10
|
+
values.push('', undefined)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return values.includes(pointerType)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isClickLikeEvent(event: Event) {
|
|
17
|
+
const type = event.type
|
|
18
|
+
return type === 'click' || type === 'mousedown' || type === 'keydown' || type === 'keyup'
|
|
19
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FloatingNodeType } from '../types'
|
|
2
|
+
|
|
3
|
+
export function getNodeChildren(
|
|
4
|
+
nodes: FloatingNodeType[],
|
|
5
|
+
id: string | undefined,
|
|
6
|
+
onlyOpenChildren = true,
|
|
7
|
+
): FloatingNodeType[] {
|
|
8
|
+
const directChildren = nodes.filter(node => node.parentId === id)
|
|
9
|
+
|
|
10
|
+
return directChildren.flatMap(child => [
|
|
11
|
+
...(!onlyOpenChildren || child.context?.open.value ? [child] : []),
|
|
12
|
+
...getNodeChildren(nodes, child.id, onlyOpenChildren),
|
|
13
|
+
])
|
|
14
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { getComputedStyle, getNodeName, isHTMLElement, isShadowRoot } from '@floating-ui/utils/dom'
|
|
2
|
+
import { ownerDocument } from '../../utils/owner'
|
|
3
|
+
import { isElementVisible } from './composite'
|
|
4
|
+
import { activeElement, contains } from './shadowDom'
|
|
5
|
+
|
|
6
|
+
export type FocusableElement = HTMLElement | SVGElement
|
|
7
|
+
|
|
8
|
+
const CANDIDATE_SELECTOR
|
|
9
|
+
= 'a[href],button,input,select,textarea,summary,details,iframe,object,embed,[tabindex],[contenteditable]:not([contenteditable="false"]),audio[controls],video[controls]'
|
|
10
|
+
|
|
11
|
+
function getParentElement(element: Element) {
|
|
12
|
+
const assignedSlot = (
|
|
13
|
+
element as Element & {
|
|
14
|
+
assignedSlot?: HTMLSlotElement | null | undefined
|
|
15
|
+
}
|
|
16
|
+
).assignedSlot
|
|
17
|
+
|
|
18
|
+
if (assignedSlot) {
|
|
19
|
+
return assignedSlot
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (element.parentElement) {
|
|
23
|
+
return element.parentElement
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const rootNode = element.getRootNode()
|
|
27
|
+
return isShadowRoot(rootNode) ? rootNode.host : null
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getDetailsSummary(details: Element) {
|
|
31
|
+
for (const child of Array.from(details.children)) {
|
|
32
|
+
if (getNodeName(child) === 'summary') {
|
|
33
|
+
return child
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function isWithinOpenDetailsSummary(element: Element, details: Element) {
|
|
41
|
+
const summary = getDetailsSummary(details)
|
|
42
|
+
return !!summary && (element === summary || contains(summary, element))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isFocusableCandidate(element: Element | null): element is FocusableElement {
|
|
46
|
+
const nodeName = element ? getNodeName(element) : ''
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
element != null
|
|
50
|
+
&& element.matches(CANDIDATE_SELECTOR)
|
|
51
|
+
&& (
|
|
52
|
+
nodeName !== 'summary'
|
|
53
|
+
|| (
|
|
54
|
+
element.parentElement != null
|
|
55
|
+
&& getNodeName(element.parentElement) === 'details'
|
|
56
|
+
&& getDetailsSummary(element.parentElement) === element
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
&& (nodeName !== 'details' || getDetailsSummary(element) == null)
|
|
60
|
+
&& (nodeName !== 'input' || (element as HTMLInputElement).type !== 'hidden')
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isFocusableElement(element: Element | null): element is FocusableElement {
|
|
65
|
+
if (!isFocusableCandidate(element) || !element.isConnected || element.matches(':disabled')) {
|
|
66
|
+
return false
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
for (let current: Element | null = element; current; current = getParentElement(current)) {
|
|
70
|
+
const isAncestor = current !== element
|
|
71
|
+
const isSlot = getNodeName(current) === 'slot'
|
|
72
|
+
|
|
73
|
+
if (current.hasAttribute('inert')) {
|
|
74
|
+
return false
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (
|
|
78
|
+
(
|
|
79
|
+
isAncestor
|
|
80
|
+
&& getNodeName(current) === 'details'
|
|
81
|
+
&& !(current as HTMLDetailsElement).open
|
|
82
|
+
&& !isWithinOpenDetailsSummary(element, current)
|
|
83
|
+
)
|
|
84
|
+
|| current.hasAttribute('hidden')
|
|
85
|
+
|| (!isSlot && !isVisibleInTabbableTree(current, isAncestor))
|
|
86
|
+
) {
|
|
87
|
+
return false
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return true
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isHTMLElementOrSVGElement(element: Element): element is FocusableElement {
|
|
95
|
+
return (
|
|
96
|
+
isHTMLElement(element)
|
|
97
|
+
|| (typeof SVGElement !== 'undefined' && element instanceof SVGElement)
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isVisibleInTabbableTree(element: Element, isAncestor: boolean) {
|
|
102
|
+
const styles = getComputedStyle(element)
|
|
103
|
+
|
|
104
|
+
if (!isAncestor) {
|
|
105
|
+
return isElementVisible(element, styles)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return styles.display !== 'none'
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getTabIndex(element: FocusableElement) {
|
|
112
|
+
const tabIndex = element.tabIndex
|
|
113
|
+
|
|
114
|
+
if (tabIndex < 0) {
|
|
115
|
+
const nodeName = getNodeName(element)
|
|
116
|
+
if (
|
|
117
|
+
nodeName === 'details'
|
|
118
|
+
|| nodeName === 'audio'
|
|
119
|
+
|| nodeName === 'video'
|
|
120
|
+
|| (isHTMLElement(element) && element.isContentEditable)
|
|
121
|
+
) {
|
|
122
|
+
return 0
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return tabIndex
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function getNamedRadioInput(element: FocusableElement) {
|
|
130
|
+
if (getNodeName(element) !== 'input') {
|
|
131
|
+
return null
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const input = element as HTMLInputElement
|
|
135
|
+
return input.type === 'radio' && input.name !== '' ? input : null
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function isTabbableRadio(element: FocusableElement, candidates: FocusableElement[]) {
|
|
139
|
+
const input = getNamedRadioInput(element)
|
|
140
|
+
|
|
141
|
+
if (!input) {
|
|
142
|
+
return true
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const checkedRadio = candidates.find((candidate) => {
|
|
146
|
+
const radio = getNamedRadioInput(candidate)
|
|
147
|
+
return radio?.name === input.name && radio.form === input.form && radio.checked
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
if (checkedRadio) {
|
|
151
|
+
return checkedRadio === input
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
candidates.find((candidate) => {
|
|
156
|
+
const radio = getNamedRadioInput(candidate)
|
|
157
|
+
return radio?.name === input.name && radio.form === input.form
|
|
158
|
+
}) === input
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function getComposedChildren(container: ParentNode): Element[] {
|
|
163
|
+
if (isHTMLElement(container) && getNodeName(container) === 'slot') {
|
|
164
|
+
const assignedElements = (container as HTMLSlotElement).assignedElements({ flatten: true })
|
|
165
|
+
if (assignedElements.length > 0) {
|
|
166
|
+
return assignedElements
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (isHTMLElement(container) && container.shadowRoot) {
|
|
171
|
+
return Array.from(container.shadowRoot.children)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return Array.from(container.children)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function appendCandidates(container: ParentNode, list: FocusableElement[]) {
|
|
178
|
+
getComposedChildren(container).forEach((child) => {
|
|
179
|
+
if (isFocusableCandidate(child)) {
|
|
180
|
+
list.push(child)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
appendCandidates(child, list)
|
|
184
|
+
})
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function appendMatchingElements(container: ParentNode, selector: string, list: FocusableElement[]) {
|
|
188
|
+
getComposedChildren(container).forEach((child) => {
|
|
189
|
+
if (isHTMLElementOrSVGElement(child) && child.matches(selector)) {
|
|
190
|
+
list.push(child)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
appendMatchingElements(child, selector, list)
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function getOwnerDocument(element: Element | null) {
|
|
198
|
+
return ownerDocument(element) ?? (typeof document !== 'undefined' ? document : null)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function getTabbableIn(container: HTMLElement, dir: 1 | -1): FocusableElement | undefined {
|
|
202
|
+
const list = tabbable(container)
|
|
203
|
+
const len = list.length
|
|
204
|
+
|
|
205
|
+
if (len === 0) {
|
|
206
|
+
return undefined
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const doc = getOwnerDocument(container)
|
|
210
|
+
const active = doc ? activeElement(doc) as FocusableElement : null
|
|
211
|
+
const index = active ? list.indexOf(active) : -1
|
|
212
|
+
const nextIndex = index === -1 ? (dir === 1 ? 0 : len - 1) : index + dir
|
|
213
|
+
|
|
214
|
+
return list[nextIndex]
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function getTabbableNearElement(referenceElement: Element | null, dir: 1 | -1) {
|
|
218
|
+
if (!referenceElement) {
|
|
219
|
+
return null
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const body = getOwnerDocument(referenceElement)?.body
|
|
223
|
+
if (!body) {
|
|
224
|
+
return null
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const list = tabbable(body)
|
|
228
|
+
const elementCount = list.length
|
|
229
|
+
|
|
230
|
+
if (elementCount === 0) {
|
|
231
|
+
return null
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const index = list.indexOf(referenceElement as FocusableElement)
|
|
235
|
+
|
|
236
|
+
if (index === -1) {
|
|
237
|
+
return null
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const nextIndex = (index + dir + elementCount) % elementCount
|
|
241
|
+
return list[nextIndex]
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export function isTabbable(element: Element | null) {
|
|
245
|
+
return isFocusableElement(element) && getTabIndex(element) >= 0
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export function focusable(container: Element) {
|
|
249
|
+
const candidates: FocusableElement[] = []
|
|
250
|
+
appendCandidates(container, candidates)
|
|
251
|
+
return candidates.filter(isFocusableElement)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function tabbable(container: Element) {
|
|
255
|
+
const candidates = focusable(container)
|
|
256
|
+
return candidates.filter(
|
|
257
|
+
element => getTabIndex(element) >= 0 && isTabbableRadio(element, candidates),
|
|
258
|
+
)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export function getNextTabbable(referenceElement: Element | null): FocusableElement | null {
|
|
262
|
+
const body = getOwnerDocument(referenceElement)?.body
|
|
263
|
+
return (body && getTabbableIn(body, 1)) || (referenceElement as FocusableElement | null)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function getPreviousTabbable(referenceElement: Element | null): FocusableElement | null {
|
|
267
|
+
const body = getOwnerDocument(referenceElement)?.body
|
|
268
|
+
return (body && getTabbableIn(body, -1)) || (referenceElement as FocusableElement | null)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function getTabbableAfterElement(referenceElement: Element | null): FocusableElement | null {
|
|
272
|
+
return getTabbableNearElement(referenceElement, 1)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export function getTabbableBeforeElement(
|
|
276
|
+
referenceElement: Element | null,
|
|
277
|
+
): FocusableElement | null {
|
|
278
|
+
return getTabbableNearElement(referenceElement, -1)
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function isOutsideEvent(event: FocusEvent, container?: Element) {
|
|
282
|
+
const containerElement = container || (event.currentTarget as Element)
|
|
283
|
+
const relatedTarget = event.relatedTarget as HTMLElement | null
|
|
284
|
+
return !relatedTarget || !contains(containerElement, relatedTarget)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export function disableFocusInside(container: HTMLElement) {
|
|
288
|
+
const tabbableElements = tabbable(container)
|
|
289
|
+
tabbableElements.forEach((element) => {
|
|
290
|
+
element.dataset.tabindex = element.getAttribute('tabindex') || ''
|
|
291
|
+
element.setAttribute('tabindex', '-1')
|
|
292
|
+
})
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function enableFocusInside(container: HTMLElement) {
|
|
296
|
+
const elements: FocusableElement[] = []
|
|
297
|
+
appendMatchingElements(container, '[data-tabindex]', elements)
|
|
298
|
+
elements.forEach((element) => {
|
|
299
|
+
const tabindex = element.getAttribute('data-tabindex')
|
|
300
|
+
element.removeAttribute('data-tabindex')
|
|
301
|
+
|
|
302
|
+
if (tabindex) {
|
|
303
|
+
element.setAttribute('tabindex', tabindex)
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
element.removeAttribute('tabindex')
|
|
307
|
+
}
|
|
308
|
+
})
|
|
309
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -15,13 +15,19 @@ export * from './form'
|
|
|
15
15
|
export * from './input'
|
|
16
16
|
export * from './merge-props'
|
|
17
17
|
export * from './meter'
|
|
18
|
+
export * from './number-field'
|
|
19
|
+
export * from './otp-field'
|
|
18
20
|
export * from './progress'
|
|
21
|
+
export * from './radio'
|
|
22
|
+
export * from './radio-group'
|
|
23
|
+
export * from './scroll-area'
|
|
19
24
|
export * from './separator'
|
|
20
25
|
export * from './slider'
|
|
21
26
|
export * from './switch'
|
|
22
|
-
export * from './
|
|
27
|
+
export * from './tabs'
|
|
23
28
|
export * from './toggle'
|
|
24
29
|
export * from './toggle-group'
|
|
25
30
|
export * from './toolbar'
|
|
31
|
+
export * from './tooltip'
|
|
26
32
|
export * from './use-render'
|
|
27
33
|
export { Slot } from './utils/slot'
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { BaseUIComponentProps, NativeButtonProps } from '../../utils/types'
|
|
3
|
+
import type { NumberFieldRootState } from '../root/NumberFieldRoot.vue'
|
|
4
|
+
import { computed, useAttrs } from 'vue'
|
|
5
|
+
import { mergeProps } from '../../merge-props/mergeProps'
|
|
6
|
+
import { useButton } from '../../use-button/useButton'
|
|
7
|
+
import { useRenderElement } from '../../utils/useRenderElement'
|
|
8
|
+
import { useNumberFieldRootContext } from '../root/NumberFieldRootContext'
|
|
9
|
+
import { useNumberFieldButton } from '../root/useNumberFieldButton'
|
|
10
|
+
import { stateAttributesMapping } from '../utils/stateAttributesMapping'
|
|
11
|
+
|
|
12
|
+
export type NumberFieldDecrementState = NumberFieldRootState
|
|
13
|
+
|
|
14
|
+
export interface NumberFieldDecrementProps
|
|
15
|
+
extends NativeButtonProps, BaseUIComponentProps<NumberFieldDecrementState> {
|
|
16
|
+
disabled?: boolean
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
defineOptions({
|
|
20
|
+
name: 'NumberFieldDecrement',
|
|
21
|
+
inheritAttrs: false,
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
const props = withDefaults(defineProps<NumberFieldDecrementProps>(), {
|
|
25
|
+
as: 'button',
|
|
26
|
+
disabled: false,
|
|
27
|
+
nativeButton: true,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const attrs = useAttrs()
|
|
31
|
+
const attrsObject = attrs as Record<string, any>
|
|
32
|
+
|
|
33
|
+
const {
|
|
34
|
+
allowInputSyncRef,
|
|
35
|
+
disabled: contextDisabled,
|
|
36
|
+
formatOptionsRef,
|
|
37
|
+
getStepAmount,
|
|
38
|
+
id,
|
|
39
|
+
incrementValue,
|
|
40
|
+
inputRef,
|
|
41
|
+
inputValue,
|
|
42
|
+
minWithDefault,
|
|
43
|
+
readOnly,
|
|
44
|
+
setValue,
|
|
45
|
+
state,
|
|
46
|
+
value,
|
|
47
|
+
valueRef,
|
|
48
|
+
locale,
|
|
49
|
+
lastChangedValueRef,
|
|
50
|
+
onValueCommitted,
|
|
51
|
+
} = useNumberFieldRootContext()
|
|
52
|
+
|
|
53
|
+
const isMin = computed(() => value.value != null && value.value <= minWithDefault.value)
|
|
54
|
+
const disabled = computed(() => props.disabled || contextDisabled.value || isMin.value)
|
|
55
|
+
|
|
56
|
+
const buttonProps = useNumberFieldButton({
|
|
57
|
+
isIncrement: false,
|
|
58
|
+
inputRef,
|
|
59
|
+
inputValue: () => inputValue.value,
|
|
60
|
+
disabled: () => disabled.value,
|
|
61
|
+
readOnly: () => readOnly.value,
|
|
62
|
+
id: () => id.value,
|
|
63
|
+
setValue,
|
|
64
|
+
getStepAmount,
|
|
65
|
+
incrementValue,
|
|
66
|
+
allowInputSyncRef,
|
|
67
|
+
formatOptionsRef,
|
|
68
|
+
valueRef,
|
|
69
|
+
locale: () => locale.value,
|
|
70
|
+
lastChangedValueRef,
|
|
71
|
+
onValueCommitted,
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const { getButtonProps, buttonRef } = useButton({
|
|
75
|
+
disabled: () => disabled.value,
|
|
76
|
+
native: () => props.nativeButton,
|
|
77
|
+
focusableWhenDisabled: true,
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
const buttonState = computed<NumberFieldDecrementState>(() => ({
|
|
81
|
+
...state.value,
|
|
82
|
+
disabled: disabled.value,
|
|
83
|
+
}))
|
|
84
|
+
|
|
85
|
+
const combinedProps = computed(() =>
|
|
86
|
+
mergeProps(buttonProps.value, attrsObject, getButtonProps()),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
const {
|
|
90
|
+
tag,
|
|
91
|
+
mergedProps,
|
|
92
|
+
renderless,
|
|
93
|
+
ref: renderRef,
|
|
94
|
+
} = useRenderElement({
|
|
95
|
+
componentProps: props,
|
|
96
|
+
state: buttonState,
|
|
97
|
+
props: combinedProps,
|
|
98
|
+
stateAttributesMapping,
|
|
99
|
+
defaultTagName: 'button',
|
|
100
|
+
ref: buttonRef,
|
|
101
|
+
})
|
|
102
|
+
</script>
|
|
103
|
+
|
|
104
|
+
<template>
|
|
105
|
+
<slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="buttonState" />
|
|
106
|
+
<component :is="tag" v-else :ref="renderRef" v-bind="mergedProps">
|
|
107
|
+
<slot :state="buttonState" />
|
|
108
|
+
</component>
|
|
109
|
+
</template>
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { BaseUIComponentProps } from '../../utils/types'
|
|
3
|
+
import type { NumberFieldRootState } from '../root/NumberFieldRoot.vue'
|
|
4
|
+
import { computed, useAttrs } from 'vue'
|
|
5
|
+
import { mergeProps } from '../../merge-props/mergeProps'
|
|
6
|
+
import { useRenderElement } from '../../utils/useRenderElement'
|
|
7
|
+
import { useNumberFieldRootContext } from '../root/NumberFieldRootContext'
|
|
8
|
+
import { stateAttributesMapping } from '../utils/stateAttributesMapping'
|
|
9
|
+
|
|
10
|
+
export type NumberFieldGroupState = NumberFieldRootState
|
|
11
|
+
|
|
12
|
+
export interface NumberFieldGroupProps extends BaseUIComponentProps<NumberFieldGroupState> {}
|
|
13
|
+
|
|
14
|
+
defineOptions({
|
|
15
|
+
name: 'NumberFieldGroup',
|
|
16
|
+
inheritAttrs: false,
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const props = defineProps<NumberFieldGroupProps>()
|
|
20
|
+
|
|
21
|
+
const attrs = useAttrs()
|
|
22
|
+
const attrsObject = attrs as Record<string, any>
|
|
23
|
+
|
|
24
|
+
const { state } = useNumberFieldRootContext()
|
|
25
|
+
|
|
26
|
+
const groupProps = computed(() => mergeProps(attrsObject, { role: 'group' }))
|
|
27
|
+
|
|
28
|
+
const {
|
|
29
|
+
tag,
|
|
30
|
+
mergedProps,
|
|
31
|
+
renderless,
|
|
32
|
+
ref: renderRef,
|
|
33
|
+
} = useRenderElement({
|
|
34
|
+
componentProps: props,
|
|
35
|
+
state,
|
|
36
|
+
props: groupProps,
|
|
37
|
+
stateAttributesMapping,
|
|
38
|
+
defaultTagName: 'div',
|
|
39
|
+
})
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
|
|
44
|
+
<component :is="tag" v-else :ref="renderRef" v-bind="mergedProps">
|
|
45
|
+
<slot :state="state" />
|
|
46
|
+
</component>
|
|
47
|
+
</template>
|