@weasel-js/ui-input 0.0.0-stage → 1.18.2
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/LICENSE +21 -0
- package/README.md +143 -2
- package/dist/index.d.ts +619 -0
- package/dist/index.js +756 -0
- package/dist/index.js.map +1 -0
- package/package.json +47 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
import { COARSE_STEPS, snapToStep } from '@weasel-js/quantity';
|
|
2
|
+
export { COARSE_STEPS } from '@weasel-js/quantity';
|
|
3
|
+
|
|
4
|
+
// src/heldAt.ts
|
|
5
|
+
function heldAt(length, at) {
|
|
6
|
+
return at !== null && Number.isInteger(at) && at >= 0 && at < length ? at : null;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// src/tree.ts
|
|
10
|
+
function treeKey(items, at, key, shape) {
|
|
11
|
+
const item = items[at];
|
|
12
|
+
if (item === void 0) return { handled: false };
|
|
13
|
+
const canToggle = shape.canToggle?.(item) ?? true;
|
|
14
|
+
const [opens, closes] = shape.dir === "rtl" ? ["ArrowLeft", "ArrowRight"] : ["ArrowRight", "ArrowLeft"];
|
|
15
|
+
if (key === opens) {
|
|
16
|
+
if (!shape.isBranch(item)) return { handled: false };
|
|
17
|
+
if (!shape.isOpen(item)) return canToggle ? { handled: true, toggle: at } : { handled: true, at: null };
|
|
18
|
+
const next = items[at + 1];
|
|
19
|
+
return { handled: true, at: next !== void 0 && shape.isChildOf(next, item) ? at + 1 : null };
|
|
20
|
+
}
|
|
21
|
+
if (key !== closes) return { handled: false };
|
|
22
|
+
if (shape.isOpen(item) && canToggle) return { handled: true, toggle: at };
|
|
23
|
+
const parent = items.findIndex((p) => shape.isChildOf(item, p));
|
|
24
|
+
return parent < 0 ? { handled: false } : { handled: true, at: parent };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/typeahead.ts
|
|
28
|
+
var TYPEAHEAD_RESET_MS = 500;
|
|
29
|
+
function isOneChar(text) {
|
|
30
|
+
return text.length > 0 && text.length <= 2 && [...text].length === 1;
|
|
31
|
+
}
|
|
32
|
+
var EMPTY_TYPEAHEAD = { text: "", at: -Infinity };
|
|
33
|
+
function typeaheadMatch(items, at, query, shape) {
|
|
34
|
+
const q = fold(query);
|
|
35
|
+
const n = items.length;
|
|
36
|
+
const from = heldAt(n, at);
|
|
37
|
+
const start = from == null ? 0 : isOneChar(query) ? from + 1 : from;
|
|
38
|
+
for (let k = 0; k < n; k++) {
|
|
39
|
+
const i = (start + k) % n;
|
|
40
|
+
const item = items[i];
|
|
41
|
+
if ((shape.canStop?.(item) ?? true) && fold(shape.label(item)).startsWith(q)) return i;
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function fold(s) {
|
|
46
|
+
return s.toLowerCase().normalize("NFC");
|
|
47
|
+
}
|
|
48
|
+
function typeaheadStep(items, from, buffer, key, now, shape, resetMs = TYPEAHEAD_RESET_MS) {
|
|
49
|
+
const prev = now - buffer.at < resetMs ? buffer.text : "";
|
|
50
|
+
const k = key.toLowerCase();
|
|
51
|
+
const cycling = prev !== "" && [...prev].every((c) => c.toLowerCase() === k);
|
|
52
|
+
const text = cycling ? key : prev + key;
|
|
53
|
+
return { buffer: { text, at: now }, hit: typeaheadMatch(items, from, text, shape) };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/list.ts
|
|
57
|
+
var always = () => true;
|
|
58
|
+
function firstStop(items, shape = {}) {
|
|
59
|
+
const i = items.findIndex(shape.canStop ?? always);
|
|
60
|
+
return i < 0 ? null : i;
|
|
61
|
+
}
|
|
62
|
+
function stepStop(items, at, dir, shape = {}) {
|
|
63
|
+
const canStop = shape.canStop ?? always;
|
|
64
|
+
const wrap = shape.wrap ?? true;
|
|
65
|
+
const n = items.length;
|
|
66
|
+
const from = heldAt(n, at);
|
|
67
|
+
let i = from ?? (dir === 1 ? -1 : n);
|
|
68
|
+
for (let k = 0; k < n; k++) {
|
|
69
|
+
i += dir;
|
|
70
|
+
if (i < 0 || i >= n) {
|
|
71
|
+
if (!wrap) break;
|
|
72
|
+
i = (i + n) % n;
|
|
73
|
+
}
|
|
74
|
+
if (canStop(items[i])) return i;
|
|
75
|
+
}
|
|
76
|
+
return !wrap && from !== null && canStop(items[from]) ? from : null;
|
|
77
|
+
}
|
|
78
|
+
function arrowDir(key, shape) {
|
|
79
|
+
const orientation = shape.orientation ?? "vertical";
|
|
80
|
+
if (orientation !== "horizontal") {
|
|
81
|
+
if (key === "ArrowDown") return 1;
|
|
82
|
+
if (key === "ArrowUp") return -1;
|
|
83
|
+
}
|
|
84
|
+
if (orientation !== "vertical") {
|
|
85
|
+
const forward = shape.dir === "rtl" ? -1 : 1;
|
|
86
|
+
if (key === "ArrowRight") return forward;
|
|
87
|
+
if (key === "ArrowLeft") return -forward;
|
|
88
|
+
}
|
|
89
|
+
return 0;
|
|
90
|
+
}
|
|
91
|
+
function rovingKey(items, at, key, shape = {}) {
|
|
92
|
+
if (key === "Home") return { handled: true, at: stepStop(items, null, 1, shape) };
|
|
93
|
+
if (key === "End") return { handled: true, at: stepStop(items, null, -1, shape) };
|
|
94
|
+
const dir = arrowDir(key, shape);
|
|
95
|
+
return dir === 0 ? { handled: false } : { handled: true, at: stepStop(items, at, dir, shape) };
|
|
96
|
+
}
|
|
97
|
+
function rowKey(items, at, press, shape = {}, searching = false, apple = false) {
|
|
98
|
+
const { key } = press;
|
|
99
|
+
const chord = !!(press.ctrlKey || press.metaKey);
|
|
100
|
+
const mod = !!(apple ? press.metaKey && !press.ctrlKey : press.ctrlKey && !press.metaKey);
|
|
101
|
+
if (shape.selectAll && key.toLowerCase() === "a" && mod && !press.altKey && !press.shiftKey) return { kind: "selectAll" };
|
|
102
|
+
const step = rovingKey(items, at, key, shape);
|
|
103
|
+
if (step.handled) return { kind: "move", at: step.at, extend: !!press.shiftKey };
|
|
104
|
+
if (shape.tree && (key === "ArrowRight" || key === "ArrowLeft")) {
|
|
105
|
+
const t = treeKey(items, at, key, shape.dir && !shape.tree.dir ? { ...shape.tree, dir: shape.dir } : shape.tree);
|
|
106
|
+
if (!t.handled) return null;
|
|
107
|
+
return "toggle" in t ? { kind: "toggle", at: t.toggle } : { kind: "move", at: t.at, extend: false };
|
|
108
|
+
}
|
|
109
|
+
if (key === " " && shape.typeahead && searching) return { kind: "type", held: true };
|
|
110
|
+
if (key === "Enter" || key === " ") return shape.activate ? { kind: "activate" } : null;
|
|
111
|
+
if (shape.typeahead && isOneChar(key) && !chord && !press.altKey) return { kind: "type", held: false };
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// src/tabOrder.ts
|
|
116
|
+
function tabSort(targets, declared) {
|
|
117
|
+
const key = (t) => declared(t) ?? Number.POSITIVE_INFINITY;
|
|
118
|
+
return [...targets].sort((a, b) => key(a) - key(b));
|
|
119
|
+
}
|
|
120
|
+
function tabStep(order, from, direction, isBefore) {
|
|
121
|
+
const forward = direction === "next";
|
|
122
|
+
const here = from === null ? -1 : order.indexOf(from);
|
|
123
|
+
let at;
|
|
124
|
+
if (here !== -1) at = here + (forward ? 1 : -1);
|
|
125
|
+
else if (from === null || !isBefore) at = forward ? 0 : order.length - 1;
|
|
126
|
+
else {
|
|
127
|
+
const after = order.findIndex((t) => isBefore(from, t));
|
|
128
|
+
const end = after === -1 ? order.length : after;
|
|
129
|
+
at = forward ? end : end - 1;
|
|
130
|
+
}
|
|
131
|
+
return order[at] ?? null;
|
|
132
|
+
}
|
|
133
|
+
function arrowSteps(key, shift, dir = "ltr") {
|
|
134
|
+
const steps = shift ? COARSE_STEPS : 1;
|
|
135
|
+
if (key === "ArrowUp") return steps;
|
|
136
|
+
if (key === "ArrowDown") return -steps;
|
|
137
|
+
const d = arrowDir(key, { orientation: "horizontal", dir });
|
|
138
|
+
return d === 0 ? null : d * steps;
|
|
139
|
+
}
|
|
140
|
+
function arrowMove(key, shift) {
|
|
141
|
+
const steps = arrowSteps(key, shift);
|
|
142
|
+
if (steps === null) return null;
|
|
143
|
+
return key === "ArrowRight" || key === "ArrowLeft" ? { dx: steps, dy: 0 } : { dx: 0, dy: -steps };
|
|
144
|
+
}
|
|
145
|
+
function rangeKey(key, shift, dir = "ltr") {
|
|
146
|
+
switch (key) {
|
|
147
|
+
case "PageUp":
|
|
148
|
+
return COARSE_STEPS;
|
|
149
|
+
case "PageDown":
|
|
150
|
+
return -COARSE_STEPS;
|
|
151
|
+
case "Home":
|
|
152
|
+
return "home";
|
|
153
|
+
case "End":
|
|
154
|
+
return "end";
|
|
155
|
+
default:
|
|
156
|
+
return arrowSteps(key, shift, dir);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// src/scalar.ts
|
|
161
|
+
function clamp(v, lo, hi) {
|
|
162
|
+
const c = v > hi ? hi : v;
|
|
163
|
+
return c < lo ? lo : c;
|
|
164
|
+
}
|
|
165
|
+
function usableStops(stops, min, max) {
|
|
166
|
+
if (!stops || stops.length === 0) return [];
|
|
167
|
+
const byValue = /* @__PURE__ */ new Map();
|
|
168
|
+
for (const stop of stops) {
|
|
169
|
+
const s = typeof stop === "number" ? { value: stop } : stop;
|
|
170
|
+
if (!(s.value >= min && s.value <= max)) continue;
|
|
171
|
+
const seen = byValue.get(s.value);
|
|
172
|
+
if (!seen || seen.label === void 0 && s.label !== void 0) byValue.set(s.value, s);
|
|
173
|
+
}
|
|
174
|
+
return [...byValue.values()].sort((a, b) => a.value - b.value);
|
|
175
|
+
}
|
|
176
|
+
function attract(v, stops, tolerance, toFraction = (x) => x) {
|
|
177
|
+
const f = toFraction(v);
|
|
178
|
+
let best = v;
|
|
179
|
+
let bestGap = tolerance;
|
|
180
|
+
for (const stop of stops) {
|
|
181
|
+
const gap = Math.abs(toFraction(stop) - f);
|
|
182
|
+
if (gap <= bestGap) {
|
|
183
|
+
best = stop;
|
|
184
|
+
bestGap = gap;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return best;
|
|
188
|
+
}
|
|
189
|
+
function evenMap(stops) {
|
|
190
|
+
const gaps = stops.length - 1;
|
|
191
|
+
return {
|
|
192
|
+
toFraction(v) {
|
|
193
|
+
const x = clamp(v, stops[0], stops[gaps]);
|
|
194
|
+
let i = 0;
|
|
195
|
+
while (i < gaps - 1 && x > stops[i + 1]) i++;
|
|
196
|
+
return (i + (x - stops[i]) / (stops[i + 1] - stops[i])) / gaps;
|
|
197
|
+
},
|
|
198
|
+
toValue(f) {
|
|
199
|
+
const c = clamp(f, 0, 1);
|
|
200
|
+
const i = Math.min(gaps - 1, Math.floor(c * gaps));
|
|
201
|
+
return stops[i] + (c * gaps - i) * (stops[i + 1] - stops[i]);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
function sliderAt(f, shape) {
|
|
206
|
+
const stops = shape.stops ?? [];
|
|
207
|
+
const v = shape.toValue(f);
|
|
208
|
+
if (shape.strict) return attract(v, stops, Infinity, shape.toFraction);
|
|
209
|
+
const q = clamp(snapToStep(v, shape.step, shape.origin ?? shape.min), shape.min, shape.max);
|
|
210
|
+
return stops.length > 0 && !shape.free ? attract(q, stops, shape.pull ?? 0, shape.toFraction) : q;
|
|
211
|
+
}
|
|
212
|
+
function stepStops(stops, v, direction, count) {
|
|
213
|
+
if (direction > 0) {
|
|
214
|
+
const first2 = stops.findIndex((stop) => stop > v);
|
|
215
|
+
if (first2 === -1) return stops[stops.length - 1];
|
|
216
|
+
return stops[Math.min(stops.length - 1, first2 + count - 1)];
|
|
217
|
+
}
|
|
218
|
+
let first = -1;
|
|
219
|
+
for (let i = stops.length - 1; i >= 0; i--) {
|
|
220
|
+
if (stops[i] < v) {
|
|
221
|
+
first = i;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (first === -1) return stops[0];
|
|
226
|
+
return stops[Math.max(0, first - (count - 1))];
|
|
227
|
+
}
|
|
228
|
+
function defaultStep(step, min, max) {
|
|
229
|
+
if (step !== void 0 && step > 0) return step;
|
|
230
|
+
return (max - min) / 100;
|
|
231
|
+
}
|
|
232
|
+
function clampOrdered(v, thumbs, index, min, max, step) {
|
|
233
|
+
const gap = step !== void 0 && step > 0 ? step : (max - min) / 1e3;
|
|
234
|
+
const below = index > 0 ? thumbs[index - 1].value : -Infinity;
|
|
235
|
+
const above = index < thumbs.length - 1 ? thumbs[index + 1].value : Infinity;
|
|
236
|
+
const lower = Math.max(min, below + gap);
|
|
237
|
+
const upper = Math.min(max, above - gap);
|
|
238
|
+
if (lower <= upper) return clamp(v, lower, upper);
|
|
239
|
+
const lo = Math.max(min, below);
|
|
240
|
+
const hi = Math.min(max, above);
|
|
241
|
+
return lo <= hi ? clamp(v, lo, hi) : clamp(v, min, max);
|
|
242
|
+
}
|
|
243
|
+
function crossEnd(from, delta, stepSize, range2, track) {
|
|
244
|
+
const [lo, hi] = range2;
|
|
245
|
+
if (from > hi) return delta < 0 ? Math.max(lo, hi + delta + stepSize) : from;
|
|
246
|
+
if (from < lo) return delta > 0 ? Math.min(hi, lo + delta - stepSize) : from;
|
|
247
|
+
if (from + delta > hi) return from < hi ? hi : track[1];
|
|
248
|
+
if (from + delta < lo) return from > lo ? lo : track[0];
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
function sliderKey(key, shift, shape) {
|
|
252
|
+
const move = rangeKey(key, shift, shape.dir);
|
|
253
|
+
if (move === null) return null;
|
|
254
|
+
const { thumbs, index, min, max, step } = shape;
|
|
255
|
+
const from = thumbs[index].value;
|
|
256
|
+
const [bLo, bHi] = shape.bounds?.() ?? [min, max];
|
|
257
|
+
const lo = Math.max(min, bLo);
|
|
258
|
+
const hi = Math.min(max, bHi);
|
|
259
|
+
const stops = shape.stops ?? [];
|
|
260
|
+
let v;
|
|
261
|
+
if (move === "home") v = lo;
|
|
262
|
+
else if (move === "end") v = hi;
|
|
263
|
+
else if (shape.keyStep) v = shape.keyStep(move);
|
|
264
|
+
else if (stops.length > 0) v = stepStops(stops, from, move > 0 ? 1 : -1, Math.abs(move));
|
|
265
|
+
else {
|
|
266
|
+
const range2 = shape.range ?? [min, max];
|
|
267
|
+
const stepSize = defaultStep(step, range2[0], range2[1]);
|
|
268
|
+
const delta = move * stepSize;
|
|
269
|
+
const crossed = crossEnd(from, delta, stepSize, range2, [min, max]);
|
|
270
|
+
if (crossed === null) v = snapToStep(from + delta, step, range2[0]);
|
|
271
|
+
else v = crossed > range2[0] && crossed < range2[1] ? snapToStep(crossed, step, range2[0]) : crossed;
|
|
272
|
+
}
|
|
273
|
+
v = clamp(v, lo, hi);
|
|
274
|
+
return shape.ordered ? clampOrdered(v, thumbs, index, lo, hi, step) : v;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// src/span.ts
|
|
278
|
+
function spanPlace(span, v) {
|
|
279
|
+
const length = span.to - span.from;
|
|
280
|
+
return length === 0 ? 0 : (v - span.from) / length;
|
|
281
|
+
}
|
|
282
|
+
function spanValue(span, f) {
|
|
283
|
+
return span.from + f * (span.to - span.from);
|
|
284
|
+
}
|
|
285
|
+
function spanShare(span, length) {
|
|
286
|
+
const whole = span.to - span.from;
|
|
287
|
+
return whole === 0 ? 0 : length / whole;
|
|
288
|
+
}
|
|
289
|
+
function spanMap(span) {
|
|
290
|
+
return { toFraction: (v) => spanPlace(span, v), toValue: (f) => spanValue(span, f) };
|
|
291
|
+
}
|
|
292
|
+
function spanHold(span, bounds) {
|
|
293
|
+
if (span.from >= bounds.from && span.to <= bounds.to) return { from: span.from, to: span.to };
|
|
294
|
+
const length = Math.min(span.to - span.from, bounds.to - bounds.from);
|
|
295
|
+
let from = span.from;
|
|
296
|
+
if (from < bounds.from) from = bounds.from;
|
|
297
|
+
if (from + length > bounds.to) from = Math.max(bounds.from, bounds.to - length);
|
|
298
|
+
return { from, to: Math.min(bounds.to, from + length) };
|
|
299
|
+
}
|
|
300
|
+
function spanPan(span, by, bounds) {
|
|
301
|
+
return spanHold({ from: span.from + by, to: span.to + by }, bounds);
|
|
302
|
+
}
|
|
303
|
+
function spanZoom(span, at, factor, bounds, minLength = 0) {
|
|
304
|
+
const length = span.to - span.from;
|
|
305
|
+
const next = Math.min(bounds.to - bounds.from, Math.max(minLength, length * factor));
|
|
306
|
+
const f = length === 0 ? 0.5 : (at - span.from) / length;
|
|
307
|
+
return spanHold({ from: at - f * next, to: at + (1 - f) * next }, bounds);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/textCommit.ts
|
|
311
|
+
function editEnd(cause, shape = {}) {
|
|
312
|
+
if (cause === "blur") return "commit";
|
|
313
|
+
if (cause.key === "Enter") return shape.multiline && cause.shift ? null : "commit";
|
|
314
|
+
if (cause.key === "Escape") return shape.escape ?? "cancel";
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
function imeKey(e) {
|
|
318
|
+
return e.isComposing === true || e.keyCode === 229 || e.key === "Process" || e.key === "Dead";
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/reorder.ts
|
|
322
|
+
var sortedSet = (at) => [...new Set(at.filter(Number.isInteger))].sort((a, b) => a - b);
|
|
323
|
+
function moveItems(items, at, to) {
|
|
324
|
+
const inside = sortedSet(at).filter((i) => i >= 0 && i < items.length);
|
|
325
|
+
const gap = Math.min(items.length, Math.max(0, to));
|
|
326
|
+
const taken = new Set(inside);
|
|
327
|
+
const out = items.filter((_, i) => !taken.has(i));
|
|
328
|
+
out.splice(movedIndex(inside, gap), 0, ...inside.map((i) => items[i]));
|
|
329
|
+
return out;
|
|
330
|
+
}
|
|
331
|
+
function movedIndex(at, to) {
|
|
332
|
+
return to - sortedSet(at).filter((i) => i < to).length;
|
|
333
|
+
}
|
|
334
|
+
function isNoopDrop(at, to) {
|
|
335
|
+
const s = sortedSet(at);
|
|
336
|
+
if (s.length === 0) return false;
|
|
337
|
+
const first = s[0];
|
|
338
|
+
const last = s[s.length - 1];
|
|
339
|
+
return last - first + 1 === s.length && to >= first && to <= last + 1;
|
|
340
|
+
}
|
|
341
|
+
function nudgeGap(at, by, length) {
|
|
342
|
+
const s = sortedSet(at);
|
|
343
|
+
if (s.length === 0) return null;
|
|
344
|
+
const to = by < 0 ? s[0] - 1 : s[s.length - 1] + 2;
|
|
345
|
+
return to < 0 || to > length ? null : to;
|
|
346
|
+
}
|
|
347
|
+
function gapByMidpoint(mids, p) {
|
|
348
|
+
const backward = mids.length > 1 && mids[mids.length - 1] < mids[0];
|
|
349
|
+
return mids.filter((m) => backward ? m >= p : m <= p).length;
|
|
350
|
+
}
|
|
351
|
+
function nearestGap(gaps, p) {
|
|
352
|
+
let best = -1;
|
|
353
|
+
for (let k = 0; k < gaps.length; k++) if (best < 0 || Math.abs(gaps[k] - p) < Math.abs(gaps[best] - p)) best = k;
|
|
354
|
+
return best;
|
|
355
|
+
}
|
|
356
|
+
function dropBand(f, band = 0.5) {
|
|
357
|
+
if (f < band) return "before";
|
|
358
|
+
return f >= 1 - band ? "after" : "into";
|
|
359
|
+
}
|
|
360
|
+
function isNudgeChord(e) {
|
|
361
|
+
return e.altKey && !e.ctrlKey && !e.metaKey;
|
|
362
|
+
}
|
|
363
|
+
function nudgeOf(e, back = "ArrowUp", on = "ArrowDown") {
|
|
364
|
+
if (!isNudgeChord(e)) return null;
|
|
365
|
+
if (e.key === back) return -1;
|
|
366
|
+
return e.key === on ? 1 : null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
// src/edgeScroll.ts
|
|
370
|
+
var EDGE_SCROLL_MARGIN = 48;
|
|
371
|
+
function edgePush(span, p, margin = EDGE_SCROLL_MARGIN) {
|
|
372
|
+
if (!(margin > 0)) return 0;
|
|
373
|
+
const fromStart = p - span.from;
|
|
374
|
+
const fromEnd = span.to - p;
|
|
375
|
+
const towardStart = fromStart <= fromEnd;
|
|
376
|
+
const distance = towardStart ? fromStart : fromEnd;
|
|
377
|
+
if (distance >= margin) return 0;
|
|
378
|
+
const strength = Math.min(1, 1 - distance / margin);
|
|
379
|
+
return towardStart ? -strength : strength;
|
|
380
|
+
}
|
|
381
|
+
function edgeScroll(bounds, point, margin = EDGE_SCROLL_MARGIN) {
|
|
382
|
+
return {
|
|
383
|
+
x: edgePush({ from: bounds.x, to: bounds.x + bounds.width }, point.x, margin),
|
|
384
|
+
y: edgePush({ from: bounds.y, to: bounds.y + bounds.height }, point.y, margin)
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// src/spatial.ts
|
|
389
|
+
var HEADING_DRIFT = 2;
|
|
390
|
+
function headingOf(key) {
|
|
391
|
+
if (key === "ArrowLeft") return "left";
|
|
392
|
+
if (key === "ArrowRight") return "right";
|
|
393
|
+
if (key === "ArrowUp") return "up";
|
|
394
|
+
return key === "ArrowDown" ? "down" : null;
|
|
395
|
+
}
|
|
396
|
+
function headingCost(from, to, heading, drift = HEADING_DRIFT) {
|
|
397
|
+
const dx = to.x + to.width / 2 - (from.x + from.width / 2);
|
|
398
|
+
const dy = to.y + to.height / 2 - (from.y + from.height / 2);
|
|
399
|
+
const across = heading === "left" || heading === "right";
|
|
400
|
+
const along = across ? dx : dy;
|
|
401
|
+
const ahead = heading === "left" || heading === "up" ? -along : along;
|
|
402
|
+
if (!(ahead > 0)) return null;
|
|
403
|
+
return ahead + drift * Math.abs(across ? dy : dx);
|
|
404
|
+
}
|
|
405
|
+
function nearestToward(from, targets, heading, drift = HEADING_DRIFT) {
|
|
406
|
+
let best = -1;
|
|
407
|
+
let bestCost = Infinity;
|
|
408
|
+
targets.forEach((to, i) => {
|
|
409
|
+
const cost = to ? headingCost(from, to, heading, drift) : null;
|
|
410
|
+
if (cost !== null && cost < bestCost) {
|
|
411
|
+
best = i;
|
|
412
|
+
bestCost = cost;
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
return best;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// src/scheduler.ts
|
|
419
|
+
var timerScheduler = {
|
|
420
|
+
now: () => Date.now(),
|
|
421
|
+
set: (fn, ms2) => {
|
|
422
|
+
const t = setTimeout(fn, ms2);
|
|
423
|
+
return () => clearTimeout(t);
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
|
|
427
|
+
// src/stepRepeat.ts
|
|
428
|
+
var REPEAT_DELAY_MS = 400;
|
|
429
|
+
var REPEAT_EVERY_MS = 60;
|
|
430
|
+
function createStepRepeat(step, spent, opts = {}) {
|
|
431
|
+
let cancel = null;
|
|
432
|
+
let key = null;
|
|
433
|
+
let hold = 0;
|
|
434
|
+
const stop = () => {
|
|
435
|
+
cancel?.();
|
|
436
|
+
cancel = null;
|
|
437
|
+
key = null;
|
|
438
|
+
hold++;
|
|
439
|
+
};
|
|
440
|
+
const stepThenWait = (k, ms2) => {
|
|
441
|
+
const mine = hold;
|
|
442
|
+
step(k);
|
|
443
|
+
if (hold !== mine) return;
|
|
444
|
+
cancel = (opts.scheduler ?? timerScheduler).set(() => {
|
|
445
|
+
if (spent(k)) return stop();
|
|
446
|
+
stepThenWait(k, opts.everyMs ?? REPEAT_EVERY_MS);
|
|
447
|
+
}, ms2);
|
|
448
|
+
};
|
|
449
|
+
return {
|
|
450
|
+
start(k) {
|
|
451
|
+
stop();
|
|
452
|
+
key = k;
|
|
453
|
+
stepThenWait(k, opts.delayMs ?? REPEAT_DELAY_MS);
|
|
454
|
+
},
|
|
455
|
+
stop,
|
|
456
|
+
held: () => key
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/sweep.ts
|
|
461
|
+
var SWEEP_CAP = 256;
|
|
462
|
+
function stepsOf(sweep, cap = SWEEP_CAP) {
|
|
463
|
+
if (!(Number.isInteger(cap) && cap >= 0)) throw new RangeError(`a cap is a whole number of steps, not ${cap}`);
|
|
464
|
+
const steps = [];
|
|
465
|
+
for (const value of sweep.walk()) {
|
|
466
|
+
if (steps.length === cap) return { steps, capped: true };
|
|
467
|
+
steps.push(value);
|
|
468
|
+
}
|
|
469
|
+
return { steps, capped: false };
|
|
470
|
+
}
|
|
471
|
+
var clamp01 = (u) => u <= 0 ? 0 : u >= 1 ? 1 : u;
|
|
472
|
+
var shown = (n) => +n.toPrecision(12);
|
|
473
|
+
var tidy = (n, scale = 0) => {
|
|
474
|
+
const t = shown(n);
|
|
475
|
+
return Math.abs(t - n) <= 2 * Number.EPSILON * Math.max(Math.abs(n), Math.abs(scale)) ? t : n;
|
|
476
|
+
};
|
|
477
|
+
var plain = (n) => String(shown(n));
|
|
478
|
+
function range(spec) {
|
|
479
|
+
const { from, to } = spec;
|
|
480
|
+
if (!Number.isFinite(from) || Number.isNaN(to)) throw new RangeError(`a range runs from a finite number to a number, not ${from} to ${to}`);
|
|
481
|
+
const span = to - from;
|
|
482
|
+
if (spec.step !== void 0) {
|
|
483
|
+
const step = spec.step;
|
|
484
|
+
if (!(step > 0)) throw new RangeError(`a range's step is above 0, not ${step}`);
|
|
485
|
+
const last = Number.isFinite(span) ? Math.floor(tidy(Math.abs(span) / step)) : Infinity;
|
|
486
|
+
const dir = span < 0 ? -1 : 1;
|
|
487
|
+
const nth = (i) => i === 0 ? from : tidy(from + dir * step * i, Math.max(Math.abs(from), step * i));
|
|
488
|
+
return {
|
|
489
|
+
at: (u) => {
|
|
490
|
+
if (!Number.isFinite(last)) throw new RangeError("an endless range has no point a fraction of the way along");
|
|
491
|
+
return nth(Math.round(clamp01(u) * last));
|
|
492
|
+
},
|
|
493
|
+
*walk() {
|
|
494
|
+
for (let i = 0; i <= last; i++) yield nth(i);
|
|
495
|
+
},
|
|
496
|
+
label: spec.label ?? plain
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
const count = spec.count;
|
|
500
|
+
if (!(Number.isInteger(count) && count > 0)) throw new RangeError(`a range's count is a whole number above 0, not ${count}`);
|
|
501
|
+
if (!Number.isFinite(span)) throw new RangeError("a range given a count needs a finite end");
|
|
502
|
+
const scale = Math.max(Math.abs(from), Math.abs(to));
|
|
503
|
+
const [lo, hi] = from <= to ? [from, to] : [to, from];
|
|
504
|
+
const along = (u) => u <= 0 ? from : u >= 1 ? to : Math.min(hi, Math.max(lo, tidy(from + span * u, scale)));
|
|
505
|
+
return {
|
|
506
|
+
at: (u) => along(clamp01(u)),
|
|
507
|
+
*walk() {
|
|
508
|
+
if (count === 1) yield from;
|
|
509
|
+
else for (let i = 0; i < count; i++) yield i === 0 ? from : i === count - 1 ? to : tidy(from + span * i / (count - 1), scale);
|
|
510
|
+
},
|
|
511
|
+
label: spec.label ?? plain
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
function values(list, label = String) {
|
|
515
|
+
if (list.length === 0) throw new RangeError("a sweep over values needs at least one");
|
|
516
|
+
return {
|
|
517
|
+
at: (u) => list[Math.round(clamp01(u) * (list.length - 1))],
|
|
518
|
+
walk: () => list,
|
|
519
|
+
label
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
var ms = (t) => `${shown(t)} ms`;
|
|
523
|
+
function frames(span, options) {
|
|
524
|
+
const { every } = options;
|
|
525
|
+
if (!(every > 0)) throw new RangeError(`frames are a time above 0 apart, not ${every}`);
|
|
526
|
+
const duration = span.duration ?? Infinity;
|
|
527
|
+
if (!(duration >= 0)) throw new RangeError(`a run lasts 0 ms or more, not ${duration}`);
|
|
528
|
+
const loop = span.loop ?? false;
|
|
529
|
+
if (typeof loop === "number" && !(loop >= 0)) throw new RangeError(`a run loops 0 times or more, not ${loop}`);
|
|
530
|
+
const passes = loop === true ? Infinity : loop === false ? 1 : loop;
|
|
531
|
+
const walked = options.passes === "all" ? passes : 1;
|
|
532
|
+
const end = duration === 0 || walked === 0 ? 0 : duration * walked;
|
|
533
|
+
const ends = walked === passes && Number.isFinite(passes) && Number.isFinite(end);
|
|
534
|
+
const origin = span.origin ?? 0;
|
|
535
|
+
const label = options.label ?? ((t) => ms(origin + t));
|
|
536
|
+
return {
|
|
537
|
+
at: (u) => {
|
|
538
|
+
if (!Number.isFinite(end)) throw new RangeError("an endless run has no point a fraction of the way along");
|
|
539
|
+
return end * clamp01(u);
|
|
540
|
+
},
|
|
541
|
+
*walk() {
|
|
542
|
+
for (let i = 0; ; i++) {
|
|
543
|
+
const t = tidy(every * i);
|
|
544
|
+
if (t > end || t === end && !ends) break;
|
|
545
|
+
yield t;
|
|
546
|
+
if (t === end) return;
|
|
547
|
+
}
|
|
548
|
+
if (ends) yield end;
|
|
549
|
+
},
|
|
550
|
+
label
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
function until(sweep, done) {
|
|
554
|
+
return {
|
|
555
|
+
at: sweep.at,
|
|
556
|
+
*walk() {
|
|
557
|
+
let i = 0;
|
|
558
|
+
for (const value of sweep.walk()) {
|
|
559
|
+
if (done(value, i++)) return;
|
|
560
|
+
yield value;
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
label: sweep.label
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// src/grid.ts
|
|
568
|
+
function rowOffset(length, columns, align = "start") {
|
|
569
|
+
const gap = Math.max(0, columns - length);
|
|
570
|
+
return align === "center" ? gap / 2 : align === "end" ? gap : 0;
|
|
571
|
+
}
|
|
572
|
+
var gcd = (a, b) => b === 0 ? a : gcd(b, a % b);
|
|
573
|
+
var MAX_COLUMNS = 2 ** 24;
|
|
574
|
+
var wholeColumns = (columns) => columns >= 1 ? Math.min(Math.floor(columns), MAX_COLUMNS) : 1;
|
|
575
|
+
function tileGrid(count, spec) {
|
|
576
|
+
const columns = wholeColumns(spec.columns);
|
|
577
|
+
const lastRow = spec.lastRow ?? "start";
|
|
578
|
+
const short = count % columns;
|
|
579
|
+
const lastStart = count - (short || columns);
|
|
580
|
+
const filling = lastRow === "fill" && short > 0;
|
|
581
|
+
const tracks = filling ? columns * short / gcd(columns, short) : 2 * columns;
|
|
582
|
+
const span = tracks / columns;
|
|
583
|
+
const lead = short === 0 || lastRow === "fill" ? 0 : span * rowOffset(short, columns, lastRow);
|
|
584
|
+
const lastAtEnd = short === 0 || lastRow === "fill" || lastRow === "end";
|
|
585
|
+
return {
|
|
586
|
+
tracks,
|
|
587
|
+
span,
|
|
588
|
+
fillSpan: filling ? tracks / short : span,
|
|
589
|
+
lead,
|
|
590
|
+
lastStart,
|
|
591
|
+
stretched: (i) => filling && i >= lastStart,
|
|
592
|
+
corners: (i) => {
|
|
593
|
+
const at = [];
|
|
594
|
+
const single = count < columns;
|
|
595
|
+
if (i === 0 && (lead === 0 || !single)) at.push("ss");
|
|
596
|
+
if (i === Math.min(columns, count) - 1 && (lastAtEnd || !single)) at.push("se");
|
|
597
|
+
if (i === lastStart && lead === 0) at.push("es");
|
|
598
|
+
if (i === count - 1 && lastAtEnd) at.push("ee");
|
|
599
|
+
return at;
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
function tileRows(count, across) {
|
|
604
|
+
const columns = wholeColumns(across);
|
|
605
|
+
return Array.from({ length: Math.ceil(count / columns) }, (_, r) => Array.from({ length: Math.min(columns, count - r * columns) }, (_2, j) => r * columns + j));
|
|
606
|
+
}
|
|
607
|
+
function tileKey(current, key, count, spec, opts = {}) {
|
|
608
|
+
const rows = tileRows(count, spec.columns);
|
|
609
|
+
const enabled = opts.enabled ?? Array(count).fill(true);
|
|
610
|
+
return rovingGridTarget(current, key, enabled, rows, { dir: opts.dir, ...gridLayout(rows, spec.columns, spec.lastRow) });
|
|
611
|
+
}
|
|
612
|
+
function gridLayout(rows, across, lastRow = "start") {
|
|
613
|
+
const columns = wholeColumns(across);
|
|
614
|
+
const short = (row) => row.length < columns;
|
|
615
|
+
return {
|
|
616
|
+
offsets: rows.map((row) => short(row) ? rowOffset(row.length, columns, lastRow) : 0),
|
|
617
|
+
widths: rows.map((row) => short(row) && lastRow === "fill" ? columns / row.length : 1)
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
function itemUnder(row, at, lo, hi, down) {
|
|
621
|
+
let best = -1;
|
|
622
|
+
let most = 0;
|
|
623
|
+
for (let j = 0; j < row.length; j++) {
|
|
624
|
+
const start = at.offset + j * at.width;
|
|
625
|
+
const cover = Math.min(hi, start + at.width) - Math.max(lo, start);
|
|
626
|
+
if (cover > most + 1e-9 || down && cover > 0 && Math.abs(cover - most) <= 1e-9) {
|
|
627
|
+
best = j;
|
|
628
|
+
most = cover;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
if (best >= 0) return row[best];
|
|
632
|
+
const nearest = Math.round(((lo + hi) / 2 - at.offset) / at.width - 0.5);
|
|
633
|
+
return row[Math.min(row.length - 1, Math.max(0, nearest))];
|
|
634
|
+
}
|
|
635
|
+
var isStop = (enabled) => enabled;
|
|
636
|
+
function rovingGridTarget(current, key, enabled, rows, layout = {}) {
|
|
637
|
+
const step = key === "ArrowDown" ? 1 : key === "ArrowUp" ? -1 : 0;
|
|
638
|
+
if (step === 0) {
|
|
639
|
+
const moved = rovingKey(enabled, current, key, { canStop: isStop, orientation: "horizontal", dir: layout.dir });
|
|
640
|
+
return moved.handled ? moved.at : null;
|
|
641
|
+
}
|
|
642
|
+
const r = rows.findIndex((row) => row.includes(current));
|
|
643
|
+
if (r < 0) return null;
|
|
644
|
+
const at = (row) => ({ offset: layout.offsets?.[row] ?? 0, width: layout.widths?.[row] ?? 1 });
|
|
645
|
+
const here = at(r);
|
|
646
|
+
const lo = here.offset + rows[r].indexOf(current) * here.width;
|
|
647
|
+
for (let rr = r + step; rr >= 0 && rr < rows.length; rr += step) {
|
|
648
|
+
const i = itemUnder(rows[rr], at(rr), lo, lo + here.width, step > 0);
|
|
649
|
+
if (i !== void 0 && enabled[i]) return i;
|
|
650
|
+
}
|
|
651
|
+
return null;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// src/selection.ts
|
|
655
|
+
var same = (a, b) => a === b || Number.isNaN(a) && Number.isNaN(b);
|
|
656
|
+
function selection(spec, values2) {
|
|
657
|
+
const mixed = spec.mixedValues ?? [];
|
|
658
|
+
if (spec.mode === "multiple") {
|
|
659
|
+
const on = spec.value;
|
|
660
|
+
const flip = (list, v, to) => to ? [...list, v] : list.filter((w) => !same(w, v));
|
|
661
|
+
return {
|
|
662
|
+
mode: "multiple",
|
|
663
|
+
followsFocus: false,
|
|
664
|
+
stateOf: (i) => on.includes(values2[i]) ? "on" : mixed.includes(values2[i]) ? "mixed" : "off",
|
|
665
|
+
press: (i) => spec.onChange(flip(on, values2[i], !on.includes(values2[i]))),
|
|
666
|
+
edit: () => {
|
|
667
|
+
const initial = on;
|
|
668
|
+
let working = initial;
|
|
669
|
+
return {
|
|
670
|
+
set: (i, to) => {
|
|
671
|
+
const v = values2[i];
|
|
672
|
+
if (working.includes(v) === to) return;
|
|
673
|
+
const next = flip(working, v, to);
|
|
674
|
+
working = next;
|
|
675
|
+
spec.onChange(next);
|
|
676
|
+
},
|
|
677
|
+
revert: () => {
|
|
678
|
+
if (working !== initial) spec.onChange([...initial]);
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
const choose = (next) => spec.onChange(next);
|
|
685
|
+
return {
|
|
686
|
+
mode: "single",
|
|
687
|
+
followsFocus: spec.followsFocus ?? true,
|
|
688
|
+
stateOf: (i) => same(spec.value, values2[i]) ? "on" : mixed.includes(values2[i]) ? "mixed" : "off",
|
|
689
|
+
press: (i) => {
|
|
690
|
+
if (!same(spec.value, values2[i])) choose(values2[i]);
|
|
691
|
+
else if (spec.allowDeselect) choose(null);
|
|
692
|
+
},
|
|
693
|
+
edit: () => {
|
|
694
|
+
const initial = spec.value;
|
|
695
|
+
let working = initial;
|
|
696
|
+
return {
|
|
697
|
+
set: (i, to) => {
|
|
698
|
+
const next = to ? values2[i] : same(working, values2[i]) && spec.allowDeselect ? null : working;
|
|
699
|
+
if (same(next, working)) return;
|
|
700
|
+
working = next;
|
|
701
|
+
choose(next);
|
|
702
|
+
},
|
|
703
|
+
revert: () => {
|
|
704
|
+
if (!same(working, initial)) choose(initial);
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
}
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
function selectionOfEach(items) {
|
|
711
|
+
return {
|
|
712
|
+
mode: "multiple",
|
|
713
|
+
followsFocus: false,
|
|
714
|
+
stateOf: (i) => items[i].selected ? "on" : "off",
|
|
715
|
+
press: (i) => items[i].onChange(!items[i].selected),
|
|
716
|
+
edit: () => {
|
|
717
|
+
const initial = items.map((it) => it.selected);
|
|
718
|
+
const working = [...initial];
|
|
719
|
+
return {
|
|
720
|
+
set: (i, to) => {
|
|
721
|
+
if (working[i] === to) return;
|
|
722
|
+
working[i] = to;
|
|
723
|
+
items[i].onChange(to);
|
|
724
|
+
},
|
|
725
|
+
revert: () => {
|
|
726
|
+
working.forEach((to, i) => {
|
|
727
|
+
if (to === initial[i]) return;
|
|
728
|
+
working[i] = initial[i];
|
|
729
|
+
items[i].onChange(initial[i]);
|
|
730
|
+
});
|
|
731
|
+
}
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
function sweepFrom(model, from) {
|
|
737
|
+
const to = model.stateOf(from) !== "on";
|
|
738
|
+
const edit = model.edit();
|
|
739
|
+
return { reach: (i) => edit.set(i, to), revert: () => edit.revert() };
|
|
740
|
+
}
|
|
741
|
+
function selectionKeys(model, count, enabled = () => true) {
|
|
742
|
+
if (model.mode === "single" && model.followsFocus) {
|
|
743
|
+
let chosen = -1;
|
|
744
|
+
for (let i = 0; i < count && chosen < 0; i++) if (model.stateOf(i) === "on") chosen = i;
|
|
745
|
+
return { tabStop: chosen >= 0 ? chosen : void 0, landed: (i) => {
|
|
746
|
+
if (enabled(i)) model.edit().set(i, true);
|
|
747
|
+
} };
|
|
748
|
+
}
|
|
749
|
+
return { activate: (i) => {
|
|
750
|
+
if (enabled(i)) model.press(i);
|
|
751
|
+
} };
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
export { EDGE_SCROLL_MARGIN, EMPTY_TYPEAHEAD, HEADING_DRIFT, REPEAT_DELAY_MS, REPEAT_EVERY_MS, SWEEP_CAP, TYPEAHEAD_RESET_MS, arrowDir, arrowMove, arrowSteps, attract, clampOrdered, createStepRepeat, dropBand, edgePush, edgeScroll, editEnd, evenMap, firstStop, frames, gapByMidpoint, gridLayout, headingCost, headingOf, imeKey, isNoopDrop, isNudgeChord, isOneChar, moveItems, movedIndex, nearestGap, nearestToward, nudgeGap, nudgeOf, range, rangeKey, rovingGridTarget, rovingKey, rowKey, rowOffset, selection, selectionKeys, selectionOfEach, sliderAt, sliderKey, spanHold, spanMap, spanPan, spanPlace, spanShare, spanValue, spanZoom, stepStop, stepsOf, sweepFrom, tabSort, tabStep, tileGrid, tileKey, tileRows, timerScheduler, treeKey, typeaheadMatch, typeaheadStep, until, usableStops, values };
|
|
755
|
+
//# sourceMappingURL=index.js.map
|
|
756
|
+
//# sourceMappingURL=index.js.map
|