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

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 +70 -8
  13. package/src/lib/components/list-table.component.js.map +1 -1
  14. package/src/lib/const/list-filter-sentence.d.ts +40 -0
  15. package/src/lib/const/list-filter-sentence.js +123 -0
  16. package/src/lib/const/list-filter-sentence.js.map +1 -0
  17. package/src/lib/const/list-filter.d.ts +8 -47
  18. package/src/lib/const/list-filter.js +2 -128
  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,86 +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
- */ 'use client';
17
- import { useCallback, useMemo } from "react";
18
- import { filterListRows, listFilterGridColumns } from "../const/list-grid-filter.js";
19
- import { useListGridFilter } from "./use-list-grid-filter.js";
20
- const NO_OPTIONS = {};
21
- const NO_HEADERS = {};
22
- /**
23
- * The grid's Filters panel and quick search for a list that HOLDS its rows
24
- * (AGL-3317) — every row it read, or the window its query capped — and so
25
- * answers both over that whole set rather than the page on screen.
26
- *
27
- * It is `useListGridFilter` with the rest of the recipe every such list
28
- * repeated: the rows matched through `filterListRows`, the columns typed
29
- * through `listFilterGridColumns`, and the props the grid and the chips
30
- * take. A list whose query can serve a clause names it in `served`, puts it
31
- * on the query, and the rows here are not matched against it again.
32
- * @deprecated AGL-3321: matches over rows a list has loaded. Serve every clause and search
33
- * word on the query instead — `planListQuery` (`@aglyn/shared-ui-jsx/const/list-query-plan`, with `nameSearchNormalizers`)
34
- * through `useListQuery` or `applyListQuery`. Removed once no list calls it.
35
- */ export function useListRowsFilter(options) {
36
- const { rows, fields, options: choices = NO_OPTIONS, headers = NO_HEADERS, search, served } = options;
37
- const selectFields = useMemo(()=>Object.keys(choices), [
38
- choices
39
- ]);
40
- const gridFilter = useListGridFilter({
41
- clauses: options.clauses,
42
- onChange: options.onChange,
43
- selectFields
44
- });
45
- const searchKey = gridFilter.searchWords.join(' ');
46
- const matched = useMemo(()=>filterListRows(rows, fields, gridFilter.clauses, {
47
- paths: search,
48
- words: gridFilter.searchWords
49
- }, served), // `searchKey` stands for the words, which are a new array each render.
50
- // eslint-disable-next-line react-hooks/exhaustive-deps
51
- [
52
- rows,
53
- fields,
54
- gridFilter.clauses,
55
- searchKey,
56
- search,
57
- served
58
- ]);
59
- const filterColumns = useCallback((columns)=>listFilterGridColumns(columns, fields, choices, headers), [
60
- fields,
61
- choices,
62
- headers
63
- ]);
64
- return {
65
- rows: matched,
66
- filtering: gridFilter.clauses.length > 0 || gridFilter.searchWords.length > 0,
67
- gridFilter,
68
- gridProps: {
69
- filterMode: 'server',
70
- filterModel: gridFilter.filterModel,
71
- onFilterModelChange: gridFilter.onFilterModelChange,
72
- quickFilter: true
73
- },
74
- chipsProps: {
75
- fields,
76
- headers,
77
- clauses: gridFilter.clauses,
78
- onChange: gridFilter.setClauses,
79
- options: choices
80
- },
81
- filterColumns
82
- };
83
- }
84
- export default useListRowsFilter;
85
-
86
- //# sourceMappingURL=use-list-rows-filter.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/hooks/use-list-rows-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 { GridColDef, GridFilterModel } from '@mui/x-data-grid'\nimport { useCallback, useMemo } from 'react'\nimport type { ListFilterField } from '../const/list-filter'\nimport {\n filterListRows,\n type ListFilterClause,\n type ListFilterOption,\n listFilterGridColumns,\n} from '../const/list-grid-filter'\nimport { type ListGridFilter, useListGridFilter } from './use-list-grid-filter'\n\nexport interface ListRowsFilterOptions<Row extends object> {\n /** Every row the list read — the whole set, or the window its query capped. */\n rows: readonly Row[]\n /** What the panel offers, in the `ListFilterField` grammar. */\n fields: readonly ListFilterField[]\n /** Choices per picked field; each field named here is shown as a select. */\n options?: Readonly<Record<string, readonly ListFilterOption[]>>\n /** How a field reads on a chip and as a hidden column's header. */\n headers?: Readonly<Record<string, string>>\n /** The row paths the quick search reads. */\n search: readonly string[]\n /** A clause the list's query already served, not matched again. */\n served?: (clause: ListFilterClause) => boolean\n /** A saved view's clauses, when the list keeps them; held here otherwise. */\n clauses?: readonly ListFilterClause[]\n onChange?: (clauses: ListFilterClause[]) => void\n}\n\n/** What a list that holds its rows hands its grid and its chips. */\nexport interface ListRowsFilter<Row> {\n /** The rows that answer every clause and the search. */\n rows: Row[]\n /** Whether anything narrows the list — a clause or a search word. */\n filtering: boolean\n gridFilter: ListGridFilter\n /** Spread onto `ListTable`: the controlled panel, answered by the list. */\n gridProps: {\n filterMode: 'server'\n filterModel: GridFilterModel\n onFilterModelChange: (model: GridFilterModel) => void\n quickFilter: true\n }\n /** Spread onto `ListFilterChips`. */\n chipsProps: {\n fields: readonly ListFilterField[]\n headers: Readonly<Record<string, string>>\n clauses: readonly ListFilterClause[]\n onChange: (clauses: ListFilterClause[]) => void\n options: Readonly<Record<string, readonly ListFilterOption[]>>\n }\n /** The list's columns, typed for the panel (`listFilterGridColumns`). */\n filterColumns: (columns: readonly GridColDef[]) => GridColDef[]\n}\n\nconst NO_OPTIONS: Readonly<Record<string, readonly ListFilterOption[]>> = {}\nconst NO_HEADERS: Readonly<Record<string, string>> = {}\n\n/**\n * The grid's Filters panel and quick search for a list that HOLDS its rows\n * (AGL-3317) — every row it read, or the window its query capped — and so\n * answers both over that whole set rather than the page on screen.\n *\n * It is `useListGridFilter` with the rest of the recipe every such list\n * repeated: the rows matched through `filterListRows`, the columns typed\n * through `listFilterGridColumns`, and the props the grid and the chips\n * take. A list whose query can serve a clause names it in `served`, puts it\n * on the query, and the rows here are not matched against it 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 useListRowsFilter<Row extends object>(\n options: ListRowsFilterOptions<Row>,\n): ListRowsFilter<Row> {\n const {\n rows,\n fields,\n options: choices = NO_OPTIONS,\n headers = NO_HEADERS,\n search,\n served,\n } = options\n const selectFields = useMemo(() => Object.keys(choices), [choices])\n const gridFilter = useListGridFilter({\n clauses: options.clauses,\n onChange: options.onChange,\n selectFields,\n })\n const searchKey = gridFilter.searchWords.join(' ')\n const matched = useMemo(\n () =>\n filterListRows(\n rows,\n fields,\n gridFilter.clauses,\n { paths: search, words: gridFilter.searchWords },\n served,\n ),\n // `searchKey` stands for the words, which are a new array each render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [rows, fields, gridFilter.clauses, searchKey, search, served],\n )\n const filterColumns = useCallback(\n (columns: readonly GridColDef[]) =>\n listFilterGridColumns(columns, fields, choices, headers),\n [fields, choices, headers],\n )\n return {\n rows: matched,\n filtering: gridFilter.clauses.length > 0 || gridFilter.searchWords.length > 0,\n gridFilter,\n gridProps: {\n filterMode: 'server',\n filterModel: gridFilter.filterModel,\n onFilterModelChange: gridFilter.onFilterModelChange,\n quickFilter: true,\n },\n chipsProps: {\n fields,\n headers,\n clauses: gridFilter.clauses,\n onChange: gridFilter.setClauses,\n options: choices,\n },\n filterColumns,\n }\n}\n\nexport default useListRowsFilter\n"],"names":["useCallback","useMemo","filterListRows","listFilterGridColumns","useListGridFilter","NO_OPTIONS","NO_HEADERS","useListRowsFilter","options","rows","fields","choices","headers","search","served","selectFields","Object","keys","gridFilter","clauses","onChange","searchKey","searchWords","join","matched","paths","words","filterColumns","columns","filtering","length","gridProps","filterMode","filterModel","onFilterModelChange","quickFilter","chipsProps","setClauses"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;AAGA,SAASA,WAAW,EAAEC,OAAO,QAAQ,QAAO;AAE5C,SACEC,cAAc,EAGdC,qBAAqB,QAChB,+BAA2B;AAClC,SAA8BC,iBAAiB,QAAQ,4BAAwB;AA8C/E,MAAMC,aAAoE,CAAC;AAC3E,MAAMC,aAA+C,CAAC;AAEtD;;;;;;;;;;;;;AAaA,GACA,OAAO,SAASC,kBACdC,OAAmC;IAEnC,MAAM,EACJC,IAAI,EACJC,MAAM,EACNF,SAASG,UAAUN,UAAU,EAC7BO,UAAUN,UAAU,EACpBO,MAAM,EACNC,MAAM,EACP,GAAGN;IACJ,MAAMO,eAAed,QAAQ,IAAMe,OAAOC,IAAI,CAACN,UAAU;QAACA;KAAQ;IAClE,MAAMO,aAAad,kBAAkB;QACnCe,SAASX,QAAQW,OAAO;QACxBC,UAAUZ,QAAQY,QAAQ;QAC1BL;IACF;IACA,MAAMM,YAAYH,WAAWI,WAAW,CAACC,IAAI,CAAC;IAC9C,MAAMC,UAAUvB,QACd,IACEC,eACEO,MACAC,QACAQ,WAAWC,OAAO,EAClB;YAAEM,OAAOZ;YAAQa,OAAOR,WAAWI,WAAW;QAAC,GAC/CR,SAEJ,uEAAuE;IACvE,uDAAuD;IACvD;QAACL;QAAMC;QAAQQ,WAAWC,OAAO;QAAEE;QAAWR;QAAQC;KAAO;IAE/D,MAAMa,gBAAgB3B,YACpB,CAAC4B,UACCzB,sBAAsByB,SAASlB,QAAQC,SAASC,UAClD;QAACF;QAAQC;QAASC;KAAQ;IAE5B,OAAO;QACLH,MAAMe;QACNK,WAAWX,WAAWC,OAAO,CAACW,MAAM,GAAG,KAAKZ,WAAWI,WAAW,CAACQ,MAAM,GAAG;QAC5EZ;QACAa,WAAW;YACTC,YAAY;YACZC,aAAaf,WAAWe,WAAW;YACnCC,qBAAqBhB,WAAWgB,mBAAmB;YACnDC,aAAa;QACf;QACAC,YAAY;YACV1B;YACAE;YACAO,SAASD,WAAWC,OAAO;YAC3BC,UAAUF,WAAWmB,UAAU;YAC/B7B,SAASG;QACX;QACAgB;IACF;AACF;AAEA,eAAepB,kBAAiB"}
@@ -1,80 +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 ListRowsFilter, type ListRowsFilterOptions } from './use-list-rows-filter';
18
- /** How many rows a paged list reads while it is filtered — see below. */
19
- export declare const PAGED_FILTER_WINDOW = 100;
20
- /**
21
- * A paged window over a live list — the shape `usePagedCollection` returns:
22
- * every row from the first page to the one being read (plus, possibly, one
23
- * probe row past it), the page, and whether more exist.
24
- */
25
- export interface PagedRowsWindow<Row> {
26
- /** Every row read so far, page 0 to the current page; may hold a probe row. */
27
- data?: readonly Row[] | null;
28
- /** The current page's rows, when nothing narrows the list. */
29
- rows: readonly Row[];
30
- hasMore: boolean;
31
- page: number;
32
- setPage: (page: number) => void;
33
- pageSize: number;
34
- setPageSize: (pageSize: number) => void;
35
- }
36
- /** What the list's grid, chips and footer take while it is paged. */
37
- export interface PagedRowsFilter<Row> extends Omit<ListRowsFilter<Row>, 'rows'> {
38
- /** The rows the grid shows: the page, or the page of the matches. */
39
- rows: Row[];
40
- /** How many rows the window read — what a filter has looked through. */
41
- read: number;
42
- /** Spread onto `ListPagination`. */
43
- pagination: {
44
- page: number;
45
- pageSize: number;
46
- rowCount: number;
47
- hasMore: boolean;
48
- onPageChange: (page: number) => void;
49
- onPageSizeChange: (pageSize: number) => void;
50
- };
51
- }
52
- /**
53
- * The grid's Filters panel and quick search over a PAGED live list
54
- * (AGL-3317), one that reads a window growing a page at a time rather than
55
- * every row.
56
- *
57
- * Unfiltered it is the pager it wraps: the grid shows the page, and the
58
- * footer turns it. While a clause or a search word is in force the list
59
- * matches over EVERYTHING the window has read — every page up to the
60
- * current one, not the page on screen — and pages the matches instead;
61
- * turning past the last match widens the window by one more page, so a
62
- * filter reaches further the further the reader goes. `read` says how far
63
- * it has looked, for the list to say so while more exist.
64
- *
65
- * ## The first filter widens the window once
66
- *
67
- * A window of one ten-row page is too little to filter: a search would look
68
- * through ten rows and call the rest "no match". So the first clause or word
69
- * widens the window to `filterWindow` rows (default
70
- * {@link PAGED_FILTER_WINDOW}) — one listener that much wider, for as long as
71
- * the list is narrowed — and clearing the last one puts the reader back on
72
- * the page they left.
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 usePagedRowsFilter<Row extends object>(paged: PagedRowsWindow<Row>, options: Omit<ListRowsFilterOptions<Row>, 'rows'> & {
78
- filterWindow?: number;
79
- }): PagedRowsFilter<Row>;
80
- export default usePagedRowsFilter;
@@ -1,129 +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
- */ 'use client';
17
- import { _ as _extends } from "@swc/helpers/_/_extends";
18
- import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
19
- import { useEffect, useMemo, useRef, useState } from "react";
20
- import { useListRowsFilter } from "./use-list-rows-filter.js";
21
- /** How many rows a paged list reads while it is filtered — see below. */ export const PAGED_FILTER_WINDOW = 100;
22
- /**
23
- * The grid's Filters panel and quick search over a PAGED live list
24
- * (AGL-3317), one that reads a window growing a page at a time rather than
25
- * every row.
26
- *
27
- * Unfiltered it is the pager it wraps: the grid shows the page, and the
28
- * footer turns it. While a clause or a search word is in force the list
29
- * matches over EVERYTHING the window has read — every page up to the
30
- * current one, not the page on screen — and pages the matches instead;
31
- * turning past the last match widens the window by one more page, so a
32
- * filter reaches further the further the reader goes. `read` says how far
33
- * it has looked, for the list to say so while more exist.
34
- *
35
- * ## The first filter widens the window once
36
- *
37
- * A window of one ten-row page is too little to filter: a search would look
38
- * through ten rows and call the rest "no match". So the first clause or word
39
- * widens the window to `filterWindow` rows (default
40
- * {@link PAGED_FILTER_WINDOW}) — one listener that much wider, for as long as
41
- * the list is narrowed — and clearing the last one puts the reader back on
42
- * the page they left.
43
- * @deprecated AGL-3321: matches over rows a list has loaded. Serve every clause and search
44
- * word on the query instead — `planListQuery` (`@aglyn/shared-ui-jsx/const/list-query-plan`, with `nameSearchNormalizers`)
45
- * through `useListQuery` or `applyListQuery`. Removed once no list calls it.
46
- */ export function usePagedRowsFilter(paged, options) {
47
- const { pageSize } = paged;
48
- const { filterWindow = PAGED_FILTER_WINDOW } = options, filterOptions = _object_without_properties_loose(options, [
49
- "filterWindow"
50
- ]);
51
- const windowRows = useMemo(()=>{
52
- var _paged_data;
53
- return ((_paged_data = paged.data) != null ? _paged_data : paged.rows).slice(0, pageSize * (paged.page + 1));
54
- }, [
55
- paged.data,
56
- paged.rows,
57
- pageSize,
58
- paged.page
59
- ]);
60
- const filter = useListRowsFilter(_extends({}, filterOptions, {
61
- rows: windowRows
62
- }));
63
- const [matchPage, setMatchPage] = useState(0);
64
- const searchKey = filter.gridFilter.searchWords.join(' ');
65
- // A narrowed list starts again at its first page.
66
- useEffect(()=>setMatchPage(0), [
67
- filter.gridFilter.clauses,
68
- searchKey
69
- ]);
70
- /** The page the reader was on before the list was narrowed. */ const before = useRef(null);
71
- const { filtering } = filter;
72
- useEffect(()=>{
73
- if (filtering && before.current === null) {
74
- before.current = paged.page;
75
- const widened = Math.max(0, Math.ceil(filterWindow / pageSize) - 1);
76
- if (widened > paged.page) paged.setPage(widened);
77
- } else if (!filtering && before.current !== null) {
78
- paged.setPage(before.current);
79
- before.current = null;
80
- }
81
- // Only the transition into and out of a filter moves the window.
82
- // eslint-disable-next-line react-hooks/exhaustive-deps
83
- }, [
84
- filtering
85
- ]);
86
- if (!filter.filtering) {
87
- return _extends({}, filter, {
88
- rows: [
89
- ...paged.rows
90
- ],
91
- read: windowRows.length,
92
- pagination: {
93
- page: paged.page,
94
- pageSize,
95
- rowCount: paged.rows.length,
96
- hasMore: paged.hasMore,
97
- onPageChange: paged.setPage,
98
- onPageSizeChange: paged.setPageSize
99
- }
100
- });
101
- }
102
- const matches = filter.rows;
103
- const shown = matches.slice(matchPage * pageSize, (matchPage + 1) * pageSize);
104
- const lastMatchPage = Math.max(0, Math.ceil(matches.length / pageSize) - 1);
105
- return _extends({}, filter, {
106
- rows: shown,
107
- read: windowRows.length,
108
- pagination: {
109
- page: matchPage,
110
- pageSize,
111
- rowCount: shown.length,
112
- hasMore: paged.hasMore || matchPage < lastMatchPage,
113
- onPageChange: (next)=>{
114
- // Past the matches read so far, the window reads another page first.
115
- if ((next + 1) * pageSize > matches.length && paged.hasMore) {
116
- paged.setPage(paged.page + 1);
117
- }
118
- setMatchPage(next);
119
- },
120
- onPageSizeChange: (next)=>{
121
- paged.setPageSize(next);
122
- setMatchPage(0);
123
- }
124
- }
125
- });
126
- }
127
- export default usePagedRowsFilter;
128
-
129
- //# sourceMappingURL=use-paged-rows-filter.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/hooks/use-paged-rows-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 { useEffect, useMemo, useRef, useState } from 'react'\nimport {\n type ListRowsFilter,\n type ListRowsFilterOptions,\n useListRowsFilter,\n} from './use-list-rows-filter'\n\n/** How many rows a paged list reads while it is filtered — see below. */\nexport const PAGED_FILTER_WINDOW = 100\n\n/**\n * A paged window over a live list — the shape `usePagedCollection` returns:\n * every row from the first page to the one being read (plus, possibly, one\n * probe row past it), the page, and whether more exist.\n */\nexport interface PagedRowsWindow<Row> {\n /** Every row read so far, page 0 to the current page; may hold a probe row. */\n data?: readonly Row[] | null\n /** The current page's rows, when nothing narrows the list. */\n rows: readonly Row[]\n hasMore: boolean\n page: number\n setPage: (page: number) => void\n pageSize: number\n setPageSize: (pageSize: number) => void\n}\n\n/** What the list's grid, chips and footer take while it is paged. */\nexport interface PagedRowsFilter<Row> extends Omit<ListRowsFilter<Row>, 'rows'> {\n /** The rows the grid shows: the page, or the page of the matches. */\n rows: Row[]\n /** How many rows the window read — what a filter has looked through. */\n read: number\n /** Spread onto `ListPagination`. */\n pagination: {\n page: number\n pageSize: number\n rowCount: number\n hasMore: boolean\n onPageChange: (page: number) => void\n onPageSizeChange: (pageSize: number) => void\n }\n}\n\n/**\n * The grid's Filters panel and quick search over a PAGED live list\n * (AGL-3317), one that reads a window growing a page at a time rather than\n * every row.\n *\n * Unfiltered it is the pager it wraps: the grid shows the page, and the\n * footer turns it. While a clause or a search word is in force the list\n * matches over EVERYTHING the window has read — every page up to the\n * current one, not the page on screen — and pages the matches instead;\n * turning past the last match widens the window by one more page, so a\n * filter reaches further the further the reader goes. `read` says how far\n * it has looked, for the list to say so while more exist.\n *\n * ## The first filter widens the window once\n *\n * A window of one ten-row page is too little to filter: a search would look\n * through ten rows and call the rest \"no match\". So the first clause or word\n * widens the window to `filterWindow` rows (default\n * {@link PAGED_FILTER_WINDOW}) — one listener that much wider, for as long as\n * the list is narrowed — and clearing the last one puts the reader back on\n * the page they left.\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 usePagedRowsFilter<Row extends object>(\n paged: PagedRowsWindow<Row>,\n options: Omit<ListRowsFilterOptions<Row>, 'rows'> & { filterWindow?: number },\n): PagedRowsFilter<Row> {\n const { pageSize } = paged\n const { filterWindow = PAGED_FILTER_WINDOW, ...filterOptions } = options\n const windowRows = useMemo(\n () => (paged.data ?? paged.rows).slice(0, pageSize * (paged.page + 1)),\n [paged.data, paged.rows, pageSize, paged.page],\n )\n const filter = useListRowsFilter({ ...filterOptions, rows: windowRows })\n const [matchPage, setMatchPage] = useState(0)\n const searchKey = filter.gridFilter.searchWords.join(' ')\n // A narrowed list starts again at its first page.\n useEffect(() => setMatchPage(0), [filter.gridFilter.clauses, searchKey])\n\n /** The page the reader was on before the list was narrowed. */\n const before = useRef<number | null>(null)\n const { filtering } = filter\n useEffect(() => {\n if (filtering && before.current === null) {\n before.current = paged.page\n const widened = Math.max(0, Math.ceil(filterWindow / pageSize) - 1)\n if (widened > paged.page) paged.setPage(widened)\n } else if (!filtering && before.current !== null) {\n paged.setPage(before.current)\n before.current = null\n }\n // Only the transition into and out of a filter moves the window.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [filtering])\n\n if (!filter.filtering) {\n return {\n ...filter,\n rows: [...paged.rows],\n read: windowRows.length,\n pagination: {\n page: paged.page,\n pageSize,\n rowCount: paged.rows.length,\n hasMore: paged.hasMore,\n onPageChange: paged.setPage,\n onPageSizeChange: paged.setPageSize,\n },\n }\n }\n const matches = filter.rows\n const shown = matches.slice(matchPage * pageSize, (matchPage + 1) * pageSize)\n const lastMatchPage = Math.max(0, Math.ceil(matches.length / pageSize) - 1)\n return {\n ...filter,\n rows: shown,\n read: windowRows.length,\n pagination: {\n page: matchPage,\n pageSize,\n rowCount: shown.length,\n hasMore: paged.hasMore || matchPage < lastMatchPage,\n onPageChange: (next) => {\n // Past the matches read so far, the window reads another page first.\n if ((next + 1) * pageSize > matches.length && paged.hasMore) {\n paged.setPage(paged.page + 1)\n }\n setMatchPage(next)\n },\n onPageSizeChange: (next) => {\n paged.setPageSize(next)\n setMatchPage(0)\n },\n },\n }\n}\n\nexport default usePagedRowsFilter\n"],"names":["useEffect","useMemo","useRef","useState","useListRowsFilter","PAGED_FILTER_WINDOW","usePagedRowsFilter","paged","options","pageSize","filterWindow","filterOptions","windowRows","data","rows","slice","page","filter","matchPage","setMatchPage","searchKey","gridFilter","searchWords","join","clauses","before","filtering","current","widened","Math","max","ceil","setPage","read","length","pagination","rowCount","hasMore","onPageChange","onPageSizeChange","setPageSize","matches","shown","lastMatchPage","next"],"mappings":"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"}