@object-ui/react 17.3.0 → 17.5.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 +908 -0
- package/README.md +39 -13
- package/dist/SchemaRenderer.d.ts +78 -5
- package/dist/SchemaRenderer.js +139 -13
- package/dist/context/AppShellContext.d.ts +21 -2
- package/dist/context/RecordContext.d.ts +0 -4
- package/dist/context/RecordContext.js +0 -2
- package/dist/context/RelatedRecordActionsContext.d.ts +29 -0
- package/dist/context/RelatedRecordActionsContext.js +6 -0
- package/dist/element-data-source/ElementDataSourceGate.d.ts +199 -0
- package/dist/element-data-source/ElementDataSourceGate.js +195 -0
- package/dist/hooks/index.d.ts +3 -0
- package/dist/hooks/index.js +10 -0
- package/dist/hooks/useActionTextLocalizer.d.ts +69 -0
- package/dist/hooks/useActionTextLocalizer.js +91 -0
- package/dist/hooks/useDatasetDimensionLabels.d.ts +104 -0
- package/dist/hooks/useDatasetDimensionLabels.js +154 -0
- package/dist/hooks/useElementDataSource.d.ts +68 -0
- package/dist/hooks/useElementDataSource.js +138 -0
- package/dist/hooks/useExpression.d.ts +58 -0
- package/dist/hooks/useExpression.js +65 -0
- package/dist/hooks/useNavigationOverlay.d.ts +58 -10
- package/dist/hooks/useOffline.d.ts +62 -36
- package/dist/hooks/useOffline.js +2 -2
- package/dist/hooks/usePageAssignment.js +4 -4
- package/dist/index.d.ts +3 -1
- package/dist/index.js +5 -1
- package/dist/schema-input.d.ts +31 -0
- package/dist/schema-input.js +31 -0
- package/dist/spec-bridge/SpecBridge.d.ts +11 -5
- package/dist/spec-bridge/SpecBridge.js +7 -1
- package/dist/spec-bridge/bridges/list-view.js +106 -14
- package/dist/spec-bridge/types.d.ts +24 -3
- package/dist/utils/i18n.d.ts +43 -8
- package/dist/utils/i18n.js +42 -4
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -121,6 +121,43 @@ function MyComponent() {
|
|
|
121
121
|
}
|
|
122
122
|
```
|
|
123
123
|
|
|
124
|
+
### useElementDataSourceSchema / ElementDataSourceGate
|
|
125
|
+
|
|
126
|
+
Consume `PageComponentSchema.dataSource` — the spec's per-element data binding
|
|
127
|
+
(`{ object, view?, filter?, sort?, limit? }`) — in a block that has its own key
|
|
128
|
+
names. `useElementDataSource` resolves the binding (fetching the object's saved
|
|
129
|
+
views so `view` can be matched); these two apply the composed result to the
|
|
130
|
+
block's schema and render the two non-final states.
|
|
131
|
+
|
|
132
|
+
```tsx
|
|
133
|
+
import { ElementDataSourceGate } from '@object-ui/react'
|
|
134
|
+
|
|
135
|
+
// `mapping` names ONLY the keys this block reads. A composed value written onto
|
|
136
|
+
// a key the block ignores would be accepted and silently dropped — the defect
|
|
137
|
+
// the binding exists to remove.
|
|
138
|
+
const OBJECT_GRID_BINDING = {
|
|
139
|
+
columns: true, // the view's FIELD list may fill `schema.columns`
|
|
140
|
+
filter: true, // AND-combined, never replaced ("additional criteria")
|
|
141
|
+
sort: true,
|
|
142
|
+
limit: 'pagination.pageSize' as const,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const ObjectGridRenderer = ({ schema, ...props }) => (
|
|
146
|
+
<ElementDataSourceGate schema={schema} mapping={OBJECT_GRID_BINDING} testId="object-grid">
|
|
147
|
+
{(bound) => <ObjectGrid schema={bound} {...props} />}
|
|
148
|
+
</ElementDataSourceGate>
|
|
149
|
+
)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`object` lands on `objectName` by default (pass `object: 'apiName'` for another
|
|
153
|
+
key, or `object: false` for a block that reads the composed binding itself).
|
|
154
|
+
Precedence: binding keys beat the component's own, view-sourced values are only a
|
|
155
|
+
baseline the component's own key overrides, and `filter` AND-combines all three.
|
|
156
|
+
A `view` name that does not resolve renders a configuration error rather than
|
|
157
|
+
falling back to the object's full scope. Use `useElementDataSourceSchema` (plus
|
|
158
|
+
the exported `ElementDataSourceErrorPanel` / `ElementDataSourceLoadingPanel`) when
|
|
159
|
+
a block cannot be wrapped — a renderer whose hooks must run before the panels.
|
|
160
|
+
|
|
124
161
|
### useRegistry
|
|
125
162
|
|
|
126
163
|
Access the component registry:
|
|
@@ -225,22 +262,11 @@ leaves the surface on its own anchor), `visibleNotificationStack` (`maxVisible`
|
|
|
225
262
|
|
|
226
263
|
## API Reference
|
|
227
264
|
|
|
228
|
-
See [full documentation](https://objectui.org/
|
|
229
|
-
|
|
230
|
-
<!-- release-metadata:v3.3.0 -->
|
|
231
|
-
|
|
232
|
-
## Compatibility
|
|
233
|
-
|
|
234
|
-
- **React:** 18.x or 19.x
|
|
235
|
-
- **Node.js:** ≥ 18
|
|
236
|
-
- **TypeScript:** ≥ 5.0 (strict mode)
|
|
237
|
-
- **`@objectstack/spec`:** ^3.3.0
|
|
238
|
-
- **`@objectstack/client`:** ^3.3.0
|
|
239
|
-
- **Tailwind CSS:** ≥ 3.4 (for packages with UI)
|
|
265
|
+
See [full documentation](https://objectui.org/docs/core/schema-renderer) for detailed API reference.
|
|
240
266
|
|
|
241
267
|
## Links
|
|
242
268
|
|
|
243
|
-
- 📚 [Documentation](https://www.objectui.org/docs/
|
|
269
|
+
- 📚 [Documentation](https://www.objectui.org/docs/core/schema-renderer)
|
|
244
270
|
- 📦 [npm package](https://www.npmjs.com/package/@object-ui/react)
|
|
245
271
|
- 📝 [Changelog](./CHANGELOG.md)
|
|
246
272
|
- 🐛 [Report an issue](https://github.com/objectstack-ai/objectui/issues)
|
package/dist/SchemaRenderer.d.ts
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
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 React, { Component } from 'react';
|
|
9
|
-
import {
|
|
8
|
+
import React, { Component, type ForwardRefExoticComponent, type RefAttributes } from 'react';
|
|
9
|
+
import type { BaseSchema } from '@object-ui/types';
|
|
10
10
|
/**
|
|
11
11
|
* Per-component Error Boundary for SchemaRenderer.
|
|
12
12
|
* Catches render errors in individual components, preventing one broken
|
|
@@ -30,7 +30,80 @@ export declare class SchemaErrorBoundary extends Component<{
|
|
|
30
30
|
handleRetry: () => void;
|
|
31
31
|
render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | React.JSX.Element | null | undefined;
|
|
32
32
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
/**
|
|
34
|
+
* The props `SchemaRenderer` DECLARES and reads itself (objectui#4548).
|
|
35
|
+
*
|
|
36
|
+
* ## Why `schema` is spelled as this union and not as a `SchemaNode`
|
|
37
|
+
*
|
|
38
|
+
* The repo carries two competing `SchemaNode` types: `@object-ui/core`'s
|
|
39
|
+
* interface (which requires `type: string`) and `@object-ui/types`' union
|
|
40
|
+
* (`BaseSchema | string | number | boolean | null | undefined`). This component
|
|
41
|
+
* matched NEITHER. It declared core's — narrower than what it accepts, so every
|
|
42
|
+
* caller holding the types union was wrong and could not be told — while its
|
|
43
|
+
* runtime returns early for strings and nullish, which core's interface forbids.
|
|
44
|
+
* The erasure below hid the mismatch completely: with props collapsed to an
|
|
45
|
+
* index signature, `schema` resolved to `any` and no call site was ever checked.
|
|
46
|
+
*
|
|
47
|
+
* So the contract is STATED HERE, by this component, as what it actually
|
|
48
|
+
* handles: an object schema, a bare string (rendered as text), or nothing at
|
|
49
|
+
* all. `number` / `boolean` are deliberately excluded — the runtime tolerates
|
|
50
|
+
* them defensively (see the primitive guard in the evaluation memo) but no
|
|
51
|
+
* author should be invited to pass them. Reconciling the two repo-wide
|
|
52
|
+
* `SchemaNode` spellings is a separate concern and deliberately not done here.
|
|
53
|
+
*/
|
|
54
|
+
export interface SchemaRendererProps {
|
|
55
|
+
schema: BaseSchema | string | null | undefined;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The open forwarding surface, named once so it reads as the decision it is.
|
|
59
|
+
*
|
|
60
|
+
* `SchemaRenderer` passes every prop it does not itself read straight through to
|
|
61
|
+
* the component the schema names, which is resolved at RUNTIME from a
|
|
62
|
+
* plugin-extensible registry — so the set of valid keys is not knowable here,
|
|
63
|
+
* and `packages/react/README.md` documents callers relying on it. The `any` is
|
|
64
|
+
* the point: this is a pass-through channel, not a typed prop.
|
|
65
|
+
*/
|
|
66
|
+
type ForwardedProps = Record<string, any>;
|
|
67
|
+
/**
|
|
68
|
+
* The renderer loop.
|
|
69
|
+
*
|
|
70
|
+
* ## The explicit type annotation is the contract, and it is deliberate
|
|
71
|
+
*
|
|
72
|
+
* `forwardRef< T, P >` routes `P` through React's `PropsWithoutRef`:
|
|
73
|
+
*
|
|
74
|
+
* Props extends any ? ('ref' extends keyof Props ? Omit< Props, 'ref' > : Props) : Props
|
|
75
|
+
*
|
|
76
|
+
* A string index signature on `P` puts `string` into `keyof Props`, so
|
|
77
|
+
* `'ref' extends keyof Props` is ALWAYS true, the `Omit` branch always runs, and
|
|
78
|
+
* `Omit` over a type carrying a string index signature keeps ONLY the index
|
|
79
|
+
* signature. Every declared prop is erased — on both sides. This component used
|
|
80
|
+
* to hand `forwardRef` a props type of `{ schema: SchemaNode } & Record< string,
|
|
81
|
+
* any >`, so its own `schema` resolved to `any` at every one of the ~376 call
|
|
82
|
+
* sites in this repo, and was not even REQUIRED: `< SchemaRenderer / >` with no
|
|
83
|
+
* schema at all type-checked (objectui#4548, measured).
|
|
84
|
+
*
|
|
85
|
+
* The fix is NOT to close the surface. This is the renderer loop: it forwards
|
|
86
|
+
* every prop it does not read to the component the schema names, resolved at
|
|
87
|
+
* RUNTIME from a plugin-extensible `ComponentRegistry`. That forwarding is a
|
|
88
|
+
* documented, load-bearing feature — `packages/react/README.md` shows
|
|
89
|
+
* `< SchemaRenderer schema={formSchema} onSubmit={handleSubmit} / >`, and
|
|
90
|
+
* `@object-ui/components`' form renderer consumes that `onSubmit` as a React
|
|
91
|
+
* prop. A closed props type would state a FALSE contract and would force every
|
|
92
|
+
* leaf plugin's props into this package to stay usable.
|
|
93
|
+
*
|
|
94
|
+
* So the two halves are separated deliberately:
|
|
95
|
+
*
|
|
96
|
+
* * the `forwardRef` TYPE ARGUMENT is the honest `SchemaRendererProps`, with
|
|
97
|
+
* no index signature — nothing for `PropsWithoutRef` to collapse, so
|
|
98
|
+
* `schema` survives to the call site typed and required; and
|
|
99
|
+
* * the open forwarding surface is stated ONCE, here, in this export
|
|
100
|
+
* annotation. Because the annotation is applied to the already-built
|
|
101
|
+
* component, `PropsWithoutRef` never runs over it, so `Record< string, any >`
|
|
102
|
+
* widens the surface WITHOUT erasing anything.
|
|
103
|
+
*
|
|
104
|
+
* The repo-wide guard (`scripts/__tests__/forwardref-props-erasure.guard.test.ts`)
|
|
105
|
+
* judges the TYPE ARGUMENT only, for exactly this reason: an index signature
|
|
106
|
+
* there is an accidental eraser, whereas one here is a stated contract.
|
|
107
|
+
*/
|
|
108
|
+
export declare const SchemaRenderer: ForwardRefExoticComponent<SchemaRendererProps & ForwardedProps & RefAttributes<unknown>>;
|
|
36
109
|
export {};
|
package/dist/SchemaRenderer.js
CHANGED
|
@@ -7,11 +7,11 @@ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-run
|
|
|
7
7
|
* LICENSE file in the root directory of this source tree.
|
|
8
8
|
*/
|
|
9
9
|
import React, { forwardRef, useContext, useMemo, useEffect, useReducer, useState, Component } from 'react';
|
|
10
|
-
import { ComponentRegistry, ExpressionEvaluator, isObjectUIError, ERROR_CODES, debugLog, debugTime, debugTimeEnd, DebugCollector, validateSchema, hasResponsiveStyles, scopeClassFor, compileScopedStyles, } from '@object-ui/core';
|
|
10
|
+
import { ComponentRegistry, ExpressionEvaluator, isObjectUIError, ERROR_CODES, debugLog, debugTime, debugTimeEnd, DebugCollector, validateSchema, hasDeclaredPredicate, hasResponsiveStyles, scopeClassFor, compileScopedStyles, } from '@object-ui/core';
|
|
11
11
|
import { SchemaRendererContext } from './context/SchemaRendererContext';
|
|
12
12
|
import { usePredicateScope } from './hooks/useExpression';
|
|
13
13
|
import { usePageVariables } from './hooks/usePageVariables';
|
|
14
|
-
import {
|
|
14
|
+
import { resolveKeyedI18nLabel } from './utils/i18n';
|
|
15
15
|
/**
|
|
16
16
|
* Dev-mode schema validation.
|
|
17
17
|
*
|
|
@@ -84,7 +84,7 @@ function validateSchemaOnce(schema) {
|
|
|
84
84
|
function resolveAriaProps(schema) {
|
|
85
85
|
const aria = {};
|
|
86
86
|
if (schema.ariaLabel) {
|
|
87
|
-
aria['aria-label'] =
|
|
87
|
+
aria['aria-label'] = resolveKeyedI18nLabel(schema.ariaLabel);
|
|
88
88
|
}
|
|
89
89
|
if (schema.ariaDescribedBy) {
|
|
90
90
|
aria['aria-describedby'] = schema.ariaDescribedBy;
|
|
@@ -131,7 +131,7 @@ export class SchemaErrorBoundary extends Component {
|
|
|
131
131
|
const error = this.state.error;
|
|
132
132
|
const isDev = globalThis.process?.env?.NODE_ENV !== 'production';
|
|
133
133
|
const objuiError = isObjectUIError(error) ? error : null;
|
|
134
|
-
return (_jsxs("div", { className: "p-4 border border-orange-400 rounded bg-orange-50 text-orange-700 my-2", role: "alert", children: [_jsxs("p", { className: "font-medium", children: ["Component", this.props.componentType ? ` "${this.props.componentType}"` : '', " failed to render"] }), _jsx("p", { className: "text-sm mt-1", children: error.message }), isDev && objuiError?.code && (_jsxs("p", { className: "text-xs mt-1 text-orange-500", children: ["Error code: ", objuiError.code, objuiError.details?.suggestion ? (_jsxs("span", { className: "block mt-0.5", children: ["\uD83D\uDCA1 ", String(objuiError.details.suggestion)] })) : null] })), _jsx("button", { onClick: this.handleRetry, className: "mt-2 text-sm underline hover:no-underline", children: "Retry" })] }));
|
|
134
|
+
return (_jsxs("div", { className: "p-4 border border-orange-400 rounded bg-orange-50 text-orange-700 my-2", role: "alert", children: [_jsxs("p", { className: "font-medium", children: ["Component", this.props.componentType ? ` "${this.props.componentType}"` : '', " failed to render"] }), _jsx("p", { className: "text-sm mt-1", children: error.message }), isDev && objuiError?.code && (_jsxs("p", { className: "text-xs mt-1 text-orange-500", children: ["Error code: ", objuiError.code, objuiError.details?.suggestion ? (_jsxs("span", { className: "block mt-0.5", children: ["\uD83D\uDCA1 ", String(objuiError.details.suggestion)] })) : null] })), _jsx("button", { type: "button", onClick: this.handleRetry, className: "mt-2 text-sm underline hover:no-underline", children: "Retry" })] }));
|
|
135
135
|
}
|
|
136
136
|
return this.props.children;
|
|
137
137
|
}
|
|
@@ -146,6 +146,47 @@ export class SchemaErrorBoundary extends Component {
|
|
|
146
146
|
* `useState` wiped (objectui#2954's latent hazard, made real by this line).
|
|
147
147
|
*/
|
|
148
148
|
const NO_DATA_SOURCE = {};
|
|
149
|
+
/**
|
|
150
|
+
* The renderer loop.
|
|
151
|
+
*
|
|
152
|
+
* ## The explicit type annotation is the contract, and it is deliberate
|
|
153
|
+
*
|
|
154
|
+
* `forwardRef< T, P >` routes `P` through React's `PropsWithoutRef`:
|
|
155
|
+
*
|
|
156
|
+
* Props extends any ? ('ref' extends keyof Props ? Omit< Props, 'ref' > : Props) : Props
|
|
157
|
+
*
|
|
158
|
+
* A string index signature on `P` puts `string` into `keyof Props`, so
|
|
159
|
+
* `'ref' extends keyof Props` is ALWAYS true, the `Omit` branch always runs, and
|
|
160
|
+
* `Omit` over a type carrying a string index signature keeps ONLY the index
|
|
161
|
+
* signature. Every declared prop is erased — on both sides. This component used
|
|
162
|
+
* to hand `forwardRef` a props type of `{ schema: SchemaNode } & Record< string,
|
|
163
|
+
* any >`, so its own `schema` resolved to `any` at every one of the ~376 call
|
|
164
|
+
* sites in this repo, and was not even REQUIRED: `< SchemaRenderer / >` with no
|
|
165
|
+
* schema at all type-checked (objectui#4548, measured).
|
|
166
|
+
*
|
|
167
|
+
* The fix is NOT to close the surface. This is the renderer loop: it forwards
|
|
168
|
+
* every prop it does not read to the component the schema names, resolved at
|
|
169
|
+
* RUNTIME from a plugin-extensible `ComponentRegistry`. That forwarding is a
|
|
170
|
+
* documented, load-bearing feature — `packages/react/README.md` shows
|
|
171
|
+
* `< SchemaRenderer schema={formSchema} onSubmit={handleSubmit} / >`, and
|
|
172
|
+
* `@object-ui/components`' form renderer consumes that `onSubmit` as a React
|
|
173
|
+
* prop. A closed props type would state a FALSE contract and would force every
|
|
174
|
+
* leaf plugin's props into this package to stay usable.
|
|
175
|
+
*
|
|
176
|
+
* So the two halves are separated deliberately:
|
|
177
|
+
*
|
|
178
|
+
* * the `forwardRef` TYPE ARGUMENT is the honest `SchemaRendererProps`, with
|
|
179
|
+
* no index signature — nothing for `PropsWithoutRef` to collapse, so
|
|
180
|
+
* `schema` survives to the call site typed and required; and
|
|
181
|
+
* * the open forwarding surface is stated ONCE, here, in this export
|
|
182
|
+
* annotation. Because the annotation is applied to the already-built
|
|
183
|
+
* component, `PropsWithoutRef` never runs over it, so `Record< string, any >`
|
|
184
|
+
* widens the surface WITHOUT erasing anything.
|
|
185
|
+
*
|
|
186
|
+
* The repo-wide guard (`scripts/__tests__/forwardref-props-erasure.guard.test.ts`)
|
|
187
|
+
* judges the TYPE ARGUMENT only, for exactly this reason: an index signature
|
|
188
|
+
* there is an accidental eraser, whereas one here is a stated contract.
|
|
189
|
+
*/
|
|
149
190
|
export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
|
|
150
191
|
const context = useContext(SchemaRendererContext);
|
|
151
192
|
const dataSource = context?.dataSource || NO_DATA_SOURCE;
|
|
@@ -176,7 +217,19 @@ export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
|
|
|
176
217
|
const autoStyleId = React.useId();
|
|
177
218
|
// Evaluate schema expressions against the data source
|
|
178
219
|
const evaluatedSchema = useMemo(() => {
|
|
179
|
-
|
|
220
|
+
// Nothing to evaluate unless the node is an OBJECT. `!schema` covers the
|
|
221
|
+
// nullish/empty cases and `typeof !== 'object'` covers every primitive —
|
|
222
|
+
// both return the value untouched for the render pass below to place.
|
|
223
|
+
//
|
|
224
|
+
// The `typeof` half is what used to be missing (objectui#4548): the guard
|
|
225
|
+
// named `string` only, so a `number` or `true` fell through to the
|
|
226
|
+
// `{ ...schema }` shallow copy on the next line, spread to an EMPTY object,
|
|
227
|
+
// lost its `type`, and surfaced as the red "Unknown component type:
|
|
228
|
+
// undefined" box — an accident of the spread, not a decision. The declared
|
|
229
|
+
// props type now excludes those primitives outright; this guard is the
|
|
230
|
+
// defence-in-depth behind it, and it is what makes the copy below provably
|
|
231
|
+
// an object spread.
|
|
232
|
+
if (!schema || typeof schema !== 'object')
|
|
180
233
|
return schema;
|
|
181
234
|
// `data` (record/datasource) plus the ambient host scope. `current_user`
|
|
182
235
|
// is aliased to `user` so both `user.email` and `current_user.email`
|
|
@@ -248,10 +301,28 @@ export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
|
|
|
248
301
|
if (newSchema.visibility !== undefined) {
|
|
249
302
|
return !evaluator.evaluateCondition(newSchema.visibility);
|
|
250
303
|
}
|
|
251
|
-
|
|
304
|
+
// Ask "is a `hidden` gate DECLARED?" — not "is the key present?"
|
|
305
|
+
// (objectui#3955). These two legs are the only ones in this chain whose
|
|
306
|
+
// verdict is NOT negated, so the evaluator's single default for "there is
|
|
307
|
+
// nothing to evaluate" (`true`, meaning *visible/enabled*) arrives here
|
|
308
|
+
// meaning HIDE: `hidden: ''` / `null` (`null !== undefined`) / a
|
|
309
|
+
// whitespace-only string / the `{ dialect, source: '' }` envelope
|
|
310
|
+
// `objectstack build` emits for an empty predicate each made the node
|
|
311
|
+
// VANISH, on the generic path, for a value the metadata never used to say
|
|
312
|
+
// anything. Harder to diagnose than its `disabled` twin below
|
|
313
|
+
// (objectui#3862): a greyed-out control is still on screen, a node that
|
|
314
|
+
// never rendered is indistinguishable from metadata that meant it.
|
|
315
|
+
//
|
|
316
|
+
// `hasDeclaredPredicate` is the repo's one definition of "declared"
|
|
317
|
+
// (core's `evaluator/declaredPredicate.ts`) — a local `&& !== ''` here
|
|
318
|
+
// would have been the Nth dialect of one question. The verdict still reads
|
|
319
|
+
// the RAW value; only the gate in front of it narrowed. Not an
|
|
320
|
+
// equivalence, and pinned as a behaviour change: an UNDECLARED `hidden` no
|
|
321
|
+
// longer short-circuits, so a declared `hiddenOn` is finally consulted.
|
|
322
|
+
if (hasDeclaredPredicate(newSchema.hidden)) {
|
|
252
323
|
return evaluator.evaluateCondition(newSchema.hidden);
|
|
253
324
|
}
|
|
254
|
-
if (newSchema.hiddenOn
|
|
325
|
+
if (hasDeclaredPredicate(newSchema.hiddenOn)) {
|
|
255
326
|
return evaluator.evaluateCondition(newSchema.hiddenOn);
|
|
256
327
|
}
|
|
257
328
|
return false;
|
|
@@ -260,11 +331,41 @@ export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
|
|
|
260
331
|
newSchema._hidden = true;
|
|
261
332
|
}
|
|
262
333
|
// Evaluate disabled: disabled / disabledOn
|
|
334
|
+
//
|
|
335
|
+
// Ask "is a `disabled` gate DECLARED?" — not "is the key present?"
|
|
336
|
+
// (objectui#3862). `!== undefined` was the widest of the three spellings this
|
|
337
|
+
// question used to have, and this is the key where breadth is not free: the
|
|
338
|
+
// evaluation entry answers an empty predicate with `true`, meaning "no
|
|
339
|
+
// condition → visible/enabled", and on `disabled` that `true` means GREYED
|
|
340
|
+
// OUT. So `disabled: ''`, `disabled: null` (`null !== undefined`), a
|
|
341
|
+
// whitespace-only string and the `{ dialect, source: '' }` envelope
|
|
342
|
+
// `objectstack build` emits for an empty predicate each disabled the node —
|
|
343
|
+
// on the GENERIC path, since this block runs for every schema type, and not
|
|
344
|
+
// as an internal flag either: `_disabled` is forwarded below as a real
|
|
345
|
+
// `disabled` prop.
|
|
346
|
+
//
|
|
347
|
+
// The `visible` / `visibleWhen` / `visibleOn` / `visibility` legs above keep
|
|
348
|
+
// `!== undefined` deliberately: their `true` is NEGATED, so an empty predicate
|
|
349
|
+
// already lands on "shown", which is what "no gate" means anyway, and
|
|
350
|
+
// narrowing them would change ALIAS PRECEDENCE rather than fix anything. The
|
|
351
|
+
// `hidden` / `hiddenOn` legs were the exception — not negated, so they carried
|
|
352
|
+
// this same defect with the polarity that makes the node VANISH — and they now
|
|
353
|
+
// read this same definition (objectui#3955).
|
|
354
|
+
//
|
|
355
|
+
// `hasDeclaredPredicate` is the one definition of "declared" (core's
|
|
356
|
+
// `evaluator/declaredPredicate.ts`, objectui#3850's ruling — read there for
|
|
357
|
+
// the scope), shared with the action renderers' `hasDeclaredVisibilityGate`
|
|
358
|
+
// and `ActionRunner`'s execution gates. A local `&& !== ''` here would have
|
|
359
|
+
// been a fourth dialect of one question, which is what objectui#3842 /
|
|
360
|
+
// objectui#3849 spent two PRs merging away. The verdict still reads the RAW
|
|
361
|
+
// value, exactly as before — only the gate in front of it narrowed. An
|
|
362
|
+
// undeclared `disabled` now falls through to `disabledOn` instead of
|
|
363
|
+
// short-circuiting on an empty predicate.
|
|
263
364
|
const isDisabled = (() => {
|
|
264
|
-
if (newSchema.disabled
|
|
365
|
+
if (hasDeclaredPredicate(newSchema.disabled)) {
|
|
265
366
|
return evaluator.evaluateCondition(newSchema.disabled);
|
|
266
367
|
}
|
|
267
|
-
if (newSchema.disabledOn
|
|
368
|
+
if (hasDeclaredPredicate(newSchema.disabledOn)) {
|
|
268
369
|
return evaluator.evaluateCondition(newSchema.disabledOn);
|
|
269
370
|
}
|
|
270
371
|
return false;
|
|
@@ -276,12 +377,25 @@ export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
|
|
|
276
377
|
}, [schema, dataSource, predicateScope, pageVariables]);
|
|
277
378
|
if (!evaluatedSchema)
|
|
278
379
|
return null;
|
|
279
|
-
// Handle visibility: if evaluated schema is hidden, render nothing
|
|
280
|
-
if (evaluatedSchema?._hidden)
|
|
281
|
-
return null;
|
|
282
380
|
// If schema is just a string, render it as text
|
|
283
381
|
if (typeof evaluatedSchema === 'string')
|
|
284
382
|
return _jsx(_Fragment, { children: evaluatedSchema });
|
|
383
|
+
// Any other primitive that reached here renders as its text too
|
|
384
|
+
// (objectui#4548). The declared props type does not admit `number` /
|
|
385
|
+
// `boolean`, so this is unreachable from typed code and exists for untyped
|
|
386
|
+
// callers and stored metadata; it replaces the empty-spread "Unknown
|
|
387
|
+
// component type: undefined" box, which said nothing true about the input.
|
|
388
|
+
if (typeof evaluatedSchema !== 'object')
|
|
389
|
+
return _jsx(_Fragment, { children: String(evaluatedSchema) });
|
|
390
|
+
// Handle visibility: if evaluated schema is hidden, render nothing.
|
|
391
|
+
//
|
|
392
|
+
// Reads AFTER the primitive narrowing above (objectui#4548) so the property
|
|
393
|
+
// access is on a known object. Behaviour is unchanged: `_hidden` is only ever
|
|
394
|
+
// set on the object copy built in the memo, so for a string or any other
|
|
395
|
+
// primitive this test was always falsy and fell through to exactly the
|
|
396
|
+
// branches that now precede it.
|
|
397
|
+
if (evaluatedSchema._hidden)
|
|
398
|
+
return null;
|
|
285
399
|
// Dev-mode validation: log once per schema object, attach visual flag
|
|
286
400
|
// when invalid. Production path returns { valid: true, messages: [] }
|
|
287
401
|
// without doing any work.
|
|
@@ -308,7 +422,19 @@ export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
|
|
|
308
422
|
// Note: We don't forward the ref to the Component because components in the registry
|
|
309
423
|
// may not support refs. The SchemaRenderer itself can still receive refs for its own use.
|
|
310
424
|
// Extract schema metadata properties that should NOT be passed as React props
|
|
311
|
-
const { type: _type, children: _children, body: _body, schema: _schema, visible: _visible, visibleWhen: _visibleWhen, visibleOn: _visibleOn, visibility: _visibility, hidden: _hidden, hiddenOn: _hiddenOn, disabled: _disabled, disabledOn: _disabledOn,
|
|
425
|
+
const { type: _type, children: _children, body: _body, schema: _schema, visible: _visible, visibleWhen: _visibleWhen, visibleOn: _visibleOn, visibility: _visibility, hidden: _hidden, hiddenOn: _hiddenOn, disabled: _disabled, disabledOn: _disabledOn,
|
|
426
|
+
// stripped: `PageComponentSchema.dataSource` is the spec's per-element data
|
|
427
|
+
// BINDING (`{ object, view, filter, sort, limit }`) — schema metadata, like
|
|
428
|
+
// `visibleWhen` above, not a visual prop. Renderers that consume it read
|
|
429
|
+
// `schema.dataSource` (element:record_picker, list-view); it must not be
|
|
430
|
+
// spread as a React prop, because several data-bound blocks take the
|
|
431
|
+
// injected data-source ADAPTER under that very name. Spreading it shadowed
|
|
432
|
+
// the adapter with a plain `{ object, view }` object, so the first
|
|
433
|
+
// `dataSource.find(…)` threw `dataSource.find is not a function` and the
|
|
434
|
+
// block reported "Couldn't load records" — writing the binding the spec
|
|
435
|
+
// documents BROKE the component (objectstack#5576). An explicit React
|
|
436
|
+
// `dataSource` prop is unaffected: it arrives via `...props`, spread last.
|
|
437
|
+
dataSource: _dataSource, _hidden: __hidden, // stripped: internal visibility flag
|
|
312
438
|
_disabled: __disabled, // stripped: internal disabled flag
|
|
313
439
|
responsiveStyles: _responsiveStyles, // stripped: compiled to scoped CSS, not a DOM prop
|
|
314
440
|
...componentProps } = evaluatedSchema;
|
|
@@ -1,7 +1,26 @@
|
|
|
1
1
|
import type { ObjectStackAdapter } from '@object-ui/data-objectstack';
|
|
2
2
|
export declare const AdapterCtx: import("react").Context<ObjectStackAdapter<unknown> | null>;
|
|
3
3
|
export declare function useAdapter(): ObjectStackAdapter | null;
|
|
4
|
-
|
|
4
|
+
/**
|
|
5
|
+
* The metadata PROVIDER's cache — the collections `<MetadataProvider>` holds in
|
|
6
|
+
* React state, plus its aggregate load flags.
|
|
7
|
+
*
|
|
8
|
+
* Renamed from `MetadataState` in objectui#4167. `@objectstack/spec` 17.0.0-rc.6
|
|
9
|
+
* publishes `MetadataState` from `@objectstack/spec/system`, and it names a
|
|
10
|
+
* different thing in the SAME domain and the SAME two words: a metadata item's
|
|
11
|
+
* LIFECYCLE state, `'draft' | 'active' | 'deprecated' | 'archived'`
|
|
12
|
+
* (`MetadataStateSchema`). That is the `AuthProviderConfig` judgement from
|
|
13
|
+
* objectstack#4115 batch 5, not the `AuthProvider` one — nothing about a JSX
|
|
14
|
+
* element can be mistaken for a zod enum, but "the metadata state" absolutely
|
|
15
|
+
* can be read as canonical for the lifecycle enum by the next agent, and the
|
|
16
|
+
* two are mutually unassignable (an object of five arrays vs a string union),
|
|
17
|
+
* so nothing would have caught the misreading at the point it was made.
|
|
18
|
+
*
|
|
19
|
+
* `Cache` rather than a bare disambiguating prefix because it is what this is:
|
|
20
|
+
* `MetadataProvider` is a TTL cache with per-type entries, and these five
|
|
21
|
+
* arrays are its materialized contents.
|
|
22
|
+
*/
|
|
23
|
+
export interface MetadataCacheState {
|
|
5
24
|
apps: any[];
|
|
6
25
|
objects: any[];
|
|
7
26
|
dashboards: any[];
|
|
@@ -11,7 +30,7 @@ export interface MetadataState {
|
|
|
11
30
|
error: Error | null;
|
|
12
31
|
}
|
|
13
32
|
export type MetadataTypeStatus = 'idle' | 'loading' | 'ready' | 'error';
|
|
14
|
-
export interface MetadataContextValue extends
|
|
33
|
+
export interface MetadataContextValue extends MetadataCacheState {
|
|
15
34
|
refresh: (type?: string) => Promise<void>;
|
|
16
35
|
invalidate: (type: string, name?: string) => void;
|
|
17
36
|
ensureType: (type: string) => Promise<any[]>;
|
|
@@ -35,10 +35,6 @@ export interface RecordContextValue<TData = any, TObjectSchema = any> {
|
|
|
35
35
|
data?: TData;
|
|
36
36
|
/** Resolved object metadata schema (fields, label, etc.). */
|
|
37
37
|
objectSchema?: TObjectSchema;
|
|
38
|
-
/** True while the record is fetching. */
|
|
39
|
-
loading?: boolean;
|
|
40
|
-
/** Last fetch error, if any. */
|
|
41
|
-
error?: Error | null;
|
|
42
38
|
/** Re-fetch the record from the source. */
|
|
43
39
|
refresh?: () => void | Promise<void>;
|
|
44
40
|
/**
|
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
* affordance. When no provider is present at all (e.g. the Studio designer, or
|
|
25
25
|
* a standalone embedded renderer) the related list stays read-only, which is
|
|
26
26
|
* the correct graceful fallback.
|
|
27
|
+
*
|
|
28
|
+
* Since objectui#4336 the same host also publishes its record-URL builder
|
|
29
|
+
* ({@link RelatedRecordActionsValue.recordHref} / `openRecord`) for any object
|
|
30
|
+
* it can route to, so the low-level renderers on a record page can turn a
|
|
31
|
+
* lookup VALUE into a link to the referenced record without assembling a URL
|
|
32
|
+
* of their own. Same provider, same builder, one route shape.
|
|
27
33
|
*/
|
|
28
34
|
import React from 'react';
|
|
29
35
|
/** A child-object action surfaced in a related list's row menu. */
|
|
@@ -92,6 +98,29 @@ export interface RelatedRecordActionsValue {
|
|
|
92
98
|
* identity.
|
|
93
99
|
*/
|
|
94
100
|
resolve: (input: ResolveRelatedRecordActionsInput) => RelatedRecordHandlers;
|
|
101
|
+
/**
|
|
102
|
+
* The host's record-detail URL for ANY object it can route to — the SAME
|
|
103
|
+
* builder {@link RelatedRecordHandlers.onView} navigates with, published as
|
|
104
|
+
* an href so a renderer can present the destination as a real anchor
|
|
105
|
+
* (middle-click, "open in new tab", "copy link address") instead of a
|
|
106
|
+
* click-only affordance.
|
|
107
|
+
*
|
|
108
|
+
* Added for objectui#4336: a lookup value is a reference to a record of a
|
|
109
|
+
* DIFFERENT object, and the renderer that displays it (`LookupCellRenderer`,
|
|
110
|
+
* shared by the detail body and the related list's cells) has no router and
|
|
111
|
+
* no knowledge of the console's route shape. Route building stays here, in
|
|
112
|
+
* the one place that already owns it — never re-derived renderer-side.
|
|
113
|
+
*
|
|
114
|
+
* Returns `null` when the host cannot route to that object (unknown object,
|
|
115
|
+
* no app context), which consumers must render as the plain value.
|
|
116
|
+
*/
|
|
117
|
+
recordHref?: (objectName: string, recordId: string | number) => string | null;
|
|
118
|
+
/**
|
|
119
|
+
* SPA-navigate to the destination {@link recordHref} addresses. Consumers
|
|
120
|
+
* call this from a plain left click on the anchor so navigation stays
|
|
121
|
+
* in-app; a modifier click is left to the browser and the href.
|
|
122
|
+
*/
|
|
123
|
+
openRecord?: (objectName: string, recordId: string | number) => void;
|
|
95
124
|
}
|
|
96
125
|
export declare const RelatedRecordActionsProvider: React.FC<{
|
|
97
126
|
value: RelatedRecordActionsValue;
|
|
@@ -25,6 +25,12 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
25
25
|
* affordance. When no provider is present at all (e.g. the Studio designer, or
|
|
26
26
|
* a standalone embedded renderer) the related list stays read-only, which is
|
|
27
27
|
* the correct graceful fallback.
|
|
28
|
+
*
|
|
29
|
+
* Since objectui#4336 the same host also publishes its record-URL builder
|
|
30
|
+
* ({@link RelatedRecordActionsValue.recordHref} / `openRecord`) for any object
|
|
31
|
+
* it can route to, so the low-level renderers on a record page can turn a
|
|
32
|
+
* lookup VALUE into a link to the referenced record without assembling a URL
|
|
33
|
+
* of their own. Same provider, same builder, one route shape.
|
|
28
34
|
*/
|
|
29
35
|
import { createContext, useContext } from 'react';
|
|
30
36
|
const RelatedRecordActionsContext = createContext(null);
|