@weasel-js/menu 0.0.0-stage → 1.10.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 orochi235
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,61 @@
1
- # Temporary Holding Version
1
+ # @weasel-js/menu
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The headless model behind weasel's context menus: entries (items, separators, submenus), a
4
+ controller that owns the highlight, open submenus, keyboard and typeahead behavior, and placement
5
+ that keeps a menu on screen. No React, no DOM.
6
+
7
+ Part of [weasel](https://github.com/orochi235/weasel), a domain-agnostic 2D
8
+ scene-graph canvas kit for React. See the
9
+ [API reference](https://orochi235.github.io/weasel/#pkg/menu).
10
+
11
+ ## Install
12
+
13
+ ```sh
14
+ npm install @weasel-js/menu
15
+ ```
16
+
17
+ ## How it fits
18
+
19
+ | Package | Role |
20
+ |---|---|
21
+ | `@weasel-js/menu` | The model: entries, `createMenuController`, `placeMenu` / `placeSubmenu`. |
22
+ | `@weasel-js/routing` | Declared menus (`MenuDef`: sections of action references) and `MenuHost`, which holds the open menu. |
23
+ | `@weasel-js/core` | The `menu.context` action, which opens a menu from a right-click or the ContextMenu key; re-exports `useMenus`. |
24
+ | `@weasel-js/ui` | `<ContextMenuHost>`, the DOM renderer, and `useContextMenu` for opening a menu from chrome. |
25
+ | `@weasel-js/hud` | `attachMenuHost`, the WebGL renderer. |
26
+
27
+ The DOM renderer does not use React Aria's `Menu`, which keeps its own state and key handling: a
28
+ second copy of the model's that would drift from the WebGL menu's.
29
+
30
+ ## Usage
31
+
32
+ Declare menus as data, mount one host, and open one from chrome:
33
+
34
+ ```tsx
35
+ import { ActionsProvider, useMenus, type MenuDef } from '@weasel-js/core';
36
+ import { ContextMenuHost, useContextMenu } from '@weasel-js/ui';
37
+
38
+ const MENUS: MenuDef[] = [
39
+ { id: 'edit', sections: [[{ action: 'duplicate' }, { action: 'delete' }]] },
40
+ ];
41
+
42
+ function EditButton() {
43
+ const onContextMenu = useContextMenu('edit');
44
+ return <button onContextMenu={onContextMenu}>Edit</button>;
45
+ }
46
+
47
+ function Chrome() {
48
+ useMenus(MENUS);
49
+ return <ContextMenuHost />;
50
+ }
51
+
52
+ // <ActionsProvider><Chrome /><EditButton /></ActionsProvider>
53
+ ```
54
+
55
+ Rows name registered actions (`duplicate`, `delete`, which act on the selection), and
56
+ `EditButton` and `Chrome` must sit under an `<ActionsProvider>`. Any menu opens by id; one with a `when` (`{ bodyTarget, bodyKind }`) also
57
+ opens on a right-click of a matching canvas body.
58
+
59
+ ## License
60
+
61
+ MIT
@@ -0,0 +1,142 @@
1
+ /** A key chip: the key, and which modifiers are held with it. `@weasel-js/routing`
2
+ * derives these from action bindings as `ActionShortcut`. */
3
+ interface MenuShortcut {
4
+ key: string;
5
+ mod: boolean;
6
+ alt: boolean;
7
+ shift: boolean;
8
+ }
9
+ /** A row that runs something when chosen. */
10
+ interface MenuItemEntry {
11
+ kind: 'item';
12
+ key: string;
13
+ label: string;
14
+ /** Opaque to the model; each renderer knows what it can draw. */
15
+ icon?: unknown;
16
+ shortcut?: MenuShortcut;
17
+ disabled?: boolean;
18
+ /** `undefined` for a row with no on/off state. */
19
+ checked?: boolean;
20
+ run(): void;
21
+ }
22
+ /** A rule between groups of rows. Never highlighted. */
23
+ interface MenuSeparatorEntry {
24
+ kind: 'separator';
25
+ key: string;
26
+ }
27
+ /** A row that opens another level. */
28
+ interface SubmenuEntry {
29
+ kind: 'submenu';
30
+ key: string;
31
+ label: string;
32
+ icon?: unknown;
33
+ disabled?: boolean;
34
+ entries: readonly MenuEntry[];
35
+ }
36
+ /** One line of a menu. */
37
+ type MenuEntry = MenuItemEntry | MenuSeparatorEntry | SubmenuEntry;
38
+ /** True for an entry the highlight can land on. */
39
+ declare function isRow(e: MenuEntry): e is MenuItemEntry | SubmenuEntry;
40
+ /**
41
+ * The entries shown at `level`, following `stack` (the highlighted index at
42
+ * each open level) down from `root`. Empty when the path does not lead to a
43
+ * submenu.
44
+ */
45
+ declare function entriesAt(root: readonly MenuEntry[], stack: readonly (number | null)[], level: number): readonly MenuEntry[];
46
+
47
+ /** A position in viewport pixels (CSS pixels, as `getBoundingClientRect` reports). */
48
+ interface Point {
49
+ x: number;
50
+ y: number;
51
+ }
52
+ /** A width and height in pixels. */
53
+ interface Size {
54
+ w: number;
55
+ h: number;
56
+ }
57
+ /** A rectangle in viewport pixels: top-left corner plus width and height. */
58
+ interface Rect {
59
+ x: number;
60
+ y: number;
61
+ w: number;
62
+ h: number;
63
+ }
64
+ /** Where a menu of `size` opened at `anchor` goes: down and right of the
65
+ * anchor, flipped on an axis that does not fit, shifted inside `bounds`
66
+ * when neither side does. */
67
+ declare function placeMenu(size: Size, anchor: Point, bounds: Rect): Point;
68
+ /** Where a submenu goes: beside `row`, right first, left if that does not
69
+ * fit, top-aligned with the row and shifted up to stay inside `bounds`. */
70
+ declare function placeSubmenu(size: Size, row: Rect, bounds: Rect): Point;
71
+
72
+ /** The highlighted index at each open level; its length is how many are open. */
73
+ type MenuStack = readonly (number | null)[];
74
+ /** What a key asks the owner of the menu to do beyond moving the highlight. */
75
+ type KeyEffect = {
76
+ kind: 'none';
77
+ } | {
78
+ kind: 'choose';
79
+ level: number;
80
+ index: number;
81
+ } | {
82
+ kind: 'close';
83
+ };
84
+ /** Index of the first row, or null for a level with none. */
85
+ declare function firstRow(entries: readonly MenuEntry[]): number | null;
86
+ /** Apply one key to the menu: the new stack, any effect, and whether the key
87
+ * was the menu's (an unhandled key goes on to typeahead). */
88
+ declare function keyStep(root: readonly MenuEntry[], stack: MenuStack, key: string): {
89
+ stack: MenuStack;
90
+ effect: KeyEffect;
91
+ handled: boolean;
92
+ };
93
+
94
+ /** The row a typed `query` lands on, searching forward from `from` with
95
+ * wrap. A one-letter query starts after the highlight so repeating a letter
96
+ * cycles; a longer one starts at it so typing on keeps the match. */
97
+ declare function typeaheadMatch(entries: readonly MenuEntry[], from: number | null, query: string): number | null;
98
+ /** How long typing can pause before the typeahead buffer starts over, in milliseconds. */
99
+ declare const TYPEAHEAD_RESET_MS = 500;
100
+
101
+ /** How long the pointer rests on a submenu row before its panel opens, in milliseconds. */
102
+ declare const SUBMENU_OPEN_MS = 200;
103
+ /** How long a move toward an open submenu keeps it open while the pointer crosses other rows, in milliseconds. */
104
+ declare const DIAGONAL_GRACE_MS = 300;
105
+ /** Clock and timer, injected so tests and headless hosts control time. */
106
+ interface MenuScheduler {
107
+ now(): number;
108
+ /** Run `fn` after `ms`; returns a cancel. */
109
+ set(fn: () => void, ms: number): () => void;
110
+ }
111
+ /** The real-time scheduler: `Date.now` and `setTimeout`. */
112
+ declare const timerScheduler: MenuScheduler;
113
+ /** What {@link createMenuController} needs: the entries, how to close, and optionally a clock. */
114
+ interface MenuControllerOptions {
115
+ entries: readonly MenuEntry[];
116
+ /** Highlight the first row at open, as a keyboard open does. */
117
+ highlightFirst?: boolean;
118
+ /** The menu is done: an item was chosen, or it was dismissed. */
119
+ onClose(): void;
120
+ scheduler?: MenuScheduler;
121
+ }
122
+ /** One open menu's behavior, independent of what draws it. */
123
+ interface MenuController {
124
+ stack(): MenuStack;
125
+ entriesAt(level: number): readonly MenuEntry[];
126
+ /** Returns whether the key was the menu's. */
127
+ key(key: string): boolean;
128
+ /** The pointer is over row `index` of `level`, or over none of its rows. */
129
+ hover(level: number, index: number | null, point?: Point): void;
130
+ press(level: number, index: number): void;
131
+ /** Where `level`'s panel is, for the diagonal allowance. */
132
+ setPanel(level: number, rect: Rect): void;
133
+ subscribe(fn: () => void): () => void;
134
+ dispose(): void;
135
+ }
136
+ /** Creates the controller that runs one open menu: highlight, keys, submenus and typeahead. */
137
+ declare function createMenuController(opts: MenuControllerOptions): MenuController;
138
+
139
+ /** Whether `p` lies in triangle `abc`, edges included. */
140
+ declare function inTriangle(p: Point, a: Point, b: Point, c: Point): boolean;
141
+
142
+ export { DIAGONAL_GRACE_MS, type KeyEffect, type MenuController, type MenuControllerOptions, type MenuEntry, type MenuItemEntry, type MenuScheduler, type MenuSeparatorEntry, type MenuShortcut, type MenuStack, type Point, type Rect, SUBMENU_OPEN_MS, type Size, type SubmenuEntry, TYPEAHEAD_RESET_MS, createMenuController, entriesAt, firstRow, inTriangle, isRow, keyStep, placeMenu, placeSubmenu, timerScheduler, typeaheadMatch };
package/dist/index.js ADDED
@@ -0,0 +1,275 @@
1
+ // src/entries.ts
2
+ function isRow(e) {
3
+ return e.kind !== "separator";
4
+ }
5
+ function entriesAt(root, stack, level) {
6
+ let entries = root;
7
+ for (let i = 0; i < level; i++) {
8
+ const at = stack[i];
9
+ const e = at == null ? void 0 : entries[at];
10
+ if (e?.kind !== "submenu") return [];
11
+ entries = e.entries;
12
+ }
13
+ return entries;
14
+ }
15
+
16
+ // src/placement.ts
17
+ function flipOrShift(at, len, lo, span) {
18
+ const hi = lo + span;
19
+ if (at + len <= hi) return Math.max(lo, at);
20
+ if (at - len >= lo) return Math.max(lo, Math.min(at - len, hi - len));
21
+ return Math.max(lo, hi - len);
22
+ }
23
+ function placeMenu(size, anchor, bounds) {
24
+ return {
25
+ x: flipOrShift(anchor.x, size.w, bounds.x, bounds.w),
26
+ y: flipOrShift(anchor.y, size.h, bounds.y, bounds.h)
27
+ };
28
+ }
29
+ function placeSubmenu(size, row, bounds) {
30
+ const right = row.x + row.w;
31
+ const hiX = bounds.x + bounds.w;
32
+ const x = right + size.w <= hiX ? right : row.x - size.w >= bounds.x ? row.x - size.w : Math.max(bounds.x, hiX - size.w);
33
+ const y = Math.max(bounds.y, Math.min(row.y, bounds.y + bounds.h - size.h));
34
+ return { x, y };
35
+ }
36
+
37
+ // src/keys.ts
38
+ var NONE = { kind: "none" };
39
+ function firstRow(entries) {
40
+ const i = entries.findIndex(isRow);
41
+ return i < 0 ? null : i;
42
+ }
43
+ function step(entries, from, dir) {
44
+ const n = entries.length;
45
+ let i = from ?? (dir === 1 ? -1 : n);
46
+ for (let k = 0; k < n; k++) {
47
+ i = (i + dir + n) % n;
48
+ if (isRow(entries[i])) return i;
49
+ }
50
+ return null;
51
+ }
52
+ function withTop(stack, index) {
53
+ return [...stack.slice(0, -1), index];
54
+ }
55
+ function openSub(root, stack) {
56
+ const level = stack.length - 1;
57
+ const at = stack[level];
58
+ const e = at == null ? void 0 : entriesAt(root, stack, level)[at];
59
+ if (e?.kind !== "submenu" || e.disabled) return null;
60
+ return [...stack, firstRow(e.entries)];
61
+ }
62
+ function keyStep(root, stack, key) {
63
+ const level = stack.length - 1;
64
+ const rows = entriesAt(root, stack, level);
65
+ const at = stack[level] ?? null;
66
+ const done = (s, effect = NONE) => ({ stack: s, effect, handled: true });
67
+ switch (key) {
68
+ case "ArrowDown":
69
+ return done(withTop(stack, step(rows, at, 1)));
70
+ case "ArrowUp":
71
+ return done(withTop(stack, step(rows, at, -1)));
72
+ case "Home":
73
+ return done(withTop(stack, step(rows, null, 1)));
74
+ case "End":
75
+ return done(withTop(stack, step(rows, null, -1)));
76
+ case "ArrowRight":
77
+ return done(openSub(root, stack) ?? stack);
78
+ case "ArrowLeft":
79
+ return done(level > 0 ? stack.slice(0, -1) : stack);
80
+ case "Escape":
81
+ return level > 0 ? done(stack.slice(0, -1)) : done(stack, { kind: "close" });
82
+ case "Tab":
83
+ return done(stack, { kind: "close" });
84
+ case "Enter":
85
+ case " ": {
86
+ const e = at == null ? void 0 : rows[at];
87
+ if (e?.kind === "submenu") return done(openSub(root, stack) ?? stack);
88
+ if (e?.kind === "item" && !e.disabled) return done(stack, { kind: "choose", level, index: at });
89
+ return done(stack);
90
+ }
91
+ default:
92
+ return { stack, effect: NONE, handled: false };
93
+ }
94
+ }
95
+
96
+ // src/typeahead.ts
97
+ function typeaheadMatch(entries, from, query) {
98
+ const q = query.toLowerCase();
99
+ const n = entries.length;
100
+ const start = from == null ? 0 : q.length === 1 ? from + 1 : from;
101
+ for (let k = 0; k < n; k++) {
102
+ const i = (start + k) % n;
103
+ const e = entries[i];
104
+ if (isRow(e) && e.label.toLowerCase().startsWith(q)) return i;
105
+ }
106
+ return null;
107
+ }
108
+ var TYPEAHEAD_RESET_MS = 500;
109
+ function typeaheadStep(entries, from, buffer, key, now) {
110
+ const prev = now - buffer.at < TYPEAHEAD_RESET_MS ? buffer.text : "";
111
+ const k = key.toLowerCase();
112
+ const cycling = prev !== "" && [...prev].every((c) => c.toLowerCase() === k);
113
+ const text = cycling ? key : prev + key;
114
+ return { buffer: { text, at: now }, hit: typeaheadMatch(entries, from, text) };
115
+ }
116
+
117
+ // src/triangle.ts
118
+ var cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
119
+ function inTriangle(p, a, b, c) {
120
+ const d1 = cross(p, a, b), d2 = cross(p, b, c), d3 = cross(p, c, a);
121
+ const neg = d1 < 0 || d2 < 0 || d3 < 0;
122
+ const pos = d1 > 0 || d2 > 0 || d3 > 0;
123
+ return !(neg && pos);
124
+ }
125
+
126
+ // src/controller.ts
127
+ var SUBMENU_OPEN_MS = 200;
128
+ var DIAGONAL_GRACE_MS = 300;
129
+ var timerScheduler = {
130
+ now: () => Date.now(),
131
+ set: (fn, ms) => {
132
+ const t = setTimeout(fn, ms);
133
+ return () => clearTimeout(t);
134
+ }
135
+ };
136
+ function createMenuController(opts) {
137
+ const { entries: root, onClose } = opts;
138
+ const sched = opts.scheduler ?? timerScheduler;
139
+ let stack = [opts.highlightFirst ? firstRow(root) : null];
140
+ const listeners = /* @__PURE__ */ new Set();
141
+ const panels = /* @__PURE__ */ new Map();
142
+ let pending = null;
143
+ let pendingKind = null;
144
+ let lastPoint = null;
145
+ let deferred = null;
146
+ let typed = { text: "", at: -Infinity };
147
+ let closed = false;
148
+ const set = (next) => {
149
+ stack = next;
150
+ for (const level of [...panels.keys()]) if (level >= next.length) panels.delete(level);
151
+ for (const fn of listeners) fn();
152
+ };
153
+ const cancel = () => {
154
+ pending?.();
155
+ pending = null;
156
+ pendingKind = null;
157
+ };
158
+ const later = (kind, fn, ms) => {
159
+ cancel();
160
+ pendingKind = kind;
161
+ pending = sched.set(() => {
162
+ pending = null;
163
+ pendingKind = null;
164
+ if (!closed) fn();
165
+ }, ms);
166
+ };
167
+ const entryAt = (level, index) => entriesAt(root, stack, level)[index];
168
+ const isRowAt = (level, index) => {
169
+ const e = entryAt(level, index);
170
+ return e !== void 0 && isRow(e);
171
+ };
172
+ const finish = () => {
173
+ closed = true;
174
+ cancel();
175
+ onClose();
176
+ };
177
+ const choose = (level, index) => {
178
+ const e = entryAt(level, index);
179
+ if (e?.kind !== "item" || e.disabled) return;
180
+ finish();
181
+ e.run();
182
+ };
183
+ const openAt = (level, index) => {
184
+ const e = entryAt(level, index);
185
+ const base = [...stack.slice(0, level), index];
186
+ set(e?.kind === "submenu" && !e.disabled ? [...base, null] : base);
187
+ };
188
+ const headingIntoChild = (level, p) => {
189
+ const child = panels.get(level + 1);
190
+ const parent = panels.get(level);
191
+ if (!child || !parent || !lastPoint || stack.length <= level + 1) return false;
192
+ const edge = child.x >= parent.x ? child.x : child.x + child.w;
193
+ return inTriangle(p, lastPoint, { x: edge, y: child.y }, { x: edge, y: child.y + child.h });
194
+ };
195
+ const hoverNow = (level, index) => {
196
+ cancel();
197
+ if (index === null && stack.length !== level + 1) return;
198
+ const next = [...stack.slice(0, level), index];
199
+ if (next.length !== stack.length || next[level] !== stack[level]) set(next);
200
+ if (index === null) return;
201
+ const e = entryAt(level, index);
202
+ if (e?.kind === "submenu" && !e.disabled) later("open", () => openAt(level, index), SUBMENU_OPEN_MS);
203
+ };
204
+ return {
205
+ stack: () => stack,
206
+ entriesAt: (level) => entriesAt(root, stack, level),
207
+ key(key) {
208
+ if (closed) return false;
209
+ const r = keyStep(root, stack, key);
210
+ if (r.handled) {
211
+ cancel();
212
+ if (r.stack !== stack) {
213
+ typed = { text: "", at: -Infinity };
214
+ set(r.stack);
215
+ }
216
+ if (r.effect.kind === "choose") choose(r.effect.level, r.effect.index);
217
+ else if (r.effect.kind === "close") finish();
218
+ return true;
219
+ }
220
+ if (key.length !== 1 || key === " ") return false;
221
+ cancel();
222
+ const level = stack.length - 1;
223
+ const t = typeaheadStep(entriesAt(root, stack, level), stack[level] ?? null, typed, key, sched.now());
224
+ typed = t.buffer;
225
+ if (t.hit !== null) set([...stack.slice(0, level), t.hit]);
226
+ return true;
227
+ },
228
+ hover(level, index, point) {
229
+ if (closed || level >= stack.length) return;
230
+ if (index !== null && !isRowAt(level, index)) return;
231
+ const sameRow = index === stack[level];
232
+ if (point && !sameRow && headingIntoChild(level, point)) {
233
+ deferred = { level, index };
234
+ if (pendingKind !== "diagonal") {
235
+ later("diagonal", () => {
236
+ if (deferred) hoverNow(deferred.level, deferred.index);
237
+ }, DIAGONAL_GRACE_MS);
238
+ }
239
+ return;
240
+ }
241
+ if (point) lastPoint = point;
242
+ if (sameRow && stack.length > level + 1) {
243
+ cancel();
244
+ return;
245
+ }
246
+ if (sameRow && pendingKind === "open") return;
247
+ hoverNow(level, index);
248
+ },
249
+ press(level, index) {
250
+ if (closed || level >= stack.length || !isRowAt(level, index)) return;
251
+ if (stack[level] === index && stack.length > level + 1) return;
252
+ cancel();
253
+ if (entryAt(level, index)?.kind === "item") choose(level, index);
254
+ else openAt(level, index);
255
+ },
256
+ setPanel(level, rect) {
257
+ panels.set(level, rect);
258
+ },
259
+ subscribe(fn) {
260
+ listeners.add(fn);
261
+ return () => {
262
+ listeners.delete(fn);
263
+ };
264
+ },
265
+ dispose() {
266
+ closed = true;
267
+ cancel();
268
+ listeners.clear();
269
+ }
270
+ };
271
+ }
272
+
273
+ export { DIAGONAL_GRACE_MS, SUBMENU_OPEN_MS, TYPEAHEAD_RESET_MS, createMenuController, entriesAt, firstRow, inTriangle, isRow, keyStep, placeMenu, placeSubmenu, timerScheduler, typeaheadMatch };
274
+ //# sourceMappingURL=index.js.map
275
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/entries.ts","../src/placement.ts","../src/keys.ts","../src/typeahead.ts","../src/triangle.ts","../src/controller.ts"],"names":[],"mappings":";AA2CO,SAAS,MAAM,CAAA,EAAiD;AACrE,EAAA,OAAO,EAAE,IAAA,KAAS,WAAA;AACpB;AAOO,SAAS,SAAA,CACd,IAAA,EACA,KAAA,EACA,KAAA,EACsB;AACtB,EAAA,IAAI,OAAA,GAAU,IAAA;AACd,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,KAAA,EAAO,CAAA,EAAA,EAAK;AAC9B,IAAA,MAAM,EAAA,GAAK,MAAM,CAAC,CAAA;AAClB,IAAA,MAAM,CAAA,GAAI,EAAA,IAAM,IAAA,GAAO,MAAA,GAAY,QAAQ,EAAE,CAAA;AAC7C,IAAA,IAAI,CAAA,EAAG,IAAA,KAAS,SAAA,EAAW,OAAO,EAAC;AACnC,IAAA,OAAA,GAAU,CAAA,CAAE,OAAA;AAAA,EACd;AACA,EAAA,OAAO,OAAA;AACT;;;ACzDA,SAAS,WAAA,CAAY,EAAA,EAAY,GAAA,EAAa,EAAA,EAAY,IAAA,EAAsB;AAC9E,EAAA,MAAM,KAAK,EAAA,GAAK,IAAA;AAChB,EAAA,IAAI,KAAK,GAAA,IAAO,EAAA,SAAW,IAAA,CAAK,GAAA,CAAI,IAAI,EAAE,CAAA;AAC1C,EAAA,IAAI,EAAA,GAAK,GAAA,IAAO,EAAA,EAAI,OAAO,IAAA,CAAK,GAAA,CAAI,EAAA,EAAI,IAAA,CAAK,GAAA,CAAI,EAAA,GAAK,GAAA,EAAK,EAAA,GAAK,GAAG,CAAC,CAAA;AACpE,EAAA,OAAO,IAAA,CAAK,GAAA,CAAI,EAAA,EAAI,EAAA,GAAK,GAAG,CAAA;AAC9B;AAKO,SAAS,SAAA,CAAU,IAAA,EAAY,MAAA,EAAe,MAAA,EAAqB;AACxE,EAAA,OAAO;AAAA,IACL,CAAA,EAAG,YAAY,MAAA,CAAO,CAAA,EAAG,KAAK,CAAA,EAAG,MAAA,CAAO,CAAA,EAAG,MAAA,CAAO,CAAC,CAAA;AAAA,IACnD,CAAA,EAAG,YAAY,MAAA,CAAO,CAAA,EAAG,KAAK,CAAA,EAAG,MAAA,CAAO,CAAA,EAAG,MAAA,CAAO,CAAC;AAAA,GACrD;AACF;AAIO,SAAS,YAAA,CAAa,IAAA,EAAY,GAAA,EAAW,MAAA,EAAqB;AACvE,EAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,CAAA,GAAI,GAAA,CAAI,CAAA;AAC1B,EAAA,MAAM,GAAA,GAAM,MAAA,CAAO,CAAA,GAAI,MAAA,CAAO,CAAA;AAC9B,EAAA,MAAM,CAAA,GAAI,QAAQ,IAAA,CAAK,CAAA,IAAK,MACxB,KAAA,GACA,GAAA,CAAI,CAAA,GAAI,IAAA,CAAK,CAAA,IAAK,MAAA,CAAO,IAAI,GAAA,CAAI,CAAA,GAAI,KAAK,CAAA,GAAI,IAAA,CAAK,IAAI,MAAA,CAAO,CAAA,EAAG,GAAA,GAAM,IAAA,CAAK,CAAC,CAAA;AACjF,EAAA,MAAM,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,MAAA,CAAO,GAAG,IAAA,CAAK,GAAA,CAAI,GAAA,CAAI,CAAA,EAAG,OAAO,CAAA,GAAI,MAAA,CAAO,CAAA,GAAI,IAAA,CAAK,CAAC,CAAC,CAAA;AAC1E,EAAA,OAAO,EAAE,GAAG,CAAA,EAAE;AAChB;;;ACxBA,IAAM,IAAA,GAAkB,EAAE,IAAA,EAAM,MAAA,EAAO;AAGhC,SAAS,SAAS,OAAA,EAA8C;AACrE,EAAA,MAAM,CAAA,GAAI,OAAA,CAAQ,SAAA,CAAU,KAAK,CAAA;AACjC,EAAA,OAAO,CAAA,GAAI,IAAI,IAAA,GAAO,CAAA;AACxB;AAEA,SAAS,IAAA,CAAK,OAAA,EAA+B,IAAA,EAAqB,GAAA,EAA4B;AAC5F,EAAA,MAAM,IAAI,OAAA,CAAQ,MAAA;AAClB,EAAA,IAAI,CAAA,GAAI,IAAA,KAAS,GAAA,KAAQ,CAAA,GAAI,EAAA,GAAK,CAAA,CAAA;AAClC,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA,EAAA,EAAK;AAC1B,IAAA,CAAA,GAAA,CAAK,CAAA,GAAI,MAAM,CAAA,IAAK,CAAA;AACpB,IAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,CAAC,CAAE,GAAG,OAAO,CAAA;AAAA,EACjC;AACA,EAAA,OAAO,IAAA;AACT;AAEA,SAAS,OAAA,CAAQ,OAAkB,KAAA,EAAiC;AAClE,EAAA,OAAO,CAAC,GAAG,KAAA,CAAM,MAAM,CAAA,EAAG,EAAE,GAAG,KAAK,CAAA;AACtC;AAEA,SAAS,OAAA,CAAQ,MAA4B,KAAA,EAAoC;AAC/E,EAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,GAAS,CAAA;AAC7B,EAAA,MAAM,EAAA,GAAK,MAAM,KAAK,CAAA;AACtB,EAAA,MAAM,CAAA,GAAI,MAAM,IAAA,GAAO,MAAA,GAAY,UAAU,IAAA,EAAM,KAAA,EAAO,KAAK,CAAA,CAAE,EAAE,CAAA;AACnE,EAAA,IAAI,CAAA,EAAG,IAAA,KAAS,SAAA,IAAa,CAAA,CAAE,UAAU,OAAO,IAAA;AAChD,EAAA,OAAO,CAAC,GAAG,KAAA,EAAO,QAAA,CAAS,CAAA,CAAE,OAAO,CAAC,CAAA;AACvC;AAIO,SAAS,OAAA,CACd,IAAA,EACA,KAAA,EACA,GAAA,EAC2D;AAC3D,EAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,GAAS,CAAA;AAC7B,EAAA,MAAM,IAAA,GAAO,SAAA,CAAU,IAAA,EAAM,KAAA,EAAO,KAAK,CAAA;AACzC,EAAA,MAAM,EAAA,GAAK,KAAA,CAAM,KAAK,CAAA,IAAK,IAAA;AAC3B,EAAA,MAAM,IAAA,GAAO,CAAC,CAAA,EAAc,MAAA,GAAoB,IAAA,MAAU,EAAE,KAAA,EAAO,CAAA,EAAG,MAAA,EAAQ,OAAA,EAAS,IAAA,EAAK,CAAA;AAC5F,EAAA,QAAQ,GAAA;AAAK,IACX,KAAK,WAAA;AAAa,MAAA,OAAO,IAAA,CAAK,QAAQ,KAAA,EAAO,IAAA,CAAK,MAAM,EAAA,EAAI,CAAC,CAAC,CAAC,CAAA;AAAA,IAC/D,KAAK,SAAA;AAAW,MAAA,OAAO,IAAA,CAAK,QAAQ,KAAA,EAAO,IAAA,CAAK,MAAM,EAAA,EAAI,EAAE,CAAC,CAAC,CAAA;AAAA,IAC9D,KAAK,MAAA;AAAQ,MAAA,OAAO,IAAA,CAAK,QAAQ,KAAA,EAAO,IAAA,CAAK,MAAM,IAAA,EAAM,CAAC,CAAC,CAAC,CAAA;AAAA,IAC5D,KAAK,KAAA;AAAO,MAAA,OAAO,IAAA,CAAK,QAAQ,KAAA,EAAO,IAAA,CAAK,MAAM,IAAA,EAAM,EAAE,CAAC,CAAC,CAAA;AAAA,IAC5D,KAAK,YAAA;AAAc,MAAA,OAAO,IAAA,CAAK,OAAA,CAAQ,IAAA,EAAM,KAAK,KAAK,KAAK,CAAA;AAAA,IAC5D,KAAK,WAAA;AAAa,MAAA,OAAO,IAAA,CAAK,QAAQ,CAAA,GAAI,KAAA,CAAM,MAAM,CAAA,EAAG,EAAE,IAAI,KAAK,CAAA;AAAA,IACpE,KAAK,QAAA;AAAU,MAAA,OAAO,KAAA,GAAQ,CAAA,GAAI,IAAA,CAAK,KAAA,CAAM,MAAM,CAAA,EAAG,EAAE,CAAC,CAAA,GAAI,IAAA,CAAK,KAAA,EAAO,EAAE,IAAA,EAAM,SAAS,CAAA;AAAA,IAC1F,KAAK,KAAA;AAAO,MAAA,OAAO,IAAA,CAAK,KAAA,EAAO,EAAE,IAAA,EAAM,SAAS,CAAA;AAAA,IAChD,KAAK,OAAA;AAAA,IACL,KAAK,GAAA,EAAK;AACR,MAAA,MAAM,CAAA,GAAI,EAAA,IAAM,IAAA,GAAO,MAAA,GAAY,KAAK,EAAE,CAAA;AAC1C,MAAA,IAAI,CAAA,EAAG,SAAS,SAAA,EAAW,OAAO,KAAK,OAAA,CAAQ,IAAA,EAAM,KAAK,CAAA,IAAK,KAAK,CAAA;AACpE,MAAA,IAAI,CAAA,EAAG,IAAA,KAAS,MAAA,IAAU,CAAC,EAAE,QAAA,EAAU,OAAO,IAAA,CAAK,KAAA,EAAO,EAAE,IAAA,EAAM,QAAA,EAAU,KAAA,EAAO,KAAA,EAAO,IAAK,CAAA;AAC/F,MAAA,OAAO,KAAK,KAAK,CAAA;AAAA,IACnB;AAAA,IACA;AAAS,MAAA,OAAO,EAAE,KAAA,EAAO,MAAA,EAAQ,IAAA,EAAM,SAAS,KAAA,EAAM;AAAA;AAE1D;;;ACjEO,SAAS,cAAA,CACd,OAAA,EACA,IAAA,EACA,KAAA,EACe;AACf,EAAA,MAAM,CAAA,GAAI,MAAM,WAAA,EAAY;AAC5B,EAAA,MAAM,IAAI,OAAA,CAAQ,MAAA;AAClB,EAAA,MAAM,KAAA,GAAQ,QAAQ,IAAA,GAAO,CAAA,GAAI,EAAE,MAAA,KAAW,CAAA,GAAI,OAAO,CAAA,GAAI,IAAA;AAC7D,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,EAAG,CAAA,EAAA,EAAK;AAC1B,IAAA,MAAM,CAAA,GAAA,CAAK,QAAQ,CAAA,IAAK,CAAA;AACxB,IAAA,MAAM,CAAA,GAAI,QAAQ,CAAC,CAAA;AACnB,IAAA,IAAI,KAAA,CAAM,CAAC,CAAA,IAAK,CAAA,CAAE,KAAA,CAAM,aAAY,CAAE,UAAA,CAAW,CAAC,CAAA,EAAG,OAAO,CAAA;AAAA,EAC9D;AACA,EAAA,OAAO,IAAA;AACT;AAGO,IAAM,kBAAA,GAAqB;AAS3B,SAAS,aAAA,CACd,OAAA,EACA,IAAA,EACA,MAAA,EACA,KACA,GAAA,EACiD;AACjD,EAAA,MAAM,OAAO,GAAA,GAAM,MAAA,CAAO,EAAA,GAAK,kBAAA,GAAqB,OAAO,IAAA,GAAO,EAAA;AAClE,EAAA,MAAM,CAAA,GAAI,IAAI,WAAA,EAAY;AAC1B,EAAA,MAAM,OAAA,GAAU,IAAA,KAAS,EAAA,IAAM,CAAC,GAAG,IAAI,CAAA,CAAE,KAAA,CAAM,CAAC,CAAA,KAAM,CAAA,CAAE,WAAA,OAAkB,CAAC,CAAA;AAC3E,EAAA,MAAM,IAAA,GAAO,OAAA,GAAU,GAAA,GAAM,IAAA,GAAO,GAAA;AACpC,EAAA,OAAO,EAAE,MAAA,EAAQ,EAAE,IAAA,EAAM,EAAA,EAAI,GAAA,EAAI,EAAG,GAAA,EAAK,cAAA,CAAe,OAAA,EAAS,IAAA,EAAM,IAAI,CAAA,EAAE;AAC/E;;;ACzCA,IAAM,QAAQ,CAAC,CAAA,EAAU,GAAU,CAAA,KAAA,CAAc,CAAA,CAAE,IAAI,CAAA,CAAE,CAAA,KAAM,EAAE,CAAA,GAAI,CAAA,CAAE,MAAM,CAAA,CAAE,CAAA,GAAI,EAAE,CAAA,KAAM,CAAA,CAAE,IAAI,CAAA,CAAE,CAAA,CAAA;AAG5F,SAAS,UAAA,CAAW,CAAA,EAAU,CAAA,EAAU,CAAA,EAAU,CAAA,EAAmB;AAC1E,EAAA,MAAM,KAAK,KAAA,CAAM,CAAA,EAAG,CAAA,EAAG,CAAC,GAAG,EAAA,GAAK,KAAA,CAAM,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA,EAAG,EAAA,GAAK,KAAA,CAAM,CAAA,EAAG,GAAG,CAAC,CAAA;AAClE,EAAA,MAAM,GAAA,GAAM,EAAA,GAAK,CAAA,IAAK,EAAA,GAAK,KAAK,EAAA,GAAK,CAAA;AACrC,EAAA,MAAM,GAAA,GAAM,EAAA,GAAK,CAAA,IAAK,EAAA,GAAK,KAAK,EAAA,GAAK,CAAA;AACrC,EAAA,OAAO,EAAE,GAAA,IAAO,GAAA,CAAA;AAClB;;;ACFO,IAAM,eAAA,GAAkB;AAExB,IAAM,iBAAA,GAAoB;AAU1B,IAAM,cAAA,GAAgC;AAAA,EAC3C,GAAA,EAAK,MAAM,IAAA,CAAK,GAAA,EAAI;AAAA,EACpB,GAAA,EAAK,CAAC,EAAA,EAAI,EAAA,KAAO;AACf,IAAA,MAAM,CAAA,GAAI,UAAA,CAAW,EAAA,EAAI,EAAE,CAAA;AAC3B,IAAA,OAAO,MAAM,aAAa,CAAC,CAAA;AAAA,EAC7B;AACF;AA4BO,SAAS,qBAAqB,IAAA,EAA6C;AAChF,EAAA,MAAM,EAAE,OAAA,EAAS,IAAA,EAAM,OAAA,EAAQ,GAAI,IAAA;AACnC,EAAA,MAAM,KAAA,GAAQ,KAAK,SAAA,IAAa,cAAA;AAChC,EAAA,IAAI,QAAmB,CAAC,IAAA,CAAK,iBAAiB,QAAA,CAAS,IAAI,IAAI,IAAI,CAAA;AACnE,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAAgB;AACtC,EAAA,MAAM,MAAA,uBAAa,GAAA,EAAkB;AACrC,EAAA,IAAI,OAAA,GAA+B,IAAA;AACnC,EAAA,IAAI,WAAA,GAA0C,IAAA;AAC9C,EAAA,IAAI,SAAA,GAA0B,IAAA;AAC9B,EAAA,IAAI,QAAA,GAA2D,IAAA;AAC/D,EAAA,IAAI,KAAA,GAAyB,EAAE,IAAA,EAAM,EAAA,EAAI,IAAI,CAAA,QAAA,EAAU;AACvD,EAAA,IAAI,MAAA,GAAS,KAAA;AAEb,EAAA,MAAM,GAAA,GAAM,CAAC,IAAA,KAAoB;AAC/B,IAAA,KAAA,GAAQ,IAAA;AACR,IAAA,KAAA,MAAW,KAAA,IAAS,CAAC,GAAG,MAAA,CAAO,IAAA,EAAM,CAAA,EAAG,IAAI,KAAA,IAAS,IAAA,CAAK,MAAA,EAAQ,MAAA,CAAO,OAAO,KAAK,CAAA;AACrF,IAAA,KAAA,MAAW,EAAA,IAAM,WAAW,EAAA,EAAG;AAAA,EACjC,CAAA;AACA,EAAA,MAAM,SAAS,MAAM;AAAE,IAAA,OAAA,IAAU;AAAG,IAAA,OAAA,GAAU,IAAA;AAAM,IAAA,WAAA,GAAc,IAAA;AAAA,EAAM,CAAA;AACxE,EAAA,MAAM,KAAA,GAAQ,CAAC,IAAA,EAA2B,EAAA,EAAgB,EAAA,KAAe;AACvE,IAAA,MAAA,EAAO;AACP,IAAA,WAAA,GAAc,IAAA;AACd,IAAA,OAAA,GAAU,KAAA,CAAM,IAAI,MAAM;AAAE,MAAA,OAAA,GAAU,IAAA;AAAM,MAAA,WAAA,GAAc,IAAA;AAAM,MAAA,IAAI,CAAC,QAAQ,EAAA,EAAG;AAAA,IAAG,GAAG,EAAE,CAAA;AAAA,EAC1F,CAAA;AACA,EAAA,MAAM,OAAA,GAAU,CAAC,KAAA,EAAe,KAAA,KAAkB,UAAU,IAAA,EAAM,KAAA,EAAO,KAAK,CAAA,CAAE,KAAK,CAAA;AACrF,EAAA,MAAM,OAAA,GAAU,CAAC,KAAA,EAAe,KAAA,KAAkB;AAChD,IAAA,MAAM,CAAA,GAAI,OAAA,CAAQ,KAAA,EAAO,KAAK,CAAA;AAC9B,IAAA,OAAO,CAAA,KAAM,MAAA,IAAa,KAAA,CAAM,CAAC,CAAA;AAAA,EACnC,CAAA;AAEA,EAAA,MAAM,SAAS,MAAM;AAAE,IAAA,MAAA,GAAS,IAAA;AAAM,IAAA,MAAA,EAAO;AAAG,IAAA,OAAA,EAAQ;AAAA,EAAG,CAAA;AAC3D,EAAA,MAAM,MAAA,GAAS,CAAC,KAAA,EAAe,KAAA,KAAkB;AAC/C,IAAA,MAAM,CAAA,GAAI,OAAA,CAAQ,KAAA,EAAO,KAAK,CAAA;AAC9B,IAAA,IAAI,CAAA,EAAG,IAAA,KAAS,MAAA,IAAU,CAAA,CAAE,QAAA,EAAU;AACtC,IAAA,MAAA,EAAO;AACP,IAAA,CAAA,CAAE,GAAA,EAAI;AAAA,EACR,CAAA;AAEA,EAAA,MAAM,MAAA,GAAS,CAAC,KAAA,EAAe,KAAA,KAAkB;AAC/C,IAAA,MAAM,CAAA,GAAI,OAAA,CAAQ,KAAA,EAAO,KAAK,CAAA;AAC9B,IAAA,MAAM,IAAA,GAAO,CAAC,GAAG,KAAA,CAAM,MAAM,CAAA,EAAG,KAAK,GAAG,KAAK,CAAA;AAC7C,IAAA,GAAA,CAAI,CAAA,EAAG,IAAA,KAAS,SAAA,IAAa,CAAC,CAAA,CAAE,QAAA,GAAW,CAAC,GAAG,IAAA,EAAM,IAAI,CAAA,GAAI,IAAI,CAAA;AAAA,EACnE,CAAA;AAGA,EAAA,MAAM,gBAAA,GAAmB,CAAC,KAAA,EAAe,CAAA,KAAsB;AAC7D,IAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,GAAA,CAAI,KAAA,GAAQ,CAAC,CAAA;AAClC,IAAA,MAAM,MAAA,GAAS,MAAA,CAAO,GAAA,CAAI,KAAK,CAAA;AAC/B,IAAA,IAAI,CAAC,KAAA,IAAS,CAAC,MAAA,IAAU,CAAC,aAAa,KAAA,CAAM,MAAA,IAAU,KAAA,GAAQ,CAAA,EAAG,OAAO,KAAA;AACzE,IAAA,MAAM,IAAA,GAAO,MAAM,CAAA,IAAK,MAAA,CAAO,IAAI,KAAA,CAAM,CAAA,GAAI,KAAA,CAAM,CAAA,GAAI,KAAA,CAAM,CAAA;AAC7D,IAAA,OAAO,WAAW,CAAA,EAAG,SAAA,EAAW,EAAE,CAAA,EAAG,IAAA,EAAM,GAAG,KAAA,CAAM,CAAA,EAAE,EAAG,EAAE,GAAG,IAAA,EAAM,CAAA,EAAG,MAAM,CAAA,GAAI,KAAA,CAAM,GAAG,CAAA;AAAA,EAC5F,CAAA;AAEA,EAAA,MAAM,QAAA,GAAW,CAAC,KAAA,EAAe,KAAA,KAAyB;AACxD,IAAA,MAAA,EAAO;AACP,IAAA,IAAI,KAAA,KAAU,IAAA,IAAQ,KAAA,CAAM,MAAA,KAAW,QAAQ,CAAA,EAAG;AAClD,IAAA,MAAM,IAAA,GAAO,CAAC,GAAG,KAAA,CAAM,MAAM,CAAA,EAAG,KAAK,GAAG,KAAK,CAAA;AAC7C,IAAA,IAAI,IAAA,CAAK,MAAA,KAAW,KAAA,CAAM,MAAA,IAAU,IAAA,CAAK,KAAK,CAAA,KAAM,KAAA,CAAM,KAAK,CAAA,EAAG,GAAA,CAAI,IAAI,CAAA;AAC1E,IAAA,IAAI,UAAU,IAAA,EAAM;AACpB,IAAA,MAAM,CAAA,GAAI,OAAA,CAAQ,KAAA,EAAO,KAAK,CAAA;AAC9B,IAAA,IAAI,CAAA,EAAG,IAAA,KAAS,SAAA,IAAa,CAAC,CAAA,CAAE,QAAA,EAAU,KAAA,CAAM,MAAA,EAAQ,MAAM,MAAA,CAAO,KAAA,EAAO,KAAK,GAAG,eAAe,CAAA;AAAA,EACrG,CAAA;AAEA,EAAA,OAAO;AAAA,IACL,OAAO,MAAM,KAAA;AAAA,IACb,WAAW,CAAC,KAAA,KAAU,SAAA,CAAU,IAAA,EAAM,OAAO,KAAK,CAAA;AAAA,IAClD,IAAI,GAAA,EAAK;AACP,MAAA,IAAI,QAAQ,OAAO,KAAA;AACnB,MAAA,MAAM,CAAA,GAAI,OAAA,CAAQ,IAAA,EAAM,KAAA,EAAO,GAAG,CAAA;AAClC,MAAA,IAAI,EAAE,OAAA,EAAS;AACb,QAAA,MAAA,EAAO;AACP,QAAA,IAAI,CAAA,CAAE,UAAU,KAAA,EAAO;AAAE,UAAA,KAAA,GAAQ,EAAE,IAAA,EAAM,EAAA,EAAI,EAAA,EAAI,CAAA,QAAA,EAAU;AAAG,UAAA,GAAA,CAAI,EAAE,KAAK,CAAA;AAAA,QAAG;AAC5E,QAAA,IAAI,CAAA,CAAE,MAAA,CAAO,IAAA,KAAS,QAAA,EAAU,MAAA,CAAO,EAAE,MAAA,CAAO,KAAA,EAAO,CAAA,CAAE,MAAA,CAAO,KAAK,CAAA;AAAA,aAAA,IAC5D,CAAA,CAAE,MAAA,CAAO,IAAA,KAAS,OAAA,EAAS,MAAA,EAAO;AAC3C,QAAA,OAAO,IAAA;AAAA,MACT;AACA,MAAA,IAAI,GAAA,CAAI,MAAA,KAAW,CAAA,IAAK,GAAA,KAAQ,KAAK,OAAO,KAAA;AAC5C,MAAA,MAAA,EAAO;AACP,MAAA,MAAM,KAAA,GAAQ,MAAM,MAAA,GAAS,CAAA;AAC7B,MAAA,MAAM,CAAA,GAAI,aAAA,CAAc,SAAA,CAAU,IAAA,EAAM,OAAO,KAAK,CAAA,EAAG,KAAA,CAAM,KAAK,KAAK,IAAA,EAAM,KAAA,EAAO,GAAA,EAAK,KAAA,CAAM,KAAK,CAAA;AACpG,MAAA,KAAA,GAAQ,CAAA,CAAE,MAAA;AACV,MAAA,IAAI,CAAA,CAAE,GAAA,KAAQ,IAAA,EAAM,GAAA,CAAI,CAAC,GAAG,KAAA,CAAM,KAAA,CAAM,CAAA,EAAG,KAAK,CAAA,EAAG,CAAA,CAAE,GAAG,CAAC,CAAA;AACzD,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAAA,IACA,KAAA,CAAM,KAAA,EAAO,KAAA,EAAO,KAAA,EAAO;AACzB,MAAA,IAAI,MAAA,IAAU,KAAA,IAAS,KAAA,CAAM,MAAA,EAAQ;AACrC,MAAA,IAAI,UAAU,IAAA,IAAQ,CAAC,OAAA,CAAQ,KAAA,EAAO,KAAK,CAAA,EAAG;AAC9C,MAAA,MAAM,OAAA,GAAU,KAAA,KAAU,KAAA,CAAM,KAAK,CAAA;AACrC,MAAA,IAAI,SAAS,CAAC,OAAA,IAAW,gBAAA,CAAiB,KAAA,EAAO,KAAK,CAAA,EAAG;AACvD,QAAA,QAAA,GAAW,EAAE,OAAO,KAAA,EAAM;AAC1B,QAAA,IAAI,gBAAgB,UAAA,EAAY;AAC9B,UAAA,KAAA,CAAM,YAAY,MAAM;AAAE,YAAA,IAAI,QAAA,EAAU,QAAA,CAAS,QAAA,CAAS,KAAA,EAAO,SAAS,KAAK,CAAA;AAAA,UAAG,GAAG,iBAAiB,CAAA;AAAA,QACxG;AACA,QAAA;AAAA,MACF;AACA,MAAA,IAAI,OAAO,SAAA,GAAY,KAAA;AACvB,MAAA,IAAI,OAAA,IAAW,KAAA,CAAM,MAAA,GAAS,KAAA,GAAQ,CAAA,EAAG;AAAE,QAAA,MAAA,EAAO;AAAG,QAAA;AAAA,MAAQ;AAC7D,MAAA,IAAI,OAAA,IAAW,gBAAgB,MAAA,EAAQ;AACvC,MAAA,QAAA,CAAS,OAAO,KAAK,CAAA;AAAA,IACvB,CAAA;AAAA,IACA,KAAA,CAAM,OAAO,KAAA,EAAO;AAClB,MAAA,IAAI,MAAA,IAAU,SAAS,KAAA,CAAM,MAAA,IAAU,CAAC,OAAA,CAAQ,KAAA,EAAO,KAAK,CAAA,EAAG;AAC/D,MAAA,IAAI,MAAM,KAAK,CAAA,KAAM,SAAS,KAAA,CAAM,MAAA,GAAS,QAAQ,CAAA,EAAG;AACxD,MAAA,MAAA,EAAO;AACP,MAAA,IAAI,OAAA,CAAQ,OAAO,KAAK,CAAA,EAAG,SAAS,MAAA,EAAQ,MAAA,CAAO,OAAO,KAAK,CAAA;AAAA,WAC1D,MAAA,CAAO,OAAO,KAAK,CAAA;AAAA,IAC1B,CAAA;AAAA,IACA,QAAA,CAAS,OAAO,IAAA,EAAM;AAAE,MAAA,MAAA,CAAO,GAAA,CAAI,OAAO,IAAI,CAAA;AAAA,IAAG,CAAA;AAAA,IACjD,UAAU,EAAA,EAAI;AACZ,MAAA,SAAA,CAAU,IAAI,EAAE,CAAA;AAChB,MAAA,OAAO,MAAM;AAAE,QAAA,SAAA,CAAU,OAAO,EAAE,CAAA;AAAA,MAAG,CAAA;AAAA,IACvC,CAAA;AAAA,IACA,OAAA,GAAU;AACR,MAAA,MAAA,GAAS,IAAA;AACT,MAAA,MAAA,EAAO;AACP,MAAA,SAAA,CAAU,KAAA,EAAM;AAAA,IAClB;AAAA,GACF;AACF","file":"index.js","sourcesContent":["/** A key chip: the key, and which modifiers are held with it. `@weasel-js/routing`\n * derives these from action bindings as `ActionShortcut`. */\nexport interface MenuShortcut {\n key: string;\n mod: boolean;\n alt: boolean;\n shift: boolean;\n}\n\n/** A row that runs something when chosen. */\nexport interface MenuItemEntry {\n kind: 'item';\n key: string;\n label: string;\n /** Opaque to the model; each renderer knows what it can draw. */\n icon?: unknown;\n shortcut?: MenuShortcut;\n disabled?: boolean;\n /** `undefined` for a row with no on/off state. */\n checked?: boolean;\n run(): void;\n}\n\n/** A rule between groups of rows. Never highlighted. */\nexport interface MenuSeparatorEntry {\n kind: 'separator';\n key: string;\n}\n\n/** A row that opens another level. */\nexport interface SubmenuEntry {\n kind: 'submenu';\n key: string;\n label: string;\n icon?: unknown;\n disabled?: boolean;\n entries: readonly MenuEntry[];\n}\n\n/** One line of a menu. */\nexport type MenuEntry = MenuItemEntry | MenuSeparatorEntry | SubmenuEntry;\n\n/** True for an entry the highlight can land on. */\nexport function isRow(e: MenuEntry): e is MenuItemEntry | SubmenuEntry {\n return e.kind !== 'separator';\n}\n\n/**\n * The entries shown at `level`, following `stack` (the highlighted index at\n * each open level) down from `root`. Empty when the path does not lead to a\n * submenu.\n */\nexport function entriesAt(\n root: readonly MenuEntry[],\n stack: readonly (number | null)[],\n level: number,\n): readonly MenuEntry[] {\n let entries = root;\n for (let i = 0; i < level; i++) {\n const at = stack[i];\n const e = at == null ? undefined : entries[at];\n if (e?.kind !== 'submenu') return [];\n entries = e.entries;\n }\n return entries;\n}\n","/** A position in viewport pixels (CSS pixels, as `getBoundingClientRect` reports). */\nexport interface Point { x: number; y: number }\n/** A width and height in pixels. */\nexport interface Size { w: number; h: number }\n/** A rectangle in viewport pixels: top-left corner plus width and height. */\nexport interface Rect { x: number; y: number; w: number; h: number }\n\n/** Start at `at`, else end there (flip), else slide inside `[lo, lo + span]`. */\nfunction flipOrShift(at: number, len: number, lo: number, span: number): number {\n const hi = lo + span;\n if (at + len <= hi) return Math.max(lo, at);\n if (at - len >= lo) return Math.max(lo, Math.min(at - len, hi - len));\n return Math.max(lo, hi - len);\n}\n\n/** Where a menu of `size` opened at `anchor` goes: down and right of the\n * anchor, flipped on an axis that does not fit, shifted inside `bounds`\n * when neither side does. */\nexport function placeMenu(size: Size, anchor: Point, bounds: Rect): Point {\n return {\n x: flipOrShift(anchor.x, size.w, bounds.x, bounds.w),\n y: flipOrShift(anchor.y, size.h, bounds.y, bounds.h),\n };\n}\n\n/** Where a submenu goes: beside `row`, right first, left if that does not\n * fit, top-aligned with the row and shifted up to stay inside `bounds`. */\nexport function placeSubmenu(size: Size, row: Rect, bounds: Rect): Point {\n const right = row.x + row.w;\n const hiX = bounds.x + bounds.w;\n const x = right + size.w <= hiX\n ? right\n : row.x - size.w >= bounds.x ? row.x - size.w : Math.max(bounds.x, hiX - size.w);\n const y = Math.max(bounds.y, Math.min(row.y, bounds.y + bounds.h - size.h));\n return { x, y };\n}\n","import { entriesAt, isRow, type MenuEntry } from './entries';\n\n/** The highlighted index at each open level; its length is how many are open. */\nexport type MenuStack = readonly (number | null)[];\n\n/** What a key asks the owner of the menu to do beyond moving the highlight. */\nexport type KeyEffect =\n | { kind: 'none' }\n | { kind: 'choose'; level: number; index: number }\n | { kind: 'close' };\n\nconst NONE: KeyEffect = { kind: 'none' };\n\n/** Index of the first row, or null for a level with none. */\nexport function firstRow(entries: readonly MenuEntry[]): number | null {\n const i = entries.findIndex(isRow);\n return i < 0 ? null : i;\n}\n\nfunction step(entries: readonly MenuEntry[], from: number | null, dir: 1 | -1): number | null {\n const n = entries.length;\n let i = from ?? (dir === 1 ? -1 : n);\n for (let k = 0; k < n; k++) {\n i = (i + dir + n) % n;\n if (isRow(entries[i]!)) return i;\n }\n return null;\n}\n\nfunction withTop(stack: MenuStack, index: number | null): MenuStack {\n return [...stack.slice(0, -1), index];\n}\n\nfunction openSub(root: readonly MenuEntry[], stack: MenuStack): MenuStack | null {\n const level = stack.length - 1;\n const at = stack[level];\n const e = at == null ? undefined : entriesAt(root, stack, level)[at];\n if (e?.kind !== 'submenu' || e.disabled) return null;\n return [...stack, firstRow(e.entries)];\n}\n\n/** Apply one key to the menu: the new stack, any effect, and whether the key\n * was the menu's (an unhandled key goes on to typeahead). */\nexport function keyStep(\n root: readonly MenuEntry[],\n stack: MenuStack,\n key: string,\n): { stack: MenuStack; effect: KeyEffect; handled: boolean } {\n const level = stack.length - 1;\n const rows = entriesAt(root, stack, level);\n const at = stack[level] ?? null;\n const done = (s: MenuStack, effect: KeyEffect = NONE) => ({ stack: s, effect, handled: true });\n switch (key) {\n case 'ArrowDown': return done(withTop(stack, step(rows, at, 1)));\n case 'ArrowUp': return done(withTop(stack, step(rows, at, -1)));\n case 'Home': return done(withTop(stack, step(rows, null, 1)));\n case 'End': return done(withTop(stack, step(rows, null, -1)));\n case 'ArrowRight': return done(openSub(root, stack) ?? stack);\n case 'ArrowLeft': return done(level > 0 ? stack.slice(0, -1) : stack);\n case 'Escape': return level > 0 ? done(stack.slice(0, -1)) : done(stack, { kind: 'close' });\n case 'Tab': return done(stack, { kind: 'close' });\n case 'Enter':\n case ' ': {\n const e = at == null ? undefined : rows[at];\n if (e?.kind === 'submenu') return done(openSub(root, stack) ?? stack);\n if (e?.kind === 'item' && !e.disabled) return done(stack, { kind: 'choose', level, index: at! });\n return done(stack);\n }\n default: return { stack, effect: NONE, handled: false };\n }\n}\n","import { isRow, type MenuEntry } from './entries';\n\n/** The row a typed `query` lands on, searching forward from `from` with\n * wrap. A one-letter query starts after the highlight so repeating a letter\n * cycles; a longer one starts at it so typing on keeps the match. */\nexport function typeaheadMatch(\n entries: readonly MenuEntry[],\n from: number | null,\n query: string,\n): number | null {\n const q = query.toLowerCase();\n const n = entries.length;\n const start = from == null ? 0 : q.length === 1 ? from + 1 : from;\n for (let k = 0; k < n; k++) {\n const i = (start + k) % n;\n const e = entries[i]!;\n if (isRow(e) && e.label.toLowerCase().startsWith(q)) return i;\n }\n return null;\n}\n\n/** How long typing can pause before the typeahead buffer starts over, in milliseconds. */\nexport const TYPEAHEAD_RESET_MS = 500;\n\nexport interface TypeaheadBuffer {\n text: string;\n at: number;\n}\n\n/** One typed character: the new buffer and the row it lands on. A key that\n * repeats the whole buffer searches that one letter, so it cycles. */\nexport function typeaheadStep(\n entries: readonly MenuEntry[],\n from: number | null,\n buffer: TypeaheadBuffer,\n key: string,\n now: number,\n): { buffer: TypeaheadBuffer; hit: number | null } {\n const prev = now - buffer.at < TYPEAHEAD_RESET_MS ? buffer.text : '';\n const k = key.toLowerCase();\n const cycling = prev !== '' && [...prev].every((c) => c.toLowerCase() === k);\n const text = cycling ? key : prev + key;\n return { buffer: { text, at: now }, hit: typeaheadMatch(entries, from, text) };\n}\n","import type { Point } from './placement';\n\nconst cross = (o: Point, a: Point, b: Point) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);\n\n/** Whether `p` lies in triangle `abc`, edges included. */\nexport function inTriangle(p: Point, a: Point, b: Point, c: Point): boolean {\n const d1 = cross(p, a, b), d2 = cross(p, b, c), d3 = cross(p, c, a);\n const neg = d1 < 0 || d2 < 0 || d3 < 0;\n const pos = d1 > 0 || d2 > 0 || d3 > 0;\n return !(neg && pos);\n}\n","import { entriesAt, isRow, type MenuEntry } from './entries';\nimport { firstRow, keyStep, type MenuStack } from './keys';\nimport type { Point, Rect } from './placement';\nimport { inTriangle } from './triangle';\nimport { typeaheadStep, type TypeaheadBuffer } from './typeahead';\n\nexport { TYPEAHEAD_RESET_MS } from './typeahead';\n/** How long the pointer rests on a submenu row before its panel opens, in milliseconds. */\nexport const SUBMENU_OPEN_MS = 200;\n/** How long a move toward an open submenu keeps it open while the pointer crosses other rows, in milliseconds. */\nexport const DIAGONAL_GRACE_MS = 300;\n\n/** Clock and timer, injected so tests and headless hosts control time. */\nexport interface MenuScheduler {\n now(): number;\n /** Run `fn` after `ms`; returns a cancel. */\n set(fn: () => void, ms: number): () => void;\n}\n\n/** The real-time scheduler: `Date.now` and `setTimeout`. */\nexport const timerScheduler: MenuScheduler = {\n now: () => Date.now(),\n set: (fn, ms) => {\n const t = setTimeout(fn, ms);\n return () => clearTimeout(t);\n },\n};\n\n/** What {@link createMenuController} needs: the entries, how to close, and optionally a clock. */\nexport interface MenuControllerOptions {\n entries: readonly MenuEntry[];\n /** Highlight the first row at open, as a keyboard open does. */\n highlightFirst?: boolean;\n /** The menu is done: an item was chosen, or it was dismissed. */\n onClose(): void;\n scheduler?: MenuScheduler;\n}\n\n/** One open menu's behavior, independent of what draws it. */\nexport interface MenuController {\n stack(): MenuStack;\n entriesAt(level: number): readonly MenuEntry[];\n /** Returns whether the key was the menu's. */\n key(key: string): boolean;\n /** The pointer is over row `index` of `level`, or over none of its rows. */\n hover(level: number, index: number | null, point?: Point): void;\n press(level: number, index: number): void;\n /** Where `level`'s panel is, for the diagonal allowance. */\n setPanel(level: number, rect: Rect): void;\n subscribe(fn: () => void): () => void;\n dispose(): void;\n}\n\n/** Creates the controller that runs one open menu: highlight, keys, submenus and typeahead. */\nexport function createMenuController(opts: MenuControllerOptions): MenuController {\n const { entries: root, onClose } = opts;\n const sched = opts.scheduler ?? timerScheduler;\n let stack: MenuStack = [opts.highlightFirst ? firstRow(root) : null];\n const listeners = new Set<() => void>();\n const panels = new Map<number, Rect>();\n let pending: (() => void) | null = null;\n let pendingKind: 'open' | 'diagonal' | null = null;\n let lastPoint: Point | null = null;\n let deferred: { level: number; index: number | null } | null = null;\n let typed: TypeaheadBuffer = { text: '', at: -Infinity };\n let closed = false;\n\n const set = (next: MenuStack) => {\n stack = next;\n for (const level of [...panels.keys()]) if (level >= next.length) panels.delete(level);\n for (const fn of listeners) fn();\n };\n const cancel = () => { pending?.(); pending = null; pendingKind = null; };\n const later = (kind: 'open' | 'diagonal', fn: () => void, ms: number) => {\n cancel();\n pendingKind = kind;\n pending = sched.set(() => { pending = null; pendingKind = null; if (!closed) fn(); }, ms);\n };\n const entryAt = (level: number, index: number) => entriesAt(root, stack, level)[index];\n const isRowAt = (level: number, index: number) => {\n const e = entryAt(level, index);\n return e !== undefined && isRow(e);\n };\n\n const finish = () => { closed = true; cancel(); onClose(); };\n const choose = (level: number, index: number) => {\n const e = entryAt(level, index);\n if (e?.kind !== 'item' || e.disabled) return;\n finish();\n e.run();\n };\n\n const openAt = (level: number, index: number) => {\n const e = entryAt(level, index);\n const base = [...stack.slice(0, level), index];\n set(e?.kind === 'submenu' && !e.disabled ? [...base, null] : base);\n };\n\n /** Heading from the point the deferral began toward the open child's near edge. */\n const headingIntoChild = (level: number, p: Point): boolean => {\n const child = panels.get(level + 1);\n const parent = panels.get(level);\n if (!child || !parent || !lastPoint || stack.length <= level + 1) return false;\n const edge = child.x >= parent.x ? child.x : child.x + child.w;\n return inTriangle(p, lastPoint, { x: edge, y: child.y }, { x: edge, y: child.y + child.h });\n };\n\n const hoverNow = (level: number, index: number | null) => {\n cancel();\n if (index === null && stack.length !== level + 1) return;\n const next = [...stack.slice(0, level), index];\n if (next.length !== stack.length || next[level] !== stack[level]) set(next);\n if (index === null) return;\n const e = entryAt(level, index);\n if (e?.kind === 'submenu' && !e.disabled) later('open', () => openAt(level, index), SUBMENU_OPEN_MS);\n };\n\n return {\n stack: () => stack,\n entriesAt: (level) => entriesAt(root, stack, level),\n key(key) {\n if (closed) return false;\n const r = keyStep(root, stack, key);\n if (r.handled) {\n cancel();\n if (r.stack !== stack) { typed = { text: '', at: -Infinity }; set(r.stack); }\n if (r.effect.kind === 'choose') choose(r.effect.level, r.effect.index);\n else if (r.effect.kind === 'close') finish();\n return true;\n }\n if (key.length !== 1 || key === ' ') return false;\n cancel();\n const level = stack.length - 1;\n const t = typeaheadStep(entriesAt(root, stack, level), stack[level] ?? null, typed, key, sched.now());\n typed = t.buffer;\n if (t.hit !== null) set([...stack.slice(0, level), t.hit]);\n return true;\n },\n hover(level, index, point) {\n if (closed || level >= stack.length) return;\n if (index !== null && !isRowAt(level, index)) return;\n const sameRow = index === stack[level];\n if (point && !sameRow && headingIntoChild(level, point)) {\n deferred = { level, index };\n if (pendingKind !== 'diagonal') {\n later('diagonal', () => { if (deferred) hoverNow(deferred.level, deferred.index); }, DIAGONAL_GRACE_MS);\n }\n return;\n }\n if (point) lastPoint = point;\n if (sameRow && stack.length > level + 1) { cancel(); return; }\n if (sameRow && pendingKind === 'open') return;\n hoverNow(level, index);\n },\n press(level, index) {\n if (closed || level >= stack.length || !isRowAt(level, index)) return;\n if (stack[level] === index && stack.length > level + 1) return;\n cancel();\n if (entryAt(level, index)?.kind === 'item') choose(level, index);\n else openAt(level, index);\n },\n setPanel(level, rect) { panels.set(level, rect); },\n subscribe(fn) {\n listeners.add(fn);\n return () => { listeners.delete(fn); };\n },\n dispose() {\n closed = true;\n cancel();\n listeners.clear();\n },\n };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,43 @@
1
1
  {
2
2
  "name": "@weasel-js/menu",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.10.1",
4
+ "description": "Headless menu model: entries, keyboard and pointer behavior, placement. No React, no DOM.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "sideEffects": false,
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "import": "./dist/index.js",
14
+ "types": "./dist/index.d.ts"
15
+ },
16
+ "./package.json": "./package.json"
17
+ },
18
+ "author": "orochi235",
19
+ "homepage": "https://orochi235.github.io/weasel/",
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "git+https://github.com/orochi235/weasel.git",
23
+ "directory": "packages/menu"
24
+ },
25
+ "bugs": {
26
+ "url": "https://github.com/orochi235/weasel/issues"
27
+ },
28
+ "engines": {
29
+ "node": ">=22"
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "README.md",
34
+ "LICENSE"
35
+ ],
36
+ "scripts": {
37
+ "build": "tsup"
38
+ },
39
+ "publishConfig": {
40
+ "access": "public",
41
+ "provenance": true
42
+ }
43
+ }