@tremolo-ui/dom 0.4.0 → 0.6.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 +934 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +580 -31
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +580 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +923 -90
- package/dist/index.js.map +1 -1
- package/package.json +6 -2
- package/src/canvas/animation.ts +322 -0
- package/src/canvas/context.ts +108 -0
- package/src/index.ts +48 -1
- package/src/midi/access.ts +83 -13
- package/src/midi/input.ts +97 -26
- package/src/midi/message.ts +37 -8
- package/src/piano/index.ts +205 -0
- package/src/pointer/drag-value.ts +365 -0
- package/src/pointer/drag.ts +340 -65
- package/src/pointer/wheel.ts +36 -1
- package/src/selection/box.ts +136 -0
- package/src/xy.ts +38 -0
package/dist/index.d.cts
CHANGED
|
@@ -1,17 +1,150 @@
|
|
|
1
|
+
import { PianoLayout, ValueRange } from "@tremolo-ui/functions";
|
|
2
|
+
//#region src/canvas/animation.d.ts
|
|
3
|
+
/** What the canvas looked like when a frame was drawn. */
|
|
4
|
+
interface AnimationFrame {
|
|
5
|
+
/** Width of the canvas in CSS pixels. */
|
|
6
|
+
width: number;
|
|
7
|
+
/** Height of the canvas in CSS pixels. */
|
|
8
|
+
height: number;
|
|
9
|
+
/** Frames drawn so far, starting at 0. */
|
|
10
|
+
count: number;
|
|
11
|
+
/** Milliseconds since the previous frame. */
|
|
12
|
+
deltaTime: number;
|
|
13
|
+
/** Milliseconds since the instance was created. */
|
|
14
|
+
elapsedTime: number;
|
|
15
|
+
/** Frames per second implied by `deltaTime`, or 0 when no time elapsed. */
|
|
16
|
+
fps: number;
|
|
17
|
+
}
|
|
18
|
+
type CanvasDrawFunction = (context: CanvasRenderingContext2D, frame: AnimationFrame) => void;
|
|
19
|
+
type CanvasInitFunction = (context: CanvasRenderingContext2D, size: {
|
|
20
|
+
width: number;
|
|
21
|
+
height: number;
|
|
22
|
+
}) => void;
|
|
23
|
+
interface AnimationCanvasOptions {
|
|
24
|
+
/** Called for every frame. */
|
|
25
|
+
draw: CanvasDrawFunction;
|
|
26
|
+
/** Called once, after the first size is known and before the first frame. */
|
|
27
|
+
init?: CanvasInitFunction;
|
|
28
|
+
/**
|
|
29
|
+
* Keep drawing on every animation frame. When off, a frame is drawn only on
|
|
30
|
+
* a resize or an explicit {@link AnimationCanvasInstance.redraw}.
|
|
31
|
+
*
|
|
32
|
+
* @default true
|
|
33
|
+
*/
|
|
34
|
+
animate?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Size in CSS pixels. Ignored when `resizable` is on.
|
|
37
|
+
*
|
|
38
|
+
* @default { width: 100, height: 100 }
|
|
39
|
+
*/
|
|
40
|
+
size?: {
|
|
41
|
+
width: number;
|
|
42
|
+
height: number;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Follow the size of the canvas's parent element instead of `size`.
|
|
46
|
+
*
|
|
47
|
+
* Fixed for the lifetime of the instance: it decides whether a
|
|
48
|
+
* `ResizeObserver` is attached.
|
|
49
|
+
*
|
|
50
|
+
* @default false
|
|
51
|
+
*/
|
|
52
|
+
resizable?: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Carry the drawing across a resize, so that the canvas does not blank for a
|
|
55
|
+
* frame while the new size is drawn.
|
|
56
|
+
*
|
|
57
|
+
* @default true
|
|
58
|
+
*/
|
|
59
|
+
reduceFlickering?: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Passed to `getContext('2d', …)`. Read once when the context is created, so
|
|
62
|
+
* it is fixed for the lifetime of the instance.
|
|
63
|
+
*
|
|
64
|
+
* @see https://developer.mozilla.org/docs/Web/API/HTMLCanvasElement/getContext#contextattributes
|
|
65
|
+
*/
|
|
66
|
+
contextAttributes?: CanvasRenderingContext2DSettings;
|
|
67
|
+
}
|
|
68
|
+
interface AnimationCanvasInstance {
|
|
69
|
+
/**
|
|
70
|
+
* Replace the given options. Lets a wrapper feed a fresh `draw` in on every
|
|
71
|
+
* render without restarting the animation, so the frame count and the
|
|
72
|
+
* elapsed time keep running.
|
|
73
|
+
*
|
|
74
|
+
* `resizable` and `contextAttributes` are fixed for the lifetime of the
|
|
75
|
+
* instance and are ignored here.
|
|
76
|
+
*
|
|
77
|
+
* While `animate` is off this also draws a frame, since nothing else would.
|
|
78
|
+
*/
|
|
79
|
+
update: (options: Partial<AnimationCanvasOptions>) => void;
|
|
80
|
+
/** Draw one frame now, whether or not `animate` is on. */
|
|
81
|
+
redraw: () => void;
|
|
82
|
+
destroy: () => void;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Drive a canvas from `requestAnimationFrame`, keeping its backing store in
|
|
86
|
+
* step with the device pixel ratio and, optionally, with the size of its
|
|
87
|
+
* parent.
|
|
88
|
+
*
|
|
89
|
+
* Drawing code works in CSS pixels: the context is scaled by the device pixel
|
|
90
|
+
* ratio, and `width` / `height` of each {@link AnimationFrame} are CSS pixels.
|
|
91
|
+
*/
|
|
92
|
+
declare function createAnimationCanvas(canvas: HTMLCanvasElement, options: AnimationCanvasOptions): AnimationCanvasInstance;
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/canvas/context.d.ts
|
|
95
|
+
/**
|
|
96
|
+
* The assignable parts of a 2D context's drawing state. They have to be
|
|
97
|
+
* carried across a resize by hand because setting `canvas.width` resets the
|
|
98
|
+
* context to its defaults. The transform and line dash are handled separately
|
|
99
|
+
* by {@link DrawingContext} because they are exposed through methods.
|
|
100
|
+
*
|
|
101
|
+
* @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/save
|
|
102
|
+
*/
|
|
103
|
+
declare const drawingState: readonly ["strokeStyle", "fillStyle", "globalAlpha", "lineWidth", "lineCap", "lineJoin", "miterLimit", "lineDashOffset", "shadowOffsetX", "shadowOffsetY", "shadowBlur", "shadowColor", "globalCompositeOperation", "filter", "font", "fontKerning", "fontStretch", "fontVariantCaps", "textAlign", "textBaseline", "direction", "letterSpacing", "textRendering", "wordSpacing", "imageSmoothingEnabled", "imageSmoothingQuality"];
|
|
104
|
+
type DrawingState = (typeof drawingState)[number];
|
|
105
|
+
type DrawingStateValue = CanvasRenderingContext2D[DrawingState];
|
|
106
|
+
type DrawingContext = Pick<CanvasRenderingContext2D, DrawingState> & {
|
|
107
|
+
/** The current line dash sequence. */
|
|
108
|
+
lineDash: number[];
|
|
109
|
+
/** The current transformation matrix. */
|
|
110
|
+
transform: DOMMatrix;
|
|
111
|
+
};
|
|
112
|
+
declare function isDrawingState(value: unknown): value is DrawingState;
|
|
113
|
+
//#endregion
|
|
1
114
|
//#region src/midi/access.d.ts
|
|
2
115
|
/** @private */
|
|
3
116
|
declare const PERMISSION_DENIED = "PERMISSION_DENIED";
|
|
4
117
|
/** @private */
|
|
5
118
|
declare const NOT_SUPPORTED = "NOT_SUPPORTED";
|
|
6
119
|
/** @private */
|
|
7
|
-
|
|
120
|
+
declare const UNAVAILABLE = "UNAVAILABLE";
|
|
121
|
+
/** @private */
|
|
122
|
+
type MIDIAccessError = typeof PERMISSION_DENIED | typeof NOT_SUPPORTED | typeof UNAVAILABLE;
|
|
123
|
+
type MIDIAccessOptions = {
|
|
124
|
+
/**
|
|
125
|
+
* Ask for system exclusive messages as well.
|
|
126
|
+
*
|
|
127
|
+
* Browsers treat this as a separate, more sensitive permission, so leave it
|
|
128
|
+
* off unless the app actually reads or sends sysex.
|
|
129
|
+
*
|
|
130
|
+
* @default false
|
|
131
|
+
*/
|
|
132
|
+
sysex?: boolean;
|
|
133
|
+
};
|
|
8
134
|
type MIDIAccessState = {
|
|
9
135
|
readonly midiAccess: MIDIAccess | null;
|
|
10
136
|
readonly error: MIDIAccessError | null;
|
|
137
|
+
/**
|
|
138
|
+
* The inputs currently connected, in the order MIDIAccess lists them.
|
|
139
|
+
*
|
|
140
|
+
* Kept up to date as devices are plugged in and unplugged, so a UI listing
|
|
141
|
+
* the devices does not have to watch `statechange` itself.
|
|
142
|
+
*/
|
|
143
|
+
readonly inputs: readonly MIDIInput[];
|
|
11
144
|
};
|
|
12
145
|
interface MIDIAccessInstance {
|
|
13
146
|
/** Request MIDI access. Safe to call more than once. */
|
|
14
|
-
request: () => void;
|
|
147
|
+
request: (options?: MIDIAccessOptions) => void;
|
|
15
148
|
getState: () => MIDIAccessState;
|
|
16
149
|
/** Snapshot for server side rendering. Always the initial state. */
|
|
17
150
|
getServerState: () => MIDIAccessState;
|
|
@@ -27,64 +160,239 @@ interface MIDIAccessInstance {
|
|
|
27
160
|
*/
|
|
28
161
|
declare function createMIDIAccess(): MIDIAccessInstance;
|
|
29
162
|
//#endregion
|
|
163
|
+
//#region src/midi/input.d.ts
|
|
164
|
+
/**
|
|
165
|
+
* Centre of the 14-bit pitch bend range: no bend.
|
|
166
|
+
*
|
|
167
|
+
* The range is not symmetric — 0 is 8192 below centre and 16383 is 8191 above
|
|
168
|
+
* — so a wheel at rest reports exactly this rather than half of the maximum.
|
|
169
|
+
*/
|
|
170
|
+
declare const PITCH_BEND_CENTER = 8192;
|
|
171
|
+
/**
|
|
172
|
+
* Every handler is given the channel last, as 0-15. MIDI channels are written
|
|
173
|
+
* 1-16 on hardware, so add one before showing it to anyone.
|
|
174
|
+
*/
|
|
175
|
+
type MIDIInputHandlers = {
|
|
176
|
+
onNoteOnEvent?: (note: number, velocity: number, channel: number) => void;
|
|
177
|
+
onNoteOffEvent?: (note: number, channel: number) => void;
|
|
178
|
+
/**
|
|
179
|
+
* The 14-bit bend, 0-16383, centred at {@link PITCH_BEND_CENTER}.
|
|
180
|
+
*
|
|
181
|
+
* The two data bytes are little-endian — the first carries the low 7 bits —
|
|
182
|
+
* which is the other way round from every other message.
|
|
183
|
+
*/
|
|
184
|
+
onPitchBendEvent?: (value: number, channel: number) => void;
|
|
185
|
+
/** `controller` is the CC number, `value` is 0-127. */
|
|
186
|
+
onControlChangeEvent?: (controller: number, value: number, channel: number) => void;
|
|
187
|
+
onProgramChangeEvent?: (program: number, channel: number) => void;
|
|
188
|
+
/** Pressure for one held note (polyphonic aftertouch). */
|
|
189
|
+
onAftertouchEvent?: (note: number, pressure: number, channel: number) => void;
|
|
190
|
+
/** Pressure for the whole channel, sent by keyboards with one sensor. */
|
|
191
|
+
onChannelPressureEvent?: (pressure: number, channel: number) => void;
|
|
192
|
+
};
|
|
193
|
+
interface MIDIInputInstance {
|
|
194
|
+
/** Replace the handlers, keeping the listeners in place. */
|
|
195
|
+
update: (handlers: MIDIInputHandlers) => void;
|
|
196
|
+
destroy: () => void;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Handle the channel voice messages of every connected input. To be used with
|
|
200
|
+
* {@link createMIDIAccess}. Internally uses {@link createMIDIMessage}, so
|
|
201
|
+
* devices plugged in later are picked up.
|
|
202
|
+
*
|
|
203
|
+
* System messages (clock, sysex, and the rest of `0xf0`-`0xff`) are not
|
|
204
|
+
* decoded here; reach for {@link createMIDIMessage} for those.
|
|
205
|
+
*/
|
|
206
|
+
declare function createMIDIInput(midiAccess: MIDIAccess | null, handlers: MIDIInputHandlers): MIDIInputInstance;
|
|
207
|
+
//#endregion
|
|
30
208
|
//#region src/midi/message.d.ts
|
|
31
209
|
interface MIDIMessageInstance {
|
|
210
|
+
/** Replace the handler, keeping the listeners in place. */
|
|
211
|
+
update: (onMIDIMessage: (event: MIDIMessageEvent) => void) => void;
|
|
32
212
|
destroy: () => void;
|
|
33
213
|
}
|
|
34
214
|
/**
|
|
35
215
|
* Listen to raw `midimessage` events on every input of a MIDIAccess.
|
|
36
216
|
*
|
|
217
|
+
* The set of inputs is followed rather than sampled: MIDIAccess fires
|
|
218
|
+
* `statechange` when a device is plugged in or unplugged, and the listeners
|
|
219
|
+
* move with it. A keyboard connected after access was granted works without
|
|
220
|
+
* the caller having to rebuild anything.
|
|
221
|
+
*
|
|
37
222
|
* Use this when you need more detail than {@link createMIDIInput} provides.
|
|
38
223
|
*/
|
|
39
224
|
declare function createMIDIMessage(midiAccess: MIDIAccess | null, onMIDIMessage: (event: MIDIMessageEvent) => void): MIDIMessageInstance;
|
|
40
225
|
//#endregion
|
|
41
|
-
//#region src/
|
|
42
|
-
type MIDIInputHandlers = {
|
|
43
|
-
onNoteOnEvent?: (note: number, velocity: number) => void;
|
|
44
|
-
onNoteOffEvent?: (note: number) => void;
|
|
45
|
-
onPitchBendEvent?: (msb: number, lsb: number) => void;
|
|
46
|
-
};
|
|
47
|
-
type MIDIInputInstance = MIDIMessageInstance;
|
|
226
|
+
//#region src/piano/index.d.ts
|
|
48
227
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
228
|
+
* What asked for a note to sound.
|
|
229
|
+
*
|
|
230
|
+
* A note stops only once everything that asked for it has let go, so two
|
|
231
|
+
* fingers on one key, or a key held by both the mouse and a MIDI keyboard,
|
|
232
|
+
* behave the way they look.
|
|
233
|
+
*
|
|
234
|
+
* Pointers use `pointer:<pointerId>`; everything else names its own source.
|
|
51
235
|
*/
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
236
|
+
type NoteSource = string;
|
|
237
|
+
interface PianoInputOptions {
|
|
238
|
+
/** Geometry of the drawn keyboard, used to find the note under a pointer. */
|
|
239
|
+
layout: PianoLayout;
|
|
240
|
+
/**
|
|
241
|
+
* Let a pointer slide from one key to the next while it is down. With it off,
|
|
242
|
+
* the key that was pressed keeps sounding until the pointer is released.
|
|
243
|
+
*
|
|
244
|
+
* @default true
|
|
245
|
+
*/
|
|
246
|
+
glissando?: boolean;
|
|
247
|
+
/**
|
|
248
|
+
* Highest note that can sound. Above it a note is refused, whichever source
|
|
249
|
+
* asks for it.
|
|
250
|
+
*
|
|
251
|
+
* @default 127
|
|
252
|
+
*/
|
|
253
|
+
midiMax?: number;
|
|
254
|
+
/** Called when a note starts sounding, not for each source that asks. */
|
|
255
|
+
onPlayNote?: (note: number, velocity?: number) => void;
|
|
256
|
+
/** Called once the last source holding a note has let go. */
|
|
257
|
+
onStopNote?: (note: number) => void;
|
|
258
|
+
/** Called whenever {@link PianoInputInstance.activeNotes} would change. */
|
|
259
|
+
onActiveNotesChange?: (notes: number[]) => void;
|
|
260
|
+
}
|
|
261
|
+
interface PianoInputInstance {
|
|
262
|
+
/**
|
|
263
|
+
* Replace the given options. Lets a wrapper feed a fresh layout in without
|
|
264
|
+
* tearing down the listeners, which would abort a drag in progress.
|
|
265
|
+
*/
|
|
266
|
+
update: (options: Partial<PianoInputOptions>) => void;
|
|
267
|
+
/**
|
|
268
|
+
* Start a note from something other than a pointer: a keyboard shortcut, a
|
|
269
|
+
* MIDI message, an imperative call.
|
|
270
|
+
*/
|
|
271
|
+
noteOn: (note: number, options?: {
|
|
272
|
+
source?: NoteSource;
|
|
273
|
+
velocity?: number;
|
|
274
|
+
}) => void;
|
|
275
|
+
/** Release a note held by `source`. */
|
|
276
|
+
noteOff: (note: number, options?: {
|
|
277
|
+
source?: NoteSource;
|
|
278
|
+
}) => void;
|
|
279
|
+
/** The notes currently sounding, ascending. */
|
|
280
|
+
activeNotes: () => number[];
|
|
281
|
+
destroy: () => void;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Drive a piano keyboard with pointers, and own what is sounding.
|
|
285
|
+
*
|
|
286
|
+
* Every way of playing a note goes through the one instance — pointers here,
|
|
287
|
+
* keyboard shortcuts and MIDI through {@link PianoInputInstance.noteOn} — so
|
|
288
|
+
* there is a single answer to what is currently held, and drawing the keys is
|
|
289
|
+
* left entirely to the wrapper.
|
|
290
|
+
*
|
|
291
|
+
* Tracks every pointer at once, so a chord can be played with several fingers.
|
|
292
|
+
*/
|
|
293
|
+
declare function createPianoInput(element: Element, options: PianoInputOptions): PianoInputInstance;
|
|
57
294
|
//#endregion
|
|
58
295
|
//#region src/pointer/drag.d.ts
|
|
59
296
|
type DragState = {
|
|
60
|
-
/** Total movement from the drag start, in screen coordinates. */
|
|
61
|
-
|
|
297
|
+
/** Total movement from the drag start, in screen coordinates. */
|
|
298
|
+
x: number;
|
|
299
|
+
y: number;
|
|
300
|
+
/** Movement since the previous event, in screen coordinates. */
|
|
62
301
|
deltaX: number;
|
|
63
|
-
deltaY: number;
|
|
302
|
+
deltaY: number;
|
|
303
|
+
/** Pointer position in viewport coordinates. */
|
|
64
304
|
clientX: number;
|
|
65
305
|
clientY: number;
|
|
306
|
+
/**
|
|
307
|
+
* Which pointer this is. Always the same value for one drag; with
|
|
308
|
+
* {@link DragOptions.multiPointer} it tells concurrent drags apart.
|
|
309
|
+
*/
|
|
310
|
+
pointerId: number;
|
|
66
311
|
event: PointerEvent;
|
|
67
312
|
};
|
|
68
313
|
type DragOptions = {
|
|
69
314
|
/**
|
|
70
|
-
* Minimum movement in pixels before `onDrag` fires.
|
|
315
|
+
* Minimum movement in pixels before `onDrag` fires. Movement below it is
|
|
316
|
+
* carried over to the next event rather than discarded, so a slow drag still
|
|
317
|
+
* reports once it adds up.
|
|
318
|
+
*
|
|
71
319
|
* Prevents `onDrag` from firing on, for example, a double click.
|
|
72
|
-
* Values below 1 are clamped to 1.
|
|
73
320
|
*
|
|
74
|
-
* @default
|
|
321
|
+
* @default 0
|
|
75
322
|
*/
|
|
76
323
|
threshold?: number;
|
|
77
324
|
/**
|
|
78
325
|
* CSS cursor to show while dragging. Applied to the element itself: pointer
|
|
79
326
|
* capture keeps it in effect even once the pointer leaves the element, so
|
|
80
327
|
* there is no need to touch the document.
|
|
328
|
+
*
|
|
329
|
+
* With {@link DragOptions.multiPointer} it is set for the first pointer and
|
|
330
|
+
* restored once the last one is up.
|
|
81
331
|
*/
|
|
82
332
|
cursor?: string;
|
|
333
|
+
/**
|
|
334
|
+
* Decide whether a pointerdown starts a drag at all.
|
|
335
|
+
*
|
|
336
|
+
* Checked before anything else — **before the pointer is captured** — so
|
|
337
|
+
* declining here leaves the whole gesture to whatever else is listening.
|
|
338
|
+
* Deciding later would be too late: the capture has already been taken from
|
|
339
|
+
* the element that was going to handle it.
|
|
340
|
+
*
|
|
341
|
+
* The use for it is a drag on a container that also holds draggable things
|
|
342
|
+
* of its own, such as a rubber-band selection that must not begin on top of
|
|
343
|
+
* one of the objects it would select.
|
|
344
|
+
*/
|
|
345
|
+
shouldStart?: (event: PointerEvent) => boolean;
|
|
346
|
+
/**
|
|
347
|
+
* Hide the pointer and read its movement directly, instead of following it
|
|
348
|
+
* around the screen.
|
|
349
|
+
*
|
|
350
|
+
* A relative drag — a knob, a stepper — does not care where the pointer is,
|
|
351
|
+
* only how far it moved, and letting it wander has two costs: the cursor
|
|
352
|
+
* ends up far from what it is holding, and **the drag stops at the edge of
|
|
353
|
+
* the screen**, where the operating system pins the pointer and the
|
|
354
|
+
* coordinates stop changing. A fine drag reaches that edge quickly.
|
|
355
|
+
*
|
|
356
|
+
* **Not for a drag whose value is the position pointed at** — anything on
|
|
357
|
+
* `elementMapping`. `clientX` / `clientY` freeze while the pointer is
|
|
358
|
+
* locked, so there is no position left to read.
|
|
359
|
+
*
|
|
360
|
+
* The request needs a user gesture, which a pointerdown is, but it can still
|
|
361
|
+
* be refused; the drag then carries on as an ordinary one. Read on
|
|
362
|
+
* pointerdown, so `update()` reaches the next drag rather than the current.
|
|
363
|
+
*
|
|
364
|
+
* @default false
|
|
365
|
+
*/
|
|
366
|
+
pointerLock?: boolean;
|
|
367
|
+
/**
|
|
368
|
+
* Track every pointer that goes down on the element, rather than only the
|
|
369
|
+
* first. Each one gets its own `onDragStart` / `onDrag` / `onDragEnd` and
|
|
370
|
+
* carries its own totals; {@link DragState.pointerId} says which is which.
|
|
371
|
+
*
|
|
372
|
+
* Fixed for the lifetime of the instance: switching part way through a drag
|
|
373
|
+
* has no meaning, so `update()` ignores it.
|
|
374
|
+
*
|
|
375
|
+
* @default false
|
|
376
|
+
*/
|
|
377
|
+
multiPointer?: boolean;
|
|
83
378
|
onDragStart?: (state: DragState) => void;
|
|
84
379
|
onDrag?: (state: DragState) => void;
|
|
380
|
+
/**
|
|
381
|
+
* Called exactly once for every drag that starts, whether tracking ends by
|
|
382
|
+
* pointer release, cancellation, capture or lock loss, or destruction.
|
|
383
|
+
*/
|
|
85
384
|
onDragEnd?: (state: DragState) => void;
|
|
86
385
|
};
|
|
87
386
|
interface DragInstance {
|
|
387
|
+
/**
|
|
388
|
+
* Replace the given options. Lets a wrapper feed fresh handlers in without
|
|
389
|
+
* tearing down the listeners, which would abort a drag in progress.
|
|
390
|
+
*
|
|
391
|
+
* `multiPointer` is fixed for the lifetime of the instance and is ignored
|
|
392
|
+
* here.
|
|
393
|
+
*/
|
|
394
|
+
update: (options: DragOptions) => void;
|
|
395
|
+
/** End any active drags before removing the instance. */
|
|
88
396
|
destroy: () => void;
|
|
89
397
|
}
|
|
90
398
|
/**
|
|
@@ -95,17 +403,198 @@ interface DragInstance {
|
|
|
95
403
|
* element gets `touch-action: none` so that touch dragging does not scroll the
|
|
96
404
|
* page, plus `user-select: none` so that a long press does not start a text
|
|
97
405
|
* selection instead.
|
|
406
|
+
*
|
|
407
|
+
* One pointer at a time by default; see {@link DragOptions.multiPointer}.
|
|
408
|
+
*/
|
|
409
|
+
declare function createDrag(element: Element, options?: DragOptions): DragInstance;
|
|
410
|
+
//#endregion
|
|
411
|
+
//#region src/xy.d.ts
|
|
412
|
+
/**
|
|
413
|
+
* A pair of per-axis values. The tuple elements are labelled, so editors show
|
|
414
|
+
* `[x: number, y: number]` rather than a bare pair.
|
|
415
|
+
*/
|
|
416
|
+
type XY<T> = [x: T, y: T];
|
|
417
|
+
/**
|
|
418
|
+
* A setting that may be given once for both axes, or per axis.
|
|
419
|
+
*
|
|
420
|
+
* A single value is told from a pair with `Array.isArray`, so the single form
|
|
421
|
+
* is only offered while `T` cannot itself be an array. Where it can, the pair
|
|
422
|
+
* is the only way to write it, since a lone array would be read as a pair.
|
|
423
|
+
*/
|
|
424
|
+
type XYInput<T> = [T] extends [readonly unknown[]] ? readonly [x: T, y: T] : T | readonly [x: T, y: T];
|
|
425
|
+
/**
|
|
426
|
+
* Spread a setting that may have been given as a single value.
|
|
427
|
+
*
|
|
428
|
+
* The parameter is written out rather than taken as `XYInput<T>`: a
|
|
429
|
+
* conditional type cannot be narrowed, so the constraint stays where it is
|
|
430
|
+
* declared and this takes both forms.
|
|
431
|
+
*
|
|
432
|
+
* The pair is copied rather than passed along, so that the result is a tuple
|
|
433
|
+
* the caller owns even when a readonly one was given.
|
|
434
|
+
*/
|
|
435
|
+
declare function toXY<T>(value: T | readonly [x: T, y: T]): XY<T>;
|
|
436
|
+
//#endregion
|
|
437
|
+
//#region src/pointer/drag-value.d.ts
|
|
438
|
+
/**
|
|
439
|
+
* How the 0-1 travel of one axis maps onto a value.
|
|
440
|
+
*
|
|
441
|
+
* Extends `ValueRange` so that a drag and an `applyDelta` nudge from a wheel
|
|
442
|
+
* or an arrow key share one description of the scaling.
|
|
443
|
+
*/
|
|
444
|
+
interface AxisOptions extends ValueRange {
|
|
445
|
+
/**
|
|
446
|
+
* Flip the axis so that its far end is `min`.
|
|
447
|
+
*
|
|
448
|
+
* Positions follow the screen: x grows to the right, y downwards. A vertical
|
|
449
|
+
* slider whose maximum is at the top therefore reverses its y axis.
|
|
450
|
+
*
|
|
451
|
+
* @default false
|
|
452
|
+
*/
|
|
453
|
+
reverse?: boolean;
|
|
454
|
+
}
|
|
455
|
+
/** Where the value of each axis currently sits, as a position (see {@link DragValueMapping}). */
|
|
456
|
+
interface MappingContext {
|
|
457
|
+
position: () => XY<number>;
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Turns pointer movement into a position on each axis: 0 is the `min` end of
|
|
461
|
+
* the travel and 1 the `max` end, before `reverse` and the scaling of
|
|
462
|
+
* {@link AxisOptions} are applied.
|
|
463
|
+
*
|
|
464
|
+
* A mapping holds the state of the drag in progress, so an instance belongs to
|
|
465
|
+
* a single {@link createDragValue} instance.
|
|
466
|
+
*/
|
|
467
|
+
interface DragValueMapping {
|
|
468
|
+
/**
|
|
469
|
+
* @returns the position, or null when it cannot be determined and the event
|
|
470
|
+
* should be ignored.
|
|
471
|
+
*/
|
|
472
|
+
start: (state: DragState, context: MappingContext) => XY<number> | null;
|
|
473
|
+
move: (state: DragState, context: MappingContext) => XY<number> | null;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Map the pointer onto the bounding rect of an element: the value *is* the
|
|
477
|
+
* position pointed at, so the middle of the element is 0.5.
|
|
478
|
+
*
|
|
479
|
+
* The element is read on every event, so it may be mounted after the drag is
|
|
480
|
+
* set up and may change size while a drag is in progress.
|
|
481
|
+
*/
|
|
482
|
+
declare function elementMapping(getElement: () => Element | null | undefined, { sensitivity }?: {
|
|
483
|
+
/**
|
|
484
|
+
* How much the movement counts, read on every move. `1` is the pointer
|
|
485
|
+
* position itself; `0.1` makes the same movement cover a tenth of the
|
|
486
|
+
* travel, which is what a fine-adjustment modifier wants.
|
|
487
|
+
*
|
|
488
|
+
* Anything but `1` turns the mapping relative: the value stops being the
|
|
489
|
+
* position pointed at and starts being where it stood when the sensitivity
|
|
490
|
+
* changed, plus the movement since. **The pointer and the value stay apart
|
|
491
|
+
* for the rest of the drag** rather than snapping back together when the
|
|
492
|
+
* key is released, since snapping would move the value nobody asked to
|
|
493
|
+
* move.
|
|
494
|
+
*/
|
|
495
|
+
sensitivity?: (state: DragState) => number;
|
|
496
|
+
}): DragValueMapping;
|
|
497
|
+
/**
|
|
498
|
+
* Move the value away from where it stood when the drag started, by the
|
|
499
|
+
* distance dragged. The pointer position itself carries no meaning, so the
|
|
500
|
+
* value can be adjusted from anywhere on the screen.
|
|
501
|
+
*/
|
|
502
|
+
declare function relativeMapping({ pixelRange, sensitivity }?: {
|
|
503
|
+
/**
|
|
504
|
+
* Pixels of movement that span the whole range.
|
|
505
|
+
* @default 100
|
|
506
|
+
*/
|
|
507
|
+
pixelRange?: XYInput<number>;
|
|
508
|
+
/**
|
|
509
|
+
* How much the movement counts, read on every move. `1` is `pixelRange` as
|
|
510
|
+
* given; `0.1` makes the same movement cover a tenth of the range, which is
|
|
511
|
+
* what a fine-adjustment modifier wants.
|
|
512
|
+
*
|
|
513
|
+
* Changing it mid-drag does not disturb the value: the travel so far is
|
|
514
|
+
* folded into the origin and measuring starts again from there.
|
|
515
|
+
*/
|
|
516
|
+
sensitivity?: (state: DragState) => number;
|
|
517
|
+
}): DragValueMapping;
|
|
518
|
+
interface DragValueOptions {
|
|
519
|
+
/** Scaling of each axis; a single value applies to both. */
|
|
520
|
+
axis: XYInput<AxisOptions>;
|
|
521
|
+
/** How pointer movement becomes a position. */
|
|
522
|
+
mapping: DragValueMapping;
|
|
523
|
+
/**
|
|
524
|
+
* The current value of each axis. Read when a drag starts, by mappings that
|
|
525
|
+
* move the value relative to it, such as {@link relativeMapping}.
|
|
526
|
+
*/
|
|
527
|
+
getValue?: () => XY<number>;
|
|
528
|
+
/**
|
|
529
|
+
* Report the value on pointer down, before any movement.
|
|
530
|
+
*
|
|
531
|
+
* Enable it where the pointer position *is* the value, so that a plain click
|
|
532
|
+
* jumps to it. Leave it off where the element being dragged is an object in
|
|
533
|
+
* its own right, so that grabbing its edge does not shift it under the
|
|
534
|
+
* pointer.
|
|
535
|
+
*
|
|
536
|
+
* @default false
|
|
537
|
+
*/
|
|
538
|
+
updateOnPointerDown?: boolean;
|
|
539
|
+
/** @see DragOptions.threshold */
|
|
540
|
+
threshold?: number;
|
|
541
|
+
/** @see DragOptions.cursor */
|
|
542
|
+
cursor?: string;
|
|
543
|
+
/**
|
|
544
|
+
* @see DragOptions.pointerLock
|
|
545
|
+
*
|
|
546
|
+
* Only for a mapping that moves the value relative to where it stood, such
|
|
547
|
+
* as {@link relativeMapping}. {@link elementMapping} reads the pointer
|
|
548
|
+
* position, and there is none while it is locked.
|
|
549
|
+
*/
|
|
550
|
+
pointerLock?: boolean;
|
|
551
|
+
/** @see DragOptions.shouldStart */
|
|
552
|
+
shouldStart?: (event: PointerEvent) => boolean;
|
|
553
|
+
onChange?: (value: XY<number>, state: DragState) => void;
|
|
554
|
+
onDragStart?: (value: XY<number>, state: DragState) => void;
|
|
555
|
+
onDragEnd?: (value: XY<number>, state: DragState) => void;
|
|
556
|
+
}
|
|
557
|
+
interface DragValueInstance {
|
|
558
|
+
/**
|
|
559
|
+
* Replace the given options. Lets a wrapper feed fresh values in without
|
|
560
|
+
* tearing down the listeners, which would abort a drag in progress.
|
|
561
|
+
*
|
|
562
|
+
* `mapping` is fixed for the lifetime of the instance and is ignored here.
|
|
563
|
+
*/
|
|
564
|
+
update: (options: Partial<DragValueOptions>) => void;
|
|
565
|
+
destroy: () => void;
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Drive a value with a pointer drag.
|
|
569
|
+
*
|
|
570
|
+
* Combines {@link createDrag} with the scaling of `@tremolo-ui/functions`: the
|
|
571
|
+
* mapping decides where the pointer sits on the 0-1 travel of each axis, and
|
|
572
|
+
* the axis options turn that into a value.
|
|
98
573
|
*/
|
|
99
|
-
declare function
|
|
100
|
-
threshold: _threshold,
|
|
101
|
-
cursor,
|
|
102
|
-
onDragStart,
|
|
103
|
-
onDrag,
|
|
104
|
-
onDragEnd
|
|
105
|
-
}?: DragOptions): DragInstance;
|
|
574
|
+
declare function createDragValue(element: Element, options: DragValueOptions): DragValueInstance;
|
|
106
575
|
//#endregion
|
|
107
576
|
//#region src/pointer/wheel.d.ts
|
|
577
|
+
interface WheelOptions {
|
|
578
|
+
/** Replace the callback through {@link WheelInstance.update}. */
|
|
579
|
+
onWheel?: (event: WheelEvent) => void;
|
|
580
|
+
/**
|
|
581
|
+
* Only report events while the focus is inside the element.
|
|
582
|
+
*
|
|
583
|
+
* A control that reacts to the wheel on hover alone takes the scroll away
|
|
584
|
+
* from the page, so passing over one in a long form silently changes its
|
|
585
|
+
* value. Requiring focus makes that an explicit act.
|
|
586
|
+
*
|
|
587
|
+
* The check is `contains`, not an identity test: the element that actually
|
|
588
|
+
* takes focus is usually a descendant, such as a thumb or an `<input>`, and
|
|
589
|
+
* a caller may have replaced it with markup of their own.
|
|
590
|
+
*
|
|
591
|
+
* @default false
|
|
592
|
+
*/
|
|
593
|
+
requireFocus?: boolean;
|
|
594
|
+
}
|
|
108
595
|
interface WheelInstance {
|
|
596
|
+
/** Replace the given options, keeping the listener in place. */
|
|
597
|
+
update: (options: WheelOptions) => void;
|
|
109
598
|
destroy: () => void;
|
|
110
599
|
}
|
|
111
600
|
/**
|
|
@@ -114,7 +603,67 @@ interface WheelInstance {
|
|
|
114
603
|
* The listener is registered with `passive: false` so that the handler can call
|
|
115
604
|
* `preventDefault()` to stop the page from scrolling.
|
|
116
605
|
*/
|
|
117
|
-
declare function createWheel(element: Element, onWheel: (event: WheelEvent) => void): WheelInstance;
|
|
606
|
+
declare function createWheel(element: Element, onWheel: (event: WheelEvent) => void, options?: WheelOptions): WheelInstance;
|
|
607
|
+
//#endregion
|
|
608
|
+
//#region src/selection/box.d.ts
|
|
609
|
+
/**
|
|
610
|
+
* A rectangle in the 0..1 space of whatever the box is drawn over, with `y`
|
|
611
|
+
* growing downwards — the same space the items are given in.
|
|
612
|
+
*/
|
|
613
|
+
interface SelectionBoxRect {
|
|
614
|
+
x: number;
|
|
615
|
+
y: number;
|
|
616
|
+
width: number;
|
|
617
|
+
height: number;
|
|
618
|
+
}
|
|
619
|
+
interface SelectionBoxOptions<Id> {
|
|
620
|
+
/**
|
|
621
|
+
* Everything the box can pick up, read on every move rather than taken as a
|
|
622
|
+
* snapshot: what is under the box changes while it is dragged, and an item
|
|
623
|
+
* may have moved since the last one.
|
|
624
|
+
*/
|
|
625
|
+
items: () => Iterable<readonly [Id, XY<number>]>;
|
|
626
|
+
/** The box as it is dragged, and `null` once it is gone. */
|
|
627
|
+
onBoxChange?: (box: SelectionBoxRect | null) => void;
|
|
628
|
+
/** What the box covers, added to whatever it started from. */
|
|
629
|
+
onSelectionChange?: (ids: Id[]) => void;
|
|
630
|
+
}
|
|
631
|
+
interface SelectionBoxBeginOptions<Id> {
|
|
632
|
+
/**
|
|
633
|
+
* Add to `selection` rather than replacing it. Ctrl / meta rather than
|
|
634
|
+
* shift, for the controls where shift is the fine-adjustment key.
|
|
635
|
+
*/
|
|
636
|
+
additive?: boolean;
|
|
637
|
+
/** What was selected before the box started. Only read when `additive`. */
|
|
638
|
+
selection?: readonly Id[];
|
|
639
|
+
}
|
|
640
|
+
interface SelectionBoxInstance<Id> {
|
|
641
|
+
/** Replace the given options, without interrupting a box in progress. */
|
|
642
|
+
update: (options: Partial<SelectionBoxOptions<Id>>) => void;
|
|
643
|
+
/** Start a box at `at`, which is one of its corners. */
|
|
644
|
+
begin: (at: XY<number>, options?: SelectionBoxBeginOptions<Id>) => void;
|
|
645
|
+
/** Drag the opposite corner to `to`. */
|
|
646
|
+
move: (to: XY<number>) => void;
|
|
647
|
+
/**
|
|
648
|
+
* Finish the box. The selection stays as it is.
|
|
649
|
+
*
|
|
650
|
+
* @returns whether a box was running, so that a caller can tell a drag from
|
|
651
|
+
* a press that selected nothing.
|
|
652
|
+
*/
|
|
653
|
+
end: () => boolean;
|
|
654
|
+
/** The box being dragged, or `null` when there is none. */
|
|
655
|
+
box: () => SelectionBoxRect | null;
|
|
656
|
+
destroy: () => void;
|
|
657
|
+
}
|
|
658
|
+
/** Whether the box covers the point, edges included. */
|
|
659
|
+
declare function selectionBoxCovers(box: SelectionBoxRect, [x, y]: XY<number>): boolean;
|
|
660
|
+
/**
|
|
661
|
+
* Selecting by dragging a box over a set of items.
|
|
662
|
+
*
|
|
663
|
+
* The drag itself is not here: the caller owns the pointer, and reports where
|
|
664
|
+
* it went in the same 0..1 space the items are given in.
|
|
665
|
+
*/
|
|
666
|
+
declare function createSelectionBox<Id>(options: SelectionBoxOptions<Id>): SelectionBoxInstance<Id>;
|
|
118
667
|
//#endregion
|
|
119
|
-
export { type DragInstance, type DragOptions, type DragState, type MIDIAccessError, type MIDIAccessInstance, type MIDIAccessState, type MIDIInputHandlers, type MIDIInputInstance, type MIDIMessageInstance, NOT_SUPPORTED, PERMISSION_DENIED, type WheelInstance, createDrag, createMIDIAccess, createMIDIInput, createMIDIMessage, createWheel };
|
|
668
|
+
export { type AnimationCanvasInstance, type AnimationCanvasOptions, type AnimationFrame, type AxisOptions, type CanvasDrawFunction, type CanvasInitFunction, type DragInstance, type DragOptions, type DragState, type DragValueInstance, type DragValueMapping, type DragValueOptions, type DrawingContext, type DrawingState, type DrawingStateValue, type MIDIAccessError, type MIDIAccessInstance, type MIDIAccessOptions, type MIDIAccessState, type MIDIInputHandlers, type MIDIInputInstance, type MIDIMessageInstance, type MappingContext, NOT_SUPPORTED, type NoteSource, PERMISSION_DENIED, PITCH_BEND_CENTER, type PianoInputInstance, type PianoInputOptions, type SelectionBoxBeginOptions, type SelectionBoxInstance, type SelectionBoxOptions, type SelectionBoxRect, UNAVAILABLE, type WheelInstance, type WheelOptions, type XY, type XYInput, createAnimationCanvas, createDrag, createDragValue, createMIDIAccess, createMIDIInput, createMIDIMessage, createPianoInput, createSelectionBox, createWheel, drawingState, elementMapping, isDrawingState, relativeMapping, selectionBoxCovers, toXY };
|
|
120
669
|
//# sourceMappingURL=index.d.cts.map
|