@object-ui/react 17.1.0 → 17.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.
- package/CHANGELOG.md +199 -0
- package/dist/context/AppShellContext.js +51 -23
- package/dist/context/InlineEditContext.d.ts +43 -0
- package/dist/context/InlineEditContext.js +3 -2
- package/dist/hooks/useExpression.d.ts +34 -15
- package/dist/hooks/useExpression.js +30 -33
- package/dist/hooks/useNavigationOverlay.d.ts +26 -13
- package/dist/hooks/useOffline.d.ts +51 -33
- package/dist/hooks/useOffline.js +3 -3
- package/dist/hooks/usePerformance.d.ts +3 -3
- package/dist/hooks/usePerformance.js +2 -2
- package/dist/spec-input.d.ts +37 -0
- package/dist/spec-input.js +8 -0
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,204 @@
|
|
|
1
1
|
# @object-ui/react
|
|
2
2
|
|
|
3
|
+
## 17.3.0
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d915c47: Relation fields (`lookup` / `master_detail` / `user` / `tree`) are now usable in action and conditional-formatting predicates: they bind as the stored foreign key on every surface, and the fields a predicate reads are included in the query projection (#3501).
|
|
8
|
+
|
|
9
|
+
Before this, one predicate over one relation field had four different fates, decided by things its author does not control. `$expand` **replaces** the id in place with the whole related record, and a view expands exactly the relations it shows as COLUMNS — so `record.owner == "U1"` was **true** where the column was absent, **false** where it was displayed, and a **fault** where the field was neither displayed nor projected (a list's `$select` was built from its columns alone, and CEL treats an absent key as a fault, not as null). A fault is fail-CLOSED on the row kebab and the selection bar and fail-OPEN on the lenient paths, so the same authoring mistake hid the button from everyone on one surface and showed it to everyone on the next, with nothing on screen to point at either. The server, meanwhile, only ever sees the id — so client and server could not agree, which is the one thing ADR-0036 / ADR-0058 exist to guarantee.
|
|
10
|
+
|
|
11
|
+
Two changes close it. `toPredicateRecord` (new, `@object-ui/core`) collapses expanded relation values back to their ids when a record is bound for evaluation — driven by the object's own field types, not by sniffing for an `id` key, so a `json` field that happens to carry one is untouched. It is threaded through `evalRowPredicate` / `resolveConditionalFormatting` (via a new `fields` option), `useRowPredicate`, `partitionBulkRows`, and both `page:header` evaluators, with the object schema supplied by `ObjectGrid` / `ListView` / `ObjectKanban` / the record context. Kanban card formatting is threaded the same way, so a rule cannot match on the grid view of a list and silently never match on its board. Display is unaffected — a detail-page title still renders the related record's name, and the schema-only `kanban-ui` entry point (which has no object schema to offer) keeps using the payload verbatim. `collectPredicateFieldRefs` / `listViewPredicates` (new) harvest the `record.x` / `data.x` references out of a view's conditional formatting, row-action defs, bulk-action defs, promoted object actions and `userActions` overrides, and add them to `$select` — intersected with the object's declared fields plus the platform columns every object carries (`isProjectableField`), because an unknown key is not ignored by every backend. No `$expand` is added: a predicate wants the foreign key, which is what an unexpanded relation already is.
|
|
12
|
+
|
|
13
|
+
- 175bd79: `toPredicateInput` is now re-exported from `@object-ui/core` instead of being reimplemented in `@object-ui/react`. Behaviour is byte-for-byte identical — the renderer-side copy in `packages/react/src/hooks/useExpression.ts` had item-for-item the same semantics as the canonical `packages/core/src/evaluator/predicateInput.ts` (booleans short-circuit, bare strings and non-`cel` dialects become `${…}`, a `{ dialect: 'cel', source }` envelope survives so `useCondition` routes it to the canonical `@objectstack/formula` engine, empty/absent predicates become `undefined`), and every existing import path (`import { toPredicateInput } from '@object-ui/react'`) keeps working with an unchanged signature. What changes is that there is now ONE implementation rather than two held in step by a parity table: #3314 is the record of what two normalizations do when left alone — they drift, and the same `visible:` predicate reaches different verdicts depending on whether the action was surfaced by `ActionEngine.getActionsForLocation` or rendered standalone. The 14-shape normalization parity table degenerated once both of its columns called the same function, so it is replaced by a single identity assertion (`react`'s export IS `core`'s function object); the engine-path-vs-renderer-path verdict parity suite is untouched and still proves the two call paths agree (#3367).
|
|
14
|
+
- Updated dependencies [18cd432]
|
|
15
|
+
- Updated dependencies [d915c47]
|
|
16
|
+
- Updated dependencies [b71fc92]
|
|
17
|
+
- Updated dependencies [65516ba]
|
|
18
|
+
- Updated dependencies [94c5b7c]
|
|
19
|
+
- Updated dependencies [ca0fa8f]
|
|
20
|
+
- Updated dependencies [3889ffb]
|
|
21
|
+
- Updated dependencies [5781fb1]
|
|
22
|
+
- Updated dependencies [7e2406a]
|
|
23
|
+
- Updated dependencies [9e9e9a9]
|
|
24
|
+
- Updated dependencies [4eeb932]
|
|
25
|
+
- Updated dependencies [5c856ec]
|
|
26
|
+
- Updated dependencies [23018cc]
|
|
27
|
+
- Updated dependencies [68b6a28]
|
|
28
|
+
- Updated dependencies [0554e88]
|
|
29
|
+
- Updated dependencies [d915c47]
|
|
30
|
+
- Updated dependencies [f44d872]
|
|
31
|
+
- Updated dependencies [28b2e65]
|
|
32
|
+
- Updated dependencies [509104a]
|
|
33
|
+
- Updated dependencies [825bbe3]
|
|
34
|
+
- Updated dependencies [6195841]
|
|
35
|
+
- Updated dependencies [5dd0127]
|
|
36
|
+
- Updated dependencies [a415684]
|
|
37
|
+
- Updated dependencies [a4cff5b]
|
|
38
|
+
- Updated dependencies [5af2852]
|
|
39
|
+
- Updated dependencies [f833d3a]
|
|
40
|
+
- Updated dependencies [a6ec93d]
|
|
41
|
+
- Updated dependencies [2a9513d]
|
|
42
|
+
- Updated dependencies [d22ae31]
|
|
43
|
+
- Updated dependencies [c7ed4c3]
|
|
44
|
+
- Updated dependencies [2409e1d]
|
|
45
|
+
- Updated dependencies [789fe3e]
|
|
46
|
+
- @object-ui/core@17.3.0
|
|
47
|
+
- @object-ui/types@17.3.0
|
|
48
|
+
- @object-ui/i18n@17.3.0
|
|
49
|
+
- @object-ui/data-objectstack@17.3.0
|
|
50
|
+
|
|
51
|
+
## 17.2.0
|
|
52
|
+
|
|
53
|
+
### Minor Changes
|
|
54
|
+
|
|
55
|
+
- a889e31: A record's approval band now shows the quorum / per-group tally the server already computes.
|
|
56
|
+
|
|
57
|
+
The showcase's `showcase_committee_quorum` node declares `behavior: 'quorum'` with
|
|
58
|
+
`minApprovals: 2` over three approvers, and even ships a pre-rendered
|
|
59
|
+
`"Committee Sign-off (2 of 3)"` label; `showcase_expense_signoff` declares
|
|
60
|
+
`per_group` (会签) with named manager / finance groups. On the business record
|
|
61
|
+
the approval band rendered none of it — the lock badge, the recall button and
|
|
62
|
+
the approve/reject actions were all correct, but a two-of-three committee step
|
|
63
|
+
looked exactly like a one-approver step. An approver could not see whether their
|
|
64
|
+
own click finalized the node or was one of three, which is the single fact a
|
|
65
|
+
quorum node exists to express (objectstack#4478).
|
|
66
|
+
|
|
67
|
+
Nothing was wrong on the wire, and nothing here papers over the server. The
|
|
68
|
+
framework computes `decision_progress` — `{ behavior, got, need, groups? }`,
|
|
69
|
+
derived from the node's own `node_config_json` snapshot, so the count a client
|
|
70
|
+
shows is the count the engine will enforce. **It attaches that block in
|
|
71
|
+
`getRequest` only**: `listRequests` deliberately skips it, because the
|
|
72
|
+
`sys_approval_action` tally it costs is per row and a list read may return
|
|
73
|
+
hundreds. The record header's `useRecordApprovals` reads
|
|
74
|
+
`GET /approvals/requests?object=…&recordId=…` — the list route — so the
|
|
75
|
+
enrichment was never in the payload it had. The hook now follows up with one
|
|
76
|
+
single read for the ONE pending row and folds the result onto it; a failed or
|
|
77
|
+
mismatched follow-up leaves the row exactly as the list sent it, so a display-only
|
|
78
|
+
enrichment can never take the approval panel down and no tally is ever invented.
|
|
79
|
+
|
|
80
|
+
`InlineEditProvider` carries the block through as `approvalProgress`, and the
|
|
81
|
+
DetailView approval band renders it beside the existing badge: a labelled
|
|
82
|
+
`role="progressbar"` with one tick per required approval for `quorum` /
|
|
83
|
+
`unanimous`, and for `per_group` a chip per group marking which have signed
|
|
84
|
+
(`finance 1/1` ✓, `manager 0/1`). Group names come from the flow author's own
|
|
85
|
+
config, so they need no locale strings; the three new label keys are added to all
|
|
86
|
+
ten packs. `first_response` nodes carry no `decision_progress` and are unchanged —
|
|
87
|
+
one decision is the whole step there, and a "1 of 1" bar would be noise.
|
|
88
|
+
|
|
89
|
+
Scored `minor` rather than `patch`: this is new observable rendering plus a new
|
|
90
|
+
public `approvalProgress` prop / `ApprovalProgress` type on `@object-ui/react`,
|
|
91
|
+
not a behavior correction inside an existing surface.
|
|
92
|
+
|
|
93
|
+
- 09d30a4: Stop declaring 18 `@object-ui/auth` / `@object-ui/components` / `@object-ui/react`
|
|
94
|
+
symbols under names `@objectstack/spec` owns (objectui#3159, objectstack#4115
|
|
95
|
+
batch 5).
|
|
96
|
+
|
|
97
|
+
**Breaking for importers of all three packages** — six exported names changed,
|
|
98
|
+
because the spec exports the same name for a _different_ thing:
|
|
99
|
+
|
|
100
|
+
| package | was | now | what the spec's same-named export actually is |
|
|
101
|
+
| :----------- | :--------------------------- | :----------------------- | :----------------------------------------------------------------------------- |
|
|
102
|
+
| `auth` | `AuthSession` | `AuthClientSession` | the SERVER's session record (`{ id, userId, expiresAt: ISO string, token? }`) |
|
|
103
|
+
| `auth` | `AuthProviderConfig` | `AuthProviderOptions` | an OAuth/OIDC provider registration (`{ id, clientId, clientSecret, scope? }`) |
|
|
104
|
+
| `components` | `FilterCondition` | `FilterBuilderCondition` | the recursive ObjectQL predicate AST (`$and`/`$or`/`$not`) |
|
|
105
|
+
| `components` | `Field` | `FieldContainer` | an object FIELD's metadata and its builder namespace |
|
|
106
|
+
| `react` | `ConflictResolutionStrategy` | `ConflictResolution` | the metadata-MERGE policy (`error \| priority \| first-wins \| last-wins`) |
|
|
107
|
+
|
|
108
|
+
The `react` rename is the odd one out: the new name is the **spec's own** name
|
|
109
|
+
for the union that hook always used, so it is a re-export rather than a dialect.
|
|
110
|
+
|
|
111
|
+
Eleven more keep their names and are now **imported or derived from the spec**
|
|
112
|
+
instead of re-declared: `TenancyPosture`, `DelegableScope` (+`DelegableAdminScope`),
|
|
113
|
+
`AuthUser`, `ShareLinkPermission`, `ShareLinkAudience`, `ShareLink`, `SortItem`,
|
|
114
|
+
`OfflineStrategy`, `OfflineCacheConfig`, `OfflineSyncConfig`, `OfflineConfig`,
|
|
115
|
+
`NavigationConfig`.
|
|
116
|
+
|
|
117
|
+
**Three of the copies were losing information, not just duplicating it.**
|
|
118
|
+
|
|
119
|
+
- `AuthUser` never declared the spec's `positions` or `tenantId` — the
|
|
120
|
+
authorization inputs. Its `[key: string]: unknown` index signature meant the
|
|
121
|
+
omission was invisible at every call site _and_ to any structural comparison
|
|
122
|
+
(the objectstack#4075 mechanism). It now `extends` the spec principal, so the
|
|
123
|
+
display-only fields (`image`, `role`, `roles`, `emailVerified`) are the delta
|
|
124
|
+
and the spec's keys arrive on their own.
|
|
125
|
+
- `useNavigationOverlay`'s copy carried the note _"inline … to avoid importing
|
|
126
|
+
from `@object-ui/types` (which may not be a direct dependency of
|
|
127
|
+
`@object-ui/react`)"_. The vocabulary belongs to `@objectstack/spec`, which
|
|
128
|
+
**is** a direct dependency — the same expired "kept local to avoid a
|
|
129
|
+
dependency" comment objectui#3169 found in `@object-ui/app-shell`.
|
|
130
|
+
- `useOffline` and `usePerformance` both opened with _"Types aligned with
|
|
131
|
+
`@objectstack/spec` v2.0.7"_. The installed spec is 17.0.0-rc.1.
|
|
132
|
+
|
|
133
|
+
`ShareLink` derives from the spec row **minus `password_hash`** — omitted rather
|
|
134
|
+
than optional, because it is the credential itself and typing it in a browser
|
|
135
|
+
package is an invitation to render it. `password_protected` (the boolean the UI
|
|
136
|
+
needs in its place) is the one local addition.
|
|
137
|
+
|
|
138
|
+
The config types derive from each schema's **input** side, not `z.infer`.
|
|
139
|
+
`useOffline(config: OfflineConfig = {})` defaults to the empty object, which the
|
|
140
|
+
output type — every `.default()`ed key required — would reject outright.
|
|
141
|
+
|
|
142
|
+
`@objectstack/spec` moves from `devDependencies` to `dependencies` in
|
|
143
|
+
`@object-ui/components`: its public type surface now references the spec.
|
|
144
|
+
|
|
145
|
+
Scored `minor`, not `major`, per this repo's fixed-group rule — objectui's major
|
|
146
|
+
tracks `@objectstack`, so breaking changes of our own ship as minor with the
|
|
147
|
+
semantics spelled out above (see AGENTS.md §版本号策略). A `major` here would carry
|
|
148
|
+
all 39 packages of the fixed group to `18.0.0` and off objectstack's 17.x line.
|
|
149
|
+
|
|
150
|
+
### Patch Changes
|
|
151
|
+
|
|
152
|
+
- ea96284: `useMetadataItem` no longer spins forever outside a `<MetadataProvider>` — the "graceful fallback" was the thing that made those consumers impossible to mount.
|
|
153
|
+
|
|
154
|
+
`useMetadata()` built its no-provider fallback **inline on every call**, so outside a provider
|
|
155
|
+
every render produced a new `getItem`. `useMetadataItem` lists `getItem` in its effect deps and,
|
|
156
|
+
on the no-name path, called `setState({ item: null, loading: false, error: null })` with a fresh
|
|
157
|
+
object each run. New identity → effect re-runs → new state object → re-render → new identity:
|
|
158
|
+
an unbreakable loop, synchronous enough to hang inside `render()` rather than fail.
|
|
159
|
+
|
|
160
|
+
So the fallback documented as the graceful path for consumers mounted outside a provider —
|
|
161
|
+
"common in unit tests that only need to assert on rendering" — was precisely what made them
|
|
162
|
+
unmountable. `record:alert` and `record:quick_actions` both call `useMetadataItem`
|
|
163
|
+
unconditionally; each pinned a core and grew unbounded (8.6 GB before the first kill) on a
|
|
164
|
+
`render()` that never returned.
|
|
165
|
+
|
|
166
|
+
Two changes, at the cause and one layer in:
|
|
167
|
+
|
|
168
|
+
- The fallback is a frozen module-level singleton, so its identity is stable across renders.
|
|
169
|
+
- The clear-state path bails out when the state is already cleared, instead of installing an
|
|
170
|
+
equal-but-new object. That covers the same loop arriving by another route — any caller whose
|
|
171
|
+
context value is rebuilt per render, which this interface explicitly invites ("hand-rolled
|
|
172
|
+
context values in tests keep working").
|
|
173
|
+
|
|
174
|
+
Found by `apps/console/src/__tests__/record-block-record-reach.test.tsx` (objectui#3149), which
|
|
175
|
+
could not mount either block until this was fixed.
|
|
176
|
+
|
|
177
|
+
- Updated dependencies [4ae0ac4]
|
|
178
|
+
- Updated dependencies [696e3c1]
|
|
179
|
+
- Updated dependencies [bca45cc]
|
|
180
|
+
- Updated dependencies [a889e31]
|
|
181
|
+
- Updated dependencies [4bf612c]
|
|
182
|
+
- Updated dependencies [335041c]
|
|
183
|
+
- Updated dependencies [b414983]
|
|
184
|
+
- Updated dependencies [256f8cc]
|
|
185
|
+
- Updated dependencies [c5ccbd5]
|
|
186
|
+
- Updated dependencies [d9668a7]
|
|
187
|
+
- Updated dependencies [4b470b9]
|
|
188
|
+
- Updated dependencies [cb82705]
|
|
189
|
+
- Updated dependencies [f572849]
|
|
190
|
+
- Updated dependencies [d3584c6]
|
|
191
|
+
- Updated dependencies [a8ad6c0]
|
|
192
|
+
- Updated dependencies [444457c]
|
|
193
|
+
- Updated dependencies [850033c]
|
|
194
|
+
- Updated dependencies [022e4c3]
|
|
195
|
+
- Updated dependencies [009e25d]
|
|
196
|
+
- Updated dependencies [726b89c]
|
|
197
|
+
- @object-ui/types@17.2.0
|
|
198
|
+
- @object-ui/core@17.2.0
|
|
199
|
+
- @object-ui/i18n@17.2.0
|
|
200
|
+
- @object-ui/data-objectstack@17.2.0
|
|
201
|
+
|
|
3
202
|
## 17.1.0
|
|
4
203
|
|
|
5
204
|
### Minor Changes
|
|
@@ -7,30 +7,50 @@ export function useAdapter() {
|
|
|
7
7
|
return useContext(AdapterCtx);
|
|
8
8
|
}
|
|
9
9
|
export const MetadataCtx = createContext(null);
|
|
10
|
+
/** Shared frozen empty list for the fallback below — see {@link NO_METADATA_PROVIDER}. */
|
|
11
|
+
const NO_ITEMS = Object.freeze([]);
|
|
12
|
+
/**
|
|
13
|
+
* The no-provider fallback, as a MODULE-LEVEL SINGLETON.
|
|
14
|
+
*
|
|
15
|
+
* Identity matters here, not just shape. `useMetadata()` used to build this
|
|
16
|
+
* object inline on every call, so outside a `<MetadataProvider>` every render
|
|
17
|
+
* produced a new `getItem` — and `useMetadataItem` lists `getItem` in its
|
|
18
|
+
* effect deps and calls `setState({...})` with a fresh object on every run. New
|
|
19
|
+
* identity → effect re-runs → new state object → re-render → new identity: an
|
|
20
|
+
* unbreakable render loop, synchronous enough to hang inside `render()`.
|
|
21
|
+
*
|
|
22
|
+
* So the fallback documented as the graceful path for consumers mounted outside
|
|
23
|
+
* a provider was the one thing that made those consumers impossible to mount —
|
|
24
|
+
* `record:alert` and `record:quick_actions` both call `useMetadataItem`
|
|
25
|
+
* unconditionally, and both spin. Found by
|
|
26
|
+
* `apps/console/src/__tests__/record-block-record-reach.test.tsx`
|
|
27
|
+
* (objectui#3149), which could not mount them at all until this was frozen.
|
|
28
|
+
*
|
|
29
|
+
* Frozen so a consumer cannot mutate the shared fallback out from under every
|
|
30
|
+
* other consumer of it.
|
|
31
|
+
*/
|
|
32
|
+
const NO_METADATA_PROVIDER = Object.freeze({
|
|
33
|
+
apps: NO_ITEMS,
|
|
34
|
+
objects: NO_ITEMS,
|
|
35
|
+
dashboards: NO_ITEMS,
|
|
36
|
+
reports: NO_ITEMS,
|
|
37
|
+
pages: NO_ITEMS,
|
|
38
|
+
loading: false,
|
|
39
|
+
error: null,
|
|
40
|
+
refresh: async () => { },
|
|
41
|
+
invalidate: () => { },
|
|
42
|
+
ensureType: async () => [],
|
|
43
|
+
getItem: async () => null,
|
|
44
|
+
getItemsByType: () => [],
|
|
45
|
+
getTypeStatus: () => 'ready',
|
|
46
|
+
});
|
|
10
47
|
export function useMetadata() {
|
|
11
48
|
const ctx = useContext(MetadataCtx);
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
return {
|
|
18
|
-
apps: [],
|
|
19
|
-
objects: [],
|
|
20
|
-
dashboards: [],
|
|
21
|
-
reports: [],
|
|
22
|
-
pages: [],
|
|
23
|
-
loading: false,
|
|
24
|
-
error: null,
|
|
25
|
-
refresh: async () => { },
|
|
26
|
-
invalidate: () => { },
|
|
27
|
-
ensureType: async () => [],
|
|
28
|
-
getItem: async () => null,
|
|
29
|
-
getItemsByType: () => [],
|
|
30
|
-
getTypeStatus: () => 'ready',
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
return ctx;
|
|
49
|
+
// Graceful fallback: a consumer rendered outside a MetadataProvider (unit
|
|
50
|
+
// tests that only assert on rendering, standalone previews) gets an empty
|
|
51
|
+
// no-op implementation rather than a crash. Production code paths should
|
|
52
|
+
// always wrap in <MetadataProvider>.
|
|
53
|
+
return ctx ?? NO_METADATA_PROVIDER;
|
|
34
54
|
}
|
|
35
55
|
export function useMetadataItem(type, name) {
|
|
36
56
|
const { getItem } = useMetadata();
|
|
@@ -41,7 +61,15 @@ export function useMetadataItem(type, name) {
|
|
|
41
61
|
});
|
|
42
62
|
useEffect(() => {
|
|
43
63
|
if (!name) {
|
|
44
|
-
|
|
64
|
+
// Bail out when already cleared instead of installing a fresh object.
|
|
65
|
+
// Belt-and-braces against the loop the frozen fallback above fixes at the
|
|
66
|
+
// source: `getItem` is an effect dep, so ANY caller whose context value
|
|
67
|
+
// is rebuilt per render — a hand-rolled one in a test, which this
|
|
68
|
+
// interface explicitly invites — would otherwise re-run this effect,
|
|
69
|
+
// re-set an equal-but-new state object, and re-render forever.
|
|
70
|
+
setState((s) => (s.item === null && !s.loading && s.error === null
|
|
71
|
+
? s
|
|
72
|
+
: { item: null, loading: false, error: null }));
|
|
45
73
|
return;
|
|
46
74
|
}
|
|
47
75
|
let cancelled = false;
|
|
@@ -22,6 +22,34 @@
|
|
|
22
22
|
* context for the draft/editing flags and drives `saving`/`error`/`reset`.
|
|
23
23
|
*/
|
|
24
24
|
import React from 'react';
|
|
25
|
+
/**
|
|
26
|
+
* Server-computed decision aggregation of the record's pending approval node
|
|
27
|
+
* (framework#3266, `decision_progress` on `GET /approvals/requests/:id`).
|
|
28
|
+
*
|
|
29
|
+
* A multi-approver node does NOT finalize on the first decision: `unanimous`
|
|
30
|
+
* needs everyone, `quorum` needs `minApprovals` of the slate, `per_group` (会签)
|
|
31
|
+
* needs each named group to sign. Without the tally, an approver looking at the
|
|
32
|
+
* record cannot tell whether their own click completes the step or is one of
|
|
33
|
+
* three — the fact the whole node is about (objectstack#4478). The server does
|
|
34
|
+
* the counting so every client renders the same "2 of 3" the engine enforces.
|
|
35
|
+
*
|
|
36
|
+
* Absent for `first_response` nodes (one decision finalizes, so there is no
|
|
37
|
+
* progress to show) and for a backend that predates the enrichment.
|
|
38
|
+
*/
|
|
39
|
+
export interface ApprovalProgress {
|
|
40
|
+
behavior: 'unanimous' | 'quorum' | 'per_group';
|
|
41
|
+
/** Approvals recorded so far — satisfied GROUPS when `behavior` is `per_group`. */
|
|
42
|
+
got: number;
|
|
43
|
+
/** Approvals required to finalize — total GROUPS when `behavior` is `per_group`. */
|
|
44
|
+
need: number;
|
|
45
|
+
/** Per-group tally, `per_group` only. */
|
|
46
|
+
groups?: Array<{
|
|
47
|
+
group: string;
|
|
48
|
+
got: number;
|
|
49
|
+
need: number;
|
|
50
|
+
satisfied: boolean;
|
|
51
|
+
}>;
|
|
52
|
+
}
|
|
25
53
|
export interface InlineEditContextValue {
|
|
26
54
|
/** True while the record is in inline-edit mode. */
|
|
27
55
|
editing: boolean;
|
|
@@ -58,6 +86,15 @@ export interface InlineEditContextValue {
|
|
|
58
86
|
* still get a coherent band. Defaults to `false`.
|
|
59
87
|
*/
|
|
60
88
|
approvalPending: boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Quorum / per-group tally of the pending approval node, when it aggregates
|
|
91
|
+
* more than one decision (objectstack#4478). Threaded verbatim from the
|
|
92
|
+
* host's approvals read so the band renders the server's count instead of
|
|
93
|
+
* re-deriving the engine's tally rules. Undefined when no approval is
|
|
94
|
+
* running, when the node finalizes on the first response, or when the host
|
|
95
|
+
* doesn't resolve approvals at all.
|
|
96
|
+
*/
|
|
97
|
+
approvalProgress?: ApprovalProgress;
|
|
61
98
|
/**
|
|
62
99
|
* Human-readable reason for the approval lock, surfaced as the band's
|
|
63
100
|
* tooltip. Optional — consumers fall back to their own localized default
|
|
@@ -109,6 +146,12 @@ export interface InlineEditProviderProps {
|
|
|
109
146
|
* only know about the lock rendering exactly as before.
|
|
110
147
|
*/
|
|
111
148
|
approvalPending?: boolean;
|
|
149
|
+
/**
|
|
150
|
+
* The pending node's server-computed decision tally (objectstack#4478).
|
|
151
|
+
* Surfaced verbatim so the band can show "2 of 3" / per-group ticks. Omitted
|
|
152
|
+
* for `first_response` nodes and for hosts that don't read approvals.
|
|
153
|
+
*/
|
|
154
|
+
approvalProgress?: ApprovalProgress;
|
|
112
155
|
/** Optional human-readable lock reason, surfaced as the band tooltip. */
|
|
113
156
|
lockedReason?: string;
|
|
114
157
|
children: React.ReactNode;
|
|
@@ -24,7 +24,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
24
24
|
*/
|
|
25
25
|
import React from 'react';
|
|
26
26
|
const InlineEditContext = React.createContext(null);
|
|
27
|
-
export const InlineEditProvider = ({ canEdit = true, locked = false, approvalPending, lockedReason, children, }) => {
|
|
27
|
+
export const InlineEditProvider = ({ canEdit = true, locked = false, approvalPending, approvalProgress, lockedReason, children, }) => {
|
|
28
28
|
// A locked record always has an approval running; the converse is what
|
|
29
29
|
// #2902 added. Defaulting this way means an un-migrated host keeps its
|
|
30
30
|
// previous behavior with no code change.
|
|
@@ -62,6 +62,7 @@ export const InlineEditProvider = ({ canEdit = true, locked = false, approvalPen
|
|
|
62
62
|
canEdit,
|
|
63
63
|
locked,
|
|
64
64
|
approvalPending: pending,
|
|
65
|
+
approvalProgress,
|
|
65
66
|
lockedReason,
|
|
66
67
|
draft,
|
|
67
68
|
autoFocusField,
|
|
@@ -73,7 +74,7 @@ export const InlineEditProvider = ({ canEdit = true, locked = false, approvalPen
|
|
|
73
74
|
reset: teardown,
|
|
74
75
|
setSaving,
|
|
75
76
|
setError,
|
|
76
|
-
}), [editing, canEdit, locked, pending, lockedReason, draft, autoFocusField, saving, error, enter, setField, teardown]);
|
|
77
|
+
}), [editing, canEdit, locked, pending, approvalProgress, lockedReason, draft, autoFocusField, saving, error, enter, setField, teardown]);
|
|
77
78
|
return _jsx(InlineEditContext.Provider, { value: value, children: children });
|
|
78
79
|
};
|
|
79
80
|
/**
|
|
@@ -26,24 +26,36 @@ export declare function PredicateScopeProvider({ scope, children, }: {
|
|
|
26
26
|
export declare function usePredicateScope(): Record<string, any>;
|
|
27
27
|
/**
|
|
28
28
|
* Normalize a schema-supplied predicate (`visible` / `enabled` / `disabled` /
|
|
29
|
-
* `hidden`) into the
|
|
29
|
+
* `hidden`) into the form `useCondition` expects — the canonical helper to use
|
|
30
|
+
* in renderers so we never end up with `${[object Object]}` after JS
|
|
31
|
+
* template-literal interpolation.
|
|
30
32
|
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* compatible variable syntax (`record.x`, etc.).
|
|
38
|
-
* - `null` / `undefined` / empty → `undefined` (default visible/enabled).
|
|
33
|
+
* **Re-exported from `@object-ui/core`, not reimplemented here.** There is
|
|
34
|
+
* exactly ONE implementation — `packages/core/src/evaluator/predicateInput.ts`
|
|
35
|
+
* — shared by the engine path (`ActionEngine.getActionsForLocation`) and the
|
|
36
|
+
* renderer path (`action-button` / `action-menu` / `action-bar` / …). See that
|
|
37
|
+
* file for the accepted input shapes and for why a `cel` envelope must survive
|
|
38
|
+
* normalization (#2661 / #3314).
|
|
39
39
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
40
|
+
* ## Why a re-export and not a twin (#3367)
|
|
41
|
+
*
|
|
42
|
+
* This module used to carry an independent renderer-side implementation with
|
|
43
|
+
* item-for-item identical semantics, held in step with the canonical one by a
|
|
44
|
+
* 14-shape normalization parity table. That table was a *guardrail against
|
|
45
|
+
* drift*, not a single source of truth — and #3314 is the record of what two
|
|
46
|
+
* normalizations do when left alone: they drift, and the same `visible:`
|
|
47
|
+
* predicate reaches different verdicts depending on which path surfaced the
|
|
48
|
+
* action. A re-export cannot drift, so the parity suite now pins identity
|
|
49
|
+
* (`react`'s export IS `core`'s function) instead of enumerating shapes, and
|
|
50
|
+
* keeps pinning the two PATHS verdict-for-verdict.
|
|
51
|
+
*
|
|
52
|
+
* Routing hook code through the `@object-ui/core` barrel is not a new coupling:
|
|
53
|
+
* this module already imports `ExpressionEvaluator` / `evalRowPredicate` from
|
|
54
|
+
* it, and `@object-ui/core` is a declared dependency of `@object-ui/react`
|
|
55
|
+
* (the reverse direction is the forbidden one — core declares "Zero React
|
|
56
|
+
* dependencies").
|
|
42
57
|
*/
|
|
43
|
-
export
|
|
44
|
-
dialect: 'cel';
|
|
45
|
-
source: string;
|
|
46
|
-
} | undefined;
|
|
58
|
+
export { toPredicateInput } from '@object-ui/core';
|
|
47
59
|
/**
|
|
48
60
|
* Hook for evaluating expressions with dynamic context
|
|
49
61
|
*
|
|
@@ -85,4 +97,11 @@ export declare function useRowPredicate(pred: unknown, row: Record<string, any>
|
|
|
85
97
|
fallback?: boolean;
|
|
86
98
|
warnOnError?: boolean;
|
|
87
99
|
label?: string;
|
|
100
|
+
/**
|
|
101
|
+
* The object's field definitions. Supplying them binds a relation field as
|
|
102
|
+
* the stored foreign key instead of whatever `$expand` substituted for it
|
|
103
|
+
* on this surface, so `record.owner == os.user.id` reaches the server's
|
|
104
|
+
* verdict — see `toPredicateRecord`.
|
|
105
|
+
*/
|
|
106
|
+
fields?: unknown;
|
|
88
107
|
}): boolean;
|
|
@@ -40,41 +40,36 @@ export function usePredicateScope() {
|
|
|
40
40
|
}
|
|
41
41
|
/**
|
|
42
42
|
* Normalize a schema-supplied predicate (`visible` / `enabled` / `disabled` /
|
|
43
|
-
* `hidden`) into the
|
|
43
|
+
* `hidden`) into the form `useCondition` expects — the canonical helper to use
|
|
44
|
+
* in renderers so we never end up with `${[object Object]}` after JS
|
|
45
|
+
* template-literal interpolation.
|
|
44
46
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* compatible variable syntax (`record.x`, etc.).
|
|
52
|
-
* - `null` / `undefined` / empty → `undefined` (default visible/enabled).
|
|
47
|
+
* **Re-exported from `@object-ui/core`, not reimplemented here.** There is
|
|
48
|
+
* exactly ONE implementation — `packages/core/src/evaluator/predicateInput.ts`
|
|
49
|
+
* — shared by the engine path (`ActionEngine.getActionsForLocation`) and the
|
|
50
|
+
* renderer path (`action-button` / `action-menu` / `action-bar` / …). See that
|
|
51
|
+
* file for the accepted input shapes and for why a `cel` envelope must survive
|
|
52
|
+
* normalization (#2661 / #3314).
|
|
53
53
|
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
54
|
+
* ## Why a re-export and not a twin (#3367)
|
|
55
|
+
*
|
|
56
|
+
* This module used to carry an independent renderer-side implementation with
|
|
57
|
+
* item-for-item identical semantics, held in step with the canonical one by a
|
|
58
|
+
* 14-shape normalization parity table. That table was a *guardrail against
|
|
59
|
+
* drift*, not a single source of truth — and #3314 is the record of what two
|
|
60
|
+
* normalizations do when left alone: they drift, and the same `visible:`
|
|
61
|
+
* predicate reaches different verdicts depending on which path surfaced the
|
|
62
|
+
* action. A re-export cannot drift, so the parity suite now pins identity
|
|
63
|
+
* (`react`'s export IS `core`'s function) instead of enumerating shapes, and
|
|
64
|
+
* keeps pinning the two PATHS verdict-for-verdict.
|
|
65
|
+
*
|
|
66
|
+
* Routing hook code through the `@object-ui/core` barrel is not a new coupling:
|
|
67
|
+
* this module already imports `ExpressionEvaluator` / `evalRowPredicate` from
|
|
68
|
+
* it, and `@object-ui/core` is a declared dependency of `@object-ui/react`
|
|
69
|
+
* (the reverse direction is the forbidden one — core declares "Zero React
|
|
70
|
+
* dependencies").
|
|
56
71
|
*/
|
|
57
|
-
export
|
|
58
|
-
if (value === null || value === undefined || value === '')
|
|
59
|
-
return undefined;
|
|
60
|
-
if (typeof value === 'boolean')
|
|
61
|
-
return value;
|
|
62
|
-
if (typeof value === 'string')
|
|
63
|
-
return `\${${value}}`;
|
|
64
|
-
if (typeof value === 'object' && typeof value.source === 'string') {
|
|
65
|
-
const src = value.source;
|
|
66
|
-
if (!src)
|
|
67
|
-
return undefined;
|
|
68
|
-
// #2661 — preserve a CEL-dialect envelope so `useCondition` routes it to the
|
|
69
|
-
// canonical `@objectstack/formula` engine (identical verdict to the server),
|
|
70
|
-
// instead of collapsing it to a `${source}` string on the legacy JS path.
|
|
71
|
-
// Every other dialect (template / unset) keeps the legacy `${…}` behavior.
|
|
72
|
-
if (value.dialect === 'cel')
|
|
73
|
-
return { dialect: 'cel', source: src };
|
|
74
|
-
return `\${${src}}`;
|
|
75
|
-
}
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
72
|
+
export { toPredicateInput } from '@object-ui/core';
|
|
78
73
|
/**
|
|
79
74
|
* Hook for evaluating expressions with dynamic context
|
|
80
75
|
*
|
|
@@ -160,6 +155,7 @@ export function useCondition(condition, context = {}, options) {
|
|
|
160
155
|
export function useRowPredicate(pred, row, options) {
|
|
161
156
|
const scope = usePredicateScope();
|
|
162
157
|
const fallback = options?.fallback ?? true;
|
|
158
|
+
const fields = options?.fields;
|
|
163
159
|
return useMemo(() => {
|
|
164
160
|
// A boolean predicate short-circuits (no expression to evaluate).
|
|
165
161
|
if (typeof pred === 'boolean')
|
|
@@ -171,6 +167,7 @@ export function useRowPredicate(pred, row, options) {
|
|
|
171
167
|
scope,
|
|
172
168
|
warnOnError: options?.warnOnError,
|
|
173
169
|
label: options?.label,
|
|
170
|
+
fields: fields,
|
|
174
171
|
});
|
|
175
|
-
}, [pred, row, scope, fallback, options?.warnOnError, options?.label]);
|
|
172
|
+
}, [pred, row, scope, fallback, options?.warnOnError, options?.label, fields]);
|
|
176
173
|
}
|
|
@@ -5,21 +5,34 @@
|
|
|
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
10
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
11
|
+
* The spec's `NavigationConfigSchema`, authoring side, with `mode` required.
|
|
12
|
+
*
|
|
13
|
+
* This was a hand copy carrying the note "inline … to avoid importing from
|
|
14
|
+
* @object-ui/types (which may not be a direct dependency of @object-ui/react)".
|
|
15
|
+
* That reasoning is about the wrong package: the vocabulary belongs to
|
|
16
|
+
* `@objectstack/spec`, which IS a direct dependency of this one — the same
|
|
17
|
+
* expired "kept local to avoid a dependency" comment objectui#3169 found in
|
|
18
|
+
* `@object-ui/app-shell`, where the dependency had likewise been there all
|
|
19
|
+
* along. Check `package.json` before believing such a note (objectstack#4115).
|
|
20
|
+
*
|
|
21
|
+
* The ONE divergence: the spec defaults `mode`, so its authoring side makes it
|
|
22
|
+
* optional; this hook dispatches on `mode` and its callers always supply one,
|
|
23
|
+
* so it is required here. Every other key is the spec's, by reference. Pinned
|
|
24
|
+
* by `__tests__/offline-nav-performance-spec-parity.test.ts`.
|
|
25
|
+
*
|
|
26
|
+
* Two per-key notes the hand copy carried, kept here because a derived alias
|
|
27
|
+
* has no members to hang them on:
|
|
28
|
+
* - `size` is the coarse overlay bucket added by #2578; `resolveOverlayWidth`
|
|
29
|
+
* below maps it through {@link OVERLAY_SIZE_WIDTHS}.
|
|
30
|
+
* - `width` is DEPRECATED by #2578 in favour of `size`. It still wins when
|
|
31
|
+
* present, because app-shell pre-resolves `size` into it.
|
|
12
32
|
*/
|
|
13
|
-
export
|
|
14
|
-
mode:
|
|
15
|
-
|
|
16
|
-
preventNavigation?: boolean;
|
|
17
|
-
openNewTab?: boolean;
|
|
18
|
-
/** Spec `NavigationConfig.size` (#2578) — coarse overlay bucket. */
|
|
19
|
-
size?: 'auto' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
20
|
-
/** @deprecated [#2578 → `size`] explicit pixel/percent width. */
|
|
21
|
-
width?: string | number;
|
|
22
|
-
}
|
|
33
|
+
export type NavigationConfig = Omit<SpecAuthoredInput<typeof NavigationConfigSchema>, 'mode'> & {
|
|
34
|
+
mode: NonNullable<SpecAuthoredInput<typeof NavigationConfigSchema>['mode']>;
|
|
35
|
+
};
|
|
23
36
|
/**
|
|
24
37
|
* Resolve the overlay width from a NavigationConfig: an explicit `width` wins
|
|
25
38
|
* (app-shell pre-resolves `size` into it); otherwise a declared bucket maps
|
|
@@ -5,14 +5,25 @@
|
|
|
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';
|
|
8
10
|
/** Offline strategy determines how data is fetched when connectivity is limited. */
|
|
9
|
-
export type OfflineStrategy
|
|
10
|
-
/**
|
|
11
|
-
|
|
11
|
+
export type { OfflineStrategy };
|
|
12
|
+
/**
|
|
13
|
+
* Conflict resolution strategy for sync operations.
|
|
14
|
+
*
|
|
15
|
+
* Renamed from `ConflictResolutionStrategy` — that name belongs to a DIFFERENT
|
|
16
|
+
* spec export (`@objectstack/spec/api`: `error | priority | first-wins |
|
|
17
|
+
* last-wins`, the metadata-merge policy). The spec's name for the union this
|
|
18
|
+
* hook actually uses is `ConflictResolution`, so the fix was to take the spec's
|
|
19
|
+
* own name and its binding at once, the same move `FieldGroup` →
|
|
20
|
+
* `ObjectFieldGroup` made in objectui#3169.
|
|
21
|
+
*/
|
|
22
|
+
export type { ConflictResolution };
|
|
12
23
|
/** Persist storage backend. */
|
|
13
|
-
export type PersistStorageType =
|
|
24
|
+
export type PersistStorageType = PersistStorage;
|
|
14
25
|
/** Eviction policy for cache management. */
|
|
15
|
-
export type EvictionPolicyType =
|
|
26
|
+
export type EvictionPolicyType = EvictionPolicy;
|
|
16
27
|
/** Sync state of the offline system. */
|
|
17
28
|
export type SyncState = 'idle' | 'syncing' | 'error' | 'offline';
|
|
18
29
|
/** A queued mutation waiting to be synced. */
|
|
@@ -23,31 +34,38 @@ export interface QueuedMutation {
|
|
|
23
34
|
resource: string;
|
|
24
35
|
data?: Record<string, unknown>;
|
|
25
36
|
}
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Cache configuration — the AUTHORING side of the spec's
|
|
39
|
+
* `OfflineCacheConfigSchema`.
|
|
40
|
+
*
|
|
41
|
+
* `persistStorage` and `evictionPolicy` carry `.default()`s, so they are
|
|
42
|
+
* optional to write and present after a parse. This hook is handed what an app
|
|
43
|
+
* author wrote, hence the input side; see {@link SpecAuthoredInput}.
|
|
44
|
+
*/
|
|
45
|
+
export type OfflineCacheConfig = SpecAuthoredInput<typeof OfflineCacheConfigSchema>;
|
|
46
|
+
/**
|
|
47
|
+
* Sync configuration — the AUTHORING side of the spec's `SyncConfigSchema`.
|
|
48
|
+
*
|
|
49
|
+
* `strategy` and `conflictResolution` carry `.default()`s, so the output type
|
|
50
|
+
* makes them required; this hook is handed what an app author wrote.
|
|
51
|
+
*/
|
|
52
|
+
export type OfflineSyncConfig = SpecAuthoredInput<typeof SyncConfigSchema>;
|
|
53
|
+
/**
|
|
54
|
+
* Top-level offline configuration — the AUTHORING side of the spec's
|
|
55
|
+
* `OfflineConfigSchema`.
|
|
56
|
+
*
|
|
57
|
+
* This name stays with `@object-ui/react` (objectui#3156 / objectui#3159): the
|
|
58
|
+
* `OfflineConfig` that used to sit in `@object-ui/types` was a service-worker
|
|
59
|
+
* ROUTE cache and has been renamed `PWAOfflineConfig`, so there is no
|
|
60
|
+
* cross-package clash left — this hook's config is the spec's concept, key for
|
|
61
|
+
* key, and takes the spec's binding.
|
|
62
|
+
*
|
|
63
|
+
* Input side, not `z.infer`, and here the reason is visible in the signature:
|
|
64
|
+
* `useOffline(config: OfflineConfig = {})` defaults to the empty object, which
|
|
65
|
+
* the output type — where `enabled`, `strategy` and `offlineIndicator` are all
|
|
66
|
+
* required once their `.default()`s have run — would reject outright.
|
|
67
|
+
*/
|
|
68
|
+
export type OfflineConfig = SpecAuthoredInput<typeof OfflineConfigSchema>;
|
|
51
69
|
/** Result returned by the useOffline hook. */
|
|
52
70
|
export interface OfflineResult {
|
|
53
71
|
/** Whether the browser is currently online. */
|
|
@@ -72,9 +90,9 @@ export interface OfflineResult {
|
|
|
72
90
|
offlineMessage: string;
|
|
73
91
|
}
|
|
74
92
|
/**
|
|
75
|
-
* Hook for offline mode detection and sync queue management.
|
|
76
|
-
*
|
|
77
|
-
*
|
|
93
|
+
* Hook for offline mode detection and sync queue management. Its config types
|
|
94
|
+
* ARE the spec's `OfflineConfigSchema` / `SyncConfigSchema` / `ConflictResolution`
|
|
95
|
+
* (authoring side) — see {@link OfflineConfig}.
|
|
78
96
|
*
|
|
79
97
|
* @example
|
|
80
98
|
* ```tsx
|
package/dist/hooks/useOffline.js
CHANGED
|
@@ -66,9 +66,9 @@ const DEFAULTS = {
|
|
|
66
66
|
queueMaxSize: 100,
|
|
67
67
|
};
|
|
68
68
|
/**
|
|
69
|
-
* Hook for offline mode detection and sync queue management.
|
|
70
|
-
*
|
|
71
|
-
*
|
|
69
|
+
* Hook for offline mode detection and sync queue management. Its config types
|
|
70
|
+
* ARE the spec's `OfflineConfigSchema` / `SyncConfigSchema` / `ConflictResolution`
|
|
71
|
+
* (authoring side) — see {@link OfflineConfig}.
|
|
72
72
|
*
|
|
73
73
|
* @example
|
|
74
74
|
* ```tsx
|
|
@@ -13,7 +13,7 @@ export interface VirtualScrollConfig {
|
|
|
13
13
|
itemHeight?: number;
|
|
14
14
|
overscan?: number;
|
|
15
15
|
}
|
|
16
|
-
/** Performance configuration
|
|
16
|
+
/** Performance configuration. */
|
|
17
17
|
export interface PerformanceConfig {
|
|
18
18
|
/** Whether to lazy-load components/data. */
|
|
19
19
|
lazyLoad?: boolean;
|
|
@@ -55,8 +55,8 @@ export interface PerformanceResult {
|
|
|
55
55
|
debounce: <T extends (...args: unknown[]) => void>(fn: T) => T;
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
|
-
* Hook for performance monitoring and configuration
|
|
59
|
-
* PerformanceConfigSchema
|
|
58
|
+
* Hook for performance monitoring and configuration. Its config type IS the
|
|
59
|
+
* spec's `PerformanceConfigSchema` (authoring side) — see {@link PerformanceConfig}.
|
|
60
60
|
*
|
|
61
61
|
* Provides resolved config values, Web Vitals metrics, and utility functions
|
|
62
62
|
* (debounce, render marking) for performance-aware components.
|
|
@@ -52,8 +52,8 @@ function getLCP() {
|
|
|
52
52
|
// usePerformance hook
|
|
53
53
|
// ---------------------------------------------------------------------------
|
|
54
54
|
/**
|
|
55
|
-
* Hook for performance monitoring and configuration
|
|
56
|
-
* PerformanceConfigSchema
|
|
55
|
+
* Hook for performance monitoring and configuration. Its config type IS the
|
|
56
|
+
* spec's `PerformanceConfigSchema` (authoring side) — see {@link PerformanceConfig}.
|
|
57
57
|
*
|
|
58
58
|
* Provides resolved config values, Web Vitals metrics, and utility functions
|
|
59
59
|
* (debounce, render marking) for performance-aware components.
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
* The AUTHORING ("input") type of a `@objectstack/spec` zod schema.
|
|
10
|
+
*
|
|
11
|
+
* ## Why not `z.infer`
|
|
12
|
+
*
|
|
13
|
+
* `z.infer` is the OUTPUT type — what `.parse()` returns, with every
|
|
14
|
+
* `.default()` already applied and therefore REQUIRED. The hooks in this
|
|
15
|
+
* package are handed configuration an app author wrote by hand, so the input
|
|
16
|
+
* side is the one that describes what they may legally omit. Deriving a config
|
|
17
|
+
* type from `z.infer` turns every defaulted key into a required one and makes
|
|
18
|
+
* the schema's own defaults unusable — the `_input`/`_output` trap the spec
|
|
19
|
+
* symbol guard's header warns about, and the one that already bit
|
|
20
|
+
* `ObjectFieldGroup` in objectui#3169.
|
|
21
|
+
*
|
|
22
|
+
* Rule of thumb: is this module WRITING the metadata, or READING what has
|
|
23
|
+
* already been parsed? Writing → input. Reading → `z.infer`.
|
|
24
|
+
*
|
|
25
|
+
* ## Why it reads `_zod`, not `z.input`
|
|
26
|
+
*
|
|
27
|
+
* `z.input<typeof S>` needs a `zod` import, and `@object-ui/react` deliberately
|
|
28
|
+
* does not depend on zod — it renders plain objects and never parses. The
|
|
29
|
+
* schema's own type already carries both sides in `_zod`, so the input type is
|
|
30
|
+
* reachable through the spec's binding alone, with no new dependency and no
|
|
31
|
+
* copy of the shape.
|
|
32
|
+
*/
|
|
33
|
+
export type SpecAuthoredInput<S extends {
|
|
34
|
+
_zod: {
|
|
35
|
+
input: unknown;
|
|
36
|
+
};
|
|
37
|
+
}> = S['_zod']['input'];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@object-ui/react",
|
|
3
|
-
"version": "17.
|
|
3
|
+
"version": "17.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "React bindings and SchemaRenderer component for Object UI",
|
|
@@ -24,20 +24,20 @@
|
|
|
24
24
|
}
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@objectstack/spec": "^17.0.0-rc.
|
|
27
|
+
"@objectstack/spec": "^17.0.0-rc.2",
|
|
28
28
|
"react-hook-form": "^7.83.0",
|
|
29
|
-
"@object-ui/core": "17.
|
|
30
|
-
"@object-ui/data-objectstack": "17.
|
|
31
|
-
"@object-ui/i18n": "17.
|
|
32
|
-
"@object-ui/types": "17.
|
|
29
|
+
"@object-ui/core": "17.3.0",
|
|
30
|
+
"@object-ui/data-objectstack": "17.3.0",
|
|
31
|
+
"@object-ui/i18n": "17.3.0",
|
|
32
|
+
"@object-ui/types": "17.3.0"
|
|
33
33
|
},
|
|
34
34
|
"peerDependencies": {
|
|
35
35
|
"react": "^18.0.0 || ^19.0.0",
|
|
36
36
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@types/react": "19.2.
|
|
40
|
-
"@types/react-dom": "19.2.
|
|
39
|
+
"@types/react": "19.2.18",
|
|
40
|
+
"@types/react-dom": "19.2.4",
|
|
41
41
|
"typescript": "^6.0.3"
|
|
42
42
|
},
|
|
43
43
|
"keywords": [
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"scripts": {
|
|
66
66
|
"build": "tsc",
|
|
67
67
|
"test": "vitest run",
|
|
68
|
-
"type-check": "tsc --noEmit",
|
|
68
|
+
"type-check": "tsc --noEmit && tsc -p tsconfig.typetests.json",
|
|
69
69
|
"lint": "eslint ."
|
|
70
70
|
}
|
|
71
71
|
}
|