@cratis/scene.engine 4.2.0 → 4.3.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 (310) hide show
  1. package/dist/cjs/editing/DescriptorCatalog.js +42 -0
  2. package/dist/cjs/editing/DescriptorCatalog.js.map +1 -0
  3. package/dist/cjs/editing/EditSession.js +44 -0
  4. package/dist/cjs/editing/EditSession.js.map +1 -0
  5. package/dist/cjs/editing/applyEdit.js +83 -0
  6. package/dist/cjs/editing/applyEdit.js.map +1 -0
  7. package/dist/cjs/editing/applyEffectiveConfiguration.js +62 -0
  8. package/dist/cjs/editing/applyEffectiveConfiguration.js.map +1 -0
  9. package/dist/cjs/editing/collectComponents.js +34 -0
  10. package/dist/cjs/editing/collectComponents.js.map +1 -0
  11. package/dist/cjs/editing/describeNode.js +63 -0
  12. package/dist/cjs/editing/describeNode.js.map +1 -0
  13. package/dist/cjs/editing/diagnostics.js +36 -0
  14. package/dist/cjs/editing/diagnostics.js.map +1 -0
  15. package/dist/cjs/editing/documentPaths.js +45 -0
  16. package/dist/cjs/editing/documentPaths.js.map +1 -0
  17. package/dist/cjs/editing/exposureEdits.js +106 -0
  18. package/dist/cjs/editing/exposureEdits.js.map +1 -0
  19. package/dist/cjs/editing/exposureGrants.js +115 -0
  20. package/dist/cjs/editing/exposureGrants.js.map +1 -0
  21. package/dist/cjs/editing/indexDocument.js +400 -0
  22. package/dist/cjs/editing/indexDocument.js.map +1 -0
  23. package/dist/cjs/editing/inspect.js +127 -0
  24. package/dist/cjs/editing/inspect.js.map +1 -0
  25. package/dist/cjs/editing/instanceEdits.js +255 -0
  26. package/dist/cjs/editing/instanceEdits.js.map +1 -0
  27. package/dist/cjs/editing/layoutTypeDescriptors.js +425 -0
  28. package/dist/cjs/editing/layoutTypeDescriptors.js.map +1 -0
  29. package/dist/cjs/editing/layoutTypeEdits.js +141 -0
  30. package/dist/cjs/editing/layoutTypeEdits.js.map +1 -0
  31. package/dist/cjs/editing/nodeIds.js +115 -0
  32. package/dist/cjs/editing/nodeIds.js.map +1 -0
  33. package/dist/cjs/editing/pathAccess.js +53 -0
  34. package/dist/cjs/editing/pathAccess.js.map +1 -0
  35. package/dist/cjs/editing/propertyEdits.js +118 -0
  36. package/dist/cjs/editing/propertyEdits.js.map +1 -0
  37. package/dist/cjs/editing/resolveEffectiveConfiguration.js +200 -0
  38. package/dist/cjs/editing/resolveEffectiveConfiguration.js.map +1 -0
  39. package/dist/cjs/editing/resolveTemplateChain.js +100 -0
  40. package/dist/cjs/editing/resolveTemplateChain.js.map +1 -0
  41. package/dist/cjs/editing/structureEdits.js +231 -0
  42. package/dist/cjs/editing/structureEdits.js.map +1 -0
  43. package/dist/cjs/editing/validateBinding.js +111 -0
  44. package/dist/cjs/editing/validateBinding.js.map +1 -0
  45. package/dist/cjs/editing/validateValue.js +114 -0
  46. package/dist/cjs/editing/validateValue.js.map +1 -0
  47. package/dist/cjs/index.js +51 -0
  48. package/dist/cjs/index.js.map +1 -1
  49. package/dist/esm/editing/DescriptorCatalog.d.ts +9 -0
  50. package/dist/esm/editing/DescriptorCatalog.d.ts.map +1 -0
  51. package/dist/esm/editing/DescriptorCatalog.js +38 -0
  52. package/dist/esm/editing/DescriptorCatalog.js.map +1 -0
  53. package/dist/esm/editing/DocumentIndex.d.ts +31 -0
  54. package/dist/esm/editing/DocumentIndex.d.ts.map +1 -0
  55. package/dist/esm/editing/DocumentIndex.js +2 -0
  56. package/dist/esm/editing/DocumentIndex.js.map +1 -0
  57. package/dist/esm/editing/EditOutcome.d.ts +7 -0
  58. package/dist/esm/editing/EditOutcome.d.ts.map +1 -0
  59. package/dist/esm/editing/EditOutcome.js +2 -0
  60. package/dist/esm/editing/EditOutcome.js.map +1 -0
  61. package/dist/esm/editing/EditSession.d.ts +20 -0
  62. package/dist/esm/editing/EditSession.d.ts.map +1 -0
  63. package/dist/esm/editing/EditSession.js +42 -0
  64. package/dist/esm/editing/EditSession.js.map +1 -0
  65. package/dist/esm/editing/EditingContext.d.ts +8 -0
  66. package/dist/esm/editing/EditingContext.d.ts.map +1 -0
  67. package/dist/esm/editing/EditingContext.js +2 -0
  68. package/dist/esm/editing/EditingContext.js.map +1 -0
  69. package/dist/esm/editing/TemplateChain.d.ts +13 -0
  70. package/dist/esm/editing/TemplateChain.d.ts.map +1 -0
  71. package/dist/esm/editing/TemplateChain.js +2 -0
  72. package/dist/esm/editing/TemplateChain.js.map +1 -0
  73. package/dist/esm/editing/applyEdit.d.ts +5 -0
  74. package/dist/esm/editing/applyEdit.d.ts.map +1 -0
  75. package/dist/esm/editing/applyEdit.js +81 -0
  76. package/dist/esm/editing/applyEdit.js.map +1 -0
  77. package/dist/esm/editing/applyEffectiveConfiguration.d.ts +3 -0
  78. package/dist/esm/editing/applyEffectiveConfiguration.d.ts.map +1 -0
  79. package/dist/esm/editing/applyEffectiveConfiguration.js +60 -0
  80. package/dist/esm/editing/applyEffectiveConfiguration.js.map +1 -0
  81. package/dist/esm/editing/collectComponents.d.ts +3 -0
  82. package/dist/esm/editing/collectComponents.d.ts.map +1 -0
  83. package/dist/esm/editing/collectComponents.js +32 -0
  84. package/dist/esm/editing/collectComponents.js.map +1 -0
  85. package/dist/esm/editing/describeNode.d.ts +11 -0
  86. package/dist/esm/editing/describeNode.d.ts.map +1 -0
  87. package/dist/esm/editing/describeNode.js +60 -0
  88. package/dist/esm/editing/describeNode.js.map +1 -0
  89. package/dist/esm/editing/diagnostics.d.ts +6 -0
  90. package/dist/esm/editing/diagnostics.d.ts.map +1 -0
  91. package/dist/esm/editing/diagnostics.js +32 -0
  92. package/dist/esm/editing/diagnostics.js.map +1 -0
  93. package/dist/esm/editing/documentPaths.d.ts +8 -0
  94. package/dist/esm/editing/documentPaths.d.ts.map +1 -0
  95. package/dist/esm/editing/documentPaths.js +41 -0
  96. package/dist/esm/editing/documentPaths.js.map +1 -0
  97. package/dist/esm/editing/exposureEdits.d.ts +5 -0
  98. package/dist/esm/editing/exposureEdits.d.ts.map +1 -0
  99. package/dist/esm/editing/exposureEdits.js +103 -0
  100. package/dist/esm/editing/exposureEdits.js.map +1 -0
  101. package/dist/esm/editing/exposureGrants.d.ts +21 -0
  102. package/dist/esm/editing/exposureGrants.d.ts.map +1 -0
  103. package/dist/esm/editing/exposureGrants.js +112 -0
  104. package/dist/esm/editing/exposureGrants.js.map +1 -0
  105. package/dist/esm/editing/given/a_layout_document.d.ts +14 -0
  106. package/dist/esm/editing/given/a_layout_document.d.ts.map +1 -0
  107. package/dist/esm/editing/given/a_layout_document.js +67 -0
  108. package/dist/esm/editing/given/a_layout_document.js.map +1 -0
  109. package/dist/esm/editing/given/a_scene_document.d.ts +20 -0
  110. package/dist/esm/editing/given/a_scene_document.d.ts.map +1 -0
  111. package/dist/esm/editing/given/a_scene_document.js +110 -0
  112. package/dist/esm/editing/given/a_scene_document.js.map +1 -0
  113. package/dist/esm/editing/index.d.ts +19 -0
  114. package/dist/esm/editing/index.d.ts.map +1 -0
  115. package/dist/esm/editing/index.js +19 -0
  116. package/dist/esm/editing/index.js.map +1 -0
  117. package/dist/esm/editing/indexDocument.d.ts +6 -0
  118. package/dist/esm/editing/indexDocument.d.ts.map +1 -0
  119. package/dist/esm/editing/indexDocument.js +396 -0
  120. package/dist/esm/editing/indexDocument.js.map +1 -0
  121. package/dist/esm/editing/inspect.d.ts +8 -0
  122. package/dist/esm/editing/inspect.d.ts.map +1 -0
  123. package/dist/esm/editing/inspect.js +124 -0
  124. package/dist/esm/editing/inspect.js.map +1 -0
  125. package/dist/esm/editing/instanceEdits.d.ts +9 -0
  126. package/dist/esm/editing/instanceEdits.d.ts.map +1 -0
  127. package/dist/esm/editing/instanceEdits.js +248 -0
  128. package/dist/esm/editing/instanceEdits.js.map +1 -0
  129. package/dist/esm/editing/layoutTypeDescriptors.d.ts +5 -0
  130. package/dist/esm/editing/layoutTypeDescriptors.d.ts.map +1 -0
  131. package/dist/esm/editing/layoutTypeDescriptors.js +421 -0
  132. package/dist/esm/editing/layoutTypeDescriptors.js.map +1 -0
  133. package/dist/esm/editing/layoutTypeEdits.d.ts +4 -0
  134. package/dist/esm/editing/layoutTypeEdits.d.ts.map +1 -0
  135. package/dist/esm/editing/layoutTypeEdits.js +139 -0
  136. package/dist/esm/editing/layoutTypeEdits.js.map +1 -0
  137. package/dist/esm/editing/nodeIds.d.ts +14 -0
  138. package/dist/esm/editing/nodeIds.d.ts.map +1 -0
  139. package/dist/esm/editing/nodeIds.js +102 -0
  140. package/dist/esm/editing/nodeIds.js.map +1 -0
  141. package/dist/esm/editing/pathAccess.d.ts +7 -0
  142. package/dist/esm/editing/pathAccess.d.ts.map +1 -0
  143. package/dist/esm/editing/pathAccess.js +48 -0
  144. package/dist/esm/editing/pathAccess.js.map +1 -0
  145. package/dist/esm/editing/propertyEdits.d.ts +15 -0
  146. package/dist/esm/editing/propertyEdits.d.ts.map +1 -0
  147. package/dist/esm/editing/propertyEdits.js +113 -0
  148. package/dist/esm/editing/propertyEdits.js.map +1 -0
  149. package/dist/esm/editing/resolveEffectiveConfiguration.d.ts +5 -0
  150. package/dist/esm/editing/resolveEffectiveConfiguration.d.ts.map +1 -0
  151. package/dist/esm/editing/resolveEffectiveConfiguration.js +198 -0
  152. package/dist/esm/editing/resolveEffectiveConfiguration.js.map +1 -0
  153. package/dist/esm/editing/resolveTemplateChain.d.ts +4 -0
  154. package/dist/esm/editing/resolveTemplateChain.d.ts.map +1 -0
  155. package/dist/esm/editing/resolveTemplateChain.js +98 -0
  156. package/dist/esm/editing/resolveTemplateChain.js.map +1 -0
  157. package/dist/esm/editing/structureEdits.d.ts +6 -0
  158. package/dist/esm/editing/structureEdits.d.ts.map +1 -0
  159. package/dist/esm/editing/structureEdits.js +227 -0
  160. package/dist/esm/editing/structureEdits.js.map +1 -0
  161. package/dist/esm/editing/validateBinding.d.ts +7 -0
  162. package/dist/esm/editing/validateBinding.d.ts.map +1 -0
  163. package/dist/esm/editing/validateBinding.js +108 -0
  164. package/dist/esm/editing/validateBinding.js.map +1 -0
  165. package/dist/esm/editing/validateValue.d.ts +8 -0
  166. package/dist/esm/editing/validateValue.d.ts.map +1 -0
  167. package/dist/esm/editing/validateValue.js +111 -0
  168. package/dist/esm/editing/validateValue.js.map +1 -0
  169. package/dist/esm/index.d.ts +1 -0
  170. package/dist/esm/index.d.ts.map +1 -1
  171. package/dist/esm/index.js +14 -0
  172. package/dist/esm/index.js.map +1 -1
  173. package/dist/esm/tsconfig.tsbuildinfo +1 -1
  174. package/editing/DescriptorCatalog.ts +51 -0
  175. package/editing/DocumentIndex.ts +73 -0
  176. package/editing/EditOutcome.ts +21 -0
  177. package/editing/EditSession.ts +50 -0
  178. package/editing/EditingContext.ts +23 -0
  179. package/editing/TemplateChain.ts +36 -0
  180. package/editing/applyEdit.ts +69 -0
  181. package/editing/applyEffectiveConfiguration.ts +53 -0
  182. package/editing/collectComponents.ts +36 -0
  183. package/editing/describeNode.ts +61 -0
  184. package/editing/diagnostics.ts +30 -0
  185. package/editing/documentPaths.ts +40 -0
  186. package/editing/exposureEdits.ts +119 -0
  187. package/editing/exposureGrants.ts +159 -0
  188. package/editing/given/a_layout_document.ts +88 -0
  189. package/editing/given/a_scene_document.ts +135 -0
  190. package/editing/index.ts +21 -0
  191. package/editing/indexDocument.ts +237 -0
  192. package/editing/inspect.ts +142 -0
  193. package/editing/instanceEdits.ts +243 -0
  194. package/editing/layoutTypeDescriptors.ts +191 -0
  195. package/editing/layoutTypeEdits.ts +135 -0
  196. package/editing/nodeIds.ts +111 -0
  197. package/editing/pathAccess.ts +81 -0
  198. package/editing/propertyEdits.ts +119 -0
  199. package/editing/resolveEffectiveConfiguration.ts +202 -0
  200. package/editing/resolveTemplateChain.ts +109 -0
  201. package/editing/structureEdits.ts +234 -0
  202. package/editing/validateBinding.ts +103 -0
  203. package/editing/validateValue.ts +130 -0
  204. package/index.ts +1 -0
  205. package/package.json +5 -4
  206. package/dist/esm/for_aggregateContributions/when_aggregating_by_contribution_point.d.ts +0 -2
  207. package/dist/esm/for_aggregateContributions/when_aggregating_by_contribution_point.d.ts.map +0 -1
  208. package/dist/esm/for_aggregateContributions/when_aggregating_by_contribution_point.js +0 -23
  209. package/dist/esm/for_aggregateContributions/when_aggregating_by_contribution_point.js.map +0 -1
  210. package/dist/esm/for_buildStarterProfile/when_checking_against_the_shared_fixture_corpus.d.ts +0 -2
  211. package/dist/esm/for_buildStarterProfile/when_checking_against_the_shared_fixture_corpus.d.ts.map +0 -1
  212. package/dist/esm/for_buildStarterProfile/when_checking_against_the_shared_fixture_corpus.js +0 -16
  213. package/dist/esm/for_buildStarterProfile/when_checking_against_the_shared_fixture_corpus.js.map +0 -1
  214. package/dist/esm/for_computeSizeClass/when_computing_the_size_class.d.ts +0 -2
  215. package/dist/esm/for_computeSizeClass/when_computing_the_size_class.d.ts.map +0 -1
  216. package/dist/esm/for_computeSizeClass/when_computing_the_size_class.js +0 -17
  217. package/dist/esm/for_computeSizeClass/when_computing_the_size_class.js.map +0 -1
  218. package/dist/esm/for_evaluateFlowArrangement/when_evaluating_against_the_shared_fixture_corpus.d.ts +0 -2
  219. package/dist/esm/for_evaluateFlowArrangement/when_evaluating_against_the_shared_fixture_corpus.d.ts.map +0 -1
  220. package/dist/esm/for_evaluateFlowArrangement/when_evaluating_against_the_shared_fixture_corpus.js +0 -43
  221. package/dist/esm/for_evaluateFlowArrangement/when_evaluating_against_the_shared_fixture_corpus.js.map +0 -1
  222. package/dist/esm/for_evaluateFreeformArrangement/when_evaluating_against_the_shared_fixture_corpus.d.ts +0 -2
  223. package/dist/esm/for_evaluateFreeformArrangement/when_evaluating_against_the_shared_fixture_corpus.d.ts.map +0 -1
  224. package/dist/esm/for_evaluateFreeformArrangement/when_evaluating_against_the_shared_fixture_corpus.js +0 -46
  225. package/dist/esm/for_evaluateFreeformArrangement/when_evaluating_against_the_shared_fixture_corpus.js.map +0 -1
  226. package/dist/esm/for_evaluateFreeformSlotArrangement/when_evaluating_against_the_shared_fixture_corpus.d.ts +0 -2
  227. package/dist/esm/for_evaluateFreeformSlotArrangement/when_evaluating_against_the_shared_fixture_corpus.d.ts.map +0 -1
  228. package/dist/esm/for_evaluateFreeformSlotArrangement/when_evaluating_against_the_shared_fixture_corpus.js +0 -25
  229. package/dist/esm/for_evaluateFreeformSlotArrangement/when_evaluating_against_the_shared_fixture_corpus.js.map +0 -1
  230. package/dist/esm/for_incompatibleStarterThemes/when_checking_against_the_shared_fixture_corpus.d.ts +0 -2
  231. package/dist/esm/for_incompatibleStarterThemes/when_checking_against_the_shared_fixture_corpus.d.ts.map +0 -1
  232. package/dist/esm/for_incompatibleStarterThemes/when_checking_against_the_shared_fixture_corpus.js +0 -17
  233. package/dist/esm/for_incompatibleStarterThemes/when_checking_against_the_shared_fixture_corpus.js.map +0 -1
  234. package/dist/esm/for_packageCatalog/when_querying_against_the_shared_fixture_corpus.d.ts +0 -2
  235. package/dist/esm/for_packageCatalog/when_querying_against_the_shared_fixture_corpus.d.ts.map +0 -1
  236. package/dist/esm/for_packageCatalog/when_querying_against_the_shared_fixture_corpus.js +0 -22
  237. package/dist/esm/for_packageCatalog/when_querying_against_the_shared_fixture_corpus.js.map +0 -1
  238. package/dist/esm/for_packageDependencies/fixtures.d.ts +0 -38
  239. package/dist/esm/for_packageDependencies/fixtures.d.ts.map +0 -1
  240. package/dist/esm/for_packageDependencies/fixtures.js +0 -5
  241. package/dist/esm/for_packageDependencies/fixtures.js.map +0 -1
  242. package/dist/esm/for_packageDependencies/when_resolving_against_the_shared_fixture_corpus.d.ts +0 -2
  243. package/dist/esm/for_packageDependencies/when_resolving_against_the_shared_fixture_corpus.d.ts.map +0 -1
  244. package/dist/esm/for_packageDependencies/when_resolving_against_the_shared_fixture_corpus.js +0 -29
  245. package/dist/esm/for_packageDependencies/when_resolving_against_the_shared_fixture_corpus.js.map +0 -1
  246. package/dist/esm/for_packageVersionRange/when_checking_against_the_shared_fixture_corpus.d.ts +0 -2
  247. package/dist/esm/for_packageVersionRange/when_checking_against_the_shared_fixture_corpus.d.ts.map +0 -1
  248. package/dist/esm/for_packageVersionRange/when_checking_against_the_shared_fixture_corpus.js +0 -10
  249. package/dist/esm/for_packageVersionRange/when_checking_against_the_shared_fixture_corpus.js.map +0 -1
  250. package/dist/esm/for_panelKind/when_telling_the_panels_apart.d.ts +0 -2
  251. package/dist/esm/for_panelKind/when_telling_the_panels_apart.d.ts.map +0 -1
  252. package/dist/esm/for_panelKind/when_telling_the_panels_apart.js +0 -38
  253. package/dist/esm/for_panelKind/when_telling_the_panels_apart.js.map +0 -1
  254. package/dist/esm/for_renderElement/when_rendering_a_mixed_tree.d.ts +0 -2
  255. package/dist/esm/for_renderElement/when_rendering_a_mixed_tree.d.ts.map +0 -1
  256. package/dist/esm/for_renderElement/when_rendering_a_mixed_tree.js +0 -46
  257. package/dist/esm/for_renderElement/when_rendering_a_mixed_tree.js.map +0 -1
  258. package/dist/esm/for_resolveBehaviors/when_resolving_additive_attachments.d.ts +0 -2
  259. package/dist/esm/for_resolveBehaviors/when_resolving_additive_attachments.d.ts.map +0 -1
  260. package/dist/esm/for_resolveBehaviors/when_resolving_additive_attachments.js +0 -68
  261. package/dist/esm/for_resolveBehaviors/when_resolving_additive_attachments.js.map +0 -1
  262. package/dist/esm/for_resolveComponentName/when_resolving_against_the_shared_fixture_corpus.d.ts +0 -2
  263. package/dist/esm/for_resolveComponentName/when_resolving_against_the_shared_fixture_corpus.d.ts.map +0 -1
  264. package/dist/esm/for_resolveComponentName/when_resolving_against_the_shared_fixture_corpus.js +0 -20
  265. package/dist/esm/for_resolveComponentName/when_resolving_against_the_shared_fixture_corpus.js.map +0 -1
  266. package/dist/esm/for_resolveScreenTemplates/when_resolving_against_the_shared_fixture_corpus.d.ts +0 -2
  267. package/dist/esm/for_resolveScreenTemplates/when_resolving_against_the_shared_fixture_corpus.d.ts.map +0 -1
  268. package/dist/esm/for_resolveScreenTemplates/when_resolving_against_the_shared_fixture_corpus.js +0 -30
  269. package/dist/esm/for_resolveScreenTemplates/when_resolving_against_the_shared_fixture_corpus.js.map +0 -1
  270. package/dist/esm/for_resolveStrings/when_resolving_a_strings_reference.d.ts +0 -2
  271. package/dist/esm/for_resolveStrings/when_resolving_a_strings_reference.d.ts.map +0 -1
  272. package/dist/esm/for_resolveStrings/when_resolving_a_strings_reference.js +0 -69
  273. package/dist/esm/for_resolveStrings/when_resolving_a_strings_reference.js.map +0 -1
  274. package/dist/esm/for_runActions/given/a_recording_dispatcher.d.ts +0 -21
  275. package/dist/esm/for_runActions/given/a_recording_dispatcher.d.ts.map +0 -1
  276. package/dist/esm/for_runActions/given/a_recording_dispatcher.js +0 -45
  277. package/dist/esm/for_runActions/given/a_recording_dispatcher.js.map +0 -1
  278. package/dist/esm/for_runActions/when_running_a_sequence_of_actions.d.ts +0 -2
  279. package/dist/esm/for_runActions/when_running_a_sequence_of_actions.d.ts.map +0 -1
  280. package/dist/esm/for_runActions/when_running_a_sequence_of_actions.js +0 -126
  281. package/dist/esm/for_runActions/when_running_a_sequence_of_actions.js.map +0 -1
  282. package/dist/esm/for_runActions/when_running_against_the_shared_fixture_corpus.d.ts +0 -2
  283. package/dist/esm/for_runActions/when_running_against_the_shared_fixture_corpus.d.ts.map +0 -1
  284. package/dist/esm/for_runActions/when_running_against_the_shared_fixture_corpus.js +0 -61
  285. package/dist/esm/for_runActions/when_running_against_the_shared_fixture_corpus.js.map +0 -1
  286. package/dist/esm/for_themeCompatibility/when_checking_against_the_shared_fixture_corpus.d.ts +0 -2
  287. package/dist/esm/for_themeCompatibility/when_checking_against_the_shared_fixture_corpus.d.ts.map +0 -1
  288. package/dist/esm/for_themeCompatibility/when_checking_against_the_shared_fixture_corpus.js +0 -18
  289. package/dist/esm/for_themeCompatibility/when_checking_against_the_shared_fixture_corpus.js.map +0 -1
  290. package/for_aggregateContributions/when_aggregating_by_contribution_point.ts +0 -31
  291. package/for_buildStarterProfile/when_checking_against_the_shared_fixture_corpus.ts +0 -32
  292. package/for_computeSizeClass/when_computing_the_size_class.ts +0 -23
  293. package/for_evaluateFlowArrangement/when_evaluating_against_the_shared_fixture_corpus.ts +0 -67
  294. package/for_evaluateFreeformArrangement/when_evaluating_against_the_shared_fixture_corpus.ts +0 -68
  295. package/for_evaluateFreeformSlotArrangement/when_evaluating_against_the_shared_fixture_corpus.ts +0 -47
  296. package/for_incompatibleStarterThemes/when_checking_against_the_shared_fixture_corpus.ts +0 -34
  297. package/for_packageCatalog/when_querying_against_the_shared_fixture_corpus.ts +0 -26
  298. package/for_packageDependencies/fixtures.ts +0 -44
  299. package/for_packageDependencies/when_resolving_against_the_shared_fixture_corpus.ts +0 -39
  300. package/for_packageVersionRange/when_checking_against_the_shared_fixture_corpus.ts +0 -13
  301. package/for_panelKind/when_telling_the_panels_apart.ts +0 -47
  302. package/for_renderElement/when_rendering_a_mixed_tree.ts +0 -58
  303. package/for_resolveBehaviors/when_resolving_additive_attachments.ts +0 -100
  304. package/for_resolveComponentName/when_resolving_against_the_shared_fixture_corpus.ts +0 -36
  305. package/for_resolveScreenTemplates/when_resolving_against_the_shared_fixture_corpus.ts +0 -49
  306. package/for_resolveStrings/when_resolving_a_strings_reference.ts +0 -85
  307. package/for_runActions/given/a_recording_dispatcher.ts +0 -72
  308. package/for_runActions/when_running_a_sequence_of_actions.ts +0 -226
  309. package/for_runActions/when_running_against_the_shared_fixture_corpus.ts +0 -124
  310. package/for_themeCompatibility/when_checking_against_the_shared_fixture_corpus.ts +0 -37
@@ -0,0 +1,202 @@
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
+ CollectionOperation, DiagnosticCode, EffectiveCollectionItem, EffectiveComponentConfiguration,
6
+ EffectiveConfiguration, EffectivePropertyValue, InstanceContribution, PropertyValueType, SceneDiagnostic, ValueSource,
7
+ } from '@cratis/scene.model';
8
+ import { DescriptorCatalog, findComponentDescriptor } from './DescriptorCatalog';
9
+ import { errorDiagnostic } from './diagnostics';
10
+ import { computeExposureGrants, ExposureGrant, grantKey } from './exposureGrants';
11
+ import { cloneData, getValueAtPath, setValueAtPath } from './pathAccess';
12
+ import { TemplateChain } from './TemplateChain';
13
+ import { validateItemValues, validateValue } from './validateValue';
14
+
15
+ /**
16
+ * Splits a flat collection item as it is stored on an element - `{ id, label, ... }` - into its id and fields.
17
+ */
18
+ function toEffectiveItem(item: unknown, index: number): EffectiveCollectionItem {
19
+ const { id, ...values } = (item !== null && typeof item === 'object' ? item : {}) as Record<string, unknown>;
20
+ return { id: typeof id === 'string' && id.length > 0 ? id : `owner-${index}`, values, origin: 'owner', fixed: true };
21
+ }
22
+
23
+ function toFlatItem(item: EffectiveCollectionItem): Record<string, unknown> {
24
+ return { id: item.id, ...item.values };
25
+ }
26
+
27
+ function scalarValue(grant: ExposureGrant, matching: InstanceContribution[], instance: string, current: EffectivePropertyValue, diagnostics: SceneDiagnostic[]): void {
28
+ for (const contribution of matching) {
29
+ const problem = contribution.value === undefined ? 'a value is required' : validateValue(grant.descriptor, contribution.value);
30
+ if (problem) {
31
+ diagnostics.push(errorDiagnostic(
32
+ DiagnosticCode.ContributionTypeMismatch,
33
+ `The saved value of '${grant.path}' set by '${instance}' is ignored: ${problem}. It is kept, and applies again when it fits.`,
34
+ { instance, component: grant.component, path: grant.path }));
35
+ continue;
36
+ }
37
+
38
+ current.value = contribution.value;
39
+ current.source = ValueSource.Instance;
40
+ current.contributedBy = instance;
41
+ }
42
+ }
43
+
44
+ function collectionValue(grant: ExposureGrant, matching: InstanceContribution[], instance: string, items: EffectiveCollectionItem[], diagnostics: SceneDiagnostic[]): boolean {
45
+ let contributed = false;
46
+
47
+ for (const contribution of matching) {
48
+ const context = { instance, component: grant.component, path: grant.path };
49
+ if (contribution.items === undefined) {
50
+ diagnostics.push(errorDiagnostic(DiagnosticCode.ContributionTypeMismatch, `The saved value of '${grant.path}' set by '${instance}' is ignored: a collection takes items.`, context));
51
+ continue;
52
+ }
53
+
54
+ if (contribution.items.length > 0 && !grant.operations.includes(CollectionOperation.Add)) {
55
+ diagnostics.push(errorDiagnostic(
56
+ DiagnosticCode.ContributionOperationNotPermitted,
57
+ `'${instance}' has items in '${grant.path}', but adding items is not exposed. They are ignored and kept.`,
58
+ context));
59
+ continue;
60
+ }
61
+
62
+ for (const item of contribution.items) {
63
+ const itemContext = { ...context, itemId: item.id };
64
+ if (items.some(existing => existing.id === item.id)) {
65
+ diagnostics.push(errorDiagnostic(DiagnosticCode.DuplicateCollectionItem, `The item id '${item.id}' in '${grant.path}' is already used.`, itemContext));
66
+ continue;
67
+ }
68
+
69
+ const values: Record<string, unknown> = {};
70
+ for (const [field, value] of Object.entries(item.values)) {
71
+ if (grant.editableFields !== undefined && !grant.editableFields.includes(field)) {
72
+ diagnostics.push(errorDiagnostic(DiagnosticCode.ContributionOperationNotPermitted, `The field '${field}' of '${grant.path}' is not exposed for editing.`, itemContext));
73
+ continue;
74
+ }
75
+
76
+ const problem = validateItemValues(grant.descriptor.item, { [field]: value }).find(candidate => candidate.field === field);
77
+ if (problem) {
78
+ diagnostics.push(errorDiagnostic(
79
+ problem.problem.startsWith('is not a field') ? DiagnosticCode.UnknownCollectionField : DiagnosticCode.ContributionTypeMismatch,
80
+ `The field '${field}' of an item in '${grant.path}' is ignored: ${problem.problem}.`,
81
+ itemContext));
82
+ continue;
83
+ }
84
+
85
+ values[field] = value;
86
+ }
87
+
88
+ items.push({ id: item.id, values, origin: instance, fixed: false });
89
+ contributed = true;
90
+ }
91
+ }
92
+
93
+ return contributed;
94
+ }
95
+
96
+ /**
97
+ * Resolves what every configurable component in a template chain is worth: the owner's own values, with each
98
+ * instance's contributions applied in nesting order.
99
+ *
100
+ * This is the one merge. The editor shows its result and the runtime renders from it, so what an author sees is
101
+ * what Play renders. Order is fixed: the owner's value first, then each level from the outermost inwards, so a
102
+ * later level wins a scalar and appends to a collection after the earlier ones.
103
+ *
104
+ * Nothing in the persisted data is changed. A contribution that cannot apply - the exposure was withdrawn, the
105
+ * property's type changed, the component is gone - is reported in `diagnostics` and left out of the result, and
106
+ * stays in `instanceContributions`, so restoring the exposure brings its value back.
107
+ *
108
+ * @param templateChain The nesting, from {@link resolveTemplateChain} or built by hand.
109
+ * @param instanceContributions What each instance has set.
110
+ * @param catalog The descriptors saying what each component supports.
111
+ */
112
+ export function resolveEffectiveConfiguration(
113
+ templateChain: TemplateChain,
114
+ instanceContributions: InstanceContribution[],
115
+ catalog: DescriptorCatalog,
116
+ ): EffectiveConfiguration {
117
+ const grants = computeExposureGrants(templateChain, catalog);
118
+ const diagnostics: SceneDiagnostic[] = [...templateChain.diagnostics, ...grants.diagnostics];
119
+ const handled = new Set<InstanceContribution>();
120
+ const components = new Map<string, EffectiveComponentConfiguration>();
121
+
122
+ for (const grant of grants.exposed) {
123
+ const owner = templateChain.levels[grant.ownerLevel];
124
+ const element = owner.elements.find(candidate => candidate.id === grant.component)!;
125
+
126
+ let configuration = components.get(grant.component);
127
+ if (!configuration) {
128
+ configuration = { component: grant.component, owner: owner.owner, properties: cloneData(element.properties), values: [] };
129
+ components.set(grant.component, configuration);
130
+ }
131
+
132
+ const stored = getValueAtPath(element.properties, grant.path);
133
+ const base = stored === undefined ? cloneData(grant.descriptor.default) : cloneData(stored);
134
+ const isCollection = grant.descriptor.valueType === PropertyValueType.Collection;
135
+ const baseItems = isCollection && Array.isArray(base) ? base.map(toEffectiveItem) : [];
136
+ const items = [...baseItems];
137
+
138
+ const current: EffectivePropertyValue = {
139
+ path: grant.path,
140
+ value: base,
141
+ source: stored === undefined ? ValueSource.Default : ValueSource.Local,
142
+ inheritedValue: base,
143
+ };
144
+ let contributed = false;
145
+
146
+ for (let index = grant.ownerLevel + 1; index < templateChain.levels.length; index++) {
147
+ const instance = templateChain.levels[index].instance;
148
+ if (!grants.byInstance.get(instance)?.has(grantKey(grant.component, grant.path))) continue;
149
+
150
+ const effectiveGrant = grants.byInstance.get(instance)!.get(grantKey(grant.component, grant.path))!;
151
+ const matching = instanceContributions.filter(contribution =>
152
+ contribution.instance === instance && contribution.component === grant.component && contribution.path === grant.path);
153
+ for (const contribution of matching) handled.add(contribution);
154
+
155
+ if (isCollection) {
156
+ if (collectionValue(effectiveGrant, matching, instance, items, diagnostics)) {
157
+ contributed = true;
158
+ current.contributedBy = instance;
159
+ }
160
+ } else {
161
+ scalarValue(effectiveGrant, matching, instance, current, diagnostics);
162
+ }
163
+ }
164
+
165
+ if (isCollection) {
166
+ current.items = items;
167
+ current.inheritedValue = baseItems.map(toFlatItem);
168
+ current.value = items.map(toFlatItem);
169
+ if (contributed) current.source = ValueSource.Instance;
170
+ }
171
+
172
+ if (current.value !== undefined) setValueAtPath(configuration.properties, grant.path, current.value);
173
+ configuration.values.push(current);
174
+ }
175
+
176
+ const levelOf = new Map(templateChain.levels.map((chainLevel, index) => [chainLevel.instance, index]));
177
+ for (const contribution of instanceContributions) {
178
+ const contributorLevel = levelOf.get(contribution.instance);
179
+ if (contributorLevel === undefined || handled.has(contribution)) continue;
180
+
181
+ const context = { instance: contribution.instance, component: contribution.component, path: contribution.path };
182
+ const owner = templateChain.levels.slice(0, contributorLevel).find(chainLevel => chainLevel.elements.some(element => element.id === contribution.component));
183
+ if (!owner) {
184
+ diagnostics.push(errorDiagnostic(
185
+ DiagnosticCode.ContributionTargetMissing,
186
+ `'${contribution.instance}' has saved values for '${contribution.component}', which is not in any layout or template above it. They are kept.`,
187
+ context));
188
+ } else if (!findComponentDescriptor(catalog, owner.elements.find(element => element.id === contribution.component)!.componentName)) {
189
+ diagnostics.push(errorDiagnostic(
190
+ DiagnosticCode.MissingComponentDescriptor,
191
+ `'${contribution.instance}' has saved values for '${contribution.component}', but nothing describes that component any more. They are kept.`,
192
+ context));
193
+ } else {
194
+ diagnostics.push(errorDiagnostic(
195
+ DiagnosticCode.ContributionNotExposed,
196
+ `'${contribution.instance}' has a saved value for '${contribution.path}' on '${contribution.component}', which is not exposed to it. It is ignored and kept.`,
197
+ context));
198
+ }
199
+ }
200
+
201
+ return { components: [...components.values()], diagnostics };
202
+ }
@@ -0,0 +1,109 @@
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
+ DiagnosticCode, DialogTemplate, EditingScope, EditingScopeKind, ExposedProperty, Layout, SceneDocument,
6
+ SceneNodeKind, Screen, ScreenTemplate,
7
+ } from '@cratis/scene.model';
8
+ import { resolveScreenTemplates } from '../resolveScreenTemplates';
9
+ import { collectComponents } from './collectComponents';
10
+ import { errorDiagnostic, warningDiagnostic } from './diagnostics';
11
+ import { TemplateChain, TemplateChainLevel } from './TemplateChain';
12
+
13
+ function exposuresOf(document: SceneDocument, owner: string): ExposedProperty[] {
14
+ return document.exposures.find(declaration => declaration.owner === owner)?.properties ?? [];
15
+ }
16
+
17
+ function level(document: SceneDocument, kind: SceneNodeKind, prefix: string, owner: Layout | ScreenTemplate | DialogTemplate | Screen): TemplateChainLevel {
18
+ return {
19
+ owner: owner.name,
20
+ kind,
21
+ instance: `${prefix}:${owner.name}`,
22
+ elements: collectComponents(owner),
23
+ exposures: exposuresOf(document, owner.name),
24
+ };
25
+ }
26
+
27
+ function templateLevels(document: SceneDocument, layout: Layout, template: ScreenTemplate, chain: TemplateChain): TemplateChainLevel[] {
28
+ const placements = resolveScreenTemplates(layout, document.screenTemplates).placements;
29
+ const ancestry: ScreenTemplate[] = [template];
30
+
31
+ let current = template.name;
32
+ for (let guard = 0; guard <= document.screenTemplates.length; guard++) {
33
+ const placement = placements.find(candidate => candidate.template === current);
34
+ if (!placement || placement.container === layout.name) break;
35
+
36
+ const parent = document.screenTemplates.find(candidate => candidate.name === placement.container);
37
+ if (!parent) break;
38
+ ancestry.unshift(parent);
39
+ current = parent.name;
40
+ }
41
+
42
+ if (!placements.some(placement => placement.template === ancestry[0].name)) {
43
+ chain.diagnostics.push(warningDiagnostic(
44
+ DiagnosticCode.UnknownScope,
45
+ `The template '${ancestry[0].name}' could not be placed in the layout '${layout.name}', so nothing outside it is part of the chain.`));
46
+ return ancestry.slice(-1).map(item => level(document, SceneNodeKind.ScreenTemplate, 'template', item));
47
+ }
48
+
49
+ return ancestry.map(item => level(document, SceneNodeKind.ScreenTemplate, 'template', item));
50
+ }
51
+
52
+ /**
53
+ * Assembles the chain of owners an editing scope sits inside: the layout, each template down to the one the
54
+ * scope is in, and the scope itself.
55
+ *
56
+ * The same chain feeds {@link resolveEffectiveConfiguration} for the runtime and the editor, so both see the same
57
+ * nesting. A scope that cannot be found yields an empty chain with an error diagnostic.
58
+ *
59
+ * @param document The document to read.
60
+ * @param scope What is being edited.
61
+ * @returns The chain, outermost first.
62
+ */
63
+ export function resolveTemplateChain(document: SceneDocument, scope: EditingScope): TemplateChain {
64
+ const chain: TemplateChain = { levels: [], diagnostics: [] };
65
+
66
+ switch (scope.kind) {
67
+ case EditingScopeKind.Layout: {
68
+ const layout = document.layouts.find(candidate => candidate.name === scope.name);
69
+ if (layout) chain.levels.push(level(document, SceneNodeKind.Layout, 'layout', layout));
70
+ break;
71
+ }
72
+
73
+ case EditingScopeKind.DialogTemplate: {
74
+ const dialog = document.dialogTemplates.find(candidate => candidate.name === scope.name);
75
+ if (dialog) chain.levels.push(level(document, SceneNodeKind.DialogTemplate, 'dialog', dialog));
76
+ break;
77
+ }
78
+
79
+ case EditingScopeKind.ScreenTemplate: {
80
+ const template = document.screenTemplates.find(candidate => candidate.name === scope.name);
81
+ const layout = document.layouts.find(candidate => candidate.name === scope.layout) ?? (scope.layout === undefined && document.layouts.length === 1 ? document.layouts[0] : undefined);
82
+ if (template && layout) {
83
+ chain.levels.push(level(document, SceneNodeKind.Layout, 'layout', layout), ...templateLevels(document, layout, template, chain));
84
+ } else if (template) {
85
+ chain.levels.push(level(document, SceneNodeKind.ScreenTemplate, 'template', template));
86
+ }
87
+
88
+ break;
89
+ }
90
+
91
+ default: {
92
+ const screen = document.screens.find(candidate => candidate.name === scope.name);
93
+ if (!screen) break;
94
+
95
+ const layout = document.layouts.find(candidate => candidate.name === screen.layout);
96
+ const template = screen.screenTemplate === undefined ? undefined : document.screenTemplates.find(candidate => candidate.name === screen.screenTemplate);
97
+ if (layout) chain.levels.push(level(document, SceneNodeKind.Layout, 'layout', layout));
98
+ if (layout && template) chain.levels.push(...templateLevels(document, layout, template, chain));
99
+ else if (template) chain.levels.push(level(document, SceneNodeKind.ScreenTemplate, 'template', template));
100
+ chain.levels.push(level(document, SceneNodeKind.Screen, 'screen', screen));
101
+ }
102
+ }
103
+
104
+ if (chain.levels.length === 0) {
105
+ chain.diagnostics.push(errorDiagnostic(DiagnosticCode.UnknownScope, `There is no ${scope.kind} named '${scope.name}'.`));
106
+ }
107
+
108
+ return chain;
109
+ }
@@ -0,0 +1,234 @@
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
+ DiagnosticCode, FlowNode, InsertNodeEdit, MoveNodeEdit, RemoveNodeEdit, SceneDiagnostic, SceneDocument, SceneElement,
6
+ SceneNodeKind,
7
+ } from '@cratis/scene.model';
8
+ import { isExternalComponent, isPanel } from '../elementKind';
9
+ import { errorDiagnostic } from './diagnostics';
10
+ import { DocumentPath, NodeRecord } from './DocumentIndex';
11
+ import { elementNodeId } from './nodeIds';
12
+ import { ensureArrayAt, getAtPath, mirroredPaths } from './documentPaths';
13
+ import { EditSession } from './EditSession';
14
+ import { findLayoutTypeDescriptor } from './DescriptorCatalog';
15
+ import { cloneData } from './pathAccess';
16
+
17
+ /**
18
+ * Where an insert or move puts its node: the arrays (one per freeform size-class copy) and what they hold.
19
+ */
20
+ interface InsertionPoint {
21
+ record: NodeRecord;
22
+ paths: DocumentPath[];
23
+
24
+ /** Whether the arrays hold flow nodes (and wrap elements in a leaf) or elements directly. */
25
+ flow: boolean;
26
+ }
27
+
28
+ function elementIdsIn(value: unknown, found: string[] = []): string[] {
29
+ if (value === null || typeof value !== 'object') return found;
30
+ if (Array.isArray(value)) {
31
+ for (const item of value) elementIdsIn(item, found);
32
+ return found;
33
+ }
34
+
35
+ const candidate = value as { id?: unknown; properties?: unknown };
36
+ if (typeof candidate.id === 'string' && candidate.properties !== null && typeof candidate.properties === 'object') found.push(candidate.id);
37
+ for (const child of Object.values(value)) elementIdsIn(child, found);
38
+ return found;
39
+ }
40
+
41
+ function resolveInsertionPoint(session: EditSession, targetId: string, slot: string | undefined, diagnostics: SceneDiagnostic[]): InsertionPoint | undefined {
42
+ const record = session.index.nodes.get(targetId);
43
+ const refuse = (code: DiagnosticCode, message: string) => {
44
+ diagnostics.push(errorDiagnostic(code, message, { nodeId: targetId }));
45
+ return undefined;
46
+ };
47
+
48
+ if (!record) return refuse(DiagnosticCode.UnknownNode, `There is no node '${targetId}'.`);
49
+ if (!session.isLocal(record)) return refuse(DiagnosticCode.NodeNotEditable, `'${targetId}' is inherited from '${session.sourceOf(record)}' and cannot be changed here.`);
50
+
51
+ const paths = (path: DocumentPath) => mirroredPaths(record.mirror, path);
52
+ switch (record.kind) {
53
+ case SceneNodeKind.Slot:
54
+ return record.contentPath
55
+ ? { record, paths: paths(record.contentPath), flow: false }
56
+ : refuse(DiagnosticCode.TargetDoesNotAcceptChildren, `'${record.label}' is a slot of a layout; a layout declares slots and screens fill them.`);
57
+
58
+ case SceneNodeKind.FlowNode:
59
+ return record.typeId === 'flowLeaf' || record.typeId === 'flowSlotLeaf'
60
+ ? refuse(DiagnosticCode.TargetDoesNotAcceptChildren, 'A flow leaf holds one element and takes no children; insert into its container.')
61
+ : { record, paths: paths([...record.path, 'children']), flow: true };
62
+
63
+ case SceneNodeKind.Element: {
64
+ const element = record.value as SceneElement;
65
+ if (isExternalComponent(element)) {
66
+ return slot
67
+ ? { record, paths: paths([...record.path, 'slots', slot]), flow: false }
68
+ : refuse(DiagnosticCode.UnknownSlot, `Say which slot of '${element.componentName}' to insert into.`);
69
+ }
70
+
71
+ return isPanel(element)
72
+ ? { record, paths: paths([...record.path, 'children']), flow: false }
73
+ : refuse(DiagnosticCode.TargetDoesNotAcceptChildren, `'${record.label}' holds a single element and takes no children.`);
74
+ }
75
+
76
+ default:
77
+ return refuse(DiagnosticCode.TargetDoesNotAcceptChildren, `'${record.label}' does not take children. Freeform arrangements place elements through their placements.`);
78
+ }
79
+ }
80
+
81
+ function checkCapacity(session: EditSession, point: InsertionPoint, current: number, diagnostics: SceneDiagnostic[]): boolean {
82
+ const maximum = findLayoutTypeDescriptor(session.context.catalog, point.record.typeId)?.capabilities.maximumChildren;
83
+ if (maximum !== undefined && current >= maximum) {
84
+ diagnostics.push(errorDiagnostic(DiagnosticCode.MaximumChildrenExceeded, `'${point.record.label}' holds at most ${maximum} children.`, { nodeId: point.record.id }));
85
+ return false;
86
+ }
87
+
88
+ return true;
89
+ }
90
+
91
+ function insertInto(session: EditSession, clone: SceneDocument, point: InsertionPoint, payload: SceneElement | FlowNode, isFlowNode: boolean, index: number | undefined, diagnostics: SceneDiagnostic[]): void {
92
+ if (isFlowNode && !point.flow) {
93
+ diagnostics.push(errorDiagnostic(DiagnosticCode.InvalidEdit, 'A flow node can only go into a flow container.', { nodeId: point.record.id }));
94
+ return;
95
+ }
96
+
97
+ const arrays = point.paths.map(path => ensureArrayAt(clone, path));
98
+ const length = arrays[0].length;
99
+ if (index !== undefined && (!Number.isInteger(index) || index < 0 || index > length)) {
100
+ diagnostics.push(errorDiagnostic(DiagnosticCode.IndexOutOfRange, `There is no position ${index} among ${length} children.`, { nodeId: point.record.id }));
101
+ return;
102
+ }
103
+
104
+ if (!checkCapacity(session, point, length, diagnostics)) return;
105
+
106
+ for (const array of arrays) {
107
+ const node = point.flow && !isFlowNode ? { content: cloneData(payload) } : cloneData(payload);
108
+ array.splice(index ?? array.length, 0, node);
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Applies an insert to a copy of the document.
114
+ */
115
+ export function applyInsertNode(session: EditSession, edit: InsertNodeEdit, clone: SceneDocument, diagnostics: SceneDiagnostic[]): void {
116
+ if ((edit.element === undefined) === (edit.flowNode === undefined)) {
117
+ diagnostics.push(errorDiagnostic(DiagnosticCode.InvalidEdit, 'Give exactly one of element and flowNode.', { nodeId: edit.target }));
118
+ return;
119
+ }
120
+
121
+ const point = resolveInsertionPoint(session, edit.target, edit.slot, diagnostics);
122
+ if (!point) return;
123
+
124
+ const identities = elementIdsIn(edit.element ?? edit.flowNode);
125
+ const duplicate = identities.find((identity, position) => identities.indexOf(identity) !== position || session.index.nodes.has(elementNodeId(identity)));
126
+ if (duplicate !== undefined) {
127
+ diagnostics.push(errorDiagnostic(DiagnosticCode.ElementIdInUse, `The element id '${duplicate}' is already used.`, { nodeId: edit.target }));
128
+ return;
129
+ }
130
+
131
+ insertInto(session, clone, point, (edit.element ?? edit.flowNode)!, edit.flowNode !== undefined, edit.index, diagnostics);
132
+ }
133
+
134
+ interface Removal {
135
+ /** The list record whose array holds the node being taken out (a flow leaf for an element it wraps). */
136
+ holder: NodeRecord;
137
+ payload: SceneElement | FlowNode;
138
+ isFlowNode: boolean;
139
+ }
140
+
141
+ function resolveRemoval(session: EditSession, nodeId: string, diagnostics: SceneDiagnostic[]): Removal | undefined {
142
+ const record = session.index.nodes.get(nodeId);
143
+ if (!record) {
144
+ diagnostics.push(errorDiagnostic(DiagnosticCode.UnknownNode, `There is no node '${nodeId}'.`, { nodeId }));
145
+ return undefined;
146
+ }
147
+
148
+ if (!session.isLocal(record)) {
149
+ diagnostics.push(errorDiagnostic(DiagnosticCode.NodeNotEditable, `'${nodeId}' is inherited from '${session.sourceOf(record)}' and cannot be changed here.`, { nodeId }));
150
+ return undefined;
151
+ }
152
+
153
+ const holder = record.leafId !== undefined ? session.index.nodes.get(record.leafId)! : record;
154
+ const isElement = record.kind === SceneNodeKind.Element;
155
+ const removable = holder.list !== undefined || (isElement && record.placementIds !== undefined);
156
+ if (!removable || (record.kind !== SceneNodeKind.Element && record.kind !== SceneNodeKind.FlowNode)) {
157
+ diagnostics.push(errorDiagnostic(DiagnosticCode.NodeNotRemovable, `'${record.label}' cannot be moved or removed on its own.`, { nodeId }));
158
+ return undefined;
159
+ }
160
+
161
+ return { holder: isElement && record.placementIds !== undefined ? record : holder, payload: record.value as SceneElement | FlowNode, isFlowNode: !isElement };
162
+ }
163
+
164
+ function takeOut(session: EditSession, clone: SceneDocument, removal: Removal): void {
165
+ const { holder } = removal;
166
+ if (holder.placementIds !== undefined && holder.kind === SceneNodeKind.Element) {
167
+ for (const placementId of holder.placementIds) {
168
+ const placement = session.index.nodes.get(placementId)!;
169
+ const array = getAtPath(clone, placement.list!.path) as unknown[];
170
+ array.splice(placement.list!.index, 1);
171
+ }
172
+
173
+ return;
174
+ }
175
+
176
+ for (const path of mirroredPaths(holder.mirror, holder.list!.path)) {
177
+ const index = holder.list!.index;
178
+ (getAtPath(clone, path) as unknown[]).splice(index, 1);
179
+ }
180
+ }
181
+
182
+ /**
183
+ * Applies a removal to a copy of the document. Removing an element a flow leaf holds removes the leaf too, and
184
+ * removing a freeform element removes its placement in every size-class variant.
185
+ */
186
+ export function applyRemoveNode(session: EditSession, edit: RemoveNodeEdit, clone: SceneDocument, diagnostics: SceneDiagnostic[]): void {
187
+ const removal = resolveRemoval(session, edit.nodeId, diagnostics);
188
+ if (removal) takeOut(session, clone, removal);
189
+ }
190
+
191
+ /**
192
+ * Applies a move - a reorder or a reparent - to a copy of the document.
193
+ *
194
+ * `index` is the position the node ends up at among its new siblings. The destination arrays are located before
195
+ * anything is taken out, so removing a sibling that shifts the destination's own address does not misdirect the
196
+ * insert.
197
+ */
198
+ export function applyMoveNode(session: EditSession, edit: MoveNodeEdit, clone: SceneDocument, diagnostics: SceneDiagnostic[]): void {
199
+ const removal = resolveRemoval(session, edit.nodeId, diagnostics);
200
+ if (!removal) return;
201
+
202
+ const point = resolveInsertionPoint(session, edit.target, edit.slot, diagnostics);
203
+ if (!point) return;
204
+
205
+ const moved = session.index.nodes.get(edit.nodeId)!;
206
+ for (let ancestor: NodeRecord | undefined = point.record; ancestor; ancestor = ancestor.parentId === undefined ? undefined : session.index.nodes.get(ancestor.parentId)) {
207
+ if (ancestor.id === moved.id) {
208
+ diagnostics.push(errorDiagnostic(DiagnosticCode.ContainmentCycle, `'${moved.label}' cannot be moved into itself or something inside it.`, { nodeId: edit.nodeId }));
209
+ return;
210
+ }
211
+ }
212
+
213
+ const payload = cloneData(removal.payload);
214
+ const arrays = point.paths.map(path => ensureArrayAt(clone, path));
215
+ takeOut(session, clone, removal);
216
+
217
+ if (removal.isFlowNode && !point.flow) {
218
+ diagnostics.push(errorDiagnostic(DiagnosticCode.InvalidEdit, 'A flow node can only go into a flow container.', { nodeId: edit.nodeId }));
219
+ return;
220
+ }
221
+
222
+ const length = arrays[0].length;
223
+ if (edit.index !== undefined && (!Number.isInteger(edit.index) || edit.index < 0 || edit.index > length)) {
224
+ diagnostics.push(errorDiagnostic(DiagnosticCode.IndexOutOfRange, `There is no position ${edit.index} among ${length} children.`, { nodeId: edit.target }));
225
+ return;
226
+ }
227
+
228
+ if (!checkCapacity(session, point, length, diagnostics)) return;
229
+
230
+ for (const array of arrays) {
231
+ const node = point.flow && !removal.isFlowNode ? { content: cloneData(payload) } : cloneData(payload);
232
+ array.splice(edit.index ?? array.length, 0, node);
233
+ }
234
+ }
@@ -0,0 +1,103 @@
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
+ DiagnosticCode, PropertyDescriptor, QueryBinding, QueryCandidate, SceneDiagnostic,
6
+ } from '@cratis/scene.model';
7
+ import { errorDiagnostic } from './diagnostics';
8
+
9
+ /**
10
+ * The types of the values a binding can draw from, keyed by binding path. Supplied by the host, which knows what
11
+ * is in scope where the query is used.
12
+ */
13
+ export interface BindingTypeScope {
14
+ types: Record<string, string>;
15
+ }
16
+
17
+ const integerTypes = new Set(['int', 'integer', 'int16', 'int32', 'int64', 'short', 'long', 'byte', 'uint', 'uint16', 'uint32', 'uint64']);
18
+ const fractionalTypes = new Set(['number', 'float', 'double', 'decimal', 'single']);
19
+
20
+ function normalizeType(type: string): string {
21
+ return type.trim().toLowerCase().replace(/\?$/, '');
22
+ }
23
+
24
+ /**
25
+ * Whether a value of one type can feed a parameter or field of another.
26
+ *
27
+ * Types compare by name. A whole number may feed a fractional one, never the reverse, and a type called `any` or
28
+ * `unknown` on either side matches anything.
29
+ */
30
+ export function areTypesCompatible(sourceType: string, targetType: string): boolean {
31
+ const source = normalizeType(sourceType);
32
+ const target = normalizeType(targetType);
33
+ if (source === target || source === 'any' || target === 'any' || source === 'unknown' || target === 'unknown') return true;
34
+ if (integerTypes.has(source)) return integerTypes.has(target) || fractionalTypes.has(target);
35
+ return fractionalTypes.has(source) && fractionalTypes.has(target);
36
+ }
37
+
38
+ /**
39
+ * Validates a query binding against the query it names and the property it is for.
40
+ *
41
+ * Reports a query that is not among the candidates, a result shape the property cannot bind to, a required
42
+ * parameter nothing supplies, a binding for a parameter the query does not have, an argument whose type does not
43
+ * fit its parameter, and a result binding that names a field the query does not return.
44
+ *
45
+ * @param descriptor The `QueryReference` property the binding is the value of.
46
+ * @param candidate The candidate the binding names, or `undefined` when the host found none.
47
+ * @param binding The binding to check.
48
+ * @param scope The types of the values arguments draw from. Without it argument types are not checked.
49
+ * @returns The problems found; empty when the binding is sound.
50
+ */
51
+ export function validateBinding(
52
+ descriptor: PropertyDescriptor,
53
+ candidate: QueryCandidate | undefined,
54
+ binding: QueryBinding,
55
+ scope?: BindingTypeScope,
56
+ ): SceneDiagnostic[] {
57
+ const path = descriptor.path;
58
+ if (!candidate || candidate.id !== binding.queryId) {
59
+ return [errorDiagnostic(DiagnosticCode.MissingQuery, `The query '${binding.query}' (${binding.queryId}) is not available here.`, { path })];
60
+ }
61
+
62
+ const diagnostics: SceneDiagnostic[] = [];
63
+ const allowedShapes = descriptor.constraints?.resultShapes;
64
+ if (allowedShapes && !allowedShapes.includes(candidate.resultShape)) {
65
+ diagnostics.push(errorDiagnostic(
66
+ DiagnosticCode.IncompatibleResultShape,
67
+ `'${descriptor.label}' binds to ${allowedShapes.join(' or ')} results, but '${candidate.name}' returns ${candidate.resultShape}.`,
68
+ { path }));
69
+ }
70
+
71
+ for (const argument of binding.arguments) {
72
+ const parameter = candidate.parameters.find(candidateParameter => candidateParameter.name === argument.parameter);
73
+ if (!parameter) {
74
+ diagnostics.push(errorDiagnostic(DiagnosticCode.UnknownParameter, `'${candidate.name}' has no parameter '${argument.parameter}'.`, { path }));
75
+ continue;
76
+ }
77
+
78
+ if (!scope) continue;
79
+ const sourceType = scope.types[argument.source.path];
80
+ if (sourceType === undefined) {
81
+ diagnostics.push(errorDiagnostic(DiagnosticCode.UnresolvedArgumentSource, `'${argument.source.path}' is not available to supply '${parameter.name}'.`, { path }));
82
+ } else if (!areTypesCompatible(sourceType, parameter.type)) {
83
+ diagnostics.push(errorDiagnostic(
84
+ DiagnosticCode.ArgumentTypeMismatch,
85
+ `'${argument.source.path}' is ${sourceType} but '${parameter.name}' expects ${parameter.type}.`,
86
+ { path }));
87
+ }
88
+ }
89
+
90
+ for (const parameter of candidate.parameters.filter(candidateParameter => candidateParameter.required)) {
91
+ if (!binding.arguments.some(argument => argument.parameter === parameter.name)) {
92
+ diagnostics.push(errorDiagnostic(DiagnosticCode.MissingRequiredParameter, `'${candidate.name}' needs a value for '${parameter.name}'.`, { path }));
93
+ }
94
+ }
95
+
96
+ for (const result of binding.results) {
97
+ if (!candidate.resultFields.some(field => field.name === result.field)) {
98
+ diagnostics.push(errorDiagnostic(DiagnosticCode.UnknownResultField, `'${candidate.name}' does not return a field '${result.field}'.`, { path }));
99
+ }
100
+ }
101
+
102
+ return diagnostics;
103
+ }