@pixel-point/toolcraft 0.0.14 → 0.0.15

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 (577) hide show
  1. package/README.md +13 -2
  2. package/package.json +1 -1
  3. package/scripts/prepare-pack.mjs +2 -28
  4. package/src/cli-io.mjs +174 -0
  5. package/src/cli-package-manager-flow.test.mjs +206 -0
  6. package/src/cli.mjs +7 -495
  7. package/src/cli.test.mjs +34 -177
  8. package/src/create-command.mjs +152 -0
  9. package/src/create-options.mjs +194 -0
  10. package/src/generate-test-assertions.mjs +27 -0
  11. package/src/generate-test-doc-assertions.mjs +162 -0
  12. package/src/generate-test-file-assertions.mjs +251 -0
  13. package/src/generate-test-fs-utils.mjs +59 -0
  14. package/src/generate-test-integrity-assertions.mjs +30 -0
  15. package/src/generate-test-integrity-framework-assertions.mjs +222 -0
  16. package/src/generate-test-integrity-manifest-assertions.mjs +150 -0
  17. package/src/generate-test-integrity-ownership-assertions.mjs +127 -0
  18. package/src/generate-test-integrity-test-utils.mjs +102 -0
  19. package/src/generate-test-package-assertions.mjs +75 -0
  20. package/src/generate-test-product-fixture.mjs +84 -0
  21. package/src/generate-test-product-fixture.test.mjs +73 -0
  22. package/src/generate-test-standalone-assertions.mjs +209 -0
  23. package/src/generate-transactional.test.mjs +151 -0
  24. package/src/generate.mjs +182 -102
  25. package/src/generate.test.mjs +242 -338
  26. package/src/generated-integrity-manifest.mjs +127 -0
  27. package/src/generated-integrity-manifest.test.mjs +151 -0
  28. package/src/generated-source-policy.mjs +22 -0
  29. package/src/generated-source-policy.test.mjs +35 -0
  30. package/src/generated-target-policy.mjs +55 -0
  31. package/src/generation-transaction.mjs +135 -0
  32. package/src/generation-transaction.test.mjs +74 -0
  33. package/src/package-json.mjs +18 -1
  34. package/src/package-json.test.mjs +38 -5
  35. package/src/package-manager.mjs +2 -0
  36. package/src/package-manager.test.mjs +5 -1
  37. package/src/packaged-template-fallback.test.mjs +126 -0
  38. package/src/prepare-pack-templates.mjs +34 -0
  39. package/templates/runtime/contracts/component-contract-builders.ts +95 -0
  40. package/templates/runtime/contracts/component-contracts.choices.test.ts +293 -0
  41. package/templates/runtime/contracts/component-contracts.choices.ts +322 -0
  42. package/templates/runtime/contracts/component-contracts.inputs.test.ts +210 -0
  43. package/templates/runtime/contracts/component-contracts.inputs.ts +252 -0
  44. package/templates/runtime/contracts/component-contracts.media-custom.test.ts +116 -0
  45. package/templates/runtime/contracts/component-contracts.media-custom.ts +144 -0
  46. package/templates/runtime/contracts/component-contracts.runtime-acceptance.test.ts +229 -0
  47. package/templates/runtime/contracts/component-contracts.runtime-labels.test.ts +76 -0
  48. package/templates/runtime/contracts/component-contracts.runtime-panels.test.ts +135 -0
  49. package/templates/runtime/contracts/component-contracts.runtime-shell.test.ts +148 -0
  50. package/templates/runtime/contracts/component-contracts.runtime.ts +361 -0
  51. package/templates/runtime/contracts/component-contracts.test.ts +39 -1458
  52. package/templates/runtime/contracts/component-contracts.ts +44 -1505
  53. package/templates/runtime/contracts/component-contracts.visual.test.ts +316 -0
  54. package/templates/runtime/contracts/component-contracts.visual.ts +385 -0
  55. package/templates/runtime/contracts/contract-manifest.test.ts +64 -0
  56. package/templates/runtime/contracts/decision-contracts.test.ts +29 -4
  57. package/templates/runtime/contracts/decision-contracts.ts +38 -1
  58. package/templates/runtime/index.ts +1 -0
  59. package/templates/runtime/react/{runtime-public-api.test.tsx → app-shell/runtime-public-api.test.tsx} +29 -7
  60. package/templates/runtime/react/{settings-transfer.test.ts → app-shell/settings-transfer.test.ts} +4 -4
  61. package/templates/runtime/react/{settings-transfer.ts → app-shell/settings-transfer.ts} +3 -3
  62. package/templates/runtime/react/{toolbar-panel.test.tsx → app-shell/toolbar-panel.test.tsx} +2 -2
  63. package/templates/runtime/react/{toolbar-panel.tsx → app-shell/toolbar-panel.tsx} +3 -3
  64. package/templates/runtime/react/{toolcraft-app.integration.test.tsx → app-shell/toolcraft-app.integration.test.tsx} +6 -6
  65. package/templates/runtime/react/{toolcraft-app.test.tsx → app-shell/toolcraft-app.test.tsx} +3 -3
  66. package/templates/runtime/react/{toolcraft-app.tsx → app-shell/toolcraft-app.tsx} +11 -8
  67. package/templates/runtime/react/{toolcraft-root.test.tsx → app-shell/toolcraft-root.test.tsx} +1 -1
  68. package/templates/runtime/react/{toolcraft-root.tsx → app-shell/toolcraft-root.tsx} +5 -5
  69. package/templates/runtime/react/{use-toolcraft.ts → app-shell/use-toolcraft.ts} +1 -1
  70. package/templates/runtime/react/canvas/canvas-default-media-layer.tsx +81 -0
  71. package/templates/runtime/react/canvas/canvas-media-transform.ts +33 -0
  72. package/templates/runtime/react/canvas/canvas-shell-interactions.test.tsx +156 -0
  73. package/templates/runtime/react/canvas/canvas-shell-media-rendering.test.tsx +87 -0
  74. package/templates/runtime/react/canvas/canvas-shell-rendering.test.tsx +102 -0
  75. package/templates/runtime/react/canvas/canvas-shell-test-utils.tsx +108 -0
  76. package/templates/runtime/react/canvas/canvas-shell-upload.test.tsx +106 -0
  77. package/templates/runtime/react/canvas/canvas-shell.tsx +146 -0
  78. package/templates/runtime/react/canvas/canvas-upload-routing.ts +84 -0
  79. package/templates/runtime/react/{media-file.ts → canvas/media-file.ts} +1 -1
  80. package/templates/runtime/react/canvas/use-canvas-drop-import.ts +145 -0
  81. package/templates/runtime/react/canvas/use-canvas-viewport-interactions.ts +227 -0
  82. package/templates/runtime/react/controls-panel/__tests__/control-conditions.test.ts +89 -0
  83. package/templates/runtime/react/{controls-panel-filedrop-reorder.test.tsx → controls-panel/__tests__/controls-panel-filedrop-reorder.test.tsx} +4 -4
  84. package/templates/runtime/react/controls-panel/__tests__/controls-panel.actions-layout.test.tsx +91 -0
  85. package/templates/runtime/react/controls-panel/__tests__/controls-panel.collection-actions.test.tsx +213 -0
  86. package/templates/runtime/react/controls-panel/__tests__/controls-panel.color-layout.test.tsx +366 -0
  87. package/templates/runtime/react/controls-panel/__tests__/controls-panel.color-picker-models.test.tsx +169 -0
  88. package/templates/runtime/react/controls-panel/__tests__/controls-panel.compound-layout.test.tsx +160 -0
  89. package/templates/runtime/react/controls-panel/__tests__/controls-panel.control-visibility.test.tsx +352 -0
  90. package/templates/runtime/react/controls-panel/__tests__/controls-panel.curves.test.tsx +226 -0
  91. package/templates/runtime/react/controls-panel/__tests__/controls-panel.floating-shell.test.tsx +13 -0
  92. package/templates/runtime/react/controls-panel/__tests__/controls-panel.font-picker.test.tsx +302 -0
  93. package/templates/runtime/react/controls-panel/__tests__/controls-panel.footer-actions.test.tsx +401 -0
  94. package/templates/runtime/react/controls-panel/__tests__/controls-panel.gradient-control.test.tsx +139 -0
  95. package/templates/runtime/react/controls-panel/__tests__/controls-panel.help-policy.test.tsx +103 -0
  96. package/templates/runtime/react/controls-panel/__tests__/controls-panel.image-picker.test.tsx +16 -0
  97. package/templates/runtime/react/controls-panel/__tests__/controls-panel.inline-layout.test.tsx +290 -0
  98. package/templates/runtime/react/controls-panel/__tests__/controls-panel.keyframes.test.tsx +175 -0
  99. package/templates/runtime/react/controls-panel/__tests__/controls-panel.media-upload-files.test.tsx +121 -0
  100. package/templates/runtime/react/controls-panel/__tests__/controls-panel.media-upload-image.test.tsx +363 -0
  101. package/templates/runtime/react/controls-panel/__tests__/controls-panel.media-upload-multi-image.test.tsx +105 -0
  102. package/templates/runtime/react/controls-panel/__tests__/controls-panel.range-slider-editing.test.tsx +149 -0
  103. package/templates/runtime/react/controls-panel/__tests__/controls-panel.section-shell.test.tsx +138 -0
  104. package/templates/runtime/react/controls-panel/__tests__/controls-panel.select-layout.test.tsx +148 -0
  105. package/templates/runtime/react/controls-panel/__tests__/controls-panel.setup-controls.test.tsx +189 -0
  106. package/templates/runtime/react/controls-panel/__tests__/controls-panel.slider-rendering.test.tsx +317 -0
  107. package/templates/runtime/react/controls-panel/__tests__/controls-panel.state-reset.test.tsx +137 -0
  108. package/templates/runtime/react/controls-panel/__tests__/controls-panel.structure.test.tsx +66 -0
  109. package/templates/runtime/react/controls-panel/__tests__/controls-panel.text-input.test.tsx +205 -0
  110. package/templates/runtime/react/controls-panel/__tests__/controls-panel.vector-interaction.test.tsx +388 -0
  111. package/templates/runtime/react/controls-panel/__tests__/controls-panel.vector-layout.test.tsx +155 -0
  112. package/templates/runtime/react/controls-panel/actions/controls-panel-actions.ts +172 -0
  113. package/templates/runtime/react/{control-conditions.ts → controls-panel/conditions/control-conditions.ts} +26 -3
  114. package/templates/runtime/react/{control-renderers.ts → controls-panel/control-renderers.ts} +2 -2
  115. package/templates/runtime/react/controls-panel/controls-panel.tsx +250 -0
  116. package/templates/runtime/react/controls-panel/keyframes/controls-panel-keyframes.tsx +265 -0
  117. package/templates/runtime/react/controls-panel/layout/controls-panel-collapse-storage.ts +131 -0
  118. package/templates/runtime/react/controls-panel/layout/controls-panel-help.tsx +132 -0
  119. package/templates/runtime/react/controls-panel/layout/controls-panel-inline-layout.tsx +311 -0
  120. package/templates/runtime/react/controls-panel/layout/controls-panel-layout.tsx +216 -0
  121. package/templates/runtime/react/controls-panel/layout/controls-panel-section.tsx +401 -0
  122. package/templates/runtime/react/controls-panel/renderers/controls-panel-action-renderer.tsx +137 -0
  123. package/templates/runtime/react/controls-panel/renderers/controls-panel-basic-renderers.tsx +421 -0
  124. package/templates/runtime/react/controls-panel/renderers/controls-panel-collection-renderer.tsx +286 -0
  125. package/templates/runtime/react/controls-panel/renderers/controls-panel-compound-renderers.tsx +366 -0
  126. package/templates/runtime/react/controls-panel/renderers/controls-panel-media-renderer.tsx +173 -0
  127. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.test.ts +23 -0
  128. package/templates/runtime/react/controls-panel/renderers/controls-panel-renderer-registry.ts +55 -0
  129. package/templates/runtime/react/controls-panel/renderers/controls-panel-settings-transfer-renderer.tsx +45 -0
  130. package/templates/runtime/react/controls-panel/testing/controls-panel-test-utils.tsx +300 -0
  131. package/templates/runtime/react/controls-panel/values/controls-panel-aspect-ratio-values.ts +94 -0
  132. package/templates/runtime/react/controls-panel/values/controls-panel-collection-values.ts +87 -0
  133. package/templates/runtime/react/controls-panel/values/controls-panel-color-values.ts +82 -0
  134. package/templates/runtime/react/controls-panel/values/controls-panel-font-values.ts +69 -0
  135. package/templates/runtime/react/controls-panel/values/controls-panel-spatial-values.ts +64 -0
  136. package/templates/runtime/react/controls-panel/values/controls-panel-value-labels.ts +93 -0
  137. package/templates/runtime/react/controls-panel/values/controls-panel-value-primitives.ts +30 -0
  138. package/templates/runtime/react/controls-panel/values/controls-panel-values.ts +7 -0
  139. package/templates/runtime/react/index.ts +13 -13
  140. package/templates/runtime/react/{layer-tree.ts → layers/layer-tree.ts} +1 -1
  141. package/templates/runtime/react/layers/layers-panel-drag-drop.test.tsx +190 -0
  142. package/templates/runtime/react/layers/layers-panel-editing.test.tsx +41 -0
  143. package/templates/runtime/react/layers/layers-panel-insert-targets.test.ts +87 -0
  144. package/templates/runtime/react/layers/layers-panel-insert-targets.ts +254 -0
  145. package/templates/runtime/react/layers/layers-panel-model.ts +15 -0
  146. package/templates/runtime/react/layers/layers-panel-rendering.test.tsx +43 -0
  147. package/templates/runtime/react/layers/layers-panel-reorder.ts +148 -0
  148. package/templates/runtime/react/layers/layers-panel-row.tsx +475 -0
  149. package/templates/runtime/react/layers/layers-panel-test-utils.tsx +135 -0
  150. package/templates/runtime/react/layers/layers-panel-visibility.test.tsx +126 -0
  151. package/templates/runtime/react/layers/layers-panel.tsx +349 -0
  152. package/templates/runtime/react/layers/use-layers-panel-drag-controller.ts +327 -0
  153. package/templates/runtime/react/{panel-host-types.ts → panel-host/panel-host-types.ts} +2 -2
  154. package/templates/runtime/react/{panel-host.test.tsx → panel-host/panel-host.test.tsx} +3 -3
  155. package/templates/runtime/react/{panel-host.tsx → panel-host/panel-host.tsx} +1 -1
  156. package/templates/runtime/react/timeline/timeline-easing-editor.tsx +409 -0
  157. package/templates/runtime/react/timeline/timeline-easing-geometry.ts +42 -0
  158. package/templates/runtime/react/timeline/timeline-easing-icons.tsx +135 -0
  159. package/templates/runtime/react/timeline/timeline-easing-model.test.ts +87 -0
  160. package/templates/runtime/react/timeline/timeline-easing-model.ts +271 -0
  161. package/templates/runtime/react/timeline/timeline-easing-popover-content.tsx +227 -0
  162. package/templates/runtime/react/timeline/timeline-easing-popover-layout.ts +69 -0
  163. package/templates/runtime/react/timeline/timeline-easing-popover.tsx +60 -0
  164. package/templates/runtime/react/timeline/timeline-event-targets.ts +19 -0
  165. package/templates/runtime/react/timeline/timeline-expanded-content.tsx +299 -0
  166. package/templates/runtime/react/timeline/timeline-icon-button.tsx +51 -0
  167. package/templates/runtime/react/timeline/timeline-keyframe-row.tsx +360 -0
  168. package/templates/runtime/react/timeline/timeline-keyframes.ts +23 -0
  169. package/templates/runtime/react/timeline/timeline-panel-floating-layout.test.tsx +328 -0
  170. package/templates/runtime/react/timeline/timeline-panel-header.tsx +404 -0
  171. package/templates/runtime/react/timeline/timeline-panel-keyframes.test.tsx +90 -0
  172. package/templates/runtime/react/timeline/timeline-panel-layout.ts +30 -0
  173. package/templates/runtime/react/timeline/timeline-panel-responsive-layout.ts +182 -0
  174. package/templates/runtime/react/timeline/timeline-panel-test-utils.tsx +88 -0
  175. package/templates/runtime/react/timeline/timeline-panel-transport.test.tsx +202 -0
  176. package/templates/runtime/react/timeline/timeline-panel-upload.test.tsx +53 -0
  177. package/templates/runtime/react/timeline/timeline-panel.tsx +449 -0
  178. package/templates/runtime/react/timeline/timeline-playback-hooks.ts +254 -0
  179. package/templates/runtime/schema/assembly-contract.ts +271 -0
  180. package/templates/runtime/schema/control-schema-normalization.ts +55 -0
  181. package/templates/runtime/schema/controls-panel-actions.ts +141 -0
  182. package/templates/runtime/schema/controls-panel-layout-groups.ts +144 -0
  183. package/templates/runtime/schema/controls-panel-normalization.ts +19 -0
  184. package/templates/runtime/schema/controls-panel-section-layout.ts +140 -0
  185. package/templates/runtime/schema/controls-panel-section-titles.ts +161 -0
  186. package/templates/runtime/schema/define-toolcraft-test-utils.ts +18 -0
  187. package/templates/runtime/schema/define-toolcraft.actions.test.ts +182 -0
  188. package/templates/runtime/schema/define-toolcraft.assembly.test.ts +246 -0
  189. package/templates/runtime/schema/define-toolcraft.canvas.test.ts +150 -0
  190. package/templates/runtime/schema/define-toolcraft.control-layout.test.ts +345 -0
  191. package/templates/runtime/schema/define-toolcraft.control-sections.test.ts +304 -0
  192. package/templates/runtime/schema/define-toolcraft.setup-canvas.test.ts +69 -0
  193. package/templates/runtime/schema/define-toolcraft.setup-persistence.test.ts +66 -0
  194. package/templates/runtime/schema/define-toolcraft.setup-render-scale-timeline.test.ts +116 -0
  195. package/templates/runtime/schema/define-toolcraft.setup-settings-transfer.test.ts +291 -0
  196. package/templates/runtime/schema/define-toolcraft.ts +24 -1449
  197. package/templates/runtime/schema/panels-schema-normalization.ts +49 -0
  198. package/templates/runtime/schema/runtime-section-titles.ts +1 -0
  199. package/templates/runtime/schema/runtime-setup-section.ts +282 -0
  200. package/templates/runtime/schema/schema-resolvers.ts +168 -0
  201. package/templates/runtime/schema/types.ts +17 -0
  202. package/templates/runtime/state/canvas-reducer.ts +106 -0
  203. package/templates/runtime/state/canvas-state.ts +209 -0
  204. package/templates/runtime/state/controls-reducer.ts +277 -0
  205. package/templates/runtime/state/history-patches.ts +174 -0
  206. package/templates/runtime/state/layer-state.ts +12 -0
  207. package/templates/runtime/state/layers-reducer.ts +318 -0
  208. package/templates/runtime/state/media-reducer.ts +283 -0
  209. package/templates/runtime/state/media-state.ts +105 -0
  210. package/templates/runtime/state/panels-reducer.ts +58 -0
  211. package/templates/runtime/state/persistence-merge.ts +53 -0
  212. package/templates/runtime/state/persistence-reader-canvas.ts +27 -0
  213. package/templates/runtime/state/persistence-reader-layers.ts +73 -0
  214. package/templates/runtime/state/persistence-reader-media.ts +82 -0
  215. package/templates/runtime/state/persistence-reader-panels.ts +56 -0
  216. package/templates/runtime/state/persistence-reader-primitives.ts +27 -0
  217. package/templates/runtime/state/persistence-reader-timeline.ts +145 -0
  218. package/templates/runtime/state/persistence-reader-values.ts +40 -0
  219. package/templates/runtime/state/persistence-readers.ts +72 -0
  220. package/templates/runtime/state/persistence-shared.ts +23 -0
  221. package/templates/runtime/state/persistence-snapshot.ts +57 -0
  222. package/templates/runtime/state/persistence.ts +16 -581
  223. package/templates/runtime/state/reducer-canvas-panels.test.ts +324 -0
  224. package/templates/runtime/state/reducer-controls.test.ts +158 -0
  225. package/templates/runtime/state/reducer-layers.test.ts +306 -0
  226. package/templates/runtime/state/reducer-media-import-sizing.test.ts +197 -0
  227. package/templates/runtime/state/reducer-media-reorder-history.test.ts +116 -0
  228. package/templates/runtime/state/reducer-media-reset.test.ts +249 -0
  229. package/templates/runtime/state/reducer-media-test-utils.ts +40 -0
  230. package/templates/runtime/state/reducer-media-transform.test.ts +52 -0
  231. package/templates/runtime/state/reducer-test-utils.ts +30 -0
  232. package/templates/runtime/state/reducer-timeline.test.ts +242 -0
  233. package/templates/runtime/state/reducer.ts +57 -1785
  234. package/templates/runtime/state/timeline-reducer.ts +412 -0
  235. package/templates/runtime/state/timeline-values.test.ts +45 -0
  236. package/templates/runtime/state/timeline-values.ts +45 -0
  237. package/templates/runtime/testing/performance-browser-policy.test.ts +81 -0
  238. package/templates/runtime/testing/performance-browser-policy.ts +102 -0
  239. package/templates/runtime/testing/performance-budget-validation.ts +84 -0
  240. package/templates/runtime/testing/performance-control-classification.test.ts +106 -0
  241. package/templates/runtime/testing/performance-control-classification.ts +101 -0
  242. package/templates/runtime/testing/performance-coverage-context.ts +44 -0
  243. package/templates/runtime/testing/performance-coverage-renderer-policy.ts +221 -0
  244. package/templates/runtime/testing/performance-coverage-scenario-policy.ts +147 -0
  245. package/templates/runtime/testing/performance-coverage-target-policy.ts +121 -0
  246. package/templates/runtime/testing/performance-coverage-validator.ts +34 -0
  247. package/templates/runtime/testing/performance-export-coverage.test.ts +234 -0
  248. package/templates/runtime/testing/performance-fixture-load-profile.test.ts +360 -0
  249. package/templates/runtime/testing/performance-fixture-media.test.ts +80 -0
  250. package/templates/runtime/testing/performance-fixture-stress.test.ts +314 -0
  251. package/templates/runtime/testing/performance-fixture-validation.ts +255 -0
  252. package/templates/runtime/testing/performance-fixture-values.ts +275 -0
  253. package/templates/runtime/testing/performance-fixture-workload.test.ts +140 -0
  254. package/templates/runtime/testing/performance-load-profile-validation.ts +467 -0
  255. package/templates/runtime/testing/performance-renderer-coverage.test.ts +325 -0
  256. package/templates/runtime/testing/performance-renderer-pipeline-coverage.test.ts +115 -0
  257. package/templates/runtime/testing/performance-renderer-pipeline-interactions.test.ts +201 -0
  258. package/templates/runtime/testing/performance-renderer-pipeline-passes.test.ts +247 -0
  259. package/templates/runtime/testing/performance-renderer-pipeline-validation.ts +360 -0
  260. package/templates/runtime/testing/performance-renderer-technique-validation.ts +329 -0
  261. package/templates/runtime/testing/performance-renderer-technique.test.ts +275 -0
  262. package/templates/runtime/testing/performance-renderer-validation.ts +2 -0
  263. package/templates/runtime/testing/performance-scenario-coverage.test.ts +303 -0
  264. package/templates/runtime/testing/performance-scenario-validation.ts +49 -0
  265. package/templates/runtime/testing/performance-schema-derived-workloads.test.ts +231 -0
  266. package/templates/runtime/testing/performance-schema-queries.ts +181 -0
  267. package/templates/runtime/testing/performance-test-fixtures/index.ts +4 -0
  268. package/templates/runtime/testing/performance-test-fixtures/renderer-pipelines.ts +322 -0
  269. package/templates/runtime/testing/performance-test-fixtures/scenarios.ts +172 -0
  270. package/templates/runtime/testing/performance-test-fixtures/schemas.ts +151 -0
  271. package/templates/runtime/testing/performance-test-fixtures/stress-fixtures.ts +133 -0
  272. package/templates/runtime/testing/performance-test-fixtures.ts +1 -0
  273. package/templates/runtime/testing/performance-types.ts +338 -0
  274. package/templates/runtime/testing/performance.ts +15 -2485
  275. package/templates/starter/AGENTS.md +41 -26
  276. package/templates/starter/LICENSE.md +18 -95
  277. package/templates/starter/NOTICE.md +3 -3
  278. package/templates/starter/docs/toolcraft/README.md +5 -5
  279. package/templates/starter/docs/toolcraft/acceptance-testing.md +36 -8
  280. package/templates/starter/docs/toolcraft/assembly-workflow.md +12 -15
  281. package/templates/starter/docs/toolcraft/component-rules.md +6 -2
  282. package/templates/starter/docs/toolcraft/core/layout.md +1 -0
  283. package/templates/starter/docs/toolcraft/core/performance.md +7 -4
  284. package/templates/starter/docs/toolcraft/core/runtime-boundary.md +11 -4
  285. package/templates/starter/docs/toolcraft/core/timeline-animation.md +1 -0
  286. package/templates/starter/docs/toolcraft/custom-controls.md +8 -1
  287. package/templates/starter/docs/toolcraft/decision-contract.md +26 -22
  288. package/templates/starter/docs/toolcraft/performance.md +42 -16
  289. package/templates/starter/docs/toolcraft/schema-reference.md +34 -23
  290. package/templates/starter/docs/toolcraft/workflow.md +25 -17
  291. package/templates/starter/e2e/app-browser-acceptance-evidence.spec.ts +111 -0
  292. package/templates/starter/e2e/app-browser-control-coverage.spec.ts +118 -0
  293. package/templates/starter/e2e/app-browser-performance-evidence.spec.ts +217 -0
  294. package/templates/starter/e2e/app-browser-performance-probe.spec.ts +322 -0
  295. package/templates/starter/e2e/app-browser-proof-session.spec.ts +196 -0
  296. package/templates/starter/e2e/app-browser-runtime-evidence.spec.ts +9 -0
  297. package/templates/starter/e2e/app-browser-runtime-provenance.spec.ts +240 -0
  298. package/templates/starter/e2e/app-browser-runtime-reporter.spec.ts +180 -0
  299. package/templates/starter/e2e/app-browser-runtime-requirements.spec.ts +310 -0
  300. package/templates/starter/e2e/app-browser-semantic-evidence.spec.ts +432 -0
  301. package/templates/starter/e2e/app-browser-shell.spec.ts +171 -0
  302. package/templates/starter/e2e/app-browser-timeline-layers-reference.spec.ts +70 -0
  303. package/templates/starter/e2e/app-performance.spec.ts +0 -491
  304. package/templates/starter/e2e/browser-acceptance-outcome-helpers.ts +97 -0
  305. package/templates/starter/e2e/browser-acceptance-transition-helpers.ts +74 -0
  306. package/templates/starter/e2e/browser-conditional-output-evidence-helpers.ts +230 -0
  307. package/templates/starter/e2e/browser-control-target-helpers.ts +109 -0
  308. package/templates/starter/e2e/browser-layer-evidence-helpers.ts +229 -0
  309. package/templates/starter/e2e/browser-proof-session.ts +392 -0
  310. package/templates/starter/e2e/browser-runtime-evidence-reporter.ts +136 -0
  311. package/templates/starter/e2e/browser-runtime-evidence-requirements.ts +290 -0
  312. package/templates/starter/e2e/browser-runtime-evidence.ts +27 -0
  313. package/templates/starter/e2e/browser-state-evidence-helpers.ts +205 -0
  314. package/templates/starter/e2e/browser-timeline-evidence-helpers.ts +319 -0
  315. package/templates/starter/e2e/canvas-handle-helpers.ts +86 -4
  316. package/templates/starter/e2e/export-artifact-helpers.ts +132 -0
  317. package/templates/starter/e2e/performance-budget-helpers.ts +159 -0
  318. package/templates/starter/e2e/performance-canvas-helpers.ts +123 -0
  319. package/templates/starter/e2e/performance-control-helpers.ts +3 -0
  320. package/templates/starter/e2e/performance-control-layout-helpers.ts +161 -0
  321. package/templates/starter/e2e/performance-fixture-helpers.ts +217 -0
  322. package/templates/starter/e2e/performance-frame-probe.ts +247 -0
  323. package/templates/starter/e2e/performance-helpers.ts +6 -854
  324. package/templates/starter/e2e/performance-interaction-measurement.ts +251 -0
  325. package/templates/starter/e2e/performance-measurement-evidence.ts +72 -0
  326. package/templates/starter/e2e/performance-output-action-helpers.spec.ts +43 -0
  327. package/templates/starter/e2e/performance-output-action-helpers.ts +109 -0
  328. package/templates/starter/e2e/performance-probe-helpers.ts +12 -0
  329. package/templates/starter/e2e/performance-render-scale-helpers.ts +55 -0
  330. package/templates/starter/e2e/performance-slider-helpers.ts +271 -0
  331. package/templates/starter/e2e/product-observable-helpers.ts +72 -17
  332. package/templates/starter/e2e/stable-outcome-helpers.ts +208 -0
  333. package/templates/starter/package.json +10 -5
  334. package/templates/starter/playwright.config.ts +5 -2
  335. package/templates/starter/scripts/check-ai-skills.mjs +14 -8
  336. package/templates/starter/scripts/check-ai-skills.test.mjs +64 -0
  337. package/templates/starter/scripts/check-toolcraft-code-health.mjs +108 -0
  338. package/templates/starter/scripts/check-toolcraft-code-health.test.mjs +213 -0
  339. package/templates/starter/scripts/check-toolcraft-docs.mjs +47 -34
  340. package/templates/starter/scripts/check-toolcraft-integrity.mjs +141 -144
  341. package/templates/starter/scripts/run-browser-performance.mjs +118 -0
  342. package/templates/starter/scripts/run-browser-performance.test.mjs +53 -0
  343. package/templates/starter/scripts/toolcraft-code-health-core.mjs +297 -0
  344. package/templates/starter/scripts/toolcraft-contract-doc-integrity.mjs +77 -0
  345. package/templates/starter/scripts/toolcraft-contract-doc-integrity.test.mjs +81 -0
  346. package/templates/starter/scripts/toolcraft-contract-manifest.json +113 -0
  347. package/templates/starter/scripts/toolcraft-contract-manifest.mjs +128 -0
  348. package/templates/starter/scripts/toolcraft-contract-manifest.test.mjs +72 -0
  349. package/templates/starter/scripts/toolcraft-integrity-manifest.mjs +172 -0
  350. package/templates/starter/scripts/toolcraft-integrity-policy.mjs +60 -0
  351. package/templates/starter/scripts/toolcraft-product-boundary-ast-utils.mjs +23 -0
  352. package/templates/starter/scripts/toolcraft-product-boundary-ast.mjs +264 -0
  353. package/templates/starter/scripts/toolcraft-product-boundary-test-fixtures.mjs +16 -0
  354. package/templates/starter/scripts/toolcraft-product-boundary.mjs +243 -0
  355. package/templates/starter/scripts/toolcraft-product-boundary.test.mjs +289 -0
  356. package/templates/starter/scripts/toolcraft-product-dependency-graph.mjs +176 -0
  357. package/templates/starter/scripts/toolcraft-product-dependency-graph.test.mjs +196 -0
  358. package/templates/starter/scripts/toolcraft-product-dependency-resolution.mjs +279 -0
  359. package/templates/starter/scripts/toolcraft-product-evidence-boundary-ast.mjs +91 -0
  360. package/templates/starter/scripts/toolcraft-product-evidence-boundary.test.mjs +138 -0
  361. package/templates/starter/scripts/toolcraft-product-style-boundary.mjs +219 -0
  362. package/templates/starter/scripts/toolcraft-source-inventory.d.mts +46 -0
  363. package/templates/starter/scripts/toolcraft-source-inventory.mjs +187 -0
  364. package/templates/starter/scripts/toolcraft-source-inventory.test.mjs +132 -0
  365. package/templates/starter/scripts/toolcraft-source-ownership.mjs +167 -0
  366. package/templates/starter/scripts/toolcraft-source-ownership.test.mjs +113 -0
  367. package/templates/starter/scripts/toolcraft-static-string.mjs +104 -0
  368. package/templates/starter/scripts/toolcraft-verification-inventory.mjs +123 -0
  369. package/templates/starter/scripts/toolcraft-verification-receipt.d.mts +19 -0
  370. package/templates/starter/scripts/toolcraft-verification-receipt.mjs +284 -0
  371. package/templates/starter/scripts/toolcraft-verification-receipt.test.mjs +282 -0
  372. package/templates/starter/scripts/toolcraft-vitest-runtime-contract.d.mts +32 -0
  373. package/templates/starter/scripts/toolcraft-vitest-runtime-contract.mjs +88 -0
  374. package/templates/starter/scripts/toolcraft-vitest-runtime-contract.test.mjs +87 -0
  375. package/templates/starter/scripts/toolcraft-vitest-runtime-evidence-reporter.mjs +109 -0
  376. package/templates/starter/scripts/toolcraft-vitest-runtime-evidence-reporter.test.mjs +111 -0
  377. package/templates/starter/scripts/toolcraft-workflow-routes.mjs +314 -0
  378. package/templates/starter/scripts/toolcraft-workflow-routes.test.mjs +204 -0
  379. package/templates/starter/src/app/acceptance/actions.ts +30 -0
  380. package/templates/starter/src/app/acceptance/animation-intent.ts +184 -0
  381. package/templates/starter/src/app/acceptance/canvas-handle-acceptance.ts +70 -0
  382. package/templates/starter/src/app/acceptance/color-bank-labels.ts +164 -0
  383. package/templates/starter/src/app/acceptance/conditions.ts +80 -0
  384. package/templates/starter/src/app/acceptance/control-acceptance-context.ts +70 -0
  385. package/templates/starter/src/app/acceptance/control-acceptance-coverage.ts +271 -0
  386. package/templates/starter/src/app/acceptance/control-acceptance-kind-rules.ts +149 -0
  387. package/templates/starter/src/app/acceptance/control-acceptance-policy.ts +189 -0
  388. package/templates/starter/src/app/acceptance/control-acceptance.ts +70 -0
  389. package/templates/starter/src/app/acceptance/control-component-rules.ts +277 -0
  390. package/templates/starter/src/app/acceptance/control-labels.ts +223 -0
  391. package/templates/starter/src/app/acceptance/control-layout-dependency-rules.ts +107 -0
  392. package/templates/starter/src/app/acceptance/control-layout-entity-rules.ts +104 -0
  393. package/templates/starter/src/app/acceptance/control-layout-model.ts +159 -0
  394. package/templates/starter/src/app/acceptance/control-layout-section-rules.ts +164 -0
  395. package/templates/starter/src/app/acceptance/control-layout.ts +45 -0
  396. package/templates/starter/src/app/acceptance/control-order.ts +123 -0
  397. package/templates/starter/src/app/acceptance/control-parts.ts +47 -0
  398. package/templates/starter/src/app/acceptance/control-section-inventory.ts +183 -0
  399. package/templates/starter/src/app/acceptance/controls.ts +27 -0
  400. package/templates/starter/src/app/acceptance/coverage.ts +81 -0
  401. package/templates/starter/src/app/acceptance/custom-controls.ts +140 -0
  402. package/templates/starter/src/app/acceptance/inline-layout.ts +192 -0
  403. package/templates/starter/src/app/acceptance/media-upload.ts +55 -0
  404. package/templates/starter/src/app/acceptance/output-background-rules.ts +135 -0
  405. package/templates/starter/src/app/acceptance/output-export-actions.test.ts +76 -0
  406. package/templates/starter/src/app/acceptance/output-export-actions.ts +34 -0
  407. package/templates/starter/src/app/acceptance/output-export-layout-rules.ts +46 -0
  408. package/templates/starter/src/app/acceptance/output-export-model.ts +146 -0
  409. package/templates/starter/src/app/acceptance/output-export.ts +45 -0
  410. package/templates/starter/src/app/acceptance/output-image-export-rules.ts +87 -0
  411. package/templates/starter/src/app/acceptance/reference-runtime-coverage.ts +96 -0
  412. package/templates/starter/src/app/acceptance/reference-runtime-feature-inventory.ts +171 -0
  413. package/templates/starter/src/app/acceptance/reference-runtime-study.ts +73 -0
  414. package/templates/starter/src/app/acceptance/reference-runtime-timeline.ts +291 -0
  415. package/templates/starter/src/app/acceptance/reference-runtime.ts +99 -0
  416. package/templates/starter/src/app/acceptance/runtime-coverage.ts +290 -0
  417. package/templates/starter/src/app/acceptance/runtime-setup.ts +117 -0
  418. package/templates/starter/src/app/acceptance/section-title-rules.ts +8 -0
  419. package/templates/starter/src/app/acceptance/sections.ts +6 -0
  420. package/templates/starter/src/app/acceptance/semantic.ts +63 -0
  421. package/templates/starter/src/app/acceptance/timeline-loop.ts +21 -0
  422. package/templates/starter/src/app/acceptance/types.ts +363 -0
  423. package/templates/starter/src/app/acceptance/validate-coverage.ts +217 -0
  424. package/templates/starter/src/app/acceptance/validation-pipeline.test.ts +56 -0
  425. package/templates/starter/src/app/acceptance/validation-pipeline.ts +60 -0
  426. package/templates/starter/src/app/acceptance/video-reference-study.ts +217 -0
  427. package/templates/starter/src/app/starter-acceptance-data.ts +21 -0
  428. package/templates/starter/src/app/starter-acceptance.animation-intent.test.ts +383 -0
  429. package/templates/starter/src/app/starter-acceptance.background-export.test.ts +349 -0
  430. package/templates/starter/src/app/starter-acceptance.base-coverage.test.ts +47 -0
  431. package/templates/starter/src/app/starter-acceptance.canvas-handles.test.ts +71 -0
  432. package/templates/starter/src/app/starter-acceptance.canvas-sizing.test.ts +199 -0
  433. package/templates/starter/src/app/starter-acceptance.compound-part-coverage.test.ts +244 -0
  434. package/templates/starter/src/app/starter-acceptance.contract-fixtures.ts +63 -0
  435. package/templates/starter/src/app/starter-acceptance.control-label-context.test.ts +172 -0
  436. package/templates/starter/src/app/starter-acceptance.control-labels.test.ts +387 -0
  437. package/templates/starter/src/app/starter-acceptance.control-naming-rules.test.ts +215 -0
  438. package/templates/starter/src/app/starter-acceptance.control-order.test.ts +274 -0
  439. package/templates/starter/src/app/starter-acceptance.control-state.test.ts +275 -0
  440. package/templates/starter/src/app/starter-acceptance.curves-rules.test.ts +184 -0
  441. package/templates/starter/src/app/starter-acceptance.custom-control-collections.test.ts +200 -0
  442. package/templates/starter/src/app/starter-acceptance.custom-control-coverage.test.ts +111 -0
  443. package/templates/starter/src/app/starter-acceptance.custom-control-fit-check.test.ts +135 -0
  444. package/templates/starter/src/app/starter-acceptance.custom-control-valid.test.ts +74 -0
  445. package/templates/starter/src/app/starter-acceptance.export-test-utils.ts +107 -0
  446. package/templates/starter/src/app/starter-acceptance.framework-boundary.test.ts +327 -0
  447. package/templates/starter/src/app/starter-acceptance.image-video-export-settings.test.ts +328 -0
  448. package/templates/starter/src/app/starter-acceptance.keyframes.test.ts +242 -0
  449. package/templates/starter/src/app/starter-acceptance.layers.test.ts +203 -0
  450. package/templates/starter/src/app/starter-acceptance.media-upload.fixtures.ts +110 -0
  451. package/templates/starter/src/app/starter-acceptance.media-upload.test.ts +153 -0
  452. package/templates/starter/src/app/starter-acceptance.output-export-rules.test.ts +97 -0
  453. package/templates/starter/src/app/starter-acceptance.persistence-coverage.test.ts +134 -0
  454. package/templates/starter/src/app/starter-acceptance.product-output-export.test.ts +202 -0
  455. package/templates/starter/src/app/starter-acceptance.product-readiness-rules.test.ts +14 -0
  456. package/templates/starter/src/app/starter-acceptance.product-readiness-test-utils.ts +25 -0
  457. package/templates/starter/src/app/starter-acceptance.product-readiness.test.ts +50 -0
  458. package/templates/starter/src/app/starter-acceptance.product-worklog.test.ts +27 -0
  459. package/templates/starter/src/app/starter-acceptance.range-slider-rules.test.ts +147 -0
  460. package/templates/starter/src/app/starter-acceptance.reference-custom-timeline.test.ts +197 -0
  461. package/templates/starter/src/app/starter-acceptance.reference-foundation.test.ts +304 -0
  462. package/templates/starter/src/app/starter-acceptance.reference-test-utils.ts +102 -0
  463. package/templates/starter/src/app/starter-acceptance.reference-timeline.test.ts +267 -0
  464. package/templates/starter/src/app/starter-acceptance.schema-test-utils.ts +116 -0
  465. package/templates/starter/src/app/starter-acceptance.section-cohesion.test.ts +373 -0
  466. package/templates/starter/src/app/starter-acceptance.section-dependencies.test.ts +260 -0
  467. package/templates/starter/src/app/starter-acceptance.section-titles.test.ts +180 -0
  468. package/templates/starter/src/app/starter-acceptance.setup-readiness.test.ts +205 -0
  469. package/templates/starter/src/app/starter-acceptance.slider-rules.test.ts +470 -0
  470. package/templates/starter/src/app/starter-acceptance.slider-unit-rules.test.ts +61 -0
  471. package/templates/starter/src/app/starter-acceptance.source-test-utils.ts +43 -0
  472. package/templates/starter/src/app/starter-acceptance.test-utils.ts +21 -0
  473. package/templates/starter/src/app/starter-acceptance.text-control-kind.test.ts +89 -0
  474. package/templates/starter/src/app/starter-acceptance.timeline-playback.test.ts +264 -0
  475. package/templates/starter/src/app/starter-acceptance.timeline-test-utils.ts +56 -0
  476. package/templates/starter/src/app/starter-acceptance.toggle-pair-layout.test.ts +200 -0
  477. package/templates/starter/src/app/starter-acceptance.toggle-parameter-layout.test.ts +277 -0
  478. package/templates/starter/src/app/starter-acceptance.ts +151 -4576
  479. package/templates/starter/src/app/starter-acceptance.video-export-test-utils.ts +37 -0
  480. package/templates/starter/src/app/starter-acceptance.video-reference-study.test.ts +262 -0
  481. package/templates/starter/src/app/starter-acceptance.worklog-test-utils.ts +428 -0
  482. package/templates/starter/src/app/starter-acceptance.worklog.test.ts +283 -0
  483. package/templates/starter/src/app/starter-automated-runtime-evidence.test.ts +34 -0
  484. package/templates/starter/src/app/starter-composition.tsx +7 -0
  485. package/templates/starter/src/app/starter-performance-test-utils.ts +275 -0
  486. package/templates/starter/src/app/starter-performance.budgets.test.ts +374 -0
  487. package/templates/starter/src/app/starter-performance.gates.test.ts +128 -0
  488. package/templates/starter/src/app/starter-performance.renderer-source.test.ts +136 -0
  489. package/templates/starter/src/app/starter-performance.renderer-technique.test.ts +291 -0
  490. package/templates/starter/src/app/starter-performance.scenarios.test.ts +355 -0
  491. package/templates/starter/src/app/test-evidence/browser-runtime-contract.test.ts +253 -0
  492. package/templates/starter/src/app/test-evidence/browser-runtime-contract.ts +243 -0
  493. package/templates/starter/src/routes/index.tsx +11 -2
  494. package/templates/starter/vite.config.ts +8 -0
  495. package/templates/ui/components/controls/color/color-control-types.ts +38 -0
  496. package/templates/ui/components/controls/color/color-control.tsx +19 -437
  497. package/templates/ui/components/controls/color/color-footer.tsx +272 -0
  498. package/templates/ui/components/controls/color/color-model-slider.tsx +271 -0
  499. package/templates/ui/components/controls/color/color-opacity-input.tsx +94 -0
  500. package/templates/ui/components/controls/color/color-surface.tsx +194 -0
  501. package/templates/ui/components/controls/color/color-value-control.tsx +159 -0
  502. package/templates/ui/components/controls/color/color-value-utils.ts +170 -0
  503. package/templates/ui/components/controls/color/index.ts +10 -3
  504. package/templates/ui/components/controls/color/palette-control-controller.ts +407 -0
  505. package/templates/ui/components/controls/color/palette-control-layout.ts +14 -0
  506. package/templates/ui/components/controls/color/palette-control-types.ts +50 -0
  507. package/templates/ui/components/controls/color/palette-control-view.tsx +174 -0
  508. package/templates/ui/components/controls/color/palette-control.tsx +6 -544
  509. package/templates/ui/components/controls/color/style-guide-color-picker-color-utils.ts +39 -0
  510. package/templates/ui/components/controls/color/style-guide-color-picker-controller.ts +370 -0
  511. package/templates/ui/components/controls/color/style-guide-color-picker-hue-handlers.ts +66 -0
  512. package/templates/ui/components/controls/color/style-guide-color-picker-interaction-state.ts +49 -0
  513. package/templates/ui/components/controls/color/style-guide-color-picker-interactions.ts +2 -2
  514. package/templates/ui/components/controls/color/style-guide-color-picker-model.ts +125 -0
  515. package/templates/ui/components/controls/color/style-guide-color-picker-parts.tsx +10 -707
  516. package/templates/ui/components/controls/color/style-guide-color-picker-surface-drag.ts +93 -0
  517. package/templates/ui/components/controls/color/style-guide-color-picker-surface-geometry.ts +114 -0
  518. package/templates/ui/components/controls/color/style-guide-color-picker-surface-preview.ts +45 -0
  519. package/templates/ui/components/controls/color/style-guide-color-picker-types.ts +84 -0
  520. package/templates/ui/components/controls/color/style-guide-color-picker-view.tsx +69 -0
  521. package/templates/ui/components/controls/color/style-guide-color-picker.tsx +6 -495
  522. package/templates/ui/components/controls/curves/curves-control.tsx +4 -3
  523. package/templates/ui/components/controls/file-drop/file-drop-control.tsx +39 -515
  524. package/templates/ui/components/controls/file-drop/file-drop-empty-state.tsx +35 -0
  525. package/templates/ui/components/controls/file-drop/file-drop-file-list.tsx +160 -0
  526. package/templates/ui/components/controls/file-drop/file-drop-image-grid.tsx +175 -0
  527. package/templates/ui/components/controls/file-drop/file-drop-model.tsx +170 -0
  528. package/templates/ui/components/controls/file-drop/file-drop-single-preview.tsx +63 -0
  529. package/templates/ui/components/controls/file-drop/file-drop-types.ts +48 -0
  530. package/templates/ui/components/controls/file-drop/index.ts +2 -2
  531. package/templates/ui/components/controls/font-picker/font-picker-control.tsx +133 -906
  532. package/templates/ui/components/controls/font-picker/font-picker-footer.tsx +116 -0
  533. package/templates/ui/components/controls/font-picker/font-picker-list.tsx +194 -0
  534. package/templates/ui/components/controls/font-picker/font-picker-popover-content.tsx +191 -0
  535. package/templates/ui/components/controls/font-picker/font-picker-value.ts +222 -0
  536. package/templates/ui/components/controls/font-picker/index.ts +4 -1
  537. package/templates/ui/components/controls/font-picker/use-font-picker-preview-pipeline.ts +163 -0
  538. package/templates/ui/components/controls/font-picker/use-font-picker-virtual-list.ts +293 -0
  539. package/templates/ui/components/controls/gradient/gradient-control-utils.ts +2 -25
  540. package/templates/ui/components/controls/gradient/gradient-control.tsx +4 -464
  541. package/templates/ui/components/controls/gradient/gradient-stops-controller.ts +332 -0
  542. package/templates/ui/components/controls/gradient/gradient-stops-track.tsx +159 -0
  543. package/templates/ui/components/controls/vector/vector-control-types.ts +26 -0
  544. package/templates/ui/components/controls/vector/vector-control.tsx +10 -486
  545. package/templates/ui/components/controls/vector/vector-pad-field.tsx +211 -0
  546. package/templates/ui/components/controls/vector/vector-pad-geometry.ts +61 -0
  547. package/templates/ui/components/controls/vector/vector-pad-parts.tsx +72 -0
  548. package/templates/ui/components/controls/vector/vector-pad-variants.ts +20 -0
  549. package/templates/ui/components/controls/vector/vector-size-field.tsx +104 -0
  550. package/templates/ui/components/controls/vector/vector-value.ts +46 -0
  551. package/templates/ui/components/primitives/slider/slider-discrete-state.ts +181 -0
  552. package/templates/ui/components/primitives/slider/slider-marker-policy.ts +52 -0
  553. package/templates/ui/components/primitives/slider/slider-pointer-dragging.ts +87 -0
  554. package/templates/ui/components/primitives/slider/slider-runtime.ts +109 -0
  555. package/templates/ui/components/primitives/slider/slider-types.ts +21 -0
  556. package/templates/ui/components/primitives/slider/slider.tsx +2 -403
  557. package/templates/runtime/react/canvas-shell.test.tsx +0 -554
  558. package/templates/runtime/react/canvas-shell.tsx +0 -585
  559. package/templates/runtime/react/controls-panel.test.tsx +0 -5446
  560. package/templates/runtime/react/controls-panel.tsx +0 -3220
  561. package/templates/runtime/react/layers-panel.test.tsx +0 -487
  562. package/templates/runtime/react/layers-panel.tsx +0 -1348
  563. package/templates/runtime/react/timeline-panel.test.tsx +0 -751
  564. package/templates/runtime/react/timeline-panel.tsx +0 -3078
  565. package/templates/runtime/schema/define-toolcraft.test.ts +0 -1742
  566. package/templates/runtime/state/reducer.test.ts +0 -1703
  567. package/templates/runtime/testing/performance.test.ts +0 -3081
  568. package/templates/starter/e2e/app-browser-acceptance.spec.ts +0 -785
  569. package/templates/starter/src/app/starter-acceptance.test.ts +0 -10068
  570. package/templates/starter/src/app/starter-performance.test.ts +0 -1583
  571. package/templates/ui/components/controls/color/style-guide-color-picker-logic.ts +0 -490
  572. /package/templates/runtime/react/{storage-key-migration.ts → app-shell/storage-key-migration.ts} +0 -0
  573. /package/templates/runtime/react/{theme-runtime.tsx → app-shell/theme-runtime.tsx} +0 -0
  574. /package/templates/runtime/react/{curve-geometry.test.ts → controls-panel/__tests__/curve-geometry.test.ts} +0 -0
  575. /package/templates/runtime/react/{panel-host-config.ts → panel-host/panel-host-config.ts} +0 -0
  576. /package/templates/runtime/react/{panel-host-geometry.test.ts → panel-host/panel-host-geometry.test.ts} +0 -0
  577. /package/templates/runtime/react/{panel-host-geometry.ts → panel-host/panel-host-geometry.ts} +0 -0
@@ -1,1348 +0,0 @@
1
- "use client";
2
-
3
- import * as React from "react";
4
- import {
5
- FolderSimpleIcon,
6
- PlusIcon,
7
- StackPlusIcon,
8
- StackSimpleIcon,
9
- } from "@phosphor-icons/react";
10
- import {
11
- Button,
12
- PanelContentSurface,
13
- PanelIconButton,
14
- PanelSurface,
15
- Popover,
16
- PopoverContent,
17
- PopoverTrigger,
18
- PrimitiveArrowIcon,
19
- ScrollFade,
20
- stopPanelHeaderButtonPointerDown,
21
- } from "@repo/ui";
22
- import { Eye, EyeOff, Trash2 } from "lucide-react";
23
-
24
- import type { ToolcraftLayer, ToolcraftPanelState } from "../state/types";
25
- import {
26
- getToolcraftLayerDepth,
27
- getToolcraftVisibleLayerRows,
28
- isToolcraftLayerInsideGroup,
29
- isToolcraftLayerVisibleInTree,
30
- } from "./layer-tree";
31
- import { PanelContainer } from "./panel-host";
32
- import type { PanelPlacement, PanelStateChange } from "./panel-host-types";
33
- import { useToolcraft } from "./use-toolcraft";
34
-
35
- export type LayersPanelProps = {
36
- className?: string;
37
- framed?: boolean;
38
- groupCreation?: boolean;
39
- onPanelStateChange?: PanelStateChange;
40
- panelPlacement?: PanelPlacement;
41
- panelState?: ToolcraftPanelState;
42
- };
43
-
44
- type LayerDropPlacement = "after" | "before";
45
-
46
- type LayerInsertTarget = {
47
- indicatorDepth?: number;
48
- layerId: string;
49
- parentGroupId?: string | null;
50
- placement: LayerDropPlacement;
51
- };
52
-
53
- type LayerPointerTarget =
54
- | {
55
- element: HTMLElement;
56
- kind: "row";
57
- }
58
- | {
59
- kind: "gap";
60
- target: LayerInsertTarget;
61
- };
62
-
63
- type LayerDragState = {
64
- dragging: boolean;
65
- layerId: string;
66
- pointerId: number;
67
- startX: number;
68
- startY: number;
69
- };
70
-
71
- const selectedLayerSurfaceClassName =
72
- "bg-[color:color-mix(in_oklab,var(--accent)_20%,transparent)]";
73
- const hoveredLayerSurfaceClassName =
74
- "hover:bg-[color:color-mix(in_oklab,var(--foreground)_10%,transparent)]";
75
- const layerDepthIndentPx = 20;
76
- const layerDragStartDistance = 4;
77
- const layerGroupDropRatioStart = 0.25;
78
- const layerGroupDropRatioEnd = 0.75;
79
-
80
- function cn(...classNames: Array<string | false | null | undefined>): string {
81
- return classNames.filter(Boolean).join(" ");
82
- }
83
-
84
- function getLayerDisplayName(layer: ToolcraftLayer): string {
85
- const displayName = layer.displayName?.trim();
86
-
87
- return displayName ? displayName : layer.name;
88
- }
89
-
90
- function isGroupLayer(layer: ToolcraftLayer): boolean {
91
- return layer.kind === "group";
92
- }
93
-
94
- function getLayerBlockIds(
95
- layers: readonly ToolcraftLayer[],
96
- layerId: string,
97
- ): Set<string> {
98
- const blockIds = new Set<string>([layerId]);
99
- let changed = true;
100
-
101
- while (changed) {
102
- changed = false;
103
-
104
- for (const layer of layers) {
105
- if (layer.parentGroupId && blockIds.has(layer.parentGroupId) && !blockIds.has(layer.id)) {
106
- blockIds.add(layer.id);
107
- changed = true;
108
- }
109
- }
110
- }
111
-
112
- return blockIds;
113
- }
114
-
115
- function getReorderedLayers({
116
- draggingLayerId,
117
- layers,
118
- target,
119
- }: {
120
- draggingLayerId: string;
121
- layers: readonly ToolcraftLayer[];
122
- target: LayerInsertTarget;
123
- }): ToolcraftLayer[] | null {
124
- if (draggingLayerId === target.layerId) {
125
- return null;
126
- }
127
-
128
- const blockIds = getLayerBlockIds(layers, draggingLayerId);
129
-
130
- if (blockIds.has(target.layerId)) {
131
- return null;
132
- }
133
-
134
- const movingBlock = layers.filter((layer) => blockIds.has(layer.id));
135
- const remainingLayers = layers.filter((layer) => !blockIds.has(layer.id));
136
- const targetIndex = remainingLayers.findIndex((layer) => layer.id === target.layerId);
137
-
138
- if (targetIndex < 0) {
139
- return null;
140
- }
141
-
142
- const targetLayer = remainingLayers[targetIndex];
143
- const insertIndex =
144
- target.placement === "after" ? targetIndex + 1 : targetIndex;
145
- const parentGroupId =
146
- target.parentGroupId === undefined
147
- ? targetLayer?.parentGroupId
148
- : (target.parentGroupId ?? undefined);
149
- const updatedMovingBlock = movingBlock.map((layer) =>
150
- layer.id === draggingLayerId ? { ...layer, parentGroupId } : layer,
151
- );
152
-
153
- return [
154
- ...remainingLayers.slice(0, insertIndex),
155
- ...updatedMovingBlock,
156
- ...remainingLayers.slice(insertIndex),
157
- ];
158
- }
159
-
160
- function getLayerDropRatioFromClientY(
161
- element: HTMLElement,
162
- clientY: number,
163
- fallbackRatio = 0.5,
164
- ): number {
165
- const rect = element.getBoundingClientRect();
166
-
167
- if (rect.height <= 0 || !Number.isFinite(clientY)) {
168
- return fallbackRatio;
169
- }
170
-
171
- return Math.max(0, Math.min(1, (clientY - rect.top) / rect.height));
172
- }
173
-
174
- function canMoveLayerIntoGroup(
175
- layers: readonly ToolcraftLayer[],
176
- draggingLayerId: string,
177
- groupLayerId: string,
178
- ): boolean {
179
- if (draggingLayerId === groupLayerId) {
180
- return false;
181
- }
182
-
183
- return !getLayerBlockIds(layers, draggingLayerId).has(groupLayerId);
184
- }
185
-
186
- function getLayerSubtreeEndIndex(
187
- layers: readonly ToolcraftLayer[],
188
- groupLayerId: string,
189
- ): number {
190
- const groupIndex = layers.findIndex((layer) => layer.id === groupLayerId);
191
-
192
- if (groupIndex < 0) {
193
- return layers.length;
194
- }
195
-
196
- let endIndex = groupIndex;
197
-
198
- for (let index = groupIndex + 1; index < layers.length; index += 1) {
199
- const layer = layers[index];
200
-
201
- if (!layer || !isToolcraftLayerInsideGroup(layers, layer, groupLayerId)) {
202
- continue;
203
- }
204
-
205
- endIndex = index;
206
- }
207
-
208
- return endIndex + 1;
209
- }
210
-
211
- function LayerNameEditor({
212
- displayName,
213
- draftName,
214
- onCancel,
215
- onCommit,
216
- onDraftNameChange,
217
- }: {
218
- displayName: string;
219
- draftName: string;
220
- onCancel: () => void;
221
- onCommit: () => void;
222
- onDraftNameChange: (value: string) => void;
223
- }): React.JSX.Element {
224
- const inputRef = React.useRef<HTMLInputElement | null>(null);
225
-
226
- React.useEffect(() => {
227
- const input = inputRef.current;
228
-
229
- if (!input) {
230
- return;
231
- }
232
-
233
- input.focus();
234
- input.select();
235
- }, []);
236
-
237
- return (
238
- <div className="flex w-full min-w-0 cursor-text items-center text-left select-text">
239
- <input
240
- aria-label={`Layer name for ${displayName}`}
241
- className="min-w-0 flex-1 cursor-text border-0 bg-transparent p-0 text-xs leading-normal font-medium text-[color:var(--foreground)] outline-none select-text"
242
- onBlur={onCommit}
243
- onChange={(event) => onDraftNameChange(event.currentTarget.value)}
244
- onClick={(event) => event.stopPropagation()}
245
- onKeyDown={(event) => {
246
- if (event.key === "Enter") {
247
- event.preventDefault();
248
- onCommit();
249
- }
250
-
251
- if (event.key === "Escape") {
252
- event.preventDefault();
253
- onCancel();
254
- }
255
- }}
256
- onPointerDown={(event) => event.stopPropagation()}
257
- ref={inputRef}
258
- value={draftName}
259
- />
260
- </div>
261
- );
262
- }
263
-
264
- function LayerNameContent({
265
- displayName,
266
- isVisible,
267
- }: {
268
- displayName: string;
269
- isVisible: boolean;
270
- }): React.JSX.Element {
271
- return (
272
- <div className="flex w-full min-w-0 cursor-default items-center text-left select-none">
273
- <ScrollFade
274
- className="no-scrollbar min-w-0"
275
- containerClassName="min-w-0 flex-1"
276
- preset="compact"
277
- side="right"
278
- watch={[displayName]}
279
- >
280
- <span
281
- className={cn(
282
- "block min-w-max pr-2 text-xs font-medium whitespace-nowrap text-[color:var(--foreground)] transition-opacity duration-150 ease-out select-none",
283
- !isVisible && "opacity-30",
284
- )}
285
- title={displayName}
286
- >
287
- {displayName}
288
- </span>
289
- </ScrollFade>
290
- </div>
291
- );
292
- }
293
-
294
- function LayerRowIcon({
295
- collapsed,
296
- displayName,
297
- hasMedia,
298
- isGroup,
299
- isVisible,
300
- onToggleCollapsed,
301
- }: {
302
- collapsed?: boolean;
303
- displayName: string;
304
- hasMedia: boolean;
305
- isGroup: boolean;
306
- isVisible: boolean;
307
- onToggleCollapsed?: () => void;
308
- }): React.JSX.Element {
309
- const iconClassName = cn(
310
- "size-3 shrink-0 text-[color:var(--foreground)] transition-opacity duration-150 ease-out",
311
- isVisible ? "opacity-60" : "opacity-30",
312
- );
313
-
314
- return (
315
- <span
316
- aria-hidden="true"
317
- className="group/layer-row-icon-hit flex h-8 w-3 shrink-0 cursor-default items-center justify-center"
318
- data-layer-row-icon-hit-area={isGroup ? "group" : undefined}
319
- data-layer-row-icon-label={
320
- isGroup ? (collapsed ? `Expand ${displayName}` : `Collapse ${displayName}`) : undefined
321
- }
322
- onClick={
323
- isGroup
324
- ? (event) => {
325
- event.stopPropagation();
326
- onToggleCollapsed?.();
327
- }
328
- : undefined
329
- }
330
- onDoubleClick={isGroup ? (event) => event.stopPropagation() : undefined}
331
- onPointerDown={isGroup ? (event) => event.stopPropagation() : undefined}
332
- >
333
- {isGroup ? (
334
- <svg
335
- className={cn(
336
- iconClassName,
337
- "transition-[opacity,transform] duration-150 ease-out",
338
- isVisible && "group-hover/layer-row-icon-hit:opacity-100",
339
- collapsed && "-rotate-90",
340
- )}
341
- data-layer-row-icon="group"
342
- fill="none"
343
- viewBox="0 0 256 256"
344
- >
345
- <path
346
- d="M58 93L125.879 160.879C127.05 162.05 128.95 162.05 130.121 160.879L198 93"
347
- stroke="currentColor"
348
- strokeLinecap="round"
349
- strokeWidth="24"
350
- />
351
- </svg>
352
- ) : hasMedia ? (
353
- <svg
354
- aria-hidden="true"
355
- className={iconClassName}
356
- data-layer-row-icon="image"
357
- fill="none"
358
- viewBox="0 0 256 256"
359
- >
360
- <rect
361
- height="196"
362
- rx="20"
363
- stroke="currentColor"
364
- strokeLinecap="round"
365
- strokeWidth="16"
366
- width="196"
367
- x="30"
368
- y="30"
369
- />
370
- <path
371
- d="M60 162.719L88.809 123.726C90.2597 121.762 93.1154 121.545 94.8465 123.266L168 196"
372
- stroke="currentColor"
373
- strokeLinecap="round"
374
- strokeWidth="16"
375
- />
376
- <circle cx="164" cy="92" fill="currentColor" r="22" />
377
- </svg>
378
- ) : (
379
- <StackSimpleIcon aria-hidden="true" className={iconClassName} data-layer-row-icon="layer" />
380
- )}
381
- </span>
382
- );
383
- }
384
-
385
- function LayerActionButtons({
386
- displayName,
387
- isEditingName,
388
- isDragging,
389
- isReorderDragging,
390
- isVisible,
391
- layer,
392
- onDelete,
393
- onToggleVisibility,
394
- }: {
395
- displayName: string;
396
- isEditingName: boolean;
397
- isDragging: boolean;
398
- isReorderDragging: boolean;
399
- isVisible: boolean;
400
- layer: ToolcraftLayer;
401
- onDelete: () => void;
402
- onToggleVisibility: () => void;
403
- }): React.JSX.Element | null {
404
- if (isEditingName) {
405
- return null;
406
- }
407
-
408
- const mutedIconStyle = isVisible ? undefined : { opacity: 0.3 };
409
-
410
- return (
411
- <div
412
- className={cn(
413
- "inline-flex w-0 translate-x-[7px] shrink-0 items-center self-center gap-px overflow-hidden opacity-0",
414
- isDragging && "pointer-events-none w-auto overflow-visible opacity-100 transition-none",
415
- !isDragging &&
416
- (isReorderDragging
417
- ? "pointer-events-none transition-none"
418
- : "transition-none group-hover/layer:w-auto group-hover/layer:overflow-visible group-hover/layer:opacity-100"),
419
- )}
420
- data-layer-actions=""
421
- data-visible={isVisible ? "true" : "false"}
422
- >
423
- <Button
424
- aria-label={layer.visible ? `Hide ${displayName}` : `Show ${displayName}`}
425
- className="cursor-default!"
426
- onClick={(event) => {
427
- event.stopPropagation();
428
- onToggleVisibility();
429
- }}
430
- onDoubleClick={(event) => event.stopPropagation()}
431
- onPointerDown={(event) => event.stopPropagation()}
432
- size="icon-sm"
433
- type="button"
434
- variant="ghost"
435
- >
436
- {layer.visible ? <Eye style={mutedIconStyle} /> : <EyeOff style={mutedIconStyle} />}
437
- </Button>
438
- <Button
439
- aria-label={`Delete ${displayName}`}
440
- className="cursor-default!"
441
- onClick={(event) => {
442
- event.stopPropagation();
443
- onDelete();
444
- }}
445
- onDoubleClick={(event) => event.stopPropagation()}
446
- onPointerDown={(event) => event.stopPropagation()}
447
- size="icon-sm"
448
- type="button"
449
- variant="ghost"
450
- >
451
- <Trash2 style={mutedIconStyle} />
452
- </Button>
453
- </div>
454
- );
455
- }
456
-
457
- function useLayerNameEditing({
458
- displayName,
459
- onRename,
460
- }: {
461
- displayName: string;
462
- onRename: (displayName: string) => void;
463
- }) {
464
- const [isEditingName, setIsEditingName] = React.useState(false);
465
- const [draftName, setDraftName] = React.useState(displayName);
466
- const skipNextBlurCommitRef = React.useRef(false);
467
-
468
- const startEditingName = (): void => {
469
- setDraftName(displayName);
470
- setIsEditingName(true);
471
- };
472
-
473
- const commitEditingName = (): void => {
474
- if (skipNextBlurCommitRef.current) {
475
- skipNextBlurCommitRef.current = false;
476
- return;
477
- }
478
-
479
- const nextDisplayName = draftName.trim().replace(/\s+/g, " ");
480
-
481
- if (nextDisplayName && nextDisplayName !== displayName) {
482
- onRename(nextDisplayName);
483
- }
484
-
485
- setIsEditingName(false);
486
- };
487
-
488
- const cancelEditingName = (): void => {
489
- skipNextBlurCommitRef.current = true;
490
- setDraftName(displayName);
491
- setIsEditingName(false);
492
- };
493
-
494
- return {
495
- cancelEditingName,
496
- commitEditingName,
497
- draftName,
498
- isEditingName,
499
- setDraftName,
500
- startEditingName,
501
- };
502
- }
503
-
504
- function LayerRow({
505
- depth,
506
- hasMedia,
507
- insertIndicatorDepth,
508
- insertPlacement,
509
- isDragging,
510
- isDropTarget,
511
- isGroupDropAvailable,
512
- isGroupHighlighted,
513
- isReorderDragging,
514
- isSelected,
515
- isVisible,
516
- layer,
517
- onDelete,
518
- onPointerCancel,
519
- onPointerDown,
520
- onPointerMove,
521
- onPointerUp,
522
- onRename,
523
- onSelect,
524
- onToggleCollapsed,
525
- onToggleVisibility,
526
- }: {
527
- depth: number;
528
- hasMedia: boolean;
529
- insertIndicatorDepth?: number;
530
- insertPlacement?: LayerDropPlacement;
531
- isDragging: boolean;
532
- isDropTarget: boolean;
533
- isGroupDropAvailable: boolean;
534
- isGroupHighlighted: boolean;
535
- isReorderDragging: boolean;
536
- isSelected: boolean;
537
- isVisible: boolean;
538
- layer: ToolcraftLayer;
539
- onDelete: () => void;
540
- onPointerCancel: React.PointerEventHandler<HTMLElement>;
541
- onPointerDown: React.PointerEventHandler<HTMLElement>;
542
- onPointerMove: React.PointerEventHandler<HTMLElement>;
543
- onPointerUp: React.PointerEventHandler<HTMLElement>;
544
- onRename: (displayName: string) => void;
545
- onSelect: () => void;
546
- onToggleCollapsed: () => void;
547
- onToggleVisibility: () => void;
548
- }): React.JSX.Element {
549
- const displayName = getLayerDisplayName(layer);
550
- const isGroup = isGroupLayer(layer);
551
- const nameEditing = useLayerNameEditing({ displayName, onRename });
552
-
553
- return (
554
- <li
555
- aria-label={displayName}
556
- aria-selected={isSelected}
557
- className={cn(
558
- "group/layer relative flex h-8 cursor-default! touch-none select-none items-center gap-px",
559
- isDragging && "cursor-grabbing",
560
- )}
561
- data-dragging={isDragging ? "true" : undefined}
562
- data-drop-indicator={insertPlacement}
563
- data-drop-target={isDropTarget ? "true" : undefined}
564
- data-layer-id={layer.id}
565
- data-reorder-dragging={isReorderDragging ? "true" : undefined}
566
- data-selected={isSelected}
567
- data-visible={isVisible ? "true" : "false"}
568
- data-template-layer-depth={depth}
569
- data-template-layer-kind={isGroup ? "group" : "layer"}
570
- data-template-layer-name={layer.id}
571
- data-template-layer-parent={layer.parentGroupId}
572
- onClick={onSelect}
573
- onDoubleClick={(event) => {
574
- event.preventDefault();
575
- nameEditing.startEditingName();
576
- }}
577
- onKeyDown={(event) => {
578
- if (
579
- nameEditing.isEditingName ||
580
- !(event.key === "Enter" || event.key === " ") ||
581
- (event.target instanceof Element && event.target.closest("button,input,select,textarea"))
582
- ) {
583
- return;
584
- }
585
-
586
- event.preventDefault();
587
- onSelect();
588
- }}
589
- onPointerCancel={onPointerCancel}
590
- onPointerDown={onPointerDown}
591
- onPointerMove={onPointerMove}
592
- onPointerUp={onPointerUp}
593
- role="option"
594
- tabIndex={0}
595
- >
596
- {insertPlacement ? (
597
- <span
598
- aria-hidden="true"
599
- className={cn(
600
- "pointer-events-none absolute right-2 z-20 h-px rounded-full bg-[color:var(--foreground)]",
601
- insertPlacement === "before" ? "-top-px" : isGroup ? "bottom-0" : "-bottom-px",
602
- )}
603
- data-layer-drop-indicator={insertPlacement}
604
- style={{
605
- left: `${
606
- 8 + Math.max(0, insertIndicatorDepth ?? depth) * layerDepthIndentPx
607
- }px`,
608
- }}
609
- />
610
- ) : null}
611
- <div
612
- className={cn(
613
- "grid h-8 min-w-0 flex-1 cursor-default! grid-cols-[minmax(0,1fr)_auto] grid-rows-[minmax(0,1fr)] items-center gap-1.5 rounded-lg border pr-2.5 pl-[7px]",
614
- "border-transparent transition-none",
615
- !isSelected && !isReorderDragging && hoveredLayerSurfaceClassName,
616
- isSelected && selectedLayerSurfaceClassName,
617
- isGroupDropAvailable && "border-[color:var(--accent)]",
618
- isDropTarget &&
619
- "bg-[color:color-mix(in_oklab,var(--accent)_10%,transparent)]",
620
- isGroupHighlighted &&
621
- "border-[color:var(--accent)] bg-[color:color-mix(in_oklab,var(--accent)_15%,transparent)]",
622
- )}
623
- data-layer-row-surface=""
624
- >
625
- <div
626
- className="flex min-w-0 items-center gap-2"
627
- style={depth > 0 ? { marginLeft: `${depth * layerDepthIndentPx}px` } : undefined}
628
- >
629
- <LayerRowIcon
630
- collapsed={isGroup ? layer.collapsed === true : undefined}
631
- displayName={displayName}
632
- hasMedia={hasMedia}
633
- isGroup={isGroup}
634
- isVisible={isVisible}
635
- onToggleCollapsed={isGroup ? onToggleCollapsed : undefined}
636
- />
637
- {nameEditing.isEditingName ? (
638
- <LayerNameEditor
639
- displayName={displayName}
640
- draftName={nameEditing.draftName}
641
- onCancel={nameEditing.cancelEditingName}
642
- onCommit={nameEditing.commitEditingName}
643
- onDraftNameChange={nameEditing.setDraftName}
644
- />
645
- ) : (
646
- <LayerNameContent displayName={displayName} isVisible={isVisible} />
647
- )}
648
- </div>
649
- <LayerActionButtons
650
- displayName={displayName}
651
- isEditingName={nameEditing.isEditingName}
652
- isDragging={isDragging}
653
- isReorderDragging={isReorderDragging}
654
- isVisible={isVisible}
655
- layer={layer}
656
- onDelete={onDelete}
657
- onToggleVisibility={onToggleVisibility}
658
- />
659
- </div>
660
- </li>
661
- );
662
- }
663
-
664
- function AddLayerPicker({
665
- groupCreation,
666
- onAddGroup,
667
- onAddLayer,
668
- }: {
669
- groupCreation: boolean;
670
- onAddGroup: () => void;
671
- onAddLayer: () => void;
672
- }): React.JSX.Element {
673
- const [open, setOpen] = React.useState(false);
674
-
675
- const addLayer = (): void => {
676
- onAddLayer();
677
- setOpen(false);
678
- };
679
- const addGroup = (): void => {
680
- onAddGroup();
681
- setOpen(false);
682
- };
683
-
684
- if (!groupCreation) {
685
- return (
686
- <Button
687
- aria-label="Add layer"
688
- data-icon-active={false}
689
- onClick={addLayer}
690
- onPointerDown={stopPanelHeaderButtonPointerDown}
691
- size="icon"
692
- type="button"
693
- variant="ghost"
694
- >
695
- <PlusIcon />
696
- </Button>
697
- );
698
- }
699
-
700
- return (
701
- <Popover onOpenChange={setOpen} open={open}>
702
- <PopoverTrigger
703
- aria-label="Add layer"
704
- onPointerDown={stopPanelHeaderButtonPointerDown}
705
- render={<Button data-icon-active={open} size="icon" type="button" variant="ghost" />}
706
- >
707
- <PlusIcon />
708
- </PopoverTrigger>
709
- <PopoverContent align="end" className="w-[148px] gap-1 p-1" side="bottom" sideOffset={4}>
710
- <Button
711
- className="h-8 justify-start gap-2 px-2 text-xs"
712
- onClick={addLayer}
713
- size="sm"
714
- type="button"
715
- variant="ghost"
716
- >
717
- <StackPlusIcon className="size-4" />
718
- Layer
719
- </Button>
720
- <Button
721
- className="h-8 justify-start gap-2 px-2 text-xs"
722
- onClick={addGroup}
723
- size="sm"
724
- type="button"
725
- variant="ghost"
726
- >
727
- <FolderSimpleIcon className="size-4" />
728
- Group
729
- </Button>
730
- </PopoverContent>
731
- </Popover>
732
- );
733
- }
734
-
735
- function LayersPanelHeader({
736
- collapsed,
737
- groupCreation,
738
- onAddGroup,
739
- onAddLayer,
740
- onToggleCollapsed,
741
- }: {
742
- collapsed: boolean;
743
- groupCreation: boolean;
744
- onAddGroup: () => void;
745
- onAddLayer: () => void;
746
- onToggleCollapsed: () => void;
747
- }): React.JSX.Element {
748
- return (
749
- <div className="shrink-0" data-slot="layers-panel-header-shell">
750
- <div
751
- className="flex h-9 touch-none items-center justify-between gap-3 pr-1 pl-3 hover:cursor-grab active:cursor-grabbing"
752
- data-panel-drag-handle=""
753
- data-slot="layers-panel-header"
754
- >
755
- <p className="m-0 min-w-0 truncate text-xs-plus font-medium text-[color:var(--foreground)]">
756
- Layers
757
- </p>
758
- <div className="inline-flex shrink-0 items-center gap-1">
759
- {collapsed ? null : (
760
- <AddLayerPicker
761
- groupCreation={groupCreation}
762
- onAddGroup={onAddGroup}
763
- onAddLayer={onAddLayer}
764
- />
765
- )}
766
- <PanelIconButton
767
- label={collapsed ? "Expand layers" : "Collapse layers"}
768
- onClick={onToggleCollapsed}
769
- onPointerDown={stopPanelHeaderButtonPointerDown}
770
- >
771
- <PrimitiveArrowIcon direction={collapsed ? "down" : "up"} />
772
- </PanelIconButton>
773
- </div>
774
- </div>
775
- </div>
776
- );
777
- }
778
-
779
- export function LayersPanel({
780
- className,
781
- framed = true,
782
- groupCreation = true,
783
- onPanelStateChange,
784
- panelPlacement,
785
- panelState,
786
- }: LayersPanelProps): React.JSX.Element | null {
787
- const { dispatch, state } = useToolcraft();
788
- const [internalCollapsed, setInternalCollapsed] = React.useState(false);
789
- const [dragState, setDragState] = React.useState<LayerDragState | null>(null);
790
- const [dropTargetGroupId, setDropTargetGroupId] = React.useState<string | null>(null);
791
- const [highlightedGroupId, setHighlightedGroupId] = React.useState<string | null>(null);
792
- const [insertTarget, setInsertTarget] = React.useState<LayerInsertTarget | null>(null);
793
- const dragStateRef = React.useRef<LayerDragState | null>(null);
794
- const dropTargetGroupIdRef = React.useRef<string | null>(null);
795
- const highlightedGroupIdRef = React.useRef<string | null>(null);
796
- const insertTargetRef = React.useRef<LayerInsertTarget | null>(null);
797
- const listRef = React.useRef<HTMLUListElement | null>(null);
798
- const collapsed = panelState?.collapsed ?? internalCollapsed;
799
- const placement = panelPlacement ?? (framed ? "frame" : "surface");
800
- const visibleLayers = React.useMemo(() => getToolcraftVisibleLayerRows(state.layers), [
801
- state.layers,
802
- ]);
803
-
804
- if (!state.schema.panels.layers) {
805
- return null;
806
- }
807
-
808
- const updateCollapsed = (nextCollapsed: boolean): void => {
809
- if (panelState?.collapsed === undefined) {
810
- setInternalCollapsed(nextCollapsed);
811
- }
812
-
813
- onPanelStateChange?.({ collapsed: nextCollapsed });
814
- };
815
-
816
- const addLayer = (): void => {
817
- const selectedLayer = state.layers.find((layer) => layer.id === state.selectedLayerId);
818
- const parentGroupId =
819
- selectedLayer?.kind === "group" ? selectedLayer.id : selectedLayer?.parentGroupId;
820
- const insertIndex = selectedLayer
821
- ? selectedLayer.kind === "group"
822
- ? getLayerSubtreeEndIndex(state.layers, selectedLayer.id)
823
- : state.layers.findIndex((layer) => layer.id === selectedLayer.id) + 1
824
- : state.layers.length;
825
-
826
- dispatch({
827
- insertIndex,
828
- layer: { kind: "layer", parentGroupId },
829
- type: "layers.add",
830
- });
831
- };
832
-
833
- const addGroup = (): void => {
834
- const selectedLayer = state.layers.find((layer) => layer.id === state.selectedLayerId);
835
- const parentGroupId =
836
- selectedLayer?.kind === "group" ? selectedLayer.id : selectedLayer?.parentGroupId;
837
- const insertIndex = selectedLayer
838
- ? selectedLayer.kind === "group"
839
- ? getLayerSubtreeEndIndex(state.layers, selectedLayer.id)
840
- : state.layers.findIndex((layer) => layer.id === selectedLayer.id) + 1
841
- : state.layers.length;
842
-
843
- dispatch({
844
- insertIndex,
845
- layer: { kind: "group", parentGroupId },
846
- type: "layers.add",
847
- });
848
- };
849
-
850
- const clearDragState = (): void => {
851
- dragStateRef.current = null;
852
- dropTargetGroupIdRef.current = null;
853
- highlightedGroupIdRef.current = null;
854
- insertTargetRef.current = null;
855
- setDragState(null);
856
- setDropTargetGroupId(null);
857
- setHighlightedGroupId(null);
858
- setInsertTarget(null);
859
- };
860
-
861
- const updateDragState = (nextDragState: LayerDragState | null): void => {
862
- dragStateRef.current = nextDragState;
863
- setDragState(nextDragState);
864
- };
865
-
866
- const updateDropTargetGroupId = (nextDropTargetGroupId: string | null): void => {
867
- dropTargetGroupIdRef.current = nextDropTargetGroupId;
868
- setDropTargetGroupId(nextDropTargetGroupId);
869
- };
870
-
871
- const updateHighlightedGroupId = (nextHighlightedGroupId: string | null): void => {
872
- highlightedGroupIdRef.current = nextHighlightedGroupId;
873
- setHighlightedGroupId(nextHighlightedGroupId);
874
- };
875
-
876
- const updateInsertTarget = (nextInsertTarget: LayerInsertTarget | null): void => {
877
- insertTargetRef.current = nextInsertTarget;
878
- setInsertTarget(nextInsertTarget);
879
- };
880
-
881
- const getCanonicalInsertTarget = (
882
- layer: ToolcraftLayer,
883
- placement: LayerDropPlacement,
884
- ): LayerInsertTarget => {
885
- if (placement === "before") {
886
- return { layerId: layer.id, placement };
887
- }
888
-
889
- const visibleIndex = visibleLayers.findIndex((visibleLayer) => visibleLayer.id === layer.id);
890
- const nextVisibleLayer = visibleIndex >= 0 ? visibleLayers[visibleIndex + 1] : undefined;
891
-
892
- return nextVisibleLayer
893
- ? { layerId: nextVisibleLayer.id, placement: "before" }
894
- : { layerId: layer.id, placement: "after" };
895
- };
896
-
897
- const getVisibleLayerIndex = (layerId: string): number =>
898
- visibleLayers.findIndex((visibleLayer) => visibleLayer.id === layerId);
899
-
900
- const isLastVisibleLayerInParentGroup = (layer: ToolcraftLayer): boolean => {
901
- if (!layer.parentGroupId) {
902
- return false;
903
- }
904
-
905
- const visibleIndex = getVisibleLayerIndex(layer.id);
906
- const nextVisibleLayer = visibleIndex >= 0 ? visibleLayers[visibleIndex + 1] : undefined;
907
-
908
- return (
909
- !nextVisibleLayer ||
910
- !isToolcraftLayerInsideGroup(state.layers, nextVisibleLayer, layer.parentGroupId)
911
- );
912
- };
913
-
914
- const isPointerAtNestedInsertDepth = (
915
- layer: ToolcraftLayer,
916
- clientX: number,
917
- ): boolean => {
918
- if (!Number.isFinite(clientX)) {
919
- return false;
920
- }
921
-
922
- const listLeft = listRef.current?.getBoundingClientRect().left ?? 0;
923
- const layerDepth = getToolcraftLayerDepth(state.layers, layer);
924
-
925
- return clientX >= listLeft + 8 + layerDepth * layerDepthIndentPx;
926
- };
927
-
928
- const getParentGroupLayer = (layer: ToolcraftLayer): ToolcraftLayer | undefined =>
929
- layer.parentGroupId
930
- ? state.layers.find(
931
- (currentLayer) => currentLayer.id === layer.parentGroupId && isGroupLayer(currentLayer),
932
- )
933
- : undefined;
934
-
935
- const getInsertTargetAfterLayerSubtree = (layer: ToolcraftLayer): LayerInsertTarget => {
936
- const layerDepth = getToolcraftLayerDepth(state.layers, layer);
937
- const visibleIndex = getVisibleLayerIndex(layer.id);
938
-
939
- if (visibleIndex < 0) {
940
- return getCanonicalInsertTarget(layer, "after");
941
- }
942
-
943
- let lastVisibleLayer = layer;
944
-
945
- for (const nextVisibleLayer of visibleLayers.slice(visibleIndex + 1)) {
946
- if (getToolcraftLayerDepth(state.layers, nextVisibleLayer) <= layerDepth) {
947
- return { layerId: nextVisibleLayer.id, placement: "before" };
948
- }
949
-
950
- lastVisibleLayer = nextVisibleLayer;
951
- }
952
-
953
- return {
954
- indicatorDepth: layerDepth,
955
- layerId: lastVisibleLayer.id,
956
- parentGroupId: layer.parentGroupId ?? null,
957
- placement: "after",
958
- };
959
- };
960
-
961
- const getOutsideParentGroupInsertTarget = (layer: ToolcraftLayer): LayerInsertTarget => {
962
- const parentGroupLayer = getParentGroupLayer(layer);
963
-
964
- return parentGroupLayer
965
- ? getInsertTargetAfterLayerSubtree(parentGroupLayer)
966
- : getCanonicalInsertTarget(layer, "after");
967
- };
968
-
969
- const getNestedBoundaryLayerForTarget = (
970
- target: LayerInsertTarget,
971
- ): ToolcraftLayer | undefined => {
972
- const targetLayer = state.layers.find((layer) => layer.id === target.layerId);
973
-
974
- if (!targetLayer) {
975
- return undefined;
976
- }
977
-
978
- if (target.placement === "after") {
979
- return isLastVisibleLayerInParentGroup(targetLayer) ? targetLayer : undefined;
980
- }
981
-
982
- const visibleIndex = getVisibleLayerIndex(targetLayer.id);
983
- const previousVisibleLayer =
984
- visibleIndex > 0 ? visibleLayers[visibleIndex - 1] : undefined;
985
-
986
- return previousVisibleLayer && isLastVisibleLayerInParentGroup(previousVisibleLayer)
987
- ? previousVisibleLayer
988
- : undefined;
989
- };
990
-
991
- const getResolvedInsertTarget = (
992
- target: LayerInsertTarget,
993
- clientX: number,
994
- ): LayerInsertTarget | null => {
995
- const boundaryLayer = getNestedBoundaryLayerForTarget(target);
996
-
997
- if (boundaryLayer) {
998
- return isPointerAtNestedInsertDepth(boundaryLayer, clientX)
999
- ? { layerId: boundaryLayer.id, placement: "after" }
1000
- : getOutsideParentGroupInsertTarget(boundaryLayer);
1001
- }
1002
-
1003
- const targetLayer = state.layers.find((layer) => layer.id === target.layerId);
1004
-
1005
- return targetLayer ? getCanonicalInsertTarget(targetLayer, target.placement) : null;
1006
- };
1007
-
1008
- const getNearestInsertTarget = (
1009
- layer: ToolcraftLayer,
1010
- dropRatio: number,
1011
- clientX: number,
1012
- ): LayerInsertTarget | null => {
1013
- if (dropRatio < 0.5) {
1014
- return { layerId: layer.id, placement: "before" };
1015
- }
1016
-
1017
- return getResolvedInsertTarget({ layerId: layer.id, placement: "after" }, clientX);
1018
- };
1019
-
1020
- const getInsertIndicatorTarget = (target: LayerInsertTarget): LayerInsertTarget => {
1021
- if (target.placement !== "before") {
1022
- return target;
1023
- }
1024
-
1025
- const visibleIndex = visibleLayers.findIndex(
1026
- (visibleLayer) => visibleLayer.id === target.layerId,
1027
- );
1028
- const previousVisibleLayer =
1029
- visibleIndex > 0 ? visibleLayers[visibleIndex - 1] : undefined;
1030
-
1031
- return previousVisibleLayer && isGroupLayer(previousVisibleLayer)
1032
- ? { layerId: previousVisibleLayer.id, placement: "after" }
1033
- : target;
1034
- };
1035
-
1036
- const getPointerTarget = (clientX: number, clientY: number): LayerPointerTarget | null => {
1037
- const targetElement = document
1038
- .elementFromPoint(clientX, clientY)
1039
- ?.closest<HTMLElement>("[data-layer-id]");
1040
-
1041
- if (targetElement) {
1042
- return { element: targetElement, kind: "row" };
1043
- }
1044
-
1045
- const listElement = listRef.current;
1046
-
1047
- if (!listElement) {
1048
- return null;
1049
- }
1050
-
1051
- const listRect = listElement.getBoundingClientRect();
1052
-
1053
- if (
1054
- clientX < listRect.left ||
1055
- clientX > listRect.right ||
1056
- clientY < listRect.top ||
1057
- clientY > listRect.bottom
1058
- ) {
1059
- return null;
1060
- }
1061
-
1062
- const rowElements = Array.from(listElement.querySelectorAll<HTMLElement>("[data-layer-id]"));
1063
-
1064
- for (const rowElement of rowElements) {
1065
- const rowRect = rowElement.getBoundingClientRect();
1066
- const layerId = rowElement.dataset.layerId;
1067
-
1068
- if (!layerId) {
1069
- continue;
1070
- }
1071
-
1072
- if (clientY < rowRect.top) {
1073
- return { kind: "gap", target: { layerId, placement: "before" } };
1074
- }
1075
-
1076
- if (clientY <= rowRect.bottom) {
1077
- return { element: rowElement, kind: "row" };
1078
- }
1079
- }
1080
-
1081
- const lastRowElement = rowElements.at(-1);
1082
- const lastLayerId = lastRowElement?.dataset.layerId;
1083
-
1084
- return lastLayerId
1085
- ? { kind: "gap", target: { layerId: lastLayerId, placement: "after" } }
1086
- : null;
1087
- };
1088
-
1089
- const clearDragTarget = (): void => {
1090
- updateDropTargetGroupId(null);
1091
- updateHighlightedGroupId(null);
1092
- updateInsertTarget(null);
1093
- };
1094
-
1095
- const updateDragTarget = (
1096
- pointerTarget: LayerPointerTarget | null,
1097
- clientX: number,
1098
- clientY: number,
1099
- activeLayerId: string,
1100
- ): void => {
1101
- if (!pointerTarget) {
1102
- clearDragTarget();
1103
- return;
1104
- }
1105
-
1106
- if (pointerTarget.kind === "gap") {
1107
- const nextInsertTarget = getResolvedInsertTarget(pointerTarget.target, clientX);
1108
-
1109
- updateDropTargetGroupId(null);
1110
- updateHighlightedGroupId(null);
1111
- updateInsertTarget(nextInsertTarget);
1112
- return;
1113
- }
1114
-
1115
- const layerId = pointerTarget.element.dataset.layerId;
1116
-
1117
- if (!layerId || layerId === activeLayerId) {
1118
- clearDragTarget();
1119
- return;
1120
- }
1121
-
1122
- const targetLayer = state.layers.find((layer) => layer.id === layerId);
1123
-
1124
- if (!targetLayer) {
1125
- clearDragTarget();
1126
- return;
1127
- }
1128
-
1129
- const canDropIntoGroup =
1130
- targetLayer.kind === "group" &&
1131
- canMoveLayerIntoGroup(state.layers, activeLayerId, targetLayer.id);
1132
- const highlightedGroup = targetLayer.kind === "group" ? targetLayer.id : null;
1133
- const dropRatio = getLayerDropRatioFromClientY(
1134
- pointerTarget.element,
1135
- clientY,
1136
- canDropIntoGroup ? 0.5 : 0,
1137
- );
1138
-
1139
- if (
1140
- canDropIntoGroup &&
1141
- dropRatio > layerGroupDropRatioStart &&
1142
- dropRatio < layerGroupDropRatioEnd
1143
- ) {
1144
- updateDropTargetGroupId(targetLayer.id);
1145
- updateHighlightedGroupId(targetLayer.id);
1146
- updateInsertTarget(null);
1147
- return;
1148
- }
1149
-
1150
- updateDropTargetGroupId(null);
1151
- updateHighlightedGroupId(highlightedGroup);
1152
- updateInsertTarget(getNearestInsertTarget(targetLayer, dropRatio, clientX));
1153
- };
1154
-
1155
- const commitDrag = (): void => {
1156
- const activeDragState = dragStateRef.current ?? dragState;
1157
- const activeDropTargetGroupId = dropTargetGroupIdRef.current ?? dropTargetGroupId;
1158
- const activeInsertTarget = insertTargetRef.current ?? insertTarget;
1159
-
1160
- if (!activeDragState?.dragging) {
1161
- clearDragState();
1162
- return;
1163
- }
1164
-
1165
- if (activeDropTargetGroupId) {
1166
- dispatch({
1167
- layerIds: [activeDragState.layerId],
1168
- parentGroupId: activeDropTargetGroupId,
1169
- type: "layers.moveToGroup",
1170
- });
1171
- clearDragState();
1172
- return;
1173
- }
1174
-
1175
- if (!activeInsertTarget) {
1176
- clearDragState();
1177
- return;
1178
- }
1179
-
1180
- const layers = getReorderedLayers({
1181
- draggingLayerId: activeDragState.layerId,
1182
- layers: state.layers,
1183
- target: activeInsertTarget,
1184
- });
1185
-
1186
- if (layers) {
1187
- dispatch({ layers, type: "layers.reorder" });
1188
- }
1189
-
1190
- clearDragState();
1191
- };
1192
-
1193
- const panelSurface = (
1194
- <PanelSurface
1195
- className={cn(
1196
- "pointer-events-auto flex max-h-[calc(100dvh-1.25rem)] w-[240px] flex-col overflow-hidden rounded-lg p-0",
1197
- className,
1198
- )}
1199
- data-toolcraft-layers-panel=""
1200
- data-panel-id="layers"
1201
- >
1202
- <LayersPanelHeader
1203
- collapsed={collapsed}
1204
- groupCreation={groupCreation}
1205
- onAddGroup={addGroup}
1206
- onAddLayer={addLayer}
1207
- onToggleCollapsed={() => updateCollapsed(!collapsed)}
1208
- />
1209
- {collapsed ? null : (
1210
- <PanelContentSurface data-slot="layers-panel-content">
1211
- <ul
1212
- aria-label="Layers"
1213
- className="flex min-h-0 flex-col gap-0.5 p-1"
1214
- data-layer-list=""
1215
- data-layer-list-dragging={dragState?.dragging ? "true" : undefined}
1216
- ref={listRef}
1217
- role="listbox"
1218
- >
1219
- {visibleLayers.map((layer) => {
1220
- const depth = getToolcraftLayerDepth(state.layers, layer);
1221
- const displayName = getLayerDisplayName(layer);
1222
- const isDragging = dragState?.layerId === layer.id && dragState.dragging;
1223
- const isReorderDragging = dragState?.dragging === true;
1224
- const isVisible = isToolcraftLayerVisibleInTree(state.layers, layer);
1225
- const hasMedia = state.mediaAssets.some((asset) => asset.layerId === layer.id);
1226
- const insertIndicatorTarget = insertTarget
1227
- ? getInsertIndicatorTarget(insertTarget)
1228
- : null;
1229
- const rowInsertPlacement =
1230
- insertIndicatorTarget?.layerId === layer.id
1231
- ? insertIndicatorTarget.placement
1232
- : undefined;
1233
-
1234
- return (
1235
- <LayerRow
1236
- depth={depth}
1237
- hasMedia={hasMedia}
1238
- insertIndicatorDepth={insertIndicatorTarget?.indicatorDepth ?? depth}
1239
- insertPlacement={rowInsertPlacement}
1240
- isDragging={isDragging}
1241
- isDropTarget={dropTargetGroupId === layer.id}
1242
- isGroupDropAvailable={
1243
- dragState?.dragging === true &&
1244
- layer.kind === "group" &&
1245
- dropTargetGroupId === layer.id &&
1246
- canMoveLayerIntoGroup(state.layers, dragState.layerId, layer.id)
1247
- }
1248
- isGroupHighlighted={highlightedGroupId === layer.id}
1249
- isReorderDragging={isReorderDragging}
1250
- isSelected={state.selectedLayerId === layer.id}
1251
- isVisible={isVisible}
1252
- key={layer.id}
1253
- layer={layer}
1254
- onDelete={() => dispatch({ layerId: layer.id, type: "layers.delete" })}
1255
- onPointerCancel={clearDragState}
1256
- onPointerDown={(event) => {
1257
- if (event.button !== 0) {
1258
- return;
1259
- }
1260
-
1261
- event.currentTarget.setPointerCapture?.(event.pointerId);
1262
- updateDragState({
1263
- dragging: false,
1264
- layerId: layer.id,
1265
- pointerId: event.pointerId,
1266
- startX: event.clientX,
1267
- startY: event.clientY,
1268
- });
1269
- }}
1270
- onPointerMove={(event) => {
1271
- const activeDragState = dragStateRef.current ?? dragState;
1272
-
1273
- if (!activeDragState || activeDragState.pointerId !== event.pointerId) {
1274
- return;
1275
- }
1276
-
1277
- const distance = Math.hypot(
1278
- event.clientX - activeDragState.startX,
1279
- event.clientY - activeDragState.startY,
1280
- );
1281
-
1282
- if (!activeDragState.dragging && distance < layerDragStartDistance) {
1283
- return;
1284
- }
1285
-
1286
- event.preventDefault();
1287
- const nextDragState = { ...activeDragState, dragging: true };
1288
-
1289
- updateDragState(nextDragState);
1290
- updateDragTarget(
1291
- getPointerTarget(event.clientX, event.clientY),
1292
- event.clientX,
1293
- event.clientY,
1294
- activeDragState.layerId,
1295
- );
1296
- }}
1297
- onPointerUp={(event) => {
1298
- const activeDragState = dragStateRef.current ?? dragState;
1299
-
1300
- if (activeDragState?.pointerId !== event.pointerId) {
1301
- return;
1302
- }
1303
-
1304
- commitDrag();
1305
- }}
1306
- onRename={(name) =>
1307
- dispatch({
1308
- layerId: layer.id,
1309
- name,
1310
- type: "layers.rename",
1311
- })
1312
- }
1313
- onSelect={() => dispatch({ layerId: layer.id, type: "layers.select" })}
1314
- onToggleCollapsed={() =>
1315
- dispatch({ layerId: layer.id, type: "layers.toggleCollapsed" })
1316
- }
1317
- onToggleVisibility={() =>
1318
- dispatch({ layerId: layer.id, type: "layers.toggleVisibility" })
1319
- }
1320
- />
1321
- );
1322
- })}
1323
- {visibleLayers.length === 0 ? (
1324
- <li className="px-2 py-3 text-xs text-[color:color-mix(in_oklab,var(--foreground)_55%,transparent)]">
1325
- No layers
1326
- </li>
1327
- ) : null}
1328
- </ul>
1329
- </PanelContentSurface>
1330
- )}
1331
- </PanelSurface>
1332
- );
1333
-
1334
- if (placement === "surface") {
1335
- return panelSurface;
1336
- }
1337
-
1338
- return (
1339
- <PanelContainer
1340
- onPanelStateChange={onPanelStateChange}
1341
- panelState={panelState}
1342
- panelType="layers"
1343
- placement={placement}
1344
- >
1345
- {panelSurface}
1346
- </PanelContainer>
1347
- );
1348
- }