@object-ui/react 17.6.0 → 18.0.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 (103) hide show
  1. package/CHANGELOG.md +4263 -0
  2. package/README.md +383 -42
  3. package/dist/SchemaRenderer.d.ts +64 -7
  4. package/dist/SchemaRenderer.js +1326 -106
  5. package/dist/context/ActionContext.js +40 -1
  6. package/dist/context/AppShellContext.d.ts +27 -0
  7. package/dist/context/DiscussionContext.d.ts +27 -0
  8. package/dist/context/DiscussionContext.js +4 -0
  9. package/dist/context/DrillNavigationContext.d.ts +13 -0
  10. package/dist/context/InlineEditContext.d.ts +151 -1
  11. package/dist/context/InlineEditContext.js +36 -2
  12. package/dist/context/PrintPageContext.d.ts +60 -0
  13. package/dist/context/PrintPageContext.js +32 -0
  14. package/dist/context/RecordContext.d.ts +48 -5
  15. package/dist/context/RecordContext.js +10 -1
  16. package/dist/context/RelatedRecordActionsContext.d.ts +72 -2
  17. package/dist/context/RelatedRecordActionsContext.js +4 -0
  18. package/dist/context/SchemaRendererContext.d.ts +61 -6
  19. package/dist/context/SchemaRendererContext.js +39 -6
  20. package/dist/context/ThemeContext.js +1 -1
  21. package/dist/context/actionRunnerTranslator.d.ts +27 -0
  22. package/dist/context/actionRunnerTranslator.js +37 -0
  23. package/dist/context/index.d.ts +1 -0
  24. package/dist/context/index.js +1 -0
  25. package/dist/element-data-source/ElementApiDataSourcePanel.d.ts +28 -0
  26. package/dist/element-data-source/ElementApiDataSourcePanel.js +10 -0
  27. package/dist/element-data-source/ElementDataSourceGate.d.ts +118 -3
  28. package/dist/element-data-source/ElementDataSourceGate.js +401 -19
  29. package/dist/element-data-source/useElementApiDataSource.d.ts +119 -0
  30. package/dist/element-data-source/useElementApiDataSource.js +198 -0
  31. package/dist/hooks/index.d.ts +3 -1
  32. package/dist/hooks/index.js +10 -1
  33. package/dist/hooks/useActionEngine.js +46 -3
  34. package/dist/hooks/useActionRunner.js +4 -0
  35. package/dist/hooks/useActionTextLocalizer.d.ts +35 -9
  36. package/dist/hooks/useActionTextLocalizer.js +70 -14
  37. package/dist/hooks/useConfigBagEvaluator.d.ts +31 -0
  38. package/dist/hooks/useConfigBagEvaluator.js +40 -0
  39. package/dist/hooks/useDatasetDimensionLabels.js +25 -10
  40. package/dist/hooks/useDensityMode.d.ts +8 -2
  41. package/dist/hooks/useDensityMode.js +8 -2
  42. package/dist/hooks/useDiscovery.d.ts +1 -10
  43. package/dist/hooks/useETagCache.js +25 -4
  44. package/dist/hooks/useElementDataSource.d.ts +15 -2
  45. package/dist/hooks/useElementDataSource.js +24 -7
  46. package/dist/hooks/useExpression.d.ts +52 -59
  47. package/dist/hooks/useExpression.js +58 -90
  48. package/dist/hooks/useFilterScope.d.ts +22 -2
  49. package/dist/hooks/useFilterScope.js +29 -4
  50. package/dist/hooks/useGlobalUndo.d.ts +10 -0
  51. package/dist/hooks/useGlobalUndo.js +112 -46
  52. package/dist/hooks/useNavigationOverlay.d.ts +76 -11
  53. package/dist/hooks/useNavigationOverlay.js +119 -9
  54. package/dist/hooks/useOffline.js +65 -11
  55. package/dist/hooks/usePageAssignment.d.ts +33 -7
  56. package/dist/hooks/usePageAssignment.js +85 -21
  57. package/dist/hooks/useRecordSearch.d.ts +34 -1
  58. package/dist/hooks/useRecordSearch.js +42 -7
  59. package/dist/hooks/useResolvedFilter.d.ts +46 -0
  60. package/dist/hooks/useResolvedFilter.js +130 -0
  61. package/dist/hooks/useSchemaPersistence.d.ts +7 -0
  62. package/dist/hooks/useSchemaPersistence.js +144 -5
  63. package/dist/hooks/useSettledSchema.d.ts +123 -0
  64. package/dist/hooks/useSettledSchema.js +186 -0
  65. package/dist/hooks/useViewData.d.ts +10 -3
  66. package/dist/hooks/useViewData.js +5 -14
  67. package/dist/index.d.ts +9 -1
  68. package/dist/index.js +48 -1
  69. package/dist/schema-input.d.ts +38 -8
  70. package/dist/schema-input.js +39 -7
  71. package/dist/utils/configBag.d.ts +68 -0
  72. package/dist/utils/configBag.js +70 -0
  73. package/dist/utils/error-message.d.ts +1 -1
  74. package/dist/utils/error-message.js +1 -1
  75. package/dist/utils/inlineAria.d.ts +61 -0
  76. package/dist/utils/inlineAria.js +70 -0
  77. package/dist/utils/localeMap.d.ts +27 -0
  78. package/dist/utils/localeMap.js +106 -0
  79. package/dist/utils/nonGridRowCeiling.d.ts +44 -0
  80. package/dist/utils/nonGridRowCeiling.js +78 -0
  81. package/dist/utils/paramsBag.d.ts +28 -0
  82. package/dist/utils/paramsBag.js +146 -0
  83. package/dist/utils/propsBagDiagnostic.d.ts +196 -0
  84. package/dist/utils/propsBagDiagnostic.js +422 -0
  85. package/dist/utils/refusedDataPropDiagnostic.d.ts +44 -0
  86. package/dist/utils/refusedDataPropDiagnostic.js +94 -0
  87. package/dist/utils/unevaluatedExpression.d.ts +44 -1
  88. package/dist/utils/unevaluatedExpression.js +92 -11
  89. package/dist/utils/visibilityDiagnostic.d.ts +282 -21
  90. package/dist/utils/visibilityDiagnostic.js +321 -46
  91. package/package.json +10 -10
  92. package/dist/hooks/useClientNotifications.d.ts +0 -47
  93. package/dist/hooks/useClientNotifications.js +0 -162
  94. package/dist/spec-bridge/SpecBridge.d.ts +0 -28
  95. package/dist/spec-bridge/SpecBridge.js +0 -56
  96. package/dist/spec-bridge/bridges/form-view.d.ts +0 -158
  97. package/dist/spec-bridge/bridges/form-view.js +0 -170
  98. package/dist/spec-bridge/bridges/list-view.d.ts +0 -20
  99. package/dist/spec-bridge/bridges/list-view.js +0 -268
  100. package/dist/spec-bridge/index.d.ts +0 -11
  101. package/dist/spec-bridge/index.js +0 -10
  102. package/dist/spec-bridge/types.d.ts +0 -55
  103. package/dist/spec-bridge/types.js +0 -8
@@ -30,6 +30,10 @@
30
30
  * it can route to, so the low-level renderers on a record page can turn a
31
31
  * lookup VALUE into a link to the referenced record without assembling a URL
32
32
  * of their own. Same provider, same builder, one route shape.
33
+ *
34
+ * Since objectui#12703 it also publishes the object-list URL builder
35
+ * ({@link RelatedRecordActionsValue.listHref} / `openList`), for a renderer
36
+ * that shows a few of an object's records and links to the rest.
33
37
  */
34
38
  import React from 'react';
35
39
  /** A child-object action surfaced in a related list's row menu. */
@@ -83,7 +87,11 @@ export interface RelatedRecordHandlers {
83
87
  * the deletion resolves.
84
88
  */
85
89
  onDelete?: (recordId: string | number, record?: unknown) => void | Promise<void>;
86
- /** Child object row-level actions (`locations: ['list_item']`), localized. */
90
+ /**
91
+ * Child object row-level actions, localized: its `list_item` actions, and
92
+ * its `record_related` ones when the list sits inside a parent record
93
+ * (objectui#11270).
94
+ */
87
95
  rowActions?: RelatedRowActionDef[];
88
96
  /** Execute one of {@link rowActions} against a specific child row. */
89
97
  onRowAction?: (action: RelatedRowActionDef, record: unknown) => void | Promise<void>;
@@ -96,8 +104,40 @@ export interface RelatedRecordHandlers {
96
104
  toolbarActions?: RelatedRowActionDef[];
97
105
  /** Execute one of {@link toolbarActions} (no row context). */
98
106
  onToolbarAction?: (action: RelatedRowActionDef) => void | Promise<void>;
107
+ /**
108
+ * Open the child object's full list page, scoped to THIS parent on the
109
+ * relationship `resolve` was handed (objectui#12741). The related list's
110
+ * "View all" link calls it, unless the block declares `showViewAll: false`.
111
+ *
112
+ * The host builds the destination because only the host knows the console's
113
+ * routes, as it does for {@link onView}. It omits the handler when no route
114
+ * it has can show that scoped list, and the link is then not drawn. One case
115
+ * is a multi-value relationship: the list route's URL filter has no
116
+ * membership operator. Another is a relationship field the viewer cannot
117
+ * read: the list route drops that condition and would show every row.
118
+ */
119
+ onViewAll?: () => void;
99
120
  }
100
- /** Input to {@link RelatedRecordActionsValue.resolve}. */
121
+ /**
122
+ * Input to {@link RelatedRecordActionsValue.resolve}.
123
+ *
124
+ * Its SHAPE says which kind of list asks (objectui#13089):
125
+ *
126
+ * - **A related list always names its `relationshipField`.** The spec's
127
+ * `record:related_list` declares it required, so a spec-valid related list
128
+ * resolves with it, with or without a resolved `parentId`.
129
+ * - **An input with no `relationshipField` (absent or empty) asks for an
130
+ * OBJECT-SCOPED list's handlers**, a list of the object's own records with
131
+ * no parent: a page `list-view` node resolves `{ objectName }` for its
132
+ * `addRecord` Add button. A host may serve such an input a create that
133
+ * links no parent, which it must not serve a related list whose parent did
134
+ * not resolve.
135
+ *
136
+ * ⚠️ The limit of reading the shape: a future consumer that resolves a list
137
+ * with no parent and no relationship, and must NOT be offered a create, cannot
138
+ * say so here. It needs an explicit member on this input, not a second reading
139
+ * of the same shape.
140
+ */
101
141
  export interface ResolveRelatedRecordActionsInput {
102
142
  /** Child object machine name (the related list's `api`). */
103
143
  objectName: string;
@@ -128,6 +168,11 @@ export interface RelatedRecordActionsValue {
128
168
  *
129
169
  * Returns `null` when the host cannot route to that object (unknown object,
130
170
  * no app context), which consumers must render as the plain value.
171
+ *
172
+ * The value is a document href, read by the browser on "open in new tab" and
173
+ * "copy link address", so it carries the host router's basename (for
174
+ * example `/_console/apps/…`), while {@link openRecord} navigates by the
175
+ * router path (objectui#12233).
131
176
  */
132
177
  recordHref?: (objectName: string, recordId: string | number) => string | null;
133
178
  /**
@@ -136,6 +181,31 @@ export interface RelatedRecordActionsValue {
136
181
  * in-app; a modifier click is left to the browser and the href.
137
182
  */
138
183
  openRecord?: (objectName: string, recordId: string | number) => void;
184
+ /**
185
+ * The host's list-page URL for an object it can route to, optionally opened
186
+ * on one of the object's saved views (objectui#12703). The list-side twin of
187
+ * {@link recordHref}: a renderer that shows a few of an object's records (the
188
+ * compact list a page draws in a narrow region) presents "View all" as a real
189
+ * anchor to this destination, without assembling a URL of its own.
190
+ *
191
+ * `viewName` names a saved view of that object, as the block binding states
192
+ * it (`dataSource.view`); absent, the destination is the object's own list.
193
+ * The destination is a view or the object, never a query: a caller does not
194
+ * encode its own filter, columns or sort into it, and the host has no
195
+ * argument to receive them.
196
+ *
197
+ * Returns `null` when the host cannot route to that object (unknown object,
198
+ * no app context); consumers then draw no link. Like {@link recordHref} it is
199
+ * a document href that carries the router's basename, while {@link openList}
200
+ * navigates by the router path.
201
+ */
202
+ listHref?: (objectName: string, viewName?: string) => string | null;
203
+ /**
204
+ * SPA-navigate to the destination {@link listHref} addresses. Consumers call
205
+ * this from a plain left click on the anchor; a modifier click is left to the
206
+ * browser and the href.
207
+ */
208
+ openList?: (objectName: string, viewName?: string) => void;
139
209
  }
140
210
  export declare const RelatedRecordActionsProvider: React.FC<{
141
211
  value: RelatedRecordActionsValue;
@@ -31,6 +31,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
31
31
  * it can route to, so the low-level renderers on a record page can turn a
32
32
  * lookup VALUE into a link to the referenced record without assembling a URL
33
33
  * of their own. Same provider, same builder, one route shape.
34
+ *
35
+ * Since objectui#12703 it also publishes the object-list URL builder
36
+ * ({@link RelatedRecordActionsValue.listHref} / `openList`), for a renderer
37
+ * that shows a few of an object's records and links to the rest.
34
38
  */
35
39
  import { createContext, useContext } from 'react';
36
40
  const RelatedRecordActionsContext = createContext(null);
@@ -1,14 +1,40 @@
1
1
  import React from 'react';
2
2
  import type { DebugFlags } from '@object-ui/core';
3
+ import type { DataSource } from '@object-ui/types';
3
4
  /**
4
- * Host-provided fetch used for `provider: 'api'` view data sources so custom
5
- * endpoints carry the same credentials (Authorization, tenant, locale headers)
6
- * as the native data channel. Optional — when absent, ApiDataSource falls back
7
- * to the bare global fetch (cookies only).
5
+ * Host-provided fetch the renderers use for their own reads of the host's API
6
+ * (a chart's metadata reads, dataset dimension labels, record-permission
7
+ * probes), so those reads carry the same credentials (Authorization, tenant,
8
+ * locale headers) as the native data channel. Optional — when absent, those
9
+ * reads fall back to the bare global fetch (cookies only). A view does not
10
+ * reach it: a view binds `provider: 'object'` or `provider: 'value'`, and a
11
+ * code-built `ApiDataSource` takes its own `fetch` in its config.
8
12
  */
9
13
  export type ApiFetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
10
14
  interface SchemaRendererContextType {
11
- dataSource: any;
15
+ /**
16
+ * The adapter the host injected, as the published `DataSource` contract
17
+ * declares it (objectui#7912).
18
+ *
19
+ * Typed, not `any`: this is the injection point for the ENTIRE renderer tree,
20
+ * and `useSchemaContext()` hands whatever is declared here to every consumer
21
+ * that reads it back. While it was `any`, a bare string passed where the
22
+ * host's adapter belongs raised nothing at compile time and failed at runtime
23
+ * on the first `find()` — the shape of a config value read from the wrong
24
+ * place. The type was never unknown: `@object-ui/types` exports `DataSource`,
25
+ * `useSettledSchema` in this same package already declares its parameter as
26
+ * `DataSource<any> | null | undefined`, and `app-shell`'s README writes
27
+ * `DataSource` against this very seam.
28
+ *
29
+ * `null | undefined` are part of the contract, not a weakening of it: a host
30
+ * renders with no adapter bound (a Studio preview, a react page before the
31
+ * AdapterProvider connects, a widget test that drives `apiFetch` alone), and
32
+ * every reader in the tree already guards for it. The union states the three
33
+ * states that actually occur and refuses everything else — a string, `{}`, a
34
+ * plain data bag, a partial adapter missing a required member. It is spelled
35
+ * exactly as `useSettledSchema` spells it, one hop away.
36
+ */
37
+ dataSource: DataSource | null | undefined;
12
38
  debug?: boolean;
13
39
  debugFlags?: DebugFlags;
14
40
  apiFetch?: ApiFetch;
@@ -17,10 +43,39 @@ declare const SchemaRendererContext: React.Context<SchemaRendererContextType | n
17
43
  export { SchemaRendererContext };
18
44
  export declare const SchemaRendererProvider: ({ children, dataSource, debug, debugFlags, apiFetch, }: {
19
45
  children: React.ReactNode;
20
- dataSource: any;
46
+ /** The host's adapter — see {@link SchemaRendererContextType.dataSource}. */
47
+ dataSource: DataSource | null | undefined;
21
48
  debug?: boolean;
22
49
  debugFlags?: DebugFlags;
23
50
  apiFetch?: ApiFetch;
24
51
  }) => React.JSX.Element;
25
52
  export declare const useSchemaContext: () => SchemaRendererContextType;
53
+ /**
54
+ * Resolve a `bind` path against the ambient predicate scope.
55
+ *
56
+ * ## What it reads, and what it deliberately does not (objectui#9308)
57
+ *
58
+ * The scope a host publishes through `PredicateScopeProvider` — the same
59
+ * channel `useCondition` / `useExpression` merge under a locally-passed
60
+ * context, and the one app-shell's `ExpressionProvider` already feeds. It is
61
+ * NOT `SchemaRendererContext.dataSource`.
62
+ *
63
+ * `dataSource` is the host's injected ADAPTER, declared as the published
64
+ * `DataSource` contract (objectui#7912). An adapter has no `users` member, no
65
+ * `value` member, no member a `bind` path names — so walking it resolved
66
+ * `undefined` for every conformant host, and the nine production readers of
67
+ * this hook have all been running their fallback (`boundData || schema.items`,
68
+ * `|| schema.nodes`, or a fall-through to their own fetch chain) ever since.
69
+ * The only hosts it answered were ones injecting a data bag through a key the
70
+ * contract says is an adapter, which has been a compile error since
71
+ * objectui#7912.
72
+ *
73
+ * Maintainer ruling 2026-09-13 (option B) points it at the channel that can
74
+ * actually answer it. No published key is added: the provider, the hook and
75
+ * the host wiring all already exist.
76
+ *
77
+ * Returns `undefined` for an absent or empty path, and for any path the scope
78
+ * does not carry — the readers' fallbacks are what run then, exactly as
79
+ * before.
80
+ */
26
81
  export declare const useDataScope: (path?: string) => any;
@@ -1,11 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useContext, useMemo } from 'react';
3
+ import { usePredicateScope } from '../hooks/useExpression.js';
3
4
  const SchemaRendererContext = createContext(null);
4
5
  export { SchemaRendererContext };
5
6
  export const SchemaRendererProvider = ({ children, dataSource, debug, debugFlags, apiFetch, }) => {
6
7
  // Nested providers (react-page, studio preview surfaces) re-wrap with their
7
8
  // own dataSource but rarely know about the host's authenticated fetch —
8
- // inherit it from the parent context so provider:'api' auth survives nesting.
9
+ // inherit it from the parent context so the host's auth survives nesting.
9
10
  const parent = useContext(SchemaRendererContext);
10
11
  const effectiveApiFetch = apiFetch ?? parent?.apiFetch;
11
12
  const value = useMemo(() => ({ dataSource, debug, debugFlags, apiFetch: effectiveApiFetch }), [dataSource, debug, debugFlags, effectiveApiFetch]);
@@ -18,13 +19,45 @@ export const useSchemaContext = () => {
18
19
  }
19
20
  return context;
20
21
  };
22
+ /**
23
+ * Resolve a `bind` path against the ambient predicate scope.
24
+ *
25
+ * ## What it reads, and what it deliberately does not (objectui#9308)
26
+ *
27
+ * The scope a host publishes through `PredicateScopeProvider` — the same
28
+ * channel `useCondition` / `useExpression` merge under a locally-passed
29
+ * context, and the one app-shell's `ExpressionProvider` already feeds. It is
30
+ * NOT `SchemaRendererContext.dataSource`.
31
+ *
32
+ * `dataSource` is the host's injected ADAPTER, declared as the published
33
+ * `DataSource` contract (objectui#7912). An adapter has no `users` member, no
34
+ * `value` member, no member a `bind` path names — so walking it resolved
35
+ * `undefined` for every conformant host, and the nine production readers of
36
+ * this hook have all been running their fallback (`boundData || schema.items`,
37
+ * `|| schema.nodes`, or a fall-through to their own fetch chain) ever since.
38
+ * The only hosts it answered were ones injecting a data bag through a key the
39
+ * contract says is an adapter, which has been a compile error since
40
+ * objectui#7912.
41
+ *
42
+ * Maintainer ruling 2026-09-13 (option B) points it at the channel that can
43
+ * actually answer it. No published key is added: the provider, the hook and
44
+ * the host wiring all already exist.
45
+ *
46
+ * Returns `undefined` for an absent or empty path, and for any path the scope
47
+ * does not carry — the readers' fallbacks are what run then, exactly as
48
+ * before.
49
+ */
21
50
  export const useDataScope = (path) => {
22
- const context = useContext(SchemaRendererContext);
23
- const dataSource = context?.dataSource;
51
+ const scope = usePredicateScope();
24
52
  if (!path)
25
53
  return undefined;
26
- if (!dataSource)
54
+ if (!scope)
27
55
  return undefined;
28
- // Simple path resolution for now. In real app might be more complex
29
- return path.split('.').reduce((acc, part) => acc && acc[part], dataSource);
56
+ // Simple path resolution for now. In real app might be more complex.
57
+ //
58
+ // The accumulator is `any` BY DECLARATION: this walk addresses arbitrary
59
+ // member names on a host-published bag, and `Record<string, any>` declares
60
+ // none of them beyond the first segment. The hook's published return type is
61
+ // unchanged — `any` before and after — so no reader of `useDataScope` moves.
62
+ return path.split('.').reduce((acc, part) => acc && acc[part], scope);
30
63
  };
@@ -131,7 +131,7 @@ export function ThemeProvider({ themes: themesInput = [], defaultTheme, defaultM
131
131
  // Apply mode class
132
132
  el.classList.remove('light', 'dark');
133
133
  el.classList.add(resolvedMode);
134
- // No favicon here: spec v17 (#3494) pruned `Theme.logo`, so the key is
134
+ // No favicon here: spec v17 (objectstack-ai/objectstack#3494) pruned `Theme.logo`, so the key is
135
135
  // stripped at parse and could never arrive. The live favicon comes from
136
136
  // operator branding (`getFaviconUrl()`), applied in the console's
137
137
  // index.html, main.tsx, and on route change.
@@ -0,0 +1,27 @@
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
+ /** What `ActionRunner.setTranslator` takes. */
9
+ type ActionRunnerTranslate = (key: string, options: {
10
+ defaultValue: string;
11
+ }) => string;
12
+ /**
13
+ * The translator both React owners of an `ActionRunner` — `<ActionProvider>`
14
+ * and `useActionRunner` — install on the runner they build, so the text the
15
+ * runner writes itself (the generic success toast an action with no
16
+ * `successMessage` ends in) renders in the session's language instead of
17
+ * English. Internal to this package; not re-exported from its entry.
18
+ *
19
+ * The function is created once per mount and reads the latest `t` when it is
20
+ * CALLED, so a language switch reaches a runner that was built before it. Both
21
+ * owners rebuild their runner only when their `context` changes, and a
22
+ * translator captured at build time would keep answering in the language that
23
+ * was active then. The identity comes from `useState`'s initializer, not from a
24
+ * memo, so nothing depends on a `useMemo` result staying put (AGENTS.md #10).
25
+ */
26
+ export declare function useActionRunnerTranslator(): ActionRunnerTranslate;
27
+ export {};
@@ -0,0 +1,37 @@
1
+ /**
2
+ * ObjectUI
3
+ * Copyright (c) 2024-present ObjectStack Inc.
4
+ *
5
+ * This source code is licensed under the MIT license found in the
6
+ * LICENSE file in the root directory of this source tree.
7
+ */
8
+ import { useEffect, useRef, useState } from 'react';
9
+ import { useObjectTranslation } from '@object-ui/i18n';
10
+ /**
11
+ * The translator both React owners of an `ActionRunner` — `<ActionProvider>`
12
+ * and `useActionRunner` — install on the runner they build, so the text the
13
+ * runner writes itself (the generic success toast an action with no
14
+ * `successMessage` ends in) renders in the session's language instead of
15
+ * English. Internal to this package; not re-exported from its entry.
16
+ *
17
+ * The function is created once per mount and reads the latest `t` when it is
18
+ * CALLED, so a language switch reaches a runner that was built before it. Both
19
+ * owners rebuild their runner only when their `context` changes, and a
20
+ * translator captured at build time would keep answering in the language that
21
+ * was active then. The identity comes from `useState`'s initializer, not from a
22
+ * memo, so nothing depends on a `useMemo` result staying put (AGENTS.md #10).
23
+ */
24
+ export function useActionRunnerTranslator() {
25
+ const { t } = useObjectTranslation();
26
+ const latest = useRef(t);
27
+ // No dependency list: after every render, the ref holds that render's `t`.
28
+ // Actions run from user events, which arrive after the commit.
29
+ useEffect(() => {
30
+ latest.current = t;
31
+ });
32
+ const [translate] = useState(() => {
33
+ const read = (key, options) => String(latest.current(key, options));
34
+ return read;
35
+ });
36
+ return translate;
37
+ }
@@ -11,3 +11,4 @@ export * from './DiscussionContext.js';
11
11
  export * from './DrillNavigationContext.js';
12
12
  export * from './HostNavigationContext.js';
13
13
  export * from './RelatedRecordActionsContext.js';
14
+ export * from './PrintPageContext.js';
@@ -11,3 +11,4 @@ export * from './DiscussionContext.js';
11
11
  export * from './DrillNavigationContext.js';
12
12
  export * from './HostNavigationContext.js';
13
13
  export * from './RelatedRecordActionsContext.js';
14
+ export * from './PrintPageContext.js';
@@ -0,0 +1,28 @@
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
+ * The error panel of a page element's API binding (objectui#12197), titled in
9
+ * the page's language.
10
+ *
11
+ * `SchemaRenderer` draws it in place of a node whose binding the spec refused
12
+ * or whose read failed. The translation hook lives HERE, not in
13
+ * `SchemaRenderer`'s body: that body runs for every node on every page, and a
14
+ * language subscription there would re-render the whole tree on a language
15
+ * change. This component mounts only on the error path, so only it subscribes.
16
+ * The panel itself is {@link ElementDataSourceErrorPanel}; the inline
17
+ * `defaultValue`s are the `en` pack's values (`pnpm check:i18n-keys`).
18
+ */
19
+ import * as React from 'react';
20
+ export interface ElementApiDataSourcePanelProps {
21
+ /** `data-testid` stem — the node's registry key. */
22
+ testId: string;
23
+ /** `refused`: the spec's schema refused the binding. `error`: the read failed. */
24
+ state: 'refused' | 'error';
25
+ /** Why — the spec's refusal, or the failed read's sentence. */
26
+ message?: string;
27
+ }
28
+ export declare function ElementApiDataSourcePanel({ testId, state, message, }: ElementApiDataSourcePanelProps): React.ReactElement;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useObjectTranslation } from '@object-ui/i18n';
3
+ import { ElementDataSourceErrorPanel } from './ElementDataSourceGate.js';
4
+ export function ElementApiDataSourcePanel({ testId, state, message, }) {
5
+ const { t } = useObjectTranslation();
6
+ const title = state === 'refused'
7
+ ? t('element.component.apiRefused', { defaultValue: 'This component’s API data source is refused' })
8
+ : t('element.component.apiUnreadable', { defaultValue: 'This component’s API data source could not be read' });
9
+ return _jsx(ElementDataSourceErrorPanel, { testId: testId, title: title, message: message });
10
+ }
@@ -50,6 +50,27 @@
50
50
  * designer emits for an unconfigured column list, and supplying the columns is
51
51
  * exactly why a view was named.
52
52
  *
53
+ * The row cap answers "did the component author one?" the same way, and for the
54
+ * same reason (objectui#9899). A cap the contract REFUSES — `pageSize: 0`,
55
+ * a negative, a fraction — is not a cap this relay may treat as the author's
56
+ * intent, so it does not suppress the one the bound view supplies. Deciding
57
+ * that question by PRESENCE is how a view's legitimate cap was dropped in
58
+ * favour of the consuming renderer's own default: measured end to end on
59
+ * `list-view` before the repair, a component carrying `pagination.pageSize: 0`
60
+ * under a view supplying `7` put `$top: 100` on the wire — the view's cap never
61
+ * reached it, so the read was WIDER than the view asked for.
62
+ *
63
+ * The VIEW's own cap can be refused too, and `@object-ui/core` drops it before
64
+ * it reaches this relay (objectui#9928). The drop is right, but it left the
65
+ * consuming renderer nothing to refuse, so it went silent — and this relay
66
+ * reports the view's refusal instead (objectui#10015). See
67
+ * {@link describeRefusedViewRowLimit} for when that message applies.
68
+ *
69
+ * The BINDING's own cap follows the same rule (objectui#10016): a `limit` the
70
+ * contract refuses is not authored, so `@object-ui/core` lets the view's cap
71
+ * through in its place, and that cap is a baseline here like any other
72
+ * view-sourced value. See {@link describeRefusedBindingRowLimit}.
73
+ *
53
74
  * ## What a mapping may NOT do
54
75
  *
55
76
  * {@link ElementDataSourceMapping} names only keys the target block genuinely
@@ -79,7 +100,7 @@
79
100
  * like it works.
80
101
  */
81
102
  import * as React from 'react';
82
- import { type ElementDataSourceConfig } from '@object-ui/core';
103
+ import { type ElementDataSourceConfig, type FilterOperatorError } from '@object-ui/core';
83
104
  import { type ElementDataSourceStatus } from '../hooks/useElementDataSource.js';
84
105
  /**
85
106
  * Where a block's row cap lives. Three spellings are real in this repo and each
@@ -139,6 +160,15 @@ export interface UseElementDataSourceSchemaResult<S> {
139
160
  config?: ElementDataSourceConfig;
140
161
  /** Author-facing explanation, set only for `missing`. */
141
162
  error?: string;
163
+ /**
164
+ * The refused filter that stopped the binding from applying — set only with
165
+ * `missing`, and `error` then carries its message (objectui#10789). Either
166
+ * merge can refuse: the view's filter with the binding's own
167
+ * ({@link useElementDataSource}), or the component's filter with that result
168
+ * (this hook). {@link ElementDataSourceGate} draws the malformed-filter
169
+ * notice from it.
170
+ */
171
+ filterRefusal?: FilterOperatorError;
142
172
  }
143
173
  /**
144
174
  * Resolve a block's `dataSource` binding and apply it to the block's own schema
@@ -163,7 +193,10 @@ export interface ElementDataSourceStatusPanelProps {
163
193
  * asserting about rather than a shared anonymous id.
164
194
  */
165
195
  testId: string;
166
- /** Panel heading; defaults to a block-neutral sentence. */
196
+ /**
197
+ * Panel heading; defaults to a block-neutral sentence read from the bundle
198
+ * (objectui#12920). A passed `title`, an empty string included, wins.
199
+ */
167
200
  title?: string;
168
201
  /** The author-facing explanation from {@link useElementDataSourceSchema}. */
169
202
  message?: string;
@@ -178,6 +211,13 @@ export declare function ElementDataSourceErrorPanel({ testId, title, message, }:
178
211
  * The "saved views are still being fetched" placeholder. Distinct from the error
179
212
  * panel because a component that treated "not resolved yet" as "does not exist"
180
213
  * would flash a configuration error on every mount.
214
+ *
215
+ * Its copy reads `common.loading` (objectui#12889), the key the rest of the
216
+ * tree reads for a bare "Loading…": it printed an English "Loading view…" on
217
+ * every locale before. `useObjectTranslation` runs only while a view resolves,
218
+ * for the reason {@link ElementDataSourceMalformedFilterPanel} gives, and the
219
+ * inline `defaultValue` is the `en` pack's value, held byte-identical to it by
220
+ * `pnpm check:i18n-keys`.
181
221
  */
182
222
  export declare function ElementDataSourceLoadingPanel({ testId, }: Pick<ElementDataSourceStatusPanelProps, 'testId'>): React.ReactElement;
183
223
  /**
@@ -225,6 +265,63 @@ export declare function noDataSourceMessage(blockKey: string, objectName?: unkno
225
265
  * metadata is answerable, so the answer is rendered where the author can see it.
226
266
  */
227
267
  export declare function NoDataSourcePanel({ testId, title, message, }: ElementDataSourceStatusPanelProps): React.ReactElement;
268
+ /**
269
+ * The wrapping seam — mark `renderer` as one that wraps {@link
270
+ * ElementDataSourceGate}, so its registrations DECLARE the `dataSource` key this
271
+ * gate READS (objectui#6678).
272
+ *
273
+ * ```tsx
274
+ * export const ObjectMapRenderer = elementDataSourceBlock<React.FC<any>>(({ schema }) => (
275
+ * <ElementDataSourceGate schema={schema} testId="object-map" …>
276
+ * {(bound) => <ObjectMap schema={bound} />}
277
+ * </ElementDataSourceGate>
278
+ * ));
279
+ * ```
280
+ *
281
+ * ## What this closes
282
+ *
283
+ * `PageComponentSchema.dataSource` is the one spelling that resolves a saved
284
+ * view for an object-bound block. It works — and because no registration
285
+ * declared it, `sdui-parser`'s `validateTree` reported it with the SAME
286
+ * `unknown-prop` warning it gives the spellings that do nothing. On the tier
287
+ * built to accept AI-authored pages the diagnostic IS the contract, so the
288
+ * tier's only signal pointed away from the one key that works.
289
+ *
290
+ * ## Why a seam and not nine declarations
291
+ *
292
+ * Maintainer ruling, 2026-08-29: option B **in the injection form**. Nine
293
+ * hand-written copies across nine packages is the shape that ruling refused —
294
+ * they drift, and the tenth block forgets. Passing through here is the only
295
+ * thing a block does; `Registry.register` emits the declaration
296
+ * (`withElementDataSourceInput`), and `ELEMENT_DATA_SOURCE_INPUT` in
297
+ * `@object-ui/core` is its single copy, beside the binding's own semantics.
298
+ *
299
+ * ## And it cannot be forgotten
300
+ *
301
+ * `scripts/check-element-data-source-declaration.mjs` fails any source that
302
+ * renders `ElementDataSourceGate` for a registration without passing that
303
+ * registration's renderer through this function. That is the mechanical half of
304
+ * "a tenth block gets it automatically"; this function is the seam it enforces.
305
+ *
306
+ * Returns the renderer UNCHANGED (the mark is held in a `WeakSet`), so it is safe
307
+ * over a `React.forwardRef` object, over `React.memo`, and over a component that
308
+ * something else already re-exports by reference.
309
+ *
310
+ * ⚠️ Re-exported from `@object-ui/core` — where the marker and the declaration
311
+ * live — rather than wrapped here: ONE function under ONE name. This export
312
+ * exists for DISCOVERABILITY, so the seam is findable beside the gate it is
313
+ * about; **call sites must import it from `@object-ui/core`**, and
314
+ * `check:element-data-source-declaration` enforces that.
315
+ *
316
+ * The rule is measured, not stylistic. A registration runs at MODULE SCOPE, 101
317
+ * suites in this repo partially mock `@object-ui/react` by hand-listing the
318
+ * exports they return, and a module-scope read of a name absent from such a list
319
+ * throws at COLLECTION time — the importing test file dies before running one
320
+ * assertion. Taking the seam from here reddened 17 files across all four CI
321
+ * shards with zero failed assertions among them. Nothing mocks `@object-ui/core`,
322
+ * and every registration module already imports `ComponentRegistry` from it.
323
+ */
324
+ export { elementDataSourceBlock } from '@object-ui/core';
228
325
  export interface ElementDataSourceGateProps<S> {
229
326
  /** The block's schema node. */
230
327
  schema: S;
@@ -253,6 +350,24 @@ export interface ElementDataSourceGateProps<S> {
253
350
  requiresDataSource?: boolean;
254
351
  /** Explanation for the no-adapter panel; see {@link noDataSourceMessage}. */
255
352
  noDataSourceMessage?: string;
353
+ /**
354
+ * Whether THIS placement has nothing to show unless it names an object — when
355
+ * true and the bound schema names none on the mapping's object key (neither
356
+ * the node's own key nor `dataSource.object` landing on it), the gate renders
357
+ * the "no object named" hint instead of the block (objectui#11605).
358
+ *
359
+ * Read AFTER the binding is applied, so a node bound by `dataSource.object`
360
+ * is never told it names no object; and AFTER the view states, so a binding
361
+ * that is still resolving or failed to resolve keeps its own panel.
362
+ *
363
+ * The call site states it, for the reason {@link requiresDataSource} gives:
364
+ * the other record sources a block can draw from (inline `data`, inline
365
+ * `customFields`, a `dataset`, a `bind` path) are each block's own, and a
366
+ * predicate guessed here would paint a configuration hint over a block that
367
+ * is working. A mapping whose `object` is `false` lands no object key, so
368
+ * this prop does nothing there.
369
+ */
370
+ requiresObject?: boolean;
256
371
  /**
257
372
  * Renders the block with the bound schema. Called during the gate's own
258
373
  * render, so it must RETURN AN ELEMENT and never call hooks itself — the
@@ -269,4 +384,4 @@ export interface ElementDataSourceGateProps<S> {
269
384
  * against a half-resolved query is how a page shows a wider answer than the one
270
385
  * that was authored.
271
386
  */
272
- export declare function ElementDataSourceGate<S>({ schema, mapping, dataSource, testId, errorTitle, requiresDataSource, noDataSourceMessage: noDataSourceText, children, }: ElementDataSourceGateProps<S>): React.ReactElement | null;
387
+ export declare function ElementDataSourceGate<S>({ schema, mapping, dataSource, testId, errorTitle, requiresDataSource, noDataSourceMessage: noDataSourceText, requiresObject, children, }: ElementDataSourceGateProps<S>): React.ReactElement | null;