@aglyn/shared-ui-jsx 1.0.0-beta.198 → 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 (34) 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 +61 -0
  9. package/src/lib/components/list-query-notices.component.js +61 -0
  10. package/src/lib/components/list-query-notices.component.js.map +1 -0
  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 +14 -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 +38 -21
  21. package/src/lib/const/list-grid-filter.js +59 -65
  22. package/src/lib/const/list-grid-filter.js.map +1 -1
  23. package/src/lib/const/list-query-plan.d.ts +170 -0
  24. package/src/lib/const/list-query-plan.js +624 -0
  25. package/src/lib/const/list-query-plan.js.map +1 -0
  26. package/src/lib/hooks/use-list-grid-filter.d.ts +7 -11
  27. package/src/lib/hooks/use-list-grid-filter.js +11 -12
  28. package/src/lib/hooks/use-list-grid-filter.js.map +1 -1
  29. package/src/lib/hooks/use-list-rows-filter.d.ts +0 -75
  30. package/src/lib/hooks/use-list-rows-filter.js +0 -83
  31. package/src/lib/hooks/use-list-rows-filter.js.map +0 -1
  32. package/src/lib/hooks/use-paged-rows-filter.d.ts +0 -77
  33. package/src/lib/hooks/use-paged-rows-filter.js +0 -126
  34. package/src/lib/hooks/use-paged-rows-filter.js.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/shared-ui-jsx",
3
- "version": "1.0.0-beta.198",
3
+ "version": "1.0.0-beta.200",
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.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",
28
+ "@aglyn/shared-data-mdi": "1.0.0-beta.200",
29
+ "@aglyn/shared-ui-theme": "1.0.0-beta.200",
30
+ "@aglyn/shared-util-tools": "1.0.0-beta.200",
31
+ "@aglyn/shared-util-vendor": "1.0.0-beta.200",
32
32
  "@mdi/js": "^7.4.47",
33
33
  "@swc/helpers": "0.5.23",
34
34
  "change-case": "^5.4.4",
@@ -14,8 +14,8 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import { type ListFilterField } from '../const/list-filter';
18
- import type { ListFilterClause, ListFilterOption } from '../const/list-grid-filter';
17
+ import type { ListFilterField } from '../const/list-filter';
18
+ import { type ListFilterClause, type ListFilterOption } from '../const/list-grid-filter';
19
19
  export interface ListFilterChipsProps {
20
20
  /** The list's grammar — every field a clause may name. */
21
21
  fields: readonly ListFilterField[];
@@ -16,18 +16,7 @@
16
16
  */ 'use client';
17
17
  import { jsx as _jsx } from "react/jsx-runtime";
18
18
  import { Chip, Stack, Tooltip } from "@mui/material";
19
- import { listFilterOperatorLabel } from "../const/list-filter.js";
20
- /** Operators that carry no value. */ const VALUELESS = new Set([
21
- 'isEmpty',
22
- 'isNotEmpty'
23
- ]);
24
- /** Operators that take several values, comma-joined the way the grammar splits them. */ const MULTI = new Set([
25
- 'isAnyOf'
26
- ]);
27
- const dayLabel = (raw)=>{
28
- const at = new Date(raw);
29
- return Number.isNaN(at.getTime()) ? raw : at.toLocaleDateString();
30
- };
19
+ import { listFilterClauseSentence } from "../const/list-grid-filter.js";
31
20
  /**
32
21
  * Every clause a list is narrowed by, as chips over the grid (AGL-2617,
33
22
  * AGL-3313; shared since AGL-3317).
@@ -41,22 +30,11 @@ const dayLabel = (raw)=>{
41
30
  * narrow the loaded rows, the served one is marked.
42
31
  */ export function ListFilterChips(props) {
43
32
  const { fields, headers, clauses, onChange, options = {}, servedField = null, marksServed = props.servedField !== undefined, disabled = false } = props;
44
- const header = (column)=>{
45
- var _headers_column;
46
- return (_headers_column = headers[column]) != null ? _headers_column : column;
47
- };
48
- /** How a clause reads: its header, its operator, and its value by label. */ const sentence = (clause)=>{
49
- var _clause_label;
50
- const field = fields.find((entry)=>entry.column === clause.field);
51
- const choices = options[clause.field];
52
- const named = (value)=>{
53
- var _ref;
54
- var _choices_find;
55
- return (_ref = choices == null ? void 0 : (_choices_find = choices.find((option)=>option.value === value)) == null ? void 0 : _choices_find.label) != null ? _ref : value;
56
- };
57
- const value = VALUELESS.has(clause.op) ? '' : MULTI.has(clause.op) ? clause.value.split(',').map((entry)=>entry.trim()).filter(Boolean).map(named).join(', ') : (_clause_label = clause.label) != null ? _clause_label : (field == null ? void 0 : field.kind) === 'date' ? dayLabel(clause.value) : named(clause.value);
58
- return `${header(clause.field)} ${listFilterOperatorLabel(clause.op)}${value ? ` ${value}` : ''}`;
59
- };
33
+ /** How a clause reads — the one wording the notices use too. */ const sentence = (clause)=>listFilterClauseSentence(clause, {
34
+ fields,
35
+ headers,
36
+ options
37
+ });
60
38
  if (!clauses.length) return null;
61
39
  return /*#__PURE__*/ _jsx(Stack, {
62
40
  direction: "row",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/list-filter-chips.component.tsx"],"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 { Chip, Stack, Tooltip } from '@mui/material'\nimport { type ListFilterField, listFilterOperatorLabel } from '../const/list-filter'\nimport type { ListFilterClause, ListFilterOption } from '../const/list-grid-filter'\n\nexport interface ListFilterChipsProps {\n /** The list's grammar — every field a clause may name. */\n fields: readonly ListFilterField[]\n /** How a field reads on a chip; a field without one reads as its column. */\n headers: Readonly<Record<string, string>>\n clauses: readonly ListFilterClause[]\n onChange: (clauses: ListFilterClause[]) => void\n /** Choices per field, so a chip names a picked value by its label. */\n options?: Readonly<Record<string, readonly ListFilterOption[]>>\n /**\n * The field the query is serving, marked so the reader knows which reached\n * everything. Omitted on a list whose every clause narrows the same rows.\n */\n servedField?: string | null\n /**\n * Whether chips carry the served / loaded-window distinction at all.\n * Defaults to whether `servedField` was passed: a list that answers every\n * clause over the same rows has nothing to distinguish.\n */\n marksServed?: boolean\n disabled?: boolean\n}\n\n/** Operators that carry no value. */\nconst VALUELESS = new Set(['isEmpty', 'isNotEmpty'])\n/** Operators that take several values, comma-joined the way the grammar splits them. */\nconst MULTI = new Set(['isAnyOf'])\n\nconst dayLabel = (raw: string): string => {\n const at = new Date(raw)\n return Number.isNaN(at.getTime()) ? raw : at.toLocaleDateString()\n}\n\n/**\n * Every clause a list is narrowed by, as chips over the grid (AGL-2617,\n * AGL-3313; shared since AGL-3317).\n *\n * The clauses are ADDED through the grid's own Filters panel; this bar is\n * what shows the SET. The free DataGrid's panel holds one item at a time\n * and a saved view holds several, so without it a list narrowed by two\n * fields would show one of the two and hide the other. Each\n * chip reads as a sentence — \"Owner is Dana\", \"Created on or after 1 Jan\"\n * — and removes its clause; where some clauses reach the query and others\n * narrow the loaded rows, the served one is marked.\n */\nexport function ListFilterChips(props: ListFilterChipsProps) {\n const {\n fields,\n headers,\n clauses,\n onChange,\n options = {},\n servedField = null,\n marksServed = props.servedField !== undefined,\n disabled = false,\n } = props\n const header = (column: string) => headers[column] ?? column\n\n /** How a clause reads: its header, its operator, and its value by label. */\n const sentence = (clause: ListFilterClause): string => {\n const field = fields.find((entry) => entry.column === clause.field)\n const choices = options[clause.field]\n const named = (value: string) =>\n choices?.find((option) => option.value === value)?.label ?? value\n const value = VALUELESS.has(clause.op)\n ? ''\n : MULTI.has(clause.op)\n ? clause.value\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean)\n .map(named)\n .join(', ')\n : clause.label ?? (field?.kind === 'date' ? dayLabel(clause.value) : named(clause.value))\n return `${header(clause.field)} ${listFilterOperatorLabel(clause.op)}${value ? ` ${value}` : ''}`\n }\n\n if (!clauses.length) return null\n return (\n <Stack\n direction=\"row\"\n spacing={1}\n useFlexGap\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n role=\"list\"\n aria-label=\"Filters\"\n >\n {clauses.map((clause, index) => {\n const served = marksServed && servedField !== null && clause.field === servedField\n const key = `${clause.field}-${clause.op}-${index}`\n const chip = (\n <Chip\n key={key}\n role=\"listitem\"\n size=\"small\"\n variant={served ? 'filled' : 'outlined'}\n color={served ? 'primary' : 'default'}\n label={sentence(clause)}\n disabled={disabled}\n onDelete={() => onChange(clauses.filter((_entry, at) => at !== index))}\n />\n )\n return marksServed ? (\n <Tooltip\n key={key}\n title={served ? 'Searched across every record' : 'Narrows the records already loaded'}\n >\n {chip}\n </Tooltip>\n ) : (\n chip\n )\n })}\n </Stack>\n )\n}\nListFilterChips.displayName = 'ListFilterChips'\n\nexport default ListFilterChips\n"],"names":["Chip","Stack","Tooltip","listFilterOperatorLabel","VALUELESS","Set","MULTI","dayLabel","raw","at","Date","Number","isNaN","getTime","toLocaleDateString","ListFilterChips","props","fields","headers","clauses","onChange","options","servedField","marksServed","undefined","disabled","header","column","sentence","clause","field","find","entry","choices","named","value","option","label","has","op","split","map","trim","filter","Boolean","join","kind","length","direction","spacing","useFlexGap","sx","alignItems","flexWrap","role","aria-label","index","served","key","chip","size","variant","color","onDelete","_entry","title","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;AAEA,SAASA,IAAI,EAAEC,KAAK,EAAEC,OAAO,QAAQ,gBAAe;AACpD,SAA+BC,uBAAuB,QAAQ,0BAAsB;AA0BpF,mCAAmC,GACnC,MAAMC,YAAY,IAAIC,IAAI;IAAC;IAAW;CAAa;AACnD,sFAAsF,GACtF,MAAMC,QAAQ,IAAID,IAAI;IAAC;CAAU;AAEjC,MAAME,WAAW,CAACC;IAChB,MAAMC,KAAK,IAAIC,KAAKF;IACpB,OAAOG,OAAOC,KAAK,CAACH,GAAGI,OAAO,MAAML,MAAMC,GAAGK,kBAAkB;AACjE;AAEA;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,gBAAgBC,KAA2B;IACzD,MAAM,EACJC,MAAM,EACNC,OAAO,EACPC,OAAO,EACPC,QAAQ,EACRC,UAAU,CAAC,CAAC,EACZC,cAAc,IAAI,EAClBC,cAAcP,MAAMM,WAAW,KAAKE,SAAS,EAC7CC,WAAW,KAAK,EACjB,GAAGT;IACJ,MAAMU,SAAS,CAACC;YAAmBT;gBAAAA,kBAAAA,OAAO,CAACS,OAAO,YAAfT,kBAAmBS;;IAEtD,0EAA0E,GAC1E,MAAMC,WAAW,CAACC;YAcVA;QAbN,MAAMC,QAAQb,OAAOc,IAAI,CAAC,CAACC,QAAUA,MAAML,MAAM,KAAKE,OAAOC,KAAK;QAClE,MAAMG,UAAUZ,OAAO,CAACQ,OAAOC,KAAK,CAAC;QACrC,MAAMI,QAAQ,CAACC;;gBACbF;2BAAAA,4BAAAA,gBAAAA,QAASF,IAAI,CAAC,CAACK,SAAWA,OAAOD,KAAK,KAAKA,2BAA3CF,cAAmDI,KAAK,mBAAIF;;QAC9D,MAAMA,QAAQ/B,UAAUkC,GAAG,CAACT,OAAOU,EAAE,IACjC,KACAjC,MAAMgC,GAAG,CAACT,OAAOU,EAAE,IACjBV,OAAOM,KAAK,CACTK,KAAK,CAAC,KACNC,GAAG,CAAC,CAACT,QAAUA,MAAMU,IAAI,IACzBC,MAAM,CAACC,SACPH,GAAG,CAACP,OACJW,IAAI,CAAC,SACRhB,gBAAAA,OAAOQ,KAAK,YAAZR,gBAAiBC,CAAAA,yBAAAA,MAAOgB,IAAI,MAAK,SAASvC,SAASsB,OAAOM,KAAK,IAAID,MAAML,OAAOM,KAAK;QAC3F,OAAO,GAAGT,OAAOG,OAAOC,KAAK,EAAE,CAAC,EAAE3B,wBAAwB0B,OAAOU,EAAE,IAAIJ,QAAQ,CAAC,CAAC,EAAEA,OAAO,GAAG,IAAI;IACnG;IAEA,IAAI,CAAChB,QAAQ4B,MAAM,EAAE,OAAO;IAC5B,qBACE,KAAC9C;QACC+C,WAAU;QACVC,SAAS;QACTC,UAAU;QACVC,IAAI;YAAEC,YAAY;YAAUC,UAAU;QAAO;QAC7CC,MAAK;QACLC,cAAW;kBAEVpC,QAAQsB,GAAG,CAAC,CAACZ,QAAQ2B;YACpB,MAAMC,SAASlC,eAAeD,gBAAgB,QAAQO,OAAOC,KAAK,KAAKR;YACvE,MAAMoC,MAAM,GAAG7B,OAAOC,KAAK,CAAC,CAAC,EAAED,OAAOU,EAAE,CAAC,CAAC,EAAEiB,OAAO;YACnD,MAAMG,qBACJ,KAAC3D;gBAECsD,MAAK;gBACLM,MAAK;gBACLC,SAASJ,SAAS,WAAW;gBAC7BK,OAAOL,SAAS,YAAY;gBAC5BpB,OAAOT,SAASC;gBAChBJ,UAAUA;gBACVsC,UAAU,IAAM3C,SAASD,QAAQwB,MAAM,CAAC,CAACqB,QAAQvD,KAAOA,OAAO+C;eAP1DE;YAUT,OAAOnC,4BACL,KAACrB;gBAEC+D,OAAOR,SAAS,iCAAiC;0BAEhDE;eAHID,OAMPC;QAEJ;;AAGN;AACA5C,gBAAgBmD,WAAW,GAAG;AAE9B,eAAenD,gBAAe"}
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/list-filter-chips.component.tsx"],"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 { Chip, Stack, Tooltip } from '@mui/material'\nimport type { ListFilterField } from '../const/list-filter'\nimport {\n type ListFilterClause,\n type ListFilterOption,\n listFilterClauseSentence,\n} from '../const/list-grid-filter'\n\nexport interface ListFilterChipsProps {\n /** The list's grammar — every field a clause may name. */\n fields: readonly ListFilterField[]\n /** How a field reads on a chip; a field without one reads as its column. */\n headers: Readonly<Record<string, string>>\n clauses: readonly ListFilterClause[]\n onChange: (clauses: ListFilterClause[]) => void\n /** Choices per field, so a chip names a picked value by its label. */\n options?: Readonly<Record<string, readonly ListFilterOption[]>>\n /**\n * The field the query is serving, marked so the reader knows which reached\n * everything. Omitted on a list whose every clause narrows the same rows.\n */\n servedField?: string | null\n /**\n * Whether chips carry the served / loaded-window distinction at all.\n * Defaults to whether `servedField` was passed: a list that answers every\n * clause over the same rows has nothing to distinguish.\n */\n marksServed?: boolean\n disabled?: boolean\n}\n\n/**\n * Every clause a list is narrowed by, as chips over the grid (AGL-2617,\n * AGL-3313; shared since AGL-3317).\n *\n * The clauses are ADDED through the grid's own Filters panel; this bar is\n * what shows the SET. The free DataGrid's panel holds one item at a time\n * and a saved view holds several, so without it a list narrowed by two\n * fields would show one of the two and hide the other. Each\n * chip reads as a sentence — \"Owner is Dana\", \"Created on or after 1 Jan\"\n * — and removes its clause; where some clauses reach the query and others\n * narrow the loaded rows, the served one is marked.\n */\nexport function ListFilterChips(props: ListFilterChipsProps) {\n const {\n fields,\n headers,\n clauses,\n onChange,\n options = {},\n servedField = null,\n marksServed = props.servedField !== undefined,\n disabled = false,\n } = props\n /** How a clause reads — the one wording the notices use too. */\n const sentence = (clause: ListFilterClause): string =>\n listFilterClauseSentence(clause, { fields, headers, options })\n\n if (!clauses.length) return null\n return (\n <Stack\n direction=\"row\"\n spacing={1}\n useFlexGap\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n role=\"list\"\n aria-label=\"Filters\"\n >\n {clauses.map((clause, index) => {\n const served = marksServed && servedField !== null && clause.field === servedField\n const key = `${clause.field}-${clause.op}-${index}`\n const chip = (\n <Chip\n key={key}\n role=\"listitem\"\n size=\"small\"\n variant={served ? 'filled' : 'outlined'}\n color={served ? 'primary' : 'default'}\n label={sentence(clause)}\n disabled={disabled}\n onDelete={() => onChange(clauses.filter((_entry, at) => at !== index))}\n />\n )\n return marksServed ? (\n <Tooltip\n key={key}\n title={served ? 'Searched across every record' : 'Narrows the records already loaded'}\n >\n {chip}\n </Tooltip>\n ) : (\n chip\n )\n })}\n </Stack>\n )\n}\nListFilterChips.displayName = 'ListFilterChips'\n\nexport default ListFilterChips\n"],"names":["Chip","Stack","Tooltip","listFilterClauseSentence","ListFilterChips","props","fields","headers","clauses","onChange","options","servedField","marksServed","undefined","disabled","sentence","clause","length","direction","spacing","useFlexGap","sx","alignItems","flexWrap","role","aria-label","map","index","served","field","key","op","chip","size","variant","color","label","onDelete","filter","_entry","at","title","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;AAEA,SAASA,IAAI,EAAEC,KAAK,EAAEC,OAAO,QAAQ,gBAAe;AAEpD,SAGEC,wBAAwB,QACnB,+BAA2B;AAyBlC;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,gBAAgBC,KAA2B;IACzD,MAAM,EACJC,MAAM,EACNC,OAAO,EACPC,OAAO,EACPC,QAAQ,EACRC,UAAU,CAAC,CAAC,EACZC,cAAc,IAAI,EAClBC,cAAcP,MAAMM,WAAW,KAAKE,SAAS,EAC7CC,WAAW,KAAK,EACjB,GAAGT;IACJ,8DAA8D,GAC9D,MAAMU,WAAW,CAACC,SAChBb,yBAAyBa,QAAQ;YAAEV;YAAQC;YAASG;QAAQ;IAE9D,IAAI,CAACF,QAAQS,MAAM,EAAE,OAAO;IAC5B,qBACE,KAAChB;QACCiB,WAAU;QACVC,SAAS;QACTC,UAAU;QACVC,IAAI;YAAEC,YAAY;YAAUC,UAAU;QAAO;QAC7CC,MAAK;QACLC,cAAW;kBAEVjB,QAAQkB,GAAG,CAAC,CAACV,QAAQW;YACpB,MAAMC,SAAShB,eAAeD,gBAAgB,QAAQK,OAAOa,KAAK,KAAKlB;YACvE,MAAMmB,MAAM,GAAGd,OAAOa,KAAK,CAAC,CAAC,EAAEb,OAAOe,EAAE,CAAC,CAAC,EAAEJ,OAAO;YACnD,MAAMK,qBACJ,KAAChC;gBAECwB,MAAK;gBACLS,MAAK;gBACLC,SAASN,SAAS,WAAW;gBAC7BO,OAAOP,SAAS,YAAY;gBAC5BQ,OAAOrB,SAASC;gBAChBF,UAAUA;gBACVuB,UAAU,IAAM5B,SAASD,QAAQ8B,MAAM,CAAC,CAACC,QAAQC,KAAOA,OAAOb;eAP1DG;YAUT,OAAOlB,4BACL,KAACV;gBAECuC,OAAOb,SAAS,iCAAiC;0BAEhDI;eAHIF,OAMPE;QAEJ;;AAGN;AACA5B,gBAAgBsC,WAAW,GAAG;AAE9B,eAAetC,gBAAe"}
@@ -0,0 +1,56 @@
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 ListFilterField } from '../const/list-filter';
18
+ import { type ListFilterClause, type ListFilterOption } from '../const/list-grid-filter';
19
+ export interface ListFilterToolbarProps {
20
+ /** The list's grammar — every field the Filters panel may name. */
21
+ fields: readonly ListFilterField[];
22
+ /** How a field reads in the panel and on its chip. */
23
+ headers: Readonly<Record<string, string>>;
24
+ /** Choices per field, for a field whose value is picked rather than typed. */
25
+ options?: Readonly<Record<string, readonly ListFilterOption[]>>;
26
+ clauses: readonly ListFilterClause[];
27
+ onChange: (clauses: ListFilterClause[]) => void;
28
+ /** The quick search. Absent, the list offers no search box. */
29
+ search?: {
30
+ words: readonly string[];
31
+ onChange: (words: string[]) => void;
32
+ placeholder?: string;
33
+ };
34
+ disabled?: boolean;
35
+ }
36
+ /**
37
+ * THE LIST TOOLBAR, FOR A LIST THAT IS NOT A TABLE (AGL-3321).
38
+ *
39
+ * A table's search box and Filters panel are the grid's own toolbar. A list
40
+ * drawn as cards, tiles or a feed has no grid, and must not become one to get
41
+ * them — its presentation is not the filter's to change. This is the same two
42
+ * controls over any presentation: a search box whose words the list puts on
43
+ * its query, and a Filters button whose panel adds one clause per field, in
44
+ * the same grammar (`ListFilterField`) and the same clause shape
45
+ * (`ListFilterClause`) the grid's panel produces. The clauses in force show
46
+ * as `ListFilterChips` beside it, and what the query could not take as
47
+ * `ListQueryNotices`, exactly as over a table.
48
+ *
49
+ * It offers only the operators each field declares (`listFilterOperators`),
50
+ * which are the ones the list's query can serve.
51
+ */
52
+ export declare function ListFilterToolbar(props: ListFilterToolbarProps): import("react").JSX.Element;
53
+ export declare namespace ListFilterToolbar {
54
+ var displayName: string;
55
+ }
56
+ export default ListFilterToolbar;
@@ -0,0 +1,280 @@
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
+ */ 'use client';
17
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
18
+ import { Badge, Button, MenuItem, Popover, Stack, TextField } from "@mui/material";
19
+ import { useEffect, useId, useMemo, useRef, useState } from "react";
20
+ import { listFilterOperatorLabel, listFilterOperators } from "../const/list-filter.js";
21
+ import { upsertListFilterClause } from "../const/list-grid-filter.js";
22
+ /** Operators that carry no value. */ const VALUELESS = new Set([
23
+ 'isEmpty',
24
+ 'isNotEmpty'
25
+ ]);
26
+ /** How long the search box waits after the last keystroke before asking. */ const SEARCH_SETTLE_MS = 300;
27
+ /**
28
+ * THE LIST TOOLBAR, FOR A LIST THAT IS NOT A TABLE (AGL-3321).
29
+ *
30
+ * A table's search box and Filters panel are the grid's own toolbar. A list
31
+ * drawn as cards, tiles or a feed has no grid, and must not become one to get
32
+ * them — its presentation is not the filter's to change. This is the same two
33
+ * controls over any presentation: a search box whose words the list puts on
34
+ * its query, and a Filters button whose panel adds one clause per field, in
35
+ * the same grammar (`ListFilterField`) and the same clause shape
36
+ * (`ListFilterClause`) the grid's panel produces. The clauses in force show
37
+ * as `ListFilterChips` beside it, and what the query could not take as
38
+ * `ListQueryNotices`, exactly as over a table.
39
+ *
40
+ * It offers only the operators each field declares (`listFilterOperators`),
41
+ * which are the ones the list's query can serve.
42
+ */ export function ListFilterToolbar(props) {
43
+ var _ref, _ref1, _operators_, _search_placeholder, _search_placeholder1, _ref2;
44
+ var _fields_;
45
+ const { fields, headers, options = {}, clauses, onChange, search, disabled = false } = props;
46
+ const panelId = useId();
47
+ const anchor = useRef(null);
48
+ const [open, setOpen] = useState(false);
49
+ // The search box is the reader's until they pause; then its words are asked.
50
+ const [typed, setTyped] = useState(()=>{
51
+ var _ref;
52
+ return ((_ref = search == null ? void 0 : search.words) != null ? _ref : []).join(' ');
53
+ });
54
+ const asked = ((_ref = search == null ? void 0 : search.words) != null ? _ref : []).join(' ');
55
+ const onSearch = search == null ? void 0 : search.onChange;
56
+ useEffect(()=>{
57
+ if (!onSearch) return;
58
+ const words = typed.split(/\s+/).filter(Boolean);
59
+ if (words.join(' ') === asked) return;
60
+ const timer = setTimeout(()=>onSearch(words), SEARCH_SETTLE_MS);
61
+ return ()=>clearTimeout(timer);
62
+ }, [
63
+ typed,
64
+ asked,
65
+ onSearch
66
+ ]);
67
+ const label = (column)=>{
68
+ var _headers_column;
69
+ return (_headers_column = headers[column]) != null ? _headers_column : column;
70
+ };
71
+ const [column, setColumn] = useState((_ref1 = (_fields_ = fields[0]) == null ? void 0 : _fields_.column) != null ? _ref1 : '');
72
+ const field = useMemo(()=>{
73
+ var _fields_find;
74
+ return (_fields_find = fields.find((entry)=>entry.column === column)) != null ? _fields_find : fields[0];
75
+ }, [
76
+ fields,
77
+ column
78
+ ]);
79
+ const operators = useMemo(()=>field ? listFilterOperators(field) : [], [
80
+ field
81
+ ]);
82
+ const [op, setOp] = useState((_operators_ = operators[0]) != null ? _operators_ : '');
83
+ const [value, setValue] = useState('');
84
+ // Opening the panel on a field shows the clause it already holds, if any.
85
+ const pick = (next)=>{
86
+ var _nextOperators_, _ref;
87
+ const nextField = fields.find((entry)=>entry.column === next);
88
+ const held = clauses.find((clause)=>clause.field === next);
89
+ const nextOperators = nextField ? listFilterOperators(nextField) : [];
90
+ setColumn(next);
91
+ setOp(held && nextOperators.includes(held.op) ? held.op : (_nextOperators_ = nextOperators[0]) != null ? _nextOperators_ : '');
92
+ setValue((_ref = held == null ? void 0 : held.value) != null ? _ref : '');
93
+ };
94
+ useEffect(()=>{
95
+ if (open) pick(column);
96
+ // Re-read only when the panel opens.
97
+ // eslint-disable-next-line react-hooks/exhaustive-deps
98
+ }, [
99
+ open
100
+ ]);
101
+ const choices = field ? options[field.column] : undefined;
102
+ const booleanChoices = [
103
+ {
104
+ value: 'true',
105
+ label: 'Yes'
106
+ },
107
+ {
108
+ value: 'false',
109
+ label: 'No'
110
+ }
111
+ ];
112
+ const picked = choices != null ? choices : (field == null ? void 0 : field.kind) === 'boolean' ? booleanChoices : undefined;
113
+ const valueless = VALUELESS.has(op);
114
+ const ready = Boolean(field && op && (valueless || value.trim()));
115
+ const apply = ()=>{
116
+ if (!field || !ready) return;
117
+ onChange(upsertListFilterClause(clauses, field.column, {
118
+ field: field.column,
119
+ op,
120
+ value: valueless ? '' : value.trim()
121
+ }));
122
+ setOpen(false);
123
+ };
124
+ return /*#__PURE__*/ _jsxs(Stack, {
125
+ direction: {
126
+ xs: 'column',
127
+ sm: 'row'
128
+ },
129
+ spacing: 1,
130
+ sx: {
131
+ alignItems: {
132
+ sm: 'center'
133
+ }
134
+ },
135
+ children: [
136
+ search ? /*#__PURE__*/ _jsx(TextField, {
137
+ size: "small",
138
+ type: "search",
139
+ value: typed,
140
+ disabled: disabled,
141
+ onChange: (event)=>setTyped(event.target.value),
142
+ placeholder: (_search_placeholder = search.placeholder) != null ? _search_placeholder : 'Search…',
143
+ slotProps: {
144
+ htmlInput: {
145
+ 'aria-label': (_search_placeholder1 = search.placeholder) != null ? _search_placeholder1 : 'Search'
146
+ }
147
+ },
148
+ sx: {
149
+ minWidth: {
150
+ sm: 280
151
+ }
152
+ }
153
+ }) : null,
154
+ fields.length ? /*#__PURE__*/ _jsxs(_Fragment, {
155
+ children: [
156
+ /*#__PURE__*/ _jsx(Badge, {
157
+ badgeContent: clauses.length,
158
+ color: "primary",
159
+ overlap: "rectangular",
160
+ children: /*#__PURE__*/ _jsx(Button, {
161
+ ref: anchor,
162
+ variant: "outlined",
163
+ size: "medium",
164
+ disabled: disabled,
165
+ "aria-haspopup": "dialog",
166
+ "aria-controls": open ? panelId : undefined,
167
+ "aria-expanded": open,
168
+ onClick: ()=>setOpen(true),
169
+ children: 'Filters'
170
+ })
171
+ }),
172
+ /*#__PURE__*/ _jsx(Popover, {
173
+ id: panelId,
174
+ open: open,
175
+ anchorEl: anchor.current,
176
+ onClose: ()=>setOpen(false),
177
+ anchorOrigin: {
178
+ vertical: 'bottom',
179
+ horizontal: 'left'
180
+ },
181
+ slotProps: {
182
+ paper: {
183
+ role: 'dialog',
184
+ 'aria-label': 'Filters'
185
+ }
186
+ },
187
+ children: /*#__PURE__*/ _jsxs(Stack, {
188
+ component: "form",
189
+ spacing: 1.5,
190
+ sx: {
191
+ p: 2,
192
+ width: {
193
+ xs: 'calc(100vw - 32px)',
194
+ sm: 340
195
+ },
196
+ maxWidth: 340
197
+ },
198
+ onSubmit: (event)=>{
199
+ event.preventDefault();
200
+ apply();
201
+ },
202
+ children: [
203
+ /*#__PURE__*/ _jsx(TextField, {
204
+ select: true,
205
+ size: "small",
206
+ label: "Field",
207
+ value: (_ref2 = field == null ? void 0 : field.column) != null ? _ref2 : '',
208
+ onChange: (event)=>pick(event.target.value),
209
+ children: fields.map((entry)=>/*#__PURE__*/ _jsx(MenuItem, {
210
+ value: entry.column,
211
+ children: label(entry.column)
212
+ }, entry.column))
213
+ }),
214
+ operators.length > 1 ? /*#__PURE__*/ _jsx(TextField, {
215
+ select: true,
216
+ size: "small",
217
+ label: "Operator",
218
+ value: op,
219
+ onChange: (event)=>setOp(event.target.value),
220
+ children: operators.map((entry)=>/*#__PURE__*/ _jsx(MenuItem, {
221
+ value: entry,
222
+ children: listFilterOperatorLabel(entry)
223
+ }, entry))
224
+ }) : null,
225
+ valueless ? null : picked ? /*#__PURE__*/ _jsx(TextField, {
226
+ select: true,
227
+ size: "small",
228
+ label: "Value",
229
+ value: value,
230
+ onChange: (event)=>setValue(event.target.value),
231
+ children: picked.map((choice)=>/*#__PURE__*/ _jsx(MenuItem, {
232
+ value: choice.value,
233
+ children: choice.label
234
+ }, choice.value))
235
+ }) : /*#__PURE__*/ _jsx(TextField, {
236
+ size: "small",
237
+ label: "Value",
238
+ type: (field == null ? void 0 : field.kind) === 'date' ? 'date' : (field == null ? void 0 : field.kind) === 'number' ? 'number' : 'text',
239
+ value: value,
240
+ onChange: (event)=>setValue(event.target.value),
241
+ slotProps: (field == null ? void 0 : field.kind) === 'date' ? {
242
+ inputLabel: {
243
+ shrink: true
244
+ }
245
+ } : undefined
246
+ }),
247
+ /*#__PURE__*/ _jsxs(Stack, {
248
+ direction: "row",
249
+ spacing: 1,
250
+ sx: {
251
+ justifyContent: 'flex-end'
252
+ },
253
+ children: [
254
+ field && clauses.some((clause)=>clause.field === field.column) ? /*#__PURE__*/ _jsx(Button, {
255
+ onClick: ()=>{
256
+ onChange(upsertListFilterClause(clauses, field.column, null));
257
+ setOpen(false);
258
+ },
259
+ children: 'Remove'
260
+ }) : null,
261
+ /*#__PURE__*/ _jsx(Button, {
262
+ type: "submit",
263
+ variant: "contained",
264
+ disabled: !ready,
265
+ children: 'Apply'
266
+ })
267
+ ]
268
+ })
269
+ ]
270
+ })
271
+ })
272
+ ]
273
+ }) : null
274
+ ]
275
+ });
276
+ }
277
+ ListFilterToolbar.displayName = 'ListFilterToolbar';
278
+ export default ListFilterToolbar;
279
+
280
+ //# sourceMappingURL=list-filter-toolbar.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/list-filter-toolbar.component.tsx"],"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 {\n Badge,\n Button,\n MenuItem,\n Popover,\n Stack,\n TextField,\n} from '@mui/material'\nimport { useEffect, useId, useMemo, useRef, useState } from 'react'\nimport {\n type ListFilterField,\n listFilterOperatorLabel,\n listFilterOperators,\n} from '../const/list-filter'\nimport {\n type ListFilterClause,\n type ListFilterOption,\n upsertListFilterClause,\n} from '../const/list-grid-filter'\n\n/** Operators that carry no value. */\nconst VALUELESS = new Set(['isEmpty', 'isNotEmpty'])\n\n/** How long the search box waits after the last keystroke before asking. */\nconst SEARCH_SETTLE_MS = 300\n\nexport interface ListFilterToolbarProps {\n /** The list's grammar — every field the Filters panel may name. */\n fields: readonly ListFilterField[]\n /** How a field reads in the panel and on its chip. */\n headers: Readonly<Record<string, string>>\n /** Choices per field, for a field whose value is picked rather than typed. */\n options?: Readonly<Record<string, readonly ListFilterOption[]>>\n clauses: readonly ListFilterClause[]\n onChange: (clauses: ListFilterClause[]) => void\n /** The quick search. Absent, the list offers no search box. */\n search?: {\n words: readonly string[]\n onChange: (words: string[]) => void\n placeholder?: string\n }\n disabled?: boolean\n}\n\n/**\n * THE LIST TOOLBAR, FOR A LIST THAT IS NOT A TABLE (AGL-3321).\n *\n * A table's search box and Filters panel are the grid's own toolbar. A list\n * drawn as cards, tiles or a feed has no grid, and must not become one to get\n * them — its presentation is not the filter's to change. This is the same two\n * controls over any presentation: a search box whose words the list puts on\n * its query, and a Filters button whose panel adds one clause per field, in\n * the same grammar (`ListFilterField`) and the same clause shape\n * (`ListFilterClause`) the grid's panel produces. The clauses in force show\n * as `ListFilterChips` beside it, and what the query could not take as\n * `ListQueryNotices`, exactly as over a table.\n *\n * It offers only the operators each field declares (`listFilterOperators`),\n * which are the ones the list's query can serve.\n */\nexport function ListFilterToolbar(props: ListFilterToolbarProps) {\n const { fields, headers, options = {}, clauses, onChange, search, disabled = false } = props\n const panelId = useId()\n const anchor = useRef<HTMLButtonElement | null>(null)\n const [open, setOpen] = useState(false)\n\n // The search box is the reader's until they pause; then its words are asked.\n const [typed, setTyped] = useState(() => (search?.words ?? []).join(' '))\n const asked = (search?.words ?? []).join(' ')\n const onSearch = search?.onChange\n useEffect(() => {\n if (!onSearch) return\n const words = typed.split(/\\s+/).filter(Boolean)\n if (words.join(' ') === asked) return\n const timer = setTimeout(() => onSearch(words), SEARCH_SETTLE_MS)\n return () => clearTimeout(timer)\n }, [typed, asked, onSearch])\n\n const label = (column: string) => headers[column] ?? column\n const [column, setColumn] = useState<string>(fields[0]?.column ?? '')\n const field = useMemo(\n () => fields.find((entry) => entry.column === column) ?? fields[0],\n [fields, column],\n )\n const operators = useMemo(() => (field ? listFilterOperators(field) : []), [field])\n const [op, setOp] = useState<string>(operators[0] ?? '')\n const [value, setValue] = useState('')\n\n // Opening the panel on a field shows the clause it already holds, if any.\n const pick = (next: string) => {\n const nextField = fields.find((entry) => entry.column === next)\n const held = clauses.find((clause) => clause.field === next)\n const nextOperators = nextField ? listFilterOperators(nextField) : []\n setColumn(next)\n setOp(held && nextOperators.includes(held.op) ? held.op : (nextOperators[0] ?? ''))\n setValue(held?.value ?? '')\n }\n useEffect(() => {\n if (open) pick(column)\n // Re-read only when the panel opens.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open])\n\n const choices = field ? options[field.column] : undefined\n const booleanChoices: readonly ListFilterOption[] = [\n { value: 'true', label: 'Yes' },\n { value: 'false', label: 'No' },\n ]\n const picked = choices ?? (field?.kind === 'boolean' ? booleanChoices : undefined)\n const valueless = VALUELESS.has(op)\n const ready = Boolean(field && op && (valueless || value.trim()))\n\n const apply = () => {\n if (!field || !ready) return\n onChange(\n upsertListFilterClause(clauses, field.column, {\n field: field.column,\n op,\n value: valueless ? '' : value.trim(),\n }),\n )\n setOpen(false)\n }\n\n return (\n <Stack\n direction={{ xs: 'column', sm: 'row' }}\n spacing={1}\n sx={{ alignItems: { sm: 'center' } }}\n >\n {search ? (\n <TextField\n size=\"small\"\n type=\"search\"\n value={typed}\n disabled={disabled}\n onChange={(event) => setTyped(event.target.value)}\n placeholder={search.placeholder ?? 'Search…'}\n slotProps={{ htmlInput: { 'aria-label': search.placeholder ?? 'Search' } }}\n sx={{ minWidth: { sm: 280 } }}\n />\n ) : null}\n {fields.length ? (\n <>\n <Badge badgeContent={clauses.length} color=\"primary\" overlap=\"rectangular\">\n <Button\n ref={anchor}\n variant=\"outlined\"\n size=\"medium\"\n disabled={disabled}\n aria-haspopup=\"dialog\"\n aria-controls={open ? panelId : undefined}\n aria-expanded={open}\n onClick={() => setOpen(true)}\n >\n {'Filters'}\n </Button>\n </Badge>\n <Popover\n id={panelId}\n open={open}\n anchorEl={anchor.current}\n onClose={() => setOpen(false)}\n anchorOrigin={{ vertical: 'bottom', horizontal: 'left' }}\n slotProps={{ paper: { role: 'dialog', 'aria-label': 'Filters' } as never }}\n >\n <Stack\n component=\"form\"\n spacing={1.5}\n sx={{ p: 2, width: { xs: 'calc(100vw - 32px)', sm: 340 }, maxWidth: 340 }}\n onSubmit={(event) => {\n event.preventDefault()\n apply()\n }}\n >\n <TextField\n select\n size=\"small\"\n label=\"Field\"\n value={field?.column ?? ''}\n onChange={(event) => pick(event.target.value)}\n >\n {fields.map((entry) => (\n <MenuItem key={entry.column} value={entry.column}>\n {label(entry.column)}\n </MenuItem>\n ))}\n </TextField>\n {operators.length > 1 ? (\n <TextField\n select\n size=\"small\"\n label=\"Operator\"\n value={op}\n onChange={(event) => setOp(event.target.value)}\n >\n {operators.map((entry) => (\n <MenuItem key={entry} value={entry}>\n {listFilterOperatorLabel(entry)}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n {valueless ? null : picked ? (\n <TextField\n select\n size=\"small\"\n label=\"Value\"\n value={value}\n onChange={(event) => setValue(event.target.value)}\n >\n {picked.map((choice) => (\n <MenuItem key={choice.value} value={choice.value}>\n {choice.label}\n </MenuItem>\n ))}\n </TextField>\n ) : (\n <TextField\n size=\"small\"\n label=\"Value\"\n type={field?.kind === 'date' ? 'date' : field?.kind === 'number' ? 'number' : 'text'}\n value={value}\n onChange={(event) => setValue(event.target.value)}\n slotProps={field?.kind === 'date' ? { inputLabel: { shrink: true } } : undefined}\n />\n )}\n <Stack direction=\"row\" spacing={1} sx={{ justifyContent: 'flex-end' }}>\n {field && clauses.some((clause) => clause.field === field.column) ? (\n <Button\n onClick={() => {\n onChange(upsertListFilterClause(clauses, field.column, null))\n setOpen(false)\n }}\n >\n {'Remove'}\n </Button>\n ) : null}\n <Button type=\"submit\" variant=\"contained\" disabled={!ready}>\n {'Apply'}\n </Button>\n </Stack>\n </Stack>\n </Popover>\n </>\n ) : null}\n </Stack>\n )\n}\nListFilterToolbar.displayName = 'ListFilterToolbar'\n\nexport default ListFilterToolbar\n"],"names":["Badge","Button","MenuItem","Popover","Stack","TextField","useEffect","useId","useMemo","useRef","useState","listFilterOperatorLabel","listFilterOperators","upsertListFilterClause","VALUELESS","Set","SEARCH_SETTLE_MS","ListFilterToolbar","props","operators","search","fields","headers","options","clauses","onChange","disabled","panelId","anchor","open","setOpen","typed","setTyped","words","join","asked","onSearch","split","filter","Boolean","timer","setTimeout","clearTimeout","label","column","setColumn","field","find","entry","op","setOp","value","setValue","pick","next","nextOperators","nextField","held","clause","includes","choices","undefined","booleanChoices","picked","kind","valueless","has","ready","trim","apply","direction","xs","sm","spacing","sx","alignItems","size","type","event","target","placeholder","slotProps","htmlInput","minWidth","length","badgeContent","color","overlap","ref","variant","aria-haspopup","aria-controls","aria-expanded","onClick","id","anchorEl","current","onClose","anchorOrigin","vertical","horizontal","paper","role","component","p","width","maxWidth","onSubmit","preventDefault","select","map","choice","inputLabel","shrink","justifyContent","some","displayName"],"mappings":"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"}
@@ -0,0 +1,61 @@
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 { ListFilterField } from '../const/list-filter';
18
+ import type { ListFilterClause, ListFilterOption } from '../const/list-grid-filter';
19
+ /**
20
+ * A plan's refusals as the notices read them: each refused clause by the
21
+ * same sentence its chip reads ("Reason is Bounced"), and the search as
22
+ * "Search". Every list maps `plan.refused` through this and nothing else.
23
+ */
24
+ export declare function listQueryRefusals(refused: ReadonlyArray<{
25
+ clause: ListFilterClause | 'search';
26
+ reason: string;
27
+ }>, context: {
28
+ fields: readonly ListFilterField[];
29
+ headers?: Readonly<Record<string, string>>;
30
+ options?: Readonly<Record<string, readonly ListFilterOption[]>>;
31
+ }): Array<{
32
+ label: string;
33
+ reason: string;
34
+ }>;
35
+ export interface ListQueryNoticesProps {
36
+ /**
37
+ * What the list was asked and could not put on its query, each as the thing
38
+ * asked ("Tags contains vip", "Search") and why.
39
+ */
40
+ refused: ReadonlyArray<{
41
+ label: string;
42
+ reason: string;
43
+ }>;
44
+ /** Said about what WAS served, e.g. that search matched one word. */
45
+ notices?: readonly string[];
46
+ }
47
+ /**
48
+ * WHAT THE QUERY DID NOT TAKE, said above the list (AGL-3321).
49
+ *
50
+ * Every filter and search word a list applies is on its Firestore query, and
51
+ * one query holds one array clause, one range and thirty disjunctions. A
52
+ * combination past that is not applied at all rather than applied to the
53
+ * rows that happen to be loaded — and a filter the reader set that is not
54
+ * applied must say so, or the list reads as the answer to a question it did
55
+ * not ask.
56
+ */
57
+ export declare function ListQueryNotices(props: ListQueryNoticesProps): import("react").JSX.Element;
58
+ export declare namespace ListQueryNotices {
59
+ var displayName: string;
60
+ }
61
+ export default ListQueryNotices;
@@ -0,0 +1,61 @@
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
+ */ 'use client';
17
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
+ import { Alert, Stack } from "@mui/material";
19
+ import { listFilterClauseSentence } from "../const/list-filter-sentence.js";
20
+ /**
21
+ * A plan's refusals as the notices read them: each refused clause by the
22
+ * same sentence its chip reads ("Reason is Bounced"), and the search as
23
+ * "Search". Every list maps `plan.refused` through this and nothing else.
24
+ */ export function listQueryRefusals(refused, context) {
25
+ return refused.map((entry)=>({
26
+ label: entry.clause === 'search' ? 'Search' : listFilterClauseSentence(entry.clause, context),
27
+ reason: entry.reason
28
+ }));
29
+ }
30
+ /**
31
+ * WHAT THE QUERY DID NOT TAKE, said above the list (AGL-3321).
32
+ *
33
+ * Every filter and search word a list applies is on its Firestore query, and
34
+ * one query holds one array clause, one range and thirty disjunctions. A
35
+ * combination past that is not applied at all rather than applied to the
36
+ * rows that happen to be loaded — and a filter the reader set that is not
37
+ * applied must say so, or the list reads as the answer to a question it did
38
+ * not ask.
39
+ */ export function ListQueryNotices(props) {
40
+ const { refused, notices = [] } = props;
41
+ if (!refused.length && !notices.length) return null;
42
+ return /*#__PURE__*/ _jsxs(Stack, {
43
+ spacing: 1,
44
+ role: "status",
45
+ "aria-label": "Filter notices",
46
+ children: [
47
+ refused.map((entry)=>/*#__PURE__*/ _jsx(Alert, {
48
+ severity: "warning",
49
+ children: `${entry.label} is not applied: ${entry.reason}.`
50
+ }, `${entry.label}:${entry.reason}`)),
51
+ notices.map((notice)=>/*#__PURE__*/ _jsx(Alert, {
52
+ severity: "info",
53
+ children: notice
54
+ }, notice))
55
+ ]
56
+ });
57
+ }
58
+ ListQueryNotices.displayName = 'ListQueryNotices';
59
+ export default ListQueryNotices;
60
+
61
+ //# sourceMappingURL=list-query-notices.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/components/list-query-notices.component.tsx"],"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 { Alert, Stack } from '@mui/material'\nimport type { ListFilterField } from '../const/list-filter'\nimport { listFilterClauseSentence } from '../const/list-filter-sentence'\nimport type { ListFilterClause, ListFilterOption } from '../const/list-grid-filter'\n\n/**\n * A plan's refusals as the notices read them: each refused clause by the\n * same sentence its chip reads (\"Reason is Bounced\"), and the search as\n * \"Search\". Every list maps `plan.refused` through this and nothing else.\n */\nexport function listQueryRefusals(\n refused: ReadonlyArray<{ clause: ListFilterClause | 'search'; reason: string }>,\n context: {\n fields: readonly ListFilterField[]\n headers?: Readonly<Record<string, string>>\n options?: Readonly<Record<string, readonly ListFilterOption[]>>\n },\n): Array<{ label: string; reason: string }> {\n return refused.map((entry) => ({\n label: entry.clause === 'search' ? 'Search' : listFilterClauseSentence(entry.clause, context),\n reason: entry.reason,\n }))\n}\n\nexport interface ListQueryNoticesProps {\n /**\n * What the list was asked and could not put on its query, each as the thing\n * asked (\"Tags contains vip\", \"Search\") and why.\n */\n refused: ReadonlyArray<{ label: string; reason: string }>\n /** Said about what WAS served, e.g. that search matched one word. */\n notices?: readonly string[]\n}\n\n/**\n * WHAT THE QUERY DID NOT TAKE, said above the list (AGL-3321).\n *\n * Every filter and search word a list applies is on its Firestore query, and\n * one query holds one array clause, one range and thirty disjunctions. A\n * combination past that is not applied at all rather than applied to the\n * rows that happen to be loaded — and a filter the reader set that is not\n * applied must say so, or the list reads as the answer to a question it did\n * not ask.\n */\nexport function ListQueryNotices(props: ListQueryNoticesProps) {\n const { refused, notices = [] } = props\n if (!refused.length && !notices.length) return null\n return (\n <Stack spacing={1} role=\"status\" aria-label=\"Filter notices\">\n {refused.map((entry) => (\n <Alert key={`${entry.label}:${entry.reason}`} severity=\"warning\">\n {`${entry.label} is not applied: ${entry.reason}.`}\n </Alert>\n ))}\n {notices.map((notice) => (\n <Alert key={notice} severity=\"info\">\n {notice}\n </Alert>\n ))}\n </Stack>\n )\n}\nListQueryNotices.displayName = 'ListQueryNotices'\n\nexport default ListQueryNotices\n"],"names":["Alert","Stack","listFilterClauseSentence","listQueryRefusals","refused","context","map","entry","label","clause","reason","ListQueryNotices","props","notices","length","spacing","role","aria-label","severity","notice","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;AAEA,SAASA,KAAK,EAAEC,KAAK,QAAQ,gBAAe;AAE5C,SAASC,wBAAwB,QAAQ,mCAA+B;AAGxE;;;;CAIC,GACD,OAAO,SAASC,kBACdC,OAA+E,EAC/EC,OAIC;IAED,OAAOD,QAAQE,GAAG,CAAC,CAACC,QAAW,CAAA;YAC7BC,OAAOD,MAAME,MAAM,KAAK,WAAW,WAAWP,yBAAyBK,MAAME,MAAM,EAAEJ;YACrFK,QAAQH,MAAMG,MAAM;QACtB,CAAA;AACF;AAYA;;;;;;;;;CASC,GACD,OAAO,SAASC,iBAAiBC,KAA4B;IAC3D,MAAM,EAAER,OAAO,EAAES,UAAU,EAAE,EAAE,GAAGD;IAClC,IAAI,CAACR,QAAQU,MAAM,IAAI,CAACD,QAAQC,MAAM,EAAE,OAAO;IAC/C,qBACE,MAACb;QAAMc,SAAS;QAAGC,MAAK;QAASC,cAAW;;YACzCb,QAAQE,GAAG,CAAC,CAACC,sBACZ,KAACP;oBAA6CkB,UAAS;8BACpD,GAAGX,MAAMC,KAAK,CAAC,iBAAiB,EAAED,MAAMG,MAAM,CAAC,CAAC,CAAC;mBADxC,GAAGH,MAAMC,KAAK,CAAC,CAAC,EAAED,MAAMG,MAAM,EAAE;YAI7CG,QAAQP,GAAG,CAAC,CAACa,uBACZ,KAACnB;oBAAmBkB,UAAS;8BAC1BC;mBADSA;;;AAMpB;AACAR,iBAAiBS,WAAW,GAAG;AAE/B,eAAeT,iBAAgB"}