@asteby/metacore-runtime-react 22.0.0 → 23.0.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.
- package/CHANGELOG.md +33 -0
- package/dist/dynamic-kanban.d.ts +19 -0
- package/dist/dynamic-kanban.d.ts.map +1 -1
- package/dist/dynamic-kanban.js +96 -41
- package/dist/dynamic-table.js +1 -1
- package/dist/use-dynamic-filters.d.ts.map +1 -1
- package/dist/use-dynamic-filters.js +25 -1
- package/dist/use-facet-loaders.d.ts +23 -7
- package/dist/use-facet-loaders.d.ts.map +1 -1
- package/dist/use-facet-loaders.js +49 -8
- package/package.json +4 -4
- package/src/__tests__/dynamic-kanban.test.tsx +101 -0
- package/src/__tests__/use-dynamic-filters.test.tsx +65 -4
- package/src/dynamic-kanban.tsx +164 -75
- package/src/dynamic-table.tsx +1 -1
- package/src/use-dynamic-filters.ts +26 -1
- package/src/use-facet-loaders.ts +76 -9
package/src/use-facet-loaders.ts
CHANGED
|
@@ -4,10 +4,30 @@
|
|
|
4
4
|
// per-field option loader + result cache lives here to guarantee they facet
|
|
5
5
|
// identically (one board and its table sibling hit the same `/facets` endpoint
|
|
6
6
|
// with the same caching).
|
|
7
|
-
import { useCallback, useEffect, useRef } from 'react'
|
|
7
|
+
import { useCallback, useEffect, useRef, useState } from 'react'
|
|
8
8
|
import { useApi } from './api-context'
|
|
9
9
|
import type { FilterOption } from './dynamic-columns-shim'
|
|
10
10
|
|
|
11
|
+
export interface UseFacetLoadersResult {
|
|
12
|
+
/**
|
|
13
|
+
* Stable per-field loader → `<facetsBase>?field=&q=&limit=`. Cached per
|
|
14
|
+
* `field+query`. Null `facetsBase` → yields `undefined` (column stays text).
|
|
15
|
+
*/
|
|
16
|
+
getFacetLoader: (
|
|
17
|
+
field: string,
|
|
18
|
+
) => ((q?: string) => Promise<FilterOption[]>) | undefined
|
|
19
|
+
/**
|
|
20
|
+
* Warms the caches for a set of facet fields in ONE parallel burst (called
|
|
21
|
+
* when metadata resolves), so a popover opens instantly with values + counts
|
|
22
|
+
* instead of showing "Cargando…". Deduped by the field set; a field whose
|
|
23
|
+
* request fails is simply omitted (it degrades to lazy/text — the rest are
|
|
24
|
+
* unaffected). Resolved options also land in `facetOptions`.
|
|
25
|
+
*/
|
|
26
|
+
prefetchFacets: (fields: string[]) => void
|
|
27
|
+
/** Prefetched options per field (empty until `prefetchFacets` resolves). */
|
|
28
|
+
facetOptions: Map<string, FilterOption[]>
|
|
29
|
+
}
|
|
30
|
+
|
|
11
31
|
/**
|
|
12
32
|
* Long-text / body columns aren't worth faceting (thousands of unique values,
|
|
13
33
|
* each a paragraph). Heuristic on the metadata, not a hardcoded name list:
|
|
@@ -29,26 +49,39 @@ export function isLongTextColumn(c: {
|
|
|
29
49
|
}
|
|
30
50
|
|
|
31
51
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* when `facetsBase` changes (model switch).
|
|
37
|
-
* factory that yields `undefined` — the caller keeps the column as plain text.
|
|
52
|
+
* Facet option machinery: a stable per-field lazy loader (`getFacetLoader`, with
|
|
53
|
+
* a `field+query` result cache) plus `prefetchFacets` to warm every facet
|
|
54
|
+
* field's values up front and `facetOptions` holding those prewarmed results.
|
|
55
|
+
* Loader identity is memoized per field (so it doesn't churn the config memo
|
|
56
|
+
* that depends on it). Caches reset when `facetsBase` changes (model switch).
|
|
38
57
|
*/
|
|
39
|
-
export function useFacetLoaders(facetsBase: string | null) {
|
|
58
|
+
export function useFacetLoaders(facetsBase: string | null): UseFacetLoadersResult {
|
|
40
59
|
const api = useApi()
|
|
41
60
|
const loaderCache = useRef<
|
|
42
61
|
Map<string, (q?: string) => Promise<FilterOption[]>>
|
|
43
62
|
>(new Map())
|
|
44
63
|
const resultCache = useRef<Map<string, FilterOption[]>>(new Map())
|
|
64
|
+
const prefetchSig = useRef<string | null>(null)
|
|
65
|
+
const mountedRef = useRef(true)
|
|
66
|
+
const [facetOptions, setFacetOptions] = useState<Map<string, FilterOption[]>>(
|
|
67
|
+
new Map(),
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
mountedRef.current = true
|
|
72
|
+
return () => {
|
|
73
|
+
mountedRef.current = false
|
|
74
|
+
}
|
|
75
|
+
}, [])
|
|
45
76
|
|
|
46
77
|
useEffect(() => {
|
|
47
78
|
loaderCache.current.clear()
|
|
48
79
|
resultCache.current.clear()
|
|
80
|
+
prefetchSig.current = null
|
|
81
|
+
setFacetOptions(new Map())
|
|
49
82
|
}, [facetsBase])
|
|
50
83
|
|
|
51
|
-
|
|
84
|
+
const getFacetLoader = useCallback(
|
|
52
85
|
(field: string) => {
|
|
53
86
|
if (!facetsBase) return undefined
|
|
54
87
|
const existing = loaderCache.current.get(field)
|
|
@@ -76,4 +109,38 @@ export function useFacetLoaders(facetsBase: string | null) {
|
|
|
76
109
|
},
|
|
77
110
|
[api, facetsBase],
|
|
78
111
|
)
|
|
112
|
+
|
|
113
|
+
const prefetchFacets = useCallback(
|
|
114
|
+
(fields: string[]) => {
|
|
115
|
+
if (!facetsBase || fields.length === 0) return
|
|
116
|
+
const sig = [...fields].sort().join('|')
|
|
117
|
+
if (prefetchSig.current === sig) return
|
|
118
|
+
prefetchSig.current = sig
|
|
119
|
+
// One parallel burst; the loader seeds its own `${field}::` cache so the
|
|
120
|
+
// subsequent lazy open (q === '') is a cache hit. allSettled so one bad
|
|
121
|
+
// field never blocks the others.
|
|
122
|
+
void Promise.allSettled(
|
|
123
|
+
fields.map(async (field) => {
|
|
124
|
+
const loader = getFacetLoader(field)
|
|
125
|
+
if (!loader) return null
|
|
126
|
+
const opts = await loader()
|
|
127
|
+
return { field, opts }
|
|
128
|
+
}),
|
|
129
|
+
).then((results) => {
|
|
130
|
+
if (!mountedRef.current) return
|
|
131
|
+
setFacetOptions((prev) => {
|
|
132
|
+
const next = new Map(prev)
|
|
133
|
+
for (const r of results) {
|
|
134
|
+
if (r.status === 'fulfilled' && r.value) {
|
|
135
|
+
next.set(r.value.field, r.value.opts)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return next
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
},
|
|
142
|
+
[facetsBase, getFacetLoader],
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
return { getFacetLoader, prefetchFacets, facetOptions }
|
|
79
146
|
}
|