@ai-matrx/records-ui 0.102.20 → 0.103.0

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,20 @@
1
1
  # Changelog — @ai-matrx/records-ui
2
2
 
3
+ ## 0.103.0
4
+
5
+ - **Notion's calculation row on the embedded grid** (Spaces N6): every column's footer cell says "Calculate" on hover and offers Notion's list in Notion's words — Count all · Count unique values · Count empty · Count not empty · Percent empty · Percent not empty · Sum · Average · Median · Min · Max · Range — narrowed to what the column can mean and to the store's `custom.agg_operations`. The pick is reported through `onPresentationChange` as `summaries` (unchanged door). The band stays plain until one is picked.
6
+ - The summary bar knows the store's three newer measures `percent_empty`, `percent_filled` (shown as a percent) and `range` (a date column's range in days).
7
+ - `searchBox={false}` on `Grid` / `ViewSwitcher` / `TablePage` (and the built-in source's embedded views): the embedded grid draws no magnifier, for a host that draws its own and hands the term through `searchOverride` (Spaces N-19).
8
+ - Both embedded magnifiers are `TapTargetSearchGroup surface="solid"` — no more `[tap-target] glass on a solid surface` on a page. Needs design-system 0.72.1.
9
+
10
+ **Consumer action:** none required. A host drawing its own search field may pass `searchBox={false}`.
11
+
12
+ ## 0.102.21
13
+
14
+ Automatic changed-only republish (docs/metadata drift since the last tag — see
15
+ `git diff npm/records-ui/v0.102.20..npm/records-ui/v0.102.21 -- apps/shared/records-ui`).
16
+ No source changes intended and no consumer action required.
17
+
3
18
  ## 0.102.20
4
19
 
5
20
  - `readTableShapes` reads markdown tables through THE GFM rule (`tableStartsAt` / `findTableEnd` / `rowCells` from `@ai-matrx/content-ir/source`) instead of a private pipe splitter and delimiter regex: edge pipes optional, `\|` stays in its cell, and the table ends where chat and the editor end it (AP-2).
package/dist/index.cjs CHANGED
@@ -7138,7 +7138,7 @@ function RecordValue({
7138
7138
  }
7139
7139
  if (inCell && ui.grid === "merged" && ui.renderText && typeof value === "string" && value.trim() !== "" && editorKindFor(field2) === "long_text" && !shownAsOf(field2) && !(0, import_core4.wholeValueOf)(document2, field2.key) && !envelopeFor(document2, field2.key)?.absent && // A long-text cell holding a kind is drawn by the kind system below, not as markdown.
7140
7140
  !kindValueOf(value)) {
7141
- return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "block max-h-[4.5em] min-w-0 overflow-hidden whitespace-normal", "data-records-cell-text": "", children: ui.renderText(value, "cell") });
7141
+ return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)("span", { className: "block h-[1.5em] min-w-0 overflow-hidden whitespace-normal", "data-records-cell-text": "", children: ui.renderText(value, "cell") });
7142
7142
  }
7143
7143
  if (isSignatureField(field2) && value !== null && value !== void 0 && value !== "") {
7144
7144
  return /* @__PURE__ */ (0, import_jsx_runtime27.jsx)(SignatureValue, { value, inCell, recordId, fieldKey: field2.key, document: document2 });
@@ -10520,8 +10520,29 @@ var MEASURE_LABEL = {
10520
10520
  median: "Median",
10521
10521
  filled: "Filled",
10522
10522
  empty: "Empty",
10523
- unique: "Unique"
10523
+ unique: "Unique",
10524
+ percent_empty: "Percent empty",
10525
+ percent_filled: "Percent filled",
10526
+ range: "Range"
10524
10527
  };
10528
+ var NOTION_CALCULATIONS = [
10529
+ { op: "count", menu: "Count all", footer: "Count" },
10530
+ { op: "unique", menu: "Count unique values", footer: "Unique" },
10531
+ { op: "empty", menu: "Count empty", footer: "Empty" },
10532
+ { op: "filled", menu: "Count not empty", footer: "Not empty" },
10533
+ { op: "percent_empty", menu: "Percent empty", footer: "Empty" },
10534
+ { op: "percent_filled", menu: "Percent not empty", footer: "Not empty" },
10535
+ { op: "sum", menu: "Sum", footer: "Sum" },
10536
+ { op: "avg", menu: "Average", footer: "Average" },
10537
+ { op: "median", menu: "Median", footer: "Median" },
10538
+ { op: "min", menu: "Min", footer: "Min" },
10539
+ { op: "max", menu: "Max", footer: "Max" },
10540
+ { op: "range", menu: "Range", footer: "Range" }
10541
+ ];
10542
+ function calculationsFor(field2, storeOps) {
10543
+ const offered = new Set(measuresFor(field2, storeOps, true));
10544
+ return NOTION_CALCULATIONS.filter((c) => offered.has(c.op));
10545
+ }
10525
10546
  var NUMERIC_ONLY = /* @__PURE__ */ new Set(["sum", "avg", "median"]);
10526
10547
  var ARITHMETIC2 = /* @__PURE__ */ new Set([
10527
10548
  "add",
@@ -10539,7 +10560,7 @@ var ARITHMETIC2 = /* @__PURE__ */ new Set([
10539
10560
  "ceiling",
10540
10561
  "floor"
10541
10562
  ]);
10542
- var ORDERED = /* @__PURE__ */ new Set(["min", "max"]);
10563
+ var ORDERED = /* @__PURE__ */ new Set(["min", "max", "range"]);
10543
10564
  function isNumericField(field2) {
10544
10565
  const kind = editorKindFor(field2);
10545
10566
  if (kind === "number" || kind === "currency" || kind === "percent" || kind === "rollup") return true;
@@ -13769,14 +13790,17 @@ function SummaryCell({
13769
13790
  value,
13770
13791
  rowCount,
13771
13792
  summaries,
13772
- onChoose
13793
+ onChoose,
13794
+ calculate = false
13773
13795
  }) {
13774
13796
  const offered = (0, import_react73.useMemo)(
13775
- () => summaries.operations ? measuresFor(field2, summaries.operations, firstColumn) : null,
13776
- [summaries.operations, field2, firstColumn]
13797
+ () => summaries.operations ? calculate ? calculationsFor(field2, summaries.operations).map((c) => c.op) : measuresFor(field2, summaries.operations, firstColumn) : null,
13798
+ [summaries.operations, field2, firstColumn, calculate]
13777
13799
  );
13778
- const label = chosen ? MEASURE_LABEL[chosen] ?? chosen : null;
13779
- const shown4 = chosen === void 0 ? null : summaries.loading && value === void 0 ? "\u2026" : value === null || value === void 0 ? "\u2014" : chosen === "count" || chosen === "filled" || chosen === "empty" || chosen === "unique" ? String(value) : null;
13800
+ const words2 = (op2, where) => calculate ? NOTION_CALCULATIONS.find((c) => c.op === op2)?.[where] ?? MEASURE_LABEL[op2] ?? op2 : MEASURE_LABEL[op2] ?? op2;
13801
+ const label = chosen ? words2(chosen, "footer") : null;
13802
+ const isDate4 = editorKindFor(field2) === "datetime";
13803
+ const shown4 = chosen === void 0 ? null : summaries.loading && value === void 0 ? "\u2026" : value === null || value === void 0 ? "\u2014" : chosen === "count" || chosen === "filled" || chosen === "empty" || chosen === "unique" ? String(value) : chosen === "percent_empty" || chosen === "percent_filled" ? `${Number(value.toFixed(2))}%` : chosen === "range" && isDate4 ? `${Math.round(value)} day${Math.round(value) === 1 ? "" : "s"}` : null;
13780
13804
  return /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_design_system42.DropdownMenu, { onOpenChange: (open) => open ? summaries.askOperations() : void 0, children: [
13781
13805
  /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system42.DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
13782
13806
  import_design_system42.Button,
@@ -13798,7 +13822,7 @@ function SummaryCell({
13798
13822
  // AGG-WITHHELD: the store withheld this column from her — say so, never a bare dash.
13799
13823
  /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "font-medium", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(WithheldValue, { field: field2, notice: summaries.withheld[field2.key] }) })
13800
13824
  ) : shown4 !== null ? /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "font-medium tabular-nums", children: shown4 }) : /* @__PURE__ */ (0, import_jsx_runtime48.jsx)("span", { className: "font-medium tabular-nums", children: /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(RecordValue, { field: field2, value, format: formatForField(field2) }) })
13801
- ] }) : "Summarize"
13825
+ ] }) : calculate ? "Calculate" : "Summarize"
13802
13826
  }
13803
13827
  ) }),
13804
13828
  /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_design_system42.DropdownMenuContent, { align: "start", children: [
@@ -13809,13 +13833,13 @@ function SummaryCell({
13809
13833
  "data-records-summary-choice": op2,
13810
13834
  onSelect: () => onChoose(op2),
13811
13835
  className: (0, import_design_system42.cn)(op2 === chosen && "font-medium"),
13812
- children: MEASURE_LABEL[op2] ?? op2
13836
+ children: words2(op2, "menu")
13813
13837
  },
13814
13838
  op2
13815
13839
  )),
13816
13840
  chosen ? /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(import_jsx_runtime48.Fragment, { children: [
13817
13841
  /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system42.DropdownMenuSeparator, {}),
13818
- /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system42.DropdownMenuItem, { onSelect: () => onChoose(null), children: "No summary" })
13842
+ /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(import_design_system42.DropdownMenuItem, { onSelect: () => onChoose(null), children: calculate ? "None" : "No summary" })
13819
13843
  ] }) : null
13820
13844
  ] })
13821
13845
  ] });
@@ -20492,7 +20516,6 @@ function UndoRedoLive({ source }) {
20492
20516
  }
20493
20517
  function UndoRedoPair({ face, onUndo, onRedo }) {
20494
20518
  const undo = { depth: { back: face.back, forward: face.forward }, busy: face.busy };
20495
- if (undo.depth.back === 0 && undo.depth.forward === 0) return null;
20496
20519
  return /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(import_jsx_runtime66.Fragment, { children: [
20497
20520
  /* @__PURE__ */ (0, import_jsx_runtime66.jsxs)(
20498
20521
  import_design_system57.Button,
@@ -21790,7 +21813,8 @@ function GridBody({
21790
21813
  newRowLabel,
21791
21814
  sortOverride,
21792
21815
  searchOverride,
21793
- onSearchChange
21816
+ onSearchChange,
21817
+ searchBox = true
21794
21818
  }) {
21795
21819
  const toolbarSlot = useTableToolbarSlot();
21796
21820
  const tableCard = (0, import_data_table11.useMatrxTableCard)();
@@ -22785,6 +22809,7 @@ function GridBody({
22785
22809
  value: summaries.values[columnId2],
22786
22810
  rowCount: summaries.rowCount,
22787
22811
  summaries,
22812
+ calculate: embedded,
22788
22813
  onChoose: (op2) => footerNow.current.choose(columnId2, op2)
22789
22814
  }
22790
22815
  );
@@ -23049,7 +23074,8 @@ function GridBody({
23049
23074
  [storePaged, query, queryTotal, classicInCard, records.data?.total]
23050
23075
  );
23051
23076
  const showAddLine = mayAddRow && rows.length > 0;
23052
- const showSummaryLine = rows.length > 0 && (embedded ? Object.keys(chosenSummaries).length > 0 : !merged || Object.keys(chosenSummaries).length > 0);
23077
+ const showSummaryLine = rows.length > 0 && (embedded || !merged || Object.keys(chosenSummaries).length > 0);
23078
+ const calculationChosen = Object.keys(chosenSummaries).length > 0;
23053
23079
  const footerFace = JSON.stringify([
23054
23080
  showAddLine ? added.busy : null,
23055
23081
  showSummaryLine ? [chosenSummaries, summaries.values, summaries.rowCount, summaries.loading] : null
@@ -23152,10 +23178,11 @@ function GridBody({
23152
23178
  search: !embedded,
23153
23179
  // EMBEDDED: Notion's magnifier — the design system's own search group, a button that
23154
23180
  // opens into a field. It is the same `query.search` the store's read asks.
23155
- ...embedded ? {
23181
+ ...embedded && searchBox ? {
23156
23182
  customSearch: /* @__PURE__ */ (0, import_jsx_runtime70.jsx)(
23157
23183
  import_design_system60.TapTargetSearchGroup,
23158
23184
  {
23185
+ surface: "solid",
23159
23186
  placeholder: "Search",
23160
23187
  value: searchOverride ?? query.search,
23161
23188
  onValueChange: (term) => {
@@ -23367,7 +23394,8 @@ function GridBody({
23367
23394
  noun,
23368
23395
  embedded,
23369
23396
  searchOverride,
23370
- onSearchChange
23397
+ onSearchChange,
23398
+ searchBox
23371
23399
  ]
23372
23400
  );
23373
23401
  const labelOfField = (0, import_react113.useCallback)(
@@ -23569,7 +23597,7 @@ function GridBody({
23569
23597
  {
23570
23598
  ...fills ? { className: "min-h-0 flex-1" } : {},
23571
23599
  frameHeight: fills ? "fill" : "content",
23572
- loadingRows: rememberedRowCount(rowMemoryKey, pageSize, fills ? "fill" : 5),
23600
+ loadingRows: rememberedRowCount(rowMemoryKey, pageSize, embedded ? fills ? "fill" : 5 : rememberedRowCount(`${tableId}:page:last`, pageSize, fills ? "fill" : 5)),
23573
23601
  ...!look.rowHeight ? { loadingRowHeight: RECORDS_GRID_ROW_PX } : {},
23574
23602
  data: rows,
23575
23603
  columns: tableColumns,
@@ -23589,7 +23617,7 @@ function GridBody({
23589
23617
  !look.wrap && "[&_tbody_td]:max-w-[28rem] [&_tbody_td]:overflow-hidden [&_tbody_td]:text-ellipsis [&_tbody_td]:whitespace-nowrap! [&_tbody_td_*]:whitespace-nowrap!",
23590
23618
  look.fit === "scroll" && "sm:[&>table]:w-max! sm:[&>table]:max-w-none! sm:[&>table]:min-w-full!",
23591
23619
  embedded && EMBEDDED_TABLE_CLASS,
23592
- embedded && !showSummaryLine && EMBEDDED_PLAIN_FOOTER_CLASS
23620
+ embedded && !calculationChosen && EMBEDDED_PLAIN_FOOTER_CLASS
23593
23621
  ),
23594
23622
  footerRows,
23595
23623
  ...embedded ? { copy: false } : copyConfig ? { copy: copyConfig } : {},
@@ -32944,6 +32972,7 @@ function ViewSwitcher({
32944
32972
  sortOverride,
32945
32973
  searchOverride,
32946
32974
  onSearchChange,
32975
+ searchBox,
32947
32976
  newRowLabel,
32948
32977
  className
32949
32978
  }) {
@@ -33011,6 +33040,7 @@ function ViewSwitcher({
33011
33040
  ...sortOverride !== void 0 ? { sortOverride } : {},
33012
33041
  ...searchOverride !== void 0 ? { searchOverride } : {},
33013
33042
  ...onSearchChange ? { onSearchChange } : {},
33043
+ ...searchBox === false ? { searchBox } : {},
33014
33044
  ...newRowLabel ? { newRowLabel } : {},
33015
33045
  ...filter ? { filter } : {},
33016
33046
  ...onClearFilter ? { onClearFilter } : {},
@@ -47662,6 +47692,7 @@ function TablePage({
47662
47692
  newRowLabel,
47663
47693
  searchOverride,
47664
47694
  onSearchChange,
47695
+ searchBox,
47665
47696
  pageSize: pageSizeAsked,
47666
47697
  seedViews,
47667
47698
  onLeave,
@@ -48551,6 +48582,7 @@ function TablePage({
48551
48582
  ...newRowLabel ? { newRowLabel } : {},
48552
48583
  ...searchOverride !== void 0 ? { searchOverride } : {},
48553
48584
  ...onSearchChange ? { onSearchChange } : {},
48585
+ ...searchBox === false ? { searchBox } : {},
48554
48586
  footer,
48555
48587
  openExport: () => setRail("export"),
48556
48588
  view: {
@@ -48857,6 +48889,7 @@ function EntityTablePageBody({
48857
48889
  presentation = "page",
48858
48890
  searchOverride,
48859
48891
  onSearchChange,
48892
+ searchBox = true,
48860
48893
  pageSize = presentation === "embedded" ? EMBEDDED_PAGE_SIZE : 100,
48861
48894
  layout: opened = "grid",
48862
48895
  onLayoutChange,
@@ -48944,9 +48977,10 @@ function EntityTablePageBody({
48944
48977
  ] }) : null,
48945
48978
  addRefused ? /* @__PURE__ */ (0, import_jsx_runtime156.jsx)(import_design_system142.ErrorBox, { className: "rounded border border-destructive/40 px-1.5 py-1 text-xs text-destructive", message: addRefused, operation: "Add a row", origin: "records-ui/entityViews" }) : null,
48946
48979
  entity.error ? /* @__PURE__ */ (0, import_jsx_runtime156.jsx)("p", { className: "px-1 py-3 text-xs text-muted-foreground", children: `This table could not be read: ${entity.error}` }) : layout === "board" && boardable ? /* @__PURE__ */ (0, import_jsx_runtime156.jsx)(EntityBoard, { entity, groupBy, onOpenRecord }) : /* @__PURE__ */ (0, import_jsx_runtime156.jsxs)(import_jsx_runtime156.Fragment, { children: [
48947
- presentation === "embedded" ? /* @__PURE__ */ (0, import_jsx_runtime156.jsx)("div", { className: "flex min-w-0 items-center justify-end", "data-records-entity-search": "", children: /* @__PURE__ */ (0, import_jsx_runtime156.jsx)(import_design_system141.TooltipProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime156.jsx)(
48980
+ presentation === "embedded" && searchBox ? /* @__PURE__ */ (0, import_jsx_runtime156.jsx)("div", { className: "flex min-w-0 items-center justify-end", "data-records-entity-search": "", children: /* @__PURE__ */ (0, import_jsx_runtime156.jsx)(import_design_system141.TooltipProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime156.jsx)(
48948
48981
  import_design_system141.TapTargetSearchGroup,
48949
48982
  {
48983
+ surface: "solid",
48950
48984
  placeholder: "Search",
48951
48985
  value: term,
48952
48986
  onValueChange: (next) => {
@@ -48983,6 +49017,7 @@ function TablePage2(props) {
48983
49017
  ...rest.pageSize ? { pageSize: rest.pageSize } : {},
48984
49018
  ...rest.searchOverride !== void 0 ? { searchOverride: rest.searchOverride } : {},
48985
49019
  ...rest.onSearchChange ? { onSearchChange: rest.onSearchChange } : {},
49020
+ ...rest.searchBox === false ? { searchBox: false } : {},
48986
49021
  ...layout ? { layout } : {},
48987
49022
  ...onLayoutChange ? { onLayoutChange } : {},
48988
49023
  ...groupBy ? { groupBy } : {},