@ai-matrx/records-ui 0.93.83 → 0.93.85

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,16 @@
1
1
  # Changelog — @ai-matrx/records-ui
2
2
 
3
+ ## 0.93.85
4
+ ### Unreleased
5
+
6
+ - **The toolbar row's last buttons are never cut: Share folds into the table's menu** (merged-grid leftovers, 2026-09-30): in a table window at its smallest size the row needed 3 px more than it had even with everything folded, and the table menu's button was cut. `toolbarFit` gains fold 4 — the page's own Share moves into the table's menu (where a phone already has it) and comes back when the row has room. A host that draws the page's actions in its own header is unchanged. Proof: `toolbarFit.test.tsx`, red on 0.93.84 (2 of 3). Consumer action: none.
7
+
8
+ ## 0.93.84
9
+
10
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
11
+ `git diff npm/records-ui/v0.93.83..npm/records-ui/v0.93.84 -- apps/shared/records-ui`).
12
+ No source changes intended and no consumer action required.
13
+
3
14
  ## 0.93.83
4
15
 
5
16
  Automatic changed-only republish (docs/metadata drift since the last tag — see
package/dist/index.cjs CHANGED
@@ -1600,111 +1600,114 @@ function RelationPicker({ field: field2, value, onChange, disabled, id, classNam
1600
1600
  if (q === "") return true;
1601
1601
  return `${titleOf(row.id)} ${apart.get(row.id) ?? ""}`.toLowerCase().includes(q);
1602
1602
  });
1603
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: (0, import_design_system4.cn)("flex flex-wrap items-center gap-1", className), children: [
1604
- picked.map((recordId) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1605
- RecordChip,
1606
- {
1607
- title: titleOf(recordId),
1608
- ...disabled ? {} : { onRemove: () => toggle(recordId) }
1609
- },
1610
- recordId
1611
- )),
1612
- disabled ? null : (
1613
- // THE ONE "PICK OR ADD" BUILDER (PickOrAdd.tsx) — the choice editor draws the same one.
1614
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1615
- PickOrAdd,
1603
+ return (
1604
+ // Never wider than where it is drawn (a grid cell beside a sticky column: B3-07) — chips truncate, the button wraps.
1605
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: (0, import_design_system4.cn)("flex min-w-0 max-w-full flex-wrap items-center gap-1", className), children: [
1606
+ picked.map((recordId) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1607
+ RecordChip,
1616
1608
  {
1617
- triggerId: id,
1618
- triggerLabel: `Pick for ${fieldName(field2)}`,
1619
- triggerText: picked.length === 0 ? "Pick" : "Change",
1620
- open,
1621
- onOpenChange: setOpen,
1622
- search,
1623
- onSearch: setSearch,
1624
- items: visible.map((row) => ({
1625
- id: row.id,
1626
- label: titleOf(row.id),
1627
- detail: apart.get(row.id) ?? null,
1628
- picked: picked.includes(row.id),
1629
- title: apart.has(row.id) ? `${titleOf(row.id)} \u2014 ${apart.get(row.id)}` : titleOf(row.id),
1630
- attrs: { "data-relation-candidate": row.id }
1631
- })),
1632
- onPick: toggle,
1633
- notice: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1634
- refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RefusalLine, { error: refusal2, className: "p-2" }) : null,
1635
- !rows && !refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading\u2026" }) : null,
1636
- full ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "p-2 text-xs text-muted-foreground", children: [
1637
- fieldName(field2),
1638
- " can point at ",
1639
- field2.relation_max,
1640
- " at most. Remove one to add another."
1641
- ] }) : null
1642
- ] }),
1643
- empty: rows ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Nothing in that table matches." }) : null,
1644
- add: allowCreate && !full && !targetRefusal && target && !creating ? {
1645
- label: search.trim() !== "" ? `Create \u201C${search.trim()}\u201D` : `New ${targetWord}`,
1646
- disabled: busy,
1647
- // TYPED SOMETHING THAT IS NOT THERE? That IS the name. The champions
1648
- // (Airtable, Notion) make the search text the new record's title in one
1649
- // press; an empty box asks for one.
1650
- onAdd: () => {
1651
- if (search.trim() !== "") void create(search);
1652
- else {
1653
- setNewTitle("");
1654
- setCreating(true);
1609
+ title: titleOf(recordId),
1610
+ ...disabled ? {} : { onRemove: () => toggle(recordId) }
1611
+ },
1612
+ recordId
1613
+ )),
1614
+ disabled ? null : (
1615
+ // THE ONE "PICK OR ADD" BUILDER (PickOrAdd.tsx) — the choice editor draws the same one.
1616
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1617
+ PickOrAdd,
1618
+ {
1619
+ triggerId: id,
1620
+ triggerLabel: `Pick for ${fieldName(field2)}`,
1621
+ triggerText: picked.length === 0 ? "Pick" : "Change",
1622
+ open,
1623
+ onOpenChange: setOpen,
1624
+ search,
1625
+ onSearch: setSearch,
1626
+ items: visible.map((row) => ({
1627
+ id: row.id,
1628
+ label: titleOf(row.id),
1629
+ detail: apart.get(row.id) ?? null,
1630
+ picked: picked.includes(row.id),
1631
+ title: apart.has(row.id) ? `${titleOf(row.id)} \u2014 ${apart.get(row.id)}` : titleOf(row.id),
1632
+ attrs: { "data-relation-candidate": row.id }
1633
+ })),
1634
+ onPick: toggle,
1635
+ notice: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_jsx_runtime5.Fragment, { children: [
1636
+ refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RefusalLine, { error: refusal2, className: "p-2" }) : null,
1637
+ !rows && !refusal2 ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading\u2026" }) : null,
1638
+ full ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("p", { className: "p-2 text-xs text-muted-foreground", children: [
1639
+ fieldName(field2),
1640
+ " can point at ",
1641
+ field2.relation_max,
1642
+ " at most. Remove one to add another."
1643
+ ] }) : null
1644
+ ] }),
1645
+ empty: rows ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Nothing in that table matches." }) : null,
1646
+ add: allowCreate && !full && !targetRefusal && target && !creating ? {
1647
+ label: search.trim() !== "" ? `Create \u201C${search.trim()}\u201D` : `New ${targetWord}`,
1648
+ disabled: busy,
1649
+ // TYPED SOMETHING THAT IS NOT THERE? That IS the name. The champions
1650
+ // (Airtable, Notion) make the search text the new record's title in one
1651
+ // press; an empty box asks for one.
1652
+ onAdd: () => {
1653
+ if (search.trim() !== "") void create(search);
1654
+ else {
1655
+ setNewTitle("");
1656
+ setCreating(true);
1657
+ }
1655
1658
  }
1656
- }
1657
- } : null,
1658
- foot: !allowCreate || full ? void 0 : targetRefusal ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RefusalLine, { error: targetRefusal, className: "p-2" }) : !target ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading that table\u2026" }) : creating ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-1 p-1", children: [
1659
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1660
- import_design_system4.BasicInput,
1661
- {
1662
- autoFocus: true,
1663
- value: newTitle,
1664
- onChange: (e) => setNewTitle(e.target.value),
1665
- onKeyDown: (e) => {
1666
- if (e.key === "Enter") {
1667
- e.preventDefault();
1668
- void create(newTitle);
1659
+ } : null,
1660
+ foot: !allowCreate || full ? void 0 : targetRefusal ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(RefusalLine, { error: targetRefusal, className: "p-2" }) : !target ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "p-2 text-xs text-muted-foreground", children: "Reading that table\u2026" }) : creating ? /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "flex items-center gap-1 p-1", children: [
1661
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1662
+ import_design_system4.BasicInput,
1663
+ {
1664
+ autoFocus: true,
1665
+ value: newTitle,
1666
+ onChange: (e) => setNewTitle(e.target.value),
1667
+ onKeyDown: (e) => {
1668
+ if (e.key === "Enter") {
1669
+ e.preventDefault();
1670
+ void create(newTitle);
1671
+ }
1672
+ },
1673
+ placeholder: `New ${targetWord}`,
1674
+ className: "h-7 flex-1 text-xs",
1675
+ "aria-label": `Name the new ${targetWord}`
1676
+ }
1677
+ ),
1678
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1679
+ import_design_system4.Button,
1680
+ {
1681
+ type: "button",
1682
+ size: "sm",
1683
+ variant: "default",
1684
+ disabled: busy || newTitle.trim() === "",
1685
+ onClick: () => void create(newTitle),
1686
+ children: busy ? "Creating\u2026" : "Create"
1687
+ }
1688
+ )
1689
+ ] }) : void 0,
1690
+ footNote: allowCreate && !full && createRefusal ? (
1691
+ // The typed title is still in the search box: the notice owns the
1692
+ // two doors out (lane REFUSAL-SWEEP).
1693
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1694
+ RefusalNotice,
1695
+ {
1696
+ error: createRefusal,
1697
+ className: "mt-1 max-w-72",
1698
+ onKeepEditing: () => setCreateRefusal(null),
1699
+ onDiscard: () => {
1700
+ setCreateRefusal(null);
1701
+ setSearch("");
1669
1702
  }
1670
- },
1671
- placeholder: `New ${targetWord}`,
1672
- className: "h-7 flex-1 text-xs",
1673
- "aria-label": `Name the new ${targetWord}`
1674
- }
1675
- ),
1676
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1677
- import_design_system4.Button,
1678
- {
1679
- type: "button",
1680
- size: "sm",
1681
- variant: "default",
1682
- disabled: busy || newTitle.trim() === "",
1683
- onClick: () => void create(newTitle),
1684
- children: busy ? "Creating\u2026" : "Create"
1685
- }
1686
- )
1687
- ] }) : void 0,
1688
- footNote: allowCreate && !full && createRefusal ? (
1689
- // The typed title is still in the search box: the notice owns the
1690
- // two doors out (lane REFUSAL-SWEEP).
1691
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1692
- RefusalNotice,
1693
- {
1694
- error: createRefusal,
1695
- className: "mt-1 max-w-72",
1696
- onKeepEditing: () => setCreateRefusal(null),
1697
- onDiscard: () => {
1698
- setCreateRefusal(null);
1699
- setSearch("");
1700
1703
  }
1701
- }
1702
- )
1703
- ) : null
1704
- }
1704
+ )
1705
+ ) : null
1706
+ }
1707
+ )
1705
1708
  )
1706
- )
1707
- ] });
1709
+ ] })
1710
+ );
1708
1711
  }
1709
1712
 
1710
1713
  // src/gridAttachments.tsx
@@ -5591,9 +5594,10 @@ function OpenCell({
5591
5594
  {
5592
5595
  ref: host,
5593
5596
  "data-matrx-cell-control": "",
5597
+ "data-matrx-cell-fill": "",
5594
5598
  "data-matrx-cell-editor": "",
5595
5599
  tabIndex: -1,
5596
- className: "relative z-[25] flex w-full min-w-[10rem] flex-col gap-1 outline-none",
5600
+ className: "relative z-[25] flex w-full min-w-0 max-w-full flex-col gap-1 outline-none",
5597
5601
  onDoubleClick: (event) => event.stopPropagation(),
5598
5602
  onKeyDown: (event) => {
5599
5603
  if (event.target?.closest?.("[data-refusal-notice], [data-records-choice-ask]")) return;
@@ -31575,13 +31579,14 @@ function ViewFilter({ tableId, view, onApply, className }) {
31575
31579
 
31576
31580
  // src/toolbarFit.ts
31577
31581
  var import_react188 = require("react");
31578
- var FOLDED_GUESS = { look: 104, chooser: 96, tools: 120 };
31582
+ var FOLDED_GUESS = { look: 104, chooser: 96, tools: 120, share: 0 };
31579
31583
  function toolbarFold(input) {
31580
31584
  const { available, needsNow, gap, folded } = input;
31581
31585
  const pieces = [
31582
31586
  { piece: input.look, stage: 1, guess: FOLDED_GUESS.look },
31583
31587
  { piece: input.chooser, stage: 2, guess: FOLDED_GUESS.chooser },
31584
- ...input.tools ? [{ piece: input.tools, stage: 3, guess: FOLDED_GUESS.tools }] : []
31588
+ ...input.tools ? [{ piece: input.tools, stage: 3, guess: FOLDED_GUESS.tools }] : [],
31589
+ ...input.tools && input.share ? [{ piece: input.share, stage: 4, guess: FOLDED_GUESS.share }] : []
31585
31590
  ];
31586
31591
  const widthAt = ({ piece, stage, guess }, fold) => {
31587
31592
  if (piece.now === 0 && piece.full === null) return 0;
@@ -31590,7 +31595,7 @@ function toolbarFold(input) {
31590
31595
  };
31591
31596
  const withGap = (width) => width > 0 ? width + gap : 0;
31592
31597
  const base = pieces.reduce((rest, p) => rest - withGap(p.piece.now), needsNow);
31593
- const stages = input.tools ? [0, 1, 2, 3] : [0, 1, 2];
31598
+ const stages = input.tools ? input.share ? [0, 1, 2, 3, 4] : [0, 1, 2, 3] : [0, 1, 2];
31594
31599
  const needs = stages.map((fold) => pieces.reduce((sum, p) => sum + withGap(widthAt(p, fold)), base));
31595
31600
  const fits = needs.findIndex((need) => need <= available + 0.5);
31596
31601
  if (fits >= 0) return stages[fits];
@@ -31631,9 +31636,10 @@ function useToolbarFit(row) {
31631
31636
  const widths = {
31632
31637
  look: { full: null, folded: null },
31633
31638
  chooser: { full: null, folded: null },
31634
- tools: { full: null, folded: null }
31639
+ tools: { full: null, folded: null },
31640
+ share: { full: null, folded: null }
31635
31641
  };
31636
- const stageOf = { look: 1, chooser: 2, tools: 3 };
31642
+ const stageOf = { look: 1, chooser: 2, tools: 3, share: 4 };
31637
31643
  let current = 0;
31638
31644
  let pending = 0;
31639
31645
  let stopped = false;
@@ -31643,7 +31649,7 @@ function useToolbarFit(row) {
31643
31649
  const available = row.clientWidth;
31644
31650
  if (available <= 0) return;
31645
31651
  const read = (name) => {
31646
- const el = name === "tools" ? row.querySelector(":scope > [data-toolbar-fit-slot]") : row.querySelector(`:scope > [data-toolbar-fit="${name}"]`);
31652
+ const el = name === "tools" ? row.querySelector(":scope > [data-toolbar-fit-slot]") : name === "share" ? row.querySelector('[data-toolbar-fit="share"]') : row.querySelector(`:scope > [data-toolbar-fit="${name}"]`);
31647
31653
  const now = !el ? 0 : name === "tools" ? slotNeeds(el) : naturalWidth(el);
31648
31654
  const w = widths[name];
31649
31655
  if (el && now > 0) {
@@ -31656,8 +31662,11 @@ function useToolbarFit(row) {
31656
31662
  const look = read("look");
31657
31663
  const chooser = read("chooser");
31658
31664
  const tools = read("tools");
31665
+ const shareEl = row.querySelector('[data-toolbar-fit="share"]');
31666
+ const shareRead = shareEl ? read("share") : null;
31667
+ const share = shareRead && (shareRead.now > 0 || shareRead.full !== null) ? shareRead : void 0;
31659
31668
  const { needs, gap } = rowNeeds(row);
31660
- const next = toolbarFold({ available, needsNow: needs, gap, folded: current, look, chooser, tools });
31669
+ const next = toolbarFold({ available, needsNow: needs, gap, folded: current, look, chooser, tools, share });
31661
31670
  if (next !== current) {
31662
31671
  current = next;
31663
31672
  setFold(next);
@@ -32074,6 +32083,7 @@ function TablePage({
32074
32083
  const chooserInline = !designated && !phone && toolbarFold2 < 2;
32075
32084
  const chooserCompact = !designated && !phone && toolbarFold2 >= 2;
32076
32085
  const controlsInSheet = phone && !designated;
32086
+ const shareInMenu = phone || toolbarFold2 >= 4;
32077
32087
  (0, import_react189.useEffect)(() => {
32078
32088
  if (face.kind !== "designated") return;
32079
32089
  if (hostFromLink || opened.layout || layoutChosen) return;
@@ -32298,7 +32308,7 @@ function TablePage({
32298
32308
  rights.share && canShare ? /* @__PURE__ */ (0, import_jsx_runtime105.jsx)(import_design_system96.Button, { size: "sm", variant: "ghost", className: "h-8", onClick: () => setShareAsked((n) => n + 1), "data-table-sheet-share": "", children: "Share\u2026" }) : null
32299
32309
  ] }) : void 0;
32300
32310
  const pageActions = /* @__PURE__ */ (0, import_jsx_runtime105.jsxs)(import_jsx_runtime105.Fragment, { children: [
32301
- organizationId ? /* @__PURE__ */ (0, import_jsx_runtime105.jsx)(
32311
+ organizationId ? /* @__PURE__ */ (0, import_jsx_runtime105.jsx)("span", { className: shareInMenu ? "hidden" : "flex shrink-0 items-center", "data-toolbar-fit": "share", children: /* @__PURE__ */ (0, import_jsx_runtime105.jsx)(
32302
32312
  ShareControl,
32303
32313
  {
32304
32314
  kind: "table",
@@ -32308,10 +32318,10 @@ function TablePage({
32308
32318
  may: rights.share,
32309
32319
  whyNot: rights.read ? rights.why("share") : void 0,
32310
32320
  initiallyOpen: shareFromLink || shareAsked > 0,
32311
- ...phone ? { className: "hidden" } : {}
32321
+ ...shareInMenu ? { className: "hidden" } : {}
32312
32322
  },
32313
- phone ? `share-${shareAsked}` : "share"
32314
- ) : null,
32323
+ shareInMenu ? `share-${shareAsked}` : "share"
32324
+ ) }, "share") : null,
32315
32325
  /* @__PURE__ */ (0, import_jsx_runtime105.jsxs)(import_design_system96.DropdownMenu, { children: [
32316
32326
  /* @__PURE__ */ (0, import_jsx_runtime105.jsx)(import_design_system96.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime105.jsx)(import_design_system96.Button, { size: "sm", variant: "ghost", "aria-label": "Table menu", title: "Table menu", "data-table-menu": "", children: /* @__PURE__ */ (0, import_jsx_runtime105.jsxs)("svg", { viewBox: "0 0 24 24", className: "h-4 w-4", fill: "currentColor", "aria-hidden": "true", children: [
32317
32327
  /* @__PURE__ */ (0, import_jsx_runtime105.jsx)("circle", { cx: "5", cy: "12", r: "1.75" }),
@@ -32334,7 +32344,7 @@ function TablePage({
32334
32344
  },
32335
32345
  entry.key
32336
32346
  )),
32337
- phone && rights.share && canShare ? /* @__PURE__ */ (0, import_jsx_runtime105.jsxs)(import_jsx_runtime105.Fragment, { children: [
32347
+ shareInMenu && rights.share && canShare ? /* @__PURE__ */ (0, import_jsx_runtime105.jsxs)(import_jsx_runtime105.Fragment, { children: [
32338
32348
  /* @__PURE__ */ (0, import_jsx_runtime105.jsx)(import_design_system96.DropdownMenuSeparator, {}),
32339
32349
  /* @__PURE__ */ (0, import_jsx_runtime105.jsx)(import_design_system96.DropdownMenuItem, { onSelect: () => setShareAsked((n) => n + 1), "data-table-menu-share": "", children: "Share\u2026" })
32340
32350
  ] }) : null,