@quadrokit/ui 0.2.13 → 0.3.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.
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { CollectionHandle, CollectionOptions } from '@quadrokit/client/runtime';
|
|
2
|
+
export type QuadroPagedCollectionFactory<T> = (options: CollectionOptions) => CollectionHandle<T>;
|
|
3
|
+
export type UseQuadroPagedCollectionOptions<T> = {
|
|
4
|
+
page: number;
|
|
5
|
+
pageSize: number;
|
|
6
|
+
/**
|
|
7
|
+
* When this value changes, the cached entity-set URI is cleared.
|
|
8
|
+
* Defaults to `pageSize` (so changing rows-per-page starts a new entity set).
|
|
9
|
+
* Pass e.g. a filter revision when list filters change.
|
|
10
|
+
*/
|
|
11
|
+
entitySetResetKey?: unknown;
|
|
12
|
+
/** Pass `quadro.SomeClass.all` (or wrap it); use a cast at the call site if `select` is path-typed. */
|
|
13
|
+
all: QuadroPagedCollectionFactory<T>;
|
|
14
|
+
} & Omit<CollectionOptions, 'page' | 'pageSize' | 'maxItems' | 'signal' | 'reuseEntitySet' | 'onEntitySetReady'>;
|
|
15
|
+
/**
|
|
16
|
+
* Loads one page from a QuadroKit collection with 4D entity-set reuse (`$top=0` once, then
|
|
17
|
+
* `__ENTITYSET` paging). Calls `release()` on unmount only.
|
|
18
|
+
*
|
|
19
|
+
* Other list options (`select`, `filter`, …) are read from a ref so `page` / `pageSize` can stay
|
|
20
|
+
* stable deps without stale closures; change `entitySetResetKey` when filters change.
|
|
21
|
+
*
|
|
22
|
+
* **`collection`** is the current `CollectionHandle` (e.g. `getFirst`, `release`, async iteration).
|
|
23
|
+
* It is `null` until the first handle is created for the active load, then cleared when the load
|
|
24
|
+
* effect cleans up (page / deps change or unmount).
|
|
25
|
+
*/
|
|
26
|
+
export declare function useQuadroPagedCollection<T>(options: UseQuadroPagedCollectionOptions<T>): {
|
|
27
|
+
rows: T[];
|
|
28
|
+
hasNext: boolean;
|
|
29
|
+
loading: boolean;
|
|
30
|
+
error: string | null;
|
|
31
|
+
collection: CollectionHandle<T> | null;
|
|
32
|
+
};
|
|
33
|
+
//# sourceMappingURL=use-quadro-paged-collection.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-quadro-paged-collection.d.ts","sourceRoot":"","sources":["../../src/hooks/use-quadro-paged-collection.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAA;AAGpF,MAAM,MAAM,4BAA4B,CAAC,CAAC,IAAI,CAAC,OAAO,EAAE,iBAAiB,KAAK,gBAAgB,CAAC,CAAC,CAAC,CAAA;AAEjG,MAAM,MAAM,+BAA+B,CAAC,CAAC,IAAI;IAC/C,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,uGAAuG;IACvG,GAAG,EAAE,4BAA4B,CAAC,CAAC,CAAC,CAAA;CACrC,GAAG,IAAI,CACN,iBAAiB,EACjB,MAAM,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,gBAAgB,GAAG,kBAAkB,CACpF,CAAA;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,wBAAwB,CAAC,CAAC,EAAE,OAAO,EAAE,+BAA+B,CAAC,CAAC,CAAC,GAAG;IACxF,IAAI,EAAE,CAAC,EAAE,CAAA;IACT,OAAO,EAAE,OAAO,CAAA;IAChB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,UAAU,EAAE,gBAAgB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAA;CACvC,CAqFA"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { useEffect, useRef, useState } from 'react';
|
|
2
|
+
/**
|
|
3
|
+
* Loads one page from a QuadroKit collection with 4D entity-set reuse (`$top=0` once, then
|
|
4
|
+
* `__ENTITYSET` paging). Calls `release()` on unmount only.
|
|
5
|
+
*
|
|
6
|
+
* Other list options (`select`, `filter`, …) are read from a ref so `page` / `pageSize` can stay
|
|
7
|
+
* stable deps without stale closures; change `entitySetResetKey` when filters change.
|
|
8
|
+
*
|
|
9
|
+
* **`collection`** is the current `CollectionHandle` (e.g. `getFirst`, `release`, async iteration).
|
|
10
|
+
* It is `null` until the first handle is created for the active load, then cleared when the load
|
|
11
|
+
* effect cleans up (page / deps change or unmount).
|
|
12
|
+
*/
|
|
13
|
+
export function useQuadroPagedCollection(options) {
|
|
14
|
+
const { page, pageSize, entitySetResetKey, all, ...listOptions } = options;
|
|
15
|
+
const resetKey = entitySetResetKey ?? pageSize;
|
|
16
|
+
const [rows, setRows] = useState([]);
|
|
17
|
+
const [hasNext, setHasNext] = useState(false);
|
|
18
|
+
const [loading, setLoading] = useState(true);
|
|
19
|
+
const [error, setError] = useState(null);
|
|
20
|
+
const [collection, setCollection] = useState(null);
|
|
21
|
+
const entitySetPathRef = useRef(null);
|
|
22
|
+
const releaseRef = useRef(null);
|
|
23
|
+
const prevResetKeyRef = useRef(resetKey);
|
|
24
|
+
const optsRef = useRef({ all, listOptions });
|
|
25
|
+
optsRef.current = { all, listOptions };
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (prevResetKeyRef.current !== resetKey) {
|
|
28
|
+
entitySetPathRef.current = null;
|
|
29
|
+
prevResetKeyRef.current = resetKey;
|
|
30
|
+
}
|
|
31
|
+
}, [resetKey]);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
return () => {
|
|
34
|
+
void releaseRef.current?.();
|
|
35
|
+
releaseRef.current = null;
|
|
36
|
+
entitySetPathRef.current = null;
|
|
37
|
+
};
|
|
38
|
+
}, []);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
// In React 18+ dev + Strict Mode, effects run twice (cleanup aborts the first in-flight
|
|
41
|
+
// request). Production does not double-invoke; expect one `$top=0` + entity-set fetches.
|
|
42
|
+
// Re-run when `entitySetResetKey` / default `pageSize` changes (e.g. filters), not only page.
|
|
43
|
+
void resetKey;
|
|
44
|
+
const ac = new AbortController();
|
|
45
|
+
setLoading(true);
|
|
46
|
+
setError(null);
|
|
47
|
+
void (async () => {
|
|
48
|
+
const { all: create, listOptions: lo } = optsRef.current;
|
|
49
|
+
const col = create({
|
|
50
|
+
...lo,
|
|
51
|
+
page,
|
|
52
|
+
pageSize,
|
|
53
|
+
maxItems: pageSize,
|
|
54
|
+
signal: ac.signal,
|
|
55
|
+
...(entitySetPathRef.current != null ? { reuseEntitySet: entitySetPathRef.current } : {}),
|
|
56
|
+
onEntitySetReady: (path) => {
|
|
57
|
+
entitySetPathRef.current = path;
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
releaseRef.current = () => col.release();
|
|
61
|
+
setCollection(col);
|
|
62
|
+
try {
|
|
63
|
+
const buffer = [];
|
|
64
|
+
for await (const row of col) {
|
|
65
|
+
buffer.push(row);
|
|
66
|
+
if (buffer.length >= pageSize)
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
if (ac.signal.aborted)
|
|
70
|
+
return;
|
|
71
|
+
const slice = buffer.slice(0, pageSize);
|
|
72
|
+
setRows(slice);
|
|
73
|
+
setHasNext(slice.length === pageSize);
|
|
74
|
+
}
|
|
75
|
+
catch (e) {
|
|
76
|
+
if (ac.signal.aborted)
|
|
77
|
+
return;
|
|
78
|
+
setError(e instanceof Error ? e.message : String(e));
|
|
79
|
+
setRows([]);
|
|
80
|
+
setHasNext(false);
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
if (!ac.signal.aborted)
|
|
84
|
+
setLoading(false);
|
|
85
|
+
}
|
|
86
|
+
})();
|
|
87
|
+
return () => {
|
|
88
|
+
ac.abort();
|
|
89
|
+
setCollection(null);
|
|
90
|
+
};
|
|
91
|
+
}, [page, pageSize, resetKey]);
|
|
92
|
+
return { rows, hasNext, loading, error, collection };
|
|
93
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export { Label } from './components/label.js';
|
|
|
5
5
|
export { ThemeToolbar } from './components/theme-toolbar.js';
|
|
6
6
|
export type { UseDismissOnInteractOutsideOptions } from './hooks/use-dismiss-on-interact-outside.js';
|
|
7
7
|
export { useDismissOnInteractOutside } from './hooks/use-dismiss-on-interact-outside.js';
|
|
8
|
+
export type { QuadroPagedCollectionFactory, UseQuadroPagedCollectionOptions, } from './hooks/use-quadro-paged-collection.js';
|
|
9
|
+
export { useQuadroPagedCollection } from './hooks/use-quadro-paged-collection.js';
|
|
8
10
|
export { cn } from './lib/utils.js';
|
|
9
11
|
export { type ColorMode, ThemeProvider, useTheme, } from './theme/theme-provider.js';
|
|
10
12
|
export { type QuadroThemePreset, quadroThemes, themeLabels, } from './theme/themes.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACjE,OAAO,EACL,IAAI,EACJ,WAAW,EACX,eAAe,EACf,UAAU,EACV,SAAS,GACV,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAA;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAC5D,YAAY,EAAE,kCAAkC,EAAE,MAAM,4CAA4C,CAAA;AACpG,OAAO,EAAE,2BAA2B,EAAE,MAAM,4CAA4C,CAAA;AACxF,OAAO,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAA;AACnC,OAAO,EACL,KAAK,SAAS,EACd,aAAa,EACb,QAAQ,GACT,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACL,KAAK,iBAAiB,EACtB,YAAY,EACZ,WAAW,GACZ,MAAM,mBAAmB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACjE,OAAO,EACL,IAAI,EACJ,WAAW,EACX,eAAe,EACf,UAAU,EACV,SAAS,GACV,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAE,KAAK,EAAE,KAAK,UAAU,EAAE,MAAM,uBAAuB,CAAA;AAC9D,OAAO,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAA;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAA;AAC5D,YAAY,EAAE,kCAAkC,EAAE,MAAM,4CAA4C,CAAA;AACpG,OAAO,EAAE,2BAA2B,EAAE,MAAM,4CAA4C,CAAA;AACxF,YAAY,EACV,4BAA4B,EAC5B,+BAA+B,GAChC,MAAM,wCAAwC,CAAA;AAC/C,OAAO,EAAE,wBAAwB,EAAE,MAAM,wCAAwC,CAAA;AACjF,OAAO,EAAE,EAAE,EAAE,MAAM,gBAAgB,CAAA;AACnC,OAAO,EACL,KAAK,SAAS,EACd,aAAa,EACb,QAAQ,GACT,MAAM,2BAA2B,CAAA;AAClC,OAAO,EACL,KAAK,iBAAiB,EACtB,YAAY,EACZ,WAAW,GACZ,MAAM,mBAAmB,CAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -4,6 +4,7 @@ export { Input } from './components/input.mjs';
|
|
|
4
4
|
export { Label } from './components/label.mjs';
|
|
5
5
|
export { ThemeToolbar } from './components/theme-toolbar.mjs';
|
|
6
6
|
export { useDismissOnInteractOutside } from './hooks/use-dismiss-on-interact-outside.mjs';
|
|
7
|
+
export { useQuadroPagedCollection } from './hooks/use-quadro-paged-collection.mjs';
|
|
7
8
|
export { cn } from './lib/utils.mjs';
|
|
8
9
|
export { ThemeProvider, useTheme, } from './theme/theme-provider.mjs';
|
|
9
10
|
export { quadroThemes, themeLabels, } from './theme/themes.mjs';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quadrokit/ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.mjs",
|
|
@@ -34,10 +34,12 @@
|
|
|
34
34
|
"tailwind-merge": "^3.5.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
+
"@quadrokit/client": "workspace:*",
|
|
37
38
|
"react": "^18 || ^19",
|
|
38
39
|
"react-dom": "^18 || ^19"
|
|
39
40
|
},
|
|
40
41
|
"devDependencies": {
|
|
42
|
+
"@quadrokit/client": "workspace:*",
|
|
41
43
|
"@types/react": "^19.2.14",
|
|
42
44
|
"@types/react-dom": "^19.2.3",
|
|
43
45
|
"react": "^19.2.4",
|