@ai-matrx/context-menu 0.1.9 → 0.1.11
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 +12 -0
- package/dist/builder.cjs +17 -18
- package/dist/builder.cjs.map +1 -1
- package/dist/builder.js +2 -3
- package/dist/builder.js.map +1 -1
- package/dist/{chunk-ECKA474B.js → chunk-2B37SXQW.js} +21 -12
- package/dist/chunk-2B37SXQW.js.map +1 -0
- package/dist/{chunk-TSOUI723.cjs → chunk-7TIZAANU.cjs} +26 -11
- package/dist/chunk-7TIZAANU.cjs.map +1 -0
- package/dist/{chunk-A24SBKSA.js → chunk-EG23Y2KW.js} +14 -13
- package/dist/{chunk-A24SBKSA.js.map → chunk-EG23Y2KW.js.map} +1 -1
- package/dist/{chunk-7VDSMZWI.cjs → chunk-ERFH2RZC.cjs} +18 -17
- package/dist/chunk-ERFH2RZC.cjs.map +1 -0
- package/dist/{chunk-EIP5YS4Y.js → chunk-HJU3RU2B.js} +27 -12
- package/dist/chunk-HJU3RU2B.js.map +1 -0
- package/dist/{chunk-SWEWOUHK.cjs → chunk-IH5WN5JP.cjs} +31 -26
- package/dist/chunk-IH5WN5JP.cjs.map +1 -0
- package/dist/{chunk-QXHLM7SS.cjs → chunk-LDMYNYFC.cjs} +42 -33
- package/dist/chunk-LDMYNYFC.cjs.map +1 -0
- package/dist/{chunk-ANJYKQAA.cjs → chunk-MBU4Y22O.cjs} +3 -3
- package/dist/{chunk-ANJYKQAA.cjs.map → chunk-MBU4Y22O.cjs.map} +1 -1
- package/dist/{chunk-L6DPISFT.js → chunk-NBEXJ25Z.js} +24 -19
- package/dist/{chunk-L6DPISFT.js.map → chunk-NBEXJ25Z.js.map} +1 -1
- package/dist/{chunk-LE6ZUVCO.js → chunk-PB3PMDKV.js} +2 -2
- package/dist/{chunk-NOYMFNSU.js → chunk-SJWKMZEJ.js} +33 -12
- package/dist/chunk-SJWKMZEJ.js.map +1 -0
- package/dist/{chunk-XERD2YCJ.cjs → chunk-WROBL3DR.cjs} +32 -11
- package/dist/chunk-WROBL3DR.cjs.map +1 -0
- package/dist/design-store.cjs +0 -1
- package/dist/design-store.cjs.map +1 -1
- package/dist/design-store.js +0 -1
- package/dist/design-store.js.map +1 -1
- package/dist/engine.cjs +64 -96
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.cts +7 -7
- package/dist/engine.d.ts +7 -7
- package/dist/engine.js +72 -104
- package/dist/engine.js.map +1 -1
- package/dist/index.cjs +3 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -3
- package/dist/menu-content.cjs +18 -19
- package/dist/menu-content.cjs.map +1 -1
- package/dist/menu-content.js +4 -5
- package/dist/menu-content.js.map +1 -1
- package/dist/menu-inspect.cjs +0 -1
- package/dist/menu-inspect.cjs.map +1 -1
- package/dist/menu-inspect.js +0 -1
- package/dist/menu-inspect.js.map +1 -1
- package/dist/model.cjs +4 -5
- package/dist/model.cjs.map +1 -1
- package/dist/model.js +3 -4
- package/dist/react.cjs +52 -44
- package/dist/react.cjs.map +1 -1
- package/dist/react.js +30 -22
- package/dist/react.js.map +1 -1
- package/package.json +6 -6
- package/dist/chunk-7VDSMZWI.cjs.map +0 -1
- package/dist/chunk-CUODNUAD.cjs +0 -24
- package/dist/chunk-CUODNUAD.cjs.map +0 -1
- package/dist/chunk-ECKA474B.js.map +0 -1
- package/dist/chunk-EIP5YS4Y.js.map +0 -1
- package/dist/chunk-NOYMFNSU.js.map +0 -1
- package/dist/chunk-PE3ZHHM5.js +0 -24
- package/dist/chunk-PE3ZHHM5.js.map +0 -1
- package/dist/chunk-QXHLM7SS.cjs.map +0 -1
- package/dist/chunk-SWEWOUHK.cjs.map +0 -1
- package/dist/chunk-TSOUI723.cjs.map +0 -1
- package/dist/chunk-XERD2YCJ.cjs.map +0 -1
- /package/dist/{chunk-LE6ZUVCO.js.map → chunk-PB3PMDKV.js.map} +0 -0
|
@@ -107,13 +107,15 @@ function MenuPanel({ nodes, env, className, style, rootProps, panelRef }) {
|
|
|
107
107
|
const searchAt = nodes.findIndex((n) => n.kind === "search");
|
|
108
108
|
const hits = q ? env.search(q) : null;
|
|
109
109
|
const above = searchAt >= 0 ? nodes.slice(0, searchAt) : [];
|
|
110
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Env.Provider, { value: env, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { className: `cm-menu ${_nullishCoalesce(className, () => ( ""))}`, style: { width: env.look.width, ...style }, root: true, rootProps, panelRef, children: nodes.length === 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "cm-empty", children: "Empty menu \u2014 drag options in" }) :
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
110
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Env.Provider, { value: env, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Panel, { className: `cm-menu ${_nullishCoalesce(className, () => ( ""))}`, style: { width: env.look.width, ...style }, root: true, rootProps, panelRef, children: nodes.length === 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "cm-empty", children: "Empty menu \u2014 drag options in" }) : (
|
|
111
|
+
// The search box keeps ONE place in the tree whether or not a query is typed, so typing
|
|
112
|
+
// never remounts it (and never drops the caret).
|
|
113
|
+
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
|
|
114
|
+
searchAt >= 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Nodes, { nodes: above }),
|
|
115
|
+
searchAt >= 0 && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, SearchBox, { node: nodes[searchAt] }, "cm-search"),
|
|
116
|
+
hits ? hits.length === 0 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "cm-empty", children: "No matches" }) : hits.map((h) => h.node.kind === "item" ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Item, { node: h.node, path: h.path, hit: true }, `hit:${h.node.id}`) : null) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Nodes, { nodes: searchAt >= 0 ? nodes.slice(searchAt + 1) : nodes })
|
|
117
|
+
] })
|
|
118
|
+
) }) });
|
|
117
119
|
}
|
|
118
120
|
function Panel({
|
|
119
121
|
children,
|
|
@@ -138,10 +140,16 @@ function Panel({
|
|
|
138
140
|
const at = rows.indexOf(document.activeElement);
|
|
139
141
|
const focus = (i) => _optionalChain([rows, 'access', _9 => _9[(i + rows.length) % rows.length], 'optionalAccess', _10 => _10.focus, 'call', _11 => _11()]);
|
|
140
142
|
switch (e.key) {
|
|
141
|
-
case "ArrowDown":
|
|
143
|
+
case "ArrowDown": {
|
|
142
144
|
e.preventDefault();
|
|
143
|
-
|
|
145
|
+
if (inSearch) {
|
|
146
|
+
const below = rows.find((r) => e.target.compareDocumentPosition(r) & Node.DOCUMENT_POSITION_FOLLOWING);
|
|
147
|
+
_optionalChain([(_nullishCoalesce(below, () => ( rows[0]))), 'optionalAccess', _12 => _12.focus, 'call', _13 => _13()]);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
focus(at < 0 ? 0 : at + 1);
|
|
144
151
|
return;
|
|
152
|
+
}
|
|
145
153
|
case "ArrowUp":
|
|
146
154
|
e.preventDefault();
|
|
147
155
|
if (inSearch) return;
|
|
@@ -160,7 +168,7 @@ function Panel({
|
|
|
160
168
|
case "ArrowRight": {
|
|
161
169
|
if (inSearch) return;
|
|
162
170
|
const row = document.activeElement;
|
|
163
|
-
const sub = _optionalChain([row, 'optionalAccess',
|
|
171
|
+
const sub = _optionalChain([row, 'optionalAccess', _14 => _14.getAttribute, 'call', _15 => _15("data-cm-sub")]);
|
|
164
172
|
if (sub) {
|
|
165
173
|
e.preventDefault();
|
|
166
174
|
intent.openNow(sub, true);
|
|
@@ -178,12 +186,12 @@ function Panel({
|
|
|
178
186
|
e.stopPropagation();
|
|
179
187
|
if (inSearch && env.query) return env.setQuery("");
|
|
180
188
|
if (onClose) onClose();
|
|
181
|
-
else _optionalChain([env, 'access',
|
|
189
|
+
else _optionalChain([env, 'access', _16 => _16.onEscape, 'optionalCall', _17 => _17()]);
|
|
182
190
|
return;
|
|
183
191
|
case "Enter":
|
|
184
192
|
if (inSearch) {
|
|
185
193
|
e.preventDefault();
|
|
186
|
-
_optionalChain([
|
|
194
|
+
_optionalChain([panel, 'access', _18 => _18.querySelector, 'call', _19 => _19('[data-cm-hit]:not([aria-disabled="true"])'), 'optionalAccess', _20 => _20.click, 'call', _21 => _21()]);
|
|
187
195
|
}
|
|
188
196
|
return;
|
|
189
197
|
default:
|
|
@@ -214,7 +222,7 @@ function Panel({
|
|
|
214
222
|
{
|
|
215
223
|
ref: (el) => {
|
|
216
224
|
ref.current = el;
|
|
217
|
-
_optionalChain([panelRef, 'optionalCall',
|
|
225
|
+
_optionalChain([panelRef, 'optionalCall', _22 => _22(el)]);
|
|
218
226
|
},
|
|
219
227
|
role: "menu",
|
|
220
228
|
tabIndex: -1,
|
|
@@ -243,9 +251,9 @@ function SearchBox({ node }) {
|
|
|
243
251
|
const env = useEnv();
|
|
244
252
|
const ref = _react.useRef.call(void 0, null);
|
|
245
253
|
_react.useEffect.call(void 0, () => {
|
|
246
|
-
if (node.autofocus) _optionalChain([ref, 'access',
|
|
254
|
+
if (node.autofocus) _optionalChain([ref, 'access', _23 => _23.current, 'optionalAccess', _24 => _24.focus, 'call', _25 => _25({ preventScroll: true })]);
|
|
247
255
|
}, [node.autofocus]);
|
|
248
|
-
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: `cm-search ${env.highlightId === node.designId ? "cm-hl" : ""}`, onClick: () => node.designId && _optionalChain([env, 'access',
|
|
256
|
+
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: `cm-search ${env.highlightId === node.designId ? "cm-hl" : ""}`, onClick: () => node.designId && _optionalChain([env, 'access', _26 => _26.onSelectNode, 'optionalCall', _27 => _27(node.designId)]), children: [
|
|
249
257
|
env.renderIcon("Search", "cm-icon"),
|
|
250
258
|
/* @__PURE__ */ _jsxruntime.jsx.call(void 0, "input", { ref, value: env.query, onChange: (e) => env.setQuery(e.target.value), placeholder: node.placeholder || "Type to filter\u2026", "aria-label": "Filter menu" })
|
|
251
259
|
] });
|
|
@@ -263,7 +271,7 @@ function Section({ node }) {
|
|
|
263
271
|
tabIndex: -1,
|
|
264
272
|
className: `cm-section-label ${node.placeholder ? "cm-slot-label" : ""}`,
|
|
265
273
|
onClick: () => {
|
|
266
|
-
if (node.designId) _optionalChain([env, 'access',
|
|
274
|
+
if (node.designId) _optionalChain([env, 'access', _28 => _28.onSelectNode, 'optionalCall', _29 => _29(node.designId)]);
|
|
267
275
|
if (node.collapsible) setOpen((o) => !o);
|
|
268
276
|
},
|
|
269
277
|
children: [
|
|
@@ -299,7 +307,7 @@ function Tile({ node }) {
|
|
|
299
307
|
...statusAttrs(node),
|
|
300
308
|
className: `cm-tile ${node.danger ? "cm-danger" : ""} ${node.ai ? "cm-ai" : ""} ${env.highlightId && env.highlightId === node.designId ? "cm-hl" : ""}`,
|
|
301
309
|
onClick: () => {
|
|
302
|
-
if (node.designId) _optionalChain([env, 'access',
|
|
310
|
+
if (node.designId) _optionalChain([env, 'access', _30 => _30.onSelectNode, 'optionalCall', _31 => _31(node.designId)]);
|
|
303
311
|
if (!node.disabled) env.onActivate(node);
|
|
304
312
|
},
|
|
305
313
|
children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Icon, { icon: node.icon, fallback: "Circle" })
|
|
@@ -307,7 +315,7 @@ function Tile({ node }) {
|
|
|
307
315
|
)
|
|
308
316
|
);
|
|
309
317
|
}
|
|
310
|
-
function Item({ node, path }) {
|
|
318
|
+
function Item({ node, path, hit }) {
|
|
311
319
|
const env = useEnv();
|
|
312
320
|
const { look } = env;
|
|
313
321
|
const [checked, setChecked] = _react.useState.call(void 0, Boolean(node.checked));
|
|
@@ -326,15 +334,16 @@ function Item({ node, path }) {
|
|
|
326
334
|
tabIndex: -1,
|
|
327
335
|
"data-cm-row": "",
|
|
328
336
|
"data-cm-label": node.label,
|
|
337
|
+
...hit ? { "data-cm-hit": "" } : {},
|
|
329
338
|
"aria-checked": settable ? checked : void 0,
|
|
330
339
|
"aria-disabled": node.disabled || void 0,
|
|
331
340
|
...reason ? { title: reason, "aria-description": reason } : {},
|
|
332
341
|
...toneAttrs(node.tone, node.toneLabel),
|
|
333
342
|
...statusAttrs(node),
|
|
334
343
|
className: `cm-item ${node.placeholder ? "cm-slot" : ""} ${node.danger ? "cm-danger" : ""} ${node.ai ? "cm-ai" : ""} ${env.highlightId && env.highlightId === node.designId ? "cm-hl" : ""}`,
|
|
335
|
-
onPointerEnter: (e) => _optionalChain([intent, 'optionalAccess',
|
|
344
|
+
onPointerEnter: (e) => _optionalChain([intent, 'optionalAccess', _32 => _32.enter, 'call', _33 => _33(null, e.currentTarget)]),
|
|
336
345
|
onClick: () => {
|
|
337
|
-
if (node.designId) _optionalChain([env, 'access',
|
|
346
|
+
if (node.designId) _optionalChain([env, 'access', _34 => _34.onSelectNode, 'optionalCall', _35 => _35(node.designId)]);
|
|
338
347
|
if (node.disabled) return;
|
|
339
348
|
if (settable) setChecked((c) => !c);
|
|
340
349
|
env.onActivate(node);
|
|
@@ -355,7 +364,7 @@ function Item({ node, path }) {
|
|
|
355
364
|
}
|
|
356
365
|
function flyoutSide(row, width) {
|
|
357
366
|
const r = row.getBoundingClientRect();
|
|
358
|
-
const bounds = _nullishCoalesce(_optionalChain([row, 'access',
|
|
367
|
+
const bounds = _nullishCoalesce(_optionalChain([row, 'access', _36 => _36.closest, 'call', _37 => _37("[data-menu-bounds]"), 'optionalAccess', _38 => _38.getBoundingClientRect, 'call', _39 => _39()]), () => ( { left: 0, right: window.innerWidth }));
|
|
359
368
|
if (r.right + width + 8 <= bounds.right) return "right";
|
|
360
369
|
if (r.left - width - 8 >= bounds.left) return "left";
|
|
361
370
|
return "below";
|
|
@@ -380,7 +389,7 @@ function Submenu({ node }) {
|
|
|
380
389
|
const env = useEnv();
|
|
381
390
|
const intent = _react.useContext.call(void 0, PanelIntent);
|
|
382
391
|
const { look } = env;
|
|
383
|
-
const open = _optionalChain([intent, 'optionalAccess',
|
|
392
|
+
const open = _optionalChain([intent, 'optionalAccess', _40 => _40.openId]) === node.id;
|
|
384
393
|
const [side, setSide] = _react.useState.call(void 0, "right");
|
|
385
394
|
const [fixed, setFixed] = _react.useState.call(void 0, null);
|
|
386
395
|
const rowRef = _react.useRef.call(void 0, null);
|
|
@@ -397,16 +406,16 @@ function Submenu({ node }) {
|
|
|
397
406
|
}, [open, env.flyout, look.width]);
|
|
398
407
|
_react.useEffect.call(void 0, () => {
|
|
399
408
|
if (!open) setFixed(null);
|
|
400
|
-
if (open && _optionalChain([intent, 'optionalAccess',
|
|
409
|
+
if (open && _optionalChain([intent, 'optionalAccess', _41 => _41.viaKey]) && flyRef.current) {
|
|
401
410
|
const first = rowsOf(flyRef.current)[0];
|
|
402
411
|
(_nullishCoalesce(first, () => ( flyRef.current))).focus();
|
|
403
412
|
}
|
|
404
|
-
}, [open, _optionalChain([intent, 'optionalAccess',
|
|
413
|
+
}, [open, _optionalChain([intent, 'optionalAccess', _42 => _42.viaKey])]);
|
|
405
414
|
const closeFlyout = _react.useCallback.call(void 0, () => {
|
|
406
|
-
_optionalChain([intent, 'optionalAccess',
|
|
407
|
-
_optionalChain([rowRef, 'access',
|
|
415
|
+
_optionalChain([intent, 'optionalAccess', _43 => _43.close, 'call', _44 => _44()]);
|
|
416
|
+
_optionalChain([rowRef, 'access', _45 => _45.current, 'optionalAccess', _46 => _46.focus, 'call', _47 => _47()]);
|
|
408
417
|
}, [intent]);
|
|
409
|
-
const flyStyle = env.flyout === "fixed" ? { position: "fixed", width: look.width, maxHeight: "calc(100vh - 16px)", overflowY: "auto", left: _nullishCoalesce(_optionalChain([fixed, 'optionalAccess',
|
|
418
|
+
const flyStyle = env.flyout === "fixed" ? { position: "fixed", width: look.width, maxHeight: "calc(100vh - 16px)", overflowY: "auto", left: _nullishCoalesce(_optionalChain([fixed, 'optionalAccess', _48 => _48.left]), () => ( -9999)), top: _nullishCoalesce(_optionalChain([fixed, 'optionalAccess', _49 => _49.top]), () => ( -9999)), visibility: fixed ? "visible" : "hidden" } : { width: look.width };
|
|
410
419
|
return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "cm-sub", children: [
|
|
411
420
|
/* @__PURE__ */ _jsxruntime.jsxs.call(void 0,
|
|
412
421
|
"button",
|
|
@@ -427,14 +436,14 @@ function Submenu({ node }) {
|
|
|
427
436
|
className: `cm-item ${node.placeholder ? "cm-slot" : ""} ${open ? "cm-open" : ""} ${env.highlightId && env.highlightId === node.designId ? "cm-hl" : ""}`,
|
|
428
437
|
onPointerEnter: (e) => {
|
|
429
438
|
if (env.flyout === "absolute") setSide(flyoutSide(e.currentTarget, look.width));
|
|
430
|
-
if (greyed) _optionalChain([intent, 'optionalAccess',
|
|
431
|
-
else _optionalChain([intent, 'optionalAccess',
|
|
439
|
+
if (greyed) _optionalChain([intent, 'optionalAccess', _50 => _50.enter, 'call', _51 => _51(null, e.currentTarget)]);
|
|
440
|
+
else _optionalChain([intent, 'optionalAccess', _52 => _52.enter, 'call', _53 => _53(node.id, e.currentTarget)]);
|
|
432
441
|
},
|
|
433
442
|
onClick: (e) => {
|
|
434
|
-
if (node.designId) _optionalChain([env, 'access',
|
|
443
|
+
if (node.designId) _optionalChain([env, 'access', _54 => _54.onSelectNode, 'optionalCall', _55 => _55(node.designId)]);
|
|
435
444
|
if (greyed) return;
|
|
436
445
|
if (env.flyout === "absolute") setSide(flyoutSide(e.currentTarget, look.width));
|
|
437
|
-
_optionalChain([intent, 'optionalAccess',
|
|
446
|
+
_optionalChain([intent, 'optionalAccess', _56 => _56.openNow, 'call', _57 => _57(node.id, e.detail === 0)]);
|
|
438
447
|
},
|
|
439
448
|
children: [
|
|
440
449
|
look.showIcons && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "span", { className: "cm-icon-slot", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, Icon, { icon: node.icon }) }),
|
|
@@ -454,7 +463,7 @@ function Submenu({ node }) {
|
|
|
454
463
|
function FlyoutBody({ node, flyRef, side }) {
|
|
455
464
|
const ref = _react.useRef.call(void 0, null);
|
|
456
465
|
_react.useLayoutEffect.call(void 0, () => {
|
|
457
|
-
const panel = _nullishCoalesce(_optionalChain([ref, 'access',
|
|
466
|
+
const panel = _nullishCoalesce(_optionalChain([ref, 'access', _58 => _58.current, 'optionalAccess', _59 => _59.closest, 'call', _60 => _60('[role="menu"]')]), () => ( null));
|
|
458
467
|
flyRef.current = panel;
|
|
459
468
|
if (panel && side) panel.setAttribute("data-side", side);
|
|
460
469
|
});
|
|
@@ -467,4 +476,4 @@ function FlyoutBody({ node, flyRef, side }) {
|
|
|
467
476
|
|
|
468
477
|
|
|
469
478
|
exports.MenuPanel = MenuPanel;
|
|
470
|
-
//# sourceMappingURL=chunk-
|
|
479
|
+
//# sourceMappingURL=chunk-LDMYNYFC.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/aidream/aidream/apps/shared/context-menu/dist/chunk-LDMYNYFC.cjs","../src/designed/MenuPanel.tsx"],"names":[],"mappings":"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","file":"/home/runner/work/aidream/aidream/apps/shared/context-menu/dist/chunk-LDMYNYFC.cjs","sourcesContent":[null,"// src/designed/MenuPanel.tsx — @ai-matrx/context-menu: THE ONE MENU RENDERER.\n//\n// Draws a resolved menu (ResolvedNode[]) — the builder's preview and the live right-click menu are\n// both this component, so what the designer sees is what opens. It owns the menu's own behaviour:\n// submenus on hover with an intent delay and a safe triangle toward the open flyout, flyouts that\n// flip at the screen's edge, the keyboard (arrows, Enter, Esc / Left closes a submenu, Right opens\n// one, typeahead), the search box, and the status marks (partial / unmatched / needs-arman).\n// This file IS the menu component: its rows are the menu's own rows (hence the ui-exception marks).\n\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useLayoutEffect,\n useRef,\n useState,\n type CSSProperties,\n type KeyboardEvent as ReactKeyboardEvent,\n type PointerEvent as ReactPointerEvent,\n type ReactNode,\n} from \"react\";\nimport type { MenuLook, MenuTone } from \"../design/model\";\nimport type { IconRef, ResolvedItem, ResolvedNode, ResolvedSearch, ResolvedSection, ResolvedSubmenu } from \"./types\";\nimport type { ResolvedHit } from \"./bindings\";\n\nexport interface MenuPanelEnv {\n look: MenuLook;\n /** Draws a lucide icon by name (the design's icons). */\n renderIcon: (name: string, className?: string) => ReactNode;\n /** A row was chosen (live: run it and close; builder: the designer's pick). */\n onActivate: (item: ResolvedItem) => void;\n /** Builder: a click on any entry selects its design node. */\n onSelectNode?: ((designId: string) => void) | undefined;\n highlightId?: string | null | undefined;\n /** absolute: flyouts sit beside their row (the builder) · fixed: measured against the screen (live). */\n flyout: \"absolute\" | \"fixed\";\n query: string;\n setQuery: (q: string) => void;\n /** The search, over the bound entries. */\n search: (q: string) => ResolvedHit[];\n /** Esc at the top: close the whole menu. */\n onEscape?: (() => void) | undefined;\n}\n\nconst Env = createContext<MenuPanelEnv | null>(null);\nfunction useEnv(): MenuPanelEnv {\n const env = useContext(Env);\n if (!env) throw new Error(\"MenuPanel: rendered outside its menu\");\n return env;\n}\n\n// ── rows: focus, keyboard ────────────────────────────────────────────────────\n\nconst ROW = \"[data-cm-row]\";\n\n/** The rows of THIS panel (not those of a flyout inside it), in order. */\nfunction rowsOf(panel: HTMLElement): HTMLElement[] {\n return [...panel.querySelectorAll<HTMLElement>(ROW)].filter((el) => el.closest('[role=\"menu\"]') === panel && el.getAttribute(\"aria-disabled\") !== \"true\");\n}\n\nfunction labelOf(el: HTMLElement): string {\n return (el.getAttribute(\"data-cm-label\") ?? el.textContent ?? \"\").trim().toLowerCase();\n}\n\nfunction reasonOf(n: ResolvedItem | ResolvedSubmenu): string | undefined {\n const parts = [n.statusReason, n.disabled ? n.disabledReason : undefined].filter(Boolean);\n return parts.length ? parts.join(\" — \") : undefined;\n}\n\nfunction toneAttrs(tone: MenuTone | undefined, toneLabel: boolean | undefined) {\n return tone && tone !== \"default\" ? { \"data-tone\": tone, ...(toneLabel ? { \"data-tone-label\": \"\" } : {}) } : {};\n}\n\nfunction Icon({ icon, fallback }: { icon?: IconRef | undefined; fallback?: string }) {\n const env = useEnv();\n if (icon?.component) {\n const C = icon.component;\n return <C className={`cm-icon ${icon.className ?? \"\"}`} {...(icon.style ? { style: icon.style } : {})} />;\n }\n const name = icon?.name ?? fallback;\n return name ? <>{env.renderIcon(name, \"cm-icon\")}</> : null;\n}\n\n// ── submenu intent: open on hover after a beat, never steal a flyout the pointer is heading to ──\n\ninterface Intent {\n openId: string | null;\n viaKey: boolean;\n enter: (id: string | null, el: HTMLElement) => void;\n openNow: (id: string, viaKey: boolean) => void;\n close: () => void;\n track: (e: ReactPointerEvent) => void;\n}\n\nfunction useIntent(): Intent {\n const [state, setState] = useState<{ openId: string | null; viaKey: boolean }>({ openId: null, viaKey: false });\n const timer = useRef<ReturnType<typeof setTimeout> | null>(null);\n const trail = useRef<{ x: number; y: number }[]>([]);\n const flyoutRect = useRef<DOMRect | null>(null);\n const stateRef = useRef(state);\n stateRef.current = state;\n useEffect(() => () => void (timer.current && clearTimeout(timer.current)), []);\n\n const heading = (): boolean => {\n const r = flyoutRect.current;\n const [a, b] = trail.current.slice(-2);\n if (!r || !a || !b) return false;\n // Is the pointer inside the triangle from where it was to the flyout's near edge?\n const towardRight = r.left >= a.x;\n const ex = towardRight ? r.left : r.right;\n const p = b;\n const t1 = { x: ex, y: r.top - 8 };\n const t2 = { x: ex, y: r.bottom + 8 };\n const sign = (p1: { x: number; y: number }, p2: { x: number; y: number }, p3: { x: number; y: number }) =>\n (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);\n const d1 = sign(p, a, t1);\n const d2 = sign(p, t1, t2);\n const d3 = sign(p, t2, a);\n const neg = d1 < 0 || d2 < 0 || d3 < 0;\n const pos = d1 > 0 || d2 > 0 || d3 > 0;\n return !(neg && pos);\n };\n\n const schedule = (fn: () => void, ms: number) => {\n if (timer.current) clearTimeout(timer.current);\n timer.current = setTimeout(fn, ms);\n };\n\n return {\n openId: state.openId,\n viaKey: state.viaKey,\n track: (e) => {\n trail.current.push({ x: e.clientX, y: e.clientY });\n if (trail.current.length > 3) trail.current.shift();\n },\n enter: (id, el) => {\n const current = stateRef.current.openId;\n if (id === current) {\n if (timer.current) clearTimeout(timer.current);\n return;\n }\n const flyout = current ? el.closest('[role=\"menu\"]')?.querySelector<HTMLElement>(`[data-cm-flyout=\"${CSS.escape(current)}\"]`) : null;\n flyoutRect.current = flyout?.getBoundingClientRect() ?? null;\n const go = () => setState({ openId: id, viaKey: false });\n if (current && heading()) {\n // On the way to the open flyout: wait, then look again.\n schedule(() => {\n if (el.matches(\":hover\")) go();\n }, 300);\n } else schedule(go, current ? 60 : 90);\n },\n openNow: (id, viaKey) => {\n if (timer.current) clearTimeout(timer.current);\n setState({ openId: id, viaKey });\n },\n close: () => {\n if (timer.current) clearTimeout(timer.current);\n setState({ openId: null, viaKey: false });\n },\n };\n}\n\nconst PanelIntent = createContext<Intent | null>(null);\n\n// ── the panel ────────────────────────────────────────────────────────────────\n\nexport interface MenuPanelProps {\n nodes: readonly ResolvedNode[];\n env: MenuPanelEnv;\n className?: string;\n style?: CSSProperties;\n /** Props for the root element (position, data attributes). */\n rootProps?: Record<string, unknown>;\n /** The root element, for the owner's own measuring and outside-click test. */\n panelRef?: (el: HTMLDivElement | null) => void;\n}\n\n/** THE MENU: the root panel (with the search box), drawn from resolved nodes. */\nexport function MenuPanel({ nodes, env, className, style, rootProps, panelRef }: MenuPanelProps) {\n const q = env.query.trim().toLowerCase();\n const searchAt = nodes.findIndex((n) => n.kind === \"search\");\n const hits = q ? env.search(q) : null;\n const above = searchAt >= 0 ? nodes.slice(0, searchAt) : [];\n return (\n <Env.Provider value={env}>\n <Panel className={`cm-menu ${className ?? \"\"}`} style={{ width: env.look.width, ...style }} root rootProps={rootProps} panelRef={panelRef}>\n {nodes.length === 0 ? (\n <div className=\"cm-empty\">Empty menu — drag options in</div>\n ) : (\n // The search box keeps ONE place in the tree whether or not a query is typed, so typing\n // never remounts it (and never drops the caret).\n <>\n {searchAt >= 0 && <Nodes nodes={above} />}\n {searchAt >= 0 && <SearchBox key=\"cm-search\" node={nodes[searchAt] as ResolvedSearch} />}\n {hits ? (\n hits.length === 0 ? (\n <div className=\"cm-empty\">No matches</div>\n ) : (\n hits.map((h) => (h.node.kind === \"item\" ? <Item key={`hit:${h.node.id}`} node={h.node} path={h.path} hit /> : null))\n )\n ) : (\n <Nodes nodes={searchAt >= 0 ? nodes.slice(searchAt + 1) : nodes} />\n )}\n </>\n )}\n </Panel>\n </Env.Provider>\n );\n}\n\nfunction Panel({\n children,\n className,\n style,\n root,\n rootProps,\n onClose,\n flyoutId,\n panelRef,\n}: {\n panelRef?: ((el: HTMLDivElement | null) => void) | undefined;\n children: ReactNode;\n className: string;\n style?: CSSProperties | undefined;\n root?: boolean;\n rootProps?: Record<string, unknown> | undefined;\n /** A flyout: Esc / Left closes it (the parent focuses its row again). */\n onClose?: (() => void) | undefined;\n flyoutId?: string | undefined;\n}) {\n const env = useEnv();\n const intent = useIntent();\n const ref = useRef<HTMLDivElement>(null);\n const typed = useRef({ text: \"\", at: 0 });\n\n const onKeyDown = (e: ReactKeyboardEvent<HTMLDivElement>) => {\n const panel = ref.current;\n if (!panel) return;\n // A key that started in a flyout inside this panel belongs to that flyout.\n if ((e.target as HTMLElement).closest('[role=\"menu\"]') !== panel) return;\n const inSearch = (e.target as HTMLElement).tagName === \"INPUT\";\n const rows = rowsOf(panel);\n const at = rows.indexOf(document.activeElement as HTMLElement);\n const focus = (i: number) => rows[(i + rows.length) % rows.length]?.focus();\n switch (e.key) {\n case \"ArrowDown\": {\n e.preventDefault();\n if (inSearch) {\n // From the search box: the first row below it.\n const below = rows.find((r) => (e.target as HTMLElement).compareDocumentPosition(r) & Node.DOCUMENT_POSITION_FOLLOWING);\n (below ?? rows[0])?.focus();\n return;\n }\n focus(at < 0 ? 0 : at + 1);\n return;\n }\n case \"ArrowUp\":\n e.preventDefault();\n if (inSearch) return;\n if (at <= 0 && root) {\n const search = panel.querySelector<HTMLInputElement>(\".cm-search input\");\n if (search) return search.focus();\n }\n focus(at < 0 ? rows.length - 1 : at - 1);\n return;\n case \"Home\":\n case \"End\":\n if (inSearch) return;\n e.preventDefault();\n focus(e.key === \"Home\" ? 0 : rows.length - 1);\n return;\n case \"ArrowRight\": {\n if (inSearch) return;\n const row = document.activeElement as HTMLElement | null;\n const sub = row?.getAttribute(\"data-cm-sub\");\n if (sub) {\n e.preventDefault();\n intent.openNow(sub, true);\n }\n return;\n }\n case \"ArrowLeft\":\n if (inSearch || !onClose) return;\n e.preventDefault();\n e.stopPropagation();\n onClose();\n return;\n case \"Escape\":\n e.preventDefault();\n e.stopPropagation();\n if (inSearch && env.query) return env.setQuery(\"\");\n if (onClose) onClose();\n else env.onEscape?.();\n return;\n case \"Enter\":\n if (inSearch) {\n e.preventDefault();\n // The first match runs (the rows above the search box are not matches).\n panel.querySelector<HTMLElement>('[data-cm-hit]:not([aria-disabled=\"true\"])')?.click();\n }\n return;\n default:\n break;\n }\n // Typeahead: a printable key jumps to the next row starting with what was typed. At the top\n // of a menu with a search box, it types into the search instead.\n if (e.key.length !== 1 || e.metaKey || e.ctrlKey || e.altKey || inSearch) return;\n const search = root ? panel.querySelector<HTMLInputElement>(\".cm-search input\") : null;\n if (search) {\n e.preventDefault();\n search.focus();\n env.setQuery(env.query + e.key);\n return;\n }\n const now = Date.now();\n typed.current = { text: now - typed.current.at > 700 ? e.key.toLowerCase() : typed.current.text + e.key.toLowerCase(), at: now };\n const start = at < 0 ? 0 : typed.current.text.length > 1 ? at : at + 1;\n for (let i = 0; i < rows.length; i++) {\n const row = rows[(start + i) % rows.length];\n if (row && labelOf(row).startsWith(typed.current.text)) {\n e.preventDefault();\n row.focus();\n return;\n }\n }\n };\n\n return (\n <PanelIntent.Provider value={intent}>\n <div\n ref={(el) => {\n ref.current = el;\n panelRef?.(el);\n }}\n role=\"menu\"\n tabIndex={-1}\n data-density={env.look.density}\n {...(flyoutId ? { \"data-cm-flyout\": flyoutId } : {})}\n className={className}\n style={style}\n onKeyDown={onKeyDown}\n onPointerMove={intent.track}\n {...(rootProps ?? {})}\n >\n {children}\n </div>\n </PanelIntent.Provider>\n );\n}\n\nfunction Nodes({ nodes }: { nodes: readonly ResolvedNode[] }) {\n return (\n <>\n {nodes.map((n) => {\n if (n.kind === \"divider\") return <div key={n.id} role=\"separator\" className=\"cm-divider\" />;\n if (n.kind === \"item\") return <Item key={n.id} node={n} />;\n if (n.kind === \"submenu\") return <Submenu key={n.id} node={n} />;\n if (n.kind === \"search\") return <SearchBox key={n.id} node={n} />;\n if (n.kind === \"note\") return <div key={n.id} className={n.tone === \"runtime\" ? \"cm-runtime\" : \"cm-empty\"}>{n.text}</div>;\n return <Section key={n.id} node={n} />;\n })}\n </>\n );\n}\n\nfunction SearchBox({ node }: { node: ResolvedSearch }) {\n const env = useEnv();\n const ref = useRef<HTMLInputElement>(null);\n useEffect(() => {\n if (node.autofocus) ref.current?.focus({ preventScroll: true });\n }, [node.autofocus]);\n return (\n <div className={`cm-search ${env.highlightId === node.designId ? \"cm-hl\" : \"\"}`} onClick={() => node.designId && env.onSelectNode?.(node.designId)}>\n {env.renderIcon(\"Search\", \"cm-icon\")}\n {/* ui-exception: the menu's own filter row — this file IS the menu component */}\n <input ref={ref} value={env.query} onChange={(e) => env.setQuery(e.target.value)} placeholder={node.placeholder || \"Type to filter…\"} aria-label=\"Filter menu\" />\n </div>\n );\n}\n\nfunction Section({ node }: { node: ResolvedSection }) {\n const env = useEnv();\n const [open, setOpen] = useState(!node.collapsible);\n const layout = node.layout;\n return (\n <div className={`cm-section ${env.highlightId && env.highlightId === node.designId ? \"cm-hl\" : \"\"}`} data-layout={layout} {...(node.feature ? { \"data-feature\": \"\" } : {})}>\n {node.label && (\n // ui-exception: a menu row of the menu component itself\n <button\n type=\"button\"\n tabIndex={-1}\n className={`cm-section-label ${node.placeholder ? \"cm-slot-label\" : \"\"}`}\n onClick={() => {\n if (node.designId) env.onSelectNode?.(node.designId);\n if (node.collapsible) setOpen((o) => !o);\n }}\n >\n {node.label}\n {node.collapsible && <span className=\"cm-caret\">{open ? \"▾\" : \"▸\"}</span>}\n </button>\n )}\n {open && (\n <div className=\"cm-section-body\">\n {layout === \"list\" ? (\n <Nodes nodes={node.children} />\n ) : (\n node.children.map((c) => (c.kind === \"item\" ? <Tile key={c.id} node={c} /> : <Nodes key={c.id} nodes={[c]} />))\n )}\n </div>\n )}\n </div>\n );\n}\n\nfunction statusAttrs(n: ResolvedItem | ResolvedSubmenu) {\n const status = n.missingRequired ? \"required-missing\" : n.status && n.status !== \"works\" ? n.status : undefined;\n return status ? { \"data-status\": status } : {};\n}\n\nfunction Tile({ node }: { node: ResolvedItem }) {\n const env = useEnv();\n const reason = reasonOf(node);\n return (\n // ui-exception: a menu row of the menu component itself\n <button\n type=\"button\"\n role=\"menuitem\"\n tabIndex={-1}\n data-cm-row=\"\"\n data-cm-label={node.label}\n title={[node.label + (node.shortcut ? ` ${node.shortcut}` : \"\"), reason].filter(Boolean).join(\"\\n\")}\n aria-label={node.label}\n aria-disabled={node.disabled || undefined}\n {...toneAttrs(node.tone, node.toneLabel)}\n {...statusAttrs(node)}\n className={`cm-tile ${node.danger ? \"cm-danger\" : \"\"} ${node.ai ? \"cm-ai\" : \"\"} ${env.highlightId && env.highlightId === node.designId ? \"cm-hl\" : \"\"}`}\n onClick={() => {\n if (node.designId) env.onSelectNode?.(node.designId);\n if (!node.disabled) env.onActivate(node);\n }}\n >\n <Icon icon={node.icon} fallback=\"Circle\" />\n </button>\n );\n}\n\nfunction Item({ node, path, hit }: { node: ResolvedItem; path?: string; hit?: boolean }) {\n const env = useEnv();\n const { look } = env;\n const [checked, setChecked] = useState(Boolean(node.checked));\n // A changed default shows at once (the designer flips \"On by default\" while looking at the row).\n useEffect(() => setChecked(Boolean(node.checked)), [node.checked]);\n const settable = node.toggle || node.switch;\n const hint = path || (look.showHints ? node.hint : undefined);\n const reason = reasonOf(node);\n const intent = useContext(PanelIntent);\n return (\n // ui-exception: a menu row of the menu component itself\n <button\n type=\"button\"\n role={settable ? \"menuitemcheckbox\" : \"menuitem\"}\n tabIndex={-1}\n data-cm-row=\"\"\n data-cm-label={node.label}\n {...(hit ? { \"data-cm-hit\": \"\" } : {})}\n aria-checked={settable ? checked : undefined}\n aria-disabled={node.disabled || undefined}\n {...(reason ? { title: reason, \"aria-description\": reason } : {})}\n {...toneAttrs(node.tone, node.toneLabel)}\n {...statusAttrs(node)}\n className={`cm-item ${node.placeholder ? \"cm-slot\" : \"\"} ${node.danger ? \"cm-danger\" : \"\"} ${node.ai ? \"cm-ai\" : \"\"} ${env.highlightId && env.highlightId === node.designId ? \"cm-hl\" : \"\"}`}\n onPointerEnter={(e) => intent?.enter(null, e.currentTarget)}\n onClick={() => {\n if (node.designId) env.onSelectNode?.(node.designId);\n if (node.disabled) return;\n if (settable) setChecked((c) => !c);\n env.onActivate(node);\n }}\n >\n {look.showIcons && (\n <span className=\"cm-icon-slot\">{node.toggle ? (checked ? env.renderIcon(\"Check\", \"cm-icon\") : null) : <Icon icon={node.icon} />}</span>\n )}\n <span className=\"cm-text\">\n <span className=\"cm-label\">{node.label}</span>\n {hint && <span className=\"cm-hint\">{hint}</span>}\n </span>\n {node.count && <span className=\"cm-count\">{node.count}</span>}\n {look.showShortcuts && node.shortcut && !node.switch && <kbd className=\"cm-shortcut\">{node.shortcut}</kbd>}\n {node.switch && <span className=\"cm-switch\" data-on={checked ? \"\" : undefined} aria-hidden=\"true\" />}\n </button>\n );\n}\n\ntype FlyoutSide = \"right\" | \"left\" | \"below\";\n\n/**\n * Builder: where a submenu opens beside its row — right when it fits, else left, else just under it.\n * \"Fits\" is measured against the nearest `[data-menu-bounds]` box (a panel that clips), else the window.\n */\nfunction flyoutSide(row: HTMLElement, width: number): FlyoutSide {\n const r = row.getBoundingClientRect();\n const bounds = row.closest<HTMLElement>(\"[data-menu-bounds]\")?.getBoundingClientRect() ?? { left: 0, right: window.innerWidth };\n if (r.right + width + 8 <= bounds.right) return \"right\";\n if (r.left - width - 8 >= bounds.left) return \"left\";\n return \"below\";\n}\n\n/** Live: the flyout's place on the screen — beside its row, flipped and clamped to stay on screen. */\nexport function placeFlyout(row: DOMRect, size: { width: number; height: number }, view: { width: number; height: number }) {\n const gap = 2;\n const margin = 8;\n let left = row.right + gap;\n let side: \"right\" | \"left\" = \"right\";\n if (left + size.width > view.width - margin) {\n const flipped = row.left - gap - size.width;\n if (flipped >= margin) {\n left = flipped;\n side = \"left\";\n } else left = Math.max(margin, view.width - margin - size.width);\n }\n let top = row.top - 5;\n if (top + size.height > view.height - margin) top = Math.max(margin, view.height - margin - size.height);\n return { left, top, side };\n}\n\nfunction Submenu({ node }: { node: ResolvedSubmenu }) {\n const env = useEnv();\n const intent = useContext(PanelIntent);\n const { look } = env;\n const open = intent?.openId === node.id;\n const [side, setSide] = useState<FlyoutSide>(\"right\");\n const [fixed, setFixed] = useState<{ left: number; top: number } | null>(null);\n const rowRef = useRef<HTMLButtonElement>(null);\n const flyRef = useRef<HTMLDivElement>(null);\n const reason = reasonOf(node);\n const greyed = node.disabled;\n\n useLayoutEffect(() => {\n if (!open || env.flyout !== \"fixed\" || !rowRef.current || !flyRef.current) return;\n const r = rowRef.current.getBoundingClientRect();\n const f = flyRef.current.getBoundingClientRect();\n const at = placeFlyout(r, { width: f.width || look.width, height: f.height }, { width: window.innerWidth, height: window.innerHeight });\n setFixed({ left: at.left, top: at.top });\n setSide(at.side);\n }, [open, env.flyout, look.width]);\n useEffect(() => {\n if (!open) setFixed(null);\n if (open && intent?.viaKey && flyRef.current) {\n const first = rowsOf(flyRef.current)[0];\n (first ?? flyRef.current).focus();\n }\n }, [open, intent?.viaKey]);\n\n const closeFlyout = useCallback(() => {\n intent?.close();\n rowRef.current?.focus();\n }, [intent]);\n\n const flyStyle: CSSProperties =\n env.flyout === \"fixed\"\n ? { position: \"fixed\", width: look.width, maxHeight: \"calc(100vh - 16px)\", overflowY: \"auto\", left: fixed?.left ?? -9999, top: fixed?.top ?? -9999, visibility: fixed ? \"visible\" : \"hidden\" }\n : { width: look.width };\n\n return (\n <div className=\"cm-sub\">\n {/* ui-exception: a menu row of the menu component itself */}\n <button\n ref={rowRef}\n type=\"button\"\n role=\"menuitem\"\n tabIndex={-1}\n data-cm-row=\"\"\n data-cm-label={node.label}\n {...(greyed ? {} : { \"data-cm-sub\": node.id })}\n aria-haspopup=\"menu\"\n aria-expanded={open}\n aria-disabled={greyed || undefined}\n {...(reason ? { title: reason, \"aria-description\": reason } : {})}\n {...toneAttrs(node.tone, node.toneLabel)}\n {...statusAttrs(node)}\n className={`cm-item ${node.placeholder ? \"cm-slot\" : \"\"} ${open ? \"cm-open\" : \"\"} ${env.highlightId && env.highlightId === node.designId ? \"cm-hl\" : \"\"}`}\n onPointerEnter={(e) => {\n if (env.flyout === \"absolute\") setSide(flyoutSide(e.currentTarget, look.width));\n if (greyed) intent?.enter(null, e.currentTarget);\n else intent?.enter(node.id, e.currentTarget);\n }}\n onClick={(e) => {\n if (node.designId) env.onSelectNode?.(node.designId);\n if (greyed) return;\n if (env.flyout === \"absolute\") setSide(flyoutSide(e.currentTarget, look.width));\n intent?.openNow(node.id, e.detail === 0);\n }}\n >\n {look.showIcons && <span className=\"cm-icon-slot\"><Icon icon={node.icon} /></span>}\n <span className=\"cm-text\">\n <span className=\"cm-label\">{node.label}</span>\n {look.showHints && node.hint && <span className=\"cm-hint\">{node.hint}</span>}\n </span>\n {node.loading && <span className=\"cm-count\">…</span>}\n {node.count && <span className=\"cm-count\">{node.count}</span>}\n {env.renderIcon(\"ChevronRight\", \"cm-icon cm-chev\")}\n </button>\n {open && (\n <Panel className=\"cm-menu cm-flyout\" flyoutId={node.id} style={flyStyle} onClose={closeFlyout}>\n <FlyoutBody node={node} flyRef={flyRef} side={env.flyout === \"absolute\" ? side : undefined} />\n </Panel>\n )}\n </div>\n );\n}\n\n/** The flyout's rows (kept apart so the panel element itself is what `flyRef` measures). */\nfunction FlyoutBody({ node, flyRef, side }: { node: ResolvedSubmenu; flyRef: React.RefObject<HTMLDivElement | null>; side: FlyoutSide | undefined }) {\n const ref = useRef<HTMLSpanElement>(null);\n useLayoutEffect(() => {\n const panel = ref.current?.closest<HTMLDivElement>('[role=\"menu\"]') ?? null;\n flyRef.current = panel;\n if (panel && side) panel.setAttribute(\"data-side\", side);\n });\n return (\n <>\n <span ref={ref} hidden />\n {node.loading && node.children.length === 0 ? (\n <div className=\"cm-empty\">Loading…</div>\n ) : node.children.length === 0 ? (\n <div className=\"cm-empty\">{node.emptyLabel ?? \"Empty submenu\"}</div>\n ) : (\n <Nodes nodes={node.children} />\n )}\n </>\n );\n}\n"]}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
var _chunkPDHJTEZKcjs = require('./chunk-PDHJTEZK.cjs');
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
var
|
|
8
|
+
var _chunkLDMYNYFCcjs = require('./chunk-LDMYNYFC.cjs');
|
|
9
9
|
|
|
10
10
|
// src/design/catalog.ts
|
|
11
11
|
var o = (id, label, icon, group, extra = {}) => ({ id, label, icon, group, status: "live", ...extra });
|
|
@@ -341,7 +341,7 @@ function MenuPreview({ design, renderIcon, highlightId, onSelectNode, onPick, av
|
|
|
341
341
|
path: h.path
|
|
342
342
|
}))
|
|
343
343
|
};
|
|
344
|
-
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
|
|
344
|
+
return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLDMYNYFCcjs.MenuPanel, { nodes, env, ...className ? { className } : {}, ...style ? { style } : {} });
|
|
345
345
|
}
|
|
346
346
|
|
|
347
347
|
|
|
@@ -354,4 +354,4 @@ function MenuPreview({ design, renderIcon, highlightId, onSelectNode, onPick, av
|
|
|
354
354
|
|
|
355
355
|
|
|
356
356
|
exports.CATALOG = CATALOG; exports.CATALOG_GROUPS = CATALOG_GROUPS; exports.catalogOption = catalogOption; exports.TEMPLATES = TEMPLATES; exports.SLOT_SOURCES = SLOT_SOURCES; exports.slotSource = slotSource; exports.slotOptions = slotOptions; exports.MenuPreview = MenuPreview;
|
|
357
|
-
//# sourceMappingURL=chunk-
|
|
357
|
+
//# sourceMappingURL=chunk-MBU4Y22O.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/aidream/aidream/apps/shared/context-menu/dist/chunk-ANJYKQAA.cjs","../src/design/catalog.ts","../src/design/MenuPreview.tsx","../src/design/preview-nodes.ts"],"names":["item"],"mappings":"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AACR;AAea;AACE,EAAA;AACG,EAAA;AACV,EAAA;AACH,EAAA;AACa,IAAA;AACP,IAAA;AACO,IAAA;AACA,IAAA;AACd,EAAA;AACJ;AAEgB;AACP,EAAA;AACT;AAGgB;AACA,EAAA;AACD,EAAA;AACD,EAAA;AACd;ADhFkB;AACA;AEnKA;AFqKA;AACA;AG9JH;AACT,EAAA;AACO,IAAA;AACX,EAAA;AACa,EAAA;AACA,EAAA;AACA,EAAA;AACJ,EAAA;AACX;AAEc;AACL,EAAA;AACC,IAAA;AACG,IAAA;AACC,IAAA;AACE,IAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACI,IAAA;AACL,IAAA;AACK,IAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,IAAA;AACV,EAAA;AACF;AAEc;AACG,EAAA;AACF,EAAA;AACJ,EAAA;AACG,IAAA;AACH,IAAA;AACC,MAAA;AACG,MAAA;AACC,MAAA;AACE,MAAA;AACJ,MAAA;AACC,MAAA;AACT,MAAA;AACU,MAAA;AACC,QAAA;AACT,QAAA;AACQ,UAAA;AACC,UAAA;AACP,UAAA;AACO,UAAA;AACC,UAAA;AACR,UAAA;AACA,UAAA;AACA,UAAA;AACF,QAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AACgB,EAAA;AACF,EAAA;AACL,EAAA;AACE,IAAA;AACX,EAAA;AACO,EAAA;AACC,IAAA;AACG,IAAA;AACC,IAAA;AACE,IAAA;AACE,IAAA;AACJ,IAAA;AACG,IAAA;AACb,IAAA;AACU,IAAA;AAKI,MAAA;AACM,MAAA;AACH,MAAA;AACD,MAAA;AACE,MAAA;AACJ,MAAA;AACK,MAAA;AACL,MAAA;AACI,MAAA;AACV,IAAA;AACV,EAAA;AACF;AAEc;AACiB,EAAA;AACb,EAAA;AACN,IAAA;AACI,IAAA;AACN,IAAA;AACK,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACD,QAAA;AACA,QAAA;AACI,QAAA;AACD,QAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACI,QAAA;AACA,QAAA;AACX,MAAA;AACQ,IAAA;AACE,MAAA;AACb,IAAA;AACF,EAAA;AACO,EAAA;AACT;AAGgB;AACC,EAAA;AACD,EAAA;AACR,EAAA;AACD,EAAA;AACE,EAAA;AACT;AHuJkB;AACA;AE3MT;AA3DA;AACE,EAAA;AACI,EAAA;AACA,EAAA;AACf;AASgB;AACE,EAAA;AACA,EAAA;AACR,IAAA;AACE,MAAA;AACI,MAAA;AACD,IAAA;AACE,MAAA;AACb,IAAA;AACF,EAAA;AACO,EAAA;AACT;AAIgB;AACA,EAAA;AACA,EAAA;AACY,EAAA;AACX,IAAA;AACb,IAAA;AACQ,IAAA;AACR,IAAA;AACA,IAAA;AACaA,IAAAA;AACF,MAAA;AACX,IAAA;AACA,IAAA;AACA,IAAA;AACS,IAAA;AAEC,MAAA;AACE,QAAA;AACA,QAAA;AACI,QAAA;AACD,QAAA;AACE,QAAA;AACA,QAAA;AACD,QAAA;AACC,QAAA;AACA,QAAA;AACD,QAAA;AACZ,MAAA;AACQ,MAAA;AACR,IAAA;AACN,EAAA;AACO,EAAA;AACT;AF6PkB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/aidream/aidream/apps/shared/context-menu/dist/chunk-ANJYKQAA.cjs","sourcesContent":[null,"// src/design/catalog.ts — @ai-matrx/context-menu: EVERY OPTION A MENU CAN OFFER, as a palette.\n//\n// The builder's left column. Each entry is something the live menu shows somewhere today, taken\n// from the real menu's own lists in matrx-frontend (features/context-menu-v3):\n// - regroup/engine-rows.ts → the engine rows (`cm:*`)\n// - regroup/proposed-grouping.ts → the 13 \"grouped by what you get\" groups and their rules\n// - proposed/proposed-arrangement.ts → the three icon strips and the object-first submenus\n// - the page rows the demos show (notes, quiz row, table row, chat message)\n// Ids are the live menu's ids wherever one exists, so a saved design names the real row.\n// When the menu source moves into this package, this list is generated from it instead.\n\nimport type { MenuNode } from \"./model\";\n\nexport type CatalogGroup =\n | \"Clipboard\"\n | \"Edit\"\n | \"AI\"\n | \"Save & export\"\n | \"Share & publish\"\n | \"Organize\"\n | \"History & compare\"\n | \"Listen\"\n | \"Page: notes\"\n | \"Page: record\"\n | \"Page: chat message\"\n | \"Tools & admin\";\n\nexport interface CatalogOption {\n /** The live menu's id where one exists (`cm:copy`), else this catalog's own. */\n id: string;\n label: string;\n icon: string;\n group: CatalogGroup;\n shortcut?: string;\n hint?: string;\n danger?: boolean;\n ai?: boolean;\n /** \"Live\" = drawn by today's menu; \"Proposed\" = only in a proposal page so far. */\n status: \"live\" | \"proposed\";\n}\n\nconst o = (\n id: string,\n label: string,\n icon: string,\n group: CatalogGroup,\n extra: Partial<Omit<CatalogOption, \"id\" | \"label\" | \"icon\" | \"group\">> = {},\n): CatalogOption => ({ id, label, icon, group, status: \"live\", ...extra });\n\nexport const CATALOG: readonly CatalogOption[] = [\n // Clipboard — the verb strip\n o(\"cm:copy\", \"Copy\", \"Copy\", \"Clipboard\", { shortcut: \"⌘C\" }),\n o(\"cm:cut\", \"Cut\", \"Scissors\", \"Clipboard\", { shortcut: \"⌘X\" }),\n o(\"cm:paste\", \"Paste\", \"ClipboardPaste\", \"Clipboard\", { shortcut: \"⌘V\" }),\n o(\"cm:undo\", \"Undo\", \"Undo2\", \"Clipboard\", { shortcut: \"⌘Z\" }),\n o(\"cm:redo\", \"Redo\", \"Redo2\", \"Clipboard\", { shortcut: \"⇧⌘Z\" }),\n o(\"cm:find\", \"Find & Replace\", \"Search\", \"Clipboard\", { shortcut: \"⌘F\" }),\n o(\"cm:select-all\", \"Select All\", \"TextSelect\", \"Clipboard\", { shortcut: \"⌘A\" }),\n o(\"cm:copy-as\", \"Copy as…\", \"CopyPlus\", \"Clipboard\"),\n o(\"cm:json\", \"Copy as JSON\", \"Braces\", \"Clipboard\"),\n o(\"copy-html-page\", \"Copy as HTML page\", \"FileCode\", \"Clipboard\"),\n o(\"cm:insert-reference\", \"Insert / Copy reference…\", \"AtSign\", \"Clipboard\"),\n\n // Edit\n o(\"cm:save\", \"Save\", \"Save\", \"Edit\", { shortcut: \"⌘S\" }),\n o(\"duplicate\", \"Duplicate\", \"CopyPlus\", \"Edit\"),\n o(\"rename\", \"Rename\", \"PenLine\", \"Edit\"),\n o(\"edit\", \"Edit\", \"Pencil\", \"Edit\"),\n o(\"favorite\", \"Add to favorites\", \"Star\", \"Edit\"),\n o(\"archive\", \"Archive\", \"Archive\", \"Edit\", { danger: true }),\n o(\"cm:delete\", \"Delete\", \"Trash2\", \"Edit\", { danger: true }),\n\n // AI\n o(\"cm:chat\", \"Chat about this\", \"MessageSquare\", \"AI\", { ai: true }),\n o(\"cm:quick-actions\", \"Quick Actions\", \"Zap\", \"AI\", { ai: true }),\n o(\"cm:placement:ai-action\", \"AI Actions\", \"Sparkles\", \"AI\", { ai: true }),\n o(\"cm:placement:content-block\", \"Content blocks\", \"Blocks\", \"AI\", { ai: true }),\n o(\"cm:placement:organization-tool\", \"Org Items\", \"Building2\", \"AI\", { ai: true }),\n o(\"cm:placement:user-tool\", \"My Items\", \"User\", \"AI\", { ai: true }),\n o(\"cm:placement:quick-action\", \"Quick actions (placement)\", \"Zap\", \"AI\", { ai: true }),\n o(\"cm:placement:bound-agent\", \"Agents\", \"Bot\", \"AI\", { ai: true }),\n o(\"send-to-agent\", \"Send to agent…\", \"Send\", \"AI\", { ai: true }),\n o(\"continue-in-chat\", \"Continue in chat\", \"MessagesSquare\", \"AI\", { ai: true }),\n o(\"regenerate-response\", \"Regenerate\", \"RefreshCw\", \"AI\", { ai: true }),\n o(\"code-block-chart\", \"Make a chart\", \"ChartColumn\", \"AI\", { ai: true }),\n o(\"ai-improve\", \"Improve writing\", \"Wand2\", \"AI\", { ai: true, status: \"proposed\" }),\n o(\"ai-summarize\", \"Summarize\", \"ListCollapse\", \"AI\", { ai: true, status: \"proposed\" }),\n o(\"ai-explain\", \"Explain\", \"Lightbulb\", \"AI\", { ai: true, status: \"proposed\" }),\n o(\"ai-translate\", \"Translate…\", \"Languages\", \"AI\", { ai: true, status: \"proposed\" }),\n\n // Save & export\n o(\"cm:export\", \"Export\", \"FileOutput\", \"Save & export\"),\n o(\"cm:convert\", \"Convert to…\", \"ArrowRightLeft\", \"Save & export\"),\n o(\"save-as-file\", \"Save as file\", \"FileDown\", \"Save & export\"),\n o(\"download-pdf\", \"Download PDF\", \"FileText\", \"Save & export\"),\n o(\"download-docx\", \"Download Word\", \"FileType\", \"Save & export\"),\n o(\"download-html\", \"Download HTML\", \"FileCode2\", \"Save & export\"),\n o(\"print\", \"Print\", \"Printer\", \"Save & export\", { shortcut: \"⌘P\" }),\n\n // Share & publish\n o(\"cm:share\", \"Share…\", \"Share2\", \"Share & publish\"),\n o(\"share-link\", \"Copy share link\", \"Link\", \"Share & publish\"),\n o(\"send-google-doc\", \"Send to Google Docs\", \"FileUp\", \"Share & publish\"),\n o(\"email-to-me\", \"Email to me\", \"Mail\", \"Share & publish\"),\n o(\"html-preview\", \"Preview as web page\", \"Eye\", \"Share & publish\"),\n o(\"share-webpage\", \"Publish as web page\", \"Globe\", \"Share & publish\"),\n\n // Organize\n o(\"cm:attach\", \"Attach to…\", \"Paperclip\", \"Organize\"),\n o(\"set-context-value\", \"Set as context\", \"Pin\", \"Organize\"),\n o(\"pin-message\", \"Pin\", \"Pin\", \"Organize\"),\n o(\"notes-and-comments\", \"Notes & comments\", \"StickyNote\", \"Organize\"),\n o(\"move\", \"Move to folder…\", \"FolderInput\", \"Organize\"),\n o(\"tags\", \"Tags…\", \"Tags\", \"Organize\"),\n\n // History & compare\n o(\"cm:view-history\", \"View history\", \"History\", \"History & compare\"),\n o(\"edit-history\", \"Edit history\", \"GitCommitVertical\", \"History & compare\"),\n o(\"fork-at-message\", \"Fork from here\", \"GitBranch\", \"History & compare\"),\n o(\"cm:compare\", \"Compare…\", \"GitCompareArrows\", \"History & compare\"),\n o(\"compare-with-clipboard\", \"Compare with clipboard\", \"ClipboardList\", \"History & compare\"),\n o(\"set-compare-base\", \"Set as compare base\", \"Flag\", \"History & compare\"),\n\n // Listen\n o(\"cm:speak\", \"Read aloud\", \"Volume2\", \"Listen\"),\n o(\"cm:listen\", \"Dictate\", \"Mic\", \"Listen\"),\n o(\"tts-voice-settings\", \"Voice settings\", \"AudioLines\", \"Listen\"),\n\n // Page rows: notes\n o(\"notes:close-tab\", \"Close tab\", \"X\", \"Page: notes\"),\n o(\"notes:close-others\", \"Close other tabs\", \"PanelTopClose\", \"Page: notes\"),\n o(\"notes:export-md\", \"Export as Markdown\", \"FileDown\", \"Page: notes\"),\n o(\"notes:share-clipboard\", \"Copy to clipboard\", \"ClipboardCopy\", \"Page: notes\"),\n o(\"notes:convert-artifacts\", \"Convert to artifacts\", \"Shapes\", \"Page: notes\"),\n o(\"notes:trash\", \"Move to Trash\", \"Trash2\", \"Page: notes\", { danger: true }),\n\n // Page rows: a record (quiz row, table row)\n o(\"row:open\", \"Open\", \"SquareArrowOutUpRight\", \"Page: record\"),\n o(\"row:open-tab\", \"Open in new tab\", \"ExternalLink\", \"Page: record\"),\n o(\"row:take\", \"Take\", \"Play\", \"Page: record\"),\n o(\"row:results\", \"Results\", \"ChartBar\", \"Page: record\"),\n o(\"row:manage\", \"Manage\", \"Settings2\", \"Page: record\"),\n o(\"row:restore\", \"Restore\", \"ArchiveRestore\", \"Page: record\"),\n\n // Page rows: chat message\n o(\"regenerate-latest\", \"Regenerate latest\", \"RotateCcw\", \"Page: chat message\", { ai: true }),\n o(\"fork-and-regenerate\", \"Fork and regenerate\", \"GitFork\", \"Page: chat message\", { ai: true }),\n o(\"feedback-up\", \"Good response\", \"ThumbsUp\", \"Page: chat message\"),\n o(\"feedback-down\", \"Bad response\", \"ThumbsDown\", \"Page: chat message\"),\n\n // Tools & admin\n o(\"cm:x:surface\", \"This page (location, context, agents)\", \"MapPin\", \"Tools & admin\"),\n o(\"cm:admin\", \"Admin tools\", \"Shield\", \"Tools & admin\"),\n o(\"feedback\", \"Send feedback\", \"MessageSquareWarning\", \"Tools & admin\"),\n o(\"keyboard-shortcuts\", \"Keyboard shortcuts\", \"Keyboard\", \"Tools & admin\", { status: \"proposed\" }),\n];\n\nexport const CATALOG_GROUPS: readonly CatalogGroup[] = [...new Set(CATALOG.map((c) => c.group))];\n\nexport function catalogOption(id: string): CatalogOption | undefined {\n return CATALOG.find((c) => c.id === id);\n}\n\n/** Ready-made pieces: dropping one inserts the whole block. */\nexport interface CatalogTemplate {\n id: string;\n label: string;\n icon: string;\n build: () => MenuNode;\n}\n\nlet seq = 0;\nconst tid = (p: string) => `${p}_t${Date.now().toString(36)}${(seq++).toString(36)}`;\n\nfunction itemFrom(id: string): MenuNode {\n const c = catalogOption(id);\n return {\n id: tid(\"i\"),\n kind: \"item\",\n catalogId: id,\n label: c?.label ?? id,\n icon: c?.icon ?? \"Circle\",\n ...(c?.shortcut ? { shortcut: c.shortcut } : {}),\n ...(c?.danger ? { danger: true } : {}),\n ...(c?.ai ? { ai: true } : {}),\n };\n}\n\nconst strip = (label: string, ids: string[]): MenuNode => ({\n id: tid(\"s\"),\n kind: \"section\",\n label,\n layout: \"row\",\n children: ids.map(itemFrom),\n});\n\nconst sub = (label: string, icon: string, ids: string[]): MenuNode => ({\n id: tid(\"m\"),\n kind: \"submenu\",\n label,\n icon,\n children: ids.map(itemFrom),\n});\n\nexport const TEMPLATES: readonly CatalogTemplate[] = [\n { id: \"strip-thing\", label: \"Icon strip — a thing\", icon: \"Rows2\", build: () => strip(\"\", [\"cm:copy\", \"cm:share\", \"duplicate\", \"favorite\", \"save-as-file\", \"archive\"]) },\n { id: \"strip-edit\", label: \"Icon strip — editable text\", icon: \"Rows2\", build: () => strip(\"\", [\"cm:cut\", \"cm:copy\", \"cm:paste\", \"cm:undo\", \"cm:redo\", \"cm:find\"]) },\n { id: \"strip-read\", label: \"Icon strip — read-only text\", icon: \"Rows2\", build: () => strip(\"\", [\"cm:copy\", \"cm:speak\", \"cm:find\", \"cm:share\"]) },\n { id: \"sub-intelligence\", label: \"Intelligence ▸\", icon: \"Sparkles\", build: () => sub(\"Intelligence\", \"Sparkles\", [\"cm:chat\", \"cm:placement:ai-action\", \"cm:placement:bound-agent\", \"send-to-agent\"]) },\n { id: \"sub-save-share\", label: \"Save & share ▸\", icon: \"Share2\", build: () => sub(\"Save & share\", \"Share2\", [\"cm:copy-as\", \"save-as-file\", \"cm:convert\", \"share-webpage\", \"cm:share\"]) },\n { id: \"sub-organize\", label: \"Organize ▸\", icon: \"Tags\", build: () => sub(\"Organize\", \"Tags\", [\"cm:attach\", \"pin-message\", \"cm:view-history\", \"cm:compare\"]) },\n { id: \"sub-tools\", label: \"Tools ▸\", icon: \"Wrench\", build: () => sub(\"Tools\", \"Wrench\", [\"cm:speak\", \"cm:quick-actions\", \"feedback\", \"cm:x:surface\", \"cm:admin\"]) },\n];\n\n// ── placeholders ─────────────────────────────────────────────────────────────\n\n/** What a placeholder can hold. A catalog group holds that group's options the design has not placed. */\nexport interface SlotSource {\n id: string;\n label: string;\n /** The default placeholder wording (\"All other AI items\"). */\n phrase: string;\n icon: string;\n /** Filled by the live menu from data (agents, a page's own rows) — the preview cannot list them. */\n runtime?: boolean;\n}\n\nexport const SLOT_SOURCES: readonly SlotSource[] = [\n { id: \"any\", label: \"Everything not placed\", phrase: \"Everything else\", icon: \"Ellipsis\" },\n { id: \"agents\", label: \"Agents & shortcuts (library)\", phrase: \"All other agents\", icon: \"Bot\", runtime: true },\n { id: \"page-rows\", label: \"This page's own rows\", phrase: \"All of this page's actions\", icon: \"PanelTop\", runtime: true },\n ...CATALOG_GROUPS.map((g) => ({\n id: `group:${g}`,\n label: g,\n phrase: g === \"AI\" ? \"All other AI items\" : `All other ${g.toLowerCase()} options`,\n icon: CATALOG.find((c) => c.group === g)?.icon ?? \"Circle\",\n })),\n];\n\nexport function slotSource(id: string): SlotSource | undefined {\n return SLOT_SOURCES.find((s) => s.id === id);\n}\n\n/** The catalog options a placeholder stands for: its kind, minus every option the design already places. */\nexport function slotOptions(fills: string, placed: ReadonlySet<string>): CatalogOption[] {\n if (fills === \"agents\" || fills === \"page-rows\") return [];\n const pool = fills === \"any\" ? CATALOG : CATALOG.filter((c) => `group:${c.group}` === fills);\n return pool.filter((c) => !placed.has(c.id));\n}\n","// src/design/MenuPreview.tsx — @ai-matrx/context-menu: DRAWS A DESIGN AS THE MENU IT DESCRIBES.\n//\n// The builder's live preview and its right-click test menu. It draws through THE ONE menu renderer\n// (`designed/MenuPanel`) that the live menu uses — the design goes in as `previewNodes` instead of\n// bound rows — so what the designer sees is what opens. Icons come from the host (`renderIcon`).\n\nimport { useMemo, useState, type CSSProperties, type ReactNode } from \"react\";\nimport { isContainer, type MenuDesign, type MenuItemNode, type MenuNode } from \"./model\";\nimport { previewNodes, type AvailabilityPreview } from \"./preview-nodes\";\nimport { MenuPanel, type MenuPanelEnv } from \"../designed/MenuPanel\";\nimport type { ResolvedHit } from \"../designed/bindings\";\n\nexport type { AvailabilityPreview } from \"./preview-nodes\";\n\nexport interface MenuPreviewProps {\n design: MenuDesign;\n renderIcon: (name: string, className?: string) => ReactNode;\n /** The entry highlighted as \"selected in the builder\". */\n highlightId?: string | null;\n /** A click on any entry. */\n onSelectNode?: (id: string) => void;\n /** A click on an item (the menu's action). */\n onPick?: (item: MenuItemNode) => void;\n /** \"unavailable\": every entry with an unavailable rule shows as that rule says. Default \"available\". */\n availability?: AvailabilityPreview;\n className?: string;\n style?: CSSProperties;\n}\n\n// ── search ───────────────────────────────────────────────────────────────────\n\n/** The words an entry answers to: its label, its hint and its keywords. */\nfunction searchable(node: MenuNode): string {\n if (node.kind === \"divider\" || node.kind === \"search\") return \"\";\n const hint = node.kind === \"item\" || node.kind === \"submenu\" ? (node.hint ?? \"\") : \"\";\n return [node.label, hint, ...(node.keywords ?? [])].join(\" \").toLowerCase();\n}\n\nexport interface Hit {\n node: MenuItemNode;\n /** Where it lives (\"Intelligence › AI Actions\"). */\n path: string;\n}\n\n/** While searching: every matching item, wherever it sits, with the submenus it is inside. */\nexport function searchMenu(nodes: readonly MenuNode[], q: string, trail: string[] = [], out: Hit[] = []): Hit[] {\n const words = q.split(/\\s+/).filter(Boolean);\n for (const n of nodes) {\n if (n.kind === \"item\") {\n const text = `${searchable(n)} ${trail.join(\" \").toLowerCase()}`;\n if (words.every((w) => text.includes(w))) out.push({ node: n, path: trail.join(\" › \") });\n } else if (isContainer(n)) {\n searchMenu(n.children, q, n.kind === \"submenu\" ? [...trail, n.label] : n.label ? [...trail, n.label] : trail, out);\n }\n }\n return out;\n}\n\n// ── the menu ─────────────────────────────────────────────────────────────────\n\nexport function MenuPreview({ design, renderIcon, highlightId, onSelectNode, onPick, availability = \"available\", className, style }: MenuPreviewProps) {\n const [query, setQuery] = useState(\"\");\n const nodes = useMemo(() => previewNodes(design, availability), [design, availability]);\n const env: MenuPanelEnv = {\n look: design.look,\n renderIcon,\n flyout: \"absolute\",\n highlightId,\n onSelectNode,\n onActivate: (item) => {\n if (item.source) onPick?.(item.source);\n },\n query,\n setQuery,\n search: (q): ResolvedHit[] =>\n searchMenu(design.nodes, q).map((h) => ({\n node: {\n kind: \"item\",\n id: h.node.id,\n designId: h.node.id,\n label: h.node.label,\n ...(h.node.icon ? { icon: { name: h.node.icon } } : {}),\n ...(h.node.shortcut ? { shortcut: h.node.shortcut } : {}),\n disabled: Boolean(h.node.disabled),\n ...(h.node.danger ? { danger: true } : {}),\n ...(h.node.ai ? { ai: true } : {}),\n source: h.node,\n },\n path: h.path,\n })),\n };\n return <MenuPanel nodes={nodes} env={env} {...(className ? { className } : {})} {...(style ? { style } : {})} />;\n}\n","// src/design/preview-nodes.ts — @ai-matrx/context-menu: A DESIGN AS THE BUILDER PREVIEWS IT.\n//\n// The builder and the live menu draw through ONE renderer (`designed/MenuPanel`). The live menu\n// feeds it the design bound to real rows (`designed/bindings`); the builder feeds it this: the\n// design as drawn, every entry kept, placeholders shown as dashed rows, and — when previewing\n// \"unavailable\" — each entry as its own unavailable rule says.\n\nimport { slotOptions, slotSource } from \"./catalog\";\nimport { isContainer, placedCatalogIds, type MenuDesign, type MenuItemNode, type MenuNode, type MenuSlotNode } from \"./model\";\nimport type { ResolvedItem, ResolvedNode } from \"../designed/types\";\n\n/** Preview what the person sees when some entries are unavailable (each by its own rule). */\nexport type AvailabilityPreview = \"available\" | \"unavailable\";\n\nfunction shown(node: MenuNode, availability: AvailabilityPreview): { hidden: boolean; greyed: boolean } {\n if (availability === \"available\" || !node.unavailableWhen) {\n return { hidden: false, greyed: node.kind === \"item\" && Boolean(node.disabled) };\n }\n const mode = node.unavailable ?? \"disable\";\n if (mode === \"hide\") return { hidden: true, greyed: false };\n if (mode === \"required\") return { hidden: false, greyed: false };\n return { hidden: false, greyed: true };\n}\n\nfunction item(node: MenuItemNode, greyed: boolean): ResolvedItem {\n return {\n kind: \"item\",\n id: node.id,\n designId: node.id,\n label: node.label,\n ...(node.icon ? { icon: { name: node.icon } } : {}),\n ...(node.shortcut ? { shortcut: node.shortcut } : {}),\n ...(node.hint ? { hint: node.hint } : {}),\n ...(node.keywords ? { keywords: node.keywords } : {}),\n disabled: greyed,\n ...(greyed && node.unavailableWhen ? { disabledReason: node.unavailableWhen } : {}),\n ...(node.danger ? { danger: true } : {}),\n ...(node.ai ? { ai: true } : {}),\n ...(node.tone ? { tone: node.tone } : {}),\n ...(node.toneLabel ? { toneLabel: true } : {}),\n ...(node.toggle ? { toggle: true, checked: Boolean(node.defaultOn) } : {}),\n ...(node.switch ? { switch: true, checked: Boolean(node.defaultOn) } : {}),\n source: node,\n };\n}\n\nfunction slot(node: MenuSlotNode, placed: ReadonlySet<string>): ResolvedNode {\n const source = slotSource(node.fills);\n const icon = node.icon ?? source?.icon ?? \"Ellipsis\";\n if (node.shape === \"feature\") {\n const n = Math.max(0, Math.min(12, node.featureItems ?? 4));\n return {\n kind: \"section\",\n id: node.id,\n designId: node.id,\n label: node.label || \"This feature\",\n layout: \"list\",\n feature: true,\n placeholder: true,\n children: [\n ...Array.from({ length: n }, (_, i): ResolvedNode => ({ kind: \"item\", id: `${node.id}:${i}`, designId: node.id, label: `Feature option ${i + 1}`, icon: { name: \"Circle\" }, disabled: false, placeholder: true })),\n {\n kind: \"submenu\",\n id: `${node.id}:more`,\n designId: node.id,\n label: \"More\",\n icon: { name: \"Ellipsis\" },\n disabled: false,\n placeholder: true,\n children: [{ kind: \"note\", id: `${node.id}:more-note`, text: \"The rest of this feature's options, filled at runtime\", tone: \"runtime\" }],\n },\n ],\n };\n }\n const options = slotOptions(node.fills, placed);\n const count = source?.runtime ? \"live\" : String(options.length);\n if (node.shape === \"items\") {\n return { kind: \"item\", id: node.id, designId: node.id, label: node.label, icon: { name: icon }, disabled: false, placeholder: true, count };\n }\n return {\n kind: \"submenu\",\n id: node.id,\n designId: node.id,\n label: node.label,\n icon: { name: icon },\n disabled: false,\n placeholder: true,\n count,\n children: source?.runtime\n ? [{ kind: \"note\", id: `${node.id}:note`, text: `Filled at runtime: ${source.label}`, tone: \"runtime\" }]\n : options.length === 0\n ? [{ kind: \"note\", id: `${node.id}:note`, text: \"Nothing left — every option of this kind is placed\", tone: \"empty\" }]\n : options.map((o) => ({\n kind: \"item\" as const,\n id: `slot-${node.id}-${o.id}`,\n label: o.label,\n icon: { name: o.icon },\n disabled: false,\n ...(o.shortcut ? { shortcut: o.shortcut } : {}),\n ...(o.ai ? { ai: true } : {}),\n ...(o.danger ? { danger: true } : {}),\n source: { id: `slot-${node.id}-${o.id}`, kind: \"item\", label: o.label, icon: o.icon, catalogId: o.id },\n })),\n };\n}\n\nfunction walk(nodes: readonly MenuNode[], placed: ReadonlySet<string>, availability: AvailabilityPreview): ResolvedNode[] {\n const out: ResolvedNode[] = [];\n for (const n of nodes) {\n const { hidden, greyed } = shown(n, availability);\n if (hidden) continue;\n if (n.kind === \"divider\") out.push({ kind: \"divider\", id: n.id, designId: n.id });\n else if (n.kind === \"search\") out.push({ kind: \"search\", id: n.id, designId: n.id, placeholder: n.placeholder, autofocus: n.autofocus });\n else if (n.kind === \"item\") out.push(item(n, greyed));\n else if (n.kind === \"slot\") out.push(slot(n, placed));\n else if (n.kind === \"submenu\") {\n out.push({\n kind: \"submenu\",\n id: n.id,\n designId: n.id,\n label: n.label,\n ...(n.icon ? { icon: { name: n.icon } } : {}),\n ...(n.hint ? { hint: n.hint } : {}),\n ...(n.tone ? { tone: n.tone } : {}),\n ...(n.toneLabel ? { toneLabel: true } : {}),\n disabled: greyed,\n children: walk(n.children, placed, availability),\n });\n } else if (isContainer(n)) {\n out.push({ kind: \"section\", id: n.id, designId: n.id, label: n.label, layout: n.layout ?? \"list\", ...(n.collapsible ? { collapsible: true } : {}), children: walk(n.children, placed, availability) });\n }\n }\n return out;\n}\n\n/** The design as the builder draws it (the old \"search box at the top\" look switch included). */\nexport function previewNodes(design: MenuDesign, availability: AvailabilityPreview = \"available\"): ResolvedNode[] {\n const placed = placedCatalogIds(design.nodes);\n const nodes = walk(design.nodes, placed, availability);\n const hasSearch = design.nodes.some((n) => n.kind === \"search\");\n if (!hasSearch && design.look.search) nodes.unshift({ kind: \"search\", id: \"look-search\", placeholder: \"Type to filter…\", autofocus: true });\n return nodes;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/aidream/aidream/apps/shared/context-menu/dist/chunk-MBU4Y22O.cjs","../src/design/catalog.ts","../src/design/MenuPreview.tsx","../src/design/preview-nodes.ts"],"names":["item"],"mappings":"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AACR;AAea;AACE,EAAA;AACG,EAAA;AACV,EAAA;AACH,EAAA;AACa,IAAA;AACP,IAAA;AACO,IAAA;AACA,IAAA;AACd,EAAA;AACJ;AAEgB;AACP,EAAA;AACT;AAGgB;AACA,EAAA;AACD,EAAA;AACD,EAAA;AACd;ADhFkB;AACA;AEnKA;AFqKA;AACA;AG9JH;AACT,EAAA;AACO,IAAA;AACX,EAAA;AACa,EAAA;AACA,EAAA;AACA,EAAA;AACJ,EAAA;AACX;AAEc;AACL,EAAA;AACC,IAAA;AACG,IAAA;AACC,IAAA;AACE,IAAA;AACH,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACC,IAAA;AACI,IAAA;AACL,IAAA;AACK,IAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACD,IAAA;AACV,EAAA;AACF;AAEc;AACG,EAAA;AACF,EAAA;AACJ,EAAA;AACG,IAAA;AACH,IAAA;AACC,MAAA;AACG,MAAA;AACC,MAAA;AACE,MAAA;AACJ,MAAA;AACC,MAAA;AACT,MAAA;AACU,MAAA;AACC,QAAA;AACT,QAAA;AACQ,UAAA;AACC,UAAA;AACP,UAAA;AACO,UAAA;AACC,UAAA;AACR,UAAA;AACA,UAAA;AACA,UAAA;AACF,QAAA;AACF,MAAA;AACF,IAAA;AACF,EAAA;AACgB,EAAA;AACF,EAAA;AACL,EAAA;AACE,IAAA;AACX,EAAA;AACO,EAAA;AACC,IAAA;AACG,IAAA;AACC,IAAA;AACE,IAAA;AACE,IAAA;AACJ,IAAA;AACG,IAAA;AACb,IAAA;AACU,IAAA;AAKI,MAAA;AACM,MAAA;AACH,MAAA;AACD,MAAA;AACE,MAAA;AACJ,MAAA;AACK,MAAA;AACL,MAAA;AACI,MAAA;AACV,IAAA;AACV,EAAA;AACF;AAEc;AACiB,EAAA;AACb,EAAA;AACN,IAAA;AACI,IAAA;AACN,IAAA;AACK,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,MAAA;AACD,QAAA;AACA,QAAA;AACI,QAAA;AACD,QAAA;AACH,QAAA;AACA,QAAA;AACA,QAAA;AACA,QAAA;AACI,QAAA;AACA,QAAA;AACX,MAAA;AACQ,IAAA;AACE,MAAA;AACb,IAAA;AACF,EAAA;AACO,EAAA;AACT;AAGgB;AACC,EAAA;AACD,EAAA;AACR,EAAA;AACD,EAAA;AACE,EAAA;AACT;AHuJkB;AACA;AE3MT;AA3DA;AACE,EAAA;AACI,EAAA;AACA,EAAA;AACf;AASgB;AACE,EAAA;AACA,EAAA;AACR,IAAA;AACE,MAAA;AACI,MAAA;AACD,IAAA;AACE,MAAA;AACb,IAAA;AACF,EAAA;AACO,EAAA;AACT;AAIgB;AACA,EAAA;AACA,EAAA;AACY,EAAA;AACX,IAAA;AACb,IAAA;AACQ,IAAA;AACR,IAAA;AACA,IAAA;AACaA,IAAAA;AACF,MAAA;AACX,IAAA;AACA,IAAA;AACA,IAAA;AACS,IAAA;AAEC,MAAA;AACE,QAAA;AACA,QAAA;AACI,QAAA;AACD,QAAA;AACE,QAAA;AACA,QAAA;AACD,QAAA;AACC,QAAA;AACA,QAAA;AACD,QAAA;AACZ,MAAA;AACQ,MAAA;AACR,IAAA;AACN,EAAA;AACO,EAAA;AACT;AF6PkB;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/aidream/aidream/apps/shared/context-menu/dist/chunk-MBU4Y22O.cjs","sourcesContent":[null,"// src/design/catalog.ts — @ai-matrx/context-menu: EVERY OPTION A MENU CAN OFFER, as a palette.\n//\n// The builder's left column. Each entry is something the live menu shows somewhere today, taken\n// from the real menu's own lists in matrx-frontend (features/context-menu-v3):\n// - regroup/engine-rows.ts → the engine rows (`cm:*`)\n// - regroup/proposed-grouping.ts → the 13 \"grouped by what you get\" groups and their rules\n// - proposed/proposed-arrangement.ts → the three icon strips and the object-first submenus\n// - the page rows the demos show (notes, quiz row, table row, chat message)\n// Ids are the live menu's ids wherever one exists, so a saved design names the real row.\n// When the menu source moves into this package, this list is generated from it instead.\n\nimport type { MenuNode } from \"./model\";\n\nexport type CatalogGroup =\n | \"Clipboard\"\n | \"Edit\"\n | \"AI\"\n | \"Save & export\"\n | \"Share & publish\"\n | \"Organize\"\n | \"History & compare\"\n | \"Listen\"\n | \"Page: notes\"\n | \"Page: record\"\n | \"Page: chat message\"\n | \"Tools & admin\";\n\nexport interface CatalogOption {\n /** The live menu's id where one exists (`cm:copy`), else this catalog's own. */\n id: string;\n label: string;\n icon: string;\n group: CatalogGroup;\n shortcut?: string;\n hint?: string;\n danger?: boolean;\n ai?: boolean;\n /** \"Live\" = drawn by today's menu; \"Proposed\" = only in a proposal page so far. */\n status: \"live\" | \"proposed\";\n}\n\nconst o = (\n id: string,\n label: string,\n icon: string,\n group: CatalogGroup,\n extra: Partial<Omit<CatalogOption, \"id\" | \"label\" | \"icon\" | \"group\">> = {},\n): CatalogOption => ({ id, label, icon, group, status: \"live\", ...extra });\n\nexport const CATALOG: readonly CatalogOption[] = [\n // Clipboard — the verb strip\n o(\"cm:copy\", \"Copy\", \"Copy\", \"Clipboard\", { shortcut: \"⌘C\" }),\n o(\"cm:cut\", \"Cut\", \"Scissors\", \"Clipboard\", { shortcut: \"⌘X\" }),\n o(\"cm:paste\", \"Paste\", \"ClipboardPaste\", \"Clipboard\", { shortcut: \"⌘V\" }),\n o(\"cm:undo\", \"Undo\", \"Undo2\", \"Clipboard\", { shortcut: \"⌘Z\" }),\n o(\"cm:redo\", \"Redo\", \"Redo2\", \"Clipboard\", { shortcut: \"⇧⌘Z\" }),\n o(\"cm:find\", \"Find & Replace\", \"Search\", \"Clipboard\", { shortcut: \"⌘F\" }),\n o(\"cm:select-all\", \"Select All\", \"TextSelect\", \"Clipboard\", { shortcut: \"⌘A\" }),\n o(\"cm:copy-as\", \"Copy as…\", \"CopyPlus\", \"Clipboard\"),\n o(\"cm:json\", \"Copy as JSON\", \"Braces\", \"Clipboard\"),\n o(\"copy-html-page\", \"Copy as HTML page\", \"FileCode\", \"Clipboard\"),\n o(\"cm:insert-reference\", \"Insert / Copy reference…\", \"AtSign\", \"Clipboard\"),\n\n // Edit\n o(\"cm:save\", \"Save\", \"Save\", \"Edit\", { shortcut: \"⌘S\" }),\n o(\"duplicate\", \"Duplicate\", \"CopyPlus\", \"Edit\"),\n o(\"rename\", \"Rename\", \"PenLine\", \"Edit\"),\n o(\"edit\", \"Edit\", \"Pencil\", \"Edit\"),\n o(\"favorite\", \"Add to favorites\", \"Star\", \"Edit\"),\n o(\"archive\", \"Archive\", \"Archive\", \"Edit\", { danger: true }),\n o(\"cm:delete\", \"Delete\", \"Trash2\", \"Edit\", { danger: true }),\n\n // AI\n o(\"cm:chat\", \"Chat about this\", \"MessageSquare\", \"AI\", { ai: true }),\n o(\"cm:quick-actions\", \"Quick Actions\", \"Zap\", \"AI\", { ai: true }),\n o(\"cm:placement:ai-action\", \"AI Actions\", \"Sparkles\", \"AI\", { ai: true }),\n o(\"cm:placement:content-block\", \"Content blocks\", \"Blocks\", \"AI\", { ai: true }),\n o(\"cm:placement:organization-tool\", \"Org Items\", \"Building2\", \"AI\", { ai: true }),\n o(\"cm:placement:user-tool\", \"My Items\", \"User\", \"AI\", { ai: true }),\n o(\"cm:placement:quick-action\", \"Quick actions (placement)\", \"Zap\", \"AI\", { ai: true }),\n o(\"cm:placement:bound-agent\", \"Agents\", \"Bot\", \"AI\", { ai: true }),\n o(\"send-to-agent\", \"Send to agent…\", \"Send\", \"AI\", { ai: true }),\n o(\"continue-in-chat\", \"Continue in chat\", \"MessagesSquare\", \"AI\", { ai: true }),\n o(\"regenerate-response\", \"Regenerate\", \"RefreshCw\", \"AI\", { ai: true }),\n o(\"code-block-chart\", \"Make a chart\", \"ChartColumn\", \"AI\", { ai: true }),\n o(\"ai-improve\", \"Improve writing\", \"Wand2\", \"AI\", { ai: true, status: \"proposed\" }),\n o(\"ai-summarize\", \"Summarize\", \"ListCollapse\", \"AI\", { ai: true, status: \"proposed\" }),\n o(\"ai-explain\", \"Explain\", \"Lightbulb\", \"AI\", { ai: true, status: \"proposed\" }),\n o(\"ai-translate\", \"Translate…\", \"Languages\", \"AI\", { ai: true, status: \"proposed\" }),\n\n // Save & export\n o(\"cm:export\", \"Export\", \"FileOutput\", \"Save & export\"),\n o(\"cm:convert\", \"Convert to…\", \"ArrowRightLeft\", \"Save & export\"),\n o(\"save-as-file\", \"Save as file\", \"FileDown\", \"Save & export\"),\n o(\"download-pdf\", \"Download PDF\", \"FileText\", \"Save & export\"),\n o(\"download-docx\", \"Download Word\", \"FileType\", \"Save & export\"),\n o(\"download-html\", \"Download HTML\", \"FileCode2\", \"Save & export\"),\n o(\"print\", \"Print\", \"Printer\", \"Save & export\", { shortcut: \"⌘P\" }),\n\n // Share & publish\n o(\"cm:share\", \"Share…\", \"Share2\", \"Share & publish\"),\n o(\"share-link\", \"Copy share link\", \"Link\", \"Share & publish\"),\n o(\"send-google-doc\", \"Send to Google Docs\", \"FileUp\", \"Share & publish\"),\n o(\"email-to-me\", \"Email to me\", \"Mail\", \"Share & publish\"),\n o(\"html-preview\", \"Preview as web page\", \"Eye\", \"Share & publish\"),\n o(\"share-webpage\", \"Publish as web page\", \"Globe\", \"Share & publish\"),\n\n // Organize\n o(\"cm:attach\", \"Attach to…\", \"Paperclip\", \"Organize\"),\n o(\"set-context-value\", \"Set as context\", \"Pin\", \"Organize\"),\n o(\"pin-message\", \"Pin\", \"Pin\", \"Organize\"),\n o(\"notes-and-comments\", \"Notes & comments\", \"StickyNote\", \"Organize\"),\n o(\"move\", \"Move to folder…\", \"FolderInput\", \"Organize\"),\n o(\"tags\", \"Tags…\", \"Tags\", \"Organize\"),\n\n // History & compare\n o(\"cm:view-history\", \"View history\", \"History\", \"History & compare\"),\n o(\"edit-history\", \"Edit history\", \"GitCommitVertical\", \"History & compare\"),\n o(\"fork-at-message\", \"Fork from here\", \"GitBranch\", \"History & compare\"),\n o(\"cm:compare\", \"Compare…\", \"GitCompareArrows\", \"History & compare\"),\n o(\"compare-with-clipboard\", \"Compare with clipboard\", \"ClipboardList\", \"History & compare\"),\n o(\"set-compare-base\", \"Set as compare base\", \"Flag\", \"History & compare\"),\n\n // Listen\n o(\"cm:speak\", \"Read aloud\", \"Volume2\", \"Listen\"),\n o(\"cm:listen\", \"Dictate\", \"Mic\", \"Listen\"),\n o(\"tts-voice-settings\", \"Voice settings\", \"AudioLines\", \"Listen\"),\n\n // Page rows: notes\n o(\"notes:close-tab\", \"Close tab\", \"X\", \"Page: notes\"),\n o(\"notes:close-others\", \"Close other tabs\", \"PanelTopClose\", \"Page: notes\"),\n o(\"notes:export-md\", \"Export as Markdown\", \"FileDown\", \"Page: notes\"),\n o(\"notes:share-clipboard\", \"Copy to clipboard\", \"ClipboardCopy\", \"Page: notes\"),\n o(\"notes:convert-artifacts\", \"Convert to artifacts\", \"Shapes\", \"Page: notes\"),\n o(\"notes:trash\", \"Move to Trash\", \"Trash2\", \"Page: notes\", { danger: true }),\n\n // Page rows: a record (quiz row, table row)\n o(\"row:open\", \"Open\", \"SquareArrowOutUpRight\", \"Page: record\"),\n o(\"row:open-tab\", \"Open in new tab\", \"ExternalLink\", \"Page: record\"),\n o(\"row:take\", \"Take\", \"Play\", \"Page: record\"),\n o(\"row:results\", \"Results\", \"ChartBar\", \"Page: record\"),\n o(\"row:manage\", \"Manage\", \"Settings2\", \"Page: record\"),\n o(\"row:restore\", \"Restore\", \"ArchiveRestore\", \"Page: record\"),\n\n // Page rows: chat message\n o(\"regenerate-latest\", \"Regenerate latest\", \"RotateCcw\", \"Page: chat message\", { ai: true }),\n o(\"fork-and-regenerate\", \"Fork and regenerate\", \"GitFork\", \"Page: chat message\", { ai: true }),\n o(\"feedback-up\", \"Good response\", \"ThumbsUp\", \"Page: chat message\"),\n o(\"feedback-down\", \"Bad response\", \"ThumbsDown\", \"Page: chat message\"),\n\n // Tools & admin\n o(\"cm:x:surface\", \"This page (location, context, agents)\", \"MapPin\", \"Tools & admin\"),\n o(\"cm:admin\", \"Admin tools\", \"Shield\", \"Tools & admin\"),\n o(\"feedback\", \"Send feedback\", \"MessageSquareWarning\", \"Tools & admin\"),\n o(\"keyboard-shortcuts\", \"Keyboard shortcuts\", \"Keyboard\", \"Tools & admin\", { status: \"proposed\" }),\n];\n\nexport const CATALOG_GROUPS: readonly CatalogGroup[] = [...new Set(CATALOG.map((c) => c.group))];\n\nexport function catalogOption(id: string): CatalogOption | undefined {\n return CATALOG.find((c) => c.id === id);\n}\n\n/** Ready-made pieces: dropping one inserts the whole block. */\nexport interface CatalogTemplate {\n id: string;\n label: string;\n icon: string;\n build: () => MenuNode;\n}\n\nlet seq = 0;\nconst tid = (p: string) => `${p}_t${Date.now().toString(36)}${(seq++).toString(36)}`;\n\nfunction itemFrom(id: string): MenuNode {\n const c = catalogOption(id);\n return {\n id: tid(\"i\"),\n kind: \"item\",\n catalogId: id,\n label: c?.label ?? id,\n icon: c?.icon ?? \"Circle\",\n ...(c?.shortcut ? { shortcut: c.shortcut } : {}),\n ...(c?.danger ? { danger: true } : {}),\n ...(c?.ai ? { ai: true } : {}),\n };\n}\n\nconst strip = (label: string, ids: string[]): MenuNode => ({\n id: tid(\"s\"),\n kind: \"section\",\n label,\n layout: \"row\",\n children: ids.map(itemFrom),\n});\n\nconst sub = (label: string, icon: string, ids: string[]): MenuNode => ({\n id: tid(\"m\"),\n kind: \"submenu\",\n label,\n icon,\n children: ids.map(itemFrom),\n});\n\nexport const TEMPLATES: readonly CatalogTemplate[] = [\n { id: \"strip-thing\", label: \"Icon strip — a thing\", icon: \"Rows2\", build: () => strip(\"\", [\"cm:copy\", \"cm:share\", \"duplicate\", \"favorite\", \"save-as-file\", \"archive\"]) },\n { id: \"strip-edit\", label: \"Icon strip — editable text\", icon: \"Rows2\", build: () => strip(\"\", [\"cm:cut\", \"cm:copy\", \"cm:paste\", \"cm:undo\", \"cm:redo\", \"cm:find\"]) },\n { id: \"strip-read\", label: \"Icon strip — read-only text\", icon: \"Rows2\", build: () => strip(\"\", [\"cm:copy\", \"cm:speak\", \"cm:find\", \"cm:share\"]) },\n { id: \"sub-intelligence\", label: \"Intelligence ▸\", icon: \"Sparkles\", build: () => sub(\"Intelligence\", \"Sparkles\", [\"cm:chat\", \"cm:placement:ai-action\", \"cm:placement:bound-agent\", \"send-to-agent\"]) },\n { id: \"sub-save-share\", label: \"Save & share ▸\", icon: \"Share2\", build: () => sub(\"Save & share\", \"Share2\", [\"cm:copy-as\", \"save-as-file\", \"cm:convert\", \"share-webpage\", \"cm:share\"]) },\n { id: \"sub-organize\", label: \"Organize ▸\", icon: \"Tags\", build: () => sub(\"Organize\", \"Tags\", [\"cm:attach\", \"pin-message\", \"cm:view-history\", \"cm:compare\"]) },\n { id: \"sub-tools\", label: \"Tools ▸\", icon: \"Wrench\", build: () => sub(\"Tools\", \"Wrench\", [\"cm:speak\", \"cm:quick-actions\", \"feedback\", \"cm:x:surface\", \"cm:admin\"]) },\n];\n\n// ── placeholders ─────────────────────────────────────────────────────────────\n\n/** What a placeholder can hold. A catalog group holds that group's options the design has not placed. */\nexport interface SlotSource {\n id: string;\n label: string;\n /** The default placeholder wording (\"All other AI items\"). */\n phrase: string;\n icon: string;\n /** Filled by the live menu from data (agents, a page's own rows) — the preview cannot list them. */\n runtime?: boolean;\n}\n\nexport const SLOT_SOURCES: readonly SlotSource[] = [\n { id: \"any\", label: \"Everything not placed\", phrase: \"Everything else\", icon: \"Ellipsis\" },\n { id: \"agents\", label: \"Agents & shortcuts (library)\", phrase: \"All other agents\", icon: \"Bot\", runtime: true },\n { id: \"page-rows\", label: \"This page's own rows\", phrase: \"All of this page's actions\", icon: \"PanelTop\", runtime: true },\n ...CATALOG_GROUPS.map((g) => ({\n id: `group:${g}`,\n label: g,\n phrase: g === \"AI\" ? \"All other AI items\" : `All other ${g.toLowerCase()} options`,\n icon: CATALOG.find((c) => c.group === g)?.icon ?? \"Circle\",\n })),\n];\n\nexport function slotSource(id: string): SlotSource | undefined {\n return SLOT_SOURCES.find((s) => s.id === id);\n}\n\n/** The catalog options a placeholder stands for: its kind, minus every option the design already places. */\nexport function slotOptions(fills: string, placed: ReadonlySet<string>): CatalogOption[] {\n if (fills === \"agents\" || fills === \"page-rows\") return [];\n const pool = fills === \"any\" ? CATALOG : CATALOG.filter((c) => `group:${c.group}` === fills);\n return pool.filter((c) => !placed.has(c.id));\n}\n","// src/design/MenuPreview.tsx — @ai-matrx/context-menu: DRAWS A DESIGN AS THE MENU IT DESCRIBES.\n//\n// The builder's live preview and its right-click test menu. It draws through THE ONE menu renderer\n// (`designed/MenuPanel`) that the live menu uses — the design goes in as `previewNodes` instead of\n// bound rows — so what the designer sees is what opens. Icons come from the host (`renderIcon`).\n\nimport { useMemo, useState, type CSSProperties, type ReactNode } from \"react\";\nimport { isContainer, type MenuDesign, type MenuItemNode, type MenuNode } from \"./model\";\nimport { previewNodes, type AvailabilityPreview } from \"./preview-nodes\";\nimport { MenuPanel, type MenuPanelEnv } from \"../designed/MenuPanel\";\nimport type { ResolvedHit } from \"../designed/bindings\";\n\nexport type { AvailabilityPreview } from \"./preview-nodes\";\n\nexport interface MenuPreviewProps {\n design: MenuDesign;\n renderIcon: (name: string, className?: string) => ReactNode;\n /** The entry highlighted as \"selected in the builder\". */\n highlightId?: string | null;\n /** A click on any entry. */\n onSelectNode?: (id: string) => void;\n /** A click on an item (the menu's action). */\n onPick?: (item: MenuItemNode) => void;\n /** \"unavailable\": every entry with an unavailable rule shows as that rule says. Default \"available\". */\n availability?: AvailabilityPreview;\n className?: string;\n style?: CSSProperties;\n}\n\n// ── search ───────────────────────────────────────────────────────────────────\n\n/** The words an entry answers to: its label, its hint and its keywords. */\nfunction searchable(node: MenuNode): string {\n if (node.kind === \"divider\" || node.kind === \"search\") return \"\";\n const hint = node.kind === \"item\" || node.kind === \"submenu\" ? (node.hint ?? \"\") : \"\";\n return [node.label, hint, ...(node.keywords ?? [])].join(\" \").toLowerCase();\n}\n\nexport interface Hit {\n node: MenuItemNode;\n /** Where it lives (\"Intelligence › AI Actions\"). */\n path: string;\n}\n\n/** While searching: every matching item, wherever it sits, with the submenus it is inside. */\nexport function searchMenu(nodes: readonly MenuNode[], q: string, trail: string[] = [], out: Hit[] = []): Hit[] {\n const words = q.split(/\\s+/).filter(Boolean);\n for (const n of nodes) {\n if (n.kind === \"item\") {\n const text = `${searchable(n)} ${trail.join(\" \").toLowerCase()}`;\n if (words.every((w) => text.includes(w))) out.push({ node: n, path: trail.join(\" › \") });\n } else if (isContainer(n)) {\n searchMenu(n.children, q, n.kind === \"submenu\" ? [...trail, n.label] : n.label ? [...trail, n.label] : trail, out);\n }\n }\n return out;\n}\n\n// ── the menu ─────────────────────────────────────────────────────────────────\n\nexport function MenuPreview({ design, renderIcon, highlightId, onSelectNode, onPick, availability = \"available\", className, style }: MenuPreviewProps) {\n const [query, setQuery] = useState(\"\");\n const nodes = useMemo(() => previewNodes(design, availability), [design, availability]);\n const env: MenuPanelEnv = {\n look: design.look,\n renderIcon,\n flyout: \"absolute\",\n highlightId,\n onSelectNode,\n onActivate: (item) => {\n if (item.source) onPick?.(item.source);\n },\n query,\n setQuery,\n search: (q): ResolvedHit[] =>\n searchMenu(design.nodes, q).map((h) => ({\n node: {\n kind: \"item\",\n id: h.node.id,\n designId: h.node.id,\n label: h.node.label,\n ...(h.node.icon ? { icon: { name: h.node.icon } } : {}),\n ...(h.node.shortcut ? { shortcut: h.node.shortcut } : {}),\n disabled: Boolean(h.node.disabled),\n ...(h.node.danger ? { danger: true } : {}),\n ...(h.node.ai ? { ai: true } : {}),\n source: h.node,\n },\n path: h.path,\n })),\n };\n return <MenuPanel nodes={nodes} env={env} {...(className ? { className } : {})} {...(style ? { style } : {})} />;\n}\n","// src/design/preview-nodes.ts — @ai-matrx/context-menu: A DESIGN AS THE BUILDER PREVIEWS IT.\n//\n// The builder and the live menu draw through ONE renderer (`designed/MenuPanel`). The live menu\n// feeds it the design bound to real rows (`designed/bindings`); the builder feeds it this: the\n// design as drawn, every entry kept, placeholders shown as dashed rows, and — when previewing\n// \"unavailable\" — each entry as its own unavailable rule says.\n\nimport { slotOptions, slotSource } from \"./catalog\";\nimport { isContainer, placedCatalogIds, type MenuDesign, type MenuItemNode, type MenuNode, type MenuSlotNode } from \"./model\";\nimport type { ResolvedItem, ResolvedNode } from \"../designed/types\";\n\n/** Preview what the person sees when some entries are unavailable (each by its own rule). */\nexport type AvailabilityPreview = \"available\" | \"unavailable\";\n\nfunction shown(node: MenuNode, availability: AvailabilityPreview): { hidden: boolean; greyed: boolean } {\n if (availability === \"available\" || !node.unavailableWhen) {\n return { hidden: false, greyed: node.kind === \"item\" && Boolean(node.disabled) };\n }\n const mode = node.unavailable ?? \"disable\";\n if (mode === \"hide\") return { hidden: true, greyed: false };\n if (mode === \"required\") return { hidden: false, greyed: false };\n return { hidden: false, greyed: true };\n}\n\nfunction item(node: MenuItemNode, greyed: boolean): ResolvedItem {\n return {\n kind: \"item\",\n id: node.id,\n designId: node.id,\n label: node.label,\n ...(node.icon ? { icon: { name: node.icon } } : {}),\n ...(node.shortcut ? { shortcut: node.shortcut } : {}),\n ...(node.hint ? { hint: node.hint } : {}),\n ...(node.keywords ? { keywords: node.keywords } : {}),\n disabled: greyed,\n ...(greyed && node.unavailableWhen ? { disabledReason: node.unavailableWhen } : {}),\n ...(node.danger ? { danger: true } : {}),\n ...(node.ai ? { ai: true } : {}),\n ...(node.tone ? { tone: node.tone } : {}),\n ...(node.toneLabel ? { toneLabel: true } : {}),\n ...(node.toggle ? { toggle: true, checked: Boolean(node.defaultOn) } : {}),\n ...(node.switch ? { switch: true, checked: Boolean(node.defaultOn) } : {}),\n source: node,\n };\n}\n\nfunction slot(node: MenuSlotNode, placed: ReadonlySet<string>): ResolvedNode {\n const source = slotSource(node.fills);\n const icon = node.icon ?? source?.icon ?? \"Ellipsis\";\n if (node.shape === \"feature\") {\n const n = Math.max(0, Math.min(12, node.featureItems ?? 4));\n return {\n kind: \"section\",\n id: node.id,\n designId: node.id,\n label: node.label || \"This feature\",\n layout: \"list\",\n feature: true,\n placeholder: true,\n children: [\n ...Array.from({ length: n }, (_, i): ResolvedNode => ({ kind: \"item\", id: `${node.id}:${i}`, designId: node.id, label: `Feature option ${i + 1}`, icon: { name: \"Circle\" }, disabled: false, placeholder: true })),\n {\n kind: \"submenu\",\n id: `${node.id}:more`,\n designId: node.id,\n label: \"More\",\n icon: { name: \"Ellipsis\" },\n disabled: false,\n placeholder: true,\n children: [{ kind: \"note\", id: `${node.id}:more-note`, text: \"The rest of this feature's options, filled at runtime\", tone: \"runtime\" }],\n },\n ],\n };\n }\n const options = slotOptions(node.fills, placed);\n const count = source?.runtime ? \"live\" : String(options.length);\n if (node.shape === \"items\") {\n return { kind: \"item\", id: node.id, designId: node.id, label: node.label, icon: { name: icon }, disabled: false, placeholder: true, count };\n }\n return {\n kind: \"submenu\",\n id: node.id,\n designId: node.id,\n label: node.label,\n icon: { name: icon },\n disabled: false,\n placeholder: true,\n count,\n children: source?.runtime\n ? [{ kind: \"note\", id: `${node.id}:note`, text: `Filled at runtime: ${source.label}`, tone: \"runtime\" }]\n : options.length === 0\n ? [{ kind: \"note\", id: `${node.id}:note`, text: \"Nothing left — every option of this kind is placed\", tone: \"empty\" }]\n : options.map((o) => ({\n kind: \"item\" as const,\n id: `slot-${node.id}-${o.id}`,\n label: o.label,\n icon: { name: o.icon },\n disabled: false,\n ...(o.shortcut ? { shortcut: o.shortcut } : {}),\n ...(o.ai ? { ai: true } : {}),\n ...(o.danger ? { danger: true } : {}),\n source: { id: `slot-${node.id}-${o.id}`, kind: \"item\", label: o.label, icon: o.icon, catalogId: o.id },\n })),\n };\n}\n\nfunction walk(nodes: readonly MenuNode[], placed: ReadonlySet<string>, availability: AvailabilityPreview): ResolvedNode[] {\n const out: ResolvedNode[] = [];\n for (const n of nodes) {\n const { hidden, greyed } = shown(n, availability);\n if (hidden) continue;\n if (n.kind === \"divider\") out.push({ kind: \"divider\", id: n.id, designId: n.id });\n else if (n.kind === \"search\") out.push({ kind: \"search\", id: n.id, designId: n.id, placeholder: n.placeholder, autofocus: n.autofocus });\n else if (n.kind === \"item\") out.push(item(n, greyed));\n else if (n.kind === \"slot\") out.push(slot(n, placed));\n else if (n.kind === \"submenu\") {\n out.push({\n kind: \"submenu\",\n id: n.id,\n designId: n.id,\n label: n.label,\n ...(n.icon ? { icon: { name: n.icon } } : {}),\n ...(n.hint ? { hint: n.hint } : {}),\n ...(n.tone ? { tone: n.tone } : {}),\n ...(n.toneLabel ? { toneLabel: true } : {}),\n disabled: greyed,\n children: walk(n.children, placed, availability),\n });\n } else if (isContainer(n)) {\n out.push({ kind: \"section\", id: n.id, designId: n.id, label: n.label, layout: n.layout ?? \"list\", ...(n.collapsible ? { collapsible: true } : {}), children: walk(n.children, placed, availability) });\n }\n }\n return out;\n}\n\n/** The design as the builder draws it (the old \"search box at the top\" look switch included). */\nexport function previewNodes(design: MenuDesign, availability: AvailabilityPreview = \"available\"): ResolvedNode[] {\n const placed = placedCatalogIds(design.nodes);\n const nodes = walk(design.nodes, placed, availability);\n const hasSearch = design.nodes.some((n) => n.kind === \"search\");\n if (!hasSearch && design.look.search) nodes.unshift({ kind: \"search\", id: \"look-search\", placeholder: \"Type to filter…\", autofocus: true });\n return nodes;\n}\n"]}
|