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.
Files changed (204) hide show
  1. package/README.md +41 -1
  2. package/dist/button/Button.cjs +53 -12
  3. package/dist/button/Button.cjs.map +1 -1
  4. package/dist/button/Button.js +26 -15
  5. package/dist/button/Button.js.map +1 -1
  6. package/dist/button/ToolbarButton.cjs +367 -0
  7. package/dist/button/ToolbarButton.cjs.map +1 -0
  8. package/dist/button/ToolbarButton.js +320 -0
  9. package/dist/button/ToolbarButton.js.map +1 -0
  10. package/dist/button/ToolbarButtonDataAttributes.cjs +27 -0
  11. package/dist/button/ToolbarButtonDataAttributes.cjs.map +1 -0
  12. package/dist/button/ToolbarButtonDataAttributes.js +21 -0
  13. package/dist/button/ToolbarButtonDataAttributes.js.map +1 -0
  14. package/dist/checkbox/index.cjs +1173 -0
  15. package/dist/checkbox/index.cjs.map +1 -0
  16. package/dist/checkbox/index.js +1048 -0
  17. package/dist/checkbox/index.js.map +1 -0
  18. package/dist/checkbox-group/CheckboxGroup.cjs +629 -0
  19. package/dist/checkbox-group/CheckboxGroup.cjs.map +1 -0
  20. package/dist/checkbox-group/CheckboxGroup.js +540 -0
  21. package/dist/checkbox-group/CheckboxGroup.js.map +1 -0
  22. package/dist/checkbox-group/CheckboxGroupDataAttributes.cjs +18 -0
  23. package/dist/checkbox-group/CheckboxGroupDataAttributes.cjs.map +1 -0
  24. package/dist/checkbox-group/CheckboxGroupDataAttributes.js +12 -0
  25. package/dist/checkbox-group/CheckboxGroupDataAttributes.js.map +1 -0
  26. package/dist/composite/composite.cjs +167 -0
  27. package/dist/composite/composite.cjs.map +1 -1
  28. package/dist/composite/composite.js +96 -1
  29. package/dist/composite/composite.js.map +1 -1
  30. package/dist/composite/constants.cjs +12 -0
  31. package/dist/composite/constants.cjs.map +1 -0
  32. package/dist/composite/constants.js +6 -0
  33. package/dist/composite/constants.js.map +1 -0
  34. package/dist/control/FieldControl.cjs +18 -343
  35. package/dist/control/FieldControl.cjs.map +1 -1
  36. package/dist/control/FieldControl.js +14 -285
  37. package/dist/control/FieldControl.js.map +1 -1
  38. package/dist/control/SliderControl.cjs +636 -0
  39. package/dist/control/SliderControl.cjs.map +1 -0
  40. package/dist/control/SliderControl.js +553 -0
  41. package/dist/control/SliderControl.js.map +1 -0
  42. package/dist/control/SliderControlDataAttributes.cjs +47 -0
  43. package/dist/control/SliderControlDataAttributes.cjs.map +1 -0
  44. package/dist/control/SliderControlDataAttributes.js +41 -0
  45. package/dist/control/SliderControlDataAttributes.js.map +1 -0
  46. package/dist/csp-provider/CSPContext.cjs +32 -0
  47. package/dist/csp-provider/CSPContext.cjs.map +1 -0
  48. package/dist/csp-provider/CSPContext.js +21 -0
  49. package/dist/csp-provider/CSPContext.js.map +1 -0
  50. package/dist/csp-provider/CSPProvider.cjs +46 -0
  51. package/dist/csp-provider/CSPProvider.cjs.map +1 -0
  52. package/dist/csp-provider/CSPProvider.js +41 -0
  53. package/dist/csp-provider/CSPProvider.js.map +1 -0
  54. package/dist/description/FieldDescription.cjs +5 -5
  55. package/dist/description/FieldDescription.cjs.map +1 -1
  56. package/dist/description/FieldDescription.js +1 -1
  57. package/dist/direction-provider/DirectionProvider.cjs +2 -2
  58. package/dist/direction-provider/DirectionProvider.cjs.map +1 -1
  59. package/dist/direction-provider/DirectionProvider.js +1 -1
  60. package/dist/error/FieldError.cjs +10 -288
  61. package/dist/error/FieldError.cjs.map +1 -1
  62. package/dist/error/FieldError.js +4 -246
  63. package/dist/error/FieldError.js.map +1 -1
  64. package/dist/form/Form.cjs +5 -4
  65. package/dist/form/Form.cjs.map +1 -1
  66. package/dist/form/Form.js +5 -4
  67. package/dist/form/Form.js.map +1 -1
  68. package/dist/group/ToolbarGroup.cjs +92 -0
  69. package/dist/group/ToolbarGroup.cjs.map +1 -0
  70. package/dist/group/ToolbarGroup.js +87 -0
  71. package/dist/group/ToolbarGroup.js.map +1 -0
  72. package/dist/group/ToolbarGroupDataAttributes.cjs +23 -0
  73. package/dist/group/ToolbarGroupDataAttributes.cjs.map +1 -0
  74. package/dist/group/ToolbarGroupDataAttributes.js +17 -0
  75. package/dist/group/ToolbarGroupDataAttributes.js.map +1 -0
  76. package/dist/header/AccordionHeader.cjs +2 -2
  77. package/dist/header/AccordionHeader.js +1 -1
  78. package/dist/image/AvatarImage.cjs +4 -4
  79. package/dist/image/AvatarImage.cjs.map +1 -1
  80. package/dist/image/AvatarImage.js +1 -1
  81. package/dist/index.cjs +80 -10
  82. package/dist/index.d.cts +2751 -612
  83. package/dist/index.d.cts.map +1 -1
  84. package/dist/index.d.ts +2751 -612
  85. package/dist/index.d.ts.map +1 -1
  86. package/dist/index.js +15 -5
  87. package/dist/index2.cjs +3651 -370
  88. package/dist/index2.cjs.map +1 -1
  89. package/dist/index2.js +3365 -270
  90. package/dist/index2.js.map +1 -1
  91. package/package.json +8 -4
  92. package/src/accordion/root/AccordionRoot.vue +2 -1
  93. package/src/checkbox/index.ts +23 -0
  94. package/src/checkbox/indicator/CheckboxIndicator.vue +102 -0
  95. package/src/checkbox/indicator/CheckboxIndicatorDataAttributes.ts +61 -0
  96. package/src/checkbox/root/CheckboxRoot.vue +632 -0
  97. package/src/checkbox/root/CheckboxRootContext.ts +22 -0
  98. package/src/checkbox/root/CheckboxRootDataAttributes.ts +54 -0
  99. package/src/checkbox/utils/useStateAttributesMapping.ts +30 -0
  100. package/src/checkbox-group/CheckboxGroup.vue +241 -0
  101. package/src/checkbox-group/CheckboxGroupContext.ts +39 -0
  102. package/src/checkbox-group/CheckboxGroupDataAttributes.ts +6 -0
  103. package/src/checkbox-group/index.ts +11 -0
  104. package/src/checkbox-group/useCheckboxGroupParent.ts +173 -0
  105. package/src/collapsible/panel/useCollapsiblePanel.ts +2 -1
  106. package/src/collapsible/root/useCollapsibleRoot.ts +3 -1
  107. package/src/composite/composite.ts +2 -0
  108. package/src/composite/item/CompositeItem.vue +7 -8
  109. package/src/composite/root/CompositeRoot.vue +12 -1
  110. package/src/csp-provider/CSPContext.ts +26 -0
  111. package/src/csp-provider/CSPProvider.vue +40 -0
  112. package/src/csp-provider/index.ts +5 -0
  113. package/src/field/item/FieldItem.vue +6 -1
  114. package/src/field/label/FieldLabel.vue +10 -51
  115. package/src/field/root/FieldRoot.vue +16 -3
  116. package/src/floating-ui-vue/types.ts +1 -4
  117. package/src/floating-ui-vue/utils/element.ts +12 -0
  118. package/src/floating-ui-vue/utils/shadowDom.ts +44 -0
  119. package/src/floating-ui-vue/utils.ts +3 -0
  120. package/src/form/Form.vue +5 -3
  121. package/src/index.ts +9 -0
  122. package/src/labelable-provider/LabelableContext.ts +2 -2
  123. package/src/labelable-provider/LabelableProvider.vue +21 -4
  124. package/src/labelable-provider/index.ts +2 -0
  125. package/src/labelable-provider/useAriaLabelledBy.ts +9 -9
  126. package/src/labelable-provider/useLabel.ts +115 -0
  127. package/src/labelable-provider/useLabelableId.ts +12 -10
  128. package/src/separator/Separator.vue +65 -0
  129. package/src/separator/SeparatorDataAttributes.ts +7 -0
  130. package/src/separator/index.ts +3 -0
  131. package/src/slider/control/SliderControl.vue +497 -0
  132. package/src/slider/control/SliderControlDataAttributes.ts +35 -0
  133. package/src/slider/index.ts +35 -0
  134. package/src/slider/indicator/SliderIndicator.vue +144 -0
  135. package/src/slider/indicator/SliderIndicatorDataAttributes.ts +35 -0
  136. package/src/slider/label/SliderLabel.vue +75 -0
  137. package/src/slider/root/SliderRoot.vue +557 -0
  138. package/src/slider/root/SliderRootContext.ts +126 -0
  139. package/src/slider/root/SliderRootDataAttributes.ts +35 -0
  140. package/src/slider/root/stateAttributesMapping.ts +13 -0
  141. package/src/slider/thumb/SliderThumb.vue +601 -0
  142. package/src/slider/thumb/SliderThumbDataAttributes.ts +39 -0
  143. package/src/slider/thumb/prehydrationScript.min.ts +5 -0
  144. package/src/slider/thumb/prehydrationScript.template.js +69 -0
  145. package/src/slider/track/SliderTrack.vue +48 -0
  146. package/src/slider/track/SliderTrackDataAttributes.ts +10 -0
  147. package/src/slider/utils/asc.ts +3 -0
  148. package/src/slider/utils/getMidpoint.ts +9 -0
  149. package/src/slider/utils/getPushedThumbValues.ts +68 -0
  150. package/src/slider/utils/getSliderValue.ts +25 -0
  151. package/src/slider/utils/replaceArrayItemAtIndex.ts +15 -0
  152. package/src/slider/utils/resolveThumbCollision.ts +177 -0
  153. package/src/slider/utils/roundValueToStep.ts +19 -0
  154. package/src/slider/utils/test-utils.ts +25 -0
  155. package/src/slider/utils/validateMinimumDistance.ts +20 -0
  156. package/src/slider/utils/valueArrayToPercentages.ts +10 -0
  157. package/src/slider/value/SliderValue.vue +90 -0
  158. package/src/slider/value/SliderValueDataAttributes.ts +35 -0
  159. package/src/switch/index.ts +14 -0
  160. package/src/switch/root/SwitchRoot.vue +448 -0
  161. package/src/switch/root/SwitchRootContext.ts +22 -0
  162. package/src/switch/root/SwitchRootDataAttributes.ts +46 -0
  163. package/src/switch/stateAttributesMapping.ts +23 -0
  164. package/src/switch/thumb/SwitchThumb.vue +59 -0
  165. package/src/switch/thumb/SwitchThumbDataAttributes.ts +46 -0
  166. package/src/toggle/Toggle.vue +211 -0
  167. package/src/toggle/ToggleDataAttributes.ts +6 -0
  168. package/src/toggle/index.ts +3 -0
  169. package/src/toggle-group/ToggleGroup.vue +224 -0
  170. package/src/toggle-group/ToggleGroupContext.ts +45 -0
  171. package/src/toggle-group/ToggleGroupDataAttributes.ts +15 -0
  172. package/src/toggle-group/index.ts +5 -0
  173. package/src/toolbar/button/ToolbarButton.vue +99 -0
  174. package/src/toolbar/button/ToolbarButtonDataAttributes.ts +15 -0
  175. package/src/toolbar/group/ToolbarGroup.vue +70 -0
  176. package/src/toolbar/group/ToolbarGroupContext.ts +23 -0
  177. package/src/toolbar/group/ToolbarGroupDataAttributes.ts +11 -0
  178. package/src/toolbar/index.ts +27 -0
  179. package/src/toolbar/input/ToolbarInput.vue +114 -0
  180. package/src/toolbar/input/ToolbarInputDataAttributes.ts +15 -0
  181. package/src/toolbar/link/ToolbarLink.vue +54 -0
  182. package/src/toolbar/link/ToolbarLinkDataAttributes.ts +7 -0
  183. package/src/toolbar/root/ToolbarRoot.vue +144 -0
  184. package/src/toolbar/root/ToolbarRootContext.ts +29 -0
  185. package/src/toolbar/root/ToolbarRootDataAttributes.ts +11 -0
  186. package/src/toolbar/separator/ToolbarSeparator.vue +41 -0
  187. package/src/toolbar/separator/ToolbarSeparatorDataAttributes.ts +7 -0
  188. package/src/use-button/useButton.ts +2 -1
  189. package/src/utils/areArraysEqual.ts +12 -0
  190. package/src/utils/clamp.ts +7 -0
  191. package/src/utils/createBaseUIEventDetails.ts +9 -0
  192. package/src/utils/formatNumber.ts +7 -0
  193. package/src/utils/owner.ts +5 -0
  194. package/src/utils/resolveAriaLabelledBy.ts +10 -0
  195. package/src/utils/useControllableState.ts +78 -14
  196. package/src/utils/useFocusableWhenDisabled.ts +6 -1
  197. package/src/utils/useMergedRefs.ts +26 -2
  198. package/src/utils/useRegisteredLabelId.ts +21 -0
  199. package/src/utils/valueToPercent.ts +7 -0
  200. package/src/utils/visuallyHidden.ts +24 -0
  201. package/dist/direction-provider/DirectionContext.cjs +0 -26
  202. package/dist/direction-provider/DirectionContext.cjs.map +0 -1
  203. package/dist/direction-provider/DirectionContext.js +0 -15
  204. package/dist/direction-provider/DirectionContext.js.map +0 -1
@@ -0,0 +1,30 @@
1
+ import type { StateAttributesMapping } from '../../utils/getStateAttributesProps'
2
+ import type { CheckboxRootState } from '../root/CheckboxRoot.vue'
3
+ import { fieldValidityMapping } from '../../field/utils/constants'
4
+ import { CheckboxRootDataAttributes } from '../root/CheckboxRootDataAttributes'
5
+
6
+ const CHECKED_ATTRS: Record<string, string> = {
7
+ [CheckboxRootDataAttributes.checked]: '',
8
+ }
9
+ const UNCHECKED_ATTRS: Record<string, string> = {
10
+ [CheckboxRootDataAttributes.unchecked]: '',
11
+ }
12
+
13
+ export function useCheckboxStateAttributesMapping(
14
+ state: Readonly<{ value: CheckboxRootState }>,
15
+ ): StateAttributesMapping<CheckboxRootState> {
16
+ return {
17
+ checked(value) {
18
+ if (state.value.indeterminate) {
19
+ return null
20
+ }
21
+
22
+ if (value) {
23
+ return CHECKED_ATTRS
24
+ }
25
+
26
+ return UNCHECKED_ATTRS
27
+ },
28
+ ...fieldValidityMapping,
29
+ }
30
+ }
@@ -0,0 +1,241 @@
1
+ <script setup lang="ts">
2
+ import type { FieldRootState } from '../field/root/FieldRoot.vue'
3
+ import type { BaseUIChangeEventDetails } from '../utils/createBaseUIEventDetails'
4
+ import type { REASONS } from '../utils/reasons'
5
+ import type { BaseUIComponentProps } from '../utils/types'
6
+ import { computed, provide, ref, useAttrs, watch, watchEffect } from 'vue'
7
+ import { useFieldRootContext } from '../field/root/FieldRootContext'
8
+ import { useField } from '../field/useField'
9
+ import { fieldValidityMapping } from '../field/utils/constants'
10
+ import { useFormContext } from '../form/FormContext'
11
+ import { useLabelableContext } from '../labelable-provider/LabelableContext'
12
+ import { mergeProps } from '../merge-props/mergeProps'
13
+ import { EMPTY_ARRAY } from '../utils/constants'
14
+ import { useBaseUiId } from '../utils/useBaseUiId'
15
+ import { useControllableState } from '../utils/useControllableState'
16
+ import { useRenderElement } from '../utils/useRenderElement'
17
+ import { checkboxGroupContextKey } from './CheckboxGroupContext'
18
+ import { useCheckboxGroupParent } from './useCheckboxGroupParent'
19
+
20
+ export interface CheckboxGroupState extends FieldRootState {
21
+ /**
22
+ * Whether the component should ignore user interaction.
23
+ */
24
+ disabled: boolean
25
+ }
26
+
27
+ export interface CheckboxGroupProps extends BaseUIComponentProps<CheckboxGroupState> {
28
+ /**
29
+ * Names of the checkboxes in the group that should be ticked.
30
+ *
31
+ * To render an uncontrolled checkbox group, use the `defaultValue` prop instead.
32
+ */
33
+ value?: string[]
34
+ /**
35
+ * Names of the checkboxes in the group that should be initially ticked.
36
+ *
37
+ * To render a controlled checkbox group, use the `value` prop instead.
38
+ */
39
+ defaultValue?: string[]
40
+ /**
41
+ * Names of all checkboxes in the group. Use this when creating a parent checkbox.
42
+ */
43
+ allValues?: string[]
44
+ /**
45
+ * Whether the component should ignore user interaction.
46
+ * @default false
47
+ */
48
+ disabled?: boolean
49
+ id?: string
50
+ }
51
+
52
+ /**
53
+ * Provides a shared state to a series of checkboxes.
54
+ *
55
+ * Documentation: [Base UI Vue Checkbox Group](https://baseui-vue.com/docs/components/checkbox-group)
56
+ */
57
+ defineOptions({
58
+ name: 'CheckboxGroup',
59
+ inheritAttrs: false,
60
+ })
61
+
62
+ const props = withDefaults(defineProps<CheckboxGroupProps>(), {
63
+ as: 'div',
64
+ disabled: false,
65
+ defaultValue: () => [],
66
+ })
67
+
68
+ const emit = defineEmits<{
69
+ /**
70
+ * Event handler called when a checkbox in the group is ticked or unticked.
71
+ * Provides the new value as an argument.
72
+ */
73
+ valueChange: [
74
+ value: string[],
75
+ eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
76
+ ]
77
+ }>()
78
+
79
+ const attrs = useAttrs()
80
+ const attrsObject = attrs as Record<string, any>
81
+
82
+ const {
83
+ disabled: fieldDisabled,
84
+ name: fieldName,
85
+ state: fieldState,
86
+ validation,
87
+ setFilled,
88
+ setDirty,
89
+ shouldValidateOnChange,
90
+ validityData,
91
+ } = useFieldRootContext()
92
+ const labelableContext = useLabelableContext()
93
+ const { labelId, getDescriptionProps } = labelableContext
94
+ const { clearErrors } = useFormContext()
95
+ const controlSource = Symbol('checkbox-group-control')
96
+
97
+ const disabled = computed(() => fieldDisabled.value || props.disabled)
98
+
99
+ const { value, setValue: setValueUnwrapped } = useControllableState<string[]>({
100
+ controlled: () => props.value,
101
+ default: () => props.defaultValue,
102
+ name: 'CheckboxGroup',
103
+ })
104
+
105
+ const generatedId = useBaseUiId()
106
+ const defaultValue = computed(() => props.defaultValue ?? [])
107
+ const allValues = computed(() => props.allValues)
108
+ const id = computed(() => props.id ?? generatedId)
109
+
110
+ watchEffect((onCleanup) => {
111
+ labelableContext.registerControlId(controlSource, id.value)
112
+
113
+ onCleanup(() => {
114
+ labelableContext.registerControlId(controlSource, undefined)
115
+ })
116
+ })
117
+
118
+ function setValue(
119
+ nextValue: string[],
120
+ eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
121
+ ) {
122
+ emit('valueChange', nextValue, eventDetails)
123
+
124
+ if (eventDetails.isCanceled) {
125
+ return
126
+ }
127
+
128
+ setValueUnwrapped(nextValue)
129
+ }
130
+
131
+ const parent = useCheckboxGroupParent({
132
+ allValues,
133
+ value,
134
+ onValueChange: setValue,
135
+ })
136
+
137
+ const controlRef = ref<HTMLElement | null>(null)
138
+
139
+ function registerControlRef(element: HTMLElement | null) {
140
+ if (controlRef.value == null && element != null && !element.hasAttribute('data-parent')) {
141
+ controlRef.value = element
142
+ }
143
+ }
144
+
145
+ useField({
146
+ enabled: computed(() => Boolean(fieldName.value)),
147
+ id,
148
+ commit: (value: unknown) => validation.commit(value),
149
+ value,
150
+ controlRef,
151
+ name: fieldName,
152
+ getValue: () => value.value,
153
+ })
154
+
155
+ watch(
156
+ () => value.value.slice(),
157
+ (nextValue) => {
158
+ if (fieldName.value) {
159
+ clearErrors(fieldName.value)
160
+ }
161
+
162
+ const initialValue = Array.isArray(validityData.value.initialValue)
163
+ ? validityData.value.initialValue as string[]
164
+ : EMPTY_ARRAY as string[]
165
+ const normalizedNextValue = normalizeSelection(nextValue)
166
+ const normalizedInitialValue = normalizeSelection(initialValue)
167
+
168
+ setFilled(nextValue.length > 0)
169
+ setDirty(!areArraysEqual(normalizedNextValue, normalizedInitialValue))
170
+
171
+ if (shouldValidateOnChange()) {
172
+ void validation.commit(nextValue)
173
+ }
174
+ else {
175
+ void validation.commit(nextValue, true)
176
+ }
177
+ },
178
+ { flush: 'sync' },
179
+ )
180
+
181
+ const state = computed<CheckboxGroupState>(() => ({
182
+ ...fieldState.value,
183
+ disabled: disabled.value,
184
+ }))
185
+
186
+ provide(checkboxGroupContextKey, {
187
+ value,
188
+ defaultValue,
189
+ allValues,
190
+ disabled,
191
+ validation,
192
+ parent,
193
+ setValue,
194
+ registerControlRef,
195
+ })
196
+
197
+ const rootProps = computed(() => mergeProps(
198
+ attrsObject,
199
+ getDescriptionProps(),
200
+ {
201
+ 'role': 'group',
202
+ 'aria-labelledby': labelId.value,
203
+ },
204
+ ))
205
+
206
+ const {
207
+ tag,
208
+ mergedProps,
209
+ renderless,
210
+ ref: renderRef,
211
+ } = useRenderElement({
212
+ componentProps: props,
213
+ state,
214
+ props: rootProps,
215
+ stateAttributesMapping: fieldValidityMapping,
216
+ defaultTagName: 'div',
217
+ })
218
+
219
+ function areArraysEqual(a: readonly string[], b: readonly string[]) {
220
+ if (a === b) {
221
+ return true
222
+ }
223
+
224
+ if (a.length !== b.length) {
225
+ return false
226
+ }
227
+
228
+ return a.every((item, index) => item === b[index])
229
+ }
230
+
231
+ function normalizeSelection(values: readonly string[]) {
232
+ return values.slice().sort()
233
+ }
234
+ </script>
235
+
236
+ <template>
237
+ <slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
238
+ <component :is="tag" v-else :ref="renderRef" v-bind="mergedProps">
239
+ <slot :state="state" />
240
+ </component>
241
+ </template>
@@ -0,0 +1,39 @@
1
+ import type { InjectionKey, Ref } from 'vue'
2
+ import type { UseFieldValidationReturnValue } from '../field/root/useFieldValidation'
3
+ import type { BaseUIChangeEventDetails } from '../utils/createBaseUIEventDetails'
4
+ import type { REASONS } from '../utils/reasons'
5
+ import type { UseCheckboxGroupParentReturnValue } from './useCheckboxGroupParent'
6
+ import { inject } from 'vue'
7
+
8
+ export interface CheckboxGroupContext {
9
+ value: Readonly<Ref<string[]>>
10
+ defaultValue: Readonly<Ref<string[]>>
11
+ allValues: Readonly<Ref<string[] | undefined>>
12
+ disabled: Readonly<Ref<boolean>>
13
+ validation: UseFieldValidationReturnValue
14
+ parent: UseCheckboxGroupParentReturnValue
15
+ setValue: (
16
+ value: string[],
17
+ eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
18
+ ) => void
19
+ registerControlRef: (element: HTMLElement | null) => void
20
+ }
21
+
22
+ export const checkboxGroupContextKey: InjectionKey<CheckboxGroupContext> = Symbol(
23
+ 'CheckboxGroupContext',
24
+ )
25
+
26
+ export function useCheckboxGroupContext(
27
+ optional: true,
28
+ ): CheckboxGroupContext | undefined
29
+ export function useCheckboxGroupContext(optional?: false): CheckboxGroupContext
30
+ export function useCheckboxGroupContext(optional = false) {
31
+ const context = inject(checkboxGroupContextKey, undefined)
32
+ if (!context && !optional) {
33
+ throw new Error(
34
+ 'Base UI Vue: CheckboxGroupContext is missing. CheckboxGroup parts must be placed within <CheckboxGroup>.',
35
+ )
36
+ }
37
+
38
+ return context
39
+ }
@@ -0,0 +1,6 @@
1
+ export enum CheckboxGroupDataAttributes {
2
+ /**
3
+ * Present when the checkbox group is disabled.
4
+ */
5
+ disabled = 'data-disabled',
6
+ }
@@ -0,0 +1,11 @@
1
+ export { default as CheckboxGroup } from './CheckboxGroup.vue'
2
+ export type { CheckboxGroupProps, CheckboxGroupState } from './CheckboxGroup.vue'
3
+ export { checkboxGroupContextKey, useCheckboxGroupContext } from './CheckboxGroupContext'
4
+
5
+ export type { CheckboxGroupContext } from './CheckboxGroupContext'
6
+ export { CheckboxGroupDataAttributes } from './CheckboxGroupDataAttributes'
7
+
8
+ export type {
9
+ UseCheckboxGroupParentParameters,
10
+ UseCheckboxGroupParentReturnValue,
11
+ } from './useCheckboxGroupParent'
@@ -0,0 +1,173 @@
1
+ import type { Ref } from 'vue'
2
+ import type { BaseUIChangeEventDetails } from '../utils/createBaseUIEventDetails'
3
+ import type { REASONS } from '../utils/reasons'
4
+ import { computed, ref, watch } from 'vue'
5
+ import { useBaseUiId } from '../utils/useBaseUiId'
6
+
7
+ export interface UseCheckboxGroupParentParameters {
8
+ allValues: Readonly<Ref<string[] | undefined>>
9
+ value: Readonly<Ref<string[]>>
10
+ onValueChange: (
11
+ value: string[],
12
+ eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
13
+ ) => void
14
+ }
15
+
16
+ export interface UseCheckboxGroupParentReturnValue {
17
+ id: string | undefined
18
+ indeterminate: Readonly<Ref<boolean>>
19
+ disabledStatesRef: Ref<Map<string, boolean>>
20
+ registeredControlIdsRef: Ref<Map<string, string>>
21
+ getParentProps: () => {
22
+ 'id': string | undefined
23
+ 'indeterminate': boolean
24
+ 'checked': boolean
25
+ 'aria-controls': string
26
+ 'onCheckedChange': (
27
+ checked: boolean,
28
+ eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
29
+ ) => void
30
+ }
31
+ getChildProps: (value: string) => {
32
+ checked: boolean
33
+ onCheckedChange: (
34
+ checked: boolean,
35
+ eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
36
+ ) => void
37
+ }
38
+ registerChildControlId: (value: string, id: string | undefined) => void
39
+ }
40
+
41
+ export function useCheckboxGroupParent(
42
+ params: UseCheckboxGroupParentParameters,
43
+ ): UseCheckboxGroupParentReturnValue {
44
+ const { allValues, value, onValueChange } = params
45
+ const resolvedAllValues = computed(() => allValues.value ?? [])
46
+
47
+ const uncontrolledStateRef = ref<string[]>(value.value)
48
+ const disabledStatesRef = ref(new Map<string, boolean>())
49
+ const registeredControlIdsRef = ref(new Map<string, string>())
50
+ const status = ref<'on' | 'off' | 'mixed'>('mixed')
51
+
52
+ const id = useBaseUiId()
53
+ const checked = computed(() => value.value.length === resolvedAllValues.value.length)
54
+ const indeterminate = computed(() =>
55
+ value.value.length !== resolvedAllValues.value.length && value.value.length > 0,
56
+ )
57
+
58
+ watch(
59
+ () => value.value.slice(),
60
+ (nextValue) => {
61
+ if (nextValue.length > 0 && nextValue.length < resolvedAllValues.value.length) {
62
+ uncontrolledStateRef.value = nextValue
63
+ }
64
+ },
65
+ { flush: 'sync' },
66
+ )
67
+
68
+ function getParentProps() {
69
+ return {
70
+ id,
71
+ 'indeterminate': indeterminate.value,
72
+ 'checked': checked.value,
73
+ 'aria-controls': resolvedAllValues.value
74
+ .map(item => registeredControlIdsRef.value.get(item))
75
+ .filter((item): item is string => Boolean(item))
76
+ .join(' '),
77
+ onCheckedChange(
78
+ _nextChecked: boolean,
79
+ eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
80
+ ) {
81
+ const uncontrolledState = uncontrolledStateRef.value
82
+
83
+ const none = resolvedAllValues.value.filter(
84
+ (item: string) =>
85
+ disabledStatesRef.value.get(item) && uncontrolledState.includes(item),
86
+ )
87
+
88
+ const all = resolvedAllValues.value.filter(
89
+ (item: string) =>
90
+ !disabledStatesRef.value.get(item)
91
+ || (disabledStatesRef.value.get(item) && uncontrolledState.includes(item)),
92
+ )
93
+
94
+ const allOnOrOff
95
+ = uncontrolledState.length === all.length || uncontrolledState.length === 0
96
+
97
+ if (allOnOrOff) {
98
+ if (value.value.length === all.length) {
99
+ onValueChange(none, eventDetails)
100
+ }
101
+ else {
102
+ onValueChange(all, eventDetails)
103
+ }
104
+ return
105
+ }
106
+
107
+ if (status.value === 'mixed') {
108
+ onValueChange(all, eventDetails)
109
+ status.value = 'on'
110
+ }
111
+ else if (status.value === 'on') {
112
+ onValueChange(none, eventDetails)
113
+ status.value = 'off'
114
+ }
115
+ else {
116
+ onValueChange(uncontrolledState, eventDetails)
117
+ status.value = 'mixed'
118
+ }
119
+ },
120
+ }
121
+ }
122
+
123
+ function getChildProps(childValue: string) {
124
+ return {
125
+ checked: value.value.includes(childValue),
126
+ onCheckedChange(
127
+ nextChecked: boolean,
128
+ eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
129
+ ) {
130
+ const newValue = value.value.slice()
131
+
132
+ if (nextChecked) {
133
+ if (!newValue.includes(childValue)) {
134
+ newValue.push(childValue)
135
+ }
136
+ }
137
+ else {
138
+ const index = newValue.indexOf(childValue)
139
+ if (index !== -1) {
140
+ newValue.splice(index, 1)
141
+ }
142
+ }
143
+
144
+ uncontrolledStateRef.value = newValue
145
+ onValueChange(newValue, eventDetails)
146
+ status.value = 'mixed'
147
+ },
148
+ }
149
+ }
150
+
151
+ function registerChildControlId(value: string, id: string | undefined) {
152
+ const nextRegisteredControlIds = new Map(registeredControlIdsRef.value)
153
+
154
+ if (id === undefined) {
155
+ nextRegisteredControlIds.delete(value)
156
+ registeredControlIdsRef.value = nextRegisteredControlIds
157
+ return
158
+ }
159
+
160
+ nextRegisteredControlIds.set(value, id)
161
+ registeredControlIdsRef.value = nextRegisteredControlIds
162
+ }
163
+
164
+ return {
165
+ id,
166
+ indeterminate,
167
+ disabledStatesRef,
168
+ registeredControlIdsRef,
169
+ getParentProps,
170
+ getChildProps,
171
+ registerChildControlId,
172
+ }
173
+ }
@@ -3,6 +3,7 @@ import type { Ref } from 'vue'
3
3
  import type { CollapsibleChangeEventDetails } from '../collapsible.types'
4
4
  import type { AnimationType, Dimensions } from '../root/useCollapsibleRoot'
5
5
  import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
6
+ import { AccordionRootDataAttributes } from '../../accordion/root/AccordionRootDataAttributes'
6
7
  import { createChangeEventDetails } from '../../utils/createBaseUIEventDetails'
7
8
  import { REASONS } from '../../utils/reasons'
8
9
  import {
@@ -167,7 +168,7 @@ export function useCollapsiblePanel(
167
168
  * Setting both to `0px` will break layout.
168
169
  */
169
170
  if (
170
- element.getAttribute('data-orientation') === 'horizontal' // TODO: type AccordionRootDataAttributes.orientation
171
+ element.getAttribute(AccordionRootDataAttributes.orientation) === 'horizontal'
171
172
  || panelStyles.transitionProperty.includes('width')
172
173
  ) {
173
174
  transitionDimensionRef.value = 'width'
@@ -109,7 +109,9 @@ export function useCollapsibleRoot(
109
109
 
110
110
  const { value: open, setValue: setOpen } = useControllableState<boolean>({
111
111
  controlled: () => (isControlled.value ? openParam?.() : undefined),
112
- default: defaultOpen,
112
+ default: () => defaultOpen,
113
+ name: 'CollapsibleRoot',
114
+ state: 'open',
113
115
  })
114
116
 
115
117
  const { mounted, setMounted, transitionStatus } = useTransitionStatus(
@@ -25,6 +25,8 @@ export const ARROW_LEFT = 'ArrowLeft'
25
25
  export const ARROW_RIGHT = 'ArrowRight'
26
26
  export const HOME = 'Home'
27
27
  export const END = 'End'
28
+ export const PAGE_UP = 'PageUp'
29
+ export const PAGE_DOWN = 'PageDown'
28
30
 
29
31
  export const HORIZONTAL_KEYS = new Set([ARROW_LEFT, ARROW_RIGHT])
30
32
  export const HORIZONTAL_KEYS_WITH_EXTRA_KEYS = new Set([
@@ -1,8 +1,11 @@
1
1
  <script setup lang="ts" generic="Metadata, State extends Record<string, any> = Record<string, any>">
2
2
  import type { StateAttributesMapping } from '../../utils/getStateAttributesProps'
3
3
  import type { BaseUIComponentProps } from '../../utils/types'
4
+ import type { RenderRef } from '../../utils/useRenderElement'
4
5
  import { computed, useAttrs } from 'vue'
6
+ import { mergeProps } from '../../merge-props/mergeProps'
5
7
  import { EMPTY_OBJECT } from '../../utils/constants'
8
+ import { useMergedRefs } from '../../utils/useMergedRefs'
6
9
  import { useRenderElement } from '../../utils/useRenderElement'
7
10
  import { useCompositeItem } from './useCompositeItem'
8
11
 
@@ -13,7 +16,7 @@ export interface CompositeItemProps<Metadata, State extends Record<string, any>>
13
16
  as?: string | any
14
17
  style?: BaseUIComponentProps<State>['style']
15
18
  metadata?: Metadata
16
- refs?: Array<HTMLElement | null>
19
+ refs?: RenderRef[]
17
20
  props?: Array<Record<string, any> | (() => Record<string, any>)>
18
21
  stateAttributesMapping?: StateAttributesMapping<State>
19
22
  state?: State
@@ -41,15 +44,11 @@ const externalProps = computed(() => {
41
44
  if (props.props) {
42
45
  props.props.forEach((prop) => {
43
46
  const p = typeof prop === 'function' ? prop() : prop
44
- externalProps = { ...externalProps, ...p }
47
+ externalProps = mergeProps(externalProps, p)
45
48
  })
46
49
  }
47
50
 
48
- return {
49
- ...attrs,
50
- ...externalProps,
51
- ...compositeProps.value,
52
- }
51
+ return mergeProps(attrs, externalProps, compositeProps.value)
53
52
  })
54
53
 
55
54
  const {
@@ -63,7 +62,7 @@ const {
63
62
  props: externalProps,
64
63
  stateAttributesMapping: props.stateAttributesMapping,
65
64
  defaultTagName: 'div',
66
- ref: compositeRef,
65
+ ref: props.refs?.length ? useMergedRefs(...props.refs, compositeRef) : compositeRef,
67
66
  })
68
67
  </script>
69
68
 
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import type { StateAttributesMapping } from '../../utils/getStateAttributesProps'
2
3
  import type { BaseUIComponentProps } from '../../utils/types'
3
4
  import type { Dimensions, ModifierKey } from '../composite'
4
5
  import type { CompositeMetadata } from '../list/CompositeList.vue'
@@ -10,6 +11,8 @@ import { compositeRootContextKey } from './CompositeRootContext'
10
11
  import { useCompositeRoot } from './useCompositeRoot'
11
12
 
12
13
  export interface CompositeRootProps extends BaseUIComponentProps<any> {
14
+ state?: Record<string, any>
15
+ stateAttributesMapping?: StateAttributesMapping<any>
13
16
  orientation?: 'horizontal' | 'vertical' | 'both'
14
17
  cols?: number
15
18
  loopFocus?: boolean
@@ -46,6 +49,13 @@ const props = withDefaults(defineProps<CompositeRootProps>(), {
46
49
  })
47
50
 
48
51
  const emit = defineEmits<CompositeRootEmits>()
52
+ defineSlots<{
53
+ default?: (props: {
54
+ ref?: ((el: Element | null) => void)
55
+ props?: Record<string, any>
56
+ state: Record<string, any>
57
+ }) => any
58
+ }>()
49
59
  const attrs = useAttrs()
50
60
  const direction = useDirection()
51
61
 
@@ -77,7 +87,7 @@ provide(
77
87
  }),
78
88
  )
79
89
 
80
- const state = computed(() => ({}))
90
+ const state = computed(() => props.state ?? {})
81
91
 
82
92
  const {
83
93
  tag,
@@ -88,6 +98,7 @@ const {
88
98
  componentProps: props,
89
99
  state,
90
100
  props: computed(() => root.getRootProps(attrs)),
101
+ stateAttributesMapping: props.stateAttributesMapping,
91
102
  defaultTagName: 'div',
92
103
  ref: root.mergedRef,
93
104
  })
@@ -0,0 +1,26 @@
1
+ import type { InjectionKey, Ref } from 'vue'
2
+ import { computed, inject } from 'vue'
3
+
4
+ export interface CSPContextValue {
5
+ nonce: Readonly<Ref<string | undefined>>
6
+ disableStyleElements: Readonly<Ref<boolean>>
7
+ }
8
+
9
+ /**
10
+ * @internal
11
+ */
12
+ export const cspContextKey = Symbol(
13
+ 'CSPContext',
14
+ ) as InjectionKey<CSPContextValue>
15
+
16
+ const DEFAULT_CSP_CONTEXT_VALUE: CSPContextValue = {
17
+ nonce: computed(() => undefined),
18
+ disableStyleElements: computed(() => false),
19
+ }
20
+
21
+ /**
22
+ * @internal
23
+ */
24
+ export function useCSPContext(): CSPContextValue {
25
+ return inject(cspContextKey, DEFAULT_CSP_CONTEXT_VALUE)
26
+ }