@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.
- package/CHANGELOG.md +23 -0
- package/README.md +14 -1
- package/dist/builder.cjs +1810 -0
- package/dist/builder.cjs.map +1 -0
- package/dist/builder.d.cts +22 -0
- package/dist/builder.d.ts +22 -0
- package/dist/builder.js +1795 -0
- package/dist/builder.js.map +1 -0
- package/dist/design-store.cjs +195 -0
- package/dist/design-store.cjs.map +1 -0
- package/dist/design-store.d.cts +42 -0
- package/dist/design-store.d.ts +42 -0
- package/dist/design-store.js +175 -0
- package/dist/design-store.js.map +1 -0
- package/dist/engine.cjs +3069 -0
- package/dist/engine.cjs.map +1 -0
- package/dist/engine.d.cts +731 -0
- package/dist/engine.d.ts +731 -0
- package/dist/engine.js +3250 -0
- package/dist/engine.js.map +1 -0
- package/dist/grouping-CB3zlD1T.d.ts +623 -0
- package/dist/grouping-CGcWsltu.d.cts +623 -0
- package/dist/host-types-AOiEYwR8.d.cts +9 -0
- package/dist/host-types-AOiEYwR8.d.ts +9 -0
- package/dist/index.cjs +583 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +60 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +561 -0
- package/dist/index.js.map +1 -0
- package/dist/menu-content.cjs +3146 -0
- package/dist/menu-content.cjs.map +1 -0
- package/dist/menu-content.d.cts +30 -0
- package/dist/menu-content.d.ts +30 -0
- package/dist/menu-content.js +3197 -0
- package/dist/menu-content.js.map +1 -0
- package/dist/model-CkLCR6WY.d.cts +123 -0
- package/dist/model-CkLCR6WY.d.ts +123 -0
- package/dist/react.cjs +3322 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +133 -0
- package/dist/react.d.ts +133 -0
- package/dist/react.js +3345 -0
- package/dist/react.js.map +1 -0
- 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
|