@weasel-js/routing 1.13.0 → 1.14.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/chunk-EUCNVGJV.js +1103 -0
- package/dist/chunk-EUCNVGJV.js.map +1 -0
- package/dist/{chunk-XVWEZTUX.js → chunk-HR5F4LBX.js} +221 -971
- package/dist/chunk-HR5F4LBX.js.map +1 -0
- package/dist/chunk-INZ63QGX.js +325 -0
- package/dist/chunk-INZ63QGX.js.map +1 -0
- package/dist/{index-UFm_a202.d.ts → index-DJ7SxEKS.d.ts} +161 -27
- package/dist/index.d.ts +1 -1
- package/dist/index.js +31 -3
- package/dist/index.js.map +1 -1
- package/dist/{internal-DQJ3fv86.d.ts → internal-CaJ4viHc.d.ts} +7 -22
- package/dist/internal.d.ts +2 -2
- package/dist/react.d.ts +9 -10
- package/dist/react.js +77 -335
- package/dist/react.js.map +1 -1
- package/dist/testing/registerAction.d.ts +22 -0
- package/dist/testing/registerAction.js +15 -0
- package/dist/testing/registerAction.js.map +1 -0
- package/package.json +17 -13
- package/dist/chunk-XVWEZTUX.js.map +0 -1
|
@@ -0,0 +1,1103 @@
|
|
|
1
|
+
import { isDev } from '@weasel-js/react';
|
|
2
|
+
import { createEmitter } from '@weasel-js/registry';
|
|
3
|
+
import { RESERVED_ID_PREFIXES, RESERVED_ID_NAMES, isApplePlatform, bindingSpec } from '@weasel-js/gestures';
|
|
4
|
+
export { RESERVED_ID_NAMES, RESERVED_ID_PREFIXES, ROUTE_FIELD_DEFINITIONS, ROUTE_TERMS, VALID_MOD_NAMES, canonicalModifiers, collapseShiftPairs, describeRoute, describeRouteParts, formatPhaseAtom, formatRoute, parseRoute } from '@weasel-js/gestures';
|
|
5
|
+
import { isPrefLeaf, isPrefSection, prefScopeIn, prefScopeUnder, prefConditionHolds, isBuiltinPref, prefAliasTarget, prefLeafAt, prefAliasedLeaf, PREF_SCOPE, prefLeaves, prefHoldsValue, prefVariantOf, prefVariantDefault, prefNumberAt, resolvePrefUnit, prefDisplayBounds, prefUnitAccepts, prefObjectBase, prefValueAtPath } from '@weasel-js/prefs';
|
|
6
|
+
import { endlessAllows } from '@weasel-js/quantity';
|
|
7
|
+
|
|
8
|
+
// src/interactions/actions/actionItems.ts
|
|
9
|
+
var NO_TAGS = Object.freeze([]);
|
|
10
|
+
function actionItems(action) {
|
|
11
|
+
if (action.invoker?.timing !== "immediate") return [];
|
|
12
|
+
if (!action.variants?.length) {
|
|
13
|
+
return [{ key: action.id, action, label: action.label, icon: action.icon, tags: action.tags ?? NO_TAGS }];
|
|
14
|
+
}
|
|
15
|
+
return action.variants.map((v) => ({
|
|
16
|
+
key: `${action.id}:${v.key}`,
|
|
17
|
+
action,
|
|
18
|
+
label: v.label,
|
|
19
|
+
icon: v.icon ?? action.icon,
|
|
20
|
+
tags: v.tags ? [...action.tags ?? NO_TAGS, ...v.tags] : action.tags ?? NO_TAGS,
|
|
21
|
+
params: v.params
|
|
22
|
+
}));
|
|
23
|
+
}
|
|
24
|
+
function actionItemTitle(item) {
|
|
25
|
+
if (item.key === item.action.id) return item.label;
|
|
26
|
+
const words = (s) => s.toLowerCase().match(/[\p{L}\p{N}]+/gu) ?? [];
|
|
27
|
+
const head = words(item.action.label);
|
|
28
|
+
const label = words(item.label);
|
|
29
|
+
for (let i = 0; i + head.length <= label.length; i++) {
|
|
30
|
+
if (head.every((w, j) => label[i + j] === w)) return item.label;
|
|
31
|
+
}
|
|
32
|
+
return `${item.action.label}: ${item.label}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/interactions/actions/actionSettings.ts
|
|
36
|
+
var EMPTY = { values: {} };
|
|
37
|
+
function readActionSettings(action, deps) {
|
|
38
|
+
const settings = action.settings;
|
|
39
|
+
if (!settings) return null;
|
|
40
|
+
try {
|
|
41
|
+
return settings.read(deps ?? {});
|
|
42
|
+
} catch {
|
|
43
|
+
return EMPTY;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function actionSettingsParams(settings, path, value) {
|
|
47
|
+
return settings.params ? settings.params(path, value) : { [path]: value };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/interactions/actions/binding.ts
|
|
51
|
+
function actionBindings(action) {
|
|
52
|
+
const gs = action.defaultBinding;
|
|
53
|
+
if (!gs) return [];
|
|
54
|
+
const raw = Array.isArray(gs) ? gs : [gs];
|
|
55
|
+
return raw.map((entry) => {
|
|
56
|
+
const isBoundObj = !("kind" in entry);
|
|
57
|
+
const spec = isBoundObj ? entry.spec : entry;
|
|
58
|
+
const opts = isBoundObj ? entry.opts : void 0;
|
|
59
|
+
return { spec, actionId: action.id, ...opts !== void 0 ? { opts } : {} };
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/interactions/actions/actionShortcuts.ts
|
|
64
|
+
function keySpecShortcut(spec) {
|
|
65
|
+
if (spec.kind !== "key" && spec.kind !== "key-held") return void 0;
|
|
66
|
+
const key = Array.isArray(spec.key) ? spec.key[0] : spec.key;
|
|
67
|
+
if (typeof key !== "string" || key.length === 0) return void 0;
|
|
68
|
+
const mods = "mods" in spec ? spec.mods : void 0;
|
|
69
|
+
return {
|
|
70
|
+
key,
|
|
71
|
+
mod: mods?.mod === true,
|
|
72
|
+
alt: mods?.alt === true,
|
|
73
|
+
shift: mods?.shift === true
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
function actionShortcuts(action, params) {
|
|
77
|
+
const out = [];
|
|
78
|
+
const seen = /* @__PURE__ */ new Set();
|
|
79
|
+
for (const { spec, opts } of actionBindings(action)) {
|
|
80
|
+
if (params && !paramsMatch(opts?.params, params)) continue;
|
|
81
|
+
const shortcut = keySpecShortcut(spec);
|
|
82
|
+
if (!shortcut) continue;
|
|
83
|
+
const token = `${shortcut.key}|${shortcut.mod}|${shortcut.alt}|${shortcut.shift}`;
|
|
84
|
+
if (seen.has(token)) continue;
|
|
85
|
+
seen.add(token);
|
|
86
|
+
out.push(shortcut);
|
|
87
|
+
}
|
|
88
|
+
return out;
|
|
89
|
+
}
|
|
90
|
+
function paramsMatch(bound, wanted) {
|
|
91
|
+
if (typeof bound !== "object" || bound === null) return false;
|
|
92
|
+
return Object.entries(wanted).every(([k, v]) => bound[k] === v);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/interactions/actions/action.ts
|
|
96
|
+
var ActionDisabledReason = {
|
|
97
|
+
SelectionRequired: "selection-required",
|
|
98
|
+
SceneEmpty: "scene-empty",
|
|
99
|
+
NotApplicable: "not-applicable",
|
|
100
|
+
/** Sentinel: the predicate threw. Surfaced by `evaluateEnabled`'s catch. */
|
|
101
|
+
PredicateThrew: "predicate-threw"
|
|
102
|
+
};
|
|
103
|
+
function actionCursor(action, params) {
|
|
104
|
+
const c = action?.cursor;
|
|
105
|
+
return typeof c === "function" ? c(params) : c;
|
|
106
|
+
}
|
|
107
|
+
var enabledSlowWarned = /* @__PURE__ */ new Set();
|
|
108
|
+
var enabledThrewWarned = /* @__PURE__ */ new Set();
|
|
109
|
+
var ENABLED_BUDGET_MS = 4;
|
|
110
|
+
function evaluateEnabled(action, deps, at) {
|
|
111
|
+
if (!action.enabled) return { enabled: true };
|
|
112
|
+
const dev = isDev();
|
|
113
|
+
const now = typeof performance !== "undefined" && typeof performance.now === "function" ? () => performance.now() : () => Date.now();
|
|
114
|
+
const start = dev ? now() : 0;
|
|
115
|
+
try {
|
|
116
|
+
const result = action.enabled(deps, at);
|
|
117
|
+
if (dev) {
|
|
118
|
+
const elapsed = now() - start;
|
|
119
|
+
if (elapsed > ENABLED_BUDGET_MS && !enabledSlowWarned.has(action.id)) {
|
|
120
|
+
enabledSlowWarned.add(action.id);
|
|
121
|
+
console.warn(
|
|
122
|
+
`weasel actions: enabled() for action "${action.id}" took ${elapsed.toFixed(2)}ms (budget ${ENABLED_BUDGET_MS}ms). The predicate must be pure and fast \u2014 see Action.enabled JSDoc.`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (result === true) return { enabled: true };
|
|
127
|
+
return { enabled: false, reason: result };
|
|
128
|
+
} catch (e) {
|
|
129
|
+
if (dev && !enabledThrewWarned.has(action.id)) {
|
|
130
|
+
enabledThrewWarned.add(action.id);
|
|
131
|
+
console.warn(
|
|
132
|
+
`weasel actions: enabled() for action "${action.id}" threw; treating as disabled. The predicate must not throw \u2014 see Action.enabled JSDoc.`,
|
|
133
|
+
e
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
return { enabled: false, reason: ActionDisabledReason.PredicateThrew };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
function evaluateActive(action, deps) {
|
|
140
|
+
if (!action.active) return void 0;
|
|
141
|
+
try {
|
|
142
|
+
return action.active(deps) === true;
|
|
143
|
+
} catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
var DEV = isDev();
|
|
148
|
+
function buildDepsFromRequires(action, depRegistry) {
|
|
149
|
+
const requires = action.requires ?? [];
|
|
150
|
+
const entries = requires.map((name) => [name, depRegistry.get(name)]);
|
|
151
|
+
const deps = Object.fromEntries(entries);
|
|
152
|
+
if (DEV) {
|
|
153
|
+
const declared = new Set(requires);
|
|
154
|
+
return new Proxy(deps, {
|
|
155
|
+
get(target, prop, receiver) {
|
|
156
|
+
if (typeof prop === "string" && !declared.has(prop) && prop !== "then") {
|
|
157
|
+
const message = `[weasel:dispatcher] action "${action.id}" read deps.${prop} but did not declare it in \`requires\`. Add \`requires: [...'${prop}']\` to the descriptor or the dep will be undefined at runtime.`;
|
|
158
|
+
console.warn(message);
|
|
159
|
+
throw new Error(message);
|
|
160
|
+
}
|
|
161
|
+
return Reflect.get(target, prop, receiver);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
return deps;
|
|
166
|
+
}
|
|
167
|
+
var ScopeNode = class {
|
|
168
|
+
/** `descends`: whether reads made here answer from the active child down —
|
|
169
|
+
* true for a root or a yoke, which chrome reads through; false for a scope,
|
|
170
|
+
* which answers for itself even with a scope nested inside it. */
|
|
171
|
+
constructor(parent, descends) {
|
|
172
|
+
this.parent = parent;
|
|
173
|
+
this.descends = descends;
|
|
174
|
+
this.tree = parent ? parent.tree : { version: 0, listeners: createEmitter() };
|
|
175
|
+
}
|
|
176
|
+
parent;
|
|
177
|
+
descends;
|
|
178
|
+
tree;
|
|
179
|
+
children = [];
|
|
180
|
+
activeChild = null;
|
|
181
|
+
/** Bump the tree's version and tell every listener in it. */
|
|
182
|
+
changed() {
|
|
183
|
+
this.tree.version++;
|
|
184
|
+
this.tree.listeners.emit();
|
|
185
|
+
}
|
|
186
|
+
/** Join the parent's children, newest last; returns the leave. */
|
|
187
|
+
mount() {
|
|
188
|
+
const parent = this.parent;
|
|
189
|
+
if (!parent) return () => {
|
|
190
|
+
};
|
|
191
|
+
parent.children.push(this);
|
|
192
|
+
this.changed();
|
|
193
|
+
let left = false;
|
|
194
|
+
return () => {
|
|
195
|
+
if (left) return;
|
|
196
|
+
left = true;
|
|
197
|
+
const i = parent.children.lastIndexOf(this);
|
|
198
|
+
if (i !== -1) parent.children.splice(i, 1);
|
|
199
|
+
if (parent.activeChild === this) parent.activeChild = null;
|
|
200
|
+
this.changed();
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
/** Make this node the one its ancestors resolve through. */
|
|
204
|
+
activate() {
|
|
205
|
+
let moved = false;
|
|
206
|
+
for (let n = this; n.parent; n = n.parent) {
|
|
207
|
+
if (n.parent.activeChild !== n) {
|
|
208
|
+
n.parent.activeChild = n;
|
|
209
|
+
moved = true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
if (moved) this.changed();
|
|
213
|
+
}
|
|
214
|
+
/** The node a lookup made here starts from: down the active children, or the
|
|
215
|
+
* newest where nothing has been activated yet. A yoke with no members yet
|
|
216
|
+
* is passed over — there is no canvas behind it to answer. */
|
|
217
|
+
leaf() {
|
|
218
|
+
let n = this;
|
|
219
|
+
for (; ; ) {
|
|
220
|
+
const next = n.activeChild ?? n.newestMember();
|
|
221
|
+
if (!next) return n;
|
|
222
|
+
n = next;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
newestMember() {
|
|
226
|
+
for (let i = this.children.length - 1; i >= 0; i--) {
|
|
227
|
+
const c = this.children[i];
|
|
228
|
+
if (!(c.descends && c.children.length === 0)) return c;
|
|
229
|
+
}
|
|
230
|
+
return null;
|
|
231
|
+
}
|
|
232
|
+
/** Where a read made through this node starts. */
|
|
233
|
+
start() {
|
|
234
|
+
return this.descends ? this.leaf() : this;
|
|
235
|
+
}
|
|
236
|
+
/** Whether this node is on the path a read from the root takes. */
|
|
237
|
+
isActive() {
|
|
238
|
+
let root = this;
|
|
239
|
+
while (root.parent) root = root.parent;
|
|
240
|
+
for (const n of root.leaf().chain()) if (n === this) return true;
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
/** This node, then each ancestor up to the root. */
|
|
244
|
+
*chain() {
|
|
245
|
+
for (let n = this; n; n = n.parent) yield n;
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
function pushOwner(stack, value) {
|
|
249
|
+
if (value === null) return () => {
|
|
250
|
+
};
|
|
251
|
+
stack.push(value);
|
|
252
|
+
let released = false;
|
|
253
|
+
return () => {
|
|
254
|
+
if (released) return;
|
|
255
|
+
released = true;
|
|
256
|
+
const i = stack.lastIndexOf(value);
|
|
257
|
+
if (i !== -1) stack.splice(i, 1);
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/interactions/actions/depNode.ts
|
|
262
|
+
var DepNode = class extends ScopeNode {
|
|
263
|
+
sources = /* @__PURE__ */ new Map();
|
|
264
|
+
registry = {
|
|
265
|
+
register: (name, source) => {
|
|
266
|
+
const key = name;
|
|
267
|
+
const entry = source;
|
|
268
|
+
const stack = this.sources.get(key);
|
|
269
|
+
if (stack) stack.push(entry);
|
|
270
|
+
else this.sources.set(key, [entry]);
|
|
271
|
+
this.changed();
|
|
272
|
+
let released = false;
|
|
273
|
+
return () => {
|
|
274
|
+
if (released) return;
|
|
275
|
+
released = true;
|
|
276
|
+
const cur = this.sources.get(key);
|
|
277
|
+
if (!cur) return;
|
|
278
|
+
const i = cur.lastIndexOf(entry);
|
|
279
|
+
if (i === -1) return;
|
|
280
|
+
cur.splice(i, 1);
|
|
281
|
+
if (cur.length === 0) this.sources.delete(key);
|
|
282
|
+
this.changed();
|
|
283
|
+
};
|
|
284
|
+
},
|
|
285
|
+
get: (name) => {
|
|
286
|
+
const stacks = [...this.start().chain()].map((n) => n.sources.get(name) ?? []);
|
|
287
|
+
return readFrom(stacks, 0, (stacks[0]?.length ?? 0) - 1);
|
|
288
|
+
},
|
|
289
|
+
subscribe: (listener) => this.tree.listeners.subscribe(listener),
|
|
290
|
+
list: () => {
|
|
291
|
+
const found = /* @__PURE__ */ new Map();
|
|
292
|
+
let depth = 0;
|
|
293
|
+
for (const n of this.start().chain()) {
|
|
294
|
+
for (const [name, stack] of n.sources) {
|
|
295
|
+
if (!found.has(name)) found.set(name, { name, depth, stacked: stack.length });
|
|
296
|
+
}
|
|
297
|
+
depth++;
|
|
298
|
+
}
|
|
299
|
+
return [...found.values()].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
|
|
300
|
+
},
|
|
301
|
+
changed: () => this.changed()
|
|
302
|
+
};
|
|
303
|
+
constructor(parent, descends) {
|
|
304
|
+
super(parent, descends);
|
|
305
|
+
NODES.set(this.registry, this);
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
function readFrom(stacks, scope, index) {
|
|
309
|
+
if (scope >= stacks.length) return void 0;
|
|
310
|
+
if (index < 0) return readFrom(stacks, scope + 1, (stacks[scope + 1]?.length ?? 0) - 1);
|
|
311
|
+
return stacks[scope][index](() => readFrom(stacks, scope, index - 1));
|
|
312
|
+
}
|
|
313
|
+
var NODES = /* @__PURE__ */ new WeakMap();
|
|
314
|
+
function depNodeOf(registry) {
|
|
315
|
+
return registry ? NODES.get(registry) ?? null : null;
|
|
316
|
+
}
|
|
317
|
+
function createDepRegistry() {
|
|
318
|
+
return new DepNode(null, true).registry;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// src/interactions/actions/registry.ts
|
|
322
|
+
function validateActionId(id) {
|
|
323
|
+
if (id.length === 0) {
|
|
324
|
+
throw new Error(`weasel: action id may not be empty`);
|
|
325
|
+
}
|
|
326
|
+
if (RESERVED_ID_PREFIXES.has(id[0])) {
|
|
327
|
+
throw new Error(
|
|
328
|
+
`weasel: action id "${id}" starts with reserved sigil "${id[0]}" (reserved set: ${[...RESERVED_ID_PREFIXES].join(" ")})`
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
if (RESERVED_ID_NAMES.has(id)) {
|
|
332
|
+
throw new Error(
|
|
333
|
+
`weasel: action id "${id}" collides with a reserved phase keyword (reserved: ${[...RESERVED_ID_NAMES].join(", ")})`
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function validateActionDefaultBinding(action) {
|
|
338
|
+
const gs = action.defaultBinding;
|
|
339
|
+
if (!gs) return;
|
|
340
|
+
const entries = Array.isArray(gs) ? gs : [gs];
|
|
341
|
+
for (const entry of entries) {
|
|
342
|
+
const spec = "kind" in entry ? entry : entry.spec;
|
|
343
|
+
const phase = spec.phase;
|
|
344
|
+
if (phase === void 0) continue;
|
|
345
|
+
const atoms = Array.isArray(phase) ? phase : [{ channel: "&", phase }];
|
|
346
|
+
for (const atom of atoms) {
|
|
347
|
+
if (atom.channel === "&") {
|
|
348
|
+
throw new Error(
|
|
349
|
+
`weasel: action "${action.id}" defaultBinding uses '&' channel which has no owning tool to resolve to. Use a named tool channel (e.g. '[rect:engaged]') or '*' instead, or move this binding to the tool's own Tool.bindings.`
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// src/interactions/actions/invoker.ts
|
|
357
|
+
function resolveParams(params) {
|
|
358
|
+
if (params === void 0) return void 0;
|
|
359
|
+
return typeof params === "function" ? params() : params;
|
|
360
|
+
}
|
|
361
|
+
function keysHeld(keys, modifiers, isMac) {
|
|
362
|
+
return keys.some((k) => {
|
|
363
|
+
if (k === "mod") return isMac ? modifiers.meta : modifiers.ctrl;
|
|
364
|
+
return modifiers[k];
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
function roleKeysOf(roles, rebinds) {
|
|
368
|
+
if (!roles) return void 0;
|
|
369
|
+
const out = {};
|
|
370
|
+
for (const id of Object.keys(roles)) out[id] = rebinds?.[id] ?? roles[id].keys;
|
|
371
|
+
return out;
|
|
372
|
+
}
|
|
373
|
+
function platformRoleKeys(keys, isMac) {
|
|
374
|
+
const out = {};
|
|
375
|
+
for (const id of Object.keys(keys)) out[id] = keys[id].map((k) => k === "mod" ? isMac ? "meta" : "ctrl" : k);
|
|
376
|
+
return out;
|
|
377
|
+
}
|
|
378
|
+
function resolveRoles(keys, modifiers, isMac) {
|
|
379
|
+
const out = {};
|
|
380
|
+
for (const id of Object.keys(keys)) out[id] = keysHeld(keys[id], modifiers, isMac);
|
|
381
|
+
return out;
|
|
382
|
+
}
|
|
383
|
+
function rebindRoles(roles, rebinds) {
|
|
384
|
+
const out = {};
|
|
385
|
+
for (const id of Object.keys(roles)) {
|
|
386
|
+
out[id] = rebinds[id] ? { ...roles[id], keys: rebinds[id] } : roles[id];
|
|
387
|
+
}
|
|
388
|
+
return out;
|
|
389
|
+
}
|
|
390
|
+
function roleReader(roles) {
|
|
391
|
+
return (ctx, id) => ctx.roles?.[id] ?? keysHeld(roles[id].keys, ctx.modifiers, isApplePlatform());
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// src/interactions/actions/tuning.ts
|
|
395
|
+
var WRAPS = /* @__PURE__ */ new WeakMap();
|
|
396
|
+
function layered(under, over) {
|
|
397
|
+
if (typeof under !== "function" && typeof over !== "function") return tunedParams([{ params: under }], over);
|
|
398
|
+
return () => tunedParams([{ params: under }], resolveParams(over));
|
|
399
|
+
}
|
|
400
|
+
function wrapInvoker(invoker, params) {
|
|
401
|
+
for (let w = invoker; w; w = WRAPS.get(w)?.inner) {
|
|
402
|
+
if (WRAPS.get(w)?.params === params) return invoker;
|
|
403
|
+
}
|
|
404
|
+
const base = (given) => tunedParams([{ params }], given);
|
|
405
|
+
const wrapped = invoker.timing === "immediate" ? { timing: "immediate", run: (deps, given) => invoker.run(deps, base(given)) } : {
|
|
406
|
+
...invoker,
|
|
407
|
+
start: (ctx, opts) => invoker.start(
|
|
408
|
+
ctx.params ? { ...ctx, params: base(ctx.params) } : ctx,
|
|
409
|
+
{ ...opts, params: layered(params, opts?.params) }
|
|
410
|
+
)
|
|
411
|
+
};
|
|
412
|
+
WRAPS.set(wrapped, { inner: invoker, params });
|
|
413
|
+
return wrapped;
|
|
414
|
+
}
|
|
415
|
+
function withBindings(own, added) {
|
|
416
|
+
const base = own === void 0 ? [] : Array.isArray(own) ? own : [own];
|
|
417
|
+
return [...base, ...added.filter((b) => !base.includes(b))];
|
|
418
|
+
}
|
|
419
|
+
var CURSOR_WRAPS = /* @__PURE__ */ new WeakMap();
|
|
420
|
+
function wrapCursor(cursor, params) {
|
|
421
|
+
if (typeof cursor !== "function") return cursor;
|
|
422
|
+
for (let w = cursor; typeof w === "function"; w = CURSOR_WRAPS.get(w)?.inner) {
|
|
423
|
+
if (CURSOR_WRAPS.get(w)?.params === params) return cursor;
|
|
424
|
+
}
|
|
425
|
+
const wrapped = (given) => cursor(tunedParams([{ params }], given));
|
|
426
|
+
CURSOR_WRAPS.set(wrapped, { inner: cursor, params });
|
|
427
|
+
return wrapped;
|
|
428
|
+
}
|
|
429
|
+
var PREVIEW_WRAPS = /* @__PURE__ */ new WeakMap();
|
|
430
|
+
function wrapPreview(preview, params) {
|
|
431
|
+
if (!preview) return preview;
|
|
432
|
+
for (let w = preview; w; w = PREVIEW_WRAPS.get(w)?.inner) {
|
|
433
|
+
if (PREVIEW_WRAPS.get(w)?.params === params) return preview;
|
|
434
|
+
}
|
|
435
|
+
const wrapped = (deps, given) => preview(deps, tunedParams([{ params }], given));
|
|
436
|
+
PREVIEW_WRAPS.set(wrapped, { inner: preview, params });
|
|
437
|
+
return wrapped;
|
|
438
|
+
}
|
|
439
|
+
function tuneOnce(action, tuning) {
|
|
440
|
+
const invoker = tuning.params && action.invoker ? wrapInvoker(action.invoker, tuning.params) : action.invoker;
|
|
441
|
+
const cursor = tuning.params ? wrapCursor(action.cursor, tuning.params) : action.cursor;
|
|
442
|
+
const preview = tuning.params ? wrapPreview(action.preview, tuning.params) : action.preview;
|
|
443
|
+
const bindings = tuning.bindings?.length ? withBindings(action.defaultBinding, tuning.bindings) : null;
|
|
444
|
+
const modifierRoles = tuning.roles && action.modifierRoles ? rebindRoles(action.modifierRoles, tuning.roles) : null;
|
|
445
|
+
if (invoker === action.invoker && cursor === action.cursor && preview === action.preview && bindings === null && modifierRoles === null) {
|
|
446
|
+
return action;
|
|
447
|
+
}
|
|
448
|
+
return {
|
|
449
|
+
...action,
|
|
450
|
+
...modifierRoles ? { modifierRoles } : {},
|
|
451
|
+
...invoker ? { invoker } : {},
|
|
452
|
+
...cursor ? { cursor } : {},
|
|
453
|
+
...preview ? { preview } : {},
|
|
454
|
+
...bindings ? { defaultBinding: bindings } : {}
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
function tunedParams(tunings, given) {
|
|
458
|
+
let out = {};
|
|
459
|
+
for (const t of tunings) out = { ...out, ...resolveParams(t.params) };
|
|
460
|
+
return { ...out, ...given };
|
|
461
|
+
}
|
|
462
|
+
function tuneAction(action, tunings) {
|
|
463
|
+
let out = action;
|
|
464
|
+
for (const t of tunings) out = tuneOnce(out, t);
|
|
465
|
+
return out;
|
|
466
|
+
}
|
|
467
|
+
var NO_OVERRIDES = /* @__PURE__ */ new Map();
|
|
468
|
+
function bindingId(b, ownerId, index) {
|
|
469
|
+
return b.opts?.id ?? (ownerId === null ? `${b.actionId}@${index}` : `${ownerId}#${index}`);
|
|
470
|
+
}
|
|
471
|
+
function resolveBindingOverrides(maps) {
|
|
472
|
+
if (maps.length === 0) return NO_OVERRIDES;
|
|
473
|
+
const out = /* @__PURE__ */ new Map();
|
|
474
|
+
for (const map of maps) {
|
|
475
|
+
for (const [id, input] of Object.entries(map)) {
|
|
476
|
+
try {
|
|
477
|
+
out.set(id, bindingSpec(input));
|
|
478
|
+
} catch (err) {
|
|
479
|
+
console.warn(`weasel: binding override "${id}" is not a route: ${err.message}`);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
return out;
|
|
484
|
+
}
|
|
485
|
+
function overridden(b, id, overrides) {
|
|
486
|
+
if (!overrides.has(id)) return b;
|
|
487
|
+
const spec = overrides.get(id);
|
|
488
|
+
return spec === null ? null : { ...b, spec };
|
|
489
|
+
}
|
|
490
|
+
function overriddenBindings(bindings, ownerId, overrides = NO_OVERRIDES) {
|
|
491
|
+
const out = [];
|
|
492
|
+
bindings.forEach((b, i) => {
|
|
493
|
+
const next = overridden(b, bindingId(b, ownerId, i), overrides);
|
|
494
|
+
if (next) out.push(next);
|
|
495
|
+
});
|
|
496
|
+
return out;
|
|
497
|
+
}
|
|
498
|
+
function overrideActionBindings(action, overrides) {
|
|
499
|
+
if (overrides.size === 0 || action.defaultBinding === void 0) return action;
|
|
500
|
+
const declared = actionBindings(action);
|
|
501
|
+
if (!declared.some((b, i) => overrides.has(bindingId(b, null, i)))) return action;
|
|
502
|
+
const kept = [];
|
|
503
|
+
declared.forEach((b, i) => {
|
|
504
|
+
const id = bindingId(b, null, i);
|
|
505
|
+
const next = overridden(b, id, overrides);
|
|
506
|
+
if (next) kept.push({ spec: next.spec, opts: { ...next.opts, id } });
|
|
507
|
+
});
|
|
508
|
+
return { ...action, defaultBinding: kept };
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
// src/menus/pickMenu.ts
|
|
512
|
+
var oneOf = (want, got) => want === void 0 || (Array.isArray(want) ? want.includes(got) : want === got);
|
|
513
|
+
function matches(when, hit) {
|
|
514
|
+
return oneOf(when.bodyTarget, hit.bodyTarget) && oneOf(when.bodyKind, hit.bodyKind);
|
|
515
|
+
}
|
|
516
|
+
function mergeMenuDefs(defs, activeTool) {
|
|
517
|
+
const live = [
|
|
518
|
+
...defs.filter((d) => d.owner === null),
|
|
519
|
+
...defs.filter((d) => d.owner !== null && d.owner === activeTool)
|
|
520
|
+
];
|
|
521
|
+
const byId = /* @__PURE__ */ new Map();
|
|
522
|
+
for (const { def } of live) {
|
|
523
|
+
const prev = byId.get(def.id);
|
|
524
|
+
if (!prev) {
|
|
525
|
+
byId.set(def.id, { def: { ...def, menu: { ...def.menu, children: { ...def.menu.children } } }, parts: 1 });
|
|
526
|
+
continue;
|
|
527
|
+
}
|
|
528
|
+
const n = prev.parts++;
|
|
529
|
+
const children = prev.def.menu.children;
|
|
530
|
+
children[`~sep${n}`] = { kind: "separator", name: "", description: "", default: void 0 };
|
|
531
|
+
for (const [k, node] of Object.entries(def.menu.children)) {
|
|
532
|
+
children[Object.hasOwn(children, k) ? `${k}~${n}` : k] = node;
|
|
533
|
+
}
|
|
534
|
+
if (!prev.def.when && def.when) prev.def.when = def.when;
|
|
535
|
+
}
|
|
536
|
+
return [...byId.values()].map((m) => m.def);
|
|
537
|
+
}
|
|
538
|
+
function pickMenu(defs, hit) {
|
|
539
|
+
return defs.find((d) => d.when !== void 0 && matches(d.when, hit));
|
|
540
|
+
}
|
|
541
|
+
var noop = () => {
|
|
542
|
+
};
|
|
543
|
+
var sep = (key) => ({ kind: "separator", key });
|
|
544
|
+
var isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
545
|
+
function pathSegment(key) {
|
|
546
|
+
return key.replace(/[\\.]/g, "\\$&");
|
|
547
|
+
}
|
|
548
|
+
function pathSegments(path) {
|
|
549
|
+
const out = [];
|
|
550
|
+
let seg = "";
|
|
551
|
+
for (let i = 0; i < path.length; i++) {
|
|
552
|
+
const ch = path[i];
|
|
553
|
+
if (ch === "\\" && i + 1 < path.length) seg += path[++i];
|
|
554
|
+
else if (ch === ".") {
|
|
555
|
+
out.push(seg);
|
|
556
|
+
seg = "";
|
|
557
|
+
} else seg += ch;
|
|
558
|
+
}
|
|
559
|
+
out.push(seg);
|
|
560
|
+
return out;
|
|
561
|
+
}
|
|
562
|
+
function setIn(container, segs, value) {
|
|
563
|
+
const [head, ...rest] = segs;
|
|
564
|
+
if (head === void 0) return value;
|
|
565
|
+
if (Array.isArray(container)) {
|
|
566
|
+
const out = [...container];
|
|
567
|
+
out[Number(head)] = setIn(container[Number(head)], rest, value);
|
|
568
|
+
return out;
|
|
569
|
+
}
|
|
570
|
+
const base = isRecord(container) ? container : {};
|
|
571
|
+
const next = setIn(base[head], rest, value);
|
|
572
|
+
if (next !== void 0) return { ...base, [head]: next };
|
|
573
|
+
const { [head]: _dropped, ...kept } = base;
|
|
574
|
+
return kept;
|
|
575
|
+
}
|
|
576
|
+
function below(outer, path, held, writable) {
|
|
577
|
+
const inside = (p) => p.startsWith(`${path}.`);
|
|
578
|
+
const rel = (p) => p.slice(path.length + 1);
|
|
579
|
+
const values = (p) => {
|
|
580
|
+
if (!inside(p)) return outer.values(p);
|
|
581
|
+
let cur = held();
|
|
582
|
+
for (const seg of pathSegments(rel(p))) cur = cur !== null && typeof cur === "object" ? cur[seg] : void 0;
|
|
583
|
+
return cur;
|
|
584
|
+
};
|
|
585
|
+
const { onChange } = outer;
|
|
586
|
+
if (!onChange || false) return { values };
|
|
587
|
+
return {
|
|
588
|
+
values,
|
|
589
|
+
onChange: (p, v) => inside(p) ? onChange(path, setIn(held(), pathSegments(rel(p)), v)) : onChange(p, v)
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function submenu(key, leaf, entries) {
|
|
593
|
+
if (entries.length === 0) return [];
|
|
594
|
+
return [{
|
|
595
|
+
kind: "submenu",
|
|
596
|
+
key,
|
|
597
|
+
label: leaf.name,
|
|
598
|
+
entries,
|
|
599
|
+
...leaf.icon ? { icon: leaf.icon } : {},
|
|
600
|
+
...leaf.access ? { access: leaf.access } : {}
|
|
601
|
+
}];
|
|
602
|
+
}
|
|
603
|
+
function objectRows(draw, leaf, key, path, off) {
|
|
604
|
+
const held = () => prefObjectBase(leaf, draw.access.values(path));
|
|
605
|
+
const access = below(draw.access, path, held);
|
|
606
|
+
return submenu(key, leaf, draw.fields(leaf.children, path, access, off || leaf.readout === true));
|
|
607
|
+
}
|
|
608
|
+
function removeRows(key, name, labels, off, drop) {
|
|
609
|
+
return {
|
|
610
|
+
kind: "submenu",
|
|
611
|
+
key,
|
|
612
|
+
label: `Remove ${name}`,
|
|
613
|
+
...off ? { disabled: true } : {},
|
|
614
|
+
entries: labels.map((label, i) => ({ kind: "item", key: `${key}/${i}`, label, disabled: off, run: () => drop(i) }))
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
function listRows(draw, leaf, key, path, off) {
|
|
618
|
+
const held = () => {
|
|
619
|
+
const v = draw.access.values(path);
|
|
620
|
+
return Array.isArray(v) ? v : Array.isArray(leaf.default) ? leaf.default : [];
|
|
621
|
+
};
|
|
622
|
+
const frozen = off || leaf.readout === true;
|
|
623
|
+
const access = below(draw.access, path, held);
|
|
624
|
+
const name = leaf.item.name || leaf.name;
|
|
625
|
+
const items = held();
|
|
626
|
+
const rows = items.flatMap((_, i) => draw.leaf({ ...leaf.item, name: `${name} ${i + 1}` }, `${path}.${i}`, access, frozen, `${path}.${i}`));
|
|
627
|
+
const { onChange } = draw.access;
|
|
628
|
+
if (onChange && !leaf.readout) {
|
|
629
|
+
const n = items.length;
|
|
630
|
+
rows.push(sep(`${path}/\xB1`), {
|
|
631
|
+
kind: "item",
|
|
632
|
+
key: `${path}/+`,
|
|
633
|
+
label: `Add ${name}`,
|
|
634
|
+
disabled: frozen || leaf.maxItems !== void 0 && n >= leaf.maxItems,
|
|
635
|
+
run: () => onChange(path, [...held(), leaf.item.default])
|
|
636
|
+
});
|
|
637
|
+
if (n > 0) {
|
|
638
|
+
rows.push(removeRows(
|
|
639
|
+
`${path}/-`,
|
|
640
|
+
name,
|
|
641
|
+
items.map((_, i) => `${name} ${i + 1}`),
|
|
642
|
+
frozen || n <= (leaf.minItems ?? 0),
|
|
643
|
+
(i) => onChange(path, held().filter((_, j) => j !== i))
|
|
644
|
+
));
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return submenu(key, leaf, rows);
|
|
648
|
+
}
|
|
649
|
+
function mapRows(draw, leaf, key, path, off) {
|
|
650
|
+
const held = () => {
|
|
651
|
+
const v = draw.access.values(path);
|
|
652
|
+
return isRecord(v) ? v : isRecord(leaf.default) ? leaf.default : {};
|
|
653
|
+
};
|
|
654
|
+
const frozen = off || leaf.readout === true;
|
|
655
|
+
const access = below(draw.access, path, held);
|
|
656
|
+
const keys = Object.keys(held());
|
|
657
|
+
const rows = keys.flatMap((k) => {
|
|
658
|
+
const at = `${path}.${pathSegment(k)}`;
|
|
659
|
+
return draw.leaf({ ...leaf.item, name: k }, at, access, frozen, at);
|
|
660
|
+
});
|
|
661
|
+
const { onChange } = draw.access;
|
|
662
|
+
if (onChange && !leaf.readout) {
|
|
663
|
+
const name = leaf.item.name || leaf.name;
|
|
664
|
+
rows.push(sep(`${path}/\xB1`), {
|
|
665
|
+
kind: "text",
|
|
666
|
+
key: `${path}/+`,
|
|
667
|
+
label: `Add ${name}`,
|
|
668
|
+
value: void 0,
|
|
669
|
+
...frozen ? { disabled: true } : {},
|
|
670
|
+
set: (k) => {
|
|
671
|
+
const now = held();
|
|
672
|
+
if (k !== "" && !Object.hasOwn(now, k)) onChange(path, { ...now, [k]: leaf.item.default });
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
if (keys.length > 0) {
|
|
676
|
+
rows.push(removeRows(`${path}/-`, name, keys, frozen, (i) => {
|
|
677
|
+
const { [keys[i]]: _dropped, ...kept } = held();
|
|
678
|
+
onChange(path, kept);
|
|
679
|
+
}));
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
return submenu(key, leaf, rows);
|
|
683
|
+
}
|
|
684
|
+
function unionRows(draw, leaf, key, path, off) {
|
|
685
|
+
const frozen = off || leaf.readout === true;
|
|
686
|
+
const current = prefVariantOf(leaf, draw.access.values(path)) ?? prefVariantOf(leaf, leaf.default);
|
|
687
|
+
const { onChange } = draw.access;
|
|
688
|
+
const choices = Object.entries(leaf.variants).map(([v2, variant2]) => ({
|
|
689
|
+
kind: "item",
|
|
690
|
+
key: `${path}/${v2}`,
|
|
691
|
+
label: variant2.name,
|
|
692
|
+
checked: v2 === current?.[0],
|
|
693
|
+
disabled: frozen || !onChange,
|
|
694
|
+
run: onChange ? () => onChange(path, prefVariantDefault(leaf, v2)) : noop
|
|
695
|
+
}));
|
|
696
|
+
if (!current) return submenu(key, leaf, choices);
|
|
697
|
+
const [v, variant] = current;
|
|
698
|
+
const held = () => {
|
|
699
|
+
const now = draw.access.values(path);
|
|
700
|
+
return prefVariantOf(leaf, now)?.[0] === v ? now : prefVariantDefault(leaf, v);
|
|
701
|
+
};
|
|
702
|
+
const fields = draw.fields(variant.children, path, below(draw.access, path, held), frozen);
|
|
703
|
+
return submenu(key, leaf, fields.length ? [...choices, sep(`${path}/|`), ...fields] : choices);
|
|
704
|
+
}
|
|
705
|
+
function fieldRows(draw, leaf, key, path, off) {
|
|
706
|
+
const held = draw.access.values(path);
|
|
707
|
+
const { onChange } = draw.access;
|
|
708
|
+
const rows = [];
|
|
709
|
+
for (const [p, l] of prefLeaves(draw.root)) {
|
|
710
|
+
if (p === path || !prefHoldsValue(l) || leaf.kinds && !leaf.kinds.includes(l.kind)) continue;
|
|
711
|
+
rows.push({
|
|
712
|
+
kind: "item",
|
|
713
|
+
key: `${path}/${p}`,
|
|
714
|
+
label: l.name || p,
|
|
715
|
+
checked: held === p,
|
|
716
|
+
disabled: off || leaf.readout === true || !onChange,
|
|
717
|
+
run: onChange ? () => onChange(path, p) : noop
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
return submenu(key, leaf, rows);
|
|
721
|
+
}
|
|
722
|
+
function paintRows(draw, leaf, key, path, off) {
|
|
723
|
+
const held = draw.access.values(path);
|
|
724
|
+
const access = leaf.access ? { access: leaf.access } : {};
|
|
725
|
+
const solid = isRecord(held) && (held.kind === void 0 || held.kind === "solid") && typeof held.color === "string";
|
|
726
|
+
if (held != null && !solid) {
|
|
727
|
+
const kind = isRecord(held) && typeof held.kind === "string" ? held.kind : "\u2014";
|
|
728
|
+
return [{ kind: "item", key, label: `${leaf.name}: ${kind}`, ...access, disabled: true, run: noop }];
|
|
729
|
+
}
|
|
730
|
+
const { onChange } = draw.access;
|
|
731
|
+
return [{
|
|
732
|
+
kind: "color",
|
|
733
|
+
key,
|
|
734
|
+
label: leaf.name,
|
|
735
|
+
...access,
|
|
736
|
+
...off ? { disabled: true } : {},
|
|
737
|
+
value: solid ? held.color : void 0,
|
|
738
|
+
...leaf.alpha ? { alpha: true } : {},
|
|
739
|
+
...leaf.readout || !onChange ? {} : {
|
|
740
|
+
set: (color) => onChange(path, { ...isRecord(held) ? held : {}, color })
|
|
741
|
+
}
|
|
742
|
+
}];
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// src/menus/resolveMenu.ts
|
|
746
|
+
var SUBMENU_AS = /* @__PURE__ */ new Set(["menu", "page", "tab", "panel"]);
|
|
747
|
+
var noop2 = () => {
|
|
748
|
+
};
|
|
749
|
+
var withAccess = (access) => access ? { access } : {};
|
|
750
|
+
var sep2 = (key) => ({ kind: "separator", key });
|
|
751
|
+
function itemOf(ctx, it, key, label, off, access) {
|
|
752
|
+
const deps = ctx.src.depsFor(it.action);
|
|
753
|
+
const point = ctx.at ? { world: ctx.at } : {};
|
|
754
|
+
if (it.action.settings) return settingsOf(ctx, it, key, label, off || !evaluateEnabled(it.action, deps, ctx.at).enabled, access);
|
|
755
|
+
return {
|
|
756
|
+
kind: "item",
|
|
757
|
+
key,
|
|
758
|
+
label,
|
|
759
|
+
...withAccess(access),
|
|
760
|
+
icon: it.icon,
|
|
761
|
+
shortcut: actionShortcuts(it.action, it.params)[0],
|
|
762
|
+
disabled: off || !evaluateEnabled(it.action, deps, ctx.at).enabled,
|
|
763
|
+
checked: evaluateActive(it.action, deps),
|
|
764
|
+
run: () => {
|
|
765
|
+
ctx.src.trigger(it.action.id, { ...ctx.params, ...it.params, ...point });
|
|
766
|
+
}
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
function settingsOf(ctx, it, key, label, off, access) {
|
|
770
|
+
const settings = it.action.settings;
|
|
771
|
+
const state = off ? null : readActionSettings(it.action, ctx.src.depsFor(it.action));
|
|
772
|
+
const mixed = new Set(state?.mixed ?? []);
|
|
773
|
+
const values = (path) => mixed.has(path) ? void 0 : prefValueAtPath(state?.values, path);
|
|
774
|
+
const onChange = (path, value) => {
|
|
775
|
+
const params = actionSettingsParams(settings, path, value);
|
|
776
|
+
if (params) ctx.src.trigger(it.action.id, { ...ctx.params, ...it.params, ...params });
|
|
777
|
+
};
|
|
778
|
+
const form = {
|
|
779
|
+
...ctx,
|
|
780
|
+
src: { ...ctx.src, values, onChange, over: settings.schema },
|
|
781
|
+
root: settings.schema,
|
|
782
|
+
read: (path, from) => from === "item" ? void 0 : values(path)
|
|
783
|
+
};
|
|
784
|
+
const entries = state ? tidy(groupRows(form, settings.schema.children, "", ROOT)) : [];
|
|
785
|
+
return {
|
|
786
|
+
kind: "submenu",
|
|
787
|
+
key,
|
|
788
|
+
label,
|
|
789
|
+
...it.icon ? { icon: it.icon } : {},
|
|
790
|
+
...withAccess(access),
|
|
791
|
+
...entries.length === 0 ? { disabled: true } : {},
|
|
792
|
+
entries
|
|
793
|
+
};
|
|
794
|
+
}
|
|
795
|
+
function itemsNamed(ctx, name) {
|
|
796
|
+
const action = ctx.byId.get(name);
|
|
797
|
+
if (action) return actionItems(action);
|
|
798
|
+
const colon = name.indexOf(":");
|
|
799
|
+
const owner = colon > 0 ? ctx.byId.get(name.slice(0, colon)) : void 0;
|
|
800
|
+
return owner ? actionItems(owner).filter((it) => it.key === name) : [];
|
|
801
|
+
}
|
|
802
|
+
function actionRows(ctx, leaf, key, off) {
|
|
803
|
+
const own = leaf.label || leaf.name;
|
|
804
|
+
if (leaf.action === void 0) {
|
|
805
|
+
if (!own) return [];
|
|
806
|
+
if (isDev()) console.warn(`weasel: menu row "${key}" names no registered action, so it does nothing`);
|
|
807
|
+
return [{ kind: "item", key, label: own, disabled: true, run: noop2 }];
|
|
808
|
+
}
|
|
809
|
+
const items = itemsNamed(ctx, leaf.action);
|
|
810
|
+
if (items.length === 1) return [itemOf(ctx, items[0], key, own || items[0].label, off, leaf.access)];
|
|
811
|
+
return items.map((it) => itemOf(ctx, it, `${key}/${it.key}`, it.label, off));
|
|
812
|
+
}
|
|
813
|
+
function siblingsOf(ctx, path) {
|
|
814
|
+
const dot = path.lastIndexOf(".");
|
|
815
|
+
const parent = dot < 0 ? void 0 : ctx.src.values(path.slice(0, dot));
|
|
816
|
+
return parent && typeof parent === "object" ? parent : void 0;
|
|
817
|
+
}
|
|
818
|
+
function booleanRows(ctx, leaf, key, path, off) {
|
|
819
|
+
const { values, onChange } = ctx.src;
|
|
820
|
+
if (!values || !onChange) return [];
|
|
821
|
+
const stored = values(path);
|
|
822
|
+
const on = leaf.encoding ? leaf.encoding.read(stored, siblingsOf(ctx, path)) : stored === true;
|
|
823
|
+
const write = (v) => leaf.encoding ? leaf.encoding.write(v, siblingsOf(ctx, path)) : v;
|
|
824
|
+
return [{
|
|
825
|
+
kind: "item",
|
|
826
|
+
key,
|
|
827
|
+
label: leaf.name,
|
|
828
|
+
...withAccess(leaf.access),
|
|
829
|
+
checked: on,
|
|
830
|
+
disabled: off || leaf.readout === true,
|
|
831
|
+
run: leaf.readout ? noop2 : () => onChange(path, write(!on))
|
|
832
|
+
}];
|
|
833
|
+
}
|
|
834
|
+
function enumRows(ctx, leaf, key, path, off) {
|
|
835
|
+
const { values, onChange } = ctx.src;
|
|
836
|
+
if (!values || !onChange) return [];
|
|
837
|
+
const stored = values(path);
|
|
838
|
+
const current = leaf.encoding ? leaf.encoding.read(stored, siblingsOf(ctx, path)) : stored;
|
|
839
|
+
if (leaf.readout) {
|
|
840
|
+
const shown = leaf.options.find((o) => o.value === current)?.label ?? "\u2014";
|
|
841
|
+
return [{ kind: "item", key, label: `${leaf.name}: ${shown}`, disabled: true, run: noop2 }];
|
|
842
|
+
}
|
|
843
|
+
const write = (v) => leaf.encoding ? leaf.encoding.write(v, siblingsOf(ctx, path)) : v;
|
|
844
|
+
return [
|
|
845
|
+
sep2(`${key}/<`),
|
|
846
|
+
...leaf.options.map((o) => ({
|
|
847
|
+
kind: "item",
|
|
848
|
+
key: `${key}/${o.value}`,
|
|
849
|
+
label: o.label,
|
|
850
|
+
checked: o.value === current,
|
|
851
|
+
disabled: off || o.disabled === true,
|
|
852
|
+
run: () => onChange(path, write(o.value))
|
|
853
|
+
})),
|
|
854
|
+
sep2(`${key}/>`)
|
|
855
|
+
];
|
|
856
|
+
}
|
|
857
|
+
function numberRows(ctx, given, key, path, off) {
|
|
858
|
+
const { values, onChange } = ctx.src;
|
|
859
|
+
if (!values || !onChange) return [];
|
|
860
|
+
const leaf = prefNumberAt(given, values);
|
|
861
|
+
const unit = leaf.unit && resolvePrefUnit(leaf.unit);
|
|
862
|
+
const stored = values(path);
|
|
863
|
+
const { min, max, step, endless } = prefDisplayBounds(leaf);
|
|
864
|
+
const disabled = off || leaf.readout === true;
|
|
865
|
+
return [{
|
|
866
|
+
kind: "number",
|
|
867
|
+
key,
|
|
868
|
+
label: leaf.name,
|
|
869
|
+
...withAccess(leaf.access),
|
|
870
|
+
...leaf.icon ? { icon: leaf.icon } : {},
|
|
871
|
+
...disabled ? { disabled } : {},
|
|
872
|
+
value: typeof stored === "number" && (Number.isFinite(stored) || endlessAllows(leaf.endless, stored)) ? unit ? unit.toDisplay(stored) : stored : void 0,
|
|
873
|
+
...min === void 0 ? {} : { min },
|
|
874
|
+
...max === void 0 ? {} : { max },
|
|
875
|
+
step,
|
|
876
|
+
...unit?.suffix === void 0 ? {} : { suffix: unit.suffix },
|
|
877
|
+
...leaf.display ? { display: leaf.display } : {},
|
|
878
|
+
...endless ? { endless } : {},
|
|
879
|
+
...leaf.infinity === void 0 ? {} : { infinity: leaf.infinity },
|
|
880
|
+
...unit ? { accepts: prefUnitAccepts(unit) } : {},
|
|
881
|
+
set: (shown) => onChange(path, unit ? unit.fromDisplay(shown) : shown)
|
|
882
|
+
}];
|
|
883
|
+
}
|
|
884
|
+
var textAt = (ctx, path) => {
|
|
885
|
+
const v = ctx.src.values?.(path);
|
|
886
|
+
return typeof v === "string" && v !== "" ? v : void 0;
|
|
887
|
+
};
|
|
888
|
+
function colorRows(ctx, leaf, key, path, off) {
|
|
889
|
+
const { values, onChange } = ctx.src;
|
|
890
|
+
if (!values) return [];
|
|
891
|
+
return [{
|
|
892
|
+
kind: "color",
|
|
893
|
+
key,
|
|
894
|
+
label: leaf.name,
|
|
895
|
+
...withAccess(leaf.access),
|
|
896
|
+
...off ? { disabled: true } : {},
|
|
897
|
+
value: textAt(ctx, path),
|
|
898
|
+
...leaf.alpha ? { alpha: true } : {},
|
|
899
|
+
...leaf.readout || !onChange ? {} : { set: (color) => onChange(path, color) }
|
|
900
|
+
}];
|
|
901
|
+
}
|
|
902
|
+
function stringRows(ctx, leaf, key, path, off) {
|
|
903
|
+
const { values, onChange } = ctx.src;
|
|
904
|
+
if (!values) return [];
|
|
905
|
+
if (leaf.readout || !onChange) {
|
|
906
|
+
return [{ kind: "item", key, label: `${leaf.name}: ${textAt(ctx, path) ?? "\u2014"}`, ...withAccess(leaf.access), disabled: true, run: noop2 }];
|
|
907
|
+
}
|
|
908
|
+
const stored = values(path);
|
|
909
|
+
return [{
|
|
910
|
+
kind: "text",
|
|
911
|
+
key,
|
|
912
|
+
label: leaf.name,
|
|
913
|
+
...withAccess(leaf.access),
|
|
914
|
+
...leaf.icon ? { icon: leaf.icon } : {},
|
|
915
|
+
...off ? { disabled: true } : {},
|
|
916
|
+
value: typeof stored === "string" ? stored : void 0,
|
|
917
|
+
set: (text) => onChange(path, text)
|
|
918
|
+
}];
|
|
919
|
+
}
|
|
920
|
+
function leafRows(ctx, leaf, key, path, off) {
|
|
921
|
+
switch (leaf.kind) {
|
|
922
|
+
case "action":
|
|
923
|
+
return actionRows(ctx, leaf, key, off);
|
|
924
|
+
case "actions": {
|
|
925
|
+
const group = leaf.group;
|
|
926
|
+
return ctx.src.actions().filter((a) => a.group === group).flatMap((a) => actionItems(a).map((it) => itemOf(ctx, it, `${key}/${it.key}`, it.label, off)));
|
|
927
|
+
}
|
|
928
|
+
case "boolean":
|
|
929
|
+
return booleanRows(ctx, leaf, key, path, off);
|
|
930
|
+
case "enum":
|
|
931
|
+
return enumRows(ctx, leaf, key, path, off);
|
|
932
|
+
case "number":
|
|
933
|
+
return numberRows(ctx, leaf, key, path, off);
|
|
934
|
+
case "color":
|
|
935
|
+
return colorRows(ctx, leaf, key, path, off);
|
|
936
|
+
case "string":
|
|
937
|
+
return stringRows(ctx, leaf, key, path, off);
|
|
938
|
+
case "object":
|
|
939
|
+
return objectRows(drawIn(ctx), leaf, key, path, off);
|
|
940
|
+
case "list":
|
|
941
|
+
return listRows(drawIn(ctx), leaf, key, path, off);
|
|
942
|
+
case "map":
|
|
943
|
+
return mapRows(drawIn(ctx), leaf, key, path, off);
|
|
944
|
+
case "union":
|
|
945
|
+
return unionRows(drawIn(ctx), leaf, key, path, off);
|
|
946
|
+
case "field":
|
|
947
|
+
return fieldRows(drawIn(ctx), leaf, key, path, off);
|
|
948
|
+
case "paint":
|
|
949
|
+
return paintRows(drawIn(ctx), leaf, key, path, off);
|
|
950
|
+
case "separator":
|
|
951
|
+
return [sep2(key)];
|
|
952
|
+
case "label":
|
|
953
|
+
return [{ kind: "item", key, label: leaf.name, ...withAccess(leaf.access), disabled: true, run: noop2 }];
|
|
954
|
+
case "alias": {
|
|
955
|
+
const alias = leaf;
|
|
956
|
+
const target = prefAliasTarget(alias, (p) => prefLeafAt(ctx.src.over ?? ctx.root, p));
|
|
957
|
+
return target ? leafRows(ctx, prefAliasedLeaf(alias, target.leaf), key, target.path, off) : [];
|
|
958
|
+
}
|
|
959
|
+
default: {
|
|
960
|
+
const { values, onChange, customRows } = ctx.src;
|
|
961
|
+
if (isBuiltinPref(leaf) || !customRows || !values) return [];
|
|
962
|
+
return [...customRows(leaf, { key, path, disabled: off, values, ...onChange ? { onChange } : {} })];
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
function through(ctx, access, entry = ctx.entry) {
|
|
967
|
+
const { values, onChange } = access;
|
|
968
|
+
const { onChange: _outer, ...src } = ctx.src;
|
|
969
|
+
return {
|
|
970
|
+
...ctx,
|
|
971
|
+
src: { ...src, values, ...onChange ? { onChange } : {} },
|
|
972
|
+
entry,
|
|
973
|
+
read: (p, from) => from === "item" ? entry === void 0 ? void 0 : values(`${entry}.${p}`) : values(p)
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
function drawIn(ctx) {
|
|
977
|
+
const { values, onChange } = ctx.src;
|
|
978
|
+
return {
|
|
979
|
+
root: ctx.src.over ?? ctx.root,
|
|
980
|
+
access: { values, ...onChange ? { onChange } : {} },
|
|
981
|
+
leaf: (leaf, key, access, off, entry) => leafRows(through(ctx, access, entry), leaf, key, key, off),
|
|
982
|
+
fields: (fields, prefix, access, off) => {
|
|
983
|
+
const scope = { ...PREF_SCOPE, enabled: !off };
|
|
984
|
+
const inner = through(ctx, access);
|
|
985
|
+
return tidy(groupRows(inner, fields, prefix, { node: {}, scope, above: scope, ...inner.entry ? { entry: inner.entry } : {} }));
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
var ROOT = { node: {}, scope: PREF_SCOPE, above: PREF_SCOPE };
|
|
990
|
+
function groupRows(ctx, children, prefix, owner) {
|
|
991
|
+
const { read } = ctx;
|
|
992
|
+
const holds = (condition) => read === void 0 || prefConditionHolds(condition, read);
|
|
993
|
+
const out = [];
|
|
994
|
+
for (const [k, node] of Object.entries(children)) {
|
|
995
|
+
const key = prefix ? `${prefix}.${k}` : k;
|
|
996
|
+
const path = !isPrefLeaf(node) && isPrefSection(node) ? prefix : key;
|
|
997
|
+
const sits = isPrefLeaf(node) ? prefScopeIn(owner, path, holds) : owner.scope;
|
|
998
|
+
const scope = prefScopeUnder(sits, node, holds);
|
|
999
|
+
if (!scope.shown) continue;
|
|
1000
|
+
const disabled = !scope.enabled;
|
|
1001
|
+
const inner = { node, scope, above: sits, ...owner.entry ? { entry: owner.entry } : {} };
|
|
1002
|
+
if (isPrefLeaf(node)) {
|
|
1003
|
+
out.push(...leafRows(ctx, node, path, path, disabled));
|
|
1004
|
+
continue;
|
|
1005
|
+
}
|
|
1006
|
+
const kids = isPrefSection(node) ? node.members : node.children;
|
|
1007
|
+
if (SUBMENU_AS.has(node.as)) {
|
|
1008
|
+
const entries = tidy(groupRows(ctx, kids, path, inner));
|
|
1009
|
+
if (entries.length) {
|
|
1010
|
+
out.push({ kind: "submenu", key, label: node.name, ...node.icon ? { icon: node.icon } : {}, ...withAccess(node.access), entries });
|
|
1011
|
+
}
|
|
1012
|
+
} else {
|
|
1013
|
+
out.push(sep2(`${key}/<`), ...groupRows(ctx, kids, path, inner), sep2(`${key}/>`));
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return out;
|
|
1017
|
+
}
|
|
1018
|
+
function tidy(entries) {
|
|
1019
|
+
const out = [];
|
|
1020
|
+
for (const e of entries) {
|
|
1021
|
+
if (e.kind === "separator" && (!out.length || out[out.length - 1].kind === "separator")) continue;
|
|
1022
|
+
out.push(e);
|
|
1023
|
+
}
|
|
1024
|
+
if (out[out.length - 1]?.kind === "separator") out.pop();
|
|
1025
|
+
return out;
|
|
1026
|
+
}
|
|
1027
|
+
function resolveSchemaMenu(schema, src, at, params) {
|
|
1028
|
+
const values = src.values;
|
|
1029
|
+
const ctx = {
|
|
1030
|
+
src,
|
|
1031
|
+
byId: new Map(src.actions().map((a) => [a.id, a])),
|
|
1032
|
+
root: schema,
|
|
1033
|
+
read: values ? (path, from) => from === "item" ? void 0 : values(path) : void 0,
|
|
1034
|
+
at,
|
|
1035
|
+
params
|
|
1036
|
+
};
|
|
1037
|
+
return tidy(groupRows(ctx, schema.children, "", ROOT));
|
|
1038
|
+
}
|
|
1039
|
+
function resolveMenu(def, src, at, params) {
|
|
1040
|
+
return resolveSchemaMenu(def.menu, src, at, params);
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// src/menus/menuHost.ts
|
|
1044
|
+
function createMenuHost(src) {
|
|
1045
|
+
const defs = [];
|
|
1046
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1047
|
+
let open = null;
|
|
1048
|
+
let seq = 0;
|
|
1049
|
+
const emit = () => {
|
|
1050
|
+
for (const fn of listeners) fn();
|
|
1051
|
+
};
|
|
1052
|
+
const close = () => {
|
|
1053
|
+
if (!open) return;
|
|
1054
|
+
open = null;
|
|
1055
|
+
emit();
|
|
1056
|
+
};
|
|
1057
|
+
const resolve = (req) => {
|
|
1058
|
+
const live = mergeMenuDefs(defs, src.activeTool());
|
|
1059
|
+
const def = req.menu !== void 0 ? live.find((d) => d.id === req.menu) : req.hit ? pickMenu(live, req.hit) : void 0;
|
|
1060
|
+
return def ? resolveMenu(def, src, req.world, req.params) : [];
|
|
1061
|
+
};
|
|
1062
|
+
return {
|
|
1063
|
+
define(def, owner = null) {
|
|
1064
|
+
const entry = { def, owner };
|
|
1065
|
+
defs.push(entry);
|
|
1066
|
+
return () => {
|
|
1067
|
+
const i = defs.indexOf(entry);
|
|
1068
|
+
if (i >= 0) defs.splice(i, 1);
|
|
1069
|
+
};
|
|
1070
|
+
},
|
|
1071
|
+
open(req) {
|
|
1072
|
+
const entries = resolve(req);
|
|
1073
|
+
if (!entries.length) {
|
|
1074
|
+
close();
|
|
1075
|
+
return false;
|
|
1076
|
+
}
|
|
1077
|
+
open = {
|
|
1078
|
+
seq: ++seq,
|
|
1079
|
+
entries,
|
|
1080
|
+
client: req.client,
|
|
1081
|
+
world: req.world,
|
|
1082
|
+
...req.under ? { under: req.under } : {},
|
|
1083
|
+
...req.side ? { side: req.side } : {},
|
|
1084
|
+
source: req.source ?? "pointer"
|
|
1085
|
+
};
|
|
1086
|
+
emit();
|
|
1087
|
+
return true;
|
|
1088
|
+
},
|
|
1089
|
+
has: (req) => resolve(req).length > 0,
|
|
1090
|
+
close,
|
|
1091
|
+
current: () => open,
|
|
1092
|
+
subscribe(fn) {
|
|
1093
|
+
listeners.add(fn);
|
|
1094
|
+
return () => {
|
|
1095
|
+
listeners.delete(fn);
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
export { ActionDisabledReason, DepNode, NO_OVERRIDES, ScopeNode, actionBindings, actionCursor, actionItemTitle, actionItems, actionSettingsParams, actionShortcuts, bindingId, buildDepsFromRequires, createDepRegistry, createMenuHost, depNodeOf, evaluateActive, evaluateEnabled, keySpecShortcut, keysHeld, mergeMenuDefs, overriddenBindings, overrideActionBindings, paramsMatch, pickMenu, platformRoleKeys, pushOwner, readActionSettings, resolveBindingOverrides, resolveMenu, resolveParams, resolveRoles, resolveSchemaMenu, roleKeysOf, roleReader, tuneAction, tunedParams, validateActionDefaultBinding, validateActionId };
|
|
1102
|
+
//# sourceMappingURL=chunk-EUCNVGJV.js.map
|
|
1103
|
+
//# sourceMappingURL=chunk-EUCNVGJV.js.map
|