@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
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_xy = require("./xy-5Oc6JeJr.cjs");
|
|
3
|
+
let _tremolo_ui_functions = require("@tremolo-ui/functions");
|
|
4
|
+
//#region src/input/check-steps.ts
|
|
5
|
+
/** Positions probed across the travel. The ends are left out so that the
|
|
6
|
+
* clamp at `min` and `max` cannot be mistaken for a press that does nothing. */
|
|
7
|
+
const PROBES = [
|
|
8
|
+
.1,
|
|
9
|
+
.2,
|
|
10
|
+
.3,
|
|
11
|
+
.4,
|
|
12
|
+
.5,
|
|
13
|
+
.6,
|
|
14
|
+
.7,
|
|
15
|
+
.8,
|
|
16
|
+
.9
|
|
17
|
+
];
|
|
18
|
+
const NONE = {
|
|
19
|
+
shiftKey: false,
|
|
20
|
+
altKey: false,
|
|
21
|
+
ctrlKey: false,
|
|
22
|
+
metaKey: false
|
|
23
|
+
};
|
|
24
|
+
const MODIFIER_STATE = {
|
|
25
|
+
shift: {
|
|
26
|
+
...NONE,
|
|
27
|
+
shiftKey: true
|
|
28
|
+
},
|
|
29
|
+
alt: {
|
|
30
|
+
...NONE,
|
|
31
|
+
altKey: true
|
|
32
|
+
},
|
|
33
|
+
ctrl: {
|
|
34
|
+
...NONE,
|
|
35
|
+
ctrlKey: true
|
|
36
|
+
},
|
|
37
|
+
meta: {
|
|
38
|
+
...NONE,
|
|
39
|
+
metaKey: true
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
/** The held keys that select each entry of a modifier-aware input option. */
|
|
43
|
+
function entries(options) {
|
|
44
|
+
if (Array.isArray(options)) return [NONE];
|
|
45
|
+
return Object.keys(options).map((key) => MODIFIER_STATE[key] ?? NONE);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Press every entry of `options` at nine points along the travel and report
|
|
49
|
+
* whether anything came of it.
|
|
50
|
+
*
|
|
51
|
+
* Run against `applyDelta` itself rather than against a reading of `step`:
|
|
52
|
+
* the whole point is that the amount, the step and the scale interact, and
|
|
53
|
+
* the pipeline is the only thing that knows how.
|
|
54
|
+
*/
|
|
55
|
+
function probe(options, range, format) {
|
|
56
|
+
const { min, max, scale = _tremolo_ui_functions.linearScale } = range;
|
|
57
|
+
const outcome = {
|
|
58
|
+
moved: false,
|
|
59
|
+
visible: false
|
|
60
|
+
};
|
|
61
|
+
for (const modifiers of entries(options)) for (const position of PROBES) {
|
|
62
|
+
const value = scale.denormalize(position, min, max);
|
|
63
|
+
const up = require_xy.applyDelta(value, 1, options, range, modifiers);
|
|
64
|
+
const down = require_xy.applyDelta(value, -1, options, range, modifiers);
|
|
65
|
+
if (up !== value || down !== value) outcome.moved = true;
|
|
66
|
+
if (!format) continue;
|
|
67
|
+
const shown = format(value);
|
|
68
|
+
if (format(up) !== shown || format(down) !== shown) outcome.visible = true;
|
|
69
|
+
}
|
|
70
|
+
return outcome;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The warnings to show when a key press or a wheel notch cannot produce a
|
|
74
|
+
* change the user can see. Empty when there is nothing to say.
|
|
75
|
+
*
|
|
76
|
+
* Two settings that are each fine on their own can cancel out, and nothing
|
|
77
|
+
* fails when they do — the control simply sits there:
|
|
78
|
+
*
|
|
79
|
+
* - **`step` coarser than the amount.** `keyboard={['raw', 0.1]}` with
|
|
80
|
+
* `step={1}` rounds every press straight back to where it started
|
|
81
|
+
* - **the display coarser than the amount.** A `format` showing two decimals
|
|
82
|
+
* of a kHz value cannot show a press worth 1 Hz
|
|
83
|
+
*
|
|
84
|
+
* The second is only reported when the press is invisible at *every* point
|
|
85
|
+
* along the travel. A display that rounds is a deliberate choice and is
|
|
86
|
+
* normally right — it is being too coarse everywhere that makes it a mistake.
|
|
87
|
+
*
|
|
88
|
+
* **Meant for development builds only.** It probes the whole travel, so call
|
|
89
|
+
* it behind an inline `process.env.NODE_ENV` check: the bundler then drops
|
|
90
|
+
* the call, and this function and its messages with it.
|
|
91
|
+
*/
|
|
92
|
+
function checkSteps({ component, axis, range, keyboard, wheel, format }) {
|
|
93
|
+
if (!range || !(range.min < range.max)) return [];
|
|
94
|
+
const where = axis ? `${component} (${axis})` : component;
|
|
95
|
+
const warnings = [];
|
|
96
|
+
for (const [name, options] of [["keyboard", keyboard], ["wheel", wheel]]) {
|
|
97
|
+
if (!options) continue;
|
|
98
|
+
const { moved, visible } = probe(options, range, format);
|
|
99
|
+
if (!moved) warnings.push(`[tremolo-ui] ${where}: \`${name}\` cannot move the value.` + (range.step !== void 0 ? ` Each press is smaller than \`step\` (${range.step}), so it rounds` : " Each press rounds") + " straight back to where it started.");
|
|
100
|
+
else if (format && !visible) warnings.push(`[tremolo-ui] ${where}: \`${name}\` moves the value, but \`format\` shows the same text before and after, everywhere in the range. The display is too coarse for it to be seen.`);
|
|
101
|
+
}
|
|
102
|
+
return warnings;
|
|
103
|
+
}
|
|
104
|
+
//#endregion
|
|
105
|
+
//#region src/input/direction.ts
|
|
106
|
+
/** Is `key` one of the four arrow keys? */
|
|
107
|
+
function isArrowKey(key) {
|
|
108
|
+
return key === "ArrowRight" || key === "ArrowLeft" || key === "ArrowUp" || key === "ArrowDown";
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* The direction an arrow key moves a single value: right and up raise it.
|
|
112
|
+
* `null` for any other key.
|
|
113
|
+
*/
|
|
114
|
+
function arrowKeyDirection(key) {
|
|
115
|
+
if (!isArrowKey(key)) return null;
|
|
116
|
+
return key === "ArrowRight" || key === "ArrowUp" ? 1 : -1;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* The axis and direction an arrow key moves a position on screen. `null` for
|
|
120
|
+
* any other key.
|
|
121
|
+
*
|
|
122
|
+
* The key picks the axis, whichever element inside the control holds the
|
|
123
|
+
* focus: a two-dimensional control is one control to the person moving it.
|
|
124
|
+
*/
|
|
125
|
+
function arrowKeyMove(key) {
|
|
126
|
+
if (!isArrowKey(key)) return null;
|
|
127
|
+
return {
|
|
128
|
+
axis: key === "ArrowRight" || key === "ArrowLeft" ? 0 : 1,
|
|
129
|
+
direction: key === "ArrowLeft" || key === "ArrowUp" ? -1 : 1
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* The direction one wheel event moves a single value: scrolling up raises it.
|
|
134
|
+
* `null` when the event carries no movement the control reads.
|
|
135
|
+
*/
|
|
136
|
+
function wheelDirection(event, { horizontal = false } = {}) {
|
|
137
|
+
if (horizontal && event.deltaX !== 0) return event.deltaX > 0 ? 1 : -1;
|
|
138
|
+
if (event.deltaY === 0) return null;
|
|
139
|
+
return event.deltaY > 0 ? -1 : 1;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* The axis and direction one wheel event moves a position on screen. `null`
|
|
143
|
+
* when the event carries no movement.
|
|
144
|
+
*
|
|
145
|
+
* Scrolling moves y, and shift switches to x. Browsers turn shift+wheel into
|
|
146
|
+
* horizontal scrolling: `deltaY` comes out empty and `deltaX` carries the
|
|
147
|
+
* movement. Reading whichever axis moved keeps shift working as the x-axis
|
|
148
|
+
* modifier — and picks up a trackpad's own horizontal gesture, which never
|
|
149
|
+
* had a modifier.
|
|
150
|
+
*/
|
|
151
|
+
function wheelMove(event) {
|
|
152
|
+
const horizontal = event.deltaX !== 0;
|
|
153
|
+
const delta = horizontal ? event.deltaX : event.deltaY;
|
|
154
|
+
if (delta === 0) return null;
|
|
155
|
+
return {
|
|
156
|
+
axis: horizontal || event.shiftKey ? 0 : 1,
|
|
157
|
+
direction: delta < 0 ? -1 : 1
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
//#endregion
|
|
161
|
+
//#region src/number-input/stepper-drag.ts
|
|
162
|
+
/**
|
|
163
|
+
* Drag up and down on the steppers of a number input to move its value, one
|
|
164
|
+
* `step` every `pixels` — up raises it, as on a knob.
|
|
165
|
+
*
|
|
166
|
+
* Counted from where the drag started moving rather than added up per
|
|
167
|
+
* event, so rounding cannot accumulate. The start is taken on the first
|
|
168
|
+
* move, not on pointerdown: a stepper button acts on pointerdown, so by then
|
|
169
|
+
* the value may already have been nudged once, and the drag carries on from
|
|
170
|
+
* there.
|
|
171
|
+
*/
|
|
172
|
+
function createStepperDrag(element, options) {
|
|
173
|
+
let opts = options;
|
|
174
|
+
let origin = null;
|
|
175
|
+
let moved = false;
|
|
176
|
+
/**
|
|
177
|
+
* Which sensitivity the drag is counting at, and where the previous event
|
|
178
|
+
* was — a key produces no pointer event of its own, so a change is only
|
|
179
|
+
* seen on the next move and has to be dated back to the one before it.
|
|
180
|
+
*/
|
|
181
|
+
let factor = 1;
|
|
182
|
+
let previousY = 0;
|
|
183
|
+
const drag = require_xy.createDrag(element, {
|
|
184
|
+
threshold: 1,
|
|
185
|
+
cursor: opts.cursor ?? "ns-resize",
|
|
186
|
+
pointerLock: opts.pointerLock,
|
|
187
|
+
onDragStart: (state) => {
|
|
188
|
+
origin = null;
|
|
189
|
+
moved = false;
|
|
190
|
+
factor = require_xy.selectModifier(opts.sensitivity ?? require_xy.DEFAULT_DRAG_SENSITIVITY, state.event).value;
|
|
191
|
+
},
|
|
192
|
+
onDrag: (state) => {
|
|
193
|
+
const { y } = state;
|
|
194
|
+
if (!origin) {
|
|
195
|
+
origin = {
|
|
196
|
+
y,
|
|
197
|
+
value: opts.getValue()
|
|
198
|
+
};
|
|
199
|
+
previousY = y;
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const sensitivity = opts.sensitivity ?? require_xy.DEFAULT_DRAG_SENSITIVITY;
|
|
203
|
+
const next = require_xy.selectModifier(sensitivity, state.event).value;
|
|
204
|
+
if (next !== factor) {
|
|
205
|
+
origin = {
|
|
206
|
+
y: previousY,
|
|
207
|
+
value: opts.getValue()
|
|
208
|
+
};
|
|
209
|
+
factor = next;
|
|
210
|
+
}
|
|
211
|
+
previousY = y;
|
|
212
|
+
const steps = Math.round(-(y - origin.y) / (opts.pixels ?? 1));
|
|
213
|
+
if (steps === 0) return;
|
|
214
|
+
moved = true;
|
|
215
|
+
const step = opts.range.step ?? 1;
|
|
216
|
+
const amounts = require_xy.mapModifier(sensitivity, (f) => ["raw", step * f]);
|
|
217
|
+
opts.onChange(require_xy.applyDelta(origin.value, steps, amounts, opts.range, state.event));
|
|
218
|
+
},
|
|
219
|
+
onDragEnd: () => {
|
|
220
|
+
moved = false;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
return {
|
|
224
|
+
update: (next) => {
|
|
225
|
+
opts = {
|
|
226
|
+
...opts,
|
|
227
|
+
...next
|
|
228
|
+
};
|
|
229
|
+
if ("pointerLock" in next) drag.update({ pointerLock: opts.pointerLock });
|
|
230
|
+
if ("cursor" in next) drag.update({ cursor: opts.cursor ?? "ns-resize" });
|
|
231
|
+
},
|
|
232
|
+
moved: () => moved,
|
|
233
|
+
destroy: () => drag.destroy()
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
//#endregion
|
|
237
|
+
//#region src/number-input/value.ts
|
|
238
|
+
/**
|
|
239
|
+
* The ranges a number input moves its value across, with the open ends
|
|
240
|
+
* filled in.
|
|
241
|
+
*
|
|
242
|
+
* A normalized amount needs a finite span even when an end is unbounded or
|
|
243
|
+
* clamping is off. Safe integers provide one without overflowing the span a
|
|
244
|
+
* scale calculates. A raw amount needs no span, so its open ends can cover
|
|
245
|
+
* every finite number instead of stopping at the safe-integer range.
|
|
246
|
+
*/
|
|
247
|
+
function numberInputRanges({ min, max, step, scale, clampValue = true }) {
|
|
248
|
+
const lo = clampValue ? min : void 0;
|
|
249
|
+
const hi = clampValue ? max : void 0;
|
|
250
|
+
return {
|
|
251
|
+
normalized: {
|
|
252
|
+
min: lo ?? Number.MIN_SAFE_INTEGER,
|
|
253
|
+
max: hi ?? Number.MAX_SAFE_INTEGER,
|
|
254
|
+
step,
|
|
255
|
+
scale
|
|
256
|
+
},
|
|
257
|
+
raw: {
|
|
258
|
+
min: lo ?? -Number.MAX_VALUE,
|
|
259
|
+
max: hi ?? Number.MAX_VALUE,
|
|
260
|
+
step,
|
|
261
|
+
scale
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Move a number input's value by one press of a key, a wheel notch or a
|
|
267
|
+
* stepper, picking the range that suits the kind of amount. See `applyDelta`.
|
|
268
|
+
*/
|
|
269
|
+
function nudgeNumberInput(value, direction, options, ranges, modifiers) {
|
|
270
|
+
const [mode] = require_xy.selectModifier(options, modifiers).value;
|
|
271
|
+
return require_xy.applyDelta(value, direction, options, mode === "raw" ? ranges.raw : ranges.normalized, modifiers);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Where the value stands against the ends: whether it can go no further
|
|
275
|
+
* down or up, and whether it lies outside them — which only an unclamped
|
|
276
|
+
* input, or a value set from outside, can do.
|
|
277
|
+
*/
|
|
278
|
+
function numberInputBounds(value, { min, max, clampValue = true }) {
|
|
279
|
+
return {
|
|
280
|
+
atMin: clampValue && min !== void 0 && value <= min,
|
|
281
|
+
atMax: clampValue && max !== void 0 && value >= max,
|
|
282
|
+
outOfRange: min !== void 0 && value < min || max !== void 0 && value > max
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* The value typed text commits to, or `null` when there is no number in it.
|
|
287
|
+
*
|
|
288
|
+
* Text with no number is not a value: the input should go back to what it
|
|
289
|
+
* was showing rather than commit a zero the user never typed. What is read
|
|
290
|
+
* is clamped here and not while typing, since clamping as the user types
|
|
291
|
+
* would make "1500" impossible to enter into an input whose max is 100.
|
|
292
|
+
*/
|
|
293
|
+
function commitNumberInputText(text, parse, { min, max, clampValue = true }) {
|
|
294
|
+
const parsed = parse(text);
|
|
295
|
+
if (!Number.isFinite(parsed)) return null;
|
|
296
|
+
let committed = parsed;
|
|
297
|
+
if (clampValue && min !== void 0) committed = Math.max(committed, min);
|
|
298
|
+
if (clampValue && max !== void 0) committed = Math.min(committed, max);
|
|
299
|
+
return committed;
|
|
300
|
+
}
|
|
301
|
+
//#endregion
|
|
302
|
+
//#region src/number-input/text.ts
|
|
303
|
+
/**
|
|
304
|
+
* Reading a number out of the text of a number input, and keeping the caret
|
|
305
|
+
* in place while the number under it changes.
|
|
306
|
+
*
|
|
307
|
+
* The text is whatever `format` made of the value — `"440 Hz"`, `"-6.0 dB"` —
|
|
308
|
+
* or a half-typed entry, so none of this assumes the text is a number alone.
|
|
309
|
+
*/
|
|
310
|
+
/** The signs a number can carry. U+2212 is what some `Intl.NumberFormat` locales write. */
|
|
311
|
+
const SIGNS = "+-−";
|
|
312
|
+
/**
|
|
313
|
+
* Where the number is in the text: from its first digit to its last, with the
|
|
314
|
+
* sign and the decimal point in front of it. Whatever is left on either side
|
|
315
|
+
* is taken for the unit, so it does not matter whether a space separates them,
|
|
316
|
+
* or what the number looks like — `+6.0`, `1e+21`, `1,000` and `1:30` are each
|
|
317
|
+
* one number. `null` when the text has no digit at all.
|
|
318
|
+
*/
|
|
319
|
+
function numberSpan(text) {
|
|
320
|
+
const first = text.search(/\d/);
|
|
321
|
+
if (first === -1) return null;
|
|
322
|
+
let start = first;
|
|
323
|
+
if (text[start - 1] === ".") start -= 1;
|
|
324
|
+
if (start > 0 && SIGNS.includes(text[start - 1])) start -= 1;
|
|
325
|
+
return {
|
|
326
|
+
start,
|
|
327
|
+
end: text.search(/\d\D*$/) + 1
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/** A plain number, and nothing else: no grouping, no other separators. */
|
|
331
|
+
const PLAIN_NUMBER = /^[+\-\u2212]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+\-\u2212]?\d+)?$/;
|
|
332
|
+
/**
|
|
333
|
+
* The number in the text, with the unit after it ignored: the default `parse`
|
|
334
|
+
* of a number input.
|
|
335
|
+
*
|
|
336
|
+
* `NaN`, which leaves the value alone, whenever the number cannot be read
|
|
337
|
+
* safely, rather than a part of it: text with no number, a number that is not
|
|
338
|
+
* a plain one (`1,000` would otherwise read as 1, and `1:30` as 1), and text
|
|
339
|
+
* with something in front of the number, which can change what it means (the
|
|
340
|
+
* `L` of a pan reading `L 30`). A `format` that writes any of those needs its
|
|
341
|
+
* own `parse`.
|
|
342
|
+
*/
|
|
343
|
+
function parseNumberText(text) {
|
|
344
|
+
const span = numberSpan(text);
|
|
345
|
+
if (!span || text.slice(0, span.start).trim() !== "") return NaN;
|
|
346
|
+
const number = text.slice(span.start, span.end);
|
|
347
|
+
return PLAIN_NUMBER.test(number) ? Number(number.replace(/\u2212/g, "-")) : NaN;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* The index the caret is measured against: the decimal point, or where one
|
|
351
|
+
* would go if the number has none — in front of an exponent, if there is one.
|
|
352
|
+
*
|
|
353
|
+
* Measuring from an end instead would slide the caret across a digit whenever
|
|
354
|
+
* the number changed length — `9.9` to `10.0` gains a character in front, `10`
|
|
355
|
+
* to `9` loses one, and so does `9e+9` to `1e+10` behind — which is exactly
|
|
356
|
+
* what stepping does.
|
|
357
|
+
*/
|
|
358
|
+
function decimalAnchor(text, span) {
|
|
359
|
+
const number = text.slice(span.start, span.end);
|
|
360
|
+
const exponent = number.search(/[eE][+\-\u2212]?\d/);
|
|
361
|
+
const mantissa = exponent === -1 ? number : number.slice(0, exponent);
|
|
362
|
+
const dot = mantissa.indexOf(".");
|
|
363
|
+
return span.start + (dot === -1 ? mantissa.length : dot);
|
|
364
|
+
}
|
|
365
|
+
const NO_NUMBER = {
|
|
366
|
+
start: 0,
|
|
367
|
+
end: 0
|
|
368
|
+
};
|
|
369
|
+
/**
|
|
370
|
+
* Where the caret is, relative to the decimal point, so that it can be put
|
|
371
|
+
* back at the same digit once the value has changed. See
|
|
372
|
+
* {@link caretAtDecimalOffset}.
|
|
373
|
+
*/
|
|
374
|
+
function caretDecimalOffset(text, caret) {
|
|
375
|
+
return caret - decimalAnchor(text, numberSpan(text) ?? NO_NUMBER);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* The caret position `offset` characters from the decimal point of the new
|
|
379
|
+
* text, kept within the number.
|
|
380
|
+
*
|
|
381
|
+
* @example
|
|
382
|
+
* // The caret sits in front of the point of "9.9" when ArrowUp turns it
|
|
383
|
+
* // into "10.0"
|
|
384
|
+
* const offset = caretDecimalOffset('9.9', 1) // 0
|
|
385
|
+
* caretAtDecimalOffset('10.0', offset) // 2: still in front of the point
|
|
386
|
+
*/
|
|
387
|
+
function caretAtDecimalOffset(text, offset) {
|
|
388
|
+
const span = numberSpan(text) ?? NO_NUMBER;
|
|
389
|
+
const place = decimalAnchor(text, span) + offset;
|
|
390
|
+
return Math.max(span.start, Math.min(place, span.end));
|
|
391
|
+
}
|
|
392
|
+
//#endregion
|
|
393
|
+
//#region src/options/replace.ts
|
|
394
|
+
/**
|
|
395
|
+
* The `update()` argument that leaves an instance with exactly `next` as its
|
|
396
|
+
* options.
|
|
397
|
+
*
|
|
398
|
+
* `update()` merges into what the instance has, which suits a wrapper that
|
|
399
|
+
* pushes one setting at a time. A wrapper handed the whole new set instead —
|
|
400
|
+
* a Svelte action, a Vue composable — has to clear what the new set no longer
|
|
401
|
+
* carries, or a handler taken away, or an option left to its default, keeps
|
|
402
|
+
* working.
|
|
403
|
+
*
|
|
404
|
+
* @example
|
|
405
|
+
* instance.update(replaceOptions(previous, next))
|
|
406
|
+
*/
|
|
407
|
+
function replaceOptions(previous, next) {
|
|
408
|
+
return {
|
|
409
|
+
...Object.fromEntries(Object.keys(previous ?? {}).map((key) => [key, void 0])),
|
|
410
|
+
...next
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
//#endregion
|
|
414
|
+
//#region src/slider/decimal-digits.ts
|
|
415
|
+
/**
|
|
416
|
+
* How many digits a number carries after the decimal point.
|
|
417
|
+
*
|
|
418
|
+
* `Slider.Marks` builds its labels by multiplying an interval, which leaves
|
|
419
|
+
* binary debris in the last digits (`0.1 * 3` is `0.30000000000000004`). The
|
|
420
|
+
* count is what tells `toFixed` how far to round that back.
|
|
421
|
+
*
|
|
422
|
+
* The exponent form has to be handled separately: `String(1e-7)` is `'1e-7'`,
|
|
423
|
+
* which has no decimal point at all, so reading the text after the point
|
|
424
|
+
* reports no digits and the interval rounds away to whole numbers.
|
|
425
|
+
*/
|
|
426
|
+
function decimalDigits(x) {
|
|
427
|
+
if (!Number.isFinite(x)) return 0;
|
|
428
|
+
const text = String(x);
|
|
429
|
+
const e = text.indexOf("e");
|
|
430
|
+
if (e === -1) return text.split(".")[1]?.length ?? 0;
|
|
431
|
+
const fraction = text.slice(0, e).split(".")[1]?.length ?? 0;
|
|
432
|
+
return Math.max(0, fraction - Number(text.slice(e + 1)));
|
|
433
|
+
}
|
|
434
|
+
//#endregion
|
|
435
|
+
//#region src/slider/marks.ts
|
|
436
|
+
/**
|
|
437
|
+
* The marks `options` asks for between `min` and `max`, in ascending order.
|
|
438
|
+
*
|
|
439
|
+
* Each value is a whole multiple of the interval, rounded to the digits the
|
|
440
|
+
* interval has: stepping by 0.1 would otherwise put binary debris in the
|
|
441
|
+
* labels (`0.1 * 3` is `0.30000000000000004`).
|
|
442
|
+
*/
|
|
443
|
+
function sliderMarks(options, min, max, step) {
|
|
444
|
+
const { per, mark = true, label = true } = typeof options === "object" ? options : { per: options };
|
|
445
|
+
const optionsList = [];
|
|
446
|
+
const interval = per === "step" ? step : per;
|
|
447
|
+
const count = Math.floor(max / interval) - Math.ceil(min / interval) + 1;
|
|
448
|
+
for (let i = 0; i < count; i++) {
|
|
449
|
+
const value = (0, _tremolo_ui_functions.toFixed)(interval * (Math.ceil(min / interval) + i), decimalDigits(interval));
|
|
450
|
+
optionsList.push({
|
|
451
|
+
value,
|
|
452
|
+
mark,
|
|
453
|
+
label
|
|
454
|
+
});
|
|
455
|
+
}
|
|
456
|
+
return optionsList;
|
|
457
|
+
}
|
|
458
|
+
//#endregion
|
|
459
|
+
//#region src/style.ts
|
|
460
|
+
/**
|
|
461
|
+
* A number as a CSS length, for a custom property.
|
|
462
|
+
*
|
|
463
|
+
* A custom property takes whatever text it is given, so `--size: 50` comes out
|
|
464
|
+
* as the invalid `50` rather than `50px`. React appends `px` to a bare number
|
|
465
|
+
* only for the properties it knows take a length, and a custom property is
|
|
466
|
+
* never one of them; Vue and Svelte append nothing at all. Anything already a
|
|
467
|
+
* string is passed through, so `'3rem'` and `'100%'` still work.
|
|
468
|
+
*/
|
|
469
|
+
function cssLength(value) {
|
|
470
|
+
return typeof value === "number" ? `${value}px` : value;
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Take an element out of sight while leaving it in the accessibility tree and
|
|
474
|
+
* in the tab order.
|
|
475
|
+
*
|
|
476
|
+
* `display: none` and `visibility: hidden` would remove it from both, and the
|
|
477
|
+
* native control underneath a headless component is what carries the ARIA and
|
|
478
|
+
* the keyboard behaviour. `pointer-events: none` is safe because nothing is
|
|
479
|
+
* ever clicked here directly: a `<label>` forwards its click, and a drag is
|
|
480
|
+
* handled by the part that is visible.
|
|
481
|
+
*
|
|
482
|
+
* Every value is a string with its unit, so the object can be handed to any
|
|
483
|
+
* framework's `style` binding as it is.
|
|
484
|
+
*/
|
|
485
|
+
const visuallyHiddenStyle = {
|
|
486
|
+
position: "absolute",
|
|
487
|
+
width: "1px",
|
|
488
|
+
height: "1px",
|
|
489
|
+
padding: "0",
|
|
490
|
+
margin: "-1px",
|
|
491
|
+
overflow: "hidden",
|
|
492
|
+
clip: "rect(0, 0, 0, 0)",
|
|
493
|
+
clipPath: "inset(50%)",
|
|
494
|
+
whiteSpace: "nowrap",
|
|
495
|
+
border: "0",
|
|
496
|
+
pointerEvents: "none"
|
|
497
|
+
};
|
|
498
|
+
//#endregion
|
|
499
|
+
exports.POINTS_EDITOR_DEFAULT_KEYBOARD = require_xy.POINTS_EDITOR_DEFAULT_KEYBOARD;
|
|
500
|
+
exports.POINTS_EDITOR_DEFAULT_WHEEL = require_xy.POINTS_EDITOR_DEFAULT_WHEEL;
|
|
501
|
+
exports.POINT_AXIS = require_xy.POINT_AXIS;
|
|
502
|
+
exports.applyDelta = require_xy.applyDelta;
|
|
503
|
+
exports.arrowKeyDirection = arrowKeyDirection;
|
|
504
|
+
exports.arrowKeyMove = arrowKeyMove;
|
|
505
|
+
exports.blackKeyWidth = require_xy.blackKeyWidth;
|
|
506
|
+
exports.caretAtDecimalOffset = caretAtDecimalOffset;
|
|
507
|
+
exports.caretDecimalOffset = caretDecimalOffset;
|
|
508
|
+
exports.checkSteps = checkSteps;
|
|
509
|
+
exports.clampPoint = require_xy.clampPoint;
|
|
510
|
+
exports.commitNumberInputText = commitNumberInputText;
|
|
511
|
+
exports.createStepperDrag = createStepperDrag;
|
|
512
|
+
exports.cssLength = cssLength;
|
|
513
|
+
exports.fitWhiteKeyWidth = require_xy.fitWhiteKeyWidth;
|
|
514
|
+
exports.getNoteRangeArray = require_xy.getNoteRangeArray;
|
|
515
|
+
exports.nudgeNumberInput = nudgeNumberInput;
|
|
516
|
+
exports.numberInputBounds = numberInputBounds;
|
|
517
|
+
exports.numberInputRanges = numberInputRanges;
|
|
518
|
+
exports.numberSpan = numberSpan;
|
|
519
|
+
exports.parseNumberText = parseNumberText;
|
|
520
|
+
exports.partitionByAccept = require_xy.partitionByAccept;
|
|
521
|
+
exports.pianoWidth = require_xy.pianoWidth;
|
|
522
|
+
exports.replaceOptions = replaceOptions;
|
|
523
|
+
exports.selectModifier = require_xy.selectModifier;
|
|
524
|
+
exports.sliderMarks = sliderMarks;
|
|
525
|
+
exports.toXY = require_xy.toXY;
|
|
526
|
+
exports.visuallyHiddenStyle = visuallyHiddenStyle;
|
|
527
|
+
exports.wheelDirection = wheelDirection;
|
|
528
|
+
exports.wheelMove = wheelMove;
|
|
529
|
+
|
|
530
|
+
//# sourceMappingURL=internal.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"internal.cjs","names":["linearScale","applyDelta","createDrag","selectModifier","DEFAULT_DRAG_SENSITIVITY","mapModifier","applyDelta","selectModifier","applyDelta","toFixed"],"sources":["../src/input/check-steps.ts","../src/input/direction.ts","../src/number-input/stepper-drag.ts","../src/number-input/value.ts","../src/number-input/text.ts","../src/options/replace.ts","../src/slider/decimal-digits.ts","../src/slider/marks.ts","../src/style.ts"],"sourcesContent":["import { linearScale, type ValueRange } from '@tremolo-ui/functions'\n\nimport { applyDelta } from './apply-delta'\nimport {\n type InputEventOption,\n type ModifierState,\n type ModifierValue,\n} from './modifiers'\n\n/** Positions probed across the travel. The ends are left out so that the\n * clamp at `min` and `max` cannot be mistaken for a press that does nothing. */\nconst PROBES = [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9]\n\nconst NONE: ModifierState = {\n shiftKey: false,\n altKey: false,\n ctrlKey: false,\n metaKey: false,\n}\n\nconst MODIFIER_STATE: Record<string, ModifierState> = {\n shift: { ...NONE, shiftKey: true },\n alt: { ...NONE, altKey: true },\n ctrl: { ...NONE, ctrlKey: true },\n meta: { ...NONE, metaKey: true },\n}\n\n/** The held keys that select each entry of a modifier-aware input option. */\nfunction entries(options: ModifierValue<InputEventOption>): ModifierState[] {\n if (Array.isArray(options)) return [NONE]\n return Object.keys(options).map((key) => MODIFIER_STATE[key] ?? NONE)\n}\n\ninterface Outcome {\n /** The press changed the value at least once across the travel. */\n moved: boolean\n /** The change reached the displayed text at least once. */\n visible: boolean\n}\n\n/**\n * Press every entry of `options` at nine points along the travel and report\n * whether anything came of it.\n *\n * Run against `applyDelta` itself rather than against a reading of `step`:\n * the whole point is that the amount, the step and the scale interact, and\n * the pipeline is the only thing that knows how.\n */\nfunction probe(\n options: ModifierValue<InputEventOption>,\n range: ValueRange,\n format?: (value: number) => string,\n): Outcome {\n const { min, max, scale = linearScale } = range\n const outcome: Outcome = { moved: false, visible: false }\n for (const modifiers of entries(options)) {\n for (const position of PROBES) {\n const value = scale.denormalize(position, min, max)\n const up = applyDelta(value, 1, options, range, modifiers)\n const down = applyDelta(value, -1, options, range, modifiers)\n if (up !== value || down !== value) outcome.moved = true\n if (!format) continue\n const shown = format(value)\n if (format(up) !== shown || format(down) !== shown) outcome.visible = true\n }\n }\n return outcome\n}\n\nexport interface CheckStepsOptions {\n /** The component, for the message. */\n component: string\n /** The axis, for a component that has more than one. */\n axis?: string\n /**\n * The range to probe, or `null` to check nothing. An unbounded input has no\n * travel to sample, so `NumberInput` passes `null` when `min` and `max` are\n * not both there.\n */\n range: ValueRange | null\n keyboard?: ModifierValue<InputEventOption> | null\n wheel?: ModifierValue<InputEventOption> | null\n /**\n * How the value is displayed, where the component shows one. Called with\n * probe values only.\n */\n format?: (value: number) => string\n}\n\n/**\n * The warnings to show when a key press or a wheel notch cannot produce a\n * change the user can see. Empty when there is nothing to say.\n *\n * Two settings that are each fine on their own can cancel out, and nothing\n * fails when they do — the control simply sits there:\n *\n * - **`step` coarser than the amount.** `keyboard={['raw', 0.1]}` with\n * `step={1}` rounds every press straight back to where it started\n * - **the display coarser than the amount.** A `format` showing two decimals\n * of a kHz value cannot show a press worth 1 Hz\n *\n * The second is only reported when the press is invisible at *every* point\n * along the travel. A display that rounds is a deliberate choice and is\n * normally right — it is being too coarse everywhere that makes it a mistake.\n *\n * **Meant for development builds only.** It probes the whole travel, so call\n * it behind an inline `process.env.NODE_ENV` check: the bundler then drops\n * the call, and this function and its messages with it.\n */\nexport function checkSteps({\n component,\n axis,\n range,\n keyboard,\n wheel,\n format,\n}: CheckStepsOptions): string[] {\n if (!range || !(range.min < range.max)) return []\n const where = axis ? `${component} (${axis})` : component\n const warnings: string[] = []\n for (const [name, options] of [\n ['keyboard', keyboard],\n ['wheel', wheel],\n ] as const) {\n if (!options) continue\n const { moved, visible } = probe(options, range, format)\n if (!moved) {\n warnings.push(\n `[tremolo-ui] ${where}: \\`${name}\\` cannot move the value.` +\n (range.step !== undefined\n ? ` Each press is smaller than \\`step\\` (${range.step}), so it rounds`\n : ' Each press rounds') +\n ' straight back to where it started.',\n )\n } else if (format && !visible) {\n warnings.push(\n `[tremolo-ui] ${where}: \\`${name}\\` moves the value, but \\`format\\`` +\n ' shows the same text before and after, everywhere in the range.' +\n ' The display is too coarse for it to be seen.',\n )\n }\n }\n return warnings\n}\n","/**\n * Which way an input moves a value, before any amount is applied.\n *\n * `applyDelta` takes the direction as given, since which key or which sign of\n * `deltaY` counts as \"up\" differs per control. The answers are collected here\n * so that every wrapper gives the same one: a knob that turns the other way\n * in one framework would be a bug nobody could see from the code.\n *\n * Two families, by what the control looks like:\n *\n * - **one value** (`Knob`, `Slider`, `NumberInput`): right and up raise it\n * - **a position on screen** (`XYPad`, `PointsEditor`): in screen coordinates,\n * x growing rightwards and y growing downwards, plus which axis moves\n *\n * A control that runs the other way (`reverse`) flips the result itself.\n */\n\n/** One of the four arrow keys, as `KeyboardEvent.key` names it. */\nexport type ArrowKey = 'ArrowRight' | 'ArrowLeft' | 'ArrowUp' | 'ArrowDown'\n\n/** Is `key` one of the four arrow keys? */\nexport function isArrowKey(key: string): key is ArrowKey {\n return (\n key === 'ArrowRight' ||\n key === 'ArrowLeft' ||\n key === 'ArrowUp' ||\n key === 'ArrowDown'\n )\n}\n\n/** A move along one of the two axes of a position on screen. */\nexport interface AxisMove {\n /** 0 = x, 1 = y. */\n axis: 0 | 1\n /** `1` towards the right or the bottom, `-1` towards the left or the top. */\n direction: 1 | -1\n}\n\n/**\n * The direction an arrow key moves a single value: right and up raise it.\n * `null` for any other key.\n */\nexport function arrowKeyDirection(key: string): 1 | -1 | null {\n if (!isArrowKey(key)) return null\n return key === 'ArrowRight' || key === 'ArrowUp' ? 1 : -1\n}\n\n/**\n * The axis and direction an arrow key moves a position on screen. `null` for\n * any other key.\n *\n * The key picks the axis, whichever element inside the control holds the\n * focus: a two-dimensional control is one control to the person moving it.\n */\nexport function arrowKeyMove(key: string): AxisMove | null {\n if (!isArrowKey(key)) return null\n return {\n axis: key === 'ArrowRight' || key === 'ArrowLeft' ? 0 : 1,\n direction: key === 'ArrowLeft' || key === 'ArrowUp' ? -1 : 1,\n }\n}\n\nexport interface WheelDirectionOptions {\n /**\n * Read horizontal scrolling as well, for a control laid out horizontally:\n * scrolling right raises the value. Vertical scrolling still counts when\n * there is no horizontal movement.\n *\n * @default false\n */\n horizontal?: boolean\n}\n\n/**\n * The direction one wheel event moves a single value: scrolling up raises it.\n * `null` when the event carries no movement the control reads.\n */\nexport function wheelDirection(\n event: Pick<WheelEvent, 'deltaX' | 'deltaY'>,\n { horizontal = false }: WheelDirectionOptions = {},\n): 1 | -1 | null {\n if (horizontal && event.deltaX !== 0) return event.deltaX > 0 ? 1 : -1\n if (event.deltaY === 0) return null\n return event.deltaY > 0 ? -1 : 1\n}\n\n/**\n * The axis and direction one wheel event moves a position on screen. `null`\n * when the event carries no movement.\n *\n * Scrolling moves y, and shift switches to x. Browsers turn shift+wheel into\n * horizontal scrolling: `deltaY` comes out empty and `deltaX` carries the\n * movement. Reading whichever axis moved keeps shift working as the x-axis\n * modifier — and picks up a trackpad's own horizontal gesture, which never\n * had a modifier.\n */\nexport function wheelMove(\n event: Pick<WheelEvent, 'deltaX' | 'deltaY' | 'shiftKey'>,\n): AxisMove | null {\n const horizontal = event.deltaX !== 0\n const delta = horizontal ? event.deltaX : event.deltaY\n if (delta === 0) return null\n return {\n axis: horizontal || event.shiftKey ? 0 : 1,\n direction: delta < 0 ? -1 : 1,\n }\n}\n","import { type ValueRange } from '@tremolo-ui/functions'\n\nimport { applyDelta } from '../input/apply-delta'\nimport { DEFAULT_DRAG_SENSITIVITY } from '../input/defaults'\nimport {\n mapModifier,\n selectModifier,\n type InputEventOption,\n type ModifierValue,\n} from '../input/modifiers'\nimport { createDrag } from '../pointer/drag'\n\nexport interface StepperDragOptions {\n /** The value now, read when the drag starts moving it. */\n getValue: () => number\n /**\n * The range the value moves across. Its `step` is what one step of the drag\n * is worth; see `numberInputRanges` for the one a number input uses.\n */\n range: ValueRange\n /**\n * How many pixels of vertical movement make one step.\n *\n * @default 1\n */\n pixels?: number\n /**\n * How much a step is worth, per modifier key: `0.1` makes the same movement\n * count a tenth as much. Pressing or releasing the key mid-drag does not\n * move the value.\n *\n * @default { default: 1, shift: 0.1 }\n */\n sensitivity?: ModifierValue<number>\n /** Hide the pointer and keep it from hitting the edge of the screen. */\n pointerLock?: boolean\n /**\n * The cursor to show while dragging. Applied to the element itself, as\n * `createDrag` does.\n *\n * @default 'ns-resize'\n */\n cursor?: string\n /** Called with the new value whenever the drag moves it. */\n onChange: (value: number) => void\n}\n\nexport interface StepperDragInstance {\n /** Replace the given options. `pointerLock` reaches the next drag. */\n update: (options: Partial<StepperDragOptions>) => void\n /**\n * Whether the drag in progress has moved the value. A stepper button's\n * press-and-hold repeat stands down once it has, so the value is not moved\n * twice.\n */\n moved: () => boolean\n destroy: () => void\n}\n\n/**\n * Drag up and down on the steppers of a number input to move its value, one\n * `step` every `pixels` — up raises it, as on a knob.\n *\n * Counted from where the drag started moving rather than added up per\n * event, so rounding cannot accumulate. The start is taken on the first\n * move, not on pointerdown: a stepper button acts on pointerdown, so by then\n * the value may already have been nudged once, and the drag carries on from\n * there.\n */\nexport function createStepperDrag(\n element: Element,\n options: StepperDragOptions,\n): StepperDragInstance {\n let opts = options\n let origin: { y: number; value: number } | null = null\n let moved = false\n /**\n * Which sensitivity the drag is counting at, and where the previous event\n * was — a key produces no pointer event of its own, so a change is only\n * seen on the next move and has to be dated back to the one before it.\n */\n let factor = 1\n let previousY = 0\n\n const drag = createDrag(element, {\n threshold: 1,\n cursor: opts.cursor ?? 'ns-resize',\n pointerLock: opts.pointerLock,\n onDragStart: (state) => {\n origin = null\n moved = false\n factor = selectModifier(\n opts.sensitivity ?? DEFAULT_DRAG_SENSITIVITY,\n state.event,\n ).value\n },\n onDrag: (state) => {\n const { y } = state\n if (!origin) {\n origin = { y, value: opts.getValue() }\n previousY = y\n return\n }\n\n const sensitivity = opts.sensitivity ?? DEFAULT_DRAG_SENSITIVITY\n // Pressing or releasing the key mid-drag must not move the value, so the\n // travel so far is folded into the origin and measuring starts again\n // from the previous event: that event's own distance belongs to the new\n // sensitivity.\n const next = selectModifier(sensitivity, state.event).value\n if (next !== factor) {\n origin = { y: previousY, value: opts.getValue() }\n factor = next\n }\n previousY = y\n\n const steps = Math.round(-(y - origin.y) / (opts.pixels ?? 1))\n if (steps === 0) return\n moved = true\n\n // The sensitivity as an amount per step, carried over as a modifier map\n // rather than resolved here: that keeps `step` out of the pipeline for\n // a modifier entry, since naming one is a request to move off the grid.\n const step = opts.range.step ?? 1\n const amounts = mapModifier(sensitivity, (f): InputEventOption => [\n 'raw',\n step * f,\n ])\n opts.onChange(\n applyDelta(origin.value, steps, amounts, opts.range, state.event),\n )\n },\n onDragEnd: () => {\n moved = false\n },\n })\n\n return {\n update: (next) => {\n opts = { ...opts, ...next }\n if ('pointerLock' in next) drag.update({ pointerLock: opts.pointerLock })\n if ('cursor' in next) drag.update({ cursor: opts.cursor ?? 'ns-resize' })\n },\n moved: () => moved,\n destroy: () => drag.destroy(),\n }\n}\n","import { type Scale, type ValueRange } from '@tremolo-ui/functions'\n\nimport { applyDelta } from '../input/apply-delta'\nimport {\n selectModifier,\n type InputEventOption,\n type ModifierState,\n type ModifierValue,\n} from '../input/modifiers'\n\n/**\n * The value a number input edits, and how far it may go.\n *\n * Unlike a slider, either end may be left open, and `clampValue: false` lets\n * the value past the ends that are set.\n */\nexport interface NumberInputValueOptions {\n min?: number\n max?: number\n step?: number\n scale?: Scale\n /**\n * Keep the value between `min` and `max`.\n *\n * @default true\n */\n clampValue?: boolean\n}\n\n/** The ranges {@link nudgeNumberInput} moves a value across. */\nexport interface NumberInputRanges {\n /** For a `normalized` amount, which needs a finite span to take a share of. */\n normalized: ValueRange\n /** For a `raw` amount, which does not. */\n raw: ValueRange\n}\n\n/**\n * The ranges a number input moves its value across, with the open ends\n * filled in.\n *\n * A normalized amount needs a finite span even when an end is unbounded or\n * clamping is off. Safe integers provide one without overflowing the span a\n * scale calculates. A raw amount needs no span, so its open ends can cover\n * every finite number instead of stopping at the safe-integer range.\n */\nexport function numberInputRanges({\n min,\n max,\n step,\n scale,\n clampValue = true,\n}: NumberInputValueOptions): NumberInputRanges {\n const lo = clampValue ? min : undefined\n const hi = clampValue ? max : undefined\n return {\n normalized: {\n min: lo ?? Number.MIN_SAFE_INTEGER,\n max: hi ?? Number.MAX_SAFE_INTEGER,\n step,\n scale,\n },\n raw: {\n min: lo ?? -Number.MAX_VALUE,\n max: hi ?? Number.MAX_VALUE,\n step,\n scale,\n },\n }\n}\n\n/**\n * Move a number input's value by one press of a key, a wheel notch or a\n * stepper, picking the range that suits the kind of amount. See `applyDelta`.\n */\nexport function nudgeNumberInput(\n value: number,\n direction: number,\n options: ModifierValue<InputEventOption>,\n ranges: NumberInputRanges,\n modifiers?: ModifierState,\n): number {\n const [mode] = selectModifier(options, modifiers).value\n return applyDelta(\n value,\n direction,\n options,\n mode === 'raw' ? ranges.raw : ranges.normalized,\n modifiers,\n )\n}\n\n/**\n * Where the value stands against the ends: whether it can go no further\n * down or up, and whether it lies outside them — which only an unclamped\n * input, or a value set from outside, can do.\n */\nexport function numberInputBounds(\n value: number,\n { min, max, clampValue = true }: NumberInputValueOptions,\n) {\n return {\n atMin: clampValue && min !== undefined && value <= min,\n atMax: clampValue && max !== undefined && value >= max,\n outOfRange:\n (min !== undefined && value < min) || (max !== undefined && value > max),\n }\n}\n\n/**\n * The value typed text commits to, or `null` when there is no number in it.\n *\n * Text with no number is not a value: the input should go back to what it\n * was showing rather than commit a zero the user never typed. What is read\n * is clamped here and not while typing, since clamping as the user types\n * would make \"1500\" impossible to enter into an input whose max is 100.\n */\nexport function commitNumberInputText(\n text: string,\n parse: (text: string) => number,\n { min, max, clampValue = true }: NumberInputValueOptions,\n): number | null {\n const parsed = parse(text)\n if (!Number.isFinite(parsed)) return null\n let committed = parsed\n if (clampValue && min !== undefined) committed = Math.max(committed, min)\n if (clampValue && max !== undefined) committed = Math.min(committed, max)\n return committed\n}\n","/**\n * Reading a number out of the text of a number input, and keeping the caret\n * in place while the number under it changes.\n *\n * The text is whatever `format` made of the value — `\"440 Hz\"`, `\"-6.0 dB\"` —\n * or a half-typed entry, so none of this assumes the text is a number alone.\n */\n\n/** The signs a number can carry. U+2212 is what some `Intl.NumberFormat` locales write. */\nconst SIGNS = '+-\\u2212'\n\n/** Where the number is in the text; the rest, on either side, is the unit. */\nexport interface NumberSpan {\n start: number\n end: number\n}\n\n/**\n * Where the number is in the text: from its first digit to its last, with the\n * sign and the decimal point in front of it. Whatever is left on either side\n * is taken for the unit, so it does not matter whether a space separates them,\n * or what the number looks like — `+6.0`, `1e+21`, `1,000` and `1:30` are each\n * one number. `null` when the text has no digit at all.\n */\nexport function numberSpan(text: string): NumberSpan | null {\n const first = text.search(/\\d/)\n if (first === -1) return null\n let start = first\n if (text[start - 1] === '.') start -= 1\n if (start > 0 && SIGNS.includes(text[start - 1])) start -= 1\n return { start, end: text.search(/\\d\\D*$/) + 1 }\n}\n\n/** A plain number, and nothing else: no grouping, no other separators. */\nconst PLAIN_NUMBER =\n /^[+\\-\\u2212]?(?:\\d+(?:\\.\\d*)?|\\.\\d+)(?:[eE][+\\-\\u2212]?\\d+)?$/\n\n/**\n * The number in the text, with the unit after it ignored: the default `parse`\n * of a number input.\n *\n * `NaN`, which leaves the value alone, whenever the number cannot be read\n * safely, rather than a part of it: text with no number, a number that is not\n * a plain one (`1,000` would otherwise read as 1, and `1:30` as 1), and text\n * with something in front of the number, which can change what it means (the\n * `L` of a pan reading `L 30`). A `format` that writes any of those needs its\n * own `parse`.\n */\nexport function parseNumberText(text: string): number {\n const span = numberSpan(text)\n if (!span || text.slice(0, span.start).trim() !== '') return NaN\n const number = text.slice(span.start, span.end)\n return PLAIN_NUMBER.test(number)\n ? Number(number.replace(/\\u2212/g, '-'))\n : NaN\n}\n\n/**\n * The index the caret is measured against: the decimal point, or where one\n * would go if the number has none — in front of an exponent, if there is one.\n *\n * Measuring from an end instead would slide the caret across a digit whenever\n * the number changed length — `9.9` to `10.0` gains a character in front, `10`\n * to `9` loses one, and so does `9e+9` to `1e+10` behind — which is exactly\n * what stepping does.\n */\nfunction decimalAnchor(text: string, span: NumberSpan) {\n const number = text.slice(span.start, span.end)\n const exponent = number.search(/[eE][+\\-\\u2212]?\\d/)\n const mantissa = exponent === -1 ? number : number.slice(0, exponent)\n const dot = mantissa.indexOf('.')\n return span.start + (dot === -1 ? mantissa.length : dot)\n}\n\nconst NO_NUMBER: NumberSpan = { start: 0, end: 0 }\n\n/**\n * Where the caret is, relative to the decimal point, so that it can be put\n * back at the same digit once the value has changed. See\n * {@link caretAtDecimalOffset}.\n */\nexport function caretDecimalOffset(text: string, caret: number): number {\n return caret - decimalAnchor(text, numberSpan(text) ?? NO_NUMBER)\n}\n\n/**\n * The caret position `offset` characters from the decimal point of the new\n * text, kept within the number.\n *\n * @example\n * // The caret sits in front of the point of \"9.9\" when ArrowUp turns it\n * // into \"10.0\"\n * const offset = caretDecimalOffset('9.9', 1) // 0\n * caretAtDecimalOffset('10.0', offset) // 2: still in front of the point\n */\nexport function caretAtDecimalOffset(text: string, offset: number): number {\n const span = numberSpan(text) ?? NO_NUMBER\n const place = decimalAnchor(text, span) + offset\n return Math.max(span.start, Math.min(place, span.end))\n}\n","/**\n * The `update()` argument that leaves an instance with exactly `next` as its\n * options.\n *\n * `update()` merges into what the instance has, which suits a wrapper that\n * pushes one setting at a time. A wrapper handed the whole new set instead —\n * a Svelte action, a Vue composable — has to clear what the new set no longer\n * carries, or a handler taken away, or an option left to its default, keeps\n * working.\n *\n * @example\n * instance.update(replaceOptions(previous, next))\n */\nexport function replaceOptions<T extends object>(\n previous: T | undefined,\n next: T | undefined,\n): Partial<T> {\n const cleared = Object.fromEntries(\n Object.keys(previous ?? {}).map((key) => [key, undefined]),\n ) as Partial<T>\n return { ...cleared, ...next }\n}\n","/**\n * How many digits a number carries after the decimal point.\n *\n * `Slider.Marks` builds its labels by multiplying an interval, which leaves\n * binary debris in the last digits (`0.1 * 3` is `0.30000000000000004`). The\n * count is what tells `toFixed` how far to round that back.\n *\n * The exponent form has to be handled separately: `String(1e-7)` is `'1e-7'`,\n * which has no decimal point at all, so reading the text after the point\n * reports no digits and the interval rounds away to whole numbers.\n */\nexport function decimalDigits(x: number): number {\n if (!Number.isFinite(x)) return 0\n\n const text = String(x)\n const e = text.indexOf('e')\n if (e === -1) return text.split('.')[1]?.length ?? 0\n\n const fraction = text.slice(0, e).split('.')[1]?.length ?? 0\n // A negative exponent pushes the point further right, a positive one pulls\n // it back past the digits that are there.\n return Math.max(0, fraction - Number(text.slice(e + 1)))\n}\n","import { toFixed } from '@tremolo-ui/functions'\n\nimport { decimalDigits } from './decimal-digits'\n\n/**\n * How `Slider.Marks` fills itself in when it is given no children: one option\n * every `per`, or every `step` of the slider. The object form turns off the\n * mark or the label for the whole set; a single option is customized by\n * writing `Slider.MarksOption` out instead.\n */\nexport type MarksOptions =\n | 'step'\n | number\n | {\n per: 'step' | number\n mark?: boolean\n label?: boolean\n }\n\n/** One mark along a slider, as {@link sliderMarks} lays them out. */\nexport interface SliderMark {\n value: number\n mark: boolean\n label: boolean\n}\n\n/**\n * The marks `options` asks for between `min` and `max`, in ascending order.\n *\n * Each value is a whole multiple of the interval, rounded to the digits the\n * interval has: stepping by 0.1 would otherwise put binary debris in the\n * labels (`0.1 * 3` is `0.30000000000000004`).\n */\nexport function sliderMarks(\n options: MarksOptions,\n min: number,\n max: number,\n step: number,\n): SliderMark[] {\n const {\n per,\n mark = true,\n label = true,\n } = typeof options === 'object' ? options : { per: options }\n const optionsList: SliderMark[] = []\n const interval = per === 'step' ? step : per\n const count = Math.floor(max / interval) - Math.ceil(min / interval) + 1\n for (let i = 0; i < count; i++) {\n const value = toFixed(\n interval * (Math.ceil(min / interval) + i),\n decimalDigits(interval),\n )\n optionsList.push({ value: value, mark: mark, label: label })\n }\n return optionsList\n}\n","/**\n * A number as a CSS length, for a custom property.\n *\n * A custom property takes whatever text it is given, so `--size: 50` comes out\n * as the invalid `50` rather than `50px`. React appends `px` to a bare number\n * only for the properties it knows take a length, and a custom property is\n * never one of them; Vue and Svelte append nothing at all. Anything already a\n * string is passed through, so `'3rem'` and `'100%'` still work.\n */\nexport function cssLength(\n value: number | string | undefined,\n): string | undefined {\n return typeof value === 'number' ? `${value}px` : value\n}\n\n/**\n * Take an element out of sight while leaving it in the accessibility tree and\n * in the tab order.\n *\n * `display: none` and `visibility: hidden` would remove it from both, and the\n * native control underneath a headless component is what carries the ARIA and\n * the keyboard behaviour. `pointer-events: none` is safe because nothing is\n * ever clicked here directly: a `<label>` forwards its click, and a drag is\n * handled by the part that is visible.\n *\n * Every value is a string with its unit, so the object can be handed to any\n * framework's `style` binding as it is.\n */\nexport const visuallyHiddenStyle = {\n position: 'absolute',\n width: '1px',\n height: '1px',\n padding: '0',\n margin: '-1px',\n overflow: 'hidden',\n clip: 'rect(0, 0, 0, 0)',\n clipPath: 'inset(50%)',\n whiteSpace: 'nowrap',\n border: '0',\n pointerEvents: 'none',\n} as const\n"],"mappings":"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"}
|