@object-ui/react 17.3.0 → 17.6.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 +1242 -0
- package/README.md +48 -21
- package/dist/LazyPluginLoader.d.ts +14 -7
- package/dist/LazyPluginLoader.js +14 -7
- package/dist/SchemaRenderer.d.ts +78 -5
- package/dist/SchemaRenderer.js +572 -34
- package/dist/context/AppShellContext.d.ts +21 -2
- package/dist/context/HostNavigationContext.d.ts +82 -0
- package/dist/context/HostNavigationContext.js +65 -0
- package/dist/context/RecordContext.d.ts +0 -4
- package/dist/context/RecordContext.js +0 -2
- package/dist/context/RelatedRecordActionsContext.d.ts +44 -0
- package/dist/context/RelatedRecordActionsContext.js +6 -0
- package/dist/context/index.d.ts +13 -12
- package/dist/context/index.js +13 -12
- package/dist/element-data-source/ElementDataSourceGate.d.ts +272 -0
- package/dist/element-data-source/ElementDataSourceGate.js +274 -0
- package/dist/hooks/index.d.ts +35 -32
- package/dist/hooks/index.js +42 -32
- package/dist/hooks/useActionEngine.js +1 -1
- package/dist/hooks/useActionTextLocalizer.d.ts +69 -0
- package/dist/hooks/useActionTextLocalizer.js +91 -0
- package/dist/hooks/useCapabilityGate.js +6 -5
- package/dist/hooks/useClientNotifications.js +2 -2
- package/dist/hooks/useCrudShortcuts.js +1 -1
- package/dist/hooks/useDatasetDimensionLabels.d.ts +104 -0
- package/dist/hooks/useDatasetDimensionLabels.js +154 -0
- package/dist/hooks/useDiscovery.js +1 -1
- package/dist/hooks/useElementDataSource.d.ts +68 -0
- package/dist/hooks/useElementDataSource.js +141 -0
- package/dist/hooks/useExpression.d.ts +82 -3
- package/dist/hooks/useExpression.js +121 -4
- package/dist/hooks/useNavigationOverlay.d.ts +59 -11
- package/dist/hooks/useOffline.d.ts +62 -36
- package/dist/hooks/useOffline.js +2 -2
- package/dist/hooks/usePageAssignment.js +5 -5
- package/dist/hooks/usePageTransition.js +1 -1
- package/dist/hooks/usePageVariableActionBridge.js +2 -2
- package/dist/hooks/useViewData.js +1 -1
- package/dist/hooks/useViewTransition.js +1 -1
- package/dist/index.d.ts +11 -9
- package/dist/index.js +12 -8
- package/dist/schema-input.d.ts +42 -0
- package/dist/schema-input.js +42 -0
- package/dist/spec-bridge/SpecBridge.d.ts +12 -6
- package/dist/spec-bridge/SpecBridge.js +9 -3
- package/dist/spec-bridge/bridges/form-view.d.ts +99 -23
- package/dist/spec-bridge/bridges/form-view.js +51 -7
- package/dist/spec-bridge/bridges/list-view.d.ts +1 -1
- package/dist/spec-bridge/bridges/list-view.js +143 -24
- package/dist/spec-bridge/index.d.ts +4 -4
- package/dist/spec-bridge/index.js +3 -3
- package/dist/spec-bridge/types.d.ts +24 -3
- package/dist/utils/error-message.d.ts +59 -0
- package/dist/utils/error-message.js +116 -1
- package/dist/utils/i18n.d.ts +43 -8
- package/dist/utils/i18n.js +42 -4
- package/dist/utils/unevaluatedExpression.d.ts +69 -0
- package/dist/utils/unevaluatedExpression.js +194 -0
- package/dist/utils/visibilityDiagnostic.d.ts +119 -0
- package/dist/utils/visibilityDiagnostic.js +284 -0
- package/package.json +8 -8
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
import { createContext, createElement, useContext, useMemo } from 'react';
|
|
9
|
-
import { ExpressionEvaluator, evalRowPredicate } from '@object-ui/core';
|
|
9
|
+
import { ExpressionEvaluator, evalRowPredicate, isLegacyDialectSource, warnNonCanonicalRowSpelling, } from '@object-ui/core';
|
|
10
10
|
/**
|
|
11
11
|
* Global predicate scope — populated by host shells (e.g. app-shell's
|
|
12
12
|
* `ExpressionProvider`) so renderers like action buttons can resolve
|
|
@@ -70,6 +70,90 @@ export function usePredicateScope() {
|
|
|
70
70
|
* dependencies").
|
|
71
71
|
*/
|
|
72
72
|
export { toPredicateInput } from '@object-ui/core';
|
|
73
|
+
/**
|
|
74
|
+
* Build the predicate context for a **row record** — the bag to hand
|
|
75
|
+
* {@link useCondition} when the thing being gated is scoped to one record.
|
|
76
|
+
*
|
|
77
|
+
* The row is bound the THREE ways the platform's row surfaces bind it:
|
|
78
|
+
* `record.status` (spec/canonical), bare `status` (row-action shorthand), and
|
|
79
|
+
* `data.status` (legacy). This is not three dialects; it is one rule, and it is
|
|
80
|
+
* `evalRowPredicate`'s rule (`core/evaluator/listConditional.ts` — the record
|
|
81
|
+
* header, list rows, the row kebab and conditional formatting all evaluate
|
|
82
|
+
* through it), restated here for the `useCondition` tier so both tiers answer
|
|
83
|
+
* an author's `visible:` the same way.
|
|
84
|
+
*
|
|
85
|
+
* ## Why a helper and not "just spread the row" (objectui#4075)
|
|
86
|
+
*
|
|
87
|
+
* `useCondition` evaluates on `new ExpressionEvaluator({ ...scope, ...context })`,
|
|
88
|
+
* so a caller that passes the row spread flat resolves the shorthand spelling
|
|
89
|
+
* and NOTHING else. The canonical spelling is the `record.` root — it is what
|
|
90
|
+
* `ExpressionEvaluator`'s CEL path binds (`bag.record` as the record
|
|
91
|
+
* namespace), what `evalRowPredicate` binds, and what the server enforces
|
|
92
|
+
* with. Under a root-only bag `record.viewer.can_act` does not read as `false`:
|
|
93
|
+
* the evaluator throws `record is not defined`, and a fail-closed `visible`
|
|
94
|
+
* turns that throw into "hidden". A correctly-authored predicate then deletes
|
|
95
|
+
* its own button, indistinguishably from the gate having said no.
|
|
96
|
+
*
|
|
97
|
+
* That was live, not theoretical: every declared action on framework's
|
|
98
|
+
* `sys_approval_request` gates on `record.viewer.*` (framework#3310 / #3424),
|
|
99
|
+
* so the whole server-declared approval decision set was invisible wherever
|
|
100
|
+
* `DeclaredActionsBar` rendered until objectui#4077 bound the row this way —
|
|
101
|
+
* and the four generic action renderers carried the same root-only binding
|
|
102
|
+
* until objectui#4075.
|
|
103
|
+
*
|
|
104
|
+
* ## The canon is `record.*` (objectui#5330, ruled 2026-08-20)
|
|
105
|
+
*
|
|
106
|
+
* All three spellings are bound here, and that is unchanged — but they are NOT
|
|
107
|
+
* peers. `record.*` is the CONTRACT; the bare shorthand and `data.*` are
|
|
108
|
+
* tolerances in a deprecation window, kept because stored metadata carries
|
|
109
|
+
* them, reported once by `warnNonCanonicalRowSpelling` from `useCondition`
|
|
110
|
+
* below, and removable only after a stored-metadata survey sizes the window
|
|
111
|
+
* (⛔ no removal before the survey — part of the same ruling).
|
|
112
|
+
*
|
|
113
|
+
* The canon states the SERVER's accept set, which is narrower than this bag:
|
|
114
|
+
* measured on `@objectstack/formula@17.1.0`, `buildScope({ record })` mounts
|
|
115
|
+
* exactly `['record']`, so of the three spellings this helper binds, only
|
|
116
|
+
* `record.*` evaluates server-side — a bare field faults `Unknown variable:
|
|
117
|
+
* status` and `data.*` faults `Unknown variable: data`. This three-way bag has
|
|
118
|
+
* no server counterpart at all, which is exactly why the warning belongs on
|
|
119
|
+
* this side. `@object-ui/core`'s `evaluator/rowPredicateCanon.ts` carries the
|
|
120
|
+
* full measurement and the layer scoping (`data` stays canonical one layer
|
|
121
|
+
* over, in a metadata-editing form — ADR-0089 D3).
|
|
122
|
+
*
|
|
123
|
+
* `record` and `data` are written AFTER the spread deliberately: a row that
|
|
124
|
+
* happens to carry a field literally named `record` or `data` must not shadow
|
|
125
|
+
* the root every predicate is written against. Same precedence as
|
|
126
|
+
* `evalRowPredicate`.
|
|
127
|
+
*
|
|
128
|
+
* This is the BINDING rule only. It deliberately does not touch the evaluation
|
|
129
|
+
* entry — `useCondition` / `toPredicateInput` / `hasDeclaredVisibilityGate`
|
|
130
|
+
* keep their pinned semantics (objectui#3492 / #3842 / #3850 / #3871), and each
|
|
131
|
+
* caller keeps its own error policy (fail-closed `visible` vs the fail-soft
|
|
132
|
+
* legs). Binding the row is a different question from what to do when the
|
|
133
|
+
* predicate faults.
|
|
134
|
+
*
|
|
135
|
+
* ## No row → bind NOTHING, do not bind an empty one
|
|
136
|
+
*
|
|
137
|
+
* `null` / `undefined` / a non-object returns an EMPTY bag, not
|
|
138
|
+
* `{ record: {}, data: {} }`. The difference is load-bearing: `useCondition`
|
|
139
|
+
* merges this context OVER the ambient predicate scope, so binding an empty
|
|
140
|
+
* record would blank out a `record` that a host had put in the scope itself —
|
|
141
|
+
* which is exactly how `action:group`'s dropdown leaf is driven in
|
|
142
|
+
* `action-group-dropdown-visible.test.tsx`, and it is a legitimate way for a
|
|
143
|
+
* host to supply the row. "This surface has no row of its own" must stay
|
|
144
|
+
* distinct from "this surface's row is empty"; only the latter is entitled to
|
|
145
|
+
* shadow the scope.
|
|
146
|
+
*
|
|
147
|
+
* @param record The row, or nothing.
|
|
148
|
+
*/
|
|
149
|
+
export function usePredicateRecordContext(record) {
|
|
150
|
+
return useMemo(() => {
|
|
151
|
+
if (record == null || typeof record !== 'object' || Array.isArray(record))
|
|
152
|
+
return {};
|
|
153
|
+
const row = record;
|
|
154
|
+
return { ...row, record: row, data: row };
|
|
155
|
+
}, [record]);
|
|
156
|
+
}
|
|
73
157
|
/**
|
|
74
158
|
* Hook for evaluating expressions with dynamic context
|
|
75
159
|
*
|
|
@@ -104,6 +188,37 @@ export function useCondition(condition, context = {}, options) {
|
|
|
104
188
|
const scope = usePredicateScope();
|
|
105
189
|
// We evaluate directly without caching the evaluator to avoid issues with context changes
|
|
106
190
|
return useMemo(() => {
|
|
191
|
+
// objectui#5330 Phase 1 — report a deprecated row-predicate spelling on
|
|
192
|
+
// the `useCondition` tier. This is the BINDING half's evaluation entry:
|
|
193
|
+
// `usePredicateRecordContext` sees the row but never the predicate text,
|
|
194
|
+
// so it structurally cannot detect a spelling, and this is the first
|
|
195
|
+
// point where the two meet.
|
|
196
|
+
//
|
|
197
|
+
// Two guards, both of which can only remove a report:
|
|
198
|
+
// - the bag must carry the `usePredicateRecordContext` signature, `data`
|
|
199
|
+
// and `record` being the SAME object by identity. A host scope that
|
|
200
|
+
// merely happens to carry a `data` key cannot satisfy that, so a
|
|
201
|
+
// non-row `useCondition` call is never judged as a row predicate.
|
|
202
|
+
// - the source must be CEL. In this tier's legacy `${…}` dialect
|
|
203
|
+
// (`'${data.status === "active"}'` — this hook's own doc example)
|
|
204
|
+
// `data.*` is the NORMAL spelling, and reporting it would be a false
|
|
205
|
+
// positive on every legacy predicate.
|
|
206
|
+
const bag = context;
|
|
207
|
+
const boundRow = bag.record != null && typeof bag.record === 'object' && bag.record === bag.data
|
|
208
|
+
? bag.record
|
|
209
|
+
: undefined;
|
|
210
|
+
if (boundRow !== undefined) {
|
|
211
|
+
const celSource = typeof condition === 'string'
|
|
212
|
+
? isLegacyDialectSource(condition)
|
|
213
|
+
? undefined
|
|
214
|
+
: condition
|
|
215
|
+
: condition && typeof condition === 'object' && condition.dialect === 'cel'
|
|
216
|
+
? condition.source
|
|
217
|
+
: undefined;
|
|
218
|
+
if (typeof celSource === 'string') {
|
|
219
|
+
warnNonCanonicalRowSpelling(celSource, boundRow, true, options?.label);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
107
222
|
const evaluator = new ExpressionEvaluator({ ...scope, ...context });
|
|
108
223
|
if (options?.throwOnError) {
|
|
109
224
|
// Fail-closed: a predicate that can't be evaluated hides/disables
|
|
@@ -141,9 +256,11 @@ export function useCondition(condition, context = {}, options) {
|
|
|
141
256
|
* routes to `@object-ui/core`'s `evalRowPredicate`: a bare string is CEL (the
|
|
142
257
|
* spec contract for `ActionSchema.visible`), a `{ dialect: 'cel', source }`
|
|
143
258
|
* envelope is always CEL, and only a legacy-dialect string falls back to the
|
|
144
|
-
* old engine (with a deprecation warning). The row is bound as `record.*`
|
|
145
|
-
*
|
|
146
|
-
*
|
|
259
|
+
* old engine (with a deprecation warning). The row is bound as `record.*` — the
|
|
260
|
+
* CANON (objectui#5330) — and, for stored metadata only, as bare fields and
|
|
261
|
+
* `data.*`, both of which now warn once; the ambient predicate scope
|
|
262
|
+
* (`features` / `user` / …) is merged alongside so deployment-level gates keep
|
|
263
|
+
* resolving.
|
|
147
264
|
*
|
|
148
265
|
* @param pred The raw predicate: `boolean` (returned as-is), a CEL string,
|
|
149
266
|
* an `{ dialect, source }` envelope, or `null`/`undefined`/`''`.
|
|
@@ -5,10 +5,11 @@
|
|
|
5
5
|
* This source code is licensed under the MIT license found in the
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
|
-
import type { NavigationConfigSchema } from '@objectstack/spec/ui';
|
|
9
|
-
import type { SpecAuthoredInput } from '../spec-input';
|
|
8
|
+
import type { NavigationConfigSchema, NavigationMode as SpecNavigationMode } from '@objectstack/spec/ui';
|
|
9
|
+
import type { SpecAuthoredInput } from '../spec-input.js';
|
|
10
10
|
/**
|
|
11
|
-
* The spec's `NavigationConfigSchema`, authoring side, with
|
|
11
|
+
* The spec's `NavigationConfigSchema`, authoring side — by reference, with no
|
|
12
|
+
* divergence of its own.
|
|
12
13
|
*
|
|
13
14
|
* This was a hand copy carrying the note "inline … to avoid importing from
|
|
14
15
|
* @object-ui/types (which may not be a direct dependency of @object-ui/react)".
|
|
@@ -18,10 +19,35 @@ import type { SpecAuthoredInput } from '../spec-input';
|
|
|
18
19
|
* `@object-ui/app-shell`, where the dependency had likewise been there all
|
|
19
20
|
* along. Check `package.json` before believing such a note (objectstack#4115).
|
|
20
21
|
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
22
|
+
* `mode` is OPTIONAL, because the spec says so and this hook agrees.
|
|
23
|
+
* `packages/spec/src/ui/view.zod.ts` declares
|
|
24
|
+
* `mode: NavigationModeSchema.default('page')`, and a `.default()` lands on the
|
|
25
|
+
* AUTHORING side as `| undefined` — so `navigation: { view: 'summary_view' }`
|
|
26
|
+
* is legal authored metadata that lets the mode default.
|
|
27
|
+
*
|
|
28
|
+
* Until objectui#4550 this alias `Omit`ted `mode` and re-added it as
|
|
29
|
+
* `NonNullable<…>`, on the stated reasoning that "this hook dispatches on
|
|
30
|
+
* `mode` and its callers always supply one". Both halves were false.
|
|
31
|
+
* `useNavigationOverlay` does not require `mode` — it DEFAULTS it
|
|
32
|
+
* (`navigation?.mode ?? 'page'`, ~140 lines below), and `'page'` is meaningful
|
|
33
|
+
* behaviour rather than a placeholder. And callers did not always supply one:
|
|
34
|
+
* `ListView` carried `schema.navigation as NavigationConfig | undefined` purely
|
|
35
|
+
* to get a spec-shaped value past this declaration — a value the hook had
|
|
36
|
+
* always been willing to take.
|
|
37
|
+
*
|
|
38
|
+
* The rule that makes this a producer-side fix rather than a caller-side one:
|
|
39
|
+
* a type in front of an implementation must not be stricter than the
|
|
40
|
+
* implementation. When it is, every caller pays in assertions, and an assertion
|
|
41
|
+
* is exactly the renderer-side workaround AGENTS.md #0.1 sends back to the
|
|
42
|
+
* producer. Here the producer was this line.
|
|
43
|
+
*
|
|
44
|
+
* `@object-ui/types` re-exports the spec's own `NavigationConfig` unchanged, so
|
|
45
|
+
* that published name and this one now agree — they did not before, which is
|
|
46
|
+
* how one monorepo shipped two `NavigationConfig`s that disagreed about whether
|
|
47
|
+
* `mode` could be omitted. Pinned by
|
|
48
|
+
* `__tests__/offline-nav-performance-spec-parity.test.ts` (the type) and
|
|
49
|
+
* `__tests__/useNavigationOverlay.modeDefault.test.tsx` (the default behaviour
|
|
50
|
+
* the relaxation rests on).
|
|
25
51
|
*
|
|
26
52
|
* Two per-key notes the hand copy carried, kept here because a derived alias
|
|
27
53
|
* has no members to hang them on:
|
|
@@ -30,9 +56,7 @@ import type { SpecAuthoredInput } from '../spec-input';
|
|
|
30
56
|
* - `width` is DEPRECATED by #2578 in favour of `size`. It still wins when
|
|
31
57
|
* present, because app-shell pre-resolves `size` into it.
|
|
32
58
|
*/
|
|
33
|
-
export type NavigationConfig =
|
|
34
|
-
mode: NonNullable<SpecAuthoredInput<typeof NavigationConfigSchema>['mode']>;
|
|
35
|
-
};
|
|
59
|
+
export type NavigationConfig = SpecAuthoredInput<typeof NavigationConfigSchema>;
|
|
36
60
|
/**
|
|
37
61
|
* Resolve the overlay width from a NavigationConfig: an explicit `width` wins
|
|
38
62
|
* (app-shell pre-resolves `size` into it); otherwise a declared bucket maps
|
|
@@ -41,7 +65,31 @@ export type NavigationConfig = Omit<SpecAuthoredInput<typeof NavigationConfigSch
|
|
|
41
65
|
* count, which only schema-aware hosts have. Exported for the parity test.
|
|
42
66
|
*/
|
|
43
67
|
export declare function resolveOverlayWidth(navigation: NavigationConfig | undefined): string | number | undefined;
|
|
44
|
-
|
|
68
|
+
/**
|
|
69
|
+
* The overlay modes — the spec's own union, DERIVED since objectui#4167.
|
|
70
|
+
*
|
|
71
|
+
* rc.6 publishes `NavigationMode` (`z.input<typeof NavigationModeSchema>`), and
|
|
72
|
+
* this alias resolved to exactly it already: `NavigationConfig['mode']` was then
|
|
73
|
+
* `NonNullable<…['mode']>`, and stripping the `undefined` that the schema's
|
|
74
|
+
* `.default()` puts on the authoring side leaves the seven-member enum itself.
|
|
75
|
+
* So the spec reference was one hop away rather than absent — the alias just
|
|
76
|
+
* reached it through a member access, which reads as a hand-written union to
|
|
77
|
+
* `check:spec-symbols` and, more to the point, to a person.
|
|
78
|
+
*
|
|
79
|
+
* Bound to the spec directly instead: the seven members now arrive from the
|
|
80
|
+
* schema that validates them. `__tests__/offline-nav-performance-spec-parity.test.ts`
|
|
81
|
+
* pins that this stays the same type as `NonNullable<NavigationConfig['mode']>`,
|
|
82
|
+
* so the two spellings cannot silently come apart if the spec ever stops
|
|
83
|
+
* defaulting `mode` or defaults it on a narrower union.
|
|
84
|
+
*
|
|
85
|
+
* The `NonNullable` in that pin is objectui#4550's mark and is load-bearing:
|
|
86
|
+
* `NavigationConfig` no longer strips the authoring-side `undefined`, so
|
|
87
|
+
* `NavigationConfig['mode']` is `NavigationMode | undefined` and a bare
|
|
88
|
+
* equality would now fail for a reason unrelated to the drift being guarded.
|
|
89
|
+
* What stays pinned is the MEMBERSHIP — the seven modes this hook switches on
|
|
90
|
+
* are exactly the seven the exported union publishes.
|
|
91
|
+
*/
|
|
92
|
+
export type NavigationMode = SpecNavigationMode;
|
|
45
93
|
export interface UseNavigationOverlayOptions {
|
|
46
94
|
/** The navigation configuration from the schema */
|
|
47
95
|
navigation?: NavigationConfig;
|
|
@@ -5,25 +5,21 @@
|
|
|
5
5
|
* This source code is licensed under the MIT license found in the
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
|
-
import type { OfflineStrategy, ConflictResolution, PersistStorage, EvictionPolicy, OfflineCacheConfigSchema, OfflineConfigSchema, SyncConfigSchema } from '@objectstack/spec/ui';
|
|
9
|
-
import type { SpecAuthoredInput } from '../spec-input';
|
|
10
8
|
/** Offline strategy determines how data is fetched when connectivity is limited. */
|
|
11
|
-
export type
|
|
9
|
+
export type OfflineStrategy = 'cache_first' | 'network_first' | 'stale_while_revalidate' | 'network_only' | 'cache_only';
|
|
12
10
|
/**
|
|
13
11
|
* Conflict resolution strategy for sync operations.
|
|
14
12
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* last-wins`, the metadata-merge policy)
|
|
18
|
-
*
|
|
19
|
-
* own name and its binding at once, the same move `FieldGroup` →
|
|
20
|
-
* `ObjectFieldGroup` made in objectui#3169.
|
|
13
|
+
* Deliberately NOT named `ConflictResolutionStrategy` — that name belongs to a
|
|
14
|
+
* different contract (`@objectstack/spec/api`: `error | priority | first-wins |
|
|
15
|
+
* last-wins`, the metadata-merge policy), and the confusion between the two is
|
|
16
|
+
* what objectui#3169 / objectstack#4115 cost.
|
|
21
17
|
*/
|
|
22
|
-
export type
|
|
18
|
+
export type ConflictResolution = 'manual' | 'client_wins' | 'server_wins' | 'last_write_wins';
|
|
23
19
|
/** Persist storage backend. */
|
|
24
|
-
export type PersistStorageType =
|
|
20
|
+
export type PersistStorageType = 'sqlite' | 'indexeddb' | 'localstorage';
|
|
25
21
|
/** Eviction policy for cache management. */
|
|
26
|
-
export type EvictionPolicyType =
|
|
22
|
+
export type EvictionPolicyType = 'lru' | 'lfu' | 'fifo';
|
|
27
23
|
/** Sync state of the offline system. */
|
|
28
24
|
export type SyncState = 'idle' | 'syncing' | 'error' | 'offline';
|
|
29
25
|
/** A queued mutation waiting to be synced. */
|
|
@@ -35,37 +31,67 @@ export interface QueuedMutation {
|
|
|
35
31
|
data?: Record<string, unknown>;
|
|
36
32
|
}
|
|
37
33
|
/**
|
|
38
|
-
* Cache configuration
|
|
39
|
-
* `OfflineCacheConfigSchema`.
|
|
34
|
+
* Cache configuration.
|
|
40
35
|
*
|
|
41
|
-
* `persistStorage` and `evictionPolicy` carry `.default()`s, so
|
|
42
|
-
* optional to
|
|
43
|
-
*
|
|
36
|
+
* `persistStorage` and `evictionPolicy` used to carry schema `.default()`s, so
|
|
37
|
+
* they stay optional to WRITE — this hook is handed what an app author wrote,
|
|
38
|
+
* which is why every key here is optional (the former `SpecAuthoredInput` /
|
|
39
|
+
* input-side reading of the retired schema).
|
|
44
40
|
*/
|
|
45
|
-
export
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
41
|
+
export interface OfflineCacheConfig {
|
|
42
|
+
/** Maximum cache size. */
|
|
43
|
+
maxSize?: number;
|
|
44
|
+
/** Time-to-live for cached entries. */
|
|
45
|
+
ttl?: number;
|
|
46
|
+
/** Storage backend. Defaults to `indexeddb` when a cache is used. */
|
|
47
|
+
persistStorage?: PersistStorageType;
|
|
48
|
+
/** Eviction policy once `maxSize` is reached. Defaults to `lru`. */
|
|
49
|
+
evictionPolicy?: EvictionPolicyType;
|
|
50
|
+
}
|
|
51
|
+
/** Sync configuration — controls how queued mutations reach the server. */
|
|
52
|
+
export interface OfflineSyncConfig {
|
|
53
|
+
/** Fetch strategy for sync requests. */
|
|
54
|
+
strategy?: OfflineStrategy;
|
|
55
|
+
/** How to resolve a server/client conflict. */
|
|
56
|
+
conflictResolution?: ConflictResolution;
|
|
57
|
+
/** Delay between retry attempts, in ms. */
|
|
58
|
+
retryInterval?: number;
|
|
59
|
+
/** Maximum retry attempts before giving up. */
|
|
60
|
+
maxRetries?: number;
|
|
61
|
+
/** Mutations flushed per sync batch. */
|
|
62
|
+
batchSize?: number;
|
|
63
|
+
}
|
|
53
64
|
/**
|
|
54
|
-
* Top-level offline configuration
|
|
55
|
-
* `OfflineConfigSchema`.
|
|
65
|
+
* Top-level offline configuration.
|
|
56
66
|
*
|
|
57
67
|
* This name stays with `@object-ui/react` (objectui#3156 / objectui#3159): the
|
|
58
68
|
* `OfflineConfig` that used to sit in `@object-ui/types` was a service-worker
|
|
59
69
|
* ROUTE cache and has been renamed `PWAOfflineConfig`, so there is no
|
|
60
|
-
* cross-package clash
|
|
61
|
-
* key, and takes the spec's binding.
|
|
70
|
+
* cross-package clash.
|
|
62
71
|
*
|
|
63
|
-
*
|
|
64
|
-
* `useOffline(config: OfflineConfig = {})` defaults to the empty object,
|
|
65
|
-
*
|
|
66
|
-
*
|
|
72
|
+
* Every key is optional, and that is load-bearing rather than incidental:
|
|
73
|
+
* `useOffline(config: OfflineConfig = {})` defaults to the empty object, so a
|
|
74
|
+
* shape that made `enabled` / `strategy` / `offlineIndicator` required — as the
|
|
75
|
+
* retired schema's OUTPUT side did, once its `.default()`s had run — would
|
|
76
|
+
* reject the hook's own default argument. {@link DEFAULTS} below applies the
|
|
77
|
+
* same values the schema defaults used to.
|
|
67
78
|
*/
|
|
68
|
-
export
|
|
79
|
+
export interface OfflineConfig {
|
|
80
|
+
/** Whether offline mode is active. Defaults to `true`. */
|
|
81
|
+
enabled?: boolean;
|
|
82
|
+
/** Fetch strategy. Defaults to `network_first`. */
|
|
83
|
+
strategy?: OfflineStrategy;
|
|
84
|
+
/** Client-side cache configuration. */
|
|
85
|
+
cache?: OfflineCacheConfig;
|
|
86
|
+
/** Sync behaviour for queued mutations. */
|
|
87
|
+
sync?: OfflineSyncConfig;
|
|
88
|
+
/** Whether to surface the offline indicator UI. Defaults to `true`. */
|
|
89
|
+
offlineIndicator?: boolean;
|
|
90
|
+
/** Message shown while offline. */
|
|
91
|
+
offlineMessage?: string;
|
|
92
|
+
/** Maximum queued mutations retained. Defaults to `100`. */
|
|
93
|
+
queueMaxSize?: number;
|
|
94
|
+
}
|
|
69
95
|
/** Result returned by the useOffline hook. */
|
|
70
96
|
export interface OfflineResult {
|
|
71
97
|
/** Whether the browser is currently online. */
|
|
@@ -91,8 +117,8 @@ export interface OfflineResult {
|
|
|
91
117
|
}
|
|
92
118
|
/**
|
|
93
119
|
* Hook for offline mode detection and sync queue management. Its config types
|
|
94
|
-
*
|
|
95
|
-
*
|
|
120
|
+
* are declared locally — see {@link OfflineConfig} for why they stopped being
|
|
121
|
+
* the spec's.
|
|
96
122
|
*
|
|
97
123
|
* @example
|
|
98
124
|
* ```tsx
|
package/dist/hooks/useOffline.js
CHANGED
|
@@ -67,8 +67,8 @@ const DEFAULTS = {
|
|
|
67
67
|
};
|
|
68
68
|
/**
|
|
69
69
|
* Hook for offline mode detection and sync queue management. Its config types
|
|
70
|
-
*
|
|
71
|
-
*
|
|
70
|
+
* are declared locally — see {@link OfflineConfig} for why they stopped being
|
|
71
|
+
* the spec's.
|
|
72
72
|
*
|
|
73
73
|
* @example
|
|
74
74
|
* ```tsx
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* when no record Page is authored.
|
|
25
25
|
*/
|
|
26
26
|
import { useEffect, useMemo, useState } from 'react';
|
|
27
|
-
import { useMetadata } from '../context/AppShellContext';
|
|
27
|
+
import { useMetadata } from '../context/AppShellContext.js';
|
|
28
28
|
function matchesAssignment(_page, _opts) {
|
|
29
29
|
// Placeholder — every page matches until rules-based assignment lands.
|
|
30
30
|
return true;
|
|
@@ -76,10 +76,10 @@ export function usePageAssignment(objectName, opts = {}) {
|
|
|
76
76
|
if (!p)
|
|
77
77
|
return false;
|
|
78
78
|
// Only `pageType: 'record'` (or bare `type: 'record'`) is a
|
|
79
|
-
// user-facing record-detail page. `pageType
|
|
80
|
-
//
|
|
81
|
-
//
|
|
82
|
-
// otherwise opening any record would render
|
|
79
|
+
// user-facing record-detail page. Any other `pageType` — notably the
|
|
80
|
+
// designer-side `'record_detail'`, dropped from `PageType` in
|
|
81
|
+
// framework#2265 but still expressible in raw metadata — must NOT be
|
|
82
|
+
// picked up here, otherwise opening any record would render it.
|
|
83
83
|
const pt = p.pageType ?? (p.type === 'record' ? 'record' : undefined);
|
|
84
84
|
const isRecord = pt === 'record';
|
|
85
85
|
if (!isRecord)
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
import { useMemo } from 'react';
|
|
9
|
-
import { useReducedMotion } from './useReducedMotion';
|
|
9
|
+
import { useReducedMotion } from './useReducedMotion.js';
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
11
11
|
// Constants
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
import * as React from 'react';
|
|
9
|
-
import { usePageVariables } from './usePageVariables';
|
|
10
|
-
import { useAction } from '../context';
|
|
9
|
+
import { usePageVariables } from './usePageVariables.js';
|
|
10
|
+
import { useAction } from '../context/index.js';
|
|
11
11
|
/**
|
|
12
12
|
* PageVariableActionBridge — publishes the live page-variable snapshot into the
|
|
13
13
|
* shared ActionRunner context as `pageVariables`, so a submit action (`api` /
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { useState, useEffect, useCallback, useMemo, useContext } from 'react';
|
|
12
12
|
import { resolveDataSource } from '@object-ui/core';
|
|
13
|
-
import { SchemaRendererContext } from '../context/SchemaRendererContext';
|
|
13
|
+
import { SchemaRendererContext } from '../context/SchemaRendererContext.js';
|
|
14
14
|
// ---------------------------------------------------------------------------
|
|
15
15
|
// Hook
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
import { useState, useCallback, useMemo } from 'react';
|
|
9
|
-
import { useReducedMotion } from './useReducedMotion';
|
|
9
|
+
import { useReducedMotion } from './useReducedMotion.js';
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
11
11
|
// Helpers
|
|
12
12
|
// ---------------------------------------------------------------------------
|
package/dist/index.d.ts
CHANGED
|
@@ -5,13 +5,15 @@
|
|
|
5
5
|
* This source code is licensed under the MIT license found in the
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
|
-
export * from './SchemaRenderer';
|
|
9
|
-
export * from './
|
|
10
|
-
export * from './
|
|
11
|
-
export * from './
|
|
12
|
-
export * from './
|
|
13
|
-
export * from './
|
|
14
|
-
export
|
|
15
|
-
export
|
|
16
|
-
export
|
|
8
|
+
export * from './SchemaRenderer.js';
|
|
9
|
+
export * from './schema-input.js';
|
|
10
|
+
export * from './hooks/index.js';
|
|
11
|
+
export * from './context/index.js';
|
|
12
|
+
export * from './LazyPluginLoader.js';
|
|
13
|
+
export * from './spec-bridge/index.js';
|
|
14
|
+
export * from './data-invalidation.js';
|
|
15
|
+
export * from './element-data-source/ElementDataSourceGate.js';
|
|
16
|
+
export { resolveKeyedI18nLabel } from './utils/i18n.js';
|
|
17
|
+
export { extractWriteErrorMessage, isPermissionError, extractFieldErrors, classifyLoadError, declaredUserMessage } from './utils/error-message.js';
|
|
18
|
+
export type { WriteFieldError, LoadErrorKind } from './utils/error-message.js';
|
|
17
19
|
export { I18nProvider, useObjectTranslation, useSafeTranslate, useObjectLabel, useSafeFieldLabel, useI18nContext, createI18n, getDirection, getAvailableLanguages, formatDate, formatDateTime, formatRelativeTime, formatCurrency, formatNumber, type I18nConfig, type I18nProviderProps, type TranslationKeys, type DateFormatOptions, type CurrencyFormatOptions, type NumberFormatOptions, } from '@object-ui/i18n';
|
package/dist/index.js
CHANGED
|
@@ -5,16 +5,20 @@
|
|
|
5
5
|
* This source code is licensed under the MIT license found in the
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
|
-
export * from './SchemaRenderer';
|
|
9
|
-
export * from './
|
|
10
|
-
export * from './
|
|
11
|
-
export * from './
|
|
12
|
-
export * from './
|
|
13
|
-
export * from './
|
|
8
|
+
export * from './SchemaRenderer.js';
|
|
9
|
+
export * from './schema-input.js';
|
|
10
|
+
export * from './hooks/index.js'; // will be empty for now
|
|
11
|
+
export * from './context/index.js'; // will be empty for now
|
|
12
|
+
export * from './LazyPluginLoader.js';
|
|
13
|
+
export * from './spec-bridge/index.js';
|
|
14
|
+
export * from './data-invalidation.js';
|
|
15
|
+
// PageComponentSchema.dataSource — mapping the spec's per-element data binding
|
|
16
|
+
// onto the schema keys each object-bound block reads (objectstack#6953).
|
|
17
|
+
export * from './element-data-source/ElementDataSourceGate.js';
|
|
14
18
|
// i18n utilities
|
|
15
|
-
export {
|
|
19
|
+
export { resolveKeyedI18nLabel } from './utils/i18n.js';
|
|
16
20
|
// Write-error surfacing utilities (shared by drag-write plugins so a failed
|
|
17
21
|
// PATCH — e.g. an RLS 403 — is never silently swallowed).
|
|
18
|
-
export { extractWriteErrorMessage, isPermissionError, extractFieldErrors } from './utils/error-message';
|
|
22
|
+
export { extractWriteErrorMessage, isPermissionError, extractFieldErrors, classifyLoadError, declaredUserMessage } from './utils/error-message.js';
|
|
19
23
|
// Built-in i18n support
|
|
20
24
|
export { I18nProvider, useObjectTranslation, useSafeTranslate, useObjectLabel, useSafeFieldLabel, useI18nContext, createI18n, getDirection, getAvailableLanguages, formatDate, formatDateTime, formatRelativeTime, formatCurrency, formatNumber, } from '@object-ui/i18n';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
import type { BaseSchema } from '@object-ui/types';
|
|
9
|
+
import type { SchemaRendererProps } from './SchemaRenderer.js';
|
|
10
|
+
/**
|
|
11
|
+
* Narrow a loosely-typed metadata node onto {@link SchemaRendererProps.schema}.
|
|
12
|
+
*
|
|
13
|
+
* `@object-ui/types` declares `SchemaNode` as
|
|
14
|
+
* `BaseSchema | string | number | boolean | null | undefined`, and a lot of
|
|
15
|
+
* metadata plumbing (page regions, header-bar actions, detail tabs, view
|
|
16
|
+
* configs) is typed with it. `SchemaRenderer` deliberately does NOT declare the
|
|
17
|
+
* `number` / `boolean` members — nobody should be invited to author them — so
|
|
18
|
+
* forwarding such a value needs one honest step in between.
|
|
19
|
+
*
|
|
20
|
+
* This is that step, and it is a TOTAL function rather than a cast: it maps the
|
|
21
|
+
* two primitive members onto their text form, which is precisely what the
|
|
22
|
+
* renderer's own defensive branch does with them. So it changes no behaviour and
|
|
23
|
+
* tells no lie — the value a caller forwards renders identically whether or not
|
|
24
|
+
* it passes through here.
|
|
25
|
+
*
|
|
26
|
+
* The bridge is PERMANENT — not scaffolding awaiting a merge. The two competing
|
|
27
|
+
* repo-wide `SchemaNode` spellings ARE reconciled (objectui#4580, PR #4608):
|
|
28
|
+
* `@object-ui/core` stopped hand-declaring its own interface and now re-exports
|
|
29
|
+
* `@object-ui/types`' union, so one declaration is left to disagree with. That
|
|
30
|
+
* reconciliation resolved in favour of the UNION, while `SchemaRenderer`'s prop
|
|
31
|
+
* stays deliberately narrow (objectui#4548 ruling Q2 — it declares no `number`
|
|
32
|
+
* or `boolean`), so a `SchemaNode` is now LESS assignable to that prop than it
|
|
33
|
+
* was, not more. This step therefore bridges two intentionally different types,
|
|
34
|
+
* and it stays. ⛔ Do not "tidy" a call site back into a direct forward: the
|
|
35
|
+
* five `apps/site` sites that were forwarding directly when PR #4608 landed are
|
|
36
|
+
* what kept `Build Docs` red on `main` for ~5 hours — each one a TS2322 naming
|
|
37
|
+
* `number` against this function's return type, spelled
|
|
38
|
+
* `string | BaseSchema | null | undefined` in objectui#4617's logs — until
|
|
39
|
+
* PR #4621 routed all five through here. An earlier revision of this paragraph
|
|
40
|
+
* said the reconciliation was still pending and invited exactly that edit.
|
|
41
|
+
*/
|
|
42
|
+
export declare function toRenderableSchema(node: BaseSchema | string | number | boolean | null | undefined): SchemaRendererProps['schema'];
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ObjectUI
|
|
3
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Narrow a loosely-typed metadata node onto {@link SchemaRendererProps.schema}.
|
|
10
|
+
*
|
|
11
|
+
* `@object-ui/types` declares `SchemaNode` as
|
|
12
|
+
* `BaseSchema | string | number | boolean | null | undefined`, and a lot of
|
|
13
|
+
* metadata plumbing (page regions, header-bar actions, detail tabs, view
|
|
14
|
+
* configs) is typed with it. `SchemaRenderer` deliberately does NOT declare the
|
|
15
|
+
* `number` / `boolean` members — nobody should be invited to author them — so
|
|
16
|
+
* forwarding such a value needs one honest step in between.
|
|
17
|
+
*
|
|
18
|
+
* This is that step, and it is a TOTAL function rather than a cast: it maps the
|
|
19
|
+
* two primitive members onto their text form, which is precisely what the
|
|
20
|
+
* renderer's own defensive branch does with them. So it changes no behaviour and
|
|
21
|
+
* tells no lie — the value a caller forwards renders identically whether or not
|
|
22
|
+
* it passes through here.
|
|
23
|
+
*
|
|
24
|
+
* The bridge is PERMANENT — not scaffolding awaiting a merge. The two competing
|
|
25
|
+
* repo-wide `SchemaNode` spellings ARE reconciled (objectui#4580, PR #4608):
|
|
26
|
+
* `@object-ui/core` stopped hand-declaring its own interface and now re-exports
|
|
27
|
+
* `@object-ui/types`' union, so one declaration is left to disagree with. That
|
|
28
|
+
* reconciliation resolved in favour of the UNION, while `SchemaRenderer`'s prop
|
|
29
|
+
* stays deliberately narrow (objectui#4548 ruling Q2 — it declares no `number`
|
|
30
|
+
* or `boolean`), so a `SchemaNode` is now LESS assignable to that prop than it
|
|
31
|
+
* was, not more. This step therefore bridges two intentionally different types,
|
|
32
|
+
* and it stays. ⛔ Do not "tidy" a call site back into a direct forward: the
|
|
33
|
+
* five `apps/site` sites that were forwarding directly when PR #4608 landed are
|
|
34
|
+
* what kept `Build Docs` red on `main` for ~5 hours — each one a TS2322 naming
|
|
35
|
+
* `number` against this function's return type, spelled
|
|
36
|
+
* `string | BaseSchema | null | undefined` in objectui#4617's logs — until
|
|
37
|
+
* PR #4621 routed all five through here. An earlier revision of this paragraph
|
|
38
|
+
* said the reconciliation was still pending and invited exactly that edit.
|
|
39
|
+
*/
|
|
40
|
+
export function toRenderableSchema(node) {
|
|
41
|
+
return typeof node === 'number' || typeof node === 'boolean' ? String(node) : node;
|
|
42
|
+
}
|