@cratis/scene.react 4.8.0 → 4.9.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 (65) hide show
  1. package/SceneElementView.tsx +10 -3
  2. package/dist/cjs/SceneElementView.js +4 -2
  3. package/dist/cjs/SceneElementView.js.map +1 -1
  4. package/dist/cjs/index.js +8 -0
  5. package/dist/cjs/index.js.map +1 -1
  6. package/dist/cjs/packages/DesignTimeBundle.js +17 -0
  7. package/dist/cjs/packages/DesignTimeBundle.js.map +1 -0
  8. package/dist/cjs/packages/PackageHostConfiguration.js +43 -0
  9. package/dist/cjs/packages/PackageHostConfiguration.js.map +1 -0
  10. package/dist/cjs/packages/ScenePackageBundle.js +2 -0
  11. package/dist/cjs/packages/ScenePackageBundle.js.map +1 -1
  12. package/dist/cjs/renderer/BindingOutputContext.js +74 -0
  13. package/dist/cjs/renderer/BindingOutputContext.js.map +1 -0
  14. package/dist/cjs/renderer/InteractiveComponent.js +7 -1
  15. package/dist/cjs/renderer/InteractiveComponent.js.map +1 -1
  16. package/dist/esm/SceneElementView.d.ts +2 -1
  17. package/dist/esm/SceneElementView.d.ts.map +1 -1
  18. package/dist/esm/SceneElementView.js +5 -3
  19. package/dist/esm/SceneElementView.js.map +1 -1
  20. package/dist/esm/index.js +3 -0
  21. package/dist/esm/index.js.map +1 -1
  22. package/dist/esm/packages/DesignTimeAction.d.ts +23 -0
  23. package/dist/esm/packages/DesignTimeAction.d.ts.map +1 -0
  24. package/dist/esm/packages/DesignTimeAction.js +2 -0
  25. package/dist/esm/packages/DesignTimeAction.js.map +1 -0
  26. package/dist/esm/packages/DesignTimeBundle.d.ts +17 -0
  27. package/dist/esm/packages/DesignTimeBundle.d.ts.map +1 -0
  28. package/dist/esm/packages/DesignTimeBundle.js +15 -0
  29. package/dist/esm/packages/DesignTimeBundle.js.map +1 -0
  30. package/dist/esm/packages/PackageHostConfiguration.d.ts +18 -0
  31. package/dist/esm/packages/PackageHostConfiguration.d.ts.map +1 -0
  32. package/dist/esm/packages/PackageHostConfiguration.js +41 -0
  33. package/dist/esm/packages/PackageHostConfiguration.js.map +1 -0
  34. package/dist/esm/packages/ScenePackageBundle.d.ts +2 -0
  35. package/dist/esm/packages/ScenePackageBundle.d.ts.map +1 -1
  36. package/dist/esm/packages/ScenePackageBundle.js +2 -0
  37. package/dist/esm/packages/ScenePackageBundle.js.map +1 -1
  38. package/dist/esm/packages/index.d.ts +3 -0
  39. package/dist/esm/packages/index.d.ts.map +1 -1
  40. package/dist/esm/packages/index.js +3 -0
  41. package/dist/esm/packages/index.js.map +1 -1
  42. package/dist/esm/renderer/BindingOutputContext.d.ts +18 -0
  43. package/dist/esm/renderer/BindingOutputContext.d.ts.map +1 -0
  44. package/dist/esm/renderer/BindingOutputContext.js +70 -0
  45. package/dist/esm/renderer/BindingOutputContext.js.map +1 -0
  46. package/dist/esm/renderer/ComponentRegistry.d.ts +2 -0
  47. package/dist/esm/renderer/ComponentRegistry.d.ts.map +1 -1
  48. package/dist/esm/renderer/InteractiveComponent.d.ts.map +1 -1
  49. package/dist/esm/renderer/InteractiveComponent.js +8 -2
  50. package/dist/esm/renderer/InteractiveComponent.js.map +1 -1
  51. package/dist/esm/renderer/index.d.ts +1 -0
  52. package/dist/esm/renderer/index.d.ts.map +1 -1
  53. package/dist/esm/renderer/index.js +1 -0
  54. package/dist/esm/renderer/index.js.map +1 -1
  55. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  56. package/package.json +5 -5
  57. package/packages/DesignTimeAction.ts +25 -0
  58. package/packages/DesignTimeBundle.ts +27 -0
  59. package/packages/PackageHostConfiguration.ts +56 -0
  60. package/packages/ScenePackageBundle.ts +5 -0
  61. package/packages/index.ts +3 -0
  62. package/renderer/BindingOutputContext.tsx +69 -0
  63. package/renderer/ComponentRegistry.ts +4 -0
  64. package/renderer/InteractiveComponent.tsx +5 -2
  65. package/renderer/index.ts +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/scene.react",
3
- "version": "4.8.0",
3
+ "version": "4.9.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.8.0",
55
- "@cratis/scene.model": "4.8.0"
54
+ "@cratis/scene.engine": "4.9.0",
55
+ "@cratis/scene.model": "4.9.0"
56
56
  },
57
57
  "devDependencies": {
58
- "@cratis/scene.engine": "4.8.0",
59
- "@cratis/scene.model": "4.8.0",
58
+ "@cratis/scene.engine": "4.9.0",
59
+ "@cratis/scene.model": "4.9.0",
60
60
  "@storybook/addon-links": "^10.6.1",
61
61
  "@storybook/react": "^10.6.1",
62
62
  "@storybook/react-vite": "^10.6.1",
@@ -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 { ComponentDescriptor, DesignTimeActionDescriptor, ExternalComponent, SceneEdit } from '@cratis/scene.model';
5
+
6
+ export interface DesignTimeActionContext {
7
+ element: ExternalComponent;
8
+ descriptor: ComponentDescriptor;
9
+ commandMetadata?: { properties: { name: string; type: string; label?: string }[] };
10
+ }
11
+
12
+ export interface DesignTimeActionResult {
13
+ edits: SceneEdit[];
14
+ diagnostics: string[];
15
+ }
16
+
17
+ /**
18
+ * Executable package-owned design-time action. Hosts apply the returned edit batch through their own undo/redo.
19
+ */
20
+ export interface DesignTimeAction {
21
+ descriptor: DesignTimeActionDescriptor;
22
+ isVisible(context: DesignTimeActionContext): boolean;
23
+ isEnabled(context: DesignTimeActionContext): boolean;
24
+ execute(context: DesignTimeActionContext): DesignTimeActionResult;
25
+ }
@@ -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
+ import { ComponentType, ReactNode } from 'react';
5
+ import { DesignTimeAction } from './DesignTimeAction';
6
+
7
+ /**
8
+ * Optional React-only design-time surface a package may publish beside its runtime bundle.
9
+ */
10
+ 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 }>>;
15
+ actions?: Record<string, DesignTimeAction>;
16
+ }
17
+
18
+ export function validateDesignTimeBundle(bundle: DesignTimeBundle | undefined): string[] {
19
+ if (!bundle) return [];
20
+ const problems: string[] = [];
21
+ for (const [name, action] of Object.entries(bundle.actions ?? {})) {
22
+ if (action.descriptor.id !== name) {
23
+ problems.push(`registers design-time action '${name}' with descriptor id '${action.descriptor.id}'`);
24
+ }
25
+ }
26
+ return problems;
27
+ }
@@ -0,0 +1,56 @@
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 { UiProfile } from '@cratis/scene.model';
5
+ import { ScenePackageBundle, mergePackageRegistries } from './ScenePackageBundle';
6
+
7
+ export interface PackageHostPolicy {
8
+ allowExecutableImports: boolean;
9
+ allowNetworkAssets: boolean;
10
+ }
11
+
12
+ export interface PackageHostConfiguration {
13
+ bundles: ScenePackageBundle[];
14
+ profile: UiProfile;
15
+ policy: PackageHostPolicy;
16
+ }
17
+
18
+ export interface ResolvedPackageHost {
19
+ bundles: ScenePackageBundle[];
20
+ components: ReturnType<typeof mergePackageRegistries>;
21
+ diagnostics: string[];
22
+ }
23
+
24
+ /**
25
+ * Resolves the approved package set a standalone or embedded React host uses.
26
+ */
27
+ export function resolvePackageHost(configuration: PackageHostConfiguration): ResolvedPackageHost {
28
+ const diagnostics: string[] = [];
29
+ const byName = new Map(configuration.bundles.map(bundle => [bundle.manifest.name, bundle]));
30
+ const bundles: ScenePackageBundle[] = [];
31
+ const runtimeSingletons = new Map<string, string>();
32
+
33
+ for (const packageName of configuration.profile.packages) {
34
+ const bundle = byName.get(packageName);
35
+ if (!bundle) {
36
+ diagnostics.push(`UI profile asks for '${packageName}' but the host did not approve a bundle for it`);
37
+ continue;
38
+ }
39
+
40
+ if (!configuration.policy.allowNetworkAssets && (bundle.manifest.assets ?? []).some(asset => /^https?:\/\//.test(asset))) {
41
+ diagnostics.push(`Package '${packageName}' declares network assets, but host policy forbids them`);
42
+ }
43
+
44
+ for (const dependency of bundle.manifest.runtimeSingletons ?? []) {
45
+ const owner = runtimeSingletons.get(dependency);
46
+ if (owner && owner !== packageName) {
47
+ diagnostics.push(`Runtime singleton '${dependency}' is provided by both '${owner}' and '${packageName}'`);
48
+ }
49
+ runtimeSingletons.set(dependency, packageName);
50
+ }
51
+
52
+ bundles.push(bundle);
53
+ }
54
+
55
+ return { bundles, components: mergePackageRegistries(bundles), diagnostics };
56
+ }
@@ -6,6 +6,7 @@ import { IconAdapterRegistry, createIconAdapterRegistry } from '../icons/IconAda
6
6
  import { IconLibraryBundle } from '../icons/IconLibraryBundle';
7
7
  import { ComponentRegistry } from '../renderer';
8
8
  import { ComponentsUiLibraryContract, validateComponentsUiLibraryContract } from './ComponentsUiLibraryContract';
9
+ import { DesignTimeBundle, validateDesignTimeBundle } from './DesignTimeBundle';
9
10
 
10
11
  /**
11
12
  * What a package actually ships to a React renderer, as opposed to what it *declares*.
@@ -77,6 +78,9 @@ export interface ScenePackageBundle {
77
78
  * authored; hosts check the loaded export with validateComponentsUiLibraryMapping before rendering.
78
79
  */
79
80
  componentsUiLibrary?: ComponentsUiLibraryContract;
81
+
82
+ /** Optional React-only design-time tooling. Runtime hosts can omit it entirely. */
83
+ designTime?: DesignTimeBundle;
80
84
  }
81
85
 
82
86
  /**
@@ -156,6 +160,7 @@ export function validatePackageBundle(bundle: ScenePackageBundle): string[] {
156
160
 
157
161
  problems.push(...validateIconLibrary(bundle));
158
162
  problems.push(...validateDescriptors(bundle));
163
+ problems.push(...validateDesignTimeBundle(bundle.designTime));
159
164
 
160
165
  return problems;
161
166
  }
package/packages/index.ts CHANGED
@@ -1,5 +1,8 @@
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 './DesignTimeAction';
5
+ export * from './DesignTimeBundle';
6
+ export * from './PackageHostConfiguration';
4
7
  export * from './ScenePackageBundle';
5
8
  export * from './ComponentsUiLibraryContract';
@@ -0,0 +1,69 @@
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, useEffect, useMemo, useState } from 'react';
5
+
6
+ export interface ComponentBindingOutputs {
7
+ setOutput(property: string, value: unknown): void;
8
+ clearOutput(property: string): void;
9
+ clearAll(): void;
10
+ }
11
+
12
+ export interface BindingOutputProviderValue {
13
+ outputs: Record<string, Record<string, unknown>>;
14
+ outputFor(elementId: string): ComponentBindingOutputs;
15
+ }
16
+
17
+ const emptyOutputs: ComponentBindingOutputs = {
18
+ setOutput: () => undefined,
19
+ clearOutput: () => undefined,
20
+ clearAll: () => undefined,
21
+ };
22
+
23
+ const Context = createContext<BindingOutputProviderValue | undefined>(undefined);
24
+
25
+ export interface BindingOutputProviderProps {
26
+ children: ReactNode;
27
+ onOutputsChanged?: (outputs: Record<string, Record<string, unknown>>) => void;
28
+ }
29
+
30
+ /**
31
+ * Tracks component output properties for typed component-property bindings and clears them with component lifecycle.
32
+ */
33
+ export function BindingOutputProvider({ children, onOutputsChanged }: BindingOutputProviderProps) {
34
+ const [outputs, setOutputs] = useState<Record<string, Record<string, unknown>>>({});
35
+
36
+ useEffect(() => {
37
+ onOutputsChanged?.(outputs);
38
+ }, [onOutputsChanged, outputs]);
39
+
40
+ const outputFor = useCallback((elementId: string): ComponentBindingOutputs => ({
41
+ setOutput: (property: string, propertyValue: unknown) => setOutputs(current => ({
42
+ ...current,
43
+ [elementId]: { ...(current[elementId] ?? {}), [property]: propertyValue },
44
+ })),
45
+ clearOutput: (property: string) => setOutputs(current => {
46
+ const elementOutputs = { ...(current[elementId] ?? {}) };
47
+ delete elementOutputs[property];
48
+ return { ...current, [elementId]: elementOutputs };
49
+ }),
50
+ clearAll: () => setOutputs(current => {
51
+ const next = { ...current };
52
+ delete next[elementId];
53
+ return next;
54
+ }),
55
+ }), []);
56
+
57
+ const value = useMemo<BindingOutputProviderValue>(() => ({ outputs, outputFor }), [outputs, outputFor]);
58
+
59
+ return <Context.Provider value={value}>{children}</Context.Provider>;
60
+ }
61
+
62
+ export function useBindingOutputs(elementId: string): ComponentBindingOutputs {
63
+ const outputFor = useContext(Context)?.outputFor;
64
+ return useMemo(() => outputFor?.(elementId) ?? emptyOutputs, [outputFor, elementId]);
65
+ }
66
+
67
+ export function useBindingOutputScope(): Record<string, Record<string, unknown>> {
68
+ return useContext(Context)?.outputs ?? {};
69
+ }
@@ -4,6 +4,7 @@
4
4
  import { ComponentType, ReactNode } from 'react';
5
5
  import { ExternalComponent } from '@cratis/scene.model';
6
6
  import { InteractionHandlers } from '../interactions';
7
+ import { ComponentBindingOutputs } from './BindingOutputContext';
7
8
 
8
9
  /**
9
10
  * The props every registered component receives.
@@ -21,6 +22,9 @@ export interface RegisteredComponentProps {
21
22
  * specification, or a gallery. Spreading it is safe either way.
22
23
  */
23
24
  interactions?: InteractionHandlers;
25
+
26
+ /** Component output property writer for typed element-to-element bindings. */
27
+ bindingOutputs?: ComponentBindingOutputs;
24
28
  }
25
29
 
26
30
  /**
@@ -1,9 +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 { ComponentType, ReactNode, createElement } from 'react';
4
+ import { ComponentType, ReactNode, createElement, useEffect } from 'react';
5
5
  import { ExternalComponent } from '@cratis/scene.model';
6
6
  import { useInteractions } from '../interactions';
7
+ import { useBindingOutputs } from './BindingOutputContext';
7
8
  import { RegisteredComponentProps } from './ComponentRegistry';
8
9
 
9
10
  export interface InteractiveComponentProps {
@@ -21,5 +22,7 @@ export interface InteractiveComponentProps {
21
22
  */
22
23
  export function InteractiveComponent({ component, element, slots }: InteractiveComponentProps) {
23
24
  const interactions = useInteractions(element.id, element.behaviors);
24
- return createElement(component, { element, slots, interactions });
25
+ const bindingOutputs = useBindingOutputs(element.id);
26
+ useEffect(() => () => bindingOutputs.clearAll(), [element.id]);
27
+ return createElement(component, { element, slots, interactions, bindingOutputs });
25
28
  }
package/renderer/index.ts CHANGED
@@ -1,6 +1,7 @@
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 './BindingOutputContext';
4
5
  export * from './ComponentRegistry';
5
6
  export * from './UnresolvedComponent';
6
7
  export * from './createReactRenderer';