base-ui-vue 0.2.0 → 0.4.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 (109) hide show
  1. package/dist/button/ToolbarButton.cjs +6 -0
  2. package/dist/button/ToolbarButton.js +1 -1
  3. package/dist/content/ScrollAreaContent.cjs +168 -0
  4. package/dist/content/ScrollAreaContent.cjs.map +1 -0
  5. package/dist/content/ScrollAreaContent.js +133 -0
  6. package/dist/content/ScrollAreaContent.js.map +1 -0
  7. package/dist/control/SliderControl.js +2 -2
  8. package/dist/corner/ScrollAreaCorner.cjs +77 -0
  9. package/dist/corner/ScrollAreaCorner.cjs.map +1 -0
  10. package/dist/corner/ScrollAreaCorner.js +72 -0
  11. package/dist/corner/ScrollAreaCorner.js.map +1 -0
  12. package/dist/decrement/NumberFieldDecrement.cjs +861 -0
  13. package/dist/decrement/NumberFieldDecrement.cjs.map +1 -0
  14. package/dist/decrement/NumberFieldDecrement.js +700 -0
  15. package/dist/decrement/NumberFieldDecrement.js.map +1 -0
  16. package/dist/fallback/AvatarFallback.cjs +2 -46
  17. package/dist/fallback/AvatarFallback.cjs.map +1 -1
  18. package/dist/fallback/AvatarFallback.js +3 -41
  19. package/dist/fallback/AvatarFallback.js.map +1 -1
  20. package/dist/group/NumberFieldGroup.cjs +72 -0
  21. package/dist/group/NumberFieldGroup.cjs.map +1 -0
  22. package/dist/group/NumberFieldGroup.js +67 -0
  23. package/dist/group/NumberFieldGroup.js.map +1 -0
  24. package/dist/increment/NumberFieldIncrement.cjs +112 -0
  25. package/dist/increment/NumberFieldIncrement.cjs.map +1 -0
  26. package/dist/increment/NumberFieldIncrement.js +107 -0
  27. package/dist/increment/NumberFieldIncrement.js.map +1 -0
  28. package/dist/index.cjs +52 -0
  29. package/dist/index.d.cts +1761 -430
  30. package/dist/index.d.cts.map +1 -1
  31. package/dist/index.d.ts +1761 -430
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +7 -2
  34. package/dist/index2.cjs +4065 -60
  35. package/dist/index2.cjs.map +1 -1
  36. package/dist/index2.js +3955 -184
  37. package/dist/index2.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/index.ts +6 -0
  40. package/src/input/Input.vue +37 -0
  41. package/src/input/InputDataAttributes.ts +30 -0
  42. package/src/input/index.ts +4 -0
  43. package/src/meter/index.ts +16 -0
  44. package/src/meter/indicator/MeterIndicator.vue +65 -0
  45. package/src/meter/label/MeterLabel.vue +63 -0
  46. package/src/meter/root/MeterRoot.vue +131 -0
  47. package/src/meter/root/MeterRootContext.ts +41 -0
  48. package/src/meter/track/MeterTrack.vue +46 -0
  49. package/src/meter/value/MeterValue.vue +85 -0
  50. package/src/number-field/decrement/NumberFieldDecrement.vue +109 -0
  51. package/src/number-field/group/NumberFieldGroup.vue +47 -0
  52. package/src/number-field/increment/NumberFieldIncrement.vue +109 -0
  53. package/src/number-field/index.ts +42 -0
  54. package/src/number-field/input/NumberFieldInput.vue +455 -0
  55. package/src/number-field/root/NumberFieldRoot.vue +626 -0
  56. package/src/number-field/root/NumberFieldRootContext.ts +94 -0
  57. package/src/number-field/root/useNumberFieldButton.ts +171 -0
  58. package/src/number-field/scrub-area/NumberFieldScrubArea.vue +359 -0
  59. package/src/number-field/scrub-area/NumberFieldScrubAreaContext.ts +26 -0
  60. package/src/number-field/scrub-area-cursor/NumberFieldScrubAreaCursor.vue +75 -0
  61. package/src/number-field/utils/constants.ts +4 -0
  62. package/src/number-field/utils/getViewportRect.ts +34 -0
  63. package/src/number-field/utils/parse.ts +248 -0
  64. package/src/number-field/utils/stateAttributesMapping.ts +9 -0
  65. package/src/number-field/utils/subscribeToVisualViewportResize.ts +27 -0
  66. package/src/number-field/utils/types.ts +24 -0
  67. package/src/number-field/utils/validate.ts +120 -0
  68. package/src/otp-field/index.ts +22 -0
  69. package/src/otp-field/input/OtpFieldInput.vue +336 -0
  70. package/src/otp-field/root/OtpFieldRoot.vue +583 -0
  71. package/src/otp-field/root/OtpFieldRootContext.ts +81 -0
  72. package/src/otp-field/utils/otp.ts +135 -0
  73. package/src/otp-field/utils/stateAttributesMapping.ts +16 -0
  74. package/src/progress/index.ts +23 -0
  75. package/src/progress/indicator/ProgressIndicator.vue +74 -0
  76. package/src/progress/label/ProgressLabel.vue +63 -0
  77. package/src/progress/root/ProgressRoot.vue +160 -0
  78. package/src/progress/root/ProgressRootContext.ts +51 -0
  79. package/src/progress/root/ProgressRootDataAttributes.ts +14 -0
  80. package/src/progress/root/stateAttributesMapping.ts +18 -0
  81. package/src/progress/track/ProgressTrack.vue +48 -0
  82. package/src/progress/value/ProgressValue.vue +92 -0
  83. package/src/scroll-area/constants.ts +2 -0
  84. package/src/scroll-area/content/ScrollAreaContent.vue +87 -0
  85. package/src/scroll-area/corner/ScrollAreaCorner.vue +64 -0
  86. package/src/scroll-area/index.ts +25 -0
  87. package/src/scroll-area/root/ScrollAreaRoot.vue +297 -0
  88. package/src/scroll-area/root/ScrollAreaRootContext.ts +89 -0
  89. package/src/scroll-area/root/ScrollAreaRootCssVars.ts +4 -0
  90. package/src/scroll-area/root/ScrollAreaRootDataAttributes.ts +9 -0
  91. package/src/scroll-area/root/stateAttributes.ts +14 -0
  92. package/src/scroll-area/scrollbar/ScrollAreaScrollbar.vue +263 -0
  93. package/src/scroll-area/scrollbar/ScrollAreaScrollbarContext.ts +20 -0
  94. package/src/scroll-area/scrollbar/ScrollAreaScrollbarCssVars.ts +4 -0
  95. package/src/scroll-area/scrollbar/ScrollAreaScrollbarDataAttributes.ts +11 -0
  96. package/src/scroll-area/thumb/ScrollAreaThumb.vue +120 -0
  97. package/src/scroll-area/thumb/ScrollAreaThumbDataAttributes.ts +3 -0
  98. package/src/scroll-area/utils/getOffset.ts +34 -0
  99. package/src/scroll-area/viewport/ScrollAreaViewport.vue +379 -0
  100. package/src/scroll-area/viewport/ScrollAreaViewportContext.ts +20 -0
  101. package/src/scroll-area/viewport/ScrollAreaViewportCssVars.ts +6 -0
  102. package/src/scroll-area/viewport/ScrollAreaViewportDataAttributes.ts +9 -0
  103. package/src/utils/detectBrowser.ts +15 -0
  104. package/src/utils/formatNumber.ts +60 -2
  105. package/src/utils/scrollEdges.ts +33 -0
  106. package/src/utils/styles.ts +28 -0
  107. package/src/utils/useInterval.ts +45 -0
  108. package/src/utils/usePressAndHold.ts +260 -0
  109. package/src/utils/useValueChanged.ts +21 -0
@@ -0,0 +1,135 @@
1
+ interface OTPValidationConfig {
2
+ slotPattern: string
3
+ getRootPattern: (length: number) => string
4
+ regexp: RegExp
5
+ inputMode: 'numeric' | 'text'
6
+ }
7
+
8
+ export type OtpValidationType = 'numeric' | 'alpha' | 'alphanumeric' | 'none'
9
+
10
+ const OTP_VALIDATION_CONFIG: Record<Exclude<OtpValidationType, 'none'>, OTPValidationConfig> = {
11
+ numeric: {
12
+ slotPattern: '\\d{1}',
13
+ getRootPattern: length => `\\d{${length}}`,
14
+ regexp: /\D/g,
15
+ inputMode: 'numeric',
16
+ },
17
+ alpha: {
18
+ slotPattern: '[a-z]{1}',
19
+ getRootPattern: length => `[a-z]{${length}}`,
20
+ regexp: /[^a-z]/gi,
21
+ inputMode: 'text',
22
+ },
23
+ alphanumeric: {
24
+ slotPattern: '[a-z0-9]{1}',
25
+ getRootPattern: length => `[a-z0-9]{${length}}`,
26
+ regexp: /[^a-z0-9]/gi,
27
+ inputMode: 'text',
28
+ },
29
+ }
30
+
31
+ export function getOTPValidationConfig(validationType: OtpValidationType) {
32
+ if (validationType === 'none') {
33
+ return null
34
+ }
35
+
36
+ return OTP_VALIDATION_CONFIG[validationType]
37
+ }
38
+
39
+ export function stripOTPWhitespace(value: string | null | undefined) {
40
+ return (value ?? '').replace(/\s/g, '')
41
+ }
42
+
43
+ function applyOTPValidation(value: string, validation: OTPValidationConfig | null) {
44
+ return validation ? value.replace(validation.regexp, '') : value
45
+ }
46
+
47
+ /**
48
+ * Normalizes user-entered OTP text by stripping whitespace, applying validation and custom
49
+ * normalization, and clamping the final value to the configured slot count.
50
+ */
51
+ export function normalizeOTPValueWithDetails(
52
+ value: string | null | undefined,
53
+ length: number,
54
+ validationType: OtpValidationType,
55
+ normalizeValue?: ((value: string) => string) | undefined,
56
+ ): readonly [value: string, didRejectCharacters: boolean] {
57
+ const strippedValue = stripOTPWhitespace(value)
58
+ const validation = getOTPValidationConfig(validationType)
59
+ let normalizedValue = applyOTPValidation(strippedValue, validation)
60
+ let didRejectCharacters = strippedValue.length > normalizedValue.length
61
+
62
+ if (normalizeValue) {
63
+ const customNormalizedValue = normalizeValue(normalizedValue)
64
+ didRejectCharacters ||= normalizedValue.length > customNormalizedValue.length
65
+ normalizedValue = applyOTPValidation(customNormalizedValue, validation)
66
+ didRejectCharacters ||= customNormalizedValue.length > normalizedValue.length
67
+ }
68
+
69
+ // Slice by Unicode code points so multi-byte characters do not split across OTP slots.
70
+ const maxLength = length < 0 ? 0 : length
71
+ const normalizedCharacters = Array.from(normalizedValue)
72
+
73
+ return [
74
+ normalizedCharacters.slice(0, maxLength).join(''),
75
+ didRejectCharacters || normalizedCharacters.length > maxLength,
76
+ ]
77
+ }
78
+
79
+ export function normalizeOTPValue(
80
+ value: string | null | undefined,
81
+ length: number,
82
+ validationType: OtpValidationType,
83
+ normalizeValue?: ((value: string) => string) | undefined,
84
+ ) {
85
+ return normalizeOTPValueWithDetails(value, length, validationType, normalizeValue)[0]
86
+ }
87
+
88
+ export function getOTPCharacters(value: string | null | undefined) {
89
+ return Array.from(value ?? '')
90
+ }
91
+
92
+ export function getOTPValueLength(value: string | null | undefined) {
93
+ return getOTPCharacters(value).length
94
+ }
95
+
96
+ export function getOTPCharacter(value: string | null | undefined, index: number) {
97
+ return getOTPCharacters(value)[index] ?? ''
98
+ }
99
+
100
+ /**
101
+ * Replaces characters starting at the provided slot index, then re-normalizes the final OTP value
102
+ * so paste and multi-character edits stay contiguous and length-bounded.
103
+ */
104
+ export function replaceOTPValue(
105
+ currentValue: string,
106
+ index: number,
107
+ nextValue: string,
108
+ length: number,
109
+ validationType: OtpValidationType,
110
+ normalizeValue?: ((value: string) => string) | undefined,
111
+ ) {
112
+ const normalizedValue = normalizeOTPValue(nextValue, length, validationType, normalizeValue)
113
+ const characters = getOTPCharacters(currentValue)
114
+ const replacementLength = getOTPValueLength(normalizedValue)
115
+ const prefix = characters.slice(0, index).join('')
116
+ const suffix = characters.slice(index + replacementLength).join('')
117
+
118
+ return normalizeOTPValue(
119
+ `${prefix}${normalizedValue}${suffix}`,
120
+ length,
121
+ validationType,
122
+ normalizeValue,
123
+ )
124
+ }
125
+
126
+ export function removeOTPCharacter(currentValue: string, index: number) {
127
+ const characters = getOTPCharacters(currentValue)
128
+
129
+ if (index < 0 || index >= characters.length) {
130
+ return currentValue
131
+ }
132
+
133
+ characters.splice(index, 1)
134
+ return characters.join('')
135
+ }
@@ -0,0 +1,16 @@
1
+ import type { StateAttributesMapping } from '../../utils/getStateAttributesProps'
2
+ import type { OtpFieldInputState } from '../input/OtpFieldInput.vue'
3
+ import type { OtpFieldRootState } from '../root/OtpFieldRoot.vue'
4
+ import { fieldValidityMapping } from '../../field/utils/constants'
5
+
6
+ export const rootStateAttributesMapping: StateAttributesMapping<OtpFieldRootState> = {
7
+ value: () => null,
8
+ length: () => null,
9
+ ...fieldValidityMapping,
10
+ }
11
+
12
+ export const inputStateAttributesMapping: StateAttributesMapping<OtpFieldInputState> = {
13
+ value: () => null,
14
+ index: () => null,
15
+ ...fieldValidityMapping,
16
+ }
@@ -0,0 +1,23 @@
1
+ export { default as ProgressIndicator } from './indicator/ProgressIndicator.vue'
2
+ export type { ProgressIndicatorProps, ProgressIndicatorState } from './indicator/ProgressIndicator.vue'
3
+
4
+ export { default as ProgressLabel } from './label/ProgressLabel.vue'
5
+ export type { ProgressLabelProps, ProgressLabelState } from './label/ProgressLabel.vue'
6
+
7
+ export { default as ProgressRoot } from './root/ProgressRoot.vue'
8
+ export type { ProgressRootProps, ProgressRootState, ProgressStatus } from './root/ProgressRoot.vue'
9
+ export { progressRootContextKey, useProgressRootContext } from './root/ProgressRootContext'
10
+ export type { ProgressRootContext } from './root/ProgressRootContext'
11
+ export { ProgressRootDataAttributes } from './root/ProgressRootDataAttributes'
12
+ export { progressStateAttributesMapping } from './root/stateAttributesMapping'
13
+
14
+ export { default as ProgressTrack } from './track/ProgressTrack.vue'
15
+ export type { ProgressTrackProps, ProgressTrackState } from './track/ProgressTrack.vue'
16
+
17
+ export { default as ProgressValue } from './value/ProgressValue.vue'
18
+ export type {
19
+ ProgressValueProps,
20
+ ProgressValueRenderlessSlotProps,
21
+ ProgressValueSlotProps,
22
+ ProgressValueState,
23
+ } from './value/ProgressValue.vue'
@@ -0,0 +1,74 @@
1
+ <script setup lang="ts">
2
+ import type { BaseUIComponentProps } from '../../utils/types'
3
+ import type { ProgressRootState } from '../root/ProgressRoot.vue'
4
+ import { computed, useAttrs } from 'vue'
5
+ import { mergeProps } from '../../merge-props/mergeProps'
6
+ import { useRenderElement } from '../../utils/useRenderElement'
7
+ import { valueToPercent } from '../../utils/valueToPercent'
8
+ import { useProgressRootContext } from '../root/ProgressRootContext'
9
+ import { progressStateAttributesMapping } from '../root/stateAttributesMapping'
10
+
11
+ export interface ProgressIndicatorState extends ProgressRootState {}
12
+ export interface ProgressIndicatorProps extends BaseUIComponentProps<ProgressIndicatorState> {}
13
+
14
+ /**
15
+ * Visualizes the completion status of the task.
16
+ * Renders a `<div>` element.
17
+ *
18
+ * Documentation: [Base UI Vue Progress](https://baseui-vue.com/docs/components/progress)
19
+ */
20
+ defineOptions({
21
+ name: 'ProgressIndicator',
22
+ inheritAttrs: false,
23
+ })
24
+
25
+ const props = withDefaults(defineProps<ProgressIndicatorProps>(), {
26
+ as: 'div',
27
+ })
28
+
29
+ const attrs = useAttrs()
30
+ const { value, min, max, state } = useProgressRootContext()
31
+
32
+ const percentage = computed(() => {
33
+ const v = value.value
34
+ if (v == null || !Number.isFinite(v)) {
35
+ return null
36
+ }
37
+ return valueToPercent(v, min.value, max.value)
38
+ })
39
+
40
+ const indicatorStyles = computed(() => {
41
+ if (percentage.value == null) {
42
+ return {}
43
+ }
44
+ return {
45
+ insetInlineStart: 0,
46
+ height: 'inherit',
47
+ width: `${percentage.value}%`,
48
+ }
49
+ })
50
+
51
+ const indicatorProps = computed(() => mergeProps(
52
+ { style: indicatorStyles.value },
53
+ attrs as Record<string, unknown>,
54
+ ))
55
+
56
+ const {
57
+ tag,
58
+ mergedProps,
59
+ renderless,
60
+ } = useRenderElement({
61
+ componentProps: props,
62
+ state,
63
+ props: indicatorProps,
64
+ defaultTagName: 'div',
65
+ stateAttributesMapping: progressStateAttributesMapping,
66
+ })
67
+ </script>
68
+
69
+ <template>
70
+ <slot v-if="renderless" :props="mergedProps" :state="state" />
71
+ <component :is="tag" v-else v-bind="mergedProps">
72
+ <slot :state="state" />
73
+ </component>
74
+ </template>
@@ -0,0 +1,63 @@
1
+ <script setup lang="ts">
2
+ import type { BaseUIComponentProps } from '../../utils/types'
3
+ import type { ProgressRootState } from '../root/ProgressRoot.vue'
4
+ import { computed, useAttrs } from 'vue'
5
+ import { useRegisteredLabelId } from '../../utils/useRegisteredLabelId'
6
+ import { useRenderElement } from '../../utils/useRenderElement'
7
+ import { useProgressRootContext } from '../root/ProgressRootContext'
8
+ import { progressStateAttributesMapping } from '../root/stateAttributesMapping'
9
+
10
+ export interface ProgressLabelState extends ProgressRootState {}
11
+ export interface ProgressLabelProps extends BaseUIComponentProps<ProgressLabelState> {
12
+ /**
13
+ * The id of the label element. When provided, it overrides the
14
+ * automatically generated one.
15
+ */
16
+ id?: string
17
+ }
18
+
19
+ /**
20
+ * An accessible label for the progress bar.
21
+ * Renders a `<span>` element.
22
+ *
23
+ * Documentation: [Base UI Vue Progress](https://baseui-vue.com/docs/components/progress)
24
+ */
25
+ defineOptions({
26
+ name: 'ProgressLabel',
27
+ inheritAttrs: false,
28
+ })
29
+
30
+ const props = withDefaults(defineProps<ProgressLabelProps>(), {
31
+ as: 'span',
32
+ })
33
+
34
+ const attrs = useAttrs()
35
+ const { setLabelId, state } = useProgressRootContext()
36
+
37
+ const id = useRegisteredLabelId(() => props.id, setLabelId)
38
+
39
+ const labelProps = computed(() => ({
40
+ ...attrs,
41
+ id: id.value,
42
+ role: 'presentation',
43
+ }))
44
+
45
+ const {
46
+ tag,
47
+ mergedProps,
48
+ renderless,
49
+ } = useRenderElement({
50
+ componentProps: props,
51
+ state,
52
+ props: labelProps,
53
+ defaultTagName: 'span',
54
+ stateAttributesMapping: progressStateAttributesMapping,
55
+ })
56
+ </script>
57
+
58
+ <template>
59
+ <slot v-if="renderless" :props="mergedProps" :state="state" />
60
+ <component :is="tag" v-else v-bind="mergedProps">
61
+ <slot :state="state" />
62
+ </component>
63
+ </template>
@@ -0,0 +1,160 @@
1
+ <script setup lang="ts">
2
+ import type { BaseUIComponentProps } from '../../utils/types'
3
+ import { computed, provide, ref, useAttrs } from 'vue'
4
+ import { formatNumberValue } from '../../utils/formatNumber'
5
+ import { useRenderElement } from '../../utils/useRenderElement'
6
+ import { visuallyHidden } from '../../utils/visuallyHidden'
7
+ import { progressRootContextKey } from './ProgressRootContext'
8
+ import { progressStateAttributesMapping } from './stateAttributesMapping'
9
+
10
+ export type ProgressStatus = 'indeterminate' | 'progressing' | 'complete'
11
+
12
+ export interface ProgressRootState {
13
+ /**
14
+ * The current status.
15
+ */
16
+ status: ProgressStatus
17
+ }
18
+
19
+ export interface ProgressRootProps extends BaseUIComponentProps<ProgressRootState> {
20
+ /**
21
+ * A string value that provides a user-friendly name for `aria-valuenow`.
22
+ * Takes precedence over `getAriaValueText`.
23
+ */
24
+ ariaValuetext?: string
25
+ /**
26
+ * Options to format the value.
27
+ */
28
+ format?: Intl.NumberFormatOptions
29
+ /**
30
+ * A function that returns a string value for `aria-valuetext`.
31
+ * Receives the formatted value (or `null` when indeterminate) and the raw
32
+ * value.
33
+ */
34
+ getAriaValueText?: (formattedValue: string | null, value: number | null) => string
35
+ /**
36
+ * The locale used by `Intl.NumberFormat` when formatting the value.
37
+ * Defaults to the user's runtime locale.
38
+ */
39
+ locale?: Intl.LocalesArgument
40
+ /**
41
+ * The maximum value.
42
+ * @default 100
43
+ */
44
+ max?: number
45
+ /**
46
+ * The minimum value.
47
+ * @default 0
48
+ */
49
+ min?: number
50
+ /**
51
+ * The current value. The component is indeterminate when value is `null`.
52
+ * @default null
53
+ */
54
+ value: number | null
55
+ }
56
+
57
+ /**
58
+ * Groups all parts of the progress bar and provides the task completion
59
+ * status to screen readers.
60
+ * Renders a `<div>` element.
61
+ *
62
+ * Documentation: [Base UI Vue Progress](https://baseui-vue.com/docs/components/progress)
63
+ */
64
+ defineOptions({
65
+ name: 'ProgressRoot',
66
+ inheritAttrs: false,
67
+ })
68
+
69
+ const props = withDefaults(defineProps<ProgressRootProps>(), {
70
+ as: 'div',
71
+ max: 100,
72
+ min: 0,
73
+ })
74
+
75
+ function getDefaultAriaValueText(formattedValue: string | null, value: number | null) {
76
+ if (value == null) {
77
+ return 'indeterminate progress'
78
+ }
79
+
80
+ return formattedValue || `${value}%`
81
+ }
82
+
83
+ const attrs = useAttrs()
84
+
85
+ const labelId = ref<string | undefined>(undefined)
86
+
87
+ const status = computed<ProgressStatus>(() => {
88
+ if (props.value == null || !Number.isFinite(props.value)) {
89
+ return 'indeterminate'
90
+ }
91
+ return props.value === props.max ? 'complete' : 'progressing'
92
+ })
93
+
94
+ const formattedValue = computed(() =>
95
+ formatNumberValue(props.value, props.locale, props.format),
96
+ )
97
+
98
+ const ariaValueText = computed(() => {
99
+ if (props.ariaValuetext !== undefined) {
100
+ return props.ariaValuetext
101
+ }
102
+
103
+ const formatted = props.value == null ? null : formattedValue.value
104
+
105
+ if (props.getAriaValueText) {
106
+ return props.getAriaValueText(formatted, props.value)
107
+ }
108
+
109
+ return getDefaultAriaValueText(formatted, props.value)
110
+ })
111
+
112
+ const state = computed<ProgressRootState>(() => ({
113
+ status: status.value,
114
+ }))
115
+
116
+ provide(progressRootContextKey, {
117
+ formattedValue,
118
+ max: computed(() => props.max),
119
+ min: computed(() => props.min),
120
+ value: computed(() => props.value),
121
+ status,
122
+ state,
123
+ setLabelId(id) {
124
+ labelId.value = id
125
+ },
126
+ })
127
+
128
+ const rootProps = computed(() => ({
129
+ 'role': 'progressbar',
130
+ 'aria-labelledby': labelId.value,
131
+ 'aria-valuemax': props.max,
132
+ 'aria-valuemin': props.min,
133
+ 'aria-valuenow': props.value ?? undefined,
134
+ 'aria-valuetext': ariaValueText.value,
135
+ ...attrs,
136
+ }))
137
+
138
+ const {
139
+ tag,
140
+ mergedProps,
141
+ renderless,
142
+ } = useRenderElement({
143
+ componentProps: props,
144
+ state,
145
+ props: rootProps,
146
+ defaultTagName: 'div',
147
+ stateAttributesMapping: progressStateAttributesMapping,
148
+ })
149
+
150
+ const visuallyHiddenStyle = visuallyHidden
151
+ </script>
152
+
153
+ <template>
154
+ <slot v-if="renderless" :props="mergedProps" :state="state" />
155
+ <component :is="tag" v-else v-bind="mergedProps">
156
+ <slot :state="state" />
157
+ <!-- Force NVDA to read the label https://github.com/mui/base-ui/issues/4184 -->
158
+ <span role="presentation" :style="visuallyHiddenStyle">x</span>
159
+ </component>
160
+ </template>
@@ -0,0 +1,51 @@
1
+ import type { ComputedRef, InjectionKey, Ref } from 'vue'
2
+ import type { ProgressRootState, ProgressStatus } from './ProgressRoot.vue'
3
+ import { inject } from 'vue'
4
+
5
+ export interface ProgressRootContext {
6
+ /**
7
+ * The formatted current value of the progress bar.
8
+ * Empty string when the component is indeterminate.
9
+ */
10
+ formattedValue: Ref<string>
11
+ /**
12
+ * The maximum allowed value of the progress bar.
13
+ */
14
+ max: Ref<number>
15
+ /**
16
+ * The minimum allowed value of the progress bar.
17
+ */
18
+ min: Ref<number>
19
+ /**
20
+ * The raw current value, or `null` when indeterminate.
21
+ */
22
+ value: Ref<number | null>
23
+ /**
24
+ * Derived completion status. Used to produce `data-*` state attributes.
25
+ */
26
+ status: ComputedRef<ProgressStatus>
27
+ /**
28
+ * The state of the root component.
29
+ */
30
+ state: ComputedRef<ProgressRootState>
31
+ /**
32
+ * Registers the DOM id of the `<ProgressLabel>` for `aria-labelledby`.
33
+ */
34
+ setLabelId: (id: string | undefined) => void
35
+ }
36
+
37
+ export const progressRootContextKey: InjectionKey<ProgressRootContext>
38
+ = Symbol('ProgressRootContext')
39
+
40
+ export function useProgressRootContext(optional: true): ProgressRootContext | undefined
41
+ export function useProgressRootContext(optional?: false): ProgressRootContext
42
+ export function useProgressRootContext(optional = false) {
43
+ const context = inject(progressRootContextKey, undefined)
44
+ if (!context && !optional) {
45
+ throw new Error(
46
+ 'Base UI Vue: ProgressRootContext is missing. Progress parts must be placed within <ProgressRoot>.',
47
+ )
48
+ }
49
+
50
+ return context
51
+ }
@@ -0,0 +1,14 @@
1
+ export enum ProgressRootDataAttributes {
2
+ /**
3
+ * Present when the progress has completed.
4
+ */
5
+ complete = 'data-complete',
6
+ /**
7
+ * Present when the progress is in indeterminate state.
8
+ */
9
+ indeterminate = 'data-indeterminate',
10
+ /**
11
+ * Present while the progress is progressing.
12
+ */
13
+ progressing = 'data-progressing',
14
+ }
@@ -0,0 +1,18 @@
1
+ import type { StateAttributesMapping } from '../../utils/getStateAttributesProps'
2
+ import type { ProgressRootState } from './ProgressRoot.vue'
3
+ import { ProgressRootDataAttributes } from './ProgressRootDataAttributes'
4
+
5
+ export const progressStateAttributesMapping: StateAttributesMapping<ProgressRootState> = {
6
+ status(value): Record<string, string> | null {
7
+ if (value === 'progressing') {
8
+ return { [ProgressRootDataAttributes.progressing]: '' }
9
+ }
10
+ if (value === 'complete') {
11
+ return { [ProgressRootDataAttributes.complete]: '' }
12
+ }
13
+ if (value === 'indeterminate') {
14
+ return { [ProgressRootDataAttributes.indeterminate]: '' }
15
+ }
16
+ return null
17
+ },
18
+ }
@@ -0,0 +1,48 @@
1
+ <script setup lang="ts">
2
+ import type { BaseUIComponentProps } from '../../utils/types'
3
+ import type { ProgressRootState } from '../root/ProgressRoot.vue'
4
+ import { useAttrs } from 'vue'
5
+ import { useRenderElement } from '../../utils/useRenderElement'
6
+ import { useProgressRootContext } from '../root/ProgressRootContext'
7
+ import { progressStateAttributesMapping } from '../root/stateAttributesMapping'
8
+
9
+ export interface ProgressTrackState extends ProgressRootState {}
10
+ export interface ProgressTrackProps extends BaseUIComponentProps<ProgressTrackState> {}
11
+
12
+ /**
13
+ * Contains the progress bar indicator.
14
+ * Renders a `<div>` element.
15
+ *
16
+ * Documentation: [Base UI Vue Progress](https://baseui-vue.com/docs/components/progress)
17
+ */
18
+ defineOptions({
19
+ name: 'ProgressTrack',
20
+ inheritAttrs: false,
21
+ })
22
+
23
+ const props = withDefaults(defineProps<ProgressTrackProps>(), {
24
+ as: 'div',
25
+ })
26
+
27
+ const attrs = useAttrs()
28
+ const { state } = useProgressRootContext()
29
+
30
+ const {
31
+ tag,
32
+ mergedProps,
33
+ renderless,
34
+ } = useRenderElement({
35
+ componentProps: props,
36
+ state,
37
+ props: attrs,
38
+ defaultTagName: 'div',
39
+ stateAttributesMapping: progressStateAttributesMapping,
40
+ })
41
+ </script>
42
+
43
+ <template>
44
+ <slot v-if="renderless" :props="mergedProps" :state="state" />
45
+ <component :is="tag" v-else v-bind="mergedProps">
46
+ <slot :state="state" />
47
+ </component>
48
+ </template>