@ai-matrx/detail 0.1.68 → 0.1.70

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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog — @ai-matrx/detail
2
2
 
3
+ ## 0.1.69
4
+ ### A shell with its own menu gets the actions as data
5
+
6
+ - `DetailShellSlots.menuActions: DetailMenuAction[]` (open as window / docked / page, copy record id — the overflow menu's entries as `{ id, label, onSelect }`) and `DetailShellSlots.navActions` (previous / next only). `DetailActions` takes `optional="none"`; `useDetailMenuActions(core)` is exported.
7
+ - Why: a record open as a canvas tab drew "More record actions" (…) beside the pane's own menu (⋮). A shell whose chrome has a menu offers `menuActions` there and renders `navActions`.
8
+ - Consumer action: a custom shell keeps working (`actions` is unchanged); one with its own menu may switch to `navActions` + `menuActions`.
9
+
3
10
  ## 0.1.68
4
11
  ### Custom fields on every record view (lane 7 STANDARD-TABLES W5)
5
12
 
@@ -272,6 +272,12 @@ interface DetailHistoryEntry {
272
272
  }
273
273
 
274
274
  /** Props every shell receives; the package fills the slots, the host owns the chrome. */
275
+ /** One of the record's optional controls as data (a shell's menu draws it). */
276
+ interface DetailMenuAction {
277
+ readonly id: string;
278
+ readonly label: string;
279
+ readonly onSelect: () => void;
280
+ }
275
281
  interface DetailShellSlots {
276
282
  /** Accessible plain-text title (window chrome, drawer title, document title). */
277
283
  title: string;
@@ -279,6 +285,13 @@ interface DetailShellSlots {
279
285
  titleNode: ReactNode;
280
286
  /** Presentation switcher · previous / next · copy id. Compact. */
281
287
  actions: ReactNode;
288
+ /**
289
+ * Previous / next only — for a shell whose chrome has its OWN menu: it puts
290
+ * `menuActions` there instead of a second overflow button ("…" beside "⋮").
291
+ */
292
+ navActions: ReactNode;
293
+ /** The optional controls (open as window / docked / page, copy id) as data, for a shell's own menu. */
294
+ menuActions: readonly DetailMenuAction[];
282
295
  children: ReactNode;
283
296
  onClose: () => void;
284
297
  }
@@ -538,4 +551,4 @@ declare function requireResolveType(host: DetailHostPorts): NonNullable<DetailHo
538
551
  /** A presentation's shell, or an error naming which entry forgot to bind it. */
539
552
  declare function requireShell<K extends keyof DetailShells>(shells: DetailShells, which: K): NonNullable<DetailShells[K]>;
540
553
 
541
- export { type DetailRef as D, type DetailInstanceData as a, type DetailPresentation as b, type DetailRecordType as c, type DetailStatus as d, type DetailRow as e, type DetailField as f, type DetailSourceHealth as g, type DetailFrameContext as h, type DetailListContext as i, type DetailLoadState as j, type DetailOpenRequest as k, type DetailDockedShellProps as l, type DetailEffectivePresentation as m, type DetailHostPorts as n, DetailHostProvider as o, type DetailPageShellProps as p, type DetailPresentationSetting as q, type DetailShellSlots as r, type DetailShells as s, type DetailWindowShellProps as t, requireResolveType as u, requireShell as v, useDetailHost as w };
554
+ export { type DetailRef as D, type DetailInstanceData as a, type DetailPresentation as b, type DetailRecordType as c, type DetailStatus as d, type DetailRow as e, type DetailField as f, type DetailSourceHealth as g, type DetailFrameContext as h, type DetailListContext as i, type DetailLoadState as j, type DetailOpenRequest as k, type DetailMenuAction as l, type DetailDockedShellProps as m, type DetailEffectivePresentation as n, type DetailHostPorts as o, DetailHostProvider as p, type DetailPageShellProps as q, type DetailPresentationSetting as r, type DetailShellSlots as s, type DetailShells as t, type DetailWindowShellProps as u, requireResolveType as v, requireShell as w, useDetailHost as x };
@@ -272,6 +272,12 @@ interface DetailHistoryEntry {
272
272
  }
273
273
 
274
274
  /** Props every shell receives; the package fills the slots, the host owns the chrome. */
275
+ /** One of the record's optional controls as data (a shell's menu draws it). */
276
+ interface DetailMenuAction {
277
+ readonly id: string;
278
+ readonly label: string;
279
+ readonly onSelect: () => void;
280
+ }
275
281
  interface DetailShellSlots {
276
282
  /** Accessible plain-text title (window chrome, drawer title, document title). */
277
283
  title: string;
@@ -279,6 +285,13 @@ interface DetailShellSlots {
279
285
  titleNode: ReactNode;
280
286
  /** Presentation switcher · previous / next · copy id. Compact. */
281
287
  actions: ReactNode;
288
+ /**
289
+ * Previous / next only — for a shell whose chrome has its OWN menu: it puts
290
+ * `menuActions` there instead of a second overflow button ("…" beside "⋮").
291
+ */
292
+ navActions: ReactNode;
293
+ /** The optional controls (open as window / docked / page, copy id) as data, for a shell's own menu. */
294
+ menuActions: readonly DetailMenuAction[];
282
295
  children: ReactNode;
283
296
  onClose: () => void;
284
297
  }
@@ -538,4 +551,4 @@ declare function requireResolveType(host: DetailHostPorts): NonNullable<DetailHo
538
551
  /** A presentation's shell, or an error naming which entry forgot to bind it. */
539
552
  declare function requireShell<K extends keyof DetailShells>(shells: DetailShells, which: K): NonNullable<DetailShells[K]>;
540
553
 
541
- export { type DetailRef as D, type DetailInstanceData as a, type DetailPresentation as b, type DetailRecordType as c, type DetailStatus as d, type DetailRow as e, type DetailField as f, type DetailSourceHealth as g, type DetailFrameContext as h, type DetailListContext as i, type DetailLoadState as j, type DetailOpenRequest as k, type DetailDockedShellProps as l, type DetailEffectivePresentation as m, type DetailHostPorts as n, DetailHostProvider as o, type DetailPageShellProps as p, type DetailPresentationSetting as q, type DetailShellSlots as r, type DetailShells as s, type DetailWindowShellProps as t, requireResolveType as u, requireShell as v, useDetailHost as w };
554
+ export { type DetailRef as D, type DetailInstanceData as a, type DetailPresentation as b, type DetailRecordType as c, type DetailStatus as d, type DetailRow as e, type DetailField as f, type DetailSourceHealth as g, type DetailFrameContext as h, type DetailListContext as i, type DetailLoadState as j, type DetailOpenRequest as k, type DetailMenuAction as l, type DetailDockedShellProps as m, type DetailEffectivePresentation as n, type DetailHostPorts as o, DetailHostProvider as p, type DetailPageShellProps as q, type DetailPresentationSetting as r, type DetailShellSlots as s, type DetailShells as t, type DetailWindowShellProps as u, requireResolveType as v, requireShell as w, useDetailHost as x };
package/dist/react.cjs CHANGED
@@ -40,6 +40,7 @@ __export(react_exports, {
40
40
  useDetailHealth: () => useDetailHealth,
41
41
  useDetailHost: () => useDetailHost,
42
42
  useDetailKeyboard: () => useDetailKeyboard,
43
+ useDetailMenuActions: () => useDetailMenuActions,
43
44
  useDetailRecord: () => useDetailRecord,
44
45
  useOpenDetail: () => useOpenDetail
45
46
  });
@@ -759,7 +760,27 @@ function DetailRecordMeta({ core }) {
759
760
  }
760
761
  );
761
762
  }
762
- function DetailActions({ core }) {
763
+ function useDetailMenuActions(core) {
764
+ const host = useDetailHost();
765
+ return [
766
+ ...PRESENTATIONS.filter((p) => p.value !== core.presentation).map(({ value, label }) => ({
767
+ id: `detail-open-${value}`,
768
+ label,
769
+ onSelect: () => core.switchTo(value)
770
+ })),
771
+ {
772
+ id: "detail-copy-id",
773
+ label: "Copy record id",
774
+ onSelect: () => {
775
+ void host.copyText(core.ref.id).then((ok) => {
776
+ if (ok) host.notify.success("Record id copied");
777
+ else host.notify.error("Could not copy the record id to the clipboard.");
778
+ });
779
+ }
780
+ }
781
+ ];
782
+ }
783
+ function DetailActions({ core, optional = "inline" }) {
763
784
  const host = useDetailHost();
764
785
  const [copied, setCopied] = (0, import_react7.useState)(false);
765
786
  const [menuOpen, setMenuOpen] = (0, import_react7.useState)(false);
@@ -824,98 +845,100 @@ function DetailActions({ core }) {
824
845
  ),
825
846
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "mx-1 h-4 w-px bg-border", "aria-hidden": true })
826
847
  ] }) : null,
827
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
828
- "span",
829
- {
830
- className: "flex items-center gap-0.5 @max-[26rem]/window-header:hidden",
831
- "data-detail-actions-collapsible": true,
832
- children: [
833
- others.map(({ value, label, Icon }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
834
- "button",
835
- {
836
- type: "button",
837
- className: ICON_BUTTON,
838
- onClick: () => core.switchTo(value),
839
- "aria-label": label,
840
- title: label,
841
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { className: "h-4 w-4" })
842
- },
843
- value
844
- )),
845
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
846
- "button",
847
- {
848
- type: "button",
849
- onClick: () => void copyId(),
850
- className: ICON_BUTTON,
851
- "aria-label": "Copy record id",
852
- title: "Copy record id",
853
- "data-detail-copy-id": true,
854
- children: copied ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CheckIcon, { className: "h-3.5 w-3.5 text-primary" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CopyIcon, { className: "h-3.5 w-3.5" })
855
- }
856
- )
857
- ]
858
- }
859
- ),
860
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "relative hidden @max-[26rem]/window-header:inline-flex", ref: menuRef, children: [
861
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
862
- "button",
863
- {
864
- type: "button",
865
- className: (0, import_design_system.cn)(ICON_BUTTON, "hidden @max-[26rem]/window-header:inline-flex"),
866
- onClick: () => setMenuOpen((wasOpen) => !wasOpen),
867
- "aria-label": "More record actions",
868
- title: "More record actions",
869
- "aria-haspopup": "menu",
870
- "aria-expanded": menuOpen,
871
- "data-detail-actions-overflow": true,
872
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MoreHorizontalIcon, { className: "h-4 w-4" })
873
- }
874
- ),
875
- menuOpen ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
876
- "div",
848
+ optional === "none" ? null : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
849
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
850
+ "span",
877
851
  {
878
- role: "menu",
879
- "aria-label": "Record actions",
880
- className: "absolute right-0 top-full z-50 mt-1 min-w-44 rounded-md border border-border bg-card p-1 shadow-md",
881
- "data-detail-actions-menu": true,
852
+ className: "flex items-center gap-0.5 @max-[26rem]/window-header:hidden",
853
+ "data-detail-actions-collapsible": true,
882
854
  children: [
883
- others.map(({ value, label, Icon }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
855
+ others.map(({ value, label, Icon }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
884
856
  "button",
885
857
  {
886
858
  type: "button",
887
- role: "menuitem",
888
- className: MENU_ITEM,
889
- onClick: () => {
890
- setMenuOpen(false);
891
- core.switchTo(value);
892
- },
893
- children: [
894
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0" }),
895
- label
896
- ]
859
+ className: ICON_BUTTON,
860
+ onClick: () => core.switchTo(value),
861
+ "aria-label": label,
862
+ title: label,
863
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { className: "h-4 w-4" })
897
864
  },
898
865
  value
899
866
  )),
900
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
867
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
901
868
  "button",
902
869
  {
903
870
  type: "button",
904
- role: "menuitem",
905
- className: MENU_ITEM,
906
- onClick: () => {
907
- setMenuOpen(false);
908
- void copyId();
909
- },
910
- children: [
911
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CopyIcon, { className: "h-3.5 w-3.5 shrink-0" }),
912
- "Copy record id"
913
- ]
871
+ onClick: () => void copyId(),
872
+ className: ICON_BUTTON,
873
+ "aria-label": "Copy record id",
874
+ title: "Copy record id",
875
+ "data-detail-copy-id": true,
876
+ children: copied ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CheckIcon, { className: "h-3.5 w-3.5 text-primary" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CopyIcon, { className: "h-3.5 w-3.5" })
914
877
  }
915
878
  )
916
879
  ]
917
880
  }
918
- ) : null
881
+ ),
882
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { className: "relative hidden @max-[26rem]/window-header:inline-flex", ref: menuRef, children: [
883
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
884
+ "button",
885
+ {
886
+ type: "button",
887
+ className: (0, import_design_system.cn)(ICON_BUTTON, "hidden @max-[26rem]/window-header:inline-flex"),
888
+ onClick: () => setMenuOpen((wasOpen) => !wasOpen),
889
+ "aria-label": "More record actions",
890
+ title: "More record actions",
891
+ "aria-haspopup": "menu",
892
+ "aria-expanded": menuOpen,
893
+ "data-detail-actions-overflow": true,
894
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MoreHorizontalIcon, { className: "h-4 w-4" })
895
+ }
896
+ ),
897
+ menuOpen ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
898
+ "div",
899
+ {
900
+ role: "menu",
901
+ "aria-label": "Record actions",
902
+ className: "absolute right-0 top-full z-50 mt-1 min-w-44 rounded-md border border-border bg-card p-1 shadow-md",
903
+ "data-detail-actions-menu": true,
904
+ children: [
905
+ others.map(({ value, label, Icon }) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
906
+ "button",
907
+ {
908
+ type: "button",
909
+ role: "menuitem",
910
+ className: MENU_ITEM,
911
+ onClick: () => {
912
+ setMenuOpen(false);
913
+ core.switchTo(value);
914
+ },
915
+ children: [
916
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Icon, { className: "h-3.5 w-3.5 shrink-0" }),
917
+ label
918
+ ]
919
+ },
920
+ value
921
+ )),
922
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
923
+ "button",
924
+ {
925
+ type: "button",
926
+ role: "menuitem",
927
+ className: MENU_ITEM,
928
+ onClick: () => {
929
+ setMenuOpen(false);
930
+ void copyId();
931
+ },
932
+ children: [
933
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CopyIcon, { className: "h-3.5 w-3.5 shrink-0" }),
934
+ "Copy record id"
935
+ ]
936
+ }
937
+ )
938
+ ]
939
+ }
940
+ ) : null
941
+ ] })
919
942
  ] })
920
943
  ] });
921
944
  }
@@ -1553,6 +1576,7 @@ function KeyboardRoot({ core, children }) {
1553
1576
  function DetailWindowPresentation({ data, onClose }) {
1554
1577
  const host = useDetailHost();
1555
1578
  const core = useDetailCore(data, "window", { onClose });
1579
+ const menuActions = useDetailMenuActions(core);
1556
1580
  const Shell = requireShell(host.shells, "Window");
1557
1581
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1558
1582
  Shell,
@@ -1563,6 +1587,8 @@ function DetailWindowPresentation({ data, onClose }) {
1563
1587
  title: core.title,
1564
1588
  titleNode: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KeyboardSlot, { core, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DetailTitle, { core }) }),
1565
1589
  actions: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KeyboardActionsSlot, { core, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DetailActions, { core }) }),
1590
+ navActions: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KeyboardActionsSlot, { core, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DetailActions, { core, optional: "none" }) }),
1591
+ menuActions,
1566
1592
  onClose: core.close,
1567
1593
  children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KeyboardRoot, { core, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DetailBody, { core }) })
1568
1594
  }
@@ -1571,6 +1597,7 @@ function DetailWindowPresentation({ data, onClose }) {
1571
1597
  function DetailDockedPresentation({ data, onClose }) {
1572
1598
  const host = useDetailHost();
1573
1599
  const core = useDetailCore(data, "docked", { onClose });
1600
+ const menuActions = useDetailMenuActions(core);
1574
1601
  const Shell = requireShell(host.shells, "Docked");
1575
1602
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1576
1603
  Shell,
@@ -1580,6 +1607,8 @@ function DetailDockedPresentation({ data, onClose }) {
1580
1607
  title: core.title,
1581
1608
  titleNode: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KeyboardSlot, { core, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DetailTitle, { core }) }),
1582
1609
  actions: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KeyboardActionsSlot, { core, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DetailActions, { core }) }),
1610
+ navActions: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KeyboardActionsSlot, { core, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DetailActions, { core, optional: "none" }) }),
1611
+ menuActions,
1583
1612
  onClose: core.close,
1584
1613
  children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KeyboardRoot, { core, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DetailBody, { core }) })
1585
1614
  }
@@ -1588,6 +1617,7 @@ function DetailDockedPresentation({ data, onClose }) {
1588
1617
  function DetailPagePresentation({ data }) {
1589
1618
  const host = useDetailHost();
1590
1619
  const core = useDetailCore(data, "page", {});
1620
+ const menuActions = useDetailMenuActions(core);
1591
1621
  const Shell = requireShell(host.shells, "Page");
1592
1622
  return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1593
1623
  Shell,
@@ -1596,6 +1626,8 @@ function DetailPagePresentation({ data }) {
1596
1626
  title: core.title,
1597
1627
  titleNode: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KeyboardSlot, { core, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DetailTitle, { core }) }),
1598
1628
  actions: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KeyboardActionsSlot, { core, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DetailActions, { core }) }),
1629
+ navActions: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KeyboardActionsSlot, { core, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DetailActions, { core, optional: "none" }) }),
1630
+ menuActions,
1599
1631
  onClose: core.close,
1600
1632
  onBack: core.leave,
1601
1633
  children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(KeyboardRoot, { core, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(DetailBody, { core }) })