@cratis/scene.engine 4.23.0 → 4.25.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 (104) hide show
  1. package/descriptors/componentBadge.ts +41 -0
  2. package/descriptors/index.ts +7 -0
  3. package/descriptors/matchComponentQuery.ts +55 -0
  4. package/descriptors/rankFrequentlyUsedComponents.ts +29 -0
  5. package/descriptors/validateComponentDescriptors.ts +122 -0
  6. package/dist/cjs/descriptors/componentBadge.js +35 -0
  7. package/dist/cjs/descriptors/componentBadge.js.map +1 -0
  8. package/dist/cjs/descriptors/matchComponentQuery.js +56 -0
  9. package/dist/cjs/descriptors/matchComponentQuery.js.map +1 -0
  10. package/dist/cjs/descriptors/rankFrequentlyUsedComponents.js +18 -0
  11. package/dist/cjs/descriptors/rankFrequentlyUsedComponents.js.map +1 -0
  12. package/dist/cjs/descriptors/validateComponentDescriptors.js +116 -0
  13. package/dist/cjs/descriptors/validateComponentDescriptors.js.map +1 -0
  14. package/dist/cjs/index.js +20 -0
  15. package/dist/cjs/index.js.map +1 -1
  16. package/dist/cjs/templateApplicability.js.map +1 -1
  17. package/dist/cjs/templates/TemplateReferenceResolution.js +15 -0
  18. package/dist/cjs/templates/TemplateReferenceResolution.js.map +1 -0
  19. package/dist/cjs/templates/applicableTemplates.js +199 -0
  20. package/dist/cjs/templates/applicableTemplates.js.map +1 -0
  21. package/dist/cjs/templates/describeTemplateCatalog.js +11 -6
  22. package/dist/cjs/templates/describeTemplateCatalog.js.map +1 -1
  23. package/dist/cjs/templates/resolveTemplateReference.js +89 -0
  24. package/dist/cjs/templates/resolveTemplateReference.js.map +1 -0
  25. package/dist/cjs/templates/validateTemplateMetadata.js +9 -0
  26. package/dist/cjs/templates/validateTemplateMetadata.js.map +1 -1
  27. package/dist/esm/descriptors/componentBadge.d.ts +3 -0
  28. package/dist/esm/descriptors/componentBadge.d.ts.map +1 -0
  29. package/dist/esm/descriptors/componentBadge.js +33 -0
  30. package/dist/esm/descriptors/componentBadge.js.map +1 -0
  31. package/dist/esm/descriptors/index.d.ts +5 -0
  32. package/dist/esm/descriptors/index.d.ts.map +1 -0
  33. package/dist/esm/descriptors/index.js +5 -0
  34. package/dist/esm/descriptors/index.js.map +1 -0
  35. package/dist/esm/descriptors/matchComponentQuery.d.ts +9 -0
  36. package/dist/esm/descriptors/matchComponentQuery.d.ts.map +1 -0
  37. package/dist/esm/descriptors/matchComponentQuery.js +54 -0
  38. package/dist/esm/descriptors/matchComponentQuery.js.map +1 -0
  39. package/dist/esm/descriptors/rankFrequentlyUsedComponents.d.ts +7 -0
  40. package/dist/esm/descriptors/rankFrequentlyUsedComponents.d.ts.map +1 -0
  41. package/dist/esm/descriptors/rankFrequentlyUsedComponents.js +15 -0
  42. package/dist/esm/descriptors/rankFrequentlyUsedComponents.js.map +1 -0
  43. package/dist/esm/descriptors/validateComponentDescriptors.d.ts +3 -0
  44. package/dist/esm/descriptors/validateComponentDescriptors.d.ts.map +1 -0
  45. package/dist/esm/descriptors/validateComponentDescriptors.js +114 -0
  46. package/dist/esm/descriptors/validateComponentDescriptors.js.map +1 -0
  47. package/dist/esm/index.d.ts +1 -0
  48. package/dist/esm/index.d.ts.map +1 -1
  49. package/dist/esm/index.js +8 -1
  50. package/dist/esm/index.js.map +1 -1
  51. package/dist/esm/templateApplicability.d.ts +1 -2
  52. package/dist/esm/templateApplicability.d.ts.map +1 -1
  53. package/dist/esm/templateApplicability.js.map +1 -1
  54. package/dist/esm/templates/ApplicableTemplateSet.d.ts +27 -0
  55. package/dist/esm/templates/ApplicableTemplateSet.d.ts.map +1 -0
  56. package/dist/esm/templates/ApplicableTemplateSet.js +2 -0
  57. package/dist/esm/templates/ApplicableTemplateSet.js.map +1 -0
  58. package/dist/esm/templates/TemplateCatalogEntry.d.ts +3 -1
  59. package/dist/esm/templates/TemplateCatalogEntry.d.ts.map +1 -1
  60. package/dist/esm/templates/TemplateLibrary.d.ts +6 -0
  61. package/dist/esm/templates/TemplateLibrary.d.ts.map +1 -0
  62. package/dist/esm/templates/TemplateLibrary.js +2 -0
  63. package/dist/esm/templates/TemplateLibrary.js.map +1 -0
  64. package/dist/esm/templates/TemplateReferenceResolution.d.ts +16 -0
  65. package/dist/esm/templates/TemplateReferenceResolution.d.ts.map +1 -0
  66. package/dist/esm/templates/TemplateReferenceResolution.js +13 -0
  67. package/dist/esm/templates/TemplateReferenceResolution.js.map +1 -0
  68. package/dist/esm/templates/TemplateTarget.d.ts +9 -0
  69. package/dist/esm/templates/TemplateTarget.d.ts.map +1 -0
  70. package/dist/esm/templates/TemplateTarget.js +2 -0
  71. package/dist/esm/templates/TemplateTarget.js.map +1 -0
  72. package/dist/esm/templates/applicableTemplates.d.ts +9 -0
  73. package/dist/esm/templates/applicableTemplates.d.ts.map +1 -0
  74. package/dist/esm/templates/applicableTemplates.js +194 -0
  75. package/dist/esm/templates/applicableTemplates.js.map +1 -0
  76. package/dist/esm/templates/describeTemplateCatalog.d.ts.map +1 -1
  77. package/dist/esm/templates/describeTemplateCatalog.js +12 -7
  78. package/dist/esm/templates/describeTemplateCatalog.js.map +1 -1
  79. package/dist/esm/templates/index.d.ts +6 -0
  80. package/dist/esm/templates/index.d.ts.map +1 -1
  81. package/dist/esm/templates/index.js +6 -0
  82. package/dist/esm/templates/index.js.map +1 -1
  83. package/dist/esm/templates/resolveTemplateReference.d.ts +5 -0
  84. package/dist/esm/templates/resolveTemplateReference.d.ts.map +1 -0
  85. package/dist/esm/templates/resolveTemplateReference.js +87 -0
  86. package/dist/esm/templates/resolveTemplateReference.js.map +1 -0
  87. package/dist/esm/templates/validateTemplateMetadata.d.ts +2 -0
  88. package/dist/esm/templates/validateTemplateMetadata.d.ts.map +1 -1
  89. package/dist/esm/templates/validateTemplateMetadata.js +8 -1
  90. package/dist/esm/templates/validateTemplateMetadata.js.map +1 -1
  91. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  92. package/index.ts +1 -0
  93. package/package.json +3 -3
  94. package/templateApplicability.ts +2 -1
  95. package/templates/ApplicableTemplateSet.ts +54 -0
  96. package/templates/TemplateCatalogEntry.ts +5 -1
  97. package/templates/TemplateLibrary.ts +16 -0
  98. package/templates/TemplateReferenceResolution.ts +35 -0
  99. package/templates/TemplateTarget.ts +23 -0
  100. package/templates/applicableTemplates.ts +148 -0
  101. package/templates/describeTemplateCatalog.ts +11 -7
  102. package/templates/index.ts +6 -0
  103. package/templates/resolveTemplateReference.ts +79 -0
  104. package/templates/validateTemplateMetadata.ts +11 -0
@@ -0,0 +1,41 @@
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 } from '@cratis/scene.model';
5
+
6
+ const vowels = 'aeiou';
7
+
8
+ /**
9
+ * The two-letter badge a component picker draws for a component that has no icon to show.
10
+ *
11
+ * An explicit `badge` wins (trimmed to two letters). Otherwise a name of several words (`Card grid`, `Stat card`) takes the
12
+ * initials of its first two words (`Cg`, `Sc`); a single word takes its first letter and the first consonant after it
13
+ * (`Button` -> `Bt`, `List` -> `Ls`), or its second letter when it has none. A camel-cased name is read as words
14
+ * (`dataTable` -> `Data table`). The first letter is upper case, the second lower case.
15
+ */
16
+ export function componentBadge(descriptor: Pick<ComponentDescriptor, 'component' | 'displayName' | 'badge'>): string {
17
+ const explicit = descriptor.badge?.trim();
18
+ if (explicit) return capitalize(explicit.slice(0, 2));
19
+
20
+ const name = descriptor.displayName?.trim() || bareName(descriptor.component);
21
+ const words = name
22
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
23
+ .split(/[^A-Za-z0-9]+/)
24
+ .filter(word => word.length > 0);
25
+
26
+ if (words.length === 0) return '??';
27
+ if (words.length > 1) return capitalize(words[0][0] + words[1][0]);
28
+
29
+ const word = words[0];
30
+ if (word.length === 1) return word.toUpperCase();
31
+ const consonant = [...word.slice(1)].find(letter => /[a-z]/i.test(letter) && !vowels.includes(letter.toLowerCase()));
32
+ return capitalize(word[0] + (consonant ?? word[1]));
33
+ }
34
+
35
+ function bareName(component: string): string {
36
+ return component.slice(component.lastIndexOf(':') + 1);
37
+ }
38
+
39
+ function capitalize(letters: string): string {
40
+ return letters.charAt(0).toUpperCase() + letters.slice(1).toLowerCase();
41
+ }
@@ -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 './validateComponentDescriptors';
5
+ export * from './componentBadge';
6
+ export * from './matchComponentQuery';
7
+ export * from './rankFrequentlyUsedComponents';
@@ -0,0 +1,55 @@
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 } from '@cratis/scene.model';
5
+
6
+ /**
7
+ * How a component answers a picker's filter text (spec PICK2).
8
+ */
9
+ export interface ComponentQueryMatch {
10
+ /** The component is shown for this filter. */
11
+ matched: boolean;
12
+
13
+ /** What matched, strongest first: the display name, the category, or one of the keywords. Absent when nothing matched. */
14
+ via?: 'name' | 'category' | 'keyword';
15
+
16
+ /** The `[start, end)` ranges of the display name to highlight. Empty unless the name matched. */
17
+ ranges: [number, number][];
18
+
19
+ /** The keyword the component matched through. Present only when `via` is `keyword`: the picker shows it as a hint. */
20
+ keyword?: string;
21
+ }
22
+
23
+ /**
24
+ * Decides whether a component is shown for the text typed in a picker's filter, and why.
25
+ *
26
+ * Matching is case-insensitive. The display name (or, when the descriptor has none, the bare component name) and the keywords match by
27
+ * substring. The category matches by the start of one of its words - `nav` finds every Navigation component, while `ta` does not find
28
+ * everything under `Data display`, which a substring match on the category would. A component is shown when any of the three
29
+ * match; it reports the first of name, category, keyword that did, so the picker highlights the name and shows a hint only when a
30
+ * keyword alone brought the component in. An empty filter matches everything and highlights nothing.
31
+ */
32
+ export function matchComponentQuery(descriptor: Pick<ComponentDescriptor, 'component' | 'displayName' | 'category' | 'keywords'>, query: string): ComponentQueryMatch {
33
+ const needle = query.trim().toLowerCase();
34
+ if (needle.length === 0) return { matched: true, ranges: [] };
35
+
36
+ const name = descriptor.displayName ?? descriptor.component.slice(descriptor.component.lastIndexOf(':') + 1);
37
+ const ranges = occurrencesOf(name.toLowerCase(), needle);
38
+ if (ranges.length > 0) return { matched: true, via: 'name', ranges };
39
+
40
+ if (descriptor.category !== undefined && descriptor.category.toLowerCase().split(/\s+/).some(word => word.startsWith(needle))) {
41
+ return { matched: true, via: 'category', ranges: [] };
42
+ }
43
+
44
+ const keyword = (descriptor.keywords ?? []).find(candidate => candidate.toLowerCase().includes(needle));
45
+ return keyword === undefined ? { matched: false, ranges: [] } : { matched: true, via: 'keyword', ranges: [], keyword };
46
+ }
47
+
48
+ function occurrencesOf(text: string, needle: string): [number, number][] {
49
+ const found: [number, number][] = [];
50
+ for (let at = text.indexOf(needle); at >= 0; at = text.indexOf(needle, at + needle.length)) {
51
+ found.push([at, at + needle.length]);
52
+ }
53
+
54
+ return found;
55
+ }
@@ -0,0 +1,29 @@
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
+ * How often and how recently one component was added to a canvas.
6
+ */
7
+ export interface ComponentUsage {
8
+ /** How many times the component was added. */
9
+ count: number;
10
+
11
+ /** When it was last added, in milliseconds since the epoch. */
12
+ last: number;
13
+ }
14
+
15
+ /** The number of components the "Frequently used" group lists (spec PICK3). */
16
+ export const frequentlyUsedLimit = 6;
17
+
18
+ /**
19
+ * The components the "Frequently used" group lists: at most `limit` (6 by default), the most used first, ties broken by the most
20
+ * recent use, and any remaining tie by the component name so the order is stable. Components never used, or whose count is not a
21
+ * positive number, are not listed, so a picker hides the group when this is empty.
22
+ */
23
+ export function rankFrequentlyUsedComponents(usage: Readonly<Record<string, ComponentUsage>>, limit: number = frequentlyUsedLimit): string[] {
24
+ return Object.entries(usage)
25
+ .filter(([, entry]) => Number.isFinite(entry.count) && entry.count > 0)
26
+ .sort(([leftName, left], [rightName, right]) => right.count - left.count || right.last - left.last || leftName.localeCompare(rightName))
27
+ .slice(0, Math.max(0, limit))
28
+ .map(([name]) => name);
29
+ }
@@ -0,0 +1,122 @@
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, ComponentDesignModeDescriptor, DiagnosticCode, DiagnosticSeverity, PropertyDescriptor, PropertyEditorKind, PropertyValueType,
6
+ SceneDiagnostic, componentCategories,
7
+ } from '@cratis/scene.model';
8
+
9
+ /**
10
+ * Checks the component metadata a package ships - what a picker and a designer read without loading a component.
11
+ *
12
+ * - **CMP1** `componentCategoryMissing` (error): a registered component has no descriptor, or its descriptor declares no category.
13
+ * - `componentCategoryUnknown` (warning): the category is not one of the model's; a package may add its own, so this is a
14
+ * nudge towards the shared vocabulary, not a refusal.
15
+ * - `componentDesignModeInvalid` (error): the design-mode descriptor names an items or collection path that is not a `Collection`
16
+ * property of the component, or repeats an action id.
17
+ * - `propertyEditorOptionsInvalid` (error): an editor's parameters cannot work - a `reference` editor that does not say what it
18
+ * references, a step that is not positive, an empty list of slice kinds.
19
+ *
20
+ * `registered` is the bare or package-qualified names of the components the package registers; when given, each must be described.
21
+ * The result is in a stable order: registered components first (in the order given), then each descriptor's findings in its own order.
22
+ */
23
+ export function validateComponentDescriptors(
24
+ descriptors: readonly ComponentDescriptor[],
25
+ registered?: readonly string[],
26
+ knownCategories: readonly string[] = componentCategories
27
+ ): SceneDiagnostic[] {
28
+ const diagnostics: SceneDiagnostic[] = [];
29
+ const described = new Map(descriptors.map(descriptor => [descriptor.component, descriptor]));
30
+
31
+ for (const component of registered ?? []) {
32
+ if (!described.has(component)) {
33
+ diagnostics.push(missingCategory(component, `'${component}' is registered but has no descriptor, so it declares no category`));
34
+ }
35
+ }
36
+
37
+ for (const descriptor of descriptors) {
38
+ const { component } = descriptor;
39
+
40
+ if (descriptor.category === undefined || descriptor.category.trim().length === 0) {
41
+ diagnostics.push(missingCategory(component, `'${component}' declares no category`));
42
+ } else if (!knownCategories.includes(descriptor.category)) {
43
+ diagnostics.push({
44
+ code: DiagnosticCode.ComponentCategoryUnknown, severity: DiagnosticSeverity.Warning, component,
45
+ message: `'${component}' declares the category '${descriptor.category}', which is not one of ${knownCategories.join(', ')}`,
46
+ });
47
+ }
48
+
49
+ if (descriptor.designMode) {
50
+ diagnostics.push(...validateDesignMode(descriptor, descriptor.designMode));
51
+ }
52
+
53
+ diagnostics.push(...validateEditorOptions(component, descriptor.properties, ''));
54
+ }
55
+
56
+ return diagnostics;
57
+ }
58
+
59
+ function missingCategory(component: string, message: string): SceneDiagnostic {
60
+ return { code: DiagnosticCode.ComponentCategoryMissing, severity: DiagnosticSeverity.Error, component, message };
61
+ }
62
+
63
+ function invalidDesignMode(component: string, message: string): SceneDiagnostic {
64
+ return { code: DiagnosticCode.ComponentDesignModeInvalid, severity: DiagnosticSeverity.Error, component, message };
65
+ }
66
+
67
+ function validateDesignMode(descriptor: ComponentDescriptor, designMode: ComponentDesignModeDescriptor): SceneDiagnostic[] {
68
+ const { component } = descriptor;
69
+ const diagnostics: SceneDiagnostic[] = [];
70
+ const collections = new Set(descriptor.properties.filter(property => property.valueType === PropertyValueType.Collection).map(property => property.path));
71
+
72
+ if (designMode.itemsPath !== undefined && !collections.has(designMode.itemsPath)) {
73
+ diagnostics.push(invalidDesignMode(component, `'${component}' names '${designMode.itemsPath}' as its design-mode items, which is not one of its Collection properties`));
74
+ }
75
+
76
+ const seenAdd = new Set<string>();
77
+ for (const action of designMode.addActions) {
78
+ if (seenAdd.has(action.id)) diagnostics.push(invalidDesignMode(component, `'${component}' declares the design-mode add action '${action.id}' more than once`));
79
+ seenAdd.add(action.id);
80
+
81
+ if (action.collectionPath !== undefined && !collections.has(action.collectionPath)) {
82
+ diagnostics.push(invalidDesignMode(component, `the design-mode add action '${action.id}' of '${component}' adds to '${action.collectionPath}', which is not one of its Collection properties`));
83
+ }
84
+ }
85
+
86
+ const seenItem = new Set<string>();
87
+ for (const action of designMode.itemToolbar) {
88
+ if (seenItem.has(action.id)) diagnostics.push(invalidDesignMode(component, `'${component}' declares the design-mode item action '${action.id}' more than once`));
89
+ seenItem.add(action.id);
90
+ }
91
+
92
+ return diagnostics;
93
+ }
94
+
95
+ function validateEditorOptions(component: string, properties: readonly PropertyDescriptor[], parent: string): SceneDiagnostic[] {
96
+ const diagnostics: SceneDiagnostic[] = [];
97
+ const invalid = (path: string, message: string): SceneDiagnostic => ({
98
+ code: DiagnosticCode.PropertyEditorOptionsInvalid, severity: DiagnosticSeverity.Error, component, path, message: `'${component}' property '${path}' ${message}`,
99
+ });
100
+
101
+ for (const property of properties) {
102
+ const path = `${parent}${property.path}`;
103
+ const options = property.editorOptions;
104
+
105
+ if (property.editorKind === PropertyEditorKind.Reference && !options?.reference?.target) {
106
+ diagnostics.push(invalid(path, "uses the 'reference' editor without saying what it references (editorOptions.reference.target)"));
107
+ }
108
+
109
+ if (options?.step !== undefined && !(options.step > 0)) {
110
+ diagnostics.push(invalid(path, 'declares a step that is not greater than zero'));
111
+ }
112
+
113
+ if (options?.kinds !== undefined && options.kinds.length === 0) {
114
+ diagnostics.push(invalid(path, 'declares an empty list of kinds, which would offer nothing'));
115
+ }
116
+
117
+ if (property.item) diagnostics.push(...validateEditorOptions(component, property.item.properties, `${path}[].`));
118
+ if (property.properties) diagnostics.push(...validateEditorOptions(component, property.properties, `${path}.`));
119
+ }
120
+
121
+ return diagnostics;
122
+ }
@@ -0,0 +1,35 @@
1
+ 'use strict';
2
+
3
+ // Copyright (c) Cratis. All rights reserved.
4
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
5
+ const vowels = 'aeiou';
6
+ /**
7
+ * The two-letter badge a component picker draws for a component that has no icon to show.
8
+ *
9
+ * An explicit `badge` wins (trimmed to two letters). Otherwise a name of several words (`Card grid`, `Stat card`) takes the
10
+ * initials of its first two words (`Cg`, `Sc`); a single word takes its first letter and the first consonant after it
11
+ * (`Button` -> `Bt`, `List` -> `Ls`), or its second letter when it has none. A camel-cased name is read as words
12
+ * (`dataTable` -> `Data table`). The first letter is upper case, the second lower case.
13
+ */ function componentBadge(descriptor) {
14
+ const explicit = descriptor.badge?.trim();
15
+ if (explicit) return capitalize(explicit.slice(0, 2));
16
+ const name = descriptor.displayName?.trim() || bareName(descriptor.component);
17
+ const words = name.replace(/([a-z0-9])([A-Z])/g, '$1 $2').split(/[^A-Za-z0-9]+/).filter((word)=>word.length > 0);
18
+ if (words.length === 0) return '??';
19
+ if (words.length > 1) return capitalize(words[0][0] + words[1][0]);
20
+ const word = words[0];
21
+ if (word.length === 1) return word.toUpperCase();
22
+ const consonant = [
23
+ ...word.slice(1)
24
+ ].find((letter)=>/[a-z]/i.test(letter) && !vowels.includes(letter.toLowerCase()));
25
+ return capitalize(word[0] + (consonant ?? word[1]));
26
+ }
27
+ function bareName(component) {
28
+ return component.slice(component.lastIndexOf(':') + 1);
29
+ }
30
+ function capitalize(letters) {
31
+ return letters.charAt(0).toUpperCase() + letters.slice(1).toLowerCase();
32
+ }
33
+
34
+ exports.componentBadge = componentBadge;
35
+ //# sourceMappingURL=componentBadge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"componentBadge.js","sources":["../../../descriptors/componentBadge.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { ComponentDescriptor } from '@cratis/scene.model';\n\nconst vowels = 'aeiou';\n\n/**\n * The two-letter badge a component picker draws for a component that has no icon to show.\n *\n * An explicit `badge` wins (trimmed to two letters). Otherwise a name of several words (`Card grid`, `Stat card`) takes the\n * initials of its first two words (`Cg`, `Sc`); a single word takes its first letter and the first consonant after it\n * (`Button` -> `Bt`, `List` -> `Ls`), or its second letter when it has none. A camel-cased name is read as words\n * (`dataTable` -> `Data table`). The first letter is upper case, the second lower case.\n */\nexport function componentBadge(descriptor: Pick<ComponentDescriptor, 'component' | 'displayName' | 'badge'>): string {\n const explicit = descriptor.badge?.trim();\n if (explicit) return capitalize(explicit.slice(0, 2));\n\n const name = descriptor.displayName?.trim() || bareName(descriptor.component);\n const words = name\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .split(/[^A-Za-z0-9]+/)\n .filter(word => word.length > 0);\n\n if (words.length === 0) return '??';\n if (words.length > 1) return capitalize(words[0][0] + words[1][0]);\n\n const word = words[0];\n if (word.length === 1) return word.toUpperCase();\n const consonant = [...word.slice(1)].find(letter => /[a-z]/i.test(letter) && !vowels.includes(letter.toLowerCase()));\n return capitalize(word[0] + (consonant ?? word[1]));\n}\n\nfunction bareName(component: string): string {\n return component.slice(component.lastIndexOf(':') + 1);\n}\n\nfunction capitalize(letters: string): string {\n return letters.charAt(0).toUpperCase() + letters.slice(1).toLowerCase();\n}\n"],"names":["vowels","componentBadge","descriptor","explicit","badge","trim","capitalize","slice","name","displayName","bareName","component","words","replace","split","filter","word","length","toUpperCase","consonant","find","letter","test","includes","toLowerCase","lastIndexOf","letters","charAt"],"mappings":";;AAAA;AACA;AAIA,MAAMA,MAAAA,GAAS,OAAA;AAEf;;;;;;;IAQO,SAASC,cAAAA,CAAeC,UAA4E,EAAA;IACvG,MAAMC,QAAAA,GAAWD,UAAAA,CAAWE,KAAK,EAAEC,IAAAA,EAAAA;AACnC,IAAA,IAAIF,UAAU,OAAOG,UAAAA,CAAWH,QAAAA,CAASI,KAAK,CAAC,CAAA,EAAG,CAAA,CAAA,CAAA;AAElD,IAAA,MAAMC,OAAON,UAAAA,CAAWO,WAAW,EAAEJ,IAAAA,EAAAA,IAAUK,QAAAA,CAASR,WAAWS,SAAS,CAAA;AAC5E,IAAA,MAAMC,KAAAA,GAAQJ,IAAAA,CACTK,OAAO,CAAC,sBAAsB,OAAA,CAAA,CAC9BC,KAAK,CAAC,eAAA,CAAA,CACNC,MAAM,CAACC,CAAAA,IAAAA,GAAQA,IAAAA,CAAKC,MAAM,GAAG,CAAA,CAAA;AAElC,IAAA,IAAIL,KAAAA,CAAMK,MAAM,KAAK,CAAA,EAAG,OAAO,IAAA;AAC/B,IAAA,IAAIL,MAAMK,MAAM,GAAG,CAAA,EAAG,OAAOX,WAAWM,KAAK,CAAC,CAAA,CAAE,CAAC,EAAE,GAAGA,KAAK,CAAC,CAAA,CAAE,CAAC,CAAA,CAAE,CAAA;IAEjE,MAAMI,IAAAA,GAAOJ,KAAK,CAAC,CAAA,CAAE;AACrB,IAAA,IAAII,KAAKC,MAAM,KAAK,CAAA,EAAG,OAAOD,KAAKE,WAAW,EAAA;AAC9C,IAAA,MAAMC,SAAAA,GAAY;AAAIH,QAAAA,GAAAA,IAAAA,CAAKT,KAAK,CAAC,CAAA;AAAG,KAAA,CAACa,IAAI,CAACC,CAAAA,MAAAA,GAAU,QAAA,CAASC,IAAI,CAACD,MAAAA,CAAAA,IAAW,CAACrB,MAAAA,CAAOuB,QAAQ,CAACF,OAAOG,WAAW,EAAA,CAAA,CAAA;IAChH,OAAOlB,UAAAA,CAAWU,IAAI,CAAC,CAAA,CAAE,IAAIG,SAAAA,IAAaH,IAAI,CAAC,CAAA,CAAE,CAAD,CAAA;AACpD;AAEA,SAASN,SAASC,SAAiB,EAAA;AAC/B,IAAA,OAAOA,UAAUJ,KAAK,CAACI,SAAAA,CAAUc,WAAW,CAAC,GAAA,CAAA,GAAO,CAAA,CAAA;AACxD;AAEA,SAASnB,WAAWoB,OAAe,EAAA;IAC/B,OAAOA,OAAAA,CAAQC,MAAM,CAAC,CAAA,CAAA,CAAGT,WAAW,KAAKQ,OAAAA,CAAQnB,KAAK,CAAC,CAAA,CAAA,CAAGiB,WAAW,EAAA;AACzE;;"}
@@ -0,0 +1,56 @@
1
+ 'use strict';
2
+
3
+ // Copyright (c) Cratis. All rights reserved.
4
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
5
+ /**
6
+ * Decides whether a component is shown for the text typed in a picker's filter, and why.
7
+ *
8
+ * Matching is case-insensitive. The display name (or, when the descriptor has none, the bare component name) and the keywords match by
9
+ * substring. The category matches by the start of one of its words - `nav` finds every Navigation component, while `ta` does not find
10
+ * everything under `Data display`, which a substring match on the category would. A component is shown when any of the three
11
+ * match; it reports the first of name, category, keyword that did, so the picker highlights the name and shows a hint only when a
12
+ * keyword alone brought the component in. An empty filter matches everything and highlights nothing.
13
+ */ function matchComponentQuery(descriptor, query) {
14
+ const needle = query.trim().toLowerCase();
15
+ if (needle.length === 0) return {
16
+ matched: true,
17
+ ranges: []
18
+ };
19
+ const name = descriptor.displayName ?? descriptor.component.slice(descriptor.component.lastIndexOf(':') + 1);
20
+ const ranges = occurrencesOf(name.toLowerCase(), needle);
21
+ if (ranges.length > 0) return {
22
+ matched: true,
23
+ via: 'name',
24
+ ranges
25
+ };
26
+ if (descriptor.category !== undefined && descriptor.category.toLowerCase().split(/\s+/).some((word)=>word.startsWith(needle))) {
27
+ return {
28
+ matched: true,
29
+ via: 'category',
30
+ ranges: []
31
+ };
32
+ }
33
+ const keyword = (descriptor.keywords ?? []).find((candidate)=>candidate.toLowerCase().includes(needle));
34
+ return keyword === undefined ? {
35
+ matched: false,
36
+ ranges: []
37
+ } : {
38
+ matched: true,
39
+ via: 'keyword',
40
+ ranges: [],
41
+ keyword
42
+ };
43
+ }
44
+ function occurrencesOf(text, needle) {
45
+ const found = [];
46
+ for(let at = text.indexOf(needle); at >= 0; at = text.indexOf(needle, at + needle.length)){
47
+ found.push([
48
+ at,
49
+ at + needle.length
50
+ ]);
51
+ }
52
+ return found;
53
+ }
54
+
55
+ exports.matchComponentQuery = matchComponentQuery;
56
+ //# sourceMappingURL=matchComponentQuery.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"matchComponentQuery.js","sources":["../../../descriptors/matchComponentQuery.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport { ComponentDescriptor } from '@cratis/scene.model';\n\n/**\n * How a component answers a picker's filter text (spec PICK2).\n */\nexport interface ComponentQueryMatch {\n /** The component is shown for this filter. */\n matched: boolean;\n\n /** What matched, strongest first: the display name, the category, or one of the keywords. Absent when nothing matched. */\n via?: 'name' | 'category' | 'keyword';\n\n /** The `[start, end)` ranges of the display name to highlight. Empty unless the name matched. */\n ranges: [number, number][];\n\n /** The keyword the component matched through. Present only when `via` is `keyword`: the picker shows it as a hint. */\n keyword?: string;\n}\n\n/**\n * Decides whether a component is shown for the text typed in a picker's filter, and why.\n *\n * Matching is case-insensitive. The display name (or, when the descriptor has none, the bare component name) and the keywords match by\n * substring. The category matches by the start of one of its words - `nav` finds every Navigation component, while `ta` does not find\n * everything under `Data display`, which a substring match on the category would. A component is shown when any of the three\n * match; it reports the first of name, category, keyword that did, so the picker highlights the name and shows a hint only when a\n * keyword alone brought the component in. An empty filter matches everything and highlights nothing.\n */\nexport function matchComponentQuery(descriptor: Pick<ComponentDescriptor, 'component' | 'displayName' | 'category' | 'keywords'>, query: string): ComponentQueryMatch {\n const needle = query.trim().toLowerCase();\n if (needle.length === 0) return { matched: true, ranges: [] };\n\n const name = descriptor.displayName ?? descriptor.component.slice(descriptor.component.lastIndexOf(':') + 1);\n const ranges = occurrencesOf(name.toLowerCase(), needle);\n if (ranges.length > 0) return { matched: true, via: 'name', ranges };\n\n if (descriptor.category !== undefined && descriptor.category.toLowerCase().split(/\\s+/).some(word => word.startsWith(needle))) {\n return { matched: true, via: 'category', ranges: [] };\n }\n\n const keyword = (descriptor.keywords ?? []).find(candidate => candidate.toLowerCase().includes(needle));\n return keyword === undefined ? { matched: false, ranges: [] } : { matched: true, via: 'keyword', ranges: [], keyword };\n}\n\nfunction occurrencesOf(text: string, needle: string): [number, number][] {\n const found: [number, number][] = [];\n for (let at = text.indexOf(needle); at >= 0; at = text.indexOf(needle, at + needle.length)) {\n found.push([at, at + needle.length]);\n }\n\n return found;\n}\n"],"names":["matchComponentQuery","descriptor","query","needle","trim","toLowerCase","length","matched","ranges","name","displayName","component","slice","lastIndexOf","occurrencesOf","via","category","undefined","split","some","word","startsWith","keyword","keywords","find","candidate","includes","text","found","at","indexOf","push"],"mappings":";;AAAA;AACA;AAqBA;;;;;;;;AAQC,IACM,SAASA,mBAAAA,CAAoBC,UAA4F,EAAEC,KAAa,EAAA;AAC3I,IAAA,MAAMC,MAAAA,GAASD,KAAAA,CAAME,IAAI,EAAA,CAAGC,WAAW,EAAA;AACvC,IAAA,IAAIF,MAAAA,CAAOG,MAAM,KAAK,CAAA,EAAG,OAAO;QAAEC,OAAAA,EAAS,IAAA;AAAMC,QAAAA,MAAAA,EAAQ;AAAG,KAAA;AAE5D,IAAA,MAAMC,IAAAA,GAAOR,UAAAA,CAAWS,WAAW,IAAIT,WAAWU,SAAS,CAACC,KAAK,CAACX,UAAAA,CAAWU,SAAS,CAACE,WAAW,CAAC,GAAA,CAAA,GAAO,CAAA,CAAA;AAC1G,IAAA,MAAML,MAAAA,GAASM,aAAAA,CAAcL,IAAAA,CAAKJ,WAAW,EAAA,EAAIF,MAAAA,CAAAA;AACjD,IAAA,IAAIK,MAAAA,CAAOF,MAAM,GAAG,CAAA,EAAG,OAAO;QAAEC,OAAAA,EAAS,IAAA;QAAMQ,GAAAA,EAAK,MAAA;AAAQP,QAAAA;AAAO,KAAA;AAEnE,IAAA,IAAIP,WAAWe,QAAQ,KAAKC,aAAahB,UAAAA,CAAWe,QAAQ,CAACX,WAAW,EAAA,CAAGa,KAAK,CAAC,KAAA,CAAA,CAAOC,IAAI,CAACC,CAAAA,OAAQA,IAAAA,CAAKC,UAAU,CAAClB,MAAAA,CAAAA,CAAAA,EAAU;QAC3H,OAAO;YAAEI,OAAAA,EAAS,IAAA;YAAMQ,GAAAA,EAAK,UAAA;AAAYP,YAAAA,MAAAA,EAAQ;AAAG,SAAA;AACxD,IAAA;AAEA,IAAA,MAAMc,UAAU,CAACrB,UAAAA,CAAWsB,QAAQ,IAAI,EAAC,EAAGC,IAAI,CAACC,CAAAA,SAAAA,GAAaA,UAAUpB,WAAW,EAAA,CAAGqB,QAAQ,CAACvB,MAAAA,CAAAA,CAAAA;AAC/F,IAAA,OAAOmB,YAAYL,SAAAA,GAAY;QAAEV,OAAAA,EAAS,KAAA;AAAOC,QAAAA,MAAAA,EAAQ;KAAG,GAAI;QAAED,OAAAA,EAAS,IAAA;QAAMQ,GAAAA,EAAK,SAAA;AAAWP,QAAAA,MAAAA,EAAQ,EAAE;AAAEc,QAAAA;AAAQ,KAAA;AACzH;AAEA,SAASR,aAAAA,CAAca,IAAY,EAAExB,MAAc,EAAA;AAC/C,IAAA,MAAMyB,QAA4B,EAAE;AACpC,IAAA,IAAK,IAAIC,EAAAA,GAAKF,IAAAA,CAAKG,OAAO,CAAC3B,SAAS0B,EAAAA,IAAM,CAAA,EAAGA,EAAAA,GAAKF,IAAAA,CAAKG,OAAO,CAAC3B,MAAAA,EAAQ0B,EAAAA,GAAK1B,MAAAA,CAAOG,MAAM,CAAA,CAAG;AACxFsB,QAAAA,KAAAA,CAAMG,IAAI,CAAC;AAACF,YAAAA,EAAAA;AAAIA,YAAAA,EAAAA,GAAK1B,OAAOG;AAAO,SAAA,CAAA;AACvC,IAAA;IAEA,OAAOsB,KAAAA;AACX;;"}
@@ -0,0 +1,18 @@
1
+ 'use strict';
2
+
3
+ // Copyright (c) Cratis. All rights reserved.
4
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
5
+ /**
6
+ * How often and how recently one component was added to a canvas.
7
+ */ /** The number of components the "Frequently used" group lists (spec PICK3). */ const frequentlyUsedLimit = 6;
8
+ /**
9
+ * The components the "Frequently used" group lists: at most `limit` (6 by default), the most used first, ties broken by the most
10
+ * recent use, and any remaining tie by the component name so the order is stable. Components never used, or whose count is not a
11
+ * positive number, are not listed, so a picker hides the group when this is empty.
12
+ */ function rankFrequentlyUsedComponents(usage, limit = frequentlyUsedLimit) {
13
+ return Object.entries(usage).filter(([, entry])=>Number.isFinite(entry.count) && entry.count > 0).sort(([leftName, left], [rightName, right])=>right.count - left.count || right.last - left.last || leftName.localeCompare(rightName)).slice(0, Math.max(0, limit)).map(([name])=>name);
14
+ }
15
+
16
+ exports.frequentlyUsedLimit = frequentlyUsedLimit;
17
+ exports.rankFrequentlyUsedComponents = rankFrequentlyUsedComponents;
18
+ //# sourceMappingURL=rankFrequentlyUsedComponents.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rankFrequentlyUsedComponents.js","sources":["../../../descriptors/rankFrequentlyUsedComponents.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\n/**\n * How often and how recently one component was added to a canvas.\n */\nexport interface ComponentUsage {\n /** How many times the component was added. */\n count: number;\n\n /** When it was last added, in milliseconds since the epoch. */\n last: number;\n}\n\n/** The number of components the \"Frequently used\" group lists (spec PICK3). */\nexport const frequentlyUsedLimit = 6;\n\n/**\n * The components the \"Frequently used\" group lists: at most `limit` (6 by default), the most used first, ties broken by the most\n * recent use, and any remaining tie by the component name so the order is stable. Components never used, or whose count is not a\n * positive number, are not listed, so a picker hides the group when this is empty.\n */\nexport function rankFrequentlyUsedComponents(usage: Readonly<Record<string, ComponentUsage>>, limit: number = frequentlyUsedLimit): string[] {\n return Object.entries(usage)\n .filter(([, entry]) => Number.isFinite(entry.count) && entry.count > 0)\n .sort(([leftName, left], [rightName, right]) => right.count - left.count || right.last - left.last || leftName.localeCompare(rightName))\n .slice(0, Math.max(0, limit))\n .map(([name]) => name);\n}\n"],"names":["frequentlyUsedLimit","rankFrequentlyUsedComponents","usage","limit","Object","entries","filter","entry","Number","isFinite","count","sort","leftName","left","rightName","right","last","localeCompare","slice","Math","max","map","name"],"mappings":";;AAAA;AACA;AAEA;;AAEC,oFAUM,MAAMA,sBAAsB;AAEnC;;;;AAIC,IACM,SAASC,4BAAAA,CAA6BC,KAA+C,EAAEC,QAAgBH,mBAAmB,EAAA;AAC7H,IAAA,OAAOI,MAAAA,CAAOC,OAAO,CAACH,KAAAA,CAAAA,CACjBI,MAAM,CAAC,CAAC,GAAGC,KAAAA,CAAM,GAAKC,MAAAA,CAAOC,QAAQ,CAACF,KAAAA,CAAMG,KAAK,CAAA,IAAKH,KAAAA,CAAMG,KAAK,GAAG,CAAA,CAAA,CACpEC,IAAI,CAAC,CAAC,CAACC,QAAAA,EAAUC,IAAAA,CAAK,EAAE,CAACC,SAAAA,EAAWC,KAAAA,CAAM,GAAKA,KAAAA,CAAML,KAAK,GAAGG,IAAAA,CAAKH,KAAK,IAAIK,KAAAA,CAAMC,IAAI,GAAGH,IAAAA,CAAKG,IAAI,IAAIJ,QAAAA,CAASK,aAAa,CAACH,SAAAA,CAAAA,CAAAA,CAC5HI,KAAK,CAAC,CAAA,EAAGC,IAAAA,CAAKC,GAAG,CAAC,CAAA,EAAGjB,KAAAA,CAAAA,CAAAA,CACrBkB,GAAG,CAAC,CAAC,CAACC,IAAAA,CAAK,GAAKA,IAAAA,CAAAA;AACzB;;;"}
@@ -0,0 +1,116 @@
1
+ 'use strict';
2
+
3
+ var scene_model = require('@cratis/scene.model');
4
+
5
+ // Copyright (c) Cratis. All rights reserved.
6
+ // Licensed under the MIT license. See LICENSE file in the project root for full license information.
7
+ /**
8
+ * Checks the component metadata a package ships - what a picker and a designer read without loading a component.
9
+ *
10
+ * - **CMP1** `componentCategoryMissing` (error): a registered component has no descriptor, or its descriptor declares no category.
11
+ * - `componentCategoryUnknown` (warning): the category is not one of the model's; a package may add its own, so this is a
12
+ * nudge towards the shared vocabulary, not a refusal.
13
+ * - `componentDesignModeInvalid` (error): the design-mode descriptor names an items or collection path that is not a `Collection`
14
+ * property of the component, or repeats an action id.
15
+ * - `propertyEditorOptionsInvalid` (error): an editor's parameters cannot work - a `reference` editor that does not say what it
16
+ * references, a step that is not positive, an empty list of slice kinds.
17
+ *
18
+ * `registered` is the bare or package-qualified names of the components the package registers; when given, each must be described.
19
+ * The result is in a stable order: registered components first (in the order given), then each descriptor's findings in its own order.
20
+ */ function validateComponentDescriptors(descriptors, registered, knownCategories = scene_model.componentCategories) {
21
+ const diagnostics = [];
22
+ const described = new Map(descriptors.map((descriptor)=>[
23
+ descriptor.component,
24
+ descriptor
25
+ ]));
26
+ for (const component of registered ?? []){
27
+ if (!described.has(component)) {
28
+ diagnostics.push(missingCategory(component, `'${component}' is registered but has no descriptor, so it declares no category`));
29
+ }
30
+ }
31
+ for (const descriptor of descriptors){
32
+ const { component } = descriptor;
33
+ if (descriptor.category === undefined || descriptor.category.trim().length === 0) {
34
+ diagnostics.push(missingCategory(component, `'${component}' declares no category`));
35
+ } else if (!knownCategories.includes(descriptor.category)) {
36
+ diagnostics.push({
37
+ code: scene_model.DiagnosticCode.ComponentCategoryUnknown,
38
+ severity: scene_model.DiagnosticSeverity.Warning,
39
+ component,
40
+ message: `'${component}' declares the category '${descriptor.category}', which is not one of ${knownCategories.join(', ')}`
41
+ });
42
+ }
43
+ if (descriptor.designMode) {
44
+ diagnostics.push(...validateDesignMode(descriptor, descriptor.designMode));
45
+ }
46
+ diagnostics.push(...validateEditorOptions(component, descriptor.properties, ''));
47
+ }
48
+ return diagnostics;
49
+ }
50
+ function missingCategory(component, message) {
51
+ return {
52
+ code: scene_model.DiagnosticCode.ComponentCategoryMissing,
53
+ severity: scene_model.DiagnosticSeverity.Error,
54
+ component,
55
+ message
56
+ };
57
+ }
58
+ function invalidDesignMode(component, message) {
59
+ return {
60
+ code: scene_model.DiagnosticCode.ComponentDesignModeInvalid,
61
+ severity: scene_model.DiagnosticSeverity.Error,
62
+ component,
63
+ message
64
+ };
65
+ }
66
+ function validateDesignMode(descriptor, designMode) {
67
+ const { component } = descriptor;
68
+ const diagnostics = [];
69
+ const collections = new Set(descriptor.properties.filter((property)=>property.valueType === scene_model.PropertyValueType.Collection).map((property)=>property.path));
70
+ if (designMode.itemsPath !== undefined && !collections.has(designMode.itemsPath)) {
71
+ diagnostics.push(invalidDesignMode(component, `'${component}' names '${designMode.itemsPath}' as its design-mode items, which is not one of its Collection properties`));
72
+ }
73
+ const seenAdd = new Set();
74
+ for (const action of designMode.addActions){
75
+ if (seenAdd.has(action.id)) diagnostics.push(invalidDesignMode(component, `'${component}' declares the design-mode add action '${action.id}' more than once`));
76
+ seenAdd.add(action.id);
77
+ if (action.collectionPath !== undefined && !collections.has(action.collectionPath)) {
78
+ diagnostics.push(invalidDesignMode(component, `the design-mode add action '${action.id}' of '${component}' adds to '${action.collectionPath}', which is not one of its Collection properties`));
79
+ }
80
+ }
81
+ const seenItem = new Set();
82
+ for (const action of designMode.itemToolbar){
83
+ if (seenItem.has(action.id)) diagnostics.push(invalidDesignMode(component, `'${component}' declares the design-mode item action '${action.id}' more than once`));
84
+ seenItem.add(action.id);
85
+ }
86
+ return diagnostics;
87
+ }
88
+ function validateEditorOptions(component, properties, parent) {
89
+ const diagnostics = [];
90
+ const invalid = (path, message)=>({
91
+ code: scene_model.DiagnosticCode.PropertyEditorOptionsInvalid,
92
+ severity: scene_model.DiagnosticSeverity.Error,
93
+ component,
94
+ path,
95
+ message: `'${component}' property '${path}' ${message}`
96
+ });
97
+ for (const property of properties){
98
+ const path = `${parent}${property.path}`;
99
+ const options = property.editorOptions;
100
+ if (property.editorKind === scene_model.PropertyEditorKind.Reference && !options?.reference?.target) {
101
+ diagnostics.push(invalid(path, "uses the 'reference' editor without saying what it references (editorOptions.reference.target)"));
102
+ }
103
+ if (options?.step !== undefined && !(options.step > 0)) {
104
+ diagnostics.push(invalid(path, 'declares a step that is not greater than zero'));
105
+ }
106
+ if (options?.kinds !== undefined && options.kinds.length === 0) {
107
+ diagnostics.push(invalid(path, 'declares an empty list of kinds, which would offer nothing'));
108
+ }
109
+ if (property.item) diagnostics.push(...validateEditorOptions(component, property.item.properties, `${path}[].`));
110
+ if (property.properties) diagnostics.push(...validateEditorOptions(component, property.properties, `${path}.`));
111
+ }
112
+ return diagnostics;
113
+ }
114
+
115
+ exports.validateComponentDescriptors = validateComponentDescriptors;
116
+ //# sourceMappingURL=validateComponentDescriptors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validateComponentDescriptors.js","sources":["../../../descriptors/validateComponentDescriptors.ts"],"sourcesContent":["// Copyright (c) Cratis. All rights reserved.\n// Licensed under the MIT license. See LICENSE file in the project root for full license information.\n\nimport {\n ComponentDescriptor, ComponentDesignModeDescriptor, DiagnosticCode, DiagnosticSeverity, PropertyDescriptor, PropertyEditorKind, PropertyValueType,\n SceneDiagnostic, componentCategories,\n} from '@cratis/scene.model';\n\n/**\n * Checks the component metadata a package ships - what a picker and a designer read without loading a component.\n *\n * - **CMP1** `componentCategoryMissing` (error): a registered component has no descriptor, or its descriptor declares no category.\n * - `componentCategoryUnknown` (warning): the category is not one of the model's; a package may add its own, so this is a\n * nudge towards the shared vocabulary, not a refusal.\n * - `componentDesignModeInvalid` (error): the design-mode descriptor names an items or collection path that is not a `Collection`\n * property of the component, or repeats an action id.\n * - `propertyEditorOptionsInvalid` (error): an editor's parameters cannot work - a `reference` editor that does not say what it\n * references, a step that is not positive, an empty list of slice kinds.\n *\n * `registered` is the bare or package-qualified names of the components the package registers; when given, each must be described.\n * The result is in a stable order: registered components first (in the order given), then each descriptor's findings in its own order.\n */\nexport function validateComponentDescriptors(\n descriptors: readonly ComponentDescriptor[],\n registered?: readonly string[],\n knownCategories: readonly string[] = componentCategories\n): SceneDiagnostic[] {\n const diagnostics: SceneDiagnostic[] = [];\n const described = new Map(descriptors.map(descriptor => [descriptor.component, descriptor]));\n\n for (const component of registered ?? []) {\n if (!described.has(component)) {\n diagnostics.push(missingCategory(component, `'${component}' is registered but has no descriptor, so it declares no category`));\n }\n }\n\n for (const descriptor of descriptors) {\n const { component } = descriptor;\n\n if (descriptor.category === undefined || descriptor.category.trim().length === 0) {\n diagnostics.push(missingCategory(component, `'${component}' declares no category`));\n } else if (!knownCategories.includes(descriptor.category)) {\n diagnostics.push({\n code: DiagnosticCode.ComponentCategoryUnknown, severity: DiagnosticSeverity.Warning, component,\n message: `'${component}' declares the category '${descriptor.category}', which is not one of ${knownCategories.join(', ')}`,\n });\n }\n\n if (descriptor.designMode) {\n diagnostics.push(...validateDesignMode(descriptor, descriptor.designMode));\n }\n\n diagnostics.push(...validateEditorOptions(component, descriptor.properties, ''));\n }\n\n return diagnostics;\n}\n\nfunction missingCategory(component: string, message: string): SceneDiagnostic {\n return { code: DiagnosticCode.ComponentCategoryMissing, severity: DiagnosticSeverity.Error, component, message };\n}\n\nfunction invalidDesignMode(component: string, message: string): SceneDiagnostic {\n return { code: DiagnosticCode.ComponentDesignModeInvalid, severity: DiagnosticSeverity.Error, component, message };\n}\n\nfunction validateDesignMode(descriptor: ComponentDescriptor, designMode: ComponentDesignModeDescriptor): SceneDiagnostic[] {\n const { component } = descriptor;\n const diagnostics: SceneDiagnostic[] = [];\n const collections = new Set(descriptor.properties.filter(property => property.valueType === PropertyValueType.Collection).map(property => property.path));\n\n if (designMode.itemsPath !== undefined && !collections.has(designMode.itemsPath)) {\n diagnostics.push(invalidDesignMode(component, `'${component}' names '${designMode.itemsPath}' as its design-mode items, which is not one of its Collection properties`));\n }\n\n const seenAdd = new Set<string>();\n for (const action of designMode.addActions) {\n if (seenAdd.has(action.id)) diagnostics.push(invalidDesignMode(component, `'${component}' declares the design-mode add action '${action.id}' more than once`));\n seenAdd.add(action.id);\n\n if (action.collectionPath !== undefined && !collections.has(action.collectionPath)) {\n diagnostics.push(invalidDesignMode(component, `the design-mode add action '${action.id}' of '${component}' adds to '${action.collectionPath}', which is not one of its Collection properties`));\n }\n }\n\n const seenItem = new Set<string>();\n for (const action of designMode.itemToolbar) {\n if (seenItem.has(action.id)) diagnostics.push(invalidDesignMode(component, `'${component}' declares the design-mode item action '${action.id}' more than once`));\n seenItem.add(action.id);\n }\n\n return diagnostics;\n}\n\nfunction validateEditorOptions(component: string, properties: readonly PropertyDescriptor[], parent: string): SceneDiagnostic[] {\n const diagnostics: SceneDiagnostic[] = [];\n const invalid = (path: string, message: string): SceneDiagnostic => ({\n code: DiagnosticCode.PropertyEditorOptionsInvalid, severity: DiagnosticSeverity.Error, component, path, message: `'${component}' property '${path}' ${message}`,\n });\n\n for (const property of properties) {\n const path = `${parent}${property.path}`;\n const options = property.editorOptions;\n\n if (property.editorKind === PropertyEditorKind.Reference && !options?.reference?.target) {\n diagnostics.push(invalid(path, \"uses the 'reference' editor without saying what it references (editorOptions.reference.target)\"));\n }\n\n if (options?.step !== undefined && !(options.step > 0)) {\n diagnostics.push(invalid(path, 'declares a step that is not greater than zero'));\n }\n\n if (options?.kinds !== undefined && options.kinds.length === 0) {\n diagnostics.push(invalid(path, 'declares an empty list of kinds, which would offer nothing'));\n }\n\n if (property.item) diagnostics.push(...validateEditorOptions(component, property.item.properties, `${path}[].`));\n if (property.properties) diagnostics.push(...validateEditorOptions(component, property.properties, `${path}.`));\n }\n\n return diagnostics;\n}\n"],"names":["validateComponentDescriptors","descriptors","registered","knownCategories","componentCategories","diagnostics","described","Map","map","descriptor","component","has","push","missingCategory","category","undefined","trim","length","includes","code","DiagnosticCode","ComponentCategoryUnknown","severity","DiagnosticSeverity","Warning","message","join","designMode","validateDesignMode","validateEditorOptions","properties","ComponentCategoryMissing","Error","invalidDesignMode","ComponentDesignModeInvalid","collections","Set","filter","property","valueType","PropertyValueType","Collection","path","itemsPath","seenAdd","action","addActions","id","add","collectionPath","seenItem","itemToolbar","parent","invalid","PropertyEditorOptionsInvalid","options","editorOptions","editorKind","PropertyEditorKind","Reference","reference","target","step","kinds","item"],"mappings":"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"}
package/dist/cjs/index.js CHANGED
@@ -27,6 +27,10 @@ var resolveIconLibraries = require('./icons/resolveIconLibraries.js');
27
27
  var EffectiveIconEntry = require('./icons/EffectiveIconEntry.js');
28
28
  var EffectiveIconCatalog = require('./icons/EffectiveIconCatalog.js');
29
29
  var analyzeIconImpact = require('./icons/analyzeIconImpact.js');
30
+ var validateComponentDescriptors = require('./descriptors/validateComponentDescriptors.js');
31
+ var componentBadge = require('./descriptors/componentBadge.js');
32
+ var matchComponentQuery = require('./descriptors/matchComponentQuery.js');
33
+ var rankFrequentlyUsedComponents = require('./descriptors/rankFrequentlyUsedComponents.js');
30
34
  var resolveComponentName = require('./resolveComponentName.js');
31
35
  var computeSizeClass = require('./computeSizeClass.js');
32
36
  var evaluateFlowArrangement = require('./evaluateFlowArrangement.js');
@@ -41,7 +45,10 @@ var packageCatalog = require('./packageCatalog.js');
41
45
  var resolveScreenTemplates = require('./resolveScreenTemplates.js');
42
46
  var templateApplicability = require('./templateApplicability.js');
43
47
  var TemplateCatalogKind = require('./templates/TemplateCatalogKind.js');
48
+ var TemplateReferenceResolution = require('./templates/TemplateReferenceResolution.js');
49
+ var applicableTemplates = require('./templates/applicableTemplates.js');
44
50
  var describeTemplateCatalog = require('./templates/describeTemplateCatalog.js');
51
+ var resolveTemplateReference = require('./templates/resolveTemplateReference.js');
45
52
  var templateMetadataRules = require('./templates/templateMetadataRules.js');
46
53
  var validateTemplateMetadata = require('./templates/validateTemplateMetadata.js');
47
54
  var buildStarterProfile = require('./buildStarterProfile.js');
@@ -117,6 +124,11 @@ exports.resolveIconLibraries = resolveIconLibraries.resolveIconLibraries;
117
124
  exports.referencesOfEntry = EffectiveIconEntry.referencesOfEntry;
118
125
  exports.EffectiveIconCatalog = EffectiveIconCatalog.EffectiveIconCatalog;
119
126
  exports.analyzeIconImpact = analyzeIconImpact.analyzeIconImpact;
127
+ exports.validateComponentDescriptors = validateComponentDescriptors.validateComponentDescriptors;
128
+ exports.componentBadge = componentBadge.componentBadge;
129
+ exports.matchComponentQuery = matchComponentQuery.matchComponentQuery;
130
+ exports.frequentlyUsedLimit = rankFrequentlyUsedComponents.frequentlyUsedLimit;
131
+ exports.rankFrequentlyUsedComponents = rankFrequentlyUsedComponents.rankFrequentlyUsedComponents;
120
132
  exports.corePackage = resolveComponentName.corePackage;
121
133
  exports.effectivePackages = resolveComponentName.effectivePackages;
122
134
  exports.resolveComponentName = resolveComponentName.resolveComponentName;
@@ -147,9 +159,17 @@ exports.validateDialogTemplateApplicability = templateApplicability.validateDial
147
159
  exports.validateLayoutApplicability = templateApplicability.validateLayoutApplicability;
148
160
  exports.validateScreenTemplateApplicability = templateApplicability.validateScreenTemplateApplicability;
149
161
  exports.TemplateCatalogKind = TemplateCatalogKind.TemplateCatalogKind;
162
+ exports.TemplateReferenceStatus = TemplateReferenceResolution.TemplateReferenceStatus;
163
+ exports.applicableTemplates = applicableTemplates.applicableTemplates;
164
+ exports.validateDialogTemplateForTarget = applicableTemplates.validateDialogTemplateForTarget;
165
+ exports.validateLayoutForTarget = applicableTemplates.validateLayoutForTarget;
166
+ exports.validateScreenTemplateForTarget = applicableTemplates.validateScreenTemplateForTarget;
150
167
  exports.describeTemplateCatalog = describeTemplateCatalog.describeTemplateCatalog;
168
+ exports.resolveTemplateReference = resolveTemplateReference.resolveTemplateReference;
151
169
  exports.isAbsoluteHttpsUrl = templateMetadataRules.isAbsoluteHttpsUrl;
152
170
  exports.isSpdxExpression = templateMetadataRules.isSpdxExpression;
171
+ exports.templateCategoryMissingCode = validateTemplateMetadata.templateCategoryMissingCode;
172
+ exports.validateTemplateCategory = validateTemplateMetadata.validateTemplateCategory;
153
173
  exports.validateTemplateMetadata = validateTemplateMetadata.validateTemplateMetadata;
154
174
  exports.buildStarterProfile = buildStarterProfile.buildStarterProfile;
155
175
  exports.incompatibleStarterThemes = incompatibleStarterThemes.incompatibleStarterThemes;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}