@jwcarman/roux-web 0.0.0 → 0.1.0

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 (81) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +22 -1
  3. package/dist/api/client.d.ts +17 -0
  4. package/dist/api/client.js +57 -0
  5. package/dist/api/config.d.ts +11 -0
  6. package/dist/api/config.js +15 -0
  7. package/dist/api/index.d.ts +3 -0
  8. package/dist/api/index.js +4 -0
  9. package/dist/api/serverAction.d.ts +23 -0
  10. package/dist/api/serverAction.js +28 -0
  11. package/dist/feedback/ConfirmProvider.d.ts +21 -0
  12. package/dist/feedback/ConfirmProvider.js +50 -0
  13. package/dist/feedback/ErrorMessage.d.ts +7 -0
  14. package/dist/feedback/ErrorMessage.js +11 -0
  15. package/dist/feedback/index.d.ts +4 -0
  16. package/dist/feedback/index.js +5 -0
  17. package/dist/feedback/notify.d.ts +16 -0
  18. package/dist/feedback/notify.js +27 -0
  19. package/dist/feedback/toaster.d.ts +7 -0
  20. package/dist/feedback/toaster.js +29 -0
  21. package/dist/form/FormDialog.d.ts +34 -0
  22. package/dist/form/FormDialog.js +36 -0
  23. package/dist/form/errors.d.ts +8 -0
  24. package/dist/form/errors.js +17 -0
  25. package/dist/form/fields/CheckboxField.d.ts +4 -0
  26. package/dist/form/fields/CheckboxField.js +25 -0
  27. package/dist/form/fields/FormField.d.ts +20 -0
  28. package/dist/form/fields/FormField.js +24 -0
  29. package/dist/form/fields/InputField.d.ts +5 -0
  30. package/dist/form/fields/InputField.js +21 -0
  31. package/dist/form/fields/NumberField.d.ts +10 -0
  32. package/dist/form/fields/NumberField.js +26 -0
  33. package/dist/form/fields/RadioGroupField.d.ts +8 -0
  34. package/dist/form/fields/RadioGroupField.js +33 -0
  35. package/dist/form/fields/SelectField.d.ts +12 -0
  36. package/dist/form/fields/SelectField.js +25 -0
  37. package/dist/form/fields/TextAreaField.d.ts +5 -0
  38. package/dist/form/fields/TextAreaField.js +21 -0
  39. package/dist/form/fields/formComponents.d.ts +18 -0
  40. package/dist/form/fields/formComponents.js +50 -0
  41. package/dist/form/form.d.ts +73 -0
  42. package/dist/form/form.js +54 -0
  43. package/dist/form/formContext.d.ts +2 -0
  44. package/dist/form/formContext.js +4 -0
  45. package/dist/form/index.d.ts +13 -0
  46. package/dist/form/index.js +14 -0
  47. package/dist/form/values.d.ts +20 -0
  48. package/dist/form/values.js +24 -0
  49. package/dist/paging/index.d.ts +1 -0
  50. package/dist/paging/index.js +1 -0
  51. package/dist/paging/paging.d.ts +80 -0
  52. package/dist/paging/paging.js +71 -0
  53. package/dist/problem/index.d.ts +1 -0
  54. package/dist/problem/index.js +1 -0
  55. package/dist/problem/problem.d.ts +22 -0
  56. package/dist/problem/problem.js +24 -0
  57. package/dist/table/FilterChips.d.ts +16 -0
  58. package/dist/table/FilterChips.js +28 -0
  59. package/dist/table/QueryTable.d.ts +11 -0
  60. package/dist/table/QueryTable.js +29 -0
  61. package/dist/table/SimpleQueryTable.d.ts +17 -0
  62. package/dist/table/SimpleQueryTable.js +37 -0
  63. package/dist/table/TablePageSizeSelect.d.ts +9 -0
  64. package/dist/table/TablePageSizeSelect.js +35 -0
  65. package/dist/table/TablePagination.d.ts +5 -0
  66. package/dist/table/TablePagination.js +65 -0
  67. package/dist/table/TableStatus.d.ts +6 -0
  68. package/dist/table/TableStatus.js +27 -0
  69. package/dist/table/TableView.d.ts +7 -0
  70. package/dist/table/TableView.js +31 -0
  71. package/dist/table/TanstackTable.d.ts +16 -0
  72. package/dist/table/TanstackTable.js +61 -0
  73. package/dist/table/index.d.ts +11 -0
  74. package/dist/table/index.js +12 -0
  75. package/dist/table/selectColumn.d.ts +17 -0
  76. package/dist/table/selectColumn.js +36 -0
  77. package/dist/table/useDebounce.d.ts +2 -0
  78. package/dist/table/useDebounce.js +10 -0
  79. package/dist/table/useQueryTable.d.ts +49 -0
  80. package/dist/table/useQueryTable.js +127 -0
  81. package/package.json +78 -2
@@ -0,0 +1,17 @@
1
+ import { type ColumnDef } from "@tanstack/react-table";
2
+ export interface SelectColumnOptions<T> {
3
+ /** The selected rows, by key; they may span pages. */
4
+ selected: ReadonlyMap<string, T>;
5
+ onSelectedChange: (selected: Map<string, T>) => void;
6
+ /** A row's stable key, such as its id: rows are new objects on every fetch. */
7
+ keyOf: (row: T) => string;
8
+ /** Names a row for its checkbox: "Select <label>". */
9
+ labelOf: (row: T) => string;
10
+ /** The header checkbox's label. */
11
+ selectAllLabel?: string;
12
+ }
13
+ /**
14
+ * A checkbox column for choosing rows, across pages. The header checkbox selects or unselects
15
+ * the rows on the page shown, leaving other pages' selections alone.
16
+ */
17
+ export declare function createSelectColumn<T>({ selected, onSelectedChange, keyOf, labelOf, selectAllLabel, }: SelectColumnOptions<T>): ColumnDef<T, unknown>;
@@ -0,0 +1,36 @@
1
+ import { Checkbox as e } from "@chakra-ui/react";
2
+ import { jsx as t, jsxs as n } from "react/jsx-runtime";
3
+ import { createColumnHelper as r } from "@tanstack/react-table";
4
+ // src/table/selectColumn.tsx
5
+ function i({ selected: i, onSelectedChange: a, keyOf: o, labelOf: s, selectAllLabel: c = "Select every row on this page" }) {
6
+ return r().display({
7
+ id: "select",
8
+ header: ({ table: r }) => {
9
+ let s = r.getRowModel().rows.map((e) => e.original), l = s.filter((e) => i.has(o(e))).length, u = s.length > 0 && l === s.length || l > 0 && "indeterminate";
10
+ function d(e) {
11
+ let t = new Map(i);
12
+ for (let n of s) e ? t.set(o(n), n) : t.delete(o(n));
13
+ a(t);
14
+ }
15
+ return /* @__PURE__ */ n(e.Root, {
16
+ checked: u,
17
+ disabled: s.length === 0,
18
+ onCheckedChange: () => d(u !== !0),
19
+ children: [/* @__PURE__ */ t(e.HiddenInput, { "aria-label": c }), /* @__PURE__ */ t(e.Control, { children: /* @__PURE__ */ t(e.Indicator, {}) })]
20
+ });
21
+ },
22
+ cell: ({ row: r }) => {
23
+ let c = o(r.original), l = i.has(c);
24
+ function u(e) {
25
+ let t = new Map(i);
26
+ e ? t.set(c, r.original) : t.delete(c), a(t);
27
+ }
28
+ return /* @__PURE__ */ n(e.Root, {
29
+ checked: l,
30
+ onCheckedChange: (e) => u(e.checked === !0),
31
+ children: [/* @__PURE__ */ t(e.HiddenInput, { "aria-label": `Select ${s(r.original)}` }), /* @__PURE__ */ t(e.Control, { children: /* @__PURE__ */ t(e.Indicator, {}) })]
32
+ });
33
+ }
34
+ });
35
+ }
36
+ export { i as createSelectColumn };
@@ -0,0 +1,2 @@
1
+ /** `value`, once it has stopped changing for `delayMs`. */
2
+ export declare function useDebounce<T>(value: T, delayMs: number): T;
@@ -0,0 +1,10 @@
1
+ import { useEffect as e, useState as t } from "react";
2
+ // src/table/useDebounce.ts
3
+ function n(n, r) {
4
+ let [i, a] = t(n);
5
+ return e(() => {
6
+ let e = setTimeout(() => a(n), r);
7
+ return () => clearTimeout(e);
8
+ }, [n, r]), i;
9
+ }
10
+ export { n as useDebounce };
@@ -0,0 +1,49 @@
1
+ import { type QueryKey } from "@tanstack/react-query";
2
+ import { type ColumnDef, type ColumnSort, type Table } from "@tanstack/react-table";
3
+ import { type PageDto, type PageParams, type SortDirection, type TableSearch } from "../paging/paging";
4
+ export declare const DEFAULT_PAGE_SIZE = 25;
5
+ export interface UseQueryTableOptions<T> {
6
+ /**
7
+ * Names the list, including any search or filter the page applies (`query` closes over the
8
+ * same values). A different key is a different list, which starts on its first page.
9
+ */
10
+ queryKey: QueryKey;
11
+ /** Fetches one page; the table supplies the page, page size and sort. */
12
+ query: (params: PageParams) => Promise<PageDto<T>>;
13
+ /** Column ids of sortable columns are the endpoint's sort names (its sort enum's values). */
14
+ columns: ColumnDef<T, unknown>[];
15
+ initialSort?: ColumnSort;
16
+ /** Rows per page, at most {@link MAX_PAGE_SIZE}; {@link DEFAULT_PAGE_SIZE} unless given. */
17
+ initialPageSize?: number;
18
+ /** A stable id per row, such as the entity's id; the row's index on the page otherwise. */
19
+ getRowId?: (row: T) => string;
20
+ /**
21
+ * The page and sort, when the caller keeps them (in the URL, say): the table shows these, with
22
+ * the defaults above for what is absent, and reports every change to {@link onParamsChange}
23
+ * instead of keeping its own. Its `search` is ignored; put the search in `queryKey` and `query`,
24
+ * and set `pageIndex` back to 0 in the same update that changes it.
25
+ */
26
+ params?: TableSearch;
27
+ onParamsChange?: (params: TableParams) => void;
28
+ }
29
+ /** A table's page and sort, as reported to {@link UseQueryTableOptions.onParamsChange}. */
30
+ export interface TableParams {
31
+ pageIndex: number;
32
+ pageSize: number;
33
+ sortBy?: string;
34
+ sortDirection?: SortDirection;
35
+ }
36
+ export interface UseQueryTableResult<T> {
37
+ table: Table<T>;
38
+ /** The page on screen: the last one loaded, while the next one loads. */
39
+ page: PageDto<T> | undefined;
40
+ isLoading: boolean;
41
+ isError: boolean;
42
+ refetch: () => Promise<unknown>;
43
+ }
44
+ /**
45
+ * A TanStack table over a jpa-utils paged endpoint: the server pages and sorts, TanStack Query
46
+ * fetches and caches, and the table holds the page and sort the user chose, or reports them to a
47
+ * caller that keeps them ({@link UseQueryTableOptions.params}).
48
+ */
49
+ export declare function useQueryTable<T>({ queryKey, query, columns, initialSort, initialPageSize, getRowId, params, onParamsChange, }: UseQueryTableOptions<T>): UseQueryTableResult<T>;
@@ -0,0 +1,127 @@
1
+ import { pageParamsOf as e } from "../paging/paging.js";
2
+ import { hashKey as t, keepPreviousData as n, useQuery as r } from "@tanstack/react-query";
3
+ import { functionalUpdate as i, getCoreRowModel as a, useReactTable as o } from "@tanstack/react-table";
4
+ import { useEffect as s, useRef as c, useState as l } from "react";
5
+ // src/table/useQueryTable.ts
6
+ var u = 25, d = [];
7
+ function f(e, t, n) {
8
+ return {
9
+ pagination: {
10
+ pageIndex: g(e.pageIndex ?? 0),
11
+ pageSize: h(e.pageSize ?? n)
12
+ },
13
+ sorting: p(e, t)
14
+ };
15
+ }
16
+ function p(e, t) {
17
+ if (e.sortBy !== void 0) return [{
18
+ id: e.sortBy,
19
+ desc: e.sortDirection === "DESC"
20
+ }];
21
+ if (t === void 0) return [];
22
+ let n = e.sortDirection === void 0 ? t.desc : e.sortDirection === "DESC";
23
+ return [{
24
+ id: t.id,
25
+ desc: n
26
+ }];
27
+ }
28
+ function m({ pagination: e, sorting: t }) {
29
+ let { pageIndex: n, pageSize: r } = e, i = t[0];
30
+ return i ? {
31
+ pageIndex: n,
32
+ pageSize: r,
33
+ sortBy: i.id,
34
+ sortDirection: i.desc ? "DESC" : "ASC"
35
+ } : {
36
+ pageIndex: n,
37
+ pageSize: r
38
+ };
39
+ }
40
+ function h(e) {
41
+ return Number.isFinite(e) ? Math.min(Math.max(1, Math.floor(e)), 100) : 25;
42
+ }
43
+ function g(e) {
44
+ return Number.isFinite(e) ? Math.max(0, Math.floor(e)) : 0;
45
+ }
46
+ function _({ queryKey: u, query: p, columns: _, initialSort: v, initialPageSize: y = 25, getRowId: b, params: x, onParamsChange: S }) {
47
+ let C = x !== void 0, [w, T] = l(() => f({}, v, y)), E = C ? f(x, v, y) : w, D = c(S);
48
+ s(() => {
49
+ D.current = S;
50
+ }, [S]);
51
+ function O(e) {
52
+ let t = {
53
+ pagination: {
54
+ pageIndex: g(e.pagination.pageIndex),
55
+ pageSize: h(e.pagination.pageSize)
56
+ },
57
+ sorting: e.sorting
58
+ };
59
+ C ? D.current?.(m(t)) : T(t);
60
+ }
61
+ let k = t(u), [A, j] = l(k);
62
+ A !== k && (j(k), T({
63
+ ...w,
64
+ pagination: {
65
+ ...w.pagination,
66
+ pageIndex: 0
67
+ }
68
+ }));
69
+ let M = E.pagination, N = e(M, E.sorting), P = r({
70
+ queryKey: [...u, N],
71
+ queryFn: () => p(N),
72
+ placeholderData: n
73
+ }), F = P.data, I = P.isPlaceholderData ? Infinity : F?.pagination.totalPageCount ?? Infinity, L = M.pageIndex > 0 && M.pageIndex >= I ? Math.max(0, I - 1) : void 0, R = M.pageSize, z = E.sorting[0]?.id, B = E.sorting[0]?.desc === !0;
74
+ return s(() => {
75
+ if (L === void 0) return;
76
+ let e = {
77
+ pagination: {
78
+ pageIndex: L,
79
+ pageSize: R
80
+ },
81
+ sorting: z === void 0 ? [] : [{
82
+ id: z,
83
+ desc: B
84
+ }]
85
+ };
86
+ C ? D.current?.(m(e)) : T(e);
87
+ }, [
88
+ L,
89
+ C,
90
+ R,
91
+ z,
92
+ B
93
+ ]), {
94
+ table: o({
95
+ data: F?.data ?? d,
96
+ columns: _,
97
+ getRowId: b,
98
+ rowCount: F?.pagination.totalElementCount ?? 0,
99
+ pageCount: F?.pagination.totalPageCount ?? 0,
100
+ state: {
101
+ pagination: M,
102
+ sorting: E.sorting
103
+ },
104
+ onPaginationChange: (e) => O({
105
+ ...E,
106
+ pagination: i(e, M)
107
+ }),
108
+ onSortingChange: (e) => O({
109
+ sorting: i(e, E.sorting),
110
+ pagination: {
111
+ ...M,
112
+ pageIndex: 0
113
+ }
114
+ }),
115
+ getCoreRowModel: a(),
116
+ autoResetPageIndex: !1,
117
+ enableMultiSort: !1,
118
+ enableSortingRemoval: !1,
119
+ sortDescFirst: !1
120
+ }),
121
+ page: F,
122
+ isLoading: P.isLoading,
123
+ isError: P.isError,
124
+ refetch: P.refetch
125
+ };
126
+ }
127
+ export { u as DEFAULT_PAGE_SIZE, _ as useQueryTable };
package/package.json CHANGED
@@ -1,11 +1,87 @@
1
1
  {
2
2
  "name": "@jwcarman/roux-web",
3
- "version": "0.0.0",
4
- "description": "Placeholder; real releases start at 0.1.0.",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "git+https://github.com/jwcarman/roux.git",
9
9
  "directory": "web/roux-web"
10
+ },
11
+ "homepage": "https://github.com/jwcarman/roux#readme",
12
+ "bugs": "https://github.com/jwcarman/roux/issues",
13
+ "sideEffects": false,
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "exports": {
18
+ "./problem": {
19
+ "types": "./dist/problem/index.d.ts",
20
+ "import": "./dist/problem/index.js"
21
+ },
22
+ "./api": {
23
+ "types": "./dist/api/index.d.ts",
24
+ "import": "./dist/api/index.js"
25
+ },
26
+ "./paging": {
27
+ "types": "./dist/paging/index.d.ts",
28
+ "import": "./dist/paging/index.js"
29
+ },
30
+ "./table": {
31
+ "types": "./dist/table/index.d.ts",
32
+ "import": "./dist/table/index.js"
33
+ },
34
+ "./feedback": {
35
+ "types": "./dist/feedback/index.d.ts",
36
+ "import": "./dist/feedback/index.js"
37
+ },
38
+ "./form": {
39
+ "types": "./dist/form/index.d.ts",
40
+ "import": "./dist/form/index.js"
41
+ }
42
+ },
43
+ "scripts": {
44
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json --emitDeclarationOnly && vite build",
45
+ "typecheck": "tsc -p tsconfig.test.json",
46
+ "test": "vitest run",
47
+ "coverage": "vitest run --coverage",
48
+ "mutate": "stryker run",
49
+ "format": "../lint-tools/node_modules/.bin/prettier --write .",
50
+ "format:check": "../lint-tools/node_modules/.bin/prettier --check .",
51
+ "lint": "../lint-tools/node_modules/.bin/eslint -c ../lint-tools/eslint.config.mjs ."
52
+ },
53
+ "peerDependencies": {
54
+ "@chakra-ui/react": "^3.38.0",
55
+ "@emotion/react": "^11.14.0",
56
+ "@tanstack/react-form": "^1.33.5",
57
+ "@tanstack/react-query": "^5.107.1",
58
+ "@tanstack/react-table": "^8.21.3",
59
+ "react": "^19.3.1",
60
+ "react-icons": "^5.7.0"
61
+ },
62
+ "devDependencies": {
63
+ "@chakra-ui/react": "3.38.0",
64
+ "@emotion/react": "11.14.0",
65
+ "@stryker-mutator/core": "10.1.1",
66
+ "@stryker-mutator/vitest-runner": "10.1.1",
67
+ "@tanstack/react-form": "1.33.5",
68
+ "@tanstack/react-query": "5.107.1",
69
+ "@tanstack/react-table": "8.21.3",
70
+ "@testing-library/dom": "10.4.2",
71
+ "@testing-library/jest-dom": "7.0.1",
72
+ "@testing-library/react": "16.3.3",
73
+ "@testing-library/user-event": "14.6.8",
74
+ "@types/node": "26.6.6",
75
+ "@types/react": "19.3.0",
76
+ "@types/react-dom": "19.3.0",
77
+ "@vitest/coverage-v8": "5.1.0",
78
+ "jsdom": "31.0.0",
79
+ "msw": "3.0.5",
80
+ "react": "19.3.1",
81
+ "react-dom": "19.3.1",
82
+ "react-icons": "5.7.0",
83
+ "typescript": "7.0.2",
84
+ "vite": "8.3.6",
85
+ "vitest": "5.1.0"
10
86
  }
11
87
  }