@object-ui/react 3.3.0 → 3.3.2

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 +18 -0
  2. package/README.md +21 -1
  3. package/dist/SchemaRenderer.js +19 -2
  4. package/dist/context/AppShellContext.d.ts +26 -0
  5. package/dist/context/AppShellContext.js +67 -0
  6. package/dist/context/index.d.ts +1 -7
  7. package/dist/context/index.js +1 -7
  8. package/package.json +32 -9
  9. package/.turbo/turbo-build.log +0 -4
  10. package/src/LazyPluginLoader.tsx +0 -174
  11. package/src/SchemaRenderer.tsx +0 -266
  12. package/src/__tests__/LazyPluginLoader.test.tsx +0 -176
  13. package/src/__tests__/SchemaRenderer.aria.test.tsx +0 -118
  14. package/src/__tests__/SchemaRenderer.debug.test.tsx +0 -69
  15. package/src/__tests__/SchemaRenderer.errorBoundary.test.tsx +0 -114
  16. package/src/__tests__/SchemaRenderer.expressions.test.tsx +0 -131
  17. package/src/__tests__/SchemaRendererProvider.smoke.test.tsx +0 -132
  18. package/src/__tests__/ThemeProvider.test.tsx +0 -451
  19. package/src/__tests__/useNavigationOverlay.test.ts +0 -578
  20. package/src/__tests__/useOffline.test.ts +0 -198
  21. package/src/__tests__/usePageTransition.test.ts +0 -214
  22. package/src/__tests__/usePageVariables.test.tsx +0 -334
  23. package/src/__tests__/usePerformance.test.ts +0 -137
  24. package/src/__tests__/useViewData.test.tsx +0 -422
  25. package/src/__tests__/view-switching.test.tsx +0 -456
  26. package/src/components/form/FieldFactory.tsx +0 -516
  27. package/src/components/form/FormRenderer.test.tsx +0 -744
  28. package/src/components/form/FormRenderer.tsx +0 -258
  29. package/src/components/form/README.md +0 -352
  30. package/src/components/form/index.ts +0 -12
  31. package/src/context/ActionContext.tsx +0 -213
  32. package/src/context/DndContext.tsx +0 -260
  33. package/src/context/NotificationContext.tsx +0 -280
  34. package/src/context/SchemaRendererContext.tsx +0 -48
  35. package/src/context/ThemeContext.tsx +0 -325
  36. package/src/context/__tests__/ActionContext.test.tsx +0 -174
  37. package/src/context/__tests__/DndContext.test.tsx +0 -150
  38. package/src/context/__tests__/NotificationContext.test.tsx +0 -176
  39. package/src/context/__tests__/useDataScope.test.tsx +0 -91
  40. package/src/context/index.ts +0 -13
  41. package/src/hooks/__tests__/useActionEngine.test.ts +0 -170
  42. package/src/hooks/__tests__/useActionRunner.test.ts +0 -153
  43. package/src/hooks/__tests__/useAnimation.test.ts +0 -82
  44. package/src/hooks/__tests__/useColumnSummary.test.ts +0 -93
  45. package/src/hooks/__tests__/useCrudShortcuts.test.ts +0 -85
  46. package/src/hooks/__tests__/useDataRefresh.test.ts +0 -115
  47. package/src/hooks/__tests__/useDebugMode.test.ts +0 -83
  48. package/src/hooks/__tests__/useDensityMode.test.ts +0 -81
  49. package/src/hooks/__tests__/useDiscovery.test.tsx +0 -247
  50. package/src/hooks/__tests__/useDynamicApp.test.ts +0 -161
  51. package/src/hooks/__tests__/useETagCache.test.ts +0 -153
  52. package/src/hooks/__tests__/useExpression.test.ts +0 -86
  53. package/src/hooks/__tests__/useFocusTrap.test.ts +0 -294
  54. package/src/hooks/__tests__/useKeyboardShortcuts.test.ts +0 -144
  55. package/src/hooks/__tests__/usePageTransition.test.ts +0 -158
  56. package/src/hooks/__tests__/usePerformanceBudget.test.ts +0 -157
  57. package/src/hooks/__tests__/useReducedMotion.test.ts +0 -104
  58. package/src/hooks/__tests__/useSchemaPersistence.test.ts +0 -220
  59. package/src/hooks/__tests__/useViewSharing.test.ts +0 -121
  60. package/src/hooks/__tests__/useViewTransition.test.ts +0 -61
  61. package/src/hooks/index.ts +0 -36
  62. package/src/hooks/useActionEngine.ts +0 -84
  63. package/src/hooks/useActionRunner.ts +0 -119
  64. package/src/hooks/useAnimation.ts +0 -117
  65. package/src/hooks/useClientNotifications.ts +0 -251
  66. package/src/hooks/useColumnSummary.ts +0 -87
  67. package/src/hooks/useCrudShortcuts.ts +0 -126
  68. package/src/hooks/useDataRefresh.ts +0 -58
  69. package/src/hooks/useDebugMode.ts +0 -95
  70. package/src/hooks/useDensityMode.ts +0 -102
  71. package/src/hooks/useDiscovery.ts +0 -168
  72. package/src/hooks/useDynamicApp.ts +0 -142
  73. package/src/hooks/useETagCache.ts +0 -404
  74. package/src/hooks/useExpression.ts +0 -56
  75. package/src/hooks/useFocusManagement.ts +0 -248
  76. package/src/hooks/useFocusTrap.ts +0 -138
  77. package/src/hooks/useGlobalUndo.ts +0 -98
  78. package/src/hooks/useKeyboardShortcuts.ts +0 -119
  79. package/src/hooks/useNavigationOverlay.ts +0 -190
  80. package/src/hooks/useOffline.ts +0 -301
  81. package/src/hooks/usePageTransition.ts +0 -187
  82. package/src/hooks/usePageVariables.tsx +0 -157
  83. package/src/hooks/usePerformance.ts +0 -246
  84. package/src/hooks/usePerformanceBudget.ts +0 -313
  85. package/src/hooks/useReducedMotion.ts +0 -53
  86. package/src/hooks/useSchemaPersistence.ts +0 -222
  87. package/src/hooks/useViewData.ts +0 -201
  88. package/src/hooks/useViewSharing.ts +0 -140
  89. package/src/hooks/useViewTransition.ts +0 -144
  90. package/src/index.ts +0 -41
  91. package/src/spec-bridge/SpecBridge.ts +0 -64
  92. package/src/spec-bridge/__tests__/P1SpecBridge.test.ts +0 -855
  93. package/src/spec-bridge/__tests__/SpecBridge.test.ts +0 -550
  94. package/src/spec-bridge/bridges/dashboard.ts +0 -180
  95. package/src/spec-bridge/bridges/form-view.ts +0 -109
  96. package/src/spec-bridge/bridges/list-view.ts +0 -187
  97. package/src/spec-bridge/bridges/page.ts +0 -136
  98. package/src/spec-bridge/index.ts +0 -14
  99. package/src/spec-bridge/types.ts +0 -38
  100. package/src/utils/i18n.ts +0 -10
  101. package/tsconfig.json +0 -17
  102. package/tsconfig.tsbuildinfo +0 -1
  103. package/vitest.config.ts +0 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # @object-ui/react
2
2
 
3
+ ## 3.3.2
4
+
5
+ ### Patch Changes
6
+
7
+ - @object-ui/types@3.3.2
8
+ - @object-ui/core@3.3.2
9
+ - @object-ui/i18n@3.3.2
10
+ - @object-ui/data-objectstack@3.3.2
11
+
12
+ ## 3.3.1
13
+
14
+ ### Patch Changes
15
+
16
+ - @object-ui/types@3.3.1
17
+ - @object-ui/core@3.3.1
18
+ - @object-ui/i18n@3.3.1
19
+ - @object-ui/data-objectstack@3.3.1
20
+
3
21
  ## 3.3.0
4
22
 
5
23
  ### Patch Changes
package/README.md CHANGED
@@ -162,6 +162,26 @@ The `previewMode` object contains:
162
162
 
163
163
  See [full documentation](https://objectui.org/api/react) for detailed API reference.
164
164
 
165
+ <!-- release-metadata:v3.3.0 -->
166
+
167
+ ## Compatibility
168
+
169
+ - **React:** 18.x or 19.x
170
+ - **Node.js:** ≥ 18
171
+ - **TypeScript:** ≥ 5.0 (strict mode)
172
+ - **`@objectstack/spec`:** ^3.3.0
173
+ - **`@objectstack/client`:** ^3.3.0
174
+ - **Tailwind CSS:** ≥ 3.4 (for packages with UI)
175
+
176
+ ## Links
177
+
178
+ - 📚 [Documentation](https://www.objectui.org/docs/packages/react)
179
+ - 📦 [npm package](https://www.npmjs.com/package/@object-ui/react)
180
+ - 📝 [Changelog](./CHANGELOG.md)
181
+ - 🐛 [Report an issue](https://github.com/objectstack-ai/objectui/issues)
182
+ - 🤝 [Contributing Guide](https://github.com/objectstack-ai/objectui/blob/main/CONTRIBUTING.md)
183
+ - 🗺️ [Roadmap](https://github.com/objectstack-ai/objectui/blob/main/ROADMAP.md)
184
+
165
185
  ## License
166
186
 
167
- MIT
187
+ MIT — see [LICENSE](./LICENSE).
@@ -6,7 +6,7 @@ import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-run
6
6
  * This source code is licensed under the MIT license found in the
7
7
  * LICENSE file in the root directory of this source tree.
8
8
  */
9
- import React, { forwardRef, useContext, useMemo, Component } from 'react';
9
+ import React, { forwardRef, useContext, useMemo, useEffect, useReducer, useState, Component } from 'react';
10
10
  import { ComponentRegistry, ExpressionEvaluator, isObjectUIError, ERROR_CODES, debugLog, debugTime, debugTimeEnd, DebugCollector, } from '@object-ui/core';
11
11
  import { SchemaRendererContext } from './context/SchemaRendererContext';
12
12
  import { resolveI18nLabel } from './utils/i18n';
@@ -66,6 +66,11 @@ export class SchemaErrorBoundary extends Component {
66
66
  export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
67
67
  const context = useContext(SchemaRendererContext);
68
68
  const dataSource = context?.dataSource || {};
69
+ // Re-render trigger when the global ComponentRegistry mutates (e.g. a
70
+ // lazy-loaded plugin finishes registering its components).
71
+ const [, forceUpdate] = useReducer((n) => n + 1, 0);
72
+ useEffect(() => ComponentRegistry.subscribe(forceUpdate), []);
73
+ const [lazyError, setLazyError] = useState(null);
69
74
  // Evaluate schema expressions against the data source
70
75
  const evaluatedSchema = useMemo(() => {
71
76
  if (!schema || typeof schema === 'string')
@@ -135,9 +140,21 @@ export const SchemaRenderer = forwardRef(({ schema, ...props }, _ref) => {
135
140
  debugLog('schema', 'Rendering schema node', { type: evaluatedSchema.type, id: evaluatedSchema.id });
136
141
  const Component = ComponentRegistry.get(evaluatedSchema.type);
137
142
  if (!Component) {
143
+ // If a lazy loader is registered for this type, kick it off — the
144
+ // registry will notify us via subscribe() once the plugin module's
145
+ // top-level register() side-effects have run.
146
+ if (!lazyError && ComponentRegistry.hasLazy(evaluatedSchema.type)) {
147
+ const pending = ComponentRegistry.loadLazy(evaluatedSchema.type);
148
+ if (pending) {
149
+ pending.catch((err) => {
150
+ setLazyError(err instanceof Error ? err : new Error(String(err)));
151
+ });
152
+ return (_jsxs("div", { className: "p-2 text-sm text-muted-foreground animate-pulse", role: "status", "aria-live": "polite", "data-lazy-loading": evaluatedSchema.type, children: ["Loading ", _jsx("code", { children: evaluatedSchema.type }), "\u2026"] }));
153
+ }
154
+ }
138
155
  debugLog('schema', 'Component not found in registry', { type: evaluatedSchema.type });
139
156
  const errorInfo = ERROR_CODES['OBJUI-001'];
140
- return (_jsxs("div", { className: "p-4 border border-red-500 rounded text-red-500 bg-red-50 my-2", role: "alert", children: [_jsxs("p", { className: "font-medium", children: ["Unknown component type: ", _jsx("strong", { children: evaluatedSchema.type })] }), globalThis.process?.env?.NODE_ENV !== 'production' && (_jsxs("p", { className: "text-xs mt-1", children: ["\uD83D\uDCA1 ", errorInfo.suggestion, " (OBJUI-001)"] })), _jsx("pre", { className: "text-xs mt-2 overflow-auto", children: JSON.stringify(evaluatedSchema, null, 2) })] }));
157
+ return (_jsxs("div", { className: "p-4 border border-red-500 rounded text-red-500 bg-red-50 my-2", role: "alert", children: [_jsxs("p", { className: "font-medium", children: ["Unknown component type: ", _jsx("strong", { children: evaluatedSchema.type })] }), lazyError && (_jsxs("p", { className: "text-xs mt-1", children: ["Failed to load plugin: ", lazyError.message] })), globalThis.process?.env?.NODE_ENV !== 'production' && (_jsxs("p", { className: "text-xs mt-1", children: ["\uD83D\uDCA1 ", errorInfo.suggestion, " (OBJUI-001)"] })), _jsx("pre", { className: "text-xs mt-2 overflow-auto", children: JSON.stringify(evaluatedSchema, null, 2) })] }));
141
158
  }
142
159
  // Note: We don't forward the ref to the Component because components in the registry
143
160
  // may not support refs. The SchemaRenderer itself can still receive refs for its own use.
@@ -0,0 +1,26 @@
1
+ import type { ObjectStackAdapter } from '@object-ui/data-objectstack';
2
+ export declare const AdapterCtx: import("react").Context<ObjectStackAdapter<unknown> | null>;
3
+ export declare function useAdapter(): ObjectStackAdapter | null;
4
+ export interface MetadataState {
5
+ apps: any[];
6
+ objects: any[];
7
+ dashboards: any[];
8
+ reports: any[];
9
+ pages: any[];
10
+ loading: boolean;
11
+ error: Error | null;
12
+ }
13
+ export interface MetadataContextValue extends MetadataState {
14
+ refresh: (type?: string) => Promise<void>;
15
+ invalidate: (type: string, name?: string) => void;
16
+ ensureType: (type: string) => Promise<any[]>;
17
+ getItem: (type: string, name: string) => Promise<any | null>;
18
+ getItemsByType: (type: string) => any[];
19
+ }
20
+ export declare const MetadataCtx: import("react").Context<MetadataContextValue | null>;
21
+ export declare function useMetadata(): MetadataContextValue;
22
+ export declare function useMetadataItem(type: string, name: string | undefined | null): {
23
+ item: any | null;
24
+ loading: boolean;
25
+ error: Error | null;
26
+ };
@@ -0,0 +1,67 @@
1
+ import { createContext, useContext, useEffect, useState } from 'react';
2
+ // ---------------------------------------------------------------------------
3
+ // AdapterContext
4
+ // ---------------------------------------------------------------------------
5
+ export const AdapterCtx = createContext(null);
6
+ export function useAdapter() {
7
+ return useContext(AdapterCtx);
8
+ }
9
+ export const MetadataCtx = createContext(null);
10
+ export function useMetadata() {
11
+ const ctx = useContext(MetadataCtx);
12
+ if (!ctx) {
13
+ // Graceful fallback: when a consumer is rendered outside a MetadataProvider
14
+ // (common in unit tests that only need to assert on rendering), return an
15
+ // empty no-op implementation rather than crash. Production code paths
16
+ // should always wrap in <MetadataProvider>.
17
+ return {
18
+ apps: [],
19
+ objects: [],
20
+ dashboards: [],
21
+ reports: [],
22
+ pages: [],
23
+ loading: false,
24
+ error: null,
25
+ refresh: async () => { },
26
+ invalidate: () => { },
27
+ ensureType: async () => [],
28
+ getItem: async () => null,
29
+ getItemsByType: () => [],
30
+ };
31
+ }
32
+ return ctx;
33
+ }
34
+ export function useMetadataItem(type, name) {
35
+ const { getItem } = useMetadata();
36
+ const [state, setState] = useState({
37
+ item: null,
38
+ loading: !!name,
39
+ error: null,
40
+ });
41
+ useEffect(() => {
42
+ if (!name) {
43
+ setState({ item: null, loading: false, error: null });
44
+ return;
45
+ }
46
+ let cancelled = false;
47
+ setState(s => ({ ...s, loading: true, error: null }));
48
+ getItem(type, name)
49
+ .then(item => {
50
+ if (!cancelled)
51
+ setState({ item, loading: false, error: null });
52
+ })
53
+ .catch(err => {
54
+ if (!cancelled) {
55
+ setState({
56
+ item: null,
57
+ loading: false,
58
+ error: err instanceof Error ? err : new Error(String(err)),
59
+ });
60
+ }
61
+ });
62
+ return () => {
63
+ cancelled = true;
64
+ };
65
+ }, [type, name, getItem]);
66
+ return state;
67
+ }
@@ -1,12 +1,6 @@
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
1
  export * from './SchemaRendererContext';
9
2
  export * from './ActionContext';
10
3
  export * from './ThemeContext';
11
4
  export * from './NotificationContext';
12
5
  export * from './DndContext';
6
+ export * from './AppShellContext';
@@ -1,12 +1,6 @@
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
1
  export * from './SchemaRendererContext';
9
2
  export * from './ActionContext';
10
3
  export * from './ThemeContext';
11
4
  export * from './NotificationContext';
12
5
  export * from './DndContext';
6
+ export * from './AppShellContext';
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@object-ui/react",
3
- "version": "3.3.0",
3
+ "version": "3.3.2",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "React bindings and SchemaRenderer component for Object UI",
7
- "homepage": "https://www.objectui.org",
7
+ "homepage": "https://www.objectui.org/docs/packages/react",
8
8
  "repository": {
9
9
  "type": "git",
10
- "url": "https://github.com/objectstack-ai/objectui.git",
10
+ "url": "git+https://github.com/objectstack-ai/objectui.git",
11
11
  "directory": "packages/react"
12
12
  },
13
13
  "bugs": {
@@ -24,11 +24,12 @@
24
24
  }
25
25
  },
26
26
  "dependencies": {
27
- "@objectstack/spec": "^4.0.3",
28
- "react-hook-form": "^7.72.1",
29
- "@object-ui/core": "3.3.0",
30
- "@object-ui/i18n": "3.3.0",
31
- "@object-ui/types": "3.3.0"
27
+ "@objectstack/spec": "^4.0.4",
28
+ "react-hook-form": "^7.75.0",
29
+ "@object-ui/core": "3.3.2",
30
+ "@object-ui/data-objectstack": "3.3.2",
31
+ "@object-ui/i18n": "3.3.2",
32
+ "@object-ui/types": "3.3.2"
32
33
  },
33
34
  "peerDependencies": {
34
35
  "react": "^18.0.0 || ^19.0.0",
@@ -37,8 +38,30 @@
37
38
  "devDependencies": {
38
39
  "@types/react": "19.2.14",
39
40
  "@types/react-dom": "19.2.3",
40
- "typescript": "^6.0.2"
41
+ "typescript": "^6.0.3"
41
42
  },
43
+ "keywords": [
44
+ "objectui",
45
+ "sdui",
46
+ "schema-driven-ui",
47
+ "react",
48
+ "tailwind",
49
+ "shadcn",
50
+ "objectstack",
51
+ "renderer",
52
+ "schema-renderer",
53
+ "hooks"
54
+ ],
55
+ "author": "ObjectStack Team <team@objectstack.ai>",
56
+ "publishConfig": {
57
+ "access": "public"
58
+ },
59
+ "files": [
60
+ "dist",
61
+ "README.md",
62
+ "CHANGELOG.md",
63
+ "LICENSE"
64
+ ],
42
65
  "scripts": {
43
66
  "build": "tsc",
44
67
  "test": "vitest run",
@@ -1,4 +0,0 @@
1
-
2
- > @object-ui/react@3.3.0 build /home/runner/work/objectui/objectui/packages/react
3
- > tsc
4
-
@@ -1,174 +0,0 @@
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
- import React, { lazy, Suspense, Component } from 'react';
10
-
11
- export interface LazyPluginOptions {
12
- /**
13
- * Fallback component to show while loading
14
- */
15
- fallback?: React.ReactNode;
16
- /**
17
- * Number of retry attempts on load failure (default: 2)
18
- */
19
- retries?: number;
20
- /**
21
- * Delay in ms between retries (default: 1000)
22
- */
23
- retryDelay?: number;
24
- /**
25
- * Custom error fallback component
26
- */
27
- errorFallback?: React.ComponentType<{ error: Error; retry: () => void }>;
28
- }
29
-
30
- /**
31
- * Error boundary for lazy-loaded plugins
32
- */
33
- interface ErrorBoundaryState {
34
- hasError: boolean;
35
- error: Error | null;
36
- }
37
-
38
- class PluginErrorBoundary extends Component<
39
- { fallback?: React.ComponentType<{ error: Error; retry: () => void }>; children: React.ReactNode },
40
- ErrorBoundaryState
41
- > {
42
- state: ErrorBoundaryState = { hasError: false, error: null };
43
-
44
- static getDerivedStateFromError(error: Error): ErrorBoundaryState {
45
- return { hasError: true, error };
46
- }
47
-
48
- handleRetry = () => {
49
- this.setState({ hasError: false, error: null });
50
- };
51
-
52
- render() {
53
- if (this.state.hasError && this.state.error) {
54
- const FallbackComponent = this.props.fallback;
55
- if (FallbackComponent) {
56
- return <FallbackComponent error={this.state.error} retry={this.handleRetry} />;
57
- }
58
- return null;
59
- }
60
- return this.props.children;
61
- }
62
- }
63
-
64
- /**
65
- * Create a lazy-loaded import function with retry support
66
- */
67
- function createRetryImport<P extends object>(
68
- importFn: () => Promise<{ default: React.ComponentType<P> }>,
69
- retries: number,
70
- retryDelay: number,
71
- ): () => Promise<{ default: React.ComponentType<P> }> {
72
- return () => {
73
- let attempt = 0;
74
- const tryImport = (): Promise<{ default: React.ComponentType<P> }> =>
75
- importFn().catch((err) => {
76
- attempt++;
77
- if (attempt <= retries) {
78
- return new Promise((resolve) =>
79
- setTimeout(() => resolve(tryImport()), retryDelay),
80
- );
81
- }
82
- throw err;
83
- });
84
- return tryImport();
85
- };
86
- }
87
-
88
- /**
89
- * Create a lazy-loaded plugin component with Suspense wrapper
90
- *
91
- * @param importFn - Dynamic import function that returns a module with default export
92
- * @param options - Configuration options for the lazy plugin
93
- * @returns A component that lazy loads the plugin
94
- *
95
- * @example
96
- * ```tsx
97
- * // Basic usage
98
- * const ObjectGrid = createLazyPlugin(
99
- * () => import('@object-ui/plugin-grid')
100
- * );
101
- *
102
- * // With custom fallback
103
- * const ObjectGrid = createLazyPlugin(
104
- * () => import('@object-ui/plugin-grid'),
105
- * { fallback: <div>Loading grid...</div> }
106
- * );
107
- *
108
- * // With retry and error handling
109
- * const ObjectGrid = createLazyPlugin(
110
- * () => import('@object-ui/plugin-grid'),
111
- * {
112
- * retries: 3,
113
- * errorFallback: ({ error, retry }) => (
114
- * <div>
115
- * <p>Failed to load: {error.message}</p>
116
- * <button onClick={retry}>Retry</button>
117
- * </div>
118
- * ),
119
- * }
120
- * );
121
- * ```
122
- */
123
- export function createLazyPlugin<P extends object = any>(
124
- importFn: () => Promise<{ default: React.ComponentType<P> }>,
125
- options?: LazyPluginOptions
126
- ): React.ComponentType<P> {
127
- const { retries = 2, retryDelay = 1000, errorFallback } = options || {};
128
-
129
- const retryImport = retries > 0
130
- ? createRetryImport(importFn, retries, retryDelay)
131
- : importFn;
132
-
133
- const LazyComponent = lazy(retryImport);
134
-
135
- const PluginWrapper: React.FC<P> = (props) => {
136
- const content = (
137
- <Suspense fallback={options?.fallback || null}>
138
- <LazyComponent {...props} />
139
- </Suspense>
140
- );
141
-
142
- if (errorFallback) {
143
- return (
144
- <PluginErrorBoundary fallback={errorFallback}>
145
- {content}
146
- </PluginErrorBoundary>
147
- );
148
- }
149
-
150
- return content;
151
- };
152
-
153
- return PluginWrapper;
154
- }
155
-
156
- /**
157
- * Preload a plugin module without rendering it.
158
- * Useful for preloading plugins that will be needed soon.
159
- *
160
- * @param importFn - Dynamic import function
161
- * @returns Promise that resolves when the module is loaded
162
- *
163
- * @example
164
- * ```tsx
165
- * // Preload on hover
166
- * const loadGrid = () => import('@object-ui/plugin-grid');
167
- * <button onMouseEnter={() => preloadPlugin(loadGrid)}>Show Grid</button>
168
- * ```
169
- */
170
- export function preloadPlugin<T = unknown>(
171
- importFn: () => Promise<T>,
172
- ): Promise<T> {
173
- return importFn();
174
- }