@ai-matrx/context-menu 0.0.0 → 0.1.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.
Files changed (45) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +14 -1
  3. package/dist/builder.cjs +1810 -0
  4. package/dist/builder.cjs.map +1 -0
  5. package/dist/builder.d.cts +22 -0
  6. package/dist/builder.d.ts +22 -0
  7. package/dist/builder.js +1795 -0
  8. package/dist/builder.js.map +1 -0
  9. package/dist/design-store.cjs +195 -0
  10. package/dist/design-store.cjs.map +1 -0
  11. package/dist/design-store.d.cts +42 -0
  12. package/dist/design-store.d.ts +42 -0
  13. package/dist/design-store.js +175 -0
  14. package/dist/design-store.js.map +1 -0
  15. package/dist/engine.cjs +3069 -0
  16. package/dist/engine.cjs.map +1 -0
  17. package/dist/engine.d.cts +731 -0
  18. package/dist/engine.d.ts +731 -0
  19. package/dist/engine.js +3250 -0
  20. package/dist/engine.js.map +1 -0
  21. package/dist/grouping-CB3zlD1T.d.ts +623 -0
  22. package/dist/grouping-CGcWsltu.d.cts +623 -0
  23. package/dist/host-types-AOiEYwR8.d.cts +9 -0
  24. package/dist/host-types-AOiEYwR8.d.ts +9 -0
  25. package/dist/index.cjs +583 -0
  26. package/dist/index.cjs.map +1 -0
  27. package/dist/index.d.cts +60 -0
  28. package/dist/index.d.ts +60 -0
  29. package/dist/index.js +561 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/menu-content.cjs +3146 -0
  32. package/dist/menu-content.cjs.map +1 -0
  33. package/dist/menu-content.d.cts +30 -0
  34. package/dist/menu-content.d.ts +30 -0
  35. package/dist/menu-content.js +3197 -0
  36. package/dist/menu-content.js.map +1 -0
  37. package/dist/model-CkLCR6WY.d.cts +123 -0
  38. package/dist/model-CkLCR6WY.d.ts +123 -0
  39. package/dist/react.cjs +3322 -0
  40. package/dist/react.cjs.map +1 -0
  41. package/dist/react.d.cts +133 -0
  42. package/dist/react.d.ts +133 -0
  43. package/dist/react.js +3345 -0
  44. package/dist/react.js.map +1 -0
  45. package/package.json +170 -1
package/dist/index.js ADDED
@@ -0,0 +1,561 @@
1
+ "use client";
2
+
3
+ // src/design/model.ts
4
+ var MENU_SURFACES = [
5
+ "text-selection",
6
+ "editable-field",
7
+ "table-row",
8
+ "page",
9
+ "chat-message",
10
+ "file",
11
+ "anywhere"
12
+ ];
13
+ var DEFAULT_LOOK = {
14
+ density: "comfortable",
15
+ width: 280,
16
+ showIcons: true,
17
+ showShortcuts: true,
18
+ showHints: false,
19
+ search: false
20
+ };
21
+ var DESIGN_FORMAT = 1;
22
+ function newId(prefix = "n") {
23
+ const rnd = typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID().slice(0, 8) : Math.random().toString(36).slice(2, 10);
24
+ return `${prefix}_${rnd}`;
25
+ }
26
+ function emptyDesign(name = "Untitled menu") {
27
+ return { designId: newId("d"), name, surface: "anywhere", look: { ...DEFAULT_LOOK }, nodes: [], notes: "" };
28
+ }
29
+ function isContainer(node) {
30
+ return node.kind === "section" || node.kind === "submenu";
31
+ }
32
+ function placedCatalogIds(nodes, out = /* @__PURE__ */ new Set()) {
33
+ for (const n of nodes) {
34
+ if (n.catalogId) out.add(n.catalogId);
35
+ if (isContainer(n)) placedCatalogIds(n.children, out);
36
+ }
37
+ return out;
38
+ }
39
+ function catalogUseCounts(nodes, out = /* @__PURE__ */ new Map()) {
40
+ for (const n of nodes) {
41
+ if (n.catalogId) out.set(n.catalogId, (out.get(n.catalogId) ?? 0) + 1);
42
+ if (isContainer(n)) catalogUseCounts(n.children, out);
43
+ }
44
+ return out;
45
+ }
46
+ function cloneNode(node) {
47
+ const copy = { ...node, id: newId(node.kind[0]) };
48
+ if (isContainer(copy)) copy.children = copy.children.map(cloneNode);
49
+ return copy;
50
+ }
51
+ function locate(nodes, id, parentId = null, depth = 0) {
52
+ for (let i = 0; i < nodes.length; i++) {
53
+ const node = nodes[i];
54
+ if (node.id === id) return { node, parentId, index: i, depth };
55
+ if (isContainer(node)) {
56
+ const inner = locate(node.children, id, node.id, depth + 1);
57
+ if (inner) return inner;
58
+ }
59
+ }
60
+ return null;
61
+ }
62
+ function contains(node, id) {
63
+ if (node.id === id) return true;
64
+ return isContainer(node) && node.children.some((c) => contains(c, id));
65
+ }
66
+ function countItems(nodes) {
67
+ let n = 0;
68
+ for (const node of nodes) {
69
+ if (node.kind === "item" || node.kind === "slot") n += 1;
70
+ else if (isContainer(node)) n += countItems(node.children) + (node.kind === "submenu" ? 1 : 0);
71
+ }
72
+ return n;
73
+ }
74
+ function mapChildren(nodes, parentId, fn) {
75
+ if (parentId === null) return fn([...nodes]);
76
+ return nodes.map((node) => {
77
+ if (!isContainer(node)) return node;
78
+ if (node.id === parentId) return { ...node, children: fn([...node.children]) };
79
+ return { ...node, children: mapChildren(node.children, parentId, fn) };
80
+ });
81
+ }
82
+ function insertNode(nodes, parentId, index, node) {
83
+ return mapChildren(nodes, parentId, (list) => {
84
+ list.splice(Math.max(0, Math.min(index, list.length)), 0, node);
85
+ return list;
86
+ });
87
+ }
88
+ function removeNode(nodes, id) {
89
+ return nodes.filter((n) => n.id !== id).map((n) => isContainer(n) ? { ...n, children: removeNode(n.children, id) } : n);
90
+ }
91
+ function updateNode(nodes, id, patch) {
92
+ return nodes.map((n) => {
93
+ if (n.id === id) return { ...n, ...patch, id: n.id, kind: n.kind };
94
+ if (isContainer(n)) return { ...n, children: updateNode(n.children, id, patch) };
95
+ return n;
96
+ });
97
+ }
98
+ function canMove(nodes, id, parentId) {
99
+ const from = locate(nodes, id);
100
+ return Boolean(from) && (parentId === null || !contains(from.node, parentId));
101
+ }
102
+ function moveNode(nodes, id, parentId, index) {
103
+ const from = locate(nodes, id);
104
+ if (!from) return nodes;
105
+ if (parentId !== null && contains(from.node, parentId)) return nodes;
106
+ let target = index;
107
+ if (from.parentId === parentId && from.index < index) target -= 1;
108
+ return insertNode(removeNode(nodes, id), parentId, target, from.node);
109
+ }
110
+ function duplicateNode(nodes, id) {
111
+ const at = locate(nodes, id);
112
+ if (!at) return [...nodes];
113
+ return insertNode(nodes, at.parentId, at.index + 1, cloneNode(at.node));
114
+ }
115
+ function wrapNode(nodes, id, kind) {
116
+ const at = locate(nodes, id);
117
+ if (!at) return [...nodes];
118
+ const wrapper = kind === "section" ? { id: newId("s"), kind: "section", label: "New section", children: [at.node] } : { id: newId("m"), kind: "submenu", label: "More", icon: "ChevronRight", children: [at.node] };
119
+ return insertNode(removeNode(nodes, id), at.parentId, at.index, wrapper);
120
+ }
121
+ function unwrapNode(nodes, id) {
122
+ const at = locate(nodes, id);
123
+ if (!at || !isContainer(at.node)) return [...nodes];
124
+ let out = removeNode(nodes, id);
125
+ at.node.children.forEach((child, i) => {
126
+ out = insertNode(out, at.parentId, at.index + i, child);
127
+ });
128
+ return out;
129
+ }
130
+ function parseDesign(raw) {
131
+ let value = raw;
132
+ if (typeof raw === "string") {
133
+ try {
134
+ value = JSON.parse(raw);
135
+ } catch {
136
+ return emptyDesign("Unreadable design");
137
+ }
138
+ }
139
+ const v = value ?? {};
140
+ const d = v.design ?? v;
141
+ return {
142
+ designId: typeof d.designId === "string" ? d.designId : newId("d"),
143
+ name: typeof d.name === "string" ? d.name : "Untitled menu",
144
+ surface: MENU_SURFACES.includes(d.surface) ? d.surface : "anywhere",
145
+ look: { ...DEFAULT_LOOK, ...d.look ?? {} },
146
+ nodes: Array.isArray(d.nodes) ? d.nodes : [],
147
+ notes: typeof d.notes === "string" ? d.notes : ""
148
+ };
149
+ }
150
+ function serializeDesign(design) {
151
+ return JSON.stringify({ format: DESIGN_FORMAT, design });
152
+ }
153
+
154
+ // src/design/catalog.ts
155
+ var o = (id, label, icon, group, extra = {}) => ({ id, label, icon, group, status: "live", ...extra });
156
+ var CATALOG = [
157
+ // Clipboard — the verb strip
158
+ o("cm:copy", "Copy", "Copy", "Clipboard", { shortcut: "\u2318C" }),
159
+ o("cm:cut", "Cut", "Scissors", "Clipboard", { shortcut: "\u2318X" }),
160
+ o("cm:paste", "Paste", "ClipboardPaste", "Clipboard", { shortcut: "\u2318V" }),
161
+ o("cm:undo", "Undo", "Undo2", "Clipboard", { shortcut: "\u2318Z" }),
162
+ o("cm:redo", "Redo", "Redo2", "Clipboard", { shortcut: "\u21E7\u2318Z" }),
163
+ o("cm:find", "Find & Replace", "Search", "Clipboard", { shortcut: "\u2318F" }),
164
+ o("cm:select-all", "Select All", "TextSelect", "Clipboard", { shortcut: "\u2318A" }),
165
+ o("cm:copy-as", "Copy as\u2026", "CopyPlus", "Clipboard"),
166
+ o("cm:json", "Copy as JSON", "Braces", "Clipboard"),
167
+ o("copy-html-page", "Copy as HTML page", "FileCode", "Clipboard"),
168
+ o("cm:insert-reference", "Insert / Copy reference\u2026", "AtSign", "Clipboard"),
169
+ // Edit
170
+ o("cm:save", "Save", "Save", "Edit", { shortcut: "\u2318S" }),
171
+ o("duplicate", "Duplicate", "CopyPlus", "Edit"),
172
+ o("rename", "Rename", "PenLine", "Edit"),
173
+ o("edit", "Edit", "Pencil", "Edit"),
174
+ o("favorite", "Add to favorites", "Star", "Edit"),
175
+ o("archive", "Archive", "Archive", "Edit", { danger: true }),
176
+ o("cm:delete", "Delete", "Trash2", "Edit", { danger: true }),
177
+ // AI
178
+ o("cm:chat", "Chat about this", "MessageSquare", "AI", { ai: true }),
179
+ o("cm:quick-actions", "Quick Actions", "Zap", "AI", { ai: true }),
180
+ o("cm:placement:ai-action", "AI Actions", "Sparkles", "AI", { ai: true }),
181
+ o("cm:placement:content-block", "Content blocks", "Blocks", "AI", { ai: true }),
182
+ o("cm:placement:organization-tool", "Org Items", "Building2", "AI", { ai: true }),
183
+ o("cm:placement:user-tool", "My Items", "User", "AI", { ai: true }),
184
+ o("cm:placement:quick-action", "Quick actions (placement)", "Zap", "AI", { ai: true }),
185
+ o("cm:placement:bound-agent", "Agents", "Bot", "AI", { ai: true }),
186
+ o("send-to-agent", "Send to agent\u2026", "Send", "AI", { ai: true }),
187
+ o("continue-in-chat", "Continue in chat", "MessagesSquare", "AI", { ai: true }),
188
+ o("regenerate-response", "Regenerate", "RefreshCw", "AI", { ai: true }),
189
+ o("code-block-chart", "Make a chart", "ChartColumn", "AI", { ai: true }),
190
+ o("ai-improve", "Improve writing", "Wand2", "AI", { ai: true, status: "proposed" }),
191
+ o("ai-summarize", "Summarize", "ListCollapse", "AI", { ai: true, status: "proposed" }),
192
+ o("ai-explain", "Explain", "Lightbulb", "AI", { ai: true, status: "proposed" }),
193
+ o("ai-translate", "Translate\u2026", "Languages", "AI", { ai: true, status: "proposed" }),
194
+ // Save & export
195
+ o("cm:export", "Export", "FileOutput", "Save & export"),
196
+ o("cm:convert", "Convert to\u2026", "ArrowRightLeft", "Save & export"),
197
+ o("save-as-file", "Save as file", "FileDown", "Save & export"),
198
+ o("download-pdf", "Download PDF", "FileText", "Save & export"),
199
+ o("download-docx", "Download Word", "FileType", "Save & export"),
200
+ o("download-html", "Download HTML", "FileCode2", "Save & export"),
201
+ o("print", "Print", "Printer", "Save & export", { shortcut: "\u2318P" }),
202
+ // Share & publish
203
+ o("cm:share", "Share\u2026", "Share2", "Share & publish"),
204
+ o("share-link", "Copy share link", "Link", "Share & publish"),
205
+ o("send-google-doc", "Send to Google Docs", "FileUp", "Share & publish"),
206
+ o("email-to-me", "Email to me", "Mail", "Share & publish"),
207
+ o("html-preview", "Preview as web page", "Eye", "Share & publish"),
208
+ o("share-webpage", "Publish as web page", "Globe", "Share & publish"),
209
+ // Organize
210
+ o("cm:attach", "Attach to\u2026", "Paperclip", "Organize"),
211
+ o("set-context-value", "Set as context", "Pin", "Organize"),
212
+ o("pin-message", "Pin", "Pin", "Organize"),
213
+ o("notes-and-comments", "Notes & comments", "StickyNote", "Organize"),
214
+ o("move", "Move to folder\u2026", "FolderInput", "Organize"),
215
+ o("tags", "Tags\u2026", "Tags", "Organize"),
216
+ // History & compare
217
+ o("cm:view-history", "View history", "History", "History & compare"),
218
+ o("edit-history", "Edit history", "GitCommitVertical", "History & compare"),
219
+ o("fork-at-message", "Fork from here", "GitBranch", "History & compare"),
220
+ o("cm:compare", "Compare\u2026", "GitCompareArrows", "History & compare"),
221
+ o("compare-with-clipboard", "Compare with clipboard", "ClipboardList", "History & compare"),
222
+ o("set-compare-base", "Set as compare base", "Flag", "History & compare"),
223
+ // Listen
224
+ o("cm:speak", "Read aloud", "Volume2", "Listen"),
225
+ o("cm:listen", "Dictate", "Mic", "Listen"),
226
+ o("tts-voice-settings", "Voice settings", "AudioLines", "Listen"),
227
+ // Page rows: notes
228
+ o("notes:close-tab", "Close tab", "X", "Page: notes"),
229
+ o("notes:close-others", "Close other tabs", "PanelTopClose", "Page: notes"),
230
+ o("notes:export-md", "Export as Markdown", "FileDown", "Page: notes"),
231
+ o("notes:share-clipboard", "Copy to clipboard", "ClipboardCopy", "Page: notes"),
232
+ o("notes:convert-artifacts", "Convert to artifacts", "Shapes", "Page: notes"),
233
+ o("notes:trash", "Move to Trash", "Trash2", "Page: notes", { danger: true }),
234
+ // Page rows: a record (quiz row, table row)
235
+ o("row:open", "Open", "SquareArrowOutUpRight", "Page: record"),
236
+ o("row:open-tab", "Open in new tab", "ExternalLink", "Page: record"),
237
+ o("row:take", "Take", "Play", "Page: record"),
238
+ o("row:results", "Results", "ChartBar", "Page: record"),
239
+ o("row:manage", "Manage", "Settings2", "Page: record"),
240
+ o("row:restore", "Restore", "ArchiveRestore", "Page: record"),
241
+ // Page rows: chat message
242
+ o("regenerate-latest", "Regenerate latest", "RotateCcw", "Page: chat message", { ai: true }),
243
+ o("fork-and-regenerate", "Fork and regenerate", "GitFork", "Page: chat message", { ai: true }),
244
+ o("feedback-up", "Good response", "ThumbsUp", "Page: chat message"),
245
+ o("feedback-down", "Bad response", "ThumbsDown", "Page: chat message"),
246
+ // Tools & admin
247
+ o("cm:x:surface", "This page (location, context, agents)", "MapPin", "Tools & admin"),
248
+ o("cm:admin", "Admin tools", "Shield", "Tools & admin"),
249
+ o("feedback", "Send feedback", "MessageSquareWarning", "Tools & admin"),
250
+ o("keyboard-shortcuts", "Keyboard shortcuts", "Keyboard", "Tools & admin", { status: "proposed" })
251
+ ];
252
+ var CATALOG_GROUPS = [...new Set(CATALOG.map((c) => c.group))];
253
+ function catalogOption(id) {
254
+ return CATALOG.find((c) => c.id === id);
255
+ }
256
+ var seq = 0;
257
+ var tid = (p) => `${p}_t${Date.now().toString(36)}${(seq++).toString(36)}`;
258
+ function itemFrom(id) {
259
+ const c = catalogOption(id);
260
+ return {
261
+ id: tid("i"),
262
+ kind: "item",
263
+ catalogId: id,
264
+ label: c?.label ?? id,
265
+ icon: c?.icon ?? "Circle",
266
+ ...c?.shortcut ? { shortcut: c.shortcut } : {},
267
+ ...c?.danger ? { danger: true } : {},
268
+ ...c?.ai ? { ai: true } : {}
269
+ };
270
+ }
271
+ var strip = (label, ids) => ({
272
+ id: tid("s"),
273
+ kind: "section",
274
+ label,
275
+ layout: "row",
276
+ children: ids.map(itemFrom)
277
+ });
278
+ var sub = (label, icon, ids) => ({
279
+ id: tid("m"),
280
+ kind: "submenu",
281
+ label,
282
+ icon,
283
+ children: ids.map(itemFrom)
284
+ });
285
+ var TEMPLATES = [
286
+ { id: "strip-thing", label: "Icon strip \u2014 a thing", icon: "Rows2", build: () => strip("", ["cm:copy", "cm:share", "duplicate", "favorite", "save-as-file", "archive"]) },
287
+ { id: "strip-edit", label: "Icon strip \u2014 editable text", icon: "Rows2", build: () => strip("", ["cm:cut", "cm:copy", "cm:paste", "cm:undo", "cm:redo", "cm:find"]) },
288
+ { id: "strip-read", label: "Icon strip \u2014 read-only text", icon: "Rows2", build: () => strip("", ["cm:copy", "cm:speak", "cm:find", "cm:share"]) },
289
+ { id: "sub-intelligence", label: "Intelligence \u25B8", icon: "Sparkles", build: () => sub("Intelligence", "Sparkles", ["cm:chat", "cm:placement:ai-action", "cm:placement:bound-agent", "send-to-agent"]) },
290
+ { id: "sub-save-share", label: "Save & share \u25B8", icon: "Share2", build: () => sub("Save & share", "Share2", ["cm:copy-as", "save-as-file", "cm:convert", "share-webpage", "cm:share"]) },
291
+ { id: "sub-organize", label: "Organize \u25B8", icon: "Tags", build: () => sub("Organize", "Tags", ["cm:attach", "pin-message", "cm:view-history", "cm:compare"]) },
292
+ { id: "sub-tools", label: "Tools \u25B8", icon: "Wrench", build: () => sub("Tools", "Wrench", ["cm:speak", "cm:quick-actions", "feedback", "cm:x:surface", "cm:admin"]) }
293
+ ];
294
+ var SLOT_SOURCES = [
295
+ { id: "any", label: "Everything not placed", phrase: "Everything else", icon: "Ellipsis" },
296
+ { id: "agents", label: "Agents & shortcuts (library)", phrase: "All other agents", icon: "Bot", runtime: true },
297
+ { id: "page-rows", label: "This page's own rows", phrase: "All of this page's actions", icon: "PanelTop", runtime: true },
298
+ ...CATALOG_GROUPS.map((g) => ({
299
+ id: `group:${g}`,
300
+ label: g,
301
+ phrase: g === "AI" ? "All other AI items" : `All other ${g.toLowerCase()} options`,
302
+ icon: CATALOG.find((c) => c.group === g)?.icon ?? "Circle"
303
+ }))
304
+ ];
305
+ function slotSource(id) {
306
+ return SLOT_SOURCES.find((s) => s.id === id);
307
+ }
308
+ function slotOptions(fills, placed) {
309
+ if (fills === "agents" || fills === "page-rows") return [];
310
+ const pool = fills === "any" ? CATALOG : CATALOG.filter((c) => `group:${c.group}` === fills);
311
+ return pool.filter((c) => !placed.has(c.id));
312
+ }
313
+
314
+ // src/design/MenuPreview.tsx
315
+ import { useMemo, useState } from "react";
316
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
317
+ function matches(node, q) {
318
+ if (!q) return true;
319
+ if (node.kind === "divider") return false;
320
+ if (node.kind === "item" || node.kind === "slot") return node.label.toLowerCase().includes(q);
321
+ return node.label.toLowerCase().includes(q) || node.children.some((c) => matches(c, q));
322
+ }
323
+ function flatten(nodes, q, out = []) {
324
+ for (const n of nodes) {
325
+ if (n.kind === "item" && matches(n, q)) out.push(n);
326
+ else if (isContainer(n)) flatten(n.children, q, out);
327
+ }
328
+ return out;
329
+ }
330
+ function MenuPreview({ design, renderIcon, highlightId, onSelectNode, onPick, className, style }) {
331
+ const [query, setQuery] = useState("");
332
+ const q = query.trim().toLowerCase();
333
+ const { look } = design;
334
+ const results = useMemo(() => q ? flatten(design.nodes, q) : null, [design.nodes, q]);
335
+ const placed = useMemo(() => placedCatalogIds(design.nodes), [design.nodes]);
336
+ const ctx = { design, renderIcon, highlightId, onSelectNode, onPick, placed };
337
+ return /* @__PURE__ */ jsxs(
338
+ "div",
339
+ {
340
+ role: "menu",
341
+ "data-density": look.density,
342
+ className: `cm-menu ${className ?? ""}`,
343
+ style: { width: look.width, ...style },
344
+ children: [
345
+ look.search && /* @__PURE__ */ jsxs("div", { className: "cm-search", children: [
346
+ renderIcon("Search", "cm-icon"),
347
+ /* @__PURE__ */ jsx("input", { value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Type to filter\u2026", "aria-label": "Filter menu" })
348
+ ] }),
349
+ results ? results.length === 0 ? /* @__PURE__ */ jsx("div", { className: "cm-empty", children: "No matches" }) : results.map((n) => /* @__PURE__ */ jsx(Item, { node: n, ctx }, n.id)) : design.nodes.length === 0 ? /* @__PURE__ */ jsx("div", { className: "cm-empty", children: "Empty menu \u2014 drag options in" }) : /* @__PURE__ */ jsx(Nodes, { nodes: design.nodes, ctx })
350
+ ]
351
+ }
352
+ );
353
+ }
354
+ function Nodes({ nodes, ctx }) {
355
+ return /* @__PURE__ */ jsx(Fragment, { children: nodes.map((n) => {
356
+ if (n.kind === "divider") return /* @__PURE__ */ jsx("div", { role: "separator", className: "cm-divider" }, n.id);
357
+ if (n.kind === "item") return /* @__PURE__ */ jsx(Item, { node: n, ctx }, n.id);
358
+ if (n.kind === "submenu") return /* @__PURE__ */ jsx(Submenu, { node: n, ctx }, n.id);
359
+ if (n.kind === "slot") return /* @__PURE__ */ jsx(Slot, { node: n, ctx }, n.id);
360
+ return /* @__PURE__ */ jsx(Section, { node: n, ctx }, n.id);
361
+ }) });
362
+ }
363
+ function Section({ node, ctx }) {
364
+ const [open, setOpen] = useState(!node.collapsible);
365
+ const layout = node.layout ?? "list";
366
+ return /* @__PURE__ */ jsxs("div", { className: `cm-section ${ctx.highlightId === node.id ? "cm-hl" : ""}`, "data-layout": layout, children: [
367
+ node.label && // ui-exception: a menu row of the menu component itself
368
+ /* @__PURE__ */ jsxs(
369
+ "button",
370
+ {
371
+ type: "button",
372
+ className: "cm-section-label",
373
+ onClick: () => {
374
+ ctx.onSelectNode?.(node.id);
375
+ if (node.collapsible) setOpen((o2) => !o2);
376
+ },
377
+ children: [
378
+ node.label,
379
+ node.collapsible && /* @__PURE__ */ jsx("span", { className: "cm-caret", children: open ? "\u25BE" : "\u25B8" })
380
+ ]
381
+ }
382
+ ),
383
+ open && /* @__PURE__ */ jsx("div", { className: "cm-section-body", children: layout === "list" ? /* @__PURE__ */ jsx(Nodes, { nodes: node.children, ctx }) : node.children.map(
384
+ (c) => c.kind === "item" ? /* @__PURE__ */ jsx(Tile, { node: c, ctx }, c.id) : /* @__PURE__ */ jsx(Nodes, { nodes: [c], ctx }, c.id)
385
+ ) })
386
+ ] });
387
+ }
388
+ function Tile({ node, ctx }) {
389
+ return (
390
+ // ui-exception: a menu row of the menu component itself
391
+ /* @__PURE__ */ jsx(
392
+ "button",
393
+ {
394
+ type: "button",
395
+ role: "menuitem",
396
+ title: node.label + (node.shortcut ? ` ${node.shortcut}` : ""),
397
+ disabled: node.disabled,
398
+ className: `cm-tile ${node.danger ? "cm-danger" : ""} ${node.ai ? "cm-ai" : ""} ${ctx.highlightId === node.id ? "cm-hl" : ""}`,
399
+ onClick: () => {
400
+ ctx.onSelectNode?.(node.id);
401
+ ctx.onPick?.(node);
402
+ },
403
+ children: ctx.renderIcon(node.icon ?? "Circle", "cm-icon")
404
+ }
405
+ )
406
+ );
407
+ }
408
+ function Item({ node, ctx }) {
409
+ const { look } = ctx.design;
410
+ const [checked, setChecked] = useState(false);
411
+ return (
412
+ // ui-exception: a menu row of the menu component itself
413
+ /* @__PURE__ */ jsxs(
414
+ "button",
415
+ {
416
+ type: "button",
417
+ role: node.toggle ? "menuitemcheckbox" : "menuitem",
418
+ "aria-checked": node.toggle ? checked : void 0,
419
+ disabled: node.disabled,
420
+ className: `cm-item ${node.danger ? "cm-danger" : ""} ${node.ai ? "cm-ai" : ""} ${ctx.highlightId === node.id ? "cm-hl" : ""}`,
421
+ onClick: () => {
422
+ ctx.onSelectNode?.(node.id);
423
+ if (node.toggle) setChecked((c) => !c);
424
+ ctx.onPick?.(node);
425
+ },
426
+ children: [
427
+ look.showIcons && /* @__PURE__ */ jsx("span", { className: "cm-icon-slot", children: node.toggle ? checked ? ctx.renderIcon("Check", "cm-icon") : null : node.icon ? ctx.renderIcon(node.icon, "cm-icon") : null }),
428
+ /* @__PURE__ */ jsxs("span", { className: "cm-text", children: [
429
+ /* @__PURE__ */ jsx("span", { className: "cm-label", children: node.label }),
430
+ look.showHints && node.hint && /* @__PURE__ */ jsx("span", { className: "cm-hint", children: node.hint })
431
+ ] }),
432
+ look.showShortcuts && node.shortcut && /* @__PURE__ */ jsx("kbd", { className: "cm-shortcut", children: node.shortcut })
433
+ ]
434
+ }
435
+ )
436
+ );
437
+ }
438
+ function Submenu({ node, ctx }) {
439
+ const { look } = ctx.design;
440
+ const [open, setOpen] = useState(false);
441
+ const [side, setSide] = useState("right");
442
+ const show = (el) => {
443
+ setSide(flyoutSide(el, look.width));
444
+ setOpen(true);
445
+ };
446
+ return /* @__PURE__ */ jsxs("div", { className: "cm-sub", onMouseEnter: (e) => show(e.currentTarget), onMouseLeave: () => setOpen(false), children: [
447
+ /* @__PURE__ */ jsxs(
448
+ "button",
449
+ {
450
+ type: "button",
451
+ role: "menuitem",
452
+ "aria-haspopup": "menu",
453
+ "aria-expanded": open,
454
+ className: `cm-item ${ctx.highlightId === node.id ? "cm-hl" : ""}`,
455
+ onClick: () => {
456
+ ctx.onSelectNode?.(node.id);
457
+ setOpen(true);
458
+ },
459
+ children: [
460
+ look.showIcons && /* @__PURE__ */ jsx("span", { className: "cm-icon-slot", children: node.icon ? ctx.renderIcon(node.icon, "cm-icon") : null }),
461
+ /* @__PURE__ */ jsxs("span", { className: "cm-text", children: [
462
+ /* @__PURE__ */ jsx("span", { className: "cm-label", children: node.label }),
463
+ look.showHints && node.hint && /* @__PURE__ */ jsx("span", { className: "cm-hint", children: node.hint })
464
+ ] }),
465
+ ctx.renderIcon("ChevronRight", "cm-icon cm-chev")
466
+ ]
467
+ }
468
+ ),
469
+ open && /* @__PURE__ */ jsx("div", { role: "menu", className: "cm-menu cm-flyout", "data-side": side, "data-density": look.density, style: { width: look.width }, children: node.children.length === 0 ? /* @__PURE__ */ jsx("div", { className: "cm-empty", children: "Empty submenu" }) : /* @__PURE__ */ jsx(Nodes, { nodes: node.children, ctx }) })
470
+ ] });
471
+ }
472
+ function flyoutSide(row, width) {
473
+ const r = row.getBoundingClientRect();
474
+ const bounds = row.closest("[data-menu-bounds]")?.getBoundingClientRect() ?? { left: 0, right: window.innerWidth };
475
+ if (r.right + width + 8 <= bounds.right) return "right";
476
+ if (r.left - width - 8 >= bounds.left) return "left";
477
+ return "below";
478
+ }
479
+ function Slot({ node, ctx }) {
480
+ const { look } = ctx.design;
481
+ const source = slotSource(node.fills);
482
+ const options = slotOptions(node.fills, ctx.placed);
483
+ const [open, setOpen] = useState(false);
484
+ const [side, setSide] = useState("right");
485
+ const count = source?.runtime ? "live" : String(options.length);
486
+ const row = (
487
+ // ui-exception: a menu row of the menu component itself
488
+ /* @__PURE__ */ jsxs(
489
+ "button",
490
+ {
491
+ type: "button",
492
+ role: "menuitem",
493
+ "aria-haspopup": node.shape === "submenu" ? "menu" : void 0,
494
+ title: source ? `Placeholder: ${source.label}` : "Placeholder",
495
+ className: `cm-item cm-slot ${ctx.highlightId === node.id ? "cm-hl" : ""}`,
496
+ onClick: () => {
497
+ ctx.onSelectNode?.(node.id);
498
+ if (node.shape === "submenu") setOpen(true);
499
+ },
500
+ children: [
501
+ look.showIcons && /* @__PURE__ */ jsx("span", { className: "cm-icon-slot", children: ctx.renderIcon(node.icon ?? source?.icon ?? "Ellipsis", "cm-icon") }),
502
+ /* @__PURE__ */ jsx("span", { className: "cm-text", children: /* @__PURE__ */ jsx("span", { className: "cm-label", children: node.label }) }),
503
+ /* @__PURE__ */ jsx("span", { className: "cm-count", children: count }),
504
+ node.shape === "submenu" && ctx.renderIcon("ChevronRight", "cm-icon cm-chev")
505
+ ]
506
+ }
507
+ )
508
+ );
509
+ if (node.shape === "items") return row;
510
+ return /* @__PURE__ */ jsxs(
511
+ "div",
512
+ {
513
+ className: "cm-sub",
514
+ onMouseEnter: (e) => {
515
+ setSide(flyoutSide(e.currentTarget, look.width));
516
+ setOpen(true);
517
+ },
518
+ onMouseLeave: () => setOpen(false),
519
+ children: [
520
+ row,
521
+ open && /* @__PURE__ */ jsx("div", { role: "menu", className: "cm-menu cm-flyout", "data-side": side, "data-density": look.density, style: { width: look.width }, children: source?.runtime ? /* @__PURE__ */ jsxs("div", { className: "cm-runtime", children: [
522
+ "Filled at runtime: ",
523
+ source.label
524
+ ] }) : options.length === 0 ? /* @__PURE__ */ jsx("div", { className: "cm-empty", children: "Nothing left \u2014 every option of this kind is placed" }) : options.map((o2) => /* @__PURE__ */ jsx(Item, { node: { id: `slot-${node.id}-${o2.id}`, kind: "item", label: o2.label, icon: o2.icon, catalogId: o2.id, ...o2.shortcut ? { shortcut: o2.shortcut } : {}, ...o2.ai ? { ai: true } : {}, ...o2.danger ? { danger: true } : {} }, ctx }, o2.id)) })
525
+ ]
526
+ }
527
+ );
528
+ }
529
+ export {
530
+ CATALOG,
531
+ CATALOG_GROUPS,
532
+ DEFAULT_LOOK,
533
+ DESIGN_FORMAT,
534
+ MENU_SURFACES,
535
+ MenuPreview,
536
+ SLOT_SOURCES,
537
+ TEMPLATES,
538
+ canMove,
539
+ catalogOption,
540
+ catalogUseCounts,
541
+ cloneNode,
542
+ contains,
543
+ countItems,
544
+ duplicateNode,
545
+ emptyDesign,
546
+ insertNode,
547
+ isContainer,
548
+ locate,
549
+ moveNode,
550
+ newId,
551
+ parseDesign,
552
+ placedCatalogIds,
553
+ removeNode,
554
+ serializeDesign,
555
+ slotOptions,
556
+ slotSource,
557
+ unwrapNode,
558
+ updateNode,
559
+ wrapNode
560
+ };
561
+ //# sourceMappingURL=index.js.map