@tremolo-ui/dom 0.5.0 → 0.7.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 +842 -101
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +490 -47
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +490 -47
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +829 -102
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
- package/src/canvas/animation.ts +49 -31
- package/src/canvas/context.ts +32 -5
- package/src/file/accept.ts +58 -0
- package/src/file/drop-zone.ts +202 -0
- package/src/index.ts +39 -2
- package/src/input/apply-delta.ts +73 -0
- package/src/input/modifiers.ts +121 -0
- package/src/midi/access.ts +83 -13
- package/src/midi/input.ts +97 -26
- package/src/midi/message.ts +37 -8
- package/src/piano/{input.ts → index.ts} +25 -5
- package/src/piano/layout.ts +183 -0
- package/src/pointer/{dragValue.ts → drag-value.ts} +155 -19
- package/src/pointer/drag.ts +215 -20
- package/src/pointer/wheel.ts +4 -2
- package/src/selection/box.ts +136 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { ValueRange } from "@tremolo-ui/functions";
|
|
3
2
|
//#region src/canvas/animation.d.ts
|
|
4
3
|
/** What the canvas looked like when a frame was drawn. */
|
|
5
4
|
interface AnimationFrame {
|
|
@@ -13,7 +12,7 @@ interface AnimationFrame {
|
|
|
13
12
|
deltaTime: number;
|
|
14
13
|
/** Milliseconds since the instance was created. */
|
|
15
14
|
elapsedTime: number;
|
|
16
|
-
/** Frames per second implied by `deltaTime
|
|
15
|
+
/** Frames per second implied by `deltaTime`, or 0 when no time elapsed. */
|
|
17
16
|
fps: number;
|
|
18
17
|
}
|
|
19
18
|
type CanvasDrawFunction = (context: CanvasRenderingContext2D, frame: AnimationFrame) => void;
|
|
@@ -34,7 +33,7 @@ interface AnimationCanvasOptions {
|
|
|
34
33
|
*/
|
|
35
34
|
animate?: boolean;
|
|
36
35
|
/**
|
|
37
|
-
* Size in CSS pixels. Ignored when `
|
|
36
|
+
* Size in CSS pixels. Ignored when `resizable` is on.
|
|
38
37
|
*
|
|
39
38
|
* @default { width: 100, height: 100 }
|
|
40
39
|
*/
|
|
@@ -50,7 +49,7 @@ interface AnimationCanvasOptions {
|
|
|
50
49
|
*
|
|
51
50
|
* @default false
|
|
52
51
|
*/
|
|
53
|
-
|
|
52
|
+
resizable?: boolean;
|
|
54
53
|
/**
|
|
55
54
|
* Carry the drawing across a resize, so that the canvas does not blank for a
|
|
56
55
|
* frame while the new size is drawn.
|
|
@@ -72,7 +71,7 @@ interface AnimationCanvasInstance {
|
|
|
72
71
|
* render without restarting the animation, so the frame count and the
|
|
73
72
|
* elapsed time keep running.
|
|
74
73
|
*
|
|
75
|
-
* `
|
|
74
|
+
* `resizable` and `contextAttributes` are fixed for the lifetime of the
|
|
76
75
|
* instance and are ignored here.
|
|
77
76
|
*
|
|
78
77
|
* While `animate` is off this also draws a frame, since nothing else would.
|
|
@@ -90,36 +89,246 @@ interface AnimationCanvasInstance {
|
|
|
90
89
|
* Drawing code works in CSS pixels: the context is scaled by the device pixel
|
|
91
90
|
* ratio, and `width` / `height` of each {@link AnimationFrame} are CSS pixels.
|
|
92
91
|
*/
|
|
93
|
-
declare function createAnimationCanvas(canvas: HTMLCanvasElement, options: AnimationCanvasOptions): AnimationCanvasInstance;
|
|
92
|
+
export declare function createAnimationCanvas(canvas: HTMLCanvasElement, options: AnimationCanvasOptions): AnimationCanvasInstance;
|
|
94
93
|
//#endregion
|
|
95
94
|
//#region src/canvas/context.d.ts
|
|
96
95
|
/**
|
|
97
|
-
* The
|
|
98
|
-
*
|
|
99
|
-
*
|
|
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
100
|
*
|
|
101
101
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/save
|
|
102
102
|
*/
|
|
103
|
-
declare const drawingState: readonly ["strokeStyle", "fillStyle", "globalAlpha", "lineWidth", "lineCap", "lineJoin", "miterLimit", "lineDashOffset", "shadowOffsetX", "shadowOffsetY", "shadowBlur", "shadowColor", "globalCompositeOperation", "font", "textAlign", "textBaseline", "direction", "imageSmoothingEnabled"];
|
|
103
|
+
export 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
104
|
type DrawingState = (typeof drawingState)[number];
|
|
105
105
|
type DrawingStateValue = CanvasRenderingContext2D[DrawingState];
|
|
106
|
-
type DrawingContext = Pick<CanvasRenderingContext2D, DrawingState
|
|
107
|
-
|
|
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
|
+
export declare function isDrawingState(value: unknown): value is DrawingState;
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/file/accept.d.ts
|
|
115
|
+
/**
|
|
116
|
+
* What an `accept` rule is matched against.
|
|
117
|
+
*
|
|
118
|
+
* `File` satisfies it, and so does `DataTransferItem` — which matters while a
|
|
119
|
+
* drag is still in the air, since the browser reports the type of what is
|
|
120
|
+
* being dragged but withholds the name.
|
|
121
|
+
*/
|
|
122
|
+
interface AcceptCandidate {
|
|
123
|
+
/** The file name, when it is known. */
|
|
124
|
+
name?: string;
|
|
125
|
+
/** The MIME type, or `''` when the browser has no type for it. */
|
|
126
|
+
type: string;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Does a file satisfy an `accept` attribute?
|
|
130
|
+
*
|
|
131
|
+
* `accept` is written the way the HTML attribute is: a comma separated list of
|
|
132
|
+
* extensions (`.wav`), MIME types (`audio/wav`) and type groups (`audio/*`).
|
|
133
|
+
* Anything that matches one entry is accepted, and an empty or missing
|
|
134
|
+
* `accept` takes everything.
|
|
135
|
+
*
|
|
136
|
+
* **The browser's own `accept` is only a hint to the file picker.** A person
|
|
137
|
+
* can switch it to "All Files", drag a file in, or pick one the picker was
|
|
138
|
+
* never asked about, so what arrives still has to be checked.
|
|
139
|
+
*
|
|
140
|
+
* A rule that cannot be decided is not treated as a rejection: with no `name`,
|
|
141
|
+
* an extension rule says nothing either way, and `accept=".wav"` reports a
|
|
142
|
+
* match rather than refusing a file it has not seen the name of. The name is
|
|
143
|
+
* there by the time the file is dropped, which is when the answer counts.
|
|
144
|
+
*/
|
|
145
|
+
export declare function matchesAccept(candidate: AcceptCandidate, accept?: string): boolean;
|
|
146
|
+
//#endregion
|
|
147
|
+
//#region src/file/drop-zone.d.ts
|
|
148
|
+
/** What is in the air over the element. */
|
|
149
|
+
interface DropZoneState {
|
|
150
|
+
/** Files are being dragged over the element. */
|
|
151
|
+
over: boolean;
|
|
152
|
+
/**
|
|
153
|
+
* None of what is being dragged matches `accept`, as far as can be told
|
|
154
|
+
* before the drop.
|
|
155
|
+
*
|
|
156
|
+
* The browser reports the type of what is being dragged but withholds the
|
|
157
|
+
* name, so a rule written as an extension cannot be decided yet and is not
|
|
158
|
+
* counted against the drag. It is decided on the drop, where the name is.
|
|
159
|
+
*/
|
|
160
|
+
invalid: boolean;
|
|
161
|
+
}
|
|
162
|
+
interface DropZoneOptions {
|
|
163
|
+
/**
|
|
164
|
+
* Which files to take, written the way the `accept` attribute of a file
|
|
165
|
+
* input is: a comma separated list of extensions (`.wav`), MIME types
|
|
166
|
+
* (`audio/wav`) and type groups (`audio/*`).
|
|
167
|
+
*/
|
|
168
|
+
accept?: string;
|
|
169
|
+
/**
|
|
170
|
+
* Take more than one file from a single drop. With it off, only the first
|
|
171
|
+
* accepted file is reported, as a file input without `multiple` does.
|
|
172
|
+
*
|
|
173
|
+
* @default false
|
|
174
|
+
*/
|
|
175
|
+
multiple?: boolean;
|
|
176
|
+
/**
|
|
177
|
+
* Refuse the drop. The drag is still swallowed rather than let through: an
|
|
178
|
+
* unhandled drop makes the browser leave the page and open the file.
|
|
179
|
+
*
|
|
180
|
+
* @default false
|
|
181
|
+
*/
|
|
182
|
+
disabled?: boolean;
|
|
183
|
+
/** Called with the dropped files that match `accept`. */
|
|
184
|
+
onDrop?: (files: File[], event: DragEvent) => void;
|
|
185
|
+
/**
|
|
186
|
+
* Called with the dropped files that do not match `accept`, so that the
|
|
187
|
+
* reason can be shown.
|
|
188
|
+
*/
|
|
189
|
+
onReject?: (files: File[], event: DragEvent) => void;
|
|
190
|
+
/** Called whenever {@link DropZoneInstance.state} would change. */
|
|
191
|
+
onStateChange?: (state: DropZoneState) => void;
|
|
192
|
+
}
|
|
193
|
+
interface DropZoneInstance {
|
|
194
|
+
/** What is in the air over the element, right now. */
|
|
195
|
+
readonly state: DropZoneState;
|
|
196
|
+
/** Replace the given options, keeping the listeners in place. */
|
|
197
|
+
update: (options: DropZoneOptions) => void;
|
|
198
|
+
destroy: () => void;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Take files dropped onto an element.
|
|
202
|
+
*
|
|
203
|
+
* ```ts
|
|
204
|
+
* const zone = createDropZone(element, {
|
|
205
|
+
* accept: 'audio/*',
|
|
206
|
+
* onDrop: (files) => load(files[0]),
|
|
207
|
+
* })
|
|
208
|
+
* ```
|
|
209
|
+
*
|
|
210
|
+
* The element needs no attribute of its own: a drop target is made by
|
|
211
|
+
* cancelling `dragover`, which this does.
|
|
212
|
+
*/
|
|
213
|
+
export declare function createDropZone(element: Element, options?: DropZoneOptions): DropZoneInstance;
|
|
214
|
+
//#endregion
|
|
215
|
+
//#region src/input/modifiers.d.ts
|
|
216
|
+
/**
|
|
217
|
+
* Options for setting the amount of keyboard and mouse wheel changes.
|
|
218
|
+
*/
|
|
219
|
+
type InputEventOption = readonly ['normalized' | 'raw', number];
|
|
220
|
+
/**
|
|
221
|
+
* A modifier key that can carry an amount of its own.
|
|
222
|
+
*
|
|
223
|
+
* `ctrl` and `meta` are kept apart rather than folded into one "command" key:
|
|
224
|
+
* a plugin UI that mirrors a desktop host usually wants the same physical key
|
|
225
|
+
* on every platform, not the platform's own convention.
|
|
226
|
+
*/
|
|
227
|
+
type Modifier = 'shift' | 'alt' | 'ctrl' | 'meta';
|
|
228
|
+
/** The modifier flags of a `WheelEvent` or a `KeyboardEvent`. */
|
|
229
|
+
interface ModifierState {
|
|
230
|
+
shiftKey: boolean;
|
|
231
|
+
altKey: boolean;
|
|
232
|
+
ctrlKey: boolean;
|
|
233
|
+
metaKey: boolean;
|
|
234
|
+
}
|
|
235
|
+
/** One setting per modifier key, with `default` for none of them. */
|
|
236
|
+
type ModifierSetting = number | InputEventOption;
|
|
237
|
+
type ModifierMap<T extends ModifierSetting> = {
|
|
238
|
+
default: T;
|
|
239
|
+
} & Partial<Record<Modifier, T>>;
|
|
240
|
+
/**
|
|
241
|
+
* A single setting, or one per modifier key.
|
|
242
|
+
*
|
|
243
|
+
* @example
|
|
244
|
+
* ['raw', 1]
|
|
245
|
+
* { default: ['raw', 1], shift: ['raw', 0.1] }
|
|
246
|
+
*/
|
|
247
|
+
type ModifierValue<T extends ModifierSetting> = T | ModifierMap<T>;
|
|
248
|
+
/**
|
|
249
|
+
* Pick the setting that applies, given the modifier keys being held.
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* selectModifier({ default: 1, shift: 0.1 }, event)
|
|
253
|
+
*/
|
|
254
|
+
export declare function selectModifier<T extends ModifierSetting>(options: ModifierValue<T>, modifiers?: ModifierState): {
|
|
255
|
+
value: T;
|
|
256
|
+
modifier: Modifier | null;
|
|
257
|
+
};
|
|
258
|
+
/**
|
|
259
|
+
* Turn every entry of a setting into another kind of setting, keeping which
|
|
260
|
+
* modifier each belongs to.
|
|
261
|
+
*
|
|
262
|
+
* A drag sensitivity is a number and a keyboard amount is a tuple, but the two
|
|
263
|
+
* describe the same thing from the caller's side. This carries one over to the
|
|
264
|
+
* other so that a component can hand a sensitivity to {@link applyDelta}
|
|
265
|
+
* without unpicking the modifier map itself — which matters, since naming a
|
|
266
|
+
* modifier is also what takes `step` out of the pipeline.
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* mapModifier({ default: 1, shift: 0.1 }, (f) => ['raw', step * f])
|
|
270
|
+
* // { default: ['raw', 1], shift: ['raw', 0.1] }
|
|
271
|
+
*/
|
|
272
|
+
export declare function mapModifier<T extends ModifierSetting, U extends ModifierSetting>(options: ModifierValue<T>, fn: (value: T) => U): ModifierValue<U>;
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region src/input/apply-delta.d.ts
|
|
275
|
+
/**
|
|
276
|
+
* Move a value by an amount of input, as reported by a wheel or an arrow key.
|
|
277
|
+
*
|
|
278
|
+
* The pipeline matches {@link createDragValue}: scale, then step, then clamp.
|
|
279
|
+
* Which key or which sign of `deltaY` counts as which direction is left to the
|
|
280
|
+
* caller, since it differs per component.
|
|
281
|
+
*
|
|
282
|
+
* @param direction which way, and how many times, to apply the option. The
|
|
283
|
+
* size of one step is `option[1]`, so this is normally `1` or `-1`.
|
|
284
|
+
*
|
|
285
|
+
* @param modifiers the event, for `options` that name a modifier key. See
|
|
286
|
+
* {@link selectModifier}.
|
|
287
|
+
*
|
|
288
|
+
* @example
|
|
289
|
+
* // ArrowDown on a slider whose keyboard option is ['raw', 1]
|
|
290
|
+
* applyDelta(value, -1, keyboard, { min, max, step, scale })
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* // Shift+ArrowDown, where `keyboard` is { default: …, shift: ['raw', 0.1] }
|
|
294
|
+
* applyDelta(value, -1, keyboard, range, event)
|
|
295
|
+
*/
|
|
296
|
+
export declare function applyDelta(value: number, direction: number, options: ModifierValue<InputEventOption>, { min, max, step, scale }: ValueRange, modifiers?: ModifierState): number;
|
|
108
297
|
//#endregion
|
|
109
298
|
//#region src/midi/access.d.ts
|
|
110
299
|
/** @private */
|
|
111
|
-
declare const PERMISSION_DENIED = "PERMISSION_DENIED";
|
|
300
|
+
export declare const PERMISSION_DENIED = "PERMISSION_DENIED";
|
|
112
301
|
/** @private */
|
|
113
|
-
declare const NOT_SUPPORTED = "NOT_SUPPORTED";
|
|
302
|
+
export declare const NOT_SUPPORTED = "NOT_SUPPORTED";
|
|
114
303
|
/** @private */
|
|
115
|
-
|
|
304
|
+
export declare const UNAVAILABLE = "UNAVAILABLE";
|
|
305
|
+
/** @private */
|
|
306
|
+
type MIDIAccessError = typeof PERMISSION_DENIED | typeof NOT_SUPPORTED | typeof UNAVAILABLE;
|
|
307
|
+
type MIDIAccessOptions = {
|
|
308
|
+
/**
|
|
309
|
+
* Ask for system exclusive messages as well.
|
|
310
|
+
*
|
|
311
|
+
* Browsers treat this as a separate, more sensitive permission, so leave it
|
|
312
|
+
* off unless the app actually reads or sends sysex.
|
|
313
|
+
*
|
|
314
|
+
* @default false
|
|
315
|
+
*/
|
|
316
|
+
sysex?: boolean;
|
|
317
|
+
};
|
|
116
318
|
type MIDIAccessState = {
|
|
117
319
|
readonly midiAccess: MIDIAccess | null;
|
|
118
320
|
readonly error: MIDIAccessError | null;
|
|
321
|
+
/**
|
|
322
|
+
* The inputs currently connected, in the order MIDIAccess lists them.
|
|
323
|
+
*
|
|
324
|
+
* Kept up to date as devices are plugged in and unplugged, so a UI listing
|
|
325
|
+
* the devices does not have to watch `statechange` itself.
|
|
326
|
+
*/
|
|
327
|
+
readonly inputs: readonly MIDIInput[];
|
|
119
328
|
};
|
|
120
329
|
interface MIDIAccessInstance {
|
|
121
330
|
/** Request MIDI access. Safe to call more than once. */
|
|
122
|
-
request: () => void;
|
|
331
|
+
request: (options?: MIDIAccessOptions) => void;
|
|
123
332
|
getState: () => MIDIAccessState;
|
|
124
333
|
/** Snapshot for server side rendering. Always the initial state. */
|
|
125
334
|
getServerState: () => MIDIAccessState;
|
|
@@ -133,37 +342,136 @@ interface MIDIAccessInstance {
|
|
|
133
342
|
* The returned instance holds the state and notifies subscribers when it changes,
|
|
134
343
|
* so it can be consumed from any framework.
|
|
135
344
|
*/
|
|
136
|
-
declare function createMIDIAccess(): MIDIAccessInstance;
|
|
345
|
+
export declare function createMIDIAccess(): MIDIAccessInstance;
|
|
346
|
+
//#endregion
|
|
347
|
+
//#region src/midi/input.d.ts
|
|
348
|
+
/**
|
|
349
|
+
* Centre of the 14-bit pitch bend range: no bend.
|
|
350
|
+
*
|
|
351
|
+
* The range is not symmetric — 0 is 8192 below centre and 16383 is 8191 above
|
|
352
|
+
* — so a wheel at rest reports exactly this rather than half of the maximum.
|
|
353
|
+
*/
|
|
354
|
+
export declare const PITCH_BEND_CENTER = 8192;
|
|
355
|
+
/**
|
|
356
|
+
* Every handler is given the channel last, as 0-15. MIDI channels are written
|
|
357
|
+
* 1-16 on hardware, so add one before showing it to anyone.
|
|
358
|
+
*/
|
|
359
|
+
type MIDIInputHandlers = {
|
|
360
|
+
onNoteOnEvent?: (note: number, velocity: number, channel: number) => void;
|
|
361
|
+
onNoteOffEvent?: (note: number, channel: number) => void;
|
|
362
|
+
/**
|
|
363
|
+
* The 14-bit bend, 0-16383, centred at {@link PITCH_BEND_CENTER}.
|
|
364
|
+
*
|
|
365
|
+
* The two data bytes are little-endian — the first carries the low 7 bits —
|
|
366
|
+
* which is the other way round from every other message.
|
|
367
|
+
*/
|
|
368
|
+
onPitchBendEvent?: (value: number, channel: number) => void;
|
|
369
|
+
/** `controller` is the CC number, `value` is 0-127. */
|
|
370
|
+
onControlChangeEvent?: (controller: number, value: number, channel: number) => void;
|
|
371
|
+
onProgramChangeEvent?: (program: number, channel: number) => void;
|
|
372
|
+
/** Pressure for one held note (polyphonic aftertouch). */
|
|
373
|
+
onAftertouchEvent?: (note: number, pressure: number, channel: number) => void;
|
|
374
|
+
/** Pressure for the whole channel, sent by keyboards with one sensor. */
|
|
375
|
+
onChannelPressureEvent?: (pressure: number, channel: number) => void;
|
|
376
|
+
};
|
|
377
|
+
interface MIDIInputInstance {
|
|
378
|
+
/** Replace the handlers, keeping the listeners in place. */
|
|
379
|
+
update: (handlers: MIDIInputHandlers) => void;
|
|
380
|
+
destroy: () => void;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Handle the channel voice messages of every connected input. To be used with
|
|
384
|
+
* {@link createMIDIAccess}. Internally uses {@link createMIDIMessage}, so
|
|
385
|
+
* devices plugged in later are picked up.
|
|
386
|
+
*
|
|
387
|
+
* System messages (clock, sysex, and the rest of `0xf0`-`0xff`) are not
|
|
388
|
+
* decoded here; reach for {@link createMIDIMessage} for those.
|
|
389
|
+
*/
|
|
390
|
+
export declare function createMIDIInput(midiAccess: MIDIAccess | null, handlers: MIDIInputHandlers): MIDIInputInstance;
|
|
137
391
|
//#endregion
|
|
138
392
|
//#region src/midi/message.d.ts
|
|
139
393
|
interface MIDIMessageInstance {
|
|
394
|
+
/** Replace the handler, keeping the listeners in place. */
|
|
395
|
+
update: (onMIDIMessage: (event: MIDIMessageEvent) => void) => void;
|
|
140
396
|
destroy: () => void;
|
|
141
397
|
}
|
|
142
398
|
/**
|
|
143
399
|
* Listen to raw `midimessage` events on every input of a MIDIAccess.
|
|
144
400
|
*
|
|
401
|
+
* The set of inputs is followed rather than sampled: MIDIAccess fires
|
|
402
|
+
* `statechange` when a device is plugged in or unplugged, and the listeners
|
|
403
|
+
* move with it. A keyboard connected after access was granted works without
|
|
404
|
+
* the caller having to rebuild anything.
|
|
405
|
+
*
|
|
145
406
|
* Use this when you need more detail than {@link createMIDIInput} provides.
|
|
146
407
|
*/
|
|
147
|
-
declare function createMIDIMessage(midiAccess: MIDIAccess | null, onMIDIMessage: (event: MIDIMessageEvent) => void): MIDIMessageInstance;
|
|
408
|
+
export declare function createMIDIMessage(midiAccess: MIDIAccess | null, onMIDIMessage: (event: MIDIMessageEvent) => void): MIDIMessageInstance;
|
|
148
409
|
//#endregion
|
|
149
|
-
//#region src/
|
|
150
|
-
type
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
onPitchBendEvent?: (msb: number, lsb: number) => void;
|
|
410
|
+
//#region src/piano/layout.d.ts
|
|
411
|
+
type NoteRange = {
|
|
412
|
+
first: number;
|
|
413
|
+
last: number;
|
|
154
414
|
};
|
|
155
|
-
type MIDIInputInstance = MIDIMessageInstance;
|
|
156
415
|
/**
|
|
157
|
-
*
|
|
158
|
-
|
|
416
|
+
* `[noteRange.first, noteRange.first + 1, ..., noteRange.last]`
|
|
417
|
+
*/
|
|
418
|
+
export declare function getNoteRangeArray(noteRange: NoteRange): number[];
|
|
419
|
+
/**
|
|
420
|
+
* The geometry of a drawn keyboard.
|
|
421
|
+
*
|
|
422
|
+
* One description is shared by the drawing and the hit testing, so a key cannot
|
|
423
|
+
* be drawn somewhere other than where it responds.
|
|
159
424
|
*/
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
425
|
+
interface PianoLayout {
|
|
426
|
+
noteRange: NoteRange;
|
|
427
|
+
/** Width of a white key, excluding {@link PianoLayout.keyGap}. */
|
|
428
|
+
whiteKeyWidth: number;
|
|
429
|
+
/**
|
|
430
|
+
* Space between two white keys. Part of the slot a white key occupies, so it
|
|
431
|
+
* still belongs to one of the keys for the purpose of hit testing.
|
|
432
|
+
*
|
|
433
|
+
* @default 1
|
|
434
|
+
*/
|
|
435
|
+
keyGap?: number;
|
|
436
|
+
/**
|
|
437
|
+
* Width of a black key, as a fraction of {@link PianoLayout.whiteKeyWidth}.
|
|
438
|
+
*
|
|
439
|
+
* @default 0.65
|
|
440
|
+
*/
|
|
441
|
+
blackKeyWidthRatio?: number;
|
|
442
|
+
/**
|
|
443
|
+
* Height of a black key, as a fraction of the height of the keyboard.
|
|
444
|
+
*
|
|
445
|
+
* @default 0.6
|
|
446
|
+
*/
|
|
447
|
+
blackKeyHeightRatio?: number;
|
|
448
|
+
}
|
|
449
|
+
/** Width of a black key in pixels. */
|
|
450
|
+
export declare function blackKeyWidth(layout: PianoLayout): number;
|
|
451
|
+
/** Width of the whole keyboard in pixels. */
|
|
452
|
+
export declare function pianoWidth(layout: PianoLayout): number;
|
|
453
|
+
/**
|
|
454
|
+
* Offset of the left edge of a key from the left edge of the keyboard, in
|
|
455
|
+
* pixels.
|
|
456
|
+
*
|
|
457
|
+
* Notes outside `noteRange` are placed too, so the value is negative below
|
|
458
|
+
* `noteRange.first`.
|
|
459
|
+
*/
|
|
460
|
+
export declare function notePosition(note: number, layout: PianoLayout): number;
|
|
461
|
+
/**
|
|
462
|
+
* The note drawn at a point, or null where there is none.
|
|
463
|
+
*
|
|
464
|
+
* Black keys are tested first, so they win where they overlap a white one. A
|
|
465
|
+
* white key covers its gap as well as its width, so the whole width of the
|
|
466
|
+
* keyboard belongs to some key and a click cannot fall between two.
|
|
467
|
+
*
|
|
468
|
+
* @param x offset from the left edge of the keyboard, in pixels
|
|
469
|
+
* @param y offset from its top edge, in pixels
|
|
470
|
+
* @param height height of the keyboard, in pixels
|
|
471
|
+
*/
|
|
472
|
+
export declare function noteAt(x: number, y: number, height: number, layout: PianoLayout): number | null;
|
|
165
473
|
//#endregion
|
|
166
|
-
//#region src/piano/
|
|
474
|
+
//#region src/piano/index.d.ts
|
|
167
475
|
/**
|
|
168
476
|
* What asked for a note to sound.
|
|
169
477
|
*
|
|
@@ -230,14 +538,17 @@ interface PianoInputInstance {
|
|
|
230
538
|
*
|
|
231
539
|
* Tracks every pointer at once, so a chord can be played with several fingers.
|
|
232
540
|
*/
|
|
233
|
-
declare function createPianoInput(element: Element, options: PianoInputOptions): PianoInputInstance;
|
|
541
|
+
export declare function createPianoInput(element: Element, options: PianoInputOptions): PianoInputInstance;
|
|
234
542
|
//#endregion
|
|
235
543
|
//#region src/pointer/drag.d.ts
|
|
236
544
|
type DragState = {
|
|
237
|
-
/** Total movement from the drag start, in screen coordinates. */
|
|
238
|
-
|
|
545
|
+
/** Total movement from the drag start, in screen coordinates. */
|
|
546
|
+
x: number;
|
|
547
|
+
y: number;
|
|
548
|
+
/** Movement since the previous event, in screen coordinates. */
|
|
239
549
|
deltaX: number;
|
|
240
|
-
deltaY: number;
|
|
550
|
+
deltaY: number;
|
|
551
|
+
/** Pointer position in viewport coordinates. */
|
|
241
552
|
clientX: number;
|
|
242
553
|
clientY: number;
|
|
243
554
|
/**
|
|
@@ -267,6 +578,40 @@ type DragOptions = {
|
|
|
267
578
|
* restored once the last one is up.
|
|
268
579
|
*/
|
|
269
580
|
cursor?: string;
|
|
581
|
+
/**
|
|
582
|
+
* Decide whether a pointerdown starts a drag at all.
|
|
583
|
+
*
|
|
584
|
+
* Checked before anything else — **before the pointer is captured** — so
|
|
585
|
+
* declining here leaves the whole gesture to whatever else is listening.
|
|
586
|
+
* Deciding later would be too late: the capture has already been taken from
|
|
587
|
+
* the element that was going to handle it.
|
|
588
|
+
*
|
|
589
|
+
* The use for it is a drag on a container that also holds draggable things
|
|
590
|
+
* of its own, such as a rubber-band selection that must not begin on top of
|
|
591
|
+
* one of the objects it would select.
|
|
592
|
+
*/
|
|
593
|
+
shouldStart?: (event: PointerEvent) => boolean;
|
|
594
|
+
/**
|
|
595
|
+
* Hide the pointer and read its movement directly, instead of following it
|
|
596
|
+
* around the screen.
|
|
597
|
+
*
|
|
598
|
+
* A relative drag — a knob, a stepper — does not care where the pointer is,
|
|
599
|
+
* only how far it moved, and letting it wander has two costs: the cursor
|
|
600
|
+
* ends up far from what it is holding, and **the drag stops at the edge of
|
|
601
|
+
* the screen**, where the operating system pins the pointer and the
|
|
602
|
+
* coordinates stop changing. A fine drag reaches that edge quickly.
|
|
603
|
+
*
|
|
604
|
+
* **Not for a drag whose value is the position pointed at** — anything on
|
|
605
|
+
* `elementMapping`. `clientX` / `clientY` freeze while the pointer is
|
|
606
|
+
* locked, so there is no position left to read.
|
|
607
|
+
*
|
|
608
|
+
* The request needs a user gesture, which a pointerdown is, but it can still
|
|
609
|
+
* be refused; the drag then carries on as an ordinary one. Read on
|
|
610
|
+
* pointerdown, so `update()` reaches the next drag rather than the current.
|
|
611
|
+
*
|
|
612
|
+
* @default false
|
|
613
|
+
*/
|
|
614
|
+
pointerLock?: boolean;
|
|
270
615
|
/**
|
|
271
616
|
* Track every pointer that goes down on the element, rather than only the
|
|
272
617
|
* first. Each one gets its own `onDragStart` / `onDrag` / `onDragEnd` and
|
|
@@ -280,6 +625,10 @@ type DragOptions = {
|
|
|
280
625
|
multiPointer?: boolean;
|
|
281
626
|
onDragStart?: (state: DragState) => void;
|
|
282
627
|
onDrag?: (state: DragState) => void;
|
|
628
|
+
/**
|
|
629
|
+
* Called exactly once for every drag that starts, whether tracking ends by
|
|
630
|
+
* pointer release, cancellation, capture or lock loss, or destruction.
|
|
631
|
+
*/
|
|
283
632
|
onDragEnd?: (state: DragState) => void;
|
|
284
633
|
};
|
|
285
634
|
interface DragInstance {
|
|
@@ -291,6 +640,7 @@ interface DragInstance {
|
|
|
291
640
|
* here.
|
|
292
641
|
*/
|
|
293
642
|
update: (options: DragOptions) => void;
|
|
643
|
+
/** End any active drags before removing the instance. */
|
|
294
644
|
destroy: () => void;
|
|
295
645
|
}
|
|
296
646
|
/**
|
|
@@ -304,7 +654,7 @@ interface DragInstance {
|
|
|
304
654
|
*
|
|
305
655
|
* One pointer at a time by default; see {@link DragOptions.multiPointer}.
|
|
306
656
|
*/
|
|
307
|
-
declare function createDrag(element: Element, options?: DragOptions): DragInstance;
|
|
657
|
+
export declare function createDrag(element: Element, options?: DragOptions): DragInstance;
|
|
308
658
|
//#endregion
|
|
309
659
|
//#region src/xy.d.ts
|
|
310
660
|
/**
|
|
@@ -330,9 +680,9 @@ type XYInput<T> = [T] extends [readonly unknown[]] ? readonly [x: T, y: T] : T |
|
|
|
330
680
|
* The pair is copied rather than passed along, so that the result is a tuple
|
|
331
681
|
* the caller owns even when a readonly one was given.
|
|
332
682
|
*/
|
|
333
|
-
declare function toXY<T>(value: T | readonly [x: T, y: T]): XY<T>;
|
|
683
|
+
export declare function toXY<T>(value: T | readonly [x: T, y: T]): XY<T>;
|
|
334
684
|
//#endregion
|
|
335
|
-
//#region src/pointer/
|
|
685
|
+
//#region src/pointer/drag-value.d.ts
|
|
336
686
|
/**
|
|
337
687
|
* How the 0-1 travel of one axis maps onto a value.
|
|
338
688
|
*
|
|
@@ -377,20 +727,41 @@ interface DragValueMapping {
|
|
|
377
727
|
* The element is read on every event, so it may be mounted after the drag is
|
|
378
728
|
* set up and may change size while a drag is in progress.
|
|
379
729
|
*/
|
|
380
|
-
declare function elementMapping(getElement: () => Element | null | undefined
|
|
730
|
+
export declare function elementMapping(getElement: () => Element | null | undefined, { sensitivity }?: {
|
|
731
|
+
/**
|
|
732
|
+
* How much the movement counts, read on every move. `1` is the pointer
|
|
733
|
+
* position itself; `0.1` makes the same movement cover a tenth of the
|
|
734
|
+
* travel, which is what a fine-adjustment modifier wants.
|
|
735
|
+
*
|
|
736
|
+
* Anything but `1` turns the mapping relative: the value stops being the
|
|
737
|
+
* position pointed at and starts being where it stood when the sensitivity
|
|
738
|
+
* changed, plus the movement since. **The pointer and the value stay apart
|
|
739
|
+
* for the rest of the drag** rather than snapping back together when the
|
|
740
|
+
* key is released, since snapping would move the value nobody asked to
|
|
741
|
+
* move.
|
|
742
|
+
*/
|
|
743
|
+
sensitivity?: (state: DragState) => number;
|
|
744
|
+
}): DragValueMapping;
|
|
381
745
|
/**
|
|
382
746
|
* Move the value away from where it stood when the drag started, by the
|
|
383
747
|
* distance dragged. The pointer position itself carries no meaning, so the
|
|
384
748
|
* value can be adjusted from anywhere on the screen.
|
|
385
749
|
*/
|
|
386
|
-
declare function relativeMapping({
|
|
387
|
-
pixelRange
|
|
388
|
-
}?: {
|
|
750
|
+
export declare function relativeMapping({ pixelRange, sensitivity }?: {
|
|
389
751
|
/**
|
|
390
752
|
* Pixels of movement that span the whole range.
|
|
391
753
|
* @default 100
|
|
392
754
|
*/
|
|
393
755
|
pixelRange?: XYInput<number>;
|
|
756
|
+
/**
|
|
757
|
+
* How much the movement counts, read on every move. `1` is `pixelRange` as
|
|
758
|
+
* given; `0.1` makes the same movement cover a tenth of the range, which is
|
|
759
|
+
* what a fine-adjustment modifier wants.
|
|
760
|
+
*
|
|
761
|
+
* Changing it mid-drag does not disturb the value: the travel so far is
|
|
762
|
+
* folded into the origin and measuring starts again from there.
|
|
763
|
+
*/
|
|
764
|
+
sensitivity?: (state: DragState) => number;
|
|
394
765
|
}): DragValueMapping;
|
|
395
766
|
interface DragValueOptions {
|
|
396
767
|
/** Scaling of each axis; a single value applies to both. */
|
|
@@ -417,6 +788,16 @@ interface DragValueOptions {
|
|
|
417
788
|
threshold?: number;
|
|
418
789
|
/** @see DragOptions.cursor */
|
|
419
790
|
cursor?: string;
|
|
791
|
+
/**
|
|
792
|
+
* @see DragOptions.pointerLock
|
|
793
|
+
*
|
|
794
|
+
* Only for a mapping that moves the value relative to where it stood, such
|
|
795
|
+
* as {@link relativeMapping}. {@link elementMapping} reads the pointer
|
|
796
|
+
* position, and there is none while it is locked.
|
|
797
|
+
*/
|
|
798
|
+
pointerLock?: boolean;
|
|
799
|
+
/** @see DragOptions.shouldStart */
|
|
800
|
+
shouldStart?: (event: PointerEvent) => boolean;
|
|
420
801
|
onChange?: (value: XY<number>, state: DragState) => void;
|
|
421
802
|
onDragStart?: (value: XY<number>, state: DragState) => void;
|
|
422
803
|
onDragEnd?: (value: XY<number>, state: DragState) => void;
|
|
@@ -438,10 +819,12 @@ interface DragValueInstance {
|
|
|
438
819
|
* mapping decides where the pointer sits on the 0-1 travel of each axis, and
|
|
439
820
|
* the axis options turn that into a value.
|
|
440
821
|
*/
|
|
441
|
-
declare function createDragValue(element: Element, options: DragValueOptions): DragValueInstance;
|
|
822
|
+
export declare function createDragValue(element: Element, options: DragValueOptions): DragValueInstance;
|
|
442
823
|
//#endregion
|
|
443
824
|
//#region src/pointer/wheel.d.ts
|
|
444
825
|
interface WheelOptions {
|
|
826
|
+
/** Replace the callback through {@link WheelInstance.update}. */
|
|
827
|
+
onWheel?: (event: WheelEvent) => void;
|
|
445
828
|
/**
|
|
446
829
|
* Only report events while the focus is inside the element.
|
|
447
830
|
*
|
|
@@ -468,7 +851,67 @@ interface WheelInstance {
|
|
|
468
851
|
* The listener is registered with `passive: false` so that the handler can call
|
|
469
852
|
* `preventDefault()` to stop the page from scrolling.
|
|
470
853
|
*/
|
|
471
|
-
declare function createWheel(element: Element, onWheel: (event: WheelEvent) => void, options?: WheelOptions): WheelInstance;
|
|
854
|
+
export declare function createWheel(element: Element, onWheel: (event: WheelEvent) => void, options?: WheelOptions): WheelInstance;
|
|
855
|
+
//#endregion
|
|
856
|
+
//#region src/selection/box.d.ts
|
|
857
|
+
/**
|
|
858
|
+
* A rectangle in the 0..1 space of whatever the box is drawn over, with `y`
|
|
859
|
+
* growing downwards — the same space the items are given in.
|
|
860
|
+
*/
|
|
861
|
+
interface SelectionBoxRect {
|
|
862
|
+
x: number;
|
|
863
|
+
y: number;
|
|
864
|
+
width: number;
|
|
865
|
+
height: number;
|
|
866
|
+
}
|
|
867
|
+
interface SelectionBoxOptions<Id> {
|
|
868
|
+
/**
|
|
869
|
+
* Everything the box can pick up, read on every move rather than taken as a
|
|
870
|
+
* snapshot: what is under the box changes while it is dragged, and an item
|
|
871
|
+
* may have moved since the last one.
|
|
872
|
+
*/
|
|
873
|
+
items: () => Iterable<readonly [Id, XY<number>]>;
|
|
874
|
+
/** The box as it is dragged, and `null` once it is gone. */
|
|
875
|
+
onBoxChange?: (box: SelectionBoxRect | null) => void;
|
|
876
|
+
/** What the box covers, added to whatever it started from. */
|
|
877
|
+
onSelectionChange?: (ids: Id[]) => void;
|
|
878
|
+
}
|
|
879
|
+
interface SelectionBoxBeginOptions<Id> {
|
|
880
|
+
/**
|
|
881
|
+
* Add to `selection` rather than replacing it. Ctrl / meta rather than
|
|
882
|
+
* shift, for the controls where shift is the fine-adjustment key.
|
|
883
|
+
*/
|
|
884
|
+
additive?: boolean;
|
|
885
|
+
/** What was selected before the box started. Only read when `additive`. */
|
|
886
|
+
selection?: readonly Id[];
|
|
887
|
+
}
|
|
888
|
+
interface SelectionBoxInstance<Id> {
|
|
889
|
+
/** Replace the given options, without interrupting a box in progress. */
|
|
890
|
+
update: (options: Partial<SelectionBoxOptions<Id>>) => void;
|
|
891
|
+
/** Start a box at `at`, which is one of its corners. */
|
|
892
|
+
begin: (at: XY<number>, options?: SelectionBoxBeginOptions<Id>) => void;
|
|
893
|
+
/** Drag the opposite corner to `to`. */
|
|
894
|
+
move: (to: XY<number>) => void;
|
|
895
|
+
/**
|
|
896
|
+
* Finish the box. The selection stays as it is.
|
|
897
|
+
*
|
|
898
|
+
* @returns whether a box was running, so that a caller can tell a drag from
|
|
899
|
+
* a press that selected nothing.
|
|
900
|
+
*/
|
|
901
|
+
end: () => boolean;
|
|
902
|
+
/** The box being dragged, or `null` when there is none. */
|
|
903
|
+
box: () => SelectionBoxRect | null;
|
|
904
|
+
destroy: () => void;
|
|
905
|
+
}
|
|
906
|
+
/** Whether the box covers the point, edges included. */
|
|
907
|
+
export declare function selectionBoxCovers(box: SelectionBoxRect, [x, y]: XY<number>): boolean;
|
|
908
|
+
/**
|
|
909
|
+
* Selecting by dragging a box over a set of items.
|
|
910
|
+
*
|
|
911
|
+
* The drag itself is not here: the caller owns the pointer, and reports where
|
|
912
|
+
* it went in the same 0..1 space the items are given in.
|
|
913
|
+
*/
|
|
914
|
+
export declare function createSelectionBox<Id>(options: SelectionBoxOptions<Id>): SelectionBoxInstance<Id>;
|
|
472
915
|
//#endregion
|
|
473
|
-
export {
|
|
916
|
+
export type { AcceptCandidate, AnimationCanvasInstance, AnimationCanvasOptions, AnimationFrame, AxisOptions, CanvasDrawFunction, CanvasInitFunction, DragInstance, DragOptions, DragState, DragValueInstance, DragValueMapping, DragValueOptions, DrawingContext, DrawingState, DrawingStateValue, DropZoneInstance, DropZoneOptions, DropZoneState, InputEventOption, MIDIAccessError, MIDIAccessInstance, MIDIAccessOptions, MIDIAccessState, MIDIInputHandlers, MIDIInputInstance, MIDIMessageInstance, MappingContext, Modifier, ModifierMap, ModifierState, ModifierValue, NoteRange, NoteSource, PianoInputInstance, PianoInputOptions, PianoLayout, SelectionBoxBeginOptions, SelectionBoxInstance, SelectionBoxOptions, SelectionBoxRect, WheelInstance, WheelOptions, XY, XYInput };
|
|
474
917
|
//# sourceMappingURL=index.d.ts.map
|