@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.
- package/CHANGELOG.md +16 -0
- package/README.md +14 -1
- package/dist/builder.cjs +1665 -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 +1650 -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 +486 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +46 -0
- package/dist/index.d.ts +46 -0
- package/dist/index.js +464 -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-BdF0TqZ3.d.cts +107 -0
- package/dist/model-BdF0TqZ3.d.ts +107 -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.d.cts
ADDED
|
@@ -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 };
|
package/dist/index.d.ts
ADDED
|
@@ -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
|