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,211 @@
1
+ <script setup lang="ts" generic="Value extends string = string">
2
+ import type { BaseUIChangeEventDetails } from '../utils/createBaseUIEventDetails'
3
+ import type { BaseUIComponentProps, NativeButtonProps } from '../utils/types'
4
+ import { computed, getCurrentInstance, useAttrs, watchEffect } from 'vue'
5
+ import CompositeItem from '../composite/item/CompositeItem.vue'
6
+ import { mergeProps } from '../merge-props'
7
+ import { useToggleGroupContext } from '../toggle-group/ToggleGroupContext'
8
+ import { useButton } from '../use-button'
9
+ import { createChangeEventDetails } from '../utils/createBaseUIEventDetails'
10
+ import { error } from '../utils/error'
11
+ import { REASONS } from '../utils/reasons'
12
+ import { useBaseUiId } from '../utils/useBaseUiId'
13
+ import { useControllableState } from '../utils/useControllableState'
14
+ import { useRenderElement } from '../utils/useRenderElement'
15
+
16
+ export interface ToggleState {
17
+ /**
18
+ * Whether the toggle is currently pressed.
19
+ */
20
+ pressed: boolean
21
+ /**
22
+ * Whether the toggle should ignore user interaction.
23
+ */
24
+ disabled: boolean
25
+ }
26
+
27
+ export interface ToggleProps<Value extends string = string>
28
+ extends NativeButtonProps, BaseUIComponentProps<ToggleState> {
29
+ /**
30
+ * Whether the toggle button is currently pressed.
31
+ * This is the controlled counterpart of `defaultPressed`.
32
+ */
33
+ pressed?: boolean
34
+ /**
35
+ * Whether the toggle button is initially pressed.
36
+ * This is the uncontrolled counterpart of `pressed`.
37
+ * @default false
38
+ */
39
+ defaultPressed?: boolean
40
+ /**
41
+ * Whether the component should ignore user interaction.
42
+ * @default false
43
+ */
44
+ disabled?: boolean
45
+ /**
46
+ * A unique string that identifies the toggle when used inside a toggle group.
47
+ */
48
+ value?: Value | undefined
49
+ }
50
+
51
+ /**
52
+ * A two-state button that can be on or off.
53
+ * Renders a `<button>` element.
54
+ *
55
+ * Documentation: [Base UI Vue Toggle](https://baseui-vue.com/docs/components/toggle)
56
+ */
57
+ defineOptions({
58
+ name: 'Toggle',
59
+ inheritAttrs: false,
60
+ })
61
+
62
+ const props = withDefaults(defineProps<ToggleProps<Value>>(), {
63
+ as: 'button',
64
+ defaultPressed: false,
65
+ disabled: false,
66
+ nativeButton: true,
67
+ })
68
+
69
+ const emit = defineEmits<{
70
+ /**
71
+ * Event handler called when the pressed state changes.
72
+ */
73
+ pressedChange: [
74
+ pressed: boolean,
75
+ eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
76
+ ]
77
+ }>()
78
+
79
+ const attrs = useAttrs()
80
+ const instance = getCurrentInstance()
81
+ const groupContext = useToggleGroupContext<Value>(true)
82
+
83
+ const generatedValue = useBaseUiId()
84
+ const toggleValue = computed(
85
+ () => (props.value || generatedValue) as Value | undefined,
86
+ )
87
+ const groupValue = computed(() => groupContext?.value.value ?? [])
88
+
89
+ const disabled = computed(
90
+ () => props.disabled || (groupContext?.disabled.value ?? false),
91
+ )
92
+
93
+ if (process.env.NODE_ENV !== 'production') {
94
+ watchEffect(() => {
95
+ if (
96
+ groupContext
97
+ && props.value === undefined
98
+ && groupContext.isValueInitialized.value
99
+ ) {
100
+ error(
101
+ 'A `<Toggle>` component rendered in a `<ToggleGroup>` has no explicit `value` prop.',
102
+ 'This will cause issues between the Toggle Group and Toggle values.',
103
+ 'Provide the `<Toggle>` with a `value` prop matching the `<ToggleGroup>` values prop type.',
104
+ )
105
+ }
106
+ })
107
+ }
108
+
109
+ const hasPressedProp = computed(() =>
110
+ Boolean(instance?.vnode.props && Object.prototype.hasOwnProperty.call(instance.vnode.props, 'pressed')),
111
+ )
112
+
113
+ const { value: pressed, setValue: setPressedState } = useControllableState<boolean>({
114
+ controlled: () => {
115
+ if (groupContext) {
116
+ return toggleValue.value !== undefined && groupValue.value.includes(toggleValue.value)
117
+ }
118
+
119
+ return hasPressedProp.value ? props.pressed : undefined
120
+ },
121
+ default: () => props.defaultPressed,
122
+ name: 'Toggle',
123
+ state: 'pressed',
124
+ })
125
+
126
+ function emitPressedChange(
127
+ nextPressed: boolean,
128
+ eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
129
+ ) {
130
+ if (toggleValue.value !== undefined) {
131
+ groupContext?.setGroupValue(toggleValue.value, nextPressed, eventDetails)
132
+ }
133
+
134
+ emit('pressedChange', nextPressed, eventDetails)
135
+ }
136
+
137
+ const { getButtonProps, buttonRef } = useButton({
138
+ disabled,
139
+ native: () => props.nativeButton ?? true,
140
+ })
141
+
142
+ const state = computed<ToggleState>(() => ({
143
+ disabled: disabled.value,
144
+ pressed: pressed.value,
145
+ }))
146
+
147
+ const buttonAttrs = computed(() => {
148
+ const { form: _form, type: _type, ...rest } = attrs as Record<string, any>
149
+ return rest
150
+ })
151
+
152
+ function createToggleButtonProps() {
153
+ return mergeProps(
154
+ getButtonProps(mergeProps(
155
+ buttonAttrs.value,
156
+ {
157
+ 'aria-pressed': pressed.value,
158
+ onClick(event: MouseEvent) {
159
+ const nextPressed = !pressed.value
160
+ const details = createChangeEventDetails(REASONS.none, event)
161
+
162
+ emitPressedChange(nextPressed, details)
163
+
164
+ if (details.isCanceled) {
165
+ return
166
+ }
167
+
168
+ setPressedState(nextPressed)
169
+ },
170
+ },
171
+ )),
172
+ groupContext
173
+ ? { 'aria-disabled': disabled.value ? 'true' : 'false' }
174
+ : undefined,
175
+ )
176
+ }
177
+
178
+ const {
179
+ tag,
180
+ mergedProps,
181
+ renderless,
182
+ ref: renderRef,
183
+ } = useRenderElement({
184
+ componentProps: props,
185
+ state,
186
+ props: computed(() => createToggleButtonProps()),
187
+ defaultTagName: 'button',
188
+ ref: buttonRef,
189
+ })
190
+
191
+ const itemRefs = [buttonRef]
192
+ </script>
193
+
194
+ <template>
195
+ <CompositeItem
196
+ v-if="groupContext"
197
+ v-slot="slotProps"
198
+ :as="as"
199
+ :class="props.class"
200
+ :style="props.style"
201
+ :state="state"
202
+ :refs="itemRefs"
203
+ :props="[() => createToggleButtonProps()]"
204
+ >
205
+ <slot v-bind="slotProps" />
206
+ </CompositeItem>
207
+ <slot v-else-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
208
+ <component :is="tag" v-else :ref="renderRef" v-bind="mergedProps">
209
+ <slot :state="state" />
210
+ </component>
211
+ </template>
@@ -0,0 +1,6 @@
1
+ export enum ToggleDataAttributes {
2
+ /**
3
+ * Present when the toggle button is pressed.
4
+ */
5
+ pressed = 'data-pressed',
6
+ }
@@ -0,0 +1,3 @@
1
+ export { default as Toggle } from './Toggle.vue'
2
+ export type { ToggleProps, ToggleState } from './Toggle.vue'
3
+ export { ToggleDataAttributes } from './ToggleDataAttributes'
@@ -0,0 +1,224 @@
1
+ <script setup lang="ts" generic="Value extends string = string">
2
+ import type { BaseUIChangeEventDetails } from '../utils/createBaseUIEventDetails'
3
+ import type { StateAttributesMapping } from '../utils/getStateAttributesProps'
4
+ import type { REASONS } from '../utils/reasons'
5
+ import type { BaseUIComponentProps, HTMLProps, Orientation } from '../utils/types'
6
+ import { computed, provide, useAttrs } from 'vue'
7
+ import CompositeRoot from '../composite/root/CompositeRoot.vue'
8
+ import { useToolbarRootContext } from '../toolbar/root/ToolbarRootContext'
9
+ import { useControllableState } from '../utils/useControllableState'
10
+ import { useRenderElement } from '../utils/useRenderElement'
11
+ import { toggleGroupContextKey } from './ToggleGroupContext'
12
+ import { ToggleGroupDataAttributes } from './ToggleGroupDataAttributes'
13
+
14
+ export interface ToggleGroupState {
15
+ /**
16
+ * Whether the component should ignore user interaction.
17
+ */
18
+ disabled: boolean
19
+ /**
20
+ * When `false` only one item in the group can be pressed. If any item in
21
+ * the group becomes pressed, the others will become unpressed.
22
+ * When `true` multiple items can be pressed.
23
+ * @default false
24
+ */
25
+ multiple: boolean
26
+ /**
27
+ * The orientation of the toggle group.
28
+ */
29
+ orientation: Orientation
30
+ }
31
+
32
+ export interface ToggleGroupProps<Value extends string = string>
33
+ extends BaseUIComponentProps<ToggleGroupState> {
34
+ /**
35
+ * The pressed values of the toggle group.
36
+ * This is the controlled counterpart of `defaultValue`.
37
+ */
38
+ value?: Value[]
39
+ /**
40
+ * The pressed values of the toggle group.
41
+ * This is the uncontrolled counterpart of `value`.
42
+ */
43
+ defaultValue?: Value[]
44
+ /**
45
+ * Whether the toggle group should ignore user interaction.
46
+ * @default false
47
+ */
48
+ disabled?: boolean
49
+ /**
50
+ * The orientation of the toggle group.
51
+ * @default 'horizontal'
52
+ */
53
+ orientation?: Orientation
54
+ /**
55
+ * Whether to loop keyboard focus back to the first item when the end of the list is reached.
56
+ * @default true
57
+ */
58
+ loopFocus?: boolean
59
+ /**
60
+ * When `false` only one item in the group can be pressed. If any item in
61
+ * the group becomes pressed, the others will become unpressed.
62
+ * When `true` multiple items can be pressed.
63
+ * @default false
64
+ */
65
+ multiple?: boolean
66
+ }
67
+
68
+ /**
69
+ * Provides a shared state to a series of toggle buttons.
70
+ *
71
+ * Documentation: [Base UI Vue Toggle Group](https://baseui-vue.com/docs/components/toggle-group)
72
+ */
73
+ defineOptions({
74
+ name: 'ToggleGroup',
75
+ inheritAttrs: false,
76
+ })
77
+
78
+ const props = withDefaults(defineProps<ToggleGroupProps<Value>>(), {
79
+ as: 'div',
80
+ disabled: false,
81
+ orientation: 'horizontal',
82
+ loopFocus: true,
83
+ multiple: false,
84
+ })
85
+
86
+ const emit = defineEmits<{
87
+ /**
88
+ * Event handler called when the group value changes.
89
+ */
90
+ valueChange: [
91
+ value: Value[],
92
+ eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
93
+ ]
94
+ }>()
95
+
96
+ const attrs = useAttrs()
97
+ const attrsObject = attrs as Record<string, unknown>
98
+ const toolbarContext = useToolbarRootContext(true)
99
+
100
+ const disabled = computed(
101
+ () => (toolbarContext?.disabled.value ?? false) || props.disabled,
102
+ )
103
+
104
+ const isValueInitialized = computed(
105
+ () => props.value !== undefined || props.defaultValue !== undefined,
106
+ )
107
+
108
+ const { value: groupValue, setValue: setValueState } = useControllableState<Value[]>({
109
+ controlled: () => props.value,
110
+ default: () => (props.value === undefined ? (props.defaultValue ?? []) : []),
111
+ name: 'ToggleGroup',
112
+ })
113
+
114
+ function setGroupValue(
115
+ newValue: Value,
116
+ nextPressed: boolean,
117
+ eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
118
+ ) {
119
+ let newGroupValue: Value[]
120
+
121
+ if (props.multiple) {
122
+ newGroupValue = groupValue.value.slice()
123
+
124
+ if (nextPressed) {
125
+ newGroupValue.push(newValue)
126
+ }
127
+ else {
128
+ const valueIndex = groupValue.value.indexOf(newValue)
129
+
130
+ if (valueIndex >= 0) {
131
+ newGroupValue.splice(valueIndex, 1)
132
+ }
133
+ }
134
+ }
135
+ else {
136
+ newGroupValue = nextPressed ? [newValue] : []
137
+ }
138
+
139
+ emit('valueChange', newGroupValue, eventDetails)
140
+
141
+ if (eventDetails.isCanceled) {
142
+ return
143
+ }
144
+
145
+ setValueState(newGroupValue)
146
+ }
147
+
148
+ provide(toggleGroupContextKey, {
149
+ disabled,
150
+ orientation: computed(() => props.orientation),
151
+ setGroupValue,
152
+ value: groupValue,
153
+ isValueInitialized,
154
+ })
155
+
156
+ const state = computed<ToggleGroupState>(() => ({
157
+ disabled: disabled.value,
158
+ multiple: props.multiple,
159
+ orientation: props.orientation,
160
+ }))
161
+
162
+ const stateAttributesMapping: StateAttributesMapping<ToggleGroupState> = {
163
+ multiple(value) {
164
+ if (value) {
165
+ return { [ToggleGroupDataAttributes.multiple]: '' }
166
+ }
167
+
168
+ return null
169
+ },
170
+ }
171
+
172
+ const forwardedAttrs = computed(() => {
173
+ const { class: _class, style: _style, ...rest } = attrsObject
174
+ return rest
175
+ })
176
+
177
+ const rootProps = computed<HTMLProps>(() => ({
178
+ 'role': 'group',
179
+ ...forwardedAttrs.value,
180
+ 'aria-orientation': props.orientation,
181
+ }))
182
+
183
+ const {
184
+ tag,
185
+ mergedProps,
186
+ renderless,
187
+ ref: renderRef,
188
+ } = useRenderElement({
189
+ componentProps: props,
190
+ state,
191
+ props: rootProps,
192
+ stateAttributesMapping,
193
+ defaultTagName: 'div',
194
+ })
195
+ </script>
196
+
197
+ <template>
198
+ <template v-if="toolbarContext">
199
+ <slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
200
+ <component :is="tag" v-else :ref="renderRef" v-bind="mergedProps">
201
+ <slot :state="state" />
202
+ </component>
203
+ </template>
204
+ <CompositeRoot
205
+ v-else
206
+ :as="as"
207
+ :class="props.class"
208
+ :style="props.style"
209
+ :orientation="props.orientation"
210
+ :state="state"
211
+ :state-attributes-mapping="stateAttributesMapping"
212
+ :loop-focus="props.loopFocus"
213
+ :enable-home-and-end-keys="true"
214
+ role="group"
215
+ v-bind="forwardedAttrs"
216
+ >
217
+ <template v-if="renderless" #default="{ ref, props: compositeProps, state: compositeState }">
218
+ <slot :ref="ref" :props="compositeProps" :state="compositeState" />
219
+ </template>
220
+ <template v-else #default="{ state: compositeState }">
221
+ <slot :state="compositeState" />
222
+ </template>
223
+ </CompositeRoot>
224
+ </template>
@@ -0,0 +1,45 @@
1
+ import type { InjectionKey, Ref } from 'vue'
2
+ import type { BaseUIChangeEventDetails } from '../utils/createBaseUIEventDetails'
3
+ import type { REASONS } from '../utils/reasons'
4
+ import type { Orientation } from '../utils/types'
5
+ import { inject } from 'vue'
6
+
7
+ export interface ToggleGroupContext<Value> {
8
+ value: Readonly<Ref<Value[]>>
9
+ setGroupValue: (
10
+ newValue: Value,
11
+ nextPressed: boolean,
12
+ eventDetails: BaseUIChangeEventDetails<typeof REASONS.none>,
13
+ ) => void
14
+ disabled: Readonly<Ref<boolean>>
15
+ orientation: Readonly<Ref<Orientation>>
16
+ /**
17
+ * Indicates whether the value has been initialized via `value` or `defaultValue` props.
18
+ * Used to determine if Toggle should warn users about data inconsistency problems.
19
+ */
20
+ isValueInitialized: Readonly<Ref<boolean>>
21
+ }
22
+
23
+ export const toggleGroupContextKey: InjectionKey<ToggleGroupContext<any>>
24
+ = Symbol('ToggleGroupContext')
25
+
26
+ export function useToggleGroupContext<Value>(
27
+ optional: true,
28
+ ): ToggleGroupContext<Value> | undefined
29
+ export function useToggleGroupContext<Value>(
30
+ optional?: false,
31
+ ): ToggleGroupContext<Value>
32
+ export function useToggleGroupContext<Value>(optional = false) {
33
+ const context = inject<ToggleGroupContext<Value> | undefined>(
34
+ toggleGroupContextKey,
35
+ undefined,
36
+ )
37
+
38
+ if (context === undefined && !optional) {
39
+ throw new Error(
40
+ 'Base UI Vue: ToggleGroupContext is missing. ToggleGroup parts must be placed within <ToggleGroup>.',
41
+ )
42
+ }
43
+
44
+ return context
45
+ }
@@ -0,0 +1,15 @@
1
+ export enum ToggleGroupDataAttributes {
2
+ /**
3
+ * Present when the toggle group is disabled.
4
+ */
5
+ disabled = 'data-disabled',
6
+ /**
7
+ * Indicates the orientation of the toggle group.
8
+ * @type {'horizontal' | 'vertical'}
9
+ */
10
+ orientation = 'data-orientation',
11
+ /**
12
+ * Present when the toggle group allows multiple buttons to be in the pressed state at the same time.
13
+ */
14
+ multiple = 'data-multiple',
15
+ }
@@ -0,0 +1,5 @@
1
+ export { default as ToggleGroup } from './ToggleGroup.vue'
2
+ export type { ToggleGroupProps, ToggleGroupState } from './ToggleGroup.vue'
3
+ export { toggleGroupContextKey, useToggleGroupContext } from './ToggleGroupContext'
4
+ export type { ToggleGroupContext } from './ToggleGroupContext'
5
+ export { ToggleGroupDataAttributes } from './ToggleGroupDataAttributes'
@@ -0,0 +1,99 @@
1
+ <script setup lang="ts">
2
+ import type { BaseUIComponentProps, NativeButtonProps } from '../../utils/types'
3
+ import type { ToolbarRootState } from '../root/ToolbarRoot.vue'
4
+ import { computed, useAttrs } from 'vue'
5
+ import CompositeItem from '../../composite/item/CompositeItem.vue'
6
+ import { useButton } from '../../use-button'
7
+ import { useToolbarGroupContext } from '../group/ToolbarGroupContext'
8
+ import { useToolbarRootContext } from '../root/ToolbarRootContext'
9
+
10
+ export interface ToolbarButtonState extends ToolbarRootState {
11
+ /**
12
+ * Whether the component is disabled.
13
+ */
14
+ disabled: boolean
15
+ /**
16
+ * Whether the component remains focusable when disabled.
17
+ */
18
+ focusable: boolean
19
+ }
20
+
21
+ export interface ToolbarButtonProps
22
+ extends NativeButtonProps,
23
+ BaseUIComponentProps<ToolbarButtonState> {
24
+ /**
25
+ * Whether the component is disabled.
26
+ * @default false
27
+ */
28
+ disabled?: boolean
29
+ /**
30
+ * Whether the component remains focusable when disabled.
31
+ * @default true
32
+ */
33
+ focusableWhenDisabled?: boolean
34
+ }
35
+
36
+ /**
37
+ * A button that can be used as-is or as a trigger for other components.
38
+ * Renders a `<button>` element.
39
+ *
40
+ * Documentation: [Base UI Vue Toolbar](https://baseui-vue.com/docs/components/toolbar)
41
+ */
42
+ defineOptions({
43
+ name: 'ToolbarButton',
44
+ inheritAttrs: false,
45
+ })
46
+
47
+ const props = withDefaults(defineProps<ToolbarButtonProps>(), {
48
+ as: 'button',
49
+ disabled: false,
50
+ focusableWhenDisabled: true,
51
+ nativeButton: true,
52
+ })
53
+
54
+ const attrs = useAttrs()
55
+ const toolbarRootContext = useToolbarRootContext()
56
+ const toolbarGroupContext = useToolbarGroupContext(true)
57
+
58
+ const disabled = computed(() =>
59
+ toolbarRootContext.disabled.value
60
+ || (toolbarGroupContext?.disabled.value ?? false)
61
+ || props.disabled,
62
+ )
63
+
64
+ const { getButtonProps, buttonRef } = useButton({
65
+ disabled,
66
+ focusableWhenDisabled: () => props.focusableWhenDisabled,
67
+ native: () => props.nativeButton ?? true,
68
+ })
69
+
70
+ const state = computed<ToolbarButtonState>(() => ({
71
+ disabled: disabled.value,
72
+ orientation: toolbarRootContext.orientation.value,
73
+ focusable: props.focusableWhenDisabled,
74
+ }))
75
+
76
+ const itemRefs = [buttonRef]
77
+ const componentProps = computed(() =>
78
+ typeof props.as === 'string'
79
+ ? {}
80
+ : { disabled: disabled.value },
81
+ )
82
+ </script>
83
+
84
+ <template>
85
+ <CompositeItem
86
+ :as="as"
87
+ :class="props.class"
88
+ :style="props.style"
89
+ :metadata="{ focusableWhenDisabled }"
90
+ :state="state"
91
+ :refs="itemRefs"
92
+ :props="[
93
+ componentProps,
94
+ () => getButtonProps(attrs as Record<string, any>),
95
+ ]"
96
+ >
97
+ <slot :state="state" />
98
+ </CompositeItem>
99
+ </template>
@@ -0,0 +1,15 @@
1
+ export enum ToolbarButtonDataAttributes {
2
+ /**
3
+ * Present when the button is disabled.
4
+ */
5
+ disabled = 'data-disabled',
6
+ /**
7
+ * Indicates the orientation of the toolbar.
8
+ * @type {'horizontal' | 'vertical'}
9
+ */
10
+ orientation = 'data-orientation',
11
+ /**
12
+ * Present when the button remains focusable when disabled.
13
+ */
14
+ focusable = 'data-focusable',
15
+ }
@@ -0,0 +1,70 @@
1
+ <script setup lang="ts">
2
+ import type { BaseUIComponentProps } from '../../utils/types'
3
+ import type { ToolbarRootState } from '../root/ToolbarRoot.vue'
4
+ import { computed, provide, useAttrs } from 'vue'
5
+ import { useRenderElement } from '../../utils/useRenderElement'
6
+ import { useToolbarRootContext } from '../root/ToolbarRootContext'
7
+ import { toolbarGroupContextKey } from './ToolbarGroupContext'
8
+
9
+ export interface ToolbarGroupState extends ToolbarRootState {}
10
+
11
+ export interface ToolbarGroupProps extends BaseUIComponentProps<ToolbarGroupState> {
12
+ /**
13
+ * Whether all toolbar items in the group are disabled.
14
+ * @default false
15
+ */
16
+ disabled?: boolean
17
+ }
18
+
19
+ /**
20
+ * Groups several toolbar items or toggles.
21
+ * Renders a `<div>` element.
22
+ *
23
+ * Documentation: [Base UI Vue Toolbar](https://baseui-vue.com/docs/components/toolbar)
24
+ */
25
+ defineOptions({
26
+ name: 'ToolbarGroup',
27
+ inheritAttrs: false,
28
+ })
29
+
30
+ const props = withDefaults(defineProps<ToolbarGroupProps>(), {
31
+ as: 'div',
32
+ disabled: false,
33
+ })
34
+
35
+ const attrs = useAttrs()
36
+ const toolbarRootContext = useToolbarRootContext()
37
+
38
+ const disabled = computed(() => toolbarRootContext.disabled.value || props.disabled)
39
+
40
+ provide(toolbarGroupContextKey, {
41
+ disabled,
42
+ })
43
+
44
+ const state = computed<ToolbarGroupState>(() => ({
45
+ disabled: disabled.value,
46
+ orientation: toolbarRootContext.orientation.value,
47
+ }))
48
+
49
+ const {
50
+ tag,
51
+ mergedProps,
52
+ renderless,
53
+ ref: renderRef,
54
+ } = useRenderElement({
55
+ componentProps: props,
56
+ state,
57
+ props: computed(() => ({
58
+ ...attrs,
59
+ role: 'group',
60
+ })),
61
+ defaultTagName: 'div',
62
+ })
63
+ </script>
64
+
65
+ <template>
66
+ <slot v-if="renderless" :ref="renderRef" :props="mergedProps" :state="state" />
67
+ <component :is="tag" v-else :ref="renderRef" v-bind="mergedProps">
68
+ <slot :state="state" />
69
+ </component>
70
+ </template>