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,260 @@
1
+ import type { MaybeRefOrGetter, Ref } from 'vue'
2
+ import { onUnmounted, toValue } from 'vue'
3
+ import { ownerWindow } from './owner'
4
+ import { useInterval } from './useInterval'
5
+ import { useTimeout } from './useTimeout'
6
+
7
+ const DEFAULT_TICK_DELAY = 60
8
+ const DEFAULT_START_DELAY = 400
9
+ const DEFAULT_SCROLL_DISTANCE = 8
10
+ const TOUCH_TIMEOUT = 50
11
+ const MAX_POINTER_MOVES_AFTER_TOUCH = 3
12
+
13
+ // Treat pen as touch-like to avoid forcing the software keyboard on stylus taps.
14
+ function isTouchLikePointerType(pointerType: string) {
15
+ return pointerType === 'touch' || pointerType === 'pen'
16
+ }
17
+
18
+ export interface UsePressAndHoldParameters {
19
+ disabled: MaybeRefOrGetter<boolean>
20
+ readOnly?: MaybeRefOrGetter<boolean>
21
+ /**
22
+ * Called on each tick during a hold. Return `false` to stop the auto-change sequence.
23
+ */
24
+ tick: (triggerEvent?: Event) => boolean
25
+ /**
26
+ * Called when the hold ends via the global `pointerup` event.
27
+ */
28
+ onStop?: (nativeEvent: PointerEvent) => void
29
+ /**
30
+ * Interval between ticks once the hold is active.
31
+ * @default 60
32
+ */
33
+ tickDelay?: number
34
+ /**
35
+ * Delay before the repeating ticks start after the initial hold.
36
+ * @default 400
37
+ */
38
+ startDelay?: number
39
+ /**
40
+ * Pointer movement distance (px) that cancels the hold and is treated as scrolling.
41
+ * @default 8
42
+ */
43
+ scrollDistance?: number
44
+ /**
45
+ * Ref to the anchor element used to resolve `ownerWindow`.
46
+ */
47
+ elementRef: Ref<HTMLElement | null>
48
+ }
49
+
50
+ export interface PressAndHoldPointerHandlers {
51
+ onTouchstart: (event: TouchEvent) => void
52
+ onTouchend: (event: TouchEvent) => void
53
+ onPointerdown: (event: PointerEvent) => void
54
+ onPointerup: (event: PointerEvent) => void
55
+ onPointermove: (event: PointerEvent) => void
56
+ onMouseenter: (event: MouseEvent) => void
57
+ onMouseleave: (event: MouseEvent) => void
58
+ onMouseup: (event: MouseEvent) => void
59
+ }
60
+
61
+ export interface UsePressAndHoldReturnValue {
62
+ pointerHandlers: PressAndHoldPointerHandlers
63
+ /**
64
+ * Returns `true` if the `onClick` handler should be skipped.
65
+ */
66
+ shouldSkipClick: (event: MouseEvent) => boolean
67
+ }
68
+
69
+ /**
70
+ * Adds press-and-hold behavior to a button element.
71
+ * On pointer down, performs one action immediately, then after a delay starts
72
+ * continuous repeated actions at a fixed interval. Handles mouse, touch, and pen inputs.
73
+ */
74
+ export function usePressAndHold(params: UsePressAndHoldParameters): UsePressAndHoldReturnValue {
75
+ const {
76
+ tick,
77
+ onStop,
78
+ tickDelay = DEFAULT_TICK_DELAY,
79
+ startDelay = DEFAULT_START_DELAY,
80
+ scrollDistance = DEFAULT_SCROLL_DISTANCE,
81
+ elementRef,
82
+ } = params
83
+
84
+ const isDisabled = () => toValue(params.disabled)
85
+ const isReadOnly = () => toValue(params.readOnly ?? false)
86
+
87
+ const startTickTimeout = useTimeout()
88
+ const tickInterval = useInterval()
89
+ const intentionalTouchCheckTimeout = useTimeout()
90
+
91
+ let isPressed = false
92
+ let movesAfterTouch = 0
93
+ let downCoords = { x: 0, y: 0 }
94
+ let isTouchingButton = false
95
+ let ignoreClick = false
96
+ let pointerType = ''
97
+ let unsubscribeFromGlobalContextMenu: () => void = () => {}
98
+
99
+ function stopAutoChange() {
100
+ intentionalTouchCheckTimeout.clear()
101
+ startTickTimeout.clear()
102
+ tickInterval.clear()
103
+ unsubscribeFromGlobalContextMenu()
104
+ unsubscribeFromGlobalContextMenu = () => {}
105
+ movesAfterTouch = 0
106
+ }
107
+
108
+ function startAutoChange(triggerNativeEvent?: Event) {
109
+ stopAutoChange()
110
+
111
+ const element = elementRef.value
112
+ if (!element) {
113
+ return
114
+ }
115
+
116
+ const win = ownerWindow(element)
117
+
118
+ function handleContextMenu(event: Event) {
119
+ event.preventDefault()
120
+ }
121
+
122
+ // A global context menu listener prevents the context menu from appearing when
123
+ // the touch is slightly outside of the element's hit area.
124
+ win.addEventListener('contextmenu', handleContextMenu)
125
+ unsubscribeFromGlobalContextMenu = () => {
126
+ win.removeEventListener('contextmenu', handleContextMenu)
127
+ }
128
+
129
+ function handlePointerUp(event: PointerEvent) {
130
+ isPressed = false
131
+ stopAutoChange()
132
+ onStop?.(event)
133
+ }
134
+ win.addEventListener('pointerup', handlePointerUp, { once: true })
135
+
136
+ if (!tick(triggerNativeEvent)) {
137
+ stopAutoChange()
138
+ return
139
+ }
140
+
141
+ startTickTimeout.start(startDelay, () => {
142
+ tickInterval.start(tickDelay, () => {
143
+ if (!tick(triggerNativeEvent)) {
144
+ stopAutoChange()
145
+ }
146
+ })
147
+ })
148
+ }
149
+
150
+ onUnmounted(() => stopAutoChange())
151
+
152
+ const pointerHandlers: PressAndHoldPointerHandlers = {
153
+ onTouchstart() {
154
+ isTouchingButton = true
155
+ },
156
+ onTouchend() {
157
+ isTouchingButton = false
158
+ },
159
+ onPointerdown(event) {
160
+ const isMainButton = !event.button || event.button === 0
161
+ if (event.defaultPrevented || !isMainButton || isDisabled() || isReadOnly()) {
162
+ return
163
+ }
164
+
165
+ pointerType = event.pointerType
166
+ ignoreClick = false
167
+ isPressed = true
168
+ downCoords = { x: event.clientX, y: event.clientY }
169
+
170
+ const isTouchPointer = isTouchLikePointerType(event.pointerType)
171
+
172
+ if (!isTouchPointer) {
173
+ event.preventDefault()
174
+ startAutoChange(event)
175
+ }
176
+ else {
177
+ // Check if the pointerdown was intentional and not the result of a scroll or
178
+ // pinch-zoom. In that case, we don't want to start the auto-change sequence.
179
+ intentionalTouchCheckTimeout.start(TOUCH_TIMEOUT, () => {
180
+ const moves = movesAfterTouch
181
+ movesAfterTouch = 0
182
+ const stillPressed = isPressed
183
+ if (stillPressed && moves < MAX_POINTER_MOVES_AFTER_TOUCH) {
184
+ startAutoChange(event)
185
+ ignoreClick = true
186
+ }
187
+ else {
188
+ ignoreClick = false
189
+ stopAutoChange()
190
+ }
191
+ })
192
+ }
193
+ },
194
+ onPointerup(event) {
195
+ if (isTouchLikePointerType(event.pointerType)) {
196
+ isPressed = false
197
+ }
198
+ },
199
+ onPointermove(event) {
200
+ if (
201
+ isDisabled()
202
+ || isReadOnly()
203
+ || !isTouchLikePointerType(event.pointerType)
204
+ || !isPressed
205
+ ) {
206
+ return
207
+ }
208
+
209
+ movesAfterTouch += 1
210
+
211
+ const { x, y } = downCoords
212
+ const dx = x - event.clientX
213
+ const dy = y - event.clientY
214
+
215
+ if (dx ** 2 + dy ** 2 > scrollDistance ** 2) {
216
+ stopAutoChange()
217
+ }
218
+ },
219
+ onMouseenter(event) {
220
+ if (
221
+ event.defaultPrevented
222
+ || isDisabled()
223
+ || isReadOnly()
224
+ || !isPressed
225
+ || isTouchingButton
226
+ || isTouchLikePointerType(pointerType)
227
+ ) {
228
+ return
229
+ }
230
+
231
+ startAutoChange(event)
232
+ },
233
+ onMouseleave() {
234
+ if (isTouchingButton) {
235
+ return
236
+ }
237
+
238
+ stopAutoChange()
239
+ },
240
+ onMouseup() {
241
+ if (isTouchingButton) {
242
+ return
243
+ }
244
+
245
+ stopAutoChange()
246
+ },
247
+ }
248
+
249
+ function shouldSkipClick(event: MouseEvent): boolean {
250
+ if (event.defaultPrevented) {
251
+ return true
252
+ }
253
+ if (isTouchLikePointerType(pointerType)) {
254
+ return ignoreClick
255
+ }
256
+ return event.detail !== 0
257
+ }
258
+
259
+ return { pointerHandlers, shouldSkipClick }
260
+ }
@@ -0,0 +1,21 @@
1
+ import type { WatchSource } from 'vue'
2
+ import { watch } from 'vue'
3
+
4
+ /**
5
+ * Runs `onChange` whenever the watched value changes, passing the previous value.
6
+ *
7
+ * Mirrors the React `useValueChanged` helper: the callback fires after the DOM has
8
+ * been updated (`flush: 'post'`), which makes it safe to read or move focus inside it.
9
+ */
10
+ export function useValueChanged<T>(
11
+ source: WatchSource<T>,
12
+ onChange: (previousValue: T) => void,
13
+ ): void {
14
+ watch(
15
+ source,
16
+ (_newValue, previousValue) => {
17
+ onChange(previousValue)
18
+ },
19
+ { flush: 'post' },
20
+ )
21
+ }