@weasel-js/ui-logic 0.0.0-stage → 1.15.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/LICENSE +21 -0
- package/README.md +108 -2
- package/dist/index.d.ts +562 -0
- package/dist/index.js +502 -0
- package/dist/index.js.map +1 -0
- package/package.json +45 -4
package/dist/index.js
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import { COARSE_STEPS, snapToStep } from '@weasel-js/quantity';
|
|
2
|
+
export { COARSE_STEPS } from '@weasel-js/quantity';
|
|
3
|
+
import { clamp } from '@weasel-js/geom';
|
|
4
|
+
|
|
5
|
+
// src/tree.ts
|
|
6
|
+
function treeKey(items, at, key, shape) {
|
|
7
|
+
const item = items[at];
|
|
8
|
+
if (item === void 0) return { handled: false };
|
|
9
|
+
const canToggle = shape.canToggle?.(item) ?? true;
|
|
10
|
+
const [opens, closes] = shape.dir === "rtl" ? ["ArrowLeft", "ArrowRight"] : ["ArrowRight", "ArrowLeft"];
|
|
11
|
+
if (key === opens) {
|
|
12
|
+
if (!shape.isBranch(item)) return { handled: false };
|
|
13
|
+
if (!shape.isOpen(item)) return canToggle ? { handled: true, toggle: at } : { handled: true, at: null };
|
|
14
|
+
const next = items[at + 1];
|
|
15
|
+
return { handled: true, at: next !== void 0 && shape.isChildOf(next, item) ? at + 1 : null };
|
|
16
|
+
}
|
|
17
|
+
if (key !== closes) return { handled: false };
|
|
18
|
+
if (shape.isOpen(item) && canToggle) return { handled: true, toggle: at };
|
|
19
|
+
const parent = items.findIndex((p) => shape.isChildOf(item, p));
|
|
20
|
+
return parent < 0 ? { handled: false } : { handled: true, at: parent };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// src/list.ts
|
|
24
|
+
var always = () => true;
|
|
25
|
+
function firstStop(items, shape = {}) {
|
|
26
|
+
const i = items.findIndex(shape.canStop ?? always);
|
|
27
|
+
return i < 0 ? null : i;
|
|
28
|
+
}
|
|
29
|
+
function stepStop(items, from, dir, shape = {}) {
|
|
30
|
+
const canStop = shape.canStop ?? always;
|
|
31
|
+
const wrap = shape.wrap ?? true;
|
|
32
|
+
const n = items.length;
|
|
33
|
+
let i = from ?? (dir === 1 ? -1 : n);
|
|
34
|
+
for (let k = 0; k < n; k++) {
|
|
35
|
+
i += dir;
|
|
36
|
+
if (i < 0 || i >= n) {
|
|
37
|
+
if (!wrap) break;
|
|
38
|
+
i = (i + n) % n;
|
|
39
|
+
}
|
|
40
|
+
if (canStop(items[i])) return i;
|
|
41
|
+
}
|
|
42
|
+
return !wrap && from !== null && canStop(items[from]) ? from : null;
|
|
43
|
+
}
|
|
44
|
+
function arrowDir(key, shape) {
|
|
45
|
+
const orientation = shape.orientation ?? "vertical";
|
|
46
|
+
if (orientation !== "horizontal") {
|
|
47
|
+
if (key === "ArrowDown") return 1;
|
|
48
|
+
if (key === "ArrowUp") return -1;
|
|
49
|
+
}
|
|
50
|
+
if (orientation !== "vertical") {
|
|
51
|
+
const forward = shape.dir === "rtl" ? -1 : 1;
|
|
52
|
+
if (key === "ArrowRight") return forward;
|
|
53
|
+
if (key === "ArrowLeft") return -forward;
|
|
54
|
+
}
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
function rovingKey(items, at, key, shape = {}) {
|
|
58
|
+
if (key === "Home") return { handled: true, at: stepStop(items, null, 1, shape) };
|
|
59
|
+
if (key === "End") return { handled: true, at: stepStop(items, null, -1, shape) };
|
|
60
|
+
const dir = arrowDir(key, shape);
|
|
61
|
+
return dir === 0 ? { handled: false } : { handled: true, at: stepStop(items, at, dir, shape) };
|
|
62
|
+
}
|
|
63
|
+
function rowKey(items, at, press, shape = {}, searching = false) {
|
|
64
|
+
const { key } = press;
|
|
65
|
+
const chord = !!(press.ctrlKey || press.metaKey);
|
|
66
|
+
if (shape.selectAll && key.toLowerCase() === "a" && chord && !press.altKey && !press.shiftKey) return { kind: "selectAll" };
|
|
67
|
+
const step = rovingKey(items, at, key, shape);
|
|
68
|
+
if (step.handled) return { kind: "move", at: step.at, extend: !!press.shiftKey };
|
|
69
|
+
if (shape.tree && (key === "ArrowRight" || key === "ArrowLeft")) {
|
|
70
|
+
const t = treeKey(items, at, key, shape.dir && !shape.tree.dir ? { ...shape.tree, dir: shape.dir } : shape.tree);
|
|
71
|
+
if (!t.handled) return null;
|
|
72
|
+
return "toggle" in t ? { kind: "toggle", at: t.toggle } : { kind: "move", at: t.at, extend: false };
|
|
73
|
+
}
|
|
74
|
+
if (key === " " && shape.typeahead && searching) return { kind: "type", held: true };
|
|
75
|
+
if (key === "Enter" || key === " ") return shape.activate ? { kind: "activate" } : null;
|
|
76
|
+
if (shape.typeahead && key.length === 1 && !chord && !press.altKey) return { kind: "type", held: false };
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/typeahead.ts
|
|
81
|
+
var TYPEAHEAD_RESET_MS = 500;
|
|
82
|
+
var EMPTY_TYPEAHEAD = { text: "", at: -Infinity };
|
|
83
|
+
function typeaheadMatch(items, from, query, shape) {
|
|
84
|
+
const q = query.toLowerCase();
|
|
85
|
+
const n = items.length;
|
|
86
|
+
const start = from == null ? 0 : q.length === 1 ? from + 1 : from;
|
|
87
|
+
for (let k = 0; k < n; k++) {
|
|
88
|
+
const i = (start + k) % n;
|
|
89
|
+
const item = items[i];
|
|
90
|
+
if ((shape.canStop?.(item) ?? true) && shape.label(item).toLowerCase().startsWith(q)) return i;
|
|
91
|
+
}
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
function typeaheadStep(items, from, buffer, key, now, shape, resetMs = TYPEAHEAD_RESET_MS) {
|
|
95
|
+
const prev = now - buffer.at < resetMs ? buffer.text : "";
|
|
96
|
+
const k = key.toLowerCase();
|
|
97
|
+
const cycling = prev !== "" && [...prev].every((c) => c.toLowerCase() === k);
|
|
98
|
+
const text = cycling ? key : prev + key;
|
|
99
|
+
return { buffer: { text, at: now }, hit: typeaheadMatch(items, from, text, shape) };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/focus.ts
|
|
103
|
+
function tabSort(targets, declared) {
|
|
104
|
+
const key = (t) => declared(t) ?? Number.POSITIVE_INFINITY;
|
|
105
|
+
return [...targets].sort((a, b) => key(a) - key(b));
|
|
106
|
+
}
|
|
107
|
+
function tabStep(order, from, direction, isBefore) {
|
|
108
|
+
const forward = direction === "next";
|
|
109
|
+
const here = from === null ? -1 : order.indexOf(from);
|
|
110
|
+
let at;
|
|
111
|
+
if (here !== -1) at = here + (forward ? 1 : -1);
|
|
112
|
+
else if (from === null || !isBefore) at = forward ? 0 : order.length - 1;
|
|
113
|
+
else {
|
|
114
|
+
const after = order.findIndex((t) => isBefore(from, t));
|
|
115
|
+
const end = after === -1 ? order.length : after;
|
|
116
|
+
at = forward ? end : end - 1;
|
|
117
|
+
}
|
|
118
|
+
return order[at] ?? null;
|
|
119
|
+
}
|
|
120
|
+
function arrowSteps(key, shift, dir = "ltr") {
|
|
121
|
+
const steps = shift ? COARSE_STEPS : 1;
|
|
122
|
+
if (key === "ArrowUp") return steps;
|
|
123
|
+
if (key === "ArrowDown") return -steps;
|
|
124
|
+
const d = arrowDir(key, { orientation: "horizontal", dir });
|
|
125
|
+
return d === 0 ? null : d * steps;
|
|
126
|
+
}
|
|
127
|
+
function arrowMove(key, shift) {
|
|
128
|
+
const steps = arrowSteps(key, shift);
|
|
129
|
+
if (steps === null) return null;
|
|
130
|
+
return key === "ArrowRight" || key === "ArrowLeft" ? { dx: steps, dy: 0 } : { dx: 0, dy: -steps };
|
|
131
|
+
}
|
|
132
|
+
function rangeKey(key, shift, dir = "ltr") {
|
|
133
|
+
switch (key) {
|
|
134
|
+
case "PageUp":
|
|
135
|
+
return COARSE_STEPS;
|
|
136
|
+
case "PageDown":
|
|
137
|
+
return -COARSE_STEPS;
|
|
138
|
+
case "Home":
|
|
139
|
+
return "home";
|
|
140
|
+
case "End":
|
|
141
|
+
return "end";
|
|
142
|
+
default:
|
|
143
|
+
return arrowSteps(key, shift, dir);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function usableStops(stops, min, max) {
|
|
147
|
+
if (!stops || stops.length === 0) return [];
|
|
148
|
+
const byValue = /* @__PURE__ */ new Map();
|
|
149
|
+
for (const stop of stops) {
|
|
150
|
+
const s = typeof stop === "number" ? { value: stop } : stop;
|
|
151
|
+
if (s.value < min || s.value > max) continue;
|
|
152
|
+
const seen = byValue.get(s.value);
|
|
153
|
+
if (!seen || seen.label === void 0 && s.label !== void 0) byValue.set(s.value, s);
|
|
154
|
+
}
|
|
155
|
+
return [...byValue.values()].sort((a, b) => a.value - b.value);
|
|
156
|
+
}
|
|
157
|
+
function attract(v, stops, tolerance, toFraction) {
|
|
158
|
+
const f = toFraction(v);
|
|
159
|
+
let best = v;
|
|
160
|
+
let bestGap = tolerance;
|
|
161
|
+
for (const stop of stops) {
|
|
162
|
+
const gap = Math.abs(toFraction(stop) - f);
|
|
163
|
+
if (gap <= bestGap) {
|
|
164
|
+
best = stop;
|
|
165
|
+
bestGap = gap;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return best;
|
|
169
|
+
}
|
|
170
|
+
function stepStops(stops, v, direction, count) {
|
|
171
|
+
if (direction > 0) {
|
|
172
|
+
const first2 = stops.findIndex((stop) => stop > v);
|
|
173
|
+
if (first2 === -1) return stops[stops.length - 1];
|
|
174
|
+
return stops[Math.min(stops.length - 1, first2 + count - 1)];
|
|
175
|
+
}
|
|
176
|
+
let first = -1;
|
|
177
|
+
for (let i = stops.length - 1; i >= 0; i--) {
|
|
178
|
+
if (stops[i] < v) {
|
|
179
|
+
first = i;
|
|
180
|
+
break;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (first === -1) return stops[0];
|
|
184
|
+
return stops[Math.max(0, first - (count - 1))];
|
|
185
|
+
}
|
|
186
|
+
function defaultStep(step, min, max) {
|
|
187
|
+
if (step !== void 0 && step > 0) return step;
|
|
188
|
+
return (max - min) / 100;
|
|
189
|
+
}
|
|
190
|
+
function clampOrdered(v, thumbs, index, min, max, step) {
|
|
191
|
+
const gap = step !== void 0 && step > 0 ? step : (max - min) / 1e3;
|
|
192
|
+
const lower = index > 0 ? thumbs[index - 1].value + gap : min;
|
|
193
|
+
const upper = index < thumbs.length - 1 ? thumbs[index + 1].value - gap : max;
|
|
194
|
+
return clamp(v, lower, upper);
|
|
195
|
+
}
|
|
196
|
+
function crossEnd(from, delta, stepSize, range, track) {
|
|
197
|
+
const [lo, hi] = range;
|
|
198
|
+
if (from > hi) return delta < 0 ? hi + delta + stepSize : from;
|
|
199
|
+
if (from < lo) return delta > 0 ? lo + delta - stepSize : from;
|
|
200
|
+
if (from + delta > hi) return from < hi ? hi : track[1];
|
|
201
|
+
if (from + delta < lo) return from > lo ? lo : track[0];
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
function sliderKey(key, shift, shape) {
|
|
205
|
+
const move = rangeKey(key, shift, shape.dir);
|
|
206
|
+
if (move === null) return null;
|
|
207
|
+
const { thumbs, index, min, max, step } = shape;
|
|
208
|
+
const from = thumbs[index].value;
|
|
209
|
+
const [bLo, bHi] = shape.bounds?.() ?? [min, max];
|
|
210
|
+
const lo = Math.max(min, bLo);
|
|
211
|
+
const hi = Math.min(max, bHi);
|
|
212
|
+
const stops = shape.stops ?? [];
|
|
213
|
+
let v;
|
|
214
|
+
if (move === "home") v = lo;
|
|
215
|
+
else if (move === "end") v = hi;
|
|
216
|
+
else if (shape.keyStep) v = shape.keyStep(move);
|
|
217
|
+
else if (stops.length > 0) v = stepStops(stops, from, move > 0 ? 1 : -1, Math.abs(move));
|
|
218
|
+
else {
|
|
219
|
+
const range = shape.range ?? [min, max];
|
|
220
|
+
const stepSize = defaultStep(step, range[0], range[1]);
|
|
221
|
+
const delta = move * stepSize;
|
|
222
|
+
v = crossEnd(from, delta, stepSize, range, [min, max]) ?? snapToStep(from + delta, step, min);
|
|
223
|
+
}
|
|
224
|
+
v = clamp(v, lo, hi);
|
|
225
|
+
return shape.ordered ? clampOrdered(v, thumbs, index, lo, hi, step) : v;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/textCommit.ts
|
|
229
|
+
function editEnd(cause, shape = {}) {
|
|
230
|
+
if (cause === "blur") return "commit";
|
|
231
|
+
if (cause.key === "Enter") return shape.multiline && cause.shift ? null : "commit";
|
|
232
|
+
if (cause.key === "Escape") return shape.escape ?? "cancel";
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
function imeKey(e) {
|
|
236
|
+
return e.isComposing === true || e.keyCode === 229 || e.key === "Process" || e.key === "Dead";
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// src/reorder.ts
|
|
240
|
+
var sortedSet = (at) => [...new Set(at)].sort((a, b) => a - b);
|
|
241
|
+
function moveItems(items, at, to) {
|
|
242
|
+
const taken = new Set(at);
|
|
243
|
+
const moved = sortedSet(at).flatMap((i) => i >= 0 && i < items.length ? [items[i]] : []);
|
|
244
|
+
const out = items.filter((_, i) => !taken.has(i));
|
|
245
|
+
out.splice(movedIndex(at, to), 0, ...moved);
|
|
246
|
+
return out;
|
|
247
|
+
}
|
|
248
|
+
function movedIndex(at, to) {
|
|
249
|
+
return to - sortedSet(at).filter((i) => i < to).length;
|
|
250
|
+
}
|
|
251
|
+
function isNoopDrop(at, to) {
|
|
252
|
+
const s = sortedSet(at);
|
|
253
|
+
if (s.length === 0) return false;
|
|
254
|
+
const first = s[0];
|
|
255
|
+
const last = s[s.length - 1];
|
|
256
|
+
return last - first + 1 === s.length && to >= first && to <= last + 1;
|
|
257
|
+
}
|
|
258
|
+
function nudgeGap(at, by, length) {
|
|
259
|
+
const s = sortedSet(at);
|
|
260
|
+
if (s.length === 0) return null;
|
|
261
|
+
const to = by < 0 ? s[0] - 1 : s[s.length - 1] + 2;
|
|
262
|
+
return to < 0 || to > length ? null : to;
|
|
263
|
+
}
|
|
264
|
+
function gapByMidpoint(mids, p) {
|
|
265
|
+
const backward = mids.length > 1 && mids[mids.length - 1] < mids[0];
|
|
266
|
+
return mids.filter((m) => backward ? m >= p : m <= p).length;
|
|
267
|
+
}
|
|
268
|
+
function nearestGap(gaps, p) {
|
|
269
|
+
let best = -1;
|
|
270
|
+
for (let k = 0; k < gaps.length; k++) if (best < 0 || Math.abs(gaps[k] - p) < Math.abs(gaps[best] - p)) best = k;
|
|
271
|
+
return best;
|
|
272
|
+
}
|
|
273
|
+
function isNudgeChord(e) {
|
|
274
|
+
return e.altKey && !e.ctrlKey && !e.metaKey;
|
|
275
|
+
}
|
|
276
|
+
function nudgeOf(e, back = "ArrowUp", on = "ArrowDown") {
|
|
277
|
+
if (!isNudgeChord(e)) return null;
|
|
278
|
+
if (e.key === back) return -1;
|
|
279
|
+
return e.key === on ? 1 : null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// src/scheduler.ts
|
|
283
|
+
var timerScheduler = {
|
|
284
|
+
now: () => Date.now(),
|
|
285
|
+
set: (fn, ms) => {
|
|
286
|
+
const t = setTimeout(fn, ms);
|
|
287
|
+
return () => clearTimeout(t);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// src/stepRepeat.ts
|
|
292
|
+
var REPEAT_DELAY_MS = 400;
|
|
293
|
+
var REPEAT_EVERY_MS = 60;
|
|
294
|
+
function createStepRepeat(step, spent, opts = {}) {
|
|
295
|
+
let cancel = null;
|
|
296
|
+
let key = null;
|
|
297
|
+
let hold = 0;
|
|
298
|
+
const stop = () => {
|
|
299
|
+
cancel?.();
|
|
300
|
+
cancel = null;
|
|
301
|
+
key = null;
|
|
302
|
+
hold++;
|
|
303
|
+
};
|
|
304
|
+
const tick = (k, ms) => {
|
|
305
|
+
cancel = (opts.scheduler ?? timerScheduler).set(() => {
|
|
306
|
+
if (spent(k)) return stop();
|
|
307
|
+
step(k);
|
|
308
|
+
tick(k, opts.everyMs ?? REPEAT_EVERY_MS);
|
|
309
|
+
}, ms);
|
|
310
|
+
};
|
|
311
|
+
return {
|
|
312
|
+
start(k) {
|
|
313
|
+
stop();
|
|
314
|
+
const mine = hold;
|
|
315
|
+
key = k;
|
|
316
|
+
step(k);
|
|
317
|
+
if (hold === mine) tick(k, opts.delayMs ?? REPEAT_DELAY_MS);
|
|
318
|
+
},
|
|
319
|
+
stop,
|
|
320
|
+
held: () => key
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// src/measure.ts
|
|
325
|
+
var UNBOUNDED = { minWidth: 0, maxWidth: Infinity, minHeight: 0, maxHeight: Infinity };
|
|
326
|
+
function exactly(width, height) {
|
|
327
|
+
return { minWidth: width, maxWidth: width, minHeight: height, maxHeight: height };
|
|
328
|
+
}
|
|
329
|
+
function fixedWidth(width) {
|
|
330
|
+
return { minWidth: width, maxWidth: width, minHeight: 0, maxHeight: Infinity };
|
|
331
|
+
}
|
|
332
|
+
function clampTo(size, min, max) {
|
|
333
|
+
return Math.max(min, Math.min(size, max));
|
|
334
|
+
}
|
|
335
|
+
function envPx(env, name, fallback) {
|
|
336
|
+
const raw = env.token(name)?.trim();
|
|
337
|
+
if (raw) {
|
|
338
|
+
const m = /^(-?\d*\.?\d+)px$/.exec(raw);
|
|
339
|
+
if (m) return Number(m[1]);
|
|
340
|
+
}
|
|
341
|
+
if (typeof fallback === "number") return fallback;
|
|
342
|
+
if (typeof fallback === "string") return envPx(env, fallback);
|
|
343
|
+
throw new Error(`envPx: ${name} is not a px length (${raw ?? "unset"})`);
|
|
344
|
+
}
|
|
345
|
+
function envNumber(env, name, fallback) {
|
|
346
|
+
const n = Number.parseFloat(env.token(name) ?? "");
|
|
347
|
+
return Number.isFinite(n) ? n : fallback;
|
|
348
|
+
}
|
|
349
|
+
function centeredBaseline(boxHeight, lineHeight, extent) {
|
|
350
|
+
const top = (boxHeight - lineHeight) / 2;
|
|
351
|
+
return top + (lineHeight - (extent.ascent + extent.descent)) / 2 + extent.ascent;
|
|
352
|
+
}
|
|
353
|
+
function offsetRect(r, dx, dy) {
|
|
354
|
+
return { x: r.x + dx, y: r.y + dy, width: r.width, height: r.height };
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/button.ts
|
|
358
|
+
function buttonParts(face) {
|
|
359
|
+
if (face.iconOnly) return [face.loading ? "spinner" : "icon"];
|
|
360
|
+
const out = [];
|
|
361
|
+
if (face.loading) out.push("spinner");
|
|
362
|
+
else if (face.leadingIcon) out.push("leading");
|
|
363
|
+
if (face.label !== void 0) out.push("label");
|
|
364
|
+
if (face.trailingIcon) out.push("trailing");
|
|
365
|
+
return out;
|
|
366
|
+
}
|
|
367
|
+
var BUTTON_SPINNER_SIZE = 14;
|
|
368
|
+
function buttonMetrics(env, size = "md", iconOnly = false) {
|
|
369
|
+
const sm = size === "sm";
|
|
370
|
+
const height = envPx(env, sm ? "--wzl-control-h-sm" : "--wzl-control-h");
|
|
371
|
+
return {
|
|
372
|
+
height,
|
|
373
|
+
padX: iconOnly ? 0 : envPx(env, "--wzl-button-pad-x", sm ? "--wzl-space-4" : "--wzl-space-6"),
|
|
374
|
+
gap: envPx(env, "--wzl-space-3"),
|
|
375
|
+
iconSize: envPx(env, "--wzl-button-icon-size", sm ? 12 : 14),
|
|
376
|
+
spinnerSize: BUTTON_SPINNER_SIZE,
|
|
377
|
+
font: {
|
|
378
|
+
family: env.token("--wzl-font-ui"),
|
|
379
|
+
size: envPx(env, "--wzl-field-font-size", sm ? "--wzl-font-size-sm" : "--wzl-font-size"),
|
|
380
|
+
weight: envNumber(env, "--wzl-button-font-weight", envNumber(env, "--wzl-font-weight-medium", 500))
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
function measureButton(face, c, env) {
|
|
385
|
+
const m = buttonMetrics(env, face.size, face.iconOnly);
|
|
386
|
+
const parts = buttonParts(face);
|
|
387
|
+
let text = null;
|
|
388
|
+
const widths = parts.map((p) => {
|
|
389
|
+
if (p === "spinner") return m.spinnerSize;
|
|
390
|
+
if (p !== "label") return m.iconSize;
|
|
391
|
+
text = env.text(face.label ?? "", m.font);
|
|
392
|
+
return text.width;
|
|
393
|
+
});
|
|
394
|
+
const gaps = Math.max(0, parts.length - 1) * m.gap;
|
|
395
|
+
const content = widths.reduce((a, b) => a + b, 0) + gaps;
|
|
396
|
+
const width = clampTo(face.iconOnly ? m.height : content + m.padX * 2, c.minWidth, c.maxWidth);
|
|
397
|
+
const height = clampTo(m.height, c.minHeight, c.maxHeight);
|
|
398
|
+
const over = content - (width - m.padX * 2);
|
|
399
|
+
const label = parts.indexOf("label");
|
|
400
|
+
if (over > 0 && label >= 0) widths[label] = Math.max(0, widths[label] - over);
|
|
401
|
+
const fitted = widths.reduce((a, b) => a + b, 0) + gaps;
|
|
402
|
+
let x = (width - fitted) / 2;
|
|
403
|
+
const laid = parts.map((part, i) => {
|
|
404
|
+
const w = widths[i];
|
|
405
|
+
const h = part === "label" ? m.font.size : part === "spinner" ? m.spinnerSize : m.iconSize;
|
|
406
|
+
const rect = { x, y: (height - h) / 2, width: w, height: h };
|
|
407
|
+
x += w + m.gap;
|
|
408
|
+
return { part, rect };
|
|
409
|
+
});
|
|
410
|
+
const baseline = text ? centeredBaseline(height, m.font.size, text) : height;
|
|
411
|
+
return { width, height, baseline, layout: { parts: laid, metrics: m } };
|
|
412
|
+
}
|
|
413
|
+
var IDLE_PRESS = { hovered: false, held: false };
|
|
414
|
+
function pressStep(s, input) {
|
|
415
|
+
switch (input.type) {
|
|
416
|
+
case "down":
|
|
417
|
+
return { state: { ...s, held: true }, pressed: false };
|
|
418
|
+
case "move":
|
|
419
|
+
return { state: s.held === input.inside ? s : { ...s, held: input.inside }, pressed: false };
|
|
420
|
+
case "up":
|
|
421
|
+
return { state: { ...s, held: false }, pressed: s.held && input.inside };
|
|
422
|
+
case "cancel":
|
|
423
|
+
return { state: { ...s, held: false }, pressed: false };
|
|
424
|
+
case "enter":
|
|
425
|
+
return { state: { ...s, hovered: true }, pressed: false };
|
|
426
|
+
case "leave":
|
|
427
|
+
return { state: { ...s, hovered: false }, pressed: false };
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
function isPressKey(key) {
|
|
431
|
+
return key === "Enter" || key === " ";
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// src/rowList.ts
|
|
435
|
+
function listMode(o) {
|
|
436
|
+
if (o.controls || o.activatable && !o.selection) return "grid";
|
|
437
|
+
return o.selection ? "listbox" : "list";
|
|
438
|
+
}
|
|
439
|
+
var LIST_ROLES = {
|
|
440
|
+
list: { container: "list", row: "listitem" },
|
|
441
|
+
listbox: { container: "listbox", row: "option" },
|
|
442
|
+
grid: { container: "grid", row: "row" }
|
|
443
|
+
};
|
|
444
|
+
function dropSide(dropIndex, i, count) {
|
|
445
|
+
if (dropIndex == null) return void 0;
|
|
446
|
+
if (dropIndex === i) return "before";
|
|
447
|
+
if (dropIndex >= count && i === count - 1) return "after";
|
|
448
|
+
return void 0;
|
|
449
|
+
}
|
|
450
|
+
var ROW_GAP = 1;
|
|
451
|
+
function rowListMetrics(env) {
|
|
452
|
+
const size = envPx(env, "--wzl-font-size-sm");
|
|
453
|
+
return {
|
|
454
|
+
rowHeight: envPx(env, "--wzl-control-h"),
|
|
455
|
+
padX: envPx(env, "--wzl-space-4"),
|
|
456
|
+
gap: envPx(env, "--wzl-space-4"),
|
|
457
|
+
emptyPadY: envPx(env, "--wzl-space-6"),
|
|
458
|
+
font: { family: env.token("--wzl-font-ui"), size, weight: 400 },
|
|
459
|
+
lineHeight: size * envNumber(env, "--wzl-leading-snug", 1.2)
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function measureRowList(face, c, env) {
|
|
463
|
+
const m = rowListMetrics(env);
|
|
464
|
+
if (face.rows.length === 0) {
|
|
465
|
+
const t = env.text(face.empty ?? "\u2014", m.font);
|
|
466
|
+
const line = m.lineHeight;
|
|
467
|
+
const width2 = clampTo(t.width + m.padX * 2, c.minWidth, c.maxWidth);
|
|
468
|
+
const height2 = clampTo(line + m.emptyPadY * 2, c.minHeight, c.maxHeight);
|
|
469
|
+
const empty = { x: m.padX, y: m.emptyPadY, width: Math.max(0, width2 - m.padX * 2), height: line };
|
|
470
|
+
return { width: width2, height: height2, baseline: m.emptyPadY + centeredBaseline(line, line, t), layout: { rows: [], empty, metrics: m } };
|
|
471
|
+
}
|
|
472
|
+
const extents = face.rows.map((r) => env.text(r.label, m.font));
|
|
473
|
+
const side = (r) => (r.leadingWidth != null ? r.leadingWidth + m.gap : 0) + (r.trailingWidth != null ? r.trailingWidth + m.gap : 0);
|
|
474
|
+
const intrinsic = Math.max(...face.rows.map((r, i) => m.padX * 2 + side(r) + extents[i].width));
|
|
475
|
+
const width = clampTo(intrinsic, c.minWidth, c.maxWidth);
|
|
476
|
+
const n = face.rows.length;
|
|
477
|
+
const height = clampTo(n * m.rowHeight + (n - 1) * ROW_GAP, c.minHeight, c.maxHeight);
|
|
478
|
+
const rows = face.rows.map((r, i) => {
|
|
479
|
+
const y = i * (m.rowHeight + ROW_GAP);
|
|
480
|
+
const h = m.rowHeight;
|
|
481
|
+
const line = m.lineHeight;
|
|
482
|
+
let x = m.padX;
|
|
483
|
+
let right = width - m.padX;
|
|
484
|
+
const box = { x: 0, y, width, height: h };
|
|
485
|
+
const leading = r.leadingWidth != null ? { x, y, width: r.leadingWidth, height: h } : void 0;
|
|
486
|
+
if (leading) x += r.leadingWidth + m.gap;
|
|
487
|
+
const trailing = r.trailingWidth != null ? { x: right - r.trailingWidth, y, width: r.trailingWidth, height: h } : void 0;
|
|
488
|
+
if (trailing) right -= r.trailingWidth + m.gap;
|
|
489
|
+
const label = { x, y: y + (h - line) / 2, width: Math.max(0, right - x), height: line };
|
|
490
|
+
return { id: r.id, rect: box, label, ...leading ? { leading } : {}, ...trailing ? { trailing } : {} };
|
|
491
|
+
});
|
|
492
|
+
const baseline = centeredBaseline(m.rowHeight, m.lineHeight, extents[0]);
|
|
493
|
+
return { width, height, baseline, layout: { rows, metrics: m } };
|
|
494
|
+
}
|
|
495
|
+
function rowAtY(layout, y) {
|
|
496
|
+
const i = layout.rows.findIndex((r) => y >= r.rect.y && y < r.rect.y + r.rect.height);
|
|
497
|
+
return i < 0 ? null : i;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
export { BUTTON_SPINNER_SIZE, EMPTY_TYPEAHEAD, IDLE_PRESS, LIST_ROLES, REPEAT_DELAY_MS, REPEAT_EVERY_MS, ROW_GAP, TYPEAHEAD_RESET_MS, UNBOUNDED, arrowMove, arrowSteps, attract, buttonMetrics, buttonParts, centeredBaseline, clampOrdered, clampTo, createStepRepeat, dropSide, editEnd, envNumber, envPx, exactly, firstStop, fixedWidth, gapByMidpoint, imeKey, isNoopDrop, isNudgeChord, isPressKey, listMode, measureButton, measureRowList, moveItems, movedIndex, nearestGap, nudgeGap, nudgeOf, offsetRect, pressStep, rangeKey, rovingKey, rowAtY, rowKey, rowListMetrics, sliderKey, stepStop, tabSort, tabStep, timerScheduler, treeKey, typeaheadMatch, typeaheadStep, usableStops };
|
|
501
|
+
//# sourceMappingURL=index.js.map
|
|
502
|
+
//# sourceMappingURL=index.js.map
|