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