@enigmax/primitives 0.22.0 → 0.23.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.
Files changed (67) hide show
  1. package/dist/chunk-3BQVOOAM.js +388 -0
  2. package/dist/{chunk-QQFNAKMY.js → chunk-45UHLZYT.js} +1 -1
  3. package/dist/chunk-4VUHQFAT.js +130 -0
  4. package/dist/{chunk-D5A2ZMAG.js → chunk-4ZPMP47J.js} +6 -1
  5. package/dist/chunk-DSBYVA7V.js +713 -0
  6. package/dist/{chunk-FWVWX67R.js → chunk-FGBIZDV2.js} +3 -3
  7. package/dist/{chunk-R4ZAEE7V.js → chunk-KEVZ5XQV.js} +1 -1
  8. package/dist/chunk-N6PDHMAX.js +213 -0
  9. package/dist/chunk-NONGREXC.js +248 -0
  10. package/dist/chunk-VKL3DEIQ.js +804 -0
  11. package/dist/chunk-WSQC3PCC.js +466 -0
  12. package/dist/context-menu-D3FtTn7v.d.ts +174 -0
  13. package/dist/{index-BHpOZncw.d.ts → index-DQNnohoo.d.ts} +1 -1
  14. package/dist/index.d.ts +5 -1
  15. package/dist/index.js +8 -4
  16. package/dist/keys-D2zJs1uB.d.ts +100 -0
  17. package/dist/next/index.d.ts +9 -2
  18. package/dist/next/index.js +20 -13
  19. package/dist/react/context-menu.d.ts +202 -0
  20. package/dist/react/context-menu.js +6 -0
  21. package/dist/react/index.d.ts +10 -3
  22. package/dist/react/index.js +19 -12
  23. package/dist/react/input.d.ts +2 -2
  24. package/dist/react/input.js +1 -1
  25. package/dist/react/palette.d.ts +1 -1
  26. package/dist/react/palette.js +3 -3
  27. package/dist/react/search.d.ts +1 -1
  28. package/dist/react/search.js +2 -2
  29. package/dist/react/select.d.ts +217 -0
  30. package/dist/react/select.js +7 -0
  31. package/dist/react/selection.d.ts +104 -0
  32. package/dist/react/selection.js +4 -0
  33. package/dist/react-router/index.d.ts +9 -2
  34. package/dist/react-router/index.js +20 -13
  35. package/dist/search/index.d.ts +2 -2
  36. package/dist/search/index.js +1 -1
  37. package/dist/{search-BD9-5O5U.d.ts → search-DYgqRp37.d.ts} +13 -1
  38. package/dist/{search-UQEXAPQB.js → search-PBZORZ7P.js} +1 -1
  39. package/dist/select-ClSy-J1f.d.ts +100 -0
  40. package/dist/selection-B_pmzHpy.d.ts +150 -0
  41. package/package.json +21 -2
  42. package/recipes/context-menu/styles.css +177 -0
  43. package/recipes/input/styles.css +9 -0
  44. package/recipes/palette/styles.css +3 -0
  45. package/recipes/search/tailwind.tsx +3 -2
  46. package/recipes/select/styles.css +230 -0
  47. package/recipes/toast/styles.css +1 -1
  48. package/registry.json +358 -1
  49. package/src/core/context-menu.ts +694 -0
  50. package/src/core/keys.ts +264 -0
  51. package/src/core/search.ts +19 -0
  52. package/src/core/select.ts +404 -0
  53. package/src/core/selection.ts +648 -0
  54. package/src/index.ts +60 -1
  55. package/src/react/context-menu/context.ts +57 -0
  56. package/src/react/context-menu/index.tsx +94 -0
  57. package/src/react/context-menu/root.tsx +846 -0
  58. package/src/react/context-menu/styles.ts +186 -0
  59. package/src/react/index.ts +68 -1
  60. package/src/react/palette/root.tsx +2 -2
  61. package/src/react/select/context.ts +56 -0
  62. package/src/react/select/index.tsx +96 -0
  63. package/src/react/select/root.tsx +839 -0
  64. package/src/react/select/styles.ts +238 -0
  65. package/src/react/selection/index.tsx +115 -0
  66. package/src/react/selection/use-selection.ts +260 -0
  67. /package/dist/{chunk-U3V4EHOB.js → chunk-3HDEZ2E7.js} +0 -0
@@ -0,0 +1,466 @@
1
+ import { shortcutList, matchesShortcut } from './chunk-4VUHQFAT.js';
2
+
3
+ // src/core/selection.ts
4
+ var DEFAULT_SELECTION_SHORTCUTS = {
5
+ selectAll: "Mod+A",
6
+ clear: "Escape",
7
+ invert: false,
8
+ moveUp: "ArrowUp",
9
+ moveDown: "ArrowDown",
10
+ moveLeft: "ArrowLeft",
11
+ moveRight: "ArrowRight",
12
+ moveHome: "Home",
13
+ moveEnd: "End",
14
+ movePageUp: "PageUp",
15
+ movePageDown: "PageDown",
16
+ extendUp: "Shift+ArrowUp",
17
+ extendDown: "Shift+ArrowDown",
18
+ extendLeft: "Shift+ArrowLeft",
19
+ extendRight: "Shift+ArrowRight",
20
+ extendHome: "Shift+Home",
21
+ extendEnd: "Shift+End",
22
+ extendPageUp: "Shift+PageUp",
23
+ extendPageDown: "Shift+PageDown",
24
+ // The cursor moving WITHOUT the selection following, which is how a file manager lets you
25
+ // reach a distant row and add it with Ctrl+Space rather than starting again.
26
+ cursorUp: "Mod+ArrowUp",
27
+ cursorDown: "Mod+ArrowDown",
28
+ toggleCursor: "Mod+Space",
29
+ open: "Enter",
30
+ rename: "F2",
31
+ delete: "Delete",
32
+ deletePermanent: "Shift+Delete",
33
+ copy: "Mod+C",
34
+ cut: "Mod+X",
35
+ paste: "Mod+V"
36
+ };
37
+ var OWNED = /* @__PURE__ */ new Set([
38
+ "selectAll",
39
+ "clear",
40
+ "invert",
41
+ "moveUp",
42
+ "moveDown",
43
+ "moveLeft",
44
+ "moveRight",
45
+ "moveHome",
46
+ "moveEnd",
47
+ "movePageUp",
48
+ "movePageDown",
49
+ "extendUp",
50
+ "extendDown",
51
+ "extendLeft",
52
+ "extendRight",
53
+ "extendHome",
54
+ "extendEnd",
55
+ "extendPageUp",
56
+ "extendPageDown",
57
+ "cursorUp",
58
+ "cursorDown",
59
+ "toggleCursor"
60
+ ]);
61
+ function createSelection(options = {}) {
62
+ let opts = { ...options };
63
+ let items = opts.items ?? [];
64
+ let selected = /* @__PURE__ */ new Set();
65
+ let cursor = -1;
66
+ let anchor = -1;
67
+ let rangeBase = null;
68
+ let marqueeBase = null;
69
+ let destroyed = false;
70
+ const listeners = /* @__PURE__ */ new Set();
71
+ function idOf(index) {
72
+ const item = items[index];
73
+ if (item === void 0) return null;
74
+ return opts.getId ? opts.getId(item, index) : String(index);
75
+ }
76
+ function selectable(index) {
77
+ const item = items[index];
78
+ if (item === void 0) return false;
79
+ return !opts.disabled?.(item, index);
80
+ }
81
+ function multiple() {
82
+ return opts.multiple ?? true;
83
+ }
84
+ function columns() {
85
+ return Math.max(1, opts.columns ?? 1);
86
+ }
87
+ function orderedIds() {
88
+ const ids = [];
89
+ for (let index = 0; index < items.length; index++) {
90
+ const id = idOf(index);
91
+ if (id !== null && selected.has(id)) ids.push(id);
92
+ }
93
+ return ids;
94
+ }
95
+ function selectedItems() {
96
+ const chosen = [];
97
+ for (let index = 0; index < items.length; index++) {
98
+ const id = idOf(index);
99
+ if (id !== null && selected.has(id)) chosen.push(items[index]);
100
+ }
101
+ return chosen;
102
+ }
103
+ function snapshot() {
104
+ const ids = orderedIds();
105
+ let count = 0;
106
+ for (let index = 0; index < items.length; index++) if (selectable(index)) count++;
107
+ return {
108
+ selected: ids,
109
+ selectedSet: new Set(ids),
110
+ items: selectedItems(),
111
+ cursor,
112
+ anchor,
113
+ count: ids.length,
114
+ allSelected: count > 0 && ids.length === count,
115
+ partiallySelected: ids.length > 0 && ids.length < count,
116
+ marquee: marqueeBase !== null
117
+ };
118
+ }
119
+ function summary() {
120
+ let count = 0;
121
+ for (let index = 0; index < items.length; index++) if (selectable(index)) count++;
122
+ return `${cursor}:${anchor}:${count}:${orderedIds().join("\0")}`;
123
+ }
124
+ let reported = [];
125
+ function emit() {
126
+ const state = snapshot();
127
+ opts.onChange?.(state);
128
+ for (const listener of listeners) listener(state);
129
+ const ids = state.selected;
130
+ if (ids.length !== reported.length || ids.some((id, index) => id !== reported[index])) {
131
+ reported = ids;
132
+ opts.onSelectionChange?.(ids, state.items);
133
+ }
134
+ }
135
+ function replace(ids) {
136
+ selected = new Set(ids);
137
+ }
138
+ function nextEnabled(from, step) {
139
+ for (let index = from; index >= 0 && index < items.length; index += step) {
140
+ if (selectable(index)) return index;
141
+ }
142
+ return -1;
143
+ }
144
+ function rangeIds(from, to) {
145
+ const [low, high] = from < to ? [from, to] : [to, from];
146
+ const ids = [];
147
+ for (let index = low; index <= high; index++) {
148
+ if (!selectable(index)) continue;
149
+ const id = idOf(index);
150
+ if (id !== null) ids.push(id);
151
+ }
152
+ return ids;
153
+ }
154
+ function applyRange(index, additive) {
155
+ const from = anchor >= 0 ? anchor : index;
156
+ const ids = rangeIds(from, index);
157
+ if (additive) {
158
+ rangeBase = rangeBase ?? new Set(selected);
159
+ replace([...rangeBase, ...ids]);
160
+ } else {
161
+ rangeBase = null;
162
+ replace(ids);
163
+ }
164
+ cursor = index;
165
+ }
166
+ function table() {
167
+ if (opts.shortcuts === false) return {};
168
+ return { ...DEFAULT_SELECTION_SHORTCUTS, ...opts.shortcuts ?? {} };
169
+ }
170
+ function commandFor(event) {
171
+ const bindings = table();
172
+ let match = null;
173
+ for (const [command, spec] of Object.entries(bindings)) {
174
+ if (spec === false || spec == null) continue;
175
+ const hit = shortcutList(spec).find((shortcut) => matchesShortcut(event, shortcut));
176
+ if (!hit) continue;
177
+ const length = specificity(hit);
178
+ if (!match || length > match.length) match = { command, length };
179
+ }
180
+ return match?.command ?? null;
181
+ }
182
+ function specificity(shortcut) {
183
+ return Number(Boolean(shortcut.mod)) + Number(Boolean(shortcut.ctrl)) + Number(Boolean(shortcut.meta)) + Number(Boolean(shortcut.shift)) + Number(Boolean(shortcut.alt));
184
+ }
185
+ function perform(command) {
186
+ const step = columns();
187
+ const page = Math.max(1, opts.page ?? 10);
188
+ switch (command) {
189
+ case "selectAll":
190
+ instance.selectAll();
191
+ return true;
192
+ case "clear":
193
+ instance.clear();
194
+ return true;
195
+ case "invert":
196
+ instance.invert();
197
+ return true;
198
+ case "moveUp":
199
+ instance.moveCursor(-step);
200
+ return true;
201
+ case "moveDown":
202
+ instance.moveCursor(step);
203
+ return true;
204
+ case "moveLeft":
205
+ instance.moveCursor(-1);
206
+ return true;
207
+ case "moveRight":
208
+ instance.moveCursor(1);
209
+ return true;
210
+ case "moveHome":
211
+ instance.moveCursor(-items.length);
212
+ return true;
213
+ case "moveEnd":
214
+ instance.moveCursor(items.length);
215
+ return true;
216
+ case "movePageUp":
217
+ instance.moveCursor(-page * step);
218
+ return true;
219
+ case "movePageDown":
220
+ instance.moveCursor(page * step);
221
+ return true;
222
+ case "extendUp":
223
+ instance.moveCursor(-step, true);
224
+ return true;
225
+ case "extendDown":
226
+ instance.moveCursor(step, true);
227
+ return true;
228
+ case "extendLeft":
229
+ instance.moveCursor(-1, true);
230
+ return true;
231
+ case "extendRight":
232
+ instance.moveCursor(1, true);
233
+ return true;
234
+ case "extendHome":
235
+ instance.moveCursor(-items.length, true);
236
+ return true;
237
+ case "extendEnd":
238
+ instance.moveCursor(items.length, true);
239
+ return true;
240
+ case "extendPageUp":
241
+ instance.moveCursor(-page * step, true);
242
+ return true;
243
+ case "extendPageDown":
244
+ instance.moveCursor(page * step, true);
245
+ return true;
246
+ case "cursorUp":
247
+ instance.moveCursor(-step, false, true);
248
+ return true;
249
+ case "cursorDown":
250
+ instance.moveCursor(step, false, true);
251
+ return true;
252
+ case "toggleCursor":
253
+ if (cursor >= 0) instance.toggle(cursor);
254
+ return true;
255
+ default:
256
+ return false;
257
+ }
258
+ }
259
+ const instance = {
260
+ get state() {
261
+ return snapshot();
262
+ },
263
+ click(index, modifiers = {}) {
264
+ if (destroyed || !selectable(index)) return;
265
+ const mod = Boolean(modifiers.ctrlKey || modifiers.metaKey);
266
+ const shift = Boolean(modifiers.shiftKey) && multiple();
267
+ if (shift) {
268
+ applyRange(index, mod && multiple());
269
+ emit();
270
+ return;
271
+ }
272
+ rangeBase = null;
273
+ if (mod && multiple()) {
274
+ this.toggle(index);
275
+ return;
276
+ }
277
+ const id = idOf(index);
278
+ if (id === null) return;
279
+ replace([id]);
280
+ cursor = index;
281
+ anchor = index;
282
+ emit();
283
+ },
284
+ keyDown(event) {
285
+ if (destroyed) return false;
286
+ const command = commandFor(event);
287
+ if (!command) return false;
288
+ const targets = selected.size > 0 ? selectedItems() : cursor >= 0 && items[cursor] !== void 0 ? [items[cursor]] : [];
289
+ const ids = selected.size > 0 ? orderedIds() : cursor >= 0 ? [idOf(cursor)].filter((id) => id !== null) : [];
290
+ let prevented = false;
291
+ const report = {
292
+ command,
293
+ items: targets,
294
+ ids,
295
+ cursor: cursor >= 0 ? items[cursor] ?? null : null,
296
+ cursorIndex: cursor,
297
+ event,
298
+ preventDefault() {
299
+ prevented = true;
300
+ report.defaultPrevented = true;
301
+ },
302
+ defaultPrevented: false
303
+ };
304
+ opts.onCommand?.(report);
305
+ if (!prevented) perform(command);
306
+ return OWNED.has(command) || opts.onCommand !== void 0;
307
+ },
308
+ select(ids) {
309
+ if (destroyed) return;
310
+ replace(ids);
311
+ emit();
312
+ },
313
+ selectIndex(index, additive = false) {
314
+ if (destroyed || !selectable(index)) return;
315
+ const id = idOf(index);
316
+ if (id === null) return;
317
+ if (additive && multiple()) selected.add(id);
318
+ else replace([id]);
319
+ cursor = index;
320
+ anchor = index;
321
+ emit();
322
+ },
323
+ toggle(index) {
324
+ if (destroyed || !selectable(index)) return;
325
+ const id = idOf(index);
326
+ if (id === null) return;
327
+ if (!multiple()) {
328
+ replace([id]);
329
+ } else if (selected.has(id)) selected.delete(id);
330
+ else selected.add(id);
331
+ cursor = index;
332
+ anchor = index;
333
+ emit();
334
+ },
335
+ selectRange(index, additive = false) {
336
+ if (destroyed || !selectable(index) || !multiple()) return;
337
+ applyRange(index, additive);
338
+ emit();
339
+ },
340
+ selectAll() {
341
+ if (destroyed || !multiple()) return;
342
+ const ids = [];
343
+ for (let index = 0; index < items.length; index++) {
344
+ if (!selectable(index)) continue;
345
+ const id = idOf(index);
346
+ if (id !== null) ids.push(id);
347
+ }
348
+ replace(ids);
349
+ emit();
350
+ },
351
+ clear() {
352
+ if (destroyed || selected.size === 0) return;
353
+ selected = /* @__PURE__ */ new Set();
354
+ rangeBase = null;
355
+ emit();
356
+ },
357
+ invert() {
358
+ if (destroyed || !multiple()) return;
359
+ const next = /* @__PURE__ */ new Set();
360
+ for (let index = 0; index < items.length; index++) {
361
+ if (!selectable(index)) continue;
362
+ const id = idOf(index);
363
+ if (id !== null && !selected.has(id)) next.add(id);
364
+ }
365
+ selected = next;
366
+ emit();
367
+ },
368
+ moveCursor(delta, extend = false, carry = false) {
369
+ if (destroyed || items.length === 0) return;
370
+ const from = cursor >= 0 ? cursor : anchor >= 0 ? anchor : delta > 0 ? -1 : items.length;
371
+ const wanted = Math.max(0, Math.min(items.length - 1, from + delta));
372
+ const forward = delta >= 0 ? 1 : -1;
373
+ const ahead = nextEnabled(wanted, forward);
374
+ const next = ahead >= 0 ? ahead : nextEnabled(wanted, -forward);
375
+ if (next < 0) return;
376
+ if (extend && multiple()) {
377
+ applyRange(next, false);
378
+ emit();
379
+ return;
380
+ }
381
+ cursor = next;
382
+ if (carry) {
383
+ emit();
384
+ return;
385
+ }
386
+ const id = idOf(next);
387
+ if (id === null) return;
388
+ replace([id]);
389
+ anchor = next;
390
+ rangeBase = null;
391
+ emit();
392
+ },
393
+ setCursor(index) {
394
+ if (destroyed || index === cursor) return;
395
+ if (index < -1 || index >= items.length) return;
396
+ cursor = index;
397
+ emit();
398
+ },
399
+ targets(index) {
400
+ const id = idOf(index);
401
+ const item = items[index];
402
+ if (item === void 0) return selectedItems();
403
+ return id !== null && selected.has(id) ? selectedItems() : [item];
404
+ },
405
+ beginMarquee(additive = false) {
406
+ if (destroyed || !multiple()) return;
407
+ marqueeBase = additive ? new Set(selected) : /* @__PURE__ */ new Set();
408
+ if (!additive && selected.size > 0) selected = /* @__PURE__ */ new Set();
409
+ emit();
410
+ },
411
+ marqueeTo(indices) {
412
+ if (destroyed || marqueeBase === null) return;
413
+ const next = new Set(marqueeBase);
414
+ for (const index of indices) {
415
+ if (!selectable(index)) continue;
416
+ const id = idOf(index);
417
+ if (id !== null) next.add(id);
418
+ }
419
+ if (next.size === selected.size && [...next].every((id) => selected.has(id))) return;
420
+ selected = next;
421
+ emit();
422
+ },
423
+ endMarquee() {
424
+ if (destroyed || marqueeBase === null) return;
425
+ marqueeBase = null;
426
+ emit();
427
+ },
428
+ bindings() {
429
+ return table();
430
+ },
431
+ binding(command) {
432
+ return table()[command] ?? false;
433
+ },
434
+ update(next) {
435
+ if (destroyed) return;
436
+ const before = summary();
437
+ const hadItems = next.items !== void 0;
438
+ opts = { ...opts, ...next };
439
+ if (hadItems) {
440
+ items = next.items ?? [];
441
+ const live = /* @__PURE__ */ new Set();
442
+ for (let index = 0; index < items.length; index++) {
443
+ const id = idOf(index);
444
+ if (id !== null) live.add(id);
445
+ }
446
+ for (const id of selected) if (!live.has(id)) selected.delete(id);
447
+ if (cursor >= items.length) cursor = items.length - 1;
448
+ if (anchor >= items.length) anchor = items.length - 1;
449
+ }
450
+ if (summary() !== before) emit();
451
+ },
452
+ subscribe(listener) {
453
+ listeners.add(listener);
454
+ return () => {
455
+ listeners.delete(listener);
456
+ };
457
+ },
458
+ destroy() {
459
+ destroyed = true;
460
+ listeners.clear();
461
+ }
462
+ };
463
+ return instance;
464
+ }
465
+
466
+ export { DEFAULT_SELECTION_SHORTCUTS, createSelection };
@@ -0,0 +1,174 @@
1
+ import { F as FuseConstructor, c as SearchOptions } from './search-DYgqRp37.js';
2
+
3
+ /**
4
+ * The parts of a context menu that are not rendering: which branch is open, where the
5
+ * highlight is inside it, what a submenu's rows are once they have been fetched, and which
6
+ * of those rows survived the filter.
7
+ *
8
+ * Framework-agnostic like every other core here, and for the same reason: the arithmetic of
9
+ * a menu is not React. What IS specific to this one is that a menu is a TREE and a select is
10
+ * a list, so everything below is keyed by a PATH - the list of item ids from the root down to
11
+ * the open submenu - rather than by an index.
12
+ *
13
+ * The behaviour it copies is the Windows one, deliberately: a right-click opens it at the
14
+ * pointer, a submenu opens after a beat of hovering and closes after a beat of leaving,
15
+ * arrows walk the rows and skip everything that cannot be chosen, Right opens a submenu and
16
+ * Left goes back, and typing jumps to a row.
17
+ */
18
+
19
+ /** A row that does something. The default kind, so `type` can be left off. */
20
+ interface ContextMenuAction {
21
+ type?: "item";
22
+ /** Unique among its SIBLINGS. The path down the tree is built from these. */
23
+ id: string;
24
+ label: string;
25
+ /** A second line under the label, for a row whose consequence is not obvious. */
26
+ description?: string;
27
+ /** Anything the renderer can draw - a node, a URL, a name. `unknown` because a core cannot know. */
28
+ icon?: unknown;
29
+ /**
30
+ * Printed on the right, the way every desktop menu prints it: `"Mod+C"`, `"F2"`,
31
+ * `"Delete"`. Written by `shortcutTokens` for the platform it is rendered on, so one
32
+ * spec is `⌘C` on a Mac and `Ctrl+C` elsewhere.
33
+ */
34
+ shortcut?: string;
35
+ /** Listed, announced as unavailable, never highlighted and never invoked. */
36
+ disabled?: boolean;
37
+ /** Deletes something. Rendered in the destructive colour and reported as such. */
38
+ destructive?: boolean;
39
+ /** A checkable row: a tick, or a radio dot when `group` is set. */
40
+ checked?: boolean;
41
+ /** Rows carrying the same group render together under its name, and check as a radio set. */
42
+ group?: string;
43
+ /** Extra words the filter should match. */
44
+ keywords?: string[];
45
+ /** A heading over this row's submenu. See `ContextMenuOptions.title` for what it is for. */
46
+ title?: string;
47
+ /** A submenu, known up front. An empty list is not a submenu: the row keeps no arrow. */
48
+ items?: readonly ContextMenuEntry[];
49
+ /**
50
+ * A submenu fetched on demand, once. What it returns is cached under this row's path for
51
+ * `cacheMs`, so reopening the branch shows the rows immediately instead of spinning
52
+ * again - which is the whole reason a slow submenu is bearable.
53
+ */
54
+ loadItems?: () => Promise<readonly ContextMenuEntry[]>;
55
+ /** Put a filter in this submenu. `"auto"` (the default) adds one from `SEARCHABLE_FROM` rows. */
56
+ searchable?: boolean | "auto";
57
+ /** Anything the caller wants back in `onSelect`. */
58
+ data?: unknown;
59
+ }
60
+ /** A rule between two blocks of rows. Never highlighted, never counted by the keyboard. */
61
+ interface ContextMenuSeparator {
62
+ type: "separator";
63
+ id?: string;
64
+ }
65
+ /** A caption over a block of rows. Same rules as a separator: visible, unreachable. */
66
+ interface ContextMenuLabel {
67
+ type: "label";
68
+ id?: string;
69
+ label: string;
70
+ }
71
+ type ContextMenuEntry = ContextMenuAction | ContextMenuSeparator | ContextMenuLabel;
72
+ /** Whether an entry is a row the keyboard and the pointer can land on. */
73
+ declare function isAction(entry: ContextMenuEntry): entry is ContextMenuAction;
74
+ /** The fields the filter reads when the caller does not say. */
75
+ declare const CONTEXT_MENU_SEARCH_KEYS: string[];
76
+ /** Where a menu was opened. Viewport coordinates, which is what a pointer event reports. */
77
+ interface ContextMenuPoint {
78
+ x: number;
79
+ y: number;
80
+ }
81
+ /** One open level: the root, then one per submenu below it. */
82
+ interface ContextMenuLevel {
83
+ /** Ids from the root down to the item that opened this level. Empty for the root. */
84
+ path: string[];
85
+ /** A heading over the rows, or null. Also the panel's accessible name. */
86
+ title: string | null;
87
+ /** Every entry at this level, separators and labels included. */
88
+ entries: ContextMenuEntry[];
89
+ /** What the panel shows: the entries, or what survived the filter. */
90
+ visible: ContextMenuEntry[];
91
+ /** Index into `visible`. -1 when nothing is highlighted. */
92
+ active: number;
93
+ query: string;
94
+ /** Whether this level draws a filter field. */
95
+ searchable: boolean;
96
+ /** An awaited submenu that has not answered yet. */
97
+ loading: boolean;
98
+ /** What the load rejected with, so the level can say so instead of staying empty. */
99
+ error: Error | null;
100
+ }
101
+ interface ContextMenuState {
102
+ open: boolean;
103
+ /** Where it was opened, in viewport coordinates. Null while closed. */
104
+ point: ContextMenuPoint | null;
105
+ /** The root level first, then one per open submenu. */
106
+ levels: ContextMenuLevel[];
107
+ }
108
+ interface ContextMenuOptions {
109
+ items?: readonly ContextMenuEntry[];
110
+ /**
111
+ * A heading over the rows, naming what the menu is acting ON - the file that was
112
+ * right-clicked, or "12 items selected". A menu opened at the pointer is the one surface
113
+ * with no context around it, so without this the rows are the only clue about what they
114
+ * will happen to.
115
+ */
116
+ title?: string;
117
+ /** Fuse.js's constructor, for fuzzy filtering. Omit it for the built-in matcher. */
118
+ fuse?: FuseConstructor;
119
+ fuseOptions?: Record<string, unknown>;
120
+ matcher?: SearchOptions<ContextMenuEntry>["matcher"];
121
+ searchKeys?: string[];
122
+ /** How long a fetched submenu stays cached. 0 refetches every time. Default 5 minutes. */
123
+ cacheMs?: number;
124
+ /** Every state change. */
125
+ onChange?: (state: ContextMenuState) => void;
126
+ /** A row was invoked. The menu has already closed unless the row keeps it open. */
127
+ onSelect?: (item: ContextMenuAction, path: string[]) => void;
128
+ onOpenChange?: (open: boolean) => void;
129
+ }
130
+ interface ContextMenuInstance {
131
+ readonly state: ContextMenuState;
132
+ /**
133
+ * Open at a point, or reopen somewhere else - a second right-click MOVES the menu.
134
+ *
135
+ * Returns whether it opened. A menu with no rows to show does NOT open: an empty box at
136
+ * the pointer says nothing, and refusing here is what lets the trigger leave the press
137
+ * alone so the browser's own menu appears instead of nothing at all.
138
+ */
139
+ open(point: ContextMenuPoint): boolean;
140
+ close(): void;
141
+ /** Move the highlight inside the deepest open level, skipping what cannot be chosen. */
142
+ move(key: ContextMenuMoveKey): void;
143
+ setActive(level: number, index: number): void;
144
+ setQuery(level: number, query: string): void;
145
+ /**
146
+ * Open the submenu of the row at `index` in `level`, closing any deeper branch.
147
+ *
148
+ * `focus` highlights its first row, which is what the KEYBOARD needs and what a pointer
149
+ * must not do: a row that looks hovered before the pointer arrives reads as the menu
150
+ * having chosen for you. Default false, so hovering is the plain case.
151
+ */
152
+ openSubmenu(level: number, index: number, focus?: boolean): void;
153
+ /** Close every level below `level`. */
154
+ closeBelow(level: number): void;
155
+ /** Invoke a row: reports it, and closes unless it is disabled or opens a submenu. */
156
+ select(level: number, index: number): void;
157
+ /** Invoke whatever is highlighted in the deepest open level. */
158
+ selectActive(): void;
159
+ /** Right on a row with a submenu opens it; on any other row it does nothing. */
160
+ enterSubmenu(): void;
161
+ /** Left closes the deepest submenu and puts the highlight back on the row that opened it. */
162
+ leaveSubmenu(): void;
163
+ /** Jump to the row starting with what was typed, the way a desktop menu does. */
164
+ typeahead(character: string): void;
165
+ /** Drop what a fetched submenu returned, so the next open asks again. */
166
+ invalidate(path?: string[]): void;
167
+ update(options: Partial<ContextMenuOptions>): void;
168
+ subscribe(listener: (state: ContextMenuState) => void): () => void;
169
+ destroy(): void;
170
+ }
171
+ type ContextMenuMoveKey = "ArrowDown" | "ArrowUp" | "Home" | "End" | "PageDown" | "PageUp";
172
+ declare function createContextMenu(options?: ContextMenuOptions): ContextMenuInstance;
173
+
174
+ export { CONTEXT_MENU_SEARCH_KEYS as C, type ContextMenuAction as a, type ContextMenuEntry as b, type ContextMenuInstance as c, type ContextMenuLabel as d, type ContextMenuLevel as e, type ContextMenuMoveKey as f, type ContextMenuOptions as g, type ContextMenuPoint as h, type ContextMenuSeparator as i, type ContextMenuState as j, createContextMenu as k, isAction as l };
@@ -1,5 +1,5 @@
1
1
  import { ReactNode, ComponentPropsWithoutRef } from 'react';
2
- import { c as SearchOptions, F as FuseConstructor, b as SearchMatch } from './search-BD9-5O5U.js';
2
+ import { c as SearchOptions, F as FuseConstructor, b as SearchMatch } from './search-DYgqRp37.js';
3
3
  import { G as GeneratePasswordOptions, E as EstimateOptions, b as PasswordStrengthReport } from './password-C8lG4Zm9.js';
4
4
 
5
5
  /**
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { M as MarqueeHover, a as MarqueeInstance, b as MarqueeOptions, c as createMarquee } from './marquee-CJ3Uwy3E.js';
2
2
  export { I as InputAction, a as InputActionState, b as InputIcon, c as InputInstance, d as InputOptions, e as createInput } from './input-BwXjFenq.js';
3
- export { F as FuseConstructor, a as FuseLike, S as SearchInstance, b as SearchMatch, c as SearchOptions, d as createSearch, s as subsequenceMatcher } from './search-BD9-5O5U.js';
3
+ export { F as FuseConstructor, a as FuseLike, S as SearchInstance, b as SearchMatch, c as SearchOptions, d as createSearch, s as shortenQuery, e as subsequenceMatcher } from './search-DYgqRp37.js';
4
4
  export { B as ButtonCooldown, a as ButtonElement, b as ButtonInstance, c as ButtonOptions, d as ButtonState, e as createButton } from './button-CaXaqG_K.js';
5
5
  export { E as EstimateOptions, G as GeneratePasswordOptions, P as PasswordAlphabet, a as PasswordScore, b as PasswordStrengthReport, e as estimatePasswordStrength, g as generatePassword } from './password-C8lG4Zm9.js';
6
6
  export { N as Notification, a as NotificationAction, b as NotificationInput, c as NotificationTone, d as Notifications, e as NotificationsOptions, P as PromiseMessages, f as createNotifications } from './notifications-BpVV6sel.js';
@@ -8,6 +8,10 @@ export { N as NetworkMonitor, a as NetworkState, S as SERVER_NETWORK_STATE, c as
8
8
  export { R as RelativeTimeFormat, a as RelativeTimeOptions, b as RelativeTimePrecision, c as RelativeTimeStyle, d as RelativeTimeTense, e as RelativeTimeView, f as ensureZone, n as normalizeDate, p as parseDuration, r as relativeTimeAttributes, g as relativeTimeView } from './relative-time-YpRTG7YH.js';
9
9
  export { F as FLAG_CDN, a as FLAG_RATIO, b as FlagConfig, c as FlagFormat, d as FlagOptions, e as FlagShape, f as FlagSource, g as FlagView, h as configureFlags, i as flagAttributes, j as flagConfig, k as flagName, l as flagSrc, m as flagView, n as normalizeFlagCode, r as resetFlagConfig } from './flags-BQU1V9rE.js';
10
10
  export { P as PaletteKey, a as PositionedRow, R as RecentEntry, b as RecentStore, c as RecentStoreOptions, d as RowGroup, e as createRecentStore, g as groupRows, i as isPaletteShortcut, m as moveActive, r as recentKey, s as shortcutLabel } from './palette-D7iuQh_T.js';
11
+ export { S as SELECT_SEARCH_KEYS, a as SelectInstance, b as SelectMoveKey, c as SelectOption, d as SelectOptions, e as SelectState, f as createSelect } from './select-ClSy-J1f.js';
12
+ export { C as CONTEXT_MENU_SEARCH_KEYS, a as ContextMenuAction, b as ContextMenuEntry, c as ContextMenuInstance, d as ContextMenuLabel, e as ContextMenuLevel, f as ContextMenuMoveKey, g as ContextMenuOptions, h as ContextMenuPoint, i as ContextMenuSeparator, j as ContextMenuState, k as createContextMenu, l as isAction } from './context-menu-D3FtTn7v.js';
13
+ export { D as DEFAULT_SELECTION_SHORTCUTS, S as SelectionClickModifiers, a as SelectionCommand, b as SelectionCommandEvent, c as SelectionCommandName, d as SelectionInstance, e as SelectionOptions, f as SelectionShortcuts, g as SelectionState, h as createSelection } from './selection-B_pmzHpy.js';
14
+ export { S as Shortcut, a as ShortcutEvent, b as ShortcutSpec, T as TYPEAHEAD_MS, c as TypeaheadState, d as TypeaheadStep, i as isApplePlatform, m as matchesShortcut, p as parseShortcut, s as shortcutList, e as shortcutText, f as shortcutTokens, t as typeaheadStep } from './keys-D2zJs1uB.js';
11
15
 
12
16
  /**
13
17
  * The icon shapes both renderers draw.
package/dist/index.js CHANGED
@@ -1,11 +1,15 @@
1
1
  export { createInput } from './chunk-KJINGUQN.js';
2
+ export { ensureZone, normalizeDate, parseDuration, relativeTimeAttributes, relativeTimeView } from './chunk-IXVMRVD4.js';
2
3
  export { estimatePasswordStrength, generatePassword } from './chunk-HC2ME5PU.js';
4
+ export { CONTEXT_MENU_SEARCH_KEYS, createContextMenu, isAction } from './chunk-3BQVOOAM.js';
5
+ export { DEFAULT_SELECTION_SHORTCUTS, createSelection } from './chunk-WSQC3PCC.js';
6
+ export { createButton } from './chunk-MMQPZGSU.js';
3
7
  export { FLAG_CDN, FLAG_RATIO, configureFlags, flagAttributes, flagConfig, flagName, flagSrc, flagView, normalizeFlagCode, resetFlagConfig } from './chunk-LMZJBOZG.js';
8
+ export { createNotifications } from './chunk-SNYUBXWQ.js';
4
9
  export { createMarquee } from './chunk-UOSSNUSC.js';
5
10
  export { SERVER_NETWORK_STATE, createNetworkMonitor } from './chunk-2QFTRNAZ.js';
6
- export { createNotifications } from './chunk-SNYUBXWQ.js';
7
- export { ensureZone, normalizeDate, parseDuration, relativeTimeAttributes, relativeTimeView } from './chunk-IXVMRVD4.js';
8
11
  export { INPUT_ICON_PATHS, iconMarkup } from './chunk-F25CGNQC.js';
12
+ export { SELECT_SEARCH_KEYS, createSelect } from './chunk-NONGREXC.js';
13
+ export { TYPEAHEAD_MS, isApplePlatform, matchesShortcut, parseShortcut, shortcutList, shortcutText, shortcutTokens, typeaheadStep } from './chunk-4VUHQFAT.js';
9
14
  export { createRecentStore, groupRows, isPaletteShortcut, moveActive, recentKey, shortcutLabel } from './chunk-DTWZDONY.js';
10
- export { createSearch, subsequenceMatcher } from './chunk-D5A2ZMAG.js';
11
- export { createButton } from './chunk-MMQPZGSU.js';
15
+ export { createSearch, shortenQuery, subsequenceMatcher } from './chunk-4ZPMP47J.js';