@ai-matrx/context-menu 0.1.0 → 0.1.2
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 +13 -0
- package/dist/builder.cjs +158 -13
- package/dist/builder.cjs.map +1 -1
- package/dist/builder.d.cts +1 -1
- package/dist/builder.d.ts +1 -1
- package/dist/builder.js +159 -14
- package/dist/builder.js.map +1 -1
- package/dist/design-store.cjs.map +1 -1
- package/dist/design-store.d.cts +1 -1
- package/dist/design-store.d.ts +1 -1
- package/dist/design-store.js.map +1 -1
- package/dist/index.cjs +102 -5
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -3
- package/dist/index.d.ts +17 -3
- package/dist/index.js +102 -5
- package/dist/index.js.map +1 -1
- package/dist/{model-BdF0TqZ3.d.cts → model-CkLCR6WY.d.cts} +19 -3
- package/dist/{model-BdF0TqZ3.d.ts → model-CkLCR6WY.d.ts} +19 -3
- package/package.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.2
|
|
4
|
+
|
|
5
|
+
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
6
|
+
`git diff npm/context-menu/v0.1.1..npm/context-menu/v0.1.2 -- apps/shared/context-menu`).
|
|
7
|
+
No source changes intended and no consumer action required.
|
|
8
|
+
|
|
9
|
+
## 0.1.1
|
|
10
|
+
|
|
11
|
+
Automatic changed-only republish (docs/metadata drift since the last tag — see
|
|
12
|
+
`git diff npm/context-menu/v0.1.0..npm/context-menu/v0.1.1 -- apps/shared/context-menu`).
|
|
13
|
+
No source changes intended and no consumer action required.
|
|
14
|
+
|
|
3
15
|
## 0.1.0
|
|
4
16
|
|
|
5
17
|
- The menu design model, option catalog, renderer, versioned design store and drag-and-drop builder.
|
|
@@ -14,3 +26,4 @@
|
|
|
14
26
|
- Repoint `@/features/context-menu-v3/*` imports: components and hooks → `@ai-matrx/context-menu/react`,
|
|
15
27
|
everything else → `@ai-matrx/context-menu/engine`; then delete `features/context-menu-v3/`.
|
|
16
28
|
|
|
29
|
+
|
package/dist/builder.cjs
CHANGED
|
@@ -180,6 +180,25 @@ var TEMPLATES = [
|
|
|
180
180
|
{ id: "sub-organize", label: "Organize \u25B8", icon: "Tags", build: () => sub("Organize", "Tags", ["cm:attach", "pin-message", "cm:view-history", "cm:compare"]) },
|
|
181
181
|
{ id: "sub-tools", label: "Tools \u25B8", icon: "Wrench", build: () => sub("Tools", "Wrench", ["cm:speak", "cm:quick-actions", "feedback", "cm:x:surface", "cm:admin"]) }
|
|
182
182
|
];
|
|
183
|
+
var SLOT_SOURCES = [
|
|
184
|
+
{ id: "any", label: "Everything not placed", phrase: "Everything else", icon: "Ellipsis" },
|
|
185
|
+
{ id: "agents", label: "Agents & shortcuts (library)", phrase: "All other agents", icon: "Bot", runtime: true },
|
|
186
|
+
{ id: "page-rows", label: "This page's own rows", phrase: "All of this page's actions", icon: "PanelTop", runtime: true },
|
|
187
|
+
...CATALOG_GROUPS.map((g) => ({
|
|
188
|
+
id: `group:${g}`,
|
|
189
|
+
label: g,
|
|
190
|
+
phrase: g === "AI" ? "All other AI items" : `All other ${g.toLowerCase()} options`,
|
|
191
|
+
icon: CATALOG.find((c) => c.group === g)?.icon ?? "Circle"
|
|
192
|
+
}))
|
|
193
|
+
];
|
|
194
|
+
function slotSource(id) {
|
|
195
|
+
return SLOT_SOURCES.find((s) => s.id === id);
|
|
196
|
+
}
|
|
197
|
+
function slotOptions(fills, placed) {
|
|
198
|
+
if (fills === "agents" || fills === "page-rows") return [];
|
|
199
|
+
const pool = fills === "any" ? CATALOG : CATALOG.filter((c) => `group:${c.group}` === fills);
|
|
200
|
+
return pool.filter((c) => !placed.has(c.id));
|
|
201
|
+
}
|
|
183
202
|
|
|
184
203
|
// src/design/MenuPreview.tsx
|
|
185
204
|
var import_react = require("react");
|
|
@@ -213,6 +232,20 @@ function emptyDesign(name = "Untitled menu") {
|
|
|
213
232
|
function isContainer(node) {
|
|
214
233
|
return node.kind === "section" || node.kind === "submenu";
|
|
215
234
|
}
|
|
235
|
+
function placedCatalogIds(nodes, out = /* @__PURE__ */ new Set()) {
|
|
236
|
+
for (const n of nodes) {
|
|
237
|
+
if (n.catalogId) out.add(n.catalogId);
|
|
238
|
+
if (isContainer(n)) placedCatalogIds(n.children, out);
|
|
239
|
+
}
|
|
240
|
+
return out;
|
|
241
|
+
}
|
|
242
|
+
function catalogUseCounts(nodes, out = /* @__PURE__ */ new Map()) {
|
|
243
|
+
for (const n of nodes) {
|
|
244
|
+
if (n.catalogId) out.set(n.catalogId, (out.get(n.catalogId) ?? 0) + 1);
|
|
245
|
+
if (isContainer(n)) catalogUseCounts(n.children, out);
|
|
246
|
+
}
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
216
249
|
function cloneNode(node) {
|
|
217
250
|
const copy = { ...node, id: newId(node.kind[0]) };
|
|
218
251
|
if (isContainer(copy)) copy.children = copy.children.map(cloneNode);
|
|
@@ -236,7 +269,7 @@ function contains(node, id) {
|
|
|
236
269
|
function countItems(nodes) {
|
|
237
270
|
let n = 0;
|
|
238
271
|
for (const node of nodes) {
|
|
239
|
-
if (node.kind === "item") n += 1;
|
|
272
|
+
if (node.kind === "item" || node.kind === "slot") n += 1;
|
|
240
273
|
else if (isContainer(node)) n += countItems(node.children) + (node.kind === "submenu" ? 1 : 0);
|
|
241
274
|
}
|
|
242
275
|
return n;
|
|
@@ -326,7 +359,7 @@ var import_jsx_runtime = require("react/jsx-runtime");
|
|
|
326
359
|
function matches(node, q) {
|
|
327
360
|
if (!q) return true;
|
|
328
361
|
if (node.kind === "divider") return false;
|
|
329
|
-
if (node.kind === "item") return node.label.toLowerCase().includes(q);
|
|
362
|
+
if (node.kind === "item" || node.kind === "slot") return node.label.toLowerCase().includes(q);
|
|
330
363
|
return node.label.toLowerCase().includes(q) || node.children.some((c) => matches(c, q));
|
|
331
364
|
}
|
|
332
365
|
function flatten(nodes, q, out = []) {
|
|
@@ -341,6 +374,8 @@ function MenuPreview({ design: design2, renderIcon, highlightId, onSelectNode, o
|
|
|
341
374
|
const q = query.trim().toLowerCase();
|
|
342
375
|
const { look } = design2;
|
|
343
376
|
const results = (0, import_react.useMemo)(() => q ? flatten(design2.nodes, q) : null, [design2.nodes, q]);
|
|
377
|
+
const placed = (0, import_react.useMemo)(() => placedCatalogIds(design2.nodes), [design2.nodes]);
|
|
378
|
+
const ctx = { design: design2, renderIcon, highlightId, onSelectNode, onPick, placed };
|
|
344
379
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
345
380
|
"div",
|
|
346
381
|
{
|
|
@@ -353,7 +388,7 @@ function MenuPreview({ design: design2, renderIcon, highlightId, onSelectNode, o
|
|
|
353
388
|
renderIcon("Search", "cm-icon"),
|
|
354
389
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Type to filter\u2026", "aria-label": "Filter menu" })
|
|
355
390
|
] }),
|
|
356
|
-
results ? results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "cm-empty", children: "No matches" }) : results.map((n) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Item, { node: n, ctx
|
|
391
|
+
results ? results.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "cm-empty", children: "No matches" }) : results.map((n) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Item, { node: n, ctx }, n.id)) : design2.nodes.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "cm-empty", children: "Empty menu \u2014 drag options in" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Nodes, { nodes: design2.nodes, ctx })
|
|
357
392
|
]
|
|
358
393
|
}
|
|
359
394
|
);
|
|
@@ -363,6 +398,7 @@ function Nodes({ nodes, ctx }) {
|
|
|
363
398
|
if (n.kind === "divider") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "separator", className: "cm-divider" }, n.id);
|
|
364
399
|
if (n.kind === "item") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Item, { node: n, ctx }, n.id);
|
|
365
400
|
if (n.kind === "submenu") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Submenu, { node: n, ctx }, n.id);
|
|
401
|
+
if (n.kind === "slot") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Slot, { node: n, ctx }, n.id);
|
|
366
402
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, { node: n, ctx }, n.id);
|
|
367
403
|
}) });
|
|
368
404
|
}
|
|
@@ -446,8 +482,7 @@ function Submenu({ node, ctx }) {
|
|
|
446
482
|
const [open, setOpen] = (0, import_react.useState)(false);
|
|
447
483
|
const [side, setSide] = (0, import_react.useState)("right");
|
|
448
484
|
const show = (el) => {
|
|
449
|
-
|
|
450
|
-
setSide(r.right + look.width + 8 > window.innerWidth ? "left" : "right");
|
|
485
|
+
setSide(flyoutSide(el, look.width));
|
|
451
486
|
setOpen(true);
|
|
452
487
|
};
|
|
453
488
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "cm-sub", onMouseEnter: (e) => show(e.currentTarget), onMouseLeave: () => setOpen(false), children: [
|
|
@@ -476,6 +511,63 @@ function Submenu({ node, ctx }) {
|
|
|
476
511
|
open && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "menu", className: "cm-menu cm-flyout", "data-side": side, "data-density": look.density, style: { width: look.width }, children: node.children.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "cm-empty", children: "Empty submenu" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Nodes, { nodes: node.children, ctx }) })
|
|
477
512
|
] });
|
|
478
513
|
}
|
|
514
|
+
function flyoutSide(row, width) {
|
|
515
|
+
const r = row.getBoundingClientRect();
|
|
516
|
+
const bounds = row.closest("[data-menu-bounds]")?.getBoundingClientRect() ?? { left: 0, right: window.innerWidth };
|
|
517
|
+
if (r.right + width + 8 <= bounds.right) return "right";
|
|
518
|
+
if (r.left - width - 8 >= bounds.left) return "left";
|
|
519
|
+
return "below";
|
|
520
|
+
}
|
|
521
|
+
function Slot({ node, ctx }) {
|
|
522
|
+
const { look } = ctx.design;
|
|
523
|
+
const source = slotSource(node.fills);
|
|
524
|
+
const options = slotOptions(node.fills, ctx.placed);
|
|
525
|
+
const [open, setOpen] = (0, import_react.useState)(false);
|
|
526
|
+
const [side, setSide] = (0, import_react.useState)("right");
|
|
527
|
+
const count = source?.runtime ? "live" : String(options.length);
|
|
528
|
+
const row = (
|
|
529
|
+
// ui-exception: a menu row of the menu component itself
|
|
530
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
531
|
+
"button",
|
|
532
|
+
{
|
|
533
|
+
type: "button",
|
|
534
|
+
role: "menuitem",
|
|
535
|
+
"aria-haspopup": node.shape === "submenu" ? "menu" : void 0,
|
|
536
|
+
title: source ? `Placeholder: ${source.label}` : "Placeholder",
|
|
537
|
+
className: `cm-item cm-slot ${ctx.highlightId === node.id ? "cm-hl" : ""}`,
|
|
538
|
+
onClick: () => {
|
|
539
|
+
ctx.onSelectNode?.(node.id);
|
|
540
|
+
if (node.shape === "submenu") setOpen(true);
|
|
541
|
+
},
|
|
542
|
+
children: [
|
|
543
|
+
look.showIcons && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-icon-slot", children: ctx.renderIcon(node.icon ?? source?.icon ?? "Ellipsis", "cm-icon") }),
|
|
544
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-text", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-label", children: node.label }) }),
|
|
545
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-count", children: count }),
|
|
546
|
+
node.shape === "submenu" && ctx.renderIcon("ChevronRight", "cm-icon cm-chev")
|
|
547
|
+
]
|
|
548
|
+
}
|
|
549
|
+
)
|
|
550
|
+
);
|
|
551
|
+
if (node.shape === "items") return row;
|
|
552
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
553
|
+
"div",
|
|
554
|
+
{
|
|
555
|
+
className: "cm-sub",
|
|
556
|
+
onMouseEnter: (e) => {
|
|
557
|
+
setSide(flyoutSide(e.currentTarget, look.width));
|
|
558
|
+
setOpen(true);
|
|
559
|
+
},
|
|
560
|
+
onMouseLeave: () => setOpen(false),
|
|
561
|
+
children: [
|
|
562
|
+
row,
|
|
563
|
+
open && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "menu", className: "cm-menu cm-flyout", "data-side": side, "data-density": look.density, style: { width: look.width }, children: source?.runtime ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "cm-runtime", children: [
|
|
564
|
+
"Filled at runtime: ",
|
|
565
|
+
source.label
|
|
566
|
+
] }) : options.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "cm-empty", children: "Nothing left \u2014 every option of this kind is placed" }) : options.map((o2) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Item, { node: { id: `slot-${node.id}-${o2.id}`, kind: "item", label: o2.label, icon: o2.icon, catalogId: o2.id, ...o2.shortcut ? { shortcut: o2.shortcut } : {}, ...o2.ai ? { ai: true } : {}, ...o2.danger ? { danger: true } : {} }, ctx }, o2.id)) })
|
|
567
|
+
]
|
|
568
|
+
}
|
|
569
|
+
);
|
|
570
|
+
}
|
|
479
571
|
|
|
480
572
|
// src/design/starters.ts
|
|
481
573
|
function maker(slug) {
|
|
@@ -985,6 +1077,7 @@ var SURFACE_LABELS = {
|
|
|
985
1077
|
anywhere: "Opens anywhere"
|
|
986
1078
|
};
|
|
987
1079
|
var SURFACE_OPTIONS = MENU_SURFACES.map((value) => ({ value, label: SURFACE_LABELS[value] }));
|
|
1080
|
+
var DesignContext = (0, import_react2.createContext)(null);
|
|
988
1081
|
var DRAFT_KEY = "matrx.context-menu.builder.draft.v1";
|
|
989
1082
|
function usePointerDrag(onDrop) {
|
|
990
1083
|
const [ghost, setGhost] = (0, import_react2.useState)(null);
|
|
@@ -1060,6 +1153,8 @@ function nodeFromPayload(p) {
|
|
|
1060
1153
|
if (p.kind === "section") return { id: newId("s"), kind: "section", label: "New section", children: [] };
|
|
1061
1154
|
if (p.kind === "submenu") return { id: newId("m"), kind: "submenu", label: "More", icon: "Ellipsis", children: [] };
|
|
1062
1155
|
if (p.kind === "divider") return { id: newId("d"), kind: "divider" };
|
|
1156
|
+
if (p.kind === "slot-items") return { id: newId("p"), kind: "slot", shape: "items", fills: "any", label: "Everything else" };
|
|
1157
|
+
if (p.kind === "slot-menu") return { id: newId("p"), kind: "slot", shape: "submenu", fills: "any", label: "More", icon: "Ellipsis" };
|
|
1063
1158
|
return { id: newId("i"), kind: "item", label: "New option", icon: "Circle" };
|
|
1064
1159
|
}
|
|
1065
1160
|
return null;
|
|
@@ -1213,7 +1308,7 @@ function MenuBuilder({ store, storeLabel, initialDesign, accountSlot }) {
|
|
|
1213
1308
|
for (const v of versions ?? []) byId.set(v.designId, [...byId.get(v.designId) ?? [], v]);
|
|
1214
1309
|
return [...byId.values()];
|
|
1215
1310
|
}, [versions]);
|
|
1216
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex h-dvh flex-col bg-background text-foreground", children: [
|
|
1311
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(DesignContext.Provider, { value: design2, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex h-dvh flex-col bg-background text-foreground", children: [
|
|
1217
1312
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("header", { className: "flex flex-wrap items-center gap-y-1 border-b border-border px-3 py-2", children: [
|
|
1218
1313
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_controls.ControlRow, { className: "min-w-0 flex-1", children: [
|
|
1219
1314
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.Field, { "aria-label": "Design name", width: "lg", value: design2.name, onChange: (e) => set((d) => ({ ...d, name: e.target.value })) }),
|
|
@@ -1336,7 +1431,7 @@ function MenuBuilder({ store, storeLabel, initialDesign, accountSlot }) {
|
|
|
1336
1431
|
children: dnd.ghost.label
|
|
1337
1432
|
}
|
|
1338
1433
|
)
|
|
1339
|
-
] });
|
|
1434
|
+
] }) });
|
|
1340
1435
|
}
|
|
1341
1436
|
function Panel({ title, children, right }) {
|
|
1342
1437
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("section", { className: "border-b border-border p-3", children: [
|
|
@@ -1355,7 +1450,9 @@ function Palette({ onAdd, startDrag, click }) {
|
|
|
1355
1450
|
const n = nodeFromPayload(p);
|
|
1356
1451
|
if (n) onAdd(n);
|
|
1357
1452
|
};
|
|
1358
|
-
const
|
|
1453
|
+
const design2 = (0, import_react2.useContext)(DesignContext);
|
|
1454
|
+
const uses = (0, import_react2.useMemo)(() => catalogUseCounts(design2?.nodes ?? []), [design2?.nodes]);
|
|
1455
|
+
const Option = ({ p, icon, label, end, dimmed }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1359
1456
|
import_controls.Tile,
|
|
1360
1457
|
{
|
|
1361
1458
|
layout: "row",
|
|
@@ -1363,6 +1460,7 @@ function Palette({ onAdd, startDrag, click }) {
|
|
|
1363
1460
|
icon: renderLucide(icon),
|
|
1364
1461
|
title: label,
|
|
1365
1462
|
end,
|
|
1463
|
+
dimmed: dimmed === true,
|
|
1366
1464
|
onPointerDown: (e) => startDrag(e, p, label),
|
|
1367
1465
|
onClick: click(() => add(p))
|
|
1368
1466
|
}
|
|
@@ -1375,14 +1473,27 @@ function Palette({ onAdd, startDrag, click }) {
|
|
|
1375
1473
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Option, { p: { type: "new", kind: "section" }, icon: "Rows3", label: "Section" }),
|
|
1376
1474
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Option, { p: { type: "new", kind: "submenu" }, icon: "ChevronsRight", label: "Submenu" }),
|
|
1377
1475
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Option, { p: { type: "new", kind: "divider" }, icon: "Minus", label: "Divider" }),
|
|
1378
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Option, { p: { type: "new", kind: "item" }, icon: "Plus", label: "Custom option" })
|
|
1476
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Option, { p: { type: "new", kind: "item" }, icon: "Plus", label: "Custom option" }),
|
|
1477
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Option, { p: { type: "new", kind: "slot-items" }, icon: "ListPlus", label: "Placeholder \u2014 items" }),
|
|
1478
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Option, { p: { type: "new", kind: "slot-menu" }, icon: "FolderPlus", label: "Placeholder \u2014 submenu" })
|
|
1379
1479
|
] }),
|
|
1380
1480
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.RowGroup, { title: "Ready-made", children: TEMPLATES.map((t) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Option, { p: { type: "template", id: t.id }, icon: t.icon, label: t.label }, t.id)) })
|
|
1381
1481
|
] }),
|
|
1382
1482
|
CATALOG_GROUPS.map((g) => {
|
|
1383
|
-
const
|
|
1483
|
+
const matching = CATALOG.filter((c) => c.group === g && shown(c));
|
|
1484
|
+
const items = [...matching.filter((c) => !uses.has(c.id)), ...matching.filter((c) => uses.has(c.id))];
|
|
1384
1485
|
if (items.length === 0) return null;
|
|
1385
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.RowGroup, { title: g, children: items.map((c) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1486
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.RowGroup, { title: g, children: items.map((c) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1487
|
+
Option,
|
|
1488
|
+
{
|
|
1489
|
+
p: { type: "catalog", id: c.id },
|
|
1490
|
+
icon: c.icon,
|
|
1491
|
+
label: c.label,
|
|
1492
|
+
dimmed: uses.has(c.id),
|
|
1493
|
+
...uses.has(c.id) ? { end: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.Badge, { children: (uses.get(c.id) ?? 0) > 1 ? `used \xD7${uses.get(c.id)}` : "used" }) } : c.status === "proposed" ? { end: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.Badge, { children: "new" }) } : {}
|
|
1494
|
+
},
|
|
1495
|
+
c.id
|
|
1496
|
+
)) }, g);
|
|
1386
1497
|
})
|
|
1387
1498
|
] })
|
|
1388
1499
|
] });
|
|
@@ -1414,7 +1525,7 @@ function Rows(props) {
|
|
|
1414
1525
|
const container = isContainer(node);
|
|
1415
1526
|
const isOver = over?.id === node.id;
|
|
1416
1527
|
const label = node.kind === "divider" ? "\u2500\u2500\u2500\u2500 divider \u2500\u2500\u2500\u2500" : node.label || (node.kind === "section" ? "(unlabelled section)" : "(no label)");
|
|
1417
|
-
const icon = node.kind === "item" || node.kind === "submenu" ? node.icon : node.kind === "section" ? "Rows3" : "Minus";
|
|
1528
|
+
const icon = node.kind === "item" || node.kind === "submenu" ? node.icon : node.kind === "slot" ? node.icon ?? slotSource(node.fills)?.icon ?? "Ellipsis" : node.kind === "section" ? "Rows3" : "Minus";
|
|
1418
1529
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
1419
1530
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
1420
1531
|
"div",
|
|
@@ -1440,6 +1551,7 @@ function Rows(props) {
|
|
|
1440
1551
|
icon && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-muted-foreground", children: renderLucide(icon, "size-4") }),
|
|
1441
1552
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: `min-w-0 flex-1 truncate ${node.kind === "divider" ? "text-xs text-muted-foreground" : ""}`, children: label }),
|
|
1442
1553
|
node.kind === "submenu" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[10px] text-muted-foreground", children: "submenu" }),
|
|
1554
|
+
node.kind === "slot" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.Badge, { children: node.shape === "submenu" ? "placeholder submenu" : "placeholder" }),
|
|
1443
1555
|
node.kind === "section" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-[10px] text-muted-foreground", children: node.layout && node.layout !== "list" ? `${node.layout} section` : "section" }),
|
|
1444
1556
|
node.kind === "item" && node.shortcut && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("kbd", { className: "text-[10px] text-muted-foreground", children: node.shortcut }),
|
|
1445
1557
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "hidden items-center gap-0.5 group-hover:flex", children: [
|
|
@@ -1542,6 +1654,7 @@ function Stage({ design: design2, selected, onSelect }) {
|
|
|
1542
1654
|
onClick: (e) => {
|
|
1543
1655
|
if (e.target === e.currentTarget) setAt(null);
|
|
1544
1656
|
},
|
|
1657
|
+
"data-menu-bounds": "",
|
|
1545
1658
|
className: "relative min-h-[300px] overflow-visible rounded-lg bg-muted/60 p-4",
|
|
1546
1659
|
children: [
|
|
1547
1660
|
at ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -1592,9 +1705,10 @@ var FLAGS = [
|
|
|
1592
1705
|
];
|
|
1593
1706
|
function Inspector({ node, onChange }) {
|
|
1594
1707
|
const catalog = node.catalogId ? catalogOption(node.catalogId) : void 0;
|
|
1595
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Panel, { title: `Selected ${node.kind}`, right: catalog ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.Badge, { children: catalog.id }) : void 0, children: [
|
|
1708
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(Panel, { title: `Selected ${node.kind === "slot" ? "placeholder" : node.kind}`, right: catalog ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.Badge, { children: catalog.id }) : void 0, children: [
|
|
1596
1709
|
node.kind === "divider" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "type-secondary", children: "A divider has no settings." }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
1597
1710
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Labelled, { label: "Label", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.Field, { "aria-label": "Label", value: node.label, onChange: (e) => onChange({ label: e.target.value }) }) }),
|
|
1711
|
+
node.kind === "slot" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SlotSettings, { node, onChange }),
|
|
1598
1712
|
(node.kind === "item" || node.kind === "submenu") && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
1599
1713
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Labelled, { label: "Icon", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(IconPicker, { value: node.icon ?? "", onChange: (icon) => onChange({ icon }) }) }),
|
|
1600
1714
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Labelled, { label: "Hint", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.Field, { "aria-label": "Hint", value: node.hint ?? "", onChange: (e) => onChange({ hint: e.target.value }) }) })
|
|
@@ -1611,6 +1725,37 @@ function Inspector({ node, onChange }) {
|
|
|
1611
1725
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Labelled, { label: "Note for the build", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.Textarea, { "aria-label": "Note for the build", rows: 2, autoGrow: true, value: node.note ?? "", onChange: (e) => onChange({ note: e.target.value }) }) })
|
|
1612
1726
|
] });
|
|
1613
1727
|
}
|
|
1728
|
+
var SLOT_SHAPES = [
|
|
1729
|
+
{ value: "items", label: "Rows here" },
|
|
1730
|
+
{ value: "submenu", label: "One submenu" }
|
|
1731
|
+
];
|
|
1732
|
+
var SLOT_OPTIONS = SLOT_SOURCES.map((src) => ({ value: src.id, label: src.label }));
|
|
1733
|
+
function SlotSettings({ node, onChange }) {
|
|
1734
|
+
const source = slotSource(node.fills);
|
|
1735
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
1736
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Labelled, { label: "Holds", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1737
|
+
import_controls.Select,
|
|
1738
|
+
{
|
|
1739
|
+
"aria-label": "What this placeholder holds",
|
|
1740
|
+
value: node.fills,
|
|
1741
|
+
options: SLOT_OPTIONS,
|
|
1742
|
+
onValueChange: (fills) => {
|
|
1743
|
+
const relabel = !node.label || node.label === source?.phrase || node.label === "Everything else" || node.label === "More";
|
|
1744
|
+
onChange({ fills, ...relabel ? { label: slotSource(fills)?.phrase ?? node.label } : {} });
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
) }),
|
|
1748
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.SettingRow, { label: "Shows as", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_controls.SegmentedControl, { "aria-label": "Shows as", value: node.shape, data: SLOT_SHAPES, onValueChange: (shape) => onChange({ shape }) }) }),
|
|
1749
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SlotContents, { fills: node.fills })
|
|
1750
|
+
] });
|
|
1751
|
+
}
|
|
1752
|
+
function SlotContents({ fills }) {
|
|
1753
|
+
const source = slotSource(fills);
|
|
1754
|
+
const design2 = (0, import_react2.useContext)(DesignContext);
|
|
1755
|
+
const left = (0, import_react2.useMemo)(() => design2 ? slotOptions(fills, placedCatalogIds(design2.nodes)) : [], [design2, fills]);
|
|
1756
|
+
if (source?.runtime) return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "type-secondary mt-2", children: "Filled by the live menu at runtime." });
|
|
1757
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "type-secondary mt-2", children: left.length === 0 ? "Nothing left \u2014 every option of this kind is already placed." : `Holds ${left.length}: ${left.slice(0, 6).map((o2) => o2.label).join(", ")}${left.length > 6 ? "\u2026" : ""}` });
|
|
1758
|
+
}
|
|
1614
1759
|
function Labelled({ label, children }) {
|
|
1615
1760
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mt-2 grid gap-1", children: [
|
|
1616
1761
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "type-meta", children: label }),
|