@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,237 @@
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, ExternalComponent, FlowArrangement, FlowContainer, FlowLeaf, FlowNode,
6
+ FreeformArrangement, FreeformSlotArrangement, Layout, LayoutType, SceneDocument, SceneElement, SceneNodeKind, Screen,
7
+ ScreenTemplate, Slot, FlowContainerKind,
8
+ } from '@cratis/scene.model';
9
+ import { isContentControl, isExternalComponent, isItemsControl, isPanel } from '../elementKind';
10
+ import { isFlowContainer, isFlowLeaf, isFlowSlotLeaf } from '../flowNodeKind';
11
+ import { isCanvas, isDockPanel, isGrid, isStackPanel, isWrapPanel } from '../panelKind';
12
+ import { errorDiagnostic } from './diagnostics';
13
+ import { DocumentIndex, DocumentPath, ListLocation, NodeRecord } from './DocumentIndex';
14
+ import {
15
+ dialogTemplateNodeId, elementNodeId, flowNodeId, freeformNodeId, layoutNodeId, ownerArrangementId, placementNodeId,
16
+ screenNodeId, screenTemplateNodeId, slotNodeId,
17
+ } from './nodeIds';
18
+
19
+ type Owner = Layout | ScreenTemplate | DialogTemplate | Screen;
20
+
21
+ interface OwnerContext {
22
+ owner: string;
23
+ ownerKind: SceneNodeKind;
24
+ ownerId: string;
25
+ }
26
+
27
+ interface Placement {
28
+ parentId?: string;
29
+ list?: ListLocation;
30
+ leafId?: string;
31
+ mirror?: NodeRecord['mirror'];
32
+ }
33
+
34
+ /**
35
+ * The layout type a panel element is, by its shape.
36
+ */
37
+ export function panelLayoutType(element: SceneElement): LayoutType | undefined {
38
+ if (!isPanel(element)) return undefined;
39
+ if (isGrid(element)) return LayoutType.GridPanel;
40
+ if (isCanvas(element)) return LayoutType.Canvas;
41
+ if (isDockPanel(element)) return LayoutType.DockPanel;
42
+ if (isStackPanel(element)) return LayoutType.StackPanel;
43
+ if (isWrapPanel(element)) return LayoutType.WrapPanel;
44
+ return undefined;
45
+ }
46
+
47
+ /**
48
+ * The layout type a flow node is.
49
+ */
50
+ export function flowLayoutType(node: FlowNode): LayoutType {
51
+ if (isFlowContainer(node)) {
52
+ switch (node.kind) {
53
+ case FlowContainerKind.Row: return LayoutType.FlowRow;
54
+ case FlowContainerKind.Column: return LayoutType.FlowColumn;
55
+ default: return LayoutType.FlowGrid;
56
+ }
57
+ }
58
+
59
+ return isFlowSlotLeaf(node) ? LayoutType.FlowSlotLeaf : LayoutType.FlowLeaf;
60
+ }
61
+
62
+ class Indexer {
63
+ readonly index: DocumentIndex = { nodes: new Map(), diagnostics: [] };
64
+
65
+ constructor(private readonly document: SceneDocument) {}
66
+
67
+ run(): DocumentIndex {
68
+ this.document.layouts.forEach((layout, position) =>
69
+ this.owner(layout, ['layouts', position], { owner: layout.name, ownerKind: SceneNodeKind.Layout, ownerId: layoutNodeId(layout.name) }, undefined));
70
+ this.document.screenTemplates.forEach((template, position) =>
71
+ this.owner(template, ['screenTemplates', position], { owner: template.name, ownerKind: SceneNodeKind.ScreenTemplate, ownerId: screenTemplateNodeId(template.name) }, 'content'));
72
+ this.document.dialogTemplates.forEach((dialog, position) =>
73
+ this.owner(dialog, ['dialogTemplates', position], { owner: dialog.name, ownerKind: SceneNodeKind.DialogTemplate, ownerId: dialogTemplateNodeId(dialog.name) }, 'content'));
74
+ this.document.screens.forEach((screen, position) =>
75
+ this.owner(screen, ['screens', position], { owner: screen.name, ownerKind: SceneNodeKind.Screen, ownerId: screenNodeId(screen.name) }, 'slotContent'));
76
+ return this.index;
77
+ }
78
+
79
+ private add(record: NodeRecord): boolean {
80
+ if (this.index.nodes.has(record.id)) return false;
81
+ this.index.nodes.set(record.id, record);
82
+ return true;
83
+ }
84
+
85
+ private owner(owner: Owner, path: DocumentPath, context: OwnerContext, contentKey: 'content' | 'slotContent' | undefined): void {
86
+ this.add({ id: context.ownerId, kind: context.ownerKind, typeId: context.ownerKind, label: owner.name, path, value: owner, owner: context.owner, ownerKind: context.ownerKind });
87
+
88
+ const content = contentKey === undefined ? undefined : (owner as unknown as Record<string, Record<string, SceneElement[]> | undefined>)[contentKey];
89
+ const ownsSlots = 'slots' in owner;
90
+ const declared: Slot[] = ownsSlots ? owner.slots : this.slotsAvailableTo(owner as Screen);
91
+ const slotNames = new Set<string>([...declared.map(slot => slot.name), ...Object.keys(content ?? {})]);
92
+
93
+ for (const name of slotNames) {
94
+ const slotId = slotNodeId(context.ownerId, name);
95
+ const declaration = ownsSlots ? declared.findIndex(slot => slot.name === name) : -1;
96
+ const contentPath: DocumentPath | undefined = contentKey === undefined ? undefined : [...path, contentKey, name];
97
+ this.add({
98
+ id: slotId, kind: SceneNodeKind.Slot, typeId: 'slot', label: name, path: declaration >= 0 ? [...path, 'slots', declaration] : contentPath!,
99
+ value: declaration >= 0 ? declared[declaration] : content?.[name], parentId: context.ownerId, owner: context.owner,
100
+ ownerKind: context.ownerKind, slotName: name, contentPath,
101
+ });
102
+
103
+ if (declaration >= 0 && declared[declaration].arrangement) {
104
+ this.arrangement(declared[declaration].arrangement!, [...path, 'slots', declaration, 'arrangement'], slotId, context);
105
+ }
106
+
107
+ (content?.[name] ?? []).forEach((element, position) =>
108
+ this.element(element, [...contentPath!, position], context, { parentId: slotId, list: { path: contentPath!, index: position } }));
109
+ }
110
+
111
+ if ('arrangement' in owner && owner.arrangement) {
112
+ this.arrangement(owner.arrangement, [...path, 'arrangement'], ownerArrangementId(context.ownerId), context, context.ownerId);
113
+ }
114
+
115
+ if ('contributions' in owner) {
116
+ owner.contributions.forEach((contribution, position) =>
117
+ this.element(contribution.content, [...path, 'contributions', position, 'content'], context, { parentId: context.ownerId }));
118
+ }
119
+ }
120
+
121
+ private slotsAvailableTo(screen: Screen): Slot[] {
122
+ const template = screen.screenTemplate === undefined ? undefined : this.document.screenTemplates.find(candidate => candidate.name === screen.screenTemplate);
123
+ if (template) return template.slots;
124
+ return this.document.layouts.find(candidate => candidate.name === screen.layout)?.slots ?? [];
125
+ }
126
+
127
+ private element(element: SceneElement, path: DocumentPath, context: OwnerContext, placement: Placement): boolean {
128
+ const id = elementNodeId(element.id);
129
+ const panelType = panelLayoutType(element);
130
+ const typeId = isExternalComponent(element) ? element.componentName
131
+ : (panelType ?? (isContentControl(element) ? 'contentControl' : (isItemsControl(element) ? 'itemsControl' : 'element')));
132
+ const properties = (element as ExternalComponent).properties;
133
+ const label = (typeof properties?.label === 'string' && properties.label)
134
+ || (isPanel(element) && (element as { name?: string }).name)
135
+ || (isExternalComponent(element) && element.name)
136
+ || typeId;
137
+
138
+ if (!this.add({
139
+ id, kind: SceneNodeKind.Element, typeId, label, path, value: element, owner: context.owner, ownerKind: context.ownerKind, ...placement,
140
+ })) {
141
+ this.index.diagnostics.push(errorDiagnostic(DiagnosticCode.DuplicateElementId, `The element id '${element.id}' is used more than once.`, { nodeId: id }));
142
+ return false;
143
+ }
144
+
145
+ if (isExternalComponent(element)) {
146
+ for (const [name, children] of Object.entries(element.slots)) {
147
+ children.forEach((child, position) =>
148
+ this.element(child, [...path, 'slots', name, position], context, { parentId: id, list: { path: [...path, 'slots', name], index: position }, mirror: placement.mirror }));
149
+ }
150
+ } else if (isContentControl(element)) {
151
+ this.element(element.content, [...path, 'content'], context, { parentId: id, mirror: placement.mirror });
152
+ } else if (isItemsControl(element)) {
153
+ this.element(element.itemTemplate, [...path, 'itemTemplate'], context, { parentId: id, mirror: placement.mirror });
154
+ } else if (isPanel(element)) {
155
+ element.children.forEach((child, position) =>
156
+ this.element(child, [...path, 'children', position], context, { parentId: id, list: { path: [...path, 'children'], index: position }, mirror: placement.mirror }));
157
+ }
158
+
159
+ return true;
160
+ }
161
+
162
+ private arrangement(arrangement: unknown, path: DocumentPath, holderId: string, context: OwnerContext, parentId = holderId): void {
163
+ if ((arrangement as FlowArrangement).root !== undefined) {
164
+ const flow = arrangement as FlowArrangement;
165
+ this.flowNode(flow.root, [...path, 'root'], holderId, 'root', [], context, { parentId });
166
+ flow.overrides?.forEach((override, position) =>
167
+ this.flowNode(override.root, [...path, 'overrides', position, 'root'], holderId, `override${position}`, [], context, { parentId }));
168
+ return;
169
+ }
170
+
171
+ const variants = (arrangement as FreeformArrangement | FreeformSlotArrangement).variants;
172
+ if (!Array.isArray(variants)) return;
173
+
174
+ const freeformId = freeformNodeId(holderId);
175
+ this.add({ id: freeformId, kind: SceneNodeKind.Freeform, typeId: LayoutType.Freeform, label: 'Freeform', path, value: arrangement, parentId, owner: context.owner, ownerKind: context.ownerKind });
176
+
177
+ const firstCopy = new Map<string, DocumentPath>();
178
+ variants.forEach((variant, variantIndex) => {
179
+ (variant.placements as { element?: SceneElement; slotName?: string }[]).forEach((placement, placementIndex) => {
180
+ const placementPath = [...path, 'variants', variantIndex, 'placements', placementIndex];
181
+ const key = placement.element?.id ?? placement.slotName ?? String(placementIndex);
182
+ const placementId = placementNodeId(holderId, variantIndex, key);
183
+ this.add({
184
+ id: placementId, kind: SceneNodeKind.Placement, typeId: 'placement', label: key, path: placementPath, value: placement,
185
+ parentId: freeformId, owner: context.owner, ownerKind: context.ownerKind,
186
+ list: { path: [...path, 'variants', variantIndex, 'placements'], index: placementIndex },
187
+ });
188
+
189
+ if (!placement.element) return;
190
+ const elementPath = [...placementPath, 'element'];
191
+ const known = this.index.nodes.get(elementNodeId(placement.element.id));
192
+ const original = firstCopy.get(placement.element.id);
193
+ if (known && original) {
194
+ known.placementIds!.push(placementId);
195
+ known.mirror!.others.push(elementPath);
196
+ return;
197
+ }
198
+
199
+ firstCopy.set(placement.element.id, elementPath);
200
+ const mirror = { prefix: elementPath, others: [] as DocumentPath[] };
201
+ if (this.element(placement.element, elementPath, context, { parentId: placementId, mirror })) {
202
+ this.index.nodes.get(elementNodeId(placement.element.id))!.placementIds = [placementId];
203
+ }
204
+ });
205
+ });
206
+
207
+ // Descendants of a freeform element carry the same mirror object, so a copy found later is seen by all of them.
208
+ }
209
+
210
+ private flowNode(node: FlowNode, path: DocumentPath, holderId: string, variant: string, indexPath: number[], context: OwnerContext, placement: Placement): void {
211
+ const id = flowNodeId(holderId, variant, indexPath);
212
+ const type = flowLayoutType(node);
213
+ this.add({ id, kind: SceneNodeKind.FlowNode, typeId: type, label: type, path, value: node, owner: context.owner, ownerKind: context.ownerKind, ...placement });
214
+
215
+ if (isFlowContainer(node)) {
216
+ (node as FlowContainer).children.forEach((child, position) =>
217
+ this.flowNode(child, [...path, 'children', position], holderId, variant, [...indexPath, position], context,
218
+ { parentId: id, list: { path: [...path, 'children'], index: position } }));
219
+ } else if (isFlowLeaf(node)) {
220
+ this.element((node as FlowLeaf).content, [...path, 'content'], context, { parentId: id, leafId: id });
221
+ }
222
+
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Indexes every addressable node of a document: layouts, templates, dialog templates, screens, their slots, the
228
+ * elements in them, and the flow and freeform arrangements that position them.
229
+ *
230
+ * The index is what inspection and editing look nodes up in. Duplicate element ids are reported, and the second
231
+ * occurrence is left out of the index.
232
+ *
233
+ * @param document The document to index.
234
+ */
235
+ export function indexDocument(document: SceneDocument): DocumentIndex {
236
+ return new Indexer(document).run();
237
+ }
@@ -0,0 +1,142 @@
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, EditTarget, InspectedCollectionItem, InspectedProperty, NodeInspection,
6
+ NodeProvenance, ProvenanceKind, PropertyDescriptor, PropertyValueType, SceneDiagnostic, SceneDocument, SceneNodeKind, ValueSource,
7
+ } from '@cratis/scene.model';
8
+ import { describeNode, readNodeValue } from './describeNode';
9
+ import { EditingContext } from './EditingContext';
10
+ import { computeExposureGrants, ExposureGrant, grantKey } from './exposureGrants';
11
+ import { indexDocument } from './indexDocument';
12
+ import { NodeRecord } from './DocumentIndex';
13
+ import { resolveEffectiveConfiguration } from './resolveEffectiveConfiguration';
14
+ import { resolveTemplateChain } from './resolveTemplateChain';
15
+ import { TemplateChain } from './TemplateChain';
16
+
17
+ /**
18
+ * Works out whether a node is the editing scope's own, and when it is not, where it comes from and whether any of
19
+ * it is configurable here.
20
+ *
21
+ * @param record The node.
22
+ * @param chain The chain the scope sits in.
23
+ * @param scopeGrants What the scope's own instance may configure.
24
+ */
25
+ export function provenanceOf(record: NodeRecord, chain: TemplateChain, scopeGrants: Map<string, ExposureGrant> | undefined): NodeProvenance {
26
+ const scopeLevel = chain.levels.at(-1);
27
+ if (scopeLevel && record.owner === scopeLevel.owner && record.ownerKind === scopeLevel.kind) return { kind: ProvenanceKind.Local };
28
+
29
+ const configurable = record.kind === SceneNodeKind.Element
30
+ && [...(scopeGrants?.values() ?? [])].some(grant => grant.component === (record.value as { id: string }).id);
31
+ return { kind: configurable ? ProvenanceKind.ConfigurableInherited : ProvenanceKind.Inherited, source: record.owner };
32
+ }
33
+
34
+ function isRemovable(record: NodeRecord, provenance: NodeProvenance): boolean {
35
+ return provenance.kind === ProvenanceKind.Local
36
+ && (record.list !== undefined || record.leafId !== undefined || record.placementIds !== undefined);
37
+ }
38
+
39
+ function toInspectedItems(raw: unknown, local: boolean): InspectedCollectionItem[] {
40
+ return (Array.isArray(raw) ? raw : []).map((item: Record<string, unknown>, position) => {
41
+ const { id, ...values } = item;
42
+ return { id: typeof id === 'string' ? id : `owner-${position}`, values, origin: 'owner', editable: local };
43
+ });
44
+ }
45
+
46
+ function inspectProperty(
47
+ descriptor: PropertyDescriptor,
48
+ record: NodeRecord,
49
+ provenance: NodeProvenance,
50
+ scopeInstance: string | undefined,
51
+ grants: Map<string, ExposureGrant> | undefined,
52
+ configuredValues: { path: string; value: unknown; source: string; items?: { id: string; values: Record<string, unknown>; origin: string }[] }[] | undefined,
53
+ ): InspectedProperty {
54
+ const currentValue = readNodeValue(record, descriptor.path);
55
+ const configured = configuredValues?.find(candidate => candidate.path === descriptor.path);
56
+ const local = provenance.kind === ProvenanceKind.Local;
57
+ const isCollection = descriptor.valueType === PropertyValueType.Collection;
58
+
59
+ const inspected: InspectedProperty = {
60
+ descriptor,
61
+ currentValue,
62
+ effectiveValue: configured ? configured.value : (currentValue ?? descriptor.default),
63
+ source: (configured?.source ?? (currentValue === undefined ? ValueSource.Default : ValueSource.Local)) as ValueSource,
64
+ editable: false,
65
+ };
66
+
67
+ const grant = !local && record.kind === SceneNodeKind.Element
68
+ ? grants?.get(grantKey((record.value as { id: string }).id, descriptor.path))
69
+ : undefined;
70
+
71
+ if (local) {
72
+ inspected.editable = descriptor.readOnly !== true;
73
+ if (inspected.editable) inspected.editTarget = EditTarget.Node;
74
+ else inspected.reason = 'This property is read-only.';
75
+ } else if (grant) {
76
+ inspected.editable = true;
77
+ inspected.editTarget = EditTarget.Instance;
78
+ if (isCollection) inspected.operations = grant.operations;
79
+ } else {
80
+ inspected.reason = `Inherited from '${provenance.source}', which does not expose it.`;
81
+ }
82
+
83
+ if (isCollection) {
84
+ const items = configured?.items
85
+ ? configured.items.map(item => ({ ...item, editable: local || (item.origin === scopeInstance && grant !== undefined && grant.operations.length > 0) }))
86
+ : toInspectedItems(currentValue, local);
87
+ inspected.items = items;
88
+ }
89
+
90
+ return inspected;
91
+ }
92
+
93
+ /**
94
+ * Describes one node for an editor: what it is, where it comes from, what can be edited on it - with current and
95
+ * effective values and effective editability - and what can be done with it.
96
+ *
97
+ * A property of a node the scope owns is edited on the node. A property of an inherited node is editable only
98
+ * when its owner exposed it, and then the edit goes to an instance contribution, never into the inherited node.
99
+ * Nothing is mutated.
100
+ *
101
+ * @param document The document to read.
102
+ * @param nodeId The node to describe; see the node id functions.
103
+ * @param context The catalog and the editing scope.
104
+ * @returns The inspection, or `undefined` when the document has no such node.
105
+ */
106
+ export function inspect(document: SceneDocument, nodeId: string, context: EditingContext): NodeInspection | undefined {
107
+ const index = indexDocument(document);
108
+ const record = index.nodes.get(nodeId);
109
+ if (!record) return undefined;
110
+
111
+ const chain = resolveTemplateChain(document, context.scope);
112
+ const grants = computeExposureGrants(chain, context.catalog);
113
+ const scopeInstance = chain.levels.at(-1)?.instance;
114
+ const scopeGrants = scopeInstance === undefined ? undefined : grants.byInstance.get(scopeInstance);
115
+ const configuration = resolveEffectiveConfiguration(chain, document.instanceContributions, context.catalog);
116
+
117
+ const provenance = provenanceOf(record, chain, scopeGrants);
118
+ const description = describeNode(record, context.catalog);
119
+ const componentId = record.kind === SceneNodeKind.Element ? (record.value as { id: string }).id : undefined;
120
+ const configuredValues = configuration.components.find(candidate => candidate.component === componentId)?.values;
121
+
122
+ const diagnostics: SceneDiagnostic[] = [
123
+ ...description.diagnostics,
124
+ ...configuration.diagnostics.filter(diagnostic => diagnostic.component !== undefined && diagnostic.component === componentId),
125
+ ];
126
+ if (chain.levels.length === 0) {
127
+ diagnostics.push(...chain.diagnostics.filter(diagnostic => diagnostic.code === DiagnosticCode.UnknownScope));
128
+ }
129
+
130
+ return {
131
+ nodeId,
132
+ kind: record.kind,
133
+ typeId: record.typeId,
134
+ label: record.label,
135
+ parentId: record.parentId,
136
+ provenance,
137
+ properties: description.properties.map(descriptor => inspectProperty(descriptor, record, provenance, scopeInstance, scopeGrants, configuredValues)),
138
+ capabilities: description.capabilities,
139
+ removable: isRemovable(record, provenance),
140
+ diagnostics,
141
+ };
142
+ }
@@ -0,0 +1,243 @@
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
+ AddCollectionItemEdit, CollectionOperation, ContributedItem, DiagnosticCode, EditCollectionItemEdit, InstanceContribution,
6
+ PropertyValueType, RemoveCollectionItemEdit, ReorderCollectionItemEdit, ResetInstanceValueEdit, SceneDiagnostic,
7
+ SceneDocument, SetInstanceValueEdit,
8
+ } from '@cratis/scene.model';
9
+ import { errorDiagnostic } from './diagnostics';
10
+ import { EditSession } from './EditSession';
11
+ import { ExposureGrant, grantKey } from './exposureGrants';
12
+ import { cloneData } from './pathAccess';
13
+ import { checkValue } from './propertyEdits';
14
+ import { validateItemValues } from './validateValue';
15
+
16
+ interface InstanceEditTarget {
17
+ instance: string;
18
+ component: string;
19
+ path: string;
20
+ }
21
+
22
+ function resolveGrant(session: EditSession, edit: InstanceEditTarget & { instance?: string }, diagnostics: SceneDiagnostic[]): { instance: string; grant: ExposureGrant } | undefined {
23
+ const context = { instance: edit.instance, component: edit.component, path: edit.path };
24
+ const instance = session.scopeInstance;
25
+ if (instance === undefined) {
26
+ diagnostics.push(errorDiagnostic(DiagnosticCode.UnknownScope, `There is no ${session.context.scope.kind} named '${session.context.scope.name}'.`, context));
27
+ return undefined;
28
+ }
29
+
30
+ if (edit.instance !== undefined && edit.instance !== instance) {
31
+ diagnostics.push(errorDiagnostic(DiagnosticCode.UnknownInstance, `Edits are made as '${instance}', not as '${edit.instance}'.`, context));
32
+ return undefined;
33
+ }
34
+
35
+ const grant = session.scopeGrants.get(grantKey(edit.component, edit.path));
36
+ if (!grant) {
37
+ diagnostics.push(errorDiagnostic(
38
+ DiagnosticCode.ContributionNotExposed,
39
+ `'${edit.path}' on '${edit.component}' is not exposed to '${instance}'. An owner exposes it, and each template between re-exposes it.`,
40
+ { ...context, instance }));
41
+ return undefined;
42
+ }
43
+
44
+ return { instance, grant };
45
+ }
46
+
47
+ function findContribution(clone: SceneDocument, instance: string, component: string, path: string): InstanceContribution | undefined {
48
+ return clone.instanceContributions.find(contribution =>
49
+ contribution.instance === instance && contribution.component === component && contribution.path === path);
50
+ }
51
+
52
+ function permitted(grant: ExposureGrant, operation: CollectionOperation, instance: string, diagnostics: SceneDiagnostic[]): boolean {
53
+ if (grant.descriptor.valueType !== PropertyValueType.Collection) {
54
+ diagnostics.push(errorDiagnostic(DiagnosticCode.InvalidEdit, `'${grant.descriptor.label}' is not a collection.`, { instance, component: grant.component, path: grant.path }));
55
+ return false;
56
+ }
57
+
58
+ if (!grant.operations.includes(operation)) {
59
+ diagnostics.push(errorDiagnostic(
60
+ DiagnosticCode.ContributionOperationNotPermitted,
61
+ `'${grant.descriptor.label}' does not allow '${operation}' from '${instance}'.`,
62
+ { instance, component: grant.component, path: grant.path }));
63
+ return false;
64
+ }
65
+
66
+ return true;
67
+ }
68
+
69
+ function checkFields(grant: ExposureGrant, values: Record<string, unknown>, itemId: string, instance: string, diagnostics: SceneDiagnostic[], requireAll: boolean): boolean {
70
+ const context = { instance, component: grant.component, path: grant.path, itemId };
71
+ let valid = true;
72
+
73
+ for (const field of Object.keys(values)) {
74
+ const known = grant.descriptor.item?.properties.some(candidate => candidate.path === field) ?? false;
75
+ if (known && grant.editableFields !== undefined && !grant.editableFields.includes(field)) {
76
+ diagnostics.push(errorDiagnostic(DiagnosticCode.ContributionOperationNotPermitted, `The field '${field}' of '${grant.descriptor.label}' is not exposed for editing.`, context));
77
+ valid = false;
78
+ }
79
+ }
80
+
81
+ const itemDescriptor = grant.descriptor.item;
82
+ const problems = validateItemValues(itemDescriptor, values).filter(problem => requireAll || problem.problem !== 'a value is required');
83
+ for (const problem of problems) {
84
+ diagnostics.push(errorDiagnostic(
85
+ problem.problem.startsWith('is not a field') ? DiagnosticCode.UnknownCollectionField : DiagnosticCode.InvalidValue,
86
+ `'${problem.field}' ${problem.problem}.`,
87
+ context));
88
+ valid = false;
89
+ }
90
+
91
+ return valid;
92
+ }
93
+
94
+ function ownItems(clone: SceneDocument, instance: string, grant: ExposureGrant): { contribution: InstanceContribution | undefined; items: ContributedItem[] } {
95
+ const contribution = findContribution(clone, instance, grant.component, grant.path);
96
+ return { contribution, items: contribution?.items ?? [] };
97
+ }
98
+
99
+ function save(clone: SceneDocument, instance: string, grant: ExposureGrant, existing: InstanceContribution | undefined, items: ContributedItem[]): void {
100
+ if (items.length === 0) {
101
+ if (existing) clone.instanceContributions.splice(clone.instanceContributions.indexOf(existing), 1);
102
+ return;
103
+ }
104
+
105
+ if (existing) existing.items = items;
106
+ else clone.instanceContributions.push({ instance, component: grant.component, path: grant.path, items });
107
+ }
108
+
109
+ /**
110
+ * Applies a scalar value set from the editing scope's instance to a copy of the document.
111
+ */
112
+ export function applySetInstanceValue(session: EditSession, edit: SetInstanceValueEdit, clone: SceneDocument, diagnostics: SceneDiagnostic[]): void {
113
+ const resolved = resolveGrant(session, { ...edit, instance: edit.instance } as InstanceEditTarget, diagnostics);
114
+ if (!resolved) return;
115
+
116
+ if (resolved.grant.descriptor.valueType === PropertyValueType.Collection) {
117
+ diagnostics.push(errorDiagnostic(DiagnosticCode.InvalidEdit, `'${resolved.grant.descriptor.label}' is a collection; add, remove, reorder and edit its items instead.`, { instance: resolved.instance, component: edit.component, path: edit.path }));
118
+ return;
119
+ }
120
+
121
+ if (!checkValue(session, resolved.grant.descriptor, edit.value, { component: edit.component, instance: resolved.instance }, diagnostics)) return;
122
+
123
+ const existing = findContribution(clone, resolved.instance, edit.component, edit.path);
124
+ if (existing) existing.value = cloneData(edit.value);
125
+ else clone.instanceContributions.push({ instance: resolved.instance, component: edit.component, path: edit.path, value: cloneData(edit.value) });
126
+ }
127
+
128
+ /**
129
+ * Applies a reset to a copy of the document: the instance's contribution is removed, so the inherited value applies.
130
+ */
131
+ export function applyResetInstanceValue(session: EditSession, edit: ResetInstanceValueEdit, clone: SceneDocument, diagnostics: SceneDiagnostic[]): void {
132
+ const resolved = resolveGrant(session, edit as InstanceEditTarget, diagnostics);
133
+ if (!resolved) return;
134
+
135
+ const existing = findContribution(clone, resolved.instance, edit.component, edit.path);
136
+ if (existing) clone.instanceContributions.splice(clone.instanceContributions.indexOf(existing), 1);
137
+ }
138
+
139
+ /**
140
+ * Applies an item addition to a copy of the document.
141
+ */
142
+ export function applyAddCollectionItem(session: EditSession, edit: AddCollectionItemEdit, clone: SceneDocument, diagnostics: SceneDiagnostic[]): void {
143
+ const resolved = resolveGrant(session, edit as InstanceEditTarget, diagnostics);
144
+ if (!resolved || !permitted(resolved.grant, CollectionOperation.Add, resolved.instance, diagnostics)) return;
145
+
146
+ const { grant, instance } = resolved;
147
+ const context = { instance, component: edit.component, path: edit.path, itemId: edit.item.id };
148
+ if (typeof edit.item.id !== 'string' || edit.item.id.length === 0) {
149
+ diagnostics.push(errorDiagnostic(DiagnosticCode.InvalidValue, 'An item needs an id.', context));
150
+ return;
151
+ }
152
+
153
+ const effective = session.configuration.components.find(candidate => candidate.component === edit.component)?.values.find(value => value.path === edit.path);
154
+ const existingItems = effective?.items ?? [];
155
+ if (existingItems.some(item => item.id === edit.item.id)) {
156
+ diagnostics.push(errorDiagnostic(DiagnosticCode.DuplicateCollectionItem, `The item id '${edit.item.id}' is already used.`, context));
157
+ return;
158
+ }
159
+
160
+ const maximum = grant.descriptor.constraints?.maximumItems;
161
+ if (maximum !== undefined && existingItems.length >= maximum) {
162
+ diagnostics.push(errorDiagnostic(DiagnosticCode.InvalidValue, `'${grant.descriptor.label}' allows at most ${maximum} items.`, context));
163
+ return;
164
+ }
165
+
166
+ if (!checkFields(grant, edit.item.values, edit.item.id, instance, diagnostics, true)) return;
167
+
168
+ const { contribution, items } = ownItems(clone, instance, grant);
169
+ if (edit.index !== undefined && (!Number.isInteger(edit.index) || edit.index < 0 || edit.index > items.length)) {
170
+ diagnostics.push(errorDiagnostic(DiagnosticCode.IndexOutOfRange, `There is no position ${edit.index} among ${items.length} items.`, context));
171
+ return;
172
+ }
173
+
174
+ const next = [...items];
175
+ next.splice(edit.index ?? next.length, 0, cloneData(edit.item));
176
+ save(clone, instance, grant, contribution, next);
177
+ }
178
+
179
+ function findOwnItem(items: ContributedItem[], itemId: string, instance: string, grant: ExposureGrant, session: EditSession, diagnostics: SceneDiagnostic[]): number {
180
+ const position = items.findIndex(item => item.id === itemId);
181
+ if (position >= 0) return position;
182
+
183
+ const effective = session.configuration.components.find(candidate => candidate.component === grant.component)?.values.find(value => value.path === grant.path);
184
+ const fixed = effective?.items?.find(item => item.id === itemId);
185
+ diagnostics.push(errorDiagnostic(
186
+ fixed ? DiagnosticCode.ContributionOperationNotPermitted : DiagnosticCode.CollectionItemNotFound,
187
+ fixed ? `The item '${itemId}' belongs to '${fixed.origin}' and cannot be changed from '${instance}'.` : `'${instance}' has no item '${itemId}' in '${grant.descriptor.label}'.`,
188
+ { instance, component: grant.component, path: grant.path, itemId }));
189
+ return -1;
190
+ }
191
+
192
+ /**
193
+ * Applies an item removal to a copy of the document.
194
+ */
195
+ export function applyRemoveCollectionItem(session: EditSession, edit: RemoveCollectionItemEdit, clone: SceneDocument, diagnostics: SceneDiagnostic[]): void {
196
+ const resolved = resolveGrant(session, edit as InstanceEditTarget, diagnostics);
197
+ if (!resolved || !permitted(resolved.grant, CollectionOperation.Remove, resolved.instance, diagnostics)) return;
198
+
199
+ const { contribution, items } = ownItems(clone, resolved.instance, resolved.grant);
200
+ const position = findOwnItem(items, edit.itemId, resolved.instance, resolved.grant, session, diagnostics);
201
+ if (position < 0) return;
202
+
203
+ save(clone, resolved.instance, resolved.grant, contribution, items.filter((_, index) => index !== position));
204
+ }
205
+
206
+ /**
207
+ * Applies an item reorder to a copy of the document.
208
+ */
209
+ export function applyReorderCollectionItem(session: EditSession, edit: ReorderCollectionItemEdit, clone: SceneDocument, diagnostics: SceneDiagnostic[]): void {
210
+ const resolved = resolveGrant(session, edit as InstanceEditTarget, diagnostics);
211
+ if (!resolved || !permitted(resolved.grant, CollectionOperation.Reorder, resolved.instance, diagnostics)) return;
212
+
213
+ const { contribution, items } = ownItems(clone, resolved.instance, resolved.grant);
214
+ const position = findOwnItem(items, edit.itemId, resolved.instance, resolved.grant, session, diagnostics);
215
+ if (position < 0) return;
216
+
217
+ if (!Number.isInteger(edit.index) || edit.index < 0 || edit.index >= items.length) {
218
+ diagnostics.push(errorDiagnostic(DiagnosticCode.IndexOutOfRange, `There is no position ${edit.index} among ${items.length} items.`, { instance: resolved.instance, component: edit.component, path: edit.path, itemId: edit.itemId }));
219
+ return;
220
+ }
221
+
222
+ const next = [...items];
223
+ const [moved] = next.splice(position, 1);
224
+ next.splice(edit.index, 0, moved);
225
+ save(clone, resolved.instance, resolved.grant, contribution, next);
226
+ }
227
+
228
+ /**
229
+ * Applies an item field change to a copy of the document.
230
+ */
231
+ export function applyEditCollectionItem(session: EditSession, edit: EditCollectionItemEdit, clone: SceneDocument, diagnostics: SceneDiagnostic[]): void {
232
+ const resolved = resolveGrant(session, edit as InstanceEditTarget, diagnostics);
233
+ if (!resolved || !permitted(resolved.grant, CollectionOperation.EditFields, resolved.instance, diagnostics)) return;
234
+
235
+ const { contribution, items } = ownItems(clone, resolved.instance, resolved.grant);
236
+ const position = findOwnItem(items, edit.itemId, resolved.instance, resolved.grant, session, diagnostics);
237
+ if (position < 0) return;
238
+
239
+ if (!checkFields(resolved.grant, { [edit.field]: edit.value }, edit.itemId, resolved.instance, diagnostics, false)) return;
240
+
241
+ const next = items.map((item, index) => index === position ? { ...item, values: { ...item.values, [edit.field]: cloneData(edit.value) } } : item);
242
+ save(clone, resolved.instance, resolved.grant, contribution, next);
243
+ }