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.
- package/dist/button/ToolbarButton.cjs +6 -0
- package/dist/button/ToolbarButton.js +1 -1
- package/dist/content/ScrollAreaContent.cjs +168 -0
- package/dist/content/ScrollAreaContent.cjs.map +1 -0
- package/dist/content/ScrollAreaContent.js +133 -0
- package/dist/content/ScrollAreaContent.js.map +1 -0
- package/dist/control/SliderControl.js +2 -2
- package/dist/corner/ScrollAreaCorner.cjs +77 -0
- package/dist/corner/ScrollAreaCorner.cjs.map +1 -0
- package/dist/corner/ScrollAreaCorner.js +72 -0
- package/dist/corner/ScrollAreaCorner.js.map +1 -0
- package/dist/decrement/NumberFieldDecrement.cjs +861 -0
- package/dist/decrement/NumberFieldDecrement.cjs.map +1 -0
- package/dist/decrement/NumberFieldDecrement.js +700 -0
- package/dist/decrement/NumberFieldDecrement.js.map +1 -0
- package/dist/fallback/AvatarFallback.cjs +2 -46
- package/dist/fallback/AvatarFallback.cjs.map +1 -1
- package/dist/fallback/AvatarFallback.js +3 -41
- package/dist/fallback/AvatarFallback.js.map +1 -1
- package/dist/group/NumberFieldGroup.cjs +72 -0
- package/dist/group/NumberFieldGroup.cjs.map +1 -0
- package/dist/group/NumberFieldGroup.js +67 -0
- package/dist/group/NumberFieldGroup.js.map +1 -0
- package/dist/increment/NumberFieldIncrement.cjs +112 -0
- package/dist/increment/NumberFieldIncrement.cjs.map +1 -0
- package/dist/increment/NumberFieldIncrement.js +107 -0
- package/dist/increment/NumberFieldIncrement.js.map +1 -0
- package/dist/index.cjs +52 -0
- package/dist/index.d.cts +1761 -430
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +1761 -430
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -2
- package/dist/index2.cjs +4065 -60
- package/dist/index2.cjs.map +1 -1
- package/dist/index2.js +3955 -184
- package/dist/index2.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +6 -0
- package/src/input/Input.vue +37 -0
- package/src/input/InputDataAttributes.ts +30 -0
- package/src/input/index.ts +4 -0
- package/src/meter/index.ts +16 -0
- package/src/meter/indicator/MeterIndicator.vue +65 -0
- package/src/meter/label/MeterLabel.vue +63 -0
- package/src/meter/root/MeterRoot.vue +131 -0
- package/src/meter/root/MeterRootContext.ts +41 -0
- package/src/meter/track/MeterTrack.vue +46 -0
- package/src/meter/value/MeterValue.vue +85 -0
- package/src/number-field/decrement/NumberFieldDecrement.vue +109 -0
- package/src/number-field/group/NumberFieldGroup.vue +47 -0
- package/src/number-field/increment/NumberFieldIncrement.vue +109 -0
- package/src/number-field/index.ts +42 -0
- package/src/number-field/input/NumberFieldInput.vue +455 -0
- package/src/number-field/root/NumberFieldRoot.vue +626 -0
- package/src/number-field/root/NumberFieldRootContext.ts +94 -0
- package/src/number-field/root/useNumberFieldButton.ts +171 -0
- package/src/number-field/scrub-area/NumberFieldScrubArea.vue +359 -0
- package/src/number-field/scrub-area/NumberFieldScrubAreaContext.ts +26 -0
- package/src/number-field/scrub-area-cursor/NumberFieldScrubAreaCursor.vue +75 -0
- package/src/number-field/utils/constants.ts +4 -0
- package/src/number-field/utils/getViewportRect.ts +34 -0
- package/src/number-field/utils/parse.ts +248 -0
- package/src/number-field/utils/stateAttributesMapping.ts +9 -0
- package/src/number-field/utils/subscribeToVisualViewportResize.ts +27 -0
- package/src/number-field/utils/types.ts +24 -0
- package/src/number-field/utils/validate.ts +120 -0
- package/src/otp-field/index.ts +22 -0
- package/src/otp-field/input/OtpFieldInput.vue +336 -0
- package/src/otp-field/root/OtpFieldRoot.vue +583 -0
- package/src/otp-field/root/OtpFieldRootContext.ts +81 -0
- package/src/otp-field/utils/otp.ts +135 -0
- package/src/otp-field/utils/stateAttributesMapping.ts +16 -0
- package/src/progress/index.ts +23 -0
- package/src/progress/indicator/ProgressIndicator.vue +74 -0
- package/src/progress/label/ProgressLabel.vue +63 -0
- package/src/progress/root/ProgressRoot.vue +160 -0
- package/src/progress/root/ProgressRootContext.ts +51 -0
- package/src/progress/root/ProgressRootDataAttributes.ts +14 -0
- package/src/progress/root/stateAttributesMapping.ts +18 -0
- package/src/progress/track/ProgressTrack.vue +48 -0
- package/src/progress/value/ProgressValue.vue +92 -0
- package/src/scroll-area/constants.ts +2 -0
- package/src/scroll-area/content/ScrollAreaContent.vue +87 -0
- package/src/scroll-area/corner/ScrollAreaCorner.vue +64 -0
- package/src/scroll-area/index.ts +25 -0
- package/src/scroll-area/root/ScrollAreaRoot.vue +297 -0
- package/src/scroll-area/root/ScrollAreaRootContext.ts +89 -0
- package/src/scroll-area/root/ScrollAreaRootCssVars.ts +4 -0
- package/src/scroll-area/root/ScrollAreaRootDataAttributes.ts +9 -0
- package/src/scroll-area/root/stateAttributes.ts +14 -0
- package/src/scroll-area/scrollbar/ScrollAreaScrollbar.vue +263 -0
- package/src/scroll-area/scrollbar/ScrollAreaScrollbarContext.ts +20 -0
- package/src/scroll-area/scrollbar/ScrollAreaScrollbarCssVars.ts +4 -0
- package/src/scroll-area/scrollbar/ScrollAreaScrollbarDataAttributes.ts +11 -0
- package/src/scroll-area/thumb/ScrollAreaThumb.vue +120 -0
- package/src/scroll-area/thumb/ScrollAreaThumbDataAttributes.ts +3 -0
- package/src/scroll-area/utils/getOffset.ts +34 -0
- package/src/scroll-area/viewport/ScrollAreaViewport.vue +379 -0
- package/src/scroll-area/viewport/ScrollAreaViewportContext.ts +20 -0
- package/src/scroll-area/viewport/ScrollAreaViewportCssVars.ts +6 -0
- package/src/scroll-area/viewport/ScrollAreaViewportDataAttributes.ts +9 -0
- package/src/utils/detectBrowser.ts +15 -0
- package/src/utils/formatNumber.ts +60 -2
- package/src/utils/scrollEdges.ts +33 -0
- package/src/utils/styles.ts +28 -0
- package/src/utils/useInterval.ts +45 -0
- package/src/utils/usePressAndHold.ts +260 -0
- 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
|
+
}
|