@ai-matrx/alchemy 0.8.13 → 0.8.17
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 +32 -1
- package/dist/actions.cjs +1 -373
- package/dist/actions.cjs.map +1 -1
- package/dist/actions.js +1 -350
- package/dist/actions.js.map +1 -1
- package/dist/checks.cjs +9 -314
- package/dist/checks.cjs.map +1 -1
- package/dist/checks.js +8 -290
- package/dist/checks.js.map +1 -1
- package/dist/core.cjs +9 -1728
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +2 -0
- package/dist/core.d.ts +2 -0
- package/dist/core.js +9 -1702
- package/dist/core.js.map +1 -1
- package/dist/declare-host.cjs +1 -18
- package/dist/declare-host.cjs.map +1 -1
- package/dist/declare.cjs +1 -638
- package/dist/declare.cjs.map +1 -1
- package/dist/declare.js +1 -615
- package/dist/declare.js.map +1 -1
- package/dist/index.cjs +9 -1728
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +9 -1702
- package/dist/index.js.map +1 -1
- package/dist/menu.cjs +1 -529
- package/dist/menu.cjs.map +1 -1
- package/dist/menu.js +1 -506
- package/dist/menu.js.map +1 -1
- package/dist/operate-document.cjs +4 -162
- package/dist/operate-document.cjs.map +1 -1
- package/dist/operate-document.js +4 -129
- package/dist/operate-document.js.map +1 -1
- package/dist/operate-rich-html.cjs +1 -128
- package/dist/operate-rich-html.cjs.map +1 -1
- package/dist/operate-rich-html.js +1 -95
- package/dist/operate-rich-html.js.map +1 -1
- package/dist/operate-server.cjs +1 -156
- package/dist/operate-server.cjs.map +1 -1
- package/dist/operate-server.js +1 -133
- package/dist/operate-server.js.map +1 -1
- package/dist/operate-xlsx.cjs +143 -34230
- package/dist/operate-xlsx.cjs.map +1 -1
- package/dist/operate-xlsx.js +143 -34214
- package/dist/operate-xlsx.js.map +1 -1
- package/dist/operate.cjs +9 -1726
- package/dist/operate.cjs.map +1 -1
- package/dist/operate.d.cts +2 -0
- package/dist/operate.d.ts +2 -0
- package/dist/operate.js +9 -1702
- package/dist/operate.js.map +1 -1
- package/dist/ports.cjs +1 -18
- package/dist/ports.cjs.map +1 -1
- package/dist/react-bar.cjs +2 -795
- package/dist/react-bar.cjs.map +1 -1
- package/dist/react-bar.js +2 -774
- package/dist/react-bar.js.map +1 -1
- package/dist/react-host.cjs +1 -299
- package/dist/react-host.cjs.map +1 -1
- package/dist/react-host.js +1 -267
- package/dist/react-host.js.map +1 -1
- package/dist/react-menu.cjs +2 -848
- package/dist/react-menu.cjs.map +1 -1
- package/dist/react-menu.js +2 -835
- package/dist/react-menu.js.map +1 -1
- package/dist/react-overflow.cjs +2 -695
- package/dist/react-overflow.cjs.map +1 -1
- package/dist/react-overflow.js +2 -674
- package/dist/react-overflow.js.map +1 -1
- package/dist/react-palette.cjs +2 -586
- package/dist/react-palette.cjs.map +1 -1
- package/dist/react-palette.js +2 -554
- package/dist/react-palette.js.map +1 -1
- package/dist/react-selection.cjs +2 -726
- package/dist/react-selection.cjs.map +1 -1
- package/dist/react-selection.js +2 -705
- package/dist/react-selection.js.map +1 -1
- package/dist/react-sheet.cjs +2 -734
- package/dist/react-sheet.cjs.map +1 -1
- package/dist/react-sheet.js +2 -713
- package/dist/react-sheet.js.map +1 -1
- package/dist/react.cjs +1 -29
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +1 -8
- package/dist/react.js.map +1 -1
- package/package.json +1 -1
package/dist/react-menu.cjs
CHANGED
|
@@ -1,850 +1,4 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
"use strict";
|
|
3
|
-
var __create = Object.create;
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
-
var __export = (target, all) => {
|
|
10
|
-
for (var name in all)
|
|
11
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
-
};
|
|
13
|
-
var __copyProps = (to, from, except, desc) => {
|
|
14
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
15
|
-
for (let key of __getOwnPropNames(from))
|
|
16
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
18
|
-
}
|
|
19
|
-
return to;
|
|
20
|
-
};
|
|
21
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
-
mod
|
|
28
|
-
));
|
|
29
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
-
|
|
31
|
-
// src/react-menu.ts
|
|
32
|
-
var react_menu_exports = {};
|
|
33
|
-
__export(react_menu_exports, {
|
|
34
|
-
ContextMenuPanel: () => ContextMenuPanel
|
|
35
|
-
});
|
|
36
|
-
module.exports = __toCommonJS(react_menu_exports);
|
|
37
|
-
|
|
38
|
-
// src/react/menu.tsx
|
|
39
|
-
var React4 = __toESM(require("react"), 1);
|
|
40
|
-
var import_react_dom = require("react-dom");
|
|
41
|
-
var import_design_system2 = require("@ai-matrx/design-system");
|
|
42
|
-
|
|
43
|
-
// src/menu/layouts.ts
|
|
44
|
-
var INLINE_SECTION_MAX = 3;
|
|
45
|
-
function sectionLabel(section) {
|
|
46
|
-
return section.label ?? null;
|
|
47
|
-
}
|
|
48
|
-
function isLeaf(node) {
|
|
49
|
-
return node.kind === "item" || node.kind === "checkbox" || node.kind === "link";
|
|
50
|
-
}
|
|
51
|
-
function fold(section) {
|
|
52
|
-
return {
|
|
53
|
-
kind: "submenu",
|
|
54
|
-
id: `${section.id}:fold`,
|
|
55
|
-
label: section.label,
|
|
56
|
-
...section.icon !== void 0 ? { icon: section.icon } : {},
|
|
57
|
-
children: section.nodes
|
|
58
|
-
};
|
|
59
|
-
}
|
|
60
|
-
function rowCount(nodes) {
|
|
61
|
-
return nodes.filter((n) => n.kind !== "separator" && n.kind !== "label").length;
|
|
62
|
-
}
|
|
63
|
-
function stripOf(model) {
|
|
64
|
-
const clipboard = model.sections.find((s) => s.group === "clipboard" && !s.primary);
|
|
65
|
-
return clipboard ? clipboard.nodes.filter(isLeaf) : [];
|
|
66
|
-
}
|
|
67
|
-
function clipboardRest(model) {
|
|
68
|
-
const clipboard = model.sections.find((s) => s.group === "clipboard" && !s.primary);
|
|
69
|
-
return clipboard ? clipboard.nodes.filter((n) => !isLeaf(n) && n.kind === "submenu") : [];
|
|
70
|
-
}
|
|
71
|
-
function classic(model) {
|
|
72
|
-
return { strip: [], sections: model.sections, filterable: false };
|
|
73
|
-
}
|
|
74
|
-
function tiered(model, filterable) {
|
|
75
|
-
const strip = stripOf(model);
|
|
76
|
-
const sections = [];
|
|
77
|
-
const rest = clipboardRest(model);
|
|
78
|
-
for (const s of model.sections) {
|
|
79
|
-
if (s.group === "clipboard" && !s.primary) {
|
|
80
|
-
if (rest.length) sections.push({ ...s, nodes: rest });
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
if (s.primary) {
|
|
84
|
-
sections.push(s);
|
|
85
|
-
continue;
|
|
86
|
-
}
|
|
87
|
-
if (!s.label || s.group === "surface-info" || rowCount(s.nodes) <= INLINE_SECTION_MAX) {
|
|
88
|
-
sections.push(s);
|
|
89
|
-
continue;
|
|
90
|
-
}
|
|
91
|
-
sections.push({ id: s.id, group: s.group, nodes: [fold({ ...s, label: s.label })] });
|
|
92
|
-
}
|
|
93
|
-
return { strip, sections, filterable };
|
|
94
|
-
}
|
|
95
|
-
function context(model, arrangement) {
|
|
96
|
-
if (arrangement === "classic") return classic(model);
|
|
97
|
-
return tiered(model, arrangement === "command");
|
|
98
|
-
}
|
|
99
|
-
function palette(model) {
|
|
100
|
-
const leaves = [];
|
|
101
|
-
const walk = (nodes, trail) => {
|
|
102
|
-
for (const n of nodes) {
|
|
103
|
-
if (isLeaf(n)) leaves.push({ node: n, breadcrumb: trail });
|
|
104
|
-
else if (n.kind === "submenu") {
|
|
105
|
-
if (n.lazy) leaves.push({ node: n, breadcrumb: trail });
|
|
106
|
-
else walk(n.children, [...trail, n.label]);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
};
|
|
110
|
-
for (const s of model.sections) walk(s.nodes, s.label ? [s.label] : []);
|
|
111
|
-
return { leaves };
|
|
112
|
-
}
|
|
113
|
-
function filterPalette(arranged, query) {
|
|
114
|
-
const words = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
115
|
-
if (!words.length) return arranged;
|
|
116
|
-
return {
|
|
117
|
-
leaves: arranged.leaves.filter((l) => {
|
|
118
|
-
const hay = `${l.node.label} ${l.breadcrumb.join(" ")}`.toLowerCase();
|
|
119
|
-
return words.every((w) => hay.includes(w));
|
|
120
|
-
})
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// src/react/engine.tsx
|
|
125
|
-
var React2 = __toESM(require("react"), 1);
|
|
126
|
-
|
|
127
|
-
// src/declare/sensitivity.ts
|
|
128
|
-
var DEFAULT_SENSITIVITY = Object.freeze({
|
|
129
|
-
exportable: true,
|
|
130
|
-
classification: "ordinary",
|
|
131
|
-
includedByDefault: true
|
|
132
|
-
});
|
|
133
|
-
function resolveSensitivity(value) {
|
|
134
|
-
return {
|
|
135
|
-
exportable: value.sensitivity?.exportable ?? DEFAULT_SENSITIVITY.exportable,
|
|
136
|
-
classification: value.sensitivity?.classification ?? DEFAULT_SENSITIVITY.classification,
|
|
137
|
-
includedByDefault: value.sensitivity?.includedByDefault ?? DEFAULT_SENSITIVITY.includedByDefault
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// src/actions/projection.ts
|
|
142
|
-
function toJson(value) {
|
|
143
|
-
if (value === void 0 || typeof value === "function" || typeof value === "symbol") return void 0;
|
|
144
|
-
if (value === null || typeof value === "string" || typeof value === "boolean") return value;
|
|
145
|
-
if (typeof value === "number") return Number.isFinite(value) ? value : null;
|
|
146
|
-
if (typeof value === "bigint") return value.toString();
|
|
147
|
-
if (value instanceof Date) return value.toISOString();
|
|
148
|
-
if (Array.isArray(value)) return value.map((v) => toJson(v) ?? null);
|
|
149
|
-
if (typeof value === "object") {
|
|
150
|
-
const out = {};
|
|
151
|
-
for (const [k, v] of Object.entries(value)) {
|
|
152
|
-
const j = toJson(v);
|
|
153
|
-
if (j !== void 0) out[k] = j;
|
|
154
|
-
}
|
|
155
|
-
return out;
|
|
156
|
-
}
|
|
157
|
-
return void 0;
|
|
158
|
-
}
|
|
159
|
-
function projectValues(raw, declared, pathPrefix = "") {
|
|
160
|
-
const byName = new Map(declared.map((d) => [d.name, d]));
|
|
161
|
-
const values = {};
|
|
162
|
-
const omissions = [];
|
|
163
|
-
for (const [name, value] of Object.entries(raw)) {
|
|
164
|
-
const path = pathPrefix ? `${pathPrefix}.${name}` : name;
|
|
165
|
-
const decl = byName.get(name);
|
|
166
|
-
if (!decl) {
|
|
167
|
-
omissions.push({ path, reason: "filtered" });
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
const sensitivity = resolveSensitivity(decl);
|
|
171
|
-
if (sensitivity.classification !== "ordinary") {
|
|
172
|
-
omissions.push({ path, reason: "excluded", classification: sensitivity.classification });
|
|
173
|
-
continue;
|
|
174
|
-
}
|
|
175
|
-
if (!sensitivity.exportable) {
|
|
176
|
-
omissions.push({ path, reason: "excluded" });
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
const json = toJson(value);
|
|
180
|
-
if (json === void 0) continue;
|
|
181
|
-
values[name] = json;
|
|
182
|
-
}
|
|
183
|
-
return { values, omissions };
|
|
184
|
-
}
|
|
185
|
-
function createProjectedReads(source) {
|
|
186
|
-
return {
|
|
187
|
-
async values(signal) {
|
|
188
|
-
if (!source.surface || !source.declaration) return { values: {}, omissions: [] };
|
|
189
|
-
const raw = await source.surface.readValues(signal);
|
|
190
|
-
const screenValues = source.declaration.values;
|
|
191
|
-
return projectValues(raw, screenValues);
|
|
192
|
-
},
|
|
193
|
-
async item(signal) {
|
|
194
|
-
const item = source.item;
|
|
195
|
-
if (!item || !source.declaration) return null;
|
|
196
|
-
const type = source.declaration.itemTypes?.find((t) => t.name === item.itemType);
|
|
197
|
-
if (!type) return { values: {}, omissions: [{ path: item.itemType, reason: "filtered" }] };
|
|
198
|
-
const raw = await item.readValues(signal);
|
|
199
|
-
return projectValues(raw, type.values, item.itemType);
|
|
200
|
-
},
|
|
201
|
-
async live(signal) {
|
|
202
|
-
if (!source.surface?.readLive) return {};
|
|
203
|
-
return source.surface.readLive(signal);
|
|
204
|
-
}
|
|
205
|
-
};
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
// src/actions/run.ts
|
|
209
|
-
async function runAction(action, target, options) {
|
|
210
|
-
const { ports } = options;
|
|
211
|
-
if (action.requiresAuth && !target.auth.authenticated) {
|
|
212
|
-
if (!ports.auth) {
|
|
213
|
-
ports.diagnostics.capture(new Error(`requiresAuth action "${action.id}" ran signed out with no auth port`), {
|
|
214
|
-
area: "actions",
|
|
215
|
-
detail: { actionId: action.id }
|
|
216
|
-
});
|
|
217
|
-
return { status: "failed", error: new Error("sign-in unavailable") };
|
|
218
|
-
}
|
|
219
|
-
ports.auth.signInThenResume({ actionId: action.id, contentFingerprint: options.contentFingerprint ?? action.id });
|
|
220
|
-
return { status: "auth-pending" };
|
|
221
|
-
}
|
|
222
|
-
const reads = createProjectedReads(options.source ?? { surface: null, declaration: target.declaration, item: null });
|
|
223
|
-
const signal = options.signal ?? new AbortController().signal;
|
|
224
|
-
try {
|
|
225
|
-
const outcome = await action.run(target, { reads, ports, signal });
|
|
226
|
-
return { status: "ran", outcome };
|
|
227
|
-
} catch (error) {
|
|
228
|
-
ports.diagnostics.capture(error, { area: "actions", detail: { actionId: action.id, stage: "run" } });
|
|
229
|
-
const label = typeof action.label === "string" ? action.label : action.id;
|
|
230
|
-
ports.notify?.error(`${label} did not finish.`, "Try again; if it keeps failing, report it from the feedback button.");
|
|
231
|
-
return { status: "failed", error };
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
// src/menu/headings.ts
|
|
236
|
-
var APPROVED_HEADINGS = /* @__PURE__ */ new Set([
|
|
237
|
-
"Save",
|
|
238
|
-
// rich-document menuStructure, e5b8d01229 (2026-05-19)
|
|
239
|
-
"Copy as",
|
|
240
|
-
// rich-document menuStructure, e5b8d01229 (2026-05-19)
|
|
241
|
-
"Export",
|
|
242
|
-
// rich-document menuStructure, e5b8d01229 (2026-05-19)
|
|
243
|
-
"Edit",
|
|
244
|
-
// rich-document menuStructure, e5b8d01229 (2026-05-19)
|
|
245
|
-
"Creator tools",
|
|
246
|
-
// rich-document menuStructure, e5b8d01229 (2026-05-19)
|
|
247
|
-
"Admin",
|
|
248
|
-
// rich-document menuStructure, e5b8d01229 (2026-05-19)
|
|
249
|
-
"App",
|
|
250
|
-
// rich-document menuStructure, e5b8d01229 (2026-05-19)
|
|
251
|
-
"Compare",
|
|
252
|
-
// rich-document menuStructure, 2e3a8a9fdf (2026-06-11)
|
|
253
|
-
"History"
|
|
254
|
-
// context-menu-v3 tiered layout — the one grouping Arman approved, 2026-08-22
|
|
255
|
-
]);
|
|
256
|
-
var CONTAINER_WORD = /^(more|more…|more\.\.\.|other|others|misc|miscellaneous|additional|extra|extras|actions|options|advanced|stuff|everything else)$/i;
|
|
257
|
-
var TARGET_SHAPE = /^(Cell|Row|Column|Table|Range|Selection|Record) · .+$/;
|
|
258
|
-
function isContainerWord(label) {
|
|
259
|
-
return CONTAINER_WORD.test(label.trim());
|
|
260
|
-
}
|
|
261
|
-
function isApprovedHeading(label, kind = "group", approved = APPROVED_HEADINGS) {
|
|
262
|
-
const l = label.trim();
|
|
263
|
-
if (!l || isContainerWord(l)) return false;
|
|
264
|
-
if (approved.has(l)) return true;
|
|
265
|
-
return kind === "target" || TARGET_SHAPE.test(l);
|
|
266
|
-
}
|
|
267
|
-
function defaultHeadingPolicy() {
|
|
268
|
-
const env = globalThis.process?.env?.NODE_ENV;
|
|
269
|
-
return env === "production" ? "inline" : "throw";
|
|
270
|
-
}
|
|
271
|
-
var UnapprovedHeadingError = class extends Error {
|
|
272
|
-
heading;
|
|
273
|
-
constructor(heading, where) {
|
|
274
|
-
super(
|
|
275
|
-
`Menu heading "${heading}" (${where}) is not an approved heading. Remedy: show these rows inline \u2014 declare no section (or kind "target" for the clicked thing's own label) \u2014 or ask Arman to add the name to APPROVED_HEADINGS in @ai-matrx/alchemy/menu. A generic container word ("More\u2026", "Other") is never approved.`
|
|
276
|
-
);
|
|
277
|
-
this.name = "UnapprovedHeadingError";
|
|
278
|
-
this.heading = heading;
|
|
279
|
-
}
|
|
280
|
-
};
|
|
281
|
-
|
|
282
|
-
// src/menu/model.ts
|
|
283
|
-
var CATEGORY_GROUP = {
|
|
284
|
-
clipboard: "clipboard",
|
|
285
|
-
copy: "transfer",
|
|
286
|
-
export: "transfer",
|
|
287
|
-
save: "document",
|
|
288
|
-
share: "document",
|
|
289
|
-
edit: "editable",
|
|
290
|
-
history: "history",
|
|
291
|
-
ai: "ai",
|
|
292
|
-
ask: "ai",
|
|
293
|
-
listen: "tools",
|
|
294
|
-
study: "tools",
|
|
295
|
-
app: "tools",
|
|
296
|
-
feedback: "quick",
|
|
297
|
-
creator: "admin",
|
|
298
|
-
admin: "admin",
|
|
299
|
-
"surface-info": "surface-info"
|
|
300
|
-
};
|
|
301
|
-
var GROUP_ORDER = [
|
|
302
|
-
"clipboard",
|
|
303
|
-
"editable",
|
|
304
|
-
"transfer",
|
|
305
|
-
"document",
|
|
306
|
-
"history",
|
|
307
|
-
"ai",
|
|
308
|
-
"tools",
|
|
309
|
-
"quick",
|
|
310
|
-
"surface",
|
|
311
|
-
"admin",
|
|
312
|
-
"surface-info"
|
|
313
|
-
];
|
|
314
|
-
var CATEGORY_ORDER = [
|
|
315
|
-
"clipboard",
|
|
316
|
-
"edit",
|
|
317
|
-
"copy",
|
|
318
|
-
"export",
|
|
319
|
-
"save",
|
|
320
|
-
"share",
|
|
321
|
-
"history",
|
|
322
|
-
"ask",
|
|
323
|
-
"ai",
|
|
324
|
-
"listen",
|
|
325
|
-
"study",
|
|
326
|
-
"app",
|
|
327
|
-
"feedback",
|
|
328
|
-
"creator",
|
|
329
|
-
"admin",
|
|
330
|
-
"surface-info"
|
|
331
|
-
];
|
|
332
|
-
function translate(i18n, key, fallback) {
|
|
333
|
-
if (!i18n) return fallback;
|
|
334
|
-
try {
|
|
335
|
-
return i18n.t(key, fallback);
|
|
336
|
-
} catch {
|
|
337
|
-
return fallback;
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
function actionToNode(resolved, target, options = {}) {
|
|
341
|
-
const { action, eligibility } = resolved;
|
|
342
|
-
const rawLabel = typeof action.label === "function" ? action.label(target) : action.label;
|
|
343
|
-
const label = translate(options.i18n, action.labelKey ?? action.id, rawLabel);
|
|
344
|
-
const base = {
|
|
345
|
-
id: action.id,
|
|
346
|
-
actionId: action.id,
|
|
347
|
-
...action.icon !== void 0 ? { icon: action.icon } : {},
|
|
348
|
-
...action.placement !== void 0 ? { placement: action.placement } : {}
|
|
349
|
-
};
|
|
350
|
-
if (action.expand) {
|
|
351
|
-
const node = {
|
|
352
|
-
...base,
|
|
353
|
-
kind: "submenu",
|
|
354
|
-
label,
|
|
355
|
-
lazy: true,
|
|
356
|
-
loading: false,
|
|
357
|
-
...action.pending?.(target) ? { pending: true } : {},
|
|
358
|
-
children: [],
|
|
359
|
-
...action.emptyLabel !== void 0 ? { emptyLabel: action.emptyLabel } : {}
|
|
360
|
-
};
|
|
361
|
-
return node;
|
|
362
|
-
}
|
|
363
|
-
if (action.href !== void 0) {
|
|
364
|
-
const href = typeof action.href === "function" ? action.href(target) : action.href;
|
|
365
|
-
return { ...base, kind: "link", label, href };
|
|
366
|
-
}
|
|
367
|
-
if (action.toggle) {
|
|
368
|
-
return {
|
|
369
|
-
...base,
|
|
370
|
-
kind: "checkbox",
|
|
371
|
-
label,
|
|
372
|
-
checked: action.pressed?.(target) ?? false,
|
|
373
|
-
...action.description !== void 0 ? { description: action.description } : {}
|
|
374
|
-
};
|
|
375
|
-
}
|
|
376
|
-
const state = action.stateIcon?.(target) ?? null;
|
|
377
|
-
const pressed = action.pressed?.(target);
|
|
378
|
-
const hint = action.hint !== void 0 ? translate(options.i18n, action.hintKey ?? `${action.id}.hint`, action.hint) : void 0;
|
|
379
|
-
return {
|
|
380
|
-
...base,
|
|
381
|
-
kind: "item",
|
|
382
|
-
label,
|
|
383
|
-
...action.iconTone !== void 0 ? { iconTone: action.iconTone } : {},
|
|
384
|
-
...state ? { stateIcon: state } : {},
|
|
385
|
-
...action.description !== void 0 ? { description: action.description } : {},
|
|
386
|
-
...hint !== void 0 ? { hint } : {},
|
|
387
|
-
...pressed !== void 0 ? { pressed } : {},
|
|
388
|
-
...action.destructive ? { destructive: true } : {},
|
|
389
|
-
...action.preserveSelection ? { preserveSelection: true } : {},
|
|
390
|
-
...eligibility.status === "unavailable-verb" ? { unavailable: { sentence: eligibility.sentence } } : {}
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
function buildMenuModel(target, resolved, options = {}) {
|
|
394
|
-
const policy = options.headingPolicy ?? defaultHeadingPolicy();
|
|
395
|
-
resolved = resolved.map((r) => {
|
|
396
|
-
const sec = r.action.section;
|
|
397
|
-
if (r.action.expand && isContainerWord(typeof r.action.label === "string" ? r.action.label : "") && policy === "throw") {
|
|
398
|
-
throw new UnapprovedHeadingError(r.action.label, `submenu of ${r.action.id}`);
|
|
399
|
-
}
|
|
400
|
-
if (!sec) return r;
|
|
401
|
-
const kind = sec.kind ?? (sec.primary ? "target" : "group");
|
|
402
|
-
if (isApprovedHeading(sec.label, kind)) return r;
|
|
403
|
-
if (policy === "throw") throw new UnapprovedHeadingError(sec.label, `section of ${r.action.id}`);
|
|
404
|
-
const { section: _dropped, ...rest } = r.action;
|
|
405
|
-
return { ...r, action: rest };
|
|
406
|
-
});
|
|
407
|
-
const ordered = resolved.map((r, index) => ({ r, index })).sort((a, b) => {
|
|
408
|
-
const ca = CATEGORY_ORDER.indexOf(a.r.action.category);
|
|
409
|
-
const cb = CATEGORY_ORDER.indexOf(b.r.action.category);
|
|
410
|
-
if (ca !== cb) return ca - cb;
|
|
411
|
-
const oa = a.r.action.order ?? 0;
|
|
412
|
-
const ob = b.r.action.order ?? 0;
|
|
413
|
-
if (oa !== ob) return oa - ob;
|
|
414
|
-
return a.index - b.index;
|
|
415
|
-
}).map((x) => x.r);
|
|
416
|
-
const sections = /* @__PURE__ */ new Map();
|
|
417
|
-
const roles = {};
|
|
418
|
-
const explicit = /* @__PURE__ */ new Map();
|
|
419
|
-
resolved.forEach((r, index) => {
|
|
420
|
-
const sec = r.action.section;
|
|
421
|
-
if (!sec) return;
|
|
422
|
-
const entry = explicit.get(sec.id) ?? { group: CATEGORY_GROUP[r.action.category], rows: [] };
|
|
423
|
-
entry.rows.push({ r, index });
|
|
424
|
-
explicit.set(sec.id, entry);
|
|
425
|
-
});
|
|
426
|
-
const placed = /* @__PURE__ */ new Set();
|
|
427
|
-
for (const r of ordered) {
|
|
428
|
-
const sec = r.action.section;
|
|
429
|
-
if (sec && placed.has(sec.id)) continue;
|
|
430
|
-
const group = sec ? explicit.get(sec.id)?.group ?? CATEGORY_GROUP[r.action.category] : CATEGORY_GROUP[r.action.category];
|
|
431
|
-
const sectionId = sec ? `section:${sec.id}` : group;
|
|
432
|
-
let section = sections.get(sectionId);
|
|
433
|
-
if (!section) {
|
|
434
|
-
section = {
|
|
435
|
-
id: sectionId,
|
|
436
|
-
group,
|
|
437
|
-
nodes: [],
|
|
438
|
-
...sec ? { label: translate(options.i18n, sec.labelKey ?? `section.${sec.id}`, sec.label) } : {},
|
|
439
|
-
...sec?.icon !== void 0 ? { icon: sec.icon } : {},
|
|
440
|
-
...sec?.primary ? { primary: true } : {},
|
|
441
|
-
...sec && (sec.kind ?? (sec.primary ? "target" : "group")) === "target" ? { target: true } : {}
|
|
442
|
-
};
|
|
443
|
-
sections.set(sectionId, section);
|
|
444
|
-
}
|
|
445
|
-
const rows = sec ? (explicit.get(sec.id)?.rows ?? []).sort((x, y) => (x.r.action.order ?? 0) - (y.r.action.order ?? 0) || x.index - y.index).map((x) => x.r) : [r];
|
|
446
|
-
if (sec) placed.add(sec.id);
|
|
447
|
-
for (const row of rows) {
|
|
448
|
-
section.nodes.push(actionToNode(row, target, options));
|
|
449
|
-
if (row.action.verb) roles[row.action.verb] = row.action.id;
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
const list = [...sections.values()].sort((a, b) => {
|
|
453
|
-
if (Boolean(a.primary) !== Boolean(b.primary)) return a.primary ? -1 : 1;
|
|
454
|
-
return GROUP_ORDER.indexOf(a.group) - GROUP_ORDER.indexOf(b.group);
|
|
455
|
-
});
|
|
456
|
-
const selected = target.selection?.text?.trim();
|
|
457
|
-
const content = options.content?.trim();
|
|
458
|
-
const header = selected ? { label: "Selected", text: target.selection?.text ?? "" } : content ? { label: "Content", text: options.content ?? "" } : null;
|
|
459
|
-
return { header, sections: list, roles };
|
|
460
|
-
}
|
|
461
|
-
function withExpandedChildren(model, nodeId, children) {
|
|
462
|
-
const visit = (nodes) => nodes.map((n) => {
|
|
463
|
-
if (n.kind !== "submenu") return n;
|
|
464
|
-
if (n.id === nodeId) {
|
|
465
|
-
return children === "loading" ? { ...n, loading: true } : { ...n, lazy: false, loading: false, pending: false, children };
|
|
466
|
-
}
|
|
467
|
-
return { ...n, children: visit(n.children) };
|
|
468
|
-
});
|
|
469
|
-
return { ...model, sections: model.sections.map((s) => ({ ...s, nodes: visit(s.nodes) })) };
|
|
470
|
-
}
|
|
471
|
-
function findNode(nodes, id) {
|
|
472
|
-
for (const n of nodes) {
|
|
473
|
-
if (n.id === id) return n;
|
|
474
|
-
if (n.kind === "submenu") {
|
|
475
|
-
const hit = findNode(n.children, id);
|
|
476
|
-
if (hit) return hit;
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
return null;
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
// src/react/context.ts
|
|
483
|
-
var React = __toESM(require("react"), 1);
|
|
484
|
-
var KEY = /* @__PURE__ */ Symbol.for("ai-matrx.alchemy.actions-context");
|
|
485
|
-
var AlchemyActionsContext = globalThis[KEY] ??= React.createContext(null);
|
|
486
|
-
function useAlchemyActions() {
|
|
487
|
-
const value = React.useContext(AlchemyActionsContext);
|
|
488
|
-
if (!value) throw new Error("Alchemy actions: render inside <AlchemyActionsProvider> (@ai-matrx/alchemy/react/host).");
|
|
489
|
-
return value;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// src/react/engine.tsx
|
|
493
|
-
var import_jsx_runtime = require("react/jsx-runtime");
|
|
494
|
-
function useMenuEngine(target, options = {}) {
|
|
495
|
-
const { registry, ports } = useAlchemyActions();
|
|
496
|
-
const [resolved, setResolved] = React2.useState([]);
|
|
497
|
-
const [model, setModel] = React2.useState(null);
|
|
498
|
-
const [tick, setTick] = React2.useState(0);
|
|
499
|
-
const expanded = React2.useRef(/* @__PURE__ */ new Map());
|
|
500
|
-
const opened = React2.useRef(/* @__PURE__ */ new Set());
|
|
501
|
-
const expandSeq = React2.useRef(/* @__PURE__ */ new Map());
|
|
502
|
-
const active = options.active ?? true;
|
|
503
|
-
const optionsRef = React2.useRef(options);
|
|
504
|
-
optionsRef.current = options;
|
|
505
|
-
React2.useEffect(() => registry.subscribe(() => setTick((t) => t + 1)), [registry]);
|
|
506
|
-
const expandAction = React2.useCallback(
|
|
507
|
-
async (action, nodeId) => {
|
|
508
|
-
if (!target || !action.expand) return;
|
|
509
|
-
const seq = (expandSeq.current.get(nodeId) ?? 0) + 1;
|
|
510
|
-
expandSeq.current.set(nodeId, seq);
|
|
511
|
-
const latest = () => expandSeq.current.get(nodeId) === seq;
|
|
512
|
-
setModel((m) => m ? withExpandedChildren(m, nodeId, "loading") : m);
|
|
513
|
-
try {
|
|
514
|
-
const children = await action.expand(target, new AbortController().signal);
|
|
515
|
-
if (!latest()) return;
|
|
516
|
-
const nodes = children.map((child) => {
|
|
517
|
-
expanded.current.set(child.id, child);
|
|
518
|
-
return actionToNode({ action: child, eligibility: { status: "available" } }, target, ports.i18n ? { i18n: ports.i18n } : {});
|
|
519
|
-
});
|
|
520
|
-
setModel((m) => m ? withExpandedChildren(m, nodeId, nodes) : m);
|
|
521
|
-
} catch (error) {
|
|
522
|
-
if (!latest()) return;
|
|
523
|
-
ports.diagnostics.capture(error, { area: "menu", detail: { nodeId, stage: "expand" } });
|
|
524
|
-
setModel((m) => m ? withExpandedChildren(m, nodeId, []) : m);
|
|
525
|
-
}
|
|
526
|
-
},
|
|
527
|
-
[target, ports]
|
|
528
|
-
);
|
|
529
|
-
React2.useEffect(() => {
|
|
530
|
-
opened.current.clear();
|
|
531
|
-
}, [target]);
|
|
532
|
-
React2.useEffect(() => {
|
|
533
|
-
if (!target || !active) return;
|
|
534
|
-
let live = true;
|
|
535
|
-
void registry.resolve(target, optionsRef.current.restrict).then((list) => {
|
|
536
|
-
if (!live) return;
|
|
537
|
-
expanded.current.clear();
|
|
538
|
-
setResolved(list);
|
|
539
|
-
setModel(buildMenuModel(target, list, { ...ports.i18n ? { i18n: ports.i18n } : {}, content: optionsRef.current.content ?? null }));
|
|
540
|
-
for (const id of opened.current) {
|
|
541
|
-
const action = list.find((r) => r.action.id === id)?.action;
|
|
542
|
-
if (action?.expand) void expandAction(action, id);
|
|
543
|
-
}
|
|
544
|
-
});
|
|
545
|
-
return () => {
|
|
546
|
-
live = false;
|
|
547
|
-
};
|
|
548
|
-
}, [registry, ports, target, active, tick, options.revision, expandAction]);
|
|
549
|
-
const resolvedRef = React2.useRef(resolved);
|
|
550
|
-
resolvedRef.current = resolved;
|
|
551
|
-
const subscribedIds = resolved.map((r) => r.action.id).join("\n");
|
|
552
|
-
React2.useEffect(() => {
|
|
553
|
-
if (!target) return;
|
|
554
|
-
let subscribing = true;
|
|
555
|
-
const onChange = () => {
|
|
556
|
-
if (!subscribing) setTick((t) => t + 1);
|
|
557
|
-
};
|
|
558
|
-
const offs = resolvedRef.current.map((r) => r.action.subscribe?.(onChange, target)).filter((off) => typeof off === "function");
|
|
559
|
-
subscribing = false;
|
|
560
|
-
return () => offs.forEach((off) => off());
|
|
561
|
-
}, [subscribedIds, target]);
|
|
562
|
-
const findAction = React2.useCallback(
|
|
563
|
-
(id) => resolved.find((r) => r.action.id === id)?.action ?? expanded.current.get(id),
|
|
564
|
-
[resolved]
|
|
565
|
-
);
|
|
566
|
-
const run = React2.useCallback(
|
|
567
|
-
async (nodeId) => {
|
|
568
|
-
if (!target || !model) return null;
|
|
569
|
-
const node = model.sections.map((s) => findNode(s.nodes, nodeId)).find(Boolean) ?? null;
|
|
570
|
-
const action = findAction(node && "actionId" in node && node.actionId ? node.actionId : nodeId);
|
|
571
|
-
if (!action) return null;
|
|
572
|
-
const o = optionsRef.current;
|
|
573
|
-
const result = await runAction(action, target, {
|
|
574
|
-
ports,
|
|
575
|
-
...o.source ? { source: o.source } : {},
|
|
576
|
-
...o.contentFingerprint ? { contentFingerprint: o.contentFingerprint } : {}
|
|
577
|
-
});
|
|
578
|
-
o.onRan?.(nodeId, result);
|
|
579
|
-
return result;
|
|
580
|
-
},
|
|
581
|
-
[target, model, findAction, ports]
|
|
582
|
-
);
|
|
583
|
-
const expand = React2.useCallback(
|
|
584
|
-
async (nodeId) => {
|
|
585
|
-
const action = findAction(nodeId);
|
|
586
|
-
if (!target || !action?.expand) return;
|
|
587
|
-
opened.current.add(nodeId);
|
|
588
|
-
await expandAction(action, nodeId);
|
|
589
|
-
},
|
|
590
|
-
[findAction, target, expandAction]
|
|
591
|
-
);
|
|
592
|
-
const icon = React2.useCallback(
|
|
593
|
-
(key, tone) => {
|
|
594
|
-
if (!key || !ports.icons) return null;
|
|
595
|
-
const Resolved = ports.icons.resolve(key);
|
|
596
|
-
return Resolved ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Resolved, { className: `h-4 w-4 shrink-0 ${tone ?? ""}`, "aria-hidden": true }) : null;
|
|
597
|
-
},
|
|
598
|
-
[ports.icons]
|
|
599
|
-
);
|
|
600
|
-
const explain = React2.useCallback(
|
|
601
|
-
(sentence) => {
|
|
602
|
-
if (ports.notify) ports.notify.info(sentence);
|
|
603
|
-
else ports.diagnostics.capture(new Error(`No notify port to explain: ${sentence}`), { area: "menu", detail: { sentence } });
|
|
604
|
-
},
|
|
605
|
-
[ports]
|
|
606
|
-
);
|
|
607
|
-
return { model, resolved, run, expand, icon, explain };
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
// src/react/dropdown-nodes.tsx
|
|
611
|
-
var React3 = __toESM(require("react"), 1);
|
|
612
|
-
var import_design_system = require("@ai-matrx/design-system");
|
|
613
|
-
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
614
|
-
function NodeList({ nodes, engine }) {
|
|
615
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: nodes.map((node) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MenuNodeView, { node, engine }, node.id)) });
|
|
616
|
-
}
|
|
617
|
-
function SectionList({
|
|
618
|
-
sections,
|
|
619
|
-
engine
|
|
620
|
-
}) {
|
|
621
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: sections.map((section, index) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(React3.Fragment, { children: [
|
|
622
|
-
index > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_design_system.DropdownMenuSeparator, {}) : null,
|
|
623
|
-
sectionLabel(section) ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_design_system.DropdownMenuLabel, { className: "text-xs font-medium text-muted-foreground", children: sectionLabel(section) }) : null,
|
|
624
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(NodeList, { nodes: section.nodes, engine })
|
|
625
|
-
] }, section.id)) });
|
|
626
|
-
}
|
|
627
|
-
function iconOf(node, engine) {
|
|
628
|
-
if (node.kind !== "item") {
|
|
629
|
-
if (!("icon" in node)) return null;
|
|
630
|
-
return engine.icon(node.icon);
|
|
631
|
-
}
|
|
632
|
-
if (node.stateIcon) {
|
|
633
|
-
const glyph = engine.icon(node.stateIcon.icon);
|
|
634
|
-
return node.stateIcon.spin ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "inline-flex animate-spin", children: glyph }) : glyph;
|
|
635
|
-
}
|
|
636
|
-
if (!("icon" in node)) return null;
|
|
637
|
-
const tone = node.pressed === false ? void 0 : node.iconTone;
|
|
638
|
-
return engine.icon(node.icon, tone);
|
|
639
|
-
}
|
|
640
|
-
function MenuNodeView({ node, engine }) {
|
|
641
|
-
switch (node.kind) {
|
|
642
|
-
case "separator":
|
|
643
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_design_system.DropdownMenuSeparator, {});
|
|
644
|
-
case "label":
|
|
645
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_design_system.DropdownMenuLabel, { className: "text-xs font-medium text-muted-foreground", children: node.label });
|
|
646
|
-
case "submenu":
|
|
647
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
648
|
-
import_design_system.DropdownMenuSub,
|
|
649
|
-
{
|
|
650
|
-
onOpenChange: (open) => {
|
|
651
|
-
if (open && node.lazy && !node.loading) void engine.expand(node.id);
|
|
652
|
-
},
|
|
653
|
-
children: [
|
|
654
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_design_system.DropdownMenuSubTrigger, { "data-alchemy-node": node.id, className: "gap-2", "aria-busy": node.pending ? true : void 0, children: [
|
|
655
|
-
iconOf(node, engine),
|
|
656
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "truncate", children: node.label }),
|
|
657
|
-
node.pending ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "ml-auto text-xs text-muted-foreground", children: "Loading\u2026" }) : null
|
|
658
|
-
] }),
|
|
659
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_design_system.DropdownMenuSubContent, { className: "max-h-[var(--radix-dropdown-menu-content-available-height)] overflow-y-auto", children: node.loading ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_design_system.DropdownMenuLabel, { className: "text-xs text-muted-foreground", children: "Loading\u2026" }) : node.children.length === 0 && !node.lazy ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_design_system.DropdownMenuLabel, { className: "text-xs text-muted-foreground", children: node.emptyLabel ?? "Nothing here yet" }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(NodeList, { nodes: node.children, engine }) })
|
|
660
|
-
]
|
|
661
|
-
}
|
|
662
|
-
);
|
|
663
|
-
case "checkbox":
|
|
664
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
665
|
-
import_design_system.DropdownMenuCheckboxItem,
|
|
666
|
-
{
|
|
667
|
-
"data-alchemy-node": node.id,
|
|
668
|
-
checked: node.checked,
|
|
669
|
-
onSelect: () => void engine.run(node.id),
|
|
670
|
-
className: "gap-2",
|
|
671
|
-
children: [
|
|
672
|
-
iconOf(node, engine),
|
|
673
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "truncate", children: node.label })
|
|
674
|
-
]
|
|
675
|
-
}
|
|
676
|
-
);
|
|
677
|
-
case "link":
|
|
678
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_design_system.DropdownMenuItem, { asChild: true, "data-alchemy-node": node.id, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("a", { href: node.href, target: node.target, className: "gap-2", children: [
|
|
679
|
-
iconOf(node, engine),
|
|
680
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "truncate", children: node.label })
|
|
681
|
-
] }) });
|
|
682
|
-
case "item":
|
|
683
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LeafItem, { node, engine });
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
function LeafItem({ node, engine }) {
|
|
687
|
-
const row = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
688
|
-
import_design_system.DropdownMenuItem,
|
|
689
|
-
{
|
|
690
|
-
"data-alchemy-node": node.id,
|
|
691
|
-
"aria-disabled": node.unavailable ? true : void 0,
|
|
692
|
-
"aria-pressed": node.pressed,
|
|
693
|
-
onSelect: (event) => {
|
|
694
|
-
if (node.unavailable) {
|
|
695
|
-
event.preventDefault();
|
|
696
|
-
return;
|
|
697
|
-
}
|
|
698
|
-
void engine.run(node.id);
|
|
699
|
-
},
|
|
700
|
-
className: `gap-2 ${node.unavailable ? "opacity-50" : ""} ${node.destructive ? "text-destructive" : ""}`,
|
|
701
|
-
children: [
|
|
702
|
-
iconOf(node, engine),
|
|
703
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
704
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "truncate", children: node.label }),
|
|
705
|
-
node.description ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "truncate text-xs text-muted-foreground", children: node.description }) : null
|
|
706
|
-
] }),
|
|
707
|
-
node.hint ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "ml-auto text-xs text-muted-foreground", children: node.hint }) : null
|
|
708
|
-
]
|
|
709
|
-
}
|
|
710
|
-
);
|
|
711
|
-
if (!node.unavailable) return row;
|
|
712
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_design_system.Tooltip, { children: [
|
|
713
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_design_system.TooltipTrigger, { asChild: true, children: row }),
|
|
714
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_design_system.TooltipContent, { children: node.unavailable.sentence })
|
|
715
|
-
] });
|
|
716
|
-
}
|
|
717
|
-
function LeafButton({
|
|
718
|
-
node,
|
|
719
|
-
engine,
|
|
720
|
-
showLabel
|
|
721
|
-
}) {
|
|
722
|
-
if (node.kind !== "item" && node.kind !== "checkbox" && node.kind !== "link") return null;
|
|
723
|
-
const unavailable = node.kind === "item" ? node.unavailable : void 0;
|
|
724
|
-
const pressed = node.kind === "item" ? node.pressed : node.kind === "checkbox" ? node.checked : void 0;
|
|
725
|
-
const glyph = iconOf(node, engine);
|
|
726
|
-
const button = /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
727
|
-
"button",
|
|
728
|
-
{
|
|
729
|
-
type: "button",
|
|
730
|
-
"data-alchemy-node": node.id,
|
|
731
|
-
"data-matrx-button": "",
|
|
732
|
-
"data-tap-floor": "",
|
|
733
|
-
"data-tap-floor-square": "",
|
|
734
|
-
"aria-label": node.label,
|
|
735
|
-
"aria-disabled": unavailable ? true : void 0,
|
|
736
|
-
"aria-pressed": pressed,
|
|
737
|
-
onMouseDown: (e) => {
|
|
738
|
-
if (node.kind === "item" && node.preserveSelection) e.preventDefault();
|
|
739
|
-
},
|
|
740
|
-
onClick: () => {
|
|
741
|
-
if (unavailable) {
|
|
742
|
-
engine.explain(unavailable.sentence);
|
|
743
|
-
return;
|
|
744
|
-
}
|
|
745
|
-
if (node.kind === "link") {
|
|
746
|
-
window.location.assign(node.href);
|
|
747
|
-
return;
|
|
748
|
-
}
|
|
749
|
-
void engine.run(node.id);
|
|
750
|
-
},
|
|
751
|
-
className: `inline-flex h-7 min-w-7 shrink-0 items-center justify-center gap-1.5 rounded-md px-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground ${unavailable ? "cursor-default opacity-50 hover:bg-transparent" : ""} ${pressed ? "text-foreground" : ""}`,
|
|
752
|
-
children: [
|
|
753
|
-
glyph,
|
|
754
|
-
showLabel || !glyph ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "whitespace-nowrap", children: node.label }) : null
|
|
755
|
-
]
|
|
756
|
-
}
|
|
757
|
-
);
|
|
758
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_design_system.Tooltip, { children: [
|
|
759
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_design_system.TooltipTrigger, { asChild: true, children: button }),
|
|
760
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_design_system.TooltipContent, { children: unavailable ? unavailable.sentence : node.label })
|
|
761
|
-
] });
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
// src/react/menu.tsx
|
|
765
|
-
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
766
|
-
function ContextMenuPanel({
|
|
767
|
-
target,
|
|
768
|
-
point,
|
|
769
|
-
open,
|
|
770
|
-
onOpenChange,
|
|
771
|
-
arrangement = "command",
|
|
772
|
-
density = "comfortable",
|
|
773
|
-
...options
|
|
774
|
-
}) {
|
|
775
|
-
const engine = useMenuEngine(target, { ...options, onRan: (id, r) => {
|
|
776
|
-
options.onRan?.(id, r);
|
|
777
|
-
onOpenChange(false);
|
|
778
|
-
} });
|
|
779
|
-
const [query, setQuery] = React4.useState("");
|
|
780
|
-
const arranged = engine.model ? context(engine.model, arrangement) : null;
|
|
781
|
-
const filtered = engine.model && query.trim() ? filterPalette(palette(engine.model), query) : null;
|
|
782
|
-
const anchor = /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system2.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
783
|
-
"span",
|
|
784
|
-
{
|
|
785
|
-
"aria-hidden": true,
|
|
786
|
-
"data-alchemy-anchor": "context",
|
|
787
|
-
style: { position: "fixed", left: point.x, top: point.y, width: 0, height: 0, pointerEvents: "none" }
|
|
788
|
-
}
|
|
789
|
-
) });
|
|
790
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system2.DropdownMenu, { open, onOpenChange, modal: false, children: [
|
|
791
|
-
typeof document === "undefined" ? anchor : (0, import_react_dom.createPortal)(anchor, document.body),
|
|
792
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
793
|
-
import_design_system2.DropdownMenuContent,
|
|
794
|
-
{
|
|
795
|
-
align: "start",
|
|
796
|
-
"data-alchemy-layout": `context-${arrangement}`,
|
|
797
|
-
onPointerUpCapture: (event) => {
|
|
798
|
-
if (event.button === 2) event.preventDefault();
|
|
799
|
-
},
|
|
800
|
-
className: `w-72 max-h-[var(--radix-dropdown-menu-content-available-height)] overflow-y-auto ${density === "compact" ? "text-xs" : ""}`,
|
|
801
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system2.TooltipProvider, { children: [
|
|
802
|
-
engine.model?.header ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_design_system2.DropdownMenuLabel, { className: "truncate text-xs font-normal text-muted-foreground", title: engine.model.header.text, children: [
|
|
803
|
-
engine.model.header.label,
|
|
804
|
-
": ",
|
|
805
|
-
engine.model.header.text
|
|
806
|
-
] }) : null,
|
|
807
|
-
!arranged ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "px-2 py-1.5 text-xs text-muted-foreground", children: "Loading\u2026" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
808
|
-
arranged.strip.length ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { role: "toolbar", "aria-label": "Clipboard", className: "flex items-center gap-0.5 px-1 py-1", children: arranged.strip.map((node) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(LeafButton, { node, engine, showLabel: false }, node.id)) }) : null,
|
|
809
|
-
arranged.filterable ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "px-1 pb-1", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
810
|
-
"input",
|
|
811
|
-
{
|
|
812
|
-
"aria-label": "Filter actions",
|
|
813
|
-
placeholder: "Type to filter\u2026",
|
|
814
|
-
value: query,
|
|
815
|
-
onChange: (e) => setQuery(e.target.value),
|
|
816
|
-
onKeyDown: (e) => {
|
|
817
|
-
e.stopPropagation();
|
|
818
|
-
if (e.key === "Enter" && filtered?.leaves[0]) void engine.run(filtered.leaves[0].node.id);
|
|
819
|
-
},
|
|
820
|
-
className: "h-7 w-full rounded-md border border-border bg-transparent px-2 text-sm outline-none"
|
|
821
|
-
}
|
|
822
|
-
) }) : null,
|
|
823
|
-
(arranged.strip.length || arranged.filterable) && arranged.sections.length ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_design_system2.DropdownMenuSeparator, {}) : null,
|
|
824
|
-
filtered ? filtered.leaves.length ? filtered.leaves.map(({ node, breadcrumb }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
825
|
-
import_design_system2.DropdownMenuItem,
|
|
826
|
-
{
|
|
827
|
-
"data-alchemy-node": node.id,
|
|
828
|
-
onSelect: () => {
|
|
829
|
-
if (node.kind === "submenu") void engine.expand(node.id);
|
|
830
|
-
else void engine.run(node.id);
|
|
831
|
-
},
|
|
832
|
-
className: "flex-col items-start gap-0",
|
|
833
|
-
children: [
|
|
834
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { children: node.label }),
|
|
835
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-xs text-muted-foreground", children: breadcrumb.join(" \u203A ") })
|
|
836
|
-
]
|
|
837
|
-
},
|
|
838
|
-
node.id
|
|
839
|
-
)) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "px-2 py-1.5 text-xs text-muted-foreground", children: [
|
|
840
|
-
"No action matches \u201C",
|
|
841
|
-
query,
|
|
842
|
-
"\u201D."
|
|
843
|
-
] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SectionList, { sections: arranged.sections, engine })
|
|
844
|
-
] })
|
|
845
|
-
] })
|
|
846
|
-
}
|
|
847
|
-
)
|
|
848
|
-
] });
|
|
849
|
-
}
|
|
2
|
+
"use strict";var __create=Object.create;var __defProp=Object.defineProperty;var __getOwnPropDesc=Object.getOwnPropertyDescriptor;var __getOwnPropNames=Object.getOwnPropertyNames;var __getProtoOf=Object.getPrototypeOf,__hasOwnProp=Object.prototype.hasOwnProperty;var __export=(target,all)=>{for(var name in all)__defProp(target,name,{get:all[name],enumerable:!0})},__copyProps=(to,from,except,desc)=>{if(from&&typeof from=="object"||typeof from=="function")for(let key of __getOwnPropNames(from))!__hasOwnProp.call(to,key)&&key!==except&&__defProp(to,key,{get:()=>from[key],enumerable:!(desc=__getOwnPropDesc(from,key))||desc.enumerable});return to};var __toESM=(mod,isNodeMode,target)=>(target=mod!=null?__create(__getProtoOf(mod)):{},__copyProps(isNodeMode||!mod||!mod.__esModule?__defProp(target,"default",{value:mod,enumerable:!0}):target,mod)),__toCommonJS=mod=>__copyProps(__defProp({},"__esModule",{value:!0}),mod);var react_menu_exports={};__export(react_menu_exports,{ContextMenuPanel:()=>ContextMenuPanel});module.exports=__toCommonJS(react_menu_exports);var React4=__toESM(require("react"),1),import_react_dom=require("react-dom"),import_design_system2=require("@ai-matrx/design-system");function sectionLabel(section){return section.label??null}function isLeaf(node){return node.kind==="item"||node.kind==="checkbox"||node.kind==="link"}function fold(section){return{kind:"submenu",id:`${section.id}:fold`,label:section.label,...section.icon!==void 0?{icon:section.icon}:{},children:section.nodes}}function rowCount(nodes){return nodes.filter(n=>n.kind!=="separator"&&n.kind!=="label").length}function stripOf(model){let clipboard=model.sections.find(s=>s.group==="clipboard"&&!s.primary);return clipboard?clipboard.nodes.filter(isLeaf):[]}function clipboardRest(model){let clipboard=model.sections.find(s=>s.group==="clipboard"&&!s.primary);return clipboard?clipboard.nodes.filter(n=>!isLeaf(n)&&n.kind==="submenu"):[]}function classic(model){return{strip:[],sections:model.sections,filterable:!1}}function tiered(model,filterable){let strip=stripOf(model),sections=[],rest=clipboardRest(model);for(let s of model.sections){if(s.group==="clipboard"&&!s.primary){rest.length&§ions.push({...s,nodes:rest});continue}if(s.primary){sections.push(s);continue}if(!s.label||s.group==="surface-info"||rowCount(s.nodes)<=3){sections.push(s);continue}sections.push({id:s.id,group:s.group,nodes:[fold({...s,label:s.label})]})}return{strip,sections,filterable}}function context(model,arrangement){return arrangement==="classic"?classic(model):tiered(model,arrangement==="command")}function palette(model){let leaves=[],walk=(nodes,trail)=>{for(let n of nodes)isLeaf(n)?leaves.push({node:n,breadcrumb:trail}):n.kind==="submenu"&&(n.lazy?leaves.push({node:n,breadcrumb:trail}):walk(n.children,[...trail,n.label]))};for(let s of model.sections)walk(s.nodes,s.label?[s.label]:[]);return{leaves}}function filterPalette(arranged,query){let words=query.toLowerCase().split(/\s+/).filter(Boolean);return words.length?{leaves:arranged.leaves.filter(l=>{let hay=`${l.node.label} ${l.breadcrumb.join(" ")}`.toLowerCase();return words.every(w=>hay.includes(w))})}:arranged}var React2=__toESM(require("react"),1);var DEFAULT_SENSITIVITY=Object.freeze({exportable:!0,classification:"ordinary",includedByDefault:!0});function resolveSensitivity(value){return{exportable:value.sensitivity?.exportable??DEFAULT_SENSITIVITY.exportable,classification:value.sensitivity?.classification??DEFAULT_SENSITIVITY.classification,includedByDefault:value.sensitivity?.includedByDefault??DEFAULT_SENSITIVITY.includedByDefault}}function toJson(value){if(!(value===void 0||typeof value=="function"||typeof value=="symbol")){if(value===null||typeof value=="string"||typeof value=="boolean")return value;if(typeof value=="number")return Number.isFinite(value)?value:null;if(typeof value=="bigint")return value.toString();if(value instanceof Date)return value.toISOString();if(Array.isArray(value))return value.map(v=>toJson(v)??null);if(typeof value=="object"){let out={};for(let[k,v]of Object.entries(value)){let j=toJson(v);j!==void 0&&(out[k]=j)}return out}}}function projectValues(raw,declared,pathPrefix=""){let byName=new Map(declared.map(d=>[d.name,d])),values={},omissions=[];for(let[name,value]of Object.entries(raw)){let path=pathPrefix?`${pathPrefix}.${name}`:name,decl=byName.get(name);if(!decl){omissions.push({path,reason:"filtered"});continue}let sensitivity=resolveSensitivity(decl);if(sensitivity.classification!=="ordinary"){omissions.push({path,reason:"excluded",classification:sensitivity.classification});continue}if(!sensitivity.exportable){omissions.push({path,reason:"excluded"});continue}let json=toJson(value);json!==void 0&&(values[name]=json)}return{values,omissions}}function createProjectedReads(source){return{async values(signal){if(!source.surface||!source.declaration)return{values:{},omissions:[]};let raw=await source.surface.readValues(signal),screenValues=source.declaration.values;return projectValues(raw,screenValues)},async item(signal){let item=source.item;if(!item||!source.declaration)return null;let type=source.declaration.itemTypes?.find(t=>t.name===item.itemType);if(!type)return{values:{},omissions:[{path:item.itemType,reason:"filtered"}]};let raw=await item.readValues(signal);return projectValues(raw,type.values,item.itemType)},async live(signal){return source.surface?.readLive?source.surface.readLive(signal):{}}}}async function runAction(action,target,options){let{ports}=options;if(action.requiresAuth&&!target.auth.authenticated)return ports.auth?(ports.auth.signInThenResume({actionId:action.id,contentFingerprint:options.contentFingerprint??action.id}),{status:"auth-pending"}):(ports.diagnostics.capture(new Error(`requiresAuth action "${action.id}" ran signed out with no auth port`),{area:"actions",detail:{actionId:action.id}}),{status:"failed",error:new Error("sign-in unavailable")});let reads=createProjectedReads(options.source??{surface:null,declaration:target.declaration,item:null}),signal=options.signal??new AbortController().signal;try{return{status:"ran",outcome:await action.run(target,{reads,ports,signal})}}catch(error){ports.diagnostics.capture(error,{area:"actions",detail:{actionId:action.id,stage:"run"}});let label=typeof action.label=="string"?action.label:action.id;return ports.notify?.error(`${label} did not finish.`,"Try again; if it keeps failing, report it from the feedback button."),{status:"failed",error}}}var APPROVED_HEADINGS=new Set(["Save","Copy as","Export","Edit","Creator tools","Admin","App","Compare","History"]),CONTAINER_WORD=/^(more|more…|more\.\.\.|other|others|misc|miscellaneous|additional|extra|extras|actions|options|advanced|stuff|everything else)$/i,TARGET_SHAPE=/^(Cell|Row|Column|Table|Range|Selection|Record) · .+$/;function isContainerWord(label){return CONTAINER_WORD.test(label.trim())}function isApprovedHeading(label,kind="group",approved=APPROVED_HEADINGS){let l=label.trim();return!l||isContainerWord(l)?!1:approved.has(l)?!0:kind==="target"||TARGET_SHAPE.test(l)}function defaultHeadingPolicy(){return globalThis.process?.env?.NODE_ENV==="production"?"inline":"throw"}var UnapprovedHeadingError=class extends Error{heading;constructor(heading,where){super(`Menu heading "${heading}" (${where}) is not an approved heading. Remedy: show these rows inline \u2014 declare no section (or kind "target" for the clicked thing's own label) \u2014 or ask Arman to add the name to APPROVED_HEADINGS in @ai-matrx/alchemy/menu. A generic container word ("More\u2026", "Other") is never approved.`),this.name="UnapprovedHeadingError",this.heading=heading}};var CATEGORY_GROUP={clipboard:"clipboard",copy:"transfer",export:"transfer",save:"document",share:"document",edit:"editable",history:"history",ai:"ai",ask:"ai",listen:"tools",study:"tools",app:"tools",feedback:"quick",creator:"admin",admin:"admin","surface-info":"surface-info"},GROUP_ORDER=["clipboard","editable","transfer","document","history","ai","tools","quick","surface","admin","surface-info"],CATEGORY_ORDER=["clipboard","edit","copy","export","save","share","history","ask","ai","listen","study","app","feedback","creator","admin","surface-info"];function translate(i18n,key,fallback){if(!i18n)return fallback;try{return i18n.t(key,fallback)}catch{return fallback}}function actionToNode(resolved,target,options={}){let{action,eligibility}=resolved,rawLabel=typeof action.label=="function"?action.label(target):action.label,label=translate(options.i18n,action.labelKey??action.id,rawLabel),base={id:action.id,actionId:action.id,...action.icon!==void 0?{icon:action.icon}:{},...action.placement!==void 0?{placement:action.placement}:{}};if(action.expand)return{...base,kind:"submenu",label,lazy:!0,loading:!1,...action.pending?.(target)?{pending:!0}:{},children:[],...action.emptyLabel!==void 0?{emptyLabel:action.emptyLabel}:{}};if(action.href!==void 0){let href=typeof action.href=="function"?action.href(target):action.href;return{...base,kind:"link",label,href}}if(action.toggle)return{...base,kind:"checkbox",label,checked:action.pressed?.(target)??!1,...action.description!==void 0?{description:action.description}:{}};let state=action.stateIcon?.(target)??null,pressed=action.pressed?.(target),hint=action.hint!==void 0?translate(options.i18n,action.hintKey??`${action.id}.hint`,action.hint):void 0;return{...base,kind:"item",label,...action.iconTone!==void 0?{iconTone:action.iconTone}:{},...state?{stateIcon:state}:{},...action.description!==void 0?{description:action.description}:{},...hint!==void 0?{hint}:{},...pressed!==void 0?{pressed}:{},...action.destructive?{destructive:!0}:{},...action.preserveSelection?{preserveSelection:!0}:{},...eligibility.status==="unavailable-verb"?{unavailable:{sentence:eligibility.sentence}}:{}}}function buildMenuModel(target,resolved,options={}){let policy=options.headingPolicy??defaultHeadingPolicy();resolved=resolved.map(r=>{let sec=r.action.section;if(r.action.expand&&isContainerWord(typeof r.action.label=="string"?r.action.label:"")&&policy==="throw")throw new UnapprovedHeadingError(r.action.label,`submenu of ${r.action.id}`);if(!sec)return r;let kind=sec.kind??(sec.primary?"target":"group");if(isApprovedHeading(sec.label,kind))return r;if(policy==="throw")throw new UnapprovedHeadingError(sec.label,`section of ${r.action.id}`);let{section:_dropped,...rest}=r.action;return{...r,action:rest}});let ordered=resolved.map((r,index)=>({r,index})).sort((a,b)=>{let ca=CATEGORY_ORDER.indexOf(a.r.action.category),cb=CATEGORY_ORDER.indexOf(b.r.action.category);if(ca!==cb)return ca-cb;let oa=a.r.action.order??0,ob=b.r.action.order??0;return oa!==ob?oa-ob:a.index-b.index}).map(x=>x.r),sections=new Map,roles={},explicit=new Map;resolved.forEach((r,index)=>{let sec=r.action.section;if(!sec)return;let entry=explicit.get(sec.id)??{group:CATEGORY_GROUP[r.action.category],rows:[]};entry.rows.push({r,index}),explicit.set(sec.id,entry)});let placed=new Set;for(let r of ordered){let sec=r.action.section;if(sec&&placed.has(sec.id))continue;let group=sec?explicit.get(sec.id)?.group??CATEGORY_GROUP[r.action.category]:CATEGORY_GROUP[r.action.category],sectionId=sec?`section:${sec.id}`:group,section=sections.get(sectionId);section||(section={id:sectionId,group,nodes:[],...sec?{label:translate(options.i18n,sec.labelKey??`section.${sec.id}`,sec.label)}:{},...sec?.icon!==void 0?{icon:sec.icon}:{},...sec?.primary?{primary:!0}:{},...sec&&(sec.kind??(sec.primary?"target":"group"))==="target"?{target:!0}:{}},sections.set(sectionId,section));let rows=sec?(explicit.get(sec.id)?.rows??[]).sort((x,y)=>(x.r.action.order??0)-(y.r.action.order??0)||x.index-y.index).map(x=>x.r):[r];sec&&placed.add(sec.id);for(let row of rows)section.nodes.push(actionToNode(row,target,options)),row.action.verb&&(roles[row.action.verb]=row.action.id)}let list=[...sections.values()].sort((a,b)=>!!a.primary!=!!b.primary?a.primary?-1:1:GROUP_ORDER.indexOf(a.group)-GROUP_ORDER.indexOf(b.group)),selected=target.selection?.text?.trim(),content=options.content?.trim();return{header:selected?{label:"Selected",text:target.selection?.text??""}:content?{label:"Content",text:options.content??""}:null,sections:list,roles}}function withExpandedChildren(model,nodeId,children){let visit=nodes=>nodes.map(n=>n.kind!=="submenu"?n:n.id===nodeId?children==="loading"?{...n,loading:!0}:{...n,lazy:!1,loading:!1,pending:!1,children}:{...n,children:visit(n.children)});return{...model,sections:model.sections.map(s=>({...s,nodes:visit(s.nodes)}))}}function findNode(nodes,id){for(let n of nodes){if(n.id===id)return n;if(n.kind==="submenu"){let hit=findNode(n.children,id);if(hit)return hit}}return null}var React=__toESM(require("react"),1),KEY=Symbol.for("ai-matrx.alchemy.actions-context"),AlchemyActionsContext=globalThis[KEY]??=React.createContext(null);function useAlchemyActions(){let value=React.useContext(AlchemyActionsContext);if(!value)throw new Error("Alchemy actions: render inside <AlchemyActionsProvider> (@ai-matrx/alchemy/react/host).");return value}function isLatestExpansion(capturedTargetEpoch,currentTargetEpoch,capturedNodeSequence,currentNodeSequence){return capturedTargetEpoch===currentTargetEpoch&&capturedNodeSequence===currentNodeSequence}var import_jsx_runtime=require("react/jsx-runtime");function useMenuEngine(target,options={}){let{registry,ports}=useAlchemyActions(),[resolved,setResolved]=React2.useState([]),[model,setModel]=React2.useState(null),[tick,setTick]=React2.useState(0),expanded=React2.useRef(new Map),expandSeq=React2.useRef(new Map),targetEpoch=React2.useRef(0),currentTarget=React2.useRef(target);currentTarget.current!==target&&(currentTarget.current=target,targetEpoch.current+=1);let active=options.active??!0,optionsRef=React2.useRef(options);optionsRef.current=options,React2.useEffect(()=>registry.subscribe(()=>setTick(t=>t+1)),[registry]);let expandAction=React2.useCallback(async(action,nodeId)=>{if(!target||!action.expand)return;let epoch=targetEpoch.current,seq=(expandSeq.current.get(nodeId)??0)+1;expandSeq.current.set(nodeId,seq);let latest=()=>isLatestExpansion(epoch,targetEpoch.current,seq,expandSeq.current.get(nodeId));setModel(m=>m&&withExpandedChildren(m,nodeId,"loading"));try{let children=await action.expand(target,new AbortController().signal);if(!latest())return;let nodes=children.map(child=>(expanded.current.set(child.id,child),actionToNode({action:child,eligibility:{status:"available"}},target,ports.i18n?{i18n:ports.i18n}:{})));setModel(m=>m&&withExpandedChildren(m,nodeId,nodes))}catch(error){if(!latest())return;ports.diagnostics.capture(error,{area:"menu",detail:{nodeId,stage:"expand"}}),setModel(m=>m&&withExpandedChildren(m,nodeId,[]))}},[target,ports]);React2.useEffect(()=>{expanded.current.clear()},[target]),React2.useEffect(()=>{if(!target||!active)return;let live=!0;return registry.resolve(target,optionsRef.current.restrict).then(list=>{if(live){setResolved(list),setModel(buildMenuModel(target,list,{...ports.i18n?{i18n:ports.i18n}:{},content:optionsRef.current.content??null}));for(let{action}of list)expanded.current.get(action.id)===null&&action.expand&&expandAction(action,action.id)}}),()=>{live=!1}},[registry,ports,target,active,tick,options.revision,expandAction]);let resolvedRef=React2.useRef(resolved);resolvedRef.current=resolved;let subscribedIds=resolved.map(r=>r.action.id).join(`
|
|
3
|
+
`);React2.useEffect(()=>{if(!target)return;let subscribing=!0,onChange=()=>{subscribing||setTick(t=>t+1)},offs=resolvedRef.current.map(r=>r.action.subscribe?.(onChange,target)).filter(off=>typeof off=="function");return subscribing=!1,()=>offs.forEach(off=>off())},[subscribedIds,target]);let findAction=React2.useCallback(id=>resolved.find(r=>r.action.id===id)?.action??expanded.current.get(id)??void 0,[resolved]),run=React2.useCallback(async nodeId=>{if(!target||!model)return null;let node=model.sections.map(s=>findNode(s.nodes,nodeId)).find(Boolean)??null,action=findAction(node&&"actionId"in node&&node.actionId?node.actionId:nodeId);if(!action)return null;let o=optionsRef.current,result=await runAction(action,target,{ports,...o.source?{source:o.source}:{},...o.contentFingerprint?{contentFingerprint:o.contentFingerprint}:{}});return o.onRan?.(nodeId,result),result},[target,model,findAction,ports]),expand=React2.useCallback(async nodeId=>{let action=findAction(nodeId);!target||!action?.expand||(expanded.current.set(nodeId,null),await expandAction(action,nodeId))},[findAction,target,expandAction]),icon=React2.useCallback((key,tone)=>{if(!key||!ports.icons)return null;let Resolved=ports.icons.resolve(key);return Resolved?(0,import_jsx_runtime.jsx)(Resolved,{className:`h-4 w-4 shrink-0 ${tone??""}`,"aria-hidden":!0}):null},[ports.icons]),explain=React2.useCallback(sentence=>{ports.notify?ports.notify.info(sentence):ports.diagnostics.capture(new Error(`No notify port to explain: ${sentence}`),{area:"menu",detail:{sentence}})},[ports]);return{model,resolved,run,expand,icon,explain}}var React3=__toESM(require("react"),1),import_design_system=require("@ai-matrx/design-system");var import_jsx_runtime2=require("react/jsx-runtime");function NodeList({nodes,engine}){return(0,import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment,{children:nodes.map(node=>(0,import_jsx_runtime2.jsx)(MenuNodeView,{node,engine},node.id))})}function SectionList({sections,engine}){return(0,import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment,{children:sections.map((section,index)=>(0,import_jsx_runtime2.jsxs)(React3.Fragment,{children:[index>0?(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuSeparator,{}):null,sectionLabel(section)?(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuLabel,{className:"text-xs font-medium text-muted-foreground",children:sectionLabel(section)}):null,(0,import_jsx_runtime2.jsx)(NodeList,{nodes:section.nodes,engine})]},section.id))})}function iconOf(node,engine){if(node.kind!=="item")return"icon"in node?engine.icon(node.icon):null;if(node.stateIcon){let glyph=engine.icon(node.stateIcon.icon);return node.stateIcon.spin?(0,import_jsx_runtime2.jsx)("span",{className:"inline-flex animate-spin",children:glyph}):glyph}if(!("icon"in node))return null;let tone=node.pressed===!1?void 0:node.iconTone;return engine.icon(node.icon,tone)}function MenuNodeView({node,engine}){switch(node.kind){case"separator":return(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuSeparator,{});case"label":return(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuLabel,{className:"text-xs font-medium text-muted-foreground",children:node.label});case"submenu":return(0,import_jsx_runtime2.jsxs)(import_design_system.DropdownMenuSub,{onOpenChange:open=>{open&&node.lazy&&!node.loading&&engine.expand(node.id)},children:[(0,import_jsx_runtime2.jsxs)(import_design_system.DropdownMenuSubTrigger,{"data-alchemy-node":node.id,className:"gap-2","aria-busy":node.pending?!0:void 0,children:[iconOf(node,engine),(0,import_jsx_runtime2.jsx)("span",{className:"truncate",children:node.label}),node.pending?(0,import_jsx_runtime2.jsx)("span",{className:"ml-auto text-xs text-muted-foreground",children:"Loading\u2026"}):null]}),(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuSubContent,{className:"max-h-[var(--radix-dropdown-menu-content-available-height)] overflow-y-auto",children:node.loading?(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuLabel,{className:"text-xs text-muted-foreground",children:"Loading\u2026"}):node.children.length===0&&!node.lazy?(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuLabel,{className:"text-xs text-muted-foreground",children:node.emptyLabel??"Nothing here yet"}):(0,import_jsx_runtime2.jsx)(NodeList,{nodes:node.children,engine})})]});case"checkbox":return(0,import_jsx_runtime2.jsxs)(import_design_system.DropdownMenuCheckboxItem,{"data-alchemy-node":node.id,checked:node.checked,onSelect:()=>{engine.run(node.id)},className:"gap-2",children:[iconOf(node,engine),(0,import_jsx_runtime2.jsx)("span",{className:"truncate",children:node.label})]});case"link":return(0,import_jsx_runtime2.jsx)(import_design_system.DropdownMenuItem,{asChild:!0,"data-alchemy-node":node.id,children:(0,import_jsx_runtime2.jsxs)("a",{href:node.href,target:node.target,className:"gap-2",children:[iconOf(node,engine),(0,import_jsx_runtime2.jsx)("span",{className:"truncate",children:node.label})]})});case"item":return(0,import_jsx_runtime2.jsx)(LeafItem,{node,engine})}}function LeafItem({node,engine}){let row=(0,import_jsx_runtime2.jsxs)(import_design_system.DropdownMenuItem,{"data-alchemy-node":node.id,"aria-disabled":node.unavailable?!0:void 0,"aria-pressed":node.pressed,onSelect:event=>{if(node.unavailable){event.preventDefault();return}engine.run(node.id)},className:`gap-2 ${node.unavailable?"opacity-50":""} ${node.destructive?"text-destructive":""}`,children:[iconOf(node,engine),(0,import_jsx_runtime2.jsxs)("span",{className:"flex min-w-0 flex-1 flex-col",children:[(0,import_jsx_runtime2.jsx)("span",{className:"truncate",children:node.label}),node.description?(0,import_jsx_runtime2.jsx)("span",{className:"truncate text-xs text-muted-foreground",children:node.description}):null]}),node.hint?(0,import_jsx_runtime2.jsx)("span",{className:"ml-auto text-xs text-muted-foreground",children:node.hint}):null]});return node.unavailable?(0,import_jsx_runtime2.jsxs)(import_design_system.Tooltip,{children:[(0,import_jsx_runtime2.jsx)(import_design_system.TooltipTrigger,{asChild:!0,children:row}),(0,import_jsx_runtime2.jsx)(import_design_system.TooltipContent,{children:node.unavailable.sentence})]}):row}function LeafButton({node,engine,showLabel}){if(node.kind!=="item"&&node.kind!=="checkbox"&&node.kind!=="link")return null;let unavailable=node.kind==="item"?node.unavailable:void 0,pressed=node.kind==="item"?node.pressed:node.kind==="checkbox"?node.checked:void 0,glyph=iconOf(node,engine),button=(0,import_jsx_runtime2.jsxs)("button",{type:"button","data-alchemy-node":node.id,"data-matrx-button":"","data-tap-floor":"","data-tap-floor-square":"","aria-label":node.label,"aria-disabled":unavailable?!0:void 0,"aria-pressed":pressed,onMouseDown:e=>{node.kind==="item"&&node.preserveSelection&&e.preventDefault()},onClick:()=>{if(unavailable){engine.explain(unavailable.sentence);return}if(node.kind==="link"){window.location.assign(node.href);return}engine.run(node.id)},className:`inline-flex h-7 min-w-7 shrink-0 items-center justify-center gap-1.5 rounded-md px-1.5 text-sm text-muted-foreground hover:bg-accent hover:text-foreground ${unavailable?"cursor-default opacity-50 hover:bg-transparent":""} ${pressed?"text-foreground":""}`,children:[glyph,showLabel||!glyph?(0,import_jsx_runtime2.jsx)("span",{className:"whitespace-nowrap",children:node.label}):null]});return(0,import_jsx_runtime2.jsxs)(import_design_system.Tooltip,{children:[(0,import_jsx_runtime2.jsx)(import_design_system.TooltipTrigger,{asChild:!0,children:button}),(0,import_jsx_runtime2.jsx)(import_design_system.TooltipContent,{children:unavailable?unavailable.sentence:node.label})]})}var import_jsx_runtime3=require("react/jsx-runtime");function ContextMenuPanel({target,point,open,onOpenChange,arrangement="command",density="comfortable",...options}){let engine=useMenuEngine(target,{...options,onRan:(id,r)=>{options.onRan?.(id,r),onOpenChange(!1)}}),[query,setQuery]=React4.useState(""),arranged=engine.model?context(engine.model,arrangement):null,filtered=engine.model&&query.trim()?filterPalette(palette(engine.model),query):null,anchor=(0,import_jsx_runtime3.jsx)(import_design_system2.DropdownMenuTrigger,{asChild:!0,children:(0,import_jsx_runtime3.jsx)("span",{"aria-hidden":!0,"data-alchemy-anchor":"context",style:{position:"fixed",left:point.x,top:point.y,width:0,height:0,pointerEvents:"none"}})});return(0,import_jsx_runtime3.jsxs)(import_design_system2.DropdownMenu,{open,onOpenChange,modal:!1,children:[typeof document>"u"?anchor:(0,import_react_dom.createPortal)(anchor,document.body),(0,import_jsx_runtime3.jsx)(import_design_system2.DropdownMenuContent,{align:"start","data-alchemy-layout":`context-${arrangement}`,onPointerUpCapture:event=>{event.button===2&&event.preventDefault()},className:`w-72 max-h-[var(--radix-dropdown-menu-content-available-height)] overflow-y-auto ${density==="compact"?"text-xs":""}`,children:(0,import_jsx_runtime3.jsxs)(import_design_system2.TooltipProvider,{children:[engine.model?.header?(0,import_jsx_runtime3.jsxs)(import_design_system2.DropdownMenuLabel,{className:"truncate text-xs font-normal text-muted-foreground",title:engine.model.header.text,children:[engine.model.header.label,": ",engine.model.header.text]}):null,arranged?(0,import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment,{children:[arranged.strip.length?(0,import_jsx_runtime3.jsx)("div",{role:"toolbar","aria-label":"Clipboard",className:"flex items-center gap-0.5 px-1 py-1",children:arranged.strip.map(node=>(0,import_jsx_runtime3.jsx)(LeafButton,{node,engine,showLabel:!1},node.id))}):null,arranged.filterable?(0,import_jsx_runtime3.jsx)("div",{className:"px-1 pb-1",children:(0,import_jsx_runtime3.jsx)("input",{"aria-label":"Filter actions",placeholder:"Type to filter\u2026",value:query,onChange:e=>setQuery(e.target.value),onKeyDown:e=>{e.stopPropagation(),e.key==="Enter"&&filtered?.leaves[0]&&engine.run(filtered.leaves[0].node.id)},className:"h-7 w-full rounded-md border border-border bg-transparent px-2 text-sm outline-none"})}):null,(arranged.strip.length||arranged.filterable)&&arranged.sections.length?(0,import_jsx_runtime3.jsx)(import_design_system2.DropdownMenuSeparator,{}):null,filtered?filtered.leaves.length?filtered.leaves.map(({node,breadcrumb})=>(0,import_jsx_runtime3.jsxs)(import_design_system2.DropdownMenuItem,{"data-alchemy-node":node.id,onSelect:()=>{node.kind==="submenu"?engine.expand(node.id):engine.run(node.id)},className:"flex-col items-start gap-0",children:[(0,import_jsx_runtime3.jsx)("span",{children:node.label}),(0,import_jsx_runtime3.jsx)("span",{className:"text-xs text-muted-foreground",children:breadcrumb.join(" \u203A ")})]},node.id)):(0,import_jsx_runtime3.jsxs)("div",{className:"px-2 py-1.5 text-xs text-muted-foreground",children:["No action matches \u201C",query,"\u201D."]}):(0,import_jsx_runtime3.jsx)(SectionList,{sections:arranged.sections,engine})]}):(0,import_jsx_runtime3.jsx)("div",{className:"px-2 py-1.5 text-xs text-muted-foreground",children:"Loading\u2026"})]})})]})}
|
|
850
4
|
//# sourceMappingURL=react-menu.cjs.map
|