@tremolo-ui/react 0.7.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 +666 -1171
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +316 -315
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +316 -315
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +643 -1123
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/src/components/AnimationCanvas/index.tsx +4 -18
- package/src/components/DropZone/index.tsx +2 -1
- package/src/components/FileInput/index.tsx +11 -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 +21 -20
- package/src/components/Knob/context.tsx +3 -74
- package/src/components/Knob/index.tsx +99 -52
- package/src/components/NumberInput/InputField.tsx +20 -33
- package/src/components/NumberInput/Stepper.tsx +76 -100
- package/src/components/NumberInput/StepperButton.tsx +15 -8
- package/src/components/NumberInput/context.tsx +26 -3
- package/src/components/NumberInput/index.tsx +175 -91
- package/src/components/Piano/index.tsx +54 -144
- package/src/components/PointsEditor/Container.tsx +15 -26
- package/src/components/PointsEditor/Point.tsx +94 -110
- package/src/components/PointsEditor/context.tsx +8 -60
- package/src/components/PointsEditor/index.tsx +41 -324
- package/src/components/Slider/Marks.tsx +5 -6
- package/src/components/Slider/MarksOption.tsx +9 -19
- package/src/components/Slider/Thumb.tsx +20 -11
- package/src/components/Slider/Track.tsx +16 -13
- package/src/components/Slider/context.tsx +3 -3
- package/src/components/Slider/index.tsx +110 -72
- package/src/components/XYPad/Area.tsx +27 -49
- package/src/components/XYPad/Thumb.tsx +20 -8
- package/src/components/XYPad/context.tsx +2 -4
- package/src/components/XYPad/index.tsx +102 -83
- package/src/components/_util/VisuallyHiddenRangeInput.tsx +1 -1
- package/src/hooks/_internal/useChangeGesture.ts +93 -0
- package/src/hooks/_internal/useCheckSteps.ts +9 -146
- package/src/hooks/useAnimationFrame.ts +37 -15
- package/src/hooks/useEventListener.ts +93 -22
- package/src/hooks/useLongPress.ts +35 -51
- package/src/hooks/useMIDIAccess.ts +0 -8
- package/src/hooks/useMIDIInput.ts +0 -3
- package/src/hooks/useWheel.ts +4 -4
- package/src/index.ts +11 -32
- 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/hooks/useInterval.ts +0 -27
- 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,30 @@ import {
|
|
|
9
8
|
} from 'react'
|
|
10
9
|
|
|
11
10
|
import {
|
|
12
|
-
|
|
11
|
+
type ChangeSource,
|
|
13
12
|
type InputEventOption,
|
|
14
|
-
type ModifierState,
|
|
15
13
|
type ModifierValue,
|
|
16
|
-
|
|
14
|
+
type PointPosition,
|
|
15
|
+
type PointsEditorPoint,
|
|
17
16
|
} from '@tremolo-ui/dom'
|
|
18
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
arrowKeyMove,
|
|
19
|
+
clampPoint,
|
|
20
|
+
POINT_AXIS,
|
|
21
|
+
selectModifier,
|
|
22
|
+
} from '@tremolo-ui/dom/internal'
|
|
19
23
|
|
|
20
24
|
import { useComposedRefs } from '../../compose-refs'
|
|
25
|
+
import { useChangeGesture } from '../../hooks/_internal/useChangeGesture'
|
|
21
26
|
import { useDragValue } from '../../hooks/useDragValue'
|
|
22
|
-
import { cssLength } from '../_util/css-length'
|
|
23
27
|
import { useCheckPlacement } from '../_util/Placement'
|
|
24
28
|
import { VisuallyHiddenRangeInput } from '../_util/VisuallyHiddenRangeInput'
|
|
25
29
|
|
|
26
|
-
import {
|
|
30
|
+
import { usePointsEditorContext } from './context'
|
|
27
31
|
|
|
28
32
|
import type { CSSVariables } from '../../css-variables'
|
|
29
33
|
|
|
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> {
|
|
34
|
+
export interface PointsEditorPointProps<T extends PointPosition> {
|
|
44
35
|
/**
|
|
45
36
|
* Where the point is, as `{ x, y }` from 0 to 1 on each axis, with `y`
|
|
46
37
|
* growing downwards.
|
|
@@ -53,23 +44,17 @@ export interface PointsEditorPointProps<T extends PointBaseType> {
|
|
|
53
44
|
*/
|
|
54
45
|
id?: string
|
|
55
46
|
/** The lowest position the point can take, per axis. An axis left out is 0. */
|
|
56
|
-
min?: Partial<
|
|
47
|
+
min?: Partial<PointPosition>
|
|
57
48
|
/** The highest position the point can take, per axis. An axis left out is 1. */
|
|
58
|
-
max?: Partial<
|
|
49
|
+
max?: Partial<PointPosition>
|
|
59
50
|
|
|
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
51
|
/** Sets `--color`, for the theme to colour the point with. */
|
|
67
52
|
color?: string
|
|
68
53
|
|
|
69
54
|
/** Overrides the `disabled` of `PointsEditor.Root`. */
|
|
70
55
|
disabled?: boolean
|
|
71
|
-
/** Overrides the `
|
|
72
|
-
|
|
56
|
+
/** Overrides the `readOnly` of `PointsEditor.Root`. */
|
|
57
|
+
readOnly?: boolean
|
|
73
58
|
|
|
74
59
|
/** Overrides the `wheel` of `PointsEditor.Root`. */
|
|
75
60
|
wheel?: ModifierValue<InputEventOption> | null
|
|
@@ -88,22 +73,30 @@ export interface PointsEditorPointProps<T extends PointBaseType> {
|
|
|
88
73
|
* Called with the new position when the point is dragged or moved by the
|
|
89
74
|
* arrow keys or the wheel, including when it moves along with a selection.
|
|
90
75
|
*/
|
|
91
|
-
onChange?: (value:
|
|
92
|
-
/**
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
76
|
+
onChange?: (value: PointPosition) => void
|
|
77
|
+
/**
|
|
78
|
+
* Called when a change of this point starts — a drag on it, the first wheel
|
|
79
|
+
* notch or arrow key while it has the focus — with where it is and what the
|
|
80
|
+
* change is made with. The points that move along with a selection report
|
|
81
|
+
* through `onChange` only: the change belongs to the point being operated.
|
|
82
|
+
*/
|
|
83
|
+
onChangeStart?: (value: PointPosition, source: ChangeSource) => void
|
|
84
|
+
/**
|
|
85
|
+
* Called when that change ends, with where the point is: on release, or
|
|
86
|
+
* `changeEndDelay` after the last wheel notch or arrow key.
|
|
87
|
+
*/
|
|
88
|
+
onChangeEnd?: (value: PointPosition, source: ChangeSource) => void
|
|
89
|
+
/**
|
|
90
|
+
* How long after the last wheel notch or arrow key the change counts as
|
|
91
|
+
* over, in milliseconds. Neither has an event that says it is done.
|
|
92
|
+
*
|
|
93
|
+
* @default 500
|
|
94
|
+
*/
|
|
95
|
+
changeEndDelay?: number
|
|
96
96
|
|
|
97
|
-
style?: CSSProperties &
|
|
98
|
-
CSSVariables<'width' | 'height' | 'color' | 'translate'>
|
|
97
|
+
style?: CSSProperties & CSSVariables<'color' | 'translate'>
|
|
99
98
|
}
|
|
100
99
|
|
|
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
100
|
/**
|
|
108
101
|
* One setting for both axes, or one per axis. The point holds a range input
|
|
109
102
|
* for each, so anything that names or describes it comes in a pair.
|
|
@@ -115,27 +108,25 @@ function perAxis(
|
|
|
115
108
|
return typeof value === 'string' ? value : value?.[axis]
|
|
116
109
|
}
|
|
117
110
|
|
|
118
|
-
export function Point<T extends
|
|
111
|
+
export function Point<T extends PointPosition>({
|
|
119
112
|
value,
|
|
120
113
|
children,
|
|
121
114
|
id: idProp,
|
|
122
115
|
min,
|
|
123
116
|
max,
|
|
124
|
-
size,
|
|
125
|
-
width,
|
|
126
|
-
height,
|
|
127
117
|
color,
|
|
128
118
|
|
|
129
119
|
disabled: _disabled,
|
|
130
|
-
|
|
120
|
+
readOnly: _readOnly,
|
|
131
121
|
wheel: _wheel,
|
|
132
122
|
keyboard: _keyboard,
|
|
133
123
|
'aria-label': ariaLabel,
|
|
134
124
|
'aria-valuetext': ariaValuetext,
|
|
135
125
|
|
|
136
126
|
onChange,
|
|
137
|
-
|
|
138
|
-
|
|
127
|
+
onChangeStart,
|
|
128
|
+
onChangeEnd,
|
|
129
|
+
changeEndDelay,
|
|
139
130
|
|
|
140
131
|
className,
|
|
141
132
|
style,
|
|
@@ -147,17 +138,14 @@ export function Point<T extends PointBaseType>({
|
|
|
147
138
|
Omit<ComponentPropsWithoutRef<'div'>, keyof PointsEditorPointProps<T>>) {
|
|
148
139
|
const {
|
|
149
140
|
containerRef,
|
|
150
|
-
|
|
141
|
+
dragCursor,
|
|
151
142
|
disabled: rootDisabled,
|
|
152
|
-
|
|
143
|
+
readOnly: rootReadOnly,
|
|
153
144
|
wheel: rootWheel,
|
|
154
145
|
keyboard: rootKeyboard,
|
|
155
146
|
dragSensitivity,
|
|
156
147
|
selection,
|
|
157
|
-
|
|
158
|
-
beginPointDrag,
|
|
159
|
-
movePointDrag,
|
|
160
|
-
nudgeSelection,
|
|
148
|
+
editor,
|
|
161
149
|
} = usePointsEditorContext()
|
|
162
150
|
|
|
163
151
|
const generatedId = useId()
|
|
@@ -165,8 +153,8 @@ export function Point<T extends PointBaseType>({
|
|
|
165
153
|
const selected = selection.includes(id)
|
|
166
154
|
|
|
167
155
|
const disabled = _disabled ?? rootDisabled
|
|
168
|
-
const
|
|
169
|
-
const inactive = disabled ||
|
|
156
|
+
const readOnly = _readOnly ?? rootReadOnly
|
|
157
|
+
const inactive = disabled || readOnly
|
|
170
158
|
// `null` means "no event" and has to survive the fallback, so `??` is not
|
|
171
159
|
// enough: only an omitted prop inherits from the root.
|
|
172
160
|
const wheel = _wheel === undefined ? rootWheel : _wheel
|
|
@@ -174,6 +162,19 @@ export function Point<T extends PointBaseType>({
|
|
|
174
162
|
|
|
175
163
|
useCheckPlacement('PointsEditor.Point', 'PointsEditor.Container')
|
|
176
164
|
|
|
165
|
+
const current = clampPoint(value, min, max)
|
|
166
|
+
const gesture = useChangeGesture(
|
|
167
|
+
current,
|
|
168
|
+
{ onChangeStart, onChangeEnd, changeEndDelay },
|
|
169
|
+
inactive,
|
|
170
|
+
)
|
|
171
|
+
// Every move reaches the point through here, whatever moved it, so the
|
|
172
|
+
// last one is what `onChangeEnd` gives.
|
|
173
|
+
const handleChange = (next: PointPosition) => {
|
|
174
|
+
gesture.changed(next)
|
|
175
|
+
onChange?.(next)
|
|
176
|
+
}
|
|
177
|
+
|
|
177
178
|
// Compared against the focus below, so the point needs its own element.
|
|
178
179
|
const [element, setElement] = useState<HTMLDivElement | null>(null)
|
|
179
180
|
const xInputRef = useRef<HTMLInputElement>(null)
|
|
@@ -182,14 +183,15 @@ export function Point<T extends PointBaseType>({
|
|
|
182
183
|
// What the editor needs to move this point along with the rest of a
|
|
183
184
|
// selection. Rewritten after every render rather than kept in the registry
|
|
184
185
|
// itself: the value changes on every frame of a drag.
|
|
185
|
-
const registration = useRef<
|
|
186
|
+
const registration = useRef<PointsEditorPoint>({
|
|
186
187
|
value,
|
|
187
188
|
min,
|
|
188
189
|
max,
|
|
189
190
|
readonly: inactive,
|
|
190
|
-
onChange,
|
|
191
|
+
onChange: onChange ? handleChange : undefined,
|
|
191
192
|
element,
|
|
192
193
|
wheel,
|
|
194
|
+
beforeWheel: () => gesture.pulse('wheel'),
|
|
193
195
|
})
|
|
194
196
|
useEffect(() => {
|
|
195
197
|
registration.current = {
|
|
@@ -197,25 +199,29 @@ export function Point<T extends PointBaseType>({
|
|
|
197
199
|
min,
|
|
198
200
|
max,
|
|
199
201
|
readonly: inactive,
|
|
200
|
-
onChange,
|
|
202
|
+
onChange: onChange ? handleChange : undefined,
|
|
201
203
|
element,
|
|
202
204
|
wheel,
|
|
205
|
+
beforeWheel: () => gesture.pulse('wheel'),
|
|
203
206
|
}
|
|
204
207
|
})
|
|
205
208
|
|
|
206
|
-
useEffect(
|
|
209
|
+
useEffect(
|
|
210
|
+
() => editor.registerPoint(id, () => registration.current),
|
|
211
|
+
[id, editor],
|
|
212
|
+
)
|
|
207
213
|
|
|
208
214
|
/** Where the pointer was when the drag started, to measure the move from. */
|
|
209
|
-
const pointerOrigin = useRef<
|
|
215
|
+
const pointerOrigin = useRef<PointPosition | null>(null)
|
|
210
216
|
|
|
211
217
|
// The value is the position itself: no scaling, and no rounding to a step.
|
|
212
218
|
const { refCallback: dragRefCallback, dragging } =
|
|
213
219
|
useDragValue<HTMLDivElement>({
|
|
214
|
-
axis:
|
|
220
|
+
axis: POINT_AXIS,
|
|
215
221
|
baseElementRef: containerRef,
|
|
216
222
|
sensitivity: (state) =>
|
|
217
223
|
selectModifier(dragSensitivity, state.event).value,
|
|
218
|
-
cursor: inactive ? undefined :
|
|
224
|
+
cursor: inactive ? undefined : dragCursor,
|
|
219
225
|
shouldStart: () => !disabled,
|
|
220
226
|
// The value is a move rather than a position: the point keeps the offset
|
|
221
227
|
// it was grabbed at, and everything else selected moves with it by the
|
|
@@ -223,68 +229,48 @@ export function Point<T extends PointBaseType>({
|
|
|
223
229
|
onChange: ([x, y]) => {
|
|
224
230
|
const origin = pointerOrigin.current
|
|
225
231
|
if (!origin) return
|
|
226
|
-
movePointDrag({ x: x - origin.x, y: y - origin.y })
|
|
232
|
+
editor.movePointDrag({ x: x - origin.x, y: y - origin.y })
|
|
227
233
|
},
|
|
228
234
|
onDragStart: ([x, y], state) => {
|
|
229
235
|
// The press decides the selection before the snapshot is taken, so it
|
|
230
236
|
// happens here rather than in an onPointerDown: a native listener on
|
|
231
237
|
// the element runs before React's, and the two would disagree.
|
|
232
|
-
beginPointDrag(id, state.event)
|
|
238
|
+
editor.beginPointDrag(id, state.event)
|
|
233
239
|
pointerOrigin.current = { x, y }
|
|
234
240
|
xInputRef.current?.focus()
|
|
235
241
|
|
|
236
|
-
if (inactive)
|
|
237
|
-
onDragStart?.(clampPoint(value, min, max))
|
|
242
|
+
if (!inactive) gesture.hold('pointer')
|
|
238
243
|
},
|
|
239
244
|
onDragEnd: () => {
|
|
240
245
|
pointerOrigin.current = null
|
|
241
|
-
|
|
242
|
-
if (inactive) return
|
|
243
|
-
onDragEnd?.(clampPoint(value, min, max))
|
|
246
|
+
gesture.end()
|
|
244
247
|
},
|
|
245
248
|
})
|
|
246
249
|
|
|
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
250
|
const refCallback = useComposedRefs<HTMLDivElement>(
|
|
266
251
|
dragRefCallback,
|
|
267
252
|
setElement,
|
|
268
253
|
)
|
|
269
254
|
|
|
270
255
|
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'
|
|
256
|
+
// The key picks the axis, whichever of the two inputs holds the focus:
|
|
257
|
+
// the focus lands on the x input, so reading the axis off the input would
|
|
258
|
+
// leave the y axis with no keys at all. y grows downwards, so ArrowUp
|
|
259
|
+
// moves the point towards 0.
|
|
260
|
+
const move = arrowKeyMove(event.key)
|
|
261
|
+
if (!move) return
|
|
279
262
|
event.preventDefault()
|
|
280
263
|
if (!onChange || inactive || !keyboard) return
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
264
|
+
gesture.pulse('keyboard')
|
|
265
|
+
editor.nudgePoint(
|
|
266
|
+
id,
|
|
267
|
+
move.axis === 0 ? 'x' : 'y',
|
|
268
|
+
move.direction,
|
|
269
|
+
keyboard,
|
|
270
|
+
event,
|
|
271
|
+
)
|
|
284
272
|
}
|
|
285
273
|
|
|
286
|
-
const current = clampPoint(value, min, max)
|
|
287
|
-
|
|
288
274
|
return (
|
|
289
275
|
// The visual point has two values, so its semantics live on the two range
|
|
290
276
|
// inputs nested inside it rather than on this drag handle.
|
|
@@ -293,7 +279,7 @@ export function Point<T extends PointBaseType>({
|
|
|
293
279
|
ref={refCallback}
|
|
294
280
|
className={className}
|
|
295
281
|
data-disabled={disabled ? '' : undefined}
|
|
296
|
-
data-readonly={
|
|
282
|
+
data-readonly={readOnly ? '' : undefined}
|
|
297
283
|
// A press lands on the point, which cannot hold focus, and the browser
|
|
298
284
|
// answers that by clearing the focus to the body — undoing the focus the
|
|
299
285
|
// drag just gave the input. Taking the focus here keeps it inside, and
|
|
@@ -304,8 +290,6 @@ export function Point<T extends PointBaseType>({
|
|
|
304
290
|
style={
|
|
305
291
|
{
|
|
306
292
|
'--color': color,
|
|
307
|
-
'--width': cssLength(size ?? width),
|
|
308
|
-
'--height': cssLength(size ?? height),
|
|
309
293
|
// The mechanics of the position below: a point is placed by its
|
|
310
294
|
// position in the container, measured from its own centre.
|
|
311
295
|
position: 'absolute',
|
|
@@ -341,17 +325,17 @@ export function Point<T extends PointBaseType>({
|
|
|
341
325
|
max={max?.[axis] ?? 1}
|
|
342
326
|
step="any"
|
|
343
327
|
disabled={disabled}
|
|
344
|
-
aria-readonly={
|
|
328
|
+
aria-readonly={readOnly}
|
|
345
329
|
aria-orientation={axis === 'x' ? 'horizontal' : 'vertical'}
|
|
346
330
|
aria-label={perAxis(ariaLabel, axis) ?? axis}
|
|
347
331
|
aria-valuetext={perAxis(ariaValuetext, axis)}
|
|
348
332
|
onChange={(event) => {
|
|
349
|
-
if (
|
|
333
|
+
if (readOnly) {
|
|
350
334
|
event.currentTarget.value = String(current[axis])
|
|
351
335
|
return
|
|
352
336
|
}
|
|
353
337
|
const delta = event.currentTarget.valueAsNumber - value[axis]
|
|
354
|
-
nudgeSelection(id, {
|
|
338
|
+
editor.nudgeSelection(id, {
|
|
355
339
|
x: axis === 'x' ? delta : 0,
|
|
356
340
|
y: axis === 'y' ? delta : 0,
|
|
357
341
|
})
|
|
@@ -2,91 +2,39 @@ 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
|
|
34
13
|
/** Inherited by every `Point`; `null` turns the wheel off. */
|
|
35
14
|
wheel: ModifierValue<InputEventOption> | null
|
|
36
15
|
/** Inherited by every `Point`; `null` turns the keyboard off. */
|
|
37
16
|
keyboard: ModifierValue<InputEventOption> | null
|
|
38
17
|
/** Inherited by every `Point`. See `PointsEditorProps.dragSensitivity`. */
|
|
39
18
|
dragSensitivity: ModifierValue<number>
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
|
|
19
|
+
/** Inherited by every `Point`. See `PointsEditorProps.dragCursor`. */
|
|
20
|
+
dragCursor: CSSProperties['cursor']
|
|
44
21
|
/**
|
|
45
22
|
* `Container` registers its element here; `Point` normalizes the pointer
|
|
46
23
|
* against it, so a point is placed by its position within the container.
|
|
47
24
|
*/
|
|
48
25
|
containerRef: RefObject<HTMLDivElement | null>
|
|
49
|
-
|
|
50
26
|
/** Whether points can be selected at all. See `PointsEditorProps.selectable`. */
|
|
51
27
|
selectable: boolean
|
|
52
28
|
/** Ids of the points currently selected. Always empty while `selectable` is off. */
|
|
53
29
|
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
30
|
/**
|
|
57
|
-
*
|
|
58
|
-
*
|
|
31
|
+
* The selection and the moves, from `createPointsEditor` in
|
|
32
|
+
* `@tremolo-ui/dom`: every `Point` registers with it, and a press, a drag,
|
|
33
|
+
* a key or the wheel goes through it so that a selection moves as one.
|
|
59
34
|
*/
|
|
60
|
-
|
|
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
|
-
/**
|
|
74
|
-
* Move the point that currently holds focus by one notch. The wheel listener
|
|
75
|
-
* belongs to `Container` rather than to every `Point`, so the lookup of who
|
|
76
|
-
* has focus happens here, where the registry is. Returns whether a point
|
|
77
|
-
* took it, so the caller knows whether to consume the event.
|
|
78
|
-
*/
|
|
79
|
-
nudgeFocusedPoint: (
|
|
80
|
-
axis: 'x' | 'y',
|
|
81
|
-
direction: number,
|
|
82
|
-
modifiers: ModifierState,
|
|
83
|
-
) => boolean
|
|
84
|
-
|
|
35
|
+
editor: PointsEditorInstance
|
|
85
36
|
/** The selection box, while one is being dragged. */
|
|
86
37
|
selectionBox: SelectionBoxRect | null
|
|
87
|
-
beginSelectionBox: (at: PointBaseType, modifiers: ModifierState) => void
|
|
88
|
-
moveSelectionBox: (to: PointBaseType) => void
|
|
89
|
-
endSelectionBox: () => void
|
|
90
38
|
}
|
|
91
39
|
|
|
92
40
|
const PointsEditorContext =
|