@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.
Files changed (62) hide show
  1. package/CHANGELOG.md +1242 -0
  2. package/README.md +48 -21
  3. package/dist/LazyPluginLoader.d.ts +14 -7
  4. package/dist/LazyPluginLoader.js +14 -7
  5. package/dist/SchemaRenderer.d.ts +78 -5
  6. package/dist/SchemaRenderer.js +572 -34
  7. package/dist/context/AppShellContext.d.ts +21 -2
  8. package/dist/context/HostNavigationContext.d.ts +82 -0
  9. package/dist/context/HostNavigationContext.js +65 -0
  10. package/dist/context/RecordContext.d.ts +0 -4
  11. package/dist/context/RecordContext.js +0 -2
  12. package/dist/context/RelatedRecordActionsContext.d.ts +44 -0
  13. package/dist/context/RelatedRecordActionsContext.js +6 -0
  14. package/dist/context/index.d.ts +13 -12
  15. package/dist/context/index.js +13 -12
  16. package/dist/element-data-source/ElementDataSourceGate.d.ts +272 -0
  17. package/dist/element-data-source/ElementDataSourceGate.js +274 -0
  18. package/dist/hooks/index.d.ts +35 -32
  19. package/dist/hooks/index.js +42 -32
  20. package/dist/hooks/useActionEngine.js +1 -1
  21. package/dist/hooks/useActionTextLocalizer.d.ts +69 -0
  22. package/dist/hooks/useActionTextLocalizer.js +91 -0
  23. package/dist/hooks/useCapabilityGate.js +6 -5
  24. package/dist/hooks/useClientNotifications.js +2 -2
  25. package/dist/hooks/useCrudShortcuts.js +1 -1
  26. package/dist/hooks/useDatasetDimensionLabels.d.ts +104 -0
  27. package/dist/hooks/useDatasetDimensionLabels.js +154 -0
  28. package/dist/hooks/useDiscovery.js +1 -1
  29. package/dist/hooks/useElementDataSource.d.ts +68 -0
  30. package/dist/hooks/useElementDataSource.js +141 -0
  31. package/dist/hooks/useExpression.d.ts +82 -3
  32. package/dist/hooks/useExpression.js +121 -4
  33. package/dist/hooks/useNavigationOverlay.d.ts +59 -11
  34. package/dist/hooks/useOffline.d.ts +62 -36
  35. package/dist/hooks/useOffline.js +2 -2
  36. package/dist/hooks/usePageAssignment.js +5 -5
  37. package/dist/hooks/usePageTransition.js +1 -1
  38. package/dist/hooks/usePageVariableActionBridge.js +2 -2
  39. package/dist/hooks/useViewData.js +1 -1
  40. package/dist/hooks/useViewTransition.js +1 -1
  41. package/dist/index.d.ts +11 -9
  42. package/dist/index.js +12 -8
  43. package/dist/schema-input.d.ts +42 -0
  44. package/dist/schema-input.js +42 -0
  45. package/dist/spec-bridge/SpecBridge.d.ts +12 -6
  46. package/dist/spec-bridge/SpecBridge.js +9 -3
  47. package/dist/spec-bridge/bridges/form-view.d.ts +99 -23
  48. package/dist/spec-bridge/bridges/form-view.js +51 -7
  49. package/dist/spec-bridge/bridges/list-view.d.ts +1 -1
  50. package/dist/spec-bridge/bridges/list-view.js +143 -24
  51. package/dist/spec-bridge/index.d.ts +4 -4
  52. package/dist/spec-bridge/index.js +3 -3
  53. package/dist/spec-bridge/types.d.ts +24 -3
  54. package/dist/utils/error-message.d.ts +59 -0
  55. package/dist/utils/error-message.js +116 -1
  56. package/dist/utils/i18n.d.ts +43 -8
  57. package/dist/utils/i18n.js +42 -4
  58. package/dist/utils/unevaluatedExpression.d.ts +69 -0
  59. package/dist/utils/unevaluatedExpression.js +194 -0
  60. package/dist/utils/visibilityDiagnostic.d.ts +119 -0
  61. package/dist/utils/visibilityDiagnostic.js +284 -0
  62. package/package.json +8 -8
package/README.md CHANGED
@@ -121,18 +121,56 @@ function MyComponent() {
121
121
  }
122
122
  ```
123
123
 
124
- ### useRegistry
124
+ ### useElementDataSourceSchema / ElementDataSourceGate
125
125
 
126
- Access the component registry:
126
+ Consume `PageComponentSchema.dataSource` — the spec's per-element data binding
127
+ (`{ object, view?, filter?, sort?, limit? }`) — in a block that has its own key
128
+ names. `useElementDataSource` resolves the binding (fetching the object's saved
129
+ views so `view` can be matched); these two apply the composed result to the
130
+ block's schema and render the two non-final states.
127
131
 
128
132
  ```tsx
129
- import { useRegistry } from '@object-ui/react'
133
+ import { ElementDataSourceGate } from '@object-ui/react'
134
+
135
+ // `mapping` names ONLY the keys this block reads. A composed value written onto
136
+ // a key the block ignores would be accepted and silently dropped — the defect
137
+ // the binding exists to remove.
138
+ const OBJECT_GRID_BINDING = {
139
+ columns: true, // the view's FIELD list may fill `schema.columns`
140
+ filter: true, // AND-combined, never replaced ("additional criteria")
141
+ sort: true,
142
+ limit: 'pagination.pageSize' as const,
143
+ }
130
144
 
131
- function MyComponent() {
132
- const registry = useRegistry()
133
- const Component = registry.get('button')
134
-
135
- return <Component {...props} />
145
+ const ObjectGridRenderer = ({ schema, ...props }) => (
146
+ <ElementDataSourceGate schema={schema} mapping={OBJECT_GRID_BINDING} testId="object-grid">
147
+ {(bound) => <ObjectGrid schema={bound} {...props} />}
148
+ </ElementDataSourceGate>
149
+ )
150
+ ```
151
+
152
+ `object` lands on `objectName` by default (pass `object: 'apiName'` for another
153
+ key, or `object: false` for a block that reads the composed binding itself).
154
+ Precedence: binding keys beat the component's own, view-sourced values are only a
155
+ baseline the component's own key overrides, and `filter` AND-combines all three.
156
+ A `view` name that does not resolve renders a configuration error rather than
157
+ falling back to the object's full scope. Use `useElementDataSourceSchema` (plus
158
+ the exported `ElementDataSourceErrorPanel` / `ElementDataSourceLoadingPanel`) when
159
+ a block cannot be wrapped — a renderer whose hooks must run before the panels.
160
+
161
+ ### ComponentRegistry
162
+
163
+ There is no registry hook: the registry is a process-level singleton exported
164
+ by `@object-ui/core`, so read it directly. Subscribe to it only when a lazily
165
+ registered plugin must trigger a re-render.
166
+
167
+ ```tsx
168
+ import { ComponentRegistry } from '@object-ui/core'
169
+
170
+ function MyComponent(props: Record<string, unknown>) {
171
+ const Component = ComponentRegistry.get('button')
172
+
173
+ return Component ? <Component {...props} /> : null
136
174
  }
137
175
  ```
138
176
 
@@ -225,22 +263,11 @@ leaves the surface on its own anchor), `visibleNotificationStack` (`maxVisible`
225
263
 
226
264
  ## API Reference
227
265
 
228
- See [full documentation](https://objectui.org/api/react) for detailed API reference.
229
-
230
- <!-- release-metadata:v3.3.0 -->
231
-
232
- ## Compatibility
233
-
234
- - **React:** 18.x or 19.x
235
- - **Node.js:** ≥ 18
236
- - **TypeScript:** ≥ 5.0 (strict mode)
237
- - **`@objectstack/spec`:** ^3.3.0
238
- - **`@objectstack/client`:** ^3.3.0
239
- - **Tailwind CSS:** ≥ 3.4 (for packages with UI)
266
+ See [full documentation](https://objectui.org/docs/core/schema-renderer) for detailed API reference.
240
267
 
241
268
  ## Links
242
269
 
243
- - 📚 [Documentation](https://www.objectui.org/docs/packages/react)
270
+ - 📚 [Documentation](https://www.objectui.org/docs/core/schema-renderer)
244
271
  - 📦 [npm package](https://www.npmjs.com/package/@object-ui/react)
245
272
  - 📝 [Changelog](./CHANGELOG.md)
246
273
  - 🐛 [Report an issue](https://github.com/objectstack-ai/objectui/issues)
@@ -30,26 +30,33 @@ export interface LazyPluginOptions {
30
30
  /**
31
31
  * Create a lazy-loaded plugin component with Suspense wrapper
32
32
  *
33
- * @param importFn - Dynamic import function that returns a module with default export
33
+ * @param importFn - Dynamic import function resolving to a module whose
34
+ * `default` is the component to render. ObjectUI plugin packages export
35
+ * their components by NAME and have no `default`, so a bare
36
+ * `() => import('@object-ui/plugin-<x>')` does NOT satisfy this — it hands
37
+ * over the module namespace object, which is neither a component at runtime
38
+ * nor assignable here. Name the component you want, as the examples do.
34
39
  * @param options - Configuration options for the lazy plugin
35
40
  * @returns A component that lazy loads the plugin
36
41
  *
37
42
  * @example
38
43
  * ```tsx
39
- * // Basic usage
44
+ * // Basic usage. `@object-ui/plugin-grid` has no default export, so unwrap the
45
+ * // named component. Use the `async` form: the `.then((m) => ({ default: m.X }))`
46
+ * // spelling infers `P` as `never` on one branch of the `then` overload.
40
47
  * const ObjectGrid = createLazyPlugin(
41
- * () => import('@object-ui/plugin-grid')
48
+ * async () => ({ default: (await import('@object-ui/plugin-grid')).ObjectGrid })
42
49
  * );
43
50
  *
44
51
  * // With custom fallback
45
- * const ObjectGrid = createLazyPlugin(
46
- * () => import('@object-ui/plugin-grid'),
52
+ * const ObjectGridWithFallback = createLazyPlugin(
53
+ * async () => ({ default: (await import('@object-ui/plugin-grid')).ObjectGrid }),
47
54
  * { fallback: <div>Loading grid...</div> }
48
55
  * );
49
56
  *
50
57
  * // With retry and error handling
51
- * const ObjectGrid = createLazyPlugin(
52
- * () => import('@object-ui/plugin-grid'),
58
+ * const ObjectGridWithRetry = createLazyPlugin(
59
+ * async () => ({ default: (await import('@object-ui/plugin-grid')).ObjectGrid }),
53
60
  * {
54
61
  * retries: 3,
55
62
  * errorFallback: ({ error, retry }) => (
@@ -58,26 +58,33 @@ function createRetryImport(importFn, retries, retryDelay) {
58
58
  /**
59
59
  * Create a lazy-loaded plugin component with Suspense wrapper
60
60
  *
61
- * @param importFn - Dynamic import function that returns a module with default export
61
+ * @param importFn - Dynamic import function resolving to a module whose
62
+ * `default` is the component to render. ObjectUI plugin packages export
63
+ * their components by NAME and have no `default`, so a bare
64
+ * `() => import('@object-ui/plugin-<x>')` does NOT satisfy this — it hands
65
+ * over the module namespace object, which is neither a component at runtime
66
+ * nor assignable here. Name the component you want, as the examples do.
62
67
  * @param options - Configuration options for the lazy plugin
63
68
  * @returns A component that lazy loads the plugin
64
69
  *
65
70
  * @example
66
71
  * ```tsx
67
- * // Basic usage
72
+ * // Basic usage. `@object-ui/plugin-grid` has no default export, so unwrap the
73
+ * // named component. Use the `async` form: the `.then((m) => ({ default: m.X }))`
74
+ * // spelling infers `P` as `never` on one branch of the `then` overload.
68
75
  * const ObjectGrid = createLazyPlugin(
69
- * () => import('@object-ui/plugin-grid')
76
+ * async () => ({ default: (await import('@object-ui/plugin-grid')).ObjectGrid })
70
77
  * );
71
78
  *
72
79
  * // With custom fallback
73
- * const ObjectGrid = createLazyPlugin(
74
- * () => import('@object-ui/plugin-grid'),
80
+ * const ObjectGridWithFallback = createLazyPlugin(
81
+ * async () => ({ default: (await import('@object-ui/plugin-grid')).ObjectGrid }),
75
82
  * { fallback: <div>Loading grid...</div> }
76
83
  * );
77
84
  *
78
85
  * // With retry and error handling
79
- * const ObjectGrid = createLazyPlugin(
80
- * () => import('@object-ui/plugin-grid'),
86
+ * const ObjectGridWithRetry = createLazyPlugin(
87
+ * async () => ({ default: (await import('@object-ui/plugin-grid')).ObjectGrid }),
81
88
  * {
82
89
  * retries: 3,
83
90
  * errorFallback: ({ error, retry }) => (
@@ -5,8 +5,8 @@
5
5
  * This source code is licensed under the MIT license found in the
6
6
  * LICENSE file in the root directory of this source tree.
7
7
  */
8
- import React, { Component } from 'react';
9
- import { SchemaNode } from '@object-ui/core';
8
+ import React, { Component, type ForwardRefExoticComponent, type RefAttributes } from 'react';
9
+ import type { BaseSchema } from '@object-ui/types';
10
10
  /**
11
11
  * Per-component Error Boundary for SchemaRenderer.
12
12
  * Catches render errors in individual components, preventing one broken
@@ -30,7 +30,80 @@ export declare class SchemaErrorBoundary extends Component<{
30
30
  handleRetry: () => void;
31
31
  render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | React.JSX.Element | null | undefined;
32
32
  }
33
- export declare const SchemaRenderer: React.ForwardRefExoticComponent<Omit<{
34
- schema: SchemaNode;
35
- } & Record<string, any>, "ref"> & React.RefAttributes<any>>;
33
+ /**
34
+ * The props `SchemaRenderer` DECLARES and reads itself (objectui#4548).
35
+ *
36
+ * ## Why `schema` is spelled as this union and not as a `SchemaNode`
37
+ *
38
+ * The repo carries two competing `SchemaNode` types: `@object-ui/core`'s
39
+ * interface (which requires `type: string`) and `@object-ui/types`' union
40
+ * (`BaseSchema | string | number | boolean | null | undefined`). This component
41
+ * matched NEITHER. It declared core's — narrower than what it accepts, so every
42
+ * caller holding the types union was wrong and could not be told — while its
43
+ * runtime returns early for strings and nullish, which core's interface forbids.
44
+ * The erasure below hid the mismatch completely: with props collapsed to an
45
+ * index signature, `schema` resolved to `any` and no call site was ever checked.
46
+ *
47
+ * So the contract is STATED HERE, by this component, as what it actually
48
+ * handles: an object schema, a bare string (rendered as text), or nothing at
49
+ * all. `number` / `boolean` are deliberately excluded — the runtime tolerates
50
+ * them defensively (see the primitive guard in the evaluation memo) but no
51
+ * author should be invited to pass them. Reconciling the two repo-wide
52
+ * `SchemaNode` spellings is a separate concern and deliberately not done here.
53
+ */
54
+ export interface SchemaRendererProps {
55
+ schema: BaseSchema | string | null | undefined;
56
+ }
57
+ /**
58
+ * The open forwarding surface, named once so it reads as the decision it is.
59
+ *
60
+ * `SchemaRenderer` passes every prop it does not itself read straight through to
61
+ * the component the schema names, which is resolved at RUNTIME from a
62
+ * plugin-extensible registry — so the set of valid keys is not knowable here,
63
+ * and `packages/react/README.md` documents callers relying on it. The `any` is
64
+ * the point: this is a pass-through channel, not a typed prop.
65
+ */
66
+ type ForwardedProps = Record<string, any>;
67
+ /**
68
+ * The renderer loop.
69
+ *
70
+ * ## The explicit type annotation is the contract, and it is deliberate
71
+ *
72
+ * `forwardRef< T, P >` routes `P` through React's `PropsWithoutRef`:
73
+ *
74
+ * Props extends any ? ('ref' extends keyof Props ? Omit< Props, 'ref' > : Props) : Props
75
+ *
76
+ * A string index signature on `P` puts `string` into `keyof Props`, so
77
+ * `'ref' extends keyof Props` is ALWAYS true, the `Omit` branch always runs, and
78
+ * `Omit` over a type carrying a string index signature keeps ONLY the index
79
+ * signature. Every declared prop is erased — on both sides. This component used
80
+ * to hand `forwardRef` a props type of `{ schema: SchemaNode } & Record< string,
81
+ * any >`, so its own `schema` resolved to `any` at every one of the ~376 call
82
+ * sites in this repo, and was not even REQUIRED: `< SchemaRenderer / >` with no
83
+ * schema at all type-checked (objectui#4548, measured).
84
+ *
85
+ * The fix is NOT to close the surface. This is the renderer loop: it forwards
86
+ * every prop it does not read to the component the schema names, resolved at
87
+ * RUNTIME from a plugin-extensible `ComponentRegistry`. That forwarding is a
88
+ * documented, load-bearing feature — `packages/react/README.md` shows
89
+ * `< SchemaRenderer schema={formSchema} onSubmit={handleSubmit} / >`, and
90
+ * `@object-ui/components`' form renderer consumes that `onSubmit` as a React
91
+ * prop. A closed props type would state a FALSE contract and would force every
92
+ * leaf plugin's props into this package to stay usable.
93
+ *
94
+ * So the two halves are separated deliberately:
95
+ *
96
+ * * the `forwardRef` TYPE ARGUMENT is the honest `SchemaRendererProps`, with
97
+ * no index signature — nothing for `PropsWithoutRef` to collapse, so
98
+ * `schema` survives to the call site typed and required; and
99
+ * * the open forwarding surface is stated ONCE, here, in this export
100
+ * annotation. Because the annotation is applied to the already-built
101
+ * component, `PropsWithoutRef` never runs over it, so `Record< string, any >`
102
+ * widens the surface WITHOUT erasing anything.
103
+ *
104
+ * The repo-wide guard (`scripts/__tests__/forwardref-props-erasure.guard.test.ts`)
105
+ * judges the TYPE ARGUMENT only, for exactly this reason: an index signature
106
+ * there is an accidental eraser, whereas one here is a stated contract.
107
+ */
108
+ export declare const SchemaRenderer: ForwardRefExoticComponent<SchemaRendererProps & ForwardedProps & RefAttributes<unknown>>;
36
109
  export {};