@cratis/scene.react 4.9.0 → 4.11.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.
- package/SceneElementView.tsx +29 -7
- package/dist/cjs/SceneElementView.js +21 -8
- package/dist/cjs/SceneElementView.js.map +1 -1
- package/dist/cjs/core/coreComponents.js +6 -0
- package/dist/cjs/core/coreComponents.js.map +1 -1
- package/dist/cjs/core/coreDescriptors.js +5 -0
- package/dist/cjs/core/coreDescriptors.js.map +1 -1
- package/dist/cjs/index.js +28 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/navigation/SceneNavigationContext.js +21 -0
- package/dist/cjs/navigation/SceneNavigationContext.js.map +1 -0
- package/dist/cjs/navigation/SceneNavigationHost.js +82 -0
- package/dist/cjs/navigation/SceneNavigationHost.js.map +1 -0
- package/dist/cjs/navigation/resolveDestination.js +54 -0
- package/dist/cjs/navigation/resolveDestination.js.map +1 -0
- package/dist/cjs/packages/DesignTimeBundle.js.map +1 -1
- package/dist/cjs/packages/DesignTimeProvider.js +23 -0
- package/dist/cjs/packages/DesignTimeProvider.js.map +1 -0
- package/dist/cjs/packages/EmbeddedPackageHost.js +15 -0
- package/dist/cjs/packages/EmbeddedPackageHost.js.map +1 -0
- package/dist/cjs/packages/PackageHostAssets.js +30 -0
- package/dist/cjs/packages/PackageHostAssets.js.map +1 -0
- package/dist/cjs/packages/PackageHostConfiguration.js +40 -4
- package/dist/cjs/packages/PackageHostConfiguration.js.map +1 -1
- package/dist/cjs/packages/PackageHostRenderMode.js +15 -0
- package/dist/cjs/packages/PackageHostRenderMode.js.map +1 -0
- package/dist/cjs/packages/PackageHostView.js +53 -0
- package/dist/cjs/packages/PackageHostView.js.map +1 -0
- package/dist/cjs/packages/StandalonePackageHost.js +15 -0
- package/dist/cjs/packages/StandalonePackageHost.js.map +1 -0
- package/dist/cjs/packages/WebViewPackageHost.js +15 -0
- package/dist/cjs/packages/WebViewPackageHost.js.map +1 -0
- package/dist/cjs/renderer/AbortableQueryRunner.js +26 -0
- package/dist/cjs/renderer/AbortableQueryRunner.js.map +1 -0
- package/dist/cjs/renderer/InteractiveComponent.js +34 -1
- package/dist/cjs/renderer/InteractiveComponent.js.map +1 -1
- package/dist/cjs/renderer/RenderBindingScopeContext.js +77 -0
- package/dist/cjs/renderer/RenderBindingScopeContext.js.map +1 -0
- package/dist/esm/SceneElementView.d.ts +5 -2
- package/dist/esm/SceneElementView.d.ts.map +1 -1
- package/dist/esm/SceneElementView.js +22 -9
- package/dist/esm/SceneElementView.js.map +1 -1
- package/dist/esm/core/coreComponents.js +6 -0
- package/dist/esm/core/coreComponents.js.map +1 -1
- package/dist/esm/core/coreDescriptors.js +5 -0
- package/dist/esm/core/coreDescriptors.js.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +12 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/navigation/DestinationResolution.d.ts +10 -0
- package/dist/esm/navigation/DestinationResolution.d.ts.map +1 -0
- package/dist/esm/navigation/DestinationResolution.js +2 -0
- package/dist/esm/navigation/DestinationResolution.js.map +1 -0
- package/dist/esm/navigation/SceneNavigationContext.d.ts +20 -0
- package/dist/esm/navigation/SceneNavigationContext.d.ts.map +1 -0
- package/dist/esm/navigation/SceneNavigationContext.js +18 -0
- package/dist/esm/navigation/SceneNavigationContext.js.map +1 -0
- package/dist/esm/navigation/SceneNavigationHost.d.ts +12 -0
- package/dist/esm/navigation/SceneNavigationHost.d.ts.map +1 -0
- package/dist/esm/navigation/SceneNavigationHost.js +80 -0
- package/dist/esm/navigation/SceneNavigationHost.js.map +1 -0
- package/dist/esm/navigation/index.d.ts +5 -0
- package/dist/esm/navigation/index.d.ts.map +1 -0
- package/dist/esm/navigation/index.js +5 -0
- package/dist/esm/navigation/index.js.map +1 -0
- package/dist/esm/navigation/resolveDestination.d.ts +5 -0
- package/dist/esm/navigation/resolveDestination.d.ts.map +1 -0
- package/dist/esm/navigation/resolveDestination.js +52 -0
- package/dist/esm/navigation/resolveDestination.js.map +1 -0
- package/dist/esm/packages/DesignTimeAction.d.ts +3 -4
- package/dist/esm/packages/DesignTimeAction.d.ts.map +1 -1
- package/dist/esm/packages/DesignTimeBundle.d.ts +8 -11
- package/dist/esm/packages/DesignTimeBundle.d.ts.map +1 -1
- package/dist/esm/packages/DesignTimeBundle.js.map +1 -1
- package/dist/esm/packages/DesignTimeComponentProps.d.ts +5 -0
- package/dist/esm/packages/DesignTimeComponentProps.d.ts.map +1 -0
- package/dist/esm/packages/DesignTimeComponentProps.js +2 -0
- package/dist/esm/packages/DesignTimeComponentProps.js.map +1 -0
- package/dist/esm/packages/DesignTimeContext.d.ts +18 -0
- package/dist/esm/packages/DesignTimeContext.d.ts.map +1 -0
- package/dist/esm/packages/DesignTimeContext.js +2 -0
- package/dist/esm/packages/DesignTimeContext.js.map +1 -0
- package/dist/esm/packages/DesignTimePropertyDisplayProps.d.ts +8 -0
- package/dist/esm/packages/DesignTimePropertyDisplayProps.d.ts.map +1 -0
- package/dist/esm/packages/DesignTimePropertyDisplayProps.js +2 -0
- package/dist/esm/packages/DesignTimePropertyDisplayProps.js.map +1 -0
- package/dist/esm/packages/DesignTimePropertyEditorProps.d.ts +9 -0
- package/dist/esm/packages/DesignTimePropertyEditorProps.d.ts.map +1 -0
- package/dist/esm/packages/DesignTimePropertyEditorProps.js +2 -0
- package/dist/esm/packages/DesignTimePropertyEditorProps.js.map +1 -0
- package/dist/esm/packages/DesignTimeProvider.d.ts +9 -0
- package/dist/esm/packages/DesignTimeProvider.d.ts.map +1 -0
- package/dist/esm/packages/DesignTimeProvider.js +20 -0
- package/dist/esm/packages/DesignTimeProvider.js.map +1 -0
- package/dist/esm/packages/EmbeddedPackageHost.d.ts +3 -0
- package/dist/esm/packages/EmbeddedPackageHost.d.ts.map +1 -0
- package/dist/esm/packages/EmbeddedPackageHost.js +13 -0
- package/dist/esm/packages/EmbeddedPackageHost.js.map +1 -0
- package/dist/esm/packages/PackageHostAssets.d.ts +6 -0
- package/dist/esm/packages/PackageHostAssets.d.ts.map +1 -0
- package/dist/esm/packages/PackageHostAssets.js +28 -0
- package/dist/esm/packages/PackageHostAssets.js.map +1 -0
- package/dist/esm/packages/PackageHostConfiguration.d.ts +11 -1
- package/dist/esm/packages/PackageHostConfiguration.d.ts.map +1 -1
- package/dist/esm/packages/PackageHostConfiguration.js +41 -5
- package/dist/esm/packages/PackageHostConfiguration.js.map +1 -1
- package/dist/esm/packages/PackageHostRenderMode.d.ts +6 -0
- package/dist/esm/packages/PackageHostRenderMode.d.ts.map +1 -0
- package/dist/esm/packages/PackageHostRenderMode.js +13 -0
- package/dist/esm/packages/PackageHostRenderMode.js.map +1 -0
- package/dist/esm/packages/PackageHostView.d.ts +13 -0
- package/dist/esm/packages/PackageHostView.d.ts.map +1 -0
- package/dist/esm/packages/PackageHostView.js +51 -0
- package/dist/esm/packages/PackageHostView.js.map +1 -0
- package/dist/esm/packages/StandalonePackageHost.d.ts +3 -0
- package/dist/esm/packages/StandalonePackageHost.d.ts.map +1 -0
- package/dist/esm/packages/StandalonePackageHost.js +13 -0
- package/dist/esm/packages/StandalonePackageHost.js.map +1 -0
- package/dist/esm/packages/WebViewPackageHost.d.ts +3 -0
- package/dist/esm/packages/WebViewPackageHost.d.ts.map +1 -0
- package/dist/esm/packages/WebViewPackageHost.js +13 -0
- package/dist/esm/packages/WebViewPackageHost.js.map +1 -0
- package/dist/esm/packages/index.d.ts +12 -0
- package/dist/esm/packages/index.d.ts.map +1 -1
- package/dist/esm/packages/index.js +12 -0
- package/dist/esm/packages/index.js.map +1 -1
- package/dist/esm/renderer/AbortableQueryRunner.d.ts +7 -0
- package/dist/esm/renderer/AbortableQueryRunner.d.ts.map +1 -0
- package/dist/esm/renderer/AbortableQueryRunner.js +24 -0
- package/dist/esm/renderer/AbortableQueryRunner.js.map +1 -0
- package/dist/esm/renderer/InteractiveComponent.d.ts.map +1 -1
- package/dist/esm/renderer/InteractiveComponent.js +35 -2
- package/dist/esm/renderer/InteractiveComponent.js.map +1 -1
- package/dist/esm/renderer/RenderBindingScopeContext.d.ts +14 -0
- package/dist/esm/renderer/RenderBindingScopeContext.d.ts.map +1 -0
- package/dist/esm/renderer/RenderBindingScopeContext.js +73 -0
- package/dist/esm/renderer/RenderBindingScopeContext.js.map +1 -0
- package/dist/esm/renderer/index.d.ts +2 -0
- package/dist/esm/renderer/index.d.ts.map +1 -1
- package/dist/esm/renderer/index.js +2 -0
- package/dist/esm/renderer/index.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/index.ts +1 -0
- package/navigation/DestinationResolution.ts +13 -0
- package/navigation/SceneNavigationContext.tsx +34 -0
- package/navigation/SceneNavigationHost.tsx +67 -0
- package/navigation/index.ts +7 -0
- package/navigation/resolveDestination.ts +50 -0
- package/package.json +5 -5
- package/packages/DesignTimeAction.ts +3 -4
- package/packages/DesignTimeBundle.ts +8 -5
- package/packages/DesignTimeComponentProps.ts +11 -0
- package/packages/DesignTimeContext.ts +32 -0
- package/packages/DesignTimePropertyDisplayProps.ts +14 -0
- package/packages/DesignTimePropertyEditorProps.ts +15 -0
- package/packages/DesignTimeProvider.tsx +25 -0
- package/packages/EmbeddedPackageHost.tsx +10 -0
- package/packages/PackageHostAssets.tsx +18 -0
- package/packages/PackageHostConfiguration.ts +62 -5
- package/packages/PackageHostRenderMode.ts +11 -0
- package/packages/PackageHostView.tsx +43 -0
- package/packages/StandalonePackageHost.tsx +10 -0
- package/packages/WebViewPackageHost.tsx +10 -0
- package/packages/index.ts +12 -0
- package/renderer/AbortableQueryRunner.ts +27 -0
- package/renderer/InteractiveComponent.tsx +32 -3
- package/renderer/RenderBindingScopeContext.tsx +73 -0
- package/renderer/index.ts +2 -0
package/index.ts
CHANGED
|
@@ -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,34 @@
|
|
|
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 { BindingScope } from '@cratis/scene.engine';
|
|
6
|
+
import { DestinationReference } from '@cratis/scene.model';
|
|
7
|
+
import { DestinationResolution } from './DestinationResolution';
|
|
8
|
+
|
|
9
|
+
export interface SceneNavigationContextValue {
|
|
10
|
+
currentScreen: string;
|
|
11
|
+
currentUrl?: string;
|
|
12
|
+
currentOutlet?: string;
|
|
13
|
+
currentDialog?: string;
|
|
14
|
+
bindingScope: BindingScope;
|
|
15
|
+
navigate(destination: DestinationReference): DestinationResolution;
|
|
16
|
+
closeDialog(): void;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const Context = createContext<SceneNavigationContextValue | undefined>(undefined);
|
|
20
|
+
|
|
21
|
+
export interface SceneNavigationProviderProps {
|
|
22
|
+
value: SceneNavigationContextValue;
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function SceneNavigationProvider({ value, children }: SceneNavigationProviderProps) {
|
|
27
|
+
return <Context.Provider value={value}>{children}</Context.Provider>;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function useSceneNavigation(): SceneNavigationContextValue {
|
|
31
|
+
const context = useContext(Context);
|
|
32
|
+
if (!context) throw new Error('No Scene navigation context is available.');
|
|
33
|
+
return context;
|
|
34
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
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 { useCallback, useMemo, useState } from 'react';
|
|
5
|
+
import { BindingScope } from '@cratis/scene.engine';
|
|
6
|
+
import { DestinationKind, DestinationReference, SceneElement } from '@cratis/scene.model';
|
|
7
|
+
import { SceneElementView } from '../SceneElementView';
|
|
8
|
+
import { ComponentRegistry } from '../renderer';
|
|
9
|
+
import { SceneNavigationContextValue, SceneNavigationProvider } from './SceneNavigationContext';
|
|
10
|
+
import { resolveDestination } from './resolveDestination';
|
|
11
|
+
|
|
12
|
+
export interface SceneNavigationHostProps {
|
|
13
|
+
screens: Record<string, SceneElement>;
|
|
14
|
+
dialogs?: Record<string, SceneElement>;
|
|
15
|
+
initialScreen: string;
|
|
16
|
+
registry: ComponentRegistry;
|
|
17
|
+
bindingScope?: BindingScope;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Executes Scene destinations against an in-memory rendered hierarchy: screen routes replace outlets,
|
|
22
|
+
* dialog destinations render dialog content, and external destinations only report their resolved action.
|
|
23
|
+
*/
|
|
24
|
+
export function SceneNavigationHost({ screens, dialogs = {}, initialScreen, registry, bindingScope = {} }: SceneNavigationHostProps) {
|
|
25
|
+
const [currentScreen, setCurrentScreen] = useState(initialScreen);
|
|
26
|
+
const [currentUrl, setCurrentUrl] = useState<string | undefined>(initialScreen);
|
|
27
|
+
const [currentOutlet, setCurrentOutlet] = useState<string | undefined>();
|
|
28
|
+
const [currentDialog, setCurrentDialog] = useState<string | undefined>();
|
|
29
|
+
|
|
30
|
+
const navigate = useCallback((destination: DestinationReference) => {
|
|
31
|
+
const resolution = resolveDestination(destination, bindingScope);
|
|
32
|
+
if (resolution.kind === DestinationKind.Dialog) {
|
|
33
|
+
setCurrentDialog(resolution.dialog);
|
|
34
|
+
return resolution;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (resolution.kind !== DestinationKind.External) {
|
|
38
|
+
setCurrentScreen(destination.screen ?? destination.slice ?? currentScreen);
|
|
39
|
+
setCurrentOutlet(resolution.outlet);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
setCurrentUrl(resolution.url);
|
|
43
|
+
return resolution;
|
|
44
|
+
}, [bindingScope, currentScreen]);
|
|
45
|
+
|
|
46
|
+
const value = useMemo<SceneNavigationContextValue>(() => ({
|
|
47
|
+
currentScreen,
|
|
48
|
+
currentUrl,
|
|
49
|
+
currentOutlet,
|
|
50
|
+
currentDialog,
|
|
51
|
+
bindingScope,
|
|
52
|
+
navigate,
|
|
53
|
+
closeDialog: () => setCurrentDialog(undefined),
|
|
54
|
+
}), [bindingScope, currentDialog, currentOutlet, currentScreen, currentUrl, navigate]);
|
|
55
|
+
|
|
56
|
+
const screen = screens[currentScreen];
|
|
57
|
+
const dialog = currentDialog ? dialogs[currentDialog] : undefined;
|
|
58
|
+
|
|
59
|
+
return <SceneNavigationProvider value={value}>
|
|
60
|
+
<div data-scene-outlet={currentOutlet ?? 'primary'} data-scene-url={currentUrl}>
|
|
61
|
+
{screen && <SceneElementView element={screen} registry={registry} dataContext={bindingScope.dataContext} queryResults={bindingScope.queryResults} componentOutputs={bindingScope.componentOutputs} />}
|
|
62
|
+
</div>
|
|
63
|
+
{dialog && <div role='dialog' data-scene-dialog={currentDialog}>
|
|
64
|
+
<SceneElementView element={dialog} registry={registry} dataContext={bindingScope.dataContext} queryResults={bindingScope.queryResults} componentOutputs={bindingScope.componentOutputs} />
|
|
65
|
+
</div>}
|
|
66
|
+
</SceneNavigationProvider>;
|
|
67
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
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 './SceneNavigationContext';
|
|
6
|
+
export * from './SceneNavigationHost';
|
|
7
|
+
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.
|
|
3
|
+
"version": "4.11.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.
|
|
55
|
-
"@cratis/scene.model": "4.
|
|
54
|
+
"@cratis/scene.engine": "4.11.0",
|
|
55
|
+
"@cratis/scene.model": "4.11.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@cratis/scene.engine": "4.
|
|
59
|
-
"@cratis/scene.model": "4.
|
|
58
|
+
"@cratis/scene.engine": "4.11.0",
|
|
59
|
+
"@cratis/scene.model": "4.11.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 {
|
|
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
|
|
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<
|
|
12
|
-
designers?: Record<string, ComponentType<
|
|
13
|
-
propertyEditors?: Record<string, ComponentType<
|
|
14
|
-
propertyDisplays?: Record<string, ComponentType<
|
|
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
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
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 { PackageHostView, PackageHostViewProps } from './PackageHostView';
|
|
5
|
+
import { PackageHostRenderMode } from './PackageHostRenderMode';
|
|
6
|
+
|
|
7
|
+
/** An embedded package host example surface for application or designer panes. */
|
|
8
|
+
export function EmbeddedPackageHost(props: Omit<PackageHostViewProps, 'mode'>) {
|
|
9
|
+
return <PackageHostView {...props} mode={PackageHostRenderMode.Embedded} />;
|
|
10
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
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 interface PackageHostAssetsProps {
|
|
5
|
+
assets: string[];
|
|
6
|
+
fonts: string[];
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Emits package-declared browser assets for a package host example.
|
|
11
|
+
*/
|
|
12
|
+
export function PackageHostAssets({ assets, fonts }: PackageHostAssetsProps) {
|
|
13
|
+
const fontSet = new Set(fonts);
|
|
14
|
+
return <>
|
|
15
|
+
{assets.filter(asset => !fontSet.has(asset)).map(asset => <link key={asset} rel='stylesheet' href={asset} data-scene-package-asset={asset} />)}
|
|
16
|
+
{fonts.map(font => <link key={font} rel='preload' href={font} as='font' crossOrigin='' data-scene-package-font={font} />)}
|
|
17
|
+
</>;
|
|
18
|
+
}
|
|
@@ -1,12 +1,17 @@
|
|
|
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 {
|
|
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;
|
|
9
11
|
allowNetworkAssets: boolean;
|
|
12
|
+
|
|
13
|
+
/** Whether this host is resolving optional design-time bundles in addition to runtime contributions. */
|
|
14
|
+
loadDesignTime?: boolean;
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
export interface PackageHostConfiguration {
|
|
@@ -18,6 +23,14 @@ export interface PackageHostConfiguration {
|
|
|
18
23
|
export interface ResolvedPackageHost {
|
|
19
24
|
bundles: ScenePackageBundle[];
|
|
20
25
|
components: ReturnType<typeof mergePackageRegistries>;
|
|
26
|
+
layouts: Layout[];
|
|
27
|
+
screenTemplates: ScreenTemplate[];
|
|
28
|
+
dialogTemplates: DialogTemplate[];
|
|
29
|
+
themes: Theme[];
|
|
30
|
+
icons: IconAdapterRegistry;
|
|
31
|
+
assets: string[];
|
|
32
|
+
fonts: string[];
|
|
33
|
+
blocked: boolean;
|
|
21
34
|
diagnostics: string[];
|
|
22
35
|
}
|
|
23
36
|
|
|
@@ -26,20 +39,44 @@ export interface ResolvedPackageHost {
|
|
|
26
39
|
*/
|
|
27
40
|
export function resolvePackageHost(configuration: PackageHostConfiguration): ResolvedPackageHost {
|
|
28
41
|
const diagnostics: string[] = [];
|
|
42
|
+
const names = new Map<string, number>();
|
|
43
|
+
for (const bundle of configuration.bundles) {
|
|
44
|
+
names.set(bundle.manifest.name, (names.get(bundle.manifest.name) ?? 0) + 1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
for (const [name, count] of names) {
|
|
48
|
+
if (count > 1) diagnostics.push(`Package '${name}' is approved more than once; duplicate bundles are ambiguous`);
|
|
49
|
+
}
|
|
50
|
+
|
|
29
51
|
const byName = new Map(configuration.bundles.map(bundle => [bundle.manifest.name, bundle]));
|
|
52
|
+
const selection = resolvePackageDependencies(configuration.profile.packages, configuration.bundles.map(bundle => bundle.manifest));
|
|
53
|
+
for (const missing of selection.missing) diagnostics.push(`Package '${missing.package}' depends on missing package '${missing.dependsOn}'`);
|
|
54
|
+
for (const conflict of selection.versionConflicts) {
|
|
55
|
+
diagnostics.push(`Package '${conflict.package}' requires '${conflict.dependsOn}' ${conflict.requiredRange} but approved version is ${conflict.actualVersion}`);
|
|
56
|
+
}
|
|
57
|
+
for (const cycle of selection.cycles) diagnostics.push(`Package dependency cycle: ${cycle.join(' -> ')}`);
|
|
58
|
+
|
|
30
59
|
const bundles: ScenePackageBundle[] = [];
|
|
31
60
|
const runtimeSingletons = new Map<string, string>();
|
|
61
|
+
const assets: string[] = [];
|
|
32
62
|
|
|
33
|
-
for (const packageName of
|
|
63
|
+
for (const packageName of selection.packages) {
|
|
34
64
|
const bundle = byName.get(packageName);
|
|
35
65
|
if (!bundle) {
|
|
36
66
|
diagnostics.push(`UI profile asks for '${packageName}' but the host did not approve a bundle for it`);
|
|
37
67
|
continue;
|
|
38
68
|
}
|
|
39
69
|
|
|
40
|
-
|
|
70
|
+
diagnostics.push(...validatePackageBundle(bundle).map(problem => `${packageName}: ${problem}`));
|
|
71
|
+
if (configuration.policy.loadDesignTime === true && !configuration.policy.allowExecutableImports && bundle.designTime) {
|
|
72
|
+
diagnostics.push(`Package '${packageName}' provides executable design-time contributions, but host policy forbids them`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const packageAssets = bundle.manifest.assets ?? [];
|
|
76
|
+
if (!configuration.policy.allowNetworkAssets && packageAssets.some(asset => /^https?:\/\//.test(asset))) {
|
|
41
77
|
diagnostics.push(`Package '${packageName}' declares network assets, but host policy forbids them`);
|
|
42
78
|
}
|
|
79
|
+
assets.push(...packageAssets);
|
|
43
80
|
|
|
44
81
|
for (const dependency of bundle.manifest.runtimeSingletons ?? []) {
|
|
45
82
|
const owner = runtimeSingletons.get(dependency);
|
|
@@ -52,5 +89,25 @@ export function resolvePackageHost(configuration: PackageHostConfiguration): Res
|
|
|
52
89
|
bundles.push(bundle);
|
|
53
90
|
}
|
|
54
91
|
|
|
55
|
-
|
|
92
|
+
const blocked = diagnostics.length > 0;
|
|
93
|
+
const approved = blocked ? [] : bundles;
|
|
94
|
+
const approvedAssets = blocked ? [] : assets;
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
bundles: approved,
|
|
98
|
+
components: mergePackageRegistries(approved),
|
|
99
|
+
layouts: approved.flatMap(bundle => bundle.layouts ?? []),
|
|
100
|
+
screenTemplates: approved.flatMap(bundle => bundle.screenTemplates ?? []),
|
|
101
|
+
dialogTemplates: approved.flatMap(bundle => bundle.dialogTemplates ?? []),
|
|
102
|
+
themes: approved.flatMap(bundle => bundle.themes ?? []),
|
|
103
|
+
icons: createIconAdapterRegistry(approved.map(bundle => bundle.iconLibrary?.adapter).filter(adapter => adapter !== undefined)),
|
|
104
|
+
assets: approvedAssets,
|
|
105
|
+
fonts: approvedAssets.filter(isFontAsset),
|
|
106
|
+
blocked,
|
|
107
|
+
diagnostics,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function isFontAsset(asset: string): boolean {
|
|
112
|
+
return /\.(woff2?|otf|ttf)(\?|$)/.test(asset) || asset.includes('/fonts/');
|
|
56
113
|
}
|
|
@@ -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
|
+
/**
|
|
5
|
+
* The host surface rendering a resolved package profile.
|
|
6
|
+
*/
|
|
7
|
+
export enum PackageHostRenderMode {
|
|
8
|
+
Standalone = 'standalone',
|
|
9
|
+
Embedded = 'embedded',
|
|
10
|
+
WebView = 'webView',
|
|
11
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
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 { SceneElement } from '@cratis/scene.model';
|
|
5
|
+
import { SceneElementView } from '../SceneElementView';
|
|
6
|
+
import { PackageHostAssets } from './PackageHostAssets';
|
|
7
|
+
import { PackageHostConfiguration, resolvePackageHost } from './PackageHostConfiguration';
|
|
8
|
+
import { PackageHostRenderMode } from './PackageHostRenderMode';
|
|
9
|
+
|
|
10
|
+
export interface PackageHostViewProps {
|
|
11
|
+
configuration: PackageHostConfiguration;
|
|
12
|
+
element: SceneElement;
|
|
13
|
+
mode?: PackageHostRenderMode;
|
|
14
|
+
dataContext?: unknown;
|
|
15
|
+
queryResults?: Record<string, unknown>;
|
|
16
|
+
componentOutputs?: Record<string, Record<string, unknown>>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Renders a Scene element through an approved package host. Diagnostics are blocking: no rejected bundle,
|
|
21
|
+
* component, asset or design-time contribution is rendered after policy/dependency validation fails.
|
|
22
|
+
*/
|
|
23
|
+
export function PackageHostView({
|
|
24
|
+
configuration,
|
|
25
|
+
element,
|
|
26
|
+
mode = PackageHostRenderMode.Embedded,
|
|
27
|
+
dataContext,
|
|
28
|
+
queryResults,
|
|
29
|
+
componentOutputs,
|
|
30
|
+
}: PackageHostViewProps) {
|
|
31
|
+
const host = resolvePackageHost(configuration);
|
|
32
|
+
if (host.blocked) {
|
|
33
|
+
return <section role='alert' data-scene-host-mode={mode} data-scene-host-blocked='true'>
|
|
34
|
+
<h2>Scene package host blocked rendering</h2>
|
|
35
|
+
<ul>{host.diagnostics.map(diagnostic => <li key={diagnostic}>{diagnostic}</li>)}</ul>
|
|
36
|
+
</section>;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return <section data-scene-host-mode={mode} data-scene-host-blocked='false'>
|
|
40
|
+
<PackageHostAssets assets={host.assets} fonts={host.fonts} />
|
|
41
|
+
<SceneElementView element={element} registry={host.components} dataContext={dataContext} queryResults={queryResults} componentOutputs={componentOutputs} />
|
|
42
|
+
</section>;
|
|
43
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
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 { PackageHostView, PackageHostViewProps } from './PackageHostView';
|
|
5
|
+
import { PackageHostRenderMode } from './PackageHostRenderMode';
|
|
6
|
+
|
|
7
|
+
/** A standalone browser package host example surface. */
|
|
8
|
+
export function StandalonePackageHost(props: Omit<PackageHostViewProps, 'mode'>) {
|
|
9
|
+
return <PackageHostView {...props} mode={PackageHostRenderMode.Standalone} />;
|
|
10
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
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 { PackageHostView, PackageHostViewProps } from './PackageHostView';
|
|
5
|
+
import { PackageHostRenderMode } from './PackageHostRenderMode';
|
|
6
|
+
|
|
7
|
+
/** A constrained webview package host example surface. */
|
|
8
|
+
export function WebViewPackageHost(props: Omit<PackageHostViewProps, 'mode'>) {
|
|
9
|
+
return <PackageHostView {...props} mode={PackageHostRenderMode.WebView} />;
|
|
10
|
+
}
|
package/packages/index.ts
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
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';
|
|
12
|
+
export * from './EmbeddedPackageHost';
|
|
13
|
+
export * from './PackageHostAssets';
|
|
6
14
|
export * from './PackageHostConfiguration';
|
|
15
|
+
export * from './PackageHostRenderMode';
|
|
16
|
+
export * from './PackageHostView';
|
|
7
17
|
export * from './ScenePackageBundle';
|
|
18
|
+
export * from './StandalonePackageHost';
|
|
19
|
+
export * from './WebViewPackageHost';
|
|
8
20
|
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
|
+
}
|