@cratis/scene.react 4.22.1 → 4.24.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 (51) hide show
  1. package/core/coreComponentMetadata.ts +27 -0
  2. package/core/coreDescriptors.ts +6 -2
  3. package/dist/cjs/core/coreComponentMetadata.js +109 -0
  4. package/dist/cjs/core/coreComponentMetadata.js.map +1 -0
  5. package/dist/cjs/core/coreDescriptors.js +4 -1
  6. package/dist/cjs/core/coreDescriptors.js.map +1 -1
  7. package/dist/cjs/index.js +10 -0
  8. package/dist/cjs/index.js.map +1 -1
  9. package/dist/cjs/packages/ScenePackageBundle.js +8 -0
  10. package/dist/cjs/packages/ScenePackageBundle.js.map +1 -1
  11. package/dist/cjs/packages/describeComponents.js +64 -0
  12. package/dist/cjs/packages/describeComponents.js.map +1 -0
  13. package/dist/cjs/packages/designModes.js +145 -0
  14. package/dist/cjs/packages/designModes.js.map +1 -0
  15. package/dist/cjs/packages/validateComponentMetadata.js +25 -0
  16. package/dist/cjs/packages/validateComponentMetadata.js.map +1 -0
  17. package/dist/esm/core/coreComponentMetadata.d.ts +3 -0
  18. package/dist/esm/core/coreComponentMetadata.d.ts.map +1 -0
  19. package/dist/esm/core/coreComponentMetadata.js +107 -0
  20. package/dist/esm/core/coreComponentMetadata.js.map +1 -0
  21. package/dist/esm/core/coreDescriptors.d.ts.map +1 -1
  22. package/dist/esm/core/coreDescriptors.js +4 -1
  23. package/dist/esm/core/coreDescriptors.js.map +1 -1
  24. package/dist/esm/index.js +3 -0
  25. package/dist/esm/index.js.map +1 -1
  26. package/dist/esm/packages/ScenePackageBundle.d.ts.map +1 -1
  27. package/dist/esm/packages/ScenePackageBundle.js +9 -1
  28. package/dist/esm/packages/ScenePackageBundle.js.map +1 -1
  29. package/dist/esm/packages/describeComponents.d.ts +12 -0
  30. package/dist/esm/packages/describeComponents.d.ts.map +1 -0
  31. package/dist/esm/packages/describeComponents.js +61 -0
  32. package/dist/esm/packages/describeComponents.js.map +1 -0
  33. package/dist/esm/packages/designModes.d.ts +10 -0
  34. package/dist/esm/packages/designModes.d.ts.map +1 -0
  35. package/dist/esm/packages/designModes.js +140 -0
  36. package/dist/esm/packages/designModes.js.map +1 -0
  37. package/dist/esm/packages/index.d.ts +3 -0
  38. package/dist/esm/packages/index.d.ts.map +1 -1
  39. package/dist/esm/packages/index.js +3 -0
  40. package/dist/esm/packages/index.js.map +1 -1
  41. package/dist/esm/packages/validateComponentMetadata.d.ts +4 -0
  42. package/dist/esm/packages/validateComponentMetadata.d.ts.map +1 -0
  43. package/dist/esm/packages/validateComponentMetadata.js +23 -0
  44. package/dist/esm/packages/validateComponentMetadata.js.map +1 -0
  45. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  46. package/package.json +5 -5
  47. package/packages/ScenePackageBundle.ts +7 -1
  48. package/packages/describeComponents.ts +78 -0
  49. package/packages/designModes.ts +74 -0
  50. package/packages/index.ts +3 -0
  51. package/packages/validateComponentMetadata.ts +22 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cratis/scene.react",
3
- "version": "4.22.1",
3
+ "version": "4.24.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.22.1",
55
- "@cratis/scene.model": "4.22.1"
54
+ "@cratis/scene.engine": "4.24.0",
55
+ "@cratis/scene.model": "4.24.0"
56
56
  },
57
57
  "devDependencies": {
58
- "@cratis/scene.engine": "4.22.1",
59
- "@cratis/scene.model": "4.22.1",
58
+ "@cratis/scene.engine": "4.24.0",
59
+ "@cratis/scene.model": "4.24.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,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
- import { ComponentDescriptor, DialogTemplate, Layout, PackageKind, PropertyDescriptor, PropertyValueType, ScenePackage, Screen, ScreenTemplate, Theme } from '@cratis/scene.model';
4
+ import { validateComponentDescriptors } from '@cratis/scene.engine';
5
+ import { ComponentDescriptor, DiagnosticCode, DialogTemplate, Layout, PackageKind, PropertyDescriptor, PropertyValueType, ScenePackage, Screen, ScreenTemplate, Theme } from '@cratis/scene.model';
5
6
  import { IconAdapterRegistry, createIconAdapterRegistry } from '../icons/IconAdapterRegistry';
6
7
  import { IconLibraryBundle } from '../icons/IconLibraryBundle';
7
8
  import { ComponentRegistry } from '../renderer';
@@ -250,6 +251,11 @@ function validateDescriptors(bundle: ScenePackageBundle): string[] {
250
251
  validateProperties(descriptor.component, descriptor.properties, '', problems);
251
252
  }
252
253
 
254
+ // The category rule (CMP1) is not checked here - it gates a designer, not a host loading a package (see validateComponentMetadata) -
255
+ // but a design-mode descriptor or editor parameter that cannot work is as much a mistake as an enum without choices.
256
+ const structural: string[] = [DiagnosticCode.ComponentDesignModeInvalid, DiagnosticCode.PropertyEditorOptionsInvalid];
257
+ problems.push(...validateComponentDescriptors(bundle.descriptors ?? []).filter(diagnostic => structural.includes(diagnostic.code)).map(diagnostic => diagnostic.message));
258
+
253
259
  return problems;
254
260
  }
255
261
 
@@ -0,0 +1,78 @@
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, ComponentDesignModeDescriptor, componentIcon } from '@cratis/scene.model';
5
+ import { componentRegistryKey } from './ScenePackageBundle';
6
+
7
+ /**
8
+ * What a package says about one of its components for a picker, besides what can be edited: the data of CMP1 and the picker's
9
+ * rows. Written once per package as a table keyed by the bare component name.
10
+ */
11
+ export interface ComponentMetadata {
12
+ /** The name a picker shows. Absent keeps the one the component's descriptor has, or the bare name. */
13
+ displayName?: string;
14
+
15
+ /** One of `ComponentCategory`, or a category of the package's own. */
16
+ category: string;
17
+
18
+ /** The key of the icon in the built-in icon library (`componentIcon`). */
19
+ icon?: string;
20
+
21
+ /** Words the picker's filter matches besides the name. */
22
+ keywords?: string[];
23
+
24
+ /** An explicit two-letter badge; absent means derived from the name. */
25
+ badge?: string;
26
+
27
+ /** How the component is edited directly on the design surface. */
28
+ designMode?: ComponentDesignModeDescriptor;
29
+ }
30
+
31
+ /** A `ComponentMetadata` entry in one line: what a package's metadata table is made of. */
32
+ export function componentMetadata(
33
+ displayName: string,
34
+ category: string,
35
+ icon: string,
36
+ keywords: string[] = [],
37
+ badge?: string,
38
+ designMode?: ComponentDesignModeDescriptor
39
+ ): ComponentMetadata {
40
+ return { displayName, category, icon, keywords, ...(badge === undefined ? {} : { badge }), ...(designMode === undefined ? {} : { designMode }) };
41
+ }
42
+
43
+ /**
44
+ * Builds the descriptors a bundle ships: the ones written by hand with the package's component metadata merged in, plus a descriptor with
45
+ * no editable properties for every component the metadata names that nobody described - such a component still has a name, a
46
+ * category and a place in a picker. A hand-written descriptor keeps everything it already says; metadata only fills in what it
47
+ * leaves out.
48
+ *
49
+ * The component list is the metadata table's own, so a component the table forgets has no descriptor and `validateComponentMetadata`
50
+ * reports it (CMP1); one the table names but the manifest does not declare is reported by `validatePackageBundle`.
51
+ */
52
+ export function describeComponents(
53
+ packageName: string,
54
+ described: readonly ComponentDescriptor[],
55
+ metadata: Readonly<Record<string, ComponentMetadata>>
56
+ ): ComponentDescriptor[] {
57
+ const describedKeys = new Set(described.map(descriptor => descriptor.component));
58
+ const bare = Object.keys(metadata)
59
+ .map(name => componentRegistryKey(packageName, name))
60
+ .filter(key => !describedKeys.has(key))
61
+ .map((key): ComponentDescriptor => ({ component: key, properties: [] }));
62
+
63
+ return [...described, ...bare].map(descriptor => withMetadata(descriptor, metadata[descriptor.component.slice(packageName.length + 1)]));
64
+ }
65
+
66
+ function withMetadata(descriptor: ComponentDescriptor, metadata: ComponentMetadata | undefined): ComponentDescriptor {
67
+ if (!metadata) return descriptor;
68
+
69
+ return {
70
+ ...descriptor,
71
+ displayName: descriptor.displayName ?? metadata.displayName ?? descriptor.component.slice(descriptor.component.indexOf(':') + 1),
72
+ category: descriptor.category ?? metadata.category,
73
+ ...(descriptor.icon === undefined && metadata.icon !== undefined ? { icon: componentIcon(metadata.icon) } : {}),
74
+ ...(descriptor.keywords === undefined && metadata.keywords !== undefined ? { keywords: metadata.keywords } : {}),
75
+ ...(descriptor.badge === undefined && metadata.badge !== undefined ? { badge: metadata.badge } : {}),
76
+ ...(descriptor.designMode === undefined && metadata.designMode !== undefined ? { designMode: metadata.designMode } : {}),
77
+ };
78
+ }
@@ -0,0 +1,74 @@
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 { ComponentDesignModeDescriptor, DesignModeItemAction, componentIcon } from '@cratis/scene.model';
5
+
6
+ /** The rule that says an item may be changed (not inherited), interpreted by the package's design-time bundle. */
7
+ export const itemEditableRule = 'item.editable';
8
+
9
+ /** The rule that says why an inherited item may not be changed. */
10
+ export const itemInheritedReason = 'item.inherited';
11
+
12
+ const mutating = (action: Omit<DesignModeItemAction, 'icon'> & { icon: string }, compact?: boolean): DesignModeItemAction => ({
13
+ id: action.id,
14
+ icon: componentIcon(action.icon),
15
+ title: action.title,
16
+ ...(compact === true ? { compact: true } : {}),
17
+ availability: action.availability ?? itemEditableRule,
18
+ readOnlyReason: itemInheritedReason,
19
+ });
20
+
21
+ export interface NavigationDesignModeOptions {
22
+ /** The `Collection` property that holds the items; absent when the host resolves them (a topbar's or sidebar's navigation region). */
23
+ itemsPath?: string;
24
+
25
+ /** A sidebar's items nest, and its toolbar sits below the item in a compact two-row form (spec DM3, DM4). */
26
+ sidebar?: boolean;
27
+ }
28
+
29
+ /**
30
+ * The design mode of a component made of navigation items (a topbar, a sidebar, a navigation bar): add an item, and on a selected
31
+ * item rename it, choose its target and rendering slot, move it earlier or later, duplicate it, delete it, and - in a sidebar - add a
32
+ * sub-item (spec DM3). Pass the `itemsPath` of a component whose items are a `Collection` property; the target and slot buttons are
33
+ * left out of such a component unless `itemsPath` is absent, because only the navigation region's items have them.
34
+ */
35
+ export function navigationDesignMode(options: NavigationDesignModeOptions = {}): ComponentDesignModeDescriptor {
36
+ const { itemsPath, sidebar = false } = options;
37
+ const regionItems = itemsPath === undefined;
38
+
39
+ return {
40
+ ...(itemsPath === undefined ? {} : { itemsPath }),
41
+ addActions: [{ id: 'addItem', label: 'Add item', icon: componentIcon('plus'), ...(itemsPath === undefined ? {} : { collectionPath: itemsPath }) }],
42
+ itemToolbar: [
43
+ mutating({ id: 'rename', icon: 'pencil', title: 'Rename' }, sidebar),
44
+ ...(regionItems ? [
45
+ mutating({ id: 'target', icon: 'link', title: 'Link target' }, sidebar),
46
+ mutating({ id: 'slot', icon: 'layout-panel-left', title: 'Rendering slot' }, sidebar),
47
+ ] : []),
48
+ mutating({ id: 'moveEarlier', icon: sidebar ? 'arrow-up' : 'arrow-left', title: 'Move earlier' }, sidebar),
49
+ mutating({ id: 'moveLater', icon: sidebar ? 'arrow-down' : 'arrow-right', title: 'Move later' }, sidebar),
50
+ mutating({ id: 'duplicate', icon: 'copy', title: 'Duplicate' }, sidebar),
51
+ mutating({ id: 'delete', icon: 'trash', title: 'Delete' }, sidebar),
52
+ ...(sidebar ? [mutating({ id: 'addSubItem', icon: 'corner-down-right', title: 'Add sub-item' }, true)] : []),
53
+ ],
54
+ };
55
+ }
56
+
57
+ /**
58
+ * The design mode of a toolbar: add a button, and on a selected button rename it, choose the slice it starts, set its emphasis,
59
+ * move it earlier or later, duplicate it, delete it (spec DM3). The buttons are the toolbar's own items, which the host resolves.
60
+ */
61
+ export function toolbarDesignMode(): ComponentDesignModeDescriptor {
62
+ return {
63
+ addActions: [{ id: 'addButton', label: 'Add button', icon: componentIcon('plus') }],
64
+ itemToolbar: [
65
+ mutating({ id: 'rename', icon: 'pencil', title: 'Rename' }),
66
+ mutating({ id: 'slice', icon: 'git-branch', title: 'Choose slice' }),
67
+ mutating({ id: 'emphasis', icon: 'contrast', title: 'Emphasis' }),
68
+ mutating({ id: 'moveEarlier', icon: 'arrow-left', title: 'Move earlier' }),
69
+ mutating({ id: 'moveLater', icon: 'arrow-right', title: 'Move later' }),
70
+ mutating({ id: 'duplicate', icon: 'copy', title: 'Duplicate' }),
71
+ mutating({ id: 'delete', icon: 'trash', title: 'Delete' }),
72
+ ],
73
+ };
74
+ }
package/packages/index.ts CHANGED
@@ -26,6 +26,9 @@ export * from './PackageHostView';
26
26
  export * from './resolveDesignTimeHost';
27
27
  export * from './runDesignTimeAction';
28
28
  export * from './ScenePackageBundle';
29
+ export * from './describeComponents';
30
+ export * from './designModes';
31
+ export * from './validateComponentMetadata';
29
32
  export * from './StandalonePackageHost';
30
33
  export * from './WebViewPackageHost';
31
34
  export * from './ComponentsUiLibraryContract';
@@ -0,0 +1,22 @@
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 { validateComponentDescriptors } from '@cratis/scene.engine';
5
+ import { SceneDiagnostic } from '@cratis/scene.model';
6
+ import { ScenePackageBundle, componentRegistryKey } from './ScenePackageBundle';
7
+
8
+ /**
9
+ * Checks the component metadata of a bundle (rule CMP1): every component its manifest declares or its registry holds is described
10
+ * and declares a category, and the design-mode and editor parameters of its descriptors are sound.
11
+ *
12
+ * This is not part of `validatePackageBundle`: that gates what a host loads, and a third-party package written before components
13
+ * had categories must still load. A designer and the in-repo packages' own specs run this one.
14
+ */
15
+ export function validateComponentMetadata(bundle: ScenePackageBundle): SceneDiagnostic[] {
16
+ const registered = new Set([
17
+ ...bundle.manifest.components.map(name => componentRegistryKey(bundle.manifest.name, name)),
18
+ ...Object.keys(bundle.components),
19
+ ]);
20
+
21
+ return validateComponentDescriptors(bundle.descriptors ?? [], [...registered]);
22
+ }