@cratis/scene.react 4.9.0 → 4.10.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 (113) hide show
  1. package/SceneElementView.tsx +29 -7
  2. package/dist/cjs/SceneElementView.js +21 -8
  3. package/dist/cjs/SceneElementView.js.map +1 -1
  4. package/dist/cjs/core/coreComponents.js +4 -0
  5. package/dist/cjs/core/coreComponents.js.map +1 -1
  6. package/dist/cjs/core/coreDescriptors.js +3 -0
  7. package/dist/cjs/core/coreDescriptors.js.map +1 -1
  8. package/dist/cjs/index.js +11 -0
  9. package/dist/cjs/index.js.map +1 -1
  10. package/dist/cjs/navigation/resolveDestination.js +54 -0
  11. package/dist/cjs/navigation/resolveDestination.js.map +1 -0
  12. package/dist/cjs/packages/DesignTimeBundle.js.map +1 -1
  13. package/dist/cjs/packages/DesignTimeProvider.js +23 -0
  14. package/dist/cjs/packages/DesignTimeProvider.js.map +1 -0
  15. package/dist/cjs/packages/PackageHostConfiguration.js +30 -2
  16. package/dist/cjs/packages/PackageHostConfiguration.js.map +1 -1
  17. package/dist/cjs/renderer/AbortableQueryRunner.js +26 -0
  18. package/dist/cjs/renderer/AbortableQueryRunner.js.map +1 -0
  19. package/dist/cjs/renderer/InteractiveComponent.js +30 -1
  20. package/dist/cjs/renderer/InteractiveComponent.js.map +1 -1
  21. package/dist/cjs/renderer/RenderBindingScopeContext.js +77 -0
  22. package/dist/cjs/renderer/RenderBindingScopeContext.js.map +1 -0
  23. package/dist/esm/SceneElementView.d.ts +5 -2
  24. package/dist/esm/SceneElementView.d.ts.map +1 -1
  25. package/dist/esm/SceneElementView.js +22 -9
  26. package/dist/esm/SceneElementView.js.map +1 -1
  27. package/dist/esm/core/coreComponents.js +4 -0
  28. package/dist/esm/core/coreComponents.js.map +1 -1
  29. package/dist/esm/core/coreDescriptors.js +3 -0
  30. package/dist/esm/core/coreDescriptors.js.map +1 -1
  31. package/dist/esm/index.d.ts +1 -0
  32. package/dist/esm/index.d.ts.map +1 -1
  33. package/dist/esm/index.js +4 -0
  34. package/dist/esm/index.js.map +1 -1
  35. package/dist/esm/navigation/DestinationResolution.d.ts +10 -0
  36. package/dist/esm/navigation/DestinationResolution.d.ts.map +1 -0
  37. package/dist/esm/navigation/DestinationResolution.js +2 -0
  38. package/dist/esm/navigation/DestinationResolution.js.map +1 -0
  39. package/dist/esm/navigation/index.d.ts +3 -0
  40. package/dist/esm/navigation/index.d.ts.map +1 -0
  41. package/dist/esm/navigation/index.js +3 -0
  42. package/dist/esm/navigation/index.js.map +1 -0
  43. package/dist/esm/navigation/resolveDestination.d.ts +5 -0
  44. package/dist/esm/navigation/resolveDestination.d.ts.map +1 -0
  45. package/dist/esm/navigation/resolveDestination.js +52 -0
  46. package/dist/esm/navigation/resolveDestination.js.map +1 -0
  47. package/dist/esm/packages/DesignTimeAction.d.ts +3 -4
  48. package/dist/esm/packages/DesignTimeAction.d.ts.map +1 -1
  49. package/dist/esm/packages/DesignTimeBundle.d.ts +8 -11
  50. package/dist/esm/packages/DesignTimeBundle.d.ts.map +1 -1
  51. package/dist/esm/packages/DesignTimeBundle.js.map +1 -1
  52. package/dist/esm/packages/DesignTimeComponentProps.d.ts +5 -0
  53. package/dist/esm/packages/DesignTimeComponentProps.d.ts.map +1 -0
  54. package/dist/esm/packages/DesignTimeComponentProps.js +2 -0
  55. package/dist/esm/packages/DesignTimeComponentProps.js.map +1 -0
  56. package/dist/esm/packages/DesignTimeContext.d.ts +18 -0
  57. package/dist/esm/packages/DesignTimeContext.d.ts.map +1 -0
  58. package/dist/esm/packages/DesignTimeContext.js +2 -0
  59. package/dist/esm/packages/DesignTimeContext.js.map +1 -0
  60. package/dist/esm/packages/DesignTimePropertyDisplayProps.d.ts +8 -0
  61. package/dist/esm/packages/DesignTimePropertyDisplayProps.d.ts.map +1 -0
  62. package/dist/esm/packages/DesignTimePropertyDisplayProps.js +2 -0
  63. package/dist/esm/packages/DesignTimePropertyDisplayProps.js.map +1 -0
  64. package/dist/esm/packages/DesignTimePropertyEditorProps.d.ts +9 -0
  65. package/dist/esm/packages/DesignTimePropertyEditorProps.d.ts.map +1 -0
  66. package/dist/esm/packages/DesignTimePropertyEditorProps.js +2 -0
  67. package/dist/esm/packages/DesignTimePropertyEditorProps.js.map +1 -0
  68. package/dist/esm/packages/DesignTimeProvider.d.ts +9 -0
  69. package/dist/esm/packages/DesignTimeProvider.d.ts.map +1 -0
  70. package/dist/esm/packages/DesignTimeProvider.js +20 -0
  71. package/dist/esm/packages/DesignTimeProvider.js.map +1 -0
  72. package/dist/esm/packages/PackageHostConfiguration.d.ts +8 -1
  73. package/dist/esm/packages/PackageHostConfiguration.d.ts.map +1 -1
  74. package/dist/esm/packages/PackageHostConfiguration.js +31 -3
  75. package/dist/esm/packages/PackageHostConfiguration.js.map +1 -1
  76. package/dist/esm/packages/index.d.ts +6 -0
  77. package/dist/esm/packages/index.d.ts.map +1 -1
  78. package/dist/esm/packages/index.js +6 -0
  79. package/dist/esm/packages/index.js.map +1 -1
  80. package/dist/esm/renderer/AbortableQueryRunner.d.ts +7 -0
  81. package/dist/esm/renderer/AbortableQueryRunner.d.ts.map +1 -0
  82. package/dist/esm/renderer/AbortableQueryRunner.js +24 -0
  83. package/dist/esm/renderer/AbortableQueryRunner.js.map +1 -0
  84. package/dist/esm/renderer/InteractiveComponent.d.ts.map +1 -1
  85. package/dist/esm/renderer/InteractiveComponent.js +31 -2
  86. package/dist/esm/renderer/InteractiveComponent.js.map +1 -1
  87. package/dist/esm/renderer/RenderBindingScopeContext.d.ts +14 -0
  88. package/dist/esm/renderer/RenderBindingScopeContext.d.ts.map +1 -0
  89. package/dist/esm/renderer/RenderBindingScopeContext.js +73 -0
  90. package/dist/esm/renderer/RenderBindingScopeContext.js.map +1 -0
  91. package/dist/esm/renderer/index.d.ts +2 -0
  92. package/dist/esm/renderer/index.d.ts.map +1 -1
  93. package/dist/esm/renderer/index.js +2 -0
  94. package/dist/esm/renderer/index.js.map +1 -1
  95. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  96. package/index.ts +1 -0
  97. package/navigation/DestinationResolution.ts +13 -0
  98. package/navigation/index.ts +5 -0
  99. package/navigation/resolveDestination.ts +50 -0
  100. package/package.json +5 -5
  101. package/packages/DesignTimeAction.ts +3 -4
  102. package/packages/DesignTimeBundle.ts +8 -5
  103. package/packages/DesignTimeComponentProps.ts +11 -0
  104. package/packages/DesignTimeContext.ts +32 -0
  105. package/packages/DesignTimePropertyDisplayProps.ts +14 -0
  106. package/packages/DesignTimePropertyEditorProps.ts +15 -0
  107. package/packages/DesignTimeProvider.tsx +25 -0
  108. package/packages/PackageHostConfiguration.ts +47 -5
  109. package/packages/index.ts +6 -0
  110. package/renderer/AbortableQueryRunner.ts +27 -0
  111. package/renderer/InteractiveComponent.tsx +27 -3
  112. package/renderer/RenderBindingScopeContext.tsx +73 -0
  113. package/renderer/index.ts +2 -0
package/index.ts CHANGED
@@ -5,6 +5,7 @@ export * from './SceneElementView';
5
5
  export * from './renderer';
6
6
  export * from './core';
7
7
  export * from './NavBar';
8
+ export * from './navigation';
8
9
  export * from './icons';
9
10
  export * from './packages';
10
11
  export * from './theme';
@@ -0,0 +1,13 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { DestinationKind } from '@cratis/scene.model';
5
+
6
+ export interface DestinationResolution {
7
+ kind: DestinationKind;
8
+ url?: string;
9
+ outlet?: string;
10
+ dialog?: string;
11
+ action: 'navigate' | 'openDialog' | 'openExternal';
12
+ diagnostics: string[];
13
+ }
@@ -0,0 +1,5 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ export * from './DestinationResolution';
5
+ export * from './resolveDestination';
@@ -0,0 +1,50 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { BindingScope, resolveBindingExpression } from '@cratis/scene.engine';
5
+ import { DestinationKind, DestinationReference } from '@cratis/scene.model';
6
+ import { DestinationResolution } from './DestinationResolution';
7
+
8
+ /**
9
+ * Resolves a destination into a renderer-neutral navigation action, deep link and target outlet/dialog.
10
+ */
11
+ export function resolveDestination(destination: DestinationReference, scope: BindingScope = {}): DestinationResolution {
12
+ const diagnostics: string[] = [];
13
+ const kind = destination.kind ?? (destination.dialog ? DestinationKind.Dialog : DestinationKind.Outlet);
14
+ const parameters = Object.entries(destination.routeParameterBindings ?? {})
15
+ .map(([name, binding]) => [name, String(resolveBindingExpression(binding, scope) ?? '')] as const);
16
+ const base = destination.route ?? identityRoute(destination);
17
+ if (!base && kind !== DestinationKind.Dialog) diagnostics.push('Destination has no routeable screen, stable identity or route.');
18
+
19
+ const url = base ? appendParameters(base, parameters) : undefined;
20
+ return {
21
+ kind,
22
+ url,
23
+ outlet: destination.outlet,
24
+ dialog: destination.dialog,
25
+ action: kind === DestinationKind.Dialog ? 'openDialog' : kind === DestinationKind.External ? 'openExternal' : 'navigate',
26
+ diagnostics,
27
+ };
28
+ }
29
+
30
+ function identityRoute(destination: DestinationReference): string | undefined {
31
+ if (destination.screen) return destination.screen;
32
+ const parts = [destination.module, destination.feature, destination.slice].filter((part): part is string => typeof part === 'string' && part.length > 0);
33
+ return parts.length ? parts.join('/') : undefined;
34
+ }
35
+
36
+ function appendParameters(route: string, parameters: readonly (readonly [string, string])[]): string {
37
+ if (!parameters.length) return route;
38
+ let replaced = route;
39
+ const unused: string[] = [];
40
+ for (const [name, value] of parameters) {
41
+ const marker = `{${name}}`;
42
+ if (replaced.includes(marker)) {
43
+ replaced = replaced.split(marker).join(encodeURIComponent(value));
44
+ } else {
45
+ unused.push(`${encodeURIComponent(name)}=${encodeURIComponent(value)}`);
46
+ }
47
+ }
48
+
49
+ return unused.length ? `${replaced}?${unused.join('&')}` : replaced;
50
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/scene.react",
3
- "version": "4.9.0",
3
+ "version": "4.10.0",
4
4
  "description": "The React renderer implementing Scene.Engine's Renderer contract against real React/DOM.",
5
5
  "keywords": [
6
6
  "cratis",
@@ -51,12 +51,12 @@
51
51
  "build-storybook": "storybook build"
52
52
  },
53
53
  "dependencies": {
54
- "@cratis/scene.engine": "4.9.0",
55
- "@cratis/scene.model": "4.9.0"
54
+ "@cratis/scene.engine": "4.10.0",
55
+ "@cratis/scene.model": "4.10.0"
56
56
  },
57
57
  "devDependencies": {
58
- "@cratis/scene.engine": "4.9.0",
59
- "@cratis/scene.model": "4.9.0",
58
+ "@cratis/scene.engine": "4.10.0",
59
+ "@cratis/scene.model": "4.10.0",
60
60
  "@storybook/addon-links": "^10.6.1",
61
61
  "@storybook/react": "^10.6.1",
62
62
  "@storybook/react-vite": "^10.6.1",
@@ -1,11 +1,10 @@
1
1
  // Copyright (c) Cratis. All rights reserved.
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
- import { ComponentDescriptor, DesignTimeActionDescriptor, ExternalComponent, SceneEdit } from '@cratis/scene.model';
4
+ import { DesignTimeActionDescriptor, SceneEdit } from '@cratis/scene.model';
5
+ import { DesignTimeContext } from './DesignTimeContext';
5
6
 
6
- export interface DesignTimeActionContext {
7
- element: ExternalComponent;
8
- descriptor: ComponentDescriptor;
7
+ export interface DesignTimeActionContext extends DesignTimeContext {
9
8
  commandMetadata?: { properties: { name: string; type: string; label?: string }[] };
10
9
  }
11
10
 
@@ -1,17 +1,20 @@
1
1
  // Copyright (c) Cratis. All rights reserved.
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
- import { ComponentType, ReactNode } from 'react';
4
+ import { ComponentType } from 'react';
5
5
  import { DesignTimeAction } from './DesignTimeAction';
6
+ import { DesignTimeComponentProps } from './DesignTimeComponentProps';
7
+ import { DesignTimePropertyDisplayProps } from './DesignTimePropertyDisplayProps';
8
+ import { DesignTimePropertyEditorProps } from './DesignTimePropertyEditorProps';
6
9
 
7
10
  /**
8
11
  * Optional React-only design-time surface a package may publish beside its runtime bundle.
9
12
  */
10
13
  export interface DesignTimeBundle {
11
- previews?: Record<string, ComponentType<{ children?: ReactNode }>>;
12
- designers?: Record<string, ComponentType<{ children?: ReactNode }>>;
13
- propertyEditors?: Record<string, ComponentType<Record<string, unknown>>>;
14
- propertyDisplays?: Record<string, ComponentType<{ value: unknown }>>;
14
+ previews?: Record<string, ComponentType<DesignTimeComponentProps>>;
15
+ designers?: Record<string, ComponentType<DesignTimeComponentProps>>;
16
+ propertyEditors?: Record<string, ComponentType<DesignTimePropertyEditorProps>>;
17
+ propertyDisplays?: Record<string, ComponentType<DesignTimePropertyDisplayProps>>;
15
18
  actions?: Record<string, DesignTimeAction>;
16
19
  }
17
20
 
@@ -0,0 +1,11 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { DesignTimeContext } from './DesignTimeContext';
5
+
6
+ /**
7
+ * Props for a package-provided preview or full component designer.
8
+ */
9
+ export interface DesignTimeComponentProps {
10
+ context: DesignTimeContext;
11
+ }
@@ -0,0 +1,32 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import {
5
+ ComponentDescriptor,
6
+ EffectiveConfiguration,
7
+ ExternalComponent,
8
+ SceneEdit,
9
+ SceneElement,
10
+ Screen,
11
+ UiProfile,
12
+ } from '@cratis/scene.model';
13
+ import { BindingDiagnostic } from '@cratis/scene.engine';
14
+ import { ScenePackageBundle } from './ScenePackageBundle';
15
+
16
+ /**
17
+ * Canonical Scene state and host capabilities a package-side design-time contribution may read and edit.
18
+ */
19
+ export interface DesignTimeContext {
20
+ element: ExternalComponent;
21
+ root: SceneElement;
22
+ screen?: Screen;
23
+ descriptor: ComponentDescriptor;
24
+ configuration?: EffectiveConfiguration;
25
+ profile: UiProfile;
26
+ bundles: ScenePackageBundle[];
27
+ permissions: Record<string, boolean>;
28
+ capabilities: Record<string, boolean>;
29
+ diagnostics: BindingDiagnostic[];
30
+ submitEdits(edits: SceneEdit[]): void;
31
+ submitAction(actionId: string, edits: SceneEdit[]): void;
32
+ }
@@ -0,0 +1,14 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { PropertyDescriptor } from '@cratis/scene.model';
5
+ import { DesignTimeContext } from './DesignTimeContext';
6
+
7
+ /**
8
+ * Props for a package-provided read-only property display renderer.
9
+ */
10
+ export interface DesignTimePropertyDisplayProps {
11
+ context: DesignTimeContext;
12
+ property: PropertyDescriptor;
13
+ value: unknown;
14
+ }
@@ -0,0 +1,15 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { PropertyDescriptor } from '@cratis/scene.model';
5
+ import { DesignTimeContext } from './DesignTimeContext';
6
+
7
+ /**
8
+ * Props for a package-provided property editor that writes canonical Scene edits.
9
+ */
10
+ export interface DesignTimePropertyEditorProps {
11
+ context: DesignTimeContext;
12
+ property: PropertyDescriptor;
13
+ value: unknown;
14
+ setValue(value: unknown): void;
15
+ }
@@ -0,0 +1,25 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { ReactNode, createContext, useContext } from 'react';
5
+ import { DesignTimeContext } from './DesignTimeContext';
6
+
7
+ const Context = createContext<DesignTimeContext | undefined>(undefined);
8
+
9
+ export interface DesignTimeProviderProps {
10
+ context: DesignTimeContext;
11
+ children: ReactNode;
12
+ }
13
+
14
+ /**
15
+ * Makes the generic package-side design-time context available to nested package components.
16
+ */
17
+ export function DesignTimeProvider({ context, children }: DesignTimeProviderProps) {
18
+ return <Context.Provider value={context}>{children}</Context.Provider>;
19
+ }
20
+
21
+ export function useDesignTimeContext(): DesignTimeContext {
22
+ const context = useContext(Context);
23
+ if (!context) throw new Error('No Scene design-time context is available.');
24
+ return context;
25
+ }
@@ -1,8 +1,10 @@
1
1
  // Copyright (c) Cratis. All rights reserved.
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
- import { UiProfile } from '@cratis/scene.model';
5
- import { ScenePackageBundle, mergePackageRegistries } from './ScenePackageBundle';
4
+ import { DialogTemplate, Layout, ScreenTemplate, Theme, UiProfile } from '@cratis/scene.model';
5
+ import { resolvePackageDependencies } from '@cratis/scene.engine';
6
+ import { IconAdapterRegistry, createIconAdapterRegistry } from '../icons/IconAdapterRegistry';
7
+ import { ScenePackageBundle, mergePackageRegistries, validatePackageBundle } from './ScenePackageBundle';
6
8
 
7
9
  export interface PackageHostPolicy {
8
10
  allowExecutableImports: boolean;
@@ -18,6 +20,12 @@ export interface PackageHostConfiguration {
18
20
  export interface ResolvedPackageHost {
19
21
  bundles: ScenePackageBundle[];
20
22
  components: ReturnType<typeof mergePackageRegistries>;
23
+ layouts: Layout[];
24
+ screenTemplates: ScreenTemplate[];
25
+ dialogTemplates: DialogTemplate[];
26
+ themes: Theme[];
27
+ icons: IconAdapterRegistry;
28
+ assets: string[];
21
29
  diagnostics: string[];
22
30
  }
23
31
 
@@ -26,20 +34,44 @@ export interface ResolvedPackageHost {
26
34
  */
27
35
  export function resolvePackageHost(configuration: PackageHostConfiguration): ResolvedPackageHost {
28
36
  const diagnostics: string[] = [];
37
+ const names = new Map<string, number>();
38
+ for (const bundle of configuration.bundles) {
39
+ names.set(bundle.manifest.name, (names.get(bundle.manifest.name) ?? 0) + 1);
40
+ }
41
+
42
+ for (const [name, count] of names) {
43
+ if (count > 1) diagnostics.push(`Package '${name}' is approved more than once; duplicate bundles are ambiguous`);
44
+ }
45
+
29
46
  const byName = new Map(configuration.bundles.map(bundle => [bundle.manifest.name, bundle]));
47
+ const selection = resolvePackageDependencies(configuration.profile.packages, configuration.bundles.map(bundle => bundle.manifest));
48
+ for (const missing of selection.missing) diagnostics.push(`Package '${missing.package}' depends on missing package '${missing.dependsOn}'`);
49
+ for (const conflict of selection.versionConflicts) {
50
+ diagnostics.push(`Package '${conflict.package}' requires '${conflict.dependsOn}' ${conflict.requiredRange} but approved version is ${conflict.actualVersion}`);
51
+ }
52
+ for (const cycle of selection.cycles) diagnostics.push(`Package dependency cycle: ${cycle.join(' -> ')}`);
53
+
30
54
  const bundles: ScenePackageBundle[] = [];
31
55
  const runtimeSingletons = new Map<string, string>();
56
+ const assets: string[] = [];
32
57
 
33
- for (const packageName of configuration.profile.packages) {
58
+ for (const packageName of selection.packages) {
34
59
  const bundle = byName.get(packageName);
35
60
  if (!bundle) {
36
61
  diagnostics.push(`UI profile asks for '${packageName}' but the host did not approve a bundle for it`);
37
62
  continue;
38
63
  }
39
64
 
40
- if (!configuration.policy.allowNetworkAssets && (bundle.manifest.assets ?? []).some(asset => /^https?:\/\//.test(asset))) {
65
+ diagnostics.push(...validatePackageBundle(bundle).map(problem => `${packageName}: ${problem}`));
66
+ if (!configuration.policy.allowExecutableImports && bundle.designTime) {
67
+ diagnostics.push(`Package '${packageName}' provides executable design-time contributions, but host policy forbids them`);
68
+ }
69
+
70
+ const packageAssets = bundle.manifest.assets ?? [];
71
+ if (!configuration.policy.allowNetworkAssets && packageAssets.some(asset => /^https?:\/\//.test(asset))) {
41
72
  diagnostics.push(`Package '${packageName}' declares network assets, but host policy forbids them`);
42
73
  }
74
+ assets.push(...packageAssets);
43
75
 
44
76
  for (const dependency of bundle.manifest.runtimeSingletons ?? []) {
45
77
  const owner = runtimeSingletons.get(dependency);
@@ -52,5 +84,15 @@ export function resolvePackageHost(configuration: PackageHostConfiguration): Res
52
84
  bundles.push(bundle);
53
85
  }
54
86
 
55
- return { bundles, components: mergePackageRegistries(bundles), diagnostics };
87
+ return {
88
+ bundles,
89
+ components: mergePackageRegistries(bundles),
90
+ layouts: bundles.flatMap(bundle => bundle.layouts ?? []),
91
+ screenTemplates: bundles.flatMap(bundle => bundle.screenTemplates ?? []),
92
+ dialogTemplates: bundles.flatMap(bundle => bundle.dialogTemplates ?? []),
93
+ themes: bundles.flatMap(bundle => bundle.themes ?? []),
94
+ icons: createIconAdapterRegistry(bundles.map(bundle => bundle.iconLibrary?.adapter).filter(adapter => adapter !== undefined)),
95
+ assets,
96
+ diagnostics,
97
+ };
56
98
  }
package/packages/index.ts CHANGED
@@ -3,6 +3,12 @@
3
3
 
4
4
  export * from './DesignTimeAction';
5
5
  export * from './DesignTimeBundle';
6
+ export * from './DesignTimeComponentProps';
7
+ export * from './DesignTimeContext';
8
+ export * from './DesignTimePropertyDisplayProps';
9
+ export * from './DesignTimePropertyEditorProps';
10
+ export * from './DesignTimeProvider';
11
+ export * from '../navigation';
6
12
  export * from './PackageHostConfiguration';
7
13
  export * from './ScenePackageBundle';
8
14
  export * from './ComponentsUiLibraryContract';
@@ -0,0 +1,27 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ export type QueryPerformer = (query: string, argumentsValue: unknown, signal: AbortSignal) => Promise<unknown>;
5
+
6
+ /**
7
+ * Runs query bindings so a newer argument set aborts and ignores older work.
8
+ */
9
+ export class AbortableQueryRunner {
10
+ #controller?: AbortController;
11
+ #generation = 0;
12
+
13
+ async run(query: string, argumentsValue: unknown, performer: QueryPerformer): Promise<unknown | undefined> {
14
+ this.#controller?.abort();
15
+ const controller = new AbortController();
16
+ const generation = ++this.#generation;
17
+ this.#controller = controller;
18
+ const result = await performer(query, argumentsValue, controller.signal);
19
+ return generation === this.#generation && !controller.signal.aborted ? result : undefined;
20
+ }
21
+
22
+ clear(): void {
23
+ this.#controller?.abort();
24
+ this.#generation++;
25
+ this.#controller = undefined;
26
+ }
27
+ }
@@ -1,11 +1,12 @@
1
1
  // Copyright (c) Cratis. All rights reserved.
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
- import { ComponentType, ReactNode, createElement, useEffect } from 'react';
5
- import { ExternalComponent } from '@cratis/scene.model';
4
+ import { ComponentType, ReactNode, createElement, useEffect, useMemo } from 'react';
5
+ import { BindingExpression, ExternalComponent } from '@cratis/scene.model';
6
6
  import { useInteractions } from '../interactions';
7
7
  import { useBindingOutputs } from './BindingOutputContext';
8
8
  import { RegisteredComponentProps } from './ComponentRegistry';
9
+ import { useRenderBindingResolver } from './RenderBindingScopeContext';
9
10
 
10
11
  export interface InteractiveComponentProps {
11
12
  component: ComponentType<RegisteredComponentProps>;
@@ -23,6 +24,29 @@ export interface InteractiveComponentProps {
23
24
  export function InteractiveComponent({ component, element, slots }: InteractiveComponentProps) {
24
25
  const interactions = useInteractions(element.id, element.behaviors);
25
26
  const bindingOutputs = useBindingOutputs(element.id);
27
+ const resolveBinding = useRenderBindingResolver();
28
+ const resolvedElement = useMemo(() => ({
29
+ ...element,
30
+ properties: resolveProperties(element.properties, resolveBinding),
31
+ }), [element, resolveBinding]);
26
32
  useEffect(() => () => bindingOutputs.clearAll(), [element.id]);
27
- return createElement(component, { element, slots, interactions, bindingOutputs });
33
+ return createElement(component, { element: resolvedElement, slots, interactions, bindingOutputs });
34
+ }
35
+
36
+ function resolveProperties(properties: Record<string, unknown>, resolveBinding: (binding: BindingExpression) => unknown): Record<string, unknown> {
37
+ return Object.fromEntries(Object.entries(properties).map(([name, value]) => [name, resolveValue(value, resolveBinding)]));
38
+ }
39
+
40
+ function resolveValue(value: unknown, resolveBinding: (binding: BindingExpression) => unknown): unknown {
41
+ if (isBindingExpression(value)) return resolveBinding(value);
42
+ if (Array.isArray(value)) return value.map(entry => resolveValue(entry, resolveBinding));
43
+ if (value !== null && typeof value === 'object') {
44
+ return Object.fromEntries(Object.entries(value as Record<string, unknown>).map(([name, entry]) => [name, resolveValue(entry, resolveBinding)]));
45
+ }
46
+
47
+ return value;
48
+ }
49
+
50
+ function isBindingExpression(value: unknown): value is BindingExpression {
51
+ return value !== null && typeof value === 'object' && !Array.isArray(value) && typeof (value as Record<string, unknown>).path === 'string';
28
52
  }
@@ -0,0 +1,73 @@
1
+ // Copyright (c) Cratis. All rights reserved.
2
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
+
4
+ import { ReactNode, createContext, useCallback, useContext, useMemo, useRef } from 'react';
5
+ import { BindingResolver, BindingScope, resolveBindingExpression } from '@cratis/scene.engine';
6
+ import { BindingExpression, BindingNullBehavior } from '@cratis/scene.model';
7
+ import { BindingOutputProvider, useBindingOutputScope } from './BindingOutputContext';
8
+
9
+ interface RenderBindingScopeValue {
10
+ resolveBinding: BindingResolver;
11
+ scope: BindingScope;
12
+ }
13
+
14
+ const Context = createContext<RenderBindingScopeValue | undefined>(undefined);
15
+
16
+ export interface RenderBindingScopeProviderProps {
17
+ children: ReactNode;
18
+ dataContext?: unknown;
19
+ queryResults?: Record<string, unknown>;
20
+ componentOutputs?: Record<string, Record<string, unknown>>;
21
+ resolveBinding?: BindingResolver;
22
+ onComponentOutputsChanged?: (outputs: Record<string, Record<string, unknown>>) => void;
23
+ }
24
+
25
+ /**
26
+ * Provides the inherited reactive binding scope for a rendered Scene tree.
27
+ */
28
+ export function RenderBindingScopeProvider(props: RenderBindingScopeProviderProps) {
29
+ const current = useContext(Context);
30
+ if (current) return <>{props.children}</>;
31
+
32
+ return (
33
+ <BindingOutputProvider onOutputsChanged={props.onComponentOutputsChanged}>
34
+ <RenderBindingScopeInner {...props} />
35
+ </BindingOutputProvider>
36
+ );
37
+ }
38
+
39
+ function RenderBindingScopeInner({ children, dataContext, queryResults, componentOutputs, resolveBinding }: RenderBindingScopeProviderProps) {
40
+ const outputs = useBindingOutputScope();
41
+ const previousValues = useRef(new Map<string, unknown>());
42
+ const scope = useMemo<BindingScope>(() => ({
43
+ dataContext,
44
+ queryResults,
45
+ componentOutputs: { ...(componentOutputs ?? {}), ...outputs },
46
+ }), [componentOutputs, dataContext, outputs, queryResults]);
47
+
48
+ const resolver = useCallback<BindingResolver>((binding: BindingExpression) => {
49
+ if (!binding.kind && resolveBinding) return resolveBinding(binding);
50
+
51
+ const key = JSON.stringify(binding);
52
+ const value = resolveBindingExpression(binding, scope);
53
+ if (binding.nullBehavior === BindingNullBehavior.Preserve && value === undefined) {
54
+ return previousValues.current.get(key);
55
+ }
56
+
57
+ if (value !== undefined) previousValues.current.set(key, value);
58
+ return value;
59
+ }, [resolveBinding, scope]);
60
+
61
+ const value = useMemo<RenderBindingScopeValue>(() => ({ resolveBinding: resolver, scope }), [resolver, scope]);
62
+ return <Context.Provider value={value}>{children}</Context.Provider>;
63
+ }
64
+
65
+ export function useRenderBindingResolver(): BindingResolver {
66
+ const context = useContext(Context);
67
+ if (!context) throw new Error('No Scene render binding scope is available.');
68
+ return context.resolveBinding;
69
+ }
70
+
71
+ export function useRenderBindingScope(): BindingScope {
72
+ return useContext(Context)?.scope ?? {};
73
+ }
package/renderer/index.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  // Copyright (c) Cratis. All rights reserved.
2
2
  // Licensed under the MIT license. See LICENSE file in the project root for full license information.
3
3
 
4
+ export * from './AbortableQueryRunner';
4
5
  export * from './BindingOutputContext';
5
6
  export * from './ComponentRegistry';
7
+ export * from './RenderBindingScopeContext';
6
8
  export * from './UnresolvedComponent';
7
9
  export * from './createReactRenderer';
8
10
  export * from './InteractiveComponent';