base-ui-vue 0.1.0 → 0.2.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/README.md +41 -1
- package/dist/button/Button.cjs +53 -12
- package/dist/button/Button.cjs.map +1 -1
- package/dist/button/Button.js +26 -15
- package/dist/button/Button.js.map +1 -1
- package/dist/button/ToolbarButton.cjs +367 -0
- package/dist/button/ToolbarButton.cjs.map +1 -0
- package/dist/button/ToolbarButton.js +320 -0
- package/dist/button/ToolbarButton.js.map +1 -0
- package/dist/button/ToolbarButtonDataAttributes.cjs +27 -0
- package/dist/button/ToolbarButtonDataAttributes.cjs.map +1 -0
- package/dist/button/ToolbarButtonDataAttributes.js +21 -0
- package/dist/button/ToolbarButtonDataAttributes.js.map +1 -0
- package/dist/checkbox/index.cjs +1173 -0
- package/dist/checkbox/index.cjs.map +1 -0
- package/dist/checkbox/index.js +1048 -0
- package/dist/checkbox/index.js.map +1 -0
- package/dist/checkbox-group/CheckboxGroup.cjs +629 -0
- package/dist/checkbox-group/CheckboxGroup.cjs.map +1 -0
- package/dist/checkbox-group/CheckboxGroup.js +540 -0
- package/dist/checkbox-group/CheckboxGroup.js.map +1 -0
- package/dist/checkbox-group/CheckboxGroupDataAttributes.cjs +18 -0
- package/dist/checkbox-group/CheckboxGroupDataAttributes.cjs.map +1 -0
- package/dist/checkbox-group/CheckboxGroupDataAttributes.js +12 -0
- package/dist/checkbox-group/CheckboxGroupDataAttributes.js.map +1 -0
- package/dist/composite/composite.cjs +167 -0
- package/dist/composite/composite.cjs.map +1 -1
- package/dist/composite/composite.js +96 -1
- package/dist/composite/composite.js.map +1 -1
- package/dist/composite/constants.cjs +12 -0
- package/dist/composite/constants.cjs.map +1 -0
- package/dist/composite/constants.js +6 -0
- package/dist/composite/constants.js.map +1 -0
- package/dist/control/FieldControl.cjs +18 -343
- package/dist/control/FieldControl.cjs.map +1 -1
- package/dist/control/FieldControl.js +14 -285
- package/dist/control/FieldControl.js.map +1 -1
- package/dist/control/SliderControl.cjs +636 -0
- package/dist/control/SliderControl.cjs.map +1 -0
- package/dist/control/SliderControl.js +553 -0
- package/dist/control/SliderControl.js.map +1 -0
- package/dist/control/SliderControlDataAttributes.cjs +47 -0
- package/dist/control/SliderControlDataAttributes.cjs.map +1 -0
- package/dist/control/SliderControlDataAttributes.js +41 -0
- package/dist/control/SliderControlDataAttributes.js.map +1 -0
- package/dist/csp-provider/CSPContext.cjs +32 -0
- package/dist/csp-provider/CSPContext.cjs.map +1 -0
- package/dist/csp-provider/CSPContext.js +21 -0
- package/dist/csp-provider/CSPContext.js.map +1 -0
- package/dist/csp-provider/CSPProvider.cjs +46 -0
- package/dist/csp-provider/CSPProvider.cjs.map +1 -0
- package/dist/csp-provider/CSPProvider.js +41 -0
- package/dist/csp-provider/CSPProvider.js.map +1 -0
- package/dist/description/FieldDescription.cjs +5 -5
- package/dist/description/FieldDescription.cjs.map +1 -1
- package/dist/description/FieldDescription.js +1 -1
- package/dist/direction-provider/DirectionProvider.cjs +2 -2
- package/dist/direction-provider/DirectionProvider.cjs.map +1 -1
- package/dist/direction-provider/DirectionProvider.js +1 -1
- package/dist/error/FieldError.cjs +10 -288
- package/dist/error/FieldError.cjs.map +1 -1
- package/dist/error/FieldError.js +4 -246
- package/dist/error/FieldError.js.map +1 -1
- package/dist/form/Form.cjs +5 -4
- package/dist/form/Form.cjs.map +1 -1
- package/dist/form/Form.js +5 -4
- package/dist/form/Form.js.map +1 -1
- package/dist/group/ToolbarGroup.cjs +92 -0
- package/dist/group/ToolbarGroup.cjs.map +1 -0
- package/dist/group/ToolbarGroup.js +87 -0
- package/dist/group/ToolbarGroup.js.map +1 -0
- package/dist/group/ToolbarGroupDataAttributes.cjs +23 -0
- package/dist/group/ToolbarGroupDataAttributes.cjs.map +1 -0
- package/dist/group/ToolbarGroupDataAttributes.js +17 -0
- package/dist/group/ToolbarGroupDataAttributes.js.map +1 -0
- package/dist/header/AccordionHeader.cjs +2 -2
- package/dist/header/AccordionHeader.js +1 -1
- package/dist/image/AvatarImage.cjs +4 -4
- package/dist/image/AvatarImage.cjs.map +1 -1
- package/dist/image/AvatarImage.js +1 -1
- package/dist/index.cjs +80 -10
- package/dist/index.d.cts +2751 -612
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +2751 -612
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -5
- package/dist/index2.cjs +3651 -370
- package/dist/index2.cjs.map +1 -1
- package/dist/index2.js +3365 -270
- package/dist/index2.js.map +1 -1
- package/package.json +8 -4
- package/src/accordion/root/AccordionRoot.vue +2 -1
- package/src/checkbox/index.ts +23 -0
- package/src/checkbox/indicator/CheckboxIndicator.vue +102 -0
- package/src/checkbox/indicator/CheckboxIndicatorDataAttributes.ts +61 -0
- package/src/checkbox/root/CheckboxRoot.vue +632 -0
- package/src/checkbox/root/CheckboxRootContext.ts +22 -0
- package/src/checkbox/root/CheckboxRootDataAttributes.ts +54 -0
- package/src/checkbox/utils/useStateAttributesMapping.ts +30 -0
- package/src/checkbox-group/CheckboxGroup.vue +241 -0
- package/src/checkbox-group/CheckboxGroupContext.ts +39 -0
- package/src/checkbox-group/CheckboxGroupDataAttributes.ts +6 -0
- package/src/checkbox-group/index.ts +11 -0
- package/src/checkbox-group/useCheckboxGroupParent.ts +173 -0
- package/src/collapsible/panel/useCollapsiblePanel.ts +2 -1
- package/src/collapsible/root/useCollapsibleRoot.ts +3 -1
- package/src/composite/composite.ts +2 -0
- package/src/composite/item/CompositeItem.vue +7 -8
- package/src/composite/root/CompositeRoot.vue +12 -1
- package/src/csp-provider/CSPContext.ts +26 -0
- package/src/csp-provider/CSPProvider.vue +40 -0
- package/src/csp-provider/index.ts +5 -0
- package/src/field/item/FieldItem.vue +6 -1
- package/src/field/label/FieldLabel.vue +10 -51
- package/src/field/root/FieldRoot.vue +16 -3
- package/src/floating-ui-vue/types.ts +1 -4
- package/src/floating-ui-vue/utils/element.ts +12 -0
- package/src/floating-ui-vue/utils/shadowDom.ts +44 -0
- package/src/floating-ui-vue/utils.ts +3 -0
- package/src/form/Form.vue +5 -3
- package/src/index.ts +9 -0
- package/src/labelable-provider/LabelableContext.ts +2 -2
- package/src/labelable-provider/LabelableProvider.vue +21 -4
- package/src/labelable-provider/index.ts +2 -0
- package/src/labelable-provider/useAriaLabelledBy.ts +9 -9
- package/src/labelable-provider/useLabel.ts +115 -0
- package/src/labelable-provider/useLabelableId.ts +12 -10
- package/src/separator/Separator.vue +65 -0
- package/src/separator/SeparatorDataAttributes.ts +7 -0
- package/src/separator/index.ts +3 -0
- package/src/slider/control/SliderControl.vue +497 -0
- package/src/slider/control/SliderControlDataAttributes.ts +35 -0
- package/src/slider/index.ts +35 -0
- package/src/slider/indicator/SliderIndicator.vue +144 -0
- package/src/slider/indicator/SliderIndicatorDataAttributes.ts +35 -0
- package/src/slider/label/SliderLabel.vue +75 -0
- package/src/slider/root/SliderRoot.vue +557 -0
- package/src/slider/root/SliderRootContext.ts +126 -0
- package/src/slider/root/SliderRootDataAttributes.ts +35 -0
- package/src/slider/root/stateAttributesMapping.ts +13 -0
- package/src/slider/thumb/SliderThumb.vue +601 -0
- package/src/slider/thumb/SliderThumbDataAttributes.ts +39 -0
- package/src/slider/thumb/prehydrationScript.min.ts +5 -0
- package/src/slider/thumb/prehydrationScript.template.js +69 -0
- package/src/slider/track/SliderTrack.vue +48 -0
- package/src/slider/track/SliderTrackDataAttributes.ts +10 -0
- package/src/slider/utils/asc.ts +3 -0
- package/src/slider/utils/getMidpoint.ts +9 -0
- package/src/slider/utils/getPushedThumbValues.ts +68 -0
- package/src/slider/utils/getSliderValue.ts +25 -0
- package/src/slider/utils/replaceArrayItemAtIndex.ts +15 -0
- package/src/slider/utils/resolveThumbCollision.ts +177 -0
- package/src/slider/utils/roundValueToStep.ts +19 -0
- package/src/slider/utils/test-utils.ts +25 -0
- package/src/slider/utils/validateMinimumDistance.ts +20 -0
- package/src/slider/utils/valueArrayToPercentages.ts +10 -0
- package/src/slider/value/SliderValue.vue +90 -0
- package/src/slider/value/SliderValueDataAttributes.ts +35 -0
- package/src/switch/index.ts +14 -0
- package/src/switch/root/SwitchRoot.vue +448 -0
- package/src/switch/root/SwitchRootContext.ts +22 -0
- package/src/switch/root/SwitchRootDataAttributes.ts +46 -0
- package/src/switch/stateAttributesMapping.ts +23 -0
- package/src/switch/thumb/SwitchThumb.vue +59 -0
- package/src/switch/thumb/SwitchThumbDataAttributes.ts +46 -0
- package/src/toggle/Toggle.vue +211 -0
- package/src/toggle/ToggleDataAttributes.ts +6 -0
- package/src/toggle/index.ts +3 -0
- package/src/toggle-group/ToggleGroup.vue +224 -0
- package/src/toggle-group/ToggleGroupContext.ts +45 -0
- package/src/toggle-group/ToggleGroupDataAttributes.ts +15 -0
- package/src/toggle-group/index.ts +5 -0
- package/src/toolbar/button/ToolbarButton.vue +99 -0
- package/src/toolbar/button/ToolbarButtonDataAttributes.ts +15 -0
- package/src/toolbar/group/ToolbarGroup.vue +70 -0
- package/src/toolbar/group/ToolbarGroupContext.ts +23 -0
- package/src/toolbar/group/ToolbarGroupDataAttributes.ts +11 -0
- package/src/toolbar/index.ts +27 -0
- package/src/toolbar/input/ToolbarInput.vue +114 -0
- package/src/toolbar/input/ToolbarInputDataAttributes.ts +15 -0
- package/src/toolbar/link/ToolbarLink.vue +54 -0
- package/src/toolbar/link/ToolbarLinkDataAttributes.ts +7 -0
- package/src/toolbar/root/ToolbarRoot.vue +144 -0
- package/src/toolbar/root/ToolbarRootContext.ts +29 -0
- package/src/toolbar/root/ToolbarRootDataAttributes.ts +11 -0
- package/src/toolbar/separator/ToolbarSeparator.vue +41 -0
- package/src/toolbar/separator/ToolbarSeparatorDataAttributes.ts +7 -0
- package/src/use-button/useButton.ts +2 -1
- package/src/utils/areArraysEqual.ts +12 -0
- package/src/utils/clamp.ts +7 -0
- package/src/utils/createBaseUIEventDetails.ts +9 -0
- package/src/utils/formatNumber.ts +7 -0
- package/src/utils/owner.ts +5 -0
- package/src/utils/resolveAriaLabelledBy.ts +10 -0
- package/src/utils/useControllableState.ts +78 -14
- package/src/utils/useFocusableWhenDisabled.ts +6 -1
- package/src/utils/useMergedRefs.ts +26 -2
- package/src/utils/useRegisteredLabelId.ts +21 -0
- package/src/utils/valueToPercent.ts +7 -0
- package/src/utils/visuallyHidden.ts +24 -0
- package/dist/direction-provider/DirectionContext.cjs +0 -26
- package/dist/direction-provider/DirectionContext.cjs.map +0 -1
- package/dist/direction-provider/DirectionContext.js +0 -15
- package/dist/direction-provider/DirectionContext.js.map +0 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { CSPContextValue } from './CSPContext'
|
|
3
|
+
import { computed, provide, toRef } from 'vue'
|
|
4
|
+
import { cspContextKey } from './CSPContext'
|
|
5
|
+
|
|
6
|
+
export interface CSPProviderProps {
|
|
7
|
+
/**
|
|
8
|
+
* The nonce value to apply to inline `<style>` and `<script>` tags.
|
|
9
|
+
*/
|
|
10
|
+
nonce?: string
|
|
11
|
+
/**
|
|
12
|
+
* Whether inline `<style>` elements created by Base UI components should not be rendered.
|
|
13
|
+
* @default false
|
|
14
|
+
*/
|
|
15
|
+
disableStyleElements?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Provides a default Content Security Policy (CSP) configuration for Base UI Vue components
|
|
20
|
+
* that require inline `<style>` or `<script>` tags.
|
|
21
|
+
*/
|
|
22
|
+
defineOptions({
|
|
23
|
+
name: 'CSPProvider',
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
const props = withDefaults(defineProps<CSPProviderProps>(), {
|
|
27
|
+
disableStyleElements: false,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
const contextValue = computed<CSPContextValue>(() => ({
|
|
31
|
+
nonce: toRef(() => props.nonce),
|
|
32
|
+
disableStyleElements: toRef(() => props.disableStyleElements),
|
|
33
|
+
}))
|
|
34
|
+
|
|
35
|
+
provide(cspContextKey, contextValue.value)
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<template>
|
|
39
|
+
<slot />
|
|
40
|
+
</template>
|
|
@@ -3,6 +3,7 @@ import type { BaseUIComponentProps } from '../../utils/types'
|
|
|
3
3
|
import type { FieldRootState } from '../root/FieldRoot.vue'
|
|
4
4
|
import type { FieldItemContext } from './FieldItemContext'
|
|
5
5
|
import { computed, provide, useAttrs } from 'vue'
|
|
6
|
+
import { useCheckboxGroupContext } from '../../checkbox-group/CheckboxGroupContext'
|
|
6
7
|
import LabelableProvider from '../../labelable-provider/LabelableProvider.vue'
|
|
7
8
|
import { useRenderElement } from '../../utils/useRenderElement'
|
|
8
9
|
import { useFieldRootContext } from '../root/FieldRootContext'
|
|
@@ -35,6 +36,10 @@ const attrs = useAttrs()
|
|
|
35
36
|
const { state, disabled: rootDisabled } = useFieldRootContext(false)
|
|
36
37
|
|
|
37
38
|
const disabled = computed(() => rootDisabled.value || props.disabled)
|
|
39
|
+
const checkboxGroupContext = useCheckboxGroupContext(true)
|
|
40
|
+
const parentId = computed(() => checkboxGroupContext?.parent.id)
|
|
41
|
+
const hasParentCheckbox = computed(() => checkboxGroupContext?.allValues.value !== undefined)
|
|
42
|
+
const controlId = computed(() => (hasParentCheckbox.value ? parentId.value : undefined))
|
|
38
43
|
|
|
39
44
|
const contextValue: FieldItemContext = {
|
|
40
45
|
disabled,
|
|
@@ -54,7 +59,7 @@ const { tag, mergedProps, renderless } = useRenderElement({
|
|
|
54
59
|
</script>
|
|
55
60
|
|
|
56
61
|
<template>
|
|
57
|
-
<LabelableProvider>
|
|
62
|
+
<LabelableProvider :control-id="controlId">
|
|
58
63
|
<slot v-if="renderless" :props="mergedProps" :state="state" />
|
|
59
64
|
<component :is="tag" v-else v-bind="mergedProps">
|
|
60
65
|
<slot :state="state" />
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { BaseUIComponentProps } from '../../utils/types'
|
|
3
3
|
import type { FieldRootState } from '../root/FieldRoot.vue'
|
|
4
|
-
import { computed, useAttrs
|
|
4
|
+
import { computed, useAttrs } from 'vue'
|
|
5
5
|
import { useLabelableContext } from '../../labelable-provider/LabelableContext'
|
|
6
|
-
import {
|
|
6
|
+
import { useLabel } from '../../labelable-provider/useLabel'
|
|
7
7
|
import { useRenderElement } from '../../utils/useRenderElement'
|
|
8
8
|
import { useFieldRootContext } from '../root/FieldRootContext'
|
|
9
9
|
import { fieldValidityMapping } from '../utils/constants'
|
|
@@ -35,60 +35,19 @@ const props = withDefaults(defineProps<FieldLabelProps>(), {
|
|
|
35
35
|
const attrs = useAttrs()
|
|
36
36
|
|
|
37
37
|
const fieldRootContext = useFieldRootContext(false)
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const labelId = computed(() => props.id ?? contextLabelId.value ?? generatedLabelId)
|
|
43
|
-
|
|
44
|
-
watchEffect((onCleanup) => {
|
|
45
|
-
const id = labelId.value
|
|
46
|
-
setLabelId(id)
|
|
47
|
-
onCleanup(() => setLabelId(undefined))
|
|
38
|
+
const { labelId } = useLabelableContext()
|
|
39
|
+
const label = useLabel({
|
|
40
|
+
id: computed(() => labelId.value ?? props.id),
|
|
41
|
+
native: computed(() => props.nativeLabel),
|
|
48
42
|
})
|
|
49
43
|
|
|
50
|
-
function handleInteraction(event: MouseEvent) {
|
|
51
|
-
const target = event.target as HTMLElement | null
|
|
52
|
-
if (target?.closest('button,input,select,textarea')) {
|
|
53
|
-
return
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (!event.defaultPrevented && event.detail > 1) {
|
|
57
|
-
event.preventDefault()
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
if (props.nativeLabel || !controlId.value) {
|
|
61
|
-
return
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const controlElement = document.getElementById(controlId.value)
|
|
65
|
-
if (controlElement) {
|
|
66
|
-
controlElement.focus()
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
44
|
const { tag, mergedProps, renderless } = useRenderElement({
|
|
71
45
|
componentProps: props,
|
|
72
46
|
state: fieldRootContext.state,
|
|
73
|
-
props: computed(() => {
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
id: labelId.value,
|
|
78
|
-
for: controlId.value ?? undefined,
|
|
79
|
-
onMousedown: handleInteraction,
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return {
|
|
84
|
-
...attrs,
|
|
85
|
-
id: labelId.value,
|
|
86
|
-
onClick: handleInteraction,
|
|
87
|
-
onPointerdown: (event: PointerEvent) => {
|
|
88
|
-
event.preventDefault()
|
|
89
|
-
},
|
|
90
|
-
}
|
|
91
|
-
}),
|
|
47
|
+
props: computed(() => ({
|
|
48
|
+
...attrs,
|
|
49
|
+
...label.props.value,
|
|
50
|
+
})),
|
|
92
51
|
stateAttributesMapping: fieldValidityMapping,
|
|
93
52
|
defaultTagName: 'label',
|
|
94
53
|
})
|
|
@@ -125,9 +125,22 @@ const parentLabelable = useLabelableContext()
|
|
|
125
125
|
const controlId = ref<string | null | undefined>(undefined)
|
|
126
126
|
const labelId = ref<string | undefined>(undefined)
|
|
127
127
|
const messageIds = ref<string[]>([])
|
|
128
|
+
const controlIdRegistrations = new Map<symbol, string | null | undefined>()
|
|
128
129
|
|
|
129
|
-
function
|
|
130
|
-
|
|
130
|
+
function registerControlId(source: symbol, id: string | null | undefined) {
|
|
131
|
+
if (id === undefined) {
|
|
132
|
+
controlIdRegistrations.delete(source)
|
|
133
|
+
}
|
|
134
|
+
else {
|
|
135
|
+
controlIdRegistrations.set(source, id)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (controlIdRegistrations.size === 0) {
|
|
139
|
+
controlId.value = undefined
|
|
140
|
+
return
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
controlId.value = controlIdRegistrations.values().next().value
|
|
131
144
|
}
|
|
132
145
|
|
|
133
146
|
function setLabelId(id: string | undefined) {
|
|
@@ -147,7 +160,7 @@ function getDescriptionProps() {
|
|
|
147
160
|
|
|
148
161
|
const labelableContext: LabelableContext = {
|
|
149
162
|
controlId,
|
|
150
|
-
|
|
163
|
+
registerControlId,
|
|
151
164
|
labelId,
|
|
152
165
|
setLabelId,
|
|
153
166
|
messageIds,
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { isShadowRoot } from '@floating-ui/utils/dom'
|
|
2
|
+
|
|
3
|
+
export function activeElement(doc: Document) {
|
|
4
|
+
let element = doc.activeElement
|
|
5
|
+
|
|
6
|
+
while (element?.shadowRoot?.activeElement != null) {
|
|
7
|
+
element = element.shadowRoot.activeElement
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
return element
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function contains(parent?: Element | null, child?: Element | null) {
|
|
14
|
+
if (!parent || !child) {
|
|
15
|
+
return false
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const rootNode = child.getRootNode?.()
|
|
19
|
+
|
|
20
|
+
if (parent.contains(child)) {
|
|
21
|
+
return true
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (rootNode && isShadowRoot(rootNode)) {
|
|
25
|
+
let next: Element | null = child
|
|
26
|
+
while (next) {
|
|
27
|
+
if (parent === next) {
|
|
28
|
+
return true
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
next = (next.parentNode as Element | null) ?? ((next as unknown as ShadowRoot).host ?? null)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function getTarget(event: Event) {
|
|
39
|
+
if ('composedPath' in event && typeof event.composedPath === 'function') {
|
|
40
|
+
return event.composedPath()[0]
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return (event as Event).target
|
|
44
|
+
}
|
package/src/form/Form.vue
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { BaseUIComponentProps } from '../utils/types'
|
|
3
3
|
import type { FormContext, FormErrors, FormField, FormValidationMode } from './FormContext'
|
|
4
|
-
import { computed, provide, ref, shallowRef, useAttrs, watch } from 'vue'
|
|
4
|
+
import { computed, nextTick, provide, ref, shallowRef, useAttrs, watch } from 'vue'
|
|
5
5
|
import { EMPTY_OBJECT } from '../utils/empty'
|
|
6
6
|
import { useRenderElement } from '../utils/useRenderElement'
|
|
7
7
|
import { formContextKey } from './FormContext'
|
|
@@ -76,12 +76,14 @@ function focusControl(el: HTMLElement | null) {
|
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
watch(internalErrors, () => {
|
|
79
|
+
watch(internalErrors, async () => {
|
|
80
80
|
if (!submittedRef.value)
|
|
81
81
|
return
|
|
82
82
|
|
|
83
83
|
submittedRef.value = false
|
|
84
84
|
|
|
85
|
+
await nextTick()
|
|
86
|
+
|
|
85
87
|
const invalidFields = Array.from(formRef.value.fields.values()).filter(
|
|
86
88
|
field => field.validityData.state.valid === false,
|
|
87
89
|
)
|
|
@@ -90,7 +92,7 @@ watch(internalErrors, () => {
|
|
|
90
92
|
const controlRef = invalidFields[0].controlRef
|
|
91
93
|
focusControl(controlRef.value)
|
|
92
94
|
}
|
|
93
|
-
})
|
|
95
|
+
}, { flush: 'post' })
|
|
94
96
|
|
|
95
97
|
function handleImperativeValidate(fieldName?: string) {
|
|
96
98
|
const values = Array.from(formRef.value.fields.values())
|
package/src/index.ts
CHANGED
|
@@ -4,11 +4,20 @@ export const version = '0.0.0'
|
|
|
4
4
|
export * from './accordion'
|
|
5
5
|
export * from './avatar'
|
|
6
6
|
export * from './button'
|
|
7
|
+
export * from './checkbox'
|
|
8
|
+
export * from './checkbox-group'
|
|
7
9
|
export * from './collapsible'
|
|
10
|
+
export * from './csp-provider'
|
|
8
11
|
export * from './direction-provider'
|
|
9
12
|
export * from './field'
|
|
10
13
|
export * from './fieldset'
|
|
11
14
|
export * from './form'
|
|
12
15
|
export * from './merge-props'
|
|
16
|
+
export * from './separator'
|
|
17
|
+
export * from './slider'
|
|
18
|
+
export * from './switch'
|
|
19
|
+
export * from './toggle'
|
|
20
|
+
export * from './toggle-group'
|
|
21
|
+
export * from './toolbar'
|
|
13
22
|
export * from './use-render'
|
|
14
23
|
export { Slot } from './utils/slot'
|
|
@@ -8,7 +8,7 @@ export interface LabelableContext {
|
|
|
8
8
|
* When `null` the association is implicit.
|
|
9
9
|
*/
|
|
10
10
|
controlId: Readonly<Ref<string | null | undefined>>
|
|
11
|
-
|
|
11
|
+
registerControlId: (source: symbol, id: string | null | undefined) => void
|
|
12
12
|
labelId: Readonly<Ref<string | undefined>>
|
|
13
13
|
setLabelId: (id: string | undefined) => void
|
|
14
14
|
messageIds: Readonly<Ref<string[]>>
|
|
@@ -18,7 +18,7 @@ export interface LabelableContext {
|
|
|
18
18
|
|
|
19
19
|
const defaultContext: LabelableContext = {
|
|
20
20
|
controlId: shallowReadonly(shallowRef<string | null | undefined>(undefined)),
|
|
21
|
-
|
|
21
|
+
registerControlId: NOOP,
|
|
22
22
|
labelId: shallowReadonly(shallowRef<string | undefined>(undefined)),
|
|
23
23
|
setLabelId: NOOP,
|
|
24
24
|
messageIds: shallowReadonly(shallowRef<string[]>([])),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import type { LabelableContext } from './LabelableContext'
|
|
3
3
|
import { provide, ref } from 'vue'
|
|
4
|
+
import { useBaseUiId } from '../utils/useBaseUiId'
|
|
4
5
|
import { labelableContextKey, useLabelableContext } from './LabelableContext'
|
|
5
6
|
|
|
6
7
|
export interface LabelableProviderProps {
|
|
@@ -12,14 +13,30 @@ defineOptions({ name: 'LabelableProvider' })
|
|
|
12
13
|
|
|
13
14
|
const props = defineProps<LabelableProviderProps>()
|
|
14
15
|
|
|
15
|
-
const
|
|
16
|
+
const defaultId = useBaseUiId()
|
|
17
|
+
const initialControlId = props.controlId === undefined ? defaultId : props.controlId
|
|
18
|
+
|
|
19
|
+
const controlId = ref<string | null | undefined>(initialControlId)
|
|
16
20
|
const labelId = ref<string | undefined>(props.labelId)
|
|
17
21
|
const messageIds = ref<string[]>([])
|
|
22
|
+
const registrations = new Map<symbol, string | null | undefined>()
|
|
18
23
|
|
|
19
24
|
const parent = useLabelableContext()
|
|
20
25
|
|
|
21
|
-
function
|
|
22
|
-
|
|
26
|
+
function registerControlId(source: symbol, id: string | null | undefined) {
|
|
27
|
+
if (id === undefined) {
|
|
28
|
+
registrations.delete(source)
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
registrations.set(source, id)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (registrations.size === 0) {
|
|
35
|
+
controlId.value = initialControlId
|
|
36
|
+
return
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
controlId.value = registrations.values().next().value
|
|
23
40
|
}
|
|
24
41
|
|
|
25
42
|
function setLabelId(id: string | undefined) {
|
|
@@ -39,7 +56,7 @@ function getDescriptionProps() {
|
|
|
39
56
|
|
|
40
57
|
const contextValue: LabelableContext = {
|
|
41
58
|
controlId,
|
|
42
|
-
|
|
59
|
+
registerControlId,
|
|
43
60
|
labelId,
|
|
44
61
|
setLabelId,
|
|
45
62
|
messageIds,
|
|
@@ -2,5 +2,7 @@ export { labelableContextKey, useLabelableContext } from './LabelableContext'
|
|
|
2
2
|
export type { LabelableContext } from './LabelableContext'
|
|
3
3
|
export { default as LabelableProvider } from './LabelableProvider.vue'
|
|
4
4
|
export type { LabelableProviderProps } from './LabelableProvider.vue'
|
|
5
|
+
export { useLabel } from './useLabel'
|
|
6
|
+
export type { UseLabelParameters } from './useLabel'
|
|
5
7
|
export { useLabelableId } from './useLabelableId'
|
|
6
8
|
export type { UseLabelableIdParameters } from './useLabelableId'
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import type { Ref } from 'vue'
|
|
2
|
-
import { computed, ref,
|
|
3
|
-
import { useBaseUiId } from '../utils/useBaseUiId'
|
|
1
|
+
import type { MaybeRefOrGetter, Ref } from 'vue'
|
|
2
|
+
import { computed, ref, toValue, watchPostEffect } from 'vue'
|
|
4
3
|
|
|
5
4
|
export interface UseAriaLabelledByParameters {
|
|
6
5
|
/**
|
|
@@ -26,7 +25,7 @@ export interface UseAriaLabelledByParameters {
|
|
|
26
25
|
* Optional base id used when generating an id
|
|
27
26
|
* for an implicit label discovered in the DOM.
|
|
28
27
|
*/
|
|
29
|
-
labelSourceId?: string
|
|
28
|
+
labelSourceId?: MaybeRefOrGetter<string | undefined>
|
|
30
29
|
}
|
|
31
30
|
|
|
32
31
|
export function useAriaLabelledBy(params: UseAriaLabelledByParameters) {
|
|
@@ -34,23 +33,24 @@ export function useAriaLabelledBy(params: UseAriaLabelledByParameters) {
|
|
|
34
33
|
|
|
35
34
|
const fallbackAriaLabelledBy = ref<string | undefined>(undefined)
|
|
36
35
|
|
|
37
|
-
const generatedLabelId =
|
|
38
|
-
|
|
39
|
-
|
|
36
|
+
const generatedLabelId = computed(() => {
|
|
37
|
+
const sourceId = toValue(labelSourceId)
|
|
38
|
+
return sourceId ? `${sourceId}-label` : undefined
|
|
39
|
+
})
|
|
40
40
|
|
|
41
41
|
const resolvedAriaLabelledBy = computed(() => {
|
|
42
42
|
const explicit = ariaLabelledBy?.value
|
|
43
43
|
const fromContext = labelId?.value
|
|
44
44
|
return explicit ?? fromContext ?? fallbackAriaLabelledBy.value
|
|
45
45
|
})
|
|
46
|
-
|
|
46
|
+
watchPostEffect(() => {
|
|
47
47
|
const explicitOrContext = ariaLabelledBy?.value || labelId?.value
|
|
48
48
|
if (explicitOrContext || !enableFallback) {
|
|
49
49
|
fallbackAriaLabelledBy.value = undefined
|
|
50
50
|
return
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
const next = getAriaLabelledBy(labelSourceRef.value, generatedLabelId)
|
|
53
|
+
const next = getAriaLabelledBy(labelSourceRef.value, generatedLabelId.value)
|
|
54
54
|
if (fallbackAriaLabelledBy.value !== next) {
|
|
55
55
|
fallbackAriaLabelledBy.value = next
|
|
56
56
|
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { MaybeRefOrGetter } from 'vue'
|
|
2
|
+
import { isHTMLElement } from '@floating-ui/utils/dom'
|
|
3
|
+
import { computed, toValue } from 'vue'
|
|
4
|
+
import { getTarget } from '../floating-ui-vue/utils'
|
|
5
|
+
import { ownerDocument } from '../utils/owner'
|
|
6
|
+
import { useRegisteredLabelId } from '../utils/useRegisteredLabelId'
|
|
7
|
+
import { useLabelableContext } from './LabelableContext'
|
|
8
|
+
|
|
9
|
+
export interface UseLabelParameters {
|
|
10
|
+
id?: MaybeRefOrGetter<string | undefined>
|
|
11
|
+
/**
|
|
12
|
+
* Control id used when no labelable context control id exists.
|
|
13
|
+
*/
|
|
14
|
+
fallbackControlId?: MaybeRefOrGetter<string | null | undefined>
|
|
15
|
+
/**
|
|
16
|
+
* Whether the rendered element is a native `<label>`.
|
|
17
|
+
* @default false
|
|
18
|
+
*/
|
|
19
|
+
native?: MaybeRefOrGetter<boolean | undefined>
|
|
20
|
+
/**
|
|
21
|
+
* Additional callback to sync the current label id with local component state/store.
|
|
22
|
+
*/
|
|
23
|
+
setLabelId?: ((nextLabelId: string | undefined) => void) | undefined
|
|
24
|
+
/**
|
|
25
|
+
* Custom focus handler for non-native labels.
|
|
26
|
+
* If omitted, focus behavior targets the resolved control id.
|
|
27
|
+
*/
|
|
28
|
+
focusControl?:
|
|
29
|
+
| ((event: MouseEvent, controlId: string | null | undefined) => void)
|
|
30
|
+
| undefined
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function useLabel(params: UseLabelParameters = {}) {
|
|
34
|
+
const {
|
|
35
|
+
id: idProp,
|
|
36
|
+
fallbackControlId,
|
|
37
|
+
native,
|
|
38
|
+
setLabelId: setLabelIdProp,
|
|
39
|
+
focusControl: focusControlProp,
|
|
40
|
+
} = params
|
|
41
|
+
|
|
42
|
+
const { controlId: contextControlId, setLabelId: setContextLabelId } = useLabelableContext()
|
|
43
|
+
|
|
44
|
+
const syncLabelId = (nextLabelId: string | undefined) => {
|
|
45
|
+
setContextLabelId(nextLabelId)
|
|
46
|
+
setLabelIdProp?.(nextLabelId)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const labelId = useRegisteredLabelId(idProp, syncLabelId)
|
|
50
|
+
const resolvedControlId = computed(() => contextControlId.value ?? toValue(fallbackControlId))
|
|
51
|
+
const isNative = computed(() => toValue(native) ?? false)
|
|
52
|
+
|
|
53
|
+
function defaultFocusControl(event: MouseEvent, controlId: string | null | undefined) {
|
|
54
|
+
if (!controlId) {
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const controlElement = ownerDocument(event.currentTarget as Element)?.getElementById(controlId)
|
|
59
|
+
if (isHTMLElement(controlElement)) {
|
|
60
|
+
focusElementWithVisible(controlElement)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function handleInteraction(event: MouseEvent) {
|
|
65
|
+
const target = getTarget(event) as HTMLElement | null
|
|
66
|
+
if (target?.closest('button,input,select,textarea')) {
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!event.defaultPrevented && event.detail > 1) {
|
|
71
|
+
event.preventDefault()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (isNative.value) {
|
|
75
|
+
return
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (focusControlProp) {
|
|
79
|
+
focusControlProp(event, resolvedControlId.value)
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
defaultFocusControl(event, resolvedControlId.value)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const labelProps = computed(() => {
|
|
87
|
+
if (isNative.value) {
|
|
88
|
+
return {
|
|
89
|
+
id: labelId.value,
|
|
90
|
+
for: resolvedControlId.value ?? undefined,
|
|
91
|
+
onMousedown: handleInteraction,
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
id: labelId.value,
|
|
97
|
+
onClick: handleInteraction,
|
|
98
|
+
onPointerdown(event: PointerEvent) {
|
|
99
|
+
event.preventDefault()
|
|
100
|
+
},
|
|
101
|
+
}
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
labelId,
|
|
106
|
+
props: labelProps,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function focusElementWithVisible(element: HTMLElement) {
|
|
111
|
+
element.focus({
|
|
112
|
+
// @ts-expect-error not available in types yet
|
|
113
|
+
focusVisible: true,
|
|
114
|
+
})
|
|
115
|
+
}
|
|
@@ -1,30 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { MaybeRefOrGetter } from 'vue'
|
|
2
|
+
import { computed, toValue, watchEffect } from 'vue'
|
|
2
3
|
import { useBaseUiId } from '../utils/useBaseUiId'
|
|
3
4
|
import { useLabelableContext } from './LabelableContext'
|
|
4
5
|
|
|
5
6
|
export interface UseLabelableIdParameters {
|
|
6
|
-
id?: string
|
|
7
|
+
id?: MaybeRefOrGetter<string | undefined>
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
export function useLabelableId(params: UseLabelableIdParameters = {}) {
|
|
10
11
|
const { id } = params
|
|
11
|
-
const { controlId,
|
|
12
|
+
const { controlId, registerControlId } = useLabelableContext()
|
|
13
|
+
const source = Symbol('labelable-id')
|
|
12
14
|
|
|
13
|
-
const defaultId = useBaseUiId(
|
|
14
|
-
const resolvedId = id ?? defaultId
|
|
15
|
+
const defaultId = useBaseUiId()
|
|
16
|
+
const resolvedId = computed(() => toValue(id) ?? defaultId)
|
|
15
17
|
|
|
16
18
|
watchEffect((onCleanup) => {
|
|
17
|
-
if (!resolvedId) {
|
|
18
|
-
|
|
19
|
+
if (!resolvedId.value) {
|
|
20
|
+
registerControlId(source, undefined)
|
|
19
21
|
return
|
|
20
22
|
}
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
registerControlId(source, resolvedId.value)
|
|
23
25
|
|
|
24
26
|
onCleanup(() => {
|
|
25
|
-
|
|
27
|
+
registerControlId(source, undefined)
|
|
26
28
|
})
|
|
27
29
|
})
|
|
28
30
|
|
|
29
|
-
return computed(() => controlId.value ??
|
|
31
|
+
return computed(() => controlId.value ?? resolvedId.value)
|
|
30
32
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { BaseUIComponentProps, Orientation } from '../utils/types'
|
|
3
|
+
import { computed, useAttrs } from 'vue'
|
|
4
|
+
import { useRenderElement } from '../utils/useRenderElement'
|
|
5
|
+
|
|
6
|
+
export interface SeparatorState {
|
|
7
|
+
/**
|
|
8
|
+
* The orientation of the separator.
|
|
9
|
+
*/
|
|
10
|
+
orientation: Orientation
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SeparatorProps extends BaseUIComponentProps<SeparatorState> {
|
|
14
|
+
/**
|
|
15
|
+
* The orientation of the separator.
|
|
16
|
+
* @default 'horizontal'
|
|
17
|
+
*/
|
|
18
|
+
orientation?: Orientation
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A separator element accessible to screen readers.
|
|
23
|
+
* Renders a `<div>` element.
|
|
24
|
+
*
|
|
25
|
+
* Documentation: [Base UI Vue Separator](https://baseui-vue.com/docs/components/separator)
|
|
26
|
+
*/
|
|
27
|
+
defineOptions({
|
|
28
|
+
name: 'Separator',
|
|
29
|
+
inheritAttrs: false,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const props = withDefaults(defineProps<SeparatorProps>(), {
|
|
33
|
+
as: 'div',
|
|
34
|
+
orientation: 'horizontal',
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const attrs = useAttrs()
|
|
38
|
+
|
|
39
|
+
const state = computed<SeparatorState>(() => ({
|
|
40
|
+
orientation: props.orientation,
|
|
41
|
+
}))
|
|
42
|
+
|
|
43
|
+
const {
|
|
44
|
+
tag,
|
|
45
|
+
mergedProps,
|
|
46
|
+
renderless,
|
|
47
|
+
ref: renderRef,
|
|
48
|
+
} = useRenderElement({
|
|
49
|
+
componentProps: props,
|
|
50
|
+
state,
|
|
51
|
+
props: computed(() => ({
|
|
52
|
+
...attrs,
|
|
53
|
+
'role': 'separator',
|
|
54
|
+
'aria-orientation': props.orientation,
|
|
55
|
+
})),
|
|
56
|
+
defaultTagName: 'div',
|
|
57
|
+
})
|
|
58
|
+
</script>
|
|
59
|
+
|
|
60
|
+
<template>
|
|
61
|
+
<slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
|
|
62
|
+
<component :is="tag" v-else :ref="renderRef" v-bind="mergedProps">
|
|
63
|
+
<slot :state="state" />
|
|
64
|
+
</component>
|
|
65
|
+
</template>
|