@ai-matrx/design-system 0.25.0 → 0.27.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.
Files changed (81) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/dist/data-table/copy-helpers.cjs.map +1 -1
  3. package/dist/data-table/copy-helpers.d.cts +3 -1
  4. package/dist/data-table/copy-helpers.d.ts +3 -1
  5. package/dist/data-table/copy-helpers.js.map +1 -1
  6. package/dist/data-table/data-row-inspector.cjs.map +1 -1
  7. package/dist/data-table/data-row-inspector.js.map +1 -1
  8. package/dist/data-table/data-row-window.cjs.map +1 -1
  9. package/dist/data-table/data-row-window.js.map +1 -1
  10. package/dist/data-table/date-filter.cjs +145 -0
  11. package/dist/data-table/date-filter.cjs.map +1 -0
  12. package/dist/data-table/date-filter.d.cts +31 -0
  13. package/dist/data-table/date-filter.d.ts +31 -0
  14. package/dist/data-table/date-filter.js +125 -0
  15. package/dist/data-table/date-filter.js.map +1 -0
  16. package/dist/data-table/facets.cjs +118 -0
  17. package/dist/data-table/facets.cjs.map +1 -0
  18. package/dist/data-table/facets.d.cts +100 -0
  19. package/dist/data-table/facets.d.ts +100 -0
  20. package/dist/data-table/facets.js +98 -0
  21. package/dist/data-table/facets.js.map +1 -0
  22. package/dist/data-table/filter-engine.cjs +88 -0
  23. package/dist/data-table/filter-engine.cjs.map +1 -1
  24. package/dist/data-table/filter-engine.d.cts +3 -1
  25. package/dist/data-table/filter-engine.d.ts +3 -1
  26. package/dist/data-table/filter-engine.js +88 -0
  27. package/dist/data-table/filter-engine.js.map +1 -1
  28. package/dist/data-table/host.cjs.map +1 -1
  29. package/dist/data-table/host.d.cts +14 -3
  30. package/dist/data-table/host.d.ts +14 -3
  31. package/dist/data-table/host.js.map +1 -1
  32. package/dist/data-table/index.cjs +918 -152
  33. package/dist/data-table/index.cjs.map +1 -1
  34. package/dist/data-table/index.d.cts +5 -2
  35. package/dist/data-table/index.d.ts +5 -2
  36. package/dist/data-table/index.js +956 -189
  37. package/dist/data-table/index.js.map +1 -1
  38. package/dist/data-table/infer-filter.cjs +2 -0
  39. package/dist/data-table/infer-filter.cjs.map +1 -1
  40. package/dist/data-table/infer-filter.d.cts +4 -2
  41. package/dist/data-table/infer-filter.d.ts +4 -2
  42. package/dist/data-table/infer-filter.js +2 -0
  43. package/dist/data-table/infer-filter.js.map +1 -1
  44. package/dist/data-table/layered-filters.d.cts +3 -1
  45. package/dist/data-table/layered-filters.d.ts +3 -1
  46. package/dist/data-table/menu-targets.cjs +77 -0
  47. package/dist/data-table/menu-targets.cjs.map +1 -0
  48. package/dist/data-table/menu-targets.d.cts +108 -0
  49. package/dist/data-table/menu-targets.d.ts +108 -0
  50. package/dist/data-table/menu-targets.js +57 -0
  51. package/dist/data-table/menu-targets.js.map +1 -0
  52. package/dist/data-table/pagination.cjs.map +1 -1
  53. package/dist/data-table/pagination.js.map +1 -1
  54. package/dist/data-table/query-control.cjs.map +1 -1
  55. package/dist/data-table/query-control.d.cts +3 -1
  56. package/dist/data-table/query-control.d.ts +3 -1
  57. package/dist/data-table/query-control.js.map +1 -1
  58. package/dist/data-table/spreadsheet.cjs +61 -13
  59. package/dist/data-table/spreadsheet.cjs.map +1 -1
  60. package/dist/data-table/spreadsheet.d.cts +36 -1
  61. package/dist/data-table/spreadsheet.d.ts +36 -1
  62. package/dist/data-table/spreadsheet.js +61 -13
  63. package/dist/data-table/spreadsheet.js.map +1 -1
  64. package/dist/data-table/types.cjs.map +1 -1
  65. package/dist/data-table/types.d.cts +3 -1
  66. package/dist/data-table/types.d.ts +3 -1
  67. package/dist/data-table/url-state.d.cts +3 -1
  68. package/dist/data-table/url-state.d.ts +3 -1
  69. package/dist/data-table/uuid-cell.cjs.map +1 -1
  70. package/dist/data-table/uuid-cell.js.map +1 -1
  71. package/dist/data-table/xlsx.cjs.map +1 -1
  72. package/dist/data-table/xlsx.d.cts +3 -1
  73. package/dist/data-table/xlsx.d.ts +3 -1
  74. package/dist/data-table/xlsx.js.map +1 -1
  75. package/dist/field-formats/index.cjs +15 -0
  76. package/dist/field-formats/index.cjs.map +1 -1
  77. package/dist/field-formats/index.js +15 -0
  78. package/dist/field-formats/index.js.map +1 -1
  79. package/dist/{types-nlLCMiqd.d.ts → types-D9JBR6ZU.d.ts} +118 -5
  80. package/dist/{types-CmQrOz-T.d.cts → types-jQddSUnj.d.cts} +118 -5
  81. package/package.json +31 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,99 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.27.0 — 2026-09-20
4
+
5
+ **Released so the app can have what landed on main after 0.26.0 and had no
6
+ version to arrive in.**
7
+
8
+ The sources changed and the package did not move, so every consumer was still
9
+ resolving 0.26.0 while the monorepo built something else — the gap that makes a
10
+ green monorepo and a broken npm consumer look identical. What is in it:
11
+
12
+ - **Date column filters** — `data-table/date-filter` with its time-range
13
+ presets, wired through `filter-engine`, `infer-filter`, the saved-view
14
+ snapshot and `MatrxDataTable`.
15
+ - **The field-format registry** gained entries and the tests that judge them.
16
+ - `SavedViewsControl` covers the new snapshot shape.
17
+
18
+ No API was removed. This release nominates what was already on main; it is not
19
+ a design decision of its own.
20
+
21
+ ## 0.26.0 — 2026-09-20
22
+
23
+ **The four things a re-point of the owner's `/data` grid would have lost, now
24
+ first-class in the shared table.**
25
+
26
+ A capability-by-capability walk of that route against this package found four
27
+ gaps. Each is built here, in the shared layer, so every table in the product
28
+ gets it — not one route.
29
+
30
+ - **THE FOUR RIGHT-CLICK LEVELS.** The package exposed exactly ONE context
31
+ descriptor — the row — so a surface wanting a cell, a column or a selection
32
+ menu had to fork the table. It now answers all four. `menu-targets.ts` reads
33
+ which level the pointer was aimed at off the anchors the table renders
34
+ (`data-matrx-header-column-id` is new on every header; body cells already
35
+ carried `data-matrx-table-column-id`), with the precedence a spreadsheet
36
+ person expects: a click inside an extended range is the RANGE, never the one
37
+ cell under it — except a range of one cell, which is a cell.
38
+ `MatrxDataTableContextMenuConfig` gained `resolveCellContext`,
39
+ `resolveColumnContext`, `resolveSelectionContext` and `resolveTableContext`;
40
+ the host's `createDefaultRowContext` became `createDefaultMenuContext`, which
41
+ takes one payload carrying the level and what it is acting on. The package
42
+ still never interprets a menu — that stays the application's.
43
+ - **ENTER TO EDIT, AND TYPE TO REPLACE.** `classifyGridKey` had classified
44
+ `edit` and `editSeeded` since the gestures landed and nothing consumed them.
45
+ `useSpreadsheetGrid` now asks its consumer through `onEditRequest(address,
46
+ seed)` and consumes the keystroke only when an editor actually opened — so a
47
+ read-only column types nothing instead of swallowing the character.
48
+ `MatrxDataTable` performs it through the SAME `editRequest` door the row
49
+ menu's "Edit row" already used, and a seeded editor opens holding the typed
50
+ character rather than the old value.
51
+ - **FILTER VALUE COUNTS.** A "Contains…" box asks the reader to already know
52
+ what is in the column. `facets.ts` counts the real values, blanks included as
53
+ a countable option, and `MatrxDataTableFacetsConfig` decides where the counts
54
+ come from: the browser when it holds every row the counts describe, the
55
+ consumer's `source` when it does not — and when neither can answer for the
56
+ whole table, the popover SAYS the counts cover only the loaded rows. A
57
+ complete answer says nothing at all. Counting happens when someone opens a
58
+ popover, never on first paint.
59
+ - **VALIDATION REFUSALS, IN THE CELL, IN WORDS.** `data-table/validation` was a
60
+ published module the table never called. `edit.validate` now judges a value
61
+ before it enters the draft and a returned sentence is a refusal: the editor
62
+ stays open holding what the person typed and the sentence renders under the
63
+ cell in amber, clearing the moment they change the value. The same judgement
64
+ screens paste, fill and clear through `spreadsheet.validateCell` — a rule a
65
+ paste can walk around is not a rule.
66
+
67
+ **Two defects only a real browser could find**, both in the seam between the
68
+ cell cursor and the editor, both green in jsdom:
69
+
70
+ - **A click selected the cell and then every keystroke went to the document
71
+ body.** `preventDefault` on a mousedown also prevents focus, so the grid
72
+ container never got it — and Enter, a typed character, an arrow and Cmd-Z all
73
+ landed on nothing. A jsdom test dispatches the keydown ON the container by
74
+ hand and can never see it. The cell cursor now focuses the grid.
75
+ - **A click on a string-editable cell dropped straight into a text box**, which
76
+ is THE CLICK LAW broken at the one place the two paths meet: the same click
77
+ starts a range, and an editor opening under the pointer eats the drag and the
78
+ next keystroke. With spreadsheet gestures on, the editor opens the way it does
79
+ in Excel — Enter, F2, the hover pencil, or typing — and an explicit
80
+ `editTrigger: "click"` does not override it.
81
+
82
+ New entry points: `/data-table/facets` and `/data-table/menu-targets`, both
83
+ pure. Suite: 101 files / 1,073 passed. Red twin
84
+ `MatrxDataTable.four-levels.red.tsx`: 13/13 fail. Three absence-asserting
85
+ clauses and one rendering clause are deliberately not twinned, and the red file
86
+ says which and why. Browser proof on a production build, 1440x900 headless
87
+ Chromium, zero console errors: right-click answered `cell · Amount`,
88
+ `column · Status`, `row · Row` and `selection · 16 cells`; Enter opened the cell
89
+ on `Ada` and typing `Z` opened it on `Z`, committing `Zara`; the Status filter
90
+ listed five values at 400 each and filtering to Done left only Done on screen;
91
+ a 500-row table declaring 84,312 said *"These counts cover the 500 rows loaded
92
+ here, not the whole table."* and a complete answer said nothing; a blank Owner
93
+ was refused in the cell with *"Owner cannot be empty. Every job needs someone on
94
+ it."* while the editor stayed open, and the same rule refused a paste
95
+ (`Pasted 0 cells.`) that never touches an editor.
96
+
3
97
  ## 0.25.0 — 2026-09-20
4
98
 
5
99
  **Five defects a real browser found that jsdom could not, and the table's own
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/data-table/tableCopy.ts","../../src/data-table/filter-engine.ts"],"sourcesContent":["import type { AgentPayloadInput } from \"./copy\";\nimport { normalizeTransferJson } from \"@ai-matrx/kit/content-transfer\";\nimport type { MatrxColumnDef, MatrxDataTableCopyConfig } from \"./types\";\nimport { columnId, getCellValue, stringifyCellValue } from \"./filter-engine\";\n\n/**\n * Typed, declared-column capture for MatrxDataTable's built-in Alchemy source.\n * It deliberately runs only at the transfer boundary: accessor functions can\n * throw useful capture errors, and rendering never evaluates them for copying.\n */\nexport function projectDefaultTableCopyRow<T>(\n row: T,\n columns: MatrxColumnDef<T>[],\n): Record<string, unknown> {\n const record: Record<string, unknown> = Object.create(null) as Record<string, unknown>;\n for (const column of columns) {\n const value = getCellValue(row, column);\n if (value !== undefined) record[columnId(column)] = value;\n }\n return normalizeTransferJson(record, { omitUndefinedObjectProperties: true }) as Record<string, unknown>;\n}\n\n/** Builds the default table's AI envelope from one already validated capture. */\nexport function buildDefaultTableViewAgentInput<T>(\n config: MatrxDataTableCopyConfig<T>,\n rows: T[],\n all: T[],\n columns: MatrxColumnDef<T>[],\n meta?: Parameters<typeof buildViewAgentInput<T>>[3],\n): AgentPayloadInput {\n const projected = rows.map((row) => projectDefaultTableCopyRow(row, columns));\n return {\n kind: config.listKind,\n location: config.location,\n description:\n meta?.scope === \"selected\"\n ? `${projected.length} selected ${projected.length === 1 ? config.label : (config.listLabel ?? `${config.label} rows`)}.`\n : (config.listDescription ?? `${config.listLabel ?? config.label} — current filtered/sorted view.`),\n data: projected,\n summary: projected.map((record) => formatDefaultTableCopyRecord(record, columns)).join(\"\\n---\\n\"),\n attributes: {\n visible_count: projected.length,\n total_count: all.length,\n search: meta?.search || undefined,\n search_match: meta?.search ? meta.searchMatchMode : undefined,\n any_of: meta?.anyOf || undefined,\n active_filters: meta?.filterCount ?? 0,\n sort: meta?.sort || undefined,\n scope: meta?.scope ?? \"view\",\n ...config.listAttributes?.(rows, all),\n },\n ...(config.listContext ? { context: config.listContext(rows, all) } : {}),\n };\n}\n\n/** Builds the default row AI envelope from one validated projection. */\nexport function buildDefaultTableRowAgentInput<T>(\n config: MatrxDataTableCopyConfig<T>,\n row: T,\n columns: MatrxColumnDef<T>[],\n): AgentPayloadInput {\n const projected = projectDefaultTableCopyRow(row, columns);\n return {\n kind: config.rowKind,\n location: config.location,\n description: config.rowDescription ?? `One ${config.label} row.`,\n data: projected,\n summary: formatDefaultTableCopyRecord(projected, columns),\n ...(config.rowAttributes ? { attributes: config.rowAttributes(row) } : {}),\n };\n}\n\nexport function formatDefaultTableCopyRecord<T>(\n record: Record<string, unknown>,\n columns: MatrxColumnDef<T>[],\n): string {\n return columns\n .map((column) => {\n const id = columnId(column);\n const label = column.label ?? (typeof column.header === \"string\" ? column.header : id);\n return `${label}: ${stringifyCellValue(record[id])}`;\n })\n .join(\"\\n\");\n}\n\n/** Human markdown table of the current view (visible columns × rows). */\nexport function rowsToMarkdownTable<T>(\n rows: T[],\n columns: MatrxColumnDef<T>[],\n): string {\n const cols = columns.filter((c) => c.filter !== false || c.accessorKey);\n if (cols.length === 0) return \"\";\n const headers = cols.map((c) => {\n if (typeof c.header === \"string\") return c.header;\n return columnId(c);\n });\n const lines = [\n `| ${headers.join(\" | \")} |`,\n `| ${headers.map(() => \"---\").join(\" | \")} |`,\n ];\n for (const row of rows) {\n const cells = cols.map((c) =>\n stringifyCellValue(getCellValue(row, c)).replace(/\\|/g, \"\\\\|\"),\n );\n lines.push(`| ${cells.join(\" | \")} |`);\n }\n return lines.join(\"\\n\");\n}\n\nexport function buildRowAgentInput<T>(\n config: MatrxDataTableCopyConfig<T>,\n row: T,\n): AgentPayloadInput {\n return {\n kind: config.rowKind,\n location: config.location,\n description: config.rowDescription ?? `One ${config.label} row.`,\n data: config.agentRow ? config.agentRow(row) : row,\n summary: config.humanRow(row),\n ...(config.rowAttributes ? { attributes: config.rowAttributes(row) } : {}),\n };\n}\n\nexport function buildViewAgentInput<T>(\n config: MatrxDataTableCopyConfig<T>,\n visible: T[],\n all: T[],\n meta?: {\n search?: string;\n searchMatchMode?: string;\n anyOf?: string;\n filterCount?: number;\n sort?: string | null;\n /** `custom` = shaped in the copy-subset window (filter & sort before copying). */\n scope?: \"view\" | \"selected\" | \"custom\";\n },\n): AgentPayloadInput {\n return {\n kind: config.listKind,\n location: config.location,\n description:\n meta?.scope === \"selected\"\n ? `${visible.length} selected ${visible.length === 1 ? config.label : (config.listLabel ?? `${config.label} rows`)}.`\n : (config.listDescription ??\n `${config.listLabel ?? config.label} — current filtered/sorted view.`),\n data: visible.map((r) => (config.agentRow ? config.agentRow(r) : r)),\n summary: visible.map((r) => config.humanRow(r)).join(\"\\n---\\n\"),\n attributes: {\n visible_count: visible.length,\n total_count: all.length,\n search: meta?.search || undefined,\n search_match: meta?.search ? meta.searchMatchMode : undefined,\n any_of: meta?.anyOf || undefined,\n active_filters: meta?.filterCount ?? 0,\n sort: meta?.sort || undefined,\n scope: meta?.scope ?? \"view\",\n ...config.listAttributes?.(visible, all),\n },\n ...(config.listContext ? { context: config.listContext(visible, all) } : {}),\n };\n}\n\n/** CSV of the current view (visible columns × rows), for ExportMenu. */\nexport function rowsToCsvFromColumns<T>(\n rows: T[],\n columns: MatrxColumnDef<T>[],\n): string {\n const cols = columns.filter((c) => c.filter !== false || c.accessorKey);\n const escape = (value: string) =>\n /[\",\\n\\r]/.test(value) ? `\"${value.replace(/\"/g, '\"\"')}\"` : value;\n const lines = [\n cols\n .map((c) => escape(typeof c.header === \"string\" ? c.header : columnId(c)))\n .join(\",\"),\n ];\n for (const row of rows) {\n lines.push(\n cols\n .map((c) => escape(stringifyCellValue(getCellValue(row, c))))\n .join(\",\"),\n );\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * The same rows the CSV export produces, as records — the shape a Google Sheet\n * (or any other row destination) takes.\n *\n * It shares `rowsToCsvFromColumns`'s column selection and cell stringification\n * ON PURPOSE: a user who exports the view as CSV and a user who sends it to a\n * Sheet must get the SAME columns, in the same order, with the same values.\n * Two independent row builders is how those two answers start disagreeing.\n */\nexport function rowsToRecordsFromColumns<T>(\n rows: T[],\n columns: MatrxColumnDef<T>[],\n): Array<Record<string, unknown>> {\n const cols = columns.filter((c) => c.filter !== false || c.accessorKey);\n const headers = cols.map((c) =>\n typeof c.header === \"string\" ? c.header : columnId(c),\n );\n return rows.map((row) => {\n const record: Record<string, unknown> = {};\n cols.forEach((c, index) => {\n record[headers[index] ?? columnId(c)] = stringifyCellValue(getCellValue(row, c));\n });\n return record;\n });\n}\n\nexport function buildViewHuman<T>(\n config: MatrxDataTableCopyConfig<T>,\n visible: T[],\n columns: MatrxColumnDef<T>[],\n): string {\n const md = rowsToMarkdownTable(visible, columns);\n const summaries = visible.map((r) => config.humanRow(r)).join(\"\\n\\n\");\n return [\n `${config.listLabel ?? config.label} (${visible.length} rows)`,\n \"\",\n md,\n \"\",\n \"---\",\n \"\",\n summaries,\n ].join(\"\\n\");\n}\n","import type {\n ColumnFilterValue,\n ColumnFiltersState,\n MatrxColumnDef,\n SortState,\n TableSearchMatchMode,\n} from \"./types\";\nimport {\n completeLayeredFilterRules,\n layeredFilterMatchesValue,\n type LayeredFilterRule,\n} from \"./layered-filters\";\n\n/**\n * Sentinel select-filter values for \"no value\" / \"any value\" — every select\n * filter offers them automatically when the column has empty cells, so any\n * table can isolate rows missing a value (or having one) without extra config.\n */\nexport const SELECT_EMPTY_VALUE = \"__empty__\";\nexport const SELECT_NOT_EMPTY_VALUE = \"__not_empty__\";\n/** Exact SQL-style NULL selection, distinct from the empty-cell option. */\nexport const SELECT_NULL_VALUE = \"__null__\";\n\nexport function columnId<T>(col: MatrxColumnDef<T>): string {\n if (col.id) return col.id;\n if (col.accessorKey) return String(col.accessorKey);\n throw new Error(\"MatrxDataTable column requires `id` or `accessorKey`\");\n}\n\nexport function getCellValue<T>(row: T, col: MatrxColumnDef<T>): unknown {\n if (col.accessorFn) return col.accessorFn(row);\n if (col.accessorKey) {\n return (row as Record<string, unknown>)[col.accessorKey];\n }\n return undefined;\n}\n\nexport function getFilterValue<T>(row: T, col: MatrxColumnDef<T>): unknown {\n return col.filterValue ? col.filterValue(row) : getCellValue(row, col);\n}\n\nexport function getSortValue<T>(row: T, col: MatrxColumnDef<T>): unknown {\n return col.sortValue ? col.sortValue(row) : getCellValue(row, col);\n}\n\nexport function stringifyCellValue(value: unknown): string {\n if (value == null) return \"\";\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return String(value);\n }\n if (value instanceof Date) return value.toISOString();\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\n/** Unicode-aware tokenization shared by whole-word table search. */\nexport function tableSearchWords(value: string): string[] {\n return value\n .toLocaleLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter(Boolean);\n}\n\n/**\n * Match the primary table search against one row's searchable text. In\n * whole-word mode every entered word must exist as a complete token, so\n * `are` matches `we are ready` but never `hardware`.\n */\nexport function matchesTableSearch(\n searchableText: string,\n query: string,\n mode: TableSearchMatchMode = \"contains\",\n): boolean {\n const normalizedQuery = query.trim().toLocaleLowerCase();\n if (!normalizedQuery) return true;\n if (mode === \"contains\") {\n return searchableText.toLocaleLowerCase().includes(normalizedQuery);\n }\n const actualWords = new Set(tableSearchWords(searchableText));\n const expectedWords = tableSearchWords(normalizedQuery);\n return (\n expectedWords.length > 0 &&\n expectedWords.every((word) => actualWords.has(word))\n );\n}\n\nfunction compareValues(a: unknown, b: unknown): number {\n if (a == null && b == null) return 0;\n if (a == null) return -1;\n if (b == null) return 1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n if (typeof a === \"boolean\" && typeof b === \"boolean\") {\n return Number(a) - Number(b);\n }\n return stringifyCellValue(a).localeCompare(stringifyCellValue(b), undefined, {\n numeric: true,\n sensitivity: \"base\",\n });\n}\n\n/** Select filters treat array-valued cells as sets, never as serialized JSON choices. */\nexport function selectCellValues(value: unknown): string[] {\n return (Array.isArray(value) ? value : [value])\n .map(stringifyCellValue)\n .filter((item) => item !== \"\");\n}\n\nexport function passesColumnFilter(\n value: unknown,\n filter: ColumnFilterValue,\n): boolean {\n const matches = (() => {\n switch (filter.kind) {\n case \"text\": {\n const s = stringifyCellValue(value);\n if (filter.mode === \"null\") return value == null;\n if (filter.mode === \"not_null\") return value != null;\n if (filter.mode === \"empty\") return s.trim() === \"\";\n if (filter.mode === \"not_empty\") return s.trim() !== \"\";\n const q = filter.value.trim().toLowerCase();\n if (!q) return true;\n return s.toLowerCase().includes(q);\n }\n case \"select\": {\n // `values: []` is an explicit multi-select state: the user deselected\n // every option, so it must produce an empty result. Legacy single-value\n // filters and an absent `values` field retain their clear/all behavior.\n if (filter.values !== undefined && filter.values.length === 0) {\n return false;\n }\n const selected = filter.values ??\n (filter.value && filter.value !== \"__all__\" ? [filter.value] : []);\n if (selected.length === 0) return true;\n const values = selectCellValues(value);\n // OR semantics across the set; sentinels compose (\"A or (empty)\").\n return selected.some((v) =>\n v === SELECT_NULL_VALUE\n ? value == null\n : v === SELECT_EMPTY_VALUE\n ? values.length === 0\n : v === SELECT_NOT_EMPTY_VALUE\n ? values.length > 0\n : v === \"\"\n ? value === \"\" || (Array.isArray(value) && value.includes(\"\"))\n : values.includes(v),\n );\n }\n case \"boolean\": {\n return Boolean(value) === filter.value;\n }\n case \"number\": {\n // A missing cell is not zero. Without this guard, Number(\"\") made a\n // blank numeric value pass filters whose range included zero.\n const raw = stringifyCellValue(value).trim();\n if (raw === \"\") return false;\n const normalized = raw.replace(/[^0-9.-]/g, \"\");\n if (normalized === \"\") return false;\n const n = typeof value === \"number\"\n ? value\n : Number(normalized);\n if (!Number.isFinite(n)) return false;\n if (filter.min !== undefined && n < filter.min) return false;\n if (filter.max !== undefined && n > filter.max) return false;\n return true;\n }\n }\n })();\n return filter.negated ? !matches : matches;\n}\n\nexport function filterAndSortRows<T>(\n rows: T[],\n columns: MatrxColumnDef<T>[],\n columnFilters: ColumnFiltersState,\n sort: SortState | null,\n globalSearch: string,\n anyOf?: { columnIds: string[]; query: string },\n layeredFilters?: readonly LayeredFilterRule[],\n globalSearchMode: TableSearchMatchMode = \"contains\",\n searchText?: (row: T) => string,\n): T[] {\n const colById = new Map(columns.map((c) => [columnId(c), c]));\n const q = globalSearch.trim().toLowerCase();\n const anyQ = anyOf?.query.trim().toLowerCase() ?? \"\";\n const activeLayeredFilters = completeLayeredFilterRules(layeredFilters);\n\n let result = rows.filter((row) => {\n for (const [id, filter] of Object.entries(columnFilters)) {\n if (!filter) continue;\n const col = colById.get(id);\n if (!col || col.filter === false) continue;\n if (!passesColumnFilter(getFilterValue(row, col), filter)) return false;\n }\n\n for (const rule of activeLayeredFilters) {\n const col = colById.get(rule.field);\n if (!col || !layeredFilterMatchesValue(getFilterValue(row, col), rule)) {\n return false;\n }\n }\n\n if (anyQ && anyOf) {\n const hit = anyOf.columnIds.some((id) => {\n const col = colById.get(id);\n if (!col) return false;\n return stringifyCellValue(getFilterValue(row, col))\n .toLowerCase()\n .includes(anyQ);\n });\n if (!hit) return false;\n }\n\n if (!q) return true;\n const searchableValues = columns\n .filter(\n (col) =>\n col.filter !== false || Boolean(col.accessorKey || col.accessorFn),\n )\n .map((col) => stringifyCellValue(getFilterValue(row, col)));\n const additionalSearchText = searchText?.(row);\n if (additionalSearchText) searchableValues.push(additionalSearchText);\n if (globalSearchMode === \"contains\") {\n return searchableValues.some((value) =>\n matchesTableSearch(value, q, \"contains\"),\n );\n }\n return matchesTableSearch(searchableValues.join(\" \"), q, \"whole_words\");\n });\n\n if (sort) {\n const col = colById.get(sort.id);\n if (col && col.sortable !== false) {\n const dir = sort.direction === \"asc\" ? 1 : -1;\n result = [...result].sort((a, b) => {\n const va = getSortValue(a, col);\n const vb = getSortValue(b, col);\n // Empty cells (null / \"\") sort LAST in both directions — sorting a\n // column should surface its values, never a wall of blanks.\n const ea = va == null || stringifyCellValue(va) === \"\";\n const eb = vb == null || stringifyCellValue(vb) === \"\";\n if (ea !== eb) return ea ? 1 : -1;\n return compareValues(va, vb) * dir;\n });\n }\n }\n\n return result;\n}\n\nexport function countActiveColumnFilters(filters: ColumnFiltersState): number {\n let n = 0;\n for (const f of Object.values(filters)) {\n if (!f) continue;\n switch (f.kind) {\n case \"text\":\n if (f.value.trim() || (f.mode && f.mode !== \"contains\")) n += 1;\n break;\n case \"select\":\n if (f.values !== undefined || (f.value && f.value !== \"__all__\"))\n n += 1;\n break;\n case \"boolean\":\n n += 1;\n break;\n case \"number\":\n if (f.min !== undefined || f.max !== undefined) n += 1;\n break;\n }\n }\n return n;\n}\n\nexport function isColumnFilterActive(\n filter: ColumnFilterValue | undefined,\n): boolean {\n if (!filter) return false;\n switch (filter.kind) {\n case \"text\":\n return (\n filter.value.trim().length > 0 ||\n (filter.mode !== undefined && filter.mode !== \"contains\")\n );\n case \"select\":\n return filter.values !== undefined\n ? true\n : Boolean(filter.value) && filter.value !== \"__all__\";\n case \"boolean\":\n return true;\n case \"number\":\n return filter.min !== undefined || filter.max !== undefined;\n }\n}\n\n/** Apply draft edits onto a row for display (shallow field merge). */\nexport function applyRowEdits<T>(\n row: T,\n edits: Record<string, unknown> | undefined,\n): T {\n if (!edits || Object.keys(edits).length === 0) return row;\n return { ...row, ...edits };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/data-table/tableCopy.ts","../../src/data-table/filter-engine.ts"],"sourcesContent":["import type { AgentPayloadInput } from \"./copy\";\nimport { normalizeTransferJson } from \"@ai-matrx/kit/content-transfer\";\nimport type { MatrxColumnDef, MatrxDataTableCopyConfig } from \"./types\";\nimport { columnId, getCellValue, stringifyCellValue } from \"./filter-engine\";\n\n/**\n * Typed, declared-column capture for MatrxDataTable's built-in Alchemy source.\n * It deliberately runs only at the transfer boundary: accessor functions can\n * throw useful capture errors, and rendering never evaluates them for copying.\n */\nexport function projectDefaultTableCopyRow<T>(\n row: T,\n columns: MatrxColumnDef<T>[],\n): Record<string, unknown> {\n const record: Record<string, unknown> = Object.create(null) as Record<string, unknown>;\n for (const column of columns) {\n const value = getCellValue(row, column);\n if (value !== undefined) record[columnId(column)] = value;\n }\n return normalizeTransferJson(record, { omitUndefinedObjectProperties: true }) as Record<string, unknown>;\n}\n\n/** Builds the default table's AI envelope from one already validated capture. */\nexport function buildDefaultTableViewAgentInput<T>(\n config: MatrxDataTableCopyConfig<T>,\n rows: T[],\n all: T[],\n columns: MatrxColumnDef<T>[],\n meta?: Parameters<typeof buildViewAgentInput<T>>[3],\n): AgentPayloadInput {\n const projected = rows.map((row) => projectDefaultTableCopyRow(row, columns));\n return {\n kind: config.listKind,\n location: config.location,\n description:\n meta?.scope === \"selected\"\n ? `${projected.length} selected ${projected.length === 1 ? config.label : (config.listLabel ?? `${config.label} rows`)}.`\n : (config.listDescription ?? `${config.listLabel ?? config.label} — current filtered/sorted view.`),\n data: projected,\n summary: projected.map((record) => formatDefaultTableCopyRecord(record, columns)).join(\"\\n---\\n\"),\n attributes: {\n visible_count: projected.length,\n total_count: all.length,\n search: meta?.search || undefined,\n search_match: meta?.search ? meta.searchMatchMode : undefined,\n any_of: meta?.anyOf || undefined,\n active_filters: meta?.filterCount ?? 0,\n sort: meta?.sort || undefined,\n scope: meta?.scope ?? \"view\",\n ...config.listAttributes?.(rows, all),\n },\n ...(config.listContext ? { context: config.listContext(rows, all) } : {}),\n };\n}\n\n/** Builds the default row AI envelope from one validated projection. */\nexport function buildDefaultTableRowAgentInput<T>(\n config: MatrxDataTableCopyConfig<T>,\n row: T,\n columns: MatrxColumnDef<T>[],\n): AgentPayloadInput {\n const projected = projectDefaultTableCopyRow(row, columns);\n return {\n kind: config.rowKind,\n location: config.location,\n description: config.rowDescription ?? `One ${config.label} row.`,\n data: projected,\n summary: formatDefaultTableCopyRecord(projected, columns),\n ...(config.rowAttributes ? { attributes: config.rowAttributes(row) } : {}),\n };\n}\n\nexport function formatDefaultTableCopyRecord<T>(\n record: Record<string, unknown>,\n columns: MatrxColumnDef<T>[],\n): string {\n return columns\n .map((column) => {\n const id = columnId(column);\n const label = column.label ?? (typeof column.header === \"string\" ? column.header : id);\n return `${label}: ${stringifyCellValue(record[id])}`;\n })\n .join(\"\\n\");\n}\n\n/** Human markdown table of the current view (visible columns × rows). */\nexport function rowsToMarkdownTable<T>(\n rows: T[],\n columns: MatrxColumnDef<T>[],\n): string {\n const cols = columns.filter((c) => c.filter !== false || c.accessorKey);\n if (cols.length === 0) return \"\";\n const headers = cols.map((c) => {\n if (typeof c.header === \"string\") return c.header;\n return columnId(c);\n });\n const lines = [\n `| ${headers.join(\" | \")} |`,\n `| ${headers.map(() => \"---\").join(\" | \")} |`,\n ];\n for (const row of rows) {\n const cells = cols.map((c) =>\n stringifyCellValue(getCellValue(row, c)).replace(/\\|/g, \"\\\\|\"),\n );\n lines.push(`| ${cells.join(\" | \")} |`);\n }\n return lines.join(\"\\n\");\n}\n\nexport function buildRowAgentInput<T>(\n config: MatrxDataTableCopyConfig<T>,\n row: T,\n): AgentPayloadInput {\n return {\n kind: config.rowKind,\n location: config.location,\n description: config.rowDescription ?? `One ${config.label} row.`,\n data: config.agentRow ? config.agentRow(row) : row,\n summary: config.humanRow(row),\n ...(config.rowAttributes ? { attributes: config.rowAttributes(row) } : {}),\n };\n}\n\nexport function buildViewAgentInput<T>(\n config: MatrxDataTableCopyConfig<T>,\n visible: T[],\n all: T[],\n meta?: {\n search?: string;\n searchMatchMode?: string;\n anyOf?: string;\n filterCount?: number;\n sort?: string | null;\n /** `custom` = shaped in the copy-subset window (filter & sort before copying). */\n scope?: \"view\" | \"selected\" | \"custom\";\n },\n): AgentPayloadInput {\n return {\n kind: config.listKind,\n location: config.location,\n description:\n meta?.scope === \"selected\"\n ? `${visible.length} selected ${visible.length === 1 ? config.label : (config.listLabel ?? `${config.label} rows`)}.`\n : (config.listDescription ??\n `${config.listLabel ?? config.label} — current filtered/sorted view.`),\n data: visible.map((r) => (config.agentRow ? config.agentRow(r) : r)),\n summary: visible.map((r) => config.humanRow(r)).join(\"\\n---\\n\"),\n attributes: {\n visible_count: visible.length,\n total_count: all.length,\n search: meta?.search || undefined,\n search_match: meta?.search ? meta.searchMatchMode : undefined,\n any_of: meta?.anyOf || undefined,\n active_filters: meta?.filterCount ?? 0,\n sort: meta?.sort || undefined,\n scope: meta?.scope ?? \"view\",\n ...config.listAttributes?.(visible, all),\n },\n ...(config.listContext ? { context: config.listContext(visible, all) } : {}),\n };\n}\n\n/** CSV of the current view (visible columns × rows), for ExportMenu. */\nexport function rowsToCsvFromColumns<T>(\n rows: T[],\n columns: MatrxColumnDef<T>[],\n): string {\n const cols = columns.filter((c) => c.filter !== false || c.accessorKey);\n const escape = (value: string) =>\n /[\",\\n\\r]/.test(value) ? `\"${value.replace(/\"/g, '\"\"')}\"` : value;\n const lines = [\n cols\n .map((c) => escape(typeof c.header === \"string\" ? c.header : columnId(c)))\n .join(\",\"),\n ];\n for (const row of rows) {\n lines.push(\n cols\n .map((c) => escape(stringifyCellValue(getCellValue(row, c))))\n .join(\",\"),\n );\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * The same rows the CSV export produces, as records — the shape a Google Sheet\n * (or any other row destination) takes.\n *\n * It shares `rowsToCsvFromColumns`'s column selection and cell stringification\n * ON PURPOSE: a user who exports the view as CSV and a user who sends it to a\n * Sheet must get the SAME columns, in the same order, with the same values.\n * Two independent row builders is how those two answers start disagreeing.\n */\nexport function rowsToRecordsFromColumns<T>(\n rows: T[],\n columns: MatrxColumnDef<T>[],\n): Array<Record<string, unknown>> {\n const cols = columns.filter((c) => c.filter !== false || c.accessorKey);\n const headers = cols.map((c) =>\n typeof c.header === \"string\" ? c.header : columnId(c),\n );\n return rows.map((row) => {\n const record: Record<string, unknown> = {};\n cols.forEach((c, index) => {\n record[headers[index] ?? columnId(c)] = stringifyCellValue(getCellValue(row, c));\n });\n return record;\n });\n}\n\nexport function buildViewHuman<T>(\n config: MatrxDataTableCopyConfig<T>,\n visible: T[],\n columns: MatrxColumnDef<T>[],\n): string {\n const md = rowsToMarkdownTable(visible, columns);\n const summaries = visible.map((r) => config.humanRow(r)).join(\"\\n\\n\");\n return [\n `${config.listLabel ?? config.label} (${visible.length} rows)`,\n \"\",\n md,\n \"\",\n \"---\",\n \"\",\n summaries,\n ].join(\"\\n\");\n}\n","import type {\n ColumnFilterValue,\n ColumnFiltersState,\n MatrxColumnDef,\n SortState,\n TableSearchMatchMode,\n} from \"./types\";\nimport {\n completeLayeredFilterRules,\n layeredFilterMatchesValue,\n type LayeredFilterRule,\n} from \"./layered-filters\";\nimport { dateFilterBounds } from \"./date-filter\";\n\n/**\n * Sentinel select-filter values for \"no value\" / \"any value\" — every select\n * filter offers them automatically when the column has empty cells, so any\n * table can isolate rows missing a value (or having one) without extra config.\n */\nexport const SELECT_EMPTY_VALUE = \"__empty__\";\nexport const SELECT_NOT_EMPTY_VALUE = \"__not_empty__\";\n/** Exact SQL-style NULL selection, distinct from the empty-cell option. */\nexport const SELECT_NULL_VALUE = \"__null__\";\n\nexport function columnId<T>(col: MatrxColumnDef<T>): string {\n if (col.id) return col.id;\n if (col.accessorKey) return String(col.accessorKey);\n throw new Error(\"MatrxDataTable column requires `id` or `accessorKey`\");\n}\n\nexport function getCellValue<T>(row: T, col: MatrxColumnDef<T>): unknown {\n if (col.accessorFn) return col.accessorFn(row);\n if (col.accessorKey) {\n return (row as Record<string, unknown>)[col.accessorKey];\n }\n return undefined;\n}\n\nexport function getFilterValue<T>(row: T, col: MatrxColumnDef<T>): unknown {\n return col.filterValue ? col.filterValue(row) : getCellValue(row, col);\n}\n\nexport function getSortValue<T>(row: T, col: MatrxColumnDef<T>): unknown {\n return col.sortValue ? col.sortValue(row) : getCellValue(row, col);\n}\n\nexport function stringifyCellValue(value: unknown): string {\n if (value == null) return \"\";\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return String(value);\n }\n if (value instanceof Date) return value.toISOString();\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\n/** Unicode-aware tokenization shared by whole-word table search. */\nexport function tableSearchWords(value: string): string[] {\n return value\n .toLocaleLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter(Boolean);\n}\n\n/**\n * Match the primary table search against one row's searchable text. In\n * whole-word mode every entered word must exist as a complete token, so\n * `are` matches `we are ready` but never `hardware`.\n */\nexport function matchesTableSearch(\n searchableText: string,\n query: string,\n mode: TableSearchMatchMode = \"contains\",\n): boolean {\n const normalizedQuery = query.trim().toLocaleLowerCase();\n if (!normalizedQuery) return true;\n if (mode === \"contains\") {\n return searchableText.toLocaleLowerCase().includes(normalizedQuery);\n }\n const actualWords = new Set(tableSearchWords(searchableText));\n const expectedWords = tableSearchWords(normalizedQuery);\n return (\n expectedWords.length > 0 &&\n expectedWords.every((word) => actualWords.has(word))\n );\n}\n\nfunction compareValues(a: unknown, b: unknown): number {\n if (a == null && b == null) return 0;\n if (a == null) return -1;\n if (b == null) return 1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n if (typeof a === \"boolean\" && typeof b === \"boolean\") {\n return Number(a) - Number(b);\n }\n return stringifyCellValue(a).localeCompare(stringifyCellValue(b), undefined, {\n numeric: true,\n sensitivity: \"base\",\n });\n}\n\n/** Select filters treat array-valued cells as sets, never as serialized JSON choices. */\nexport function selectCellValues(value: unknown): string[] {\n return (Array.isArray(value) ? value : [value])\n .map(stringifyCellValue)\n .filter((item) => item !== \"\");\n}\n\nexport function passesColumnFilter(\n value: unknown,\n filter: ColumnFilterValue,\n): boolean {\n const matches = (() => {\n switch (filter.kind) {\n case \"text\": {\n const s = stringifyCellValue(value);\n if (filter.mode === \"null\") return value == null;\n if (filter.mode === \"not_null\") return value != null;\n if (filter.mode === \"empty\") return s.trim() === \"\";\n if (filter.mode === \"not_empty\") return s.trim() !== \"\";\n const q = filter.value.trim().toLowerCase();\n if (!q) return true;\n return s.toLowerCase().includes(q);\n }\n case \"select\": {\n // `values: []` is an explicit multi-select state: the user deselected\n // every option, so it must produce an empty result. Legacy single-value\n // filters and an absent `values` field retain their clear/all behavior.\n if (filter.values !== undefined && filter.values.length === 0) {\n return false;\n }\n const selected = filter.values ??\n (filter.value && filter.value !== \"__all__\" ? [filter.value] : []);\n if (selected.length === 0) return true;\n const values = selectCellValues(value);\n // OR semantics across the set; sentinels compose (\"A or (empty)\").\n return selected.some((v) =>\n v === SELECT_NULL_VALUE\n ? value == null\n : v === SELECT_EMPTY_VALUE\n ? values.length === 0\n : v === SELECT_NOT_EMPTY_VALUE\n ? values.length > 0\n : v === \"\"\n ? value === \"\" || (Array.isArray(value) && value.includes(\"\"))\n : values.includes(v),\n );\n }\n case \"boolean\": {\n return Boolean(value) === filter.value;\n }\n case \"number\": {\n // A missing cell is not zero. Without this guard, Number(\"\") made a\n // blank numeric value pass filters whose range included zero.\n const raw = stringifyCellValue(value).trim();\n if (raw === \"\") return false;\n const normalized = raw.replace(/[^0-9.-]/g, \"\");\n if (normalized === \"\") return false;\n const n = typeof value === \"number\"\n ? value\n : Number(normalized);\n if (!Number.isFinite(n)) return false;\n if (filter.min !== undefined && n < filter.min) return false;\n if (filter.max !== undefined && n > filter.max) return false;\n return true;\n }\n case \"date\": {\n const raw = value instanceof Date ? value : new Date(stringifyCellValue(value));\n if (Number.isNaN(raw.valueOf())) return false;\n const { since, until } = dateFilterBounds(filter);\n const timestamp = raw.valueOf();\n if (since !== undefined) {\n const sinceTimestamp = new Date(since).valueOf();\n if (Number.isNaN(sinceTimestamp) || timestamp < sinceTimestamp) return false;\n }\n if (until !== undefined) {\n const untilTimestamp = new Date(until).valueOf();\n if (Number.isNaN(untilTimestamp) || timestamp >= untilTimestamp) return false;\n }\n return true;\n }\n }\n })();\n return filter.negated ? !matches : matches;\n}\n\nexport function filterAndSortRows<T>(\n rows: T[],\n columns: MatrxColumnDef<T>[],\n columnFilters: ColumnFiltersState,\n sort: SortState | null,\n globalSearch: string,\n anyOf?: { columnIds: string[]; query: string },\n layeredFilters?: readonly LayeredFilterRule[],\n globalSearchMode: TableSearchMatchMode = \"contains\",\n searchText?: (row: T) => string,\n): T[] {\n const colById = new Map(columns.map((c) => [columnId(c), c]));\n const q = globalSearch.trim().toLowerCase();\n const anyQ = anyOf?.query.trim().toLowerCase() ?? \"\";\n const activeLayeredFilters = completeLayeredFilterRules(layeredFilters);\n\n let result = rows.filter((row) => {\n for (const [id, filter] of Object.entries(columnFilters)) {\n if (!filter) continue;\n const col = colById.get(id);\n if (!col || col.filter === false) continue;\n if (!passesColumnFilter(getFilterValue(row, col), filter)) return false;\n }\n\n for (const rule of activeLayeredFilters) {\n const col = colById.get(rule.field);\n if (!col || !layeredFilterMatchesValue(getFilterValue(row, col), rule)) {\n return false;\n }\n }\n\n if (anyQ && anyOf) {\n const hit = anyOf.columnIds.some((id) => {\n const col = colById.get(id);\n if (!col) return false;\n return stringifyCellValue(getFilterValue(row, col))\n .toLowerCase()\n .includes(anyQ);\n });\n if (!hit) return false;\n }\n\n if (!q) return true;\n const searchableValues = columns\n .filter(\n (col) =>\n col.filter !== false || Boolean(col.accessorKey || col.accessorFn),\n )\n .map((col) => stringifyCellValue(getFilterValue(row, col)));\n const additionalSearchText = searchText?.(row);\n if (additionalSearchText) searchableValues.push(additionalSearchText);\n if (globalSearchMode === \"contains\") {\n return searchableValues.some((value) =>\n matchesTableSearch(value, q, \"contains\"),\n );\n }\n return matchesTableSearch(searchableValues.join(\" \"), q, \"whole_words\");\n });\n\n if (sort) {\n const col = colById.get(sort.id);\n if (col && col.sortable !== false) {\n const dir = sort.direction === \"asc\" ? 1 : -1;\n result = [...result].sort((a, b) => {\n const va = getSortValue(a, col);\n const vb = getSortValue(b, col);\n // Empty cells (null / \"\") sort LAST in both directions — sorting a\n // column should surface its values, never a wall of blanks.\n const ea = va == null || stringifyCellValue(va) === \"\";\n const eb = vb == null || stringifyCellValue(vb) === \"\";\n if (ea !== eb) return ea ? 1 : -1;\n return compareValues(va, vb) * dir;\n });\n }\n }\n\n return result;\n}\n\nexport function countActiveColumnFilters(filters: ColumnFiltersState): number {\n let n = 0;\n for (const f of Object.values(filters)) {\n if (!f) continue;\n switch (f.kind) {\n case \"text\":\n if (f.value.trim() || (f.mode && f.mode !== \"contains\")) n += 1;\n break;\n case \"select\":\n if (f.values !== undefined || (f.value && f.value !== \"__all__\"))\n n += 1;\n break;\n case \"boolean\":\n n += 1;\n break;\n case \"number\":\n if (f.min !== undefined || f.max !== undefined) n += 1;\n break;\n case \"date\":\n if (isColumnFilterActive(f)) n += 1;\n break;\n }\n }\n return n;\n}\n\nexport function isColumnFilterActive(\n filter: ColumnFilterValue | undefined,\n): boolean {\n if (!filter) return false;\n switch (filter.kind) {\n case \"text\":\n return (\n filter.value.trim().length > 0 ||\n (filter.mode !== undefined && filter.mode !== \"contains\")\n );\n case \"select\":\n return filter.values !== undefined\n ? true\n : Boolean(filter.value) && filter.value !== \"__all__\";\n case \"boolean\":\n return true;\n case \"number\":\n return filter.min !== undefined || filter.max !== undefined;\n case \"date\": {\n const bounds = dateFilterBounds(filter);\n return bounds.since !== undefined || bounds.until !== undefined;\n }\n }\n}\n\n/** Apply draft edits onto a row for display (shallow field merge). */\nexport function applyRowEdits<T>(\n row: T,\n edits: Record<string, unknown> | undefined,\n): T {\n if (!edits || Object.keys(edits).length === 0) return row;\n return { ...row, ...edits };\n}\n"],"mappings":"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","names":[]}
@@ -1,5 +1,5 @@
1
1
  import { AgentPayloadInput } from './copy-types.cjs';
2
- import { g as MatrxDataTableCopyConfig, M as MatrxColumnDef } from '../types-CmQrOz-T.cjs';
2
+ import { g as MatrxDataTableCopyConfig, M as MatrxColumnDef } from '../types-jQddSUnj.cjs';
3
3
  import '../content-transfer.cjs';
4
4
  import 'react';
5
5
  import '@ai-matrx/kit/content-transfer';
@@ -7,8 +7,10 @@ import '../content-transfer/icon.cjs';
7
7
  import '../types-CJWU1Gae.cjs';
8
8
  import './grouping.cjs';
9
9
  import './fill.cjs';
10
+ import './facets.cjs';
10
11
  import './spreadsheet.cjs';
11
12
  import './grid-selection.cjs';
13
+ import './date-filter.cjs';
12
14
 
13
15
  /**
14
16
  * Typed, declared-column capture for MatrxDataTable's built-in Alchemy source.
@@ -1,5 +1,5 @@
1
1
  import { AgentPayloadInput } from './copy-types.js';
2
- import { g as MatrxDataTableCopyConfig, M as MatrxColumnDef } from '../types-nlLCMiqd.js';
2
+ import { g as MatrxDataTableCopyConfig, M as MatrxColumnDef } from '../types-D9JBR6ZU.js';
3
3
  import '../content-transfer.js';
4
4
  import 'react';
5
5
  import '@ai-matrx/kit/content-transfer';
@@ -7,8 +7,10 @@ import '../content-transfer/icon.js';
7
7
  import '../types-CJWU1Gae.js';
8
8
  import './grouping.js';
9
9
  import './fill.js';
10
+ import './facets.js';
10
11
  import './spreadsheet.js';
11
12
  import './grid-selection.js';
13
+ import './date-filter.js';
12
14
 
13
15
  /**
14
16
  * Typed, declared-column capture for MatrxDataTable's built-in Alchemy source.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/data-table/tableCopy.ts","../../src/data-table/filter-engine.ts"],"sourcesContent":["import type { AgentPayloadInput } from \"./copy\";\nimport { normalizeTransferJson } from \"@ai-matrx/kit/content-transfer\";\nimport type { MatrxColumnDef, MatrxDataTableCopyConfig } from \"./types\";\nimport { columnId, getCellValue, stringifyCellValue } from \"./filter-engine\";\n\n/**\n * Typed, declared-column capture for MatrxDataTable's built-in Alchemy source.\n * It deliberately runs only at the transfer boundary: accessor functions can\n * throw useful capture errors, and rendering never evaluates them for copying.\n */\nexport function projectDefaultTableCopyRow<T>(\n row: T,\n columns: MatrxColumnDef<T>[],\n): Record<string, unknown> {\n const record: Record<string, unknown> = Object.create(null) as Record<string, unknown>;\n for (const column of columns) {\n const value = getCellValue(row, column);\n if (value !== undefined) record[columnId(column)] = value;\n }\n return normalizeTransferJson(record, { omitUndefinedObjectProperties: true }) as Record<string, unknown>;\n}\n\n/** Builds the default table's AI envelope from one already validated capture. */\nexport function buildDefaultTableViewAgentInput<T>(\n config: MatrxDataTableCopyConfig<T>,\n rows: T[],\n all: T[],\n columns: MatrxColumnDef<T>[],\n meta?: Parameters<typeof buildViewAgentInput<T>>[3],\n): AgentPayloadInput {\n const projected = rows.map((row) => projectDefaultTableCopyRow(row, columns));\n return {\n kind: config.listKind,\n location: config.location,\n description:\n meta?.scope === \"selected\"\n ? `${projected.length} selected ${projected.length === 1 ? config.label : (config.listLabel ?? `${config.label} rows`)}.`\n : (config.listDescription ?? `${config.listLabel ?? config.label} — current filtered/sorted view.`),\n data: projected,\n summary: projected.map((record) => formatDefaultTableCopyRecord(record, columns)).join(\"\\n---\\n\"),\n attributes: {\n visible_count: projected.length,\n total_count: all.length,\n search: meta?.search || undefined,\n search_match: meta?.search ? meta.searchMatchMode : undefined,\n any_of: meta?.anyOf || undefined,\n active_filters: meta?.filterCount ?? 0,\n sort: meta?.sort || undefined,\n scope: meta?.scope ?? \"view\",\n ...config.listAttributes?.(rows, all),\n },\n ...(config.listContext ? { context: config.listContext(rows, all) } : {}),\n };\n}\n\n/** Builds the default row AI envelope from one validated projection. */\nexport function buildDefaultTableRowAgentInput<T>(\n config: MatrxDataTableCopyConfig<T>,\n row: T,\n columns: MatrxColumnDef<T>[],\n): AgentPayloadInput {\n const projected = projectDefaultTableCopyRow(row, columns);\n return {\n kind: config.rowKind,\n location: config.location,\n description: config.rowDescription ?? `One ${config.label} row.`,\n data: projected,\n summary: formatDefaultTableCopyRecord(projected, columns),\n ...(config.rowAttributes ? { attributes: config.rowAttributes(row) } : {}),\n };\n}\n\nexport function formatDefaultTableCopyRecord<T>(\n record: Record<string, unknown>,\n columns: MatrxColumnDef<T>[],\n): string {\n return columns\n .map((column) => {\n const id = columnId(column);\n const label = column.label ?? (typeof column.header === \"string\" ? column.header : id);\n return `${label}: ${stringifyCellValue(record[id])}`;\n })\n .join(\"\\n\");\n}\n\n/** Human markdown table of the current view (visible columns × rows). */\nexport function rowsToMarkdownTable<T>(\n rows: T[],\n columns: MatrxColumnDef<T>[],\n): string {\n const cols = columns.filter((c) => c.filter !== false || c.accessorKey);\n if (cols.length === 0) return \"\";\n const headers = cols.map((c) => {\n if (typeof c.header === \"string\") return c.header;\n return columnId(c);\n });\n const lines = [\n `| ${headers.join(\" | \")} |`,\n `| ${headers.map(() => \"---\").join(\" | \")} |`,\n ];\n for (const row of rows) {\n const cells = cols.map((c) =>\n stringifyCellValue(getCellValue(row, c)).replace(/\\|/g, \"\\\\|\"),\n );\n lines.push(`| ${cells.join(\" | \")} |`);\n }\n return lines.join(\"\\n\");\n}\n\nexport function buildRowAgentInput<T>(\n config: MatrxDataTableCopyConfig<T>,\n row: T,\n): AgentPayloadInput {\n return {\n kind: config.rowKind,\n location: config.location,\n description: config.rowDescription ?? `One ${config.label} row.`,\n data: config.agentRow ? config.agentRow(row) : row,\n summary: config.humanRow(row),\n ...(config.rowAttributes ? { attributes: config.rowAttributes(row) } : {}),\n };\n}\n\nexport function buildViewAgentInput<T>(\n config: MatrxDataTableCopyConfig<T>,\n visible: T[],\n all: T[],\n meta?: {\n search?: string;\n searchMatchMode?: string;\n anyOf?: string;\n filterCount?: number;\n sort?: string | null;\n /** `custom` = shaped in the copy-subset window (filter & sort before copying). */\n scope?: \"view\" | \"selected\" | \"custom\";\n },\n): AgentPayloadInput {\n return {\n kind: config.listKind,\n location: config.location,\n description:\n meta?.scope === \"selected\"\n ? `${visible.length} selected ${visible.length === 1 ? config.label : (config.listLabel ?? `${config.label} rows`)}.`\n : (config.listDescription ??\n `${config.listLabel ?? config.label} — current filtered/sorted view.`),\n data: visible.map((r) => (config.agentRow ? config.agentRow(r) : r)),\n summary: visible.map((r) => config.humanRow(r)).join(\"\\n---\\n\"),\n attributes: {\n visible_count: visible.length,\n total_count: all.length,\n search: meta?.search || undefined,\n search_match: meta?.search ? meta.searchMatchMode : undefined,\n any_of: meta?.anyOf || undefined,\n active_filters: meta?.filterCount ?? 0,\n sort: meta?.sort || undefined,\n scope: meta?.scope ?? \"view\",\n ...config.listAttributes?.(visible, all),\n },\n ...(config.listContext ? { context: config.listContext(visible, all) } : {}),\n };\n}\n\n/** CSV of the current view (visible columns × rows), for ExportMenu. */\nexport function rowsToCsvFromColumns<T>(\n rows: T[],\n columns: MatrxColumnDef<T>[],\n): string {\n const cols = columns.filter((c) => c.filter !== false || c.accessorKey);\n const escape = (value: string) =>\n /[\",\\n\\r]/.test(value) ? `\"${value.replace(/\"/g, '\"\"')}\"` : value;\n const lines = [\n cols\n .map((c) => escape(typeof c.header === \"string\" ? c.header : columnId(c)))\n .join(\",\"),\n ];\n for (const row of rows) {\n lines.push(\n cols\n .map((c) => escape(stringifyCellValue(getCellValue(row, c))))\n .join(\",\"),\n );\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * The same rows the CSV export produces, as records — the shape a Google Sheet\n * (or any other row destination) takes.\n *\n * It shares `rowsToCsvFromColumns`'s column selection and cell stringification\n * ON PURPOSE: a user who exports the view as CSV and a user who sends it to a\n * Sheet must get the SAME columns, in the same order, with the same values.\n * Two independent row builders is how those two answers start disagreeing.\n */\nexport function rowsToRecordsFromColumns<T>(\n rows: T[],\n columns: MatrxColumnDef<T>[],\n): Array<Record<string, unknown>> {\n const cols = columns.filter((c) => c.filter !== false || c.accessorKey);\n const headers = cols.map((c) =>\n typeof c.header === \"string\" ? c.header : columnId(c),\n );\n return rows.map((row) => {\n const record: Record<string, unknown> = {};\n cols.forEach((c, index) => {\n record[headers[index] ?? columnId(c)] = stringifyCellValue(getCellValue(row, c));\n });\n return record;\n });\n}\n\nexport function buildViewHuman<T>(\n config: MatrxDataTableCopyConfig<T>,\n visible: T[],\n columns: MatrxColumnDef<T>[],\n): string {\n const md = rowsToMarkdownTable(visible, columns);\n const summaries = visible.map((r) => config.humanRow(r)).join(\"\\n\\n\");\n return [\n `${config.listLabel ?? config.label} (${visible.length} rows)`,\n \"\",\n md,\n \"\",\n \"---\",\n \"\",\n summaries,\n ].join(\"\\n\");\n}\n","import type {\n ColumnFilterValue,\n ColumnFiltersState,\n MatrxColumnDef,\n SortState,\n TableSearchMatchMode,\n} from \"./types\";\nimport {\n completeLayeredFilterRules,\n layeredFilterMatchesValue,\n type LayeredFilterRule,\n} from \"./layered-filters\";\n\n/**\n * Sentinel select-filter values for \"no value\" / \"any value\" — every select\n * filter offers them automatically when the column has empty cells, so any\n * table can isolate rows missing a value (or having one) without extra config.\n */\nexport const SELECT_EMPTY_VALUE = \"__empty__\";\nexport const SELECT_NOT_EMPTY_VALUE = \"__not_empty__\";\n/** Exact SQL-style NULL selection, distinct from the empty-cell option. */\nexport const SELECT_NULL_VALUE = \"__null__\";\n\nexport function columnId<T>(col: MatrxColumnDef<T>): string {\n if (col.id) return col.id;\n if (col.accessorKey) return String(col.accessorKey);\n throw new Error(\"MatrxDataTable column requires `id` or `accessorKey`\");\n}\n\nexport function getCellValue<T>(row: T, col: MatrxColumnDef<T>): unknown {\n if (col.accessorFn) return col.accessorFn(row);\n if (col.accessorKey) {\n return (row as Record<string, unknown>)[col.accessorKey];\n }\n return undefined;\n}\n\nexport function getFilterValue<T>(row: T, col: MatrxColumnDef<T>): unknown {\n return col.filterValue ? col.filterValue(row) : getCellValue(row, col);\n}\n\nexport function getSortValue<T>(row: T, col: MatrxColumnDef<T>): unknown {\n return col.sortValue ? col.sortValue(row) : getCellValue(row, col);\n}\n\nexport function stringifyCellValue(value: unknown): string {\n if (value == null) return \"\";\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return String(value);\n }\n if (value instanceof Date) return value.toISOString();\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\n/** Unicode-aware tokenization shared by whole-word table search. */\nexport function tableSearchWords(value: string): string[] {\n return value\n .toLocaleLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter(Boolean);\n}\n\n/**\n * Match the primary table search against one row's searchable text. In\n * whole-word mode every entered word must exist as a complete token, so\n * `are` matches `we are ready` but never `hardware`.\n */\nexport function matchesTableSearch(\n searchableText: string,\n query: string,\n mode: TableSearchMatchMode = \"contains\",\n): boolean {\n const normalizedQuery = query.trim().toLocaleLowerCase();\n if (!normalizedQuery) return true;\n if (mode === \"contains\") {\n return searchableText.toLocaleLowerCase().includes(normalizedQuery);\n }\n const actualWords = new Set(tableSearchWords(searchableText));\n const expectedWords = tableSearchWords(normalizedQuery);\n return (\n expectedWords.length > 0 &&\n expectedWords.every((word) => actualWords.has(word))\n );\n}\n\nfunction compareValues(a: unknown, b: unknown): number {\n if (a == null && b == null) return 0;\n if (a == null) return -1;\n if (b == null) return 1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n if (typeof a === \"boolean\" && typeof b === \"boolean\") {\n return Number(a) - Number(b);\n }\n return stringifyCellValue(a).localeCompare(stringifyCellValue(b), undefined, {\n numeric: true,\n sensitivity: \"base\",\n });\n}\n\n/** Select filters treat array-valued cells as sets, never as serialized JSON choices. */\nexport function selectCellValues(value: unknown): string[] {\n return (Array.isArray(value) ? value : [value])\n .map(stringifyCellValue)\n .filter((item) => item !== \"\");\n}\n\nexport function passesColumnFilter(\n value: unknown,\n filter: ColumnFilterValue,\n): boolean {\n const matches = (() => {\n switch (filter.kind) {\n case \"text\": {\n const s = stringifyCellValue(value);\n if (filter.mode === \"null\") return value == null;\n if (filter.mode === \"not_null\") return value != null;\n if (filter.mode === \"empty\") return s.trim() === \"\";\n if (filter.mode === \"not_empty\") return s.trim() !== \"\";\n const q = filter.value.trim().toLowerCase();\n if (!q) return true;\n return s.toLowerCase().includes(q);\n }\n case \"select\": {\n // `values: []` is an explicit multi-select state: the user deselected\n // every option, so it must produce an empty result. Legacy single-value\n // filters and an absent `values` field retain their clear/all behavior.\n if (filter.values !== undefined && filter.values.length === 0) {\n return false;\n }\n const selected = filter.values ??\n (filter.value && filter.value !== \"__all__\" ? [filter.value] : []);\n if (selected.length === 0) return true;\n const values = selectCellValues(value);\n // OR semantics across the set; sentinels compose (\"A or (empty)\").\n return selected.some((v) =>\n v === SELECT_NULL_VALUE\n ? value == null\n : v === SELECT_EMPTY_VALUE\n ? values.length === 0\n : v === SELECT_NOT_EMPTY_VALUE\n ? values.length > 0\n : v === \"\"\n ? value === \"\" || (Array.isArray(value) && value.includes(\"\"))\n : values.includes(v),\n );\n }\n case \"boolean\": {\n return Boolean(value) === filter.value;\n }\n case \"number\": {\n // A missing cell is not zero. Without this guard, Number(\"\") made a\n // blank numeric value pass filters whose range included zero.\n const raw = stringifyCellValue(value).trim();\n if (raw === \"\") return false;\n const normalized = raw.replace(/[^0-9.-]/g, \"\");\n if (normalized === \"\") return false;\n const n = typeof value === \"number\"\n ? value\n : Number(normalized);\n if (!Number.isFinite(n)) return false;\n if (filter.min !== undefined && n < filter.min) return false;\n if (filter.max !== undefined && n > filter.max) return false;\n return true;\n }\n }\n })();\n return filter.negated ? !matches : matches;\n}\n\nexport function filterAndSortRows<T>(\n rows: T[],\n columns: MatrxColumnDef<T>[],\n columnFilters: ColumnFiltersState,\n sort: SortState | null,\n globalSearch: string,\n anyOf?: { columnIds: string[]; query: string },\n layeredFilters?: readonly LayeredFilterRule[],\n globalSearchMode: TableSearchMatchMode = \"contains\",\n searchText?: (row: T) => string,\n): T[] {\n const colById = new Map(columns.map((c) => [columnId(c), c]));\n const q = globalSearch.trim().toLowerCase();\n const anyQ = anyOf?.query.trim().toLowerCase() ?? \"\";\n const activeLayeredFilters = completeLayeredFilterRules(layeredFilters);\n\n let result = rows.filter((row) => {\n for (const [id, filter] of Object.entries(columnFilters)) {\n if (!filter) continue;\n const col = colById.get(id);\n if (!col || col.filter === false) continue;\n if (!passesColumnFilter(getFilterValue(row, col), filter)) return false;\n }\n\n for (const rule of activeLayeredFilters) {\n const col = colById.get(rule.field);\n if (!col || !layeredFilterMatchesValue(getFilterValue(row, col), rule)) {\n return false;\n }\n }\n\n if (anyQ && anyOf) {\n const hit = anyOf.columnIds.some((id) => {\n const col = colById.get(id);\n if (!col) return false;\n return stringifyCellValue(getFilterValue(row, col))\n .toLowerCase()\n .includes(anyQ);\n });\n if (!hit) return false;\n }\n\n if (!q) return true;\n const searchableValues = columns\n .filter(\n (col) =>\n col.filter !== false || Boolean(col.accessorKey || col.accessorFn),\n )\n .map((col) => stringifyCellValue(getFilterValue(row, col)));\n const additionalSearchText = searchText?.(row);\n if (additionalSearchText) searchableValues.push(additionalSearchText);\n if (globalSearchMode === \"contains\") {\n return searchableValues.some((value) =>\n matchesTableSearch(value, q, \"contains\"),\n );\n }\n return matchesTableSearch(searchableValues.join(\" \"), q, \"whole_words\");\n });\n\n if (sort) {\n const col = colById.get(sort.id);\n if (col && col.sortable !== false) {\n const dir = sort.direction === \"asc\" ? 1 : -1;\n result = [...result].sort((a, b) => {\n const va = getSortValue(a, col);\n const vb = getSortValue(b, col);\n // Empty cells (null / \"\") sort LAST in both directions — sorting a\n // column should surface its values, never a wall of blanks.\n const ea = va == null || stringifyCellValue(va) === \"\";\n const eb = vb == null || stringifyCellValue(vb) === \"\";\n if (ea !== eb) return ea ? 1 : -1;\n return compareValues(va, vb) * dir;\n });\n }\n }\n\n return result;\n}\n\nexport function countActiveColumnFilters(filters: ColumnFiltersState): number {\n let n = 0;\n for (const f of Object.values(filters)) {\n if (!f) continue;\n switch (f.kind) {\n case \"text\":\n if (f.value.trim() || (f.mode && f.mode !== \"contains\")) n += 1;\n break;\n case \"select\":\n if (f.values !== undefined || (f.value && f.value !== \"__all__\"))\n n += 1;\n break;\n case \"boolean\":\n n += 1;\n break;\n case \"number\":\n if (f.min !== undefined || f.max !== undefined) n += 1;\n break;\n }\n }\n return n;\n}\n\nexport function isColumnFilterActive(\n filter: ColumnFilterValue | undefined,\n): boolean {\n if (!filter) return false;\n switch (filter.kind) {\n case \"text\":\n return (\n filter.value.trim().length > 0 ||\n (filter.mode !== undefined && filter.mode !== \"contains\")\n );\n case \"select\":\n return filter.values !== undefined\n ? true\n : Boolean(filter.value) && filter.value !== \"__all__\";\n case \"boolean\":\n return true;\n case \"number\":\n return filter.min !== undefined || filter.max !== undefined;\n }\n}\n\n/** Apply draft edits onto a row for display (shallow field merge). */\nexport function applyRowEdits<T>(\n row: T,\n edits: Record<string, unknown> | undefined,\n): T {\n if (!edits || Object.keys(edits).length === 0) return row;\n return { ...row, ...edits };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/data-table/tableCopy.ts","../../src/data-table/filter-engine.ts"],"sourcesContent":["import type { AgentPayloadInput } from \"./copy\";\nimport { normalizeTransferJson } from \"@ai-matrx/kit/content-transfer\";\nimport type { MatrxColumnDef, MatrxDataTableCopyConfig } from \"./types\";\nimport { columnId, getCellValue, stringifyCellValue } from \"./filter-engine\";\n\n/**\n * Typed, declared-column capture for MatrxDataTable's built-in Alchemy source.\n * It deliberately runs only at the transfer boundary: accessor functions can\n * throw useful capture errors, and rendering never evaluates them for copying.\n */\nexport function projectDefaultTableCopyRow<T>(\n row: T,\n columns: MatrxColumnDef<T>[],\n): Record<string, unknown> {\n const record: Record<string, unknown> = Object.create(null) as Record<string, unknown>;\n for (const column of columns) {\n const value = getCellValue(row, column);\n if (value !== undefined) record[columnId(column)] = value;\n }\n return normalizeTransferJson(record, { omitUndefinedObjectProperties: true }) as Record<string, unknown>;\n}\n\n/** Builds the default table's AI envelope from one already validated capture. */\nexport function buildDefaultTableViewAgentInput<T>(\n config: MatrxDataTableCopyConfig<T>,\n rows: T[],\n all: T[],\n columns: MatrxColumnDef<T>[],\n meta?: Parameters<typeof buildViewAgentInput<T>>[3],\n): AgentPayloadInput {\n const projected = rows.map((row) => projectDefaultTableCopyRow(row, columns));\n return {\n kind: config.listKind,\n location: config.location,\n description:\n meta?.scope === \"selected\"\n ? `${projected.length} selected ${projected.length === 1 ? config.label : (config.listLabel ?? `${config.label} rows`)}.`\n : (config.listDescription ?? `${config.listLabel ?? config.label} — current filtered/sorted view.`),\n data: projected,\n summary: projected.map((record) => formatDefaultTableCopyRecord(record, columns)).join(\"\\n---\\n\"),\n attributes: {\n visible_count: projected.length,\n total_count: all.length,\n search: meta?.search || undefined,\n search_match: meta?.search ? meta.searchMatchMode : undefined,\n any_of: meta?.anyOf || undefined,\n active_filters: meta?.filterCount ?? 0,\n sort: meta?.sort || undefined,\n scope: meta?.scope ?? \"view\",\n ...config.listAttributes?.(rows, all),\n },\n ...(config.listContext ? { context: config.listContext(rows, all) } : {}),\n };\n}\n\n/** Builds the default row AI envelope from one validated projection. */\nexport function buildDefaultTableRowAgentInput<T>(\n config: MatrxDataTableCopyConfig<T>,\n row: T,\n columns: MatrxColumnDef<T>[],\n): AgentPayloadInput {\n const projected = projectDefaultTableCopyRow(row, columns);\n return {\n kind: config.rowKind,\n location: config.location,\n description: config.rowDescription ?? `One ${config.label} row.`,\n data: projected,\n summary: formatDefaultTableCopyRecord(projected, columns),\n ...(config.rowAttributes ? { attributes: config.rowAttributes(row) } : {}),\n };\n}\n\nexport function formatDefaultTableCopyRecord<T>(\n record: Record<string, unknown>,\n columns: MatrxColumnDef<T>[],\n): string {\n return columns\n .map((column) => {\n const id = columnId(column);\n const label = column.label ?? (typeof column.header === \"string\" ? column.header : id);\n return `${label}: ${stringifyCellValue(record[id])}`;\n })\n .join(\"\\n\");\n}\n\n/** Human markdown table of the current view (visible columns × rows). */\nexport function rowsToMarkdownTable<T>(\n rows: T[],\n columns: MatrxColumnDef<T>[],\n): string {\n const cols = columns.filter((c) => c.filter !== false || c.accessorKey);\n if (cols.length === 0) return \"\";\n const headers = cols.map((c) => {\n if (typeof c.header === \"string\") return c.header;\n return columnId(c);\n });\n const lines = [\n `| ${headers.join(\" | \")} |`,\n `| ${headers.map(() => \"---\").join(\" | \")} |`,\n ];\n for (const row of rows) {\n const cells = cols.map((c) =>\n stringifyCellValue(getCellValue(row, c)).replace(/\\|/g, \"\\\\|\"),\n );\n lines.push(`| ${cells.join(\" | \")} |`);\n }\n return lines.join(\"\\n\");\n}\n\nexport function buildRowAgentInput<T>(\n config: MatrxDataTableCopyConfig<T>,\n row: T,\n): AgentPayloadInput {\n return {\n kind: config.rowKind,\n location: config.location,\n description: config.rowDescription ?? `One ${config.label} row.`,\n data: config.agentRow ? config.agentRow(row) : row,\n summary: config.humanRow(row),\n ...(config.rowAttributes ? { attributes: config.rowAttributes(row) } : {}),\n };\n}\n\nexport function buildViewAgentInput<T>(\n config: MatrxDataTableCopyConfig<T>,\n visible: T[],\n all: T[],\n meta?: {\n search?: string;\n searchMatchMode?: string;\n anyOf?: string;\n filterCount?: number;\n sort?: string | null;\n /** `custom` = shaped in the copy-subset window (filter & sort before copying). */\n scope?: \"view\" | \"selected\" | \"custom\";\n },\n): AgentPayloadInput {\n return {\n kind: config.listKind,\n location: config.location,\n description:\n meta?.scope === \"selected\"\n ? `${visible.length} selected ${visible.length === 1 ? config.label : (config.listLabel ?? `${config.label} rows`)}.`\n : (config.listDescription ??\n `${config.listLabel ?? config.label} — current filtered/sorted view.`),\n data: visible.map((r) => (config.agentRow ? config.agentRow(r) : r)),\n summary: visible.map((r) => config.humanRow(r)).join(\"\\n---\\n\"),\n attributes: {\n visible_count: visible.length,\n total_count: all.length,\n search: meta?.search || undefined,\n search_match: meta?.search ? meta.searchMatchMode : undefined,\n any_of: meta?.anyOf || undefined,\n active_filters: meta?.filterCount ?? 0,\n sort: meta?.sort || undefined,\n scope: meta?.scope ?? \"view\",\n ...config.listAttributes?.(visible, all),\n },\n ...(config.listContext ? { context: config.listContext(visible, all) } : {}),\n };\n}\n\n/** CSV of the current view (visible columns × rows), for ExportMenu. */\nexport function rowsToCsvFromColumns<T>(\n rows: T[],\n columns: MatrxColumnDef<T>[],\n): string {\n const cols = columns.filter((c) => c.filter !== false || c.accessorKey);\n const escape = (value: string) =>\n /[\",\\n\\r]/.test(value) ? `\"${value.replace(/\"/g, '\"\"')}\"` : value;\n const lines = [\n cols\n .map((c) => escape(typeof c.header === \"string\" ? c.header : columnId(c)))\n .join(\",\"),\n ];\n for (const row of rows) {\n lines.push(\n cols\n .map((c) => escape(stringifyCellValue(getCellValue(row, c))))\n .join(\",\"),\n );\n }\n return lines.join(\"\\n\");\n}\n\n/**\n * The same rows the CSV export produces, as records — the shape a Google Sheet\n * (or any other row destination) takes.\n *\n * It shares `rowsToCsvFromColumns`'s column selection and cell stringification\n * ON PURPOSE: a user who exports the view as CSV and a user who sends it to a\n * Sheet must get the SAME columns, in the same order, with the same values.\n * Two independent row builders is how those two answers start disagreeing.\n */\nexport function rowsToRecordsFromColumns<T>(\n rows: T[],\n columns: MatrxColumnDef<T>[],\n): Array<Record<string, unknown>> {\n const cols = columns.filter((c) => c.filter !== false || c.accessorKey);\n const headers = cols.map((c) =>\n typeof c.header === \"string\" ? c.header : columnId(c),\n );\n return rows.map((row) => {\n const record: Record<string, unknown> = {};\n cols.forEach((c, index) => {\n record[headers[index] ?? columnId(c)] = stringifyCellValue(getCellValue(row, c));\n });\n return record;\n });\n}\n\nexport function buildViewHuman<T>(\n config: MatrxDataTableCopyConfig<T>,\n visible: T[],\n columns: MatrxColumnDef<T>[],\n): string {\n const md = rowsToMarkdownTable(visible, columns);\n const summaries = visible.map((r) => config.humanRow(r)).join(\"\\n\\n\");\n return [\n `${config.listLabel ?? config.label} (${visible.length} rows)`,\n \"\",\n md,\n \"\",\n \"---\",\n \"\",\n summaries,\n ].join(\"\\n\");\n}\n","import type {\n ColumnFilterValue,\n ColumnFiltersState,\n MatrxColumnDef,\n SortState,\n TableSearchMatchMode,\n} from \"./types\";\nimport {\n completeLayeredFilterRules,\n layeredFilterMatchesValue,\n type LayeredFilterRule,\n} from \"./layered-filters\";\nimport { dateFilterBounds } from \"./date-filter\";\n\n/**\n * Sentinel select-filter values for \"no value\" / \"any value\" — every select\n * filter offers them automatically when the column has empty cells, so any\n * table can isolate rows missing a value (or having one) without extra config.\n */\nexport const SELECT_EMPTY_VALUE = \"__empty__\";\nexport const SELECT_NOT_EMPTY_VALUE = \"__not_empty__\";\n/** Exact SQL-style NULL selection, distinct from the empty-cell option. */\nexport const SELECT_NULL_VALUE = \"__null__\";\n\nexport function columnId<T>(col: MatrxColumnDef<T>): string {\n if (col.id) return col.id;\n if (col.accessorKey) return String(col.accessorKey);\n throw new Error(\"MatrxDataTable column requires `id` or `accessorKey`\");\n}\n\nexport function getCellValue<T>(row: T, col: MatrxColumnDef<T>): unknown {\n if (col.accessorFn) return col.accessorFn(row);\n if (col.accessorKey) {\n return (row as Record<string, unknown>)[col.accessorKey];\n }\n return undefined;\n}\n\nexport function getFilterValue<T>(row: T, col: MatrxColumnDef<T>): unknown {\n return col.filterValue ? col.filterValue(row) : getCellValue(row, col);\n}\n\nexport function getSortValue<T>(row: T, col: MatrxColumnDef<T>): unknown {\n return col.sortValue ? col.sortValue(row) : getCellValue(row, col);\n}\n\nexport function stringifyCellValue(value: unknown): string {\n if (value == null) return \"\";\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"boolean\") {\n return String(value);\n }\n if (value instanceof Date) return value.toISOString();\n try {\n return JSON.stringify(value);\n } catch {\n return String(value);\n }\n}\n\n/** Unicode-aware tokenization shared by whole-word table search. */\nexport function tableSearchWords(value: string): string[] {\n return value\n .toLocaleLowerCase()\n .split(/[^\\p{L}\\p{N}]+/u)\n .filter(Boolean);\n}\n\n/**\n * Match the primary table search against one row's searchable text. In\n * whole-word mode every entered word must exist as a complete token, so\n * `are` matches `we are ready` but never `hardware`.\n */\nexport function matchesTableSearch(\n searchableText: string,\n query: string,\n mode: TableSearchMatchMode = \"contains\",\n): boolean {\n const normalizedQuery = query.trim().toLocaleLowerCase();\n if (!normalizedQuery) return true;\n if (mode === \"contains\") {\n return searchableText.toLocaleLowerCase().includes(normalizedQuery);\n }\n const actualWords = new Set(tableSearchWords(searchableText));\n const expectedWords = tableSearchWords(normalizedQuery);\n return (\n expectedWords.length > 0 &&\n expectedWords.every((word) => actualWords.has(word))\n );\n}\n\nfunction compareValues(a: unknown, b: unknown): number {\n if (a == null && b == null) return 0;\n if (a == null) return -1;\n if (b == null) return 1;\n if (typeof a === \"number\" && typeof b === \"number\") return a - b;\n if (typeof a === \"boolean\" && typeof b === \"boolean\") {\n return Number(a) - Number(b);\n }\n return stringifyCellValue(a).localeCompare(stringifyCellValue(b), undefined, {\n numeric: true,\n sensitivity: \"base\",\n });\n}\n\n/** Select filters treat array-valued cells as sets, never as serialized JSON choices. */\nexport function selectCellValues(value: unknown): string[] {\n return (Array.isArray(value) ? value : [value])\n .map(stringifyCellValue)\n .filter((item) => item !== \"\");\n}\n\nexport function passesColumnFilter(\n value: unknown,\n filter: ColumnFilterValue,\n): boolean {\n const matches = (() => {\n switch (filter.kind) {\n case \"text\": {\n const s = stringifyCellValue(value);\n if (filter.mode === \"null\") return value == null;\n if (filter.mode === \"not_null\") return value != null;\n if (filter.mode === \"empty\") return s.trim() === \"\";\n if (filter.mode === \"not_empty\") return s.trim() !== \"\";\n const q = filter.value.trim().toLowerCase();\n if (!q) return true;\n return s.toLowerCase().includes(q);\n }\n case \"select\": {\n // `values: []` is an explicit multi-select state: the user deselected\n // every option, so it must produce an empty result. Legacy single-value\n // filters and an absent `values` field retain their clear/all behavior.\n if (filter.values !== undefined && filter.values.length === 0) {\n return false;\n }\n const selected = filter.values ??\n (filter.value && filter.value !== \"__all__\" ? [filter.value] : []);\n if (selected.length === 0) return true;\n const values = selectCellValues(value);\n // OR semantics across the set; sentinels compose (\"A or (empty)\").\n return selected.some((v) =>\n v === SELECT_NULL_VALUE\n ? value == null\n : v === SELECT_EMPTY_VALUE\n ? values.length === 0\n : v === SELECT_NOT_EMPTY_VALUE\n ? values.length > 0\n : v === \"\"\n ? value === \"\" || (Array.isArray(value) && value.includes(\"\"))\n : values.includes(v),\n );\n }\n case \"boolean\": {\n return Boolean(value) === filter.value;\n }\n case \"number\": {\n // A missing cell is not zero. Without this guard, Number(\"\") made a\n // blank numeric value pass filters whose range included zero.\n const raw = stringifyCellValue(value).trim();\n if (raw === \"\") return false;\n const normalized = raw.replace(/[^0-9.-]/g, \"\");\n if (normalized === \"\") return false;\n const n = typeof value === \"number\"\n ? value\n : Number(normalized);\n if (!Number.isFinite(n)) return false;\n if (filter.min !== undefined && n < filter.min) return false;\n if (filter.max !== undefined && n > filter.max) return false;\n return true;\n }\n case \"date\": {\n const raw = value instanceof Date ? value : new Date(stringifyCellValue(value));\n if (Number.isNaN(raw.valueOf())) return false;\n const { since, until } = dateFilterBounds(filter);\n const timestamp = raw.valueOf();\n if (since !== undefined) {\n const sinceTimestamp = new Date(since).valueOf();\n if (Number.isNaN(sinceTimestamp) || timestamp < sinceTimestamp) return false;\n }\n if (until !== undefined) {\n const untilTimestamp = new Date(until).valueOf();\n if (Number.isNaN(untilTimestamp) || timestamp >= untilTimestamp) return false;\n }\n return true;\n }\n }\n })();\n return filter.negated ? !matches : matches;\n}\n\nexport function filterAndSortRows<T>(\n rows: T[],\n columns: MatrxColumnDef<T>[],\n columnFilters: ColumnFiltersState,\n sort: SortState | null,\n globalSearch: string,\n anyOf?: { columnIds: string[]; query: string },\n layeredFilters?: readonly LayeredFilterRule[],\n globalSearchMode: TableSearchMatchMode = \"contains\",\n searchText?: (row: T) => string,\n): T[] {\n const colById = new Map(columns.map((c) => [columnId(c), c]));\n const q = globalSearch.trim().toLowerCase();\n const anyQ = anyOf?.query.trim().toLowerCase() ?? \"\";\n const activeLayeredFilters = completeLayeredFilterRules(layeredFilters);\n\n let result = rows.filter((row) => {\n for (const [id, filter] of Object.entries(columnFilters)) {\n if (!filter) continue;\n const col = colById.get(id);\n if (!col || col.filter === false) continue;\n if (!passesColumnFilter(getFilterValue(row, col), filter)) return false;\n }\n\n for (const rule of activeLayeredFilters) {\n const col = colById.get(rule.field);\n if (!col || !layeredFilterMatchesValue(getFilterValue(row, col), rule)) {\n return false;\n }\n }\n\n if (anyQ && anyOf) {\n const hit = anyOf.columnIds.some((id) => {\n const col = colById.get(id);\n if (!col) return false;\n return stringifyCellValue(getFilterValue(row, col))\n .toLowerCase()\n .includes(anyQ);\n });\n if (!hit) return false;\n }\n\n if (!q) return true;\n const searchableValues = columns\n .filter(\n (col) =>\n col.filter !== false || Boolean(col.accessorKey || col.accessorFn),\n )\n .map((col) => stringifyCellValue(getFilterValue(row, col)));\n const additionalSearchText = searchText?.(row);\n if (additionalSearchText) searchableValues.push(additionalSearchText);\n if (globalSearchMode === \"contains\") {\n return searchableValues.some((value) =>\n matchesTableSearch(value, q, \"contains\"),\n );\n }\n return matchesTableSearch(searchableValues.join(\" \"), q, \"whole_words\");\n });\n\n if (sort) {\n const col = colById.get(sort.id);\n if (col && col.sortable !== false) {\n const dir = sort.direction === \"asc\" ? 1 : -1;\n result = [...result].sort((a, b) => {\n const va = getSortValue(a, col);\n const vb = getSortValue(b, col);\n // Empty cells (null / \"\") sort LAST in both directions — sorting a\n // column should surface its values, never a wall of blanks.\n const ea = va == null || stringifyCellValue(va) === \"\";\n const eb = vb == null || stringifyCellValue(vb) === \"\";\n if (ea !== eb) return ea ? 1 : -1;\n return compareValues(va, vb) * dir;\n });\n }\n }\n\n return result;\n}\n\nexport function countActiveColumnFilters(filters: ColumnFiltersState): number {\n let n = 0;\n for (const f of Object.values(filters)) {\n if (!f) continue;\n switch (f.kind) {\n case \"text\":\n if (f.value.trim() || (f.mode && f.mode !== \"contains\")) n += 1;\n break;\n case \"select\":\n if (f.values !== undefined || (f.value && f.value !== \"__all__\"))\n n += 1;\n break;\n case \"boolean\":\n n += 1;\n break;\n case \"number\":\n if (f.min !== undefined || f.max !== undefined) n += 1;\n break;\n case \"date\":\n if (isColumnFilterActive(f)) n += 1;\n break;\n }\n }\n return n;\n}\n\nexport function isColumnFilterActive(\n filter: ColumnFilterValue | undefined,\n): boolean {\n if (!filter) return false;\n switch (filter.kind) {\n case \"text\":\n return (\n filter.value.trim().length > 0 ||\n (filter.mode !== undefined && filter.mode !== \"contains\")\n );\n case \"select\":\n return filter.values !== undefined\n ? true\n : Boolean(filter.value) && filter.value !== \"__all__\";\n case \"boolean\":\n return true;\n case \"number\":\n return filter.min !== undefined || filter.max !== undefined;\n case \"date\": {\n const bounds = dateFilterBounds(filter);\n return bounds.since !== undefined || bounds.until !== undefined;\n }\n }\n}\n\n/** Apply draft edits onto a row for display (shallow field merge). */\nexport function applyRowEdits<T>(\n row: T,\n edits: Record<string, unknown> | undefined,\n): T {\n if (!edits || Object.keys(edits).length === 0) return row;\n return { ...row, ...edits };\n}\n"],"mappings":"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","names":[]}