@tremolo-ui/vue 0.8.0 → 0.9.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/index.cjs +865 -611
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +954 -189
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +954 -189
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +339 -128
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/components/AnimationCanvas/AnimationCanvas.ts +27 -30
- package/src/components/AnimationCanvas/index.ts +1 -5
- package/src/components/DropZone/DropZone.ts +19 -9
- package/src/components/DropZone/index.ts +1 -1
- package/src/components/FileInput/FileInput.ts +28 -14
- package/src/components/FileInput/index.ts +1 -1
- package/src/components/Knob/Knob.ts +107 -57
- package/src/components/Knob/KnobThumb.ts +32 -22
- package/src/components/Knob/index.ts +2 -2
- package/src/components/NumberInput/NumberInput.ts +136 -53
- package/src/components/NumberInput/NumberInputField.ts +5 -2
- package/src/components/NumberInput/NumberInputStepper.ts +25 -4
- package/src/components/NumberInput/context.ts +12 -0
- package/src/components/NumberInput/index.ts +1 -1
- package/src/components/NumberInput/stepper-button.ts +8 -1
- package/src/components/Piano/Piano.ts +51 -43
- package/src/components/Piano/index.ts +6 -1
- package/src/components/PointsEditor/PointsEditor.ts +43 -35
- package/src/components/PointsEditor/PointsEditorContainer.ts +2 -6
- package/src/components/PointsEditor/PointsEditorPoint.ts +82 -42
- package/src/components/PointsEditor/context.ts +1 -1
- package/src/components/PointsEditor/index.ts +5 -2
- package/src/components/Slider/Slider.ts +92 -49
- package/src/components/Slider/SliderMarks.ts +28 -14
- package/src/components/Slider/SliderMarksOption.ts +54 -33
- package/src/components/Slider/SliderThumb.ts +26 -8
- package/src/components/Slider/SliderTrack.ts +31 -16
- package/src/components/Slider/context.ts +1 -1
- package/src/components/Slider/index.ts +8 -5
- package/src/components/XYPad/XYPad.ts +94 -56
- package/src/components/XYPad/XYPadThumb.ts +21 -14
- package/src/components/XYPad/index.ts +2 -2
- package/src/components/_util/change-gesture.ts +44 -0
- package/src/components/_util/useCheckSteps.ts +1 -1
- package/src/components/_util/visually-hidden-range-input.ts +1 -1
- package/src/composables/useInstance.ts +1 -1
- package/src/composables/useWheel.ts +5 -2
- package/src/index.ts +21 -4
|
@@ -1,5 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type ComponentObjectPropsOptions,
|
|
3
|
+
computed,
|
|
4
|
+
defineComponent,
|
|
5
|
+
type ExtractPublicPropTypes,
|
|
6
|
+
h,
|
|
7
|
+
type PropType,
|
|
8
|
+
provide,
|
|
9
|
+
ref,
|
|
10
|
+
} from 'vue'
|
|
2
11
|
|
|
12
|
+
import { type ChangeSource } from '@tremolo-ui/dom'
|
|
3
13
|
import {
|
|
4
14
|
commitNumberInputText,
|
|
5
15
|
numberInputBounds,
|
|
@@ -7,66 +17,89 @@ import {
|
|
|
7
17
|
nudgeNumberInput,
|
|
8
18
|
parseNumberText,
|
|
9
19
|
wheelDirection,
|
|
10
|
-
} from '@tremolo-ui/dom'
|
|
20
|
+
} from '@tremolo-ui/dom/internal'
|
|
11
21
|
import { linearScale, type Scale } from '@tremolo-ui/functions'
|
|
12
22
|
|
|
13
23
|
import { useWheel } from '../../composables/useWheel'
|
|
24
|
+
import { useChangeGesture } from '../_util/change-gesture'
|
|
14
25
|
import { dragSensitivityProp, keyboardProp, wheelProp } from '../_util/props'
|
|
15
26
|
import { useCheckSteps } from '../_util/useCheckSteps'
|
|
16
27
|
|
|
17
|
-
import { NumberInputKey } from './context'
|
|
28
|
+
import { NumberInputGestureKey, NumberInputKey } from './context'
|
|
29
|
+
|
|
30
|
+
const numberInputProps = {
|
|
31
|
+
/** The current value. Bind it with `v-model`. */
|
|
32
|
+
modelValue: { type: Number, required: true },
|
|
33
|
+
/** The lowest value. Leave it out for no lower end. */
|
|
34
|
+
min: Number,
|
|
35
|
+
/** The highest value. Leave it out for no upper end. */
|
|
36
|
+
max: Number,
|
|
37
|
+
/** Granularity of the value. @default 1 */
|
|
38
|
+
step: { type: Number, default: 1 },
|
|
39
|
+
/** How the value is distributed, for a `normalized` amount. */
|
|
40
|
+
scale: { type: Object as PropType<Scale>, default: () => linearScale },
|
|
41
|
+
/** The text shown for a value. @default String */
|
|
42
|
+
format: {
|
|
43
|
+
type: Function as PropType<(value: number) => string>,
|
|
44
|
+
default: String,
|
|
45
|
+
},
|
|
46
|
+
/** Read a value back out of the text. Has to undo `format`. */
|
|
47
|
+
parse: {
|
|
48
|
+
type: Function as PropType<(text: string) => number>,
|
|
49
|
+
default: parseNumberText,
|
|
50
|
+
},
|
|
51
|
+
/** Keep the value within `min` and `max` when committed or stepped. */
|
|
52
|
+
clampValue: { type: Boolean, default: true },
|
|
53
|
+
wheel: wheelProp,
|
|
54
|
+
keyboard: keyboardProp,
|
|
55
|
+
dragSensitivity: dragSensitivityProp,
|
|
56
|
+
/** Pixels of vertical drag on the stepper per `step`. `null` turns it off. */
|
|
57
|
+
drag: { type: Number as PropType<number | null>, default: 1 },
|
|
58
|
+
/** Hide the cursor while dragging the stepper. */
|
|
59
|
+
pointerLock: Boolean,
|
|
60
|
+
/** The cursor to show while dragging the stepper. @default 'ns-resize' */
|
|
61
|
+
dragCursor: { type: String, default: 'ns-resize' },
|
|
62
|
+
/** Select the text when the field takes focus. @default 'none' */
|
|
63
|
+
selectOnFocus: {
|
|
64
|
+
type: String as PropType<'all' | 'number' | 'none'>,
|
|
65
|
+
default: 'none',
|
|
66
|
+
},
|
|
67
|
+
/** Show the plain value while the field has focus. */
|
|
68
|
+
unformatOnFocus: Boolean,
|
|
69
|
+
/** Put the caret back at the same digit after an arrow key step. */
|
|
70
|
+
keepCaretOnStep: Boolean,
|
|
71
|
+
/** Commit and leave the field on Enter. @default true */
|
|
72
|
+
blurOnEnter: { type: Boolean, default: true },
|
|
73
|
+
/** Make the input unchangeable and remove it from the tab order. */
|
|
74
|
+
disabled: Boolean,
|
|
75
|
+
/** Make the value unchangeable while leaving the field focusable. */
|
|
76
|
+
readonly: Boolean,
|
|
77
|
+
/**
|
|
78
|
+
* How long after the last wheel notch, arrow key or typed character a change
|
|
79
|
+
* counts as over, in milliseconds, for `change-end`.
|
|
80
|
+
* @default 500
|
|
81
|
+
*/
|
|
82
|
+
changeEndDelay: { type: Number, default: 500 },
|
|
83
|
+
} satisfies ComponentObjectPropsOptions
|
|
84
|
+
|
|
85
|
+
export type NumberInputProps = ExtractPublicPropTypes<typeof numberInputProps>
|
|
18
86
|
|
|
19
87
|
/** Input with some useful functions for entering numbers. Bind it with `v-model`. */
|
|
20
88
|
export const NumberInput = /* @__PURE__ */ defineComponent({
|
|
21
89
|
name: 'NumberInput',
|
|
22
|
-
props:
|
|
23
|
-
/** The current value. Bind it with `v-model`. */
|
|
24
|
-
modelValue: { type: Number, required: true },
|
|
25
|
-
/** The lowest value. Leave it out for no lower end. */
|
|
26
|
-
min: Number,
|
|
27
|
-
/** The highest value. Leave it out for no upper end. */
|
|
28
|
-
max: Number,
|
|
29
|
-
/** Granularity of the value. @default 1 */
|
|
30
|
-
step: { type: Number, default: 1 },
|
|
31
|
-
/** How the value is distributed, for a `normalized` amount. */
|
|
32
|
-
scale: { type: Object as PropType<Scale>, default: () => linearScale },
|
|
33
|
-
/** The text shown for a value. @default String */
|
|
34
|
-
format: {
|
|
35
|
-
type: Function as PropType<(value: number) => string>,
|
|
36
|
-
default: String,
|
|
37
|
-
},
|
|
38
|
-
/** Read a value back out of the text. Has to undo `format`. */
|
|
39
|
-
parse: {
|
|
40
|
-
type: Function as PropType<(text: string) => number>,
|
|
41
|
-
default: parseNumberText,
|
|
42
|
-
},
|
|
43
|
-
/** Keep the value within `min` and `max` when committed or stepped. */
|
|
44
|
-
clampValue: { type: Boolean, default: true },
|
|
45
|
-
wheel: wheelProp,
|
|
46
|
-
keyboard: keyboardProp,
|
|
47
|
-
dragSensitivity: dragSensitivityProp,
|
|
48
|
-
/** Pixels of vertical drag on the stepper per `step`. `null` turns it off. */
|
|
49
|
-
drag: { type: Number as PropType<number | null>, default: 1 },
|
|
50
|
-
/** Hide the cursor while dragging the stepper. */
|
|
51
|
-
pointerLock: Boolean,
|
|
52
|
-
/** Select the text when the field takes focus. @default 'none' */
|
|
53
|
-
selectOnFocus: {
|
|
54
|
-
type: String as PropType<'all' | 'number' | 'none'>,
|
|
55
|
-
default: 'none',
|
|
56
|
-
},
|
|
57
|
-
/** Show the plain value while the field has focus. */
|
|
58
|
-
unformatOnFocus: Boolean,
|
|
59
|
-
/** Put the caret back at the same digit after an arrow key step. */
|
|
60
|
-
keepCaretOnStep: Boolean,
|
|
61
|
-
/** Commit and leave the field on Enter. @default true */
|
|
62
|
-
blurOnEnter: { type: Boolean, default: true },
|
|
63
|
-
/** Make the input unchangeable and remove it from the tab order. */
|
|
64
|
-
disabled: Boolean,
|
|
65
|
-
/** Make the value unchangeable while leaving the field focusable. */
|
|
66
|
-
readonly: Boolean,
|
|
67
|
-
},
|
|
90
|
+
props: numberInputProps,
|
|
68
91
|
emits: {
|
|
69
92
|
'update:modelValue': (value: number) => typeof value === 'number',
|
|
93
|
+
/**
|
|
94
|
+
* A change of the value started — a press on a stepper, the first wheel
|
|
95
|
+
* notch, arrow key or typed character — with the value before it and what
|
|
96
|
+
* it is made with.
|
|
97
|
+
*/
|
|
98
|
+
changeStart: (value: number, _source: ChangeSource) =>
|
|
99
|
+
typeof value === 'number',
|
|
100
|
+
/** The change ended, with the value it ended on. */
|
|
101
|
+
changeEnd: (value: number, _source: ChangeSource) =>
|
|
102
|
+
typeof value === 'number',
|
|
70
103
|
},
|
|
71
104
|
setup(props, { slots, emit, expose }) {
|
|
72
105
|
const root = ref<HTMLDivElement | null>(null)
|
|
@@ -115,12 +148,46 @@ export const NumberInput = /* @__PURE__ */ defineComponent({
|
|
|
115
148
|
format: props.format,
|
|
116
149
|
}))
|
|
117
150
|
|
|
151
|
+
const gesture = useChangeGesture({
|
|
152
|
+
value: () => props.modelValue,
|
|
153
|
+
inactive: () => inactive.value,
|
|
154
|
+
endDelay: () => props.changeEndDelay,
|
|
155
|
+
onStart: (value, source) => emit('changeStart', value, source),
|
|
156
|
+
onEnd: (value, source) => emit('changeEnd', value, source),
|
|
157
|
+
})
|
|
158
|
+
|
|
118
159
|
function changeValue(next: number) {
|
|
119
160
|
if (inactive.value) return
|
|
120
161
|
draft.value = null
|
|
121
|
-
if (next
|
|
162
|
+
if (next === props.modelValue) return
|
|
163
|
+
gesture.changed(next)
|
|
164
|
+
emit('update:modelValue', next)
|
|
122
165
|
}
|
|
123
166
|
|
|
167
|
+
let pressed = false
|
|
168
|
+
provide(NumberInputGestureKey, {
|
|
169
|
+
press: () => {
|
|
170
|
+
if (inactive.value) return
|
|
171
|
+
gesture.hold('pointer')
|
|
172
|
+
// Called again by every repeat of a held stepper; one release will do.
|
|
173
|
+
if (pressed) return
|
|
174
|
+
pressed = true
|
|
175
|
+
// Released wherever the pointer ends up: the stepper drag captures
|
|
176
|
+
// it, and a press that wanders off the button still ends.
|
|
177
|
+
const release = () => {
|
|
178
|
+
pressed = false
|
|
179
|
+
gesture.end()
|
|
180
|
+
window.removeEventListener('pointerup', release)
|
|
181
|
+
window.removeEventListener('pointercancel', release)
|
|
182
|
+
}
|
|
183
|
+
window.addEventListener('pointerup', release)
|
|
184
|
+
window.addEventListener('pointercancel', release)
|
|
185
|
+
},
|
|
186
|
+
key: () => {
|
|
187
|
+
if (!inactive.value) gesture.pulse('keyboard')
|
|
188
|
+
},
|
|
189
|
+
})
|
|
190
|
+
|
|
124
191
|
provide(NumberInputKey, {
|
|
125
192
|
get value() {
|
|
126
193
|
return props.modelValue
|
|
@@ -152,6 +219,9 @@ export const NumberInput = /* @__PURE__ */ defineComponent({
|
|
|
152
219
|
get pointerLock() {
|
|
153
220
|
return props.pointerLock
|
|
154
221
|
},
|
|
222
|
+
get dragCursor() {
|
|
223
|
+
return props.dragCursor
|
|
224
|
+
},
|
|
155
225
|
get selectOnFocus() {
|
|
156
226
|
return props.selectOnFocus
|
|
157
227
|
},
|
|
@@ -188,7 +258,12 @@ export const NumberInput = /* @__PURE__ */ defineComponent({
|
|
|
188
258
|
// Deliberately unclamped: clamping here would make "1500" impossible
|
|
189
259
|
// to type into an input whose max is 100.
|
|
190
260
|
const parsed = props.parse(next)
|
|
191
|
-
if (Number.isFinite(parsed))
|
|
261
|
+
if (!Number.isFinite(parsed)) return
|
|
262
|
+
if (parsed !== props.modelValue) {
|
|
263
|
+
gesture.pulse('keyboard')
|
|
264
|
+
gesture.changed(parsed)
|
|
265
|
+
}
|
|
266
|
+
emit('update:modelValue', parsed)
|
|
192
267
|
},
|
|
193
268
|
commitDraft: () => {
|
|
194
269
|
if (draft.value === null || inactive.value) return
|
|
@@ -199,8 +274,15 @@ export const NumberInput = /* @__PURE__ */ defineComponent({
|
|
|
199
274
|
})
|
|
200
275
|
// Text with no number is not a value: the field goes back to what it
|
|
201
276
|
// was showing.
|
|
202
|
-
if (committed === null)
|
|
203
|
-
|
|
277
|
+
if (committed === null) {
|
|
278
|
+
draft.value = null
|
|
279
|
+
return
|
|
280
|
+
}
|
|
281
|
+
// The typing was a gesture of its own, and committing is where it
|
|
282
|
+
// ends.
|
|
283
|
+
if (committed !== props.modelValue) gesture.pulse('keyboard')
|
|
284
|
+
changeValue(committed)
|
|
285
|
+
gesture.end()
|
|
204
286
|
},
|
|
205
287
|
changeValue,
|
|
206
288
|
nudge: (direction, option, modifiers) =>
|
|
@@ -224,6 +306,7 @@ export const NumberInput = /* @__PURE__ */ defineComponent({
|
|
|
224
306
|
const direction = wheelDirection(event)
|
|
225
307
|
if (!props.wheel || inactive.value || direction === null) return
|
|
226
308
|
event.preventDefault()
|
|
309
|
+
gesture.pulse('wheel')
|
|
227
310
|
changeValue(
|
|
228
311
|
nudgeNumberInput(
|
|
229
312
|
props.modelValue,
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
computed,
|
|
3
3
|
defineComponent,
|
|
4
4
|
h,
|
|
5
|
+
inject,
|
|
5
6
|
nextTick,
|
|
6
7
|
onBeforeUnmount,
|
|
7
8
|
ref,
|
|
@@ -12,9 +13,9 @@ import {
|
|
|
12
13
|
caretAtDecimalOffset,
|
|
13
14
|
caretDecimalOffset,
|
|
14
15
|
numberSpan,
|
|
15
|
-
} from '@tremolo-ui/dom'
|
|
16
|
+
} from '@tremolo-ui/dom/internal'
|
|
16
17
|
|
|
17
|
-
import { useNumberInputContext } from './context'
|
|
18
|
+
import { NumberInputGestureKey, useNumberInputContext } from './context'
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
21
|
* The text field, and the only place the value can be typed. While the user
|
|
@@ -24,6 +25,7 @@ export const NumberInputField = /* @__PURE__ */ defineComponent({
|
|
|
24
25
|
name: 'NumberInputField',
|
|
25
26
|
setup(_, { expose }) {
|
|
26
27
|
const field = useNumberInputContext()
|
|
28
|
+
const gesture = inject(NumberInputGestureKey, null)
|
|
27
29
|
const input = ref<HTMLInputElement | null>(null)
|
|
28
30
|
const focused = ref(false)
|
|
29
31
|
watchEffect(() => field.setInput(input.value))
|
|
@@ -58,6 +60,7 @@ export const NumberInputField = /* @__PURE__ */ defineComponent({
|
|
|
58
60
|
field.keepCaretOnStep && element.selectionStart !== null
|
|
59
61
|
? caretDecimalOffset(from, element.selectionStart)
|
|
60
62
|
: null
|
|
63
|
+
gesture?.key()
|
|
61
64
|
field.nudge(direction, field.keyboard!, event)
|
|
62
65
|
if (offset === null) return
|
|
63
66
|
await nextTick()
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import { computed, defineComponent, h, provide, ref, watch } from 'vue'
|
|
1
|
+
import { computed, defineComponent, h, inject, provide, ref, watch } from 'vue'
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createStepperDrag,
|
|
5
|
+
type StepperDragInstance,
|
|
6
|
+
} from '@tremolo-ui/dom/internal'
|
|
4
7
|
|
|
5
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
NumberInputGestureKey,
|
|
10
|
+
StepperKey,
|
|
11
|
+
useNumberInputContext,
|
|
12
|
+
} from './context'
|
|
6
13
|
|
|
7
14
|
/**
|
|
8
15
|
* The area the steppers sit in, and a drag handle in its own right: dragging
|
|
@@ -12,6 +19,7 @@ export const NumberInputStepper = /* @__PURE__ */ defineComponent({
|
|
|
12
19
|
name: 'NumberInputStepper',
|
|
13
20
|
setup(_, { slots }) {
|
|
14
21
|
const field = useNumberInputContext()
|
|
22
|
+
const gesture = inject(NumberInputGestureKey, null)
|
|
15
23
|
const el = ref<HTMLDivElement | null>(null)
|
|
16
24
|
let instance: StepperDragInstance | null = null
|
|
17
25
|
|
|
@@ -26,6 +34,7 @@ export const NumberInputStepper = /* @__PURE__ */ defineComponent({
|
|
|
26
34
|
pixels: field.drag ?? 1,
|
|
27
35
|
sensitivity: field.dragSensitivity,
|
|
28
36
|
pointerLock: field.pointerLock,
|
|
37
|
+
cursor: field.dragCursor,
|
|
29
38
|
})
|
|
30
39
|
|
|
31
40
|
watch(
|
|
@@ -49,6 +58,18 @@ export const NumberInputStepper = /* @__PURE__ */ defineComponent({
|
|
|
49
58
|
|
|
50
59
|
provide(StepperKey, { moved: () => instance?.moved() ?? false })
|
|
51
60
|
|
|
52
|
-
return () =>
|
|
61
|
+
return () =>
|
|
62
|
+
h(
|
|
63
|
+
'div',
|
|
64
|
+
{
|
|
65
|
+
ref: el,
|
|
66
|
+
// A drag on the stepper is a press like any other: held from here
|
|
67
|
+
// until the pointer is released.
|
|
68
|
+
onPointerdown: () => {
|
|
69
|
+
if (enabled.value) gesture?.press()
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
slots.default?.(),
|
|
73
|
+
)
|
|
53
74
|
},
|
|
54
75
|
})
|
|
@@ -21,6 +21,7 @@ export interface NumberInputContextValue {
|
|
|
21
21
|
readonly drag: number | null
|
|
22
22
|
readonly dragSensitivity: ModifierValue<number>
|
|
23
23
|
readonly pointerLock: boolean
|
|
24
|
+
readonly dragCursor: string
|
|
24
25
|
readonly selectOnFocus: 'all' | 'number' | 'none'
|
|
25
26
|
readonly unformatOnFocus: boolean
|
|
26
27
|
readonly keepCaretOnStep: boolean
|
|
@@ -58,6 +59,17 @@ export function useNumberInputContext(): NumberInputContextValue {
|
|
|
58
59
|
return injectContext(NumberInputKey, 'NumberInput')
|
|
59
60
|
}
|
|
60
61
|
|
|
62
|
+
/** @internal The change gesture of `NumberInput`, for the parts that change the value. */
|
|
63
|
+
export interface NumberInputGestureValue {
|
|
64
|
+
/** A press on a stepper, held until the pointer is released anywhere. */
|
|
65
|
+
press: () => void
|
|
66
|
+
/** An arrow key in `NumberInputField`. */
|
|
67
|
+
key: () => void
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export const NumberInputGestureKey: InjectionKey<NumberInputGestureValue> =
|
|
71
|
+
Symbol('NumberInputGesture')
|
|
72
|
+
|
|
61
73
|
/** @internal Whether the drag on `NumberInputStepper` has moved the value. */
|
|
62
74
|
export const StepperKey: InjectionKey<{ moved: () => boolean }> =
|
|
63
75
|
Symbol('NumberInputStepper')
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { useNumberInputContext, type NumberInputContextValue } from './context'
|
|
2
|
-
export { NumberInput } from './NumberInput'
|
|
2
|
+
export { NumberInput, type NumberInputProps } from './NumberInput'
|
|
3
3
|
export { NumberInputDecrementStepper } from './NumberInputDecrementStepper'
|
|
4
4
|
export { NumberInputField } from './NumberInputField'
|
|
5
5
|
export { NumberInputIncrementStepper } from './NumberInputIncrementStepper'
|
|
@@ -2,7 +2,11 @@ import { computed, defineComponent, h, inject } from 'vue'
|
|
|
2
2
|
|
|
3
3
|
import { useLongPress } from '../../composables/useLongPress'
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
NumberInputGestureKey,
|
|
7
|
+
StepperKey,
|
|
8
|
+
useNumberInputContext,
|
|
9
|
+
} from './context'
|
|
6
10
|
|
|
7
11
|
/** The body of `NumberInputIncrementStepper` and `NumberInputDecrementStepper`. */
|
|
8
12
|
export function stepperButton(name: string, direction: 1 | -1) {
|
|
@@ -11,12 +15,15 @@ export function stepperButton(name: string, direction: 1 | -1) {
|
|
|
11
15
|
setup(_, { slots }) {
|
|
12
16
|
const field = useNumberInputContext()
|
|
13
17
|
const stepper = inject(StepperKey, null)
|
|
18
|
+
const gesture = inject(NumberInputGestureKey, null)
|
|
14
19
|
const blocked = computed(
|
|
15
20
|
() => field.disabled || (direction > 0 ? field.atMax : field.atMin),
|
|
16
21
|
)
|
|
17
22
|
const press = useLongPress({
|
|
18
23
|
onPress: () => {
|
|
19
24
|
if (field.disabled || field.readonly) return
|
|
25
|
+
// On every press and repeat; the gesture is held from the first.
|
|
26
|
+
gesture?.press()
|
|
20
27
|
// Once the pointer has actually travelled, the drag on the stepper
|
|
21
28
|
// owns the value; repeating on top of it would move it twice.
|
|
22
29
|
if (stepper?.moved()) return
|
|
@@ -1,27 +1,31 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type ComponentObjectPropsOptions,
|
|
2
3
|
computed,
|
|
3
4
|
defineComponent,
|
|
5
|
+
type ExtractPublicPropTypes,
|
|
4
6
|
h,
|
|
5
|
-
ref,
|
|
6
|
-
watch,
|
|
7
7
|
type HTMLAttributes,
|
|
8
8
|
type PropType,
|
|
9
|
+
ref,
|
|
9
10
|
type SlotsType,
|
|
11
|
+
watch,
|
|
10
12
|
} from 'vue'
|
|
11
13
|
|
|
12
14
|
import {
|
|
13
|
-
blackKeyWidth,
|
|
14
15
|
createPianoInput,
|
|
15
|
-
fitWhiteKeyWidth,
|
|
16
|
-
getNoteRangeArray,
|
|
17
16
|
notePosition,
|
|
18
|
-
pianoWidth,
|
|
19
17
|
type KeyboardShortcuts,
|
|
20
18
|
type KeyboardShortcutsScope,
|
|
21
19
|
type NoteRange,
|
|
22
20
|
type PianoInputInstance,
|
|
23
21
|
type PianoLayout,
|
|
24
22
|
} from '@tremolo-ui/dom'
|
|
23
|
+
import {
|
|
24
|
+
blackKeyWidth,
|
|
25
|
+
fitWhiteKeyWidth,
|
|
26
|
+
getNoteRangeArray,
|
|
27
|
+
pianoWidth,
|
|
28
|
+
} from '@tremolo-ui/dom/internal'
|
|
25
29
|
import { isWhiteKey, noteKey } from '@tremolo-ui/functions'
|
|
26
30
|
|
|
27
31
|
/** What a key is, when the `label` slot or `keyProps` is asked about it. */
|
|
@@ -39,49 +43,53 @@ export interface KeyState {
|
|
|
39
43
|
export type KeyAttributes = HTMLAttributes &
|
|
40
44
|
Record<`data-${string}`, string | number | boolean | undefined>
|
|
41
45
|
|
|
46
|
+
const pianoProps = {
|
|
47
|
+
/** The notes drawn, from `first` to `last`. */
|
|
48
|
+
noteRange: { type: Object as PropType<NoteRange>, required: true },
|
|
49
|
+
/** Let a pointer slide from one key to the next. @default true */
|
|
50
|
+
glissando: { type: Boolean, default: true },
|
|
51
|
+
/** Highest note that can sound. @default 127 */
|
|
52
|
+
midiMax: { type: Number, default: 127 },
|
|
53
|
+
/** Play notes from the computer keyboard. See `SHORTCUTS` in `@tremolo-ui/dom`. */
|
|
54
|
+
keyboardShortcuts: Object as PropType<KeyboardShortcuts>,
|
|
55
|
+
/** Where keyboard shortcuts listen. @default 'root' */
|
|
56
|
+
keyboardShortcutsScope: {
|
|
57
|
+
type: String as PropType<KeyboardShortcutsScope>,
|
|
58
|
+
default: 'root',
|
|
59
|
+
},
|
|
60
|
+
/** Follow the width of the parent element. */
|
|
61
|
+
resizable: Boolean,
|
|
62
|
+
/** Width of a white key in pixels. @default 40 */
|
|
63
|
+
whiteKeyWidth: { type: Number, default: 40 },
|
|
64
|
+
/** Space between two white keys, in pixels. @default 1 */
|
|
65
|
+
keyGap: { type: Number, default: 1 },
|
|
66
|
+
/** Width of a black key, as a fraction of a white one. @default 0.65 */
|
|
67
|
+
blackKeyWidthRatio: { type: Number, default: 0.65 },
|
|
68
|
+
/** Height of a black key, as a fraction of the keyboard. @default 0.6 */
|
|
69
|
+
blackKeyHeightRatio: { type: Number, default: 0.6 },
|
|
70
|
+
/** Classes for the label inside each key. */
|
|
71
|
+
classes: Object as PropType<{
|
|
72
|
+
keyLabelWrapper?: string
|
|
73
|
+
keyLabel?: string
|
|
74
|
+
}>,
|
|
75
|
+
/**
|
|
76
|
+
* Extra attributes for one key, by note. The geometry of the key is
|
|
77
|
+
* applied after the returned style and cannot be overridden.
|
|
78
|
+
*/
|
|
79
|
+
keyProps: Function as PropType<
|
|
80
|
+
(note: number, state: KeyState) => KeyAttributes
|
|
81
|
+
>,
|
|
82
|
+
} satisfies ComponentObjectPropsOptions
|
|
83
|
+
|
|
84
|
+
export type PianoProps = ExtractPublicPropTypes<typeof pianoProps>
|
|
85
|
+
|
|
42
86
|
/**
|
|
43
87
|
* Customizable piano. The keys are drawn by the component; what goes inside
|
|
44
88
|
* one is the `label` slot, which receives the note and its state.
|
|
45
89
|
*/
|
|
46
90
|
export const Piano = /* @__PURE__ */ defineComponent({
|
|
47
91
|
name: 'Piano',
|
|
48
|
-
props:
|
|
49
|
-
/** The notes drawn, from `first` to `last`. */
|
|
50
|
-
noteRange: { type: Object as PropType<NoteRange>, required: true },
|
|
51
|
-
/** Let a pointer slide from one key to the next. @default true */
|
|
52
|
-
glissando: { type: Boolean, default: true },
|
|
53
|
-
/** Highest note that can sound. @default 127 */
|
|
54
|
-
midiMax: { type: Number, default: 127 },
|
|
55
|
-
/** Play notes from the computer keyboard. See `SHORTCUTS` in `@tremolo-ui/dom`. */
|
|
56
|
-
keyboardShortcuts: Object as PropType<KeyboardShortcuts>,
|
|
57
|
-
/** Where keyboard shortcuts listen. @default 'root' */
|
|
58
|
-
keyboardShortcutsScope: {
|
|
59
|
-
type: String as PropType<KeyboardShortcutsScope>,
|
|
60
|
-
default: 'root',
|
|
61
|
-
},
|
|
62
|
-
/** Follow the width of the parent element. */
|
|
63
|
-
resizable: Boolean,
|
|
64
|
-
/** Width of a white key in pixels. @default 40 */
|
|
65
|
-
whiteKeyWidth: { type: Number, default: 40 },
|
|
66
|
-
/** Space between two white keys, in pixels. @default 1 */
|
|
67
|
-
keyGap: { type: Number, default: 1 },
|
|
68
|
-
/** Width of a black key, as a fraction of a white one. @default 0.65 */
|
|
69
|
-
blackKeyWidthRatio: { type: Number, default: 0.65 },
|
|
70
|
-
/** Height of a black key, as a fraction of the keyboard. @default 0.6 */
|
|
71
|
-
blackKeyHeightRatio: { type: Number, default: 0.6 },
|
|
72
|
-
/** Classes for the label inside each key. */
|
|
73
|
-
classes: Object as PropType<{
|
|
74
|
-
keyLabelWrapper?: string
|
|
75
|
-
keyLabel?: string
|
|
76
|
-
}>,
|
|
77
|
-
/**
|
|
78
|
-
* Extra attributes for one key, by note. The geometry of the key is
|
|
79
|
-
* applied after the returned style and cannot be overridden.
|
|
80
|
-
*/
|
|
81
|
-
keyProps: Function as PropType<
|
|
82
|
-
(note: number, state: KeyState) => KeyAttributes
|
|
83
|
-
>,
|
|
84
|
-
},
|
|
92
|
+
props: pianoProps,
|
|
85
93
|
emits: {
|
|
86
94
|
/** A note started sounding. */
|
|
87
95
|
playNote: (note: number, _velocity?: number) => typeof note === 'number',
|
|
@@ -1,64 +1,72 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type ComponentObjectPropsOptions,
|
|
2
3
|
computed,
|
|
3
4
|
defineComponent,
|
|
5
|
+
type ExtractPublicPropTypes,
|
|
4
6
|
h,
|
|
5
7
|
onScopeDispose,
|
|
8
|
+
type PropType,
|
|
6
9
|
provide,
|
|
7
10
|
shallowRef,
|
|
8
11
|
watchEffect,
|
|
9
|
-
type PropType,
|
|
10
12
|
} from 'vue'
|
|
11
13
|
|
|
12
14
|
import {
|
|
13
15
|
createPointsEditor,
|
|
14
16
|
DEFAULT_DRAG_SENSITIVITY,
|
|
15
|
-
POINTS_EDITOR_DEFAULT_KEYBOARD,
|
|
16
|
-
POINTS_EDITOR_DEFAULT_WHEEL,
|
|
17
17
|
type InputEventOption,
|
|
18
18
|
type ModifierValue,
|
|
19
19
|
type SelectionBoxRect,
|
|
20
20
|
} from '@tremolo-ui/dom'
|
|
21
|
+
import {
|
|
22
|
+
POINTS_EDITOR_DEFAULT_KEYBOARD,
|
|
23
|
+
POINTS_EDITOR_DEFAULT_WHEEL,
|
|
24
|
+
} from '@tremolo-ui/dom/internal'
|
|
21
25
|
|
|
22
26
|
import { PointsEditorKey } from './context'
|
|
23
27
|
|
|
24
28
|
const EMPTY: readonly string[] = []
|
|
25
29
|
|
|
30
|
+
const pointsEditorProps = {
|
|
31
|
+
/** Make the points unchangeable and remove them from the tab order. */
|
|
32
|
+
disabled: Boolean,
|
|
33
|
+
/** Make the points unmovable. */
|
|
34
|
+
readonly: Boolean,
|
|
35
|
+
/** The cursor to show while dragging a point. @default 'grabbing' */
|
|
36
|
+
dragCursor: { type: String, default: 'grabbing' },
|
|
37
|
+
/** How much one wheel notch moves the focused point. */
|
|
38
|
+
wheel: {
|
|
39
|
+
type: [Array, Object] as PropType<ModifierValue<InputEventOption> | null>,
|
|
40
|
+
default: () => POINTS_EDITOR_DEFAULT_WHEEL,
|
|
41
|
+
},
|
|
42
|
+
/** How much one arrow key press moves a point. */
|
|
43
|
+
keyboard: {
|
|
44
|
+
type: [Array, Object] as PropType<ModifierValue<InputEventOption> | null>,
|
|
45
|
+
default: () => POINTS_EDITOR_DEFAULT_KEYBOARD,
|
|
46
|
+
},
|
|
47
|
+
/** How much a drag moves a point, per modifier key. */
|
|
48
|
+
dragSensitivity: {
|
|
49
|
+
type: [Number, Object] as PropType<ModifierValue<number>>,
|
|
50
|
+
default: () => DEFAULT_DRAG_SENSITIVITY,
|
|
51
|
+
},
|
|
52
|
+
/**
|
|
53
|
+
* Let points be selected, and a selection be moved as one. A selection
|
|
54
|
+
* updates several points in the same tick.
|
|
55
|
+
*/
|
|
56
|
+
selectable: Boolean,
|
|
57
|
+
/** Ids of the selected points. Bind it with `v-model:selection`. */
|
|
58
|
+
selection: Array as PropType<string[]>,
|
|
59
|
+
} satisfies ComponentObjectPropsOptions
|
|
60
|
+
|
|
61
|
+
export type PointsEditorProps = ExtractPublicPropTypes<typeof pointsEditorProps>
|
|
62
|
+
|
|
26
63
|
/**
|
|
27
64
|
* Multiple point controller. Bind the selection with `v-model:selection` to
|
|
28
65
|
* hold it yourself; the editor keeps its own otherwise.
|
|
29
66
|
*/
|
|
30
67
|
export const PointsEditor = /* @__PURE__ */ defineComponent({
|
|
31
68
|
name: 'PointsEditor',
|
|
32
|
-
props:
|
|
33
|
-
/** Make the points unchangeable and remove them from the tab order. */
|
|
34
|
-
disabled: Boolean,
|
|
35
|
-
/** Make the points unmovable. */
|
|
36
|
-
readonly: Boolean,
|
|
37
|
-
/** The cursor to show while dragging a point. @default { cursor: 'grabbing' } */
|
|
38
|
-
externalStyles: Object as PropType<{ cursor?: string }>,
|
|
39
|
-
/** How much one wheel notch moves the focused point. */
|
|
40
|
-
wheel: {
|
|
41
|
-
type: [Array, Object] as PropType<ModifierValue<InputEventOption> | null>,
|
|
42
|
-
default: () => POINTS_EDITOR_DEFAULT_WHEEL,
|
|
43
|
-
},
|
|
44
|
-
/** How much one arrow key press moves a point. */
|
|
45
|
-
keyboard: {
|
|
46
|
-
type: [Array, Object] as PropType<ModifierValue<InputEventOption> | null>,
|
|
47
|
-
default: () => POINTS_EDITOR_DEFAULT_KEYBOARD,
|
|
48
|
-
},
|
|
49
|
-
/** How much a drag moves a point, per modifier key. */
|
|
50
|
-
dragSensitivity: {
|
|
51
|
-
type: [Number, Object] as PropType<ModifierValue<number>>,
|
|
52
|
-
default: () => DEFAULT_DRAG_SENSITIVITY,
|
|
53
|
-
},
|
|
54
|
-
/**
|
|
55
|
-
* Let points be selected, and a selection be moved as one. A selection
|
|
56
|
-
* updates several points in the same tick.
|
|
57
|
-
*/
|
|
58
|
-
selectable: Boolean,
|
|
59
|
-
/** Ids of the selected points. Bind it with `v-model:selection`. */
|
|
60
|
-
selection: Array as PropType<string[]>,
|
|
61
|
-
},
|
|
69
|
+
props: pointsEditorProps,
|
|
62
70
|
emits: {
|
|
63
71
|
'update:selection': (selection: string[]) => Array.isArray(selection),
|
|
64
72
|
},
|
|
@@ -110,8 +118,8 @@ export const PointsEditor = /* @__PURE__ */ defineComponent({
|
|
|
110
118
|
get dragSensitivity() {
|
|
111
119
|
return props.dragSensitivity
|
|
112
120
|
},
|
|
113
|
-
get
|
|
114
|
-
return props.
|
|
121
|
+
get dragCursor() {
|
|
122
|
+
return props.dragCursor
|
|
115
123
|
},
|
|
116
124
|
get selectable() {
|
|
117
125
|
return props.selectable
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import { computed, defineComponent, h, ref, watch, watchEffect } from 'vue'
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
elementMapping,
|
|
6
|
-
POINT_AXIS,
|
|
7
|
-
wheelMove,
|
|
8
|
-
} from '@tremolo-ui/dom'
|
|
3
|
+
import { createDragValue, elementMapping } from '@tremolo-ui/dom'
|
|
4
|
+
import { POINT_AXIS, wheelMove } from '@tremolo-ui/dom/internal'
|
|
9
5
|
|
|
10
6
|
import { useWheel } from '../../composables/useWheel'
|
|
11
7
|
import { providePlacement } from '../_util/placement'
|