@salsita-ai/sdk 0.50.0-develop.aabb56df09 → 0.50.0-develop.ab6a7ca8ae

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 (1230) hide show
  1. package/README.md +3 -3
  2. package/dist/admin/create-admin-pages-from-spec.d.ts +1 -1
  3. package/dist/application-spec/core/components/component-filter.d.ts +53 -0
  4. package/dist/application-spec/core/components/component-filter.js +1 -0
  5. package/dist/application-spec/core/components/index.d.ts +2 -0
  6. package/dist/application-spec/core/components/index.js +1 -0
  7. package/dist/application-spec/core/components/sdk-code-ref.d.ts +9 -0
  8. package/dist/application-spec/core/components/sdk-code-ref.js +1 -0
  9. package/dist/application-spec/core/enhanced-json-value.d.ts +0 -68
  10. package/dist/application-spec/core/enhanced-json-value.js +1 -1
  11. package/dist/application-spec/core/expressions/index.d.ts +2 -0
  12. package/dist/application-spec/core/expressions/index.js +1 -0
  13. package/dist/application-spec/core/expressions/relation-expression-params.d.ts +11 -0
  14. package/dist/application-spec/core/expressions/sdk-expression-implementation.d.ts +24 -0
  15. package/dist/application-spec/core/expressions/sdk-expression-implementation.js +1 -0
  16. package/dist/application-spec/core/index.d.ts +2 -0
  17. package/dist/application-spec/core/index.js +1 -1
  18. package/dist/application-spec/core/json-value.d.ts +1 -1
  19. package/dist/application-spec/{core → enhanced-json}/eval-to-constant.d.ts +1 -1
  20. package/dist/application-spec/enhanced-json/index.d.ts +4 -0
  21. package/dist/application-spec/enhanced-json/index.js +1 -0
  22. package/dist/application-spec/enhanced-json/is-enhanced-json-array.d.ts +2 -0
  23. package/dist/application-spec/enhanced-json/is-enhanced-json-array.js +1 -0
  24. package/dist/application-spec/enhanced-json/resolve-enhanced-json-value.d.ts +18 -0
  25. package/dist/application-spec/enhanced-json/resolve-enhanced-json-value.js +1 -0
  26. package/dist/application-spec/enhanced-json/schema-from-value.d.ts +24 -0
  27. package/dist/application-spec/enhanced-json/schema-from-value.js +1 -0
  28. package/dist/application-spec/enhanced-json/stringify-enhanced-json-value.d.ts +14 -0
  29. package/dist/application-spec/enhanced-json/stringify-enhanced-json-value.js +1 -0
  30. package/dist/application-spec/enhanced-json/validate-enhanced-json-value-caps.d.ts +12 -0
  31. package/dist/application-spec/enhanced-json/validate-enhanced-json-value-caps.js +1 -0
  32. package/dist/application-spec/enhanced-json/validate-no-legacy-code-ref.d.ts +13 -0
  33. package/dist/application-spec/enhanced-json/validate-no-legacy-code-ref.js +1 -0
  34. package/dist/application-spec/errors/diagnostic-codes.d.ts +9 -0
  35. package/dist/application-spec/errors/diagnostic-codes.js +1 -1
  36. package/dist/application-spec/errors/diagnostic-messages.d.ts +12 -0
  37. package/dist/application-spec/errors/diagnostic-messages.js +1 -1
  38. package/dist/application-spec/generate-application/code/common/index.d.ts +1 -0
  39. package/dist/application-spec/generate-application/code/common/index.js +1 -1
  40. package/dist/application-spec/generate-application/code/common/project-module-list.d.ts +8 -0
  41. package/dist/application-spec/generate-application/code/common/project-module-list.js +3 -0
  42. package/dist/application-spec/generate-application/code/domain-runtime-files.js +1 -1
  43. package/dist/application-spec/generate-application/code/project-scaffolding.d.ts +8 -1
  44. package/dist/application-spec/generate-application/code/project-scaffolding.js +2 -2
  45. package/dist/application-spec/generate-application/code/standalone-app-files.d.ts +2 -2
  46. package/dist/application-spec/generate-application/code/standalone-app-files.js +1 -1
  47. package/dist/application-spec/generate-application/code/templates/admin-domain-code.d.ts +1 -2
  48. package/dist/application-spec/generate-application/code/templates/admin-domain-code.js +6 -14
  49. package/dist/application-spec/generate-application/code/templates/admin-frontend-code.d.ts +1 -2
  50. package/dist/application-spec/generate-application/code/templates/admin-frontend-code.js +4 -4
  51. package/dist/application-spec/generate-application/code/templates/configurator-config.js +6 -4
  52. package/dist/application-spec/generate-application/code/templates/configurator-domain-code.d.ts +1 -2
  53. package/dist/application-spec/generate-application/code/templates/configurator-domain-code.js +7 -16
  54. package/dist/application-spec/generate-application/code/templates/configurator-frontend-code.d.ts +1 -2
  55. package/dist/application-spec/generate-application/code/templates/configurator-frontend-code.js +7 -9
  56. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-assistant.d.ts +2 -2
  57. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-assistant.js +1 -1
  58. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-component-optics.js +4 -4
  59. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-defaults.d.ts +4 -2
  60. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-defaults.js +1 -1
  61. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-domain-block.js +56 -56
  62. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-invariants.d.ts +1 -1
  63. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-invariants.js +1 -1
  64. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-phx-globals.js +3 -0
  65. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-relation-expressions.js +1 -1
  66. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-resources.d.ts +6 -8
  67. package/dist/application-spec/generate-application/code/templates/domain-spec/generate-resources.js +11 -11
  68. package/dist/application-spec/generate-application/code/templates/domain-spec/index.d.ts +1 -1
  69. package/dist/application-spec/generate-application/code/templates/domain-spec/index.js +1 -1
  70. package/dist/application-spec/generate-application/code/templates/domain-spec.js +12 -12
  71. package/dist/application-spec/generate-application/code/templates/domain.js +5 -5
  72. package/dist/application-spec/generate-application/code/templates/globals.js +5 -4
  73. package/dist/application-spec/generate-application/code/templates/index.d.ts +1 -8
  74. package/dist/application-spec/generate-application/code/templates/index.js +1 -1
  75. package/dist/application-spec/generate-application/code/templates/package-json.js +1 -1
  76. package/dist/application-spec/generate-application/code/templates/skills/phoenix-cli-skill.js +1 -1
  77. package/dist/application-spec/generate-application/code/templates/skills/phoenix-create-configurator-skill.js +93 -41
  78. package/dist/application-spec/generate-application/code/templates/skills/phoenix-review-skill.js +10 -4
  79. package/dist/application-spec/generate-application/code/templates/skills/phoenix-skill.js +1 -1
  80. package/dist/application-spec/generate-application/code/templates/skills/phoenix-variants-skill.d.ts +1 -0
  81. package/dist/application-spec/generate-application/code/templates/skills/phoenix-variants-skill.js +6 -0
  82. package/dist/application-spec/generate-application/code/templates/skills/sdk-content.d.ts +46 -14
  83. package/dist/application-spec/generate-application/code/templates/skills/sdk-content.js +19 -7
  84. package/dist/application-spec/generate-application/code/templates/webhook-builders.js +9 -4
  85. package/dist/application-spec/generate-application/code/templates/webhook-index.d.ts +6 -1
  86. package/dist/application-spec/generate-application/code/templates/webhook-index.js +10 -1
  87. package/dist/application-spec/generate-application/code/templates/webhooks-config.js +2 -2
  88. package/dist/application-spec/generate-application/index.d.ts +2 -0
  89. package/dist/application-spec/generate-application/index.js +1 -1
  90. package/dist/application-spec/generate-application/runtime/admin-runtime/admin-runtime.d.ts +6 -2
  91. package/dist/application-spec/generate-application/runtime/admin-runtime/admin-runtime.js +1 -1
  92. package/dist/application-spec/generate-application/runtime/application-runtime.d.ts +8 -3
  93. package/dist/application-spec/generate-application/runtime/application-runtime.js +1 -1
  94. package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-runtime.d.ts +5 -3
  95. package/dist/application-spec/generate-application/runtime/configurator-runtime/configurator-runtime.js +1 -1
  96. package/dist/application-spec/generate-application/runtime/factory-data/factory-data-runtime.d.ts +43 -0
  97. package/dist/application-spec/generate-application/runtime/factory-data/factory-data-runtime.js +1 -0
  98. package/dist/application-spec/generate-application/runtime/factory-data/index.d.ts +3 -0
  99. package/dist/application-spec/generate-application/runtime/factory-data/index.js +1 -0
  100. package/dist/application-spec/generate-application/runtime/factory-data/recipe-defaults.d.ts +13 -0
  101. package/dist/application-spec/generate-application/runtime/factory-data/recipe-defaults.js +1 -0
  102. package/dist/application-spec/generate-application/runtime/factory-data/static-violations.d.ts +16 -0
  103. package/dist/application-spec/generate-application/runtime/factory-data/static-violations.js +1 -0
  104. package/dist/application-spec/generate-application/runtime/migrations/index.d.ts +1 -0
  105. package/dist/application-spec/generate-application/runtime/migrations/index.js +1 -0
  106. package/dist/application-spec/generate-application/runtime/migrations/migration-runtime.d.ts +53 -0
  107. package/dist/application-spec/generate-application/runtime/migrations/migration-runtime.js +1 -0
  108. package/dist/application-spec/generate-application/runtime/sections/assistant-runtime.d.ts +4 -3
  109. package/dist/application-spec/generate-application/runtime/sections/assistant-runtime.js +1 -1
  110. package/dist/application-spec/generate-application/runtime/sections/build-sdk-optic.d.ts +1 -1
  111. package/dist/application-spec/generate-application/runtime/sections/build-sdk-optic.js +1 -1
  112. package/dist/application-spec/generate-application/runtime/sections/component-optic.d.ts +2 -1
  113. package/dist/application-spec/generate-application/runtime/sections/expression-runtime.js +1 -1
  114. package/dist/application-spec/generate-application/runtime/sections/transformers-runtime.d.ts +2 -4
  115. package/dist/application-spec/index.d.ts +1 -0
  116. package/dist/application-spec/index.js +1 -1
  117. package/dist/application-spec/parse-application-files/categorize/categorize-files.d.ts +6 -0
  118. package/dist/application-spec/parse-application-files/categorize/categorize-files.js +1 -1
  119. package/dist/application-spec/parse-application-files/categorize/module-analysis.d.ts +1 -1
  120. package/dist/application-spec/parse-application-files/categorize/module-analysis.js +1 -1
  121. package/dist/application-spec/parse-application-files/raw-spec/raw-assistant.d.ts +16 -3
  122. package/dist/application-spec/parse-application-files/raw-spec/raw-assistant.js +1 -1
  123. package/dist/application-spec/parse-application-files/raw-spec/raw-spec-file.js +1 -1
  124. package/dist/application-spec/parse-application-files/spec/application/admin-spec/admin-spec.js +1 -1
  125. package/dist/application-spec/parse-application-files/spec/application/configurator-spec/configurator-spec.js +1 -1
  126. package/dist/application-spec/parse-application-files/spec/components/component-entry.d.ts +1 -9
  127. package/dist/application-spec/parse-application-files/spec/components/component-entry.js +1 -1
  128. package/dist/application-spec/parse-application-files/spec/components/component-filter.d.ts +1 -54
  129. package/dist/application-spec/parse-application-files/spec/components/component-filter.js +1 -1
  130. package/dist/application-spec/parse-application-files/spec/components/index.d.ts +1 -1
  131. package/dist/application-spec/parse-application-files/spec/components/index.js +1 -1
  132. package/dist/application-spec/parse-application-files/spec/expressions/expression.d.ts +1 -28
  133. package/dist/application-spec/parse-application-files/spec/expressions/expression.js +1 -1
  134. package/dist/application-spec/parse-application-files/spec/expressions/expressions.d.ts +1 -1
  135. package/dist/application-spec/parse-application-files/spec/expressions/expressions.js +1 -1
  136. package/dist/application-spec/parse-application-files/spec/expressions/index.d.ts +0 -1
  137. package/dist/application-spec/parse-application-files/spec/expressions/index.js +1 -1
  138. package/dist/application-spec/parse-application-files/spec/expressions/memo-eval-plan.js +1 -1
  139. package/dist/application-spec/parse-application-files/spec/expressions/user-expressions.js +1 -1
  140. package/dist/application-spec/parse-application-files/spec/model/defaults.d.ts +7 -2
  141. package/dist/application-spec/parse-application-files/spec/model/defaults.js +1 -1
  142. package/dist/application-spec/parse-application-files/spec/relations/reference-field-type.d.ts +25 -0
  143. package/dist/application-spec/parse-application-files/spec/relations/reference-field-type.js +1 -0
  144. package/dist/application-spec/parse-application-files/spec/relations/relation-field-mismatch.d.ts +46 -0
  145. package/dist/application-spec/parse-application-files/spec/relations/relation-field-mismatch.js +1 -0
  146. package/dist/application-spec/parse-application-files/spec/relations/relations.d.ts +4 -3
  147. package/dist/application-spec/parse-application-files/spec/relations/relations.js +1 -1
  148. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-field-type.d.ts +3 -8
  149. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-field-type.js +1 -1
  150. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-schema.d.ts +9 -43
  151. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-schema.js +1 -1
  152. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant-server-settings.d.ts +2 -2
  153. package/dist/application-spec/parse-application-files/spec/sections/assistant/assistant.d.ts +5 -21
  154. package/dist/application-spec/parse-application-files/spec/sections/infra.d.ts +2 -0
  155. package/dist/application-spec/parse-application-files/spec/sections/infra.js +1 -1
  156. package/dist/client/authorized-client.d.ts +3 -1
  157. package/dist/client/authorized-client.js +1 -1
  158. package/dist/client/contract.d.ts +34 -0
  159. package/dist/client/contract.js +1 -1
  160. package/dist/client/index.d.ts +2 -2
  161. package/dist/client/index.js +1 -1
  162. package/dist/client/local-authorized-client.d.ts +3 -1
  163. package/dist/client/local-authorized-client.js +1 -1
  164. package/dist/client/memory-authorized-client.d.ts +3 -1
  165. package/dist/client/memory-authorized-client.js +1 -1
  166. package/dist/client/phoenix-api.d.ts +367 -30
  167. package/dist/configurator/application-runtime/extensions/create-configurator-extensions.js +1 -1
  168. package/dist/configurator/application-runtime/extensions/selectors.d.ts +3 -1
  169. package/dist/configurator/application-runtime/extensions/selectors.js +1 -1
  170. package/dist/configurator/application-runtime/extensions/systems/assistant-system/assistant-system.d.ts +136 -6
  171. package/dist/configurator/application-runtime/extensions/systems/assistant-system/assistant-system.js +1 -1
  172. package/dist/configurator/application-runtime/extensions/systems/assistant-system/graphql-tools.d.ts +7 -1
  173. package/dist/configurator/application-runtime/extensions/systems/assistant-system/graphql-tools.js +1 -1
  174. package/dist/configurator/application-runtime/extensions/systems/drag-and-drop-system/drag-and-drop-system.d.ts +3 -3
  175. package/dist/configurator/application-runtime/extensions/systems/graphql-system/entity-meta.js +1 -1
  176. package/dist/configurator/application-runtime/extensions/systems/graphql-system/graphql-system.d.ts +20 -4
  177. package/dist/configurator/application-runtime/extensions/systems/graphql-system/graphql-system.js +1 -1
  178. package/dist/configurator/application-runtime/extensions/systems/graphql-system/inject-assistant-lenses.d.ts +11 -0
  179. package/dist/configurator/application-runtime/extensions/systems/graphql-system/inject-assistant-lenses.js +1 -0
  180. package/dist/configurator/application-runtime/extensions/systems/index.d.ts +1 -1
  181. package/dist/configurator/application-runtime/extensions/systems/layout-system/layout-system.d.ts +2 -2
  182. package/dist/configurator/application-runtime/extensions/systems/layout-system/layout-system.js +1 -1
  183. package/dist/configurator/common/configurator-interface.d.ts +26 -1
  184. package/dist/configurator/common/configurator-interface.js +1 -1
  185. package/dist/configurator/common/frontend-analytics.d.ts +2 -0
  186. package/dist/configurator/common/frontend-analytics.js +1 -1
  187. package/dist/configurator/common/index.d.ts +2 -0
  188. package/dist/configurator/common/index.js +1 -1
  189. package/dist/configurator/common/use-finalize-action.d.ts +31 -0
  190. package/dist/configurator/common/use-finalize-action.js +1 -0
  191. package/dist/configurator/common/use-reset-camera.d.ts +6 -0
  192. package/dist/configurator/common/use-reset-camera.js +1 -0
  193. package/dist/configurator/configurator-context/configurator-hooks/layout-hooks.d.ts +1 -1
  194. package/dist/configurator/pages/DefaultConfiguratorLayout.d.ts +6 -0
  195. package/dist/configurator/pages/DefaultConfiguratorLayout.js +1 -0
  196. package/dist/configurator/pages/LoadedConfiguratorDetailPage.js +1 -1
  197. package/dist/configurator/pages/configurator-layout-name.d.ts +2 -0
  198. package/dist/configurator/pages/configurator-layout-name.js +1 -0
  199. package/dist/configurator/pages/default-configurator-elements.d.ts +7 -0
  200. package/dist/configurator/pages/default-configurator-elements.js +1 -0
  201. package/dist/configurator/pages/index.d.ts +1 -0
  202. package/dist/configurator/pages/index.js +1 -1
  203. package/dist/configurator/scene/scene-controls/hotspot-drag-handle/HotspotDragHandle.d.ts +1 -1
  204. package/dist/configurator/scene/scene-controls/hotspot-drag-handle/HotspotDragSceneHandle.d.ts +1 -1
  205. package/dist/configurator/scene/scene-entity/dimension-indicators/DimensionIndicator.d.ts +1 -1
  206. package/dist/configurator/scene/scene-entity/dimension-indicators/DimensionIndicatorInternal.d.ts +1 -1
  207. package/dist/configurator/scene/scene-entity/drag-and-drop/shared.d.ts +2 -2
  208. package/dist/configurator/scene/scene-entity/drag-and-drop/use-drag-object.d.ts +1 -1
  209. package/dist/configurator/scene/scene-entity/materials/RemoteReplacedMaterial.js +1 -1
  210. package/dist/configurator/scene/scene-entity/objects/SceneEntityObjectTextDecal.d.ts +1 -1
  211. package/dist/configurator/ui/assistant/AssistantChips.d.ts +8 -2
  212. package/dist/configurator/ui/assistant/AssistantChips.js +1 -1
  213. package/dist/configurator/ui/assistant/AssistantComposer.d.ts +2 -1
  214. package/dist/configurator/ui/assistant/AssistantComposer.js +1 -1
  215. package/dist/configurator/ui/assistant/AssistantMarkdown.d.ts +1 -1
  216. package/dist/configurator/ui/assistant/AssistantMarkdown.js +1 -1
  217. package/dist/configurator/ui/assistant/AssistantThread.d.ts +6 -2
  218. package/dist/configurator/ui/assistant/AssistantThread.js +1 -1
  219. package/dist/configurator/ui/assistant/entity-link/AssistantEntityLink.d.ts +13 -0
  220. package/dist/configurator/ui/assistant/entity-link/AssistantEntityLink.js +1 -0
  221. package/dist/configurator/ui/assistant/entity-link/entity-link-selection.d.ts +22 -0
  222. package/dist/configurator/ui/assistant/entity-link/entity-link-selection.js +1 -0
  223. package/dist/configurator/ui/assistant/entity-link/entity-link-state.d.ts +27 -0
  224. package/dist/configurator/ui/assistant/entity-link/entity-link-state.js +1 -0
  225. package/dist/configurator/ui/assistant/entity-link/entity-link-target.d.ts +5 -0
  226. package/dist/configurator/ui/assistant/entity-link/entity-link-target.js +1 -0
  227. package/dist/configurator/ui/assistant/entity-link/index.d.ts +3 -0
  228. package/dist/configurator/ui/assistant/entity-link/index.js +1 -0
  229. package/dist/configurator/ui/assistant/thread-items.d.ts +13 -2
  230. package/dist/configurator/ui/assistant/thread-items.js +1 -1
  231. package/dist/configurator/ui/assistant/tool-chip.d.ts +8 -1
  232. package/dist/configurator/ui/assistant/tool-chip.js +1 -1
  233. package/dist/configurator/ui/configurator-layout/sidebar-keys/index.d.ts +1 -0
  234. package/dist/configurator/ui/configurator-layout/sidebar-keys/index.js +1 -0
  235. package/dist/configurator/ui/configurator-layout/sidebar-keys/sidebar-keys.d.ts +11 -0
  236. package/dist/configurator/ui/configurator-layout/sidebar-keys/sidebar-keys.js +1 -0
  237. package/dist/configurator/ui/configurator-layout/slots/desktop-sidebar/DesktopSidebar.d.ts +101 -0
  238. package/dist/configurator/ui/configurator-layout/slots/desktop-sidebar/DesktopSidebar.js +16 -0
  239. package/dist/configurator/ui/configurator-layout/slots/desktop-sidebar/index.d.ts +1 -0
  240. package/dist/configurator/ui/configurator-layout/slots/desktop-sidebar/index.js +1 -0
  241. package/dist/configurator/ui/configurator-layout/slots/services/InvariantViolations.d.ts +4 -4
  242. package/dist/configurator/ui/configurator-layout/slots/services/InvariantViolations.js +1 -1
  243. package/dist/configurator/ui/configurator-layout/slots/services/SideEffects.d.ts +3 -6
  244. package/dist/configurator/ui/configurator-layout/slots/services/SideEffects.js +1 -1
  245. package/dist/configurator/ui/configurator-layout/slots/slots.d.ts +1 -0
  246. package/dist/configurator/ui/configurator-layout/slots/slots.js +1 -1
  247. package/dist/configurator/ui/configurator-layout/universal/DesktopLayout.d.ts +0 -74
  248. package/dist/configurator/ui/configurator-layout/universal/DesktopLayout.js +5 -20
  249. package/dist/configurator/ui/configurator-layout/universal/DesktopPreview.js +1 -1
  250. package/dist/configurator/ui/configurator-layout/universal/MobileLayout.js +4 -4
  251. package/dist/configurator/ui/configurator-layout/universal/MobilePreview.js +1 -1
  252. package/dist/configurator/ui/configurator-layout/universal/use-layout-scene-safe-area.d.ts +47 -0
  253. package/dist/configurator/ui/configurator-layout/universal/use-layout-scene-safe-area.js +1 -0
  254. package/dist/configurator/ui/configurator-layout/universal/use-scene-safe-area.d.ts +11 -26
  255. package/dist/configurator/ui/configurator-layout/universal/use-scene-safe-area.js +1 -1
  256. package/dist/configurator/ui/legacy/layout/steps/StepsNavigation.d.ts +2 -2
  257. package/dist/configurator/ui/phoenix-layout-elements/header/common/step-navigation/StepsNavigation.d.ts +2 -2
  258. package/dist/docs/complex-use-cases/filtered-controls-with-fix-dialog.mdx +100 -117
  259. package/dist/docs/complex-use-cases/navigation-reset-selection.mdx +18 -12
  260. package/dist/docs/complex-use-cases/sketching.mdx +0 -38
  261. package/dist/docs/controls/select-attribute/cardselect.mdx +9 -13
  262. package/dist/docs/dimensions/3d-scene-integration/dimension-indicators.mdx +2 -2
  263. package/dist/docs/dimensions/3d-scene-integration/editable-angle-indicator.mdx +1 -1
  264. package/dist/docs/dimensions/3d-scene-integration/editable-distance-dimension-indicator.mdx +1 -1
  265. package/dist/docs/dimensions/3d-scene-integration/list-selector-dimension-indicator.mdx +1 -1
  266. package/dist/docs/drag-and-drop/drag-handle.mdx +1 -1
  267. package/dist/docs/drag-and-drop/hotspot-handle.mdx +1 -1
  268. package/dist/docs/drag-and-drop/planes.mdx +1 -1
  269. package/dist/docs/drag-and-drop/rotatable.mdx +2 -2
  270. package/dist/docs/index.json +42 -1
  271. package/dist/docs/integrations/woocommerce/local-development.mdx +1 -1
  272. package/dist/docs/layouts/custom-layout.mdx +2125 -0
  273. package/dist/docs/layouts/custom-price.mdx +14 -16
  274. package/dist/docs/layouts/header/fixed.mdx +2 -2
  275. package/dist/docs/layouts/header/floating.mdx +2 -2
  276. package/dist/docs/layouts/sidebar/in-a-custom-layout.mdx +380 -0
  277. package/dist/docs/layouts/sidebar/tabs.mdx +24 -21
  278. package/dist/docs/layouts/sidebar-footer/basic.mdx +32 -77
  279. package/dist/docs/other-features/client-side-rendering/declarative.mdx +1 -1
  280. package/dist/docs/other-features/domain-actions.mdx +1 -1
  281. package/dist/docs/other-features/side-effects.mdx +50 -45
  282. package/dist/docs/other-features/transformers.mdx +5 -1
  283. package/dist/docs/other-features/violation-messages.mdx +52 -56
  284. package/dist/docs/phoenix/admin.mdx +9 -24
  285. package/dist/docs/phoenix/application-states.mdx +9 -7
  286. package/dist/docs/phoenix/configurator-interface.mdx +237 -80
  287. package/dist/docs/phoenix/deployments.mdx +1 -1
  288. package/dist/docs/phoenix/domain-model.mdx +4 -0
  289. package/dist/docs/phoenix/expression-context.mdx +11 -3
  290. package/dist/docs/phoenix/finalization.mdx +1 -1
  291. package/dist/docs/phoenix/getting-started.mdx +1 -1
  292. package/dist/docs/phoenix/layout-slots.mdx +116 -0
  293. package/dist/docs/phoenix/localization.mdx +3 -22
  294. package/dist/docs/phoenix/phoenix-cli.mdx +148 -55
  295. package/dist/docs/phoenix/project-structure.mdx +32 -13
  296. package/dist/docs/phoenix/quickstart.mdx +2 -2
  297. package/dist/docs/phoenix/sdk-builder-workflow.mdx +12 -11
  298. package/dist/docs/phoenix/testing.mdx +140 -0
  299. package/dist/docs/phoenix/webhook-membership.mdx +10 -9
  300. package/dist/docs/phoenix/webhooks.mdx +19 -2
  301. package/dist/docs/recipes-and-delete/recipe-hierarchy-selection.mdx +15 -13
  302. package/dist/docs/relations/hierarchy.mdx +13 -2
  303. package/dist/docs/ux-guidelines/choosing-a-control.mdx +11 -0
  304. package/dist/domain/admin-domain/__generated/enhanced-model.d.ts +1 -2
  305. package/dist/domain/admin-domain/__generated/model/versions/v1.d.ts +1 -1
  306. package/dist/domain/admin-domain/__generated/model/versions/v2.d.ts +1 -1
  307. package/dist/domain/admin-domain/__generated/model/versions/v3.d.ts +1 -1
  308. package/dist/domain/admin-domain/__generated/model/versions/v4.d.ts +1 -1
  309. package/dist/domain/admin-domain/__generated/model/versions/v5.d.ts +1 -1
  310. package/dist/domain/admin-domain/__generated/model/versions/v6.d.ts +2 -2
  311. package/dist/domain/admin-domain/__generated/model/versions/v6.js +1 -1
  312. package/dist/domain/admin-domain/implementation/common-admin-expressions.d.ts +26 -14
  313. package/dist/domain/admin-domain/implementation/common-admin-migrations.d.ts +1 -1
  314. package/dist/domain/admin-domain/implementation/common-admin-publish.d.ts +1 -2
  315. package/dist/domain/admin-domain/index.d.ts +9 -5
  316. package/dist/domain/admin-domain/index.js +1 -1
  317. package/dist/domain/application/GetApplicationWrapperHandler.d.ts +2 -2
  318. package/dist/domain/application/GetThemeHandler.d.ts +1 -1
  319. package/dist/domain/application/GetWebComponentOptions.d.ts +1 -1
  320. package/dist/domain/application/admin-page-factories.d.ts +1 -2
  321. package/dist/domain/application/application-config.d.ts +30 -97
  322. package/dist/domain/application/assistant/assistant-fields.d.ts +12 -0
  323. package/dist/domain/application/assistant/assistant-fields.js +1 -0
  324. package/dist/domain/application/assistant/assistant-manifest.d.ts +71 -0
  325. package/dist/domain/application/assistant/assistant-resolved-manifest.d.ts +131 -0
  326. package/dist/domain/application/assistant/assistant-resolved-manifest.js +1 -0
  327. package/dist/domain/application/assistant/assistant-value-type.d.ts +9 -0
  328. package/dist/domain/application/assistant/assistant-value-type.js +1 -0
  329. package/dist/domain/application/assistant/build-assistant-lenses.d.ts +14 -0
  330. package/dist/domain/application/assistant/build-assistant-lenses.js +5 -0
  331. package/dist/domain/application/assistant/build-assistant-manifest.d.ts +4 -0
  332. package/dist/domain/application/assistant/build-assistant-manifest.js +1 -0
  333. package/dist/domain/application/assistant/create-assistant-execution-schema.d.ts +32 -0
  334. package/dist/domain/application/assistant/create-assistant-execution-schema.js +1 -0
  335. package/dist/domain/application/assistant/generate-assistant-prompt.d.ts +3 -0
  336. package/dist/domain/application/assistant/generate-assistant-prompt.js +1 -0
  337. package/dist/domain/application/assistant/index.d.ts +9 -0
  338. package/dist/domain/application/assistant/index.js +1 -0
  339. package/dist/domain/application/assistant/session-setup/AssistantSetupHandler.d.ts +20 -0
  340. package/dist/domain/application/assistant/session-setup/AssistantSetupHandler.js +1 -0
  341. package/dist/domain/application/assistant/session-setup/apply-assistant-setup.d.ts +10 -0
  342. package/dist/domain/application/assistant/session-setup/apply-assistant-setup.js +1 -0
  343. package/dist/domain/application/assistant/session-setup/assistant-setup-manifest.d.ts +48 -0
  344. package/dist/domain/application/assistant/session-setup/assistant-setup-manifest.js +1 -0
  345. package/dist/domain/application/assistant/session-setup/create-session-assistant.d.ts +15 -0
  346. package/dist/domain/application/assistant/session-setup/create-session-assistant.js +1 -0
  347. package/dist/domain/application/assistant/session-setup/index.d.ts +4 -0
  348. package/dist/domain/application/assistant/session-setup/index.js +1 -0
  349. package/dist/domain/application/assistant-field-options.d.ts +2 -3
  350. package/dist/domain/application/assistant-mutations.d.ts +3 -5
  351. package/dist/domain/application/assistant-mutations.js +1 -1
  352. package/dist/domain/application/assistant-recipe-groups.d.ts +3 -7
  353. package/dist/domain/application/configurator-builder.d.ts +36 -18
  354. package/dist/domain/application/configurator-builder.js +1 -1
  355. package/dist/domain/application/create-initial-published-data.d.ts +6 -7
  356. package/dist/domain/application/index.d.ts +3 -6
  357. package/dist/domain/application/index.js +1 -1
  358. package/dist/domain/application/published-data/DataRowValidatorHandler.d.ts +1 -1
  359. package/dist/domain/application/published-data/FilterPublishedChunksHandler.d.ts +1 -1
  360. package/dist/domain/application/published-data/MapPublishedChunkedDataHandler.d.ts +1 -1
  361. package/dist/domain/application/published-data/MapPublishedRevisionDataHandler.d.ts +1 -1
  362. package/dist/domain/application/published-data/SheetValidatorHandler.d.ts +1 -1
  363. package/dist/domain/application/published-data/build-legend-data.d.ts +1 -1
  364. package/dist/domain/application/published-data/create-finalize-admin-domain.d.ts +1 -2
  365. package/dist/domain/application/published-data/format-field-type.d.ts +1 -1
  366. package/dist/domain/application/published-data/map-data-to-sheet.d.ts +1 -1
  367. package/dist/domain/application/published-data/map-published-chunked-data.d.ts +3 -3
  368. package/dist/domain/application/published-data/map-published-data.d.ts +7 -4
  369. package/dist/domain/application/published-data/validate-sheet-data.d.ts +1 -1
  370. package/dist/domain/available-system-components.js +1 -1
  371. package/dist/domain/core/action/action-factory.d.ts +11 -0
  372. package/dist/domain/core/{action.d.ts → action/action.d.ts} +22 -18
  373. package/dist/domain/core/action/index.d.ts +2 -0
  374. package/dist/domain/core/action/index.js +1 -0
  375. package/dist/domain/{components → core/component}/component-reader-types.d.ts +1 -2
  376. package/dist/domain/{components → core/component}/debug-state-component.d.ts +1 -1
  377. package/dist/domain/core/component/debug-state-component.js +1 -0
  378. package/dist/domain/core/component/domain-component-optic.d.ts +65 -0
  379. package/dist/domain/core/component/domain-component-optic.js +16 -0
  380. package/dist/domain/{components → core/component}/drag-state-component.d.ts +1 -1
  381. package/dist/domain/core/component/drag-state-component.js +1 -0
  382. package/dist/domain/{components → core/component}/export-state-component.d.ts +1 -1
  383. package/dist/domain/core/component/export-state-component.js +1 -0
  384. package/dist/domain/{components → core/component}/fullscreen-state-component.d.ts +1 -1
  385. package/dist/domain/core/component/fullscreen-state-component.js +1 -0
  386. package/dist/domain/{components → core/component}/group-components-by-entity-id-and-name.d.ts +1 -2
  387. package/dist/domain/core/component/group-components-by-entity-id-and-name.js +1 -0
  388. package/dist/domain/{components → core/component}/hover-state-component.d.ts +1 -1
  389. package/dist/domain/core/component/hover-state-component.js +1 -0
  390. package/dist/domain/core/component/index.d.ts +18 -0
  391. package/dist/domain/core/component/index.js +1 -0
  392. package/dist/domain/{components → core/component}/legacy-filter.d.ts +4 -19
  393. package/dist/domain/{components → core/component}/navigation-state-component.d.ts +1 -1
  394. package/dist/domain/core/component/navigation-state-component.js +1 -0
  395. package/dist/domain/{components → core/component}/selection-state-component.d.ts +1 -1
  396. package/dist/domain/core/component/selection-state-component.js +1 -0
  397. package/dist/domain/{components → core/component}/system-component-builder.d.ts +1 -2
  398. package/dist/domain/core/component/system-component-builder.js +1 -0
  399. package/dist/domain/{components → core/component}/system-component-filter.d.ts +38 -20
  400. package/dist/domain/core/component/system-virtual-entity.js +1 -0
  401. package/dist/domain/{components → core/component}/tool-state-component.d.ts +1 -1
  402. package/dist/domain/core/component/tool-state-component.js +1 -0
  403. package/dist/domain/{components → core/component}/updates.d.ts +1 -2
  404. package/dist/domain/{components → core/component}/view-state-component.d.ts +1 -1
  405. package/dist/domain/core/component/view-state-component.js +1 -0
  406. package/dist/domain/{components → core/component}/viewport-state-component.d.ts +1 -1
  407. package/dist/domain/core/component/viewport-state-component.js +1 -0
  408. package/dist/domain/core/data-schema/data-cell-value.d.ts +4 -0
  409. package/dist/domain/core/data-schema/data-schema-array-field.d.ts +5 -0
  410. package/dist/domain/core/data-schema/data-schema-field-object.d.ts +4 -0
  411. package/dist/domain/core/data-schema/data-schema-field.d.ts +2 -0
  412. package/dist/domain/core/data-schema/data-schema-field.js +1 -0
  413. package/dist/domain/core/data-schema/data-schema-localized-field.d.ts +4 -0
  414. package/dist/domain/core/data-schema/data-schema-localized-field.js +1 -0
  415. package/dist/domain/core/data-schema/data-schema-primitive-field.d.ts +5 -0
  416. package/dist/domain/core/data-schema/data-schema-primitive-field.js +1 -0
  417. package/dist/domain/core/data-schema/data-schema.d.ts +2 -0
  418. package/dist/domain/core/data-schema/data-schema.js +1 -0
  419. package/dist/domain/core/data-schema/index.d.ts +7 -0
  420. package/dist/domain/core/data-schema/index.js +1 -0
  421. package/dist/domain/core/decoder/create-decoder.d.ts +6 -0
  422. package/dist/domain/core/decoder/create-decoder.js +1 -0
  423. package/dist/domain/core/decoder/decoder.d.ts +6 -0
  424. package/dist/domain/core/decoder/decoder.js +1 -0
  425. package/dist/domain/core/decoder/index.d.ts +2 -0
  426. package/dist/domain/core/decoder/index.js +1 -0
  427. package/dist/domain/core/domain-factory.d.ts +4 -5
  428. package/dist/domain/core/domain-factory.js +1 -1
  429. package/dist/domain/core/domain.d.ts +4 -5
  430. package/dist/domain/core/domain.js +1 -1
  431. package/dist/domain/core/emission/index.d.ts +8 -0
  432. package/dist/domain/core/emission/index.js +1 -0
  433. package/dist/domain/core/emission/reset-state-emission.d.ts +12 -0
  434. package/dist/domain/core/emission/reset-state-emission.js +1 -0
  435. package/dist/domain/core/emission/state-emission.d.ts +4 -0
  436. package/dist/domain/core/emission/state-emission.js +1 -0
  437. package/dist/domain/core/emission/state-update-emission.d.ts +14 -0
  438. package/dist/domain/core/emission/state-update-emission.js +1 -0
  439. package/dist/domain/core/emission/state-validation-result.d.ts +18 -0
  440. package/dist/domain/core/emission/state-validation-result.js +1 -0
  441. package/dist/domain/core/emission/transformation-log.d.ts +2 -0
  442. package/dist/domain/core/emission/transformation-log.js +1 -0
  443. package/dist/domain/core/emission/transformer-log-entry.d.ts +6 -0
  444. package/dist/domain/core/emission/transformer-log-entry.js +1 -0
  445. package/dist/domain/core/emission/transformer-log.d.ts +11 -0
  446. package/dist/domain/core/emission/transformer-log.js +1 -0
  447. package/dist/domain/core/emission/transformer-phase.d.ts +1 -0
  448. package/dist/domain/core/emission/transformer-phase.js +1 -0
  449. package/dist/domain/core/expression/aliased-expressions.d.ts +10 -0
  450. package/dist/domain/core/expression/aliased-expressions.js +1 -0
  451. package/dist/domain/core/expression/constant-expressions.d.ts +16 -0
  452. package/dist/domain/core/expression/constant-expressions.js +1 -0
  453. package/dist/domain/core/expression/custom-expression.d.ts +25 -0
  454. package/dist/domain/core/expression/custom-expression.js +1 -0
  455. package/dist/domain/core/expression/expression.d.ts +46 -0
  456. package/dist/domain/core/expression/expression.js +1 -0
  457. package/dist/domain/core/expression/index.d.ts +4 -0
  458. package/dist/domain/core/expression/index.js +1 -0
  459. package/dist/domain/core/index.d.ts +13 -17
  460. package/dist/domain/core/index.js +1 -1
  461. package/dist/domain/{invariants → core/invariant}/builtin-invariant.d.ts +2 -2
  462. package/dist/domain/core/invariant/custom-invariants.d.ts +13 -0
  463. package/dist/domain/core/invariant/custom-invariants.js +1 -0
  464. package/dist/domain/core/invariant/index.d.ts +5 -0
  465. package/dist/domain/core/invariant/index.js +1 -0
  466. package/dist/domain/core/invariant/invariants.d.ts +10 -0
  467. package/dist/domain/core/invariant/invariants.js +1 -0
  468. package/dist/domain/core/invariant/violation-component.js +1 -0
  469. package/dist/domain/{invariants → core/invariant}/violation.d.ts +52 -4
  470. package/dist/domain/core/invariant/violation.js +1 -0
  471. package/dist/domain/core/{component.d.ts → model/component.d.ts} +1 -1
  472. package/dist/domain/core/model/index.d.ts +6 -0
  473. package/dist/domain/core/model/index.js +1 -0
  474. package/dist/domain/core/{model.d.ts → model/model.d.ts} +1 -1
  475. package/dist/domain/core/{reflection.d.ts → model/reflection.d.ts} +1 -1
  476. package/dist/domain/core/model/reflection.js +1 -0
  477. package/dist/domain/core/recipe/index.d.ts +1 -0
  478. package/dist/domain/core/recipe/index.js +1 -0
  479. package/dist/domain/core/recipe/recipes.d.ts +23 -0
  480. package/dist/domain/core/recipe/recipes.js +1 -0
  481. package/dist/domain/core/relation/all-of-type-relation.d.ts +19 -0
  482. package/dist/domain/core/relation/all-of-type-relation.js +1 -0
  483. package/dist/domain/{relations → core/relation}/component-factories.d.ts +1 -2
  484. package/dist/domain/core/relation/component-factories.js +1 -0
  485. package/dist/domain/{relations → core/relation}/composed-of.d.ts +1 -1
  486. package/dist/domain/core/relation/composed-of.js +1 -0
  487. package/dist/domain/core/relation/direct-relation.d.ts +17 -0
  488. package/dist/domain/core/relation/index.d.ts +8 -0
  489. package/dist/domain/core/relation/index.js +1 -0
  490. package/dist/domain/core/relation/indirect-relation.d.ts +25 -0
  491. package/dist/domain/core/relation/indirect-relation.js +1 -0
  492. package/dist/domain/{relations → core/relation}/reference-component.d.ts +4 -5
  493. package/dist/domain/core/relation/reference-component.js +1 -0
  494. package/dist/domain/{relations → core/relation}/reference-field.d.ts +1 -1
  495. package/dist/domain/core/relation/reference-field.js +1 -0
  496. package/dist/domain/core/relation/relation-factory.d.ts +22 -0
  497. package/dist/domain/core/relation/relation-factory.js +1 -0
  498. package/dist/domain/core/schema/enhanced-json-schema.js +1 -0
  499. package/dist/domain/core/schema/extended-schema-validator.js +1 -0
  500. package/dist/domain/core/schema/index.d.ts +3 -0
  501. package/dist/domain/core/schema/index.js +1 -0
  502. package/dist/domain/core/schema/schema-validator.js +1 -0
  503. package/dist/domain/core/state/id-sequence.d.ts +15 -0
  504. package/dist/domain/core/state/id-sequence.js +1 -0
  505. package/dist/domain/core/state/index.d.ts +3 -0
  506. package/dist/domain/core/state/index.js +1 -0
  507. package/dist/domain/core/{migrations.d.ts → state/migrations.d.ts} +15 -47
  508. package/dist/domain/core/state/migrations.js +1 -0
  509. package/dist/domain/core/{state.d.ts → state/state.d.ts} +2 -3
  510. package/dist/domain/domain-builder.d.ts +11 -11
  511. package/dist/domain/domain-builder.js +1 -1
  512. package/dist/domain/domain-context/action-system/action-system.d.ts +2 -4
  513. package/dist/domain/domain-context/action-system/action-system.js +1 -1
  514. package/dist/domain/domain-context/action-system/index.d.ts +1 -0
  515. package/dist/domain/domain-context/action-system/index.js +1 -0
  516. package/dist/domain/domain-context/analytics-system/index.d.ts +2 -0
  517. package/dist/domain/domain-context/analytics-system/index.js +1 -0
  518. package/dist/domain/domain-context/create-transformer.d.ts +2 -2
  519. package/dist/domain/domain-context/dimension-system/dimension-state.js +1 -1
  520. package/dist/domain/domain-context/dimension-system/dimension-system.d.ts +3 -4
  521. package/dist/domain/domain-context/dimension-system/dimension-system.js +1 -1
  522. package/dist/domain/domain-context/dimension-system/index.d.ts +1 -0
  523. package/dist/domain/domain-context/dimension-system/index.js +1 -0
  524. package/dist/domain/domain-context/domain-context-systems.d.ts +8 -10
  525. package/dist/domain/domain-context/index.d.ts +8 -18
  526. package/dist/domain/domain-context/index.js +1 -1
  527. package/dist/domain/domain-context/navigation-system/index.d.ts +3 -0
  528. package/dist/domain/domain-context/navigation-system/index.js +1 -0
  529. package/dist/domain/domain-context/navigation-system/navigation-system.d.ts +5 -8
  530. package/dist/domain/domain-context/navigation-system/navigation-system.js +1 -1
  531. package/dist/domain/domain-context/navigation-system/navigation-updates.d.ts +1 -12
  532. package/dist/domain/domain-context/navigation-system/navigation-updates.js +1 -1
  533. package/dist/domain/domain-context/navigation-system/selection-transformers.d.ts +2 -2
  534. package/dist/domain/domain-context/navigation-system/selection-transformers.js +1 -1
  535. package/dist/domain/domain-context/recipe-system/index.d.ts +1 -0
  536. package/dist/domain/domain-context/recipe-system/index.js +1 -0
  537. package/dist/domain/domain-context/recipe-system/recipe-cooker-system.d.ts +2 -4
  538. package/dist/domain/domain-context/recipe-system/recipe-cooker-system.js +1 -1
  539. package/dist/domain/domain-context/relation-transformers/cascade-deletes.d.ts +3 -3
  540. package/dist/domain/domain-context/relation-transformers/cascade-deletes.js +1 -1
  541. package/dist/domain/domain-context/relation-transformers/cascade-ref-pruning.d.ts +3 -3
  542. package/dist/domain/domain-context/relation-transformers/cascade-ref-pruning.js +1 -1
  543. package/dist/domain/domain-context/relation-transformers/create-entity-refs.d.ts +3 -3
  544. package/dist/domain/domain-context/relation-transformers/create-entity-refs.js +2 -2
  545. package/dist/domain/domain-context/relation-transformers/index.d.ts +4 -0
  546. package/dist/domain/domain-context/relation-transformers/index.js +1 -0
  547. package/dist/domain/domain-context/relation-transformers/replace-refs.d.ts +3 -3
  548. package/dist/domain/domain-context/relation-transformers/replace-refs.js +1 -1
  549. package/dist/domain/domain-context/store-system/apply-updates.d.ts +1 -3
  550. package/dist/domain/domain-context/store-system/apply-updates.js +1 -1
  551. package/dist/domain/domain-context/store-system/index.d.ts +4 -0
  552. package/dist/domain/domain-context/store-system/index.js +1 -0
  553. package/dist/domain/domain-context/store-system/next-domain-entity-id.d.ts +1 -13
  554. package/dist/domain/domain-context/store-system/next-domain-entity-id.js +1 -1
  555. package/dist/domain/domain-context/store-system/reconcile-projected-entities.d.ts +1 -2
  556. package/dist/domain/domain-context/store-system/reconcile-projected-entities.js +1 -1
  557. package/dist/domain/domain-context/store-system/store-system.d.ts +2 -39
  558. package/dist/domain/domain-context/store-system/store-system.js +2 -2
  559. package/dist/domain/domain-context/store-system/system-state.d.ts +2 -4
  560. package/dist/domain/domain-context/store-system/system-state.js +1 -1
  561. package/dist/domain/domain-context/transformers/actions-builder.d.ts +1 -2
  562. package/dist/domain/domain-context/transformers/actions-builder.js +1 -1
  563. package/dist/domain/domain-context/transformers/auto-snap-transformer.d.ts +6 -10
  564. package/dist/domain/domain-context/transformers/auto-snap-transformer.js +1 -1
  565. package/dist/domain/domain-context/transformers/chain-builder.d.ts +3 -3
  566. package/dist/domain/domain-context/transformers/context.d.ts +2 -7
  567. package/dist/domain/domain-context/transformers/context.js +1 -1
  568. package/dist/domain/domain-context/transformers/entity-builder.d.ts +1 -5
  569. package/dist/domain/domain-context/transformers/entity-builder.js +1 -1
  570. package/dist/domain/domain-context/transformers/index.d.ts +1 -1
  571. package/dist/domain/domain-context/transformers/index.js +1 -1
  572. package/dist/domain/domain-context/transformers/then-builder.d.ts +1 -2
  573. package/dist/domain/domain-context/transformers/then-builder.js +1 -1
  574. package/dist/domain/domain-context/transformers/when-builder.d.ts +3 -9
  575. package/dist/domain/domain-context/transformers/when-builder.js +1 -1
  576. package/dist/domain/domain-context/undo-system.d.ts +29 -3
  577. package/dist/domain/domain-context/undo-system.js +1 -1
  578. package/dist/domain/domain-runtime/access/selectors.d.ts +9 -0
  579. package/dist/domain/domain-runtime/access/selectors.js +1 -1
  580. package/dist/domain/domain-runtime/access/types.d.ts +2 -0
  581. package/dist/domain/domain-runtime/access/types.js +1 -1
  582. package/dist/domain/domain-runtime/domain-context/action-context.d.ts +11 -0
  583. package/dist/domain/domain-runtime/domain-context/action-context.js +1 -0
  584. package/dist/domain/domain-runtime/domain-context/component-reader.d.ts +1 -4
  585. package/dist/domain/domain-runtime/domain-context/component-reader.js +1 -1
  586. package/dist/domain/domain-runtime/domain-context/domain-context.js +1 -1
  587. package/dist/domain/domain-runtime/domain-context/expression-context.d.ts +8 -0
  588. package/dist/domain/domain-runtime/domain-context/expression-context.js +1 -0
  589. package/dist/domain/domain-runtime/domain-context/index.d.ts +3 -0
  590. package/dist/domain/domain-runtime/domain-context/runtime-expression-context.d.ts +18 -0
  591. package/dist/domain/domain-runtime/domain-context/runtime-expression-context.js +1 -0
  592. package/dist/domain/domain-runtime/domain-context/selectors.d.ts +19 -25
  593. package/dist/domain/domain-runtime/domain-context/selectors.js +1 -1
  594. package/dist/domain/domain-runtime/domain-context/types.d.ts +4 -1
  595. package/dist/domain/domain-runtime/domain-runtime.d.ts +88 -72
  596. package/dist/domain/domain-runtime/errors/implementation-error-collector.d.ts +19 -0
  597. package/dist/domain/domain-runtime/errors/implementation-error-collector.js +1 -0
  598. package/dist/domain/domain-runtime/errors/implementation-error.d.ts +46 -0
  599. package/dist/domain/domain-runtime/errors/implementation-error.js +1 -0
  600. package/dist/domain/domain-runtime/errors/implementation-errors.d.ts +13 -0
  601. package/dist/domain/domain-runtime/errors/implementation-errors.js +1 -0
  602. package/dist/domain/domain-runtime/errors/index.d.ts +4 -0
  603. package/dist/domain/domain-runtime/errors/index.js +1 -0
  604. package/dist/domain/domain-runtime/errors/to-implementation-errors.d.ts +10 -0
  605. package/dist/domain/domain-runtime/errors/to-implementation-errors.js +1 -0
  606. package/dist/domain/domain-runtime/index.d.ts +1 -0
  607. package/dist/domain/domain-runtime/index.js +1 -1
  608. package/dist/domain/domain-runtime/spec/create-violation-expressions.d.ts +24 -0
  609. package/dist/domain/domain-runtime/spec/create-violation-expressions.js +1 -0
  610. package/dist/domain/domain-runtime/spec/index.d.ts +5 -0
  611. package/dist/domain/domain-runtime/spec/index.js +1 -1
  612. package/dist/domain/domain-runtime/spec/initialize.d.ts +11 -0
  613. package/dist/domain/domain-runtime/spec/initialize.js +1 -1
  614. package/dist/domain/{expressions → domain-runtime/spec}/memoize-expression.d.ts +1 -2
  615. package/dist/domain/{application → domain-runtime/spec/module-checks}/element-registration.d.ts +6 -1
  616. package/dist/domain/domain-runtime/spec/module-checks/element-registration.js +1 -0
  617. package/dist/domain/{application → domain-runtime/spec/module-checks}/handler-registration.d.ts +5 -0
  618. package/dist/domain/domain-runtime/spec/module-checks/handler-registration.js +1 -0
  619. package/dist/domain/domain-runtime/spec/module-checks/index.d.ts +8 -0
  620. package/dist/domain/domain-runtime/spec/module-checks/index.js +1 -0
  621. package/dist/domain/domain-runtime/spec/module-checks/project-modules.d.ts +32 -0
  622. package/dist/domain/domain-runtime/spec/module-checks/project-modules.js +1 -0
  623. package/dist/domain/domain-runtime/spec/module-checks/resolve-action-modules.d.ts +4 -0
  624. package/dist/domain/domain-runtime/spec/module-checks/resolve-action-modules.js +1 -0
  625. package/dist/domain/domain-runtime/spec/module-checks/resolve-component-optic-modules.d.ts +4 -0
  626. package/dist/domain/domain-runtime/spec/module-checks/resolve-component-optic-modules.js +1 -0
  627. package/dist/domain/domain-runtime/spec/module-checks/resolve-expression-modules.d.ts +15 -0
  628. package/dist/domain/domain-runtime/spec/module-checks/resolve-expression-modules.js +1 -0
  629. package/dist/domain/domain-runtime/spec/module-checks/resolve-imported-transformers.d.ts +22 -0
  630. package/dist/domain/domain-runtime/spec/module-checks/resolve-imported-transformers.js +1 -0
  631. package/dist/domain/domain-runtime/spec/module-checks/resolve-invariant-modules.d.ts +6 -0
  632. package/dist/domain/domain-runtime/spec/module-checks/resolve-invariant-modules.js +1 -0
  633. package/dist/domain/{actions → domain-runtime/spec}/prepare-action-implementations.d.ts +1 -1
  634. package/dist/domain/domain-runtime/spec/prepare-action-implementations.js +5 -0
  635. package/dist/domain/{expressions → domain-runtime/spec}/prepare-domain-expressions.d.ts +1 -1
  636. package/dist/domain/domain-runtime/spec/selectors.d.ts +7 -8
  637. package/dist/domain/domain-runtime/spec/selectors.js +1 -1
  638. package/dist/domain/domain-runtime/spec/types.d.ts +8 -6
  639. package/dist/domain/{system-components → extensions}/angle-indicator.d.ts +1 -1
  640. package/dist/domain/extensions/angle-indicator.js +1 -0
  641. package/dist/domain/{system-components → extensions}/attribute-switch.d.ts +1 -1
  642. package/dist/domain/extensions/attribute-switch.js +1 -0
  643. package/dist/domain/{system-components → extensions}/autorotate-camera-component.d.ts +1 -1
  644. package/dist/domain/extensions/autorotate-camera-component.js +1 -0
  645. package/dist/domain/{system-components → extensions}/basic-sidebar-footer.d.ts +1 -1
  646. package/dist/domain/extensions/basic-sidebar-footer.js +1 -0
  647. package/dist/domain/extensions/camera-based-hidden-entity-object.js +1 -0
  648. package/dist/domain/extensions/camera-based-hidden-entity.js +1 -0
  649. package/dist/domain/{system-components → extensions}/canvas-config.d.ts +2 -2
  650. package/dist/domain/extensions/canvas-config.js +1 -0
  651. package/dist/domain/extensions/canvas-custom-environment.js +1 -0
  652. package/dist/domain/{system-components → extensions}/canvas-padding.d.ts +1 -1
  653. package/dist/domain/extensions/canvas-padding.js +1 -0
  654. package/dist/domain/{system-components → extensions}/canvas-preset-environment.d.ts +1 -1
  655. package/dist/domain/extensions/canvas-preset-environment.js +1 -0
  656. package/dist/domain/{system-components → extensions}/child-entity-recipe-hierarchy.d.ts +2 -2
  657. package/dist/domain/extensions/child-entity-recipe-hierarchy.js +1 -0
  658. package/dist/domain/{system-components → extensions}/client-side-image.d.ts +2 -2
  659. package/dist/domain/extensions/client-side-image.js +1 -0
  660. package/dist/domain/extensions/color-value.js +1 -0
  661. package/dist/domain/{system-components → extensions}/common/angle-indicator-schema.d.ts +3 -3
  662. package/dist/domain/extensions/common/angle-indicator-schema.js +1 -0
  663. package/dist/domain/{system-components → extensions}/common/dimension-indicator-schema.d.ts +3 -3
  664. package/dist/domain/extensions/common/dimension-indicator-schema.js +1 -0
  665. package/dist/domain/extensions/common/index.d.ts +4 -0
  666. package/dist/domain/extensions/common/index.js +1 -0
  667. package/dist/domain/{system-components → extensions}/custom-header.d.ts +2 -2
  668. package/dist/domain/{system-components → extensions}/custom-header.js +1 -1
  669. package/dist/domain/extensions/custom-price-footer.js +1 -0
  670. package/dist/domain/{system-components → extensions}/custom-sidebar-footer.d.ts +2 -2
  671. package/dist/domain/{system-components → extensions}/custom-sidebar-footer.js +1 -1
  672. package/dist/domain/{system-components → extensions}/dimension-indicator.d.ts +1 -1
  673. package/dist/domain/extensions/dimension-indicator.js +1 -0
  674. package/dist/domain/{system-components → extensions}/editable-angle-indicator.d.ts +4 -3
  675. package/dist/domain/extensions/editable-angle-indicator.js +1 -0
  676. package/dist/domain/{system-components → extensions}/editable-distance-dimension-indicator.d.ts +4 -3
  677. package/dist/domain/extensions/editable-distance-dimension-indicator.js +1 -0
  678. package/dist/domain/{system-components → extensions}/editable-offset-indicator.d.ts +4 -3
  679. package/dist/domain/extensions/editable-offset-indicator.js +1 -0
  680. package/dist/domain/{system-components → extensions}/editable-size-indicator.d.ts +3 -2
  681. package/dist/domain/extensions/editable-size-indicator.js +1 -0
  682. package/dist/domain/{system-components → extensions}/entity-header.d.ts +1 -1
  683. package/dist/domain/extensions/entity-header.js +1 -0
  684. package/dist/domain/{system-components → extensions}/exit-tool-mode-fab.d.ts +1 -1
  685. package/dist/domain/extensions/exit-tool-mode-fab.js +1 -0
  686. package/dist/domain/{system-components → extensions}/finalize-action.d.ts +1 -1
  687. package/dist/domain/extensions/finalize-action.js +1 -0
  688. package/dist/domain/extensions/finalize-component.js +1 -0
  689. package/dist/domain/extensions/fit-selection-orthographic-camera-component.js +1 -0
  690. package/dist/domain/extensions/fit-selection-perspective-camera-component.js +1 -0
  691. package/dist/domain/{system-components → extensions}/fixed-header.d.ts +1 -1
  692. package/dist/domain/extensions/fixed-header.js +1 -0
  693. package/dist/domain/{system-components → extensions}/floating-header.d.ts +1 -1
  694. package/dist/domain/extensions/floating-header.js +1 -0
  695. package/dist/domain/{system-components → extensions}/hidden-header.d.ts +1 -1
  696. package/dist/domain/extensions/hidden-header.js +1 -0
  697. package/dist/domain/{system-components → extensions}/hidden-sidebar-footer.d.ts +1 -1
  698. package/dist/domain/extensions/hidden-sidebar-footer.js +1 -0
  699. package/dist/domain/{system-components → extensions}/horizontal-tabs.d.ts +3 -3
  700. package/dist/domain/extensions/horizontal-tabs.js +1 -0
  701. package/dist/domain/extensions/index.d.ts +69 -0
  702. package/dist/domain/extensions/index.js +1 -0
  703. package/dist/domain/{system-components → extensions}/list-selector-dimension-indicator.d.ts +4 -3
  704. package/dist/domain/extensions/list-selector-dimension-indicator.js +1 -0
  705. package/dist/domain/extensions/multilingual-text-value.js +1 -0
  706. package/dist/domain/{system-components → extensions}/navigation-steps.d.ts +2 -2
  707. package/dist/domain/extensions/navigation-steps.js +1 -0
  708. package/dist/domain/{system-components → extensions}/offset-anchors.d.ts +1 -1
  709. package/dist/domain/extensions/offset-anchors.js +1 -0
  710. package/dist/domain/{system-components → extensions}/offset-indicator.d.ts +1 -2
  711. package/dist/domain/extensions/offset-indicator.js +1 -0
  712. package/dist/domain/{system-components → extensions}/offset.d.ts +1 -1
  713. package/dist/domain/extensions/offset.js +1 -0
  714. package/dist/domain/{system-components → extensions}/other-action.d.ts +1 -1
  715. package/dist/domain/extensions/other-action.js +1 -0
  716. package/dist/domain/{system-components → extensions}/property-page.d.ts +2 -2
  717. package/dist/domain/extensions/property-page.js +1 -0
  718. package/dist/domain/{system-components → extensions}/read-sidebar-footer.d.ts +1 -1
  719. package/dist/domain/extensions/read-sidebar-footer.js +1 -0
  720. package/dist/domain/{system-components → extensions}/replace-entity-recipe-hierarchy.d.ts +2 -2
  721. package/dist/domain/extensions/replace-entity-recipe-hierarchy.js +1 -0
  722. package/dist/domain/extensions/rotatable.js +1 -0
  723. package/dist/domain/{system-components → extensions}/rotation-handle.d.ts +2 -2
  724. package/dist/domain/extensions/rotation-handle.js +1 -0
  725. package/dist/domain/{system-components → extensions}/save-template-thumbnail.d.ts +2 -2
  726. package/dist/domain/extensions/save-template-thumbnail.js +1 -0
  727. package/dist/domain/{system-components → extensions}/scene-entity-add-fab.d.ts +1 -1
  728. package/dist/domain/extensions/scene-entity-add-fab.js +1 -0
  729. package/dist/domain/extensions/scene-entity-animation.js +1 -0
  730. package/dist/domain/extensions/scene-entity-control.js +1 -0
  731. package/dist/domain/{system-components → extensions}/scene-entity-delete-fab.d.ts +1 -1
  732. package/dist/domain/extensions/scene-entity-delete-fab.js +1 -0
  733. package/dist/domain/extensions/scene-entity-floor-object.js +1 -0
  734. package/dist/domain/{system-components → extensions}/scene-entity-html-hotspot.d.ts +1 -1
  735. package/dist/domain/extensions/scene-entity-html-hotspot.js +1 -0
  736. package/dist/domain/{system-components → extensions}/scene-entity-navigation-hotspot.d.ts +1 -1
  737. package/dist/domain/extensions/scene-entity-navigation-hotspot.js +1 -0
  738. package/dist/domain/{system-components → extensions}/scene-entity-object-html-hotspot.d.ts +1 -1
  739. package/dist/domain/extensions/scene-entity-object-html-hotspot.js +1 -0
  740. package/dist/domain/{system-components → extensions}/scene-entity-select-hotspot.d.ts +1 -1
  741. package/dist/domain/extensions/scene-entity-select-hotspot.js +1 -0
  742. package/dist/domain/extensions/scene-entity-wall-object.js +1 -0
  743. package/dist/domain/extensions/selectable-component.js +1 -0
  744. package/dist/domain/{system-components → extensions}/sidebar-tabs.d.ts +1 -1
  745. package/dist/domain/extensions/sidebar-tabs.js +1 -0
  746. package/dist/domain/extensions/simple-summary.js +1 -0
  747. package/dist/domain/{system-components → extensions}/size.d.ts +1 -1
  748. package/dist/domain/extensions/size.js +1 -0
  749. package/dist/domain/{system-components → extensions}/sketching-draw.d.ts +1 -1
  750. package/dist/domain/extensions/sketching-draw.js +1 -0
  751. package/dist/domain/{system-components → extensions}/sketching-edit.d.ts +2 -2
  752. package/dist/domain/extensions/sketching-edit.js +1 -0
  753. package/dist/domain/extensions/summary-component.js +1 -0
  754. package/dist/domain/extensions/text-value.js +1 -0
  755. package/dist/domain/extensions/tool-controls.js +1 -0
  756. package/dist/domain/{system-components → extensions}/tool-menu-fab.d.ts +2 -2
  757. package/dist/domain/extensions/tool-menu-fab.js +1 -0
  758. package/dist/domain/{system-components → extensions}/toolbar.d.ts +2 -2
  759. package/dist/domain/{system-components → extensions}/toolbar.js +1 -1
  760. package/dist/domain/{system-components → extensions}/universal-sidebar-footer.d.ts +1 -1
  761. package/dist/domain/extensions/universal-sidebar-footer.js +1 -0
  762. package/dist/domain/extensions/view-controls.js +1 -0
  763. package/dist/domain/index.d.ts +2 -32
  764. package/dist/domain/index.js +1 -1
  765. package/dist/domain/mocks/create-mock-domain-context.d.ts +1 -1
  766. package/dist/domain/mocks/create-mock-domain-runtime-services.d.ts +1 -1
  767. package/dist/domain/mocks/create-mock-domain-runtime.d.ts +30 -21
  768. package/dist/domain/mocks/create-mock-domain-runtime.js +1 -1
  769. package/dist/domain/mocks/create-mock-domain.d.ts +1 -1
  770. package/dist/domain/mocks/create-mock-domain.js +1 -1
  771. package/dist/domain/mocks/create-mock-state.d.ts +2 -2
  772. package/dist/domain/mocks/create-test-application-config.d.ts +59 -0
  773. package/dist/domain/mocks/create-test-application-config.js +1 -0
  774. package/dist/domain/mocks/index.d.ts +1 -0
  775. package/dist/domain/mocks/index.js +1 -1
  776. package/dist/domain/projections/blender-projections/blender-projections.d.ts +2 -3
  777. package/dist/domain/projections/blender-projections/blender-projections.js +1 -1
  778. package/dist/domain/projections/blender-projections/camera.d.ts +2 -2
  779. package/dist/domain/projections/collision-projections.d.ts +1 -1
  780. package/dist/domain/projections/composition-projections.d.ts +1 -1
  781. package/dist/domain/projections/composition-projections.js +1 -1
  782. package/dist/domain/projections/constraint-projections.d.ts +1 -1
  783. package/dist/domain/projections/index.d.ts +1 -1
  784. package/dist/domain/projections/position-projections.d.ts +1 -1
  785. package/dist/domain/projections/projected-entities-projections.d.ts +2 -3
  786. package/dist/domain/projections/snap-matching.d.ts +1 -2
  787. package/dist/domain/projections/snap-utils.d.ts +1 -2
  788. package/dist/domain/projections/snapping-projections.d.ts +1 -1
  789. package/dist/domain/projections/validation-projections.d.ts +3 -20
  790. package/dist/domain/projections/validation-projections.js +1 -1
  791. package/dist/domain/sketching/sketching-projections.d.ts +1 -1
  792. package/dist/domain/system-components/admin-sidebar.d.ts +1 -1
  793. package/dist/domain/system-components/admin-sidebar.js +1 -1
  794. package/dist/domain/system-components/angle-dimension.js +1 -1
  795. package/dist/domain/system-components/application-config.d.ts +3 -3
  796. package/dist/domain/system-components/application-config.js +1 -1
  797. package/dist/domain/system-components/attribute-selection.js +1 -1
  798. package/dist/domain/system-components/bounding-box-component.d.ts +1 -1
  799. package/dist/domain/system-components/bounding-box-component.js +1 -1
  800. package/dist/domain/system-components/bounding-sphere-component.d.ts +1 -1
  801. package/dist/domain/system-components/bounding-sphere-component.js +1 -1
  802. package/dist/domain/system-components/child-entity-recipes.d.ts +1 -1
  803. package/dist/domain/system-components/child-entity-recipes.js +1 -1
  804. package/dist/domain/system-components/child-entity-selection.d.ts +1 -2
  805. package/dist/domain/system-components/child-entity-selection.js +1 -1
  806. package/dist/domain/system-components/collision-box-geometry.d.ts +5 -3
  807. package/dist/domain/system-components/collision-box-geometry.js +1 -1
  808. package/dist/domain/system-components/collision-geometries.d.ts +1 -1
  809. package/dist/domain/system-components/collision-geometry.d.ts +5 -3
  810. package/dist/domain/system-components/collision-geometry.js +1 -1
  811. package/dist/domain/system-components/collision-point-geometry.d.ts +5 -3
  812. package/dist/domain/system-components/collision-point-geometry.js +1 -1
  813. package/dist/domain/system-components/common/index.d.ts +0 -4
  814. package/dist/domain/system-components/common/index.js +1 -1
  815. package/dist/domain/system-components/common/material-override-schema.d.ts +2 -2
  816. package/dist/domain/system-components/common/recipes.d.ts +21 -4
  817. package/dist/domain/system-components/common/recipes.js +1 -1
  818. package/dist/domain/system-components/constraint.d.ts +11 -24
  819. package/dist/domain/system-components/constraint.js +1 -1
  820. package/dist/domain/system-components/create-blueprint.d.ts +1 -1
  821. package/dist/domain/system-components/custom-scene-entity.d.ts +1 -1
  822. package/dist/domain/system-components/custom-scene-entity.js +1 -1
  823. package/dist/domain/system-components/detail.js +1 -1
  824. package/dist/domain/system-components/distance-dimension-select.d.ts +1 -1
  825. package/dist/domain/system-components/distance-dimension-select.js +1 -1
  826. package/dist/domain/system-components/distance-dimension.d.ts +1 -1
  827. package/dist/domain/system-components/distance-dimension.js +1 -1
  828. package/dist/domain/system-components/drag-handle.d.ts +2 -2
  829. package/dist/domain/system-components/drag-handle.js +1 -1
  830. package/dist/domain/system-components/fit-entity-orthographic-camera-component.js +1 -1
  831. package/dist/domain/system-components/fit-entity-perspective-camera-component.js +1 -1
  832. package/dist/domain/system-components/fit-root-orthographic-camera-component.js +1 -1
  833. package/dist/domain/system-components/fit-root-perspective-camera-component.js +1 -1
  834. package/dist/domain/system-components/generic-dimension.d.ts +1 -1
  835. package/dist/domain/system-components/generic-dimension.js +1 -1
  836. package/dist/domain/system-components/hidden-entity.js +1 -1
  837. package/dist/domain/system-components/hotspot-drag-handle.d.ts +2 -3
  838. package/dist/domain/system-components/hotspot-drag-handle.js +1 -1
  839. package/dist/domain/system-components/index.d.ts +0 -68
  840. package/dist/domain/system-components/index.js +1 -1
  841. package/dist/domain/system-components/material-override-property-transition.js +1 -1
  842. package/dist/domain/system-components/material-override-transformer.js +1 -1
  843. package/dist/domain/system-components/material-swatch.js +1 -1
  844. package/dist/domain/system-components/navigation-page.js +1 -1
  845. package/dist/domain/system-components/navigation-panel-list.js +1 -1
  846. package/dist/domain/system-components/phoenix-entity-elements/admin-data-table-sidebar.d.ts +1 -1
  847. package/dist/domain/system-components/phoenix-layout-elements/admin-connected-resource-page.d.ts +1 -1
  848. package/dist/domain/system-components/phoenix-layout-elements/admin-data-table-actions.d.ts +1 -1
  849. package/dist/domain/system-components/phoenix-layout-elements/admin-data-table-page.d.ts +1 -1
  850. package/dist/domain/system-components/pivot-point.d.ts +1 -1
  851. package/dist/domain/system-components/pivot-point.js +1 -1
  852. package/dist/domain/system-components/price.js +1 -1
  853. package/dist/domain/system-components/projected-entities.d.ts +1 -1
  854. package/dist/domain/system-components/projected-entities.js +1 -1
  855. package/dist/domain/system-components/replace-entity-recipes.d.ts +1 -1
  856. package/dist/domain/system-components/replace-entity-recipes.js +1 -1
  857. package/dist/domain/system-components/scene-entity-ambient-light.js +1 -1
  858. package/dist/domain/system-components/scene-entity-box-object.js +1 -1
  859. package/dist/domain/system-components/scene-entity-directional-light.js +1 -1
  860. package/dist/domain/system-components/scene-entity-local-material-override.js +1 -1
  861. package/dist/domain/system-components/scene-entity-material-override.d.ts +1 -1
  862. package/dist/domain/system-components/scene-entity-material-override.js +1 -1
  863. package/dist/domain/system-components/scene-entity-object-clip.js +1 -1
  864. package/dist/domain/system-components/scene-entity-object-decal.js +1 -1
  865. package/dist/domain/system-components/scene-entity-object-group.js +1 -1
  866. package/dist/domain/system-components/scene-entity-object-local-material-override.js +1 -1
  867. package/dist/domain/system-components/scene-entity-object-material-override.d.ts +1 -1
  868. package/dist/domain/system-components/scene-entity-object-material-override.js +1 -1
  869. package/dist/domain/system-components/scene-entity-object-properties.js +1 -1
  870. package/dist/domain/system-components/scene-entity-object-remote-material-override.js +1 -1
  871. package/dist/domain/system-components/scene-entity-object-text-decal.d.ts +1 -1
  872. package/dist/domain/system-components/scene-entity-object-text-decal.js +1 -1
  873. package/dist/domain/system-components/scene-entity-object-transformer.js +1 -1
  874. package/dist/domain/system-components/scene-entity-object-transimissive-material-override.d.ts +1 -1
  875. package/dist/domain/system-components/scene-entity-object-transimissive-material-override.js +1 -1
  876. package/dist/domain/system-components/scene-entity-object.js +1 -1
  877. package/dist/domain/system-components/scene-entity-point-light.js +1 -1
  878. package/dist/domain/system-components/scene-entity-properties.js +1 -1
  879. package/dist/domain/system-components/scene-entity-rect-area-light.js +1 -1
  880. package/dist/domain/system-components/scene-entity-remote-material-override.js +1 -1
  881. package/dist/domain/system-components/scene-entity-remote-object.js +1 -1
  882. package/dist/domain/system-components/scene-entity-spot-light.js +1 -1
  883. package/dist/domain/system-components/scene-entity-transimissive-material-override.d.ts +1 -1
  884. package/dist/domain/system-components/scene-entity-transimissive-material-override.js +1 -1
  885. package/dist/domain/system-components/sketching-line.d.ts +1 -1
  886. package/dist/domain/system-components/sketching-line.js +1 -1
  887. package/dist/domain/system-components/sketching-rectangle.d.ts +1 -1
  888. package/dist/domain/system-components/sketching-rectangle.js +1 -1
  889. package/dist/domain/system-components/sketching.d.ts +1 -1
  890. package/dist/domain/system-components/sketching.js +1 -1
  891. package/dist/domain/system-components/snap-axis.d.ts +1 -1
  892. package/dist/domain/system-components/snap-axis.js +1 -1
  893. package/dist/domain/system-components/snap-edge.d.ts +1 -1
  894. package/dist/domain/system-components/snap-edge.js +1 -1
  895. package/dist/domain/system-components/snap-plane.d.ts +1 -1
  896. package/dist/domain/system-components/snap-plane.js +1 -1
  897. package/dist/domain/system-components/snap-point.d.ts +1 -1
  898. package/dist/domain/system-components/snap-point.js +1 -1
  899. package/dist/domain/system-components/static-orthographic-camera-component.d.ts +1 -1
  900. package/dist/domain/system-components/static-orthographic-camera-component.js +1 -1
  901. package/dist/domain/system-components/static-perspective-camera-component.d.ts +1 -1
  902. package/dist/domain/system-components/static-perspective-camera-component.js +1 -1
  903. package/dist/domain/utils/eval-cache.d.ts +2 -3
  904. package/dist/domain/utils/map-nested-object.d.ts +1 -1
  905. package/dist/domain/utils/memo-eval.d.ts +2 -4
  906. package/dist/editor/Editor.js +1 -1
  907. package/dist/editor/domain-editors/use-implementation-files.d.ts +8 -8
  908. package/dist/editor/domain-editors/use-implementation-files.js +1 -1
  909. package/dist/editor/mocks/EditorMockContext.js +1 -1
  910. package/dist/editor/variants/VariantAnswerFooter.d.ts +17 -0
  911. package/dist/editor/variants/VariantAnswerFooter.js +1 -0
  912. package/dist/editor/variants/VariantPreviewHandler.d.ts +6 -0
  913. package/dist/editor/variants/VariantPreviewHandler.js +1 -0
  914. package/dist/editor/variants/variant-preview-styles.d.ts +49 -0
  915. package/dist/editor/variants/variant-preview-styles.js +1 -0
  916. package/dist/i18n/locales/cs/messages.js +1 -1
  917. package/dist/i18n/locales/da/messages.js +1 -1
  918. package/dist/i18n/locales/de/messages.js +1 -1
  919. package/dist/i18n/locales/de-AT/messages.js +1 -1
  920. package/dist/i18n/locales/en/messages.js +1 -1
  921. package/dist/i18n/locales/en-AU/messages.js +1 -1
  922. package/dist/i18n/locales/en-GB/messages.js +1 -1
  923. package/dist/i18n/locales/es/messages.js +1 -1
  924. package/dist/i18n/locales/fr/messages.js +1 -1
  925. package/dist/i18n/locales/hu/messages.js +1 -1
  926. package/dist/i18n/locales/it/messages.js +1 -1
  927. package/dist/i18n/locales/nl/messages.js +1 -1
  928. package/dist/i18n/locales/pl/messages.js +1 -1
  929. package/dist/i18n/locales/pt/messages.js +1 -1
  930. package/dist/i18n/locales/ro/messages.js +1 -1
  931. package/dist/i18n/locales/sk/messages.js +1 -1
  932. package/dist/i18n/locales/tr/messages.js +1 -1
  933. package/dist/legacy/domain-cli/gen/configurator-builder.js +30 -16
  934. package/dist/legacy/domain-cli/gen/constant-expressions.js +1 -3
  935. package/dist/legacy/domain-cli/gen/defaults.js +1 -7
  936. package/dist/legacy/domain-cli/gen/implementation.js +7 -11
  937. package/dist/legacy/domain-cli/gen/invariants.js +7 -6
  938. package/dist/legacy/domain-cli/gen/relations.js +1 -1
  939. package/dist/legacy/domain-cli/utils/cache.d.ts +1 -1
  940. package/dist/legacy/domain-cli/utils/cache.js +1 -1
  941. package/dist/legacy/domain-spec/__generated/domain-schema.d.ts +81 -68
  942. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/claude-md.d.ts +1 -1
  943. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/cursor-rules.d.ts +1 -1
  944. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/shared-sdk-content.d.ts +1 -1
  945. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/agents/shared-sdk-content.js +7 -0
  946. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/domain-index.js +17 -15
  947. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/env-example.d.ts +1 -1
  948. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/env-example.js +4 -0
  949. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/frontend-admin.js +16 -15
  950. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/frontend-configurator.js +16 -15
  951. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/frontend-embedded-admin.js +23 -22
  952. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/frontend-embedded-configurator.js +16 -15
  953. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/generator-shared.d.ts +7 -2
  954. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/generator-shared.js +8 -12
  955. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/index.d.ts +1 -1
  956. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/index.js +1 -1
  957. package/dist/legacy/phoenix-cli/project-gen/generators/file-templates/webhook-builders.js +7 -7
  958. package/dist/phoenix/application-spec/application-spec-builder.js +1 -1
  959. package/dist/phoenix/application-spec/resolve-deployment-data.d.ts +4 -3
  960. package/dist/phoenix/application-spec/resolve-deployment-data.js +2 -2
  961. package/dist/phoenix/cli.js +1 -1
  962. package/dist/phoenix/commands/check.js +1 -1
  963. package/dist/phoenix/commands/deploy.js +1 -1
  964. package/dist/phoenix/commands/dev.js +5 -5
  965. package/dist/phoenix/commands/fix.js +1 -1
  966. package/dist/phoenix/commands/index.d.ts +1 -0
  967. package/dist/phoenix/commands/index.js +1 -1
  968. package/dist/phoenix/commands/init.d.ts +4 -4
  969. package/dist/phoenix/commands/init.js +1 -1
  970. package/dist/phoenix/commands/prepare-deployment-command.js +2 -2
  971. package/dist/phoenix/commands/prepare-local-environment-command.js +1 -1
  972. package/dist/phoenix/commands/prettier.js +1 -1
  973. package/dist/phoenix/commands/story-dev.js +2 -2
  974. package/dist/phoenix/commands/variants.d.ts +35 -0
  975. package/dist/phoenix/commands/variants.js +1 -0
  976. package/dist/phoenix/editor-server/commands-router.d.ts +68 -1
  977. package/dist/phoenix/editor-server/commands-router.js +1 -1
  978. package/dist/phoenix/editor-server/editor-router.d.ts +65 -0
  979. package/dist/phoenix/editor-server/editor-server.d.ts +3 -3
  980. package/dist/phoenix/editor-server/editor-server.js +1 -1
  981. package/dist/phoenix/editor-server/protocol.d.ts +1 -0
  982. package/dist/phoenix/editor-server/spec-router.js +1 -1
  983. package/dist/phoenix/editor-server/trpc.d.ts +3 -0
  984. package/dist/phoenix/scripts/build-admin.js +1 -1
  985. package/dist/phoenix/services/application-spec-project-watcher.d.ts +15 -2
  986. package/dist/phoenix/services/application-spec-project-watcher.js +3 -3
  987. package/dist/phoenix/services/index.d.ts +4 -0
  988. package/dist/phoenix/services/index.js +1 -1
  989. package/dist/phoenix/services/phoenix-application.d.ts +21 -0
  990. package/dist/phoenix/services/phoenix-application.js +1 -0
  991. package/dist/phoenix/services/phoenix-store.d.ts +48 -0
  992. package/dist/phoenix/services/phoenix-store.js +1 -0
  993. package/dist/phoenix/services/phx-variant-previews-plugin.d.ts +9 -0
  994. package/dist/phoenix/services/phx-variant-previews-plugin.js +1 -0
  995. package/dist/phoenix/services/prepare-development-environment.js +1 -1
  996. package/dist/phoenix/services/source-archive.d.ts +1 -0
  997. package/dist/phoenix/services/source-archive.js +1 -1
  998. package/dist/phoenix/services/swc.js +1 -1
  999. package/dist/phoenix/services/variant-file-session.d.ts +36 -0
  1000. package/dist/phoenix/services/variant-file-session.js +1 -0
  1001. package/dist/phoenix/services/variant-preview-service.d.ts +80 -0
  1002. package/dist/phoenix/services/variant-preview-service.js +1 -0
  1003. package/dist/phoenix/services/vite-config.js +1 -1
  1004. package/dist/phoenix/utils/git/index.d.ts +1 -0
  1005. package/dist/phoenix/utils/git/index.js +1 -0
  1006. package/dist/phoenix/utils/git/is-inside-git-repository.d.ts +6 -0
  1007. package/dist/phoenix/utils/git/is-inside-git-repository.js +1 -0
  1008. package/dist/phoenix/utils/index.d.ts +1 -0
  1009. package/dist/phoenix/utils/index.js +1 -1
  1010. package/dist/react-sdk/application-runtime/hooks/use-access-service.d.ts +2 -0
  1011. package/dist/react-sdk/application-runtime/hooks/use-domain-application-props.d.ts +5 -2
  1012. package/dist/react-sdk/application-runtime/hooks/use-spec.d.ts +1 -1
  1013. package/dist/react-sdk/application-runtime/mocks/create-mock-application-runtime.d.ts +1 -1
  1014. package/dist/react-sdk/application-runtime/mocks/create-mock-application-runtime.js +1 -1
  1015. package/dist/react-sdk/application-runtime/routing/access-service.d.ts +1 -0
  1016. package/dist/react-sdk/application-runtime/runtime/create-application-runtime.d.ts +57 -40
  1017. package/dist/react-sdk/application-runtime/runtime/create-application-runtime.js +1 -1
  1018. package/dist/react-sdk/domain-context/common-hooks/use-configurator-messages.d.ts +30 -0
  1019. package/dist/react-sdk/domain-context/common-hooks/use-configurator-messages.js +1 -0
  1020. package/dist/react-sdk/domain-context/common-hooks/use-domain-context.d.ts +9 -9
  1021. package/dist/react-sdk/domain-context/index.d.ts +2 -1
  1022. package/dist/react-sdk/domain-context/index.js +1 -1
  1023. package/dist/react-sdk/domain-context/system-hooks/navigation-hooks.d.ts +11 -0
  1024. package/dist/react-sdk/domain-context/system-hooks/navigation-hooks.js +1 -1
  1025. package/dist/react-sdk/domain-controls/hooks/use-angle-dimension.d.ts +1 -1
  1026. package/dist/ui-sdk/theme/types.d.ts +1 -1
  1027. package/dist/webhook/builders/common.d.ts +8 -0
  1028. package/dist/webhook/builders/webhook-builder.d.ts +1 -1
  1029. package/dist/webhook/builders/webhook-builder.js +1 -1
  1030. package/package.json +33 -26
  1031. package/schema/domain-spec.json +1115 -209
  1032. package/dist/application-spec/generate-application/code/templates/env-ci.d.ts +0 -1
  1033. package/dist/application-spec/generate-application/code/templates/env-ci.js +0 -25
  1034. package/dist/application-spec/generate-application/code/templates/env-example.d.ts +0 -1
  1035. package/dist/application-spec/generate-application/code/templates/env-example.js +0 -90
  1036. package/dist/application-spec/generate-application/code/templates/prettier-ignore.d.ts +0 -1
  1037. package/dist/application-spec/generate-application/code/templates/prettier-ignore.js +0 -6
  1038. package/dist/application-spec/generate-application/code/templates/private-assets-readme.d.ts +0 -1
  1039. package/dist/application-spec/generate-application/code/templates/private-assets-readme.js +0 -24
  1040. package/dist/application-spec/generate-application/code/templates/public-assets-readme.d.ts +0 -1
  1041. package/dist/application-spec/generate-application/code/templates/public-assets-readme.js +0 -24
  1042. package/dist/application-spec/generate-application/code/templates/readme.d.ts +0 -1
  1043. package/dist/application-spec/generate-application/code/templates/readme.js +0 -23
  1044. package/dist/application-spec/generate-application/code/templates/skills/agents-md.d.ts +0 -1
  1045. package/dist/application-spec/generate-application/code/templates/skills/agents-md.js +0 -2
  1046. package/dist/application-spec/generate-application/code/templates/webhook-implementation.d.ts +0 -1
  1047. package/dist/application-spec/generate-application/code/templates/webhook-implementation.js +0 -7
  1048. package/dist/domain/actions/action-factory.d.ts +0 -10
  1049. package/dist/domain/actions/prepare-action-implementations.js +0 -5
  1050. package/dist/domain/application/element-registration.js +0 -1
  1051. package/dist/domain/application/handler-registration.js +0 -1
  1052. package/dist/domain/application/resolve-imported-transformers.d.ts +0 -13
  1053. package/dist/domain/application/resolve-imported-transformers.js +0 -1
  1054. package/dist/domain/application/resource-builder.d.ts +0 -13
  1055. package/dist/domain/application/resource-builder.js +0 -1
  1056. package/dist/domain/components/debug-state-component.js +0 -1
  1057. package/dist/domain/components/domain-component-optic.d.ts +0 -76
  1058. package/dist/domain/components/domain-component-optic.js +0 -16
  1059. package/dist/domain/components/drag-state-component.js +0 -1
  1060. package/dist/domain/components/export-state-component.js +0 -1
  1061. package/dist/domain/components/fullscreen-state-component.js +0 -1
  1062. package/dist/domain/components/group-components-by-entity-id-and-name.js +0 -1
  1063. package/dist/domain/components/hover-state-component.js +0 -1
  1064. package/dist/domain/components/index.d.ts +0 -2
  1065. package/dist/domain/components/index.js +0 -1
  1066. package/dist/domain/components/navigation-state-component.js +0 -1
  1067. package/dist/domain/components/selection-state-component.js +0 -1
  1068. package/dist/domain/components/system-component-builder.js +0 -1
  1069. package/dist/domain/components/system-virtual-entity.js +0 -1
  1070. package/dist/domain/components/tool-state-component.js +0 -1
  1071. package/dist/domain/components/view-state-component.js +0 -1
  1072. package/dist/domain/components/viewport-state-component.js +0 -1
  1073. package/dist/domain/core/expression.d.ts +0 -50
  1074. package/dist/domain/core/json-schema.js +0 -1
  1075. package/dist/domain/core/migrations.js +0 -1
  1076. package/dist/domain/core/reflection.js +0 -1
  1077. package/dist/domain/domain-context/recipe-system/recipes.d.ts +0 -57
  1078. package/dist/domain/domain-context/recipe-system/recipes.js +0 -1
  1079. package/dist/domain/expressions/constant-expressions.d.ts +0 -9
  1080. package/dist/domain/expressions/constant-expressions.js +0 -1
  1081. package/dist/domain/expressions/custom-expression.d.ts +0 -13
  1082. package/dist/domain/expressions/custom-expression.js +0 -1
  1083. package/dist/domain/expressions/index.d.ts +0 -1
  1084. package/dist/domain/expressions/index.js +0 -1
  1085. package/dist/domain/invariants/custom-invariants.d.ts +0 -22
  1086. package/dist/domain/invariants/custom-invariants.js +0 -1
  1087. package/dist/domain/invariants/invariants.d.ts +0 -68
  1088. package/dist/domain/invariants/invariants.js +0 -1
  1089. package/dist/domain/invariants/violation-component.js +0 -1
  1090. package/dist/domain/invariants/violation.js +0 -1
  1091. package/dist/domain/relations/all-of-type-relation.d.ts +0 -33
  1092. package/dist/domain/relations/all-of-type-relation.js +0 -1
  1093. package/dist/domain/relations/component-factories.js +0 -1
  1094. package/dist/domain/relations/composed-of.js +0 -1
  1095. package/dist/domain/relations/direct-relation.d.ts +0 -29
  1096. package/dist/domain/relations/indirect-relation.d.ts +0 -81
  1097. package/dist/domain/relations/indirect-relation.js +0 -1
  1098. package/dist/domain/relations/reference-component.js +0 -1
  1099. package/dist/domain/relations/relation-factory.d.ts +0 -19
  1100. package/dist/domain/relations/relation-factory.js +0 -1
  1101. package/dist/domain/relations/relation-implementation.d.ts +0 -8
  1102. package/dist/domain/system-components/angle-indicator.js +0 -1
  1103. package/dist/domain/system-components/attribute-switch.js +0 -1
  1104. package/dist/domain/system-components/autorotate-camera-component.js +0 -1
  1105. package/dist/domain/system-components/basic-sidebar-footer.js +0 -1
  1106. package/dist/domain/system-components/camera-based-hidden-entity-object.js +0 -1
  1107. package/dist/domain/system-components/camera-based-hidden-entity.js +0 -1
  1108. package/dist/domain/system-components/canvas-config.js +0 -1
  1109. package/dist/domain/system-components/canvas-custom-environment.js +0 -1
  1110. package/dist/domain/system-components/canvas-padding.js +0 -1
  1111. package/dist/domain/system-components/canvas-preset-environment.js +0 -1
  1112. package/dist/domain/system-components/child-entity-recipe-hierarchy.js +0 -1
  1113. package/dist/domain/system-components/client-side-image.js +0 -1
  1114. package/dist/domain/system-components/color-value.js +0 -1
  1115. package/dist/domain/system-components/common/angle-indicator-schema.js +0 -1
  1116. package/dist/domain/system-components/common/dimension-indicator-schema.js +0 -1
  1117. package/dist/domain/system-components/custom-price-footer.js +0 -1
  1118. package/dist/domain/system-components/dimension-indicator.js +0 -1
  1119. package/dist/domain/system-components/editable-angle-indicator.js +0 -1
  1120. package/dist/domain/system-components/editable-distance-dimension-indicator.js +0 -1
  1121. package/dist/domain/system-components/editable-offset-indicator.js +0 -1
  1122. package/dist/domain/system-components/editable-size-indicator.js +0 -1
  1123. package/dist/domain/system-components/entity-header.js +0 -1
  1124. package/dist/domain/system-components/exit-tool-mode-fab.js +0 -1
  1125. package/dist/domain/system-components/finalize-action.js +0 -1
  1126. package/dist/domain/system-components/finalize-component.js +0 -1
  1127. package/dist/domain/system-components/fit-selection-orthographic-camera-component.js +0 -1
  1128. package/dist/domain/system-components/fit-selection-perspective-camera-component.js +0 -1
  1129. package/dist/domain/system-components/fixed-header.js +0 -1
  1130. package/dist/domain/system-components/floating-header.js +0 -1
  1131. package/dist/domain/system-components/hidden-header.js +0 -1
  1132. package/dist/domain/system-components/hidden-sidebar-footer.js +0 -1
  1133. package/dist/domain/system-components/horizontal-tabs.js +0 -1
  1134. package/dist/domain/system-components/list-selector-dimension-indicator.js +0 -1
  1135. package/dist/domain/system-components/multilingual-text-value.js +0 -1
  1136. package/dist/domain/system-components/navigation-steps.js +0 -1
  1137. package/dist/domain/system-components/offset-anchors.js +0 -1
  1138. package/dist/domain/system-components/offset-indicator.js +0 -1
  1139. package/dist/domain/system-components/offset.js +0 -1
  1140. package/dist/domain/system-components/other-action.js +0 -1
  1141. package/dist/domain/system-components/property-page.js +0 -1
  1142. package/dist/domain/system-components/read-sidebar-footer.js +0 -1
  1143. package/dist/domain/system-components/replace-entity-recipe-hierarchy.js +0 -1
  1144. package/dist/domain/system-components/rotatable.js +0 -1
  1145. package/dist/domain/system-components/rotation-handle.js +0 -1
  1146. package/dist/domain/system-components/save-template-thumbnail.js +0 -1
  1147. package/dist/domain/system-components/scene-entity-add-fab.js +0 -1
  1148. package/dist/domain/system-components/scene-entity-animation.js +0 -1
  1149. package/dist/domain/system-components/scene-entity-control.js +0 -1
  1150. package/dist/domain/system-components/scene-entity-delete-fab.js +0 -1
  1151. package/dist/domain/system-components/scene-entity-floor-object.js +0 -1
  1152. package/dist/domain/system-components/scene-entity-html-hotspot.js +0 -1
  1153. package/dist/domain/system-components/scene-entity-navigation-hotspot.js +0 -1
  1154. package/dist/domain/system-components/scene-entity-object-html-hotspot.js +0 -1
  1155. package/dist/domain/system-components/scene-entity-select-hotspot.js +0 -1
  1156. package/dist/domain/system-components/scene-entity-wall-object.js +0 -1
  1157. package/dist/domain/system-components/selectable-component.js +0 -1
  1158. package/dist/domain/system-components/sidebar-tabs.js +0 -1
  1159. package/dist/domain/system-components/simple-summary.js +0 -1
  1160. package/dist/domain/system-components/size.js +0 -1
  1161. package/dist/domain/system-components/sketching-draw.js +0 -1
  1162. package/dist/domain/system-components/sketching-edit.js +0 -1
  1163. package/dist/domain/system-components/summary-component.js +0 -1
  1164. package/dist/domain/system-components/text-value.js +0 -1
  1165. package/dist/domain/system-components/tool-controls.js +0 -1
  1166. package/dist/domain/system-components/tool-menu-fab.js +0 -1
  1167. package/dist/domain/system-components/universal-sidebar-footer.js +0 -1
  1168. package/dist/domain/system-components/view-controls.js +0 -1
  1169. package/dist/phoenix/application-spec/generate-assistant-prompt.d.ts +0 -13
  1170. package/dist/phoenix/application-spec/generate-assistant-prompt.js +0 -1
  1171. /package/dist/{domain/components/component-reader-types.js → application-spec/core/expressions/relation-expression-params.js} +0 -0
  1172. /package/dist/application-spec/{core → enhanced-json}/eval-code-uses-self.d.ts +0 -0
  1173. /package/dist/application-spec/{core → enhanced-json}/eval-code-uses-self.js +0 -0
  1174. /package/dist/application-spec/{core → enhanced-json}/eval-to-constant.js +0 -0
  1175. /package/dist/domain/{core/action.js → application/assistant/assistant-manifest.js} +0 -0
  1176. /package/dist/domain/{actions → core/action}/action-factory.js +0 -0
  1177. /package/dist/domain/core/{enhanced-json-schema.js → action/action.js} +0 -0
  1178. /package/dist/domain/core/{expression.js → component/component-reader-types.js} +0 -0
  1179. /package/dist/domain/{components → core/component}/legacy-filter.js +0 -0
  1180. /package/dist/domain/{components → core/component}/system-component-filter.js +0 -0
  1181. /package/dist/domain/{components → core/component}/system-virtual-entity.d.ts +0 -0
  1182. /package/dist/domain/{components → core/component}/updates.js +0 -0
  1183. /package/dist/domain/core/{schema-validator.js → data-schema/data-cell-value.js} +0 -0
  1184. /package/dist/domain/{relations/reference-field.js → core/data-schema/data-schema-array-field.js} +0 -0
  1185. /package/dist/domain/{relations/relation-implementation.js → core/data-schema/data-schema-field-object.js} +0 -0
  1186. /package/dist/domain/{invariants → core/invariant}/builtin-invariant.js +0 -0
  1187. /package/dist/domain/{invariants → core/invariant}/violation-component.d.ts +0 -0
  1188. /package/dist/domain/core/{alias.d.ts → model/alias.d.ts} +0 -0
  1189. /package/dist/domain/core/{alias.js → model/alias.js} +0 -0
  1190. /package/dist/domain/core/{common-definitions.d.ts → model/common-definitions.d.ts} +0 -0
  1191. /package/dist/domain/core/{common-definitions.js → model/common-definitions.js} +0 -0
  1192. /package/dist/domain/core/{component.js → model/component.js} +0 -0
  1193. /package/dist/domain/core/{model.js → model/model.js} +0 -0
  1194. /package/dist/domain/core/{preset.d.ts → model/preset.d.ts} +0 -0
  1195. /package/dist/domain/core/{preset.js → model/preset.js} +0 -0
  1196. /package/dist/domain/{relations → core/relation}/direct-relation.js +0 -0
  1197. /package/dist/domain/core/{schema-validator-ajv.d.ts → schema/create-ajv-schema-validator.d.ts} +0 -0
  1198. /package/dist/domain/core/{schema-validator-ajv.js → schema/create-ajv-schema-validator.js} +0 -0
  1199. /package/dist/domain/core/{schema-validator-csp.d.ts → schema/create-csp-schema-validator.d.ts} +0 -0
  1200. /package/dist/domain/core/{schema-validator-csp.js → schema/create-csp-schema-validator.js} +0 -0
  1201. /package/dist/domain/core/{enhanced-json-schema.d.ts → schema/enhanced-json-schema.d.ts} +0 -0
  1202. /package/dist/domain/core/{json-schema.d.ts → schema/extended-schema-validator.d.ts} +0 -0
  1203. /package/dist/domain/core/{schema-validator.d.ts → schema/schema-validator.d.ts} +0 -0
  1204. /package/dist/domain/core/{state.js → state/state.js} +0 -0
  1205. /package/dist/domain/{expressions → domain-runtime/spec}/memoize-expression.js +0 -0
  1206. /package/dist/domain/{expressions → domain-runtime/spec}/prepare-domain-expressions.js +0 -0
  1207. /package/dist/domain/{system-components → extensions}/camera-based-hidden-entity-object.d.ts +0 -0
  1208. /package/dist/domain/{system-components → extensions}/camera-based-hidden-entity.d.ts +0 -0
  1209. /package/dist/domain/{system-components → extensions}/canvas-custom-environment.d.ts +0 -0
  1210. /package/dist/domain/{system-components → extensions}/color-value.d.ts +0 -0
  1211. /package/dist/domain/{system-components → extensions}/common/dimension-indicators.d.ts +0 -0
  1212. /package/dist/domain/{system-components → extensions}/common/dimension-indicators.js +0 -0
  1213. /package/dist/domain/{system-components → extensions}/common/editable-indicator-button-variant.d.ts +0 -0
  1214. /package/dist/domain/{system-components → extensions}/common/editable-indicator-button-variant.js +0 -0
  1215. /package/dist/domain/{system-components → extensions}/custom-price-footer.d.ts +0 -0
  1216. /package/dist/domain/{system-components → extensions}/finalize-component.d.ts +0 -0
  1217. /package/dist/domain/{system-components → extensions}/fit-selection-orthographic-camera-component.d.ts +0 -0
  1218. /package/dist/domain/{system-components → extensions}/fit-selection-perspective-camera-component.d.ts +0 -0
  1219. /package/dist/domain/{system-components → extensions}/multilingual-text-value.d.ts +0 -0
  1220. /package/dist/domain/{system-components → extensions}/rotatable.d.ts +0 -0
  1221. /package/dist/domain/{system-components → extensions}/scene-entity-animation.d.ts +0 -0
  1222. /package/dist/domain/{system-components → extensions}/scene-entity-control.d.ts +0 -0
  1223. /package/dist/domain/{system-components → extensions}/scene-entity-floor-object.d.ts +0 -0
  1224. /package/dist/domain/{system-components → extensions}/scene-entity-wall-object.d.ts +0 -0
  1225. /package/dist/domain/{system-components → extensions}/selectable-component.d.ts +0 -0
  1226. /package/dist/domain/{system-components → extensions}/simple-summary.d.ts +0 -0
  1227. /package/dist/domain/{system-components → extensions}/summary-component.d.ts +0 -0
  1228. /package/dist/domain/{system-components → extensions}/text-value.d.ts +0 -0
  1229. /package/dist/domain/{system-components → extensions}/tool-controls.d.ts +0 -0
  1230. /package/dist/domain/{system-components → extensions}/view-controls.d.ts +0 -0
@@ -0,0 +1,2125 @@
1
+ # Custom Layout
2
+
3
+ Make the configurator look like the brand's own product page. A furniture shop wants its table configurator to read like the rest of its site: a floating panel on the left with the product's name, the edge finishes as picture cards, the price in large type and one clear call to action, and the table in the space beside it. On a phone the panel becomes a sheet at the bottom, and a button on the table's edge offers the finishes right where they apply. The project writes that page as one React component. Phoenix supplies the 3D scene, the product's state and a few hooks; every pixel around the scene is the project's own.
4
+
5
+ - [Choose this approach](#choose-this-approach)
6
+ - [Register the layout](#register-the-layout)
7
+ - [What the layout has to do](#what-the-layout-has-to-do)
8
+ - [Read and change the product](#read-and-change-the-product)
9
+ - [Draw the choices](#draw-the-choices)
10
+ - [Offer fullscreen](#offer-fullscreen)
11
+ - [Show messages](#show-messages)
12
+ - [Request a quote](#request-a-quote)
13
+ - [Serve read mode](#serve-read-mode)
14
+ - [Fit a phone](#fit-a-phone)
15
+ - [Put a control on the product](#put-a-control-on-the-product)
16
+ - [The spec the layout reads](#the-spec-the-layout-reads)
17
+ - [Try the example](#try-the-example)
18
+ - [Test cases](#test-cases)
19
+ - [Known gaps](#known-gaps)
20
+ - [Related pages](#related-pages)
21
+ - [Complete Implementation](#complete-implementation)
22
+
23
+ ## Choose this approach
24
+
25
+ Write your own layout when the configurator has to follow a design the built-in layout cannot take on: the brand's typography, controls and spacing, a panel on the left, a top bar with the price, an area the built-in layout has no place for.
26
+
27
+ Stay with the built-in layout when its structure fits and only its parts change. [Universal](?path=/docs/layouts-universal--docs) demonstrates that layout, and the pages of its parts describe what the spec configures: [Header](?path=/docs/layouts-header-floating--docs), [Sidebar](?path=/docs/layouts-sidebar-general--docs), [Sidebar Footer](?path=/docs/layouts-sidebar-footer-basic--docs), [Toolbar](?path=/docs/layouts-toolbar--docs) and [View Controls](?path=/docs/layouts-view-controls--docs).
28
+
29
+ A project layout replaces all of that. None of the built-in sidebar, footer, toolbar or property pages is rendered for it, so the layout builds every control the user needs. This example uses no SDK component that draws UI: only the scene, the safe-area hook and hooks that read and change the product's state. A layout may also mount the SDK's own pieces where it wants them, see [Configurator Interface → Loaded Configuration UI](?path=/docs/phoenix-configurator-interface--docs#loaded-configuration-ui).
30
+
31
+ ## Register the layout
32
+
33
+ Create `src/elements/ConfiguratorLayout.tsx` with a default export made by `phx.frontend.createCustomPhoenixLayoutElement`. The name `ConfiguratorLayout` is what makes it the layout: Phoenix renders the element registered under that name for every loaded configuration, in owner and in read mode, whatever `layout` the spec sets. Nothing in the YAML refers to it.
34
+
35
+ ```tsx
36
+ import { Box } from '@mui/material'
37
+ import { useState } from 'react'
38
+
39
+ import { LayoutSlots, useSceneSafeArea } from '@salsita-ai/sdk/configurator'
40
+ import { useActiveViewports } from '@salsita-ai/sdk/react'
41
+
42
+ import { phx } from '../../.phoenix/phx.js'
43
+ import { BottomSheet } from '../_shared/BottomSheet.js'
44
+ import { FullscreenToggle } from '../_shared/FullscreenToggle.js'
45
+ import { MessageToast } from '../_shared/MessageToast.js'
46
+ import { ProductPanel } from '../_shared/ProductPanel.js'
47
+
48
+ const GUTTER = 24
49
+ const PANEL_WIDTH = 380
50
+ const PHONE_GUTTER = 16
51
+ const TOUCH_TARGET = 44
52
+
53
+ const ConfiguratorLayout = () => {
54
+ const { containerRef, safeAreaRef } = useSceneSafeArea()
55
+ const isFullscreen = phx.frontend.useIsFullscreenActive()
56
+ // `null` until the container has been measured; the layout shows only the scene until then
57
+ const viewports = useActiveViewports()
58
+ const [isSheetOpen, setSheetOpen] = useState(false)
59
+
60
+ const isDesktop = viewports?.includes('desktop') ?? false
61
+ const gutter = isDesktop ? GUTTER : PHONE_GUTTER
62
+
63
+ return (
64
+ <Box
65
+ component="div"
66
+ ref={containerRef}
67
+ // `clip`, not `hidden`: what overflows, such as the sheet's fill below its edge, can never be scrolled into view,
68
+ // not even by the browser when the focus moves
69
+ sx={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', overflow: 'clip' }}
70
+ >
71
+ <LayoutSlots.SharedCanvas />
72
+
73
+ {viewports !== null && isDesktop && !isFullscreen && (
74
+ <Box
75
+ component="aside"
76
+ sx={{
77
+ position: 'absolute',
78
+ insetBlockStart: GUTTER,
79
+ insetInlineStart: GUTTER,
80
+ inlineSize: PANEL_WIDTH,
81
+ maxBlockSize: `calc(100% - ${2 * GUTTER}px)`,
82
+ display: 'flex',
83
+ flexDirection: 'column',
84
+ borderRadius: 5,
85
+ bgcolor: 'background.paper',
86
+ boxShadow: '0 12px 40px rgba(0, 0, 0, 0.12)',
87
+ // A very short container, such as a phone held sideways, scrolls the panel instead of clipping its footer
88
+ overflowX: 'hidden',
89
+ overflowY: 'auto',
90
+ overscrollBehavior: 'contain',
91
+ }}
92
+ >
93
+ <ProductPanel />
94
+ </Box>
95
+ )}
96
+
97
+ {viewports !== null && (
98
+ <Box
99
+ component="div"
100
+ ref={safeAreaRef}
101
+ sx={{
102
+ // Desktop: right of the panel. Phone: above the sheet, whose height the column gives the rest.
103
+ ...(isDesktop
104
+ ? {
105
+ position: 'absolute',
106
+ insetBlock: 0,
107
+ insetInlineStart: isFullscreen ? 0 : PANEL_WIDTH + 2 * GUTTER,
108
+ insetInlineEnd: 0,
109
+ }
110
+ : { position: 'relative', flex: '1 1 0', minBlockSize: 0 }),
111
+ pointerEvents: 'none',
112
+ }}
113
+ >
114
+ <Box
115
+ component="div"
116
+ sx={{ position: 'absolute', insetBlockStart: gutter, insetInlineEnd: gutter, pointerEvents: 'auto' }}
117
+ >
118
+ <FullscreenToggle compact={!isDesktop} />
119
+ </Box>
120
+ <Box
121
+ component="div"
122
+ sx={{
123
+ position: 'absolute',
124
+ pointerEvents: 'auto',
125
+ // Desktop: bottom left. Phone: at the top beside the fullscreen button, clear of the sheet's handle.
126
+ ...(isDesktop
127
+ ? { insetBlockEnd: GUTTER, insetInlineStart: GUTTER }
128
+ : { insetBlockStart: gutter, insetInline: `${gutter}px ${2 * gutter + TOUCH_TARGET}px` }),
129
+ }}
130
+ >
131
+ <MessageToast />
132
+ </Box>
133
+ </Box>
134
+ )}
135
+
136
+ {viewports !== null && !isDesktop && !isFullscreen && (
137
+ <BottomSheet open={isSheetOpen} onOpenChange={setSheetOpen} label="Product details">
138
+ <ProductPanel collapsed={!isSheetOpen} inSheet />
139
+ </BottomSheet>
140
+ )}
141
+ </Box>
142
+ )
143
+ }
144
+
145
+ export default phx.frontend.createCustomPhoenixLayoutElement('ConfiguratorLayout', ConfiguratorLayout)
146
+ ```
147
+
148
+ The components it uses live in `src/_shared/`, which Phoenix does not import by convention, so the layout imports them itself. Application projects receive `phx` as a global. Storybook examples cannot rely on that project environment, so these files import it from `../../.phoenix/phx.js`; leave that import out in a project.
149
+
150
+ ## What the layout has to do
151
+
152
+ - **Position the root and cover the page.** `LayoutSlots.SharedCanvas` is absolutely positioned and fills its nearest positioned ancestor. The root here is `position: absolute; inset: 0`.
153
+ - **Mount the shared canvas.** The app loader stays on screen until the scene has loaded. A layout without `LayoutSlots.SharedCanvas` never gets past the loader unless the project sets `appLoader: legacy`.
154
+ - **Put the UI above the canvas.** The canvas is opaque. Content that is positioned and comes after it in the markup is painted above it; unpositioned content is painted under it and cannot be seen.
155
+ - **Tell the camera where the free area is.** `useSceneSafeArea()` returns two refs. `containerRef` goes on the element that covers the whole canvas, `safeAreaRef` on the region the product should stay visible in, here everything right of the panel, or above the sheet on a phone. The camera then frames the product in that region. The hook measures again whenever either of the two elements resizes; for a change no resize reports, such as a `transform` on an ancestor settling at the end of an animation, it also returns `update` to call. Without the two refs the product is centred in the whole canvas and sits against the panel.
156
+ - **Let the pointer through.** The safe-area element lies over the canvas, so it sets `pointerEvents: 'none'`; otherwise the scene cannot be orbited. The fullscreen button inside it switches pointer events back on.
157
+ - **Honour fullscreen.** Fullscreen is a flag in the product's state, not a browser feature. The layout reads it with `phx.frontend.useIsFullscreenActive()`, removes the panel or the sheet and lets the safe area cover the canvas, so the camera frames the product in the whole canvas.
158
+ - **Fit the width it is given.** The configurator can be embedded in a narrow column or opened on a phone. The layout reads the container's width class with `useActiveViewports()` and renders a desktop or a phone arrangement, see [Fit a phone](#fit-a-phone).
159
+ - **Serve read mode.** The same element renders a configuration shared for viewing, and the layout decides what a viewer sees.
160
+
161
+ ## Read and change the product
162
+
163
+ The panel reads everything it shows from hooks and writes the customer's choice back through one of them. The table lists the hooks the whole layout uses:
164
+
165
+ ```tsx
166
+ import { Box, Button, Typography } from '@mui/material'
167
+
168
+ import { detailComponent, finalizeActionComponent } from '@salsita-ai/sdk/domain'
169
+ import { PhoenixHandlerBoundary, useResolveLocalizable } from '@salsita-ai/sdk/react'
170
+
171
+ import { phx } from '../../.phoenix/phx.js'
172
+ import { EdgeFinishCards } from './EdgeFinishCards.js'
173
+ import { ReviewActions } from './ReviewActions.js'
174
+
175
+ const BUTTON_SX = { minBlockSize: 48, borderRadius: 999, textTransform: 'none', fontWeight: 600 } as const
176
+
177
+ interface ProductPanelProps {
178
+ // Only the name, the price and the action, for a collapsed bottom sheet
179
+ readonly collapsed?: boolean
180
+ // In a bottom sheet the cards region keeps the top of the cards in view, and the sheet scrolls when that does not fit
181
+ readonly inSheet?: boolean
182
+ }
183
+
184
+ export const ProductPanel = ({ collapsed = false, inSheet = false }: ProductPanelProps) => {
185
+ const resolve = useResolveLocalizable()
186
+ const detail = phx.frontend.useFirstRootComponent(detailComponent)
187
+ const price = phx.frontend.useRootEntityPrice()
188
+ const tableId = phx.frontend.useRootEntityId()
189
+ const edgeFinish = phx.frontend.useAttributeSelection(tableId, 'edgeFinish')
190
+ const { accessInfo } = phx.frontend.useAccessService()
191
+ const navigationState = phx.frontend.useActiveNavigationState()
192
+ const pushNavigate = phx.frontend.usePushNavigate()
193
+ const popNavigate = phx.frontend.usePopNavigate()
194
+ const navigate = phx.frontend.useNavigate()
195
+ const finalizeActions = phx.frontend.useComponents({ componentTypes: [finalizeActionComponent] }) ?? []
196
+
197
+ const isReadMode = accessInfo.viewMode
198
+ const isReview = isReadMode || navigationState !== 'configure'
199
+ // The spec's primary finalize action, or its first one
200
+ const quoteAction = finalizeActions.find(action => action.state.variant === 'primary') ?? finalizeActions[0]
201
+ const chosenEdge = edgeFinish.options.find(option => option.value === edgeFinish.value)
202
+
203
+ return (
204
+ <>
205
+ <Box component="header" sx={{ display: 'grid', gap: 1, padding: 3, paddingBlockEnd: 2 }}>
206
+ {isReadMode ? (
207
+ <Typography
208
+ variant="overline"
209
+ sx={{ justifySelf: 'start', paddingInline: 1.5, borderRadius: 999, bgcolor: 'action.selected' }}
210
+ >
211
+ View only
212
+ </Typography>
213
+ ) : (
214
+ <Typography variant="overline" color="text.secondary">
215
+ {resolve(detail?.state.subtitle)}
216
+ </Typography>
217
+ )}
218
+ <Typography variant="h5" component="h2" fontWeight={700}>
219
+ {resolve(detail?.state.title)}
220
+ </Typography>
221
+ {!collapsed && (
222
+ <Typography variant="body2" color="text.secondary">
223
+ {resolve(detail?.state.description)}
224
+ </Typography>
225
+ )}
226
+ </Box>
227
+
228
+ {!collapsed && (
229
+ <Box
230
+ component="div"
231
+ sx={{
232
+ display: 'grid',
233
+ gap: 1.5,
234
+ paddingInline: 3,
235
+ paddingBlockEnd: 3,
236
+ // In a short container the region shrinks and scrolls; a scroll past its end moves its container on
237
+ minBlockSize: inSheet ? 120 : 0,
238
+ overflowY: 'auto',
239
+ }}
240
+ >
241
+ <Typography variant="subtitle2" component="h3">
242
+ Edge finish
243
+ </Typography>
244
+ {isReview ? (
245
+ <Box component="div" sx={{ padding: 2, borderRadius: 3, bgcolor: 'action.hover' }}>
246
+ <Typography variant="body2" fontWeight={600}>
247
+ {resolve(chosenEdge?.title)}
248
+ </Typography>
249
+ <Typography variant="caption" color="text.secondary">
250
+ {resolve(chosenEdge?.description)}
251
+ </Typography>
252
+ </Box>
253
+ ) : (
254
+ <EdgeFinishCards selection={edgeFinish} />
255
+ )}
256
+ </Box>
257
+ )}
258
+
259
+ <Box component="footer" sx={{ display: 'grid', gap: 2, padding: 3, borderTop: 1, borderColor: 'divider' }}>
260
+ <Box component="div" sx={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between' }}>
261
+ <Typography variant="body2" color="text.secondary">
262
+ Total price
263
+ </Typography>
264
+ <Typography variant="h5" component="p" fontWeight={700}>
265
+ {price}
266
+ </Typography>
267
+ </Box>
268
+ {!isReadMode && navigationState === 'configure' && (
269
+ <Button size="large" variant="contained" onClick={() => pushNavigate('checkout')} sx={BUTTON_SX}>
270
+ Review and request a quote
271
+ </Button>
272
+ )}
273
+ {!isReadMode && navigationState === 'checkout' && (
274
+ <>
275
+ <Typography variant="body2" color="text.secondary">
276
+ Your table is ready for a quote. A real shop collects the contact details here.
277
+ </Typography>
278
+ {quoteAction ? (
279
+ <PhoenixHandlerBoundary name={quoteAction.state.handler}>
280
+ <ReviewActions
281
+ handler={quoteAction.state.handler}
282
+ label={resolve(quoteAction.state.label)}
283
+ onBack={popNavigate}
284
+ />
285
+ </PhoenixHandlerBoundary>
286
+ ) : (
287
+ <Button size="large" variant="outlined" onClick={popNavigate} sx={BUTTON_SX}>
288
+ Back to the configuration
289
+ </Button>
290
+ )}
291
+ </>
292
+ )}
293
+ {!isReadMode && navigationState === 'requested' && (
294
+ <>
295
+ <Typography variant="body2" role="status">
296
+ Thank you. Your quote request is on its way, and the shop replies within two working days.
297
+ </Typography>
298
+ <Button size="large" variant="outlined" onClick={() => navigate('configure')} sx={BUTTON_SX}>
299
+ Back to the configuration
300
+ </Button>
301
+ </>
302
+ )}
303
+ </Box>
304
+ </>
305
+ )
306
+ }
307
+ ```
308
+
309
+ | Hook | What the layout uses it for |
310
+ | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
311
+ | `phx.frontend.useFirstRootComponent(detailComponent)` | The `detail` component of the root entity: name, subtitle and description. `detailComponent` comes from `@salsita-ai/sdk/domain`; the hook returns `null` without one. |
312
+ | `phx.frontend.useRootEntityId()` | The id of the table, which the control hooks take. |
313
+ | `phx.frontend.useAttributeSelection(entityId, instanceName)` | The `attributeSelection` component named `edgeFinish`: the current `value` and the `options`, each with `title`, `description`, `value`, `disabled` and `onSelect`. |
314
+ | `phx.frontend.useRootEntityPrice()` | The root entity's `price` component, formatted for the current locale and currency, or `null` without one. |
315
+ | `phx.frontend.useAccessService()` | `accessInfo.viewMode` is `true` while a configuration shared for viewing is open, and also while the host sets the `readonly` property. |
316
+ | `phx.frontend.useActiveNavigationState()` | The active navigation state; the panel shows its review step while it is `checkout`. |
317
+ | `phx.frontend.usePushNavigate()` and `phx.frontend.usePopNavigate()` | Enter the review step and leave it again. |
318
+ | `phx.frontend.useNavigate()` | Return from the confirmation to the configuration, forgetting the review step. |
319
+ | `phx.frontend.useComponents({ componentTypes: [finalizeActionComponent] })` | The spec's `finalizeAction` components; the panel offers the primary one in its review step. `finalizeActionComponent` comes from `@salsita-ai/sdk/domain`. |
320
+ | `phx.frontend.useFinalizeAction(handler)` | Run the finalize action's handler, with its pending state, its error and whether finalizing is possible; see [Request a quote](#request-a-quote). |
321
+ | `phx.frontend.useIsFullscreenActive()` and `phx.frontend.useSetFullscreen()` | Read the fullscreen flag, and set it from the layout's own button. |
322
+ | `useActiveViewports()` from `@salsita-ai/sdk/react` | The width classes of the configurator's container, or `null` before it is measured; the layout renders its phone arrangement unless it includes `desktop`, see [Fit a phone](#fit-a-phone). |
323
+ | `phx.frontend.useConfiguratorMessages(onMessage)` | Receive violations, side-effect hints and errors one by one, with their text resolved, for the layout's own toast; see [Show messages](#show-messages). |
324
+ | `useResolveLocalizable()` from `@salsita-ai/sdk/react` | Turns the localizable texts of the spec into strings in the current language. [Localization](?path=/docs/phoenix-localization--docs) describes it and `LocalizableMessage`. |
325
+ | `PhoenixHandlerBoundary` from `@salsita-ai/sdk/react` | Wraps the review step's buttons, so an edited handler takes effect without a reload. |
326
+
327
+ Calling an option's `onSelect` is the whole write: it changes `edgeFinish` on the table, the scene morphs the edge and the price expression recalculates. The option fields are named for display: the spec's `label` arrives as `title`, its `img` as `imageSrc`.
328
+
329
+ The review step is the layout's own: the button pushes the `checkout` navigation state, and the panel shows a summary instead of the cards. There **Request a quote** runs the spec's finalize action, whose handler moves the panel on to the `requested` state and its confirmation; see [Request a quote](#request-a-quote). A real shop collects the contact details in the review step before it sends the request.
330
+
331
+ ## Draw the choices
332
+
333
+ The edge finish is a visual choice, so the panel shows it as cards with a drawing of each profile, as [Choosing a Control](?path=/docs/ux-guidelines-choosing-a-control--docs) recommends for shapes. The selected card has a border in the theme's primary colour, a tinted background and a check mark, so the state does not depend on colour alone. The cards form a radio group: the group has `role="radiogroup"` and a label, each card `role="radio"` and `aria-checked`, only the selected card is in the tab order, and the arrow keys move the selection, skipping disabled options, as in a native radio group. Each card is at least 44 pixels high and shows a focus ring for keyboard users.
334
+
335
+ ```tsx
336
+ import { Box, ButtonBase, type SxProps, type Theme, Typography } from '@mui/material'
337
+ import type { KeyboardEvent } from 'react'
338
+
339
+ import { type UseAttributeSelection, useResolveLocalizable } from '@salsita-ai/sdk/react'
340
+
341
+ // Cross-sections of the tabletop's edge, keyed by the option values of the spec.
342
+ const EDGE_PROFILES: Readonly<Record<string, string>> = {
343
+ None: 'M0 4H32V20H0Z',
344
+ Facet: 'M0 4H26L32 10V14L26 20H0Z',
345
+ Round: 'M0 4H24A8 8 0 0 1 24 20H0Z',
346
+ TreeTrunk: 'M0 4H27C33 5 28 9 34 11C29 13 34 17 27 20H0Z',
347
+ }
348
+
349
+ const CHECK_BADGE: SxProps<Theme> = {
350
+ position: 'absolute',
351
+ insetBlockStart: 10,
352
+ insetInlineEnd: 10,
353
+ display: 'grid',
354
+ placeItems: 'center',
355
+ inlineSize: 22,
356
+ blockSize: 22,
357
+ borderRadius: '50%',
358
+ bgcolor: 'primary.main',
359
+ color: 'primary.contrastText',
360
+ fontSize: 13,
361
+ }
362
+
363
+ // Arrow keys move the selection between the cards, as in a native radio group.
364
+ const ARROW_STEPS: Readonly<Record<string, number | undefined>> = {
365
+ ArrowRight: 1,
366
+ ArrowDown: 1,
367
+ ArrowLeft: -1,
368
+ ArrowUp: -1,
369
+ }
370
+
371
+ interface EdgeFinishCardsProps {
372
+ readonly selection: ReturnType<UseAttributeSelection>
373
+ }
374
+
375
+ export const EdgeFinishCards = ({ selection }: EdgeFinishCardsProps) => {
376
+ const resolve = useResolveLocalizable()
377
+ const { options } = selection
378
+ // The card in the tab order: the selected one, or the first enabled one. Disabled cards are never in it.
379
+ const selectedIndex = options.findIndex(option => option.value === selection.value && !option.disabled)
380
+ const focusIndex = selectedIndex >= 0 ? selectedIndex : options.findIndex(option => !option.disabled)
381
+
382
+ const handleKeyDown = (event: KeyboardEvent<HTMLElement>, index: number) => {
383
+ const step = ARROW_STEPS[event.key]
384
+
385
+ if (step === undefined) {
386
+ return
387
+ }
388
+
389
+ event.preventDefault()
390
+ // The next enabled option in the arrow's direction, wrapping around; disabled options are skipped.
391
+ const count = options.length
392
+ const next = Array.from(
393
+ { length: count - 1 },
394
+ (_, offset) => (((index + step * (offset + 1)) % count) + count) % count
395
+ ).find(candidate => !options[candidate]?.disabled)
396
+
397
+ if (next === undefined) {
398
+ return
399
+ }
400
+
401
+ options[next]?.onSelect()
402
+ event.currentTarget.parentElement?.querySelectorAll<HTMLElement>('[role="radio"]')[next]?.focus()
403
+ }
404
+
405
+ return (
406
+ <Box
407
+ component="div"
408
+ role="radiogroup"
409
+ aria-label="Edge finish"
410
+ sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 1.5 }}
411
+ >
412
+ {options.map((option, index) => {
413
+ const isSelected = option.value === selection.value
414
+
415
+ return (
416
+ <ButtonBase
417
+ key={String(option.value)}
418
+ role="radio"
419
+ aria-checked={isSelected}
420
+ tabIndex={index === focusIndex ? 0 : -1}
421
+ disabled={option.disabled}
422
+ onClick={option.onSelect}
423
+ onKeyDown={event => handleKeyDown(event, index)}
424
+ sx={{
425
+ position: 'relative',
426
+ display: 'grid',
427
+ justifyItems: 'start',
428
+ gap: 0.5,
429
+ padding: 2,
430
+ borderRadius: 3,
431
+ border: 2,
432
+ borderColor: isSelected ? 'primary.main' : 'divider',
433
+ bgcolor: isSelected ? 'action.hover' : 'transparent',
434
+ textAlign: 'start',
435
+ transition: 'border-color 150ms, background-color 150ms',
436
+ '&:hover': { borderColor: isSelected ? 'primary.main' : 'text.disabled' },
437
+ '&.Mui-focusVisible': { outline: 2, outlineColor: 'primary.main', outlineOffset: 2 },
438
+ '&.Mui-disabled': { opacity: 0.4 },
439
+ }}
440
+ >
441
+ {isSelected && (
442
+ <Box component="span" aria-hidden sx={CHECK_BADGE}>
443
+ ✓
444
+ </Box>
445
+ )}
446
+ <Box
447
+ component="svg"
448
+ viewBox="0 0 36 24"
449
+ aria-hidden
450
+ sx={{ inlineSize: 60, blockSize: 40, color: '#b98b5e' }}
451
+ >
452
+ <path d={EDGE_PROFILES[String(option.value)]} fill="currentColor" />
453
+ </Box>
454
+ <Typography variant="body2" fontWeight={600}>
455
+ {resolve(option.title)}
456
+ </Typography>
457
+ <Typography variant="caption" color="text.secondary">
458
+ {resolve(option.description)}
459
+ </Typography>
460
+ </ButtonBase>
461
+ )
462
+ })}
463
+ </Box>
464
+ )
465
+ }
466
+ ```
467
+
468
+ The drawings are keyed by the option values of the spec. A spec can carry a picture per option instead, as `img`, which the hook returns as `imageSrc`; [CardSelect](?path=/docs/controls-select-attribute-cardselect--docs) shows the SDK's own card control.
469
+
470
+ ## Offer fullscreen
471
+
472
+ Fullscreen is a flag in the configurator's state, not the browser's fullscreen mode. `phx.frontend.useIsFullscreenActive()` reads it and `phx.frontend.useSetFullscreen()` returns a function that sets it: `setFullscreen(true)` turns it on, `setFullscreen(false)` off. The layout's own button flips it, and the layout decides what to hide.
473
+
474
+ ```tsx
475
+ import { Button, Tooltip } from '@mui/material'
476
+
477
+ import { phx } from '../../.phoenix/phx.js'
478
+
479
+ const ENTER_ICON = 'M4 9V4h5M15 4h5v5M20 15v5h-5M9 20H4v-5'
480
+ const EXIT_ICON = 'M9 4v5H4M15 4v5h5M15 20v-5h5M9 20v-5H4'
481
+
482
+ interface FullscreenToggleProps {
483
+ // Only the icon, with the label as its accessible name and tooltip, where a label would cover the product
484
+ readonly compact?: boolean
485
+ }
486
+
487
+ export const FullscreenToggle = ({ compact = false }: FullscreenToggleProps) => {
488
+ const isFullscreen = phx.frontend.useIsFullscreenActive()
489
+ const setFullscreen = phx.frontend.useSetFullscreen()
490
+ const label = isFullscreen ? 'Exit fullscreen' : 'Fullscreen'
491
+ const icon = (
492
+ <svg viewBox="0 0 24 24" width={20} height={20} fill="none" stroke="currentColor" strokeWidth={2}>
493
+ <path d={isFullscreen ? EXIT_ICON : ENTER_ICON} />
494
+ </svg>
495
+ )
496
+
497
+ return (
498
+ <Tooltip title={compact ? label : ''}>
499
+ <Button
500
+ onClick={() => setFullscreen(!isFullscreen)}
501
+ aria-label={compact ? label : undefined}
502
+ startIcon={compact ? undefined : icon}
503
+ sx={{
504
+ minBlockSize: 44,
505
+ minInlineSize: 44,
506
+ paddingInline: compact ? 0 : 2.5,
507
+ borderRadius: 999,
508
+ bgcolor: 'background.paper',
509
+ color: 'text.primary',
510
+ boxShadow: '0 4px 16px rgba(0, 0, 0, 0.12)',
511
+ textTransform: 'none',
512
+ fontWeight: 600,
513
+ '&:hover': { bgcolor: 'background.paper', boxShadow: '0 6px 20px rgba(0, 0, 0, 0.18)' },
514
+ }}
515
+ >
516
+ {compact ? icon : label}
517
+ </Button>
518
+ </Tooltip>
519
+ )
520
+ }
521
+ ```
522
+
523
+ The button stays in the free area in both states, so the way back is always where the user left it. [Interaction Modes](?path=/docs/ux-guidelines-interaction-modes--docs#full-screen-preview) describes what fullscreen hides.
524
+
525
+ ## Show messages
526
+
527
+ When a change is rejected because it breaks an invariant, when a committed change carries a hint, or when an error is thrown while a change is validated, the configurator has a message for the customer. `phx.frontend.useConfiguratorMessages(onMessage)` calls `onMessage` once for each, with the text already in the customer's language, and the layout shows it its own way. Here a toast shows a message for six seconds, colored by its severity, at the bottom of the free area on a desktop and at its top on a phone:
528
+
529
+ ```tsx
530
+ import { Alert } from '@mui/material'
531
+ import { useEffect, useState } from 'react'
532
+
533
+ import type { ConfiguratorMessage } from '@salsita-ai/sdk/react'
534
+
535
+ import { phx } from '../../.phoenix/phx.js'
536
+
537
+ const VIOLATION_SEVERITY = { fatal: 'error', fixable: 'error', warning: 'warning', notice: 'info' } as const
538
+ const RANK = { info: 0, warning: 1, error: 2 } as const
539
+ const VISIBLE_MS = 6000
540
+
541
+ const severityOf = (message: ConfiguratorMessage) => {
542
+ if (message.kind === 'violation') {
543
+ return VIOLATION_SEVERITY[message.violation.severity]
544
+ }
545
+
546
+ return message.kind === 'error' ? 'error' : 'info'
547
+ }
548
+
549
+ export const MessageToast = () => {
550
+ const [message, setMessage] = useState<ConfiguratorMessage | null>(null)
551
+
552
+ // A rejected change also reports the notices already present; keep the worst message while it is shown
553
+ phx.frontend.useConfiguratorMessages(next => {
554
+ setMessage(shown => (shown !== null && RANK[severityOf(next)] < RANK[severityOf(shown)] ? shown : next))
555
+ })
556
+
557
+ useEffect(() => {
558
+ if (message === null) {
559
+ return
560
+ }
561
+
562
+ const timeout = setTimeout(() => setMessage(null), VISIBLE_MS)
563
+
564
+ return () => clearTimeout(timeout)
565
+ }, [message])
566
+
567
+ if (message === null) {
568
+ return null
569
+ }
570
+
571
+ return (
572
+ <Alert
573
+ severity={severityOf(message)}
574
+ onClose={() => setMessage(null)}
575
+ sx={{ maxInlineSize: 420, borderRadius: 3, boxShadow: '0 12px 40px rgba(0, 0, 0, 0.12)' }}
576
+ >
577
+ {message.text}
578
+ </Alert>
579
+ )
580
+ }
581
+ ```
582
+
583
+ The hook takes a new function on every render without missing a message, so passing the state setter, or an inline function, is fine. `message.kind` is `'violation'`, `'sideEffect'` or `'error'`; a violation also carries `entityId` and the `violation` itself, with its `severity`. A rejected change brings one message for each violation of the rejected state, including notices and warnings that were already present, so the toast keeps the most severe message while it is shown instead of letting a later notice replace the reason for the rejection. The layout mounts the toast in the safe-area element, which sets `pointerEvents: 'none'`, so the toast's wrapper switches pointer events back on for the close button.
584
+
585
+ To give the toast something to show, the spec registers one transformer. Choosing **Tree trunk** keeps the change and adds a hint to it, which arrives as a `sideEffect` message:
586
+
587
+ ```ts
588
+ import { msg } from '@lingui/core/macro'
589
+
590
+ import { phx } from '../../.phoenix/phx.js'
591
+
592
+ // A tree-trunk edge is cut by hand; choosing it carries a hint, which reaches the layout as a side-effect message
593
+ export default phx.frontend.transformerBuilder.when
594
+ .entity(['Table'])
595
+ .modified({ atFields: ['edgeFinish'] })
596
+ .then.overrideMatchingUpdate(({ update }) => {
597
+ if (update.stateDelta.edgeFinish !== 'TreeTrunk') {
598
+ return [update]
599
+ }
600
+
601
+ return [{ ...update, hint: msg`Tree trunk edges are cut by hand, so delivery takes three weeks longer.` }]
602
+ })
603
+ .build('tree-trunk-delivery')
604
+ ```
605
+
606
+ [Configurator Interface → Loaded Configuration UI](?path=/docs/phoenix-configurator-interface--docs#loaded-configuration-ui) lists the messages the hook does not deliver: a failure to save, errors of the built-in finalize and other actions, and preset errors stay snackbars.
607
+
608
+ ## Request a quote
609
+
610
+ The spec declares what finalizing means for this product as a `finalizeAction` component, and the project registers the handler it names:
611
+
612
+ ```yaml
613
+ components:
614
+ Table:
615
+ finalizeAction:
616
+ quote:
617
+ variant: primary
618
+ label: Request a quote
619
+ title: Send the configuration to the shop for a quote
620
+ icon: PaperPlaneTilt
621
+ handler: requestQuote
622
+ ```
623
+
624
+ ```ts
625
+ import { useCallback } from 'react'
626
+
627
+ import { phx } from '../../.phoenix/phx.js'
628
+
629
+ // Stands in for the shop's quote service: the request takes a moment, then the panel shows the confirmation
630
+ export default phx.frontend.createPhoenixHandler('requestQuote', () => {
631
+ const navigate = phx.frontend.useNavigate()
632
+
633
+ return {
634
+ handler: useCallback(async () => {
635
+ await new Promise(resolve => setTimeout(resolve, 1500))
636
+ navigate('requested')
637
+ }, [navigate]),
638
+ }
639
+ })
640
+ ```
641
+
642
+ The handler stands in for the shop's quote service: it waits a moment, as a request would, and then enters the `requested` navigation state, in which the panel thanks the customer. A handler is a hook factory, so it can use any hook, here `phx.frontend.useNavigate()`.
643
+
644
+ The panel lists the finalize actions with `phx.frontend.useComponents({ componentTypes: [finalizeActionComponent] })` and offers the primary one, or the first when none is primary, in its review step. It renders the review step's buttons in a component of their own inside `<PhoenixHandlerBoundary name={handler}>`, because `phx.frontend.useFinalizeAction(handler)` runs the project's handler factory: it is called once per component, never in a loop, and the boundary remounts the buttons when the handler is edited. **Back to the configuration** lives in the same component, so it can be disabled while the request is pending.
645
+
646
+ ```tsx
647
+ import { Alert, Box, Button, CircularProgress, Typography } from '@mui/material'
648
+
649
+ import { phx } from '../../.phoenix/phx.js'
650
+
651
+ const BUTTON_SX = { minBlockSize: 48, borderRadius: 999, textTransform: 'none', fontWeight: 600 } as const
652
+
653
+ interface ReviewActionsProps {
654
+ readonly handler: string
655
+ readonly label: string
656
+ readonly onBack: () => void
657
+ }
658
+
659
+ // The review step's buttons: one finalize action and the way back. `useFinalizeAction` runs the action's handler,
660
+ // so this component calls it exactly once and is rendered inside a `PhoenixHandlerBoundary` for that handler.
661
+ // Back is disabled while the request is pending, so the customer cannot change the configuration while it is sent.
662
+ // The pending state belongs to this component: when the panel unmounts, as in fullscreen, the request goes on and
663
+ // the remounted buttons are enabled again.
664
+ export const ReviewActions = ({ handler, label, onBack }: ReviewActionsProps) => {
665
+ const { run, isPending, error, disabled, hints } = phx.frontend.useFinalizeAction(handler)
666
+
667
+ return (
668
+ <Box component="div" sx={{ display: 'grid', gap: 2 }}>
669
+ <Box component="div" sx={{ display: 'grid', gap: 1 }}>
670
+ <Button
671
+ size="large"
672
+ variant="contained"
673
+ disabled={disabled || isPending}
674
+ onClick={run}
675
+ startIcon={isPending ? <CircularProgress size={18} color="inherit" /> : undefined}
676
+ sx={BUTTON_SX}
677
+ >
678
+ {isPending ? 'Sending the request…' : label}
679
+ </Button>
680
+ {disabled && hints && (
681
+ <Typography variant="caption" color="text.secondary">
682
+ {hints}
683
+ </Typography>
684
+ )}
685
+ {error && <Alert severity="error">The request could not be sent: {error.message}</Alert>}
686
+ </Box>
687
+ <Button size="large" variant="outlined" disabled={isPending} onClick={onBack} sx={BUTTON_SX}>
688
+ Back to the configuration
689
+ </Button>
690
+ </Box>
691
+ )
692
+ }
693
+ ```
694
+
695
+ - `run()` records the finalize-button click for analytics and runs the handler; it does nothing while `disabled` or `isPending`.
696
+ - While `isPending` the button shows a spinner and cannot be clicked again, and **Back to the configuration** is disabled, so the customer cannot change the configuration while it is sent. `isPending` and `error` belong to the mounted component: entering fullscreen unmounts the panel, the request goes on, and the panel shown after fullscreen starts with both buttons enabled. A layout that must rule that out keeps the review step mounted, or hides its fullscreen button while a request runs.
697
+ - `error` is what the handler rejected with. The layout shows it under the button; no snackbar is raised. It is cleared when the customer tries again.
698
+ - `disabled` and `hints` come from the configuration's violations: while a `warning` or worse holds, or the configuration failed to save, finalizing is not possible and `hints` says why.
699
+
700
+ The review step stays: a quote request is a commitment, so the customer sees the summary first, and a real shop collects the contact details there. A product that finalizes without a review offers the button in place of **Review and request a quote**. [Configurator Interface → Hooks for a Project Layout](?path=/docs/phoenix-configurator-interface--docs#hooks-for-a-project-layout) describes the hook, and [Finalization](?path=/docs/phoenix-finalization--docs) what a real flow does on the server.
701
+
702
+ ## Serve read mode
703
+
704
+ While `accessInfo.viewMode` is `true` the panel marks the view as view only, replaces the cards with the chosen finish and shows the price without an action; the finalize action is not offered, since `useFinalizeAction` does not check read mode itself. The hooks do not hide or disable anything in read mode: what a viewer can change is decided by what the layout renders. Rotating and zooming the scene, and fullscreen, stay available, as [Interaction Modes](?path=/docs/ux-guidelines-interaction-modes--docs#view-only-mode) recommends. The button on the table's edge is hidden for viewers by the spec, see [Put a control on the product](#put-a-control-on-the-product).
705
+
706
+ ## Fit a phone
707
+
708
+ A panel 380 pixels wide beside the scene leaves nothing of the table on a phone. Below a desktop width the layout puts the panel into a sheet at the bottom and lets the camera frame the table in the area above it. The layout above shows both arrangements.
709
+
710
+ **The switch.** `useActiveViewports()` from `@salsita-ai/sdk/react` returns the width classes the configurator's container satisfies. The container decides, not the window, so a configurator embedded in a narrow column of a wide page gets the phone arrangement too. The built-in layout switches on `desktop` as well.
711
+
712
+ | Value | When it is active |
713
+ | ----------- | ---------------------------------------------------------------------------------- |
714
+ | `phone` | The container is narrower than 480 pixels. |
715
+ | `mobile` | The container is at most 768 pixels wide. |
716
+ | `tablet` | The container is wider than 480 and narrower than 1024 pixels. |
717
+ | `desktop` | The container is wider than 768 pixels; `mobile` and `desktop` exclude each other. |
718
+ | `landscape` | The browser window, not the container, is wider than it is high. |
719
+ | `portrait` | The browser window, not the container, is higher than it is wide. |
720
+
721
+ The hook returns `null` until the container has been measured, and the layout renders only the scene until then, so a phone never shows the desktop panel for a frame.
722
+
723
+ **The sheet.** On a phone the layout is a column: the safe-area element takes the height the sheet leaves, and the sheet sits below it. The sheet has two positions. Collapsed, it shows the name, the price and the action; open, it shows the description and the cards too. The `collapsed` prop of the panel hides the description and the cards, and nothing else, and `inSheet` keeps the top of the cards in view. The sheet scrolls what does not fit, so the price, the review step, **Request a quote** and the thank-you message are reachable in both positions.
724
+
725
+ ```tsx
726
+ import { Box, ButtonBase } from '@mui/material'
727
+ import { type MouseEvent, type PointerEvent, type ReactNode, useRef } from 'react'
728
+
729
+ // A drag shorter than this is a tap, and the handle's click toggles the sheet
730
+ const TAP_DISTANCE = 6
731
+ // A drag that ends this far from where it started snaps to the other position
732
+ const SNAP_DISTANCE = 48
733
+ // A drag faster than this, in pixels per millisecond, snaps in its direction even when it is short
734
+ const SNAP_VELOCITY = 0.5
735
+ // How far the sheet follows the finger towards the position it is already in
736
+ const OVERDRAG = 0.25
737
+
738
+ interface BottomSheetProps {
739
+ readonly open: boolean
740
+ readonly onOpenChange: (open: boolean) => void
741
+ readonly label: string
742
+ readonly children: ReactNode
743
+ }
744
+
745
+ interface Drag {
746
+ readonly startY: number
747
+ readonly startTime: number
748
+ delta: number
749
+ }
750
+
751
+ // A persistent sheet at the bottom of the layout with two positions, open and collapsed. It is not a modal: the scene
752
+ // above it stays interactive. Its height comes from its content, so the layout's safe area ends where the sheet
753
+ // starts and changes only when the sheet snaps. While the handle is dragged the sheet follows the finger with a
754
+ // transform, which the safe area does not see, so the camera refits once per snap and not on every move.
755
+ export const BottomSheet = ({ open, onOpenChange, label, children }: BottomSheetProps) => {
756
+ const sheetRef = useRef<HTMLElement>(null)
757
+ const dragRef = useRef<Drag | null>(null)
758
+ const draggedRef = useRef(false)
759
+
760
+ const moveSheet = (offset: number, animate: boolean) => {
761
+ if (sheetRef.current) {
762
+ sheetRef.current.style.transition = animate ? 'transform 200ms ease-out' : 'none'
763
+ sheetRef.current.style.transform = offset === 0 ? '' : `translateY(${offset}px)`
764
+ }
765
+ }
766
+
767
+ const handlePointerDown = (event: PointerEvent<HTMLButtonElement>) => {
768
+ event.currentTarget.setPointerCapture(event.pointerId)
769
+ dragRef.current = { startY: event.clientY, startTime: event.timeStamp, delta: 0 }
770
+ draggedRef.current = false
771
+ }
772
+
773
+ const handlePointerMove = (event: PointerEvent<HTMLButtonElement>) => {
774
+ const drag = dragRef.current
775
+
776
+ if (drag === null) {
777
+ return
778
+ }
779
+
780
+ drag.delta = event.clientY - drag.startY
781
+ // Down closes an open sheet and up opens a collapsed one; the other direction only gives a little
782
+ const towardsOther = open ? drag.delta > 0 : drag.delta < 0
783
+ moveSheet(towardsOther ? drag.delta : drag.delta * OVERDRAG, false)
784
+ }
785
+
786
+ const handlePointerEnd = (event: PointerEvent<HTMLButtonElement>) => {
787
+ const drag = dragRef.current
788
+ dragRef.current = null
789
+
790
+ if (drag === null) {
791
+ return
792
+ }
793
+
794
+ moveSheet(0, true)
795
+
796
+ if (Math.abs(drag.delta) < TAP_DISTANCE) {
797
+ return
798
+ }
799
+
800
+ draggedRef.current = true
801
+ const velocity = drag.delta / Math.max(event.timeStamp - drag.startTime, 1)
802
+ const flicked = Math.abs(drag.delta) >= SNAP_DISTANCE || Math.abs(velocity) >= SNAP_VELOCITY
803
+
804
+ if (flicked && (open ? drag.delta > 0 : drag.delta < 0)) {
805
+ onOpenChange(!open)
806
+ }
807
+ }
808
+
809
+ // A tap, Enter or Space toggles the sheet. The pointer click that ends a drag has already been handled; a keyboard
810
+ // click (`detail` 0) always toggles, also after a touch drag that was followed by no click.
811
+ const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
812
+ const dragged = draggedRef.current
813
+ draggedRef.current = false
814
+
815
+ if (dragged && event.detail !== 0) {
816
+ return
817
+ }
818
+
819
+ onOpenChange(!open)
820
+ }
821
+
822
+ return (
823
+ <Box
824
+ component="section"
825
+ ref={sheetRef}
826
+ aria-label={label}
827
+ sx={{
828
+ position: 'relative',
829
+ flexShrink: 0,
830
+ display: 'flex',
831
+ flexDirection: 'column',
832
+ maxBlockSize: '55%',
833
+ paddingBlockEnd: 'env(safe-area-inset-bottom)',
834
+ borderStartStartRadius: 20,
835
+ borderStartEndRadius: 20,
836
+ bgcolor: 'background.paper',
837
+ boxShadow: '0 -8px 32px rgba(0, 0, 0, 0.12)',
838
+ // Fills the gap below the sheet while it is dragged up
839
+ '&::after': {
840
+ content: '""',
841
+ position: 'absolute',
842
+ insetInline: 0,
843
+ insetBlockStart: '100%',
844
+ blockSize: '100vh',
845
+ bgcolor: 'background.paper',
846
+ },
847
+ }}
848
+ >
849
+ <ButtonBase
850
+ aria-expanded={open}
851
+ disableRipple
852
+ aria-label={open ? `Collapse ${label.toLowerCase()}` : `Expand ${label.toLowerCase()}`}
853
+ onClick={handleClick}
854
+ onPointerDown={handlePointerDown}
855
+ onPointerMove={handlePointerMove}
856
+ onPointerUp={handlePointerEnd}
857
+ onPointerCancel={handlePointerEnd}
858
+ sx={{
859
+ flexShrink: 0,
860
+ blockSize: 36,
861
+ touchAction: 'none',
862
+ '&.Mui-focusVisible': { outline: 2, outlineColor: 'primary.main', outlineOffset: -4, borderRadius: 3 },
863
+ }}
864
+ >
865
+ <Box component="span" sx={{ inlineSize: 40, blockSize: 4, borderRadius: 2, bgcolor: 'text.disabled' }} />
866
+ </ButtonBase>
867
+ {/* A column, so the panel's cards region shrinks first and the price and action stay in view; below the cards
868
+ region's minimum height the content scrolls as a whole, as the review step does on a short phone */}
869
+ <Box
870
+ component="div"
871
+ sx={{
872
+ display: 'flex',
873
+ flexDirection: 'column',
874
+ flex: '1 1 auto',
875
+ minBlockSize: 0,
876
+ overflowY: 'auto',
877
+ overscrollBehavior: 'contain',
878
+ }}
879
+ >
880
+ {children}
881
+ </Box>
882
+ </Box>
883
+ )
884
+ }
885
+ ```
886
+
887
+ - The handle is a real button with `aria-expanded`. A tap, Enter or Space toggles the sheet, and dragging it up or down snaps the sheet to the other position when the drag is long or fast enough. `touchAction: 'none'` is set on the handle only, so the cards still scroll by touch.
888
+ - The sheet's height comes from its content and is at most 55% of the configurator; when the content does not fit, the cards region shrinks until only the top of the cards shows and scrolls, and below that the sheet's content scrolls as a whole. On a phone with room for the header, the top of the cards and the footer, the price and the action stay in view with the sheet open; on a shorter one the customer scrolls the sheet to them, and the review step's buttons are reached the same way in both positions. A scroll on the cards that reaches their end moves the sheet on. Because the safe-area element is its neighbour in the column, the free area ends where the sheet starts without anything being measured by the layout.
889
+ - While the handle is dragged the sheet follows the finger with a `transform`. `useSceneSafeArea()` does not see a transform, so the free area changes once, when the sheet snaps, and the camera refits once. A safe area that followed the finger would refit the camera on every move and reset an orbit the customer made.
890
+ - The sheet is not a modal: no backdrop covers the scene, so the table can be orbited with one finger and zoomed with two while the sheet is open. MUI's temporary `Drawer` and `SwipeableDrawer` are modals and would block the scene; their persistent and permanent variants have no backdrop, but no drag handle with two positions either.
891
+
892
+ **What else moves.** The toast sits at the top of the free area, so it does not cover the sheet's handle, and the fullscreen button shows only its icon, with its label as tooltip and accessible name, so the toast has room beside it. Fullscreen removes the sheet as it removes the panel on a desktop. The sheet keeps its position while fullscreen is on.
893
+
894
+ **A camera for the phone.** The desktop camera is close enough to fill a wide free area, and on a narrow one it cuts off the ends of the table. So each camera has a filter on the viewport: the desktop camera `main` applies while `desktop` is active, and a second camera, `phone`, with a larger distance frames the whole table while `mobile` is. The filters exclude each other once the container is measured. Before that, for example when an image of the configuration is rendered, no viewport is known and a viewport filter matches, so both cameras apply and the higher priority of `main` decides:
895
+
896
+ ```yaml
897
+ components:
898
+ Table:
899
+ fitRootPerspectiveCamera:
900
+ main:
901
+ priority: 2
902
+ fov: 35
903
+ near: 100
904
+ far: 10000
905
+ minDistance: 1000
906
+ maxDistance: 9000
907
+ distance: 5000
908
+ azimuthAngle: 1.5
909
+ polarAngle: 1.1
910
+ fitMeshChanges: true
911
+ $filter:
912
+ viewports: [desktop]
913
+ phone:
914
+ priority: 1
915
+ fov: 35
916
+ near: 100
917
+ far: 10000
918
+ minDistance: 1000
919
+ maxDistance: 12000
920
+ distance: 8000
921
+ azimuthAngle: 1.5
922
+ polarAngle: 1.1
923
+ fitMeshChanges: true
924
+ $filter:
925
+ viewports: [mobile]
926
+ ```
927
+
928
+ ## Put a control on the product
929
+
930
+ The edge finish is a detail of the tabletop, so the layout also offers it on the table itself: a round button on the middle of the long edge opens the four finishes next to the edge. On a phone it is a way to change the edge without opening the sheet.
931
+
932
+ A control in the scene is a spec component that names an element of the project. `htmlHotspot` places an HTML element at an offset from the entity's origin, in the model's units, here millimetres: half the table's depth towards the camera and just below the top's surface. Its `$filter` shows it only to the owner and only while the `configure` navigation state is active, so a viewer and the review step do not get it, and the element does not check either itself:
933
+
934
+ ```yaml
935
+ components:
936
+ Table:
937
+ htmlHotspot:
938
+ edgeFinish:
939
+ name: EdgeFinishHotspot
940
+ offsetX: 500
941
+ offsetY: 800
942
+ offsetZ: 0
943
+ $filter:
944
+ modes: [owner]
945
+ navigationStates: [configure]
946
+ ```
947
+
948
+ The element is registered with `phx.frontend.createCustomPhoenixEntityElement` under the name the spec gives. It receives the id of the entity the hotspot belongs to, and reads the same `attributeSelection` the panel reads:
949
+
950
+ ```tsx
951
+ import { Box, ButtonBase, Popover, Tooltip } from '@mui/material'
952
+ import { useState } from 'react'
953
+
954
+ import { phx } from '../../.phoenix/phx.js'
955
+ import { EdgeFinishCards } from '../_shared/EdgeFinishCards.js'
956
+
957
+ // The edge profile the button shows, the square edge of the cards' drawings
958
+ const EDGE_ICON = 'M3 7H17L21 11V13L17 17H3Z'
959
+
960
+ // Rendered by the spec's `htmlHotspot` on the table's edge: a button on the product that opens the edge finishes
961
+ // next to the edge, with the same cards as the panel. The spec's `$filter` decides when it is shown.
962
+ export default phx.frontend.createCustomPhoenixEntityElement('EdgeFinishHotspot', ({ entityId }) => {
963
+ const edgeFinish = phx.frontend.useAttributeSelection(entityId, 'edgeFinish')
964
+ const [anchor, setAnchor] = useState<HTMLElement | null>(null)
965
+
966
+ return (
967
+ <>
968
+ <Tooltip title="Edge finish">
969
+ <ButtonBase
970
+ aria-label="Edge finish"
971
+ aria-haspopup="dialog"
972
+ aria-expanded={anchor !== null}
973
+ onClick={event => setAnchor(event.currentTarget)}
974
+ sx={{
975
+ display: 'grid',
976
+ placeItems: 'center',
977
+ inlineSize: 44,
978
+ blockSize: 44,
979
+ borderRadius: '50%',
980
+ bgcolor: 'background.paper',
981
+ color: 'primary.main',
982
+ boxShadow: '0 4px 16px rgba(0, 0, 0, 0.2)',
983
+ '&.Mui-focusVisible': { outline: 2, outlineColor: 'primary.main', outlineOffset: 2 },
984
+ }}
985
+ >
986
+ <svg viewBox="0 0 24 24" width={22} height={22} aria-hidden>
987
+ <path d={EDGE_ICON} fill="currentColor" />
988
+ </svg>
989
+ </ButtonBase>
990
+ </Tooltip>
991
+ <Popover
992
+ open={anchor !== null}
993
+ anchorEl={anchor}
994
+ onClose={() => setAnchor(null)}
995
+ anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
996
+ transformOrigin={{ vertical: 'bottom', horizontal: 'center' }}
997
+ slotProps={{
998
+ paper: { role: 'dialog', 'aria-label': 'Edge finish', sx: { borderRadius: 4, marginBlockStart: -1 } },
999
+ }}
1000
+ >
1001
+ <Box component="div" sx={{ inlineSize: 340, maxInlineSize: 'calc(100vw - 32px)', padding: 2 }}>
1002
+ <EdgeFinishCards selection={edgeFinish} />
1003
+ </Box>
1004
+ </Popover>
1005
+ </>
1006
+ )
1007
+ })
1008
+ ```
1009
+
1010
+ - The hotspot is rendered inside the scene, but it has the theme and every `phx.frontend` hook, like the layout. MUI's `Popover` and `Tooltip` render into the configurator's own modal root, so they are styled and work inside its shadow root.
1011
+ - The cards in the popover are the panel's `EdgeFinishCards`, used a second time. A choice changes the edge, the price and the panel's cards at once, and the popover stays open so the customer can compare the finishes on the table.
1012
+ - Keyboard: the button is in the tab order, Enter and Space open the popover and move the focus into it, the arrow keys change the finish, and Escape closes it and returns the focus to the button.
1013
+ - `controlHotspot: true` would hide the hotspot while a change is being applied, which would also unmount the open popover on every choice, so this hotspot does not set it.
1014
+ - The scene's HTML is painted inside the canvas's stacking context, under the layout's panel and sheet. A hotspot that a panel covers cannot be clicked there.
1015
+
1016
+ [Html Hotspots](?path=/docs/controls-scene-html-hotspots--docs) describes the component and its other properties.
1017
+
1018
+ ## The spec the layout reads
1019
+
1020
+ The layout reads four components of the root entity and its navigation states; the scene components are in the complete implementation below.
1021
+
1022
+ ```yaml
1023
+ navigationStates:
1024
+ - configure
1025
+ - checkout
1026
+ - requested
1027
+
1028
+ components:
1029
+ Table:
1030
+ detail:
1031
+ main:
1032
+ title: Linden dining table
1033
+ subtitle: Solid oak, seats six
1034
+ description: A solid oak top on slender steel legs, finished with the edge profile of your choice.
1035
+ price:
1036
+ main:
1037
+ price:
1038
+ $read: $price
1039
+ attributeSelection:
1040
+ edgeFinish:
1041
+ value:
1042
+ $bind: edgeFinish
1043
+ options:
1044
+ - label: Square
1045
+ description: Crisp and modern
1046
+ value: None
1047
+ - label: Facet
1048
+ description: A light bevel
1049
+ value: Facet
1050
+ - label: Round
1051
+ description: Soft to the touch
1052
+ value: Round
1053
+ - label: Tree trunk
1054
+ description: The natural edge
1055
+ value: TreeTrunk
1056
+ finalizeAction:
1057
+ quote:
1058
+ variant: primary
1059
+ label: Request a quote
1060
+ title: Send the configuration to the shop for a quote
1061
+ icon: PaperPlaneTilt
1062
+ handler: requestQuote
1063
+
1064
+ transformers:
1065
+ - name: tree-trunk-delivery
1066
+ ```
1067
+
1068
+ `$price` is a custom expression in `src/expressions/table.ts` that adds a surcharge per finish, and `tree-trunk-delivery` is the transformer in `src/transformers/tree-trunk-delivery.ts` that adds the hint shown in [Show messages](#show-messages), and `requestQuote` is the handler in `src/handlers/request-quote.ts`. The example sets no `applicationConfig` and no layout components: `layout`, the header, the sidebar footer, the toolbar, `viewControls` and property pages configure the built-in layout and have no effect on this one. The `finalizeAction` is not one of them: it describes the product's own finalizing, which this layout offers itself.
1069
+
1070
+ ## Try the example
1071
+
1072
+ To see read mode, open **Mocks** in the editor rail on the left and apply the **Read User** template.
1073
+
1074
+ The same layout in a frame 390 pixels wide, as on a phone. Opened on its own from the sidebar, the **Phone** story sets Storybook's viewport to 390 × 844.
1075
+
1076
+ ## Test cases
1077
+
1078
+ - The panel is on the left and the table is centred in the area to the right of it, not in the whole canvas.
1079
+ - Choosing **Tree trunk** moves the check mark to that card, changes the edge of the tabletop and raises the price from $1,290.00 to $1,630.00.
1080
+ - Choosing **Tree trunk** also shows "Tree trunk edges are cut by hand, so delivery takes three weeks longer." at the bottom of the free area, until its close button is clicked or six seconds pass. Choosing another finish shows nothing.
1081
+ - Tab reaches the selected card, and the arrow keys move the selection to the next or previous card.
1082
+ - Dragging in the area to the right of the panel orbits the camera.
1083
+ - **Review and request a quote** replaces the cards with the chosen finish and offers **Request a quote**; **Back to the configuration** brings the cards back.
1084
+ - **Request a quote** shows a spinner and "Sending the request…" for about a second and a half, with **Back to the configuration** disabled, then "Thank you. Your quote request is on its way, and the shop replies within two working days."; **Back to the configuration** then returns to the cards.
1085
+ - **Fullscreen** removes the panel and the table moves to the centre of the canvas; **Exit fullscreen** brings both back.
1086
+ - With the **Read User** mock the panel says **View only**, shows the chosen finish and the price, and has no button, so no quote can be requested.
1087
+ - A round button sits on the middle of the table's long edge. Clicking it opens the four finishes next to it; choosing **Round** there moves the check mark in the popover and in the panel, rounds the edge and raises the price to $1,410.00. Escape closes the popover.
1088
+ - Tab reaches the button on the edge, and Enter or Space opens the popover.
1089
+ - The button on the edge is gone in the review step, in the confirmation and with the **Read User** mock, and stays in fullscreen.
1090
+ - In the **Phone** story the panel is a sheet at the bottom showing the name, the price and **Review and request a quote**, and the whole table is visible above it.
1091
+ - Tapping the sheet's handle, or dragging it up, opens the sheet with the cards; the table moves up once the sheet has snapped and stays fully visible. Dragging the handle down, or tapping it again, collapses the sheet.
1092
+ - On the phone, the review step, **Request a quote** and the thank-you message work with the sheet collapsed and open.
1093
+ - On the phone, choosing **Tree trunk** shows the hint at the top of the free area, beside the fullscreen button.
1094
+ - On the phone, the fullscreen button removes the sheet and the table moves to the centre of the canvas; pressing it again brings the sheet back in the position it had.
1095
+
1096
+ ## Known gaps
1097
+
1098
+ A project layout has only what it renders. This one leaves out the following, each of which a product may need. [Layout Slots](?path=/docs/phoenix-layout-slots--docs) describes every `LayoutSlots` member named here.
1099
+
1100
+ - **Messages that stay snackbars.** The toast shows violations, side effects and errors, and the finalize button shows its own error. A failure to save the configuration is still raised as a snackbar, which renders into `LayoutSlots.DesktopSnackbarsRoot`; this layout mounts no snackbar root, so it appears outside the configurator. A layout that wants it inside mounts the root.
1101
+ - **Toolbar actions.** Undo and redo, camera reset, measurements, sharing and language switching are not offered. A layout can mount `LayoutSlots.DesktopToolbar` for the toolbar the spec configures, or build its own buttons.
1102
+ - **View controls, tool controls and leaving a tool mode.** A spec can configure view controls and tool controls, and a tool mode has a button that leaves it. The built-in layout places them with `LayoutSlots.DesktopViewControls`, `LayoutSlots.DesktopToolControls` and `LayoutSlots.DesktopCallToAction`; this layout mounts none of them, so they do not appear. Nor does this layout build view buttons or a camera reset of its own.
1103
+ - **A project's layout dialog.** A dialog the project registers with `phx.frontend.createLayoutDialog` is shown by `LayoutDialog`, which the built-in layout mounts. This layout does not; a layout that needs the dialog mounts `LayoutDialog` from `@salsita-ai/sdk/configurator`.
1104
+ - **Selection.** The table has no parts to select. A product with selectable parts reads `phx.frontend.useSelectedDomainEntityId()` and shows the controls of the selected part.
1105
+ - **Clone to edit in read mode.** With `shareCloneToEdit` set, the built-in layout's `LayoutSlots.DesktopReadonlyBanner` offers **Edit configuration** to a viewer; this layout's "View only" label offers nothing. A layout mounts the banner, or builds its own action with `LayoutSlots.useIsCloningAllowed()` and `LayoutSlots.CloneToEditDialog`.
1106
+ - **Back and close.** The built-in header's back button returns to the product choice, runs the host's close action or leaves the expanded view, whichever applies. This layout has none of them. `useCloseAction()` from `@salsita-ai/sdk/configurator` returns the host's close action, or `null` when there is none; the hook that chooses among the three is not exported.
1107
+ - **Analytics.** The finalize button records `FinalizeButtonClicked` through `useFinalizeAction`. The built-in fullscreen button records `FullScreenModeToggled`; this layout's fullscreen and other buttons record nothing. A layout that wants the events pushes `FrontendAnalyticsFactory.FullScreenModeToggled()` and the like, from `@salsita-ai/sdk/configurator`, with `phx.frontend.useAnalytics().push(...)`.
1108
+ - **Preview start screen.** The built-in layout shows a preview while `usePreview().isPreview` is set, which `preview: auto` does on phones without the project opting in. This layout ignores it and always renders the interface.
1109
+ - **Assistant panel.** A project layout gets it only with `LayoutSlots.DesktopSidebar`, the built-in sidebar as one piece, see [Sidebar in a Custom Layout](?path=/docs/layouts-sidebar-in-a-custom-layout--docs); this layout does not use it.
1110
+ - **"Powered by Salsita" mark.** The built-in layout shows it; a project layout decides whether to mount `LayoutSlots.DesktopPoweredBy`, and this one does not.
1111
+
1112
+ ## Related pages
1113
+
1114
+ - [Configurator Interface → Loaded Configuration UI](?path=/docs/phoenix-configurator-interface--docs#loaded-configuration-ui) is the reference for the element name, the minimal layout, the SDK pieces a layout can mount and the hooks it reads.
1115
+ - [Universal](?path=/docs/layouts-universal--docs) is the built-in layout this one replaces.
1116
+ - [Header / Custom](?path=/docs/layouts-header-custom--docs) replaces only the header, with the price from `useRootEntityPrice()`.
1117
+ - [Canvas Padding](?path=/docs/layouts-canvas-padding--docs) scales the gaps `useSceneSafeArea()` measures.
1118
+ - [Presets / Custom](?path=/docs/layouts-presets-custom--docs) replaces the preset screen the same way.
1119
+
1120
+ ## Complete Implementation
1121
+
1122
+ {/* AUTO-GENERATED SECTION: This section is automatically updated by 'phoenix story build' */}
1123
+
1124
+ <details>
1125
+ <summary>Click to view the complete implementation files</summary>
1126
+
1127
+ ### ./spec/domain.yaml
1128
+
1129
+ ```yaml
1130
+ model:
1131
+ latest:
1132
+ entityTypes:
1133
+ - Table
1134
+ aliases:
1135
+ RootEntity:
1136
+ - Table
1137
+ commonTypes:
1138
+ EdgeFinish:
1139
+ enum:
1140
+ - None
1141
+ - Facet
1142
+ - Round
1143
+ - TreeTrunk
1144
+ shapes:
1145
+ Table:
1146
+ edgeFinish:
1147
+ $ref: '#/model/latest/commonTypes/EdgeFinish'
1148
+
1149
+ presets:
1150
+ - rootEntityType: Table
1151
+ initialDomainEntities:
1152
+ - id: Table-1
1153
+ type: Table
1154
+ edgeFinish: None
1155
+
1156
+ navigationStates:
1157
+ - configure
1158
+ - checkout
1159
+ - requested
1160
+
1161
+ components:
1162
+ Table:
1163
+ detail:
1164
+ main:
1165
+ title: Linden dining table
1166
+ subtitle: Solid oak, seats six
1167
+ description: A solid oak top on slender steel legs, finished with the edge profile of your choice.
1168
+ price:
1169
+ main:
1170
+ price:
1171
+ $read: $price
1172
+ attributeSelection:
1173
+ edgeFinish:
1174
+ value:
1175
+ $bind: edgeFinish
1176
+ options:
1177
+ - label: Square
1178
+ description: Crisp and modern
1179
+ value: None
1180
+ - label: Facet
1181
+ description: A light bevel
1182
+ value: Facet
1183
+ - label: Round
1184
+ description: Soft to the touch
1185
+ value: Round
1186
+ - label: Tree trunk
1187
+ description: The natural edge
1188
+ value: TreeTrunk
1189
+ finalizeAction:
1190
+ quote:
1191
+ variant: primary
1192
+ label: Request a quote
1193
+ title: Send the configuration to the shop for a quote
1194
+ icon: PaperPlaneTilt
1195
+ handler: requestQuote
1196
+ htmlHotspot:
1197
+ edgeFinish:
1198
+ name: EdgeFinishHotspot
1199
+ offsetX: 500
1200
+ offsetY: 800
1201
+ offsetZ: 0
1202
+ $filter:
1203
+ modes: [owner]
1204
+ navigationStates: [configure]
1205
+ fitRootPerspectiveCamera:
1206
+ main:
1207
+ priority: 2
1208
+ fov: 35
1209
+ near: 100
1210
+ far: 10000
1211
+ minDistance: 1000
1212
+ maxDistance: 9000
1213
+ distance: 5000
1214
+ azimuthAngle: 1.5
1215
+ polarAngle: 1.1
1216
+ fitMeshChanges: true
1217
+ $filter:
1218
+ viewports: [desktop]
1219
+ phone:
1220
+ priority: 1
1221
+ fov: 35
1222
+ near: 100
1223
+ far: 10000
1224
+ minDistance: 1000
1225
+ maxDistance: 12000
1226
+ distance: 8000
1227
+ azimuthAngle: 1.5
1228
+ polarAngle: 1.1
1229
+ fitMeshChanges: true
1230
+ $filter:
1231
+ viewports: [mobile]
1232
+ sceneEntityRemoteObject:
1233
+ top:
1234
+ fileUrl: /models/tables.gltf
1235
+ objectName: RECHTHOEK_RECTANGLE
1236
+ dimensionUnit: m
1237
+ leg:
1238
+ fileUrl: /models/tables.gltf
1239
+ objectName: AXL_LEG
1240
+ dimensionUnit: m
1241
+ sceneEntityObjectProperties:
1242
+ top:
1243
+ offsetY: 820
1244
+ morphTargetInfluences:
1245
+ Facet:
1246
+ value:
1247
+ $read: $edgeFinishFacet
1248
+ scaleOne: 1
1249
+ scaleZero: 0
1250
+ Bol:
1251
+ value:
1252
+ $read: $edgeFinishRound
1253
+ scaleOne: 1
1254
+ scaleZero: 0
1255
+ Boomstamrand:
1256
+ value:
1257
+ $read: $edgeFinishTreeTrunk
1258
+ scaleOne: 1
1259
+ scaleZero: 0
1260
+ leg:
1261
+ morphTargetInfluences:
1262
+ Length:
1263
+ value: 1000
1264
+ scaleOne: 3000
1265
+ scaleZero: 1400
1266
+ Width:
1267
+ value: 700
1268
+ scaleOne: 1300
1269
+ scaleZero: 600
1270
+ Height:
1271
+ value: 780
1272
+ scaleOne: 780
1273
+ scaleZero: 740
1274
+
1275
+ transformers:
1276
+ - name: tree-trunk-delivery
1277
+ ```
1278
+
1279
+ ### ./src/\_shared/BottomSheet.tsx
1280
+
1281
+ ```tsx
1282
+ import { Box, ButtonBase } from '@mui/material'
1283
+ import { type MouseEvent, type PointerEvent, type ReactNode, useRef } from 'react'
1284
+
1285
+ // A drag shorter than this is a tap, and the handle's click toggles the sheet
1286
+ const TAP_DISTANCE = 6
1287
+ // A drag that ends this far from where it started snaps to the other position
1288
+ const SNAP_DISTANCE = 48
1289
+ // A drag faster than this, in pixels per millisecond, snaps in its direction even when it is short
1290
+ const SNAP_VELOCITY = 0.5
1291
+ // How far the sheet follows the finger towards the position it is already in
1292
+ const OVERDRAG = 0.25
1293
+
1294
+ interface BottomSheetProps {
1295
+ readonly open: boolean
1296
+ readonly onOpenChange: (open: boolean) => void
1297
+ readonly label: string
1298
+ readonly children: ReactNode
1299
+ }
1300
+
1301
+ interface Drag {
1302
+ readonly startY: number
1303
+ readonly startTime: number
1304
+ delta: number
1305
+ }
1306
+
1307
+ // A persistent sheet at the bottom of the layout with two positions, open and collapsed. It is not a modal: the scene
1308
+ // above it stays interactive. Its height comes from its content, so the layout's safe area ends where the sheet
1309
+ // starts and changes only when the sheet snaps. While the handle is dragged the sheet follows the finger with a
1310
+ // transform, which the safe area does not see, so the camera refits once per snap and not on every move.
1311
+ export const BottomSheet = ({ open, onOpenChange, label, children }: BottomSheetProps) => {
1312
+ const sheetRef = useRef<HTMLElement>(null)
1313
+ const dragRef = useRef<Drag | null>(null)
1314
+ const draggedRef = useRef(false)
1315
+
1316
+ const moveSheet = (offset: number, animate: boolean) => {
1317
+ if (sheetRef.current) {
1318
+ sheetRef.current.style.transition = animate ? 'transform 200ms ease-out' : 'none'
1319
+ sheetRef.current.style.transform = offset === 0 ? '' : `translateY(${offset}px)`
1320
+ }
1321
+ }
1322
+
1323
+ const handlePointerDown = (event: PointerEvent<HTMLButtonElement>) => {
1324
+ event.currentTarget.setPointerCapture(event.pointerId)
1325
+ dragRef.current = { startY: event.clientY, startTime: event.timeStamp, delta: 0 }
1326
+ draggedRef.current = false
1327
+ }
1328
+
1329
+ const handlePointerMove = (event: PointerEvent<HTMLButtonElement>) => {
1330
+ const drag = dragRef.current
1331
+
1332
+ if (drag === null) {
1333
+ return
1334
+ }
1335
+
1336
+ drag.delta = event.clientY - drag.startY
1337
+ // Down closes an open sheet and up opens a collapsed one; the other direction only gives a little
1338
+ const towardsOther = open ? drag.delta > 0 : drag.delta < 0
1339
+ moveSheet(towardsOther ? drag.delta : drag.delta * OVERDRAG, false)
1340
+ }
1341
+
1342
+ const handlePointerEnd = (event: PointerEvent<HTMLButtonElement>) => {
1343
+ const drag = dragRef.current
1344
+ dragRef.current = null
1345
+
1346
+ if (drag === null) {
1347
+ return
1348
+ }
1349
+
1350
+ moveSheet(0, true)
1351
+
1352
+ if (Math.abs(drag.delta) < TAP_DISTANCE) {
1353
+ return
1354
+ }
1355
+
1356
+ draggedRef.current = true
1357
+ const velocity = drag.delta / Math.max(event.timeStamp - drag.startTime, 1)
1358
+ const flicked = Math.abs(drag.delta) >= SNAP_DISTANCE || Math.abs(velocity) >= SNAP_VELOCITY
1359
+
1360
+ if (flicked && (open ? drag.delta > 0 : drag.delta < 0)) {
1361
+ onOpenChange(!open)
1362
+ }
1363
+ }
1364
+
1365
+ // A tap, Enter or Space toggles the sheet. The pointer click that ends a drag has already been handled; a keyboard
1366
+ // click (`detail` 0) always toggles, also after a touch drag that was followed by no click.
1367
+ const handleClick = (event: MouseEvent<HTMLButtonElement>) => {
1368
+ const dragged = draggedRef.current
1369
+ draggedRef.current = false
1370
+
1371
+ if (dragged && event.detail !== 0) {
1372
+ return
1373
+ }
1374
+
1375
+ onOpenChange(!open)
1376
+ }
1377
+
1378
+ return (
1379
+ <Box
1380
+ component="section"
1381
+ ref={sheetRef}
1382
+ aria-label={label}
1383
+ sx={{
1384
+ position: 'relative',
1385
+ flexShrink: 0,
1386
+ display: 'flex',
1387
+ flexDirection: 'column',
1388
+ maxBlockSize: '55%',
1389
+ paddingBlockEnd: 'env(safe-area-inset-bottom)',
1390
+ borderStartStartRadius: 20,
1391
+ borderStartEndRadius: 20,
1392
+ bgcolor: 'background.paper',
1393
+ boxShadow: '0 -8px 32px rgba(0, 0, 0, 0.12)',
1394
+ // Fills the gap below the sheet while it is dragged up
1395
+ '&::after': {
1396
+ content: '""',
1397
+ position: 'absolute',
1398
+ insetInline: 0,
1399
+ insetBlockStart: '100%',
1400
+ blockSize: '100vh',
1401
+ bgcolor: 'background.paper',
1402
+ },
1403
+ }}
1404
+ >
1405
+ <ButtonBase
1406
+ aria-expanded={open}
1407
+ disableRipple
1408
+ aria-label={open ? `Collapse ${label.toLowerCase()}` : `Expand ${label.toLowerCase()}`}
1409
+ onClick={handleClick}
1410
+ onPointerDown={handlePointerDown}
1411
+ onPointerMove={handlePointerMove}
1412
+ onPointerUp={handlePointerEnd}
1413
+ onPointerCancel={handlePointerEnd}
1414
+ sx={{
1415
+ flexShrink: 0,
1416
+ blockSize: 36,
1417
+ touchAction: 'none',
1418
+ '&.Mui-focusVisible': { outline: 2, outlineColor: 'primary.main', outlineOffset: -4, borderRadius: 3 },
1419
+ }}
1420
+ >
1421
+ <Box component="span" sx={{ inlineSize: 40, blockSize: 4, borderRadius: 2, bgcolor: 'text.disabled' }} />
1422
+ </ButtonBase>
1423
+ {/* A column, so the panel's cards region shrinks first and the price and action stay in view; below the cards
1424
+ region's minimum height the content scrolls as a whole, as the review step does on a short phone */}
1425
+ <Box
1426
+ component="div"
1427
+ sx={{
1428
+ display: 'flex',
1429
+ flexDirection: 'column',
1430
+ flex: '1 1 auto',
1431
+ minBlockSize: 0,
1432
+ overflowY: 'auto',
1433
+ overscrollBehavior: 'contain',
1434
+ }}
1435
+ >
1436
+ {children}
1437
+ </Box>
1438
+ </Box>
1439
+ )
1440
+ }
1441
+ ```
1442
+
1443
+ ### ./src/\_shared/EdgeFinishCards.tsx
1444
+
1445
+ ```tsx
1446
+ import { Box, ButtonBase, type SxProps, type Theme, Typography } from '@mui/material'
1447
+ import type { KeyboardEvent } from 'react'
1448
+
1449
+ import { type UseAttributeSelection, useResolveLocalizable } from '@salsita-ai/sdk/react'
1450
+
1451
+ // Cross-sections of the tabletop's edge, keyed by the option values of the spec.
1452
+ const EDGE_PROFILES: Readonly<Record<string, string>> = {
1453
+ None: 'M0 4H32V20H0Z',
1454
+ Facet: 'M0 4H26L32 10V14L26 20H0Z',
1455
+ Round: 'M0 4H24A8 8 0 0 1 24 20H0Z',
1456
+ TreeTrunk: 'M0 4H27C33 5 28 9 34 11C29 13 34 17 27 20H0Z',
1457
+ }
1458
+
1459
+ const CHECK_BADGE: SxProps<Theme> = {
1460
+ position: 'absolute',
1461
+ insetBlockStart: 10,
1462
+ insetInlineEnd: 10,
1463
+ display: 'grid',
1464
+ placeItems: 'center',
1465
+ inlineSize: 22,
1466
+ blockSize: 22,
1467
+ borderRadius: '50%',
1468
+ bgcolor: 'primary.main',
1469
+ color: 'primary.contrastText',
1470
+ fontSize: 13,
1471
+ }
1472
+
1473
+ // Arrow keys move the selection between the cards, as in a native radio group.
1474
+ const ARROW_STEPS: Readonly<Record<string, number | undefined>> = {
1475
+ ArrowRight: 1,
1476
+ ArrowDown: 1,
1477
+ ArrowLeft: -1,
1478
+ ArrowUp: -1,
1479
+ }
1480
+
1481
+ interface EdgeFinishCardsProps {
1482
+ readonly selection: ReturnType<UseAttributeSelection>
1483
+ }
1484
+
1485
+ export const EdgeFinishCards = ({ selection }: EdgeFinishCardsProps) => {
1486
+ const resolve = useResolveLocalizable()
1487
+ const { options } = selection
1488
+ // The card in the tab order: the selected one, or the first enabled one. Disabled cards are never in it.
1489
+ const selectedIndex = options.findIndex(option => option.value === selection.value && !option.disabled)
1490
+ const focusIndex = selectedIndex >= 0 ? selectedIndex : options.findIndex(option => !option.disabled)
1491
+
1492
+ const handleKeyDown = (event: KeyboardEvent<HTMLElement>, index: number) => {
1493
+ const step = ARROW_STEPS[event.key]
1494
+
1495
+ if (step === undefined) {
1496
+ return
1497
+ }
1498
+
1499
+ event.preventDefault()
1500
+ // The next enabled option in the arrow's direction, wrapping around; disabled options are skipped.
1501
+ const count = options.length
1502
+ const next = Array.from(
1503
+ { length: count - 1 },
1504
+ (_, offset) => (((index + step * (offset + 1)) % count) + count) % count
1505
+ ).find(candidate => !options[candidate]?.disabled)
1506
+
1507
+ if (next === undefined) {
1508
+ return
1509
+ }
1510
+
1511
+ options[next]?.onSelect()
1512
+ event.currentTarget.parentElement?.querySelectorAll<HTMLElement>('[role="radio"]')[next]?.focus()
1513
+ }
1514
+
1515
+ return (
1516
+ <Box
1517
+ component="div"
1518
+ role="radiogroup"
1519
+ aria-label="Edge finish"
1520
+ sx={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 1.5 }}
1521
+ >
1522
+ {options.map((option, index) => {
1523
+ const isSelected = option.value === selection.value
1524
+
1525
+ return (
1526
+ <ButtonBase
1527
+ key={String(option.value)}
1528
+ role="radio"
1529
+ aria-checked={isSelected}
1530
+ tabIndex={index === focusIndex ? 0 : -1}
1531
+ disabled={option.disabled}
1532
+ onClick={option.onSelect}
1533
+ onKeyDown={event => handleKeyDown(event, index)}
1534
+ sx={{
1535
+ position: 'relative',
1536
+ display: 'grid',
1537
+ justifyItems: 'start',
1538
+ gap: 0.5,
1539
+ padding: 2,
1540
+ borderRadius: 3,
1541
+ border: 2,
1542
+ borderColor: isSelected ? 'primary.main' : 'divider',
1543
+ bgcolor: isSelected ? 'action.hover' : 'transparent',
1544
+ textAlign: 'start',
1545
+ transition: 'border-color 150ms, background-color 150ms',
1546
+ '&:hover': { borderColor: isSelected ? 'primary.main' : 'text.disabled' },
1547
+ '&.Mui-focusVisible': { outline: 2, outlineColor: 'primary.main', outlineOffset: 2 },
1548
+ '&.Mui-disabled': { opacity: 0.4 },
1549
+ }}
1550
+ >
1551
+ {isSelected && (
1552
+ <Box component="span" aria-hidden sx={CHECK_BADGE}>
1553
+ ✓
1554
+ </Box>
1555
+ )}
1556
+ <Box
1557
+ component="svg"
1558
+ viewBox="0 0 36 24"
1559
+ aria-hidden
1560
+ sx={{ inlineSize: 60, blockSize: 40, color: '#b98b5e' }}
1561
+ >
1562
+ <path d={EDGE_PROFILES[String(option.value)]} fill="currentColor" />
1563
+ </Box>
1564
+ <Typography variant="body2" fontWeight={600}>
1565
+ {resolve(option.title)}
1566
+ </Typography>
1567
+ <Typography variant="caption" color="text.secondary">
1568
+ {resolve(option.description)}
1569
+ </Typography>
1570
+ </ButtonBase>
1571
+ )
1572
+ })}
1573
+ </Box>
1574
+ )
1575
+ }
1576
+ ```
1577
+
1578
+ ### ./src/\_shared/FullscreenToggle.tsx
1579
+
1580
+ ```tsx
1581
+ import { Button, Tooltip } from '@mui/material'
1582
+
1583
+ import { phx } from '../../.phoenix/phx.js'
1584
+
1585
+ const ENTER_ICON = 'M4 9V4h5M15 4h5v5M20 15v5h-5M9 20H4v-5'
1586
+ const EXIT_ICON = 'M9 4v5H4M15 4v5h5M15 20v-5h5M9 20v-5H4'
1587
+
1588
+ interface FullscreenToggleProps {
1589
+ // Only the icon, with the label as its accessible name and tooltip, where a label would cover the product
1590
+ readonly compact?: boolean
1591
+ }
1592
+
1593
+ export const FullscreenToggle = ({ compact = false }: FullscreenToggleProps) => {
1594
+ const isFullscreen = phx.frontend.useIsFullscreenActive()
1595
+ const setFullscreen = phx.frontend.useSetFullscreen()
1596
+ const label = isFullscreen ? 'Exit fullscreen' : 'Fullscreen'
1597
+ const icon = (
1598
+ <svg viewBox="0 0 24 24" width={20} height={20} fill="none" stroke="currentColor" strokeWidth={2}>
1599
+ <path d={isFullscreen ? EXIT_ICON : ENTER_ICON} />
1600
+ </svg>
1601
+ )
1602
+
1603
+ return (
1604
+ <Tooltip title={compact ? label : ''}>
1605
+ <Button
1606
+ onClick={() => setFullscreen(!isFullscreen)}
1607
+ aria-label={compact ? label : undefined}
1608
+ startIcon={compact ? undefined : icon}
1609
+ sx={{
1610
+ minBlockSize: 44,
1611
+ minInlineSize: 44,
1612
+ paddingInline: compact ? 0 : 2.5,
1613
+ borderRadius: 999,
1614
+ bgcolor: 'background.paper',
1615
+ color: 'text.primary',
1616
+ boxShadow: '0 4px 16px rgba(0, 0, 0, 0.12)',
1617
+ textTransform: 'none',
1618
+ fontWeight: 600,
1619
+ '&:hover': { bgcolor: 'background.paper', boxShadow: '0 6px 20px rgba(0, 0, 0, 0.18)' },
1620
+ }}
1621
+ >
1622
+ {compact ? icon : label}
1623
+ </Button>
1624
+ </Tooltip>
1625
+ )
1626
+ }
1627
+ ```
1628
+
1629
+ ### ./src/\_shared/MessageToast.tsx
1630
+
1631
+ ```tsx
1632
+ import { Alert } from '@mui/material'
1633
+ import { useEffect, useState } from 'react'
1634
+
1635
+ import type { ConfiguratorMessage } from '@salsita-ai/sdk/react'
1636
+
1637
+ import { phx } from '../../.phoenix/phx.js'
1638
+
1639
+ const VIOLATION_SEVERITY = { fatal: 'error', fixable: 'error', warning: 'warning', notice: 'info' } as const
1640
+ const RANK = { info: 0, warning: 1, error: 2 } as const
1641
+ const VISIBLE_MS = 6000
1642
+
1643
+ const severityOf = (message: ConfiguratorMessage) => {
1644
+ if (message.kind === 'violation') {
1645
+ return VIOLATION_SEVERITY[message.violation.severity]
1646
+ }
1647
+
1648
+ return message.kind === 'error' ? 'error' : 'info'
1649
+ }
1650
+
1651
+ export const MessageToast = () => {
1652
+ const [message, setMessage] = useState<ConfiguratorMessage | null>(null)
1653
+
1654
+ // A rejected change also reports the notices already present; keep the worst message while it is shown
1655
+ phx.frontend.useConfiguratorMessages(next => {
1656
+ setMessage(shown => (shown !== null && RANK[severityOf(next)] < RANK[severityOf(shown)] ? shown : next))
1657
+ })
1658
+
1659
+ useEffect(() => {
1660
+ if (message === null) {
1661
+ return
1662
+ }
1663
+
1664
+ const timeout = setTimeout(() => setMessage(null), VISIBLE_MS)
1665
+
1666
+ return () => clearTimeout(timeout)
1667
+ }, [message])
1668
+
1669
+ if (message === null) {
1670
+ return null
1671
+ }
1672
+
1673
+ return (
1674
+ <Alert
1675
+ severity={severityOf(message)}
1676
+ onClose={() => setMessage(null)}
1677
+ sx={{ maxInlineSize: 420, borderRadius: 3, boxShadow: '0 12px 40px rgba(0, 0, 0, 0.12)' }}
1678
+ >
1679
+ {message.text}
1680
+ </Alert>
1681
+ )
1682
+ }
1683
+ ```
1684
+
1685
+ ### ./src/\_shared/ProductPanel.tsx
1686
+
1687
+ ```tsx
1688
+ import { Box, Button, Typography } from '@mui/material'
1689
+
1690
+ import { detailComponent, finalizeActionComponent } from '@salsita-ai/sdk/domain'
1691
+ import { PhoenixHandlerBoundary, useResolveLocalizable } from '@salsita-ai/sdk/react'
1692
+
1693
+ import { phx } from '../../.phoenix/phx.js'
1694
+ import { EdgeFinishCards } from './EdgeFinishCards.js'
1695
+ import { ReviewActions } from './ReviewActions.js'
1696
+
1697
+ const BUTTON_SX = { minBlockSize: 48, borderRadius: 999, textTransform: 'none', fontWeight: 600 } as const
1698
+
1699
+ interface ProductPanelProps {
1700
+ // Only the name, the price and the action, for a collapsed bottom sheet
1701
+ readonly collapsed?: boolean
1702
+ // In a bottom sheet the cards region keeps the top of the cards in view, and the sheet scrolls when that does not fit
1703
+ readonly inSheet?: boolean
1704
+ }
1705
+
1706
+ export const ProductPanel = ({ collapsed = false, inSheet = false }: ProductPanelProps) => {
1707
+ const resolve = useResolveLocalizable()
1708
+ const detail = phx.frontend.useFirstRootComponent(detailComponent)
1709
+ const price = phx.frontend.useRootEntityPrice()
1710
+ const tableId = phx.frontend.useRootEntityId()
1711
+ const edgeFinish = phx.frontend.useAttributeSelection(tableId, 'edgeFinish')
1712
+ const { accessInfo } = phx.frontend.useAccessService()
1713
+ const navigationState = phx.frontend.useActiveNavigationState()
1714
+ const pushNavigate = phx.frontend.usePushNavigate()
1715
+ const popNavigate = phx.frontend.usePopNavigate()
1716
+ const navigate = phx.frontend.useNavigate()
1717
+ const finalizeActions = phx.frontend.useComponents({ componentTypes: [finalizeActionComponent] }) ?? []
1718
+
1719
+ const isReadMode = accessInfo.viewMode
1720
+ const isReview = isReadMode || navigationState !== 'configure'
1721
+ // The spec's primary finalize action, or its first one
1722
+ const quoteAction = finalizeActions.find(action => action.state.variant === 'primary') ?? finalizeActions[0]
1723
+ const chosenEdge = edgeFinish.options.find(option => option.value === edgeFinish.value)
1724
+
1725
+ return (
1726
+ <>
1727
+ <Box component="header" sx={{ display: 'grid', gap: 1, padding: 3, paddingBlockEnd: 2 }}>
1728
+ {isReadMode ? (
1729
+ <Typography
1730
+ variant="overline"
1731
+ sx={{ justifySelf: 'start', paddingInline: 1.5, borderRadius: 999, bgcolor: 'action.selected' }}
1732
+ >
1733
+ View only
1734
+ </Typography>
1735
+ ) : (
1736
+ <Typography variant="overline" color="text.secondary">
1737
+ {resolve(detail?.state.subtitle)}
1738
+ </Typography>
1739
+ )}
1740
+ <Typography variant="h5" component="h2" fontWeight={700}>
1741
+ {resolve(detail?.state.title)}
1742
+ </Typography>
1743
+ {!collapsed && (
1744
+ <Typography variant="body2" color="text.secondary">
1745
+ {resolve(detail?.state.description)}
1746
+ </Typography>
1747
+ )}
1748
+ </Box>
1749
+
1750
+ {!collapsed && (
1751
+ <Box
1752
+ component="div"
1753
+ sx={{
1754
+ display: 'grid',
1755
+ gap: 1.5,
1756
+ paddingInline: 3,
1757
+ paddingBlockEnd: 3,
1758
+ // In a short container the region shrinks and scrolls; a scroll past its end moves its container on
1759
+ minBlockSize: inSheet ? 120 : 0,
1760
+ overflowY: 'auto',
1761
+ }}
1762
+ >
1763
+ <Typography variant="subtitle2" component="h3">
1764
+ Edge finish
1765
+ </Typography>
1766
+ {isReview ? (
1767
+ <Box component="div" sx={{ padding: 2, borderRadius: 3, bgcolor: 'action.hover' }}>
1768
+ <Typography variant="body2" fontWeight={600}>
1769
+ {resolve(chosenEdge?.title)}
1770
+ </Typography>
1771
+ <Typography variant="caption" color="text.secondary">
1772
+ {resolve(chosenEdge?.description)}
1773
+ </Typography>
1774
+ </Box>
1775
+ ) : (
1776
+ <EdgeFinishCards selection={edgeFinish} />
1777
+ )}
1778
+ </Box>
1779
+ )}
1780
+
1781
+ <Box component="footer" sx={{ display: 'grid', gap: 2, padding: 3, borderTop: 1, borderColor: 'divider' }}>
1782
+ <Box component="div" sx={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between' }}>
1783
+ <Typography variant="body2" color="text.secondary">
1784
+ Total price
1785
+ </Typography>
1786
+ <Typography variant="h5" component="p" fontWeight={700}>
1787
+ {price}
1788
+ </Typography>
1789
+ </Box>
1790
+ {!isReadMode && navigationState === 'configure' && (
1791
+ <Button size="large" variant="contained" onClick={() => pushNavigate('checkout')} sx={BUTTON_SX}>
1792
+ Review and request a quote
1793
+ </Button>
1794
+ )}
1795
+ {!isReadMode && navigationState === 'checkout' && (
1796
+ <>
1797
+ <Typography variant="body2" color="text.secondary">
1798
+ Your table is ready for a quote. A real shop collects the contact details here.
1799
+ </Typography>
1800
+ {quoteAction ? (
1801
+ <PhoenixHandlerBoundary name={quoteAction.state.handler}>
1802
+ <ReviewActions
1803
+ handler={quoteAction.state.handler}
1804
+ label={resolve(quoteAction.state.label)}
1805
+ onBack={popNavigate}
1806
+ />
1807
+ </PhoenixHandlerBoundary>
1808
+ ) : (
1809
+ <Button size="large" variant="outlined" onClick={popNavigate} sx={BUTTON_SX}>
1810
+ Back to the configuration
1811
+ </Button>
1812
+ )}
1813
+ </>
1814
+ )}
1815
+ {!isReadMode && navigationState === 'requested' && (
1816
+ <>
1817
+ <Typography variant="body2" role="status">
1818
+ Thank you. Your quote request is on its way, and the shop replies within two working days.
1819
+ </Typography>
1820
+ <Button size="large" variant="outlined" onClick={() => navigate('configure')} sx={BUTTON_SX}>
1821
+ Back to the configuration
1822
+ </Button>
1823
+ </>
1824
+ )}
1825
+ </Box>
1826
+ </>
1827
+ )
1828
+ }
1829
+ ```
1830
+
1831
+ ### ./src/\_shared/ReviewActions.tsx
1832
+
1833
+ ```tsx
1834
+ import { Alert, Box, Button, CircularProgress, Typography } from '@mui/material'
1835
+
1836
+ import { phx } from '../../.phoenix/phx.js'
1837
+
1838
+ const BUTTON_SX = { minBlockSize: 48, borderRadius: 999, textTransform: 'none', fontWeight: 600 } as const
1839
+
1840
+ interface ReviewActionsProps {
1841
+ readonly handler: string
1842
+ readonly label: string
1843
+ readonly onBack: () => void
1844
+ }
1845
+
1846
+ // The review step's buttons: one finalize action and the way back. `useFinalizeAction` runs the action's handler,
1847
+ // so this component calls it exactly once and is rendered inside a `PhoenixHandlerBoundary` for that handler.
1848
+ // Back is disabled while the request is pending, so the customer cannot change the configuration while it is sent.
1849
+ // The pending state belongs to this component: when the panel unmounts, as in fullscreen, the request goes on and
1850
+ // the remounted buttons are enabled again.
1851
+ export const ReviewActions = ({ handler, label, onBack }: ReviewActionsProps) => {
1852
+ const { run, isPending, error, disabled, hints } = phx.frontend.useFinalizeAction(handler)
1853
+
1854
+ return (
1855
+ <Box component="div" sx={{ display: 'grid', gap: 2 }}>
1856
+ <Box component="div" sx={{ display: 'grid', gap: 1 }}>
1857
+ <Button
1858
+ size="large"
1859
+ variant="contained"
1860
+ disabled={disabled || isPending}
1861
+ onClick={run}
1862
+ startIcon={isPending ? <CircularProgress size={18} color="inherit" /> : undefined}
1863
+ sx={BUTTON_SX}
1864
+ >
1865
+ {isPending ? 'Sending the request…' : label}
1866
+ </Button>
1867
+ {disabled && hints && (
1868
+ <Typography variant="caption" color="text.secondary">
1869
+ {hints}
1870
+ </Typography>
1871
+ )}
1872
+ {error && <Alert severity="error">The request could not be sent: {error.message}</Alert>}
1873
+ </Box>
1874
+ <Button size="large" variant="outlined" disabled={isPending} onClick={onBack} sx={BUTTON_SX}>
1875
+ Back to the configuration
1876
+ </Button>
1877
+ </Box>
1878
+ )
1879
+ }
1880
+ ```
1881
+
1882
+ ### ./src/elements/ConfiguratorLayout.tsx
1883
+
1884
+ ```tsx
1885
+ import { Box } from '@mui/material'
1886
+ import { useState } from 'react'
1887
+
1888
+ import { LayoutSlots, useSceneSafeArea } from '@salsita-ai/sdk/configurator'
1889
+ import { useActiveViewports } from '@salsita-ai/sdk/react'
1890
+
1891
+ import { phx } from '../../.phoenix/phx.js'
1892
+ import { BottomSheet } from '../_shared/BottomSheet.js'
1893
+ import { FullscreenToggle } from '../_shared/FullscreenToggle.js'
1894
+ import { MessageToast } from '../_shared/MessageToast.js'
1895
+ import { ProductPanel } from '../_shared/ProductPanel.js'
1896
+
1897
+ const GUTTER = 24
1898
+ const PANEL_WIDTH = 380
1899
+ const PHONE_GUTTER = 16
1900
+ const TOUCH_TARGET = 44
1901
+
1902
+ const ConfiguratorLayout = () => {
1903
+ const { containerRef, safeAreaRef } = useSceneSafeArea()
1904
+ const isFullscreen = phx.frontend.useIsFullscreenActive()
1905
+ // `null` until the container has been measured; the layout shows only the scene until then
1906
+ const viewports = useActiveViewports()
1907
+ const [isSheetOpen, setSheetOpen] = useState(false)
1908
+
1909
+ const isDesktop = viewports?.includes('desktop') ?? false
1910
+ const gutter = isDesktop ? GUTTER : PHONE_GUTTER
1911
+
1912
+ return (
1913
+ <Box
1914
+ component="div"
1915
+ ref={containerRef}
1916
+ // `clip`, not `hidden`: what overflows, such as the sheet's fill below its edge, can never be scrolled into view,
1917
+ // not even by the browser when the focus moves
1918
+ sx={{ position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', overflow: 'clip' }}
1919
+ >
1920
+ <LayoutSlots.SharedCanvas />
1921
+
1922
+ {viewports !== null && isDesktop && !isFullscreen && (
1923
+ <Box
1924
+ component="aside"
1925
+ sx={{
1926
+ position: 'absolute',
1927
+ insetBlockStart: GUTTER,
1928
+ insetInlineStart: GUTTER,
1929
+ inlineSize: PANEL_WIDTH,
1930
+ maxBlockSize: `calc(100% - ${2 * GUTTER}px)`,
1931
+ display: 'flex',
1932
+ flexDirection: 'column',
1933
+ borderRadius: 5,
1934
+ bgcolor: 'background.paper',
1935
+ boxShadow: '0 12px 40px rgba(0, 0, 0, 0.12)',
1936
+ // A very short container, such as a phone held sideways, scrolls the panel instead of clipping its footer
1937
+ overflowX: 'hidden',
1938
+ overflowY: 'auto',
1939
+ overscrollBehavior: 'contain',
1940
+ }}
1941
+ >
1942
+ <ProductPanel />
1943
+ </Box>
1944
+ )}
1945
+
1946
+ {viewports !== null && (
1947
+ <Box
1948
+ component="div"
1949
+ ref={safeAreaRef}
1950
+ sx={{
1951
+ // Desktop: right of the panel. Phone: above the sheet, whose height the column gives the rest.
1952
+ ...(isDesktop
1953
+ ? {
1954
+ position: 'absolute',
1955
+ insetBlock: 0,
1956
+ insetInlineStart: isFullscreen ? 0 : PANEL_WIDTH + 2 * GUTTER,
1957
+ insetInlineEnd: 0,
1958
+ }
1959
+ : { position: 'relative', flex: '1 1 0', minBlockSize: 0 }),
1960
+ pointerEvents: 'none',
1961
+ }}
1962
+ >
1963
+ <Box
1964
+ component="div"
1965
+ sx={{ position: 'absolute', insetBlockStart: gutter, insetInlineEnd: gutter, pointerEvents: 'auto' }}
1966
+ >
1967
+ <FullscreenToggle compact={!isDesktop} />
1968
+ </Box>
1969
+ <Box
1970
+ component="div"
1971
+ sx={{
1972
+ position: 'absolute',
1973
+ pointerEvents: 'auto',
1974
+ // Desktop: bottom left. Phone: at the top beside the fullscreen button, clear of the sheet's handle.
1975
+ ...(isDesktop
1976
+ ? { insetBlockEnd: GUTTER, insetInlineStart: GUTTER }
1977
+ : { insetBlockStart: gutter, insetInline: `${gutter}px ${2 * gutter + TOUCH_TARGET}px` }),
1978
+ }}
1979
+ >
1980
+ <MessageToast />
1981
+ </Box>
1982
+ </Box>
1983
+ )}
1984
+
1985
+ {viewports !== null && !isDesktop && !isFullscreen && (
1986
+ <BottomSheet open={isSheetOpen} onOpenChange={setSheetOpen} label="Product details">
1987
+ <ProductPanel collapsed={!isSheetOpen} inSheet />
1988
+ </BottomSheet>
1989
+ )}
1990
+ </Box>
1991
+ )
1992
+ }
1993
+
1994
+ export default phx.frontend.createCustomPhoenixLayoutElement('ConfiguratorLayout', ConfiguratorLayout)
1995
+ ```
1996
+
1997
+ ### ./src/elements/EdgeFinishHotspot.tsx
1998
+
1999
+ ```tsx
2000
+ import { Box, ButtonBase, Popover, Tooltip } from '@mui/material'
2001
+ import { useState } from 'react'
2002
+
2003
+ import { phx } from '../../.phoenix/phx.js'
2004
+ import { EdgeFinishCards } from '../_shared/EdgeFinishCards.js'
2005
+
2006
+ // The edge profile the button shows, the square edge of the cards' drawings
2007
+ const EDGE_ICON = 'M3 7H17L21 11V13L17 17H3Z'
2008
+
2009
+ // Rendered by the spec's `htmlHotspot` on the table's edge: a button on the product that opens the edge finishes
2010
+ // next to the edge, with the same cards as the panel. The spec's `$filter` decides when it is shown.
2011
+ export default phx.frontend.createCustomPhoenixEntityElement('EdgeFinishHotspot', ({ entityId }) => {
2012
+ const edgeFinish = phx.frontend.useAttributeSelection(entityId, 'edgeFinish')
2013
+ const [anchor, setAnchor] = useState<HTMLElement | null>(null)
2014
+
2015
+ return (
2016
+ <>
2017
+ <Tooltip title="Edge finish">
2018
+ <ButtonBase
2019
+ aria-label="Edge finish"
2020
+ aria-haspopup="dialog"
2021
+ aria-expanded={anchor !== null}
2022
+ onClick={event => setAnchor(event.currentTarget)}
2023
+ sx={{
2024
+ display: 'grid',
2025
+ placeItems: 'center',
2026
+ inlineSize: 44,
2027
+ blockSize: 44,
2028
+ borderRadius: '50%',
2029
+ bgcolor: 'background.paper',
2030
+ color: 'primary.main',
2031
+ boxShadow: '0 4px 16px rgba(0, 0, 0, 0.2)',
2032
+ '&.Mui-focusVisible': { outline: 2, outlineColor: 'primary.main', outlineOffset: 2 },
2033
+ }}
2034
+ >
2035
+ <svg viewBox="0 0 24 24" width={22} height={22} aria-hidden>
2036
+ <path d={EDGE_ICON} fill="currentColor" />
2037
+ </svg>
2038
+ </ButtonBase>
2039
+ </Tooltip>
2040
+ <Popover
2041
+ open={anchor !== null}
2042
+ anchorEl={anchor}
2043
+ onClose={() => setAnchor(null)}
2044
+ anchorOrigin={{ vertical: 'top', horizontal: 'center' }}
2045
+ transformOrigin={{ vertical: 'bottom', horizontal: 'center' }}
2046
+ slotProps={{
2047
+ paper: { role: 'dialog', 'aria-label': 'Edge finish', sx: { borderRadius: 4, marginBlockStart: -1 } },
2048
+ }}
2049
+ >
2050
+ <Box component="div" sx={{ inlineSize: 340, maxInlineSize: 'calc(100vw - 32px)', padding: 2 }}>
2051
+ <EdgeFinishCards selection={edgeFinish} />
2052
+ </Box>
2053
+ </Popover>
2054
+ </>
2055
+ )
2056
+ })
2057
+ ```
2058
+
2059
+ ### ./src/expressions/table.ts
2060
+
2061
+ ```ts
2062
+ import { phx } from '../../.phoenix/phx.js'
2063
+
2064
+ const EDGE_FINISH_SURCHARGE = { None: 0, Facet: 90, Round: 120, TreeTrunk: 340 }
2065
+
2066
+ export default phx.domain.implementCustomExpressions({
2067
+ Table: {
2068
+ $price: s => {
2069
+ return 1290 + EDGE_FINISH_SURCHARGE[s.edgeFinish]
2070
+ },
2071
+ $edgeFinishFacet: s => {
2072
+ return s.edgeFinish === 'Facet' ? 1 : 0
2073
+ },
2074
+ $edgeFinishRound: s => {
2075
+ return s.edgeFinish === 'Round' ? 1 : 0
2076
+ },
2077
+ $edgeFinishTreeTrunk: s => {
2078
+ return s.edgeFinish === 'TreeTrunk' ? 1 : 0
2079
+ },
2080
+ },
2081
+ })
2082
+ ```
2083
+
2084
+ ### ./src/handlers/request-quote.ts
2085
+
2086
+ ```ts
2087
+ import { useCallback } from 'react'
2088
+
2089
+ import { phx } from '../../.phoenix/phx.js'
2090
+
2091
+ // Stands in for the shop's quote service: the request takes a moment, then the panel shows the confirmation
2092
+ export default phx.frontend.createPhoenixHandler('requestQuote', () => {
2093
+ const navigate = phx.frontend.useNavigate()
2094
+
2095
+ return {
2096
+ handler: useCallback(async () => {
2097
+ await new Promise(resolve => setTimeout(resolve, 1500))
2098
+ navigate('requested')
2099
+ }, [navigate]),
2100
+ }
2101
+ })
2102
+ ```
2103
+
2104
+ ### ./src/transformers/tree-trunk-delivery.ts
2105
+
2106
+ ```ts
2107
+ import { msg } from '@lingui/core/macro'
2108
+
2109
+ import { phx } from '../../.phoenix/phx.js'
2110
+
2111
+ // A tree-trunk edge is cut by hand; choosing it carries a hint, which reaches the layout as a side-effect message
2112
+ export default phx.frontend.transformerBuilder.when
2113
+ .entity(['Table'])
2114
+ .modified({ atFields: ['edgeFinish'] })
2115
+ .then.overrideMatchingUpdate(({ update }) => {
2116
+ if (update.stateDelta.edgeFinish !== 'TreeTrunk') {
2117
+ return [update]
2118
+ }
2119
+
2120
+ return [{ ...update, hint: msg`Tree trunk edges are cut by hand, so delivery takes three weeks longer.` }]
2121
+ })
2122
+ .build('tree-trunk-delivery')
2123
+ ```
2124
+
2125
+ </details>