@deftui/react 0.3.0 → 0.3.2

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 (35) hide show
  1. package/dist/index.d.mts +4 -3
  2. package/dist/index.mjs +2 -3
  3. package/dist/vendor/README.md +4 -2
  4. package/dist/vendor/coss/calendar.mjs +2 -0
  5. package/dist/vendor/coss/card.d.mts +0 -6
  6. package/dist/vendor/coss/card.mjs +1 -68
  7. package/dist/vendor/reui.io/cascader/cascader-async.d.mts +20 -0
  8. package/dist/vendor/reui.io/cascader/cascader-async.mjs +578 -0
  9. package/dist/vendor/reui.io/cascader/cascader-context.d.mts +12 -0
  10. package/dist/vendor/reui.io/cascader/cascader-context.mjs +56 -0
  11. package/dist/vendor/reui.io/cascader/cascader-footer.d.mts +60 -0
  12. package/dist/vendor/reui.io/cascader/cascader-footer.mjs +445 -0
  13. package/dist/vendor/reui.io/cascader/cascader-i18n.mjs +64 -0
  14. package/dist/vendor/reui.io/cascader/cascader-item.d.mts +92 -0
  15. package/dist/vendor/reui.io/cascader/cascader-item.mjs +533 -0
  16. package/dist/vendor/reui.io/cascader/cascader-lib.d.mts +3 -0
  17. package/dist/vendor/reui.io/cascader/cascader-lib.mjs +688 -0
  18. package/dist/vendor/reui.io/cascader/cascader-nav.d.mts +56 -0
  19. package/dist/vendor/reui.io/cascader/cascader-nav.mjs +310 -0
  20. package/dist/vendor/reui.io/cascader/cascader-types.d.mts +140 -0
  21. package/dist/vendor/reui.io/cascader/cascader-virtual.d.mts +18 -0
  22. package/dist/vendor/reui.io/cascader/cascader-virtual.mjs +159 -0
  23. package/dist/vendor/reui.io/cascader/cascader.d.mts +299 -0
  24. package/dist/vendor/reui.io/cascader/cascader.mjs +1470 -0
  25. package/dist/vendor/reui.io/frame.d.mts +22 -0
  26. package/dist/vendor/reui.io/frame.mjs +106 -0
  27. package/dist/vendor/reui.io/timeline.d.mts +21 -0
  28. package/dist/vendor/reui.io/timeline.mjs +127 -0
  29. package/dist/widgets/datetime.mjs +0 -5
  30. package/dist/widgets/export.d.mts +28 -0
  31. package/dist/widgets/index.d.mts +64 -19
  32. package/dist/widgets/index.mjs +54 -10
  33. package/package.json +2 -2
  34. package/dist/vendor/tailored/file-tree.d.mts +0 -21
  35. package/dist/vendor/tailored/file-tree.mjs +0 -146
@@ -0,0 +1,578 @@
1
+ "use client";
2
+ import { useCascaderState } from "./cascader-context.mjs";
3
+ import { CASCADER_ROOT_KEY, isCascaderMoreNode } from "./cascader-lib.mjs";
4
+ import * as React$1 from "react";
5
+ // src/vendor/reui.io/cascader/cascader-async.tsx
6
+ /**
7
+ * Highlight dwell before `prefetch` fetches: long enough that holding ArrowDown
8
+ * does not fire a request per row, short enough to beat the ArrowRight press.
9
+ */
10
+ const PREFETCH_DELAY = 150;
11
+ /** Request keys for the two non-level requests. Never collide with a value. */
12
+ const SEARCH_KEY = "\0search";
13
+ const RESOLVE_PREFIX = "\0resolve:";
14
+ const NO_STATE = {
15
+ loading: false,
16
+ error: false,
17
+ hasMore: false
18
+ };
19
+ /** Stable empty result, so an idle search never churns the state context. */
20
+ const NO_RESULTS = [];
21
+ function createStore() {
22
+ return {
23
+ pages: /* @__PURE__ */ new Map(),
24
+ states: /* @__PURE__ */ new Map(),
25
+ detached: /* @__PURE__ */ new Map(),
26
+ links: /* @__PURE__ */ new Map()
27
+ };
28
+ }
29
+ function sameLoadState(a, b) {
30
+ return a.loading === b.loading && a.error === b.error && a.hasMore === b.hasMore && a.cursor === b.cursor;
31
+ }
32
+ /**
33
+ * Copy-on-write, and a NO-OP when nothing changed: the merged index is memoised
34
+ * on store identity, so a fresh object for an unchanged state would rebuild it.
35
+ */
36
+ function withLoadState(store, key, update) {
37
+ const current = store.states.get(key) ?? NO_STATE;
38
+ const next = update(current);
39
+ if (store.states.has(key) && sameLoadState(current, next)) return store;
40
+ const states = new Map(store.states);
41
+ states.set(key, next);
42
+ return {
43
+ ...store,
44
+ states
45
+ };
46
+ }
47
+ function withPage(store, key, items, options) {
48
+ const previous = options.append ? store.pages.get(key) ?? [] : [];
49
+ const seen = new Set(previous.map((node) => node.value));
50
+ const merged = previous.slice();
51
+ for (const item of items) {
52
+ if (seen.has(item.value)) continue;
53
+ seen.add(item.value);
54
+ merged.push(item);
55
+ }
56
+ const pages = new Map(store.pages);
57
+ pages.set(key, merged);
58
+ const states = new Map(store.states);
59
+ states.set(key, {
60
+ loading: false,
61
+ error: false,
62
+ hasMore: options.hasMore,
63
+ cursor: options.cursor
64
+ });
65
+ return {
66
+ ...store,
67
+ pages,
68
+ states
69
+ };
70
+ }
71
+ function withDetached(store, items) {
72
+ let detached = null;
73
+ for (const item of items) {
74
+ if (store.detached.get(item.value) === item) continue;
75
+ detached = detached ?? new Map(store.detached);
76
+ detached.set(item.value, item);
77
+ }
78
+ if (!detached) return store;
79
+ return {
80
+ ...store,
81
+ detached
82
+ };
83
+ }
84
+ /**
85
+ * Records a resolved ancestor chain, root first, as `detached` nodes plus
86
+ * `links`, never as rows in `pages`: a stub there made an unloaded level read
87
+ * as loaded. Writes no `states`, so every level on it still fetches for real.
88
+ */
89
+ function withChain(store, chain) {
90
+ if (chain.length === 0) return store;
91
+ const detached = new Map(store.detached);
92
+ const links = new Map(store.links);
93
+ let parentKey = CASCADER_ROOT_KEY;
94
+ for (const node of chain) {
95
+ detached.set(node.value, node);
96
+ links.set(node.value, parentKey);
97
+ parentKey = node.value;
98
+ }
99
+ return {
100
+ ...store,
101
+ detached,
102
+ links
103
+ };
104
+ }
105
+ /**
106
+ * The loader. A SIBLING of the `buildCascaderIndex` memo, never inside it: the
107
+ * build stays pure in `items`, the merge pure in that build plus this store.
108
+ */
109
+ function useCascaderLoader({ base, getChildren, onSearch, resolveValue, searchDebounce = 250, loadKey, prefetch = false, onLoadError, enabled, query, levels, path, values }) {
110
+ const [store, setStore] = React$1.useState(createStore);
111
+ const [search, setSearch] = React$1.useState(null);
112
+ /**
113
+ * Latest callbacks, WRITTEN IN AN EFFECT: `getChildren` is inline in most
114
+ * consumers, so closing over it would refire every in-flight request per
115
+ * re-render. The ref is what keeps the request machinery `[]`-dep. Declared
116
+ * FIRST, since effects run in declaration order, so the level effect below
117
+ * already sees the current commit.
118
+ */
119
+ const latest = React$1.useRef({
120
+ base,
121
+ store,
122
+ getChildren,
123
+ onSearch,
124
+ resolveValue,
125
+ onLoadError,
126
+ prefetch,
127
+ path,
128
+ query
129
+ });
130
+ React$1.useEffect(() => {
131
+ latest.current = {
132
+ base,
133
+ store,
134
+ getChildren,
135
+ onSearch,
136
+ resolveValue,
137
+ onLoadError,
138
+ prefetch,
139
+ path,
140
+ query
141
+ };
142
+ });
143
+ /** One AbortController PER KEY: columns mode runs several levels at once. */
144
+ const controllers = React$1.useRef(/* @__PURE__ */ new Map());
145
+ /** Monotonic per key. The stale guard for out-of-order responses. */
146
+ const requestIds = React$1.useRef(/* @__PURE__ */ new Map());
147
+ /** In-flight `(level, cursor)` signatures, so a duplicate ask is free. */
148
+ const inflight = React$1.useRef(/* @__PURE__ */ new Map());
149
+ /**
150
+ * The `(child count, cursor)` signature at the last paging fire, per level.
151
+ * Guards what `hasMore` cannot: a page of zero new items while the server
152
+ * still says `hasMore`. Cursor is IN the signature because an all-duplicates
153
+ * page advances it while the count stands still - real progress, which a
154
+ * count-only latch would brick forever.
155
+ */
156
+ const moreLatch = React$1.useRef(/* @__PURE__ */ new Map());
157
+ /** Values `resolveValue` has already been asked about, so it asks once. */
158
+ const attempted = React$1.useRef(/* @__PURE__ */ new Set());
159
+ /**
160
+ * Values whose resolve failed. They stay `attempted` until the next open
161
+ * rather than the next values pass: with inline `items` every parent render
162
+ * is one, and an `onLoadError` that set parent state looped at network speed.
163
+ */
164
+ const failed = React$1.useRef(/* @__PURE__ */ new Set());
165
+ /** Every node the loader has seen, so a level key can name its own node. */
166
+ const known = React$1.useRef(/* @__PURE__ */ new Map());
167
+ const timers = React$1.useRef({ prefetch: null });
168
+ /** Bumped by a `loadKey` change, so responses from before it are dropped. */
169
+ const epoch = React$1.useRef(0);
170
+ const remember = React$1.useCallback((nodes) => {
171
+ const map = known.current;
172
+ const walk = (list) => {
173
+ for (const node of list) {
174
+ map.set(node.value, node);
175
+ if (node.children?.length) walk(node.children);
176
+ }
177
+ };
178
+ walk(nodes);
179
+ }, []);
180
+ const abortKey = React$1.useCallback((key) => {
181
+ const controller = controllers.current.get(key);
182
+ if (!controller) return;
183
+ controllers.current.delete(key);
184
+ inflight.current.delete(key);
185
+ controller.abort();
186
+ }, []);
187
+ const runLoad = React$1.useCallback((key, reason, cursor, append) => {
188
+ const { getChildren: loader, base: currentBase } = latest.current;
189
+ if (!loader) return;
190
+ const signature = `${append ? "1" : "0"}:${cursor ?? ""}`;
191
+ if (inflight.current.get(key) === signature) return;
192
+ abortKey(key);
193
+ const controller = new AbortController();
194
+ controllers.current.set(key, controller);
195
+ inflight.current.set(key, signature);
196
+ const requestId = (requestIds.current.get(key) ?? 0) + 1;
197
+ requestIds.current.set(key, requestId);
198
+ const startEpoch = epoch.current;
199
+ const node = key === "\0root" ? null : currentBase.byValue.get(key) ?? known.current.get(key) ?? {
200
+ value: key,
201
+ label: key
202
+ };
203
+ setStore((prev) => withLoadState(prev, key, (state) => ({
204
+ ...state,
205
+ loading: true,
206
+ error: false
207
+ })));
208
+ const settle = () => {
209
+ if (inflight.current.get(key) === signature) inflight.current.delete(key);
210
+ if (controllers.current.get(key) === controller) controllers.current.delete(key);
211
+ };
212
+ const stale = () => controller.signal.aborted || startEpoch !== epoch.current || requestId !== requestIds.current.get(key);
213
+ Promise.resolve().then(() => loader(node, {
214
+ signal: controller.signal,
215
+ cursor,
216
+ reason
217
+ })).then((result) => {
218
+ settle();
219
+ if (stale()) return;
220
+ const items = Array.isArray(result) ? result : result.items;
221
+ const nextCursor = Array.isArray(result) ? void 0 : result.nextCursor;
222
+ const hasMore = Array.isArray(result) ? false : result.hasMore ?? nextCursor != null;
223
+ if (!append) moreLatch.current.delete(key);
224
+ remember(items);
225
+ setStore((prev) => withPage(prev, key, items, {
226
+ append,
227
+ hasMore,
228
+ cursor: nextCursor
229
+ }));
230
+ }).catch((error) => {
231
+ settle();
232
+ if (stale()) return;
233
+ setStore((prev) => withLoadState(prev, key, (state) => ({
234
+ ...state,
235
+ loading: false,
236
+ error: true
237
+ })));
238
+ latest.current.onLoadError?.(error, {
239
+ parent: key === "\0root" ? null : key,
240
+ reason
241
+ });
242
+ });
243
+ }, [abortKey, remember]);
244
+ const ensureLevel = React$1.useCallback((key, reason) => {
245
+ const { getChildren: loader, store: current, base: index } = latest.current;
246
+ if (!loader) return;
247
+ if (current.states.has(key)) return;
248
+ if (index.childrenOf.has(key)) return;
249
+ runLoad(key, reason, void 0, false);
250
+ }, [runLoad]);
251
+ const loadMore = React$1.useCallback((key) => {
252
+ const { getChildren: loader, store: current } = latest.current;
253
+ if (!loader) return;
254
+ const state = current.states.get(key);
255
+ if (!state || state.loading || !state.hasMore) return;
256
+ const signature = `${current.pages.get(key)?.length ?? 0}:${state.cursor ?? ""}`;
257
+ if (moreLatch.current.get(key) === signature) return;
258
+ moreLatch.current.set(key, signature);
259
+ runLoad(key, "more", state.cursor, true);
260
+ }, [runLoad]);
261
+ const retryLevel = React$1.useCallback((key) => {
262
+ const { getChildren: loader, store: current } = latest.current;
263
+ if (!loader) return;
264
+ const state = current.states.get(key);
265
+ if (!state?.error) return;
266
+ const loaded = current.pages.get(key)?.length ?? 0;
267
+ moreLatch.current.delete(key);
268
+ runLoad(key, "retry", loaded > 0 ? state.cursor : void 0, loaded > 0);
269
+ }, [runLoad]);
270
+ /**
271
+ * `detached` and `links` are deliberately untouched: chains and search hits
272
+ * belong to no level, and the trigger needs their path while the new page is
273
+ * in flight.
274
+ */
275
+ const invalidateLevel = React$1.useCallback((value) => {
276
+ const key = value ?? "\0root";
277
+ abortKey(key);
278
+ requestIds.current.set(key, (requestIds.current.get(key) ?? 0) + 1);
279
+ moreLatch.current.delete(key);
280
+ setStore((prev) => {
281
+ if (!prev.states.has(key) && !prev.pages.has(key)) return prev;
282
+ const states = new Map(prev.states);
283
+ states.delete(key);
284
+ const pages = new Map(prev.pages);
285
+ pages.delete(key);
286
+ return {
287
+ ...prev,
288
+ pages,
289
+ states
290
+ };
291
+ });
292
+ }, [abortKey]);
293
+ const prefetchNode = React$1.useCallback((node) => {
294
+ const { prefetch: on, getChildren: loader, store: current } = latest.current;
295
+ if (!on || !loader || !node) return;
296
+ if (isCascaderMoreNode(node)) return;
297
+ if (!node.hasChildren) return;
298
+ if (current.states.has(node.value)) return;
299
+ const holder = timers.current;
300
+ if (holder.prefetch) clearTimeout(holder.prefetch);
301
+ holder.prefetch = setTimeout(() => {
302
+ holder.prefetch = null;
303
+ ensureLevel(node.value, "prefetch");
304
+ }, PREFETCH_DELAY);
305
+ }, [ensureLevel]);
306
+ const runSearch = React$1.useCallback((text) => {
307
+ const { onSearch: searcher, path: currentPath } = latest.current;
308
+ if (!searcher) return;
309
+ abortKey(SEARCH_KEY);
310
+ const controller = new AbortController();
311
+ controllers.current.set(SEARCH_KEY, controller);
312
+ const requestId = (requestIds.current.get(SEARCH_KEY) ?? 0) + 1;
313
+ requestIds.current.set(SEARCH_KEY, requestId);
314
+ const startEpoch = epoch.current;
315
+ setSearch((prev) => ({
316
+ query: text,
317
+ results: prev?.results ?? [],
318
+ loading: true,
319
+ error: false
320
+ }));
321
+ const settle = () => {
322
+ if (controllers.current.get(SEARCH_KEY) === controller) controllers.current.delete(SEARCH_KEY);
323
+ };
324
+ const stale = () => controller.signal.aborted || startEpoch !== epoch.current || requestId !== requestIds.current.get(SEARCH_KEY);
325
+ Promise.resolve().then(() => searcher(text, {
326
+ signal: controller.signal,
327
+ path: currentPath
328
+ })).then((result) => {
329
+ settle();
330
+ if (stale()) return;
331
+ const items = Array.isArray(result) ? result : result.items;
332
+ remember(items);
333
+ setStore((prev) => withDetached(prev, items));
334
+ setSearch({
335
+ query: text,
336
+ results: items,
337
+ loading: false,
338
+ error: false
339
+ });
340
+ }).catch((error) => {
341
+ settle();
342
+ if (stale()) return;
343
+ setSearch((prev) => ({
344
+ query: text,
345
+ results: prev?.results ?? [],
346
+ loading: false,
347
+ error: true
348
+ }));
349
+ latest.current.onLoadError?.(error, {
350
+ parent: null,
351
+ reason: "search"
352
+ });
353
+ });
354
+ }, [abortKey, remember]);
355
+ const retrySearch = React$1.useCallback(() => {
356
+ const text = latest.current.query.trim();
357
+ if (text) runSearch(text);
358
+ }, [runSearch]);
359
+ const runResolve = React$1.useCallback((value) => {
360
+ const { resolveValue: resolver } = latest.current;
361
+ if (!resolver) return;
362
+ const key = `${RESOLVE_PREFIX}${value}`;
363
+ abortKey(key);
364
+ const controller = new AbortController();
365
+ controllers.current.set(key, controller);
366
+ const startEpoch = epoch.current;
367
+ const settle = () => {
368
+ if (controllers.current.get(key) === controller) controllers.current.delete(key);
369
+ };
370
+ Promise.resolve().then(() => resolver(value, {
371
+ signal: controller.signal,
372
+ reason: "resolve"
373
+ })).then((chain) => {
374
+ settle();
375
+ if (controller.signal.aborted || startEpoch !== epoch.current) return;
376
+ if (!chain?.length) return;
377
+ remember(chain);
378
+ setStore((prev) => withChain(prev, chain));
379
+ }).catch((error) => {
380
+ const live = controllers.current.get(key) === controller;
381
+ settle();
382
+ if (live) failed.current.add(value);
383
+ if (controller.signal.aborted || startEpoch !== epoch.current) return;
384
+ latest.current.onLoadError?.(error, {
385
+ parent: null,
386
+ reason: "resolve"
387
+ });
388
+ });
389
+ }, [abortKey, remember]);
390
+ const cancelPanel = React$1.useCallback(() => {
391
+ const keys = Array.from(controllers.current.keys()).filter((key) => !key.startsWith(RESOLVE_PREFIX));
392
+ for (const key of keys) {
393
+ controllers.current.get(key)?.abort();
394
+ controllers.current.delete(key);
395
+ }
396
+ inflight.current.clear();
397
+ const holder = timers.current;
398
+ if (holder.prefetch) {
399
+ clearTimeout(holder.prefetch);
400
+ holder.prefetch = null;
401
+ }
402
+ if (keys.length === 0) return;
403
+ setStore((prev) => {
404
+ let states = null;
405
+ for (const key of keys) {
406
+ const state = prev.states.get(key);
407
+ if (!state?.loading) continue;
408
+ states = states ?? new Map(prev.states);
409
+ if ((prev.pages.get(key)?.length ?? 0) > 0) states.set(key, {
410
+ ...state,
411
+ loading: false
412
+ });
413
+ else states.delete(key);
414
+ }
415
+ return states ? {
416
+ ...prev,
417
+ states
418
+ } : prev;
419
+ });
420
+ setSearch((prev) => prev?.loading ? {
421
+ ...prev,
422
+ loading: false
423
+ } : prev);
424
+ }, []);
425
+ const loadKeyRef = React$1.useRef(null);
426
+ React$1.useEffect(() => {
427
+ const previous = loadKeyRef.current;
428
+ loadKeyRef.current = { key: loadKey };
429
+ if (!previous || Object.is(previous.key, loadKey)) return;
430
+ epoch.current += 1;
431
+ for (const controller of controllers.current.values()) controller.abort();
432
+ controllers.current.clear();
433
+ inflight.current.clear();
434
+ moreLatch.current.clear();
435
+ attempted.current.clear();
436
+ failed.current.clear();
437
+ known.current.clear();
438
+ const holder = timers.current;
439
+ if (holder.prefetch) {
440
+ clearTimeout(holder.prefetch);
441
+ holder.prefetch = null;
442
+ }
443
+ setStore(createStore);
444
+ setSearch(null);
445
+ }, [loadKey]);
446
+ React$1.useEffect(() => {
447
+ if (enabled) return;
448
+ if (controllers.current.size === 0) return;
449
+ cancelPanel();
450
+ }, [enabled, cancelPanel]);
451
+ React$1.useEffect(() => {
452
+ const active = controllers.current;
453
+ const holder = timers.current;
454
+ const pending = inflight.current;
455
+ const ids = requestIds.current;
456
+ const tried = attempted.current;
457
+ const parked = failed.current;
458
+ return () => {
459
+ for (const controller of active.values()) controller.abort();
460
+ active.clear();
461
+ pending.clear();
462
+ ids.clear();
463
+ tried.clear();
464
+ parked.clear();
465
+ if (holder.prefetch) clearTimeout(holder.prefetch);
466
+ };
467
+ }, []);
468
+ const hasLoader = typeof getChildren === "function";
469
+ const hasSearch = typeof onSearch === "function";
470
+ const hasResolve = typeof resolveValue === "function";
471
+ const trimmed = query.trim();
472
+ const levelsKey = JSON.stringify(levels);
473
+ const valuesKey = JSON.stringify(values);
474
+ React$1.useEffect(() => {
475
+ if (!enabled || !hasLoader) return;
476
+ for (const key of levels) ensureLevel(key, "level");
477
+ }, [
478
+ enabled,
479
+ hasLoader,
480
+ levelsKey,
481
+ store,
482
+ ensureLevel
483
+ ]);
484
+ React$1.useEffect(() => {
485
+ if (!hasSearch) return void 0;
486
+ if (!enabled || !trimmed) {
487
+ abortKey(SEARCH_KEY);
488
+ setSearch((prev) => prev === null ? prev : null);
489
+ return;
490
+ }
491
+ const timer = setTimeout(() => runSearch(trimmed), searchDebounce);
492
+ return () => {
493
+ clearTimeout(timer);
494
+ abortKey(SEARCH_KEY);
495
+ };
496
+ }, [
497
+ hasSearch,
498
+ enabled,
499
+ trimmed,
500
+ searchDebounce,
501
+ abortKey,
502
+ runSearch
503
+ ]);
504
+ React$1.useEffect(() => {
505
+ if (!enabled || failed.current.size === 0) return;
506
+ for (const value of failed.current) attempted.current.delete(value);
507
+ failed.current.clear();
508
+ }, [enabled]);
509
+ React$1.useEffect(() => {
510
+ if (!hasResolve) return;
511
+ for (const value of values) {
512
+ if (!value) continue;
513
+ if (attempted.current.has(value)) continue;
514
+ if (base.byValue.has(value) || known.current.has(value)) continue;
515
+ attempted.current.add(value);
516
+ runResolve(value);
517
+ }
518
+ }, [
519
+ enabled,
520
+ hasResolve,
521
+ valuesKey,
522
+ base,
523
+ runResolve,
524
+ loadKey
525
+ ]);
526
+ const searchResults = React$1.useMemo(() => {
527
+ if (!hasSearch || !trimmed) return null;
528
+ if (!search || search.query !== trimmed) return NO_RESULTS;
529
+ return search.results;
530
+ }, [
531
+ hasSearch,
532
+ trimmed,
533
+ search
534
+ ]);
535
+ const searchState = React$1.useMemo(() => {
536
+ if (!hasSearch || !trimmed) return null;
537
+ const settled = search?.query === trimmed;
538
+ return {
539
+ loading: !settled || !!search?.loading,
540
+ error: settled && !!search?.error,
541
+ hasMore: false
542
+ };
543
+ }, [
544
+ hasSearch,
545
+ trimmed,
546
+ search
547
+ ]);
548
+ return React$1.useMemo(() => ({
549
+ active: hasLoader,
550
+ store,
551
+ states: store.states,
552
+ searchResults,
553
+ searchState,
554
+ ensureLevel,
555
+ loadMore,
556
+ retryLevel,
557
+ retrySearch,
558
+ invalidateLevel,
559
+ prefetchNode
560
+ }), [
561
+ hasLoader,
562
+ store,
563
+ searchResults,
564
+ searchState,
565
+ ensureLevel,
566
+ loadMore,
567
+ retryLevel,
568
+ retrySearch,
569
+ invalidateLevel,
570
+ prefetchNode
571
+ ]);
572
+ }
573
+ /** One level's load state, `null` when never fetched. Omit for the root. */
574
+ function useCascaderLoadState(parent) {
575
+ const { loadStates } = useCascaderState();
576
+ return loadStates.get(parent ?? "\0root") ?? null;
577
+ }
578
+ export { useCascaderLoadState, useCascaderLoader };
@@ -0,0 +1,12 @@
1
+ import { CascaderNode } from "./cascader-types.mjs";
2
+ import "react";
3
+ export interface CascaderItemState<T = unknown> {
4
+ branch: boolean;
5
+ selected: boolean;
6
+ /** The node's own flag or a disabled ancestor's, as `isCascaderDisabled`. */
7
+ disabled: boolean;
8
+ depth: number;
9
+ count: number;
10
+ /** Ancestor chain, root first. Populated for deep and global search rows. */
11
+ path: CascaderNode<T>[];
12
+ }
@@ -0,0 +1,56 @@
1
+ "use client";
2
+ import * as React$1 from "react";
3
+ // src/vendor/reui.io/cascader/cascader-context.tsx
4
+ const CascaderStateContext = React$1.createContext(void 0);
5
+ /**
6
+ * One provider serves every `T`, so the context holds an erased `unknown` value
7
+ * and this cast restores it. The primitive never inspects the payload.
8
+ */
9
+ function useCascaderState() {
10
+ const context = React$1.useContext(CascaderStateContext);
11
+ if (!context) throw new Error("useCascaderState must be used within a Cascader");
12
+ return context;
13
+ }
14
+ const CascaderActionsContext = React$1.createContext(void 0);
15
+ function useCascaderActions() {
16
+ const context = React$1.useContext(CascaderActionsContext);
17
+ if (!context) throw new Error("useCascaderActions must be used within a Cascader");
18
+ return context;
19
+ }
20
+ const CascaderRenderContext = React$1.createContext({});
21
+ function useCascaderRender() {
22
+ return React$1.useContext(CascaderRenderContext);
23
+ }
24
+ const NO_HIGHLIGHT = {
25
+ index: -1,
26
+ value: null
27
+ };
28
+ function createCascaderHighlightStore() {
29
+ let snapshot = NO_HIGHLIGHT;
30
+ const listeners = /* @__PURE__ */ new Set();
31
+ return {
32
+ subscribe(onStoreChange) {
33
+ listeners.add(onStoreChange);
34
+ return () => {
35
+ listeners.delete(onStoreChange);
36
+ };
37
+ },
38
+ getSnapshot() {
39
+ return snapshot;
40
+ },
41
+ set(next) {
42
+ if (next.index === snapshot.index && next.value === snapshot.value) return;
43
+ snapshot = next;
44
+ for (const listener of listeners) listener();
45
+ }
46
+ };
47
+ }
48
+ /** Shared and permanently empty, so the hook degrades outside a `Cascader`. */
49
+ const FALLBACK_HIGHLIGHT_STORE = createCascaderHighlightStore();
50
+ const CascaderHighlightContext = React$1.createContext(FALLBACK_HIGHLIGHT_STORE);
51
+ /** Subscribes to the highlight. Re-renders ONLY the calling component. */
52
+ function useCascaderHighlight() {
53
+ const store = React$1.useContext(CascaderHighlightContext);
54
+ return React$1.useSyncExternalStore(store.subscribe, store.getSnapshot, store.getSnapshot);
55
+ }
56
+ export { CascaderActionsContext, CascaderHighlightContext, CascaderRenderContext, CascaderStateContext, createCascaderHighlightStore, useCascaderActions, useCascaderHighlight, useCascaderRender, useCascaderState };
@@ -0,0 +1,60 @@
1
+ import * as React$1 from "react";
2
+ import { Popover } from "@base-ui/react";
3
+ export type CascaderFooterProps = React$1.ComponentProps<"div">;
4
+ /**
5
+ * Actions pinned below the list, a SIBLING of `CascaderList`. Children win
6
+ * over the root's `actions` prop; with neither it renders nothing.
7
+ */
8
+ declare function CascaderFooter({ className, children, onKeyDown, ...props }: CascaderFooterProps): React$1.JSX.Element | null;
9
+ export interface CascaderActionProps extends Omit<React$1.ComponentProps<"button">, "onSelect"> {
10
+ icon?: React$1.ReactNode;
11
+ /** Fires on press, after `onClick`, and not at all when disabled. */
12
+ onSelect?: () => void;
13
+ }
14
+ /**
15
+ * One footer command, shaped like a row and deliberately NOT one. A real
16
+ * `<button>`, never a `Combobox.Item`: an item would join the arrow-key ring,
17
+ * appear in `filteredItems`, and vanish the moment a query matched nothing -
18
+ * exactly when "Create new attribute" is most useful. Disabled is
19
+ * `aria-disabled`, never the native attribute: that one is not a tab stop and
20
+ * the panel's Tab order reads `button:not([disabled])`, so a footer whose ONLY
21
+ * row is a disabled command had no stop after the search field (measured on
22
+ * `c-cascader-8`). Staying focusable costs the guards below, plus the
23
+ * greyed-out look: `CASCADER_ACTION_CLASS` keys that off `aria-disabled`
24
+ * instead of `:disabled`.
25
+ */
26
+ declare function CascaderAction({ className, icon, children, onSelect, onClick, onKeyDown, disabled, ...props }: CascaderActionProps): React$1.JSX.Element;
27
+ export interface CascaderSubmenuProps {
28
+ open?: boolean;
29
+ defaultOpen?: boolean;
30
+ onOpenChange?: (open: boolean) => void;
31
+ children?: React$1.ReactNode;
32
+ }
33
+ /**
34
+ * A footer row plus the flyout it opens. Registers with the cascader root
35
+ * while open, which turns one Escape into two. Cleared in an EFFECT, so the
36
+ * flyout still reads as open during the event that closed it.
37
+ */
38
+ declare function CascaderSubmenu({ open: openProp, defaultOpen, onOpenChange, children }: CascaderSubmenuProps): React$1.JSX.Element;
39
+ export interface CascaderSubmenuTriggerProps extends Omit<React$1.ComponentProps<"button">, "onSelect"> {
40
+ icon?: React$1.ReactNode;
41
+ }
42
+ /**
43
+ * The footer row that opens the flyout, and its anchor. `aria-haspopup="menu"`
44
+ * rather than the `dialog` Base UI would announce: what opens is a list of
45
+ * commands with roving focus. `disabled` is intercepted, not forwarded:
46
+ * `Popover.Trigger` runs it through `useButton`, which writes the NATIVE
47
+ * attribute for a native `<button>` and takes the row out of the panel's Tab
48
+ * ring, the defect `CascaderAction` documents, and this component exposes no
49
+ * `focusableWhenDisabled` to opt out. Published as `aria-disabled`, with the
50
+ * arrow keys, Enter and Space, and the click closed by hand (`useClick`
51
+ * ignores `defaultPrevented`).
52
+ */
53
+ declare function CascaderSubmenuTrigger({ className, icon, children, onKeyDown, onClick, disabled, ...props }: CascaderSubmenuTriggerProps): React$1.JSX.Element;
54
+ export interface CascaderSubmenuContentProps extends Popover.Popup.Props, Pick<Popover.Positioner.Props, "side" | "align" | "sideOffset" | "alignOffset"> {}
55
+ /**
56
+ * The flyout itself. `Portal` with NO `container`: a nested portal resolves to
57
+ * the parent portal node, the mechanism the file header describes.
58
+ */
59
+ declare function CascaderSubmenuContent({ className, children, onKeyDown, side, align, sideOffset, alignOffset, ...props }: CascaderSubmenuContentProps): React$1.JSX.Element;
60
+ export { CascaderAction, CascaderFooter, CascaderSubmenu, CascaderSubmenuContent, CascaderSubmenuTrigger };