@ai-matrx/context-menu 0.1.3 → 0.1.5

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/dist/index.cjs CHANGED
@@ -26,9 +26,11 @@ __export(src_exports, {
26
26
  DEFAULT_LOOK: () => DEFAULT_LOOK,
27
27
  DESIGN_FORMAT: () => DESIGN_FORMAT,
28
28
  MENU_SURFACES: () => MENU_SURFACES,
29
+ MENU_TONES: () => MENU_TONES,
29
30
  MenuPreview: () => MenuPreview,
30
31
  SLOT_SOURCES: () => SLOT_SOURCES,
31
32
  TEMPLATES: () => TEMPLATES,
33
+ UNAVAILABLE_MODES: () => UNAVAILABLE_MODES,
32
34
  canMove: () => canMove,
33
35
  catalogOption: () => catalogOption,
34
36
  catalogUseCounts: () => catalogUseCounts,
@@ -55,6 +57,28 @@ __export(src_exports, {
55
57
  module.exports = __toCommonJS(src_exports);
56
58
 
57
59
  // src/design/model.ts
60
+ var MENU_TONES = [
61
+ "default",
62
+ "primary",
63
+ "success",
64
+ "warning",
65
+ "info",
66
+ "destructive",
67
+ "sky",
68
+ "blue",
69
+ "indigo",
70
+ "violet",
71
+ "fuchsia",
72
+ "rose",
73
+ "orange",
74
+ "amber",
75
+ "lime",
76
+ "emerald",
77
+ "teal",
78
+ "cyan",
79
+ "slate"
80
+ ];
81
+ var UNAVAILABLE_MODES = ["disable", "hide", "required"];
58
82
  var MENU_SURFACES = [
59
83
  "text-selection",
60
84
  "editable-field",
@@ -368,52 +392,90 @@ function slotOptions(fills, placed) {
368
392
  // src/design/MenuPreview.tsx
369
393
  var import_react = require("react");
370
394
  var import_jsx_runtime = require("react/jsx-runtime");
371
- function matches(node, q) {
372
- if (!q) return true;
373
- if (node.kind === "divider") return false;
374
- if (node.kind === "item" || node.kind === "slot") return node.label.toLowerCase().includes(q);
375
- return node.label.toLowerCase().includes(q) || node.children.some((c) => matches(c, q));
395
+ function searchable(node) {
396
+ if (node.kind === "divider" || node.kind === "search") return "";
397
+ const hint = node.kind === "item" || node.kind === "submenu" ? node.hint ?? "" : "";
398
+ return [node.label, hint, ...node.keywords ?? []].join(" ").toLowerCase();
376
399
  }
377
- function flatten(nodes, q, out = []) {
400
+ function searchMenu(nodes, q, trail = [], out = []) {
401
+ const words = q.split(/\s+/).filter(Boolean);
378
402
  for (const n of nodes) {
379
- if (n.kind === "item" && matches(n, q)) out.push(n);
380
- else if (isContainer(n)) flatten(n.children, q, out);
403
+ if (n.kind === "item") {
404
+ const text = `${searchable(n)} ${trail.join(" ").toLowerCase()}`;
405
+ if (words.every((w) => text.includes(w))) out.push({ node: n, path: trail.join(" \u203A ") });
406
+ } else if (isContainer(n)) {
407
+ searchMenu(n.children, q, n.kind === "submenu" ? [...trail, n.label] : n.label ? [...trail, n.label] : trail, out);
408
+ }
381
409
  }
382
410
  return out;
383
411
  }
384
- function MenuPreview({ design, renderIcon, highlightId, onSelectNode, onPick, className, style }) {
412
+ function MenuPreview({ design, renderIcon, highlightId, onSelectNode, onPick, availability = "available", className, style }) {
385
413
  const [query, setQuery] = (0, import_react.useState)("");
386
414
  const q = query.trim().toLowerCase();
387
415
  const { look } = design;
388
- const results = (0, import_react.useMemo)(() => q ? flatten(design.nodes, q) : null, [design.nodes, q]);
389
416
  const placed = (0, import_react.useMemo)(() => placedCatalogIds(design.nodes), [design.nodes]);
390
- const ctx = { design, renderIcon, highlightId, onSelectNode, onPick, placed };
391
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
392
- "div",
393
- {
394
- role: "menu",
395
- "data-density": look.density,
396
- className: `cm-menu ${className ?? ""}`,
397
- style: { width: look.width, ...style },
398
- children: [
399
- look.search && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "cm-search", children: [
400
- renderIcon("Search", "cm-icon"),
401
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { value: query, onChange: (e) => setQuery(e.target.value), placeholder: "Type to filter\u2026", "aria-label": "Filter menu" })
402
- ] }),
403
- 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)) : design.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: design.nodes, ctx })
404
- ]
405
- }
406
- );
417
+ const ctx = { design, renderIcon, highlightId, onSelectNode, onPick, placed, availability, query, setQuery };
418
+ const searchAt = design.nodes.findIndex((n) => n.kind === "search");
419
+ const topSearch = searchAt < 0 && look.search ? { id: "look-search", kind: "search", placeholder: "Type to filter\u2026", autofocus: true } : null;
420
+ const hits = (0, import_react.useMemo)(() => q ? searchMenu(design.nodes, q) : null, [design.nodes, q]);
421
+ const above = searchAt >= 0 ? design.nodes.slice(0, searchAt) : [];
422
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { role: "menu", "data-density": look.density, className: `cm-menu ${className ?? ""}`, style: { width: look.width, ...style }, children: [
423
+ topSearch && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchBox, { node: topSearch, ctx }),
424
+ design.nodes.length === 0 && !topSearch ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "cm-empty", children: "Empty menu \u2014 drag options in" }) : hits ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
425
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Nodes, { nodes: above, ctx }),
426
+ searchAt >= 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchBox, { node: design.nodes[searchAt], ctx }),
427
+ hits.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "cm-empty", children: "No matches" }) : hits.map((h) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Item, { node: h.node, ctx, path: h.path }, h.node.id))
428
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Nodes, { nodes: design.nodes, ctx })
429
+ ] });
430
+ }
431
+ function SearchBox({ node, ctx }) {
432
+ const ref = (0, import_react.useRef)(null);
433
+ (0, import_react.useEffect)(() => {
434
+ if (node.autofocus) ref.current?.focus({ preventScroll: true });
435
+ }, [node.autofocus]);
436
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `cm-search ${ctx.highlightId === node.id ? "cm-hl" : ""}`, onClick: () => ctx.onSelectNode?.(node.id), children: [
437
+ ctx.renderIcon("Search", "cm-icon"),
438
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
439
+ "input",
440
+ {
441
+ ref,
442
+ value: ctx.query,
443
+ onChange: (e) => ctx.setQuery(e.target.value),
444
+ onKeyDown: (e) => {
445
+ if (e.key === "Escape" && ctx.query) {
446
+ e.stopPropagation();
447
+ ctx.setQuery("");
448
+ }
449
+ },
450
+ placeholder: node.placeholder || "Type to filter\u2026",
451
+ "aria-label": "Filter menu"
452
+ }
453
+ )
454
+ ] });
455
+ }
456
+ function shown(node, ctx) {
457
+ if (ctx.availability === "available" || !node.unavailableWhen) {
458
+ return { hidden: false, greyed: node.kind === "item" && Boolean(node.disabled) };
459
+ }
460
+ const mode = node.unavailable ?? "disable";
461
+ if (mode === "hide") return { hidden: true, greyed: false };
462
+ if (mode === "required") return { hidden: false, greyed: false };
463
+ return { hidden: false, greyed: true };
407
464
  }
408
465
  function Nodes({ nodes, ctx }) {
409
466
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: nodes.map((n) => {
467
+ if (shown(n, ctx).hidden) return null;
410
468
  if (n.kind === "divider") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { role: "separator", className: "cm-divider" }, n.id);
411
469
  if (n.kind === "item") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Item, { node: n, ctx }, n.id);
412
470
  if (n.kind === "submenu") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Submenu, { node: n, ctx }, n.id);
413
- if (n.kind === "slot") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Slot, { node: n, ctx }, n.id);
471
+ if (n.kind === "slot") return n.shape === "feature" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FeatureSlot, { node: n, ctx }, n.id) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Slot, { node: n, ctx }, n.id);
472
+ if (n.kind === "search") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SearchBox, { node: n, ctx }, n.id);
414
473
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Section, { node: n, ctx }, n.id);
415
474
  }) });
416
475
  }
476
+ function toneAttrs(tone, toneLabel) {
477
+ return tone && tone !== "default" ? { "data-tone": tone, ...toneLabel ? { "data-tone-label": "" } : {} } : {};
478
+ }
417
479
  function Section({ node, ctx }) {
418
480
  const [open, setOpen] = (0, import_react.useState)(!node.collapsible);
419
481
  const layout = node.layout ?? "list";
@@ -435,11 +497,12 @@ function Section({ node, ctx }) {
435
497
  }
436
498
  ),
437
499
  open && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "cm-section-body", children: layout === "list" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Nodes, { nodes: node.children, ctx }) : node.children.map(
438
- (c) => c.kind === "item" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tile, { node: c, ctx }, c.id) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Nodes, { nodes: [c], ctx }, c.id)
500
+ (c) => shown(c, ctx).hidden ? null : c.kind === "item" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Tile, { node: c, ctx }, c.id) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Nodes, { nodes: [c], ctx }, c.id)
439
501
  ) })
440
502
  ] });
441
503
  }
442
504
  function Tile({ node, ctx }) {
505
+ const { greyed } = shown(node, ctx);
443
506
  return (
444
507
  // ui-exception: a menu row of the menu component itself
445
508
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
@@ -448,7 +511,8 @@ function Tile({ node, ctx }) {
448
511
  type: "button",
449
512
  role: "menuitem",
450
513
  title: node.label + (node.shortcut ? ` ${node.shortcut}` : ""),
451
- disabled: node.disabled,
514
+ disabled: greyed,
515
+ ...toneAttrs(node.tone, node.toneLabel),
452
516
  className: `cm-tile ${node.danger ? "cm-danger" : ""} ${node.ai ? "cm-ai" : ""} ${ctx.highlightId === node.id ? "cm-hl" : ""}`,
453
517
  onClick: () => {
454
518
  ctx.onSelectNode?.(node.id);
@@ -459,31 +523,37 @@ function Tile({ node, ctx }) {
459
523
  )
460
524
  );
461
525
  }
462
- function Item({ node, ctx }) {
526
+ function Item({ node, ctx, path }) {
463
527
  const { look } = ctx.design;
464
- const [checked, setChecked] = (0, import_react.useState)(false);
528
+ const [checked, setChecked] = (0, import_react.useState)(Boolean(node.defaultOn));
529
+ (0, import_react.useEffect)(() => setChecked(Boolean(node.defaultOn)), [node.defaultOn]);
530
+ const { greyed } = shown(node, ctx);
531
+ const settable = node.toggle || node.switch;
532
+ const hint = path || (look.showHints ? node.hint : void 0);
465
533
  return (
466
534
  // ui-exception: a menu row of the menu component itself
467
535
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
468
536
  "button",
469
537
  {
470
538
  type: "button",
471
- role: node.toggle ? "menuitemcheckbox" : "menuitem",
472
- "aria-checked": node.toggle ? checked : void 0,
473
- disabled: node.disabled,
539
+ role: settable ? "menuitemcheckbox" : "menuitem",
540
+ "aria-checked": settable ? checked : void 0,
541
+ disabled: greyed,
542
+ ...toneAttrs(node.tone, node.toneLabel),
474
543
  className: `cm-item ${node.danger ? "cm-danger" : ""} ${node.ai ? "cm-ai" : ""} ${ctx.highlightId === node.id ? "cm-hl" : ""}`,
475
544
  onClick: () => {
476
545
  ctx.onSelectNode?.(node.id);
477
- if (node.toggle) setChecked((c) => !c);
546
+ if (settable) setChecked((c) => !c);
478
547
  ctx.onPick?.(node);
479
548
  },
480
549
  children: [
481
550
  look.showIcons && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-icon-slot", children: node.toggle ? checked ? ctx.renderIcon("Check", "cm-icon") : null : node.icon ? ctx.renderIcon(node.icon, "cm-icon") : null }),
482
551
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "cm-text", children: [
483
552
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-label", children: node.label }),
484
- look.showHints && node.hint && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-hint", children: node.hint })
553
+ hint && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-hint", children: hint })
485
554
  ] }),
486
- look.showShortcuts && node.shortcut && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("kbd", { className: "cm-shortcut", children: node.shortcut })
555
+ look.showShortcuts && node.shortcut && !node.switch && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("kbd", { className: "cm-shortcut", children: node.shortcut }),
556
+ node.switch && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-switch", "data-on": checked ? "" : void 0, "aria-hidden": "true" })
487
557
  ]
488
558
  }
489
559
  )
@@ -493,35 +563,46 @@ function Submenu({ node, ctx }) {
493
563
  const { look } = ctx.design;
494
564
  const [open, setOpen] = (0, import_react.useState)(false);
495
565
  const [side, setSide] = (0, import_react.useState)("right");
496
- const show = (el) => {
497
- setSide(flyoutSide(el, look.width));
498
- setOpen(true);
499
- };
500
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "cm-sub", onMouseEnter: (e) => show(e.currentTarget), onMouseLeave: () => setOpen(false), children: [
501
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
502
- "button",
503
- {
504
- type: "button",
505
- role: "menuitem",
506
- "aria-haspopup": "menu",
507
- "aria-expanded": open,
508
- className: `cm-item ${ctx.highlightId === node.id ? "cm-hl" : ""}`,
509
- onClick: () => {
510
- ctx.onSelectNode?.(node.id);
511
- setOpen(true);
512
- },
513
- children: [
514
- look.showIcons && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-icon-slot", children: node.icon ? ctx.renderIcon(node.icon, "cm-icon") : null }),
515
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "cm-text", children: [
516
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-label", children: node.label }),
517
- look.showHints && node.hint && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-hint", children: node.hint })
518
- ] }),
519
- ctx.renderIcon("ChevronRight", "cm-icon cm-chev")
520
- ]
521
- }
522
- ),
523
- 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 }) })
524
- ] });
566
+ const { greyed } = shown(node, ctx);
567
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
568
+ "div",
569
+ {
570
+ className: "cm-sub",
571
+ onMouseEnter: (e) => {
572
+ if (greyed) return;
573
+ setSide(flyoutSide(e.currentTarget, look.width));
574
+ setOpen(true);
575
+ },
576
+ onMouseLeave: () => setOpen(false),
577
+ children: [
578
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
579
+ "button",
580
+ {
581
+ type: "button",
582
+ role: "menuitem",
583
+ "aria-haspopup": "menu",
584
+ "aria-expanded": open,
585
+ disabled: greyed,
586
+ ...toneAttrs(node.tone, node.toneLabel),
587
+ className: `cm-item ${ctx.highlightId === node.id ? "cm-hl" : ""}`,
588
+ onClick: () => {
589
+ ctx.onSelectNode?.(node.id);
590
+ setOpen(true);
591
+ },
592
+ children: [
593
+ look.showIcons && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-icon-slot", children: node.icon ? ctx.renderIcon(node.icon, "cm-icon") : null }),
594
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "cm-text", children: [
595
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-label", children: node.label }),
596
+ look.showHints && node.hint && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-hint", children: node.hint })
597
+ ] }),
598
+ ctx.renderIcon("ChevronRight", "cm-icon cm-chev")
599
+ ]
600
+ }
601
+ ),
602
+ 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 }) })
603
+ ]
604
+ }
605
+ );
525
606
  }
526
607
  function flyoutSide(row, width) {
527
608
  const r = row.getBoundingClientRect();
@@ -530,37 +611,42 @@ function flyoutSide(row, width) {
530
611
  if (r.left - width - 8 >= bounds.left) return "left";
531
612
  return "below";
532
613
  }
533
- function Slot({ node, ctx }) {
614
+ function SlotRow({ node, ctx, label, icon, count, more, onOpen }) {
534
615
  const { look } = ctx.design;
535
616
  const source = slotSource(node.fills);
536
- const options = slotOptions(node.fills, ctx.placed);
537
- const [open, setOpen] = (0, import_react.useState)(false);
538
- const [side, setSide] = (0, import_react.useState)("right");
539
- const count = source?.runtime ? "live" : String(options.length);
540
- const row = (
617
+ return (
541
618
  // ui-exception: a menu row of the menu component itself
542
619
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
543
620
  "button",
544
621
  {
545
622
  type: "button",
546
623
  role: "menuitem",
547
- "aria-haspopup": node.shape === "submenu" ? "menu" : void 0,
624
+ "aria-haspopup": more ? "menu" : void 0,
548
625
  title: source ? `Placeholder: ${source.label}` : "Placeholder",
549
626
  className: `cm-item cm-slot ${ctx.highlightId === node.id ? "cm-hl" : ""}`,
550
627
  onClick: () => {
551
628
  ctx.onSelectNode?.(node.id);
552
- if (node.shape === "submenu") setOpen(true);
629
+ onOpen?.();
553
630
  },
554
631
  children: [
555
- look.showIcons && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-icon-slot", children: ctx.renderIcon(node.icon ?? source?.icon ?? "Ellipsis", "cm-icon") }),
556
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-text", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-label", children: node.label }) }),
557
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-count", children: count }),
558
- node.shape === "submenu" && ctx.renderIcon("ChevronRight", "cm-icon cm-chev")
632
+ look.showIcons && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-icon-slot", children: ctx.renderIcon(icon, "cm-icon") }),
633
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-text", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-label", children: label }) }),
634
+ count && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cm-count", children: count }),
635
+ more && ctx.renderIcon("ChevronRight", "cm-icon cm-chev")
559
636
  ]
560
637
  }
561
638
  )
562
639
  );
563
- if (node.shape === "items") return row;
640
+ }
641
+ function Slot({ node, ctx }) {
642
+ const { look } = ctx.design;
643
+ const source = slotSource(node.fills);
644
+ const options = slotOptions(node.fills, ctx.placed);
645
+ const [open, setOpen] = (0, import_react.useState)(false);
646
+ const [side, setSide] = (0, import_react.useState)("right");
647
+ const icon = node.icon ?? source?.icon ?? "Ellipsis";
648
+ const count = source?.runtime ? "live" : String(options.length);
649
+ if (node.shape === "items") return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SlotRow, { node, ctx, label: node.label, icon, count });
564
650
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
565
651
  "div",
566
652
  {
@@ -571,13 +657,47 @@ function Slot({ node, ctx }) {
571
657
  },
572
658
  onMouseLeave: () => setOpen(false),
573
659
  children: [
574
- row,
660
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SlotRow, { node, ctx, label: node.label, icon, count, more: true, onOpen: () => setOpen(true) }),
575
661
  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: [
576
662
  "Filled at runtime: ",
577
663
  source.label
578
- ] }) : 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)) })
664
+ ] }) : 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)(
665
+ Item,
666
+ {
667
+ 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 } : {} },
668
+ ctx
669
+ },
670
+ o2.id
671
+ )) })
579
672
  ]
580
673
  }
581
674
  );
582
675
  }
676
+ function FeatureSlot({ node, ctx }) {
677
+ const { look } = ctx.design;
678
+ const n = Math.max(0, Math.min(12, node.featureItems ?? 4));
679
+ const [open, setOpen] = (0, import_react.useState)(false);
680
+ const [side, setSide] = (0, import_react.useState)("right");
681
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: `cm-section ${ctx.highlightId === node.id ? "cm-hl" : ""}`, "data-layout": "list", children: [
682
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", className: "cm-section-label cm-slot-label", onClick: () => ctx.onSelectNode?.(node.id), children: node.label || "This feature" }),
683
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "cm-section-body", children: [
684
+ Array.from({ length: n }, (_, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SlotRow, { node, ctx, label: `Feature option ${i + 1}`, icon: "Circle" }, i)),
685
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
686
+ "div",
687
+ {
688
+ className: "cm-sub",
689
+ onMouseEnter: (e) => {
690
+ setSide(flyoutSide(e.currentTarget, look.width));
691
+ setOpen(true);
692
+ },
693
+ onMouseLeave: () => setOpen(false),
694
+ children: [
695
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SlotRow, { node, ctx, label: "More", icon: "Ellipsis", more: true, onOpen: () => setOpen(true) }),
696
+ 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: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "cm-runtime", children: "The rest of this feature's options, filled at runtime" }) })
697
+ ]
698
+ }
699
+ )
700
+ ] })
701
+ ] });
702
+ }
583
703
  //# sourceMappingURL=index.cjs.map