@object-ui/react 17.5.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 +334 -0
- package/README.md +9 -8
- package/dist/LazyPluginLoader.d.ts +14 -7
- package/dist/LazyPluginLoader.js +14 -7
- package/dist/SchemaRenderer.js +434 -22
- package/dist/context/HostNavigationContext.d.ts +82 -0
- package/dist/context/HostNavigationContext.js +65 -0
- package/dist/context/RelatedRecordActionsContext.d.ts +15 -0
- package/dist/context/index.d.ts +13 -12
- package/dist/context/index.js +13 -12
- package/dist/element-data-source/ElementDataSourceGate.d.ts +75 -2
- package/dist/element-data-source/ElementDataSourceGate.js +81 -2
- package/dist/hooks/index.d.ts +35 -35
- package/dist/hooks/index.js +35 -35
- package/dist/hooks/useActionEngine.js +1 -1
- 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.js +1 -1
- package/dist/hooks/useDiscovery.js +1 -1
- package/dist/hooks/useElementDataSource.js +5 -2
- package/dist/hooks/useExpression.d.ts +24 -3
- package/dist/hooks/useExpression.js +56 -4
- package/dist/hooks/useNavigationOverlay.d.ts +1 -1
- package/dist/hooks/usePageAssignment.js +1 -1
- 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 -11
- package/dist/index.js +10 -10
- package/dist/schema-input.d.ts +16 -5
- package/dist/schema-input.js +15 -4
- package/dist/spec-bridge/SpecBridge.d.ts +1 -1
- package/dist/spec-bridge/SpecBridge.js +2 -2
- 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 +37 -10
- package/dist/spec-bridge/index.d.ts +4 -4
- package/dist/spec-bridge/index.js +3 -3
- package/dist/utils/error-message.d.ts +59 -0
- package/dist/utils/error-message.js +116 -1
- 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 +7 -7
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* ObjectUI
|
|
4
|
+
* Copyright (c) 2024-present ObjectStack Inc.
|
|
5
|
+
*
|
|
6
|
+
* This source code is licensed under the MIT license found in the
|
|
7
|
+
* LICENSE file in the root directory of this source tree.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* HostNavigationContext — the host's own in-app navigate, offered to renderers.
|
|
11
|
+
*
|
|
12
|
+
* A published renderer package cannot navigate correctly on its own. Its only
|
|
13
|
+
* dependency-free option is a browser-level navigation
|
|
14
|
+
* (`window.location.assign`), which resolves a rooted path such as `/thanks`
|
|
15
|
+
* against the ORIGIN. Under a host mounted at a sub-path — the framework CLI
|
|
16
|
+
* configures one for every embedded deployment — that leaves the application
|
|
17
|
+
* entirely. This is objectui#4989 defect 4, and the same class as
|
|
18
|
+
* objectui#4190 / #4181.
|
|
19
|
+
*
|
|
20
|
+
* The host is the only party that knows its mount, so the host supplies the
|
|
21
|
+
* navigate. This context is that seam: **optional injection**, ruled by the
|
|
22
|
+
* maintainer on 2026-08-17. Two mechanisms were rejected in that ruling and are
|
|
23
|
+
* named here so they are not re-proposed:
|
|
24
|
+
*
|
|
25
|
+
* - **router-presence sniffing** (a renderer reading React Router's context
|
|
26
|
+
* when it happens to be there) — implicit magic, and it forces every
|
|
27
|
+
* renderer that wants mount-awareness to import `react-router`;
|
|
28
|
+
* - **a `react-router` peer requirement** — it imposes module resolution on
|
|
29
|
+
* router-less consumers. Two real ones exist in this repo (`apps/site`,
|
|
30
|
+
* `packages/plugin-view`), and a published renderer's whole property is
|
|
31
|
+
* that it drops into ANY React application.
|
|
32
|
+
*
|
|
33
|
+
* Explicit injection keeps every renderer dependency-free and puts
|
|
34
|
+
* mount-awareness on the type face where a host can see it is missing.
|
|
35
|
+
*
|
|
36
|
+
* ## Absent is a supported state, not a degraded one
|
|
37
|
+
*
|
|
38
|
+
* The default value is an empty object, so `useHostNavigation()` outside a
|
|
39
|
+
* provider answers `{ navigate: undefined }` and never throws. A consumer reads
|
|
40
|
+
* it as "no host navigate — fall back to whatever this renderer did before",
|
|
41
|
+
* which for a router-less host is already the correct behaviour: with no
|
|
42
|
+
* router there is no basename, so origin-rooted resolution is right. The seam
|
|
43
|
+
* changes what a MOUNTED host gets, and changes nothing else.
|
|
44
|
+
*
|
|
45
|
+
* ## Relationship to the other navigation seams in this package
|
|
46
|
+
*
|
|
47
|
+
* `DrillNavigationContext.openRecordList` is a SEMANTIC navigation — "show me
|
|
48
|
+
* the records behind this aggregate" — that only the console can turn into a
|
|
49
|
+
* URL. `ActionProvider`'s `onNavigate` is the ActionRunner's handler for
|
|
50
|
+
* `type: 'navigate'` actions, reachable only through an action dispatch. This
|
|
51
|
+
* one is neither: it is the plain "go to this already-resolved in-app path"
|
|
52
|
+
* that a renderer holds a destination for and cannot perform correctly itself.
|
|
53
|
+
* All three are supplied by the same shell from the same router `navigate`.
|
|
54
|
+
*/
|
|
55
|
+
import { createContext, useContext } from 'react';
|
|
56
|
+
export const HostNavigationContext = createContext({});
|
|
57
|
+
export const HostNavigationProvider = ({ value, children }) => (_jsx(HostNavigationContext.Provider, { value: value, children: children }));
|
|
58
|
+
/**
|
|
59
|
+
* Consume the host-provided in-app navigate. Answers `{}` (i.e. `navigate`
|
|
60
|
+
* undefined) when no host wired one — that is a supported state, see the module
|
|
61
|
+
* docblock.
|
|
62
|
+
*/
|
|
63
|
+
export function useHostNavigation() {
|
|
64
|
+
return useContext(HostNavigationContext);
|
|
65
|
+
}
|
|
@@ -61,6 +61,21 @@ export interface RelatedRecordHandlers {
|
|
|
61
61
|
* argument is needed here (增).
|
|
62
62
|
*/
|
|
63
63
|
onCreate?: () => void;
|
|
64
|
+
/**
|
|
65
|
+
* [#4646] Offer "+ New", but GREYED — the `userActions.create.disabledWhen`
|
|
66
|
+
* verdict for the scope this toolbar sits in (the host parent record on a
|
|
67
|
+
* record page's related list).
|
|
68
|
+
*
|
|
69
|
+
* A separate channel from omitting {@link onCreate} because hidden and
|
|
70
|
+
* disabled are different answers, exactly as they are for the row Edit/Delete
|
|
71
|
+
* predicates (objectui#2614) and for the record header's (objectui#4419 /
|
|
72
|
+
* PR #4515): `visibleWhen` decides whether the affordance exists at all,
|
|
73
|
+
* `disabledWhen` decides whether the existing affordance can be pressed. The
|
|
74
|
+
* host still supplies `onCreate` in the disabled case, so the button keeps
|
|
75
|
+
* its label and tooltip and the user can see that creating is a thing this
|
|
76
|
+
* list does — just not right now.
|
|
77
|
+
*/
|
|
78
|
+
createDisabled?: boolean;
|
|
64
79
|
/** Open an edit form for an existing child record (改). */
|
|
65
80
|
onEdit?: (recordId: string | number, record?: unknown) => void;
|
|
66
81
|
/**
|
package/dist/context/index.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
export * from './SchemaRendererContext';
|
|
2
|
-
export * from './PreviewModeContext';
|
|
3
|
-
export * from './ActionContext';
|
|
4
|
-
export * from './ThemeContext';
|
|
5
|
-
export * from './NotificationContext';
|
|
6
|
-
export * from './DndContext';
|
|
7
|
-
export * from './AppShellContext';
|
|
8
|
-
export * from './RecordContext';
|
|
9
|
-
export * from './InlineEditContext';
|
|
10
|
-
export * from './DiscussionContext';
|
|
11
|
-
export * from './DrillNavigationContext';
|
|
12
|
-
export * from './
|
|
1
|
+
export * from './SchemaRendererContext.js';
|
|
2
|
+
export * from './PreviewModeContext.js';
|
|
3
|
+
export * from './ActionContext.js';
|
|
4
|
+
export * from './ThemeContext.js';
|
|
5
|
+
export * from './NotificationContext.js';
|
|
6
|
+
export * from './DndContext.js';
|
|
7
|
+
export * from './AppShellContext.js';
|
|
8
|
+
export * from './RecordContext.js';
|
|
9
|
+
export * from './InlineEditContext.js';
|
|
10
|
+
export * from './DiscussionContext.js';
|
|
11
|
+
export * from './DrillNavigationContext.js';
|
|
12
|
+
export * from './HostNavigationContext.js';
|
|
13
|
+
export * from './RelatedRecordActionsContext.js';
|
package/dist/context/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
export * from './SchemaRendererContext';
|
|
2
|
-
export * from './PreviewModeContext';
|
|
3
|
-
export * from './ActionContext';
|
|
4
|
-
export * from './ThemeContext';
|
|
5
|
-
export * from './NotificationContext';
|
|
6
|
-
export * from './DndContext';
|
|
7
|
-
export * from './AppShellContext';
|
|
8
|
-
export * from './RecordContext';
|
|
9
|
-
export * from './InlineEditContext';
|
|
10
|
-
export * from './DiscussionContext';
|
|
11
|
-
export * from './DrillNavigationContext';
|
|
12
|
-
export * from './
|
|
1
|
+
export * from './SchemaRendererContext.js';
|
|
2
|
+
export * from './PreviewModeContext.js';
|
|
3
|
+
export * from './ActionContext.js';
|
|
4
|
+
export * from './ThemeContext.js';
|
|
5
|
+
export * from './NotificationContext.js';
|
|
6
|
+
export * from './DndContext.js';
|
|
7
|
+
export * from './AppShellContext.js';
|
|
8
|
+
export * from './RecordContext.js';
|
|
9
|
+
export * from './InlineEditContext.js';
|
|
10
|
+
export * from './DiscussionContext.js';
|
|
11
|
+
export * from './DrillNavigationContext.js';
|
|
12
|
+
export * from './HostNavigationContext.js';
|
|
13
|
+
export * from './RelatedRecordActionsContext.js';
|
|
@@ -59,6 +59,17 @@
|
|
|
59
59
|
* therefore leaves `limit` unmapped rather than parking it somewhere plausible,
|
|
60
60
|
* and the gap is recorded at the call site instead of being papered over here.
|
|
61
61
|
*
|
|
62
|
+
* ## A block with no ADAPTER fails loudly too (objectui#5378)
|
|
63
|
+
*
|
|
64
|
+
* The section below is about an unresolvable view NAME. The coarser failure —
|
|
65
|
+
* no runtime adapter reached the block at all — used to render an empty shell
|
|
66
|
+
* in exactly the same silence, and that is what `requiresDataSource` +
|
|
67
|
+
* {@link NoDataSourcePanel} now report. Measured on the published
|
|
68
|
+
* getting-started guide: all five of its blocks resolved no adapter, fetched
|
|
69
|
+
* nothing, and said nothing. The two panels are deliberately the same shape;
|
|
70
|
+
* they answer "which view?" and "which data source?", and an author who sees
|
|
71
|
+
* either one is being told where to look.
|
|
72
|
+
*
|
|
62
73
|
* ## An unresolvable `view` fails loudly
|
|
63
74
|
*
|
|
64
75
|
* When the named view does not exist the gate renders a configuration error
|
|
@@ -69,7 +80,7 @@
|
|
|
69
80
|
*/
|
|
70
81
|
import * as React from 'react';
|
|
71
82
|
import { type ElementDataSourceConfig } from '@object-ui/core';
|
|
72
|
-
import { type ElementDataSourceStatus } from '../hooks/useElementDataSource';
|
|
83
|
+
import { type ElementDataSourceStatus } from '../hooks/useElementDataSource.js';
|
|
73
84
|
/**
|
|
74
85
|
* Where a block's row cap lives. Three spellings are real in this repo and each
|
|
75
86
|
* is the ONLY one its block reads: `list-view`/`object-grid` read
|
|
@@ -169,6 +180,51 @@ export declare function ElementDataSourceErrorPanel({ testId, title, message, }:
|
|
|
169
180
|
* would flash a configuration error on every mount.
|
|
170
181
|
*/
|
|
171
182
|
export declare function ElementDataSourceLoadingPanel({ testId, }: Pick<ElementDataSourceStatusPanelProps, 'testId'>): React.ReactElement;
|
|
183
|
+
/**
|
|
184
|
+
* The adapter this block would use, resolved the one way every block in the
|
|
185
|
+
* family resolves it: an explicit value first, the `SchemaRendererProvider`
|
|
186
|
+
* context second.
|
|
187
|
+
*
|
|
188
|
+
* The `isElementDataSourceConfig` guard is the same one
|
|
189
|
+
* {@link useElementDataSourceSchema} applies — a host handing us the spec
|
|
190
|
+
* BINDING under this name is handing us metadata, not an adapter, and counting
|
|
191
|
+
* it as one is how "no data source" reads as "resolved".
|
|
192
|
+
*
|
|
193
|
+
* `T` is what the CALLER expects to have been injected, and the assertion is
|
|
194
|
+
* the caller's, not this hook's: an adapter arrives here as a `dataSource` prop
|
|
195
|
+
* or a context value that nothing in this package can narrow, so the type has
|
|
196
|
+
* to be declared by whoever knows which interface their block consumes. Leave
|
|
197
|
+
* it off and you get `unknown`, which is the honest default.
|
|
198
|
+
*/
|
|
199
|
+
export declare function useResolvedDataSource<T = unknown>(explicit?: unknown): T | undefined;
|
|
200
|
+
/**
|
|
201
|
+
* The author-facing sentence a block with no adapter says out loud.
|
|
202
|
+
*
|
|
203
|
+
* One wording for the whole object-bound family, because the reader's mistake
|
|
204
|
+
* is the same one every time and the fix is the same one every time: the
|
|
205
|
+
* adapter is injected by an ANCESTOR (`SchemaRendererProvider`), so nothing
|
|
206
|
+
* written on the block itself can supply it. Naming the block and the object
|
|
207
|
+
* it was about to read is what turns "nothing happened" into an address.
|
|
208
|
+
*/
|
|
209
|
+
export declare function noDataSourceMessage(blockKey: string, objectName?: unknown): string;
|
|
210
|
+
/**
|
|
211
|
+
* The "this block resolved no adapter" panel — objectui#5378 item 2, and the
|
|
212
|
+
* #5349 shape it belongs to.
|
|
213
|
+
*
|
|
214
|
+
* Until this existed, a block that resolved no adapter rendered an EMPTY SHELL:
|
|
215
|
+
* `object-grid` painted a header-only table, `object-form` a field-less card,
|
|
216
|
+
* `detail-view` nothing at all — no error, no warning, no empty state that
|
|
217
|
+
* explained itself. Measured on the published getting-started guide
|
|
218
|
+
* (`content/docs/guide/building-crud-app.md`), every one of its five blocks was
|
|
219
|
+
* in that state and the page reported success while fetching nothing.
|
|
220
|
+
*
|
|
221
|
+
* The empty shell is the defect, not a side effect of it: it is indistinguishable
|
|
222
|
+
* from "the query ran and matched no rows", which is the reading an author (very
|
|
223
|
+
* often an AI author) takes, and it points at the DATA when the fault is in the
|
|
224
|
+
* WIRING one level up. Same posture as {@link ElementDataSourceErrorPanel}: the
|
|
225
|
+
* metadata is answerable, so the answer is rendered where the author can see it.
|
|
226
|
+
*/
|
|
227
|
+
export declare function NoDataSourcePanel({ testId, title, message, }: ElementDataSourceStatusPanelProps): React.ReactElement;
|
|
172
228
|
export interface ElementDataSourceGateProps<S> {
|
|
173
229
|
/** The block's schema node. */
|
|
174
230
|
schema: S;
|
|
@@ -180,6 +236,23 @@ export interface ElementDataSourceGateProps<S> {
|
|
|
180
236
|
testId: string;
|
|
181
237
|
/** Heading for the unresolvable-view panel. */
|
|
182
238
|
errorTitle?: string;
|
|
239
|
+
/**
|
|
240
|
+
* Whether THIS placement cannot do its job without a runtime adapter — when
|
|
241
|
+
* true and none resolves, the gate renders {@link NoDataSourcePanel} instead
|
|
242
|
+
* of the block (objectui#5378 item 2).
|
|
243
|
+
*
|
|
244
|
+
* The gate cannot compute this and does not try. "Needs an adapter" is a
|
|
245
|
+
* statement about the block's own fallbacks, and every block in the family
|
|
246
|
+
* has different ones: an `object-grid` carrying inline `data` rows needs no
|
|
247
|
+
* adapter, an `object-form` with inline `customFields` needs none, a
|
|
248
|
+
* `detail-view` handed a `data` record needs none. A predicate guessed HERE
|
|
249
|
+
* would paint a configuration error over a block that is working, which is a
|
|
250
|
+
* worse failure than the silence this replaces — so the call site, which
|
|
251
|
+
* knows its own fallbacks, states it.
|
|
252
|
+
*/
|
|
253
|
+
requiresDataSource?: boolean;
|
|
254
|
+
/** Explanation for the no-adapter panel; see {@link noDataSourceMessage}. */
|
|
255
|
+
noDataSourceMessage?: string;
|
|
183
256
|
/**
|
|
184
257
|
* Renders the block with the bound schema. Called during the gate's own
|
|
185
258
|
* render, so it must RETURN AN ELEMENT and never call hooks itself — the
|
|
@@ -196,4 +269,4 @@ export interface ElementDataSourceGateProps<S> {
|
|
|
196
269
|
* against a half-resolved query is how a page shows a wider answer than the one
|
|
197
270
|
* that was authored.
|
|
198
271
|
*/
|
|
199
|
-
export declare function ElementDataSourceGate<S>({ schema, mapping, dataSource, testId, errorTitle, children, }: ElementDataSourceGateProps<S>): React.ReactElement | null;
|
|
272
|
+
export declare function ElementDataSourceGate<S>({ schema, mapping, dataSource, testId, errorTitle, requiresDataSource, noDataSourceMessage: noDataSourceText, children, }: ElementDataSourceGateProps<S>): React.ReactElement | null;
|
|
@@ -60,6 +60,17 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
60
60
|
* therefore leaves `limit` unmapped rather than parking it somewhere plausible,
|
|
61
61
|
* and the gap is recorded at the call site instead of being papered over here.
|
|
62
62
|
*
|
|
63
|
+
* ## A block with no ADAPTER fails loudly too (objectui#5378)
|
|
64
|
+
*
|
|
65
|
+
* The section below is about an unresolvable view NAME. The coarser failure —
|
|
66
|
+
* no runtime adapter reached the block at all — used to render an empty shell
|
|
67
|
+
* in exactly the same silence, and that is what `requiresDataSource` +
|
|
68
|
+
* {@link NoDataSourcePanel} now report. Measured on the published
|
|
69
|
+
* getting-started guide: all five of its blocks resolved no adapter, fetched
|
|
70
|
+
* nothing, and said nothing. The two panels are deliberately the same shape;
|
|
71
|
+
* they answer "which view?" and "which data source?", and an author who sees
|
|
72
|
+
* either one is being told where to look.
|
|
73
|
+
*
|
|
63
74
|
* ## An unresolvable `view` fails loudly
|
|
64
75
|
*
|
|
65
76
|
* When the named view does not exist the gate renders a configuration error
|
|
@@ -70,7 +81,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
70
81
|
*/
|
|
71
82
|
import * as React from 'react';
|
|
72
83
|
import { isElementDataSourceConfig, mergeFilterNodes, } from '@object-ui/core';
|
|
73
|
-
import { useElementDataSource, } from '../hooks/useElementDataSource';
|
|
84
|
+
import { useElementDataSource, } from '../hooks/useElementDataSource.js';
|
|
85
|
+
import { SchemaRendererContext } from '../context/SchemaRendererContext.js';
|
|
74
86
|
const readLimit = (base, key) => {
|
|
75
87
|
if (key === 'pagination.pageSize')
|
|
76
88
|
return base.pagination?.pageSize;
|
|
@@ -174,6 +186,64 @@ export function ElementDataSourceErrorPanel({ testId, title = 'This component’
|
|
|
174
186
|
export function ElementDataSourceLoadingPanel({ testId, }) {
|
|
175
187
|
return (_jsx("div", { className: "p-2 text-sm text-muted-foreground animate-pulse", role: "status", "aria-live": "polite", "data-testid": `${testId}-resolving-view`, children: "Loading view\u2026" }));
|
|
176
188
|
}
|
|
189
|
+
/**
|
|
190
|
+
* The adapter this block would use, resolved the one way every block in the
|
|
191
|
+
* family resolves it: an explicit value first, the `SchemaRendererProvider`
|
|
192
|
+
* context second.
|
|
193
|
+
*
|
|
194
|
+
* The `isElementDataSourceConfig` guard is the same one
|
|
195
|
+
* {@link useElementDataSourceSchema} applies — a host handing us the spec
|
|
196
|
+
* BINDING under this name is handing us metadata, not an adapter, and counting
|
|
197
|
+
* it as one is how "no data source" reads as "resolved".
|
|
198
|
+
*
|
|
199
|
+
* `T` is what the CALLER expects to have been injected, and the assertion is
|
|
200
|
+
* the caller's, not this hook's: an adapter arrives here as a `dataSource` prop
|
|
201
|
+
* or a context value that nothing in this package can narrow, so the type has
|
|
202
|
+
* to be declared by whoever knows which interface their block consumes. Leave
|
|
203
|
+
* it off and you get `unknown`, which is the honest default.
|
|
204
|
+
*/
|
|
205
|
+
export function useResolvedDataSource(explicit) {
|
|
206
|
+
const context = React.useContext(SchemaRendererContext);
|
|
207
|
+
const named = isElementDataSourceConfig(explicit) ? undefined : explicit;
|
|
208
|
+
return (named ?? context?.dataSource ?? undefined);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* The author-facing sentence a block with no adapter says out loud.
|
|
212
|
+
*
|
|
213
|
+
* One wording for the whole object-bound family, because the reader's mistake
|
|
214
|
+
* is the same one every time and the fix is the same one every time: the
|
|
215
|
+
* adapter is injected by an ANCESTOR (`SchemaRendererProvider`), so nothing
|
|
216
|
+
* written on the block itself can supply it. Naming the block and the object
|
|
217
|
+
* it was about to read is what turns "nothing happened" into an address.
|
|
218
|
+
*/
|
|
219
|
+
export function noDataSourceMessage(blockKey, objectName) {
|
|
220
|
+
const target = typeof objectName === 'string' && objectName.trim().length > 0
|
|
221
|
+
? `“${objectName}”`
|
|
222
|
+
: 'its object';
|
|
223
|
+
return (`“${blockKey}” has no data source, so it cannot read ${target}. ` +
|
|
224
|
+
`Inject one from an ancestor — <SchemaRendererProvider dataSource={…}> from ` +
|
|
225
|
+
`@object-ui/react — or hand this block a dataSource of its own.`);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* The "this block resolved no adapter" panel — objectui#5378 item 2, and the
|
|
229
|
+
* #5349 shape it belongs to.
|
|
230
|
+
*
|
|
231
|
+
* Until this existed, a block that resolved no adapter rendered an EMPTY SHELL:
|
|
232
|
+
* `object-grid` painted a header-only table, `object-form` a field-less card,
|
|
233
|
+
* `detail-view` nothing at all — no error, no warning, no empty state that
|
|
234
|
+
* explained itself. Measured on the published getting-started guide
|
|
235
|
+
* (`content/docs/guide/building-crud-app.md`), every one of its five blocks was
|
|
236
|
+
* in that state and the page reported success while fetching nothing.
|
|
237
|
+
*
|
|
238
|
+
* The empty shell is the defect, not a side effect of it: it is indistinguishable
|
|
239
|
+
* from "the query ran and matched no rows", which is the reading an author (very
|
|
240
|
+
* often an AI author) takes, and it points at the DATA when the fault is in the
|
|
241
|
+
* WIRING one level up. Same posture as {@link ElementDataSourceErrorPanel}: the
|
|
242
|
+
* metadata is answerable, so the answer is rendered where the author can see it.
|
|
243
|
+
*/
|
|
244
|
+
export function NoDataSourcePanel({ testId, title = 'No data source resolved', message, }) {
|
|
245
|
+
return (_jsxs("div", { className: "p-4 border border-red-500 rounded text-red-500 bg-red-50 my-2", role: "alert", "data-testid": `${testId}-no-data-source`, children: [_jsx("p", { className: "font-medium", children: title }), message ? _jsx("p", { className: "text-sm mt-1", children: message }) : null] }));
|
|
246
|
+
}
|
|
177
247
|
/**
|
|
178
248
|
* Wrap an object-bound block so its `dataSource` binding reaches the keys it
|
|
179
249
|
* reads, and so the two non-final resolution states render once, the same way,
|
|
@@ -183,8 +253,17 @@ export function ElementDataSourceLoadingPanel({ testId, }) {
|
|
|
183
253
|
* against a half-resolved query is how a page shows a wider answer than the one
|
|
184
254
|
* that was authored.
|
|
185
255
|
*/
|
|
186
|
-
export function ElementDataSourceGate({ schema, mapping, dataSource, testId, errorTitle, children, }) {
|
|
256
|
+
export function ElementDataSourceGate({ schema, mapping, dataSource, testId, errorTitle, requiresDataSource, noDataSourceMessage: noDataSourceText, children, }) {
|
|
187
257
|
const bound = useElementDataSourceSchema(schema, mapping, dataSource);
|
|
258
|
+
const adapter = useResolvedDataSource(dataSource);
|
|
259
|
+
// Reported BEFORE the view state, because with no adapter every downstream
|
|
260
|
+
// answer is a symptom of this one: `useElementDataSource` cannot list the
|
|
261
|
+
// object's saved views either, so a block carrying a `view` would otherwise
|
|
262
|
+
// report "this data source cannot list the saved views" — true, and pointing
|
|
263
|
+
// at the view name instead of at the missing injection.
|
|
264
|
+
if (requiresDataSource && adapter == null) {
|
|
265
|
+
return _jsx(NoDataSourcePanel, { testId: testId, message: noDataSourceText });
|
|
266
|
+
}
|
|
188
267
|
if (bound.status === 'missing') {
|
|
189
268
|
return _jsx(ElementDataSourceErrorPanel, { testId: testId, title: errorTitle, message: bound.error });
|
|
190
269
|
}
|
package/dist/hooks/index.d.ts
CHANGED
|
@@ -5,38 +5,38 @@
|
|
|
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 './useExpression';
|
|
9
|
-
export * from './useFilterScope';
|
|
10
|
-
export * from './useActionRunner';
|
|
11
|
-
export * from './useNavigationOverlay';
|
|
12
|
-
export * from './usePageVariables';
|
|
13
|
-
export * from './usePageVariableActionBridge';
|
|
14
|
-
export * from './useViewData';
|
|
15
|
-
export * from './useElementDataSource';
|
|
16
|
-
export * from './useDynamicApp';
|
|
17
|
-
export * from './useDiscovery';
|
|
18
|
-
export * from './useFocusTrap';
|
|
19
|
-
export * from './useFocusManagement';
|
|
20
|
-
export * from './useKeyboardShortcuts';
|
|
21
|
-
export * from './useCrudShortcuts';
|
|
22
|
-
export * from './useReducedMotion';
|
|
23
|
-
export * from './useAnimation';
|
|
24
|
-
export * from './useDensityMode';
|
|
25
|
-
export * from './useViewSharing';
|
|
26
|
-
export * from './useClientNotifications';
|
|
27
|
-
export * from './useOffline';
|
|
28
|
-
export * from './usePerformance';
|
|
29
|
-
export * from './usePerformanceBudget';
|
|
30
|
-
export * from './usePageTransition';
|
|
31
|
-
export * from './useViewTransition';
|
|
32
|
-
export * from './useETagCache';
|
|
33
|
-
export * from './useSchemaPersistence';
|
|
34
|
-
export * from './useGlobalUndo';
|
|
35
|
-
export * from './useDebugMode';
|
|
36
|
-
export * from './useActionEngine';
|
|
37
|
-
export * from './useActionTextLocalizer';
|
|
38
|
-
export * from './useCapabilityGate';
|
|
39
|
-
export * from './useDatasetDimensionLabels';
|
|
40
|
-
export * from './useDataRefresh';
|
|
41
|
-
export * from './usePageAssignment';
|
|
42
|
-
export * from './useRecordSearch';
|
|
8
|
+
export * from './useExpression.js';
|
|
9
|
+
export * from './useFilterScope.js';
|
|
10
|
+
export * from './useActionRunner.js';
|
|
11
|
+
export * from './useNavigationOverlay.js';
|
|
12
|
+
export * from './usePageVariables.js';
|
|
13
|
+
export * from './usePageVariableActionBridge.js';
|
|
14
|
+
export * from './useViewData.js';
|
|
15
|
+
export * from './useElementDataSource.js';
|
|
16
|
+
export * from './useDynamicApp.js';
|
|
17
|
+
export * from './useDiscovery.js';
|
|
18
|
+
export * from './useFocusTrap.js';
|
|
19
|
+
export * from './useFocusManagement.js';
|
|
20
|
+
export * from './useKeyboardShortcuts.js';
|
|
21
|
+
export * from './useCrudShortcuts.js';
|
|
22
|
+
export * from './useReducedMotion.js';
|
|
23
|
+
export * from './useAnimation.js';
|
|
24
|
+
export * from './useDensityMode.js';
|
|
25
|
+
export * from './useViewSharing.js';
|
|
26
|
+
export * from './useClientNotifications.js';
|
|
27
|
+
export * from './useOffline.js';
|
|
28
|
+
export * from './usePerformance.js';
|
|
29
|
+
export * from './usePerformanceBudget.js';
|
|
30
|
+
export * from './usePageTransition.js';
|
|
31
|
+
export * from './useViewTransition.js';
|
|
32
|
+
export * from './useETagCache.js';
|
|
33
|
+
export * from './useSchemaPersistence.js';
|
|
34
|
+
export * from './useGlobalUndo.js';
|
|
35
|
+
export * from './useDebugMode.js';
|
|
36
|
+
export * from './useActionEngine.js';
|
|
37
|
+
export * from './useActionTextLocalizer.js';
|
|
38
|
+
export * from './useCapabilityGate.js';
|
|
39
|
+
export * from './useDatasetDimensionLabels.js';
|
|
40
|
+
export * from './useDataRefresh.js';
|
|
41
|
+
export * from './usePageAssignment.js';
|
|
42
|
+
export * from './useRecordSearch.js';
|
package/dist/hooks/index.js
CHANGED
|
@@ -5,46 +5,46 @@
|
|
|
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 './useExpression';
|
|
8
|
+
export * from './useExpression.js';
|
|
9
9
|
// Session scope for filter placeholders ({current_user_id}/{current_org_id}).
|
|
10
|
-
export * from './useFilterScope';
|
|
11
|
-
export * from './useActionRunner';
|
|
12
|
-
export * from './useNavigationOverlay';
|
|
13
|
-
export * from './usePageVariables';
|
|
14
|
-
export * from './usePageVariableActionBridge';
|
|
15
|
-
export * from './useViewData';
|
|
10
|
+
export * from './useFilterScope.js';
|
|
11
|
+
export * from './useActionRunner.js';
|
|
12
|
+
export * from './useNavigationOverlay.js';
|
|
13
|
+
export * from './usePageVariables.js';
|
|
14
|
+
export * from './usePageVariableActionBridge.js';
|
|
15
|
+
export * from './useViewData.js';
|
|
16
16
|
// PageComponentSchema.dataSource — the spec's per-element data binding.
|
|
17
|
-
export * from './useElementDataSource';
|
|
18
|
-
export * from './useDynamicApp';
|
|
19
|
-
export * from './useDiscovery';
|
|
20
|
-
export * from './useFocusTrap';
|
|
21
|
-
export * from './useFocusManagement';
|
|
22
|
-
export * from './useKeyboardShortcuts';
|
|
23
|
-
export * from './useCrudShortcuts';
|
|
24
|
-
export * from './useReducedMotion';
|
|
25
|
-
export * from './useAnimation';
|
|
26
|
-
export * from './useDensityMode';
|
|
27
|
-
export * from './useViewSharing';
|
|
28
|
-
export * from './useClientNotifications';
|
|
29
|
-
export * from './useOffline';
|
|
30
|
-
export * from './usePerformance';
|
|
31
|
-
export * from './usePerformanceBudget';
|
|
32
|
-
export * from './usePageTransition';
|
|
33
|
-
export * from './useViewTransition';
|
|
34
|
-
export * from './useETagCache';
|
|
35
|
-
export * from './useSchemaPersistence';
|
|
36
|
-
export * from './useGlobalUndo';
|
|
37
|
-
export * from './useDebugMode';
|
|
38
|
-
export * from './useActionEngine';
|
|
17
|
+
export * from './useElementDataSource.js';
|
|
18
|
+
export * from './useDynamicApp.js';
|
|
19
|
+
export * from './useDiscovery.js';
|
|
20
|
+
export * from './useFocusTrap.js';
|
|
21
|
+
export * from './useFocusManagement.js';
|
|
22
|
+
export * from './useKeyboardShortcuts.js';
|
|
23
|
+
export * from './useCrudShortcuts.js';
|
|
24
|
+
export * from './useReducedMotion.js';
|
|
25
|
+
export * from './useAnimation.js';
|
|
26
|
+
export * from './useDensityMode.js';
|
|
27
|
+
export * from './useViewSharing.js';
|
|
28
|
+
export * from './useClientNotifications.js';
|
|
29
|
+
export * from './useOffline.js';
|
|
30
|
+
export * from './usePerformance.js';
|
|
31
|
+
export * from './usePerformanceBudget.js';
|
|
32
|
+
export * from './usePageTransition.js';
|
|
33
|
+
export * from './useViewTransition.js';
|
|
34
|
+
export * from './useETagCache.js';
|
|
35
|
+
export * from './useSchemaPersistence.js';
|
|
36
|
+
export * from './useGlobalUndo.js';
|
|
37
|
+
export * from './useDebugMode.js';
|
|
38
|
+
export * from './useActionEngine.js';
|
|
39
39
|
// The ONE application of `useObjectLabel`'s action resolvers over the three
|
|
40
40
|
// authored strings an action carries (label / confirmText / successMessage).
|
|
41
|
-
export * from './useActionTextLocalizer';
|
|
42
|
-
export * from './useCapabilityGate';
|
|
41
|
+
export * from './useActionTextLocalizer.js';
|
|
42
|
+
export * from './useCapabilityGate.js';
|
|
43
43
|
// The analytics label net's React glue, consumed by BOTH plugin-dashboard's
|
|
44
44
|
// `DatasetWidget` and plugin-report's dataset block (objectui#4389). It lives
|
|
45
45
|
// here rather than in `@object-ui/core` because it reads `SchemaRendererContext`
|
|
46
46
|
// — see the file header for the measured dependency direction.
|
|
47
|
-
export * from './useDatasetDimensionLabels';
|
|
48
|
-
export * from './useDataRefresh';
|
|
49
|
-
export * from './usePageAssignment';
|
|
50
|
-
export * from './useRecordSearch';
|
|
47
|
+
export * from './useDatasetDimensionLabels.js';
|
|
48
|
+
export * from './useDataRefresh.js';
|
|
49
|
+
export * from './usePageAssignment.js';
|
|
50
|
+
export * from './useRecordSearch.js';
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*/
|
|
24
24
|
import { useCallback, useContext, useMemo } from 'react';
|
|
25
25
|
import { ActionEngine, } from '@object-ui/core';
|
|
26
|
-
import { ActionCtxReact } from '../context/ActionContext';
|
|
26
|
+
import { ActionCtxReact } from '../context/ActionContext.js';
|
|
27
27
|
export function useActionEngine(options = {}) {
|
|
28
28
|
const { actions = [], context = {} } = options;
|
|
29
29
|
// When wrapped in an <ActionProvider>, reuse its ActionRunner so that:
|
|
@@ -23,13 +23,14 @@
|
|
|
23
23
|
*
|
|
24
24
|
* **Fail-OPEN when unknown.** No runner, no user, no `systemPermissions` array:
|
|
25
25
|
* the action shows. Unknown is not denied, the server is the authority
|
|
26
|
-
* (ADR-
|
|
27
|
-
*
|
|
28
|
-
* nothing" and gates
|
|
26
|
+
* (the framework's ADR-0124 D1 — server enforces, client is courtesy), and
|
|
27
|
+
* hiding a permitted user's button on missing client data is the worse
|
|
28
|
+
* failure. An EMPTY array is not unknown — it means "holds nothing" and gates
|
|
29
|
+
* normally.
|
|
29
30
|
*/
|
|
30
31
|
import { useCallback, useContext } from 'react';
|
|
31
|
-
import { ActionCtxReact } from '../context/ActionContext';
|
|
32
|
-
import { usePredicateScope } from './useExpression';
|
|
32
|
+
import { ActionCtxReact } from '../context/ActionContext.js';
|
|
33
|
+
import { usePredicateScope } from './useExpression.js';
|
|
33
34
|
/**
|
|
34
35
|
* The caller's system capabilities, or `undefined` when the host never supplied
|
|
35
36
|
* them (→ callers must fail open).
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
* server side exists.
|
|
27
27
|
*/
|
|
28
28
|
import { useCallback, useContext, useEffect, useRef, useState } from 'react';
|
|
29
|
-
import { SchemaRendererContext } from '../context/SchemaRendererContext';
|
|
30
|
-
import { useNotifications } from '../context/NotificationContext';
|
|
29
|
+
import { SchemaRendererContext } from '../context/SchemaRendererContext.js';
|
|
30
|
+
import { useNotifications } from '../context/NotificationContext.js';
|
|
31
31
|
/* ------------------------------------------------------------------ */
|
|
32
32
|
/* Helpers */
|
|
33
33
|
/* ------------------------------------------------------------------ */
|
|
@@ -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 { useKeyboardShortcuts, } from './useKeyboardShortcuts';
|
|
9
|
+
import { useKeyboardShortcuts, } from './useKeyboardShortcuts.js';
|
|
10
10
|
/**
|
|
11
11
|
* Registers standard CRUD keyboard shortcuts using the useKeyboardShortcuts hook.
|
|
12
12
|
*
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
import { useContext, useEffect, useMemo, useState } from 'react';
|
|
62
62
|
import { deriveDimensionLabelMaps, loadDimensionFieldMeta, } from '@object-ui/core';
|
|
63
63
|
import { useSafeFieldLabel } from '@object-ui/i18n';
|
|
64
|
-
import { SchemaRendererContext } from '../context/SchemaRendererContext';
|
|
64
|
+
import { SchemaRendererContext } from '../context/SchemaRendererContext.js';
|
|
65
65
|
/**
|
|
66
66
|
* Resolve one dataset query's dimension field metadata — the LOCALE-FREE half.
|
|
67
67
|
*
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
import { useState, useEffect, useContext } from 'react';
|
|
9
|
-
import { SchemaRendererContext } from '../context/SchemaRendererContext';
|
|
9
|
+
import { SchemaRendererContext } from '../context/SchemaRendererContext.js';
|
|
10
10
|
/**
|
|
11
11
|
* The backward-compatible "can I actually use this service?" check
|
|
12
12
|
* (ADR-0076 D12 console slice):
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { useContext, useEffect, useMemo, useState } from 'react';
|
|
23
23
|
import { collectSavedViews, composeElementDataSource, elementDataSourceViewNotFoundMessage, isElementDataSourceConfig, resolveSavedView, } from '@object-ui/core';
|
|
24
|
-
import { SchemaRendererContext } from '../context/SchemaRendererContext';
|
|
24
|
+
import { SchemaRendererContext } from '../context/SchemaRendererContext.js';
|
|
25
25
|
const isRecord = (v) => !!v && typeof v === 'object';
|
|
26
26
|
/**
|
|
27
27
|
* Read every saved view for `object` from the two places they live, in the same
|
|
@@ -45,7 +45,10 @@ async function fetchSavedViews(dataSource, object) {
|
|
|
45
45
|
if (!canReadObjectDef && !canReadOverlay)
|
|
46
46
|
return { capable: false, views: {} };
|
|
47
47
|
const embedded = canReadObjectDef
|
|
48
|
-
? dataSource.getObjectSchema(object).then(
|
|
48
|
+
? dataSource.getObjectSchema(object).then(
|
|
49
|
+
// `listViews` is canonical (#5362); the snake leg is a compatibility READ for
|
|
50
|
+
// stored pre-settlement documents (never censused: objectstack#7917).
|
|
51
|
+
(def) => (isRecord(def) ? (def.listViews ?? def.list_views) : undefined), () => undefined)
|
|
49
52
|
: Promise.resolve(undefined);
|
|
50
53
|
const overlay = canReadOverlay
|
|
51
54
|
? dataSource.listViews(object).then((rows) => rows, () => undefined)
|