@aglyn/shared-ui-jsx 1.0.0-beta.232 → 1.0.0-beta.233

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.232",
3
+ "version": "1.0.0-beta.233",
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.232",
29
- "@aglyn/shared-ui-theme": "1.0.0-beta.232",
30
- "@aglyn/shared-util-tools": "1.0.0-beta.232",
31
- "@aglyn/shared-util-vendor": "1.0.0-beta.232",
28
+ "@aglyn/shared-data-mdi": "1.0.0-beta.233",
29
+ "@aglyn/shared-ui-theme": "1.0.0-beta.233",
30
+ "@aglyn/shared-util-tools": "1.0.0-beta.233",
31
+ "@aglyn/shared-util-vendor": "1.0.0-beta.233",
32
32
  "@mdi/js": "^7.4.47",
33
33
  "@swc/helpers": "0.5.23",
34
34
  "change-case": "^5.4.4",
@@ -1,9 +1,13 @@
1
1
  import type { GridSortDirection, GridSortModel } from '@mui/x-data-grid';
2
2
  import { type ListPageSort } from '@aglyn/shared-util-tools/list-query/list-column-sort';
3
3
  import type { ListQuerySort } from '@aglyn/shared-util-tools/list-query/list-query-plan';
4
- /** How a column's header sorts, as `ListTable` reads it. */
4
+ /**
5
+ * How a column's header sorts, as `ListTable` reads it: by the query, over
6
+ * the loaded page (and says so), or over a list loaded WHOLE (`complete`),
7
+ * where a value sort orders every row and is exact.
8
+ */
5
9
  export interface ListColumnSortColumn {
6
- mode: 'query' | 'page';
10
+ mode: 'query' | 'page' | 'loaded';
7
11
  sortingOrder: GridSortDirection[];
8
12
  }
9
13
  export interface ListColumnSort<Row = any> {
@@ -53,5 +57,13 @@ export interface ListColumnSortOptions<Row> {
53
57
  pageSorts?: Readonly<Record<string, ListPageSort<Row>>>;
54
58
  /** Headers, for the page-sort notice: field → label. */
55
59
  headers?: Readonly<Record<string, string>>;
60
+ /**
61
+ * The rows are the WHOLE list, not a page of it (strategy 4) — a roster
62
+ * read in full and paged by the grid's own footer. A `pageSorts` column
63
+ * then orders every row, so its header is exact: mode `loaded`, no "this
64
+ * page" description and no notice. Lets a fully loaded list sort its own
65
+ * columns and a plugin column's comparator through ONE order.
66
+ */
67
+ complete?: boolean;
56
68
  }
57
69
  export declare function useListColumnSort<Row>(options: ListColumnSortOptions<Row>): ListColumnSort<Row>;
@@ -19,7 +19,7 @@ import { listPageSortNotice, sortListRows } from "@aglyn/shared-util-tools/list-
19
19
  const same = (a, b)=>Boolean(a && b && a.path === b.path && a.direction === b.direction);
20
20
  export function useListColumnSort(options) {
21
21
  var _options_sort;
22
- const { sorts, defaultSort = null, orderBy, rows, pageSorts, headers, onSortChange } = options;
22
+ const { sorts, defaultSort = null, orderBy, rows, pageSorts, headers, onSortChange, complete = false } = options;
23
23
  const [heldSort, setHeldSort] = useState(defaultSort);
24
24
  const controlled = options.sort !== undefined;
25
25
  const querySort = controlled ? (_options_sort = options.sort) != null ? _options_sort : null : heldSort;
@@ -49,7 +49,7 @@ export function useListColumnSort(options) {
49
49
  for (const field of Object.keys(pageSorts != null ? pageSorts : {})){
50
50
  var _byField1, _field, _1;
51
51
  (_1 = (_byField1 = byField)[_field = field]) != null ? _1 : _byField1[_field] = {
52
- mode: 'page',
52
+ mode: complete ? 'loaded' : 'page',
53
53
  sortingOrder: [
54
54
  'asc',
55
55
  'desc',
@@ -61,7 +61,8 @@ export function useListColumnSort(options) {
61
61
  }, [
62
62
  sorts,
63
63
  pageSorts,
64
- defaultSort
64
+ defaultSort,
65
+ complete
65
66
  ]);
66
67
  const shown = orderBy != null ? orderBy : querySort;
67
68
  const sortModel = useMemo(()=>{
@@ -88,7 +89,8 @@ export function useListColumnSort(options) {
88
89
  var _columns_first_field;
89
90
  const [first] = model;
90
91
  const direction = first == null ? void 0 : first.sort;
91
- if (first && direction && ((_columns_first_field = columns[first.field]) == null ? void 0 : _columns_first_field.mode) === 'page') {
92
+ const mode = first ? (_columns_first_field = columns[first.field]) == null ? void 0 : _columns_first_field.mode : undefined;
93
+ if (first && direction && (mode === 'page' || mode === 'loaded')) {
92
94
  // The query's order stands: a page sort re-reads nothing.
93
95
  setActive({
94
96
  kind: 'page',
@@ -133,12 +135,13 @@ export function useListColumnSort(options) {
133
135
  ]);
134
136
  const notices = useMemo(()=>{
135
137
  var _ref;
136
- return (active == null ? void 0 : active.kind) === 'page' ? [
138
+ return (active == null ? void 0 : active.kind) === 'page' && !complete ? [
137
139
  listPageSortNotice((_ref = headers == null ? void 0 : headers[active.field]) != null ? _ref : active.field)
138
140
  ] : [];
139
141
  }, [
140
142
  active,
141
- headers
143
+ headers,
144
+ complete
142
145
  ]);
143
146
  return {
144
147
  sort: querySort,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/hooks/use-list-column-sort.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'use client'\n\nimport type { GridSortDirection, GridSortModel } from '@mui/x-data-grid'\nimport { useCallback, useMemo, useState } from 'react'\nimport {\n type ListPageSort,\n listPageSortNotice,\n sortListRows,\n} from '@aglyn/shared-util-tools/list-query/list-column-sort'\nimport type { ListQuerySort } from '@aglyn/shared-util-tools/list-query/list-query-plan'\n\n/*\n * A LIST'S HEADER SORTS, ONE HOOK (AGL-3680).\n *\n * Bridges MUI's `GridSortModel` and the list's `ListQuerySort`s, the way the\n * staff Organizations list did by hand: a header backed by a query order asks\n * the QUERY for it (`sort`, which the caller hands its query or route), and\n * a header backed only by a value on the loaded rows sorts the PAGE\n * (`pageSorts`) and says so. The grid itself never sorts — `sortingMode` is\n * always `server` — so nothing on screen claims an order the query did not\n * give. See `list-column-sort.ts` in `@aglyn/shared-util-tools` for which\n * strategy a column takes.\n *\n * const columnSort = useListColumnSort({ sorts, defaultSort, rows, pageSorts, headers })\n * // ask the query for `columnSort.sort`; draw\n * <ListTable columns={…} rows={columnSort.rows} columnSort={columnSort} … />\n */\n\n/** How a column's header sorts, as `ListTable` reads it. */\nexport interface ListColumnSortColumn {\n mode: 'query' | 'page'\n sortingOrder: GridSortDirection[]\n}\n\nexport interface ListColumnSort<Row = any> {\n /** The order to ask the query for, or null for the list's own order. */\n sort: ListQuerySort | null\n sortModel: GridSortModel\n onSortModelChange: (model: GridSortModel) => void\n sortingMode: 'server'\n /** The rows to draw: the page, sorted while a page header is active. */\n rows: readonly Row[]\n /** What the reader is told about the order on screen. */\n notices: string[]\n /** Every column that sorts, by field. A column not here does not. */\n columns: Readonly<Record<string, ListColumnSortColumn>>\n /**\n * A page sort by a comparator a column hands over itself — a plugin column\n * that draws its own header. `null` clears it if it is still that column's.\n */\n sortPage: (id: string, compare: ((a: Row, b: Row) => number) | null) => void\n /** The comparator column sorting the page, or null. */\n pageSortedBy: string | null\n}\n\nexport interface ListColumnSortOptions<Row> {\n /**\n * The orders the list's query serves. Each one with a `column` is a header\n * sort; the rest (the default id order) are ignored here. Usually the\n * declaration's `sorts`.\n */\n sorts: readonly ListQuerySort[]\n /** The order asked while no header is: one of `sorts`, or null. */\n defaultSort?: ListQuerySort | null\n /**\n * The asked order, CONTROLLED — for a list whose query is read before the\n * rows this hook sorts exist (the order feeds the read; the read feeds the\n * rows). Omitted, the hook holds it.\n */\n sort?: ListQuerySort | null\n onSortChange?: (sort: ListQuerySort | null) => void\n /**\n * The order the query WILL read in — the plan's `orderBy` — when that can\n * differ from the one asked (an `alone` fallback, a range filter). The\n * header shows that order, not the one clicked.\n */\n orderBy?: ListQuerySort | null\n rows: readonly Row[]\n /** Columns sorted over the loaded page (strategy 3): field → value. */\n pageSorts?: Readonly<Record<string, ListPageSort<Row>>>\n /** Headers, for the page-sort notice: field → label. */\n headers?: Readonly<Record<string, string>>\n}\n\ntype Active<Row> =\n | { kind: 'page'; field: string; direction: 'asc' | 'desc' }\n | { kind: 'compare'; id: string; compare: (a: Row, b: Row) => number }\n | null\n\nconst same = (a: ListQuerySort | null | undefined, b: ListQuerySort | null | undefined) =>\n Boolean(a && b && a.path === b.path && a.direction === b.direction)\n\nexport function useListColumnSort<Row>(options: ListColumnSortOptions<Row>): ListColumnSort<Row> {\n const { sorts, defaultSort = null, orderBy, rows, pageSorts, headers, onSortChange } = options\n const [heldSort, setHeldSort] = useState<ListQuerySort | null>(defaultSort)\n const controlled = options.sort !== undefined\n const querySort = controlled ? (options.sort ?? null) : heldSort\n const setQuerySort = useCallback(\n (next: ListQuerySort | null) => {\n if (!controlled) setHeldSort(next)\n onSortChange?.(next)\n },\n [controlled, onSortChange],\n )\n const [active, setActive] = useState<Active<Row>>(null)\n\n const columns = useMemo(() => {\n const byField: Record<string, ListColumnSortColumn> = {}\n for (const sort of sorts) {\n if (!sort.column) continue\n const entry = (byField[sort.column] ??= { mode: 'query', sortingOrder: [] })\n if (!entry.sortingOrder.includes(sort.direction)) entry.sortingOrder.push(sort.direction)\n }\n for (const entry of Object.entries(byField)) {\n // A header that IS the default order has nowhere to clear to.\n if (entry[0] !== defaultSort?.column) entry[1].sortingOrder.push(null)\n }\n for (const field of Object.keys(pageSorts ?? {})) {\n byField[field] ??= { mode: 'page', sortingOrder: ['asc', 'desc', null] }\n }\n return byField\n }, [sorts, pageSorts, defaultSort])\n\n const shown = orderBy ?? querySort\n const sortModel = useMemo<GridSortModel>(() => {\n if (active?.kind === 'page') return [{ field: active.field, sort: active.direction }]\n if (active?.kind === 'compare') return []\n const header = sorts.find((sort) => sort.column && same(sort, shown))\n return header?.column ? [{ field: header.column, sort: header.direction }] : []\n }, [active, sorts, shown])\n\n const onSortModelChange = useCallback(\n (model: GridSortModel) => {\n const [first] = model\n const direction = first?.sort\n if (first && direction && columns[first.field]?.mode === 'page') {\n // The query's order stands: a page sort re-reads nothing.\n setActive({ kind: 'page', field: first.field, direction })\n return\n }\n setActive(null)\n setQuerySort(\n (first &&\n direction &&\n sorts.find((sort) => sort.column === first.field && sort.direction === direction)) ||\n defaultSort,\n )\n },\n [columns, sorts, defaultSort, setQuerySort],\n )\n\n const sortPage = useCallback((id: string, compare: ((a: Row, b: Row) => number) | null) => {\n setActive((current) => {\n if (compare) {\n return current?.kind === 'compare' && current.id === id && current.compare === compare\n ? current\n : { kind: 'compare', id, compare }\n }\n return current?.kind === 'compare' && current.id === id ? null : current\n })\n }, [])\n\n const sorted = useMemo(() => {\n if (active?.kind === 'compare') return [...rows].sort(active.compare)\n if (active?.kind === 'page') {\n const value = pageSorts?.[active.field]\n return value ? sortListRows(rows, value, active.direction) : rows\n }\n return rows\n }, [rows, active, pageSorts])\n\n const notices = useMemo(\n () =>\n active?.kind === 'page'\n ? [listPageSortNotice(headers?.[active.field] ?? active.field)]\n : [],\n [active, headers],\n )\n\n return {\n sort: querySort,\n sortModel,\n onSortModelChange,\n sortingMode: 'server',\n rows: sorted,\n notices,\n columns,\n sortPage,\n pageSortedBy: active?.kind === 'compare' ? active.id : null,\n }\n}\n"],"names":["useCallback","useMemo","useState","listPageSortNotice","sortListRows","same","a","b","Boolean","path","direction","useListColumnSort","options","sorts","defaultSort","orderBy","rows","pageSorts","headers","onSortChange","heldSort","setHeldSort","controlled","sort","undefined","querySort","setQuerySort","next","active","setActive","columns","byField","column","entry","mode","sortingOrder","includes","push","Object","entries","field","keys","shown","sortModel","kind","header","find","onSortModelChange","model","first","sortPage","id","compare","current","sorted","value","notices","sortingMode","pageSortedBy"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../../libs/shared/ui/jsx/src/lib/hooks/use-list-column-sort.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'use client'\n\nimport type { GridSortDirection, GridSortModel } from '@mui/x-data-grid'\nimport { useCallback, useMemo, useState } from 'react'\nimport {\n type ListPageSort,\n listPageSortNotice,\n sortListRows,\n} from '@aglyn/shared-util-tools/list-query/list-column-sort'\nimport type { ListQuerySort } from '@aglyn/shared-util-tools/list-query/list-query-plan'\n\n/*\n * A LIST'S HEADER SORTS, ONE HOOK (AGL-3680).\n *\n * Bridges MUI's `GridSortModel` and the list's `ListQuerySort`s, the way the\n * staff Organizations list did by hand: a header backed by a query order asks\n * the QUERY for it (`sort`, which the caller hands its query or route), and\n * a header backed only by a value on the loaded rows sorts the PAGE\n * (`pageSorts`) and says so. The grid itself never sorts — `sortingMode` is\n * always `server` — so nothing on screen claims an order the query did not\n * give. See `list-column-sort.ts` in `@aglyn/shared-util-tools` for which\n * strategy a column takes.\n *\n * const columnSort = useListColumnSort({ sorts, defaultSort, rows, pageSorts, headers })\n * // ask the query for `columnSort.sort`; draw\n * <ListTable columns={…} rows={columnSort.rows} columnSort={columnSort} … />\n */\n\n/**\n * How a column's header sorts, as `ListTable` reads it: by the query, over\n * the loaded page (and says so), or over a list loaded WHOLE (`complete`),\n * where a value sort orders every row and is exact.\n */\nexport interface ListColumnSortColumn {\n mode: 'query' | 'page' | 'loaded'\n sortingOrder: GridSortDirection[]\n}\n\nexport interface ListColumnSort<Row = any> {\n /** The order to ask the query for, or null for the list's own order. */\n sort: ListQuerySort | null\n sortModel: GridSortModel\n onSortModelChange: (model: GridSortModel) => void\n sortingMode: 'server'\n /** The rows to draw: the page, sorted while a page header is active. */\n rows: readonly Row[]\n /** What the reader is told about the order on screen. */\n notices: string[]\n /** Every column that sorts, by field. A column not here does not. */\n columns: Readonly<Record<string, ListColumnSortColumn>>\n /**\n * A page sort by a comparator a column hands over itself — a plugin column\n * that draws its own header. `null` clears it if it is still that column's.\n */\n sortPage: (id: string, compare: ((a: Row, b: Row) => number) | null) => void\n /** The comparator column sorting the page, or null. */\n pageSortedBy: string | null\n}\n\nexport interface ListColumnSortOptions<Row> {\n /**\n * The orders the list's query serves. Each one with a `column` is a header\n * sort; the rest (the default id order) are ignored here. Usually the\n * declaration's `sorts`.\n */\n sorts: readonly ListQuerySort[]\n /** The order asked while no header is: one of `sorts`, or null. */\n defaultSort?: ListQuerySort | null\n /**\n * The asked order, CONTROLLED — for a list whose query is read before the\n * rows this hook sorts exist (the order feeds the read; the read feeds the\n * rows). Omitted, the hook holds it.\n */\n sort?: ListQuerySort | null\n onSortChange?: (sort: ListQuerySort | null) => void\n /**\n * The order the query WILL read in — the plan's `orderBy` — when that can\n * differ from the one asked (an `alone` fallback, a range filter). The\n * header shows that order, not the one clicked.\n */\n orderBy?: ListQuerySort | null\n rows: readonly Row[]\n /** Columns sorted over the loaded page (strategy 3): field → value. */\n pageSorts?: Readonly<Record<string, ListPageSort<Row>>>\n /** Headers, for the page-sort notice: field → label. */\n headers?: Readonly<Record<string, string>>\n /**\n * The rows are the WHOLE list, not a page of it (strategy 4) — a roster\n * read in full and paged by the grid's own footer. A `pageSorts` column\n * then orders every row, so its header is exact: mode `loaded`, no \"this\n * page\" description and no notice. Lets a fully loaded list sort its own\n * columns and a plugin column's comparator through ONE order.\n */\n complete?: boolean\n}\n\ntype Active<Row> =\n | { kind: 'page'; field: string; direction: 'asc' | 'desc' }\n | { kind: 'compare'; id: string; compare: (a: Row, b: Row) => number }\n | null\n\nconst same = (a: ListQuerySort | null | undefined, b: ListQuerySort | null | undefined) =>\n Boolean(a && b && a.path === b.path && a.direction === b.direction)\n\nexport function useListColumnSort<Row>(options: ListColumnSortOptions<Row>): ListColumnSort<Row> {\n const {\n sorts,\n defaultSort = null,\n orderBy,\n rows,\n pageSorts,\n headers,\n onSortChange,\n complete = false,\n } = options\n const [heldSort, setHeldSort] = useState<ListQuerySort | null>(defaultSort)\n const controlled = options.sort !== undefined\n const querySort = controlled ? (options.sort ?? null) : heldSort\n const setQuerySort = useCallback(\n (next: ListQuerySort | null) => {\n if (!controlled) setHeldSort(next)\n onSortChange?.(next)\n },\n [controlled, onSortChange],\n )\n const [active, setActive] = useState<Active<Row>>(null)\n\n const columns = useMemo(() => {\n const byField: Record<string, ListColumnSortColumn> = {}\n for (const sort of sorts) {\n if (!sort.column) continue\n const entry = (byField[sort.column] ??= { mode: 'query', sortingOrder: [] })\n if (!entry.sortingOrder.includes(sort.direction)) entry.sortingOrder.push(sort.direction)\n }\n for (const entry of Object.entries(byField)) {\n // A header that IS the default order has nowhere to clear to.\n if (entry[0] !== defaultSort?.column) entry[1].sortingOrder.push(null)\n }\n for (const field of Object.keys(pageSorts ?? {})) {\n byField[field] ??= {\n mode: complete ? 'loaded' : 'page',\n sortingOrder: ['asc', 'desc', null],\n }\n }\n return byField\n }, [sorts, pageSorts, defaultSort, complete])\n\n const shown = orderBy ?? querySort\n const sortModel = useMemo<GridSortModel>(() => {\n if (active?.kind === 'page') return [{ field: active.field, sort: active.direction }]\n if (active?.kind === 'compare') return []\n const header = sorts.find((sort) => sort.column && same(sort, shown))\n return header?.column ? [{ field: header.column, sort: header.direction }] : []\n }, [active, sorts, shown])\n\n const onSortModelChange = useCallback(\n (model: GridSortModel) => {\n const [first] = model\n const direction = first?.sort\n const mode = first ? columns[first.field]?.mode : undefined\n if (first && direction && (mode === 'page' || mode === 'loaded')) {\n // The query's order stands: a page sort re-reads nothing.\n setActive({ kind: 'page', field: first.field, direction })\n return\n }\n setActive(null)\n setQuerySort(\n (first &&\n direction &&\n sorts.find((sort) => sort.column === first.field && sort.direction === direction)) ||\n defaultSort,\n )\n },\n [columns, sorts, defaultSort, setQuerySort],\n )\n\n const sortPage = useCallback((id: string, compare: ((a: Row, b: Row) => number) | null) => {\n setActive((current) => {\n if (compare) {\n return current?.kind === 'compare' && current.id === id && current.compare === compare\n ? current\n : { kind: 'compare', id, compare }\n }\n return current?.kind === 'compare' && current.id === id ? null : current\n })\n }, [])\n\n const sorted = useMemo(() => {\n if (active?.kind === 'compare') return [...rows].sort(active.compare)\n if (active?.kind === 'page') {\n const value = pageSorts?.[active.field]\n return value ? sortListRows(rows, value, active.direction) : rows\n }\n return rows\n }, [rows, active, pageSorts])\n\n const notices = useMemo(\n () =>\n active?.kind === 'page' && !complete\n ? [listPageSortNotice(headers?.[active.field] ?? active.field)]\n : [],\n [active, headers, complete],\n )\n\n return {\n sort: querySort,\n sortModel,\n onSortModelChange,\n sortingMode: 'server',\n rows: sorted,\n notices,\n columns,\n sortPage,\n pageSortedBy: active?.kind === 'compare' ? active.id : null,\n }\n}\n"],"names":["useCallback","useMemo","useState","listPageSortNotice","sortListRows","same","a","b","Boolean","path","direction","useListColumnSort","options","sorts","defaultSort","orderBy","rows","pageSorts","headers","onSortChange","complete","heldSort","setHeldSort","controlled","sort","undefined","querySort","setQuerySort","next","active","setActive","columns","byField","column","entry","mode","sortingOrder","includes","push","Object","entries","field","keys","shown","sortModel","kind","header","find","onSortModelChange","model","first","sortPage","id","compare","current","sorted","value","notices","sortingMode","pageSortedBy"],"mappings":"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"}