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,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,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>
|