@tremolo-ui/react 0.7.0 → 0.8.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 +346 -1015
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -221
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +76 -221
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +333 -978
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/DropZone/index.tsx +2 -1
- package/src/components/FileInput/index.tsx +8 -13
- package/src/components/Knob/ActiveLine.tsx +5 -3
- package/src/components/Knob/InactiveLine.tsx +6 -4
- package/src/components/Knob/SVGRoot.tsx +3 -3
- package/src/components/Knob/Thumb.tsx +3 -2
- package/src/components/Knob/context.tsx +3 -74
- package/src/components/Knob/index.tsx +19 -16
- package/src/components/NumberInput/InputField.tsx +12 -27
- package/src/components/NumberInput/Stepper.tsx +49 -97
- package/src/components/NumberInput/StepperButton.tsx +3 -4
- package/src/components/NumberInput/context.tsx +2 -1
- package/src/components/NumberInput/index.tsx +40 -71
- package/src/components/Piano/index.tsx +35 -142
- package/src/components/PointsEditor/Container.tsx +15 -26
- package/src/components/PointsEditor/Point.tsx +38 -85
- package/src/components/PointsEditor/context.tsx +5 -56
- package/src/components/PointsEditor/index.tsx +29 -311
- package/src/components/Slider/Marks.tsx +3 -5
- package/src/components/Slider/MarksOption.tsx +7 -18
- package/src/components/Slider/Track.tsx +2 -1
- package/src/components/Slider/index.tsx +29 -28
- package/src/components/XYPad/Area.tsx +27 -49
- package/src/components/XYPad/Thumb.tsx +3 -1
- package/src/components/XYPad/context.tsx +1 -3
- package/src/components/XYPad/index.tsx +33 -46
- package/src/components/_util/VisuallyHiddenRangeInput.tsx +1 -1
- package/src/hooks/_internal/useCheckSteps.ts +9 -146
- package/src/hooks/useLongPress.ts +35 -51
- package/src/hooks/useMIDIAccess.ts +0 -8
- package/src/hooks/useMIDIInput.ts +0 -3
- package/src/index.ts +2 -27
- package/src/components/Piano/keyboard-shortcuts.ts +0 -61
- package/src/components/Slider/type.ts +0 -46
- package/src/components/_util/css-length.ts +0 -13
- package/src/components/_util/decimal-digits.ts +0 -23
- package/src/components/_util/visually-hidden.ts +0 -25
- package/src/input-event.ts +0 -32
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ComponentPropsWithoutRef,
|
|
3
3
|
CSSProperties,
|
|
4
|
-
useCallback,
|
|
5
4
|
useEffect,
|
|
6
5
|
useId,
|
|
7
6
|
useRef,
|
|
@@ -9,38 +8,26 @@ import {
|
|
|
9
8
|
} from 'react'
|
|
10
9
|
|
|
11
10
|
import {
|
|
12
|
-
|
|
11
|
+
arrowKeyMove,
|
|
12
|
+
clampPoint,
|
|
13
13
|
type InputEventOption,
|
|
14
|
-
type ModifierState,
|
|
15
14
|
type ModifierValue,
|
|
15
|
+
POINT_AXIS,
|
|
16
|
+
type PointPosition,
|
|
17
|
+
type PointsEditorPoint,
|
|
16
18
|
selectModifier,
|
|
17
19
|
} from '@tremolo-ui/dom'
|
|
18
|
-
import { clamp } from '@tremolo-ui/functions'
|
|
19
20
|
|
|
20
21
|
import { useComposedRefs } from '../../compose-refs'
|
|
21
22
|
import { useDragValue } from '../../hooks/useDragValue'
|
|
22
|
-
import { cssLength } from '../_util/css-length'
|
|
23
23
|
import { useCheckPlacement } from '../_util/Placement'
|
|
24
24
|
import { VisuallyHiddenRangeInput } from '../_util/VisuallyHiddenRangeInput'
|
|
25
25
|
|
|
26
|
-
import {
|
|
26
|
+
import { usePointsEditorContext } from './context'
|
|
27
27
|
|
|
28
28
|
import type { CSSVariables } from '../../css-variables'
|
|
29
29
|
|
|
30
|
-
export
|
|
31
|
-
|
|
32
|
-
export function clampPoint(
|
|
33
|
-
point: PointBaseType,
|
|
34
|
-
min?: Partial<PointBaseType>,
|
|
35
|
-
max?: Partial<PointBaseType>,
|
|
36
|
-
) {
|
|
37
|
-
const { x, y } = point
|
|
38
|
-
const newX = clamp(x, min?.x ?? 0, max?.x ?? 1)
|
|
39
|
-
const newY = clamp(y, min?.y ?? 0, max?.y ?? 1)
|
|
40
|
-
return { x: newX, y: newY }
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export interface PointsEditorPointProps<T extends PointBaseType> {
|
|
30
|
+
export interface PointsEditorPointProps<T extends PointPosition> {
|
|
44
31
|
/**
|
|
45
32
|
* Where the point is, as `{ x, y }` from 0 to 1 on each axis, with `y`
|
|
46
33
|
* growing downwards.
|
|
@@ -53,16 +40,10 @@ export interface PointsEditorPointProps<T extends PointBaseType> {
|
|
|
53
40
|
*/
|
|
54
41
|
id?: string
|
|
55
42
|
/** The lowest position the point can take, per axis. An axis left out is 0. */
|
|
56
|
-
min?: Partial<
|
|
43
|
+
min?: Partial<PointPosition>
|
|
57
44
|
/** The highest position the point can take, per axis. An axis left out is 1. */
|
|
58
|
-
max?: Partial<
|
|
45
|
+
max?: Partial<PointPosition>
|
|
59
46
|
|
|
60
|
-
/** Width and height at once. Sets both `--width` and `--height`. */
|
|
61
|
-
size?: number | string
|
|
62
|
-
/** Sets `--width`; the size the theme gives it stands when omitted. */
|
|
63
|
-
width?: number | string
|
|
64
|
-
/** Sets `--height`; the size the theme gives it stands when omitted. */
|
|
65
|
-
height?: number | string
|
|
66
47
|
/** Sets `--color`, for the theme to colour the point with. */
|
|
67
48
|
color?: string
|
|
68
49
|
|
|
@@ -88,22 +69,15 @@ export interface PointsEditorPointProps<T extends PointBaseType> {
|
|
|
88
69
|
* Called with the new position when the point is dragged or moved by the
|
|
89
70
|
* arrow keys or the wheel, including when it moves along with a selection.
|
|
90
71
|
*/
|
|
91
|
-
onChange?: (value:
|
|
72
|
+
onChange?: (value: PointPosition) => void
|
|
92
73
|
/** Called when a drag on this point starts, with where the point is. */
|
|
93
|
-
onDragStart?: (value:
|
|
74
|
+
onDragStart?: (value: PointPosition) => void
|
|
94
75
|
/** Called when that drag ends, with where the point is. */
|
|
95
|
-
onDragEnd?: (value:
|
|
76
|
+
onDragEnd?: (value: PointPosition) => void
|
|
96
77
|
|
|
97
|
-
style?: CSSProperties &
|
|
98
|
-
CSSVariables<'width' | 'height' | 'color' | 'translate'>
|
|
78
|
+
style?: CSSProperties & CSSVariables<'color' | 'translate'>
|
|
99
79
|
}
|
|
100
80
|
|
|
101
|
-
/**
|
|
102
|
-
* A point is placed by its position within the container, so its value is a
|
|
103
|
-
* position: 0..1 on each axis, with y growing downwards.
|
|
104
|
-
*/
|
|
105
|
-
export const AXIS = { min: 0, max: 1 }
|
|
106
|
-
|
|
107
81
|
/**
|
|
108
82
|
* One setting for both axes, or one per axis. The point holds a range input
|
|
109
83
|
* for each, so anything that names or describes it comes in a pair.
|
|
@@ -115,15 +89,12 @@ function perAxis(
|
|
|
115
89
|
return typeof value === 'string' ? value : value?.[axis]
|
|
116
90
|
}
|
|
117
91
|
|
|
118
|
-
export function Point<T extends
|
|
92
|
+
export function Point<T extends PointPosition>({
|
|
119
93
|
value,
|
|
120
94
|
children,
|
|
121
95
|
id: idProp,
|
|
122
96
|
min,
|
|
123
97
|
max,
|
|
124
|
-
size,
|
|
125
|
-
width,
|
|
126
|
-
height,
|
|
127
98
|
color,
|
|
128
99
|
|
|
129
100
|
disabled: _disabled,
|
|
@@ -154,10 +125,7 @@ export function Point<T extends PointBaseType>({
|
|
|
154
125
|
keyboard: rootKeyboard,
|
|
155
126
|
dragSensitivity,
|
|
156
127
|
selection,
|
|
157
|
-
|
|
158
|
-
beginPointDrag,
|
|
159
|
-
movePointDrag,
|
|
160
|
-
nudgeSelection,
|
|
128
|
+
editor,
|
|
161
129
|
} = usePointsEditorContext()
|
|
162
130
|
|
|
163
131
|
const generatedId = useId()
|
|
@@ -182,7 +150,7 @@ export function Point<T extends PointBaseType>({
|
|
|
182
150
|
// What the editor needs to move this point along with the rest of a
|
|
183
151
|
// selection. Rewritten after every render rather than kept in the registry
|
|
184
152
|
// itself: the value changes on every frame of a drag.
|
|
185
|
-
const registration = useRef<
|
|
153
|
+
const registration = useRef<PointsEditorPoint>({
|
|
186
154
|
value,
|
|
187
155
|
min,
|
|
188
156
|
max,
|
|
@@ -203,15 +171,18 @@ export function Point<T extends PointBaseType>({
|
|
|
203
171
|
}
|
|
204
172
|
})
|
|
205
173
|
|
|
206
|
-
useEffect(
|
|
174
|
+
useEffect(
|
|
175
|
+
() => editor.registerPoint(id, () => registration.current),
|
|
176
|
+
[id, editor],
|
|
177
|
+
)
|
|
207
178
|
|
|
208
179
|
/** Where the pointer was when the drag started, to measure the move from. */
|
|
209
|
-
const pointerOrigin = useRef<
|
|
180
|
+
const pointerOrigin = useRef<PointPosition | null>(null)
|
|
210
181
|
|
|
211
182
|
// The value is the position itself: no scaling, and no rounding to a step.
|
|
212
183
|
const { refCallback: dragRefCallback, dragging } =
|
|
213
184
|
useDragValue<HTMLDivElement>({
|
|
214
|
-
axis:
|
|
185
|
+
axis: POINT_AXIS,
|
|
215
186
|
baseElementRef: containerRef,
|
|
216
187
|
sensitivity: (state) =>
|
|
217
188
|
selectModifier(dragSensitivity, state.event).value,
|
|
@@ -223,13 +194,13 @@ export function Point<T extends PointBaseType>({
|
|
|
223
194
|
onChange: ([x, y]) => {
|
|
224
195
|
const origin = pointerOrigin.current
|
|
225
196
|
if (!origin) return
|
|
226
|
-
movePointDrag({ x: x - origin.x, y: y - origin.y })
|
|
197
|
+
editor.movePointDrag({ x: x - origin.x, y: y - origin.y })
|
|
227
198
|
},
|
|
228
199
|
onDragStart: ([x, y], state) => {
|
|
229
200
|
// The press decides the selection before the snapshot is taken, so it
|
|
230
201
|
// happens here rather than in an onPointerDown: a native listener on
|
|
231
202
|
// the element runs before React's, and the two would disagree.
|
|
232
|
-
beginPointDrag(id, state.event)
|
|
203
|
+
editor.beginPointDrag(id, state.event)
|
|
233
204
|
pointerOrigin.current = { x, y }
|
|
234
205
|
xInputRef.current?.focus()
|
|
235
206
|
|
|
@@ -244,43 +215,27 @@ export function Point<T extends PointBaseType>({
|
|
|
244
215
|
},
|
|
245
216
|
})
|
|
246
217
|
|
|
247
|
-
const nudge = useCallback(
|
|
248
|
-
(
|
|
249
|
-
axis: 'x' | 'y',
|
|
250
|
-
direction: number,
|
|
251
|
-
option: ModifierValue<InputEventOption>,
|
|
252
|
-
modifiers: ModifierState,
|
|
253
|
-
) => {
|
|
254
|
-
const next = applyDelta(value[axis], direction, option, AXIS, modifiers)
|
|
255
|
-
// As a move, so that the rest of the selection comes along and the whole
|
|
256
|
-
// group stops together at the edge.
|
|
257
|
-
nudgeSelection(id, {
|
|
258
|
-
x: axis === 'x' ? next - value.x : 0,
|
|
259
|
-
y: axis === 'y' ? next - value.y : 0,
|
|
260
|
-
})
|
|
261
|
-
},
|
|
262
|
-
[value, id, nudgeSelection],
|
|
263
|
-
)
|
|
264
|
-
|
|
265
218
|
const refCallback = useComposedRefs<HTMLDivElement>(
|
|
266
219
|
dragRefCallback,
|
|
267
220
|
setElement,
|
|
268
221
|
)
|
|
269
222
|
|
|
270
223
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
// no keys at all.
|
|
278
|
-
const axis = key === 'ArrowRight' || key === 'ArrowLeft' ? 'x' : 'y'
|
|
224
|
+
// The key picks the axis, whichever of the two inputs holds the focus:
|
|
225
|
+
// the focus lands on the x input, so reading the axis off the input would
|
|
226
|
+
// leave the y axis with no keys at all. y grows downwards, so ArrowUp
|
|
227
|
+
// moves the point towards 0.
|
|
228
|
+
const move = arrowKeyMove(event.key)
|
|
229
|
+
if (!move) return
|
|
279
230
|
event.preventDefault()
|
|
280
231
|
if (!onChange || inactive || !keyboard) return
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
232
|
+
editor.nudgePoint(
|
|
233
|
+
id,
|
|
234
|
+
move.axis === 0 ? 'x' : 'y',
|
|
235
|
+
move.direction,
|
|
236
|
+
keyboard,
|
|
237
|
+
event,
|
|
238
|
+
)
|
|
284
239
|
}
|
|
285
240
|
|
|
286
241
|
const current = clampPoint(value, min, max)
|
|
@@ -304,8 +259,6 @@ export function Point<T extends PointBaseType>({
|
|
|
304
259
|
style={
|
|
305
260
|
{
|
|
306
261
|
'--color': color,
|
|
307
|
-
'--width': cssLength(size ?? width),
|
|
308
|
-
'--height': cssLength(size ?? height),
|
|
309
262
|
// The mechanics of the position below: a point is placed by its
|
|
310
263
|
// position in the container, measured from its own centre.
|
|
311
264
|
position: 'absolute',
|
|
@@ -351,7 +304,7 @@ export function Point<T extends PointBaseType>({
|
|
|
351
304
|
return
|
|
352
305
|
}
|
|
353
306
|
const delta = event.currentTarget.valueAsNumber - value[axis]
|
|
354
|
-
nudgeSelection(id, {
|
|
307
|
+
editor.nudgeSelection(id, {
|
|
355
308
|
x: axis === 'x' ? delta : 0,
|
|
356
309
|
y: axis === 'y' ? delta : 0,
|
|
357
310
|
})
|
|
@@ -2,32 +2,11 @@ import { createContext, CSSProperties, RefObject, useContext } from 'react'
|
|
|
2
2
|
|
|
3
3
|
import {
|
|
4
4
|
type InputEventOption,
|
|
5
|
-
type ModifierState,
|
|
6
5
|
type ModifierValue,
|
|
6
|
+
type PointsEditorInstance,
|
|
7
7
|
type SelectionBoxRect,
|
|
8
8
|
} from '@tremolo-ui/dom'
|
|
9
9
|
|
|
10
|
-
import type { PointBaseType } from './Point'
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* What a `Point` tells the editor about itself, so that a selection can be
|
|
14
|
-
* moved without the editor knowing how the points are stored.
|
|
15
|
-
*
|
|
16
|
-
* Held behind a ref and rewritten on every render: the value changes on every
|
|
17
|
-
* frame of a drag, and a registry keyed on it would be rebuilt just as often.
|
|
18
|
-
*/
|
|
19
|
-
export interface PointRegistration {
|
|
20
|
-
value: PointBaseType
|
|
21
|
-
min?: Partial<PointBaseType>
|
|
22
|
-
max?: Partial<PointBaseType>
|
|
23
|
-
readonly: boolean
|
|
24
|
-
onChange?: (value: PointBaseType) => void
|
|
25
|
-
/** The point's own element, to match the focus against. */
|
|
26
|
-
element: HTMLElement | null
|
|
27
|
-
/** The wheel option this point resolved, root inheritance applied. */
|
|
28
|
-
wheel: ModifierValue<InputEventOption> | null
|
|
29
|
-
}
|
|
30
|
-
|
|
31
10
|
export type PointsEditorContextValue = {
|
|
32
11
|
disabled: boolean
|
|
33
12
|
readonly: boolean
|
|
@@ -40,53 +19,23 @@ export type PointsEditorContextValue = {
|
|
|
40
19
|
externalStyles: {
|
|
41
20
|
cursor?: CSSProperties['cursor']
|
|
42
21
|
}
|
|
43
|
-
|
|
44
22
|
/**
|
|
45
23
|
* `Container` registers its element here; `Point` normalizes the pointer
|
|
46
24
|
* against it, so a point is placed by its position within the container.
|
|
47
25
|
*/
|
|
48
26
|
containerRef: RefObject<HTMLDivElement | null>
|
|
49
|
-
|
|
50
27
|
/** Whether points can be selected at all. See `PointsEditorProps.selectable`. */
|
|
51
28
|
selectable: boolean
|
|
52
29
|
/** Ids of the points currently selected. Always empty while `selectable` is off. */
|
|
53
30
|
selection: readonly string[]
|
|
54
|
-
/** Register a point so that a selection can move it with the rest. */
|
|
55
|
-
registerPoint: (id: string, entry: RefObject<PointRegistration>) => () => void
|
|
56
|
-
/**
|
|
57
|
-
* Whether the element is a point, or inside one. The registry knows which
|
|
58
|
-
* elements the points are, so nothing has to be recognised by a class name.
|
|
59
|
-
*/
|
|
60
|
-
isPointElement: (element: Element | null | undefined) => boolean
|
|
61
|
-
/**
|
|
62
|
-
* A pointer went down on a point: works out the new selection and takes the
|
|
63
|
-
* snapshot the move will be measured against.
|
|
64
|
-
*/
|
|
65
|
-
beginPointDrag: (id: string, modifiers: ModifierState) => void
|
|
66
|
-
/** Move everything the drag picked up, by one amount, clamped as one. */
|
|
67
|
-
movePointDrag: (delta: PointBaseType) => void
|
|
68
|
-
/**
|
|
69
|
-
* Move the selection by an amount that did not come from a drag — an arrow
|
|
70
|
-
* key or a wheel notch. The current values are the starting point.
|
|
71
|
-
*/
|
|
72
|
-
nudgeSelection: (id: string, delta: PointBaseType) => void
|
|
73
31
|
/**
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* took it, so the caller knows whether to consume the event.
|
|
32
|
+
* The selection and the moves, from `createPointsEditor` in
|
|
33
|
+
* `@tremolo-ui/dom`: every `Point` registers with it, and a press, a drag,
|
|
34
|
+
* a key or the wheel goes through it so that a selection moves as one.
|
|
78
35
|
*/
|
|
79
|
-
|
|
80
|
-
axis: 'x' | 'y',
|
|
81
|
-
direction: number,
|
|
82
|
-
modifiers: ModifierState,
|
|
83
|
-
) => boolean
|
|
84
|
-
|
|
36
|
+
editor: PointsEditorInstance
|
|
85
37
|
/** The selection box, while one is being dragged. */
|
|
86
38
|
selectionBox: SelectionBoxRect | null
|
|
87
|
-
beginSelectionBox: (at: PointBaseType, modifiers: ModifierState) => void
|
|
88
|
-
moveSelectionBox: (to: PointBaseType) => void
|
|
89
|
-
endSelectionBox: () => void
|
|
90
39
|
}
|
|
91
40
|
|
|
92
41
|
const PointsEditorContext =
|