@aglyn/shared-ui-jsx 1.0.0-beta.196 → 1.0.0-beta.198

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/shared-ui-jsx",
3
- "version": "1.0.0-beta.196",
3
+ "version": "1.0.0-beta.198",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,10 +25,10 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/shared-data-mdi": "1.0.0-beta.196",
29
- "@aglyn/shared-ui-theme": "1.0.0-beta.196",
30
- "@aglyn/shared-util-tools": "1.0.0-beta.196",
31
- "@aglyn/shared-util-vendor": "1.0.0-beta.196",
28
+ "@aglyn/shared-data-mdi": "1.0.0-beta.198",
29
+ "@aglyn/shared-ui-theme": "1.0.0-beta.198",
30
+ "@aglyn/shared-util-tools": "1.0.0-beta.198",
31
+ "@aglyn/shared-util-vendor": "1.0.0-beta.198",
32
32
  "@mdi/js": "^7.4.47",
33
33
  "@swc/helpers": "0.5.23",
34
34
  "change-case": "^5.4.4",
@@ -147,13 +147,39 @@ const SELECT_FROM_GRID = {
147
147
  filterOperators: operators
148
148
  };
149
149
  };
150
+ /*
151
+ * A column made a select keeps what it DREW. The grid formats a
152
+ * `singleSelect` cell as the label of the option its value matches, and as
153
+ * nothing when none does — so a column whose `valueGetter` hands back a
154
+ * label (an address for an actor id, a site name for a scope id) went
155
+ * blank the moment it gained a filter (AGL-3321). A column with its own
156
+ * `renderCell` is left alone; any other draws the matching option's label
157
+ * when there is one, and its own value when there is not.
158
+ */ const keepsItsCell = (column, field)=>{
159
+ var _options_field_column;
160
+ if (column.renderCell) return {};
161
+ const labels = new Map(((_options_field_column = options[field.column]) != null ? _options_field_column : []).map((choice)=>[
162
+ choice.value,
163
+ choice.label
164
+ ]));
165
+ return {
166
+ renderCell: (params)=>{
167
+ var _labels_get;
168
+ const value = params.value;
169
+ if (value === undefined || value === null) return '';
170
+ return (_labels_get = labels.get(String(value))) != null ? _labels_get : String(value);
171
+ }
172
+ };
173
+ };
150
174
  const shown = columns.map((column)=>{
151
175
  if (column.filterable === false && column.hideable === false) return column;
152
176
  const field = fields.find((entry)=>entry.column === column.field);
153
177
  if (!field) return _extends({}, column, {
154
178
  filterable: false
155
179
  });
156
- if (options[field.column]) return _extends({}, column, selectProps(field));
180
+ if (options[field.column]) {
181
+ return _extends({}, column, selectProps(field), keepsItsCell(column, field));
182
+ }
157
183
  return _extends({}, column, listFilterColumn(fields, field.column));
158
184
  });
159
185
  const present = columns.map((column)=>column.field);
@@ -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 */\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 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]) return { ...column, ...selectProps(field) } as GridColDef\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 */\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","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 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 */\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 */\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":";AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,4BAA4B,QAGvB,mBAAkB;AACzB,SACEC,kBAAkB,EAClBC,mBAAmB,EAGnBC,gBAAgB,EAChBC,mBAAmB,EACnBC,eAAe,QACV,mBAAe;AA+BtB;;;;;;;;;;CAUC,GACD,OAAO,SAASC,kBACdC,MAAc,EACdC,IAAuD,EACvDC,OAAeF,MAAM;IAErB,OAAQC;QACN,KAAK;YACH,OAAO;gBACLD;gBACAE;gBACAD,MAAM;gBACNE,WAAW;oBAAC;oBAAY;oBAAkB;oBAAU;oBAAc;oBAAY;oBAAW;iBAAa;YACxG;QACF,KAAK;YACH,OAAO;gBAAEH;gBAAQE;gBAAMD,MAAM;gBAASE,WAAW;oBAAC;oBAAU;oBAAgB;iBAAU;YAAC;QACzF,KAAK;YACH,OAAO;gBAAEH;gBAAQE;gBAAMD,MAAM;gBAAUG,UAAU;YAAW;QAC9D,KAAK;YACH,OAAO;gBAAEJ;gBAAQE;gBAAMD,MAAM;gBAAQG,UAAU;YAAW;QAC5D;YACE,OAAO;gBAAEJ;gBAAQE;gBAAMD,MAAM;YAAU;IAC3C;AACF;AAEA,4DAA4D,GAC5D,MAAMI,iBAAmD;IACvDC,QAAQ;IACRC,cAAc;IACdC,SAAS;AACX;AACA,MAAMC,mBAAqD;IACzDC,IAAI;IACJC,KAAK;IACLH,SAAS;AACX;AAaA,gFAAgF,GAChF,MAAMI,cAAc,CAACC;QACnBnB;YAAAA,uBAAAA,mBAAmB;QAAEoB,OAAO;YAACD;SAAK;IAAC,EAAE,CAAC,EAAE,YAAxCnB,uBAA4C;;AAE9C,sDAAsD,GACtD,OAAO,MAAMqB,iBAAsC;IACjDC,QAAQ,CAACC,SAAY,CAAA;YACnBC,UAAUD,OAAOE,EAAE;YACnBC,OAAOH,OAAOG,KAAK,KAAK,KAAKC,YAAYJ,OAAOG,KAAK;QACvD,CAAA;IACAE,UAAUV;AACZ,EAAC;AAED,8EAA8E,GAC9E,OAAO,MAAMW,kBAAuC;IAClDP,QAAQ,CAACC;QACP,MAAMC,WAAWb,cAAc,CAACY,OAAOE,EAAE,CAAC;QAC1C,IAAI,CAACD,UAAU,OAAO;QACtB,OAAO;YACLA;YACAE,OACEF,aAAa,YACTD,OAAOG,KAAK,CAACI,KAAK,CAAC,KAAKC,GAAG,CAAC,CAACC,QAAUA,MAAMC,IAAI,IAAIC,MAAM,CAACC,WAC5DZ,OAAOG,KAAK;QACpB;IACF;IACAE,UAAU,CAACT;QACT,MAAMM,KAAKV,gBAAgB,CAACqB,OAAOjB,KAAKK,QAAQ,EAAE;QAClD,MAAMa,UAAUZ,KAAKP,YAAYC,QAAQ;QACzC,OAAOkB,WAAWZ,KAAK,aAAKY;YAASZ;aAAO;IAC9C;AACF,EAAC;AAED,oEAAoE,GACpE,OAAO,SAASa,oBAAoBC,OAA0B;IAC5D,MAAMC,QAAQD,QAAQR,GAAG,CAAC,CAACN,KAAOd,cAAc,CAACc,GAAG,EAAES,MAAM,CAACC;IAC7D,OAAOpC,+BAA+BmC,MAAM,CAAC,CAACV,WAC5CgB,MAAMC,QAAQ,CAACjB,SAASE,KAAK;AAEjC;AAEA;;;;;;;;;;CAUC,GACD,OAAO,SAASgB,sBACdC,OAA8B,EAC9BC,MAAkC,EAClCC,UAAiE,CAAC,CAAC,EACnEC,UAA4C,CAAC,CAAC;IAE9C,MAAMC,cAAc,CAACC;YACHH;QAAhB,MAAMI,WAAUJ,wBAAAA,OAAO,CAACG,MAAM1C,MAAM,CAAC,YAArBuC,wBAAyB,EAAE;QAC3C,MAAMpC,YAAY6B,oBAAoBnC,oBAAoB6C;QAC1D,OAAO;YACLE,MAAM;YACNC,cAAcF,QAAQlB,GAAG,CAAC,CAACqB,SAAY,CAAA;oBAAE1B,OAAO0B,OAAO1B,KAAK;oBAAE2B,OAAOD,OAAOC,KAAK;gBAAC,CAAA;YAClFC,YAAYL,QAAQM,MAAM,GAAG,KAAK9C,UAAU8C,MAAM,GAAG;YACrDC,iBAAiB/C;QACnB;IACF;IACA;;;;;;;;GAQC,GACD,MAAMgD,eAAe,CAACnD,QAAoB0C;YAGrCH;QAFH,IAAIvC,OAAOoD,UAAU,EAAE,OAAO,CAAC;QAC/B,MAAMC,SAAS,IAAIC,IACjB,EAACf,wBAAAA,OAAO,CAACG,MAAM1C,MAAM,CAAC,YAArBuC,wBAAyB,EAAE,EAAEd,GAAG,CAAC,CAACqB,SAAW;gBAACA,OAAO1B,KAAK;gBAAE0B,OAAOC,KAAK;aAAC;QAE5E,OAAO;YACLK,YAAY,CAACG;oBAGJF;gBAFP,MAAMjC,QAAQmC,OAAOnC,KAAK;gBAC1B,IAAIA,UAAUC,aAAaD,UAAU,MAAM,OAAO;gBAClD,QAAOiC,cAAAA,OAAOG,GAAG,CAAC1B,OAAOV,mBAAlBiC,cAA6BvB,OAAOV;YAC7C;QACF;IACF;IACA,MAAMc,QAAQG,QAAQZ,GAAG,CAAC,CAACzB;QACzB,IAAIA,OAAOgD,UAAU,KAAK,SAAShD,OAAOyD,QAAQ,KAAK,OAAO,OAAOzD;QACrE,MAAM0C,QAAQJ,OAAOoB,IAAI,CAAC,CAAChC,QAAUA,MAAM1B,MAAM,KAAKA,OAAO0C,KAAK;QAClE,IAAI,CAACA,OAAO,OAAO,aAAK1C;YAAQgD,YAAY;;QAC5C,IAAIT,OAAO,CAACG,MAAM1C,MAAM,CAAC,EAAE;YACzB,OAAO,aAAKA,QAAWyC,YAAYC,QAAWS,aAAanD,QAAQ0C;QACrE;QACA,OAAO,aAAK1C,QAAWJ,iBAAiB0C,QAAQI,MAAM1C,MAAM;IAC9D;IACA,MAAM2D,UAAUtB,QAAQZ,GAAG,CAAC,CAACzB,SAAWA,OAAO0C,KAAK;IACpD,MAAMkB,SAASjE,oBAAoB2C,QAAQqB,SAASnB,SAASf,GAAG,CAC9D,CAACzB;QACC,MAAM0C,QAAQJ,OAAOoB,IAAI,CAAC,CAAChC,QAAUA,MAAM1B,MAAM,KAAKA,OAAO0C,KAAK;QAClE,OAAOA,SAASH,OAAO,CAACG,MAAM1C,MAAM,CAAC,GAChC,aAAKA,QAAWyC,YAAYC,UAC7B1C;IACN;IAEF,OAAO;WAAIkC;WAAU0B;KAAO;AAC9B;AAEA;;;;;CAKC,GACD,OAAO,SAASC,qBACdC,GAAW,EACXC,KAAwB,EACxBC,KAAwB;IAExB,MAAMC,QAAQD,MAAMvC,GAAG,CAAC,CAACyC,OAASA,KAAKvC,IAAI,GAAGwC,WAAW,IAAIvC,MAAM,CAACC;IACpE,IAAI,CAACoC,MAAMhB,MAAM,EAAE,OAAO;IAC1B,MAAMmB,SAASL,MACZM,OAAO,CAAC,CAACnE;QACR,MAAMkB,QAAQlB,KACXsB,KAAK,CAAC,KACN8C,MAAM,CACL,CAACC,IAAIC,MAASD,MAAM,OAAOlD,YAAY,AAACkD,EAA8B,CAACC,IAAI,EAC3EV;QAEJ,OAAOW,MAAMC,OAAO,CAACtD,SAASA,QAAQ;YAACA;SAAM;IAC/C,GACCQ,MAAM,CAAC,CAACR,QAAoC,OAAOA,UAAU,YAAY,OAAOA,UAAU,UAC1FK,GAAG,CAAC,CAACL,QAAUU,OAAOV,OAAO+C,WAAW,IACxCvC,MAAM,CAACC;IACV,OAAOoC,MAAMU,KAAK,CAAC,CAACT,OAASE,OAAOQ,IAAI,CAAC,CAACxD,QAAUA,MAAMe,QAAQ,CAAC+B;AACrE;AAEA;;;;;CAKC,GACD,OAAO,SAASW,eACdC,IAAoB,EACpBxC,MAAkC,EAClCyC,OAAoC,EACpCC,MAA8D,EAC9DC,IAA4C;IAE5C,MAAMC,UAAUD,OAAOF,QAAQnD,MAAM,CAAC,CAACX,SAAW,CAACgE,KAAKhE,WAAW8D;IACnE,OAAOD,KAAKlD,MAAM,CAChB,CAACkC,MACCoB,QAAQP,KAAK,CAAC,CAAC1D,SAAWnB,gBAAgBgE,KAAKxB,QAAQrB,YACvD4C,qBAAqBC,KAAKkB,OAAOjB,KAAK,EAAEiB,OAAOhB,KAAK;AAE1D;AAEA;;;;CAIC,GACD,OAAO,SAASmB,uBACdJ,OAA0B,EAC1BrC,KAAa,EACb0C,IAAmB;IAEnB,MAAMb,KAAKQ,QAAQM,SAAS,CAAC,CAACpE,SAAWA,OAAOyB,KAAK,KAAKA;IAC1D,MAAM4C,OAAOP,QAAQnD,MAAM,CAAC,CAACX,SAAWA,OAAOyB,KAAK,KAAKA;IACzD,IAAI,CAAC0C,MAAM,OAAOE;IAClB,IAAIf,OAAO,CAAC,GAAG,OAAO;WAAIe;QAAMF;KAAK;IACrC,OAAO;WAAIE,KAAKC,KAAK,CAAC,GAAGhB;QAAKa;WAASE,KAAKC,KAAK,CAAChB;KAAI;AACxD"}