@aglyn/shared-ui-jsx 1.0.0-beta.199 → 1.0.0-beta.200

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 (33) hide show
  1. package/package.json +5 -5
  2. package/src/lib/components/list-filter-chips.component.d.ts +2 -2
  3. package/src/lib/components/list-filter-chips.component.js +6 -28
  4. package/src/lib/components/list-filter-chips.component.js.map +1 -1
  5. package/src/lib/components/list-filter-toolbar.component.d.ts +56 -0
  6. package/src/lib/components/list-filter-toolbar.component.js +280 -0
  7. package/src/lib/components/list-filter-toolbar.component.js.map +1 -0
  8. package/src/lib/components/list-query-notices.component.d.ts +18 -0
  9. package/src/lib/components/list-query-notices.component.js +11 -0
  10. package/src/lib/components/list-query-notices.component.js.map +1 -1
  11. package/src/lib/components/list-table.component.d.ts +16 -0
  12. package/src/lib/components/list-table.component.js +57 -8
  13. package/src/lib/components/list-table.component.js.map +1 -1
  14. package/src/lib/const/list-filter-sentence.d.ts +39 -0
  15. package/src/lib/const/list-filter-sentence.js +107 -0
  16. package/src/lib/const/list-filter-sentence.js.map +1 -0
  17. package/src/lib/const/list-filter.d.ts +8 -46
  18. package/src/lib/const/list-filter.js +54 -113
  19. package/src/lib/const/list-filter.js.map +1 -1
  20. package/src/lib/const/list-grid-filter.d.ts +39 -28
  21. package/src/lib/const/list-grid-filter.js +59 -71
  22. package/src/lib/const/list-grid-filter.js.map +1 -1
  23. package/src/lib/const/list-query-plan.d.ts +5 -0
  24. package/src/lib/const/list-query-plan.js.map +1 -1
  25. package/src/lib/hooks/use-list-grid-filter.d.ts +7 -11
  26. package/src/lib/hooks/use-list-grid-filter.js +11 -12
  27. package/src/lib/hooks/use-list-grid-filter.js.map +1 -1
  28. package/src/lib/hooks/use-list-rows-filter.d.ts +0 -78
  29. package/src/lib/hooks/use-list-rows-filter.js +0 -86
  30. package/src/lib/hooks/use-list-rows-filter.js.map +0 -1
  31. package/src/lib/hooks/use-paged-rows-filter.d.ts +0 -80
  32. package/src/lib/hooks/use-paged-rows-filter.js +0 -129
  33. package/src/lib/hooks/use-paged-rows-filter.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/const/list-grid-filter.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n getGridSingleSelectOperators,\n type GridColDef,\n type GridFilterItem,\n} from '@mui/x-data-grid'\nimport {\n gridFilterRequests,\n hiddenFilterColumns,\n type ListFilterField,\n type ListFilterRequest,\n listFilterColumn,\n listFilterOperators,\n matchListFilter,\n} from './list-filter'\n\n/*\n * ONE path from a list's filter clauses to the data grid's own Filters panel\n * and quick search, and back (AGL-3313, shared by every list since AGL-3317).\n *\n * A list stores what it is narrowed by as clauses — `{ field, op, value }`,\n * the shape saved views hold — and edits them through the grid's toolbar\n * rather than controls of its own. What a list declares is its fields, as\n * the `ListFilterField` grammar (`./list-filter`), plus the choices of the\n * fields whose values are picked rather than typed. The grid then offers\n * each as a typed column: a `singleSelect` with its choices where there are\n * choices, the operators the grammar allows otherwise.\n *\n * The clauses keep their stored shape, so a view saved before the panel\n * reads unchanged: the panel shows `equals` as the select's `is` and writes\n * `is` back as `equals`, and a list whose clauses were stored in some other\n * shape says so with a codec.\n */\n\n/** One clause a list is narrowed by; `label` names a picked value on a chip. */\nexport interface ListFilterClause extends ListFilterRequest {\n label?: string\n}\n\n/** A choice for a field whose value is picked rather than typed. */\nexport interface ListFilterOption {\n value: string\n label: string\n}\n\n/**\n * A field of a list that holds its rows and answers the panel in memory.\n *\n * The grammar's derived operators describe what a FIRESTORE query can\n * serve, so a text field with no lower-case twin offers nothing. A list\n * that matches over rows it already has is not held to that: plain\n * JavaScript answers a mid-string `contains` and both empty operators, so\n * the field names them (`operators`), and `matchListFilter` answers them.\n * `select` is a field picked from choices — the list passes them as the\n * field's options, and the panel shows a select over them.\n * @deprecated AGL-3321: matches over rows a list has loaded. Serve every clause and search\n * word on the query instead — `planListQuery` (`@aglyn/shared-ui-jsx/const/list-query-plan`, with `nameSearchNormalizers`)\n * through `useListQuery` or `applyListQuery`. Removed once no list calls it.\n*/\nexport function inMemoryListField(\n column: string,\n kind: 'text' | 'select' | 'number' | 'date' | 'boolean',\n path: string = column,\n): ListFilterField {\n switch (kind) {\n case 'text':\n return {\n column,\n path,\n kind: 'text',\n operators: ['contains', 'doesNotContain', 'equals', 'startsWith', 'endsWith', 'isEmpty', 'isNotEmpty'],\n }\n case 'select':\n return { column, path, kind: 'exact', operators: ['equals', 'doesNotEqual', 'isAnyOf'] }\n case 'number':\n return { column, path, kind: 'number', presence: 'nullable' }\n case 'date':\n return { column, path, kind: 'date', presence: 'nullable' }\n default:\n return { column, path, kind: 'boolean' }\n }\n}\n\n/** The grid operator each stored select operator shows as. */\nconst SELECT_TO_GRID: Readonly<Record<string, string>> = {\n equals: 'is',\n doesNotEqual: 'not',\n isAnyOf: 'isAnyOf',\n}\nconst SELECT_FROM_GRID: Readonly<Record<string, string>> = {\n is: 'equals',\n not: 'doesNotEqual',\n isAnyOf: 'isAnyOf',\n}\n\n/**\n * How one field's stored clause and the panel's item translate. A field\n * with choices uses {@link listSelectCodec}; a typed one,\n * {@link listPlainCodec}. A list whose clauses predate the panel supplies\n * its own.\n */\nexport interface ListGridFilterCodec {\n toItem: (clause: ListFilterClause) => Pick<GridFilterItem, 'operator' | 'value'> | null\n toClause: (item: GridFilterItem) => ListFilterClause | null\n}\n\n/** The panel's item as a clause, through the grammar's one notion of \"usable\". */\nconst plainClause = (item: GridFilterItem): ListFilterClause | null =>\n gridFilterRequests({ items: [item] })[0] ?? null\n\n/** A typed field: the stored operator IS the grid's. */\nexport const listPlainCodec: ListGridFilterCodec = {\n toItem: (clause) => ({\n operator: clause.op,\n value: clause.value === '' ? undefined : clause.value,\n }),\n toClause: plainClause,\n}\n\n/** A picked field: `equals` shows as `is`, and `isAnyOf` holds a comma list. */\nexport const listSelectCodec: ListGridFilterCodec = {\n toItem: (clause) => {\n const operator = SELECT_TO_GRID[clause.op]\n if (!operator) return null\n return {\n operator,\n value:\n operator === 'isAnyOf'\n ? clause.value.split(',').map((entry) => entry.trim()).filter(Boolean)\n : clause.value,\n }\n },\n toClause: (item) => {\n const op = SELECT_FROM_GRID[String(item.operator)]\n const request = op ? plainClause(item) : null\n return request && op ? { ...request, op } : null\n },\n}\n\n/** The select operators a field's allowed clause operators map to. */\nexport function listSelectOperators(allowed: readonly string[]) {\n const shown = allowed.map((op) => SELECT_TO_GRID[op]).filter(Boolean)\n return getGridSingleSelectOperators().filter((operator) =>\n shown.includes(operator.value),\n )\n}\n\n/**\n * The list's columns, each made filterable exactly as far as its field is\n * declared, and every declared field that is not a column added as a\n * permanently hidden one — the panel lists columns, so a field with none\n * could not be reached (`hiddenFilterColumns`).\n *\n * A field with choices becomes a `singleSelect` over them, whatever the\n * column rendered before; one without keeps the grammar's operators; a\n * column no field declares offers no filter, because the list, not the\n * grid, answers every filter and would have nothing to answer it with.\n */\nexport function listFilterGridColumns(\n columns: readonly GridColDef[],\n fields: readonly ListFilterField[],\n options: Readonly<Record<string, readonly ListFilterOption[]>> = {},\n headers: Readonly<Record<string, string>> = {},\n): GridColDef[] {\n const selectProps = (field: ListFilterField) => {\n const choices = options[field.column] ?? []\n const operators = listSelectOperators(listFilterOperators(field))\n return {\n type: 'singleSelect' as const,\n valueOptions: choices.map((choice) => ({ value: choice.value, label: choice.label })),\n filterable: choices.length > 0 && operators.length > 0,\n filterOperators: operators,\n }\n }\n /*\n * A column made a select keeps what it DREW. The grid formats a\n * `singleSelect` cell as the label of the option its value matches, and as\n * nothing when none does — so a column whose `valueGetter` hands back a\n * label (an address for an actor id, a site name for a scope id) went\n * blank the moment it gained a filter (AGL-3321). A column with its own\n * `renderCell` is left alone; any other draws the matching option's label\n * when there is one, and its own value when there is not.\n */\n const keepsItsCell = (column: GridColDef, field: ListFilterField) => {\n if (column.renderCell) return {}\n const labels = new Map(\n (options[field.column] ?? []).map((choice) => [choice.value, choice.label]),\n )\n return {\n renderCell: (params: { value?: unknown }) => {\n const value = params.value\n if (value === undefined || value === null) return ''\n return labels.get(String(value)) ?? String(value)\n },\n }\n }\n const shown = columns.map((column): GridColDef => {\n if (column.filterable === false && column.hideable === false) return column\n const field = fields.find((entry) => entry.column === column.field)\n if (!field) return { ...column, filterable: false }\n if (options[field.column]) {\n return { ...column, ...selectProps(field), ...keepsItsCell(column, field) } as GridColDef\n }\n return { ...column, ...listFilterColumn(fields, field.column) }\n })\n const present = columns.map((column) => column.field)\n const hidden = hiddenFilterColumns(fields, present, headers).map(\n (column): GridColDef => {\n const field = fields.find((entry) => entry.column === column.field)\n return field && options[field.column]\n ? ({ ...column, ...selectProps(field) } as GridColDef)\n : column\n },\n )\n return [...shown, ...hidden]\n}\n\n/**\n * Whether a row answers a quick search: every whitespace-separated word\n * appears, case-insensitively, in SOME of the row's searched values. A\n * blank search matches every row, so an emptied box is the list unfiltered.\n * An array value (tags) is searched member by member.\n */\nexport function listRowMatchesSearch(\n row: object,\n paths: readonly string[],\n words: readonly string[],\n): boolean {\n const asked = words.map((word) => word.trim().toLowerCase()).filter(Boolean)\n if (!asked.length) return true\n const values = paths\n .flatMap((path) => {\n const value = path\n .split('.')\n .reduce<unknown>(\n (at, key) => (at == null ? undefined : (at as Record<string, unknown>)[key]),\n row,\n )\n return Array.isArray(value) ? value : [value]\n })\n .filter((value): value is string | number => typeof value === 'string' || typeof value === 'number')\n .map((value) => String(value).toLowerCase())\n .filter(Boolean)\n return asked.every((word) => values.some((value) => value.includes(word)))\n}\n\n/**\n * The rows that answer every clause and the quick search — for a list that\n * holds its whole data set (or the window its query already narrowed) and\n * so answers the panel itself. A clause `skip` names is one the query\n * already served, and is not matched again.\n * @deprecated AGL-3321: matches over rows a list has loaded. Serve every clause and search\n * word on the query instead — `planListQuery` (`@aglyn/shared-ui-jsx/const/list-query-plan`, with `nameSearchNormalizers`)\n * through `useListQuery` or `applyListQuery`. Removed once no list calls it.\n*/\nexport function filterListRows<Row extends object>(\n rows: readonly Row[],\n fields: readonly ListFilterField[],\n clauses: readonly ListFilterClause[],\n search: { paths: readonly string[]; words: readonly string[] },\n skip?: (clause: ListFilterClause) => boolean,\n): Row[] {\n const applied = skip ? clauses.filter((clause) => !skip(clause)) : clauses\n return rows.filter(\n (row) =>\n applied.every((clause) => matchListFilter(row, fields, clause)) &&\n listRowMatchesSearch(row, search.paths, search.words),\n )\n}\n\n/**\n * The clauses with one field's clause set: replaced in place where the field\n * already had one, appended where it did not, removed when `next` is null.\n * A field holds ONE clause, which is what the panel edits.\n */\nexport function upsertListFilterClause<Clause extends ListFilterClause>(\n clauses: readonly Clause[],\n field: string,\n next: Clause | null,\n): Clause[] {\n const at = clauses.findIndex((clause) => clause.field === field)\n const rest = clauses.filter((clause) => clause.field !== field)\n if (!next) return rest\n if (at === -1) return [...rest, next]\n return [...rest.slice(0, at), next, ...rest.slice(at)]\n}\n"],"names":["getGridSingleSelectOperators","gridFilterRequests","hiddenFilterColumns","listFilterColumn","listFilterOperators","matchListFilter","inMemoryListField","column","kind","path","operators","presence","SELECT_TO_GRID","equals","doesNotEqual","isAnyOf","SELECT_FROM_GRID","is","not","plainClause","item","items","listPlainCodec","toItem","clause","operator","op","value","undefined","toClause","listSelectCodec","split","map","entry","trim","filter","Boolean","String","request","listSelectOperators","allowed","shown","includes","listFilterGridColumns","columns","fields","options","headers","selectProps","field","choices","type","valueOptions","choice","label","filterable","length","filterOperators","keepsItsCell","renderCell","labels","Map","params","get","hideable","find","present","hidden","listRowMatchesSearch","row","paths","words","asked","word","toLowerCase","values","flatMap","reduce","at","key","Array","isArray","every","some","filterListRows","rows","clauses","search","skip","applied","upsertListFilterClause","next","findIndex","rest","slice"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/const/list-grid-filter.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n getGridBooleanOperators,\n getGridDateOperators,\n getGridNumericOperators,\n getGridSingleSelectOperators,\n getGridStringOperators,\n type GridColDef,\n type GridFilterItem,\n type GridFilterOperator,\n} from '@mui/x-data-grid'\nimport {\n gridFilterRequests,\n type ListFilterField,\n type ListFilterKind,\n type ListFilterRequest,\n listFilterOperators,\n} from './list-filter'\n\n/*\n * ONE path from a list's filter clauses to the data grid's own Filters panel\n * and quick search, and back (AGL-3313, shared by every list since AGL-3317).\n *\n * A list stores what it is narrowed by as clauses — `{ field, op, value }`,\n * the shape saved views hold — and edits them through the grid's toolbar\n * rather than controls of its own. What a list declares is its fields, as\n * the `ListFilterField` grammar (`./list-filter`), plus the choices of the\n * fields whose values are picked rather than typed. The grid then offers\n * each as a typed column: a `singleSelect` with its choices where there are\n * choices, the operators the grammar allows otherwise.\n *\n * The clauses keep their stored shape, so a view saved before the panel\n * reads unchanged: the panel shows `equals` as the select's `is` and writes\n * `is` back as `equals`, and a list whose clauses were stored in some other\n * shape says so with a codec.\n */\n\n/** One clause a list is narrowed by; `label` names a picked value on a chip. */\nexport interface ListFilterClause extends ListFilterRequest {\n label?: string\n}\n\n/** A choice for a field whose value is picked rather than typed. */\nexport interface ListFilterOption {\n value: string\n label: string\n}\n\nconst operatorPool = (kind: ListFilterKind): GridFilterOperator[] => {\n switch (kind) {\n case 'boolean':\n return getGridBooleanOperators()\n case 'number':\n return getGridNumericOperators()\n case 'date':\n return getGridDateOperators()\n default:\n return getGridStringOperators()\n }\n}\n\n/**\n * The MUI operators for a field — the same list `listFilterOperators` names,\n * resolved to the grid's own operator objects so the panel keeps its native\n * inputs (a date picker for a date, a number field for a number).\n */\nexport function gridFilterOperators(\n field: ListFilterField,\n): GridFilterOperator[] {\n const allowed = listFilterOperators(field)\n return operatorPool(field.kind).filter((operator) =>\n allowed.includes(operator.value),\n )\n}\n\n/**\n * Spread onto a `GridColDef` to make a column filterable exactly as far as the\n * query can serve it. A column with NO declared field is turned off entirely —\n * deliberately, because the alternative is a funnel icon that opens a panel\n * nothing honours.\n */\nexport function listFilterColumn(\n fields: readonly ListFilterField[],\n column: string,\n): { filterable: boolean; filterOperators?: GridFilterOperator[] } {\n const field = fields.find((entry) => entry.column === column)\n if (!field) return { filterable: false }\n const operators = gridFilterOperators(field)\n return operators.length\n ? { filterable: true, filterOperators: operators }\n : { filterable: false }\n}\n\n/**\n * Fields a reader can filter by that are NOT columns on the table.\n *\n * MUI's filter panel lists COLUMNS — `gridFilterableColumnDefinitionsSelector`\n * reads every column definition, hidden ones included — so a filterable field\n * with no column is a field nobody can reach however well the route answers it.\n * Declaring it as a permanently hidden column keeps one source of truth, the\n * field list, instead of a second list of \"extra filters\" that drifts from it.\n *\n * `hideable: false` keeps them out of Manage columns as well: a column with no\n * `renderCell` and no width has nothing to show, and a reader who unhid one\n * would get a strip of blank cells for their trouble.\n *\n * Pair with {@link hiddenFilterVisibility}, which is what actually hides them.\n */\nexport function hiddenFilterColumns(\n fields: readonly ListFilterField[],\n visible: readonly string[],\n headers: Readonly<Record<string, string>> = {},\n): Array<{\n field: string\n headerName: string\n hideable: boolean\n filterable: boolean\n filterOperators?: GridFilterOperator[]\n}> {\n return fields\n .filter((field) => !visible.includes(field.column))\n .map((field) => ({\n field: field.column,\n headerName: headers[field.column] ?? field.column,\n hideable: false,\n ...listFilterColumn(fields, field.column),\n }))\n .filter((column) => column.filterable)\n}\n\n/** The grid operator each stored select operator shows as. */\nconst SELECT_TO_GRID: Readonly<Record<string, string>> = {\n equals: 'is',\n doesNotEqual: 'not',\n isAnyOf: 'isAnyOf',\n}\nconst SELECT_FROM_GRID: Readonly<Record<string, string>> = {\n is: 'equals',\n not: 'doesNotEqual',\n isAnyOf: 'isAnyOf',\n}\n\n/**\n * How one field's stored clause and the panel's item translate. A field\n * with choices uses {@link listSelectCodec}; a typed one,\n * {@link listPlainCodec}. A list whose clauses predate the panel supplies\n * its own.\n */\nexport interface ListGridFilterCodec {\n toItem: (clause: ListFilterClause) => Pick<GridFilterItem, 'operator' | 'value'> | null\n toClause: (item: GridFilterItem) => ListFilterClause | null\n}\n\n/** The panel's item as a clause, through the grammar's one notion of \"usable\". */\nconst plainClause = (item: GridFilterItem): ListFilterClause | null =>\n gridFilterRequests({ items: [item] })[0] ?? null\n\n/** A typed field: the stored operator IS the grid's. */\nexport const listPlainCodec: ListGridFilterCodec = {\n toItem: (clause) => ({\n operator: clause.op,\n value: clause.value === '' ? undefined : clause.value,\n }),\n toClause: plainClause,\n}\n\n/** A picked field: `equals` shows as `is`, and `isAnyOf` holds a comma list. */\nexport const listSelectCodec: ListGridFilterCodec = {\n toItem: (clause) => {\n const operator = SELECT_TO_GRID[clause.op]\n if (!operator) return null\n return {\n operator,\n value:\n operator === 'isAnyOf'\n ? clause.value.split(',').map((entry) => entry.trim()).filter(Boolean)\n : clause.value,\n }\n },\n toClause: (item) => {\n const op = SELECT_FROM_GRID[String(item.operator)]\n const request = op ? plainClause(item) : null\n return request && op ? { ...request, op } : null\n },\n}\n\n/** The select operators a field's allowed clause operators map to. */\nexport function listSelectOperators(allowed: readonly string[]) {\n const shown = allowed.map((op) => SELECT_TO_GRID[op]).filter(Boolean)\n return getGridSingleSelectOperators().filter((operator) =>\n shown.includes(operator.value),\n )\n}\n\n/**\n * The list's columns, each made filterable exactly as far as its field is\n * declared, and every declared field that is not a column added as a\n * permanently hidden one — the panel lists columns, so a field with none\n * could not be reached (`hiddenFilterColumns`).\n *\n * A field with choices becomes a `singleSelect` over them, whatever the\n * column rendered before; one without keeps the grammar's operators; a\n * column no field declares offers no filter, because the list, not the\n * grid, answers every filter and would have nothing to answer it with.\n */\nexport function listFilterGridColumns(\n columns: readonly GridColDef[],\n fields: readonly ListFilterField[],\n options: Readonly<Record<string, readonly ListFilterOption[]>> = {},\n headers: Readonly<Record<string, string>> = {},\n): GridColDef[] {\n const selectProps = (field: ListFilterField) => {\n const choices = options[field.column] ?? []\n const operators = listSelectOperators(listFilterOperators(field))\n return {\n type: 'singleSelect' as const,\n valueOptions: choices.map((choice) => ({ value: choice.value, label: choice.label })),\n filterable: choices.length > 0 && operators.length > 0,\n filterOperators: operators,\n }\n }\n /*\n * A column made a select keeps what it DREW. The grid formats a\n * `singleSelect` cell as the label of the option its value matches, and as\n * nothing when none does — so a column whose `valueGetter` hands back a\n * label (an address for an actor id, a site name for a scope id) went\n * blank the moment it gained a filter (AGL-3321). A column with its own\n * `renderCell` is left alone; any other draws the matching option's label\n * when there is one, and its own value when there is not.\n */\n const keepsItsCell = (column: GridColDef, field: ListFilterField) => {\n if (column.renderCell) return {}\n const labels = new Map(\n (options[field.column] ?? []).map((choice) => [choice.value, choice.label]),\n )\n return {\n renderCell: (params: { value?: unknown }) => {\n const value = params.value\n if (value === undefined || value === null) return ''\n return labels.get(String(value)) ?? String(value)\n },\n }\n }\n const shown = columns.map((column): GridColDef => {\n if (column.filterable === false && column.hideable === false) return column\n const field = fields.find((entry) => entry.column === column.field)\n if (!field) return { ...column, filterable: false }\n if (options[field.column]) {\n return { ...column, ...selectProps(field), ...keepsItsCell(column, field) } as GridColDef\n }\n return { ...column, ...listFilterColumn(fields, field.column) }\n })\n const present = columns.map((column) => column.field)\n const hidden = hiddenFilterColumns(fields, present, headers).map(\n (column): GridColDef => {\n const field = fields.find((entry) => entry.column === column.field)\n return field && options[field.column]\n ? ({ ...column, ...selectProps(field) } as GridColDef)\n : column\n },\n )\n return [...shown, ...hidden]\n}\n\n/**\n * Whether a row answers a quick search: every whitespace-separated word\n * appears, case-insensitively, in SOME of the row's searched values. A\n * blank search matches every row, so an emptied box is the list unfiltered.\n * An array value (tags) is searched member by member.\n */\nexport function listRowMatchesSearch(\n row: object,\n paths: readonly string[],\n words: readonly string[],\n): boolean {\n const asked = words.map((word) => word.trim().toLowerCase()).filter(Boolean)\n if (!asked.length) return true\n const values = paths\n .flatMap((path) => {\n const value = path\n .split('.')\n .reduce<unknown>(\n (at, key) => (at == null ? undefined : (at as Record<string, unknown>)[key]),\n row,\n )\n return Array.isArray(value) ? value : [value]\n })\n .filter((value): value is string | number => typeof value === 'string' || typeof value === 'number')\n .map((value) => String(value).toLowerCase())\n .filter(Boolean)\n return asked.every((word) => values.some((value) => value.includes(word)))\n}\n\n/**\n * The clauses with one field's clause set: replaced in place where the field\n * already had one, appended where it did not, removed when `next` is null.\n * A field holds ONE clause, which is what the panel edits.\n */\nexport function upsertListFilterClause<Clause extends ListFilterClause>(\n clauses: readonly Clause[],\n field: string,\n next: Clause | null,\n): Clause[] {\n const at = clauses.findIndex((clause) => clause.field === field)\n const rest = clauses.filter((clause) => clause.field !== field)\n if (!next) return rest\n if (at === -1) return [...rest, next]\n return [...rest.slice(0, at), next, ...rest.slice(at)]\n}\n\nexport { listFilterClauseSentence } from './list-filter-sentence'\n"],"names":["getGridBooleanOperators","getGridDateOperators","getGridNumericOperators","getGridSingleSelectOperators","getGridStringOperators","gridFilterRequests","listFilterOperators","operatorPool","kind","gridFilterOperators","field","allowed","filter","operator","includes","value","listFilterColumn","fields","column","find","entry","filterable","operators","length","filterOperators","hiddenFilterColumns","visible","headers","map","headerName","hideable","SELECT_TO_GRID","equals","doesNotEqual","isAnyOf","SELECT_FROM_GRID","is","not","plainClause","item","items","listPlainCodec","toItem","clause","op","undefined","toClause","listSelectCodec","split","trim","Boolean","String","request","listSelectOperators","shown","listFilterGridColumns","columns","options","selectProps","choices","type","valueOptions","choice","label","keepsItsCell","renderCell","labels","Map","params","get","present","hidden","listRowMatchesSearch","row","paths","words","asked","word","toLowerCase","values","flatMap","path","reduce","at","key","Array","isArray","every","some","upsertListFilterClause","clauses","next","findIndex","rest","slice","listFilterClauseSentence"],"mappings":"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"}
@@ -65,6 +65,11 @@ export interface ListQueryDeclaration {
65
65
  * scoped list writes `<scopeToken><join><prefix>` tokens and the query
66
66
  * asks `array-contains-any` over the scope's tokens joined to the word —
67
67
  * one array clause that answers both.
68
+ *
69
+ * ⛔ Rules that grant a list from `visibleTo.hasAny(...)` DENY this
70
+ * query, because it no longer constrains `visibleTo`. Declare it only for
71
+ * readers whose rule needs no `visibleTo` term, or for an Admin-SDK read;
72
+ * see `SCOPED_SEARCH_JOIN` in `@aglyn/aglyn/app-utils/name-search`.
68
73
  */
69
74
  scoped?: {
70
75
  tokensPath: string;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/const/list-query-plan.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n type ListFilterField,\n type ListFilterRequest,\n listFilterDay,\n listFilterOperators,\n} from './list-filter'\n\n/*\n * EVERY CLAUSE ON THE QUERY (AGL-3321).\n *\n * The AGL-2501 contract turned ONE filter into a Firestore query — the list\n * put the first servable clause on its query and matched the rest over the\n * rows it had loaded, and search the same way. A clause matched over a loaded\n * page answers \"no match\" for a record on the next one, which is the one\n * answer a list must never give wrongly.\n *\n * This composes them all. A list declares its fields (the same\n * `ListFilterField` grammar), the sorts it offers and the token array its\n * quick search reads; `planListQuery` turns the clauses in force and the\n * search words into ONE query — every predicate, one order — and says which\n * it could not put there and why. Nothing is matched in memory afterwards:\n * a clause the plan refuses is not applied, and the list says so, rather\n * than being applied to some rows and not others.\n *\n * The plan is SDK-free and model-free: the text normalizers the written\n * search fields were stamped with come in as `ListQueryNormalizers` — the\n * platform's are `nameSearchNormalizers` (`@aglyn/aglyn/app-utils/name-search`)\n * — so the query asks for exactly the keys the writers stored. The web twin (`listQueryConstraints` in the instance\n * library) and the Admin twin (`applyListQuery` in the console) turn it into\n * their SDK's query, so the two cannot disagree about what a clause means.\n *\n * ## What one Firestore query can hold\n *\n * - any number of equalities (`==`, `in`), composed through INDEX MERGING:\n * one `(field, sort)` composite per equality field serves every\n * combination of them, measured against production (AGL-3321);\n * - ONE array clause — `array-contains` or `array-contains-any` — which a\n * search token, a `contains` filter and a scope clause all compete for;\n * - ONE inequality field (a range, `!=`, a prefix range), which must then\n * lead the order — so a range orders the list by the field it ranges over;\n * - at most thirty disjunctions, which an `in` and an\n * `array-contains-any` multiply.\n *\n * The declaration's `listQueryIndexes` enumerates the composites those\n * shapes need, and each list's spec holds the index file to it.\n */\n\n/**\n * How the written search fields were normalized, so a typed value becomes the\n * key the query can match: the lower-cased key (`equals`, `startsWith`), the\n * one word-prefix token (`contains`, search), and the reversed key\n * (`endsWith`). `maxPrefix` is the longest token a writer stores.\n */\nexport interface ListQueryNormalizers {\n key(value: string): string\n token(value: string): string\n reversed(value: string): string\n maxPrefix: number\n}\n\n/** A predicate on the query, SDK-free. `path` `__name__` is the document id. */\nexport type ListQueryOp =\n | '=='\n | '!='\n | '<'\n | '<='\n | '>'\n | '>='\n | 'in'\n | 'array-contains'\n | 'array-contains-any'\n\nexport type ListQueryValue = string | number | boolean | null | Date | readonly (string | number)[]\n\nexport interface ListQueryFilter {\n path: string\n op: ListQueryOp\n value: ListQueryValue\n}\n\nexport interface ListQuerySort {\n path: string\n direction: 'asc' | 'desc'\n /** The grid column this order sorts, when the list lets the grid sort by it. */\n column?: string\n}\n\n/** The document id, as a path. */\nexport const LIST_QUERY_ID_PATH = '__name__'\n\n/** Firestore's cap on disjunctions — `in` values times `array-contains-any` values. */\nexport const LIST_QUERY_DISJUNCTIONS = 30\n\n/** A very high private-use codepoint: `[p, p + HIGH]` spans every string starting `p`. */\nconst HIGH = '\\uf8ff'\n\nexport interface ListQueryDeclaration {\n /** Every field the Filters panel may name. */\n fields: readonly ListFilterField[]\n /**\n * The orders the list offers, its default FIRST. The query takes no other:\n * each is a composite the index file carries for every filterable field.\n */\n sorts: readonly ListQuerySort[]\n /**\n * The quick search: the token array every write stamps (see\n * `nameSearchTokens`), queried with `array-contains` on one typed word.\n * Absent, the list offers no search box.\n */\n search?: {\n tokensPath: string\n /**\n * Under a scope clause the search cannot be a SECOND array clause, so a\n * scoped list writes `<scopeToken><join><prefix>` tokens and the query\n * asks `array-contains-any` over the scope's tokens joined to the word —\n * one array clause that answers both.\n */\n scoped?: { tokensPath: string; join: string }\n }\n /**\n * Keys a presence-map field (`keysOf`) is filtered by, for the index\n * enumeration: `sources.form`, `sources.order`, …\n */\n keys?: Readonly<Record<string, readonly string[]>>\n}\n\n/** What the list asks this time. */\nexport interface ListQueryRequest {\n clauses: readonly ListFilterRequest[]\n /** The quick search's words. */\n search?: readonly string[]\n /** The order asked for; a sort the declaration does not offer is ignored. */\n sort?: ListQuerySort | null\n /**\n * Predicates the list always applies — its scope — which count against\n * the same limits. An `array-contains-any` scope is what a scoped search\n * folds into (`declaration.search.scoped`).\n */\n base?: readonly ListQueryFilter[]\n}\n\nexport interface ListQueryRefusal {\n /** The clause refused, or `search`. */\n clause: ListFilterRequest | 'search'\n reason: string\n}\n\nexport interface ListQueryPlan {\n /** Every predicate, base included, in the order the SDK should add them. */\n filters: ListQueryFilter[]\n /** The one order: the range field's when there is one, else the sort. */\n orderBy: ListQuerySort\n /** The clauses on the query. */\n served: ListFilterRequest[]\n /** The search word on the query, or null. */\n searched: string | null\n /** What was asked and could not be put on the query, with why. */\n refused: ListQueryRefusal[]\n /** Said to the reader about what WAS served — e.g. one search word of two. */\n notices: string[]\n}\n\n/** How one clause lands on a query, before it is composed with the others. */\ninterface ClauseShape {\n filters: ListQueryFilter[]\n /** The inequality field the clause needs to lead the order, if any. */\n inequality?: string\n /** The clause is the query's array clause. */\n array?: boolean\n /** `in` values, for the disjunction budget. */\n disjunctions?: number\n}\n\nconst csv = (raw: string): string[] =>\n raw\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean)\n\nconst dayBounds = (raw: string): { start: Date; end: Date } | null => {\n const start = listFilterDay(raw)\n if (Number.isNaN(start.getTime())) return null\n const day = new Date(start)\n day.setHours(0, 0, 0, 0)\n const end = new Date(day)\n end.setDate(end.getDate() + 1)\n return { start: day, end }\n}\n\n/** The order a range over `path` puts the list in. */\nconst rangeOrder = (\n declaration: ListQueryDeclaration,\n field: ListFilterField | undefined,\n path: string,\n): ListQuerySort => {\n const offered = declaration.sorts.find((sort) => sort.path === path)\n if (offered) return offered\n return { path, direction: field?.kind === 'date' ? 'desc' : 'asc' }\n}\n\n/**\n * One clause as predicates, or the reason it has none. The same operator\n * meanings as the AGL-2501 twins (`applyListFilter`, `listFilterConstraints`),\n * which this replaces for a list that composes clauses.\n */\nfunction shapeClause(\n field: ListFilterField,\n input: ListFilterRequest,\n names: ListQueryNormalizers,\n): ClauseShape | string {\n if (field.windowOnly) return 'this field is not stored where a query can reach it'\n if (!listFilterOperators(field).includes(input.op)) {\n return `${input.op} is not something this list can ask of ${field.column}`\n }\n const raw = (input.value ?? '').trim()\n const op = input.op\n\n if (op === 'isEmpty') {\n if (field.presence !== 'nullable') return 'only a field stored as null can be asked for empty'\n return { filters: [{ path: field.path, op: '==', value: null }] }\n }\n if (op === 'isNotEmpty') {\n if (field.presence === 'always') return 'this field is never empty'\n return { filters: [{ path: field.path, op: '!=', value: null }], inequality: field.path }\n }\n if (field.kind === 'boolean') {\n if (raw !== 'true' && raw !== 'false') return 'pick true or false'\n return { filters: [{ path: field.path, op: '==', value: raw === 'true' }] }\n }\n if (!raw) return 'no value yet'\n\n if (field.keysOf) {\n if (op !== 'equals' || !/^[A-Za-z0-9_]+$/.test(raw)) return 'pick one of the choices'\n return { filters: [{ path: `${field.path}.${raw}`, op: '==', value: true }] }\n }\n\n if (field.kind === 'text') {\n if (op === 'contains' && field.tokensPath) {\n const token = field.verbatimTokens ? raw : names.token(raw)\n if (!token) return 'no value yet'\n return {\n filters: [{ path: field.tokensPath, op: 'array-contains', value: token }],\n array: true,\n }\n }\n if (op === 'equals' && field.lowerPath) {\n return { filters: [{ path: field.lowerPath, op: '==', value: names.key(raw) }] }\n }\n if (op === 'equals' && field.presence === 'always' && !field.lowerPath) {\n return { filters: [{ path: field.path, op: '==', value: raw }] }\n }\n if (op === 'startsWith' && field.lowerPath) {\n const key = names.key(raw)\n return {\n filters: [\n { path: field.lowerPath, op: '>=', value: key },\n { path: field.lowerPath, op: '<=', value: `${key}${HIGH}` },\n ],\n inequality: field.lowerPath,\n }\n }\n if (op === 'endsWith' && field.reversedPath) {\n const key = names.reversed(raw)\n return {\n filters: [\n { path: field.reversedPath, op: '>=', value: key },\n { path: field.reversedPath, op: '<=', value: `${key}${HIGH}` },\n ],\n inequality: field.reversedPath,\n }\n }\n if (op === 'isAnyOf' && field.lowerPath) {\n const values = csv(raw).map((value) => names.key(value))\n if (values.length > LIST_QUERY_DISJUNCTIONS) return `at most ${LIST_QUERY_DISJUNCTIONS} values`\n return {\n filters: [{ path: field.lowerPath, op: 'in', value: values }],\n disjunctions: values.length,\n }\n }\n return `${op} is not something this list can ask of ${field.column}`\n }\n\n if (field.kind === 'id') {\n if (op === 'equals') return { filters: [{ path: LIST_QUERY_ID_PATH, op: '==', value: raw }] }\n if (op === 'isAnyOf') {\n const values = csv(raw)\n if (values.length > LIST_QUERY_DISJUNCTIONS) return `at most ${LIST_QUERY_DISJUNCTIONS} values`\n return {\n filters: [{ path: LIST_QUERY_ID_PATH, op: 'in', value: values }],\n disjunctions: values.length,\n }\n }\n if (op === 'startsWith') {\n return {\n filters: [\n { path: LIST_QUERY_ID_PATH, op: '>=', value: raw },\n { path: LIST_QUERY_ID_PATH, op: '<=', value: `${raw}${HIGH}` },\n ],\n inequality: LIST_QUERY_ID_PATH,\n }\n }\n return `${op} is not something this list can ask of ${field.column}`\n }\n\n if (field.kind === 'exact') {\n if (op === 'equals') return { filters: [{ path: field.path, op: '==', value: raw }] }\n if (op === 'isAnyOf') {\n const values = csv(raw)\n if (values.length > LIST_QUERY_DISJUNCTIONS) return `at most ${LIST_QUERY_DISJUNCTIONS} values`\n if (field.tokensPath) {\n // An array field asked \"any of these\": one array clause.\n return {\n filters: [{ path: field.tokensPath, op: 'array-contains-any', value: values }],\n array: true,\n disjunctions: values.length,\n }\n }\n return {\n filters: [{ path: field.path, op: 'in', value: values }],\n disjunctions: values.length,\n }\n }\n if (op === 'contains' && field.tokensPath) {\n return {\n filters: [{ path: field.tokensPath, op: 'array-contains', value: raw }],\n array: true,\n }\n }\n return `${op} is not something this list can ask of ${field.column}`\n }\n\n if (field.kind === 'number') {\n const value = Number(raw)\n if (!Number.isFinite(value)) return 'type a number'\n if (op === '=') return { filters: [{ path: field.path, op: '==', value }] }\n const comparison: Record<string, ListQueryOp> = {\n '!=': '!=',\n '>': '>',\n '>=': '>=',\n '<': '<',\n '<=': '<=',\n }\n const found = comparison[op]\n if (!found) return `${op} is not something this list can ask of ${field.column}`\n return { filters: [{ path: field.path, op: found, value }], inequality: field.path }\n }\n\n if (field.kind === 'date') {\n const day = dayBounds(raw)\n if (!day) return 'pick a date'\n // A `…AtMs` field is compared as the number it is stored as.\n const at = (date: Date): Date | number =>\n field.storedAs === 'millis' ? date.getTime() : date\n if (op === 'is') {\n return {\n filters: [\n { path: field.path, op: '>=', value: at(day.start) },\n { path: field.path, op: '<', value: at(day.end) },\n ],\n inequality: field.path,\n }\n }\n const bound: Record<string, [ListQueryOp, Date]> = {\n after: ['>=', day.end],\n onOrAfter: ['>=', day.start],\n before: ['<', day.start],\n onOrBefore: ['<', day.end],\n }\n const found = bound[op]\n if (!found) return `${op} is not something this list can ask of ${field.column}`\n return { filters: [{ path: field.path, op: found[0], value: at(found[1]) }], inequality: field.path }\n }\n\n return `${op} is not something this list can ask of ${field.column}`\n}\n\nconst disjunctionsOf = (filter: ListQueryFilter): number =>\n filter.op === 'in' || filter.op === 'array-contains-any'\n ? Math.max(1, Array.isArray(filter.value) ? filter.value.length : 1)\n : 1\n\n/**\n * Every clause and the search, as ONE query — or as much of it as one query\n * can hold, with the rest refused by name.\n *\n * Order of claim: the base (the list's scope) first, then the search, then\n * the clauses in the order they were added. A clause that would be a second\n * array clause, a second inequality field or push the disjunctions past\n * thirty is refused, and the list shows the refusal beside its chip; the\n * clauses before it stand. The search is claimed before the clauses because\n * it is the thing a reader typed last and looks at.\n */\nexport function planListQuery(\n declaration: ListQueryDeclaration,\n request: ListQueryRequest,\n names: ListQueryNormalizers,\n): ListQueryPlan {\n const filters: ListQueryFilter[] = [...(request.base ?? [])]\n const served: ListFilterRequest[] = []\n const refused: ListQueryRefusal[] = []\n const notices: string[] = []\n let arrayTaken = filters.some(\n (filter) => filter.op === 'array-contains' || filter.op === 'array-contains-any',\n )\n let inequality: string | null =\n filters.find((filter) => ['!=', '<', '<=', '>', '>='].includes(filter.op))?.path ?? null\n let disjunctions = filters.reduce((product, filter) => product * disjunctionsOf(filter), 1)\n\n // The search.\n let searched: string | null = null\n const words = (request.search ?? []).map((word) => word.trim()).filter(Boolean)\n if (words.length) {\n const search = declaration.search\n const token = names.token(words.join(' '))\n if (!search) {\n refused.push({ clause: 'search', reason: 'this list has no search' })\n } else if (!token) {\n // Nothing searchable was typed.\n } else {\n const scopeAt = filters.findIndex(\n (filter) => filter.op === 'array-contains-any' || filter.op === 'array-contains',\n )\n if (scopeAt === -1) {\n filters.push({ path: search.tokensPath, op: 'array-contains', value: token })\n arrayTaken = true\n searched = token\n } else if (search.scoped) {\n // Fold the search into the scope: one array clause answers both.\n const scope = filters[scopeAt]\n const scopes = Array.isArray(scope.value) ? scope.value : [scope.value]\n filters.splice(scopeAt, 1, {\n path: search.scoped.tokensPath,\n op: 'array-contains-any',\n value: scopes.map((entry) => `${String(entry)}${search.scoped?.join}${token}`),\n })\n searched = token\n } else {\n refused.push({\n clause: 'search',\n reason: 'this list is already narrowed to what you can see, which search cannot combine with',\n })\n }\n if (searched && words.length > 1) {\n notices.push(\n `Search matches one word at a time: showing results for \"${token}\".`,\n )\n }\n if (searched && names.key(words[0]).length > names.maxPrefix) {\n notices.push(`Search reads the first ${names.maxPrefix} letters of a word.`)\n }\n }\n }\n\n // The clauses.\n for (const clause of request.clauses) {\n const field = declaration.fields.find((entry) => entry.column === clause.field)\n if (!field) {\n refused.push({ clause, reason: 'this list does not filter by that' })\n continue\n }\n const shape = shapeClause(field, clause, names)\n if (typeof shape === 'string') {\n refused.push({ clause, reason: shape })\n continue\n }\n if (shape.array && arrayTaken) {\n refused.push({\n clause,\n reason: searched\n ? 'cannot be combined with the search — clear the search to use it'\n : 'cannot be combined with another \"contains\" or \"any of\" filter on a list',\n })\n continue\n }\n if (shape.inequality && inequality && shape.inequality !== inequality) {\n refused.push({\n clause,\n reason: 'only one range (dates, numbers, starts with) can apply at a time',\n })\n continue\n }\n const product =\n disjunctions * shape.filters.reduce((total, filter) => total * disjunctionsOf(filter), 1)\n if (product > LIST_QUERY_DISJUNCTIONS) {\n refused.push({ clause, reason: `too many values at once (the limit is ${LIST_QUERY_DISJUNCTIONS})` })\n continue\n }\n filters.push(...shape.filters)\n served.push(clause)\n if (shape.array) arrayTaken = true\n if (shape.inequality) inequality = shape.inequality\n disjunctions = product\n }\n\n // The order.\n const asked = request.sort\n ? declaration.sorts.find(\n (sort) => sort.path === request.sort?.path && sort.direction === request.sort?.direction,\n )\n : undefined\n const orderBy = inequality\n ? rangeOrder(\n declaration,\n declaration.fields.find(\n (field) =>\n field.path === inequality ||\n field.lowerPath === inequality ||\n field.reversedPath === inequality,\n ),\n inequality,\n )\n : (asked ?? declaration.sorts[0] ?? { path: LIST_QUERY_ID_PATH, direction: 'asc' })\n\n return { filters, orderBy, served, searched, refused, notices }\n}\n\n/** A composite the plan's shapes need, in the index file's own terms. */\nexport interface ListQueryIndex {\n fields: Array<{ fieldPath: string; order?: 'ASCENDING' | 'DESCENDING'; arrayConfig?: 'CONTAINS' }>\n}\n\nconst ORDER = { asc: 'ASCENDING', desc: 'DESCENDING' } as const\n\n/**\n * Every composite a declaration's queries can need, under index merging:\n * one `(predicate field, order)` index per equality or array field and per\n * order the list can take — its sorts, and the order each range imposes.\n *\n * `base` names the scope predicates the list always applies (their paths\n * and whether they are array clauses), which need their own index per order\n * like any other equality. Pinned by each list's spec against\n * `cloud/firebase-firestore.indexes.json`.\n */\nexport function listQueryIndexes(\n declaration: ListQueryDeclaration,\n base: readonly { path: string; array?: boolean }[] = [],\n): ListQueryIndex[] {\n const equalities = new Map<string, boolean>() // path → is array clause\n const orders: ListQuerySort[] = [...declaration.sorts]\n const addOrder = (sort: ListQuerySort) => {\n if (!orders.some((entry) => entry.path === sort.path && entry.direction === sort.direction)) {\n orders.push(sort)\n }\n }\n for (const entry of base) equalities.set(entry.path, Boolean(entry.array))\n if (declaration.search) {\n equalities.set(declaration.search.tokensPath, true)\n if (declaration.search.scoped) equalities.set(declaration.search.scoped.tokensPath, true)\n }\n for (const field of declaration.fields) {\n if (field.windowOnly) continue\n const ops = listFilterOperators(field)\n const has = (...names: string[]) => names.some((name) => ops.includes(name))\n if (field.keysOf) {\n for (const key of declaration.keys?.[field.column] ?? []) {\n equalities.set(`${field.path}.${key}`, false)\n }\n continue\n }\n if (field.kind === 'text') {\n if (has('contains') && field.tokensPath) equalities.set(field.tokensPath, true)\n if (has('equals', 'isAnyOf') && field.lowerPath) equalities.set(field.lowerPath, false)\n if (has('equals') && !field.lowerPath && field.presence === 'always') equalities.set(field.path, false)\n if (has('startsWith') && field.lowerPath) addOrder(rangeOrder(declaration, field, field.lowerPath))\n if (has('endsWith') && field.reversedPath) addOrder(rangeOrder(declaration, field, field.reversedPath))\n } else if (field.kind === 'exact') {\n if (has('equals', 'isAnyOf')) equalities.set(field.path, false)\n if (has('contains', 'isAnyOf') && field.tokensPath) equalities.set(field.tokensPath, true)\n } else if (field.kind === 'boolean') {\n equalities.set(field.path, false)\n } else if (field.kind === 'number') {\n if (has('=')) equalities.set(field.path, false)\n if (has('!=', '>', '>=', '<', '<=')) addOrder(rangeOrder(declaration, field, field.path))\n } else if (field.kind === 'date') {\n if (has('is', 'after', 'onOrAfter', 'before', 'onOrBefore')) {\n addOrder(rangeOrder(declaration, field, field.path))\n }\n }\n if (has('isEmpty')) equalities.set(field.path, false)\n if (has('isNotEmpty')) addOrder(rangeOrder(declaration, field, field.path))\n }\n const indexes: ListQueryIndex[] = []\n const seen = new Set<string>()\n for (const [path, array] of equalities) {\n for (const order of orders) {\n if (order.path === path || order.path === LIST_QUERY_ID_PATH) continue\n const index: ListQueryIndex = {\n fields: [\n array ? { fieldPath: path, arrayConfig: 'CONTAINS' } : { fieldPath: path, order: 'ASCENDING' },\n { fieldPath: order.path, order: ORDER[order.direction] },\n ],\n }\n const key = JSON.stringify(index)\n if (!seen.has(key)) {\n seen.add(key)\n indexes.push(index)\n }\n }\n }\n return indexes\n}\n\n/** An index entry as `cloud/firebase-firestore.indexes.json` stores it. */\ninterface IndexFileEntry {\n collectionGroup: string\n queryScope: string\n fields: Array<{ fieldPath: string; order?: string; arrayConfig?: string }>\n}\n\n/**\n * The composites a declaration needs that an index file does not hold — what\n * a list's spec asserts is empty, so a declared filter cannot ship without\n * the index that serves it.\n */\nexport function missingListQueryIndexes(\n file: { indexes: readonly IndexFileEntry[] },\n collectionGroup: string,\n needed: readonly ListQueryIndex[],\n queryScope: 'COLLECTION' | 'COLLECTION_GROUP' = 'COLLECTION',\n): ListQueryIndex[] {\n const shape = (fields: IndexFileEntry['fields']) =>\n fields\n .filter((field) => field.fieldPath !== LIST_QUERY_ID_PATH)\n .map((field) => `${field.fieldPath}:${field.order ?? field.arrayConfig}`)\n .join(',')\n const present = new Set(\n file.indexes\n .filter((index) => index.collectionGroup === collectionGroup && index.queryScope === queryScope)\n .map((index) => shape(index.fields)),\n )\n return needed.filter((index) => !present.has(shape(index.fields)))\n}\n"],"names":["listFilterDay","listFilterOperators","LIST_QUERY_ID_PATH","LIST_QUERY_DISJUNCTIONS","HIGH","csv","raw","split","map","entry","trim","filter","Boolean","dayBounds","start","Number","isNaN","getTime","day","Date","setHours","end","setDate","getDate","rangeOrder","declaration","field","path","offered","sorts","find","sort","direction","kind","shapeClause","input","names","windowOnly","includes","op","column","value","presence","filters","inequality","keysOf","test","tokensPath","token","verbatimTokens","array","lowerPath","key","reversedPath","reversed","values","length","disjunctions","isFinite","comparison","found","at","date","storedAs","bound","after","onOrAfter","before","onOrBefore","disjunctionsOf","Math","max","Array","isArray","planListQuery","request","asked","base","served","refused","notices","arrayTaken","some","reduce","product","searched","words","search","word","join","push","clause","reason","scopeAt","findIndex","scoped","scope","scopes","splice","String","maxPrefix","clauses","fields","shape","total","undefined","orderBy","ORDER","asc","desc","listQueryIndexes","equalities","Map","orders","addOrder","set","ops","has","name","keys","indexes","seen","Set","order","index","fieldPath","arrayConfig","JSON","stringify","add","missingListQueryIndexes","file","collectionGroup","needed","queryScope","present"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/const/list-query-plan.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n type ListFilterField,\n type ListFilterRequest,\n listFilterDay,\n listFilterOperators,\n} from './list-filter'\n\n/*\n * EVERY CLAUSE ON THE QUERY (AGL-3321).\n *\n * The AGL-2501 contract turned ONE filter into a Firestore query — the list\n * put the first servable clause on its query and matched the rest over the\n * rows it had loaded, and search the same way. A clause matched over a loaded\n * page answers \"no match\" for a record on the next one, which is the one\n * answer a list must never give wrongly.\n *\n * This composes them all. A list declares its fields (the same\n * `ListFilterField` grammar), the sorts it offers and the token array its\n * quick search reads; `planListQuery` turns the clauses in force and the\n * search words into ONE query — every predicate, one order — and says which\n * it could not put there and why. Nothing is matched in memory afterwards:\n * a clause the plan refuses is not applied, and the list says so, rather\n * than being applied to some rows and not others.\n *\n * The plan is SDK-free and model-free: the text normalizers the written\n * search fields were stamped with come in as `ListQueryNormalizers` — the\n * platform's are `nameSearchNormalizers` (`@aglyn/aglyn/app-utils/name-search`)\n * — so the query asks for exactly the keys the writers stored. The web twin (`listQueryConstraints` in the instance\n * library) and the Admin twin (`applyListQuery` in the console) turn it into\n * their SDK's query, so the two cannot disagree about what a clause means.\n *\n * ## What one Firestore query can hold\n *\n * - any number of equalities (`==`, `in`), composed through INDEX MERGING:\n * one `(field, sort)` composite per equality field serves every\n * combination of them, measured against production (AGL-3321);\n * - ONE array clause — `array-contains` or `array-contains-any` — which a\n * search token, a `contains` filter and a scope clause all compete for;\n * - ONE inequality field (a range, `!=`, a prefix range), which must then\n * lead the order — so a range orders the list by the field it ranges over;\n * - at most thirty disjunctions, which an `in` and an\n * `array-contains-any` multiply.\n *\n * The declaration's `listQueryIndexes` enumerates the composites those\n * shapes need, and each list's spec holds the index file to it.\n */\n\n/**\n * How the written search fields were normalized, so a typed value becomes the\n * key the query can match: the lower-cased key (`equals`, `startsWith`), the\n * one word-prefix token (`contains`, search), and the reversed key\n * (`endsWith`). `maxPrefix` is the longest token a writer stores.\n */\nexport interface ListQueryNormalizers {\n key(value: string): string\n token(value: string): string\n reversed(value: string): string\n maxPrefix: number\n}\n\n/** A predicate on the query, SDK-free. `path` `__name__` is the document id. */\nexport type ListQueryOp =\n | '=='\n | '!='\n | '<'\n | '<='\n | '>'\n | '>='\n | 'in'\n | 'array-contains'\n | 'array-contains-any'\n\nexport type ListQueryValue = string | number | boolean | null | Date | readonly (string | number)[]\n\nexport interface ListQueryFilter {\n path: string\n op: ListQueryOp\n value: ListQueryValue\n}\n\nexport interface ListQuerySort {\n path: string\n direction: 'asc' | 'desc'\n /** The grid column this order sorts, when the list lets the grid sort by it. */\n column?: string\n}\n\n/** The document id, as a path. */\nexport const LIST_QUERY_ID_PATH = '__name__'\n\n/** Firestore's cap on disjunctions — `in` values times `array-contains-any` values. */\nexport const LIST_QUERY_DISJUNCTIONS = 30\n\n/** A very high private-use codepoint: `[p, p + HIGH]` spans every string starting `p`. */\nconst HIGH = '\\uf8ff'\n\nexport interface ListQueryDeclaration {\n /** Every field the Filters panel may name. */\n fields: readonly ListFilterField[]\n /**\n * The orders the list offers, its default FIRST. The query takes no other:\n * each is a composite the index file carries for every filterable field.\n */\n sorts: readonly ListQuerySort[]\n /**\n * The quick search: the token array every write stamps (see\n * `nameSearchTokens`), queried with `array-contains` on one typed word.\n * Absent, the list offers no search box.\n */\n search?: {\n tokensPath: string\n /**\n * Under a scope clause the search cannot be a SECOND array clause, so a\n * scoped list writes `<scopeToken><join><prefix>` tokens and the query\n * asks `array-contains-any` over the scope's tokens joined to the word —\n * one array clause that answers both.\n *\n * ⛔ Rules that grant a list from `visibleTo.hasAny(...)` DENY this\n * query, because it no longer constrains `visibleTo`. Declare it only for\n * readers whose rule needs no `visibleTo` term, or for an Admin-SDK read;\n * see `SCOPED_SEARCH_JOIN` in `@aglyn/aglyn/app-utils/name-search`.\n */\n scoped?: { tokensPath: string; join: string }\n }\n /**\n * Keys a presence-map field (`keysOf`) is filtered by, for the index\n * enumeration: `sources.form`, `sources.order`, …\n */\n keys?: Readonly<Record<string, readonly string[]>>\n}\n\n/** What the list asks this time. */\nexport interface ListQueryRequest {\n clauses: readonly ListFilterRequest[]\n /** The quick search's words. */\n search?: readonly string[]\n /** The order asked for; a sort the declaration does not offer is ignored. */\n sort?: ListQuerySort | null\n /**\n * Predicates the list always applies — its scope — which count against\n * the same limits. An `array-contains-any` scope is what a scoped search\n * folds into (`declaration.search.scoped`).\n */\n base?: readonly ListQueryFilter[]\n}\n\nexport interface ListQueryRefusal {\n /** The clause refused, or `search`. */\n clause: ListFilterRequest | 'search'\n reason: string\n}\n\nexport interface ListQueryPlan {\n /** Every predicate, base included, in the order the SDK should add them. */\n filters: ListQueryFilter[]\n /** The one order: the range field's when there is one, else the sort. */\n orderBy: ListQuerySort\n /** The clauses on the query. */\n served: ListFilterRequest[]\n /** The search word on the query, or null. */\n searched: string | null\n /** What was asked and could not be put on the query, with why. */\n refused: ListQueryRefusal[]\n /** Said to the reader about what WAS served — e.g. one search word of two. */\n notices: string[]\n}\n\n/** How one clause lands on a query, before it is composed with the others. */\ninterface ClauseShape {\n filters: ListQueryFilter[]\n /** The inequality field the clause needs to lead the order, if any. */\n inequality?: string\n /** The clause is the query's array clause. */\n array?: boolean\n /** `in` values, for the disjunction budget. */\n disjunctions?: number\n}\n\nconst csv = (raw: string): string[] =>\n raw\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean)\n\nconst dayBounds = (raw: string): { start: Date; end: Date } | null => {\n const start = listFilterDay(raw)\n if (Number.isNaN(start.getTime())) return null\n const day = new Date(start)\n day.setHours(0, 0, 0, 0)\n const end = new Date(day)\n end.setDate(end.getDate() + 1)\n return { start: day, end }\n}\n\n/** The order a range over `path` puts the list in. */\nconst rangeOrder = (\n declaration: ListQueryDeclaration,\n field: ListFilterField | undefined,\n path: string,\n): ListQuerySort => {\n const offered = declaration.sorts.find((sort) => sort.path === path)\n if (offered) return offered\n return { path, direction: field?.kind === 'date' ? 'desc' : 'asc' }\n}\n\n/**\n * One clause as predicates, or the reason it has none. The same operator\n * meanings as the AGL-2501 twins (`applyListFilter`, `listFilterConstraints`),\n * which this replaces for a list that composes clauses.\n */\nfunction shapeClause(\n field: ListFilterField,\n input: ListFilterRequest,\n names: ListQueryNormalizers,\n): ClauseShape | string {\n if (field.windowOnly) return 'this field is not stored where a query can reach it'\n if (!listFilterOperators(field).includes(input.op)) {\n return `${input.op} is not something this list can ask of ${field.column}`\n }\n const raw = (input.value ?? '').trim()\n const op = input.op\n\n if (op === 'isEmpty') {\n if (field.presence !== 'nullable') return 'only a field stored as null can be asked for empty'\n return { filters: [{ path: field.path, op: '==', value: null }] }\n }\n if (op === 'isNotEmpty') {\n if (field.presence === 'always') return 'this field is never empty'\n return { filters: [{ path: field.path, op: '!=', value: null }], inequality: field.path }\n }\n if (field.kind === 'boolean') {\n if (raw !== 'true' && raw !== 'false') return 'pick true or false'\n return { filters: [{ path: field.path, op: '==', value: raw === 'true' }] }\n }\n if (!raw) return 'no value yet'\n\n if (field.keysOf) {\n if (op !== 'equals' || !/^[A-Za-z0-9_]+$/.test(raw)) return 'pick one of the choices'\n return { filters: [{ path: `${field.path}.${raw}`, op: '==', value: true }] }\n }\n\n if (field.kind === 'text') {\n if (op === 'contains' && field.tokensPath) {\n const token = field.verbatimTokens ? raw : names.token(raw)\n if (!token) return 'no value yet'\n return {\n filters: [{ path: field.tokensPath, op: 'array-contains', value: token }],\n array: true,\n }\n }\n if (op === 'equals' && field.lowerPath) {\n return { filters: [{ path: field.lowerPath, op: '==', value: names.key(raw) }] }\n }\n if (op === 'equals' && field.presence === 'always' && !field.lowerPath) {\n return { filters: [{ path: field.path, op: '==', value: raw }] }\n }\n if (op === 'startsWith' && field.lowerPath) {\n const key = names.key(raw)\n return {\n filters: [\n { path: field.lowerPath, op: '>=', value: key },\n { path: field.lowerPath, op: '<=', value: `${key}${HIGH}` },\n ],\n inequality: field.lowerPath,\n }\n }\n if (op === 'endsWith' && field.reversedPath) {\n const key = names.reversed(raw)\n return {\n filters: [\n { path: field.reversedPath, op: '>=', value: key },\n { path: field.reversedPath, op: '<=', value: `${key}${HIGH}` },\n ],\n inequality: field.reversedPath,\n }\n }\n if (op === 'isAnyOf' && field.lowerPath) {\n const values = csv(raw).map((value) => names.key(value))\n if (values.length > LIST_QUERY_DISJUNCTIONS) return `at most ${LIST_QUERY_DISJUNCTIONS} values`\n return {\n filters: [{ path: field.lowerPath, op: 'in', value: values }],\n disjunctions: values.length,\n }\n }\n return `${op} is not something this list can ask of ${field.column}`\n }\n\n if (field.kind === 'id') {\n if (op === 'equals') return { filters: [{ path: LIST_QUERY_ID_PATH, op: '==', value: raw }] }\n if (op === 'isAnyOf') {\n const values = csv(raw)\n if (values.length > LIST_QUERY_DISJUNCTIONS) return `at most ${LIST_QUERY_DISJUNCTIONS} values`\n return {\n filters: [{ path: LIST_QUERY_ID_PATH, op: 'in', value: values }],\n disjunctions: values.length,\n }\n }\n if (op === 'startsWith') {\n return {\n filters: [\n { path: LIST_QUERY_ID_PATH, op: '>=', value: raw },\n { path: LIST_QUERY_ID_PATH, op: '<=', value: `${raw}${HIGH}` },\n ],\n inequality: LIST_QUERY_ID_PATH,\n }\n }\n return `${op} is not something this list can ask of ${field.column}`\n }\n\n if (field.kind === 'exact') {\n if (op === 'equals') return { filters: [{ path: field.path, op: '==', value: raw }] }\n if (op === 'isAnyOf') {\n const values = csv(raw)\n if (values.length > LIST_QUERY_DISJUNCTIONS) return `at most ${LIST_QUERY_DISJUNCTIONS} values`\n if (field.tokensPath) {\n // An array field asked \"any of these\": one array clause.\n return {\n filters: [{ path: field.tokensPath, op: 'array-contains-any', value: values }],\n array: true,\n disjunctions: values.length,\n }\n }\n return {\n filters: [{ path: field.path, op: 'in', value: values }],\n disjunctions: values.length,\n }\n }\n if (op === 'contains' && field.tokensPath) {\n return {\n filters: [{ path: field.tokensPath, op: 'array-contains', value: raw }],\n array: true,\n }\n }\n return `${op} is not something this list can ask of ${field.column}`\n }\n\n if (field.kind === 'number') {\n const value = Number(raw)\n if (!Number.isFinite(value)) return 'type a number'\n if (op === '=') return { filters: [{ path: field.path, op: '==', value }] }\n const comparison: Record<string, ListQueryOp> = {\n '!=': '!=',\n '>': '>',\n '>=': '>=',\n '<': '<',\n '<=': '<=',\n }\n const found = comparison[op]\n if (!found) return `${op} is not something this list can ask of ${field.column}`\n return { filters: [{ path: field.path, op: found, value }], inequality: field.path }\n }\n\n if (field.kind === 'date') {\n const day = dayBounds(raw)\n if (!day) return 'pick a date'\n // A `…AtMs` field is compared as the number it is stored as.\n const at = (date: Date): Date | number =>\n field.storedAs === 'millis' ? date.getTime() : date\n if (op === 'is') {\n return {\n filters: [\n { path: field.path, op: '>=', value: at(day.start) },\n { path: field.path, op: '<', value: at(day.end) },\n ],\n inequality: field.path,\n }\n }\n const bound: Record<string, [ListQueryOp, Date]> = {\n after: ['>=', day.end],\n onOrAfter: ['>=', day.start],\n before: ['<', day.start],\n onOrBefore: ['<', day.end],\n }\n const found = bound[op]\n if (!found) return `${op} is not something this list can ask of ${field.column}`\n return { filters: [{ path: field.path, op: found[0], value: at(found[1]) }], inequality: field.path }\n }\n\n return `${op} is not something this list can ask of ${field.column}`\n}\n\nconst disjunctionsOf = (filter: ListQueryFilter): number =>\n filter.op === 'in' || filter.op === 'array-contains-any'\n ? Math.max(1, Array.isArray(filter.value) ? filter.value.length : 1)\n : 1\n\n/**\n * Every clause and the search, as ONE query — or as much of it as one query\n * can hold, with the rest refused by name.\n *\n * Order of claim: the base (the list's scope) first, then the search, then\n * the clauses in the order they were added. A clause that would be a second\n * array clause, a second inequality field or push the disjunctions past\n * thirty is refused, and the list shows the refusal beside its chip; the\n * clauses before it stand. The search is claimed before the clauses because\n * it is the thing a reader typed last and looks at.\n */\nexport function planListQuery(\n declaration: ListQueryDeclaration,\n request: ListQueryRequest,\n names: ListQueryNormalizers,\n): ListQueryPlan {\n const filters: ListQueryFilter[] = [...(request.base ?? [])]\n const served: ListFilterRequest[] = []\n const refused: ListQueryRefusal[] = []\n const notices: string[] = []\n let arrayTaken = filters.some(\n (filter) => filter.op === 'array-contains' || filter.op === 'array-contains-any',\n )\n let inequality: string | null =\n filters.find((filter) => ['!=', '<', '<=', '>', '>='].includes(filter.op))?.path ?? null\n let disjunctions = filters.reduce((product, filter) => product * disjunctionsOf(filter), 1)\n\n // The search.\n let searched: string | null = null\n const words = (request.search ?? []).map((word) => word.trim()).filter(Boolean)\n if (words.length) {\n const search = declaration.search\n const token = names.token(words.join(' '))\n if (!search) {\n refused.push({ clause: 'search', reason: 'this list has no search' })\n } else if (!token) {\n // Nothing searchable was typed.\n } else {\n const scopeAt = filters.findIndex(\n (filter) => filter.op === 'array-contains-any' || filter.op === 'array-contains',\n )\n if (scopeAt === -1) {\n filters.push({ path: search.tokensPath, op: 'array-contains', value: token })\n arrayTaken = true\n searched = token\n } else if (search.scoped) {\n // Fold the search into the scope: one array clause answers both.\n const scope = filters[scopeAt]\n const scopes = Array.isArray(scope.value) ? scope.value : [scope.value]\n filters.splice(scopeAt, 1, {\n path: search.scoped.tokensPath,\n op: 'array-contains-any',\n value: scopes.map((entry) => `${String(entry)}${search.scoped?.join}${token}`),\n })\n searched = token\n } else {\n refused.push({\n clause: 'search',\n reason: 'this list is already narrowed to what you can see, which search cannot combine with',\n })\n }\n if (searched && words.length > 1) {\n notices.push(\n `Search matches one word at a time: showing results for \"${token}\".`,\n )\n }\n if (searched && names.key(words[0]).length > names.maxPrefix) {\n notices.push(`Search reads the first ${names.maxPrefix} letters of a word.`)\n }\n }\n }\n\n // The clauses.\n for (const clause of request.clauses) {\n const field = declaration.fields.find((entry) => entry.column === clause.field)\n if (!field) {\n refused.push({ clause, reason: 'this list does not filter by that' })\n continue\n }\n const shape = shapeClause(field, clause, names)\n if (typeof shape === 'string') {\n refused.push({ clause, reason: shape })\n continue\n }\n if (shape.array && arrayTaken) {\n refused.push({\n clause,\n reason: searched\n ? 'cannot be combined with the search — clear the search to use it'\n : 'cannot be combined with another \"contains\" or \"any of\" filter on a list',\n })\n continue\n }\n if (shape.inequality && inequality && shape.inequality !== inequality) {\n refused.push({\n clause,\n reason: 'only one range (dates, numbers, starts with) can apply at a time',\n })\n continue\n }\n const product =\n disjunctions * shape.filters.reduce((total, filter) => total * disjunctionsOf(filter), 1)\n if (product > LIST_QUERY_DISJUNCTIONS) {\n refused.push({ clause, reason: `too many values at once (the limit is ${LIST_QUERY_DISJUNCTIONS})` })\n continue\n }\n filters.push(...shape.filters)\n served.push(clause)\n if (shape.array) arrayTaken = true\n if (shape.inequality) inequality = shape.inequality\n disjunctions = product\n }\n\n // The order.\n const asked = request.sort\n ? declaration.sorts.find(\n (sort) => sort.path === request.sort?.path && sort.direction === request.sort?.direction,\n )\n : undefined\n const orderBy = inequality\n ? rangeOrder(\n declaration,\n declaration.fields.find(\n (field) =>\n field.path === inequality ||\n field.lowerPath === inequality ||\n field.reversedPath === inequality,\n ),\n inequality,\n )\n : (asked ?? declaration.sorts[0] ?? { path: LIST_QUERY_ID_PATH, direction: 'asc' })\n\n return { filters, orderBy, served, searched, refused, notices }\n}\n\n/** A composite the plan's shapes need, in the index file's own terms. */\nexport interface ListQueryIndex {\n fields: Array<{ fieldPath: string; order?: 'ASCENDING' | 'DESCENDING'; arrayConfig?: 'CONTAINS' }>\n}\n\nconst ORDER = { asc: 'ASCENDING', desc: 'DESCENDING' } as const\n\n/**\n * Every composite a declaration's queries can need, under index merging:\n * one `(predicate field, order)` index per equality or array field and per\n * order the list can take — its sorts, and the order each range imposes.\n *\n * `base` names the scope predicates the list always applies (their paths\n * and whether they are array clauses), which need their own index per order\n * like any other equality. Pinned by each list's spec against\n * `cloud/firebase-firestore.indexes.json`.\n */\nexport function listQueryIndexes(\n declaration: ListQueryDeclaration,\n base: readonly { path: string; array?: boolean }[] = [],\n): ListQueryIndex[] {\n const equalities = new Map<string, boolean>() // path → is array clause\n const orders: ListQuerySort[] = [...declaration.sorts]\n const addOrder = (sort: ListQuerySort) => {\n if (!orders.some((entry) => entry.path === sort.path && entry.direction === sort.direction)) {\n orders.push(sort)\n }\n }\n for (const entry of base) equalities.set(entry.path, Boolean(entry.array))\n if (declaration.search) {\n equalities.set(declaration.search.tokensPath, true)\n if (declaration.search.scoped) equalities.set(declaration.search.scoped.tokensPath, true)\n }\n for (const field of declaration.fields) {\n if (field.windowOnly) continue\n const ops = listFilterOperators(field)\n const has = (...names: string[]) => names.some((name) => ops.includes(name))\n if (field.keysOf) {\n for (const key of declaration.keys?.[field.column] ?? []) {\n equalities.set(`${field.path}.${key}`, false)\n }\n continue\n }\n if (field.kind === 'text') {\n if (has('contains') && field.tokensPath) equalities.set(field.tokensPath, true)\n if (has('equals', 'isAnyOf') && field.lowerPath) equalities.set(field.lowerPath, false)\n if (has('equals') && !field.lowerPath && field.presence === 'always') equalities.set(field.path, false)\n if (has('startsWith') && field.lowerPath) addOrder(rangeOrder(declaration, field, field.lowerPath))\n if (has('endsWith') && field.reversedPath) addOrder(rangeOrder(declaration, field, field.reversedPath))\n } else if (field.kind === 'exact') {\n if (has('equals', 'isAnyOf')) equalities.set(field.path, false)\n if (has('contains', 'isAnyOf') && field.tokensPath) equalities.set(field.tokensPath, true)\n } else if (field.kind === 'boolean') {\n equalities.set(field.path, false)\n } else if (field.kind === 'number') {\n if (has('=')) equalities.set(field.path, false)\n if (has('!=', '>', '>=', '<', '<=')) addOrder(rangeOrder(declaration, field, field.path))\n } else if (field.kind === 'date') {\n if (has('is', 'after', 'onOrAfter', 'before', 'onOrBefore')) {\n addOrder(rangeOrder(declaration, field, field.path))\n }\n }\n if (has('isEmpty')) equalities.set(field.path, false)\n if (has('isNotEmpty')) addOrder(rangeOrder(declaration, field, field.path))\n }\n const indexes: ListQueryIndex[] = []\n const seen = new Set<string>()\n for (const [path, array] of equalities) {\n for (const order of orders) {\n if (order.path === path || order.path === LIST_QUERY_ID_PATH) continue\n const index: ListQueryIndex = {\n fields: [\n array ? { fieldPath: path, arrayConfig: 'CONTAINS' } : { fieldPath: path, order: 'ASCENDING' },\n { fieldPath: order.path, order: ORDER[order.direction] },\n ],\n }\n const key = JSON.stringify(index)\n if (!seen.has(key)) {\n seen.add(key)\n indexes.push(index)\n }\n }\n }\n return indexes\n}\n\n/** An index entry as `cloud/firebase-firestore.indexes.json` stores it. */\ninterface IndexFileEntry {\n collectionGroup: string\n queryScope: string\n fields: Array<{ fieldPath: string; order?: string; arrayConfig?: string }>\n}\n\n/**\n * The composites a declaration needs that an index file does not hold — what\n * a list's spec asserts is empty, so a declared filter cannot ship without\n * the index that serves it.\n */\nexport function missingListQueryIndexes(\n file: { indexes: readonly IndexFileEntry[] },\n collectionGroup: string,\n needed: readonly ListQueryIndex[],\n queryScope: 'COLLECTION' | 'COLLECTION_GROUP' = 'COLLECTION',\n): ListQueryIndex[] {\n const shape = (fields: IndexFileEntry['fields']) =>\n fields\n .filter((field) => field.fieldPath !== LIST_QUERY_ID_PATH)\n .map((field) => `${field.fieldPath}:${field.order ?? field.arrayConfig}`)\n .join(',')\n const present = new Set(\n file.indexes\n .filter((index) => index.collectionGroup === collectionGroup && index.queryScope === queryScope)\n .map((index) => shape(index.fields)),\n )\n return needed.filter((index) => !present.has(shape(index.fields)))\n}\n"],"names":["listFilterDay","listFilterOperators","LIST_QUERY_ID_PATH","LIST_QUERY_DISJUNCTIONS","HIGH","csv","raw","split","map","entry","trim","filter","Boolean","dayBounds","start","Number","isNaN","getTime","day","Date","setHours","end","setDate","getDate","rangeOrder","declaration","field","path","offered","sorts","find","sort","direction","kind","shapeClause","input","names","windowOnly","includes","op","column","value","presence","filters","inequality","keysOf","test","tokensPath","token","verbatimTokens","array","lowerPath","key","reversedPath","reversed","values","length","disjunctions","isFinite","comparison","found","at","date","storedAs","bound","after","onOrAfter","before","onOrBefore","disjunctionsOf","Math","max","Array","isArray","planListQuery","request","asked","base","served","refused","notices","arrayTaken","some","reduce","product","searched","words","search","word","join","push","clause","reason","scopeAt","findIndex","scoped","scope","scopes","splice","String","maxPrefix","clauses","fields","shape","total","undefined","orderBy","ORDER","asc","desc","listQueryIndexes","equalities","Map","orders","addOrder","set","ops","has","name","keys","indexes","seen","Set","order","index","fieldPath","arrayConfig","JSON","stringify","add","missingListQueryIndexes","file","collectionGroup","needed","queryScope","present"],"mappings":"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"}
@@ -28,13 +28,9 @@ export interface ListGridFilterOptions {
28
28
  /** A field whose stored clauses predate the panel, translated its own way. */
29
29
  codecs?: Readonly<Record<string, ListGridFilterCodec>>;
30
30
  /**
31
- * The query serves ONE clause, so the panel's replaces every other — a
32
- * server-paged list, which has no loaded window to narrow further.
33
- * `keepAlongside` names the clauses that do narrow the loaded page and so
34
- * may stand beside it.
31
+ * The query serves ONE clause, so the panel's replaces every other.
35
32
  */
36
33
  single?: boolean;
37
- keepAlongside?: (clause: ListFilterClause) => boolean;
38
34
  /**
39
35
  * The quick search's words, held by the list when it needs them before
40
36
  * the grid's columns exist; the hook holds them itself otherwise.
@@ -74,12 +70,12 @@ export interface ListGridFilter {
74
70
  *
75
71
  * ## The list answers, never the grid
76
72
  *
77
- * Every list passes `filterMode="server"`: the grid holds one page, or a
78
- * window the query already narrowed, so a filter the grid ran itself would
79
- * answer "no match" for a row on the next page. The list reads the clauses
80
- * — onto its query where it can, over its loaded rows where it cannot
81
- * (`filterListRows`) — and the quick search's words, and hands the grid the
82
- * rows that answer.
73
+ * Every list passes `filterMode="server"`: the grid holds one page, so a
74
+ * filter the grid ran itself would answer "no match" for a row on the next
75
+ * page. The list puts the clauses and the quick search's words on its query
76
+ * (`planListQuery`, AGL-3321), refuses by name what one query cannot hold,
77
+ * and hands the grid the page that query returned. Nothing is matched over
78
+ * rows already loaded.
83
79
  */
84
80
  export declare function useListGridFilter(options?: ListGridFilterOptions): ListGridFilter;
85
81
  export default useListGridFilter;
@@ -36,16 +36,16 @@ const NO_CLAUSES = [];
36
36
  *
37
37
  * ## The list answers, never the grid
38
38
  *
39
- * Every list passes `filterMode="server"`: the grid holds one page, or a
40
- * window the query already narrowed, so a filter the grid ran itself would
41
- * answer "no match" for a row on the next page. The list reads the clauses
42
- * — onto its query where it can, over its loaded rows where it cannot
43
- * (`filterListRows`) — and the quick search's words, and hands the grid the
44
- * rows that answer.
39
+ * Every list passes `filterMode="server"`: the grid holds one page, so a
40
+ * filter the grid ran itself would answer "no match" for a row on the next
41
+ * page. The list puts the clauses and the quick search's words on its query
42
+ * (`planListQuery`, AGL-3321), refuses by name what one query cannot hold,
43
+ * and hands the grid the page that query returned. Nothing is matched over
44
+ * rows already loaded.
45
45
  */ export function useListGridFilter(options = {}) {
46
46
  var _options_clauses, _ref, _ref1;
47
47
  var _options_search, _options_search1;
48
- const { selectFields = [], codecs = {}, single = false, keepAlongside } = options;
48
+ const { selectFields = [], codecs = {}, single = false } = options;
49
49
  const [ownClauses, setOwnClauses] = useState([]);
50
50
  const controlled = options.clauses !== undefined;
51
51
  const clauses = (_options_clauses = options.clauses) != null ? _options_clauses : controlled ? NO_CLAUSES : ownClauses;
@@ -129,9 +129,9 @@ const NO_CLAUSES = [];
129
129
  return;
130
130
  }
131
131
  setPending(null);
132
- // A clause that stands alongside the served one is added beside it;
133
- // any other replaces whatever the query was serving.
134
- const base = single && !(keepAlongside == null ? void 0 : keepAlongside(clause)) ? clauses.filter((entry)=>entry.field === field || (keepAlongside == null ? void 0 : keepAlongside(entry))) : clauses;
132
+ // On a single-clause list the new clause replaces whatever the query
133
+ // was serving.
134
+ const base = single ? clauses.filter((entry)=>entry.field === field) : clauses;
135
135
  const next = upsertListFilterClause(base, field, clause);
136
136
  const same = next.length === clauses.length && next.every((entry, at)=>entry.field === clauses[at].field && entry.op === clauses[at].op && entry.value === clauses[at].value);
137
137
  if (!same) onChange(next);
@@ -142,8 +142,7 @@ const NO_CLAUSES = [];
142
142
  clauses,
143
143
  onChange,
144
144
  codecFor,
145
- single,
146
- keepAlongside
145
+ single
147
146
  ]);
148
147
  return {
149
148
  filterModel,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/hooks/use-list-grid-filter.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport type { GridFilterItem, GridFilterModel } from '@mui/x-data-grid'\nimport { useCallback, useMemo, useState } from 'react'\nimport {\n type ListFilterClause,\n type ListGridFilterCodec,\n listPlainCodec,\n listSelectCodec,\n upsertListFilterClause,\n} from '../const/list-grid-filter'\n\nexport interface ListGridFilterOptions {\n /**\n * The clauses the list is narrowed by — a saved view's working filters.\n * Omitted, the hook holds them itself, for a list with no saved views.\n */\n clauses?: readonly ListFilterClause[]\n onChange?: (clauses: ListFilterClause[]) => void\n /** Fields whose values are picked, which the panel shows as a select. */\n selectFields?: readonly string[]\n /** A field whose stored clauses predate the panel, translated its own way. */\n codecs?: Readonly<Record<string, ListGridFilterCodec>>\n /**\n * The query serves ONE clause, so the panel's replaces every other — a\n * server-paged list, which has no loaded window to narrow further.\n * `keepAlongside` names the clauses that do narrow the loaded page and so\n * may stand beside it.\n */\n single?: boolean\n keepAlongside?: (clause: ListFilterClause) => boolean\n /**\n * The quick search's words, held by the list when it needs them before\n * the grid's columns exist; the hook holds them itself otherwise.\n */\n search?: { words: string[]; onChange: (words: string[]) => void }\n}\n\n/** What the grid is handed, and what the list filters its rows by. */\nexport interface ListGridFilter {\n /** Controlled: the panel's one item, and the quick search's words. */\n filterModel: GridFilterModel\n onFilterModelChange: (model: GridFilterModel) => void\n /** The quick search's words, for the list to match its rows against. */\n searchWords: string[]\n /** Every clause in force — the ones given, or the ones the hook holds. */\n clauses: readonly ListFilterClause[]\n /** Replaces every clause; what the chips call to remove one. */\n setClauses: (clauses: ListFilterClause[]) => void\n}\n\nconst NO_CLAUSES: readonly ListFilterClause[] = []\n\n/**\n * The grid's Filters panel and quick search, bound to a list's clauses\n * (AGL-3313; shared since AGL-3317). See `const/list-grid-filter.ts` for the\n * shape both sides keep.\n *\n * ## One field, one clause; the panel edits one at a time\n *\n * The free DataGrid's panel holds a single item, while a list may be\n * narrowed by several clauses. So each FIELD holds one clause, and the panel\n * is a window onto whichever field was last filtered: setting a value writes\n * that field's clause, clearing it or deleting the row removes it, and\n * choosing another column in the panel moves the window without touching\n * the clause the reader just set. Filtering one field and then another is\n * therefore both, which is what a reader who does it means; the chips over\n * the grid (`ListFilterChips`) show every clause and remove any of them.\n *\n * ## The list answers, never the grid\n *\n * Every list passes `filterMode=\"server\"`: the grid holds one page, or a\n * window the query already narrowed, so a filter the grid ran itself would\n * answer \"no match\" for a row on the next page. The list reads the clauses\n * — onto its query where it can, over its loaded rows where it cannot\n * (`filterListRows`) — and the quick search's words, and hands the grid the\n * rows that answer.\n */\nexport function useListGridFilter(options: ListGridFilterOptions = {}): ListGridFilter {\n const { selectFields = [], codecs = {}, single = false, keepAlongside } = options\n\n const [ownClauses, setOwnClauses] = useState<ListFilterClause[]>([])\n const controlled = options.clauses !== undefined\n const clauses = options.clauses ?? (controlled ? NO_CLAUSES : ownClauses)\n const onChange = useMemo(\n () => (controlled ? (options.onChange ?? (() => undefined)) : setOwnClauses),\n [controlled, options.onChange],\n )\n\n const codecFor = useCallback(\n (field: string): ListGridFilterCodec =>\n codecs[field] ?? (selectFields.includes(field) ? listSelectCodec : listPlainCodec),\n [codecs, selectFields],\n )\n\n /** The field the panel shows, once the reader has pointed it anywhere. */\n const [focus, setFocus] = useState<string | null>(null)\n /** The panel's row while it holds no value yet — a column chosen, a value not. */\n const [pending, setPending] = useState<GridFilterItem | null>(null)\n const [ownWords, setOwnWords] = useState<string[]>([])\n const searchWords = options.search?.words ?? ownWords\n const setSearchWords = options.search?.onChange ?? setOwnWords\n\n /** Whatever the panel last pointed at, else the newest clause it can show. */\n const shownField = useMemo(() => {\n if (focus) return focus\n for (let at = clauses.length - 1; at >= 0; at -= 1) {\n if (codecFor(clauses[at].field).toItem(clauses[at])) return clauses[at].field\n }\n return null\n }, [focus, clauses, codecFor])\n\n const filterModel = useMemo<GridFilterModel>(() => {\n const clause = shownField\n ? clauses.find((entry) => entry.field === shownField)\n : undefined\n const shown = clause ? codecFor(clause.field).toItem(clause) : null\n const item: GridFilterItem | null =\n pending && pending.field === shownField\n ? pending\n : clause && shown\n ? { id: 'list', field: clause.field, ...shown }\n : null\n return { items: item ? [item] : [], quickFilterValues: searchWords }\n }, [shownField, clauses, codecFor, pending, searchWords])\n\n const onFilterModelChange = useCallback(\n (model: GridFilterModel) => {\n const words = (model.quickFilterValues ?? []).map((word) => String(word))\n if (words.join(' ') !== searchWords.join(' ')) setSearchWords(words)\n\n const item = model.items[0]\n // The row deleted: the field it showed is no longer filtered.\n if (!item) {\n if (shownField && clauses.some((clause) => clause.field === shownField)) {\n onChange(upsertListFilterClause(clauses, shownField, null))\n }\n setPending(null)\n setFocus(null)\n return\n }\n const field = String(item.field)\n const moved = field !== shownField\n setFocus(field)\n const clause = codecFor(field).toClause(item)\n if (!clause) {\n // No value yet. A value cleared on the field already shown removes\n // its clause; a column just chosen leaves the last one standing.\n setPending({ ...item, id: 'list' })\n if (!moved && clauses.some((entry) => entry.field === field)) {\n onChange(upsertListFilterClause(clauses, field, null))\n }\n return\n }\n setPending(null)\n // A clause that stands alongside the served one is added beside it;\n // any other replaces whatever the query was serving.\n const base =\n single && !keepAlongside?.(clause)\n ? clauses.filter((entry) => entry.field === field || keepAlongside?.(entry))\n : clauses\n const next = upsertListFilterClause(base, field, clause)\n const same =\n next.length === clauses.length &&\n next.every(\n (entry, at) =>\n entry.field === clauses[at].field &&\n entry.op === clauses[at].op &&\n entry.value === clauses[at].value,\n )\n if (!same) onChange(next)\n },\n [searchWords, setSearchWords, shownField, clauses, onChange, codecFor, single, keepAlongside],\n )\n\n return { filterModel, onFilterModelChange, searchWords, clauses, setClauses: onChange }\n}\n\nexport default useListGridFilter\n"],"names":["useCallback","useMemo","useState","listPlainCodec","listSelectCodec","upsertListFilterClause","NO_CLAUSES","useListGridFilter","options","selectFields","codecs","single","keepAlongside","ownClauses","setOwnClauses","controlled","clauses","undefined","onChange","codecFor","field","includes","focus","setFocus","pending","setPending","ownWords","setOwnWords","searchWords","search","words","setSearchWords","shownField","at","length","toItem","filterModel","clause","find","entry","shown","item","id","items","quickFilterValues","onFilterModelChange","model","map","word","String","join","some","moved","toClause","base","filter","next","same","every","op","value","setClauses"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/hooks/use-list-grid-filter.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n'use client'\n\nimport type { GridFilterItem, GridFilterModel } from '@mui/x-data-grid'\nimport { useCallback, useMemo, useState } from 'react'\nimport {\n type ListFilterClause,\n type ListGridFilterCodec,\n listPlainCodec,\n listSelectCodec,\n upsertListFilterClause,\n} from '../const/list-grid-filter'\n\nexport interface ListGridFilterOptions {\n /**\n * The clauses the list is narrowed by — a saved view's working filters.\n * Omitted, the hook holds them itself, for a list with no saved views.\n */\n clauses?: readonly ListFilterClause[]\n onChange?: (clauses: ListFilterClause[]) => void\n /** Fields whose values are picked, which the panel shows as a select. */\n selectFields?: readonly string[]\n /** A field whose stored clauses predate the panel, translated its own way. */\n codecs?: Readonly<Record<string, ListGridFilterCodec>>\n /**\n * The query serves ONE clause, so the panel's replaces every other.\n */\n single?: boolean\n /**\n * The quick search's words, held by the list when it needs them before\n * the grid's columns exist; the hook holds them itself otherwise.\n */\n search?: { words: string[]; onChange: (words: string[]) => void }\n}\n\n/** What the grid is handed, and what the list filters its rows by. */\nexport interface ListGridFilter {\n /** Controlled: the panel's one item, and the quick search's words. */\n filterModel: GridFilterModel\n onFilterModelChange: (model: GridFilterModel) => void\n /** The quick search's words, for the list to match its rows against. */\n searchWords: string[]\n /** Every clause in force — the ones given, or the ones the hook holds. */\n clauses: readonly ListFilterClause[]\n /** Replaces every clause; what the chips call to remove one. */\n setClauses: (clauses: ListFilterClause[]) => void\n}\n\nconst NO_CLAUSES: readonly ListFilterClause[] = []\n\n/**\n * The grid's Filters panel and quick search, bound to a list's clauses\n * (AGL-3313; shared since AGL-3317). See `const/list-grid-filter.ts` for the\n * shape both sides keep.\n *\n * ## One field, one clause; the panel edits one at a time\n *\n * The free DataGrid's panel holds a single item, while a list may be\n * narrowed by several clauses. So each FIELD holds one clause, and the panel\n * is a window onto whichever field was last filtered: setting a value writes\n * that field's clause, clearing it or deleting the row removes it, and\n * choosing another column in the panel moves the window without touching\n * the clause the reader just set. Filtering one field and then another is\n * therefore both, which is what a reader who does it means; the chips over\n * the grid (`ListFilterChips`) show every clause and remove any of them.\n *\n * ## The list answers, never the grid\n *\n * Every list passes `filterMode=\"server\"`: the grid holds one page, so a\n * filter the grid ran itself would answer \"no match\" for a row on the next\n * page. The list puts the clauses and the quick search's words on its query\n * (`planListQuery`, AGL-3321), refuses by name what one query cannot hold,\n * and hands the grid the page that query returned. Nothing is matched over\n * rows already loaded.\n */\nexport function useListGridFilter(options: ListGridFilterOptions = {}): ListGridFilter {\n const { selectFields = [], codecs = {}, single = false } = options\n\n const [ownClauses, setOwnClauses] = useState<ListFilterClause[]>([])\n const controlled = options.clauses !== undefined\n const clauses = options.clauses ?? (controlled ? NO_CLAUSES : ownClauses)\n const onChange = useMemo(\n () => (controlled ? (options.onChange ?? (() => undefined)) : setOwnClauses),\n [controlled, options.onChange],\n )\n\n const codecFor = useCallback(\n (field: string): ListGridFilterCodec =>\n codecs[field] ?? (selectFields.includes(field) ? listSelectCodec : listPlainCodec),\n [codecs, selectFields],\n )\n\n /** The field the panel shows, once the reader has pointed it anywhere. */\n const [focus, setFocus] = useState<string | null>(null)\n /** The panel's row while it holds no value yet — a column chosen, a value not. */\n const [pending, setPending] = useState<GridFilterItem | null>(null)\n const [ownWords, setOwnWords] = useState<string[]>([])\n const searchWords = options.search?.words ?? ownWords\n const setSearchWords = options.search?.onChange ?? setOwnWords\n\n /** Whatever the panel last pointed at, else the newest clause it can show. */\n const shownField = useMemo(() => {\n if (focus) return focus\n for (let at = clauses.length - 1; at >= 0; at -= 1) {\n if (codecFor(clauses[at].field).toItem(clauses[at])) return clauses[at].field\n }\n return null\n }, [focus, clauses, codecFor])\n\n const filterModel = useMemo<GridFilterModel>(() => {\n const clause = shownField\n ? clauses.find((entry) => entry.field === shownField)\n : undefined\n const shown = clause ? codecFor(clause.field).toItem(clause) : null\n const item: GridFilterItem | null =\n pending && pending.field === shownField\n ? pending\n : clause && shown\n ? { id: 'list', field: clause.field, ...shown }\n : null\n return { items: item ? [item] : [], quickFilterValues: searchWords }\n }, [shownField, clauses, codecFor, pending, searchWords])\n\n const onFilterModelChange = useCallback(\n (model: GridFilterModel) => {\n const words = (model.quickFilterValues ?? []).map((word) => String(word))\n if (words.join(' ') !== searchWords.join(' ')) setSearchWords(words)\n\n const item = model.items[0]\n // The row deleted: the field it showed is no longer filtered.\n if (!item) {\n if (shownField && clauses.some((clause) => clause.field === shownField)) {\n onChange(upsertListFilterClause(clauses, shownField, null))\n }\n setPending(null)\n setFocus(null)\n return\n }\n const field = String(item.field)\n const moved = field !== shownField\n setFocus(field)\n const clause = codecFor(field).toClause(item)\n if (!clause) {\n // No value yet. A value cleared on the field already shown removes\n // its clause; a column just chosen leaves the last one standing.\n setPending({ ...item, id: 'list' })\n if (!moved && clauses.some((entry) => entry.field === field)) {\n onChange(upsertListFilterClause(clauses, field, null))\n }\n return\n }\n setPending(null)\n // On a single-clause list the new clause replaces whatever the query\n // was serving.\n const base = single ? clauses.filter((entry) => entry.field === field) : clauses\n const next = upsertListFilterClause(base, field, clause)\n const same =\n next.length === clauses.length &&\n next.every(\n (entry, at) =>\n entry.field === clauses[at].field &&\n entry.op === clauses[at].op &&\n entry.value === clauses[at].value,\n )\n if (!same) onChange(next)\n },\n [searchWords, setSearchWords, shownField, clauses, onChange, codecFor, single],\n )\n\n return { filterModel, onFilterModelChange, searchWords, clauses, setClauses: onChange }\n}\n\nexport default useListGridFilter\n"],"names":["useCallback","useMemo","useState","listPlainCodec","listSelectCodec","upsertListFilterClause","NO_CLAUSES","useListGridFilter","options","selectFields","codecs","single","ownClauses","setOwnClauses","controlled","clauses","undefined","onChange","codecFor","field","includes","focus","setFocus","pending","setPending","ownWords","setOwnWords","searchWords","search","words","setSearchWords","shownField","at","length","toItem","filterModel","clause","find","entry","shown","item","id","items","quickFilterValues","onFilterModelChange","model","map","word","String","join","some","moved","toClause","base","filter","next","same","every","op","value","setClauses"],"mappings":"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"}
@@ -1,78 +0,0 @@
1
- /**
2
- * @license
3
- * Copyright 2026 Aglyn LLC
4
- *
5
- * Licensed under the Apache License, Version 2.0 (the "License");
6
- * you may not use this file except in compliance with the License.
7
- * You may obtain a copy of the License at
8
- *
9
- * http://www.apache.org/licenses/LICENSE-2.0
10
- *
11
- * Unless required by applicable law or agreed to in writing, software
12
- * distributed under the License is distributed on an "AS IS" BASIS,
13
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
- * See the License for the specific language governing permissions and
15
- * limitations under the License.
16
- */
17
- import type { GridColDef, GridFilterModel } from '@mui/x-data-grid';
18
- import type { ListFilterField } from '../const/list-filter';
19
- import { type ListFilterClause, type ListFilterOption } from '../const/list-grid-filter';
20
- import { type ListGridFilter } from './use-list-grid-filter';
21
- export interface ListRowsFilterOptions<Row extends object> {
22
- /** Every row the list read — the whole set, or the window its query capped. */
23
- rows: readonly Row[];
24
- /** What the panel offers, in the `ListFilterField` grammar. */
25
- fields: readonly ListFilterField[];
26
- /** Choices per picked field; each field named here is shown as a select. */
27
- options?: Readonly<Record<string, readonly ListFilterOption[]>>;
28
- /** How a field reads on a chip and as a hidden column's header. */
29
- headers?: Readonly<Record<string, string>>;
30
- /** The row paths the quick search reads. */
31
- search: readonly string[];
32
- /** A clause the list's query already served, not matched again. */
33
- served?: (clause: ListFilterClause) => boolean;
34
- /** A saved view's clauses, when the list keeps them; held here otherwise. */
35
- clauses?: readonly ListFilterClause[];
36
- onChange?: (clauses: ListFilterClause[]) => void;
37
- }
38
- /** What a list that holds its rows hands its grid and its chips. */
39
- export interface ListRowsFilter<Row> {
40
- /** The rows that answer every clause and the search. */
41
- rows: Row[];
42
- /** Whether anything narrows the list — a clause or a search word. */
43
- filtering: boolean;
44
- gridFilter: ListGridFilter;
45
- /** Spread onto `ListTable`: the controlled panel, answered by the list. */
46
- gridProps: {
47
- filterMode: 'server';
48
- filterModel: GridFilterModel;
49
- onFilterModelChange: (model: GridFilterModel) => void;
50
- quickFilter: true;
51
- };
52
- /** Spread onto `ListFilterChips`. */
53
- chipsProps: {
54
- fields: readonly ListFilterField[];
55
- headers: Readonly<Record<string, string>>;
56
- clauses: readonly ListFilterClause[];
57
- onChange: (clauses: ListFilterClause[]) => void;
58
- options: Readonly<Record<string, readonly ListFilterOption[]>>;
59
- };
60
- /** The list's columns, typed for the panel (`listFilterGridColumns`). */
61
- filterColumns: (columns: readonly GridColDef[]) => GridColDef[];
62
- }
63
- /**
64
- * The grid's Filters panel and quick search for a list that HOLDS its rows
65
- * (AGL-3317) — every row it read, or the window its query capped — and so
66
- * answers both over that whole set rather than the page on screen.
67
- *
68
- * It is `useListGridFilter` with the rest of the recipe every such list
69
- * repeated: the rows matched through `filterListRows`, the columns typed
70
- * through `listFilterGridColumns`, and the props the grid and the chips
71
- * take. A list whose query can serve a clause names it in `served`, puts it
72
- * on the query, and the rows here are not matched against it again.
73
- * @deprecated AGL-3321: matches over rows a list has loaded. Serve every clause and search
74
- * word on the query instead — `planListQuery` (`@aglyn/shared-ui-jsx/const/list-query-plan`, with `nameSearchNormalizers`)
75
- * through `useListQuery` or `applyListQuery`. Removed once no list calls it.
76
- */
77
- export declare function useListRowsFilter<Row extends object>(options: ListRowsFilterOptions<Row>): ListRowsFilter<Row>;
78
- export default useListRowsFilter;