@object-ui/react 17.3.0 → 17.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1242 -0
- package/README.md +48 -21
- package/dist/LazyPluginLoader.d.ts +14 -7
- package/dist/LazyPluginLoader.js +14 -7
- package/dist/SchemaRenderer.d.ts +78 -5
- package/dist/SchemaRenderer.js +572 -34
- package/dist/context/AppShellContext.d.ts +21 -2
- package/dist/context/HostNavigationContext.d.ts +82 -0
- package/dist/context/HostNavigationContext.js +65 -0
- package/dist/context/RecordContext.d.ts +0 -4
- package/dist/context/RecordContext.js +0 -2
- package/dist/context/RelatedRecordActionsContext.d.ts +44 -0
- package/dist/context/RelatedRecordActionsContext.js +6 -0
- package/dist/context/index.d.ts +13 -12
- package/dist/context/index.js +13 -12
- package/dist/element-data-source/ElementDataSourceGate.d.ts +272 -0
- package/dist/element-data-source/ElementDataSourceGate.js +274 -0
- package/dist/hooks/index.d.ts +35 -32
- package/dist/hooks/index.js +42 -32
- package/dist/hooks/useActionEngine.js +1 -1
- package/dist/hooks/useActionTextLocalizer.d.ts +69 -0
- package/dist/hooks/useActionTextLocalizer.js +91 -0
- package/dist/hooks/useCapabilityGate.js +6 -5
- package/dist/hooks/useClientNotifications.js +2 -2
- package/dist/hooks/useCrudShortcuts.js +1 -1
- package/dist/hooks/useDatasetDimensionLabels.d.ts +104 -0
- package/dist/hooks/useDatasetDimensionLabels.js +154 -0
- package/dist/hooks/useDiscovery.js +1 -1
- package/dist/hooks/useElementDataSource.d.ts +68 -0
- package/dist/hooks/useElementDataSource.js +141 -0
- package/dist/hooks/useExpression.d.ts +82 -3
- package/dist/hooks/useExpression.js +121 -4
- package/dist/hooks/useNavigationOverlay.d.ts +59 -11
- package/dist/hooks/useOffline.d.ts +62 -36
- package/dist/hooks/useOffline.js +2 -2
- package/dist/hooks/usePageAssignment.js +5 -5
- package/dist/hooks/usePageTransition.js +1 -1
- package/dist/hooks/usePageVariableActionBridge.js +2 -2
- package/dist/hooks/useViewData.js +1 -1
- package/dist/hooks/useViewTransition.js +1 -1
- package/dist/index.d.ts +11 -9
- package/dist/index.js +12 -8
- package/dist/schema-input.d.ts +42 -0
- package/dist/schema-input.js +42 -0
- package/dist/spec-bridge/SpecBridge.d.ts +12 -6
- package/dist/spec-bridge/SpecBridge.js +9 -3
- package/dist/spec-bridge/bridges/form-view.d.ts +99 -23
- package/dist/spec-bridge/bridges/form-view.js +51 -7
- package/dist/spec-bridge/bridges/list-view.d.ts +1 -1
- package/dist/spec-bridge/bridges/list-view.js +143 -24
- package/dist/spec-bridge/index.d.ts +4 -4
- package/dist/spec-bridge/index.js +3 -3
- package/dist/spec-bridge/types.d.ts +24 -3
- package/dist/utils/error-message.d.ts +59 -0
- package/dist/utils/error-message.js +116 -1
- package/dist/utils/i18n.d.ts +43 -8
- package/dist/utils/i18n.js +42 -4
- package/dist/utils/unevaluatedExpression.d.ts +69 -0
- package/dist/utils/unevaluatedExpression.js +194 -0
- package/dist/utils/visibilityDiagnostic.d.ts +119 -0
- package/dist/utils/visibilityDiagnostic.js +284 -0
- package/package.json +8 -8
|
@@ -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[]>;
|
|
@@ -0,0 +1,82 @@
|
|
|
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
|
+
* HostNavigationContext — the host's own in-app navigate, offered to renderers.
|
|
10
|
+
*
|
|
11
|
+
* A published renderer package cannot navigate correctly on its own. Its only
|
|
12
|
+
* dependency-free option is a browser-level navigation
|
|
13
|
+
* (`window.location.assign`), which resolves a rooted path such as `/thanks`
|
|
14
|
+
* against the ORIGIN. Under a host mounted at a sub-path — the framework CLI
|
|
15
|
+
* configures one for every embedded deployment — that leaves the application
|
|
16
|
+
* entirely. This is objectui#4989 defect 4, and the same class as
|
|
17
|
+
* objectui#4190 / #4181.
|
|
18
|
+
*
|
|
19
|
+
* The host is the only party that knows its mount, so the host supplies the
|
|
20
|
+
* navigate. This context is that seam: **optional injection**, ruled by the
|
|
21
|
+
* maintainer on 2026-08-17. Two mechanisms were rejected in that ruling and are
|
|
22
|
+
* named here so they are not re-proposed:
|
|
23
|
+
*
|
|
24
|
+
* - **router-presence sniffing** (a renderer reading React Router's context
|
|
25
|
+
* when it happens to be there) — implicit magic, and it forces every
|
|
26
|
+
* renderer that wants mount-awareness to import `react-router`;
|
|
27
|
+
* - **a `react-router` peer requirement** — it imposes module resolution on
|
|
28
|
+
* router-less consumers. Two real ones exist in this repo (`apps/site`,
|
|
29
|
+
* `packages/plugin-view`), and a published renderer's whole property is
|
|
30
|
+
* that it drops into ANY React application.
|
|
31
|
+
*
|
|
32
|
+
* Explicit injection keeps every renderer dependency-free and puts
|
|
33
|
+
* mount-awareness on the type face where a host can see it is missing.
|
|
34
|
+
*
|
|
35
|
+
* ## Absent is a supported state, not a degraded one
|
|
36
|
+
*
|
|
37
|
+
* The default value is an empty object, so `useHostNavigation()` outside a
|
|
38
|
+
* provider answers `{ navigate: undefined }` and never throws. A consumer reads
|
|
39
|
+
* it as "no host navigate — fall back to whatever this renderer did before",
|
|
40
|
+
* which for a router-less host is already the correct behaviour: with no
|
|
41
|
+
* router there is no basename, so origin-rooted resolution is right. The seam
|
|
42
|
+
* changes what a MOUNTED host gets, and changes nothing else.
|
|
43
|
+
*
|
|
44
|
+
* ## Relationship to the other navigation seams in this package
|
|
45
|
+
*
|
|
46
|
+
* `DrillNavigationContext.openRecordList` is a SEMANTIC navigation — "show me
|
|
47
|
+
* the records behind this aggregate" — that only the console can turn into a
|
|
48
|
+
* URL. `ActionProvider`'s `onNavigate` is the ActionRunner's handler for
|
|
49
|
+
* `type: 'navigate'` actions, reachable only through an action dispatch. This
|
|
50
|
+
* one is neither: it is the plain "go to this already-resolved in-app path"
|
|
51
|
+
* that a renderer holds a destination for and cannot perform correctly itself.
|
|
52
|
+
* All three are supplied by the same shell from the same router `navigate`.
|
|
53
|
+
*/
|
|
54
|
+
import React from 'react';
|
|
55
|
+
export interface HostNavigationValue {
|
|
56
|
+
/**
|
|
57
|
+
* Navigate to an in-app path, resolved by the HOST — so a mounted host's
|
|
58
|
+
* basename is applied and the transition stays inside the application.
|
|
59
|
+
*
|
|
60
|
+
* `to` is an already-resolved, application-relative path (e.g. `/thanks`,
|
|
61
|
+
* `/apps/crm/accounts/r1`), never an absolute URL: a renderer that holds an
|
|
62
|
+
* external destination must not launder it through the host's router. It is
|
|
63
|
+
* the CALLER's job to have judged the destination — this seam performs a
|
|
64
|
+
* navigation, it does not authorize one.
|
|
65
|
+
*
|
|
66
|
+
* Absent when no host wired it; consumers fall back to their own default.
|
|
67
|
+
*/
|
|
68
|
+
navigate?: (to: string, options?: {
|
|
69
|
+
replace?: boolean;
|
|
70
|
+
}) => void;
|
|
71
|
+
}
|
|
72
|
+
export declare const HostNavigationContext: React.Context<HostNavigationValue>;
|
|
73
|
+
export declare const HostNavigationProvider: React.FC<{
|
|
74
|
+
value: HostNavigationValue;
|
|
75
|
+
children: React.ReactNode;
|
|
76
|
+
}>;
|
|
77
|
+
/**
|
|
78
|
+
* Consume the host-provided in-app navigate. Answers `{}` (i.e. `navigate`
|
|
79
|
+
* undefined) when no host wired one — that is a supported state, see the module
|
|
80
|
+
* docblock.
|
|
81
|
+
*/
|
|
82
|
+
export declare function useHostNavigation(): HostNavigationValue;
|
|
@@ -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
|
+
}
|
|
@@ -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. */
|
|
@@ -55,6 +61,21 @@ export interface RelatedRecordHandlers {
|
|
|
55
61
|
* argument is needed here (增).
|
|
56
62
|
*/
|
|
57
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;
|
|
58
79
|
/** Open an edit form for an existing child record (改). */
|
|
59
80
|
onEdit?: (recordId: string | number, record?: unknown) => void;
|
|
60
81
|
/**
|
|
@@ -92,6 +113,29 @@ export interface RelatedRecordActionsValue {
|
|
|
92
113
|
* identity.
|
|
93
114
|
*/
|
|
94
115
|
resolve: (input: ResolveRelatedRecordActionsInput) => RelatedRecordHandlers;
|
|
116
|
+
/**
|
|
117
|
+
* The host's record-detail URL for ANY object it can route to — the SAME
|
|
118
|
+
* builder {@link RelatedRecordHandlers.onView} navigates with, published as
|
|
119
|
+
* an href so a renderer can present the destination as a real anchor
|
|
120
|
+
* (middle-click, "open in new tab", "copy link address") instead of a
|
|
121
|
+
* click-only affordance.
|
|
122
|
+
*
|
|
123
|
+
* Added for objectui#4336: a lookup value is a reference to a record of a
|
|
124
|
+
* DIFFERENT object, and the renderer that displays it (`LookupCellRenderer`,
|
|
125
|
+
* shared by the detail body and the related list's cells) has no router and
|
|
126
|
+
* no knowledge of the console's route shape. Route building stays here, in
|
|
127
|
+
* the one place that already owns it — never re-derived renderer-side.
|
|
128
|
+
*
|
|
129
|
+
* Returns `null` when the host cannot route to that object (unknown object,
|
|
130
|
+
* no app context), which consumers must render as the plain value.
|
|
131
|
+
*/
|
|
132
|
+
recordHref?: (objectName: string, recordId: string | number) => string | null;
|
|
133
|
+
/**
|
|
134
|
+
* SPA-navigate to the destination {@link recordHref} addresses. Consumers
|
|
135
|
+
* call this from a plain left click on the anchor so navigation stays
|
|
136
|
+
* in-app; a modifier click is left to the browser and the href.
|
|
137
|
+
*/
|
|
138
|
+
openRecord?: (objectName: string, recordId: string | number) => void;
|
|
95
139
|
}
|
|
96
140
|
export declare const RelatedRecordActionsProvider: React.FC<{
|
|
97
141
|
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);
|
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';
|
|
@@ -0,0 +1,272 @@
|
|
|
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
|
+
* Mapping half of consuming `PageComponentSchema.dataSource` — the spec's
|
|
9
|
+
* `ElementDataSourceSchema` per-element data binding (objectstack#5576 landed
|
|
10
|
+
* the resolution; objectstack#6953 wires it to the remaining blocks).
|
|
11
|
+
*
|
|
12
|
+
* `@object-ui/core` owns the pure half (telling the binding apart from a runtime
|
|
13
|
+
* adapter, matching a view name, composing the view with the binding's own
|
|
14
|
+
* keys). {@link useElementDataSource} owns the fetch half. This module owns the
|
|
15
|
+
* LAST hop: writing the composed result onto the schema keys a given block
|
|
16
|
+
* actually reads, and rendering the two non-final states.
|
|
17
|
+
*
|
|
18
|
+
* It exists because that hop was the part every block would otherwise copy.
|
|
19
|
+
* `dataSource` is declared on EVERY page component, so eight blocks need the
|
|
20
|
+
* same precedence table (below) with only the KEY NAMES differing — which is a
|
|
21
|
+
* mapping description, not eight algorithms. A per-block copy is how the two
|
|
22
|
+
* halves drift: one block ANDs the filters and the next replaces them, and the
|
|
23
|
+
* spec's "additional filter criteria" quietly becomes two dialects.
|
|
24
|
+
*
|
|
25
|
+
* `plugin-list`'s `ListViewBlock` carried the ORIGINAL copy of the table
|
|
26
|
+
* (objectstack#5576, which predates this module) and was collapsed onto this one
|
|
27
|
+
* by objectui#4038 — on the acceptance criterion that objectstack#5576's whole
|
|
28
|
+
* suite passes against this implementation untouched, which it does. Every
|
|
29
|
+
* object-bound block now reads the rules below from here, so this is the one
|
|
30
|
+
* place a change to them belongs.
|
|
31
|
+
*
|
|
32
|
+
* ## Precedence — one table, applied everywhere
|
|
33
|
+
*
|
|
34
|
+
* Three kinds of value arrive and they do not have the same standing:
|
|
35
|
+
*
|
|
36
|
+
* - **`dataSource.*` keys are authoritative.** The author wrote them on THIS
|
|
37
|
+
* placement and the spec says the binding "overrides page-level object
|
|
38
|
+
* context", so they beat the component's own same-named key.
|
|
39
|
+
* - **View-sourced values are a baseline.** A `view` is a *reference*; a key
|
|
40
|
+
* written on the component itself is more specific than the view it points
|
|
41
|
+
* at, so the component's key wins over one the view supplied.
|
|
42
|
+
* - **`filter` never overrides — it combines.** The spec describes the
|
|
43
|
+
* binding's filter as "*Additional* filter criteria", so component filter,
|
|
44
|
+
* view filter and binding filter all AND together through
|
|
45
|
+
* {@link mergeFilterNodes}. A binding can only narrow what the view already
|
|
46
|
+
* restricts, never widen it: a mistyped per-element filter cannot expose rows
|
|
47
|
+
* the saved view excluded.
|
|
48
|
+
*
|
|
49
|
+
* An authored-but-EMPTY `columns` counts as "not authored": `[]` is what the
|
|
50
|
+
* designer emits for an unconfigured column list, and supplying the columns is
|
|
51
|
+
* exactly why a view was named.
|
|
52
|
+
*
|
|
53
|
+
* ## What a mapping may NOT do
|
|
54
|
+
*
|
|
55
|
+
* {@link ElementDataSourceMapping} names only keys the target block genuinely
|
|
56
|
+
* reads. Writing a composed key onto a schema key the block ignores would
|
|
57
|
+
* reproduce the very defect this wiring removes — a value accepted and dropped —
|
|
58
|
+
* one layer further in, where it is even harder to see. A block with no row cap
|
|
59
|
+
* therefore leaves `limit` unmapped rather than parking it somewhere plausible,
|
|
60
|
+
* and the gap is recorded at the call site instead of being papered over here.
|
|
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
|
+
*
|
|
73
|
+
* ## An unresolvable `view` fails loudly
|
|
74
|
+
*
|
|
75
|
+
* When the named view does not exist the gate renders a configuration error
|
|
76
|
+
* instead of letting the block fall back to the object's default scope. Silently
|
|
77
|
+
* widening a named view to "all records" is the failure class the binding exists
|
|
78
|
+
* to remove, and it is the one an AI-authored page hides best: the page looks
|
|
79
|
+
* like it works.
|
|
80
|
+
*/
|
|
81
|
+
import * as React from 'react';
|
|
82
|
+
import { type ElementDataSourceConfig } from '@object-ui/core';
|
|
83
|
+
import { type ElementDataSourceStatus } from '../hooks/useElementDataSource.js';
|
|
84
|
+
/**
|
|
85
|
+
* Where a block's row cap lives. Three spellings are real in this repo and each
|
|
86
|
+
* is the ONLY one its block reads: `list-view`/`object-grid` read
|
|
87
|
+
* `pagination.pageSize` (falling back to a flat `pageSize`), and
|
|
88
|
+
* `record:related_list` reads the spec's flat `limit`.
|
|
89
|
+
*/
|
|
90
|
+
export type ElementDataSourceLimitKey = 'limit' | 'pageSize' | 'pagination.pageSize';
|
|
91
|
+
/**
|
|
92
|
+
* Which schema keys a block reads, so the composed binding lands on those and
|
|
93
|
+
* nothing else.
|
|
94
|
+
*
|
|
95
|
+
* Every field is opt-IN. The default mapping writes only the object name,
|
|
96
|
+
* because that is the one key every object-bound block in this repo reads; a
|
|
97
|
+
* block that also reads a filter, a sort, a column list or a row cap says so
|
|
98
|
+
* here, naming the key it reads.
|
|
99
|
+
*/
|
|
100
|
+
export interface ElementDataSourceMapping {
|
|
101
|
+
/**
|
|
102
|
+
* Schema key the binding's `object` lands on — `'objectName'` for every
|
|
103
|
+
* object-bound block in this repo, which is the default. `false` maps nothing
|
|
104
|
+
* (for a block that reads the composed object itself, like
|
|
105
|
+
* `element:record_picker`, whose object lives under `properties`).
|
|
106
|
+
*/
|
|
107
|
+
object?: string | false;
|
|
108
|
+
/**
|
|
109
|
+
* Set when the block renders a FIELD column list a saved view can supply.
|
|
110
|
+
* Leave unset for a block whose `columns` mean something else — an
|
|
111
|
+
* `object-kanban`'s `columns` are its swimlane groups, not fields, and a
|
|
112
|
+
* view's field list written there would render a broken board.
|
|
113
|
+
*/
|
|
114
|
+
columns?: boolean;
|
|
115
|
+
/** Set when the block reads `schema.filter` as its query filter. */
|
|
116
|
+
filter?: boolean;
|
|
117
|
+
/** Set when the block reads `schema.sort` as its query ordering. */
|
|
118
|
+
sort?: boolean;
|
|
119
|
+
/** The key a row cap lands on; omit for a block that enforces no cap. */
|
|
120
|
+
limit?: ElementDataSourceLimitKey;
|
|
121
|
+
/**
|
|
122
|
+
* Set when the block renders several view kinds off `schema.viewType`
|
|
123
|
+
* (`list-view` is the only such container: its registry `inputs` enumerate
|
|
124
|
+
* grid/kanban/gallery/…, so naming a saved kanban view and rendering a grid
|
|
125
|
+
* would be a silently wrong answer).
|
|
126
|
+
*/
|
|
127
|
+
viewType?: boolean;
|
|
128
|
+
}
|
|
129
|
+
export interface UseElementDataSourceSchemaResult<S> {
|
|
130
|
+
/** Resolution state; see {@link ElementDataSourceStatus}. */
|
|
131
|
+
status: ElementDataSourceStatus;
|
|
132
|
+
/**
|
|
133
|
+
* The schema with the composed binding applied. Returned by REFERENCE when
|
|
134
|
+
* there is nothing to apply, so a block that carries no binding never sees a
|
|
135
|
+
* new schema identity (which would remount it and refetch on every render).
|
|
136
|
+
*/
|
|
137
|
+
schema: S;
|
|
138
|
+
/** The binding as authored, or `undefined` when the node carries none. */
|
|
139
|
+
config?: ElementDataSourceConfig;
|
|
140
|
+
/** Author-facing explanation, set only for `missing`. */
|
|
141
|
+
error?: string;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Resolve a block's `dataSource` binding and apply it to the block's own schema
|
|
145
|
+
* keys, per {@link ElementDataSourceMapping}.
|
|
146
|
+
*
|
|
147
|
+
* @param schema The block's schema node (its `dataSource` is read).
|
|
148
|
+
* @param mapping Which schema keys this block reads.
|
|
149
|
+
* @param dataSource Explicit adapter; falls back to `SchemaRendererContext`.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```tsx
|
|
153
|
+
* const bound = useElementDataSourceSchema(schema, { filter: true, sort: true });
|
|
154
|
+
* if (bound.status === 'missing') return <ElementDataSourceErrorPanel message={bound.error} />;
|
|
155
|
+
* return <ObjectCalendar schema={bound.schema} />;
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
export declare function useElementDataSourceSchema<S>(schema: S, mapping?: ElementDataSourceMapping, dataSource?: unknown): UseElementDataSourceSchemaResult<S>;
|
|
159
|
+
export interface ElementDataSourceStatusPanelProps {
|
|
160
|
+
/**
|
|
161
|
+
* `data-testid` stem, normally the block's registry key — the panels append
|
|
162
|
+
* `-datasource-error` / `-resolving-view`, so a test names the block it is
|
|
163
|
+
* asserting about rather than a shared anonymous id.
|
|
164
|
+
*/
|
|
165
|
+
testId: string;
|
|
166
|
+
/** Panel heading; defaults to a block-neutral sentence. */
|
|
167
|
+
title?: string;
|
|
168
|
+
/** The author-facing explanation from {@link useElementDataSourceSchema}. */
|
|
169
|
+
message?: string;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The "named view does not resolve" panel. Same posture (and shape) as
|
|
173
|
+
* `SchemaRenderer`'s "Unknown component type": authored metadata pointing at
|
|
174
|
+
* something that is not there, reported where the author can see it.
|
|
175
|
+
*/
|
|
176
|
+
export declare function ElementDataSourceErrorPanel({ testId, title, message, }: ElementDataSourceStatusPanelProps): React.ReactElement;
|
|
177
|
+
/**
|
|
178
|
+
* The "saved views are still being fetched" placeholder. Distinct from the error
|
|
179
|
+
* panel because a component that treated "not resolved yet" as "does not exist"
|
|
180
|
+
* would flash a configuration error on every mount.
|
|
181
|
+
*/
|
|
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;
|
|
228
|
+
export interface ElementDataSourceGateProps<S> {
|
|
229
|
+
/** The block's schema node. */
|
|
230
|
+
schema: S;
|
|
231
|
+
/** Which schema keys this block reads; see {@link ElementDataSourceMapping}. */
|
|
232
|
+
mapping?: ElementDataSourceMapping;
|
|
233
|
+
/** Explicit adapter; falls back to `SchemaRendererContext`. */
|
|
234
|
+
dataSource?: unknown;
|
|
235
|
+
/** `data-testid` stem for the two status panels — the block's registry key. */
|
|
236
|
+
testId: string;
|
|
237
|
+
/** Heading for the unresolvable-view panel. */
|
|
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;
|
|
256
|
+
/**
|
|
257
|
+
* Renders the block with the bound schema. Called during the gate's own
|
|
258
|
+
* render, so it must RETURN AN ELEMENT and never call hooks itself — the
|
|
259
|
+
* block's hooks belong to the block.
|
|
260
|
+
*/
|
|
261
|
+
children: (schema: S) => React.ReactElement | null;
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Wrap an object-bound block so its `dataSource` binding reaches the keys it
|
|
265
|
+
* reads, and so the two non-final resolution states render once, the same way,
|
|
266
|
+
* for every block.
|
|
267
|
+
*
|
|
268
|
+
* The block itself is not mounted while a named view is unresolved: rendering it
|
|
269
|
+
* against a half-resolved query is how a page shows a wider answer than the one
|
|
270
|
+
* that was authored.
|
|
271
|
+
*/
|
|
272
|
+
export declare function ElementDataSourceGate<S>({ schema, mapping, dataSource, testId, errorTitle, requiresDataSource, noDataSourceMessage: noDataSourceText, children, }: ElementDataSourceGateProps<S>): React.ReactElement | null;
|