@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.
Files changed (48) hide show
  1. package/CHANGELOG.md +334 -0
  2. package/README.md +9 -8
  3. package/dist/LazyPluginLoader.d.ts +14 -7
  4. package/dist/LazyPluginLoader.js +14 -7
  5. package/dist/SchemaRenderer.js +434 -22
  6. package/dist/context/HostNavigationContext.d.ts +82 -0
  7. package/dist/context/HostNavigationContext.js +65 -0
  8. package/dist/context/RelatedRecordActionsContext.d.ts +15 -0
  9. package/dist/context/index.d.ts +13 -12
  10. package/dist/context/index.js +13 -12
  11. package/dist/element-data-source/ElementDataSourceGate.d.ts +75 -2
  12. package/dist/element-data-source/ElementDataSourceGate.js +81 -2
  13. package/dist/hooks/index.d.ts +35 -35
  14. package/dist/hooks/index.js +35 -35
  15. package/dist/hooks/useActionEngine.js +1 -1
  16. package/dist/hooks/useCapabilityGate.js +6 -5
  17. package/dist/hooks/useClientNotifications.js +2 -2
  18. package/dist/hooks/useCrudShortcuts.js +1 -1
  19. package/dist/hooks/useDatasetDimensionLabels.js +1 -1
  20. package/dist/hooks/useDiscovery.js +1 -1
  21. package/dist/hooks/useElementDataSource.js +5 -2
  22. package/dist/hooks/useExpression.d.ts +24 -3
  23. package/dist/hooks/useExpression.js +56 -4
  24. package/dist/hooks/useNavigationOverlay.d.ts +1 -1
  25. package/dist/hooks/usePageAssignment.js +1 -1
  26. package/dist/hooks/usePageTransition.js +1 -1
  27. package/dist/hooks/usePageVariableActionBridge.js +2 -2
  28. package/dist/hooks/useViewData.js +1 -1
  29. package/dist/hooks/useViewTransition.js +1 -1
  30. package/dist/index.d.ts +11 -11
  31. package/dist/index.js +10 -10
  32. package/dist/schema-input.d.ts +16 -5
  33. package/dist/schema-input.js +15 -4
  34. package/dist/spec-bridge/SpecBridge.d.ts +1 -1
  35. package/dist/spec-bridge/SpecBridge.js +2 -2
  36. package/dist/spec-bridge/bridges/form-view.d.ts +99 -23
  37. package/dist/spec-bridge/bridges/form-view.js +51 -7
  38. package/dist/spec-bridge/bridges/list-view.d.ts +1 -1
  39. package/dist/spec-bridge/bridges/list-view.js +37 -10
  40. package/dist/spec-bridge/index.d.ts +4 -4
  41. package/dist/spec-bridge/index.js +3 -3
  42. package/dist/utils/error-message.d.ts +59 -0
  43. package/dist/utils/error-message.js +116 -1
  44. package/dist/utils/unevaluatedExpression.d.ts +69 -0
  45. package/dist/utils/unevaluatedExpression.js +194 -0
  46. package/dist/utils/visibilityDiagnostic.d.ts +119 -0
  47. package/dist/utils/visibilityDiagnostic.js +284 -0
  48. 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
  /**
@@ -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 './RelatedRecordActionsContext';
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';
@@ -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 './RelatedRecordActionsContext';
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
  }
@@ -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';
@@ -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-0057 D10), and hiding a permitted user's button on missing client data
27
- * is the worse failure. An EMPTY array is not unknown — it means "holds
28
- * nothing" and gates normally.
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((def) => (isRecord(def) ? (def.listViews ?? def.list_views) : undefined), () => undefined)
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)