-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathuseCascadingOptions.ts
More file actions
55 lines (52 loc) · 2.81 KB
/
Copy pathuseCascadingOptions.ts
File metadata and controls
55 lines (52 loc) · 2.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
import { useContext, useMemo } from 'react';
import {
resolveCascadingOptions,
type CascadingOptions,
type OptionLike,
type DependsOnInput,
} from '@object-ui/core';
import { SchemaRendererContext, usePredicateScope } from '@object-ui/react';
/**
* Shared per-option cascading / role-gating resolution for the option widgets
* (`SelectField` single, `MultiSelectField`, `RadioField`) — the client half of
* ADR-0058 (#2284). Each option may carry a `visibleWhen` CEL predicate; the
* offered set narrows against the live form record (`record.country == 'cn'`) +
* the global predicate scope (`'admin' in current_user.positions`). A field
* declares which sibling fields drive its list via `dependsOn`; while any is
* empty the list is *gated* — callers surface a "select the parent first" hint
* rather than an unfiltered set, mirroring the dependent-lookup UX.
*
* This is the React wrapper that sources `record` (the live form values) and the
* predicate scope from context; the actual resolution is the pure
* {@link resolveCascadingOptions} in `@object-ui/core`, shared with the form
* renderer so gating/filtering can never drift between them (#2715).
*/
export type CascadingOptionsResult<T extends OptionLike> = CascadingOptions<T>;
export function useCascadingOptions<T extends OptionLike>(
rawOptions: readonly T[],
dependsOn: DependsOnInput,
dependentValues: Record<string, unknown> | undefined,
): CascadingOptionsResult<T> {
// Live form values for cascading options — injected by the form renderer as
// `dependentValues` (same channel dependent lookups use). `current_user` etc.
// come from the global predicate scope so role/context predicates resolve too.
//
// ⚠️ This used to say the chain falls back to "the record on
// SchemaRendererContext". There is no such record. `SchemaRendererContextType`
// declares exactly `dataSource` / `debug` / `debugFlags` / `apiFetch`, so the
// `?? ctx?.formValues ?? ctx?.data` tail below is unconditionally `{}` in
// production — unsettable, not merely unset, because no host can populate a
// member the type does not declare. `dependentValues` is today the only
// channel that can carry a record; reached without it, a `dependsOn` option
// list stays gated. The reads are left in place: objectui#7206 owns the open
// question of whether that channel becomes real or is retired.
const ctx = useContext(SchemaRendererContext) as any;
const record = useMemo<Record<string, unknown>>(() => {
return (dependentValues ?? ctx?.formValues ?? ctx?.data ?? {}) as Record<string, unknown>;
}, [dependentValues, ctx?.formValues, ctx?.data]);
const predicateScope = usePredicateScope();
return useMemo(
() => resolveCascadingOptions(rawOptions, record, dependsOn, predicateScope),
[rawOptions, record, dependsOn, predicateScope],
);
}