@tremolo-ui/dom 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 +343 -595
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +230 -284
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +230 -284
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +325 -572
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +530 -0
- package/dist/internal.cjs.map +1 -0
- package/dist/internal.d.cts +365 -0
- package/dist/internal.d.cts.map +1 -0
- package/dist/internal.d.ts +365 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +500 -0
- package/dist/internal.js.map +1 -0
- package/dist/marks-DK7xJmGM.d.cts +305 -0
- package/dist/marks-DK7xJmGM.d.cts.map +1 -0
- package/dist/marks-DK7xJmGM.d.ts +305 -0
- package/dist/marks-DK7xJmGM.d.ts.map +1 -0
- package/dist/xy-5Oc6JeJr.cjs +952 -0
- package/dist/xy-5Oc6JeJr.cjs.map +1 -0
- package/dist/xy-lNFl1CTO.js +827 -0
- package/dist/xy-lNFl1CTO.js.map +1 -0
- package/package.json +12 -2
- package/src/canvas/animation.ts +8 -0
- package/src/canvas/context.ts +0 -5
- package/src/file/accept.ts +17 -0
- package/src/file/drop-zone.ts +8 -10
- package/src/index.ts +44 -32
- package/src/input/change-gesture.ts +104 -0
- package/src/input/check-steps.ts +144 -0
- package/src/input/defaults.ts +32 -0
- package/src/input/direction.ts +107 -0
- package/src/internal.ts +51 -0
- package/src/knob/geometry.ts +100 -0
- package/src/midi/access.ts +16 -15
- package/src/midi/input.ts +2 -9
- package/src/number-input/stepper-drag.ts +147 -0
- package/src/number-input/text.ts +100 -0
- package/src/number-input/value.ts +129 -0
- package/src/options/replace.ts +22 -0
- package/src/piano/index.ts +128 -2
- package/src/piano/layout.ts +19 -0
- package/src/piano/shortcuts.ts +86 -0
- package/src/pointer/drag-value.ts +3 -1
- package/src/pointer/long-press.ts +103 -0
- package/src/pointer/wheel.ts +10 -7
- package/src/points-editor/index.ts +367 -0
- package/src/position.ts +20 -0
- package/src/slider/decimal-digits.ts +23 -0
- package/src/slider/marks.ts +56 -0
- package/src/style.ts +41 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { D as ModifierState, E as ModifierMap, O as ModifierValue, S as notePosition, T as Modifier, _ as NoteRange, c as PointsEditorInstance, f as createPointsEditor, h as XYInput, l as PointsEditorOptions, m as XY, p as SelectionBoxRect, s as PointPosition, t as MarksOptions, u as PointsEditorPoint, v as PianoLayout, w as InputEventOption } from "./marks-DK7xJmGM.js";
|
|
2
|
+
import { Scale, ValueRange } from "@tremolo-ui/functions";
|
|
2
3
|
//#region src/canvas/animation.d.ts
|
|
3
4
|
/** What the canvas looked like when a frame was drawn. */
|
|
4
5
|
interface AnimationFrame {
|
|
@@ -15,7 +16,15 @@ interface AnimationFrame {
|
|
|
15
16
|
/** Frames per second implied by `deltaTime`, or 0 when no time elapsed. */
|
|
16
17
|
fps: number;
|
|
17
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* Draws one frame, given the 2D context and the frame: the size in CSS pixels,
|
|
21
|
+
* `count`, `deltaTime`, `elapsedTime` and `fps`.
|
|
22
|
+
*/
|
|
18
23
|
type CanvasDrawFunction = (context: CanvasRenderingContext2D, frame: AnimationFrame) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Sets up what every frame shares. Called once, with the context and the size
|
|
26
|
+
* in CSS pixels, before the first frame; not again on a resize.
|
|
27
|
+
*/
|
|
19
28
|
type CanvasInitFunction = (context: CanvasRenderingContext2D, size: {
|
|
20
29
|
width: number;
|
|
21
30
|
height: number;
|
|
@@ -91,59 +100,6 @@ interface AnimationCanvasInstance {
|
|
|
91
100
|
*/
|
|
92
101
|
export declare function createAnimationCanvas(canvas: HTMLCanvasElement, options: AnimationCanvasOptions): AnimationCanvasInstance;
|
|
93
102
|
//#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
|
-
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
|
-
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
|
-
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
103
|
//#region src/file/drop-zone.d.ts
|
|
148
104
|
/** What is in the air over the element. */
|
|
149
105
|
interface DropZoneState {
|
|
@@ -184,7 +140,8 @@ interface DropZoneOptions {
|
|
|
184
140
|
onDrop?: (files: File[], event: DragEvent) => void;
|
|
185
141
|
/**
|
|
186
142
|
* Called with the dropped files that do not match `accept`, so that the
|
|
187
|
-
* reason can be shown.
|
|
143
|
+
* reason can be shown. It comes after `onDrop` for the same drop, so a list
|
|
144
|
+
* of rejected files can be cleared in `onDrop` and filled here.
|
|
188
145
|
*/
|
|
189
146
|
onReject?: (files: File[], event: DragEvent) => void;
|
|
190
147
|
/** Called whenever {@link DropZoneInstance.state} would change. */
|
|
@@ -212,98 +169,134 @@ interface DropZoneInstance {
|
|
|
212
169
|
*/
|
|
213
170
|
export declare function createDropZone(element: Element, options?: DropZoneOptions): DropZoneInstance;
|
|
214
171
|
//#endregion
|
|
215
|
-
//#region src/input/
|
|
172
|
+
//#region src/input/change-gesture.d.ts
|
|
173
|
+
/** What a change of value was made with. */
|
|
174
|
+
type ChangeSource = 'pointer' | 'wheel' | 'keyboard' | 'doubleClick';
|
|
175
|
+
interface ChangeGestureOptions {
|
|
176
|
+
/** Called before the first change of a gesture. */
|
|
177
|
+
onStart?: (source: ChangeSource) => void;
|
|
178
|
+
/** Called after the last change of a gesture. */
|
|
179
|
+
onEnd?: (source: ChangeSource) => void;
|
|
180
|
+
/**
|
|
181
|
+
* How long after the last wheel notch or key press a gesture counts as
|
|
182
|
+
* over, in milliseconds. Neither has an event that says it is done, so the
|
|
183
|
+
* gesture ends when nothing more arrives for this long.
|
|
184
|
+
*
|
|
185
|
+
* @default 500
|
|
186
|
+
*/
|
|
187
|
+
endDelay?: number;
|
|
188
|
+
}
|
|
189
|
+
interface ChangeGestureInstance {
|
|
190
|
+
/**
|
|
191
|
+
* Start a gesture that lasts until {@link end}, such as a drag. A gesture of
|
|
192
|
+
* another kind in progress ends first.
|
|
193
|
+
*/
|
|
194
|
+
hold: (source: ChangeSource) => void;
|
|
195
|
+
/**
|
|
196
|
+
* One step of a gesture with no end of its own, such as a wheel notch:
|
|
197
|
+
* starts one if none is in progress, and ends it `endDelay` after the last
|
|
198
|
+
* step. While a held gesture is in progress the step belongs to it.
|
|
199
|
+
*/
|
|
200
|
+
pulse: (source: ChangeSource) => void;
|
|
201
|
+
/**
|
|
202
|
+
* A gesture that starts and ends around a single change, such as a reset:
|
|
203
|
+
* `change` runs between the two.
|
|
204
|
+
*/
|
|
205
|
+
instant: (source: ChangeSource, change: () => void) => void;
|
|
206
|
+
/** End the gesture in progress, if any. */
|
|
207
|
+
end: () => void;
|
|
208
|
+
/** Whether a gesture is in progress. */
|
|
209
|
+
active: () => boolean;
|
|
210
|
+
/** Replace the given options. A gesture in progress picks them up. */
|
|
211
|
+
update: (options: Partial<ChangeGestureOptions>) => void;
|
|
212
|
+
/** End a gesture in progress, so that nothing is left touched. */
|
|
213
|
+
destroy: () => void;
|
|
214
|
+
}
|
|
216
215
|
/**
|
|
217
|
-
*
|
|
216
|
+
* Track when a change of value starts and ends, whatever made it: a drag, the
|
|
217
|
+
* wheel, the keys or a reset. A host that records automation needs both ends
|
|
218
|
+
* to know when the control is touched.
|
|
218
219
|
*/
|
|
219
|
-
|
|
220
|
+
export declare function createChangeGesture(options?: ChangeGestureOptions): ChangeGestureInstance;
|
|
221
|
+
//#endregion
|
|
222
|
+
//#region src/input/defaults.d.ts
|
|
220
223
|
/**
|
|
221
|
-
*
|
|
224
|
+
* The keyboard amount used by Knob, NumberInput, Slider, and XYPad by default:
|
|
225
|
+
* 1 per press in the units of the value, and 0.1 with shift held. That is one
|
|
226
|
+
* `step` only while `step` is 1; a coarser `step` rounds 1 straight back, which
|
|
227
|
+
* `checkSteps` warns about.
|
|
222
228
|
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
* on every platform, not the platform's own convention.
|
|
229
|
+
* A modifier entry is not snapped to `step`, which is what lets the finer
|
|
230
|
+
* amount move at all — see `applyDelta`.
|
|
226
231
|
*/
|
|
227
|
-
|
|
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>>;
|
|
232
|
+
export declare const DEFAULT_KEYBOARD_OPTIONS: ModifierValue<InputEventOption>;
|
|
240
233
|
/**
|
|
241
|
-
*
|
|
234
|
+
* The wheel amount used by Knob, NumberInput, Slider, and XYPad by default.
|
|
242
235
|
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
* { default: ['raw', 1], shift: ['raw', 0.1] }
|
|
236
|
+
* Browsers turn shift+wheel into horizontal scrolling, which empties `deltaY`
|
|
237
|
+
* and fills `deltaX`, so no modifier is bound here.
|
|
246
238
|
*/
|
|
247
|
-
|
|
239
|
+
export declare const DEFAULT_WHEEL_OPTIONS: ModifierValue<InputEventOption>;
|
|
248
240
|
/**
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
* @example
|
|
252
|
-
* selectModifier({ default: 1, shift: 0.1 }, event)
|
|
241
|
+
* The drag sensitivity used by value controls by default. Shift makes the
|
|
242
|
+
* same movement cover a tenth of the range, matching the arrow keys.
|
|
253
243
|
*/
|
|
254
|
-
export declare
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
244
|
+
export declare const DEFAULT_DRAG_SENSITIVITY: ModifierValue<number>;
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region src/knob/geometry.d.ts
|
|
247
|
+
/**
|
|
248
|
+
* Width and height of the viewBox a knob is drawn in. The arcs and the thumb
|
|
249
|
+
* are laid out in these units, and the SVG scales them to the knob's size.
|
|
250
|
+
*/
|
|
251
|
+
export declare const KNOB_VIEWBOX_SIZE = 100;
|
|
252
|
+
interface KnobAngleOptions {
|
|
253
|
+
value: number;
|
|
254
|
+
min: number;
|
|
255
|
+
max: number;
|
|
256
|
+
scale: Scale;
|
|
257
|
+
/** Where the active arc starts from, so that it can grow from the middle. */
|
|
258
|
+
startValue: number;
|
|
259
|
+
/** How far the knob turns from `min` to `max`, in degrees. */
|
|
260
|
+
angleRange: number;
|
|
261
|
+
}
|
|
258
262
|
/**
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
263
|
+
* The angles a knob is drawn with, in degrees clockwise from the top.
|
|
264
|
+
*
|
|
265
|
+
* The travel is centred on the top, so `angleRange` of 270 runs from -135 to
|
|
266
|
+
* 135. Derived from the value alone, so a wrapper can call it while rendering.
|
|
267
|
+
*/
|
|
268
|
+
interface KnobAngles {
|
|
269
|
+
/** The value, normalized to 0..1 along the scale. */
|
|
270
|
+
p: number;
|
|
271
|
+
/** Where the travel starts. */
|
|
272
|
+
r1: number;
|
|
273
|
+
/** Where the active arc starts: the lower of the value and `startValue`. */
|
|
274
|
+
r2: number;
|
|
275
|
+
/** Where the active arc ends: the higher of the value and `startValue`. */
|
|
276
|
+
r3: number;
|
|
277
|
+
/** Where the travel ends. */
|
|
278
|
+
r4: number;
|
|
279
|
+
}
|
|
280
|
+
export declare function knobAngles({ value, min, max, scale, startValue, angleRange }: KnobAngleOptions): KnobAngles;
|
|
281
|
+
/**
|
|
282
|
+
* The radius that keeps a stroke of `strokeWidth` inside the viewBox: half of
|
|
283
|
+
* the stroke falls outside the path it is drawn along.
|
|
271
284
|
*/
|
|
272
|
-
export declare function
|
|
285
|
+
export declare function knobArcRadius(strokeWidth: number | string | undefined): number;
|
|
286
|
+
/** Build an SVG path for an arc, splitting full turns into drawable segments. */
|
|
287
|
+
export declare function knobArcPath(startAngle: number, endAngle: number, radius: number): string;
|
|
273
288
|
//#endregion
|
|
274
|
-
//#region src/
|
|
289
|
+
//#region src/midi/access.d.ts
|
|
275
290
|
/**
|
|
276
|
-
*
|
|
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
|
+
* Why MIDI access could not be had.
|
|
291
292
|
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
293
|
+
* - `'NOT_SUPPORTED'`: the browser has no Web MIDI API, and asking again will
|
|
294
|
+
* not change that
|
|
295
|
+
* - `'PERMISSION_DENIED'`: the user or the browser said no. Asking again is
|
|
296
|
+
* worthwhile
|
|
297
|
+
* - `'UNAVAILABLE'`: anything else
|
|
295
298
|
*/
|
|
296
|
-
|
|
297
|
-
//#endregion
|
|
298
|
-
//#region src/midi/access.d.ts
|
|
299
|
-
/** @private */
|
|
300
|
-
export declare const PERMISSION_DENIED = "PERMISSION_DENIED";
|
|
301
|
-
/** @private */
|
|
302
|
-
export declare const NOT_SUPPORTED = "NOT_SUPPORTED";
|
|
303
|
-
/** @private */
|
|
304
|
-
export declare const UNAVAILABLE = "UNAVAILABLE";
|
|
305
|
-
/** @private */
|
|
306
|
-
type MIDIAccessError = typeof PERMISSION_DENIED | typeof NOT_SUPPORTED | typeof UNAVAILABLE;
|
|
299
|
+
type MIDIAccessError = 'NOT_SUPPORTED' | 'PERMISSION_DENIED' | 'UNAVAILABLE';
|
|
307
300
|
type MIDIAccessOptions = {
|
|
308
301
|
/**
|
|
309
302
|
* Ask for system exclusive messages as well.
|
|
@@ -345,13 +338,6 @@ interface MIDIAccessInstance {
|
|
|
345
338
|
export declare function createMIDIAccess(): MIDIAccessInstance;
|
|
346
339
|
//#endregion
|
|
347
340
|
//#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
341
|
/**
|
|
356
342
|
* Every handler is given the channel last, as 0-15. MIDI channels are written
|
|
357
343
|
* 1-16 on hardware, so add one before showing it to anyone.
|
|
@@ -360,7 +346,8 @@ type MIDIInputHandlers = {
|
|
|
360
346
|
onNoteOnEvent?: (note: number, velocity: number, channel: number) => void;
|
|
361
347
|
onNoteOffEvent?: (note: number, channel: number) => void;
|
|
362
348
|
/**
|
|
363
|
-
* The 14-bit bend, 0-16383, centred at
|
|
349
|
+
* The 14-bit bend, 0-16383, centred at 8192. `normalizePitchBend` in
|
|
350
|
+
* `@tremolo-ui/functions` turns it into -1 to 1.
|
|
364
351
|
*
|
|
365
352
|
* The two data bytes are little-endian — the first carries the low 7 bits —
|
|
366
353
|
* which is the other way round from every other message.
|
|
@@ -407,69 +394,37 @@ interface MIDIMessageInstance {
|
|
|
407
394
|
*/
|
|
408
395
|
export declare function createMIDIMessage(midiAccess: MIDIAccess | null, onMIDIMessage: (event: MIDIMessageEvent) => void): MIDIMessageInstance;
|
|
409
396
|
//#endregion
|
|
410
|
-
//#region src/piano/
|
|
411
|
-
type
|
|
412
|
-
first: number;
|
|
413
|
-
last: number;
|
|
414
|
-
};
|
|
415
|
-
/**
|
|
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.
|
|
424
|
-
*/
|
|
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;
|
|
397
|
+
//#region src/piano/shortcuts.d.ts
|
|
398
|
+
type KeyboardShortcuts = {
|
|
436
399
|
/**
|
|
437
|
-
*
|
|
400
|
+
* Keys laid out from `noteRange.first`, one entry per semitone.
|
|
438
401
|
*
|
|
439
|
-
*
|
|
402
|
+
* An empty string leaves that note without a shortcut: `KeyboardEvent.key` is
|
|
403
|
+
* never empty, so the entry can never match. Use it to skip the black keys
|
|
404
|
+
* (see {@link SHORTCUTS.HOME_ROW_NATURAL}) and keep the remaining entries
|
|
405
|
+
* lined up with the notes.
|
|
440
406
|
*/
|
|
441
|
-
|
|
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;
|
|
407
|
+
keys: string[];
|
|
408
|
+
};
|
|
453
409
|
/**
|
|
454
|
-
*
|
|
455
|
-
* pixels.
|
|
456
|
-
*
|
|
457
|
-
* Notes outside `noteRange` are placed too, so the value is negative below
|
|
458
|
-
* `noteRange.first`.
|
|
410
|
+
* Ready-made keyboard layouts. Both assume `noteRange.first` is a C.
|
|
459
411
|
*/
|
|
460
|
-
export declare
|
|
412
|
+
export declare const SHORTCUTS: {
|
|
413
|
+
/** Every semitone from C, over the two rows of a QWERTY keyboard. */
|
|
414
|
+
HOME_ROW: {
|
|
415
|
+
keys: string[];
|
|
416
|
+
};
|
|
417
|
+
/** The white keys only, on the home row. Black keys have no shortcut. */
|
|
418
|
+
HOME_ROW_NATURAL: {
|
|
419
|
+
keys: string[];
|
|
420
|
+
};
|
|
421
|
+
};
|
|
461
422
|
/**
|
|
462
|
-
*
|
|
463
|
-
*
|
|
464
|
-
*
|
|
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
|
|
423
|
+
* Where keyboard shortcuts listen. `root` handles keys only while the piano
|
|
424
|
+
* or one of its descendants has focus; `window` handles them anywhere on the
|
|
425
|
+
* page except in editable elements.
|
|
471
426
|
*/
|
|
472
|
-
|
|
427
|
+
type KeyboardShortcutsScope = 'root' | 'window';
|
|
473
428
|
//#endregion
|
|
474
429
|
//#region src/piano/index.d.ts
|
|
475
430
|
/**
|
|
@@ -499,6 +454,24 @@ interface PianoInputOptions {
|
|
|
499
454
|
* @default 127
|
|
500
455
|
*/
|
|
501
456
|
midiMax?: number;
|
|
457
|
+
/**
|
|
458
|
+
* Play notes from the computer keyboard: `keys[i]` plays
|
|
459
|
+
* `layout.noteRange.first + i`. `SHORTCUTS` has ready-made layouts.
|
|
460
|
+
*
|
|
461
|
+
* A held key keeps its note until it is released, the focus leaves (with
|
|
462
|
+
* `root`), or the window loses focus. Changing the keys, the scope or the
|
|
463
|
+
* note range releases every note a key is holding, since the key would
|
|
464
|
+
* mean something else on release.
|
|
465
|
+
*/
|
|
466
|
+
keyboardShortcuts?: KeyboardShortcuts;
|
|
467
|
+
/**
|
|
468
|
+
* Where keyboard shortcuts listen: on the element (`root`), which has to
|
|
469
|
+
* be focusable, or anywhere on the page (`window`). Keys typed into an
|
|
470
|
+
* editable element are ignored either way.
|
|
471
|
+
*
|
|
472
|
+
* @default 'root'
|
|
473
|
+
*/
|
|
474
|
+
keyboardShortcutsScope?: KeyboardShortcutsScope;
|
|
502
475
|
/** Called when a note starts sounding, not for each source that asks. */
|
|
503
476
|
onPlayNote?: (note: number, velocity?: number) => void;
|
|
504
477
|
/** Called once the last source holding a note has let go. */
|
|
@@ -513,8 +486,8 @@ interface PianoInputInstance {
|
|
|
513
486
|
*/
|
|
514
487
|
update: (options: Partial<PianoInputOptions>) => void;
|
|
515
488
|
/**
|
|
516
|
-
* Start a note from something other than a pointer
|
|
517
|
-
*
|
|
489
|
+
* Start a note from something other than a pointer or a shortcut: a MIDI
|
|
490
|
+
* message, an imperative call.
|
|
518
491
|
*/
|
|
519
492
|
noteOn: (note: number, options?: {
|
|
520
493
|
source?: NoteSource;
|
|
@@ -656,32 +629,6 @@ interface DragInstance {
|
|
|
656
629
|
*/
|
|
657
630
|
export declare function createDrag(element: Element, options?: DragOptions): DragInstance;
|
|
658
631
|
//#endregion
|
|
659
|
-
//#region src/xy.d.ts
|
|
660
|
-
/**
|
|
661
|
-
* A pair of per-axis values. The tuple elements are labelled, so editors show
|
|
662
|
-
* `[x: number, y: number]` rather than a bare pair.
|
|
663
|
-
*/
|
|
664
|
-
type XY<T> = [x: T, y: T];
|
|
665
|
-
/**
|
|
666
|
-
* A setting that may be given once for both axes, or per axis.
|
|
667
|
-
*
|
|
668
|
-
* A single value is told from a pair with `Array.isArray`, so the single form
|
|
669
|
-
* is only offered while `T` cannot itself be an array. Where it can, the pair
|
|
670
|
-
* is the only way to write it, since a lone array would be read as a pair.
|
|
671
|
-
*/
|
|
672
|
-
type XYInput<T> = [T] extends [readonly unknown[]] ? readonly [x: T, y: T] : T | readonly [x: T, y: T];
|
|
673
|
-
/**
|
|
674
|
-
* Spread a setting that may have been given as a single value.
|
|
675
|
-
*
|
|
676
|
-
* The parameter is written out rather than taken as `XYInput<T>`: a
|
|
677
|
-
* conditional type cannot be narrowed, so the constraint stays where it is
|
|
678
|
-
* declared and this takes both forms.
|
|
679
|
-
*
|
|
680
|
-
* The pair is copied rather than passed along, so that the result is a tuple
|
|
681
|
-
* the caller owns even when a readonly one was given.
|
|
682
|
-
*/
|
|
683
|
-
export declare function toXY<T>(value: T | readonly [x: T, y: T]): XY<T>;
|
|
684
|
-
//#endregion
|
|
685
632
|
//#region src/pointer/drag-value.d.ts
|
|
686
633
|
/**
|
|
687
634
|
* How the 0-1 travel of one axis maps onto a value.
|
|
@@ -821,10 +768,58 @@ interface DragValueInstance {
|
|
|
821
768
|
*/
|
|
822
769
|
export declare function createDragValue(element: Element, options: DragValueOptions): DragValueInstance;
|
|
823
770
|
//#endregion
|
|
771
|
+
//#region src/pointer/long-press.d.ts
|
|
772
|
+
interface LongPressOptions {
|
|
773
|
+
/** Called once on the press, then again every `interval` after `delay`. */
|
|
774
|
+
onPress: () => void;
|
|
775
|
+
/**
|
|
776
|
+
* How long the press has to be held before it starts repeating, in
|
|
777
|
+
* milliseconds.
|
|
778
|
+
*
|
|
779
|
+
* @default 500
|
|
780
|
+
*/
|
|
781
|
+
delay?: number;
|
|
782
|
+
/**
|
|
783
|
+
* How often it repeats once it has started, in milliseconds.
|
|
784
|
+
*
|
|
785
|
+
* @default 40
|
|
786
|
+
*/
|
|
787
|
+
interval?: number;
|
|
788
|
+
}
|
|
789
|
+
interface LongPressInstance {
|
|
790
|
+
/**
|
|
791
|
+
* Start a press, from a `pointerdown` or with no event at all. Only the
|
|
792
|
+
* primary button starts one, and a press already in progress is left alone.
|
|
793
|
+
*/
|
|
794
|
+
start: (event?: Pick<PointerEvent, 'button' | 'pointerId'>) => void;
|
|
795
|
+
/** End the press, as releasing the pointer would. */
|
|
796
|
+
stop: () => void;
|
|
797
|
+
/** Replace the given options. The press in progress picks them up. */
|
|
798
|
+
update: (options: Partial<LongPressOptions>) => void;
|
|
799
|
+
/** Whether a press is in progress. */
|
|
800
|
+
pressed: () => boolean;
|
|
801
|
+
destroy: () => void;
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Repeat an action while a pointer is held down: once on the press, then
|
|
805
|
+
* again every `interval` after `delay` — the way a stepper button or a
|
|
806
|
+
* key held on a keyboard behaves.
|
|
807
|
+
*
|
|
808
|
+
* The release is listened for on the window, not on the element: the
|
|
809
|
+
* pointer may be let go anywhere. Only the pointer that started the press
|
|
810
|
+
* ends it, so a second finger lifting elsewhere does not. The window losing
|
|
811
|
+
* focus ends it too, since the release would never arrive.
|
|
812
|
+
*/
|
|
813
|
+
export declare function createLongPress(options: LongPressOptions): LongPressInstance;
|
|
814
|
+
//#endregion
|
|
824
815
|
//#region src/pointer/wheel.d.ts
|
|
825
816
|
interface WheelOptions {
|
|
826
|
-
/**
|
|
827
|
-
|
|
817
|
+
/**
|
|
818
|
+
* Called for every wheel event that `requireFocus` lets through. The
|
|
819
|
+
* listener is not passive, so this may call `preventDefault()` to keep the
|
|
820
|
+
* page from scrolling.
|
|
821
|
+
*/
|
|
822
|
+
onWheel: (event: WheelEvent) => void;
|
|
828
823
|
/**
|
|
829
824
|
* Only report events while the focus is inside the element.
|
|
830
825
|
*
|
|
@@ -842,7 +837,7 @@ interface WheelOptions {
|
|
|
842
837
|
}
|
|
843
838
|
interface WheelInstance {
|
|
844
839
|
/** Replace the given options, keeping the listener in place. */
|
|
845
|
-
update: (options: WheelOptions) => void;
|
|
840
|
+
update: (options: Partial<WheelOptions>) => void;
|
|
846
841
|
destroy: () => void;
|
|
847
842
|
}
|
|
848
843
|
/**
|
|
@@ -851,67 +846,18 @@ interface WheelInstance {
|
|
|
851
846
|
* The listener is registered with `passive: false` so that the handler can call
|
|
852
847
|
* `preventDefault()` to stop the page from scrolling.
|
|
853
848
|
*/
|
|
854
|
-
export declare function createWheel(element: Element,
|
|
849
|
+
export declare function createWheel(element: Element, options: WheelOptions): WheelInstance;
|
|
855
850
|
//#endregion
|
|
856
|
-
//#region src/
|
|
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;
|
|
851
|
+
//#region src/position.d.ts
|
|
908
852
|
/**
|
|
909
|
-
*
|
|
853
|
+
* Where a value sits along a track, as a whole percentage from its start.
|
|
910
854
|
*
|
|
911
|
-
*
|
|
912
|
-
*
|
|
855
|
+
* Normalized along the scale, so a thumb, the fill behind it and the marks
|
|
856
|
+
* all sit on the curve the drag follows. `reversed` measures from the other
|
|
857
|
+
* end — for a track that grows upwards or leftwards on screen, since CSS
|
|
858
|
+
* places things from the top and the left.
|
|
913
859
|
*/
|
|
914
|
-
export declare function
|
|
860
|
+
export declare function valuePercent(value: number, { min, max, scale }: Pick<ValueRange, 'min' | 'max' | 'scale'>, reversed?: boolean): number;
|
|
915
861
|
//#endregion
|
|
916
|
-
export
|
|
862
|
+
export { type AnimationCanvasInstance, type AnimationCanvasOptions, type AnimationFrame, type AxisOptions, type CanvasDrawFunction, type CanvasInitFunction, type ChangeGestureInstance, type ChangeGestureOptions, type ChangeSource, type DragInstance, type DragOptions, type DragState, type DragValueInstance, type DragValueMapping, type DragValueOptions, type DropZoneInstance, type DropZoneOptions, type DropZoneState, type InputEventOption, type KeyboardShortcuts, type KeyboardShortcutsScope, type KnobAngleOptions, type KnobAngles, type LongPressInstance, type LongPressOptions, type MIDIAccessError, type MIDIAccessInstance, type MIDIAccessOptions, type MIDIAccessState, type MIDIInputHandlers, type MIDIInputInstance, type MIDIMessageInstance, type MappingContext, type MarksOptions, type Modifier, type ModifierMap, type ModifierState, type ModifierValue, type NoteRange, type NoteSource, type PianoInputInstance, type PianoInputOptions, type PianoLayout, type PointPosition, type PointsEditorInstance, type PointsEditorOptions, type PointsEditorPoint, type SelectionBoxRect, type WheelInstance, type WheelOptions, type XY, type XYInput, createPointsEditor, notePosition };
|
|
917
863
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/canvas/animation.ts","../src/
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/canvas/animation.ts","../src/file/drop-zone.ts","../src/input/change-gesture.ts","../src/input/defaults.ts","../src/knob/geometry.ts","../src/midi/access.ts","../src/midi/input.ts","../src/midi/message.ts","../src/piano/shortcuts.ts","../src/piano/index.ts","../src/pointer/drag.ts","../src/pointer/drag-value.ts","../src/pointer/long-press.ts","../src/pointer/wheel.ts","../src/position.ts"],"mappings":"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"}
|