@cratis/scene.react 4.3.0 → 4.5.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/core/CoreNavigationBar.tsx +8 -2
- package/dist/cjs/core/CoreNavigationBar.js +13 -3
- package/dist/cjs/core/CoreNavigationBar.js.map +1 -1
- package/dist/cjs/icons/IconAdapterContext.js +21 -0
- package/dist/cjs/icons/IconAdapterContext.js.map +1 -0
- package/dist/cjs/icons/IconAdapterRegistry.js +45 -0
- package/dist/cjs/icons/IconAdapterRegistry.js.map +1 -0
- package/dist/cjs/icons/SceneIcon.js +42 -0
- package/dist/cjs/icons/SceneIcon.js.map +1 -0
- package/dist/cjs/icons/useIconGlyph.js +86 -0
- package/dist/cjs/icons/useIconGlyph.js.map +1 -0
- package/dist/cjs/index.js +11 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/packages/ScenePackageBundle.js +41 -0
- package/dist/cjs/packages/ScenePackageBundle.js.map +1 -1
- package/dist/esm/core/CoreNavigationBar.d.ts.map +1 -1
- package/dist/esm/core/CoreNavigationBar.js +14 -4
- package/dist/esm/core/CoreNavigationBar.js.map +1 -1
- package/dist/esm/icons/IconAdapter.d.ts +13 -0
- package/dist/esm/icons/IconAdapter.d.ts.map +1 -0
- package/dist/esm/icons/IconAdapter.js +2 -0
- package/dist/esm/icons/IconAdapter.js.map +1 -0
- package/dist/esm/icons/IconAdapterContext.d.ts +8 -0
- package/dist/esm/icons/IconAdapterContext.d.ts.map +1 -0
- package/dist/esm/icons/IconAdapterContext.js +18 -0
- package/dist/esm/icons/IconAdapterContext.js.map +1 -0
- package/dist/esm/icons/IconAdapterRegistry.d.ts +9 -0
- package/dist/esm/icons/IconAdapterRegistry.d.ts.map +1 -0
- package/dist/esm/icons/IconAdapterRegistry.js +42 -0
- package/dist/esm/icons/IconAdapterRegistry.js.map +1 -0
- package/dist/esm/icons/IconLibraryBundle.d.ts +7 -0
- package/dist/esm/icons/IconLibraryBundle.d.ts.map +1 -0
- package/dist/esm/icons/IconLibraryBundle.js +2 -0
- package/dist/esm/icons/IconLibraryBundle.js.map +1 -0
- package/dist/esm/icons/SceneIcon.d.ts +14 -0
- package/dist/esm/icons/SceneIcon.d.ts.map +1 -0
- package/dist/esm/icons/SceneIcon.js +40 -0
- package/dist/esm/icons/SceneIcon.js.map +1 -0
- package/dist/esm/icons/index.d.ts +7 -0
- package/dist/esm/icons/index.d.ts.map +1 -0
- package/dist/esm/icons/index.js +7 -0
- package/dist/esm/icons/index.js.map +1 -0
- package/dist/esm/icons/useIconGlyph.d.ts +14 -0
- package/dist/esm/icons/useIconGlyph.d.ts.map +1 -0
- package/dist/esm/icons/useIconGlyph.js +84 -0
- package/dist/esm/icons/useIconGlyph.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +5 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/packages/ScenePackageBundle.d.ts +4 -0
- package/dist/esm/packages/ScenePackageBundle.d.ts.map +1 -1
- package/dist/esm/packages/ScenePackageBundle.js +42 -2
- package/dist/esm/packages/ScenePackageBundle.js.map +1 -1
- package/dist/esm/tsconfig.tsbuildinfo +1 -1
- package/icons/IconAdapter.ts +52 -0
- package/icons/IconAdapterContext.tsx +21 -0
- package/icons/IconAdapterRegistry.ts +50 -0
- package/icons/IconLibraryBundle.ts +25 -0
- package/icons/SceneIcon.tsx +73 -0
- package/icons/index.ts +9 -0
- package/icons/useIconGlyph.ts +79 -0
- package/index.ts +1 -0
- package/package.json +5 -5
- package/packages/ScenePackageBundle.ts +58 -1
|
@@ -0,0 +1,52 @@
|
|
|
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 } from 'react';
|
|
5
|
+
import { IconReference } from '@cratis/scene.model';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* What an icon library's glyph is handed to draw itself. The glyph owns only its artwork: `SceneIcon`
|
|
9
|
+
* has already sized and colored the box it sits in and made it accessible, so a glyph just fills it.
|
|
10
|
+
*/
|
|
11
|
+
export interface IconGlyphProps {
|
|
12
|
+
/**
|
|
13
|
+
* The width and height to draw at - a number of pixels or any CSS length.
|
|
14
|
+
*/
|
|
15
|
+
size: number | string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The color to draw in; `currentColor` unless the caller chose one.
|
|
19
|
+
*/
|
|
20
|
+
color: string;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* An optional class for the artwork element.
|
|
24
|
+
*/
|
|
25
|
+
className?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* One icon's artwork, as a component.
|
|
30
|
+
*/
|
|
31
|
+
export type IconGlyph = ComponentType<IconGlyphProps>;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* How one icon library's references become pixels in React. The model persists only an
|
|
35
|
+
* {@link IconReference}; an adapter is the library's own answer to "what does this one look like here",
|
|
36
|
+
* so no markup, class name or component is ever persisted - and a library can be re-rendered, or replaced
|
|
37
|
+
* by one with an adapter for another renderer, without touching a single stored value.
|
|
38
|
+
*
|
|
39
|
+
* Artwork is loaded per icon and on demand, so a library with thousands of icons costs a screen only the
|
|
40
|
+
* ones it shows.
|
|
41
|
+
*/
|
|
42
|
+
export interface IconAdapter {
|
|
43
|
+
/**
|
|
44
|
+
* The library this adapter renders - an {@link IconReference.library}.
|
|
45
|
+
*/
|
|
46
|
+
library: string;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Loads the artwork for one reference. Resolves to `undefined` when the library has no such icon.
|
|
50
|
+
*/
|
|
51
|
+
loadGlyph(reference: IconReference): Promise<IconGlyph | undefined> | IconGlyph | undefined;
|
|
52
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
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 { IconAdapterRegistry } from './IconAdapterRegistry';
|
|
6
|
+
|
|
7
|
+
const IconAdapterContext = createContext<IconAdapterRegistry | undefined>(undefined);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Supplies the icon adapters every `SceneIcon` below it renders through.
|
|
11
|
+
*/
|
|
12
|
+
export const IconAdapterProvider = ({ registry, children }: { registry: IconAdapterRegistry; children?: ReactNode }) => (
|
|
13
|
+
<IconAdapterContext.Provider value={registry}>{children}</IconAdapterContext.Provider>
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The icon adapters in scope, or `undefined` outside an {@link IconAdapterProvider}.
|
|
18
|
+
*/
|
|
19
|
+
export function useIconAdapters(): IconAdapterRegistry | undefined {
|
|
20
|
+
return useContext(IconAdapterContext);
|
|
21
|
+
}
|
|
@@ -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 { IconAdapter } from './IconAdapter';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The icon adapters a renderer has, one per library. Registering is explicit and never replaces: two
|
|
8
|
+
* adapters for one library would mean an icon renders differently depending on registration order, which
|
|
9
|
+
* is the silent swapping the library-qualified reference exists to prevent.
|
|
10
|
+
*/
|
|
11
|
+
export class IconAdapterRegistry {
|
|
12
|
+
readonly #adapters = new Map<string, IconAdapter>();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Adds an adapter.
|
|
16
|
+
*
|
|
17
|
+
* @throws Error when an adapter for the same library is already registered.
|
|
18
|
+
*/
|
|
19
|
+
register(adapter: IconAdapter): this {
|
|
20
|
+
if (this.#adapters.has(adapter.library)) {
|
|
21
|
+
throw new Error(`An icon adapter for the library '${adapter.library}' is already registered`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.#adapters.set(adapter.library, adapter);
|
|
25
|
+
return this;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The adapter for a library, or `undefined` when this renderer has none.
|
|
30
|
+
*/
|
|
31
|
+
get(library: string): IconAdapter | undefined {
|
|
32
|
+
return this.#adapters.get(library);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The libraries this registry can render.
|
|
37
|
+
*/
|
|
38
|
+
get libraries(): string[] {
|
|
39
|
+
return [...this.#adapters.keys()];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Creates a registry holding the given adapters.
|
|
45
|
+
*/
|
|
46
|
+
export function createIconAdapterRegistry(adapters: IconAdapter[] = []): IconAdapterRegistry {
|
|
47
|
+
const registry = new IconAdapterRegistry();
|
|
48
|
+
adapters.forEach((adapter) => registry.register(adapter));
|
|
49
|
+
return registry;
|
|
50
|
+
}
|
|
@@ -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 { IconCatalogSource } from '@cratis/scene.engine';
|
|
5
|
+
import { IconAdapter } from './IconAdapter';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* What an icon-library package ships to a React renderer, as opposed to what its manifest declares.
|
|
9
|
+
*
|
|
10
|
+
* The manifest says the library exists, what it is licensed under and which renderers it supports; this
|
|
11
|
+
* is the code behind it. The catalog is the metadata a picker searches, loaded on demand; the adapter
|
|
12
|
+
* turns one reference at a time into artwork. They are separate because a catalog is renderer-neutral
|
|
13
|
+
* and an adapter is not.
|
|
14
|
+
*/
|
|
15
|
+
export interface IconLibraryBundle {
|
|
16
|
+
/**
|
|
17
|
+
* Where the library's icon catalog is loaded from.
|
|
18
|
+
*/
|
|
19
|
+
catalog: IconCatalogSource;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* How the library's references are drawn in React.
|
|
23
|
+
*/
|
|
24
|
+
adapter: IconAdapter;
|
|
25
|
+
}
|
|
@@ -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 } from 'react';
|
|
5
|
+
import { IconReference } from '@cratis/scene.model';
|
|
6
|
+
import { IconAdapterRegistry } from './IconAdapterRegistry';
|
|
7
|
+
import { useIconAdapters } from './IconAdapterContext';
|
|
8
|
+
import { useIconGlyph } from './useIconGlyph';
|
|
9
|
+
|
|
10
|
+
export interface SceneIconProps {
|
|
11
|
+
/**
|
|
12
|
+
* The icon to draw.
|
|
13
|
+
*/
|
|
14
|
+
reference: IconReference;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The adapters to render through. Defaults to the ones an `IconAdapterProvider` supplies.
|
|
18
|
+
*/
|
|
19
|
+
adapters?: IconAdapterRegistry;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The width and height of the icon: a number of pixels or any CSS length. Defaults to `1em`, so an
|
|
23
|
+
* icon scales with the text it sits in.
|
|
24
|
+
*/
|
|
25
|
+
size?: number | string;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The color to draw in. Defaults to `currentColor`.
|
|
29
|
+
*/
|
|
30
|
+
color?: string;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The accessible name. With a label the icon is announced as an image; without one it is decorative
|
|
34
|
+
* and hidden from assistive technology - an icon next to its own text label should not be read twice.
|
|
35
|
+
*/
|
|
36
|
+
label?: string;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* An optional class for the box the icon sits in.
|
|
40
|
+
*/
|
|
41
|
+
className?: string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* What to show when the icon cannot be drawn - no adapter, no such icon, a failed load. Nothing by
|
|
45
|
+
* default; the box keeps its size so layout does not shift.
|
|
46
|
+
*/
|
|
47
|
+
fallback?: ReactNode;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Draws a renderer-neutral {@link IconReference} through the adapter registered for its library, honoring
|
|
52
|
+
* size, color and accessible label. The reference is all that is persisted; this is where it becomes pixels.
|
|
53
|
+
*/
|
|
54
|
+
export const SceneIcon = ({ reference, adapters, size = '1em', color = 'currentColor', label, className, fallback }: SceneIconProps) => {
|
|
55
|
+
const inScope = useIconAdapters();
|
|
56
|
+
const state = useIconGlyph(reference, adapters ?? inScope);
|
|
57
|
+
const Glyph = state.status === 'ready' ? state.glyph : undefined;
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<span
|
|
61
|
+
className={className}
|
|
62
|
+
role={label ? 'img' : undefined}
|
|
63
|
+
aria-label={label}
|
|
64
|
+
aria-hidden={label ? undefined : true}
|
|
65
|
+
data-icon-library={reference.library}
|
|
66
|
+
data-icon-key={reference.key}
|
|
67
|
+
data-icon-variant={reference.variant}
|
|
68
|
+
data-icon-state={state.status}
|
|
69
|
+
style={{ display: 'inline-flex', alignItems: 'center', justifyContent: 'center', width: size, height: size, color, flexShrink: 0 }}>
|
|
70
|
+
{Glyph ? <Glyph size={size} color={color} /> : state.status === 'unavailable' ? fallback : null}
|
|
71
|
+
</span>
|
|
72
|
+
);
|
|
73
|
+
};
|
package/icons/index.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
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 './IconAdapter';
|
|
5
|
+
export * from './IconAdapterRegistry';
|
|
6
|
+
export * from './IconAdapterContext';
|
|
7
|
+
export * from './useIconGlyph';
|
|
8
|
+
export * from './SceneIcon';
|
|
9
|
+
export * from './IconLibraryBundle';
|
|
@@ -0,0 +1,79 @@
|
|
|
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 { useEffect, useState } from 'react';
|
|
5
|
+
import { IconReference } from '@cratis/scene.model';
|
|
6
|
+
import { IconAdapter, IconGlyph } from './IconAdapter';
|
|
7
|
+
import { IconAdapterRegistry } from './IconAdapterRegistry';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Where loading one icon's artwork stands.
|
|
11
|
+
*/
|
|
12
|
+
export type IconGlyphState =
|
|
13
|
+
| { status: 'loading' }
|
|
14
|
+
| { status: 'ready'; glyph: IconGlyph }
|
|
15
|
+
| { status: 'unavailable'; reason: string };
|
|
16
|
+
|
|
17
|
+
const loads = new WeakMap<IconAdapter, Map<string, Promise<IconGlyph | undefined>>>();
|
|
18
|
+
|
|
19
|
+
function keyOf(reference: IconReference): string {
|
|
20
|
+
return JSON.stringify([reference.key, reference.variant ?? null]);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* One load per adapter and icon, shared between every place the icon is drawn; a failed load is
|
|
25
|
+
* forgotten so the next render can try again.
|
|
26
|
+
*/
|
|
27
|
+
function load(adapter: IconAdapter, reference: IconReference): Promise<IconGlyph | undefined> {
|
|
28
|
+
let perAdapter = loads.get(adapter);
|
|
29
|
+
if (!perAdapter) {
|
|
30
|
+
perAdapter = new Map();
|
|
31
|
+
loads.set(adapter, perAdapter);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const key = keyOf(reference);
|
|
35
|
+
let pending = perAdapter.get(key);
|
|
36
|
+
if (!pending) {
|
|
37
|
+
pending = Promise.resolve().then(() => adapter.loadGlyph(reference));
|
|
38
|
+
perAdapter.set(key, pending);
|
|
39
|
+
pending.catch(() => perAdapter.delete(key));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return pending;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Loads the artwork for a reference through the adapter registered for its library. An absent registry,
|
|
47
|
+
* a library with no adapter, an icon the library lacks and a failed load are all `unavailable`, each with
|
|
48
|
+
* its reason - never a different icon.
|
|
49
|
+
*/
|
|
50
|
+
export function useIconGlyph(reference: IconReference, adapters: IconAdapterRegistry | undefined): IconGlyphState {
|
|
51
|
+
const [state, setState] = useState<IconGlyphState>({ status: 'loading' });
|
|
52
|
+
const { library, key, variant } = reference;
|
|
53
|
+
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
let isCurrent = true;
|
|
56
|
+
const adapter = adapters?.get(library);
|
|
57
|
+
if (!adapter) {
|
|
58
|
+
setState({ status: 'unavailable', reason: `No icon adapter is registered for the library '${library}'` });
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
setState({ status: 'loading' });
|
|
63
|
+
load(adapter, { library, key, variant }).then(
|
|
64
|
+
(glyph) => {
|
|
65
|
+
if (!isCurrent) return;
|
|
66
|
+
setState(glyph ? { status: 'ready', glyph } : { status: 'unavailable', reason: `The library '${library}' has no icon '${key}'` });
|
|
67
|
+
},
|
|
68
|
+
(error: unknown) => {
|
|
69
|
+
if (isCurrent) setState({ status: 'unavailable', reason: error instanceof Error ? error.message : String(error) });
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return () => {
|
|
74
|
+
isCurrent = false;
|
|
75
|
+
};
|
|
76
|
+
}, [adapters, library, key, variant]);
|
|
77
|
+
|
|
78
|
+
return state;
|
|
79
|
+
}
|
package/index.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cratis/scene.react",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.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.5.0",
|
|
55
|
+
"@cratis/scene.model": "4.5.0"
|
|
56
56
|
},
|
|
57
57
|
"devDependencies": {
|
|
58
|
-
"@cratis/scene.engine": "4.
|
|
59
|
-
"@cratis/scene.model": "4.
|
|
58
|
+
"@cratis/scene.engine": "4.5.0",
|
|
59
|
+
"@cratis/scene.model": "4.5.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,7 +1,9 @@
|
|
|
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, DialogTemplate, Layout, PropertyValueType, ScenePackage, Screen, ScreenTemplate, Theme } from '@cratis/scene.model';
|
|
4
|
+
import { ComponentDescriptor, DialogTemplate, Layout, PackageKind, PropertyValueType, ScenePackage, Screen, ScreenTemplate, Theme } from '@cratis/scene.model';
|
|
5
|
+
import { IconAdapterRegistry, createIconAdapterRegistry } from '../icons/IconAdapterRegistry';
|
|
6
|
+
import { IconLibraryBundle } from '../icons/IconLibraryBundle';
|
|
5
7
|
import { ComponentRegistry } from '../renderer';
|
|
6
8
|
|
|
7
9
|
/**
|
|
@@ -62,6 +64,12 @@ export interface ScenePackageBundle {
|
|
|
62
64
|
* The themes this package ships, if any.
|
|
63
65
|
*/
|
|
64
66
|
themes?: Theme[];
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* The icon catalog and renderer adapter of an icon-library package. Present exactly when the
|
|
70
|
+
* manifest declares an icon library.
|
|
71
|
+
*/
|
|
72
|
+
iconLibrary?: IconLibraryBundle;
|
|
65
73
|
}
|
|
66
74
|
|
|
67
75
|
/**
|
|
@@ -132,7 +140,47 @@ export function validatePackageBundle(bundle: ScenePackageBundle): string[] {
|
|
|
132
140
|
}
|
|
133
141
|
}
|
|
134
142
|
|
|
143
|
+
problems.push(...validateIconLibrary(bundle));
|
|
135
144
|
problems.push(...validateDescriptors(bundle));
|
|
145
|
+
|
|
146
|
+
return problems;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function validateIconLibrary(bundle: ScenePackageBundle): string[] {
|
|
150
|
+
const problems: string[] = [];
|
|
151
|
+
const { manifest, iconLibrary } = bundle;
|
|
152
|
+
const declaresIconLibrary = manifest.kind === PackageKind.IconLibrary || manifest.iconLibrary !== undefined;
|
|
153
|
+
|
|
154
|
+
if (manifest.kind === PackageKind.IconLibrary && !manifest.iconLibrary) {
|
|
155
|
+
problems.push('is an icon library but declares no iconLibrary metadata');
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (manifest.kind !== PackageKind.IconLibrary && manifest.iconLibrary) {
|
|
159
|
+
problems.push('declares iconLibrary metadata but is not of kind IconLibrary');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (declaresIconLibrary && !iconLibrary) {
|
|
163
|
+
problems.push('declares an icon library but the bundle provides no catalog or adapter for it');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!declaresIconLibrary && iconLibrary) {
|
|
167
|
+
problems.push('provides an icon catalog and adapter, which the manifest does not declare');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (iconLibrary) {
|
|
171
|
+
if (iconLibrary.catalog.library !== manifest.name) {
|
|
172
|
+
problems.push(`provides an icon catalog for '${iconLibrary.catalog.library}' instead of '${manifest.name}'`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (iconLibrary.adapter.library !== manifest.name) {
|
|
176
|
+
problems.push(`provides an icon adapter for '${iconLibrary.adapter.library}' instead of '${manifest.name}'`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (manifest.iconLibrary && !manifest.iconLibrary.renderers.includes('react')) {
|
|
180
|
+
problems.push("provides a React icon adapter but does not list 'react' among its icon renderers");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
136
184
|
return problems;
|
|
137
185
|
}
|
|
138
186
|
|
|
@@ -182,3 +230,12 @@ function validateDescriptors(bundle: ScenePackageBundle): string[] {
|
|
|
182
230
|
export function mergePackageRegistries(bundles: ScenePackageBundle[]): ComponentRegistry {
|
|
183
231
|
return Object.assign({}, ...bundles.map((bundle) => bundle.components)) as ComponentRegistry;
|
|
184
232
|
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Collects the icon adapters of several bundles into the registry `SceneIcon` renders through.
|
|
236
|
+
*
|
|
237
|
+
* @throws Error when two bundles provide an adapter for the same library.
|
|
238
|
+
*/
|
|
239
|
+
export function mergeIconAdapters(bundles: ScenePackageBundle[]): IconAdapterRegistry {
|
|
240
|
+
return createIconAdapterRegistry(bundles.flatMap((bundle) => (bundle.iconLibrary ? [bundle.iconLibrary.adapter] : [])));
|
|
241
|
+
}
|