@ai-matrx/design-system 0.20.5 → 0.20.8

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 (37) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/content-transfer.cjs +35 -15
  3. package/dist/content-transfer.cjs.map +1 -1
  4. package/dist/content-transfer.css +10 -2
  5. package/dist/content-transfer.js +35 -15
  6. package/dist/content-transfer.js.map +1 -1
  7. package/dist/data-table/copy-helpers.cjs.map +1 -1
  8. package/dist/data-table/copy-helpers.js.map +1 -1
  9. package/dist/data-table/data-row-inspector.cjs +33 -13
  10. package/dist/data-table/data-row-inspector.cjs.map +1 -1
  11. package/dist/data-table/data-row-inspector.js +33 -13
  12. package/dist/data-table/data-row-inspector.js.map +1 -1
  13. package/dist/data-table/data-row-window.cjs +33 -13
  14. package/dist/data-table/data-row-window.cjs.map +1 -1
  15. package/dist/data-table/data-row-window.js +33 -13
  16. package/dist/data-table/data-row-window.js.map +1 -1
  17. package/dist/data-table/filter-engine.cjs +6 -2
  18. package/dist/data-table/filter-engine.cjs.map +1 -1
  19. package/dist/data-table/filter-engine.d.cts +3 -1
  20. package/dist/data-table/filter-engine.d.ts +3 -1
  21. package/dist/data-table/filter-engine.js +6 -2
  22. package/dist/data-table/filter-engine.js.map +1 -1
  23. package/dist/data-table/index.cjs +294 -228
  24. package/dist/data-table/index.cjs.map +1 -1
  25. package/dist/data-table/index.js +136 -70
  26. package/dist/data-table/index.js.map +1 -1
  27. package/dist/data-table/infer-filter.cjs +11 -7
  28. package/dist/data-table/infer-filter.cjs.map +1 -1
  29. package/dist/data-table/infer-filter.js +11 -7
  30. package/dist/data-table/infer-filter.js.map +1 -1
  31. package/dist/data-table/query-control.cjs +11 -7
  32. package/dist/data-table/query-control.cjs.map +1 -1
  33. package/dist/data-table/query-control.js +11 -7
  34. package/dist/data-table/query-control.js.map +1 -1
  35. package/dist/data-table/xlsx.cjs.map +1 -1
  36. package/dist/data-table/xlsx.js.map +1 -1
  37. package/package.json +1 -1
@@ -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\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\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 const selected =\n filter.values ??\n (filter.value && filter.value !== \"__all__\" ? [filter.value] : []);\n if (selected.length === 0) return true;\n const s = stringifyCellValue(value);\n // OR semantics across the set; sentinels compose (\"A or (empty)\").\n return selected.some((v) =>\n v === SELECT_EMPTY_VALUE\n ? s === \"\"\n : v === SELECT_NOT_EMPTY_VALUE\n ? s !== \"\"\n : s === v,\n );\n }\n case \"boolean\": {\n return Boolean(value) === filter.value;\n }\n case \"number\": {\n const n =\n typeof value === \"number\"\n ? value\n : Number(stringifyCellValue(value).replace(/[^0-9.-]/g, \"\"));\n if (Number.isNaN(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 ? f.values.length > 0 : 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\n ? filter.values.length > 0\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\";\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\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 const selected =\n 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_EMPTY_VALUE\n ? values.length === 0\n : v === SELECT_NOT_EMPTY_VALUE\n ? values.length > 0\n : values.includes(v),\n );\n }\n case \"boolean\": {\n return Boolean(value) === filter.value;\n }\n case \"number\": {\n const n =\n typeof value === \"number\"\n ? value\n : Number(stringifyCellValue(value).replace(/[^0-9.-]/g, \"\"));\n if (Number.isNaN(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 ? f.values.length > 0 : 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\n ? filter.values.length > 0\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 +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\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\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 const selected =\n filter.values ??\n (filter.value && filter.value !== \"__all__\" ? [filter.value] : []);\n if (selected.length === 0) return true;\n const s = stringifyCellValue(value);\n // OR semantics across the set; sentinels compose (\"A or (empty)\").\n return selected.some((v) =>\n v === SELECT_EMPTY_VALUE\n ? s === \"\"\n : v === SELECT_NOT_EMPTY_VALUE\n ? s !== \"\"\n : s === v,\n );\n }\n case \"boolean\": {\n return Boolean(value) === filter.value;\n }\n case \"number\": {\n const n =\n typeof value === \"number\"\n ? value\n : Number(stringifyCellValue(value).replace(/[^0-9.-]/g, \"\"));\n if (Number.isNaN(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 ? f.values.length > 0 : 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\n ? filter.values.length > 0\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\";\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\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 const selected =\n 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_EMPTY_VALUE\n ? values.length === 0\n : v === SELECT_NOT_EMPTY_VALUE\n ? values.length > 0\n : values.includes(v),\n );\n }\n case \"boolean\": {\n return Boolean(value) === filter.value;\n }\n case \"number\": {\n const n =\n typeof value === \"number\"\n ? value\n : Number(stringifyCellValue(value).replace(/[^0-9.-]/g, \"\"));\n if (Number.isNaN(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 ? f.values.length > 0 : 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\n ? filter.values.length > 0\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":[]}
@@ -1145,7 +1145,7 @@ var useContentTransferCapabilities = () => React9.useContext(CapabilityContext);
1145
1145
  var SurfaceContext = React9.createContext(null);
1146
1146
  function MenuPresentation({ label, hint, icon: Icon2 }) {
1147
1147
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1148
- Icon2 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Icon2, { className: "matrx-alchemy-menu-icon" }) : null,
1148
+ Icon2 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Icon2, { className: "matrx-alchemy-menu-icon" }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PaletteGlyph, { file: true }),
1149
1149
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
1150
1150
  label,
1151
1151
  hint ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("small", { className: "matrx-alchemy-menu-hint", children: hint }) : null
@@ -1167,8 +1167,11 @@ function DestinationPresentation({ action }) {
1167
1167
  };
1168
1168
  const path = paths[action.id.replace(/^matrx:/, "")];
1169
1169
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1170
- !action.icon ? path ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { className: "matrx-alchemy-palette-glyph", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: path }) }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PaletteGlyph, { file: true }) : null,
1171
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MenuPresentation, { ...action })
1170
+ action.icon ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(action.icon, { className: "matrx-alchemy-menu-icon" }) : path ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { className: "matrx-alchemy-palette-glyph", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: path }) }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PaletteGlyph, { file: true }),
1171
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
1172
+ action.label,
1173
+ action.hint ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("small", { className: "matrx-alchemy-menu-hint", children: action.hint }) : null
1174
+ ] })
1172
1175
  ] });
1173
1176
  }
1174
1177
  function ReceiptLink({ target }) {
@@ -1178,6 +1181,11 @@ function ReceiptLink({ target }) {
1178
1181
  ] }) : null;
1179
1182
  }
1180
1183
  function PaletteGlyph({ format, ai = false, file = false, kind }) {
1184
+ if (kind === "link") return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { className: "matrx-alchemy-palette-glyph", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M10 13a5 5 0 0 0 7 0l3-3a5 5 0 0 0-7-7l-2 2M14 11a5 5 0 0 0-7 0l-3 3a5 5 0 0 0 7 7l2-2" }) });
1185
+ if (kind === "surface") return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { className: "matrx-alchemy-palette-glyph", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
1186
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }),
1187
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M3 9h18M9 9v12" })
1188
+ ] });
1181
1189
  if (kind === "ai-copy") return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("svg", { className: "matrx-alchemy-palette-glyph", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
1182
1190
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("rect", { x: "5", y: "5", width: "11", height: "14", rx: "2" }),
1183
1191
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M9 3h6a2 2 0 0 1 2 2v10" }),
@@ -1658,7 +1666,7 @@ function ContentTransferMenuSession({ references = [], referenceActions = [], so
1658
1666
  const presentation = options.action;
1659
1667
  const Icon2 = presentation?.icon;
1660
1668
  const fallbackLabel = options.file && !/^download\b/i.test(label2) ? `Download ${label2}` : label2;
1661
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "button", className: "matrx-alchemy-palette-tile", "aria-label": presentation?.ariaLabel ?? (presentation ? fallbackLabel : options.ariaLabel ?? `${resolvedMode === "copy" ? "Copy" : "Download"} ${label2}`), onClick: () => {
1669
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "button", className: "matrx-alchemy-palette-tile", title: id === "table-data" ? "JSON using column display names" : id === "table-schema" ? "Table schema and rows" : label2, "aria-label": presentation?.ariaLabel ?? (presentation ? fallbackLabel : options.ariaLabel ?? `${resolvedMode === "copy" ? "Copy" : "Download"} ${label2}`), onClick: () => {
1662
1670
  closePalette();
1663
1671
  onClick();
1664
1672
  }, children: [
@@ -1720,7 +1728,7 @@ function ContentTransferMenuSession({ references = [], referenceActions = [], so
1720
1728
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PaletteGlyph, { kind: "ai-copy" }),
1721
1729
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
1722
1730
  "Copy with AI context",
1723
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("small", { className: "matrx-alchemy-menu-hint", children: "Content, source and capture details" })
1731
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("small", { className: "matrx-alchemy-menu-hint", children: "Include source context" })
1724
1732
  ] })
1725
1733
  ] }) : null, items: quickVariants("ai"), onRun: (variant) => {
1726
1734
  closePalette();
@@ -1733,32 +1741,44 @@ function ContentTransferMenuSession({ references = [], referenceActions = [], so
1733
1741
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PaletteGlyph, { kind: "sliders" }),
1734
1742
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { children: [
1735
1743
  "Prepare for AI",
1736
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("small", { className: "matrx-alchemy-menu-hint", children: "Review and edit before copying" })
1744
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("small", { className: "matrx-alchemy-menu-hint", children: "Trim, edit or summarize" })
1737
1745
  ] })
1738
1746
  ] }) })
1739
1747
  ] }) : null,
1740
1748
  prepareAvailable && (nearestWorkspaceKey || variants.length) ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PaletteRows, { title: "Other sources", items: [], extra: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
1741
- nearestWorkspaceKey && nearestSurface ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "matrx-alchemy-palette-row", onClick: () => {
1749
+ nearestWorkspaceKey && nearestSurface ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "button", className: "matrx-alchemy-palette-row", onClick: () => {
1742
1750
  closePalette();
1743
1751
  void openWorkspace(nearestWorkspaceKey, { source: nearestSurface, label: nearestSurface.surfaceName });
1744
- }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "Prepare this surface" }) }) : null,
1752
+ }, children: [
1753
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PaletteGlyph, { kind: "surface" }),
1754
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: "Prepare this page" })
1755
+ ] }) : null,
1745
1756
  variants.map((variant) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "matrx-alchemy-palette-row", "aria-label": variant.ariaLabel, onClick: () => {
1746
1757
  closePalette();
1747
1758
  void openWorkspace(variantWorkspaceKeys.get(variant.id), variant);
1748
1759
  }, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(MenuPresentation, { ...variant, label: `Prepare ${variant.label}` }) }, variant.id))
1749
1760
  ] }) }) : null,
1750
1761
  references.length || referenceActions.length ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("section", { className: "matrx-alchemy-palette-section", children: [
1751
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { children: "Live references" }),
1752
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "matrx-alchemy-menu-hint", children: "Pointers to saved data, independent of filters and edits." }),
1753
- references.map((reference) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "matrx-alchemy-palette-row", onClick: () => void copyReference(reference), children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: reference.label }) }, reference.id)),
1754
- referenceActions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "matrx-alchemy-palette-row", onClick: () => void openReference(action), children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: action.label }) }, action.id))
1762
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h3", { children: "References" }),
1763
+ referenceActions.map((action) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "button", className: "matrx-alchemy-palette-row", onClick: () => void openReference(action), children: [
1764
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PaletteGlyph, { kind: "link" }),
1765
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: action.label }),
1766
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { className: "matrx-alchemy-disclosure", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "m6 4 4 4-4 4" }) })
1767
+ ] }, action.id)),
1768
+ references.map((reference) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("button", { type: "button", className: "matrx-alchemy-palette-row", title: "Copies a reference to saved data", onClick: () => void copyReference(reference), children: [
1769
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PaletteGlyph, { kind: "link" }),
1770
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: reference.label })
1771
+ ] }, reference.id))
1755
1772
  ] }) : null,
1756
1773
  ["save", "chat", "email", "other"].map((group) => {
1757
1774
  const entries = ordinaryActions.filter((action) => (action.group ?? "other") === group);
1758
1775
  return entries.length ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("details", { className: "matrx-alchemy-palette-destinations", children: [
1759
1776
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("summary", { children: [
1760
1777
  group === "save" ? "Save to Matrx" : group === "chat" ? "Continue with AI" : group === "email" ? "Email to me" : "Actions",
1761
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: entries.length })
1778
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { className: "matrx-alchemy-disclosure-meta", children: [
1779
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { children: entries.length }),
1780
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { className: "matrx-alchemy-disclosure", viewBox: "0 0 16 16", fill: "none", stroke: "currentColor", strokeWidth: "1.5", "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "m6 4 4 4-4 4" }) })
1781
+ ] })
1762
1782
  ] }),
1763
1783
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { children: entries.map((action) => /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", className: "matrx-alchemy-palette-row", "aria-label": action.ariaLabel, onClick: () => {
1764
1784
  closePalette();