@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.
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { M as MenuDesign } from './model-BdF0TqZ3.cjs';
3
+ import { M as MenuDesign } from './model-CkLCR6WY.cjs';
4
4
  import { DesignStore } from './design-store.cjs';
5
5
  import '@ai-matrx/records/typed-table';
6
6
  import '@ai-matrx/records/core';
package/dist/builder.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import * as React from 'react';
2
2
  import { ReactNode } from 'react';
3
- import { M as MenuDesign } from './model-BdF0TqZ3.js';
3
+ import { M as MenuDesign } from './model-CkLCR6WY.js';
4
4
  import { DesignStore } from './design-store.js';
5
5
  import '@ai-matrx/records/typed-table';
6
6
  import '@ai-matrx/records/core';
package/dist/builder.js CHANGED
@@ -1,7 +1,7 @@
1
1
  "use client";
2
2
 
3
3
  // src/builder/MenuBuilder.tsx
4
- import { useCallback, useEffect, useMemo as useMemo2, useRef, useState as useState2 } from "react";
4
+ import { createContext, useCallback, useContext, useEffect, useMemo as useMemo2, useRef, useState as useState2 } from "react";
5
5
 
6
6
  // src/design/catalog.ts
7
7
  var o = (id, label, icon, group, extra = {}) => ({ id, label, icon, group, status: "live", ...extra });
@@ -143,6 +143,25 @@ var TEMPLATES = [
143
143
  { id: "sub-organize", label: "Organize \u25B8", icon: "Tags", build: () => sub("Organize", "Tags", ["cm:attach", "pin-message", "cm:view-history", "cm:compare"]) },
144
144
  { id: "sub-tools", label: "Tools \u25B8", icon: "Wrench", build: () => sub("Tools", "Wrench", ["cm:speak", "cm:quick-actions", "feedback", "cm:x:surface", "cm:admin"]) }
145
145
  ];
146
+ var SLOT_SOURCES = [
147
+ { id: "any", label: "Everything not placed", phrase: "Everything else", icon: "Ellipsis" },
148
+ { id: "agents", label: "Agents & shortcuts (library)", phrase: "All other agents", icon: "Bot", runtime: true },
149
+ { id: "page-rows", label: "This page's own rows", phrase: "All of this page's actions", icon: "PanelTop", runtime: true },
150
+ ...CATALOG_GROUPS.map((g) => ({
151
+ id: `group:${g}`,
152
+ label: g,
153
+ phrase: g === "AI" ? "All other AI items" : `All other ${g.toLowerCase()} options`,
154
+ icon: CATALOG.find((c) => c.group === g)?.icon ?? "Circle"
155
+ }))
156
+ ];
157
+ function slotSource(id) {
158
+ return SLOT_SOURCES.find((s) => s.id === id);
159
+ }
160
+ function slotOptions(fills, placed) {
161
+ if (fills === "agents" || fills === "page-rows") return [];
162
+ const pool = fills === "any" ? CATALOG : CATALOG.filter((c) => `group:${c.group}` === fills);
163
+ return pool.filter((c) => !placed.has(c.id));
164
+ }
146
165
 
147
166
  // src/design/MenuPreview.tsx
148
167
  import { useMemo, useState } from "react";
@@ -176,6 +195,20 @@ function emptyDesign(name = "Untitled menu") {
176
195
  function isContainer(node) {
177
196
  return node.kind === "section" || node.kind === "submenu";
178
197
  }
198
+ function placedCatalogIds(nodes, out = /* @__PURE__ */ new Set()) {
199
+ for (const n of nodes) {
200
+ if (n.catalogId) out.add(n.catalogId);
201
+ if (isContainer(n)) placedCatalogIds(n.children, out);
202
+ }
203
+ return out;
204
+ }
205
+ function catalogUseCounts(nodes, out = /* @__PURE__ */ new Map()) {
206
+ for (const n of nodes) {
207
+ if (n.catalogId) out.set(n.catalogId, (out.get(n.catalogId) ?? 0) + 1);
208
+ if (isContainer(n)) catalogUseCounts(n.children, out);
209
+ }
210
+ return out;
211
+ }
179
212
  function cloneNode(node) {
180
213
  const copy = { ...node, id: newId(node.kind[0]) };
181
214
  if (isContainer(copy)) copy.children = copy.children.map(cloneNode);
@@ -199,7 +232,7 @@ function contains(node, id) {
199
232
  function countItems(nodes) {
200
233
  let n = 0;
201
234
  for (const node of nodes) {
202
- if (node.kind === "item") n += 1;
235
+ if (node.kind === "item" || node.kind === "slot") n += 1;
203
236
  else if (isContainer(node)) n += countItems(node.children) + (node.kind === "submenu" ? 1 : 0);
204
237
  }
205
238
  return n;
@@ -289,7 +322,7 @@ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
289
322
  function matches(node, q) {
290
323
  if (!q) return true;
291
324
  if (node.kind === "divider") return false;
292
- if (node.kind === "item") return node.label.toLowerCase().includes(q);
325
+ if (node.kind === "item" || node.kind === "slot") return node.label.toLowerCase().includes(q);
293
326
  return node.label.toLowerCase().includes(q) || node.children.some((c) => matches(c, q));
294
327
  }
295
328
  function flatten(nodes, q, out = []) {
@@ -304,6 +337,8 @@ function MenuPreview({ design: design2, renderIcon, highlightId, onSelectNode, o
304
337
  const q = query.trim().toLowerCase();
305
338
  const { look } = design2;
306
339
  const results = useMemo(() => q ? flatten(design2.nodes, q) : null, [design2.nodes, q]);
340
+ const placed = useMemo(() => placedCatalogIds(design2.nodes), [design2.nodes]);
341
+ const ctx = { design: design2, renderIcon, highlightId, onSelectNode, onPick, placed };
307
342
  return /* @__PURE__ */ jsxs(
308
343
  "div",
309
344
  {
@@ -316,7 +351,7 @@ function MenuPreview({ design: design2, renderIcon, highlightId, onSelectNode, o
316
351
  renderIcon("Search", "cm-icon"),
317
352
  /* @__PURE__ */ jsx("input", { value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Type to filter\u2026", "aria-label": "Filter menu" })
318
353
  ] }),
319
- results ? results.length === 0 ? /* @__PURE__ */ jsx("div", { className: "cm-empty", children: "No matches" }) : results.map((n) => /* @__PURE__ */ jsx(Item, { node: n, ctx: { design: design2, renderIcon, highlightId, onSelectNode, onPick } }, n.id)) : design2.nodes.length === 0 ? /* @__PURE__ */ jsx("div", { className: "cm-empty", children: "Empty menu \u2014 drag options in" }) : /* @__PURE__ */ jsx(Nodes, { nodes: design2.nodes, ctx: { design: design2, renderIcon, highlightId, onSelectNode, onPick } })
354
+ results ? results.length === 0 ? /* @__PURE__ */ jsx("div", { className: "cm-empty", children: "No matches" }) : results.map((n) => /* @__PURE__ */ jsx(Item, { node: n, ctx }, n.id)) : design2.nodes.length === 0 ? /* @__PURE__ */ jsx("div", { className: "cm-empty", children: "Empty menu \u2014 drag options in" }) : /* @__PURE__ */ jsx(Nodes, { nodes: design2.nodes, ctx })
320
355
  ]
321
356
  }
322
357
  );
@@ -326,6 +361,7 @@ function Nodes({ nodes, ctx }) {
326
361
  if (n.kind === "divider") return /* @__PURE__ */ jsx("div", { role: "separator", className: "cm-divider" }, n.id);
327
362
  if (n.kind === "item") return /* @__PURE__ */ jsx(Item, { node: n, ctx }, n.id);
328
363
  if (n.kind === "submenu") return /* @__PURE__ */ jsx(Submenu, { node: n, ctx }, n.id);
364
+ if (n.kind === "slot") return /* @__PURE__ */ jsx(Slot, { node: n, ctx }, n.id);
329
365
  return /* @__PURE__ */ jsx(Section, { node: n, ctx }, n.id);
330
366
  }) });
331
367
  }
@@ -409,8 +445,7 @@ function Submenu({ node, ctx }) {
409
445
  const [open, setOpen] = useState(false);
410
446
  const [side, setSide] = useState("right");
411
447
  const show = (el) => {
412
- const r = el.getBoundingClientRect();
413
- setSide(r.right + look.width + 8 > window.innerWidth ? "left" : "right");
448
+ setSide(flyoutSide(el, look.width));
414
449
  setOpen(true);
415
450
  };
416
451
  return /* @__PURE__ */ jsxs("div", { className: "cm-sub", onMouseEnter: (e) => show(e.currentTarget), onMouseLeave: () => setOpen(false), children: [
@@ -439,6 +474,63 @@ function Submenu({ node, ctx }) {
439
474
  open && /* @__PURE__ */ jsx("div", { role: "menu", className: "cm-menu cm-flyout", "data-side": side, "data-density": look.density, style: { width: look.width }, children: node.children.length === 0 ? /* @__PURE__ */ jsx("div", { className: "cm-empty", children: "Empty submenu" }) : /* @__PURE__ */ jsx(Nodes, { nodes: node.children, ctx }) })
440
475
  ] });
441
476
  }
477
+ function flyoutSide(row, width) {
478
+ const r = row.getBoundingClientRect();
479
+ const bounds = row.closest("[data-menu-bounds]")?.getBoundingClientRect() ?? { left: 0, right: window.innerWidth };
480
+ if (r.right + width + 8 <= bounds.right) return "right";
481
+ if (r.left - width - 8 >= bounds.left) return "left";
482
+ return "below";
483
+ }
484
+ function Slot({ node, ctx }) {
485
+ const { look } = ctx.design;
486
+ const source = slotSource(node.fills);
487
+ const options = slotOptions(node.fills, ctx.placed);
488
+ const [open, setOpen] = useState(false);
489
+ const [side, setSide] = useState("right");
490
+ const count = source?.runtime ? "live" : String(options.length);
491
+ const row = (
492
+ // ui-exception: a menu row of the menu component itself
493
+ /* @__PURE__ */ jsxs(
494
+ "button",
495
+ {
496
+ type: "button",
497
+ role: "menuitem",
498
+ "aria-haspopup": node.shape === "submenu" ? "menu" : void 0,
499
+ title: source ? `Placeholder: ${source.label}` : "Placeholder",
500
+ className: `cm-item cm-slot ${ctx.highlightId === node.id ? "cm-hl" : ""}`,
501
+ onClick: () => {
502
+ ctx.onSelectNode?.(node.id);
503
+ if (node.shape === "submenu") setOpen(true);
504
+ },
505
+ children: [
506
+ look.showIcons && /* @__PURE__ */ jsx("span", { className: "cm-icon-slot", children: ctx.renderIcon(node.icon ?? source?.icon ?? "Ellipsis", "cm-icon") }),
507
+ /* @__PURE__ */ jsx("span", { className: "cm-text", children: /* @__PURE__ */ jsx("span", { className: "cm-label", children: node.label }) }),
508
+ /* @__PURE__ */ jsx("span", { className: "cm-count", children: count }),
509
+ node.shape === "submenu" && ctx.renderIcon("ChevronRight", "cm-icon cm-chev")
510
+ ]
511
+ }
512
+ )
513
+ );
514
+ if (node.shape === "items") return row;
515
+ return /* @__PURE__ */ jsxs(
516
+ "div",
517
+ {
518
+ className: "cm-sub",
519
+ onMouseEnter: (e) => {
520
+ setSide(flyoutSide(e.currentTarget, look.width));
521
+ setOpen(true);
522
+ },
523
+ onMouseLeave: () => setOpen(false),
524
+ children: [
525
+ row,
526
+ open && /* @__PURE__ */ jsx("div", { role: "menu", className: "cm-menu cm-flyout", "data-side": side, "data-density": look.density, style: { width: look.width }, children: source?.runtime ? /* @__PURE__ */ jsxs("div", { className: "cm-runtime", children: [
527
+ "Filled at runtime: ",
528
+ source.label
529
+ ] }) : options.length === 0 ? /* @__PURE__ */ jsx("div", { className: "cm-empty", children: "Nothing left \u2014 every option of this kind is placed" }) : options.map((o2) => /* @__PURE__ */ jsx(Item, { node: { id: `slot-${node.id}-${o2.id}`, kind: "item", label: o2.label, icon: o2.icon, catalogId: o2.id, ...o2.shortcut ? { shortcut: o2.shortcut } : {}, ...o2.ai ? { ai: true } : {}, ...o2.danger ? { danger: true } : {} }, ctx }, o2.id)) })
530
+ ]
531
+ }
532
+ );
533
+ }
442
534
 
443
535
  // src/design/starters.ts
444
536
  function maker(slug) {
@@ -965,6 +1057,7 @@ var SURFACE_LABELS = {
965
1057
  anywhere: "Opens anywhere"
966
1058
  };
967
1059
  var SURFACE_OPTIONS = MENU_SURFACES.map((value) => ({ value, label: SURFACE_LABELS[value] }));
1060
+ var DesignContext = createContext(null);
968
1061
  var DRAFT_KEY = "matrx.context-menu.builder.draft.v1";
969
1062
  function usePointerDrag(onDrop) {
970
1063
  const [ghost, setGhost] = useState2(null);
@@ -1040,6 +1133,8 @@ function nodeFromPayload(p) {
1040
1133
  if (p.kind === "section") return { id: newId("s"), kind: "section", label: "New section", children: [] };
1041
1134
  if (p.kind === "submenu") return { id: newId("m"), kind: "submenu", label: "More", icon: "Ellipsis", children: [] };
1042
1135
  if (p.kind === "divider") return { id: newId("d"), kind: "divider" };
1136
+ if (p.kind === "slot-items") return { id: newId("p"), kind: "slot", shape: "items", fills: "any", label: "Everything else" };
1137
+ if (p.kind === "slot-menu") return { id: newId("p"), kind: "slot", shape: "submenu", fills: "any", label: "More", icon: "Ellipsis" };
1043
1138
  return { id: newId("i"), kind: "item", label: "New option", icon: "Circle" };
1044
1139
  }
1045
1140
  return null;
@@ -1193,7 +1288,7 @@ function MenuBuilder({ store, storeLabel, initialDesign, accountSlot }) {
1193
1288
  for (const v of versions ?? []) byId.set(v.designId, [...byId.get(v.designId) ?? [], v]);
1194
1289
  return [...byId.values()];
1195
1290
  }, [versions]);
1196
- return /* @__PURE__ */ jsxs2("div", { className: "flex h-dvh flex-col bg-background text-foreground", children: [
1291
+ return /* @__PURE__ */ jsx3(DesignContext.Provider, { value: design2, children: /* @__PURE__ */ jsxs2("div", { className: "flex h-dvh flex-col bg-background text-foreground", children: [
1197
1292
  /* @__PURE__ */ jsxs2("header", { className: "flex flex-wrap items-center gap-y-1 border-b border-border px-3 py-2", children: [
1198
1293
  /* @__PURE__ */ jsxs2(ControlRow, { className: "min-w-0 flex-1", children: [
1199
1294
  /* @__PURE__ */ jsx3(Field, { "aria-label": "Design name", width: "lg", value: design2.name, onChange: (e) => set((d) => ({ ...d, name: e.target.value })) }),
@@ -1316,7 +1411,7 @@ function MenuBuilder({ store, storeLabel, initialDesign, accountSlot }) {
1316
1411
  children: dnd.ghost.label
1317
1412
  }
1318
1413
  )
1319
- ] });
1414
+ ] }) });
1320
1415
  }
1321
1416
  function Panel({ title, children, right }) {
1322
1417
  return /* @__PURE__ */ jsxs2("section", { className: "border-b border-border p-3", children: [
@@ -1335,7 +1430,9 @@ function Palette({ onAdd, startDrag, click }) {
1335
1430
  const n = nodeFromPayload(p);
1336
1431
  if (n) onAdd(n);
1337
1432
  };
1338
- const Option = ({ p, icon, label, end }) => /* @__PURE__ */ jsx3(
1433
+ const design2 = useContext(DesignContext);
1434
+ const uses = useMemo2(() => catalogUseCounts(design2?.nodes ?? []), [design2?.nodes]);
1435
+ const Option = ({ p, icon, label, end, dimmed }) => /* @__PURE__ */ jsx3(
1339
1436
  Tile2,
1340
1437
  {
1341
1438
  layout: "row",
@@ -1343,6 +1440,7 @@ function Palette({ onAdd, startDrag, click }) {
1343
1440
  icon: renderLucide(icon),
1344
1441
  title: label,
1345
1442
  end,
1443
+ dimmed: dimmed === true,
1346
1444
  onPointerDown: (e) => startDrag(e, p, label),
1347
1445
  onClick: click(() => add(p))
1348
1446
  }
@@ -1355,14 +1453,27 @@ function Palette({ onAdd, startDrag, click }) {
1355
1453
  /* @__PURE__ */ jsx3(Option, { p: { type: "new", kind: "section" }, icon: "Rows3", label: "Section" }),
1356
1454
  /* @__PURE__ */ jsx3(Option, { p: { type: "new", kind: "submenu" }, icon: "ChevronsRight", label: "Submenu" }),
1357
1455
  /* @__PURE__ */ jsx3(Option, { p: { type: "new", kind: "divider" }, icon: "Minus", label: "Divider" }),
1358
- /* @__PURE__ */ jsx3(Option, { p: { type: "new", kind: "item" }, icon: "Plus", label: "Custom option" })
1456
+ /* @__PURE__ */ jsx3(Option, { p: { type: "new", kind: "item" }, icon: "Plus", label: "Custom option" }),
1457
+ /* @__PURE__ */ jsx3(Option, { p: { type: "new", kind: "slot-items" }, icon: "ListPlus", label: "Placeholder \u2014 items" }),
1458
+ /* @__PURE__ */ jsx3(Option, { p: { type: "new", kind: "slot-menu" }, icon: "FolderPlus", label: "Placeholder \u2014 submenu" })
1359
1459
  ] }),
1360
1460
  /* @__PURE__ */ jsx3(RowGroup, { title: "Ready-made", children: TEMPLATES.map((t) => /* @__PURE__ */ jsx3(Option, { p: { type: "template", id: t.id }, icon: t.icon, label: t.label }, t.id)) })
1361
1461
  ] }),
1362
1462
  CATALOG_GROUPS.map((g) => {
1363
- const items = CATALOG.filter((c) => c.group === g && shown(c));
1463
+ const matching = CATALOG.filter((c) => c.group === g && shown(c));
1464
+ const items = [...matching.filter((c) => !uses.has(c.id)), ...matching.filter((c) => uses.has(c.id))];
1364
1465
  if (items.length === 0) return null;
1365
- return /* @__PURE__ */ jsx3(RowGroup, { title: g, children: items.map((c) => /* @__PURE__ */ jsx3(Option, { p: { type: "catalog", id: c.id }, icon: c.icon, label: c.label, ...c.status === "proposed" ? { end: /* @__PURE__ */ jsx3(Badge, { children: "new" }) } : {} }, c.id)) }, g);
1466
+ return /* @__PURE__ */ jsx3(RowGroup, { title: g, children: items.map((c) => /* @__PURE__ */ jsx3(
1467
+ Option,
1468
+ {
1469
+ p: { type: "catalog", id: c.id },
1470
+ icon: c.icon,
1471
+ label: c.label,
1472
+ dimmed: uses.has(c.id),
1473
+ ...uses.has(c.id) ? { end: /* @__PURE__ */ jsx3(Badge, { children: (uses.get(c.id) ?? 0) > 1 ? `used \xD7${uses.get(c.id)}` : "used" }) } : c.status === "proposed" ? { end: /* @__PURE__ */ jsx3(Badge, { children: "new" }) } : {}
1474
+ },
1475
+ c.id
1476
+ )) }, g);
1366
1477
  })
1367
1478
  ] })
1368
1479
  ] });
@@ -1394,7 +1505,7 @@ function Rows(props) {
1394
1505
  const container = isContainer(node);
1395
1506
  const isOver = over?.id === node.id;
1396
1507
  const label = node.kind === "divider" ? "\u2500\u2500\u2500\u2500 divider \u2500\u2500\u2500\u2500" : node.label || (node.kind === "section" ? "(unlabelled section)" : "(no label)");
1397
- const icon = node.kind === "item" || node.kind === "submenu" ? node.icon : node.kind === "section" ? "Rows3" : "Minus";
1508
+ 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";
1398
1509
  return /* @__PURE__ */ jsxs2("div", { children: [
1399
1510
  /* @__PURE__ */ jsxs2(
1400
1511
  "div",
@@ -1420,6 +1531,7 @@ function Rows(props) {
1420
1531
  icon && /* @__PURE__ */ jsx3("span", { className: "text-muted-foreground", children: renderLucide(icon, "size-4") }),
1421
1532
  /* @__PURE__ */ jsx3("span", { className: `min-w-0 flex-1 truncate ${node.kind === "divider" ? "text-xs text-muted-foreground" : ""}`, children: label }),
1422
1533
  node.kind === "submenu" && /* @__PURE__ */ jsx3("span", { className: "text-[10px] text-muted-foreground", children: "submenu" }),
1534
+ node.kind === "slot" && /* @__PURE__ */ jsx3(Badge, { children: node.shape === "submenu" ? "placeholder submenu" : "placeholder" }),
1423
1535
  node.kind === "section" && /* @__PURE__ */ jsx3("span", { className: "text-[10px] text-muted-foreground", children: node.layout && node.layout !== "list" ? `${node.layout} section` : "section" }),
1424
1536
  node.kind === "item" && node.shortcut && /* @__PURE__ */ jsx3("kbd", { className: "text-[10px] text-muted-foreground", children: node.shortcut }),
1425
1537
  /* @__PURE__ */ jsxs2("span", { className: "hidden items-center gap-0.5 group-hover:flex", children: [
@@ -1522,6 +1634,7 @@ function Stage({ design: design2, selected, onSelect }) {
1522
1634
  onClick: (e) => {
1523
1635
  if (e.target === e.currentTarget) setAt(null);
1524
1636
  },
1637
+ "data-menu-bounds": "",
1525
1638
  className: "relative min-h-[300px] overflow-visible rounded-lg bg-muted/60 p-4",
1526
1639
  children: [
1527
1640
  at ? /* @__PURE__ */ jsx3(
@@ -1572,9 +1685,10 @@ var FLAGS = [
1572
1685
  ];
1573
1686
  function Inspector({ node, onChange }) {
1574
1687
  const catalog = node.catalogId ? catalogOption(node.catalogId) : void 0;
1575
- return /* @__PURE__ */ jsxs2(Panel, { title: `Selected ${node.kind}`, right: catalog ? /* @__PURE__ */ jsx3(Badge, { children: catalog.id }) : void 0, children: [
1688
+ return /* @__PURE__ */ jsxs2(Panel, { title: `Selected ${node.kind === "slot" ? "placeholder" : node.kind}`, right: catalog ? /* @__PURE__ */ jsx3(Badge, { children: catalog.id }) : void 0, children: [
1576
1689
  node.kind === "divider" ? /* @__PURE__ */ jsx3("p", { className: "type-secondary", children: "A divider has no settings." }) : /* @__PURE__ */ jsxs2(Fragment2, { children: [
1577
1690
  /* @__PURE__ */ jsx3(Labelled, { label: "Label", children: /* @__PURE__ */ jsx3(Field, { "aria-label": "Label", value: node.label, onChange: (e) => onChange({ label: e.target.value }) }) }),
1691
+ node.kind === "slot" && /* @__PURE__ */ jsx3(SlotSettings, { node, onChange }),
1578
1692
  (node.kind === "item" || node.kind === "submenu") && /* @__PURE__ */ jsxs2(Fragment2, { children: [
1579
1693
  /* @__PURE__ */ jsx3(Labelled, { label: "Icon", children: /* @__PURE__ */ jsx3(IconPicker, { value: node.icon ?? "", onChange: (icon) => onChange({ icon }) }) }),
1580
1694
  /* @__PURE__ */ jsx3(Labelled, { label: "Hint", children: /* @__PURE__ */ jsx3(Field, { "aria-label": "Hint", value: node.hint ?? "", onChange: (e) => onChange({ hint: e.target.value }) }) })
@@ -1591,6 +1705,37 @@ function Inspector({ node, onChange }) {
1591
1705
  /* @__PURE__ */ jsx3(Labelled, { label: "Note for the build", children: /* @__PURE__ */ jsx3(Textarea, { "aria-label": "Note for the build", rows: 2, autoGrow: true, value: node.note ?? "", onChange: (e) => onChange({ note: e.target.value }) }) })
1592
1706
  ] });
1593
1707
  }
1708
+ var SLOT_SHAPES = [
1709
+ { value: "items", label: "Rows here" },
1710
+ { value: "submenu", label: "One submenu" }
1711
+ ];
1712
+ var SLOT_OPTIONS = SLOT_SOURCES.map((src) => ({ value: src.id, label: src.label }));
1713
+ function SlotSettings({ node, onChange }) {
1714
+ const source = slotSource(node.fills);
1715
+ return /* @__PURE__ */ jsxs2(Fragment2, { children: [
1716
+ /* @__PURE__ */ jsx3(Labelled, { label: "Holds", children: /* @__PURE__ */ jsx3(
1717
+ Select,
1718
+ {
1719
+ "aria-label": "What this placeholder holds",
1720
+ value: node.fills,
1721
+ options: SLOT_OPTIONS,
1722
+ onValueChange: (fills) => {
1723
+ const relabel = !node.label || node.label === source?.phrase || node.label === "Everything else" || node.label === "More";
1724
+ onChange({ fills, ...relabel ? { label: slotSource(fills)?.phrase ?? node.label } : {} });
1725
+ }
1726
+ }
1727
+ ) }),
1728
+ /* @__PURE__ */ jsx3(SettingRow, { label: "Shows as", children: /* @__PURE__ */ jsx3(SegmentedControl, { "aria-label": "Shows as", value: node.shape, data: SLOT_SHAPES, onValueChange: (shape) => onChange({ shape }) }) }),
1729
+ /* @__PURE__ */ jsx3(SlotContents, { fills: node.fills })
1730
+ ] });
1731
+ }
1732
+ function SlotContents({ fills }) {
1733
+ const source = slotSource(fills);
1734
+ const design2 = useContext(DesignContext);
1735
+ const left = useMemo2(() => design2 ? slotOptions(fills, placedCatalogIds(design2.nodes)) : [], [design2, fills]);
1736
+ if (source?.runtime) return /* @__PURE__ */ jsx3("p", { className: "type-secondary mt-2", children: "Filled by the live menu at runtime." });
1737
+ return /* @__PURE__ */ jsx3("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" : ""}` });
1738
+ }
1594
1739
  function Labelled({ label, children }) {
1595
1740
  return /* @__PURE__ */ jsxs2("div", { className: "mt-2 grid gap-1", children: [
1596
1741
  /* @__PURE__ */ jsx3("span", { className: "type-meta", children: label }),