@jxsuite/studio 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/canvas.html +21 -0
  2. package/dist/chunks/Overlay.dev-qfjzkms8.js +23 -0
  3. package/dist/chunks/{Overlay.dev-cszx7yn5.js.map → Overlay.dev-qfjzkms8.js.map} +1 -1
  4. package/dist/chunks/{adapter-z8fkmbmv.js → adapter-qfnesn5v.js} +2 -2
  5. package/dist/chunks/{cf-settings-mmyv436d.js → cf-settings-73sh5z4p.js} +3 -3
  6. package/dist/chunks/{components-0hzentwq.js → components-2bpqp4mq.js} +5 -7
  7. package/dist/chunks/{components-0hzentwq.js.map → components-2bpqp4mq.js.map} +1 -1
  8. package/dist/chunks/content-source-34gnkbnn.js +38 -0
  9. package/dist/chunks/content-source-34gnkbnn.js.map +9 -0
  10. package/dist/chunks/convert-to-repeater-t8kxre5s.js +258 -0
  11. package/dist/chunks/convert-to-repeater-t8kxre5s.js.map +10 -0
  12. package/dist/chunks/extension-sections-6mqydz35.js +301 -0
  13. package/dist/chunks/extension-sections-6mqydz35.js.map +11 -0
  14. package/dist/chunks/files-8kn321h1.js +70 -0
  15. package/dist/chunks/files-8kn321h1.js.map +9 -0
  16. package/dist/chunks/{format-host-2c188n41.js → format-host-mkwsmt8d.js} +3 -3
  17. package/dist/chunks/git-panel-tjbr191d.js +51 -0
  18. package/dist/chunks/git-panel-tjbr191d.js.map +9 -0
  19. package/dist/chunks/{index-baeg2x3y.js → index-7pxchcce.js} +4 -3
  20. package/dist/chunks/{index-baeg2x3y.js.map → index-7pxchcce.js.map} +3 -3
  21. package/dist/chunks/{jx-validate-qx8degj7.js → jx-validate-v6qgknrs.js} +3 -3
  22. package/dist/chunks/layers-panel-87pb25km.js +58 -0
  23. package/dist/chunks/layers-panel-87pb25km.js.map +9 -0
  24. package/dist/chunks/{monaco-setup-zjdp9zdw.js → monaco-setup-e0yh25j4.js} +4 -75
  25. package/dist/chunks/{monaco-setup-zjdp9zdw.js.map → monaco-setup-e0yh25j4.js.map} +3 -4
  26. package/dist/chunks/publish-panel-m0fcgdrt.js +21 -0
  27. package/dist/chunks/publish-panel-m0fcgdrt.js.map +9 -0
  28. package/dist/chunks/quick-search-rhs6cbsb.js +53 -0
  29. package/dist/chunks/quick-search-rhs6cbsb.js.map +9 -0
  30. package/dist/chunks/right-panel-zpdxd98t.js +37 -0
  31. package/dist/chunks/right-panel-zpdxd98t.js.map +9 -0
  32. package/dist/chunks/section-registry-zmckaxf4.js +30 -0
  33. package/dist/chunks/section-registry-zmckaxf4.js.map +9 -0
  34. package/dist/chunks/sp-overlay.dev-9ran0sfq.js +18 -0
  35. package/dist/chunks/sp-overlay.dev-9ran0sfq.js.map +9 -0
  36. package/dist/chunks/sp-popover.dev-rebmdncr.js +9 -0
  37. package/dist/chunks/sp-popover.dev-rebmdncr.js.map +9 -0
  38. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js +15 -0
  39. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js.map +9 -0
  40. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js +15 -0
  41. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js.map +9 -0
  42. package/dist/chunks/{sp-tray.dev-pb2fsd65.js → sp-tray.dev-ey59vfh7.js} +10 -8
  43. package/dist/chunks/{sp-tray.dev-pb2fsd65.js.map → sp-tray.dev-ey59vfh7.js.map} +2 -2
  44. package/dist/chunks/{studio-9qr6fq7p.js → studio-0rqqhrdy.js} +10 -378
  45. package/dist/chunks/{studio-9qr6fq7p.js.map → studio-0rqqhrdy.js.map} +4 -14
  46. package/dist/chunks/{studio-5x6tqqec.js → studio-2sj1jj3v.js} +10 -8
  47. package/dist/chunks/{studio-5x6tqqec.js.map → studio-2sj1jj3v.js.map} +2 -2
  48. package/dist/chunks/{studio-h3fw0wn8.js → studio-3fk1ac9z.js} +6 -4
  49. package/dist/chunks/{studio-h3fw0wn8.js.map → studio-3fk1ac9z.js.map} +2 -2
  50. package/dist/chunks/studio-41exgzhq.js +6690 -0
  51. package/dist/chunks/studio-41exgzhq.js.map +38 -0
  52. package/dist/chunks/{studio-7v2bpweq.js → studio-4mqb9a0j.js} +6 -4
  53. package/dist/chunks/{studio-7v2bpweq.js.map → studio-4mqb9a0j.js.map} +2 -2
  54. package/dist/chunks/studio-50k77cca.js +1787 -0
  55. package/dist/chunks/studio-50k77cca.js.map +18 -0
  56. package/dist/chunks/{studio-56v3zwk6.js → studio-6a0h7sne.js} +10 -8
  57. package/dist/chunks/{studio-56v3zwk6.js.map → studio-6a0h7sne.js.map} +2 -2
  58. package/dist/chunks/studio-6f0am8f2.js +53 -0
  59. package/dist/chunks/studio-6f0am8f2.js.map +10 -0
  60. package/dist/chunks/studio-6nmjc5qk.js +7002 -0
  61. package/dist/chunks/studio-6nmjc5qk.js.map +32 -0
  62. package/dist/chunks/studio-7afd21nw.js +67 -0
  63. package/dist/chunks/studio-7afd21nw.js.map +10 -0
  64. package/dist/chunks/{studio-xze4zrs1.js → studio-7bmdvzne.js} +2 -2
  65. package/dist/chunks/studio-7bmdvzne.js.map +10 -0
  66. package/dist/chunks/studio-8jpzqk5s.js +128 -0
  67. package/dist/chunks/studio-8jpzqk5s.js.map +10 -0
  68. package/dist/chunks/studio-94m77tgk.js +1482 -0
  69. package/dist/chunks/studio-94m77tgk.js.map +13 -0
  70. package/dist/chunks/studio-9je6w5sq.js +1000 -0
  71. package/dist/chunks/studio-9je6w5sq.js.map +16 -0
  72. package/dist/chunks/{studio-w7c6kvk7.js → studio-a7pzveq1.js} +2 -2
  73. package/dist/chunks/{studio-97qcgvdn.js → studio-adn0z1k3.js} +14 -8
  74. package/dist/chunks/{studio-97qcgvdn.js.map → studio-adn0z1k3.js.map} +3 -3
  75. package/dist/chunks/{studio-kr88ryr7.js → studio-bsxxx3x2.js} +17 -15
  76. package/dist/chunks/{studio-kr88ryr7.js.map → studio-bsxxx3x2.js.map} +7 -6
  77. package/dist/chunks/studio-bw5dz6km.js +608 -0
  78. package/dist/chunks/studio-bw5dz6km.js.map +21 -0
  79. package/dist/chunks/studio-c6s5getc.js +4232 -0
  80. package/dist/chunks/studio-c6s5getc.js.map +39 -0
  81. package/dist/chunks/studio-cjm7fdjf.js +451 -0
  82. package/dist/chunks/studio-cjm7fdjf.js.map +14 -0
  83. package/dist/chunks/{studio-cx91mnh8.js → studio-ckwz4d3x.js} +53 -53
  84. package/dist/chunks/{studio-cx91mnh8.js.map → studio-ckwz4d3x.js.map} +5 -5
  85. package/dist/chunks/studio-dg7zyt5x.js +28 -0
  86. package/dist/chunks/studio-dg7zyt5x.js.map +10 -0
  87. package/dist/chunks/studio-dzcyvvvz.js +166 -0
  88. package/dist/chunks/studio-dzcyvvvz.js.map +10 -0
  89. package/dist/chunks/{studio-7kqe41sa.js → studio-evkr5d3b.js} +15 -7
  90. package/dist/chunks/{studio-7kqe41sa.js.map → studio-evkr5d3b.js.map} +5 -4
  91. package/dist/chunks/studio-f12h6177.js +411 -0
  92. package/dist/chunks/studio-f12h6177.js.map +13 -0
  93. package/dist/chunks/studio-hbp1s8m9.js +1817 -0
  94. package/dist/chunks/studio-hbp1s8m9.js.map +14 -0
  95. package/dist/chunks/studio-k2sxjph5.js +15295 -0
  96. package/dist/chunks/studio-k2sxjph5.js.map +235 -0
  97. package/dist/chunks/studio-myje51t3.js +385 -0
  98. package/dist/chunks/studio-myje51t3.js.map +12 -0
  99. package/dist/chunks/studio-na6rwzhw.js +353 -0
  100. package/dist/chunks/studio-na6rwzhw.js.map +11 -0
  101. package/dist/chunks/{studio-4k0m4fmp.js → studio-p471gg5n.js} +126 -3
  102. package/dist/chunks/studio-p471gg5n.js.map +14 -0
  103. package/dist/chunks/{studio-9bjwf0d4.js → studio-rgwcpast.js} +5 -46
  104. package/dist/chunks/{studio-9bjwf0d4.js.map → studio-rgwcpast.js.map} +4 -5
  105. package/dist/chunks/{studio-hhj92gcr.js → studio-s519zc99.js} +3 -3
  106. package/dist/chunks/studio-sas7mt3k.js +75 -0
  107. package/dist/chunks/studio-sas7mt3k.js.map +10 -0
  108. package/dist/chunks/studio-t5wqwzdh.js +74 -0
  109. package/dist/chunks/studio-t5wqwzdh.js.map +10 -0
  110. package/dist/chunks/{studio-9qpspwfn.js → studio-temv5bw4.js} +5 -5
  111. package/dist/chunks/{studio-9qpspwfn.js.map → studio-temv5bw4.js.map} +1 -1
  112. package/dist/chunks/{studio-qkvt37c0.js → studio-tr20m8g8.js} +669 -18
  113. package/dist/chunks/studio-tr20m8g8.js.map +9 -0
  114. package/dist/chunks/{studio-ygzav7z5.js → studio-v96qfnww.js} +5514 -4291
  115. package/dist/chunks/studio-v96qfnww.js.map +54 -0
  116. package/dist/chunks/studio-vcetc91k.js +659 -0
  117. package/dist/chunks/studio-vcetc91k.js.map +10 -0
  118. package/dist/chunks/{studio-n9zmpxjg.js → studio-vmymjefp.js} +2972 -4937
  119. package/dist/chunks/studio-vmymjefp.js.map +98 -0
  120. package/dist/chunks/{studio-bghqfmea.js → studio-wwseqyw0.js} +2 -2
  121. package/dist/chunks/studio-x9hmy5kr.js +390 -0
  122. package/dist/chunks/studio-x9hmy5kr.js.map +10 -0
  123. package/dist/chunks/studio-xa0ce0n7.js +335 -0
  124. package/dist/chunks/studio-xa0ce0n7.js.map +10 -0
  125. package/dist/chunks/studio-xm3dbxvn.js +378 -0
  126. package/dist/chunks/studio-xm3dbxvn.js.map +19 -0
  127. package/dist/chunks/{studio-nqw433kv.js → studio-xmvbhg31.js} +2 -2
  128. package/dist/chunks/studio-zv37yrfa.js +1054 -0
  129. package/dist/chunks/studio-zv37yrfa.js.map +13 -0
  130. package/dist/chunks/{ws-client-q7hkz3j9.js → ws-client-654ts66j.js} +6 -4
  131. package/dist/chunks/ws-client-654ts66j.js.map +11 -0
  132. package/dist/iframe-entry.js +668 -230
  133. package/dist/studio.js +23711 -35185
  134. package/index.html +35 -4685
  135. package/package.json +17 -15
  136. package/src/about/about-modal.ts +42 -11
  137. package/src/browse/library-commands.ts +240 -0
  138. package/src/browse/library-layouts.ts +369 -0
  139. package/src/browse/library-model.ts +396 -0
  140. package/src/browse/library-pane.ts +1049 -0
  141. package/src/browse/library-preview.ts +252 -0
  142. package/src/browse/library-source.ts +150 -0
  143. package/src/canvas/canvas-helpers.ts +33 -9
  144. package/src/canvas/canvas-live-render.ts +45 -29
  145. package/src/canvas/canvas-patcher.ts +151 -31
  146. package/src/canvas/canvas-perf.ts +46 -7
  147. package/src/canvas/canvas-render.ts +1092 -220
  148. package/src/canvas/canvas-surface.ts +240 -0
  149. package/src/canvas/canvas-utils.ts +1166 -124
  150. package/src/canvas/iframe-drop.ts +2 -1
  151. package/src/canvas/iframe-entry.ts +241 -4
  152. package/src/canvas/iframe-host.ts +1226 -143
  153. package/src/canvas/iframe-inline-edit.ts +31 -0
  154. package/src/canvas/iframe-interaction.ts +76 -16
  155. package/src/canvas/iframe-keys.ts +107 -45
  156. package/src/canvas/iframe-overlay.ts +38 -1
  157. package/src/canvas/iframe-protocol.ts +137 -2
  158. package/src/canvas/iframe-render.ts +70 -2
  159. package/src/canvas/iframe-slash.ts +6 -0
  160. package/src/canvas/path-mapping.ts +56 -6
  161. package/src/canvas/surface-registry.ts +292 -0
  162. package/src/collab/collab-commands.ts +244 -0
  163. package/src/collab/collab-session.ts +198 -32
  164. package/src/collab/collab-state.ts +28 -2
  165. package/src/collab/presence-chips.ts +100 -9
  166. package/src/commands/active-registry.ts +42 -0
  167. package/src/commands/app-commands.ts +176 -0
  168. package/src/commands/budget.ts +158 -0
  169. package/src/commands/command-args.ts +253 -0
  170. package/src/commands/context.ts +387 -0
  171. package/src/commands/defaults.ts +580 -0
  172. package/src/commands/keymap.ts +571 -0
  173. package/src/commands/levels.ts +345 -0
  174. package/src/commands/live-context.ts +193 -0
  175. package/src/commands/reference.ts +227 -0
  176. package/src/commands/registry.ts +293 -0
  177. package/src/component-props.ts +3 -2
  178. package/src/content/draft-state.ts +106 -0
  179. package/src/content/entry-commands.ts +282 -0
  180. package/src/content/entry-editor.ts +327 -0
  181. package/src/content/entry-fields.ts +116 -0
  182. package/src/content/entry-model.ts +175 -0
  183. package/src/editor/canvas-context-menu.ts +17 -11
  184. package/src/editor/canvas-slash-bridge.ts +11 -4
  185. package/src/editor/context-menu.ts +804 -204
  186. package/src/editor/convert-to-component.ts +31 -10
  187. package/src/editor/convert-to-repeater.ts +3 -2
  188. package/src/editor/inline-edit-apply.ts +5 -5
  189. package/src/editor/inline-edit.ts +86 -50
  190. package/src/editor/insert-zone-action.ts +1 -1
  191. package/src/editor/shortcuts.ts +1043 -329
  192. package/src/editor/slash-menu.ts +1 -0
  193. package/src/files/elements.ts +166 -0
  194. package/src/files/file-ops.ts +178 -31
  195. package/src/files/files.ts +898 -268
  196. package/src/files/fs-events.ts +21 -0
  197. package/src/files/media-meta.ts +203 -0
  198. package/src/files/media-paths.ts +141 -0
  199. package/src/files/media-upload.ts +7 -3
  200. package/src/files/media-usage.ts +189 -0
  201. package/src/github/github-auth.ts +235 -33
  202. package/src/github/github-publish.ts +80 -21
  203. package/src/grid/grid-controller.ts +152 -12
  204. package/src/grid/grid-layout.ts +225 -20
  205. package/src/grid/grid-open.ts +120 -0
  206. package/src/grid/grid-panel.ts +554 -39
  207. package/src/grid/grid-source.ts +27 -1
  208. package/src/grid/redirects-grid.ts +599 -0
  209. package/src/grid/redirects.ts +398 -0
  210. package/src/grid/sources/content-source.ts +108 -2
  211. package/src/i18n/i18n-commands.ts +322 -0
  212. package/src/new-project/add-repo-modal.ts +8 -10
  213. package/src/new-project/import-tab.ts +34 -9
  214. package/src/new-project/location-fields.ts +9 -1
  215. package/src/new-project/new-project-modal.ts +270 -262
  216. package/src/packages/jxsuite-update.ts +97 -48
  217. package/src/packages/pull-package-sync.ts +18 -9
  218. package/src/page-params.ts +2 -1
  219. package/src/panels/activity-bar.ts +152 -91
  220. package/src/panels/activity-panel.ts +481 -0
  221. package/src/panels/ai-chat/chat-markdown.ts +8 -3
  222. package/src/panels/ai-chat/chat-view.ts +245 -18
  223. package/src/panels/ai-chat/composer.ts +31 -5
  224. package/src/panels/ai-chat/sessions-view.ts +17 -10
  225. package/src/panels/ai-panel.ts +279 -120
  226. package/src/panels/block-action-bar.ts +1115 -224
  227. package/src/panels/bottom-dock.ts +337 -0
  228. package/src/panels/canvas-dnd-bridge.ts +37 -8
  229. package/src/panels/chat-panel.ts +29 -12
  230. package/src/panels/data-explorer.ts +254 -112
  231. package/src/panels/data-grid.ts +10 -4
  232. package/src/panels/dnd.ts +28 -51
  233. package/src/panels/editors.ts +434 -107
  234. package/src/panels/elements-panel.ts +66 -2
  235. package/src/panels/empty-state.ts +112 -0
  236. package/src/panels/events-panel.ts +596 -82
  237. package/src/panels/formula-workspace.ts +408 -218
  238. package/src/panels/frontmatter-fields.ts +51 -10
  239. package/src/panels/frontmatter-panel.ts +357 -79
  240. package/src/panels/git-panel.ts +375 -164
  241. package/src/panels/head-panel.ts +791 -85
  242. package/src/panels/i18n-panel.ts +592 -0
  243. package/src/panels/imports-panel.ts +95 -85
  244. package/src/panels/jump-bar.ts +584 -0
  245. package/src/panels/layers-panel.ts +1106 -328
  246. package/src/panels/left-panel.ts +109 -204
  247. package/src/panels/navigator-panels.ts +131 -0
  248. package/src/panels/overlays.ts +7 -2
  249. package/src/panels/pane-context.ts +1477 -0
  250. package/src/panels/pane-grid.ts +406 -0
  251. package/src/panels/panel-registry.ts +340 -0
  252. package/src/panels/panel-scheduler.ts +75 -0
  253. package/src/panels/problems-panel.ts +234 -0
  254. package/src/panels/properties-panel.ts +946 -1139
  255. package/src/panels/provenance.ts +258 -0
  256. package/src/panels/quick-search.ts +791 -122
  257. package/src/panels/right-panel.ts +228 -102
  258. package/src/panels/seo-modal.ts +395 -0
  259. package/src/panels/settings-pane.ts +203 -0
  260. package/src/panels/signals-panel.ts +354 -137
  261. package/src/panels/statement-editor.ts +41 -25
  262. package/src/panels/statusbar.ts +342 -106
  263. package/src/panels/style-panel.ts +974 -463
  264. package/src/panels/style-utils.ts +3 -3
  265. package/src/panels/stylebook-doc.ts +4 -2
  266. package/src/panels/stylebook-layers-panel.ts +3 -2
  267. package/src/panels/stylebook-panel.ts +51 -26
  268. package/src/panels/tab-strip.ts +1058 -78
  269. package/src/panels/target-line.ts +274 -0
  270. package/src/panels/toolbar.ts +631 -484
  271. package/src/panels/welcome-screen.ts +301 -198
  272. package/src/platform-errors.ts +26 -3
  273. package/src/platform.ts +96 -2
  274. package/src/platforms/cloud.ts +71 -8
  275. package/src/platforms/devserver.ts +92 -29
  276. package/src/publish/deploy-checklist.ts +383 -0
  277. package/src/publish/pages-service.ts +10 -1
  278. package/src/publish/publish-commands.ts +218 -0
  279. package/src/publish/publish-panel.ts +110 -16
  280. package/src/recent-projects.ts +3 -2
  281. package/src/services/a11y-report.ts +505 -0
  282. package/src/services/ai-models.ts +75 -2
  283. package/src/services/ai-project-tools.ts +29 -10
  284. package/src/services/ai-system-prompt.ts +53 -14
  285. package/src/services/ai-tools.ts +39 -3
  286. package/src/services/ai-writes.ts +154 -0
  287. package/src/services/announce.ts +97 -0
  288. package/src/services/automation.ts +424 -175
  289. package/src/services/cem-export.ts +9 -4
  290. package/src/services/clock.ts +61 -0
  291. package/src/services/document-assistant.ts +36 -8
  292. package/src/services/idle.ts +179 -0
  293. package/src/services/import-client.ts +26 -0
  294. package/src/services/monaco-buffer.ts +627 -0
  295. package/src/services/monaco-lazy.ts +36 -0
  296. package/src/services/notify.ts +271 -0
  297. package/src/services/profile.ts +155 -0
  298. package/src/services/references.ts +0 -0
  299. package/src/services/token-lint.ts +3 -2
  300. package/src/services/tool-executor.ts +53 -7
  301. package/src/services/trusted-types.ts +123 -0
  302. package/src/settings/contexts-section.ts +427 -0
  303. package/src/settings/contributed-section.ts +149 -11
  304. package/src/settings/css-vars-editor.ts +306 -349
  305. package/src/settings/defs-editor.ts +93 -34
  306. package/src/settings/dependencies-editor.ts +7 -7
  307. package/src/settings/extension-sections.ts +37 -5
  308. package/src/settings/general-settings.ts +204 -121
  309. package/src/settings/locales-section.ts +347 -0
  310. package/src/settings/preferences-accounts.ts +140 -0
  311. package/src/settings/preferences-dialog.ts +620 -0
  312. package/src/settings/preferences-keymap.ts +270 -0
  313. package/src/settings/project-sections.ts +260 -0
  314. package/src/settings/section-registry.ts +169 -0
  315. package/src/settings/settings-document.ts +385 -0
  316. package/src/shell.ts +1549 -0
  317. package/src/site-context.ts +45 -20
  318. package/src/state.ts +14 -275
  319. package/src/store.ts +88 -43
  320. package/src/studio.ts +790 -346
  321. package/src/style/live-preview.ts +36 -0
  322. package/src/style/project-styles.ts +349 -0
  323. package/src/style/token-ref.ts +123 -0
  324. package/src/tabs/patch-ops.ts +16 -4
  325. package/src/tabs/project-config.ts +416 -0
  326. package/src/tabs/selection.ts +323 -0
  327. package/src/tabs/tab.ts +182 -47
  328. package/src/tabs/transact.ts +209 -28
  329. package/src/types.ts +68 -13
  330. package/src/ui/ai-credentials-form.ts +39 -35
  331. package/src/ui/ai-managed-connect.ts +113 -0
  332. package/src/ui/dynamic-slot.ts +224 -81
  333. package/src/ui/expression-editor.ts +71 -63
  334. package/src/ui/field-input.ts +4 -6
  335. package/src/ui/field-row.ts +215 -38
  336. package/src/ui/form-controls.ts +194 -7
  337. package/src/ui/layers.ts +492 -69
  338. package/src/ui/media-picker.ts +115 -25
  339. package/src/ui/panel-resize.ts +109 -91
  340. package/src/ui/progress-modal.ts +131 -50
  341. package/src/ui/regions.ts +462 -0
  342. package/src/ui/schema-form.ts +229 -45
  343. package/src/ui/spectrum.ts +24 -8
  344. package/src/ui/timing.ts +49 -0
  345. package/src/ui/value-selector.ts +20 -0
  346. package/src/ui/value-source.ts +348 -0
  347. package/src/ui/virtual-window.ts +400 -0
  348. package/src/utils/edit-display.ts +3 -2
  349. package/src/utils/inherited-style.ts +67 -37
  350. package/src/view.ts +64 -78
  351. package/src/workspace/pane-derive.ts +1258 -0
  352. package/src/workspace/session.ts +317 -0
  353. package/src/workspace/workspace.ts +1345 -23
  354. package/styles/canvas.css +653 -0
  355. package/styles/forced-colors.css +106 -0
  356. package/styles/inspector.css +1454 -0
  357. package/styles/overlays.css +2704 -0
  358. package/styles/panels.css +1701 -0
  359. package/styles/shell.css +1423 -0
  360. package/styles/tokens.css +151 -0
  361. package/dist/chunks/Overlay.dev-cszx7yn5.js +0 -22
  362. package/dist/chunks/extension-sections-qct0gt8f.js +0 -531
  363. package/dist/chunks/extension-sections-qct0gt8f.js.map +0 -12
  364. package/dist/chunks/files-02zn5y24.js +0 -50
  365. package/dist/chunks/files-02zn5y24.js.map +0 -9
  366. package/dist/chunks/git-panel-mbb0y0hz.js +0 -28
  367. package/dist/chunks/git-panel-mbb0y0hz.js.map +0 -9
  368. package/dist/chunks/layers-panel-9qekmqk1.js +0 -26
  369. package/dist/chunks/layers-panel-9qekmqk1.js.map +0 -9
  370. package/dist/chunks/settings-modal-gj0hrah4.js +0 -29
  371. package/dist/chunks/settings-modal-gj0hrah4.js.map +0 -9
  372. package/dist/chunks/sp-overlay.dev-earn5as8.js +0 -17
  373. package/dist/chunks/sp-overlay.dev-earn5as8.js.map +0 -9
  374. package/dist/chunks/sp-popover.dev-frxtfejn.js +0 -8
  375. package/dist/chunks/sp-popover.dev-frxtfejn.js.map +0 -9
  376. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js +0 -14
  377. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js.map +0 -9
  378. package/dist/chunks/sp-tooltip.dev-a9fywesm.js +0 -21
  379. package/dist/chunks/sp-tooltip.dev-a9fywesm.js.map +0 -10
  380. package/dist/chunks/studio-4k0m4fmp.js.map +0 -13
  381. package/dist/chunks/studio-568nx4t5.js +0 -469
  382. package/dist/chunks/studio-568nx4t5.js.map +0 -14
  383. package/dist/chunks/studio-9smfzqhc.js +0 -19
  384. package/dist/chunks/studio-9smfzqhc.js.map +0 -10
  385. package/dist/chunks/studio-asvf0j9e.js +0 -429
  386. package/dist/chunks/studio-asvf0j9e.js.map +0 -13
  387. package/dist/chunks/studio-d77qm3pk.js +0 -3427
  388. package/dist/chunks/studio-d77qm3pk.js.map +0 -26
  389. package/dist/chunks/studio-j6gqyjf4.js +0 -1854
  390. package/dist/chunks/studio-j6gqyjf4.js.map +0 -18
  391. package/dist/chunks/studio-n9zmpxjg.js.map +0 -115
  392. package/dist/chunks/studio-qkvt37c0.js.map +0 -9
  393. package/dist/chunks/studio-rq0vxb19.js +0 -3744
  394. package/dist/chunks/studio-rq0vxb19.js.map +0 -22
  395. package/dist/chunks/studio-wd6ewdvd.js +0 -853
  396. package/dist/chunks/studio-wd6ewdvd.js.map +0 -12
  397. package/dist/chunks/studio-xq0t7n5t.js +0 -140
  398. package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
  399. package/dist/chunks/studio-xze4zrs1.js.map +0 -10
  400. package/dist/chunks/studio-ygzav7z5.js.map +0 -46
  401. package/dist/chunks/studio-zmy7241e.js +0 -399
  402. package/dist/chunks/studio-zmy7241e.js.map +0 -12
  403. package/dist/chunks/ws-client-q7hkz3j9.js.map +0 -11
  404. package/src/browse/browse-modal.ts +0 -69
  405. package/src/browse/browse.ts +0 -904
  406. package/src/canvas/canvas-diff.ts +0 -216
  407. package/src/canvas/nested-site-style.ts +0 -58
  408. package/src/new-project/design-fields.ts +0 -264
  409. package/src/new-project/templates.ts +0 -61
  410. package/src/panels/tab-bar.ts +0 -548
  411. package/src/settings/settings-modal.ts +0 -203
  412. /package/dist/chunks/{adapter-z8fkmbmv.js.map → adapter-qfnesn5v.js.map} +0 -0
  413. /package/dist/chunks/{cf-settings-mmyv436d.js.map → cf-settings-73sh5z4p.js.map} +0 -0
  414. /package/dist/chunks/{format-host-2c188n41.js.map → format-host-mkwsmt8d.js.map} +0 -0
  415. /package/dist/chunks/{jx-validate-qx8degj7.js.map → jx-validate-v6qgknrs.js.map} +0 -0
  416. /package/dist/chunks/{studio-w7c6kvk7.js.map → studio-a7pzveq1.js.map} +0 -0
  417. /package/dist/chunks/{studio-hhj92gcr.js.map → studio-s519zc99.js.map} +0 -0
  418. /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
  419. /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
@@ -1,12 +1,57 @@
1
1
  /// <reference lib="dom" />
2
+ /**
3
+ * The Logic tab — everything about how this element BEHAVES.
4
+ *
5
+ * Plan §6.5 re-split the inspector by task rather than by data type, and this is the tab that
6
+ * gained by it. Wiring a `$switch`, wiring a repeater to a collection and wiring a click handler
7
+ * are one job; they were three, split across two tabs, because a `$switch` is stored as a property
8
+ * and a click handler is stored as a property-shaped function, and the old split followed the
9
+ * storage. So Logic is now:
10
+ *
11
+ * - **Repeating list** — the `$prototype: "Array"` node's items / filter / sort, and its template.
12
+ * - **Condition** — the `$switch` expression and its cases.
13
+ * - **Events** — declared events, bindings, inline bodies, expressions.
14
+ * - **Observed Attributes · CSS Properties · CSS Parts** — a custom element's outward contract.
15
+ *
16
+ * The last three arrived from the Content tab, where they were three near-identical hand-written
17
+ * flex rows; they are now three calls to {@link renderStaticKvRow}.
18
+ *
19
+ * Everything here reads `session.selection`, so the tab is selection-level in the same sense the
20
+ * rest of the inspector is: no selection is an empty state, not a blank panel.
21
+ */
22
+
2
23
  import { getNodeAtPath, renderOnly } from "../store";
3
24
  import { html, nothing } from "lit-html";
4
25
  import { live } from "lit-html/directives/live.js";
5
26
  import { activeTab } from "../workspace/workspace";
6
- import { mutateAddDef, mutateUpdateProperty, transactDoc } from "../tabs/transact";
27
+ import { primarySelection, unifyValues } from "../tabs/selection";
28
+ import { renderProvenanceChip } from "./provenance";
29
+ import {
30
+ mutateAddDef,
31
+ mutateAddSwitchCase,
32
+ mutateRemoveSwitchCase,
33
+ mutateRenameSwitchCase,
34
+ mutateUpdateProperty,
35
+ transactDoc,
36
+ } from "../tabs/transact";
37
+ import { clickAnythingTo, renderEmptyState, staleSelectionMessage } from "./empty-state";
7
38
  import { renderExpressionEditor } from "../ui/expression-editor";
8
39
  import { renderStatementEditor } from "./statement-editor";
40
+ import { inspectorStatementsRegion } from "../ui/regions";
9
41
  import { livePreviewExpression } from "../services/live-preview";
42
+ import { renderFieldRow, renderStaticKvRow } from "../ui/field-row";
43
+ import { renderDynamicSlot, slotMode, switchSlotMode } from "../ui/dynamic-slot";
44
+ import { VALUE_SOURCE_LABELS, capsForPosition } from "../ui/value-source";
45
+ import { spTextField } from "../ui/field-input";
46
+ import {
47
+ bindableSignalNames,
48
+ defaultAsString,
49
+ isInspectorSectionOpen,
50
+ mapSignalsFor,
51
+ setInspectorSection,
52
+ } from "./properties-panel";
53
+ import { collectCssParts } from "./signals-panel";
54
+ import { openLogicTarget } from "./formula-workspace";
10
55
  import {
11
56
  getEventBinding,
12
57
  isExpressionDef,
@@ -16,8 +61,26 @@ import {
16
61
  } from "@jxsuite/schema/guards";
17
62
 
18
63
  import type { JsonValue } from "../types";
19
- import type { CemEvent, JxFunctionDef, JxPrototypeDef } from "@jxsuite/schema/types";
64
+ import type { TemplateResult } from "lit-html";
65
+ import type { SlotCapsSource, SlotMode, SignalOption } from "../ui/dynamic-slot";
66
+ import type { JxPath } from "../state";
67
+ import type {
68
+ CemEvent,
69
+ JxEventBinding,
70
+ JxFunctionDef,
71
+ JxMutableNode,
72
+ JxPrototypeDef,
73
+ } from "@jxsuite/schema/types";
20
74
 
75
+ /**
76
+ * The events worth SUGGESTING — not the events an element may have.
77
+ *
78
+ * These ten were a closed `sp-picker`, so `ondragover`, `onpointerdown`, `onwheel`, `onpaste` and
79
+ * every custom event a component emits were unbindable from the Inspector: the plan's §6.5 asks for
80
+ * "a free-form combobox instead of a hard-coded list of ten", and `ui/value-selector.ts`'s own
81
+ * docstring names this field as the reason it exists ("what lets a control like the event-name
82
+ * field stop being a list of ten"). The widget was written and never wired.
83
+ */
21
84
  export const EVENT_NAMES = [
22
85
  "onclick",
23
86
  "oninput",
@@ -31,21 +94,307 @@ export const EVENT_NAMES = [
31
94
  "onmouseleave",
32
95
  ];
33
96
 
34
- /** @param {{ isCustomElementDoc: () => boolean }} helpers */
35
- export function eventsSidebarTemplate(helpers: { isCustomElementDoc: () => boolean }) {
36
- const { isCustomElementDoc } = helpers;
37
- const tab = activeTab.value;
38
- const selection = tab?.session.selection;
39
- const document = tab?.doc.document;
40
- if (!selection) {
41
- return html`<div class="empty-state">Select an element to edit events</div>`;
97
+ /** {@link EVENT_NAMES} as the combobox's option list, plus whatever this element already declares. */
98
+ function eventNameOptions(declared: readonly string[]): { label: string; value: string }[] {
99
+ const seen = new Set<string>();
100
+ const out: { label: string; value: string }[] = [];
101
+ for (const name of [...declared, ...EVENT_NAMES]) {
102
+ if (!seen.has(name)) {
103
+ seen.add(name);
104
+ out.push({ label: name, value: name });
105
+ }
42
106
  }
43
- const node = getNodeAtPath(document!, selection);
44
- if (!node) {
45
- return html`<div class="empty-state">Node not found</div>`;
107
+ return out;
108
+ }
109
+
110
+ /**
111
+ * The rungs an `on*` handler permits, derived from the schema (`RefObject | ExpressionEntry |
112
+ * FunctionDef`) rather than listed here.
113
+ *
114
+ * This picker used to read Inline code / Expression / Existing function — a fourth private dialect
115
+ * for the one ladder §6.6 collapses, so a user who learned "Formula" on a property row met
116
+ * "Expression" here and had no reason to think they were the same thing.
117
+ */
118
+ const HANDLER_MODES: SlotMode[] = capsForPosition("eventHandler");
119
+
120
+ /** The value a handler starts at on each rung, when the user has never been on that rung before. */
121
+ function seedForHandlerMode(mode: SlotMode, functionDefs: [string, unknown][]): JsonValue {
122
+ if (mode === "function") {
123
+ return { $prototype: "Function", body: "", parameters: [] };
46
124
  }
125
+ if (mode === "expression") {
126
+ return { $expression: { operator: "=", target: null } };
127
+ }
128
+ const [firstFn] = functionDefs;
129
+ return firstFn ? { $ref: `#/state/${firstFn[0]}` } : { $ref: "" };
130
+ }
131
+
132
+ // ─── The bindable row ────────────────────────────────────────────────────────
133
+
134
+ /** Arguments for {@link bindableFieldRow}. */
135
+ interface BindableRowOpts {
136
+ /** The `data-prop` key — what `inspector/field:<prop>` addresses. */
137
+ prop: string;
138
+ label: string;
139
+ /** The raw document value: a JSON literal, `{ $ref }`, a `${}` template or an `$expression`. */
140
+ rawValue: unknown;
141
+ onChange: (v?: JsonValue) => void;
142
+ /** Removes the position outright. Absent means the position is mandatory and cannot be cleared. */
143
+ onClear?: (() => void) | undefined;
144
+ /** Extra signals this position may bind to — `$map/item` and friends inside a template. */
145
+ extraSignals?: SignalOption[] | null;
146
+ /** Stable identity for the dynamic slot's per-mode value memory; must include the node path. */
147
+ fieldKey: string;
148
+ /** The document position being edited — the rungs follow from its schema, never from here. */
149
+ caps: SlotCapsSource;
150
+ }
151
+
152
+ /**
153
+ * A bindable value as a first-class field row.
154
+ *
155
+ * This used to render a bare `<div class="field-row">` with a label and a widget: no set-dot, no
156
+ * clear affordance, no error slot — the three things every other row in the inspector has had since
157
+ * `renderFieldRow` landed. Repeater and Switch fields were the last second-class rows in the
158
+ * inspector, which is exactly what §12 P5 item 6 names.
159
+ *
160
+ * Every position it serves is a single-line expression (a collection, a filter, a sort key, a
161
+ * `$switch`), so it draws one literal editor rather than dispatching over a widget type — the
162
+ * textarea and checkbox arms it used to carry had no caller in either tab.
163
+ */
164
+ function bindableFieldRow({
165
+ prop,
166
+ label,
167
+ rawValue,
168
+ onChange,
169
+ onClear,
170
+ extraSignals = null,
171
+ fieldKey,
172
+ caps,
173
+ }: BindableRowOpts) {
174
+ const defs = activeTab.value!.doc.document.state || {};
175
+ const boundRef = isRef(rawValue) ? rawValue.$ref : null;
176
+ const staticVal = slotMode(rawValue) === "literal" ? (rawValue ?? "") : "";
47
177
 
48
- const defs = document!.state || {};
178
+ // De-escalating to literal restores the bound signal's declared default (old unbind behavior).
179
+ const literalDefault = boundRef
180
+ ? defaultAsString(defs[boundRef.startsWith("#/state/") ? boundRef.slice(8) : boundRef]) ||
181
+ undefined
182
+ : undefined;
183
+
184
+ const slot = renderDynamicSlot({
185
+ caps,
186
+ extraSignals,
187
+ fieldKey,
188
+ literalDefault,
189
+ onChange,
190
+ staticWidget: spTextField(`prop:${label}`, String(staticVal), (v: string) => onChange(v)),
191
+ stateDefs: bindableSignalNames(activeTab.value!.doc.document),
192
+ value: rawValue,
193
+ });
194
+ return renderFieldRow({
195
+ hasValue: rawValue !== undefined && rawValue !== "",
196
+ label,
197
+ labelExtra: slot.modeButton,
198
+ prop,
199
+ widget: slot.widget,
200
+ ...(onClear ? { onClear } : {}),
201
+ });
202
+ }
203
+
204
+ // ─── Repeating list ──────────────────────────────────────────────────────────
205
+
206
+ /**
207
+ * The repeater's items / filter / sort, and the way into its template.
208
+ *
209
+ * Filter and Sort are always drawn. They used to hide behind `+ Add filter`, which seeded `{ $ref:
210
+ * "#/state/" }` — a binding to the empty pointer, which is not a signal — so the row opened already
211
+ * bound to nothing and the only way to type a literal was to cycle the mode ring twice (§11.4).
212
+ * There is no seed to fix, because a repeater has exactly three inputs and an empty row states that
213
+ * better than a button does: type into it and it is set, clear it and it is gone.
214
+ */
215
+ function renderRepeaterFieldsTemplate(node: JxMutableNode, path: JxPath) {
216
+ const key = path.join("/");
217
+ const clear = (name: string) => () =>
218
+ transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, name));
219
+ const optional = (name: "filter" | "sort", label: string) =>
220
+ bindableFieldRow({
221
+ caps: name === "filter" ? "repeaterFilter" : "repeaterSort",
222
+ fieldKey: `prop|${key}|${name}`,
223
+ label,
224
+ onChange: (v?: JsonValue) =>
225
+ transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, name, v || undefined)),
226
+ onClear: clear(name),
227
+ prop: name,
228
+ rawValue: node[name],
229
+ });
230
+ return html`
231
+ ${bindableFieldRow({
232
+ caps: "repeaterItems",
233
+ fieldKey: `prop|${key}|items`,
234
+ label: "Items",
235
+ onChange: (v?: JsonValue) =>
236
+ transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "items", v)),
237
+ prop: "items",
238
+ rawValue: node.items,
239
+ })}
240
+ ${optional("filter", "Filter")} ${optional("sort", "Sort")}
241
+ ${
242
+ node.map
243
+ ? html`
244
+ <sp-action-button
245
+ size="s"
246
+ class="logic-edit-template"
247
+ @click=${() => {
248
+ activeTab.value!.session.selection = [[...path, "map"]];
249
+ }}
250
+ >Edit template →</sp-action-button
251
+ >
252
+ `
253
+ : nothing
254
+ }
255
+ `;
256
+ }
257
+
258
+ // ─── Condition ───────────────────────────────────────────────────────────────
259
+
260
+ /** The `$switch` expression and its cases — each case a field row like every other. */
261
+ function renderSwitchFieldsTemplate(
262
+ node: JxMutableNode,
263
+ path: JxPath,
264
+ mapSignals: SignalOption[] | null,
265
+ ) {
266
+ const caseNames = Object.keys(node.cases || {});
267
+ return html`
268
+ ${bindableFieldRow({
269
+ // `SwitchDef` is a $ref and nothing else, so From data… is the only rung — a $switch is
270
+ // Inherently dynamic, and de-escalating it would delete the key and demote the node.
271
+ caps: "switchDiscriminant",
272
+ extraSignals: mapSignals,
273
+ fieldKey: `prop|${path.join("/")}|$switch`,
274
+ label: "Expression",
275
+ onChange: (v?: JsonValue) =>
276
+ transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "$switch", v)),
277
+ prop: "$switch",
278
+ rawValue: node.$switch,
279
+ })}
280
+ <div class="logic-subhead">Cases</div>
281
+ ${caseNames.map((caseName) => {
282
+ let debounce: ReturnType<typeof setTimeout> | undefined;
283
+ return renderFieldRow({
284
+ hasValue: true,
285
+ label: "Case",
286
+ prop: `case:${caseName}`,
287
+ provenance: {
288
+ onClick: () =>
289
+ transactDoc(activeTab.value!, (t) => mutateRemoveSwitchCase(t, path, caseName)),
290
+ state: "set",
291
+ title: `Remove case "${caseName}"`,
292
+ },
293
+ widget: html`
294
+ <div class="logic-case-row">
295
+ <sp-textfield
296
+ size="s"
297
+ class="logic-case-name"
298
+ .value=${live(caseName)}
299
+ @input=${(e: Event) => {
300
+ clearTimeout(debounce);
301
+ const next = (e.target as HTMLInputElement).value;
302
+ debounce = setTimeout(() => {
303
+ if (next && next !== caseName) {
304
+ transactDoc(activeTab.value, (t) =>
305
+ mutateRenameSwitchCase(t, path, caseName, next),
306
+ );
307
+ }
308
+ }, 500);
309
+ }}
310
+ ></sp-textfield>
311
+ <sp-action-button
312
+ size="xs"
313
+ quiet
314
+ class="logic-case-open"
315
+ title="Edit case"
316
+ @click=${(e: Event) => {
317
+ e.stopPropagation();
318
+ activeTab.value!.session.selection = [[...path, "cases", caseName]];
319
+ }}
320
+ >→</sp-action-button
321
+ >
322
+ </div>
323
+ `,
324
+ });
325
+ })}
326
+ <span
327
+ class="kv-add"
328
+ @click=${() => {
329
+ transactDoc(activeTab.value, (t) =>
330
+ mutateAddSwitchCase(t, path, `case${caseNames.length + 1}`),
331
+ );
332
+ }}
333
+ >+ Add case</span
334
+ >
335
+ `;
336
+ }
337
+
338
+ // ─── Events ──────────────────────────────────────────────────────────────────
339
+
340
+ /** The event bindings themselves — declared events, then one block per bound `on*` key. */
341
+ /**
342
+ * The Logic tab's Mixed contract (§6.5), stated once because it is a judgement, not a mechanism.
343
+ *
344
+ * Wiring splits in two, and a multi-selection treats the halves differently:
345
+ *
346
+ * - **Which events exist, and how each is produced** — the event key, its Value Source rung, and its
347
+ * removal — is a property of the BATCH. Binding one handler to six buttons, or clearing it off
348
+ * all six, is a single decision, so those three controls write to every selected element inside
349
+ * one transaction and one undo step.
350
+ * - **What a handler CONTAINS** — a function body, an expression's operands, a repeater's source and
351
+ * template — is the PRIMARY's. Six elements do not share one handler body; broadcasting a
352
+ * keystroke into six different bodies would destroy five of them, and showing one body while
353
+ * claiming to edit six is the lie the Mixed chip exists to prevent.
354
+ *
355
+ * So the row header states `mixed (n)` when the selected elements disagree about a key — including
356
+ * when some of them do not bind it at all — and the body below it stays what it always was: the
357
+ * primary's, edited alone.
358
+ *
359
+ * @param {readonly JxPath[]} targets
360
+ * @param {string} key
361
+ * @param {JsonValue} [value] — omitted deletes the key.
362
+ */
363
+ function commitToTargets(targets: readonly JxPath[], key: string, value?: JsonValue): void {
364
+ transactDoc(activeTab.value, (t) => {
365
+ for (const target of targets) {
366
+ mutateUpdateProperty(t, target, key, value);
367
+ }
368
+ });
369
+ }
370
+
371
+ /**
372
+ * How many selected elements disagree about one event key — 0 when they agree or there is one.
373
+ *
374
+ * @param {JxMutableNode} doc
375
+ * @param {readonly JxPath[]} targets
376
+ * @param {string} evKey
377
+ * @returns {number}
378
+ */
379
+ function mixedEventCount(doc: JxMutableNode, targets: readonly JxPath[], evKey: string): number {
380
+ if (targets.length < 2) {
381
+ return 0;
382
+ }
383
+ const values = targets.map((path) => {
384
+ const n = getNodeAtPath(doc, path) as JxMutableNode | undefined;
385
+ return n ? (getEventBinding(n, evKey) ?? null) : null;
386
+ });
387
+ return unifyValues(values).mixed ? targets.length : 0;
388
+ }
389
+
390
+ function renderEventsBody(
391
+ node: JxMutableNode,
392
+ selection: JxPath,
393
+ isCustomElement: boolean,
394
+ targets: readonly JxPath[] = [selection],
395
+ ) {
396
+ const tab = activeTab.value!;
397
+ const defs = tab.doc.document.state || {};
49
398
  const functionDefs = Object.entries(defs).filter(
50
399
  ([, d]) =>
51
400
  (d as JxPrototypeDef)?.$prototype === "Function" || (d as Record<string, unknown>)?.$handler,
@@ -53,7 +402,7 @@ export function eventsSidebarTemplate(helpers: { isCustomElementDoc: () => boole
53
402
 
54
403
  // Declared CEM events (custom element docs)
55
404
  let declaredEventsT: unknown = nothing;
56
- if (isCustomElementDoc()) {
405
+ if (isCustomElement) {
57
406
  const allEmits: (CemEvent & { _fn: string })[] = [];
58
407
  for (const [fnName, d] of Object.entries(defs)) {
59
408
  if (isFunctionDef(d) && Array.isArray(d.emits)) {
@@ -85,93 +434,96 @@ export function eventsSidebarTemplate(helpers: { isCustomElementDoc: () => boole
85
434
  }
86
435
  }
87
436
 
88
- // Find existing event bindings
89
- const eventKeys = Object.keys(node).filter(
90
- (k) => k.startsWith("on") && getEventBinding(node, k) !== undefined,
91
- );
437
+ // Find existing event bindings. Resolved once, as pairs: asking `getEventBinding` for the key and
438
+ // Then again for the value left a `if (!evVal) return nothing` arm nothing could ever reach.
439
+ const eventEntries = Object.keys(node)
440
+ .filter((k) => k.startsWith("on"))
441
+ .map((k) => [k, getEventBinding(node, k)] as const)
442
+ .filter((pair): pair is [string, JxEventBinding] => pair[1] !== undefined);
92
443
 
93
444
  return html`
94
445
  <div class="events-panel">
95
446
  ${declaredEventsT}
96
447
  <div class="events-section">
97
448
  ${
98
- eventKeys.length > 0
449
+ eventEntries.length > 0
99
450
  ? html` <sp-field-label size="s">Event Bindings</sp-field-label> `
100
451
  : nothing
101
452
  }
102
- ${eventKeys.map((evKey) => {
103
- const evVal = getEventBinding(node, evKey);
104
- if (!evVal) {
105
- return nothing;
106
- }
453
+ ${eventEntries.map(([evKey, evVal]) => {
107
454
  const inlineFn: JxFunctionDef | null = isFunctionDef(evVal) ? evVal : null;
108
455
  const expression = isExpressionDef(evVal) ? evVal.$expression : null;
109
456
  const refValue = isRef(evVal) ? evVal.$ref : null;
110
- const currentMode = inlineFn ? "inline" : expression ? "$expression" : "ref";
457
+ const currentMode = slotMode(evVal);
458
+ const modeFieldKey = `event|${selection.join("/")}|${evKey}`;
111
459
  return html`
112
460
  <div class="event-binding">
113
461
  <div class="event-row">
114
- <sp-picker
462
+ <jx-value-selector
115
463
  size="s"
116
464
  class="event-name"
117
- .value=${live(evKey)}
465
+ .value=${evKey}
466
+ .options=${eventNameOptions(Object.keys(node).filter((k) => k.startsWith("on")))}
118
467
  @change=${(e: Event) => {
119
- const newKey = (e.target as HTMLInputElement).value;
120
- if (newKey && newKey !== evKey) {
468
+ const newKey = (e.target as HTMLInputElement).value.trim();
469
+ // A NAME, not a menu choice: the field is free-form now, so it has to refuse
470
+ // What it cannot bind rather than trusting a list it no longer constrains.
471
+ if (newKey && newKey !== evKey && /^on[a-z][\da-z-]*$/i.test(newKey)) {
472
+ // Renaming the key moves the binding on every selected element, in one step.
121
473
  transactDoc(activeTab.value, (t) => {
122
- mutateUpdateProperty(t, selection, evKey);
123
- mutateUpdateProperty(t, selection, newKey, evVal as JsonValue);
474
+ for (const target of targets) {
475
+ const existing = getEventBinding(
476
+ getNodeAtPath(t.doc.document, target) as JxMutableNode,
477
+ evKey,
478
+ );
479
+ if (existing === undefined) {
480
+ continue;
481
+ }
482
+ mutateUpdateProperty(t, target, evKey);
483
+ mutateUpdateProperty(t, target, newKey, existing as JsonValue);
484
+ }
124
485
  });
125
486
  }
126
487
  }}
127
488
  >
128
- ${[evKey, ...EVENT_NAMES.filter((n) => n !== evKey)].map(
129
- (n) => html`<sp-menu-item value=${n}>${n}</sp-menu-item>`,
130
- )}
131
- </sp-picker>
489
+ ></jx-value-selector
490
+ >
132
491
  <sp-picker
133
492
  size="s"
134
493
  class="event-mode"
135
494
  .value=${live(currentMode)}
136
495
  @change=${(e: Event) => {
137
- const newMode = (e.target as HTMLInputElement).value;
138
- if (newMode === "inline") {
139
- transactDoc(activeTab.value, (t) =>
140
- mutateUpdateProperty(t, selection, evKey, {
141
- $prototype: "Function",
142
- body: "",
143
- parameters: [],
144
- }),
145
- );
146
- } else if (newMode === "$expression") {
147
- transactDoc(activeTab.value, (t) =>
148
- mutateUpdateProperty(t, selection, evKey, {
149
- $expression: { operator: "=", target: null },
150
- }),
151
- );
152
- } else {
153
- const [firstFn] = functionDefs;
154
- transactDoc(activeTab.value, (t) =>
155
- mutateUpdateProperty(
156
- t,
157
- selection,
158
- evKey,
159
- firstFn ? { $ref: `#/state/${firstFn[0]}` } : { $ref: "" },
160
- ),
161
- );
162
- }
496
+ const next = (e.target as HTMLInputElement).value as SlotMode;
497
+ /* The same ladder the inspector's rows offer, remembering the representation
498
+ it left: switching to Formula and back used to throw the handler body away. */
499
+ commitToTargets(
500
+ targets,
501
+ evKey,
502
+ switchSlotMode(
503
+ modeFieldKey,
504
+ currentMode,
505
+ next,
506
+ evVal as JsonValue,
507
+ seedForHandlerMode(next, functionDefs),
508
+ ) ?? seedForHandlerMode(next, functionDefs),
509
+ );
163
510
  }}
164
511
  >
165
- <sp-menu-item value="inline">inline</sp-menu-item>
166
- <sp-menu-item value="$expression">$expression</sp-menu-item>
167
- <sp-menu-item value="ref">$ref</sp-menu-item>
512
+ ${HANDLER_MODES.map(
513
+ (m) => html`<sp-menu-item value=${m}>${VALUE_SOURCE_LABELS[m]}</sp-menu-item>`,
514
+ )}
168
515
  </sp-picker>
169
- <sp-action-button
170
- size="xs"
171
- quiet
172
- @click=${() =>
173
- transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, selection, evKey))}
174
- >
516
+ ${renderProvenanceChip(evKey, {
517
+ state: mixedEventCount(tab.doc.document, targets, evKey) > 0 ? "mixed" : "set",
518
+ donor: String(targets.length),
519
+ onClick: () => commitToTargets(targets, evKey),
520
+ title:
521
+ mixedEventCount(tab.doc.document, targets, evKey) > 0
522
+ ? `${targets.length} selected elements bind ${evKey} differently — ` +
523
+ `clearing removes it from all of them`
524
+ : `Clear ${evKey}`,
525
+ })}
526
+ <sp-action-button size="xs" quiet @click=${() => commitToTargets(targets, evKey)}>
175
527
  <sp-icon-delete slot="icon"></sp-icon-delete>
176
528
  </sp-action-button>
177
529
  </div>
@@ -230,6 +582,12 @@ export function eventsSidebarTemplate(helpers: { isCustomElementDoc: () => boole
230
582
  {
231
583
  allowEventRef: true,
232
584
  emits: inlineFn.emits ?? [],
585
+ // The Inspector's copy, named by the handler it edits. It answered
586
+ // To `navigator/statements` until the docks were split, and then
587
+ // To a bare `inspector/statements` — which this `.map` stamps once
588
+ // Per structured handler, so two handlers on a node made two
589
+ // Elements answer to it and `resolveRegion` took the second.
590
+ region: inspectorStatementsRegion(evKey),
233
591
  stateDefs: Object.keys(defs),
234
592
  stateEntries: defs,
235
593
  },
@@ -258,11 +616,10 @@ export function eventsSidebarTemplate(helpers: { isCustomElementDoc: () => boole
258
616
  quiet
259
617
  title="Open in editor"
260
618
  @click=${() => {
261
- tab.session.ui.editingFunction = {
262
- eventKey: evKey,
263
- path: selection,
264
- type: "event",
265
- };
619
+ openLogicTarget({
620
+ editing: { eventKey: evKey, path: selection, type: "event" },
621
+ surface: "function",
622
+ });
266
623
  }}
267
624
  >
268
625
  <sp-icon-code slot="icon"></sp-icon-code>
@@ -309,14 +666,13 @@ export function eventsSidebarTemplate(helpers: { isCustomElementDoc: () => boole
309
666
  quiet
310
667
  title="Open in formula workspace"
311
668
  @click=${() => {
312
- tab.session.ui.editingFormula = {
313
- eventKey: evKey,
314
- path: selection,
315
- type: "event",
316
- };
669
+ openLogicTarget({
670
+ editing: { eventKey: evKey, path: selection, type: "event" },
671
+ surface: "formula",
672
+ });
317
673
  }}
318
674
  >
319
- <sp-icon-full-screen slot="icon"></sp-icon-full-screen>
675
+ <sp-icon-align-bottom slot="icon"></sp-icon-align-bottom>
320
676
  </sp-action-button>
321
677
  </div>
322
678
  `
@@ -390,3 +746,161 @@ export function eventsSidebarTemplate(helpers: { isCustomElementDoc: () => boole
390
746
  </div>
391
747
  `;
392
748
  }
749
+
750
+ // ─── The custom element's outward contract ───────────────────────────────────
751
+
752
+ /** Observed attributes: the `attribute` names a page may set from markup, and what they feed. */
753
+ function renderObservedAttrsSection(document: JxMutableNode) {
754
+ const state = document.state || {};
755
+ const entries = Object.entries(state).filter(([, d]) => (d as Record<string, unknown>).attribute);
756
+ return html`
757
+ <sp-accordion-item
758
+ label="Observed Attributes"
759
+ ?open=${isInspectorSectionOpen("__observed", false)}
760
+ @sp-accordion-item-toggle=${() =>
761
+ setInspectorSection("__observed", !isInspectorSectionOpen("__observed", false))}
762
+ >
763
+ <div class="style-section-body">
764
+ ${
765
+ entries.length === 0
766
+ ? renderEmptyState({
767
+ compact: true,
768
+ message:
769
+ "Attributes let a page set this component from markup. " +
770
+ 'Name an "attribute" on a data entry to expose it here.',
771
+ })
772
+ : entries.map(([key, d]) => {
773
+ const def = d as Record<string, unknown>;
774
+ return renderStaticKvRow({
775
+ detail: `→ ${key}`,
776
+ name: String(def.attribute),
777
+ ...(def.type ? { value: String(def.type) } : {}),
778
+ ...(def.reflects ? { tags: ["reflects"] } : {}),
779
+ });
780
+ })
781
+ }
782
+ </div>
783
+ </sp-accordion-item>
784
+ `;
785
+ }
786
+
787
+ /** The `--custom-properties` a page may override on this component. */
788
+ function renderCssPropsSection(node: JxMutableNode) {
789
+ const style = node.style || {};
790
+ const cssProps = Object.entries(style).filter(([k]) => k.startsWith("--"));
791
+ if (cssProps.length === 0) {
792
+ return nothing;
793
+ }
794
+ return html`
795
+ <sp-accordion-item
796
+ label="CSS Properties"
797
+ ?open=${isInspectorSectionOpen("__cssprops", false)}
798
+ @sp-accordion-item-toggle=${() =>
799
+ setInspectorSection("__cssprops", !isInspectorSectionOpen("__cssprops", false))}
800
+ >
801
+ <div class="style-section-body">
802
+ ${cssProps.map(([prop, val]) => renderStaticKvRow({ name: prop, value: String(val) }))}
803
+ </div>
804
+ </sp-accordion-item>
805
+ `;
806
+ }
807
+
808
+ /** The `part` names a page may style inside this component's shadow tree. */
809
+ function renderCssPartsSection(document: JxMutableNode) {
810
+ const parts = collectCssParts(document);
811
+ if (parts.length === 0) {
812
+ return nothing;
813
+ }
814
+ return html`
815
+ <sp-accordion-item
816
+ label="CSS Parts"
817
+ ?open=${isInspectorSectionOpen("__cssparts", false)}
818
+ @sp-accordion-item-toggle=${() =>
819
+ setInspectorSection("__cssparts", !isInspectorSectionOpen("__cssparts", false))}
820
+ >
821
+ <div class="style-section-body">
822
+ ${parts.map((p) => renderStaticKvRow({ detail: `<${p.tag}>`, name: p.name }))}
823
+ </div>
824
+ </sp-accordion-item>
825
+ `;
826
+ }
827
+
828
+ // ─── The tab ─────────────────────────────────────────────────────────────────
829
+
830
+ /**
831
+ * The Logic tab.
832
+ *
833
+ * @param {{ isCustomElementDoc: () => boolean }} helpers — injected because the answer needs the
834
+ * tab's mode and ui, which the dock already has in hand.
835
+ */
836
+ export function renderLogicPanelTemplate(helpers: {
837
+ isCustomElementDoc: () => boolean;
838
+ }): TemplateResult {
839
+ const { isCustomElementDoc } = helpers;
840
+ const tab = activeTab.value;
841
+ const selection = primarySelection(tab?.session.selection);
842
+ const doc = tab?.doc.document;
843
+ if (!selection) {
844
+ return renderEmptyState({ message: clickAnythingTo("wire it up") });
845
+ }
846
+ const node = getNodeAtPath(doc!, selection);
847
+ if (!node) {
848
+ return renderEmptyState({ message: staleSelectionMessage() });
849
+ }
850
+
851
+ // The whole selection the Logic tab wires. `[selection]` when one element is selected — which is
852
+ // Every existing call site's behaviour, unchanged.
853
+ const targets: JxPath[] =
854
+ tab!.session.selection.length > 0 ? tab!.session.selection : [selection];
855
+ const isMapNode = node.$prototype === "Array";
856
+ const isSwitchNode = Boolean(node.$switch);
857
+ const isRoot = selection.length === 0;
858
+ const isCustomElement = isCustomElementDoc();
859
+ const mapSignals = mapSignalsFor(selection);
860
+
861
+ const repeaterT = isMapNode
862
+ ? html`
863
+ <sp-accordion-item label="Repeating list" open>
864
+ <div class="style-section-body">${renderRepeaterFieldsTemplate(node, selection)}</div>
865
+ </sp-accordion-item>
866
+ `
867
+ : nothing;
868
+
869
+ const switchT = isSwitchNode
870
+ ? html`
871
+ <sp-accordion-item label="Condition" open>
872
+ <div class="style-section-body">
873
+ ${renderSwitchFieldsTemplate(node, selection, mapSignals)}
874
+ </div>
875
+ </sp-accordion-item>
876
+ `
877
+ : nothing;
878
+
879
+ // A repeater is not an element: it has no `on*` position to bind, and offering "Add Event" on one
880
+ // Writes a handler onto a node the renderer never mounts.
881
+ const eventsT = isMapNode
882
+ ? nothing
883
+ : html`
884
+ <sp-accordion-item label="Events" open>
885
+ <div class="style-section-body">
886
+ ${renderEventsBody(node, selection, isCustomElement, targets)}
887
+ </div>
888
+ </sp-accordion-item>
889
+ `;
890
+
891
+ const contractT =
892
+ isCustomElement && isRoot
893
+ ? html`
894
+ ${renderObservedAttrsSection(doc!)} ${renderCssPropsSection(node)}
895
+ ${renderCssPartsSection(doc!)}
896
+ `
897
+ : nothing;
898
+
899
+ return html`
900
+ <div class="style-sidebar">
901
+ <sp-accordion allow-multiple size="s">
902
+ ${repeaterT} ${switchT} ${eventsT} ${contractT}
903
+ </sp-accordion>
904
+ </div>
905
+ `;
906
+ }