@jxsuite/studio 4.0.0 → 5.0.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 (613) hide show
  1. package/README.md +1 -1
  2. package/data/elements-meta.json +5 -15
  3. package/data/html-meta.json +51 -6
  4. package/dist/chunks/{2020-xp8vnwma.js → 2020-g79n01v9.js} +14 -14
  5. package/dist/chunks/action-group-sp6tna8v.js +118 -0
  6. package/dist/chunks/adapter-nj7tnjfn.js +13 -0
  7. package/dist/chunks/cf-account-picker-fyf63pmy.js +495 -0
  8. package/dist/chunks/cf-settings-71bv5zn9.js +20 -0
  9. package/dist/chunks/color-area-v17df7b8.js +154 -0
  10. package/dist/chunks/color-field-anwybnkn.js +208 -0
  11. package/dist/chunks/color-slider-44ez96fc.js +79 -0
  12. package/dist/chunks/color-xgg3kfw7.js +35 -0
  13. package/dist/chunks/combobox-5e7jev23.js +236 -0
  14. package/dist/chunks/components-2w1f35qs.js +18 -0
  15. package/dist/chunks/content-source-3wqeas45.js +46 -0
  16. package/dist/chunks/convert-to-repeater-d0cgnb6d.js +639 -0
  17. package/dist/chunks/dialog-s890b918.js +85 -0
  18. package/dist/chunks/editor-3qhbg51x.js +35 -0
  19. package/dist/chunks/entry-commands-d6g50j7s.js +42 -0
  20. package/dist/chunks/extension-sections-fy7q4fe9.js +651 -0
  21. package/dist/chunks/file-ops-3btj6st0.js +47 -0
  22. package/dist/chunks/files-ef8fgs3w.js +72 -0
  23. package/dist/chunks/format-host-3c33nwr8.js +40 -0
  24. package/dist/chunks/fs-events-13tj6sex.js +18 -0
  25. package/dist/chunks/git-panel-4wp2rn9b.js +49 -0
  26. package/dist/chunks/icons-yy10wk9r.js +402 -0
  27. package/dist/chunks/iframe-entry-15g3n6xv.js +114 -0
  28. package/dist/chunks/iframe-entry-b27vq81n.js +203 -0
  29. package/dist/chunks/iframe-entry-nbyxee1y.js +154 -0
  30. package/dist/chunks/iframe-host-jgesd924.js +115 -0
  31. package/dist/chunks/{index-nj44jpzj.js → index-mdmyx6s2.js} +11 -11
  32. package/dist/chunks/index-n4j7qxyr.js +109 -0
  33. package/dist/chunks/{javascript-q8kpmfah.js → javascript-xvrfrpkb.js} +8 -9
  34. package/dist/chunks/{jsonMode-dkr040ga.js → jsonMode-tyx2kj6p.js} +526 -527
  35. package/dist/chunks/jx-validate-j0wxk72w.js +15 -0
  36. package/dist/chunks/layers-1nq5evsh.js +29 -0
  37. package/dist/chunks/layers-panel-ar2gh90g.js +40 -0
  38. package/dist/chunks/listbox-6v2ac08j.js +44 -0
  39. package/dist/chunks/lit-html-m244w9nh.js +17 -0
  40. package/dist/chunks/media-picker-2qbetq73.js +33 -0
  41. package/dist/chunks/menu-q99sy9rf.js +285 -0
  42. package/dist/chunks/menu-rf7n72mf.js +14 -0
  43. package/dist/chunks/{monaco-binding-rj6xpx9y.js → monaco-binding-sw6en4dw.js} +12 -15
  44. package/dist/chunks/monaco-buffer-bhy3hb6m.js +24 -0
  45. package/dist/chunks/{monaco-setup-rws7k4br.js → monaco-setup-8r62fj6y.js} +27 -37
  46. package/dist/chunks/notify-628t0anh.js +21 -0
  47. package/dist/chunks/number-field-5cndyft8.js +72 -0
  48. package/dist/chunks/parse-0zx14y15.js +10 -0
  49. package/dist/chunks/platform-ts9cg1a4.js +15 -0
  50. package/dist/chunks/popover-47z3ytva.js +25 -0
  51. package/dist/chunks/publish-panel-98f7sr3g.js +27 -0
  52. package/dist/chunks/quick-search-srzz83k2.js +45 -0
  53. package/dist/chunks/references-n553f0e1.js +26 -0
  54. package/dist/chunks/right-panel-rrgx2dn3.js +45 -0
  55. package/dist/chunks/section-registry-5pq32t0q.js +25 -0
  56. package/dist/chunks/select-2r16ez41.js +49 -0
  57. package/dist/chunks/split-g7nm1rgb.js +191 -0
  58. package/dist/chunks/studio-0hqbpbd0.js +62 -0
  59. package/dist/chunks/studio-18avnhjp.js +2430 -0
  60. package/dist/chunks/{studio-kryvsw6j.js → studio-1k325pwf.js} +48 -48
  61. package/dist/chunks/{studio-hbtp0eh1.js → studio-25hkdrkw.js} +356 -85
  62. package/dist/chunks/studio-26d7myd1.js +258 -0
  63. package/dist/chunks/{studio-p8j86wag.js → studio-38emantv.js} +22 -5
  64. package/dist/chunks/studio-3gxv60tn.js +422 -0
  65. package/dist/chunks/{studio-8swrjbny.js → studio-3hxkd92t.js} +275 -163
  66. package/dist/chunks/studio-4m0haseg.js +185 -0
  67. package/dist/chunks/{studio-ynxwkrzd.js → studio-4wbb9ct0.js} +1953 -1785
  68. package/dist/chunks/{studio-gv8vvshc.js → studio-5523bpzt.js} +11 -11
  69. package/dist/chunks/{studio-5q69gfag.js → studio-57mf66w2.js} +1753 -2484
  70. package/dist/chunks/studio-5jy81t3s.js +17463 -0
  71. package/dist/chunks/studio-5rt7sqa0.js +1168 -0
  72. package/dist/chunks/studio-5te5tqng.js +81 -0
  73. package/dist/chunks/{studio-p4ngc046.js → studio-6ewqv390.js} +9 -9
  74. package/dist/chunks/{studio-9ab8gqsm.js → studio-6nzaktvy.js} +7 -7
  75. package/dist/chunks/studio-6xa21ej9.js +28 -0
  76. package/dist/chunks/{studio-5ehhackm.js → studio-727xh0e3.js} +19 -19
  77. package/dist/chunks/{studio-eenfkt4e.js → studio-79y5sz51.js} +52 -38
  78. package/dist/chunks/{studio-4q0wznzf.js → studio-7e11jpa9.js} +461 -145
  79. package/dist/chunks/{studio-q7v10cg2.js → studio-7r42c6ca.js} +17 -7
  80. package/dist/chunks/studio-7xptrg0j.js +954 -0
  81. package/dist/chunks/{studio-cft9r4r7.js → studio-81390n6f.js} +22 -22
  82. package/dist/chunks/{studio-q43a5y9h.js → studio-8jn5yhyn.js} +16 -16
  83. package/dist/chunks/studio-8qtrpzr3.js +839 -0
  84. package/dist/chunks/{studio-dg7zyt5x.js → studio-98b3z07n.js} +1 -1
  85. package/dist/chunks/studio-9xxz1q69.js +1356 -0
  86. package/dist/chunks/studio-akbww0j5.js +13615 -0
  87. package/dist/chunks/studio-arfpe389.js +195 -0
  88. package/dist/chunks/{studio-2zkz8evn.js → studio-az3zd49t.js} +473 -281
  89. package/dist/chunks/studio-cpvb2ed1.js +1171 -0
  90. package/dist/chunks/{studio-9r6j5ev1.js → studio-d0300636.js} +5078 -19
  91. package/dist/chunks/{studio-bgxt93ga.js → studio-d055c37e.js} +13 -13
  92. package/dist/chunks/studio-d1ja2x9r.js +362 -0
  93. package/dist/chunks/{studio-b9b6t1dg.js → studio-e1f24eff.js} +5664 -5666
  94. package/dist/chunks/{studio-4pqqxbrt.js → studio-em9nw8eh.js} +301 -112
  95. package/dist/chunks/studio-erdatzvp.js +1190 -0
  96. package/dist/chunks/{studio-g1wdche0.js → studio-esrxgwh6.js} +80 -80
  97. package/dist/chunks/studio-fabxk6r9.js +2811 -0
  98. package/dist/chunks/{studio-3n4ndrzh.js → studio-h4nvnjvz.js} +303 -303
  99. package/dist/chunks/studio-hhvs4sdg.js +195 -0
  100. package/dist/chunks/{studio-68n67094.js → studio-kcgff4r0.js} +15 -18
  101. package/dist/chunks/{studio-685s0ytq.js → studio-nqgawfks.js} +572 -572
  102. package/dist/chunks/{studio-xrapxwwc.js → studio-p1dw8wfw.js} +55 -29
  103. package/dist/chunks/studio-pm6hgy7r.js +4763 -0
  104. package/dist/chunks/studio-q0a3yk4n.js +33 -0
  105. package/dist/chunks/{studio-wcy8trfx.js → studio-q3x06vmj.js} +23 -22
  106. package/dist/chunks/studio-qahxvnan.js +111 -0
  107. package/dist/chunks/{studio-53n8bp04.js → studio-r6xmmtfh.js} +1 -1
  108. package/dist/chunks/{studio-kjjsjak4.js → studio-rkstz940.js} +3661 -6136
  109. package/dist/chunks/{studio-tvgyw9xw.js → studio-rnyf3f8e.js} +83 -250
  110. package/dist/chunks/studio-rpyah2ew.js +2805 -0
  111. package/dist/chunks/studio-rs76k67r.js +12 -0
  112. package/dist/chunks/{studio-yxrkyv8a.js → studio-s6z853aq.js} +12108 -11766
  113. package/dist/chunks/{studio-sf7zjgv1.js → studio-sgdc5pfc.js} +8 -8
  114. package/dist/chunks/studio-tb3ev01j.js +763 -0
  115. package/dist/chunks/{studio-t5wqwzdh.js → studio-tjjk7jvk.js} +1 -1
  116. package/dist/chunks/studio-vhv5tgxm.js +51 -0
  117. package/dist/chunks/{studio-htsf0w6m.js → studio-vz68yn9e.js} +31 -154
  118. package/dist/chunks/studio-wqpngpsk.js +174 -0
  119. package/dist/chunks/{studio-eesa1myv.js → studio-x3rty6tr.js} +58 -58
  120. package/dist/chunks/studio-xsykpvsh.js +8448 -0
  121. package/dist/chunks/{studio-92ab2jqe.js → studio-y9zedyjn.js} +14 -14
  122. package/dist/chunks/studio-yxa81f45.js +12 -0
  123. package/dist/chunks/swatch-group-t0n6h2rn.js +139 -0
  124. package/dist/chunks/tabs-wbp1cdws.js +156 -0
  125. package/dist/chunks/textfield-abk6qyee.js +73 -0
  126. package/dist/chunks/toast-780pvfbv.js +27 -0
  127. package/dist/chunks/toast-host-3sqv46sn.js +116 -0
  128. package/dist/chunks/toolbar-xbh5bhtr.js +194 -0
  129. package/dist/chunks/tooltip-eptb614w.js +369 -0
  130. package/dist/chunks/tree-a73p0pqm.js +367 -0
  131. package/dist/chunks/{tsMode-76kr53q0.js → tsMode-jy4m86rv.js} +83 -85
  132. package/dist/chunks/workspace-wrew8zk4.js +69 -0
  133. package/dist/chunks/{ws-client-6shc5rze.js → ws-client-ny4zc51r.js} +20 -22
  134. package/dist/iframe-entry.js +1812 -698
  135. package/dist/manifest.json +7 -0
  136. package/dist/studio.js +52900 -36573
  137. package/dist/workers/editor.worker.js +1040 -1085
  138. package/dist/workers/json.worker.js +1946 -1991
  139. package/dist/workers/ts.worker.js +4960 -5005
  140. package/favicon.ico +0 -0
  141. package/index.html +2 -0
  142. package/package.json +22 -55
  143. package/src/about/about-modal.ts +73 -123
  144. package/src/browse/library-commands.ts +20 -49
  145. package/src/browse/library-layouts.ts +87 -218
  146. package/src/browse/library-pane.ts +406 -438
  147. package/src/canvas/canvas-helpers.ts +0 -49
  148. package/src/canvas/canvas-live-render.ts +25 -2
  149. package/src/canvas/canvas-render.ts +666 -231
  150. package/src/canvas/canvas-surface.ts +0 -1
  151. package/src/canvas/canvas-utils.ts +310 -117
  152. package/src/canvas/dialog-path.ts +76 -0
  153. package/src/canvas/dialog-state.ts +67 -0
  154. package/src/canvas/diff-marks.ts +252 -0
  155. package/src/canvas/diff-toolbar.ts +349 -0
  156. package/src/canvas/diff-view.ts +123 -0
  157. package/src/canvas/edit-width-drag.ts +187 -0
  158. package/src/canvas/edit-width.ts +150 -0
  159. package/src/canvas/iframe-entry.ts +125 -8
  160. package/src/canvas/iframe-host.ts +271 -15
  161. package/src/canvas/iframe-interaction.ts +133 -13
  162. package/src/canvas/iframe-patch.ts +7 -1
  163. package/src/canvas/iframe-position.ts +5 -6
  164. package/src/canvas/iframe-protocol.ts +106 -5
  165. package/src/canvas/iframe-render.ts +373 -5
  166. package/src/canvas/iframe-slash.ts +2 -2
  167. package/src/canvas/iframe-subtree.ts +5 -8
  168. package/src/canvas/path-mapping.ts +25 -3
  169. package/src/canvas/popover-path.ts +106 -0
  170. package/src/canvas/popover-state.ts +155 -0
  171. package/src/canvas/surface-registry.ts +21 -10
  172. package/src/collab/presence-chips.ts +51 -73
  173. package/src/commands/app-commands.ts +10 -0
  174. package/src/commands/budget.ts +12 -0
  175. package/src/commands/command-args.ts +369 -6
  176. package/src/commands/defaults.ts +44 -14
  177. package/src/commands/live-context.ts +11 -5
  178. package/src/commands/reference.ts +12 -2
  179. package/src/commands/registry.ts +165 -8
  180. package/src/content/entry-commands.ts +16 -23
  181. package/src/content/entry-editor.ts +144 -101
  182. package/src/editor/canvas-slash-bridge.ts +2 -2
  183. package/src/editor/context-menu.ts +49 -200
  184. package/src/editor/convert-to-component.ts +42 -82
  185. package/src/editor/convert-to-repeater.ts +132 -173
  186. package/src/editor/inline-edit.ts +11 -0
  187. package/src/editor/shortcuts.ts +65 -26
  188. package/src/editor/slash-menu.ts +150 -157
  189. package/src/files/file-ops.ts +117 -11
  190. package/src/files/files.ts +949 -664
  191. package/src/files/json-layout.ts +474 -0
  192. package/src/files/media-paths.ts +13 -42
  193. package/src/files/media-usage.ts +41 -131
  194. package/src/files/serialize-document.ts +8 -1
  195. package/src/format/convert-file.ts +16 -10
  196. package/src/format/format-host.ts +50 -1
  197. package/src/github/github-auth.ts +71 -29
  198. package/src/github/github-publish.ts +57 -65
  199. package/src/grid/cell-editors.ts +46 -4
  200. package/src/grid/cell-popovers.ts +65 -62
  201. package/src/grid/grid-open.ts +86 -64
  202. package/src/grid/grid-panel.ts +390 -469
  203. package/src/grid/redirects-grid.ts +40 -44
  204. package/src/hosting/document.ts +2 -0
  205. package/src/hosting/layout.ts +10 -0
  206. package/src/i18n/i18n-commands.ts +127 -62
  207. package/src/media/media-pane.ts +83 -133
  208. package/src/new-project/add-repo-modal.ts +125 -176
  209. package/src/new-project/import-tab.ts +201 -250
  210. package/src/new-project/location-fields.ts +101 -165
  211. package/src/new-project/new-project-modal.ts +376 -367
  212. package/src/packages/jxsuite-update.ts +76 -18
  213. package/src/panels/activity-panel.ts +137 -104
  214. package/src/panels/ai-chat/chat-markdown.ts +15 -13
  215. package/src/panels/ai-chat/chat-view.ts +307 -472
  216. package/src/panels/ai-chat/composer.ts +175 -153
  217. package/src/panels/ai-chat/sessions-view.ts +31 -62
  218. package/src/panels/ai-panel.ts +210 -185
  219. package/src/panels/block-action-bar.ts +437 -643
  220. package/src/panels/bottom-dock.ts +112 -47
  221. package/src/panels/chat-panel.ts +19 -14
  222. package/src/panels/data-explorer.ts +141 -102
  223. package/src/panels/data-grid.ts +183 -150
  224. package/src/panels/dnd.ts +75 -20
  225. package/src/panels/editors.ts +26 -19
  226. package/src/panels/elements-panel.ts +243 -186
  227. package/src/panels/empty-state.ts +26 -42
  228. package/src/panels/events-panel.ts +950 -729
  229. package/src/panels/formula-workspace.ts +434 -207
  230. package/src/panels/frontmatter-fields.ts +185 -153
  231. package/src/panels/frontmatter-panel.ts +411 -187
  232. package/src/panels/git-diff-open.ts +148 -0
  233. package/src/panels/git-panel.ts +679 -605
  234. package/src/panels/head-panel.ts +617 -435
  235. package/src/panels/i18n-panel.ts +153 -133
  236. package/src/panels/imports-panel.ts +388 -414
  237. package/src/panels/jump-bar.ts +174 -147
  238. package/src/panels/layers-panel.ts +785 -671
  239. package/src/panels/left-panel.ts +166 -94
  240. package/src/panels/navigator-panels.ts +1 -1
  241. package/src/panels/pane-context.ts +529 -677
  242. package/src/panels/pane-grid.ts +265 -241
  243. package/src/panels/panel-registry.ts +15 -4
  244. package/src/panels/problems-panel.ts +177 -99
  245. package/src/panels/properties-panel.ts +1525 -966
  246. package/src/panels/provenance.ts +31 -113
  247. package/src/panels/quick-search.ts +217 -172
  248. package/src/panels/right-panel.ts +135 -169
  249. package/src/panels/seo-modal.ts +346 -167
  250. package/src/panels/settings-menu.ts +109 -646
  251. package/src/panels/settings-pane.ts +102 -86
  252. package/src/panels/signals-panel.ts +1318 -1172
  253. package/src/panels/statement-editor.ts +556 -428
  254. package/src/panels/style-panel.ts +1367 -630
  255. package/src/panels/style-utils.ts +36 -32
  256. package/src/panels/stylebook-doc.ts +30 -73
  257. package/src/panels/stylebook-layers-panel.ts +202 -109
  258. package/src/panels/stylebook-panel.ts +124 -88
  259. package/src/panels/tab-strip.ts +385 -287
  260. package/src/platforms/cloud.ts +347 -86
  261. package/src/platforms/devserver.ts +13 -0
  262. package/src/publish/deploy-checklist.ts +124 -78
  263. package/src/publish/publish-commands.ts +4 -12
  264. package/src/publish/publish-panel.ts +165 -299
  265. package/src/services/a11y-report.ts +100 -43
  266. package/src/services/ai-command-tools.ts +468 -0
  267. package/src/services/ai-import-tools.ts +50 -12
  268. package/src/services/ai-models.ts +108 -1
  269. package/src/services/ai-session-store.ts +6 -0
  270. package/src/services/ai-system-prompt.ts +110 -18
  271. package/src/services/ai-tools.ts +28 -143
  272. package/src/services/ai-write-report.ts +174 -0
  273. package/src/services/context-manager.ts +12 -1
  274. package/src/services/document-assistant.ts +50 -2
  275. package/src/services/idle.ts +16 -14
  276. package/src/services/live-surfaces.ts +237 -0
  277. package/src/services/model-uri.ts +75 -0
  278. package/src/services/notify.ts +63 -6
  279. package/src/services/popover-report.ts +325 -0
  280. package/src/services/surface-registry.ts +56 -0
  281. package/src/services/tool-executor.ts +17 -0
  282. package/src/settings/contexts-section.ts +301 -299
  283. package/src/settings/contributed-section.ts +361 -222
  284. package/src/settings/css-vars-editor.ts +320 -325
  285. package/src/settings/defs-editor.ts +572 -535
  286. package/src/settings/dependencies-editor.ts +79 -107
  287. package/src/settings/extension-commands.ts +379 -0
  288. package/src/settings/extension-rows.ts +113 -0
  289. package/src/settings/extensions-section.ts +287 -0
  290. package/src/settings/general-settings.ts +130 -181
  291. package/src/settings/head-editor.ts +10 -305
  292. package/src/settings/locales-section.ts +146 -196
  293. package/src/settings/preferences-accounts.ts +193 -12
  294. package/src/settings/preferences-dialog.ts +384 -307
  295. package/src/settings/project-sections.ts +108 -181
  296. package/src/settings/schema-field-ui.ts +11 -399
  297. package/src/settings/section-registry.ts +8 -1
  298. package/src/settings/settings-document.ts +31 -29
  299. package/src/shell/tree.ts +42 -87
  300. package/src/shell.ts +48 -36
  301. package/src/studio.ts +90 -67
  302. package/src/style/token-ref.ts +0 -39
  303. package/src/surfaces/about.json +263 -0
  304. package/src/surfaces/about.ts +163 -0
  305. package/src/surfaces/add-repo.json +486 -0
  306. package/src/surfaces/add-repo.ts +236 -0
  307. package/src/surfaces/ai-chat.json +2185 -0
  308. package/src/surfaces/ai-chat.ts +549 -0
  309. package/src/surfaces/ai-credentials-form.json +332 -0
  310. package/src/surfaces/ai-credentials-form.ts +151 -0
  311. package/src/surfaces/ai-managed-connect.json +108 -0
  312. package/src/surfaces/ai-managed-connect.ts +123 -0
  313. package/src/surfaces/ai-model-picker.json +114 -0
  314. package/src/surfaces/ai-model-picker.ts +139 -0
  315. package/src/surfaces/block-action-bar.json +640 -0
  316. package/src/surfaces/block-action-bar.ts +368 -0
  317. package/src/surfaces/bottom-dock.json +154 -0
  318. package/src/surfaces/bottom-dock.ts +125 -0
  319. package/src/surfaces/canvas-stage.json +444 -0
  320. package/src/surfaces/canvas-stage.ts +253 -0
  321. package/src/surfaces/cf-account-picker.json +300 -0
  322. package/src/surfaces/cf-account-picker.ts +214 -0
  323. package/src/surfaces/commandbar.json +984 -0
  324. package/src/{panels/toolbar.ts → surfaces/commandbar.ts} +414 -389
  325. package/src/surfaces/convert-repeater.json +309 -0
  326. package/src/surfaces/convert-repeater.ts +276 -0
  327. package/src/surfaces/data-actions.json +157 -0
  328. package/src/surfaces/data-actions.ts +115 -0
  329. package/src/surfaces/dialog.json +289 -0
  330. package/src/surfaces/dialog.ts +343 -0
  331. package/src/surfaces/diff-toolbar.json +266 -0
  332. package/src/surfaces/diff-toolbar.ts +139 -0
  333. package/src/surfaces/doc-header.json +739 -0
  334. package/src/surfaces/doc-header.ts +223 -0
  335. package/src/surfaces/empty-state.json +152 -0
  336. package/src/surfaces/empty-state.ts +173 -0
  337. package/src/surfaces/entry-editor.json +281 -0
  338. package/src/surfaces/entry-editor.ts +191 -0
  339. package/src/surfaces/expression-editor.json +977 -0
  340. package/src/surfaces/expression-editor.ts +266 -0
  341. package/src/surfaces/files-panel.json +592 -0
  342. package/src/surfaces/files-panel.ts +301 -0
  343. package/src/surfaces/formula-palette.json +364 -0
  344. package/src/surfaces/formula-palette.ts +340 -0
  345. package/src/surfaces/git-panel.json +1822 -0
  346. package/src/surfaces/git-panel.ts +272 -0
  347. package/src/surfaces/github-auth.json +112 -0
  348. package/src/surfaces/github-auth.ts +139 -0
  349. package/src/surfaces/github-publish.json +206 -0
  350. package/src/surfaces/github-publish.ts +169 -0
  351. package/src/surfaces/grid-cell.json +218 -0
  352. package/src/surfaces/grid-cell.ts +120 -0
  353. package/src/surfaces/grid-open.json +147 -0
  354. package/src/surfaces/grid-open.ts +87 -0
  355. package/src/surfaces/grid-panel.json +706 -0
  356. package/src/surfaces/grid-panel.ts +206 -0
  357. package/src/surfaces/grid-replace.json +179 -0
  358. package/src/surfaces/grid-replace.ts +89 -0
  359. package/src/surfaces/grid-views.json +446 -0
  360. package/src/surfaces/grid-views.ts +172 -0
  361. package/src/surfaces/inspector-dock.json +187 -0
  362. package/src/surfaces/inspector-dock.ts +161 -0
  363. package/src/surfaces/jump-bar.json +243 -0
  364. package/src/surfaces/jump-bar.ts +129 -0
  365. package/src/surfaces/jxsuite-update.json +146 -0
  366. package/src/surfaces/jxsuite-update.ts +149 -0
  367. package/src/surfaces/library-pane.json +1509 -0
  368. package/src/surfaces/library-pane.ts +363 -0
  369. package/src/surfaces/logic-panel.json +1949 -0
  370. package/src/surfaces/logic-panel.ts +369 -0
  371. package/src/surfaces/logic-workspace.json +685 -0
  372. package/src/surfaces/logic-workspace.ts +238 -0
  373. package/src/surfaces/media-browser.json +303 -0
  374. package/src/surfaces/media-browser.ts +145 -0
  375. package/src/surfaces/media-field.json +163 -0
  376. package/src/surfaces/media-field.ts +116 -0
  377. package/src/surfaces/media-pane.json +513 -0
  378. package/src/surfaces/media-pane.ts +197 -0
  379. package/src/surfaces/menu.json +232 -0
  380. package/src/surfaces/menu.ts +375 -0
  381. package/src/surfaces/navigator-dock.json +113 -0
  382. package/src/surfaces/navigator-dock.ts +130 -0
  383. package/src/surfaces/new-project.json +1654 -0
  384. package/src/surfaces/new-project.ts +411 -0
  385. package/src/surfaces/palette.json +535 -0
  386. package/src/surfaces/pane-context.json +1497 -0
  387. package/src/surfaces/pane-context.ts +513 -0
  388. package/src/surfaces/pane-grid.json +188 -0
  389. package/src/surfaces/pane-grid.ts +250 -0
  390. package/src/surfaces/panel-activity.json +563 -0
  391. package/src/surfaces/panel-activity.ts +187 -0
  392. package/src/surfaces/panel-data.json +141 -0
  393. package/src/surfaces/panel-data.ts +156 -0
  394. package/src/surfaces/panel-deploy-checklist.json +283 -0
  395. package/src/surfaces/panel-deploy-checklist.ts +175 -0
  396. package/src/surfaces/panel-elements.json +367 -0
  397. package/src/surfaces/panel-elements.ts +175 -0
  398. package/src/surfaces/panel-i18n.json +569 -0
  399. package/src/surfaces/panel-i18n.ts +212 -0
  400. package/src/surfaces/panel-imports.json +780 -0
  401. package/src/surfaces/panel-imports.ts +209 -0
  402. package/src/surfaces/panel-outline.json +734 -0
  403. package/src/surfaces/panel-outline.ts +326 -0
  404. package/src/surfaces/panel-page.json +1017 -0
  405. package/src/surfaces/panel-page.ts +240 -0
  406. package/src/surfaces/panel-problems.json +497 -0
  407. package/src/surfaces/panel-problems.ts +157 -0
  408. package/src/surfaces/panel-signals.json +1973 -0
  409. package/src/surfaces/panel-signals.ts +436 -0
  410. package/src/surfaces/panel-stylebook-layers.json +317 -0
  411. package/src/surfaces/panel-stylebook-layers.ts +155 -0
  412. package/src/surfaces/preferences.json +850 -0
  413. package/src/surfaces/preferences.ts +332 -0
  414. package/src/surfaces/progress-modal.json +110 -0
  415. package/src/surfaces/progress-modal.ts +150 -0
  416. package/src/surfaces/properties-panel.json +1578 -0
  417. package/src/surfaces/properties-panel.ts +337 -0
  418. package/src/surfaces/publish.json +945 -0
  419. package/src/surfaces/publish.ts +392 -0
  420. package/src/surfaces/push-plan.json +110 -0
  421. package/src/surfaces/push-plan.ts +87 -0
  422. package/src/surfaces/rail.json +255 -0
  423. package/src/surfaces/rail.ts +206 -0
  424. package/src/surfaces/reference-field.json +191 -0
  425. package/src/surfaces/reference-field.ts +168 -0
  426. package/src/surfaces/schema-builder.json +1011 -0
  427. package/src/surfaces/schema-builder.ts +181 -0
  428. package/src/surfaces/schema-form.json +1013 -0
  429. package/src/surfaces/schema-form.ts +287 -0
  430. package/src/surfaces/secret-field.json +60 -0
  431. package/src/surfaces/secret-field.ts +146 -0
  432. package/src/surfaces/seo.json +932 -0
  433. package/src/surfaces/seo.ts +334 -0
  434. package/src/surfaces/settings-contexts.json +538 -0
  435. package/src/surfaces/settings-contexts.ts +196 -0
  436. package/src/surfaces/settings-contributed.json +489 -0
  437. package/src/surfaces/settings-contributed.ts +199 -0
  438. package/src/surfaces/settings-css-vars.json +820 -0
  439. package/src/surfaces/settings-css-vars.ts +199 -0
  440. package/src/surfaces/settings-defs.json +1363 -0
  441. package/src/surfaces/settings-defs.ts +232 -0
  442. package/src/surfaces/settings-deploy.json +108 -0
  443. package/src/surfaces/settings-deploy.ts +135 -0
  444. package/src/surfaces/settings-extensions.json +486 -0
  445. package/src/surfaces/settings-extensions.ts +201 -0
  446. package/src/surfaces/settings-head.json +779 -0
  447. package/src/surfaces/settings-head.ts +397 -0
  448. package/src/surfaces/settings-locales.json +420 -0
  449. package/src/surfaces/settings-locales.ts +137 -0
  450. package/src/surfaces/settings-overview.json +423 -0
  451. package/src/surfaces/settings-overview.ts +218 -0
  452. package/src/surfaces/settings-packages.json +549 -0
  453. package/src/surfaces/settings-packages.ts +158 -0
  454. package/src/surfaces/settings-pane.json +147 -0
  455. package/src/surfaces/settings-pane.ts +150 -0
  456. package/src/surfaces/settings-rawjson.json +104 -0
  457. package/src/surfaces/settings-rawjson.ts +107 -0
  458. package/src/surfaces/shell.json +99 -0
  459. package/src/surfaces/shell.ts +30 -0
  460. package/src/surfaces/slash-menu.json +180 -0
  461. package/src/surfaces/slash-menu.ts +272 -0
  462. package/src/surfaces/statements.json +607 -0
  463. package/src/surfaces/statements.ts +238 -0
  464. package/src/surfaces/statusbar.json +94 -0
  465. package/src/{panels → surfaces}/statusbar.ts +163 -90
  466. package/src/surfaces/style-panel.json +1643 -0
  467. package/src/surfaces/style-panel.ts +338 -0
  468. package/src/surfaces/stylebook-chrome.json +122 -0
  469. package/src/surfaces/stylebook-chrome.ts +122 -0
  470. package/src/surfaces/tab-strip.json +448 -0
  471. package/src/surfaces/tab-strip.ts +200 -0
  472. package/src/surfaces/target-line.json +419 -0
  473. package/src/surfaces/target-line.ts +349 -0
  474. package/src/surfaces/toasts.json +189 -0
  475. package/src/surfaces/welcome.json +571 -0
  476. package/src/surfaces/welcome.ts +338 -0
  477. package/src/tabs/project-config.ts +71 -15
  478. package/src/tabs/tab.ts +52 -1
  479. package/src/types.ts +49 -3
  480. package/src/ui/ai-credentials-form.ts +104 -100
  481. package/src/ui/ai-managed-connect.ts +116 -49
  482. package/src/ui/ai-model-picker.ts +102 -47
  483. package/src/ui/cf-account-picker.ts +135 -0
  484. package/src/ui/color-selector.ts +80 -301
  485. package/src/ui/dynamic-slot.ts +43 -283
  486. package/src/ui/expression-editor.ts +797 -576
  487. package/src/ui/field-input.ts +8 -181
  488. package/src/ui/form-controls.ts +838 -366
  489. package/src/ui/formula-chips.ts +80 -85
  490. package/src/ui/kit.ts +33 -0
  491. package/src/ui/layers.ts +293 -593
  492. package/src/ui/media-picker.ts +203 -284
  493. package/src/ui/panel-resize.ts +251 -64
  494. package/src/ui/popover-surface.ts +153 -0
  495. package/src/ui/progress-modal.ts +22 -60
  496. package/src/ui/regions.ts +13 -7
  497. package/src/ui/schema-form.ts +906 -440
  498. package/src/ui/surface.ts +122 -0
  499. package/src/ui/unit-selector.ts +15 -131
  500. package/src/ui/value-source.ts +14 -0
  501. package/src/ui/virtual-window.ts +88 -9
  502. package/src/utils/canvas-media.ts +72 -0
  503. package/src/utils/edit-display.ts +7 -2
  504. package/src/utils/element-selectors.ts +69 -0
  505. package/src/utils/link-target.ts +2 -2
  506. package/src/utils/studio-utils.ts +0 -10
  507. package/src/view.ts +15 -0
  508. package/src/workspace/pane-derive.ts +36 -14
  509. package/src/workspace/workspace.ts +15 -6
  510. package/styles/canvas.css +72 -467
  511. package/styles/forced-colors.css +48 -82
  512. package/styles/forced-colors.json +41 -0
  513. package/styles/inspector.css +2 -1301
  514. package/styles/overlays.css +96 -2823
  515. package/styles/panels.css +6 -1423
  516. package/styles/shell-frame.css +272 -0
  517. package/styles/shell-frame.json +227 -0
  518. package/styles/shell.css +0 -1532
  519. package/styles/tokens.css +65 -136
  520. package/styles/tokens.json +84 -0
  521. package/dist/chunks/Overlay.dev-723bv0df.js +0 -23
  522. package/dist/chunks/adapter-jkcyhgtr.js +0 -14
  523. package/dist/chunks/cf-settings-45fgyx8y.js +0 -21
  524. package/dist/chunks/components-zmx10t62.js +0 -21
  525. package/dist/chunks/content-source-t88vb2dr.js +0 -49
  526. package/dist/chunks/convert-to-repeater-dhw1nrz9.js +0 -268
  527. package/dist/chunks/editor-cptq5jp1.js +0 -37
  528. package/dist/chunks/entry-commands-6z6bhxg0.js +0 -46
  529. package/dist/chunks/extension-sections-3ydgjbdy.js +0 -311
  530. package/dist/chunks/file-ops-rskmhxyq.js +0 -48
  531. package/dist/chunks/files-0cratsr0.js +0 -92
  532. package/dist/chunks/flow-qbfgrpeh.js +0 -634
  533. package/dist/chunks/focus-trap.esm-ggawf1hy.js +0 -1024
  534. package/dist/chunks/focus-visible-6eg70z18.js +0 -167
  535. package/dist/chunks/format-host-tgcmfs4p.js +0 -43
  536. package/dist/chunks/fs-events-hrrg328g.js +0 -21
  537. package/dist/chunks/git-panel-0yywyk76.js +0 -58
  538. package/dist/chunks/index-zqxmqxmh.js +0 -109
  539. package/dist/chunks/jx-validate-8q2g05x1.js +0 -19
  540. package/dist/chunks/layers-panel-z3tqe5cd.js +0 -67
  541. package/dist/chunks/layers-q75tj2mf.js +0 -45
  542. package/dist/chunks/lit-html-j055mkh8.js +0 -22
  543. package/dist/chunks/monaco-buffer-dqw0xxpx.js +0 -25
  544. package/dist/chunks/notify-yjm79n4s.js +0 -30
  545. package/dist/chunks/parse-24d0gay3.js +0 -19
  546. package/dist/chunks/platform-ba1j1gbh.js +0 -16
  547. package/dist/chunks/publish-panel-54jvhskq.js +0 -28
  548. package/dist/chunks/quick-search-cgfmjwq5.js +0 -65
  549. package/dist/chunks/references-3qx8fkyp.js +0 -29
  550. package/dist/chunks/right-panel-cqp8bc4v.js +0 -46
  551. package/dist/chunks/section-registry-sx1ykvkw.js +0 -30
  552. package/dist/chunks/sp-overlay.dev-yztmtrs1.js +0 -18
  553. package/dist/chunks/sp-popover.dev-n2etmqdp.js +0 -9
  554. package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +0 -15
  555. package/dist/chunks/sp-tooltip.dev-xcvpnqr0.js +0 -15
  556. package/dist/chunks/sp-tray.dev-tkbscm06.js +0 -199
  557. package/dist/chunks/studio-0a5qrh01.js +0 -3
  558. package/dist/chunks/studio-18j3bmcz.js +0 -14
  559. package/dist/chunks/studio-1hbqnt19.js +0 -1823
  560. package/dist/chunks/studio-4rzyrtjz.js +0 -426
  561. package/dist/chunks/studio-5rywrkxz.js +0 -1903
  562. package/dist/chunks/studio-7afd21nw.js +0 -67
  563. package/dist/chunks/studio-7nte2hj4.js +0 -613
  564. package/dist/chunks/studio-8jpzqk5s.js +0 -128
  565. package/dist/chunks/studio-8q517dfr.js +0 -24
  566. package/dist/chunks/studio-9c4k4vhv.js +0 -357
  567. package/dist/chunks/studio-9n4mrmnb.js +0 -184
  568. package/dist/chunks/studio-a0tswy0w.js +0 -58
  569. package/dist/chunks/studio-ahjfdy6e.js +0 -387
  570. package/dist/chunks/studio-ahnzd9rg.js +0 -23
  571. package/dist/chunks/studio-bw5dz6km.js +0 -608
  572. package/dist/chunks/studio-cb12yz9w.js +0 -15055
  573. package/dist/chunks/studio-d1ks410h.js +0 -175
  574. package/dist/chunks/studio-eqq1w638.js +0 -113
  575. package/dist/chunks/studio-ex8zyp98.js +0 -1813
  576. package/dist/chunks/studio-h4rxscfc.js +0 -10
  577. package/dist/chunks/studio-kyh9avvv.js +0 -28
  578. package/dist/chunks/studio-kyk2v92a.js +0 -659
  579. package/dist/chunks/studio-m3hwn7y1.js +0 -120
  580. package/dist/chunks/studio-mmv72a27.js +0 -4344
  581. package/dist/chunks/studio-n8b9wqwy.js +0 -291
  582. package/dist/chunks/studio-pdfjntbk.js +0 -6939
  583. package/dist/chunks/studio-qq6jym04.js +0 -53
  584. package/dist/chunks/studio-r1r7afxx.js +0 -1066
  585. package/dist/chunks/studio-rdx8jcxy.js +0 -83
  586. package/dist/chunks/studio-rn9ta2mz.js +0 -111
  587. package/dist/chunks/studio-t7qb9db8.js +0 -665
  588. package/dist/chunks/studio-tmc5emrw.js +0 -8
  589. package/dist/chunks/studio-txwkm6p9.js +0 -12
  590. package/dist/chunks/studio-v38hmvx1.js +0 -390
  591. package/dist/chunks/studio-vadzgkrz.js +0 -318
  592. package/dist/chunks/studio-wx5609xd.js +0 -341
  593. package/dist/chunks/studio-z6nw2ek2.js +0 -18
  594. package/dist/chunks/studio-zczps70q.js +0 -378
  595. package/dist/chunks/workspace-xpres40v.js +0 -89
  596. package/src/panels/activity-bar.ts +0 -251
  597. package/src/panels/panel-scheduler.ts +0 -219
  598. package/src/panels/style-inputs.ts +0 -237
  599. package/src/panels/target-line.ts +0 -274
  600. package/src/panels/welcome-screen.ts +0 -346
  601. package/src/ui/button-group.ts +0 -102
  602. package/src/ui/choice-field.ts +0 -72
  603. package/src/ui/field-row.ts +0 -249
  604. package/src/ui/formula-palette.ts +0 -213
  605. package/src/ui/icons.ts +0 -52
  606. package/src/ui/jx-theme.ts +0 -130
  607. package/src/ui/spectrum.ts +0 -340
  608. package/src/ui/value-selector.ts +0 -174
  609. package/src/ui/widgets.ts +0 -172
  610. /package/dist/chunks/{javascript-s56a8dpk.css → javascript-4abzjcy9.css} +0 -0
  611. /package/dist/chunks/{jsonMode-zr9sw0c7.css → jsonMode-vvmpmk34.css} +0 -0
  612. /package/dist/chunks/{monaco-setup-pav68602.css → monaco-setup-8vbyqnhr.css} +0 -0
  613. /package/dist/chunks/{tsMode-ef187txx.css → tsMode-hnxxb73h.css} +0 -0
@@ -1,1399 +1,28 @@
1
1
  import {
2
- BLESSED_GLOBALS,
3
- BLESSED_OPERATORS,
4
- PURE_METHOD_OPS,
5
- intlHelpersClause,
6
- rectOf,
7
- renderEmptyState
8
- } from "./studio-kjjsjak4.js";
2
+ intlHelpersClause
3
+ } from "./studio-5jy81t3s.js";
9
4
  import {
10
- getLayerSlot
11
- } from "./studio-t7qb9db8.js";
12
- import {
13
- LANGUAGE_TAG_PATTERN,
14
- activeTab
15
- } from "./studio-2zkz8evn.js";
16
- import {
17
- classMap,
18
- ifDefined,
19
- styleMap
20
- } from "./studio-m3hwn7y1.js";
21
- import {
22
- live,
23
- ref
24
- } from "./studio-4rzyrtjz.js";
25
- import {
26
- html,
27
- nothing,
28
- render
29
- } from "./studio-kryvsw6j.js";
30
- import {
31
- cloneValue
32
- } from "./studio-5ehhackm.js";
33
- import {
34
- isFunctionDef,
35
- isJsonObject,
36
- isNamedFormulaDef,
37
- isRef,
38
- isTemplateString
39
- } from "./studio-q7v10cg2.js";
40
-
41
- // src/panels/provenance.ts
42
- var PROVENANCE_TITLES = {
43
- bound: "Bound",
44
- default: "Not set",
45
- inherited: "Inherited",
46
- mixed: "Mixed",
47
- set: "Set here"
48
- };
49
- function provenanceText(p) {
50
- if (p.state === "inherited") {
51
- return p.donor ? `from ${p.donor}` : "inherited";
52
- }
53
- if (p.state === "bound") {
54
- return p.donor ?? "bound";
55
- }
56
- if (p.state === "mixed") {
57
- return p.donor ? `mixed (${p.donor})` : "mixed";
58
- }
59
- return "";
60
- }
61
- function provenanceTitle(prop, p) {
62
- if (p.title) {
63
- return p.title;
64
- }
65
- const lead = PROVENANCE_TITLES[p.state];
66
- if (p.state === "inherited") {
67
- return `${lead} from ${p.donor ?? "the cascade"} — click to go there`;
68
- }
69
- if (p.state === "bound") {
70
- return p.donor ? `${lead} to ${p.donor} — click to open it` : lead;
71
- }
72
- if (p.state === "set") {
73
- return `${lead} — click to clear ${prop}`;
74
- }
75
- if (p.state === "mixed") {
76
- const count = p.donor ? `${p.donor} selected elements` : "the selected elements";
77
- return `${lead} — ${count} have different values for ${prop}; typing sets them all`;
78
- }
79
- return `${lead} — ${prop} falls back to the browser default`;
80
- }
81
- function renderProvenanceChip(prop, p) {
82
- if (p.state === "default") {
83
- return nothing;
84
- }
85
- const text = provenanceText(p);
86
- const title = provenanceTitle(prop, p);
87
- const isDot = p.state === "set";
88
- const classes = classMap({
89
- "provenance-chip": true,
90
- [`provenance-chip--${p.state}`]: true,
91
- "set-dot": isDot
92
- });
93
- const { onClick } = p;
94
- if (!onClick) {
95
- return html`<span class=${classes} title=${title}>${text}</span>`;
96
- }
97
- const activate = (e) => {
98
- e.stopPropagation();
99
- e.preventDefault();
100
- onClick();
101
- };
102
- return isDot ? html`<span class=${classes} title=${title} @click=${activate}>${text}</span>` : html`<button type="button" class=${classes} title=${title} @click=${activate}>
103
- ${text}
104
- </button>`;
105
- }
106
- function countProvenance(states) {
107
- const counts = { bound: 0, inherited: 0, mixed: 0, set: 0 };
108
- for (const state of states) {
109
- if (state !== "default") {
110
- counts[state] += 1;
111
- }
112
- }
113
- return counts;
114
- }
115
- function hasProvenance(counts) {
116
- return counts.set > 0 || counts.inherited > 0 || counts.bound > 0 || counts.mixed > 0;
117
- }
118
- function provenanceSummaryText(counts) {
119
- const parts = [];
120
- if (counts.set > 0) {
121
- parts.push(`${counts.set} set here`);
122
- }
123
- if (counts.inherited > 0) {
124
- parts.push(`${counts.inherited} inherited`);
125
- }
126
- if (counts.bound > 0) {
127
- parts.push(`${counts.bound} bound`);
128
- }
129
- if (counts.mixed > 0) {
130
- parts.push(`${counts.mixed} mixed`);
131
- }
132
- return parts.length > 0 ? parts.join(" · ") : "nothing set";
133
- }
134
- function renderProvenanceDots(counts, opts = {}) {
135
- if (!hasProvenance(counts)) {
136
- return nothing;
137
- }
138
- const summary = provenanceSummaryText(counts);
139
- return html`
140
- <span class="provenance-dots" title=${summary} aria-label=${summary}>
141
- ${counts.set > 0 ? renderProvenanceChip("section", {
142
- state: "set",
143
- title: opts.clearTitle ?? summary,
144
- ...opts.onClearSet ? { onClick: opts.onClearSet } : {}
145
- }) : nothing}
146
- ${counts.inherited > 0 ? html`<span
147
- class="provenance-chip provenance-chip--inherited set-dot"
148
- title=${summary}
149
- ></span>` : nothing}
150
- ${counts.bound > 0 ? html`<span
151
- class="provenance-chip provenance-chip--bound set-dot"
152
- title=${summary}
153
- ></span>` : nothing}
154
- ${counts.mixed > 0 ? html`<span
155
- class="provenance-chip provenance-chip--mixed set-dot"
156
- title=${summary}
157
- ></span>` : nothing}
158
- </span>
159
- `;
160
- }
161
-
162
- // src/ui/field-row.ts
163
- function renderFieldRow({
164
- prop,
165
- label,
166
- hasValue,
167
- onClear,
168
- widget,
169
- labelExtra,
170
- span,
171
- warning,
172
- provenance,
173
- error,
174
- errorCount
175
- }) {
176
- const invalid = Boolean(error);
177
- const chip = provenance ?? (hasValue && onClear ? { onClick: onClear, state: "set", title: `Clear ${prop}` } : undefined);
178
- return html`
179
- <div
180
- class=${classMap({
181
- "style-row": true,
182
- "style-row--invalid": invalid,
183
- "style-row--warning": Boolean(warning) && !invalid
184
- })}
185
- data-prop=${prop}
186
- style=${span === 2 ? "grid-column: 1 / -1" : ""}
187
- >
188
- <div class="style-row-label">
189
- ${chip ? renderProvenanceChip(prop, chip) : nothing}
190
- <sp-field-label size="s" title=${prop}>${label}</sp-field-label>
191
- ${labelExtra ?? nothing}
192
- </div>
193
- ${widget}
194
- ${invalid ? html`<p class="style-row-error" role="alert">
195
- ${error}${errorCount !== undefined && errorCount > 1 ? html`<span class="style-row-error-count">×${errorCount}</span>` : nothing}
196
- </p>` : nothing}
197
- </div>
198
- `;
199
- }
200
- function renderKvRow({
201
- name,
202
- value,
203
- onCommit,
204
- onDelete,
205
- placeholderFor,
206
- debounceMs = 400
207
- }) {
208
- let debounce;
209
- let currentName = name;
210
- let currentValue = value;
211
- const commit = () => {
212
- clearTimeout(debounce);
213
- debounce = setTimeout(() => onCommit(currentName, currentValue), debounceMs);
214
- };
215
- return html`
216
- <div class="kv-row" data-prop=${name}>
217
- <sp-textfield
218
- size="s"
219
- class="kv-key"
220
- .value=${name}
221
- @input=${(e) => {
222
- currentName = e.target.value;
223
- commit();
224
- }}
225
- @change=${placeholderFor ? (e) => {
226
- const cell = e.target.closest(".kv-row")?.querySelector(".kv-val");
227
- cell?.setAttribute("placeholder", placeholderFor(e.target.value));
228
- } : nothing}
229
- ></sp-textfield>
230
- <sp-textfield
231
- size="s"
232
- class="kv-val"
233
- .value=${value}
234
- placeholder=${placeholderFor ? placeholderFor(name) : ""}
235
- @input=${(e) => {
236
- currentValue = e.target.value;
237
- commit();
238
- }}
239
- ></sp-textfield>
240
- <sp-action-button size="xs" quiet title="Remove ${name}" @click=${onDelete}>
241
- <sp-icon-close slot="icon"></sp-icon-close>
242
- </sp-action-button>
243
- </div>
244
- `;
245
- }
246
- function renderStaticKvRow({ name, detail, value, tags }) {
247
- return html`
248
- <div class="kv-static-row" data-prop=${name}>
249
- <code class="kv-static-name">${name}</code>
250
- ${detail ? html`<span class="kv-static-detail">${detail}</span>` : nothing}
251
- ${value ? html`<span class="kv-static-value">${value}</span>` : nothing}
252
- ${(tags ?? []).map((tag) => html`<span class="kv-static-tag">${tag}</span>`)}
253
- </div>
254
- `;
255
- }
256
-
257
- // src/ui/formula-chips.ts
258
- function isExprNode(value) {
259
- return isJsonObject(value) && typeof value.operator === "string";
260
- }
261
- function refLabel(ref2) {
262
- if (ref2.startsWith("#/state/")) {
263
- return ref2.slice("#/state/".length);
264
- }
265
- if (ref2.startsWith("window#/")) {
266
- return ref2.slice("window#/".length).replaceAll("/", ".");
267
- }
268
- return ref2 || "?";
269
- }
270
- function operandLabel(value) {
271
- if (isRef(value)) {
272
- return refLabel(value.$ref);
273
- }
274
- if (isExprNode(value)) {
275
- return `(${chipSummary(value)})`;
276
- }
277
- if (typeof value === "string") {
278
- return JSON.stringify(value);
279
- }
280
- return String(value);
281
- }
282
- function targetPath(basePath, hops) {
283
- return [...basePath, ...Array.from({ length: hops }, () => "target")];
284
- }
285
- function unrollChain(node, basePath) {
286
- const nodes = [];
287
- let current = node;
288
- for (;; ) {
289
- nodes.push(current);
290
- const next = current.target;
291
- if (isExprNode(next)) {
292
- current = next;
293
- } else {
294
- break;
295
- }
296
- }
297
- const deepest = nodes.at(-1);
298
- const head = "target" in deepest || deepest.target !== undefined ? { path: targetPath(basePath, nodes.length), value: deepest.target } : null;
299
- const rootFirst = nodes.map((n, i) => ({ node: n, path: targetPath(basePath, i) }));
300
- return { head, links: rootFirst.toReversed() };
301
- }
302
- function chipSummary(node) {
303
- if (!isExprNode(node)) {
304
- return operandLabel(node);
305
- }
306
- const { head, links } = unrollChain(node, []);
307
- const parts = [];
308
- if (head) {
309
- parts.push(operandLabel(head.value));
310
- }
311
- for (const link of links) {
312
- parts.push(String(link.node.operator));
313
- }
314
- return parts.join(" › ");
315
- }
316
- var CHIP_STYLE = "display:inline-flex;align-items:center;gap:4px;max-width:180px;padding:1px 7px;" + "border:1px solid var(--spectrum-gray-300, #3c3c3c);border-radius:10px;cursor:pointer;" + "background:var(--spectrum-gray-100, #232323);color:var(--spectrum-gray-800, #d0d0d0);" + "font-size:11px;font-family:var(--spectrum-code-font-family, monospace);line-height:18px";
317
- function renderChipBadge(preview, pathKey) {
318
- const text = preview?.values.get(pathKey);
319
- if (text === undefined) {
320
- return nothing;
321
- }
322
- return html`
323
- <span
324
- class="expr-live-badge"
325
- title=${text}
326
- style="font-family:var(--spectrum-code-font-family, monospace);font-size:10px;line-height:16px;padding:0 5px;border-radius:4px;background:var(--spectrum-gray-200, #323232);color:var(--spectrum-seafoam-900, #35a690);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:120px;flex-shrink:1"
327
- >${text}</span
328
- >
329
- `;
330
- }
331
- function renderChip(label, path, onSelect, preview, group = false) {
332
- return html`
333
- <button
334
- type="button"
335
- class=${group ? "formula-chip formula-chip--group" : "formula-chip"}
336
- data-path=${path.join("/")}
337
- style=${CHIP_STYLE}
338
- title=${label}
339
- @click=${() => onSelect(path)}
340
- >
341
- <span style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:110px"
342
- >${label}</span
343
- >
344
- ${renderChipBadge(preview, path.join("/"))}
345
- </button>
346
- `;
347
- }
348
- function groupChips(link, onSelect, preview) {
349
- const chips = [];
350
- const add = (operand, path) => {
351
- if (isExprNode(operand)) {
352
- chips.push(renderChip(`(${chipSummary(operand)})`, path, onSelect, preview, true));
353
- }
354
- };
355
- const { value, initial, cases } = link.node;
356
- if (Array.isArray(value)) {
357
- for (const [i, item] of value.entries()) {
358
- add(item, [...link.path, "value", i]);
359
- }
360
- } else {
361
- add(value, [...link.path, "value"]);
362
- }
363
- add(initial, [...link.path, "initial"]);
364
- if (isJsonObject(cases)) {
365
- for (const [key, operand] of Object.entries(cases)) {
366
- add(operand, [...link.path, "cases", key]);
367
- }
368
- }
369
- add(link.node.default, [...link.path, "default"]);
370
- return chips;
371
- }
372
- function renderFormulaChips(node, onSelect, opts = {}) {
373
- if (!isExprNode(node)) {
374
- return html`${nothing}`;
375
- }
376
- const preview = opts.preview ?? null;
377
- const basePath = opts.path ?? [];
378
- const { head, links } = unrollChain(node, basePath);
379
- const chips = [];
380
- if (head) {
381
- chips.push(renderChip(operandLabel(head.value), head.path, onSelect, preview));
382
- }
383
- for (const link of links) {
384
- chips.push(renderChip(String(link.node.operator), link.path, onSelect, preview), ...groupChips(link, onSelect, preview));
385
- }
386
- return html`
387
- <div
388
- class="formula-chips"
389
- style="display:flex;flex-wrap:wrap;gap:4px;align-items:center;padding:2px 0 6px"
390
- >
391
- ${chips}
392
- </div>
393
- `;
394
- }
395
-
396
- // ../formulas/src/catalog.ts
397
- var catalog = [
398
- {
399
- description: "Arithmetic mean of an array of numbers; 0 for an empty array.",
400
- expression: {
401
- operator: "?:",
402
- target: {
403
- $ref: "$args/values/length"
404
- },
405
- value: {
406
- operator: "/",
407
- target: {
408
- operator: "reduce",
409
- target: {
410
- $ref: "$args/values"
411
- },
412
- initial: 0,
413
- value: {
414
- operator: "+",
415
- target: {
416
- $ref: "$reduce/acc"
417
- },
418
- value: {
419
- $ref: "$map/item"
420
- }
421
- }
422
- },
423
- value: {
424
- $ref: "$args/values/length"
425
- }
426
- },
427
- initial: 0
428
- },
429
- name: "average",
430
- parameters: [
431
- {
432
- name: "values",
433
- type: {
434
- text: "number[]"
435
- }
436
- }
437
- ]
438
- },
439
- {
440
- description: "Uppercase the first character of a string, leaving the rest unchanged (the CSS text-transform: capitalize shape for a single word).",
441
- expression: {
442
- operator: "+",
443
- target: {
444
- operator: "toUpperCase",
445
- target: {
446
- operator: "charAt",
447
- target: {
448
- $ref: "$args/text"
449
- },
450
- value: 0
451
- }
452
- },
453
- value: {
454
- operator: "slice",
455
- target: {
456
- $ref: "$args/text"
457
- },
458
- value: 1
459
- }
460
- },
461
- name: "capitalize",
462
- parameters: [
463
- {
464
- name: "text",
465
- type: {
466
- text: "string"
467
- }
468
- }
469
- ]
470
- },
471
- {
472
- description: "Bound a number to the [min, max] range (the TC39 Math.clamp shape): returns min when below, max when above, the value itself otherwise.",
473
- expression: {
474
- operator: "call",
475
- target: {
476
- $ref: "window#/Math/min"
477
- },
478
- value: [
479
- {
480
- operator: "call",
481
- target: {
482
- $ref: "window#/Math/max"
483
- },
484
- value: [
485
- {
486
- $ref: "$args/value"
487
- },
488
- {
489
- $ref: "$args/min"
490
- }
491
- ]
492
- },
493
- {
494
- $ref: "$args/max"
495
- }
496
- ]
497
- },
498
- name: "clamp",
499
- parameters: [
500
- {
501
- name: "value",
502
- type: {
503
- text: "number"
504
- }
505
- },
506
- {
507
- name: "min",
508
- type: {
509
- text: "number"
510
- }
511
- },
512
- {
513
- name: "max",
514
- type: {
515
- text: "number"
516
- }
517
- }
518
- ]
519
- },
520
- {
521
- description: "A copy of an array with the falsy elements removed (Array.prototype.filter on truthiness).",
522
- expression: {
523
- operator: "filter",
524
- target: {
525
- $ref: "$args/values"
526
- },
527
- value: {
528
- operator: "!",
529
- target: {
530
- operator: "!",
531
- target: {
532
- $ref: "$map/item"
533
- }
534
- }
535
- }
536
- },
537
- name: "compact",
538
- parameters: [
539
- {
540
- name: "values",
541
- type: {
542
- text: "unknown[]"
543
- }
544
- }
545
- ]
546
- },
547
- {
548
- description: "The length of an array or string, or 0 when the value is missing.",
549
- expression: {
550
- operator: "??",
551
- target: {
552
- $ref: "$args/values/length"
553
- },
554
- value: 0
555
- },
556
- name: "count",
557
- parameters: [
558
- {
559
- name: "values",
560
- type: {
561
- text: "unknown[] | string"
562
- }
563
- }
564
- ]
565
- },
566
- {
567
- description: "The first element of an array (index 0), or null when empty.",
568
- expression: {
569
- operator: "??",
570
- target: {
571
- $ref: "$args/values/0"
572
- },
573
- value: null
574
- },
575
- name: "first",
576
- parameters: [
577
- {
578
- name: "values",
579
- type: {
580
- text: "unknown[]"
581
- }
582
- }
583
- ]
584
- },
585
- {
586
- description: 'The uppercase first letters of each whitespace-separated word, joined (e.g. "ada lovelace" → "AL").',
587
- expression: {
588
- operator: "join",
589
- target: {
590
- operator: "map",
591
- target: {
592
- operator: "split",
593
- target: {
594
- $ref: "$args/name"
595
- },
596
- value: " "
597
- },
598
- value: {
599
- operator: "toUpperCase",
600
- target: {
601
- operator: "charAt",
602
- target: {
603
- $ref: "$map/item"
604
- },
605
- value: 0
606
- }
607
- }
608
- },
609
- value: ""
610
- },
611
- name: "initials",
612
- parameters: [
613
- {
614
- name: "name",
615
- type: {
616
- text: "string"
617
- }
618
- }
619
- ]
620
- },
621
- {
622
- description: "True when an array or string has no elements (length 0 — a missing value counts as empty).",
623
- expression: {
624
- operator: "===",
625
- target: {
626
- operator: "??",
627
- target: {
628
- $ref: "$args/value/length"
629
- },
630
- value: 0
631
- },
632
- value: 0
633
- },
634
- name: "isEmpty",
635
- parameters: [
636
- {
637
- name: "value",
638
- type: {
639
- text: "unknown[] | string"
640
- }
641
- }
642
- ]
643
- },
644
- {
645
- description: "The last element of an array (Array.prototype.at(-1)), or undefined when empty.",
646
- expression: {
647
- operator: "at",
648
- target: {
649
- $ref: "$args/values"
650
- },
651
- value: -1
652
- },
653
- name: "last",
654
- parameters: [
655
- {
656
- name: "values",
657
- type: {
658
- text: "unknown[]"
659
- }
660
- }
661
- ]
662
- },
663
- {
664
- description: "The largest number in an array (Math.max folded over the values); undefined for an empty array.",
665
- expression: {
666
- operator: "reduce",
667
- target: {
668
- $ref: "$args/values"
669
- },
670
- initial: {
671
- $ref: "$args/values/0"
672
- },
673
- value: {
674
- operator: "call",
675
- target: {
676
- $ref: "window#/Math/max"
677
- },
678
- value: [
679
- {
680
- $ref: "$reduce/acc"
681
- },
682
- {
683
- $ref: "$map/item"
684
- }
685
- ]
686
- }
687
- },
688
- name: "max",
689
- parameters: [
690
- {
691
- name: "values",
692
- type: {
693
- text: "number[]"
694
- }
695
- }
696
- ]
697
- },
698
- {
699
- description: "The smallest number in an array (Math.min folded over the values); undefined for an empty array.",
700
- expression: {
701
- operator: "reduce",
702
- target: {
703
- $ref: "$args/values"
704
- },
705
- initial: {
706
- $ref: "$args/values/0"
707
- },
708
- value: {
709
- operator: "call",
710
- target: {
711
- $ref: "window#/Math/min"
712
- },
713
- value: [
714
- {
715
- $ref: "$reduce/acc"
716
- },
717
- {
718
- $ref: "$map/item"
719
- }
720
- ]
721
- }
722
- },
723
- name: "min",
724
- parameters: [
725
- {
726
- name: "values",
727
- type: {
728
- text: "number[]"
729
- }
730
- }
731
- ]
732
- },
733
- {
734
- description: "part / whole as a percentage (0 when the whole is 0 or missing).",
735
- expression: {
736
- operator: "?:",
737
- target: {
738
- $ref: "$args/whole"
739
- },
740
- value: {
741
- operator: "*",
742
- target: {
743
- operator: "/",
744
- target: {
745
- $ref: "$args/part"
746
- },
747
- value: {
748
- $ref: "$args/whole"
749
- }
750
- },
751
- value: 100
752
- },
753
- initial: 0
754
- },
755
- name: "percent",
756
- parameters: [
757
- {
758
- name: "part",
759
- type: {
760
- text: "number"
761
- }
762
- },
763
- {
764
- name: "whole",
765
- type: {
766
- text: "number"
767
- }
768
- }
769
- ]
770
- },
771
- {
772
- description: "Round a number to the given decimal places (Math.round against a Math.pow(10, digits) scale).",
773
- expression: {
774
- operator: "/",
775
- target: {
776
- operator: "call",
777
- target: {
778
- $ref: "window#/Math/round"
779
- },
780
- value: [
781
- {
782
- operator: "*",
783
- target: {
784
- $ref: "$args/value"
785
- },
786
- value: {
787
- operator: "call",
788
- target: {
789
- $ref: "window#/Math/pow"
790
- },
791
- value: [
792
- 10,
793
- {
794
- $ref: "$args/digits"
795
- }
796
- ]
797
- }
798
- }
799
- ]
800
- },
801
- value: {
802
- operator: "call",
803
- target: {
804
- $ref: "window#/Math/pow"
805
- },
806
- value: [
807
- 10,
808
- {
809
- $ref: "$args/digits"
810
- }
811
- ]
812
- }
813
- },
814
- name: "roundTo",
815
- parameters: [
816
- {
817
- name: "value",
818
- type: {
819
- text: "number"
820
- }
821
- },
822
- {
823
- name: "digits",
824
- type: {
825
- text: "number"
826
- },
827
- default: 0
828
- }
829
- ]
830
- },
831
- {
832
- description: "Add every number in an array (Array.prototype.reduce with +, seeded at 0).",
833
- expression: {
834
- operator: "reduce",
835
- target: {
836
- $ref: "$args/values"
837
- },
838
- initial: 0,
839
- value: {
840
- operator: "+",
841
- target: {
842
- $ref: "$reduce/acc"
843
- },
844
- value: {
845
- $ref: "$map/item"
846
- }
847
- }
848
- },
849
- name: "sum",
850
- parameters: [
851
- {
852
- name: "values",
853
- type: {
854
- text: "number[]"
855
- }
856
- }
857
- ]
858
- },
859
- {
860
- description: "Shorten a string to at most the given length, appending a horizontal ellipsis when cut.",
861
- expression: {
862
- operator: "?:",
863
- target: {
864
- operator: ">",
865
- target: {
866
- $ref: "$args/text/length"
867
- },
868
- value: {
869
- $ref: "$args/length"
870
- }
871
- },
872
- value: {
873
- operator: "+",
874
- target: {
875
- operator: "slice",
876
- target: {
877
- $ref: "$args/text"
878
- },
879
- value: [
880
- 0,
881
- {
882
- $ref: "$args/length"
883
- }
884
- ]
885
- },
886
- value: "…"
887
- },
888
- initial: {
889
- $ref: "$args/text"
890
- }
891
- },
892
- name: "truncate",
893
- parameters: [
894
- {
895
- name: "text",
896
- type: {
897
- text: "string"
898
- }
899
- },
900
- {
901
- name: "length",
902
- type: {
903
- text: "number"
904
- }
905
- }
906
- ]
907
- }
908
- ];
909
- // src/ui/formula-catalog.ts
910
- function applyCatalogPick(entry, onChange, opts) {
911
- if (entry.ensure && !opts?.stateEntries?.[entry.ensure.name]) {
912
- opts?.onInsertDef?.(entry.ensure.name, entry.ensure.def);
913
- }
914
- onChange(entry.insert());
915
- }
916
- var REF_TARGET = { description: "Writable pointer the operation applies to", name: "target" };
917
- var LEFT = { description: "Left operand", name: "target" };
918
- var RIGHT = { description: "Right operand", name: "value" };
919
- function assignMeta(op, verb) {
920
- return {
921
- description: `${verb} assignment operator (ECMA \`${op}\`): stores the result at the target pointer.`,
922
- group: "Assignment",
923
- insert: () => ({ operator: op, target: { $ref: "" }, value: null }),
924
- parameters: [REF_TARGET, { description: "Value assigned to the target", name: "value" }]
925
- };
926
- }
927
- function binaryMeta(group, op, description) {
928
- return {
929
- description,
930
- group,
931
- insert: () => ({ operator: op, target: null, value: null }),
932
- parameters: [LEFT, RIGHT]
933
- };
934
- }
935
- function arrayMeta(op, description, withValue) {
936
- return {
937
- description,
938
- group: "Array methods",
939
- insert: () => withValue ? { operator: op, target: { $ref: "" }, value: null } : { operator: op, target: { $ref: "" } },
940
- parameters: withValue ? [REF_TARGET, { description: "Element to add", name: "value" }] : [REF_TARGET]
941
- };
942
- }
943
- function aggregateMeta(op, description, withInitial) {
944
- const node = {
945
- operator: op,
946
- target: { $ref: "" },
947
- value: { operator: "!", target: null }
948
- };
949
- return {
950
- description,
951
- group: "Aggregate",
952
- insert: () => withInitial ? { ...node, initial: 0 } : { ...node },
953
- parameters: [
954
- { description: "Array pointer to iterate", name: "target" },
955
- { description: "Per-item expression ($map/item, $map/index)", name: "value" },
956
- ...withInitial ? [{ description: "Initial accumulator ($reduce/acc)", name: "initial" }] : []
957
- ]
958
- };
959
- }
960
- var OPERATOR_META = {
961
- "!": {
962
- description: "Logical NOT operator (ECMA `!`): coerces the operand to boolean and negates it.",
963
- group: "Unary",
964
- insert: () => ({ operator: "!", target: null }),
965
- parameters: [{ description: "Operand to negate", name: "target" }]
966
- },
967
- "!==": binaryMeta("Comparison", "!==", "Strict inequality operator (ECMA `!==`): true when the operands differ without coercion."),
968
- "%": binaryMeta("Arithmetic", "%", "Remainder operator (ECMA `%`): remainder of dividing the left operand by the right."),
969
- "&&": binaryMeta("Logical", "&&", "Logical AND operator (ECMA `&&`): yields the right operand when the left is truthy."),
970
- "*": binaryMeta("Arithmetic", "*", "Multiplication operator (ECMA `*`): numeric product of the operands."),
971
- "*=": assignMeta("*=", "Multiplication"),
972
- "+": binaryMeta("Arithmetic", "+", "Addition operator (ECMA `+`): numeric addition, or string concatenation."),
973
- "+=": assignMeta("+=", "Addition"),
974
- "-": binaryMeta("Arithmetic", "-", "Subtraction operator (ECMA `-`); with no value operand it is unary negation."),
975
- "-=": assignMeta("-=", "Subtraction"),
976
- "/": binaryMeta("Arithmetic", "/", "Division operator (ECMA `/`): quotient of the operands."),
977
- "/=": assignMeta("/=", "Division"),
978
- "<": binaryMeta("Comparison", "<", "Less-than operator (ECMA `<`): relational comparison of the operands."),
979
- "<=": binaryMeta("Comparison", "<=", "Less-than-or-equal operator (ECMA `<=`): relational comparison of the operands."),
980
- "=": assignMeta("=", "Simple"),
981
- "===": binaryMeta("Comparison", "===", "Strict equality operator (ECMA `===`): true when the operands are equal without coercion."),
982
- ">": binaryMeta("Comparison", ">", "Greater-than operator (ECMA `>`): relational comparison of the operands."),
983
- ">=": binaryMeta("Comparison", ">=", "Greater-than-or-equal operator (ECMA `>=`): relational comparison of the operands."),
984
- "??": binaryMeta("Logical", "??", "Nullish coalescing operator (ECMA `??`): yields the right operand when the left is null or undefined."),
985
- "?:": {
986
- description: "Conditional operator (ECMA `?:`): evaluates the test in target, yielding value when truthy, initial otherwise.",
987
- group: "Conditional",
988
- insert: () => ({ initial: null, operator: "?:", target: null, value: null }),
989
- parameters: [
990
- { description: "Test condition", name: "target" },
991
- { description: "Result when the test is truthy", name: "value" },
992
- { description: "Result when the test is falsy", name: "initial" }
993
- ]
994
- },
995
- "||": binaryMeta("Logical", "||", "Logical OR operator (ECMA `||`): yields the right operand when the left is falsy."),
996
- call: {
997
- description: "Function.prototype.call (ECMA): invokes a named formula (#/state/…) or blessed pure global (window#/…) with positional arguments.",
998
- group: "Function",
999
- insert: () => ({ operator: "call", target: { $ref: "" }, value: [] }),
1000
- parameters: [
1001
- { description: "Callee pointer: #/state/<formula> or window#/<global>", name: "target" },
1002
- { description: "Positional argument list", name: "value" }
1003
- ]
1004
- },
1005
- filter: aggregateMeta("filter", "Array.prototype.filter (ECMA): keeps elements whose per-item expression is truthy.", false),
1006
- map: aggregateMeta("map", "Array.prototype.map (ECMA): transforms each element via the per-item expression.", false),
1007
- pop: arrayMeta("pop", "Array.prototype.pop (ECMA): removes and returns the last element of the target array.", false),
1008
- push: arrayMeta("push", "Array.prototype.push (ECMA): appends the value to the target array.", true),
1009
- reduce: aggregateMeta("reduce", "Array.prototype.reduce (ECMA): folds the array into an accumulator via the per-item expression.", true),
1010
- shift: arrayMeta("shift", "Array.prototype.shift (ECMA): removes and returns the first element of the target array.", false),
1011
- splice: {
1012
- description: "Array.prototype.splice (ECMA): removes and/or inserts elements at an index of the target array.",
1013
- group: "Array methods",
1014
- insert: () => ({ operator: "splice", target: { $ref: "" }, value: [null] }),
1015
- parameters: [
1016
- REF_TARGET,
1017
- { description: "Arguments: start, deleteCount, items…", name: "value" }
1018
- ]
1019
- },
1020
- switch: {
1021
- description: "Switch selection (ECMA `switch` semantics): matches the target's string form against case keys, falling back to default.",
1022
- group: "Conditional",
1023
- insert: () => ({ cases: {}, default: null, operator: "switch", target: null }),
1024
- parameters: [
1025
- { description: "Discriminant value", name: "target" },
1026
- { description: "Matched value → result operand map", name: "cases" },
1027
- { description: "Result when no case matches", name: "default" }
1028
- ]
1029
- },
1030
- unshift: arrayMeta("unshift", "Array.prototype.unshift (ECMA): prepends the value to the target array.", true)
1031
- };
1032
- function operatorEntries() {
1033
- const out = [];
1034
- for (const op of BLESSED_OPERATORS) {
1035
- const meta = Object.hasOwn(OPERATOR_META, op) ? OPERATOR_META[op] : undefined;
1036
- if (meta) {
1037
- out.push({
1038
- description: meta.description,
1039
- group: meta.group,
1040
- insert: meta.insert,
1041
- kind: "operator",
1042
- label: op,
1043
- name: op,
1044
- parameters: meta.parameters
1045
- });
1046
- } else if (PURE_METHOD_OPS.has(op)) {
1047
- out.push(pureMethodEntry(op));
1048
- }
1049
- }
1050
- return out;
1051
- }
1052
- var PURE_METHOD_PROTO = {
1053
- at: "Array/String",
1054
- charAt: "String",
1055
- concat: "Array/String",
1056
- endsWith: "String",
1057
- flat: "Array",
1058
- includes: "Array/String",
1059
- indexOf: "Array/String",
1060
- join: "Array",
1061
- lastIndexOf: "Array/String",
1062
- normalize: "String",
1063
- padEnd: "String",
1064
- padStart: "String",
1065
- repeat: "String",
1066
- replaceAll: "String",
1067
- slice: "Array/String",
1068
- split: "String",
1069
- startsWith: "String",
1070
- toFixed: "Number",
1071
- toLocaleLowerCase: "String",
1072
- toLocaleString: "Number",
1073
- toLocaleUpperCase: "String",
1074
- toLowerCase: "String",
1075
- toPrecision: "Number",
1076
- toReversed: "Array",
1077
- toSorted: "Array",
1078
- toSpliced: "Array",
1079
- toUpperCase: "String",
1080
- trim: "String",
1081
- trimEnd: "String",
1082
- trimStart: "String",
1083
- with: "Array"
1084
- };
1085
- var ZERO_ARG_METHODS = new Set([
1086
- "flat",
1087
- "normalize",
1088
- "toLocaleLowerCase",
1089
- "toLocaleString",
1090
- "toLocaleUpperCase",
1091
- "toLowerCase",
1092
- "toReversed",
1093
- "toSorted",
1094
- "toUpperCase",
1095
- "trim",
1096
- "trimEnd",
1097
- "trimStart"
1098
- ]);
1099
- function pureMethodEntry(op) {
1100
- const proto = PURE_METHOD_PROTO[op] ?? "Standard library";
1101
- return {
1102
- description: `${proto}.prototype.${op} (ECMA) — pure: never mutates the receiver. Receiver in target; argument (or argument list) in value.`,
1103
- group: `${proto} methods`,
1104
- insert: () => ZERO_ARG_METHODS.has(op) ? { operator: op, target: { $ref: "" } } : { operator: op, target: { $ref: "" }, value: null },
1105
- kind: "operator",
1106
- label: op,
1107
- name: op,
1108
- parameters: []
1109
- };
1110
- }
1111
- var HELPER_DESCRIPTIONS = {
1112
- "Intl/formatDate": "Intl.formatDate(value, locale?, options?) — blessed helper wrapping new Intl.DateTimeFormat(locale, options).format(new Date(value)).",
1113
- "Intl/formatNumber": "Intl.formatNumber(value, locale?, options?) — blessed helper wrapping new Intl.NumberFormat(locale, options).format(value).",
1114
- "Intl/formatRelativeTime": "Intl.formatRelativeTime(value, unit, locale?, options?) — blessed helper wrapping new Intl.RelativeTimeFormat(locale, options).format(value, unit)."
1115
- };
1116
- function globalEntries() {
1117
- const out = [];
1118
- for (const name of BLESSED_GLOBALS) {
1119
- const slash = name.indexOf("/");
1120
- const group = slash === -1 ? "globalThis" : name.slice(0, slash);
1121
- const label = name.replaceAll("/", ".");
1122
- out.push({
1123
- description: HELPER_DESCRIPTIONS[name] ?? `${label} — blessed pure global from the ECMAScript standard library, invoked via the call operator (window#/${name}).`,
1124
- group,
1125
- insert: () => ({ operator: "call", target: { $ref: `window#/${name}` }, value: [] }),
1126
- kind: "global",
1127
- label,
1128
- name,
1129
- parameters: []
1130
- });
1131
- }
1132
- return out;
1133
- }
1134
- function toParameterInfo(p) {
1135
- if (typeof p === "string") {
1136
- return p ? { name: p } : null;
1137
- }
1138
- if (!isJsonObject(p) || typeof p.name !== "string" || !p.name) {
1139
- return null;
1140
- }
1141
- const info = { name: p.name };
1142
- const { type } = p;
1143
- if (typeof type === "string") {
1144
- info.type = type;
1145
- } else if (isJsonObject(type)) {
1146
- if (typeof type.text === "string") {
1147
- info.type = type.text;
1148
- } else if (typeof type.type === "string") {
1149
- info.type = type.type;
1150
- }
1151
- }
1152
- if (typeof p.description === "string") {
1153
- info.description = p.description;
1154
- }
1155
- if ("default" in p && p.default !== undefined) {
1156
- info.default = p.default;
1157
- }
1158
- return info;
1159
- }
1160
- function namedFormulaEntries(state) {
1161
- const out = [];
1162
- for (const [key, def] of Object.entries(state ?? {})) {
1163
- if (!isNamedFormulaDef(def)) {
1164
- continue;
1165
- }
1166
- const parameters = def.parameters.map((p) => toParameterInfo(p)).filter((p) => p !== null);
1167
- const description = typeof def.description === "string" && def.description ? def.description : typeof def.$title === "string" && def.$title ? def.$title : `Named formula "${key}" from document state.`;
1168
- out.push({
1169
- description,
1170
- group: "Formulas",
1171
- insert: () => ({
1172
- operator: "call",
1173
- target: { $ref: `#/state/${key}` },
1174
- value: parameters.map((p) => p.default ?? null)
1175
- }),
1176
- kind: "formula",
1177
- label: key,
1178
- name: key,
1179
- parameters
1180
- });
1181
- }
1182
- return out;
1183
- }
1184
- function packagedFormulaEntries(state) {
1185
- const out = [];
1186
- for (const formula of catalog) {
1187
- if (state?.[formula.name]) {
1188
- continue;
1189
- }
1190
- const parameters = formula.parameters.map((p) => toParameterInfo(p)).filter((p) => p !== null);
1191
- out.push({
1192
- description: formula.description,
1193
- ensure: {
1194
- def: {
1195
- $description: formula.description,
1196
- $expression: formula.expression,
1197
- parameters: formula.parameters
1198
- },
1199
- name: formula.name
1200
- },
1201
- group: "Formulas library",
1202
- insert: () => ({
1203
- operator: "call",
1204
- target: { $ref: `#/state/${formula.name}` },
1205
- value: parameters.map((p) => p.default ?? null)
1206
- }),
1207
- kind: "formula",
1208
- label: formula.name,
1209
- name: formula.name,
1210
- parameters
1211
- });
1212
- }
1213
- return out;
1214
- }
1215
- function formulaCatalog(state) {
1216
- return [
1217
- ...namedFormulaEntries(state),
1218
- ...packagedFormulaEntries(state),
1219
- ...operatorEntries(),
1220
- ...globalEntries()
1221
- ];
1222
- }
1223
- function calleeEntry(ref2, state) {
1224
- if (ref2.startsWith("window#/")) {
1225
- const name = ref2.slice("window#/".length);
1226
- return globalEntries().find((e) => e.name === name);
1227
- }
1228
- if (ref2.startsWith("#/state/")) {
1229
- const key = ref2.slice("#/state/".length);
1230
- return namedFormulaEntries(state).find((e) => e.name === key);
1231
- }
1232
- return;
1233
- }
1234
-
1235
- // src/ui/formula-palette.ts
1236
- var _open = false;
1237
- var _opts = null;
1238
- var _query = "";
1239
- var _selectedIndex = 0;
1240
- function getContainer() {
1241
- return getLayerSlot("popover", "formula-palette");
1242
- }
1243
- function openFormulaPalette(opts) {
1244
- _open = true;
1245
- _opts = opts;
1246
- _query = "";
1247
- _selectedIndex = 0;
1248
- renderOverlay();
1249
- }
1250
- function closeFormulaPalette() {
1251
- _open = false;
1252
- _opts = null;
1253
- renderOverlay();
1254
- }
1255
- function filteredEntries() {
1256
- const entries = _opts?.entries ?? [];
1257
- const needle = _query.trim().toLowerCase();
1258
- if (!needle) {
1259
- return entries;
1260
- }
1261
- return entries.filter((e) => e.label.toLowerCase().includes(needle) || e.name.toLowerCase().includes(needle) || e.group.toLowerCase().includes(needle) || e.description.toLowerCase().includes(needle));
1262
- }
1263
- function groupedEntries(entries) {
1264
- const groups = new Map;
1265
- for (const entry of entries) {
1266
- const bucket = groups.get(entry.group);
1267
- if (bucket) {
1268
- bucket.push(entry);
1269
- } else {
1270
- groups.set(entry.group, [entry]);
1271
- }
1272
- }
1273
- return [...groups.entries()];
1274
- }
1275
- function pickEntry(entry) {
1276
- const opts = _opts;
1277
- closeFormulaPalette();
1278
- opts?.onPick(entry);
1279
- }
1280
- function onInput(e) {
1281
- _query = e.target.value;
1282
- _selectedIndex = 0;
1283
- renderOverlay();
1284
- }
1285
- function onKeydown(e) {
1286
- const entries = filteredEntries();
1287
- switch (e.key) {
1288
- case "ArrowDown": {
1289
- e.preventDefault();
1290
- _selectedIndex = Math.min(_selectedIndex + 1, entries.length - 1);
1291
- renderOverlay();
1292
- break;
1293
- }
1294
- case "ArrowUp": {
1295
- e.preventDefault();
1296
- _selectedIndex = Math.max(_selectedIndex - 1, 0);
1297
- renderOverlay();
1298
- break;
1299
- }
1300
- case "Enter": {
1301
- e.preventDefault();
1302
- if (entries[_selectedIndex]) {
1303
- pickEntry(entries[_selectedIndex]);
1304
- }
1305
- break;
1306
- }
1307
- case "Escape": {
1308
- e.preventDefault();
1309
- closeFormulaPalette();
1310
- break;
1311
- }
1312
- default: {
1313
- break;
1314
- }
1315
- }
1316
- }
1317
- function panelStyle() {
1318
- const anchor = _opts?.anchor;
1319
- if (!anchor) {
1320
- return "";
1321
- }
1322
- const rect = rectOf(anchor);
1323
- if (!rect || rect.top === 0 && rect.left === 0 && rect.width === 0) {
1324
- return "";
1325
- }
1326
- const left = Math.max(8, Math.min(rect.left, (globalThis.innerWidth || 1200) - 568));
1327
- return `position:fixed;left:${left}px;top:${rect.bottom + 4}px;margin:0`;
1328
- }
1329
- function renderOverlay() {
1330
- const container = getContainer();
1331
- if (!_open) {
1332
- render(nothing, container);
1333
- return;
1334
- }
1335
- const entries = filteredEntries();
1336
- const groups = groupedEntries(entries);
1337
- const hasQuery = _query.trim().length > 0;
1338
- const tpl = html`
1339
- <div class="quick-search-overlay formula-palette-overlay" @click=${closeFormulaPalette}>
1340
- <div
1341
- class="quick-search-panel formula-palette"
1342
- style=${panelStyle()}
1343
- @click=${(e) => e.stopPropagation()}
1344
- >
1345
- <input
1346
- class="quick-search-input formula-palette-input"
1347
- type="text"
1348
- placeholder="Search formulas, operators, globals…"
1349
- .value=${live(_query)}
1350
- @input=${onInput}
1351
- @keydown=${onKeydown}
1352
- ${ref((el) => {
1353
- if (el) {
1354
- requestAnimationFrame(() => el.focus());
1355
- }
1356
- })}
1357
- />
1358
- <div class="quick-search-results">
1359
- ${entries.length === 0 ? html`<div class="quick-search-empty">
1360
- ${hasQuery ? "No results" : "No entries available"}
1361
- </div>` : nothing}
1362
- ${groups.map(([group, groupEntries]) => html`
1363
- <div class="quick-search-section-label">${group}</div>
1364
- ${groupEntries.map((entry) => {
1365
- const index = entries.indexOf(entry);
1366
- return html`
1367
- <div
1368
- class=${classMap({
1369
- "quick-search-item": true,
1370
- selected: index === _selectedIndex
1371
- })}
1372
- @click=${() => pickEntry(entry)}
1373
- @mouseenter=${() => {
1374
- _selectedIndex = index;
1375
- renderOverlay();
1376
- }}
1377
- >
1378
- <span
1379
- class="quick-search-name"
1380
- style="font-family:var(--spectrum-code-font-family, monospace)"
1381
- >${entry.label}</span
1382
- >
1383
- <span class="quick-search-path" title=${entry.description}
1384
- >${entry.description}</span
1385
- >
1386
- <span class="quick-search-badge">${entry.kind}</span>
1387
- </div>
1388
- `;
1389
- })}
1390
- `)}
1391
- </div>
1392
- </div>
1393
- </div>
1394
- `;
1395
- render(tpl, container);
1396
- }
5
+ LANGUAGE_TAG_PATTERN,
6
+ activeTab2
7
+ } from "./studio-az3zd49t.js";
8
+ import {
9
+ isRef2,
10
+ isFunctionDef,
11
+ isTemplateString
12
+ } from "./studio-7r42c6ca.js";
13
+ import {
14
+ reactive
15
+ } from "./studio-p1dw8wfw.js";
16
+ import {
17
+ registerSurface,
18
+ mountSurface
19
+ } from "./studio-erdatzvp.js";
20
+ import {
21
+ openMenu2
22
+ } from "./studio-3gxv60tn.js";
23
+ import {
24
+ cloneValue2
25
+ } from "./studio-727xh0e3.js";
1397
26
 
1398
27
  // ../schema/defs/tag-expression.schema.ts
1399
28
  var tagExpressionSchema = {
@@ -1530,10 +159,28 @@ var refObjectSchema = {
1530
159
  // ../schema/defs/style-object.schema.ts
1531
160
  var styleObjectSchema = {
1532
161
  additionalProperties: {
1533
- oneOf: [{ type: "string" }, { type: "number" }, { $ref: "#/$defs/StyleObject" }]
162
+ anyOf: [
163
+ { type: "string" },
164
+ { type: "number" },
165
+ { $ref: "#/$defs/RefObject" },
166
+ { $ref: "#/$defs/StyleObject" }
167
+ ]
168
+ },
169
+ description: "CSS style definition. camelCase property names follow CSSOM convention. " + "Keys starting with :, ., &, or [ are treated as nested CSS selectors. " + "Keys matching $media breakpoint names (or other @-prefixed at-rules) group nested rules. " + "Nesting is recursive: selector and at-rule groups may nest to arbitrary depth " + "(e.g. breakpoint → selector → pseudo-class), mirroring the compiler's recursive emission. " + "An object carrying $ref is a reactive VALUE, and the runtime reads it as one. It also " + "matches the nested-block branch vacuously, since a block admits a string under any key, " + "which is why these branches are anyOf rather than oneOf. " + "A DECLARATION at-rule (@font-face, @property, @position-try, @counter-style) may hold an " + "ARRAY of blocks, which is that key written more than once, in order: an object's keys are " + "unique, so @font-face — the at-rule whose identity is not in its key — could not otherwise " + "state a family's second weight at all.",
170
+ patternProperties: {
171
+ "^@(font-face|property|position-try|counter-style)": {
172
+ anyOf: [
173
+ { $ref: "#/$defs/StyleObject" },
174
+ { items: { $ref: "#/$defs/StyleObject" }, minItems: 1, type: "array" }
175
+ ]
176
+ }
177
+ },
178
+ properties: {
179
+ $description: {
180
+ description: "Why this rule exists. Emitted as a comment by static builds, ignored at runtime.",
181
+ type: "string"
182
+ }
1534
183
  },
1535
- description: "CSS style definition. camelCase property names follow CSSOM convention. " + "Keys starting with :, ., &, or [ are treated as nested CSS selectors. " + "Keys matching $media breakpoint names (or other @-prefixed at-rules) group nested rules. " + "Nesting is recursive: selector and at-rule groups may nest to arbitrary depth " + "(e.g. breakpoint → selector → pseudo-class), mirroring the compiler's recursive emission.",
1536
- properties: {},
1537
184
  type: "object"
1538
185
  };
1539
186
  // ../schema/defs/children-value.schema.ts
@@ -1546,6 +193,15 @@ var arrayNamespaceSchema = {
1546
193
  items: {
1547
194
  oneOf: [{ $ref: "#/$defs/RefObject" }, { type: "array" }]
1548
195
  },
196
+ key: {
197
+ additionalProperties: false,
198
+ description: "The identity of each rendered row: a $map/item pointer read per item. A row whose key " + "survives a change keeps its DOM node and its effects across reorders, insertions and " + "removals; without a key, rows are keyed by index. $map/index is not a key — an index " + "names a position, not a row.",
199
+ properties: {
200
+ $ref: { pattern: "^\\$map/item(/.+)?$", type: "string" }
201
+ },
202
+ required: ["$ref"],
203
+ type: "object"
204
+ },
1549
205
  map: { $ref: "#/$defs/ElementDef" },
1550
206
  sort: { $ref: "#/$defs/RefObject" }
1551
207
  },
@@ -1613,8 +269,10 @@ var elementPropertyValueSchema = {
1613
269
  };
1614
270
  var switchDefSchema = {
1615
271
  additionalProperties: false,
1616
- description: "Reactive $ref that drives which case to render.",
1617
- properties: { $ref: { $ref: "#/$defs/StateRef" } },
272
+ description: "Reactive $ref that drives which case to render: a state entry, or inside a mapped array's template the row's $map/item or $map/index (spec §14.1).",
273
+ properties: {
274
+ $ref: { oneOf: [{ $ref: "#/$defs/StateRef" }, { $ref: "#/$defs/MapRef" }], type: "string" }
275
+ },
1618
276
  required: ["$ref"],
1619
277
  type: "object"
1620
278
  };
@@ -2087,6 +745,7 @@ var CLASS_METHOD_ROLES = [
2087
745
  "accessor",
2088
746
  "parse",
2089
747
  "serialize",
748
+ "rewrite",
2090
749
  "discover",
2091
750
  "load",
2092
751
  "projectData",
@@ -2102,7 +761,7 @@ var CLASS_METHOD_ROLES = [
2102
761
  "testConnection"
2103
762
  ];
2104
763
  var classMethodDefSchema = {
2105
- description: "A class method or accessor definition. Capability roles (parse, serialize, discover, load) " + "mark static methods that hosts (compiler, server, studio) invoke for format dispatch.",
764
+ description: "A class method or accessor definition. Capability roles (parse, serialize, rewrite, discover, load) " + "mark static methods that hosts (compiler, server, studio) invoke for format dispatch.",
2106
765
  properties: {
2107
766
  $prototype: { const: "Function", type: "string" },
2108
767
  access: { enum: ["public", "private", "protected"], type: "string" },
@@ -2861,7 +1520,7 @@ var VALUE_SOURCE_HINTS = {
2861
1520
  template: "Text with ${…} placeholders that fill in from signals."
2862
1521
  };
2863
1522
  function slotMode(value) {
2864
- if (isRef(value)) {
1523
+ if (isRef2(value)) {
2865
1524
  return "ref";
2866
1525
  }
2867
1526
  if (isFunctionDef(value)) {
@@ -2976,6 +1635,7 @@ function deriveSlotCaps(schema) {
2976
1635
  var ELEMENT_TAG_SCHEMA = {
2977
1636
  anyOf: [tagNameSchema, ...elementTagNameSchema.anyOf.slice(1)]
2978
1637
  };
1638
+ var TAG_EXPRESSION_OPERATORS = tagExpressionSchema.anyOf.map((branch) => branch.properties.operator.const);
2979
1639
  var SLOT_POSITION_SCHEMAS = {
2980
1640
  attribute: attributesObjectSchema.additionalProperties,
2981
1641
  componentProp: propsObjectSchema.additionalProperties,
@@ -3007,666 +1667,23 @@ function configFieldSchema(field) {
3007
1667
  return { anyOf: [declared, { $ref: "#/$defs/RefObject" }] };
3008
1668
  }
3009
1669
 
3010
- // src/services/context-resolver.ts
3011
- var PARAM_SEGMENT = /^\{@(\w+)\}$/;
3012
- function resolveContextPointer(pointer, opts) {
3013
- if (!pointer.startsWith("#/$context/")) {
3014
- return;
3015
- }
3016
- const segments = [];
3017
- for (const raw of pointer.slice("#/$context/".length).split("/")) {
3018
- const match = raw.match(PARAM_SEGMENT);
3019
- if (match) {
3020
- const value = opts.scope?.[match[1]];
3021
- if (typeof value !== "string" && typeof value !== "number") {
3022
- return;
3023
- }
3024
- const substituted = String(value);
3025
- if (!substituted) {
3026
- return;
3027
- }
3028
- segments.push(substituted);
3029
- } else {
3030
- segments.push(raw);
3031
- }
3032
- }
3033
- if (segments[0] === "$formats") {
3034
- if (segments.length !== 1) {
3035
- return;
3036
- }
3037
- return (opts.formats ?? []).map((format) => format.name);
3038
- }
3039
- let node = opts.projectConfig;
3040
- for (const segment of segments) {
3041
- if (node === null || typeof node !== "object") {
3042
- return;
3043
- }
3044
- node = node[segment];
3045
- }
3046
- return node;
3047
- }
3048
-
3049
- // src/ui/expression-editor.ts
3050
- var UNARY_OPS = new Set(["!", "-"]);
3051
- var BINARY_OPS = new Set([
3052
- "+",
3053
- "-",
3054
- "*",
3055
- "/",
3056
- "%",
3057
- "===",
3058
- "!==",
3059
- "<",
3060
- "<=",
3061
- ">",
3062
- ">=",
3063
- "&&",
3064
- "||",
3065
- "??"
3066
- ]);
3067
- var ASSIGN_OPS = new Set(["=", "+=", "-=", "*=", "/="]);
3068
- function isActionExpression(node) {
3069
- const op = node?.operator;
3070
- return typeof op === "string" && ASSIGN_OPS.has(op);
3071
- }
3072
- var NO_ARG_OPS = new Set(["pop", "shift"]);
3073
- var ONE_ARG_OPS = new Set(["push", "unshift"]);
3074
- var ZERO_ARG_METHOD_OPS = new Set([
3075
- "flat",
3076
- "normalize",
3077
- "toLocaleLowerCase",
3078
- "toLocaleString",
3079
- "toLocaleUpperCase",
3080
- "toLowerCase",
3081
- "toReversed",
3082
- "toSorted",
3083
- "toUpperCase",
3084
- "trim",
3085
- "trimEnd",
3086
- "trimStart"
3087
- ]);
3088
- var OPERATOR_GROUPS = [
3089
- { label: "Assignment", ops: ["=", "+=", "-=", "*=", "/="] },
3090
- { label: "Unary", ops: ["!", "-"] },
3091
- { label: "Arithmetic", ops: ["+", "-", "*", "/", "%"] },
3092
- { label: "Comparison", ops: ["===", "!==", "<", "<=", ">", ">="] },
3093
- { label: "Logical", ops: ["&&", "||", "??"] },
3094
- { label: "Conditional", ops: ["?:", "switch"] },
3095
- {
3096
- label: "Array methods",
3097
- ops: ["push", "pop", "shift", "unshift", "splice"]
3098
- },
3099
- {
3100
- label: "Pure methods (String)",
3101
- ops: [
3102
- "toUpperCase",
3103
- "toLowerCase",
3104
- "trim",
3105
- "trimStart",
3106
- "trimEnd",
3107
- "split",
3108
- "startsWith",
3109
- "endsWith",
3110
- "padStart",
3111
- "padEnd",
3112
- "replaceAll",
3113
- "repeat",
3114
- "charAt",
3115
- "normalize"
3116
- ]
3117
- },
3118
- {
3119
- label: "Pure methods (Array)",
3120
- ops: [
3121
- "includes",
3122
- "indexOf",
3123
- "lastIndexOf",
3124
- "join",
3125
- "slice",
3126
- "concat",
3127
- "at",
3128
- "flat",
3129
- "toSorted",
3130
- "toReversed",
3131
- "toSpliced",
3132
- "with"
3133
- ]
3134
- },
3135
- { label: "Pure methods (Number)", ops: ["toFixed", "toPrecision", "toLocaleString"] },
3136
- { label: "Aggregate", ops: ["reduce", "map", "filter"] },
3137
- { label: "Function", ops: ["call"] }
3138
- ];
3139
- var INFO_DEFAULTS = {
3140
- callArgs: false,
3141
- needsInitial: false,
3142
- needsValue: false,
3143
- spliceArray: false,
3144
- switchCases: false,
3145
- targetMustBeRef: false,
3146
- valueIsNode: false
3147
- };
3148
- function operatorInfo(op) {
3149
- if (UNARY_OPS.has(op)) {
3150
- return { ...INFO_DEFAULTS };
3151
- }
3152
- if (op === "?:") {
3153
- return { ...INFO_DEFAULTS, needsInitial: true, needsValue: true };
3154
- }
3155
- if (op === "switch") {
3156
- return { ...INFO_DEFAULTS, switchCases: true };
3157
- }
3158
- if (op === "call") {
3159
- return { ...INFO_DEFAULTS, callArgs: true, targetMustBeRef: true };
3160
- }
3161
- if (PURE_METHOD_OPS.has(op)) {
3162
- return { ...INFO_DEFAULTS, needsValue: !ZERO_ARG_METHOD_OPS.has(op) };
3163
- }
3164
- if (BINARY_OPS.has(op)) {
3165
- return { ...INFO_DEFAULTS, needsValue: true };
3166
- }
3167
- if (ASSIGN_OPS.has(op)) {
3168
- return { ...INFO_DEFAULTS, needsValue: true, targetMustBeRef: true };
3169
- }
3170
- if (NO_ARG_OPS.has(op)) {
3171
- return { ...INFO_DEFAULTS, targetMustBeRef: true };
3172
- }
3173
- if (ONE_ARG_OPS.has(op)) {
3174
- return { ...INFO_DEFAULTS, needsValue: true, targetMustBeRef: true };
3175
- }
3176
- if (op === "splice") {
3177
- return { ...INFO_DEFAULTS, needsValue: true, spliceArray: true, targetMustBeRef: true };
3178
- }
3179
- if (op === "reduce") {
3180
- return {
3181
- ...INFO_DEFAULTS,
3182
- needsInitial: true,
3183
- needsValue: true,
3184
- targetMustBeRef: true,
3185
- valueIsNode: true
3186
- };
3187
- }
3188
- if (op === "map" || op === "filter") {
3189
- return { ...INFO_DEFAULTS, needsValue: true, targetMustBeRef: true, valueIsNode: true };
3190
- }
3191
- return { ...INFO_DEFAULTS };
3192
- }
3193
- function operandMode(operand) {
3194
- if (operand && typeof operand === "object") {
3195
- if ("$ref" in operand) {
3196
- return "ref";
3197
- }
3198
- if ("operator" in operand) {
3199
- return "expression";
3200
- }
3201
- }
3202
- return "literal";
3203
- }
3204
- function calleeParamLabels(target, state) {
3205
- const ref2 = isRef(target) ? target.$ref : "";
3206
- const entry = ref2 ? calleeEntry(ref2, state) : undefined;
3207
- return entry?.kind === "formula" ? entry.parameters.map((p) => p.name) : [];
3208
- }
3209
- function defaultForMode(mode) {
3210
- if (mode === "ref") {
3211
- return { $ref: "" };
3212
- }
3213
- if (mode === "expression") {
3214
- return { operator: "!", target: null };
3215
- }
3216
- return null;
3217
- }
3218
- function literalType(val) {
3219
- if (val === null || val === undefined) {
3220
- return "null";
3221
- }
3222
- if (typeof val === "boolean") {
3223
- return "boolean";
3224
- }
3225
- if (typeof val === "number") {
3226
- return "number";
3227
- }
3228
- return "string";
3229
- }
3230
- function defaultForLiteralType(type) {
3231
- if (type === "number") {
3232
- return 0;
3233
- }
3234
- if (type === "boolean") {
3235
- return false;
3236
- }
3237
- if (type === "null") {
3238
- return null;
3239
- }
3240
- return "";
3241
- }
3242
- function expressionHint(node) {
3243
- if (!isJsonObject(node) || typeof node.operator !== "string") {
3244
- return "$expression";
3245
- }
3246
- const expr = node;
3247
- const op = expr.operator;
3248
- const { target } = expr;
3249
- const targetLabel = isRef(target) ? target.$ref.replace("#/state/", "") : isJsonObject(target) && typeof target.operator === "string" ? `(${target.operator}…)` : String(target ?? "?");
3250
- if (ASSIGN_OPS.has(op) || ONE_ARG_OPS.has(op)) {
3251
- return `${op} ${targetLabel}`;
3252
- }
3253
- if (NO_ARG_OPS.has(op)) {
3254
- return `${op}(${targetLabel})`;
3255
- }
3256
- if (op === "splice") {
3257
- return `splice(${targetLabel})`;
3258
- }
3259
- if (op === "call") {
3260
- return `${targetLabel.replace("window#/", "").replaceAll("/", ".")}(…)`;
3261
- }
3262
- if (op === "reduce" || op === "map" || op === "filter" || PURE_METHOD_OPS.has(op)) {
3263
- return `${op}(${targetLabel})`;
3264
- }
3265
- if (op === "?:") {
3266
- return `${targetLabel} ? … : …`;
3267
- }
3268
- if (op === "switch") {
3269
- return `switch(${targetLabel})`;
3270
- }
3271
- if (UNARY_OPS.has(op)) {
3272
- return `${op}${targetLabel}`;
3273
- }
3274
- return `${targetLabel} ${op} …`;
3275
- }
3276
- function renderValueBadge(preview, pathKey) {
3277
- const text = preview?.values.get(pathKey);
3278
- if (text === undefined) {
3279
- return nothing;
3280
- }
3281
- return html`<span class="expr-live-badge" title=${text}>${text}</span>`;
3282
- }
3283
- function renderRefPicker(refVal, onRefChange, opts) {
3284
- const stateRefs = (opts.stateDefs || []).map((k) => `#/state/${k}`);
3285
- const eventRefs = opts.allowEventRef ? ["event#/detail", "event#/target/value"] : [];
3286
- const allRefs = [...stateRefs, ...eventRefs];
3287
- const isCustom = refVal && !allRefs.includes(refVal);
3288
- if (allRefs.length === 0 && !refVal) {
3289
- return renderEmptyState({
3290
- compact: true,
3291
- detail: "Add one in the State panel and it shows up here.",
3292
- message: "A binding points at a value this page holds."
3293
- });
3294
- }
3295
- return html`
3296
- <sp-picker
3297
- size="s"
3298
- class="expr-ref"
3299
- placeholder="Select…"
3300
- .value=${live(isCustom ? "__custom__" : refVal || "")}
3301
- @change=${(e) => {
3302
- const val = e.target.value;
3303
- if (val === "__custom__") {
3304
- return;
3305
- }
3306
- onRefChange(val);
3307
- }}
3308
- >
3309
- ${stateRefs.map((r) => html`<sp-menu-item value=${r}>${r.replace("#/state/", "")}</sp-menu-item>`)}
3310
- ${eventRefs.length > 0 ? html`
3311
- <sp-menu-divider></sp-menu-divider>
3312
- ${eventRefs.map((r) => html`<sp-menu-item value=${r}>${r}</sp-menu-item>`)}
3313
- ` : nothing}
3314
- </sp-picker>
3315
- `;
3316
- }
3317
- function renderLiteralEditor(operand, onChange) {
3318
- const type = literalType(operand);
3319
- return html`
3320
- <div class="expr-literal">
3321
- <sp-picker
3322
- size="s"
3323
- class="expr-literal-type"
3324
- .value=${live(type)}
3325
- @change=${(e) => {
3326
- const newType = e.target.value;
3327
- onChange(defaultForLiteralType(newType));
3328
- }}
3329
- >
3330
- <sp-menu-item value="string">str</sp-menu-item>
3331
- <sp-menu-item value="number">num</sp-menu-item>
3332
- <sp-menu-item value="boolean">bool</sp-menu-item>
3333
- <sp-menu-item value="null">null</sp-menu-item>
3334
- </sp-picker>
3335
- ${type === "string" ? html`<sp-textfield
3336
- size="s"
3337
- class="expr-literal-value"
3338
- .value=${live(String(operand ?? ""))}
3339
- @input=${(e) => onChange(e.target.value)}
3340
- ></sp-textfield>` : type === "number" ? html`<sp-number-field
3341
- size="s"
3342
- class="expr-literal-value"
3343
- .value=${live(Number(operand ?? 0))}
3344
- @change=${(e) => onChange(Number(e.target.value))}
3345
- ></sp-number-field>` : type === "boolean" ? html`<sp-checkbox
3346
- size="s"
3347
- class="expr-literal-bool"
3348
- ?checked=${Boolean(operand)}
3349
- @change=${(e) => onChange(e.target.checked)}
3350
- >true</sp-checkbox
3351
- >` : html`<span class="expr-literal-null">null</span>`}
3352
- </div>
3353
- `;
3354
- }
3355
- function renderOperandEditor(operand, onChange, opts) {
3356
- if (opts.mustBeRef) {
3357
- const refVal = operand?.$ref ?? "";
3358
- return html`
3359
- <div class="expr-operand">${renderRefPicker(refVal, (r) => onChange({ $ref: r }), opts)}</div>
3360
- `;
3361
- }
3362
- const mode = operandMode(operand);
3363
- return html`
3364
- <div class="expr-operand">
3365
- <sp-picker
3366
- size="s"
3367
- class="expr-operand-mode"
3368
- label="Value source"
3369
- .value=${live(mode)}
3370
- @change=${(e) => {
3371
- const newMode = e.target.value;
3372
- onChange(defaultForMode(newMode));
3373
- }}
3374
- >
3375
- <sp-menu-item value="literal">${VALUE_SOURCE_LABELS.literal}</sp-menu-item>
3376
- <sp-menu-item value="ref">${VALUE_SOURCE_LABELS.ref}</sp-menu-item>
3377
- <sp-menu-item value="expression">${VALUE_SOURCE_LABELS.expression}</sp-menu-item>
3378
- </sp-picker>
3379
- ${mode === "literal" ? renderLiteralEditor(operand, onChange) : mode === "ref" ? renderRefPicker(operand?.$ref ?? "", (r) => onChange({ $ref: r }), opts) : renderExpressionEditor(operand, onChange, {
3380
- ...opts,
3381
- depth: opts.depth + 1
3382
- })}
3383
- </div>
3384
- `;
3385
- }
3386
- function renderSpliceArgsEditor(args, onChange, opts, naming = {}) {
3387
- const safeArgs = Array.isArray(args) ? args : [];
3388
- const labels = naming.labels ?? ["start", "del", "item"];
3389
- const fallbackLabel = naming.fallbackLabel ?? "item";
3390
- return html`
3391
- <div class="array-object-field">
3392
- ${safeArgs.map((arg, idx) => html`
3393
- <div class="array-object-row">
3394
- <span class="array-object-label">${labels[idx] ?? fallbackLabel}</span>
3395
- ${renderOperandEditor(arg, (newArg) => {
3396
- const updated = [...safeArgs];
3397
- updated[idx] = newArg;
3398
- onChange(updated);
3399
- }, { ...opts, mustBeRef: false })}
3400
- <sp-action-button
3401
- quiet
3402
- size="xs"
3403
- @click=${() => {
3404
- const updated = safeArgs.filter((_, i) => i !== idx);
3405
- onChange(updated.length > 0 ? updated : [null]);
3406
- }}
3407
- >
3408
- <sp-icon-delete slot="icon"></sp-icon-delete>
3409
- </sp-action-button>
3410
- </div>
3411
- `)}
3412
- <sp-action-button quiet size="s" @click=${() => onChange([...safeArgs, null])}>
3413
- + Add arg
3414
- </sp-action-button>
3415
- </div>
3416
- `;
3417
- }
3418
- var _operatorMenuCache = OPERATOR_GROUPS.map((group, i) => html`
3419
- ${i > 0 ? html`<sp-menu-divider></sp-menu-divider>` : nothing}
3420
- <sp-menu-group>
3421
- <span slot="header">${group.label}</span>
3422
- ${group.ops.map((op) => html`<sp-menu-item value=${op}>${op}</sp-menu-item>`)}
3423
- </sp-menu-group>
3424
- `);
3425
- function renderExpressionEditor(node, onChange, opts) {
3426
- const depth = opts.depth ?? 0;
3427
- const safeNode = node && typeof node === "object" ? node : { operator: "=", target: null };
3428
- const op = safeNode.operator || "=";
3429
- const info = operatorInfo(op);
3430
- const preview = opts.preview ?? null;
3431
- const path = opts.path ?? [];
3432
- const pathKey = path.join("/");
3433
- const sub = (...segs) => [...path, ...segs].join("/");
3434
- const withBadge = (widget, key) => html`<div class="expr-widget">${widget}${renderValueBadge(preview, key)}</div>`;
3435
- const rootBadge = depth === 0 && preview && !preview.mutating ? renderValueBadge(preview, pathKey) : nothing;
3436
- return html`
3437
- <div class=${depth > 0 ? "expression-editor expression-editor--nested" : "expression-editor"}>
3438
- ${depth === 0 ? renderFormulaChips(safeNode, opts.onChipSelect ?? (() => {}), { path, preview }) : nothing}
3439
- ${depth === 0 && preview?.error ? html`<div class="expr-error">${preview.error}</div>` : nothing}
3440
- ${renderFieldRow({
3441
- hasValue: false,
3442
- label: "Operator",
3443
- prop: "operator",
3444
- widget: html`
3445
- <div class="expr-widget">
3446
- <sp-picker
3447
- size="s"
3448
- class="expr-operator"
3449
- .value=${live(op)}
3450
- @change=${(e) => {
3451
- const newOp = e.target.value;
3452
- const newInfo = operatorInfo(newOp);
3453
- const updated = {
3454
- operator: newOp,
3455
- target: safeNode.target
3456
- };
3457
- if (newInfo.targetMustBeRef && operandMode(safeNode.target) !== "ref") {
3458
- updated.target = { $ref: "" };
3459
- }
3460
- if (newInfo.needsValue) {
3461
- if (newInfo.valueIsNode) {
3462
- const val = safeNode.value;
3463
- updated.value = val?.operator ? safeNode.value : { operator: "!", target: null };
3464
- } else if (newInfo.spliceArray) {
3465
- updated.value = Array.isArray(safeNode.value) ? safeNode.value : [null];
3466
- } else {
3467
- updated.value = safeNode.value ?? null;
3468
- }
3469
- }
3470
- if (newInfo.needsInitial) {
3471
- updated.initial = safeNode.initial ?? (newOp === "?:" ? null : 0);
3472
- }
3473
- if (newInfo.switchCases) {
3474
- updated.cases = isJsonObject(safeNode.cases) ? safeNode.cases : {};
3475
- if ("default" in safeNode) {
3476
- updated.default = safeNode.default;
3477
- }
3478
- }
3479
- if (newInfo.callArgs) {
3480
- updated.value = Array.isArray(safeNode.value) ? safeNode.value : [];
3481
- }
3482
- onChange(updated);
3483
- }}
3484
- >
3485
- ${_operatorMenuCache}
3486
- </sp-picker>
3487
- <sp-action-button
3488
- quiet
3489
- size="s"
3490
- class="expr-browse-catalog"
3491
- title="Browse catalog"
3492
- @click=${(e) => openFormulaPalette({
3493
- anchor: e.currentTarget,
3494
- entries: formulaCatalog(opts.stateEntries),
3495
- onPick: (entry) => applyCatalogPick(entry, onChange, {
3496
- onInsertDef: opts.onInsertDef,
3497
- stateEntries: opts.stateEntries
3498
- })
3499
- })}
3500
- >
3501
- <sp-icon-brackets slot="icon"></sp-icon-brackets>
3502
- </sp-action-button>
3503
- ${rootBadge}
3504
- </div>
3505
- `
3506
- })}
3507
- ${renderFieldRow({
3508
- hasValue: false,
3509
- label: op === "?:" ? "If" : op === "switch" ? "On" : op === "call" ? "Callee" : "Target",
3510
- prop: "target",
3511
- widget: withBadge(renderOperandEditor(safeNode.target, (t) => onChange({ ...safeNode, target: t }), {
3512
- ...opts,
3513
- depth,
3514
- mustBeRef: info.targetMustBeRef,
3515
- path: [...path, "target"]
3516
- }), sub("target"))
3517
- })}
3518
- ${info.needsValue && !info.valueIsNode && !info.spliceArray ? renderFieldRow({
3519
- hasValue: false,
3520
- label: op === "?:" ? "Then" : "Value",
3521
- prop: "value",
3522
- widget: withBadge(renderOperandEditor(safeNode.value, (v) => onChange({ ...safeNode, value: v }), {
3523
- ...opts,
3524
- depth,
3525
- mustBeRef: false,
3526
- path: [...path, "value"]
3527
- }), sub("value"))
3528
- }) : nothing}
3529
- ${info.needsValue && info.valueIsNode ? html`
3530
- <div class="expr-subsection">
3531
- ${renderFieldRow({
3532
- hasValue: false,
3533
- label: "Per-item",
3534
- prop: "value",
3535
- widget: nothing
3536
- })}
3537
- ${renderExpressionEditor(safeNode.value?.operator ? safeNode.value : { operator: "!", target: null }, (v) => onChange({ ...safeNode, value: v }), { ...opts, depth: depth + 1, path: [...path, "value"] })}
3538
- </div>
3539
- ` : nothing}
3540
- ${info.spliceArray ? html`
3541
- <div class="expr-subsection">
3542
- ${renderFieldRow({
3543
- hasValue: false,
3544
- label: "Args",
3545
- prop: "value",
3546
- widget: nothing
3547
- })}
3548
- ${renderSpliceArgsEditor(safeNode.value, (v) => onChange({ ...safeNode, value: v }), {
3549
- ...opts,
3550
- depth
3551
- })}
3552
- </div>
3553
- ` : nothing}
3554
- ${info.callArgs ? html`
3555
- <div class="expr-subsection">
3556
- ${renderFieldRow({
3557
- hasValue: false,
3558
- label: "Args",
3559
- prop: "value",
3560
- widget: nothing
3561
- })}
3562
- ${renderSpliceArgsEditor(safeNode.value, (v) => onChange({ ...safeNode, value: v }), { ...opts, depth }, {
3563
- fallbackLabel: "arg",
3564
- labels: calleeParamLabels(safeNode.target, opts.stateEntries)
3565
- })}
3566
- </div>
3567
- ` : nothing}
3568
- ${info.switchCases ? renderSwitchCasesEditor(safeNode, onChange, { ...opts, depth, path }, withBadge) : nothing}
3569
- ${info.needsInitial ? renderFieldRow({
3570
- hasValue: false,
3571
- label: op === "?:" ? "Else" : "Initial",
3572
- prop: "initial",
3573
- widget: withBadge(renderOperandEditor(safeNode.initial, (v) => onChange({ ...safeNode, initial: v }), {
3574
- ...opts,
3575
- depth,
3576
- mustBeRef: false,
3577
- path: [...path, "initial"]
3578
- }), sub("initial"))
3579
- }) : nothing}
3580
- </div>
3581
- `;
3582
- }
3583
- function renderSwitchCasesEditor(safeNode, onChange, opts, withBadge) {
3584
- const cases = isJsonObject(safeNode.cases) ? safeNode.cases : {};
3585
- const entries = Object.entries(cases);
3586
- const setCases = (next) => onChange({ ...safeNode, cases: next });
3587
- return html`
3588
- <div class="switch-cases">
3589
- ${entries.map(([key, operand]) => html`
3590
- <div class="switch-case-row">
3591
- <sp-textfield
3592
- size="s"
3593
- class="switch-case-key"
3594
- placeholder="value"
3595
- .value=${live(key)}
3596
- @change=${(e) => {
3597
- const newKey = e.target.value;
3598
- if (newKey === key) {
3599
- return;
3600
- }
3601
- const next = {};
3602
- for (const [k, v] of entries) {
3603
- next[k === key ? newKey : k] = v;
3604
- }
3605
- setCases(next);
3606
- }}
3607
- ></sp-textfield>
3608
- ${withBadge(renderOperandEditor(operand, (v) => setCases({ ...cases, [key]: v }), {
3609
- ...opts,
3610
- mustBeRef: false,
3611
- path: [...opts.path, "cases", key]
3612
- }), [...opts.path, "cases", key].join("/"))}
3613
- <sp-action-button
3614
- quiet
3615
- size="xs"
3616
- @click=${() => {
3617
- const next = { ...cases };
3618
- delete next[key];
3619
- setCases(next);
3620
- }}
3621
- >
3622
- <sp-icon-delete slot="icon"></sp-icon-delete>
3623
- </sp-action-button>
3624
- </div>
3625
- `)}
3626
- <div class="switch-case-row">
3627
- <span class="switch-case-key switch-case-default">default</span>
3628
- ${withBadge(renderOperandEditor(safeNode.default, (v) => onChange({ ...safeNode, default: v }), {
3629
- ...opts,
3630
- mustBeRef: false,
3631
- path: [...opts.path, "default"]
3632
- }), [...opts.path, "default"].join("/"))}
3633
- </div>
3634
- <sp-action-button
3635
- quiet
3636
- size="s"
3637
- @click=${() => {
3638
- let n = entries.length + 1;
3639
- let key = `case ${n}`;
3640
- while (Object.hasOwn(cases, key)) {
3641
- n += 1;
3642
- key = `case ${n}`;
3643
- }
3644
- setCases({ ...cases, [key]: null });
3645
- }}
3646
- >
3647
- + Add case
3648
- </sp-action-button>
3649
- </div>
3650
- `;
3651
- }
3652
-
3653
1670
  // src/ui/dynamic-slot.ts
3654
1671
  var slotModeMemory = new Map;
3655
1672
  var slotModeRendered = new Map;
3656
1673
  function memoryKey(fieldKey, mode) {
3657
- return `${activeTab.value?.id ?? "-"}|${fieldKey}|${mode}`;
1674
+ return `${activeTab2.value?.id ?? "-"}|${fieldKey}|${mode}`;
3658
1675
  }
3659
1676
  function fieldScopeKey(fieldKey) {
3660
- return `${activeTab.value?.id ?? "-"}|${fieldKey}`;
1677
+ return `${activeTab2.value?.id ?? "-"}|${fieldKey}`;
3661
1678
  }
3662
1679
  function stashSlotValue(fieldKey, mode, value) {
3663
- slotModeMemory.set(memoryKey(fieldKey, mode), cloneValue(value));
1680
+ slotModeMemory.set(memoryKey(fieldKey, mode), cloneValue2(value));
3664
1681
  }
3665
1682
  function hasStashedSlotValue(fieldKey, mode) {
3666
1683
  return slotModeMemory.has(memoryKey(fieldKey, mode));
3667
1684
  }
3668
1685
  function recallSlotValue(fieldKey, mode) {
3669
- return cloneValue(slotModeMemory.get(memoryKey(fieldKey, mode)));
1686
+ return cloneValue2(slotModeMemory.get(memoryKey(fieldKey, mode)));
3670
1687
  }
3671
1688
  function setSlotMode(fieldKey, mode) {
3672
1689
  slotModeRendered.set(fieldScopeKey(fieldKey), mode);
@@ -3682,17 +1699,14 @@ function effectiveSlotMode(fieldKey, value) {
3682
1699
  slotModeRendered.set(key, mode);
3683
1700
  return mode;
3684
1701
  }
3685
- function hasRefOptions(opts) {
3686
- return opts.stateDefs.length > 0 || Boolean(opts.extraSignals?.length);
3687
- }
3688
- function defaultForSlotMode(mode, opts) {
1702
+ function slotModeSeed(mode, sources) {
3689
1703
  switch (mode) {
3690
1704
  case "ref": {
3691
- const [first] = opts.stateDefs;
3692
- return first ? { $ref: `#/state/${first}` } : { $ref: opts.extraSignals?.[0]?.value ?? "" };
1705
+ const [first] = sources.stateDefs;
1706
+ return first ? { $ref: `#/state/${first}` } : { $ref: sources.extraSignals?.[0]?.value ?? "" };
3693
1707
  }
3694
1708
  case "template": {
3695
- const [first] = opts.stateDefs;
1709
+ const [first] = sources.stateDefs;
3696
1710
  return first ? `\${state.${first}}` : "${}";
3697
1711
  }
3698
1712
  case "expression": {
@@ -3702,153 +1716,1092 @@ function defaultForSlotMode(mode, opts) {
3702
1716
  return { $prototype: "Function", body: "", parameters: [] };
3703
1717
  }
3704
1718
  default: {
3705
- return opts.literalDefault;
1719
+ return sources.literalDefault;
3706
1720
  }
3707
1721
  }
3708
1722
  }
3709
- function refOptions(opts) {
3710
- return [
3711
- ...opts.stateDefs.map((name) => ({ label: name, value: `#/state/${name}` })),
3712
- ...opts.extraSignals ?? []
3713
- ];
1723
+ function switchSlotMode(fieldKey, from, to, current, seed) {
1724
+ stashSlotValue(fieldKey, from, current);
1725
+ setSlotMode(fieldKey, to);
1726
+ return hasStashedSlotValue(fieldKey, to) ? recallSlotValue(fieldKey, to) : seed;
3714
1727
  }
3715
- function renderCustomRefWidget(refVal, opts) {
3716
- return html`
3717
- <jx-value-selector
3718
- size="s"
3719
- style="flex:1"
3720
- placeholder="#/state/…"
3721
- .value=${refVal}
3722
- .options=${refOptions(opts)}
3723
- @change=${(e) => {
3724
- const v = (e.detail?.value ?? e.target.value ?? "").trim();
3725
- if (v) {
3726
- opts.onChange({ $ref: v });
3727
- } else {
3728
- opts.onChange();
1728
+ // src/surfaces/schema-form.json
1729
+ var schema_form_default = {
1730
+ $schema: "https://jxsuite.com/schema/v1",
1731
+ $id: "StudioSchemaForm",
1732
+ $description: "The shared schema→form engine as a document: one row per property of a JSON Schema, drawn from a projection `ui/schema-form.ts` computes. Nothing here reads a schema. The flow decides which of nine controls a property gets, what its options are, which rungs of the value ladder the position permits, what a commit is worth and whether the value is refused; this document draws the answer. That split is what lets the Entry editor, Project Settings and the Signals panel all embed the same form and get the same row — and it is why the engine hands back a HOST ELEMENT rather than a template: a document clears the host it is given, so a caller that is still lit renders an empty element for it and puts it in its own tree (studio-ui-guidelines.md §9.3, §9.4).",
1733
+ tagName: "div",
1734
+ attributes: {
1735
+ part: "form"
1736
+ },
1737
+ style: {
1738
+ $description: "What `.style-row`, `.style-row-label`, `.style-row--invalid`, `.style-row-error`, `.style-row-error-count`, `.array-object-field`, `.array-object-row` and `.schema-param-editor` said in `styles/inspector.css`, keyed on `part` and re-pointed at the studio token layer. Those rules are DEAD in the stylesheet now rather than shared: `ui/field-row.ts` drew the same row for the inspector panels and `ui/expression-editor.ts` the same array rows, and both converted, so this document's copy is the only emitter left. Every `--spectrum-font-size-*` reference is gone rather than renamed — those names resolve only inside `<sp-theme>`, and a document mounted in a settings pane or an entry editor is outside it, so each would silently have taken its hard-coded fallback.",
1739
+ display: "contents",
1740
+ '& [part="field"]': {
1741
+ padding: "var(--jx-space-1) 0"
1742
+ },
1743
+ '& [part="value"]': {
1744
+ $description: "The control line: the source chip, then whatever the rung draws. `min-width: 0` is load-bearing — without it a monospaced pointer or a long JSON line pushes the row past the pane's right edge instead of scrolling inside its own control.",
1745
+ display: "flex",
1746
+ alignItems: "flex-start",
1747
+ gap: "var(--jx-space-2)",
1748
+ minWidth: "0"
1749
+ },
1750
+ '& [part="value"] > *': {
1751
+ minWidth: "0"
1752
+ },
1753
+ '& [part="widget"] > *': {
1754
+ $description: "The switch wrapper is `display: contents`, so whatever rung it drew is the flex item of the line above rather than a box inside a box — which is what keeps `min-width: 0` reaching the control that actually needs it.",
1755
+ flex: "1",
1756
+ minWidth: "0"
1757
+ },
1758
+ '& [part="source"]': {
1759
+ $description: "Quiet while the value is fixed, accent-tinted the moment it is produced from something else — the one place in the row that says a value is not what it looks like.",
1760
+ flex: "none",
1761
+ color: "var(--fg-dim)",
1762
+ fontSize: "var(--jx-text-sm)"
1763
+ },
1764
+ '& [part="source"][data-source="bound"]': {
1765
+ color: "var(--accent)"
1766
+ },
1767
+ '& [part="pointer-group"]': {
1768
+ display: "flex",
1769
+ flex: "1",
1770
+ gap: "var(--jx-space-2)",
1771
+ minWidth: "0"
1772
+ },
1773
+ '& [part="pointer-group"] > *': {
1774
+ flex: "1",
1775
+ minWidth: "0"
1776
+ },
1777
+ '& [part="json"]': {
1778
+ display: "flex",
1779
+ flexDirection: "column",
1780
+ flex: "1",
1781
+ gap: "var(--jx-space-1)",
1782
+ minWidth: "0"
1783
+ },
1784
+ '& [part="chips"]': {
1785
+ display: "flex",
1786
+ flexWrap: "wrap",
1787
+ gap: "var(--jx-space-1)"
1788
+ },
1789
+ '& [part="chip"]': {
1790
+ padding: "1px var(--jx-space-2)",
1791
+ borderRadius: "var(--radius)",
1792
+ background: "var(--bg)",
1793
+ fontSize: "var(--jx-text-sm)",
1794
+ color: "var(--fg-dim)"
1795
+ },
1796
+ '& [part="rows"]': {
1797
+ display: "flex",
1798
+ flexDirection: "column",
1799
+ flex: "1",
1800
+ gap: "var(--jx-space-1)",
1801
+ minWidth: "0"
1802
+ },
1803
+ '& [part="row"]': {
1804
+ $description: "Wraps rather than scrolls: an item with five declared properties in a 260px inspector is a column of controls, not a horizontal scroller nobody finds the end of.",
1805
+ display: "flex",
1806
+ flexWrap: "wrap",
1807
+ alignItems: "center",
1808
+ gap: "var(--jx-space-1)",
1809
+ minWidth: "0"
1810
+ },
1811
+ '& [part="row-add"]': {
1812
+ alignSelf: "flex-start"
1813
+ },
1814
+ '& [part="field-error"]': {
1815
+ $description: "The left rule is what makes the line read as belonging to the control above it rather than to the row below.",
1816
+ margin: "var(--jx-space-1) 0 0",
1817
+ paddingLeft: "var(--jx-space-2)",
1818
+ borderLeft: "2px solid var(--danger)",
1819
+ fontSize: "var(--jx-text-sm)",
1820
+ lineHeight: "1.35",
1821
+ color: "var(--danger)"
1822
+ },
1823
+ '& [part="field-error-count"]': {
1824
+ marginLeft: "var(--jx-space-1)",
1825
+ fontFamily: "var(--font-mono)",
1826
+ opacity: "0.75"
3729
1827
  }
3730
- }}
3731
- ></jx-value-selector>
3732
- `;
3733
- }
3734
- function renderRefWidget(refVal, opts) {
3735
- if (opts.allowCustomRef) {
3736
- return renderCustomRefWidget(refVal, opts);
3737
- }
3738
- return html`
3739
- <sp-picker
3740
- size="s"
3741
- quiet
3742
- style="flex:1"
3743
- placeholder="— select signal —"
3744
- value=${refVal || nothing}
3745
- @change=${(e) => {
3746
- const v = e.target.value;
3747
- if (v) {
3748
- opts.onChange({ $ref: v });
3749
- } else {
3750
- opts.onChange();
1828
+ },
1829
+ children: [
1830
+ {
1831
+ $prototype: "Array",
1832
+ items: {
1833
+ $ref: "#/state/fields"
1834
+ },
1835
+ key: {
1836
+ $ref: "$map/item/key"
1837
+ },
1838
+ map: {
1839
+ $description: "One property of the schema. `jx-field` owns the label, the required mark, the invalid shade and the association between the label and whatever control ends up in the slot, so none of that is written here — which is the whole of what the four hand-rolled `.style-row` variants used to carry.",
1840
+ tagName: "jx-field",
1841
+ attributes: {
1842
+ part: "field",
1843
+ "data-prop": "${$map.item.prop}"
1844
+ },
1845
+ $props: {
1846
+ label: {
1847
+ $ref: "$map/item/label"
1848
+ },
1849
+ description: {
1850
+ $ref: "$map/item/description"
1851
+ },
1852
+ required: {
1853
+ $ref: "$map/item/required"
1854
+ },
1855
+ invalid: {
1856
+ $ref: "$map/item/invalid"
1857
+ },
1858
+ span: true
1859
+ },
1860
+ children: [
1861
+ {
1862
+ tagName: "div",
1863
+ attributes: {
1864
+ part: "value",
1865
+ role: "none"
1866
+ },
1867
+ children: [
1868
+ {
1869
+ tagName: "span",
1870
+ attributes: {
1871
+ part: "source-slot",
1872
+ role: "none"
1873
+ },
1874
+ style: {
1875
+ display: "contents"
1876
+ },
1877
+ $switch: {
1878
+ $ref: "$map/item/hasChip"
1879
+ },
1880
+ cases: {
1881
+ true: {
1882
+ $description: "The Value Source chip (§6.3): what produces this value, in the same four words every other bindable position in Studio uses. It opens the rung picker rather than cycling, so any rung the position permits is one action away. Drawn only where the host named somewhere a value could come from — a settings form that names none edits fixed values and shows no chip.",
1883
+ tagName: "jx-action-button",
1884
+ attributes: {
1885
+ part: "source",
1886
+ "data-source": "${$map.item.source}"
1887
+ },
1888
+ $props: {
1889
+ label: {
1890
+ $ref: "$map/item/sourceName"
1891
+ },
1892
+ hint: {
1893
+ $ref: "$map/item/sourceHint"
1894
+ },
1895
+ size: "sm",
1896
+ haspopup: "menu",
1897
+ disabled: {
1898
+ $ref: "$map/item/sourceLocked"
1899
+ }
1900
+ },
1901
+ onclick: {
1902
+ $prototype: "Function",
1903
+ body: [
1904
+ {
1905
+ operator: "call",
1906
+ target: {
1907
+ $ref: "#/state/pickSource"
1908
+ },
1909
+ value: [
1910
+ {
1911
+ $ref: "$map/item/key"
1912
+ },
1913
+ {
1914
+ $ref: "event#/currentTarget"
1915
+ }
1916
+ ]
1917
+ }
1918
+ ]
1919
+ },
1920
+ children: [
1921
+ {
1922
+ tagName: "span",
1923
+ textContent: "${$map.item.sourceLabel}"
1924
+ }
1925
+ ]
1926
+ }
1927
+ }
1928
+ },
1929
+ {
1930
+ tagName: "span",
1931
+ attributes: {
1932
+ part: "widget",
1933
+ role: "none"
1934
+ },
1935
+ style: {
1936
+ display: "contents"
1937
+ },
1938
+ $switch: {
1939
+ $ref: "$map/item/kind"
1940
+ },
1941
+ cases: {
1942
+ text: {
1943
+ $description: "The default control, and the one every scalar without a narrower answer lands on. Commits on input, debounced by the flow, which is what makes the canvas and the preview follow a value being typed rather than waiting for a blur.",
1944
+ tagName: "jx-textfield",
1945
+ attributes: {
1946
+ part: "text"
1947
+ },
1948
+ $props: {
1949
+ label: {
1950
+ $ref: "$map/item/label"
1951
+ },
1952
+ value: {
1953
+ $ref: "$map/item/value"
1954
+ },
1955
+ placeholder: {
1956
+ $ref: "$map/item/placeholder"
1957
+ },
1958
+ size: "sm",
1959
+ invalid: {
1960
+ $ref: "$map/item/invalid"
1961
+ },
1962
+ mono: {
1963
+ $ref: "$map/item/mono"
1964
+ },
1965
+ multiline: {
1966
+ $ref: "$map/item/multiline"
1967
+ }
1968
+ },
1969
+ oninput: {
1970
+ $prototype: "Function",
1971
+ body: [
1972
+ {
1973
+ operator: "call",
1974
+ target: {
1975
+ $ref: "#/state/edit"
1976
+ },
1977
+ value: [
1978
+ {
1979
+ $ref: "$map/item/key"
1980
+ },
1981
+ {
1982
+ $ref: "event#/target/value"
1983
+ }
1984
+ ]
1985
+ }
1986
+ ]
1987
+ },
1988
+ onchange: {
1989
+ $prototype: "Function",
1990
+ body: [
1991
+ {
1992
+ operator: "call",
1993
+ target: {
1994
+ $ref: "#/state/commit"
1995
+ },
1996
+ value: [
1997
+ {
1998
+ $ref: "$map/item/key"
1999
+ },
2000
+ {
2001
+ $ref: "event#/target/value"
2002
+ }
2003
+ ]
2004
+ }
2005
+ ]
2006
+ }
2007
+ },
2008
+ template: {
2009
+ $description: "The Mixed text rung: a string with `${…}` holes in it. Monospaced because what is being written is half text and half pointer, and committed on change rather than on input — a half-typed placeholder is not a template anybody meant.",
2010
+ tagName: "jx-textfield",
2011
+ attributes: {
2012
+ part: "template"
2013
+ },
2014
+ $props: {
2015
+ label: {
2016
+ $ref: "$map/item/label"
2017
+ },
2018
+ value: {
2019
+ $ref: "$map/item/value"
2020
+ },
2021
+ placeholder: "${state.templatePlaceholder}",
2022
+ size: "sm",
2023
+ mono: true
2024
+ },
2025
+ onchange: {
2026
+ $prototype: "Function",
2027
+ body: [
2028
+ {
2029
+ operator: "call",
2030
+ target: {
2031
+ $ref: "#/state/commit"
2032
+ },
2033
+ value: [
2034
+ {
2035
+ $ref: "$map/item/key"
2036
+ },
2037
+ {
2038
+ $ref: "event#/target/value"
2039
+ }
2040
+ ]
2041
+ }
2042
+ ]
2043
+ }
2044
+ },
2045
+ pointer: {
2046
+ $description: "The From data… rung. It is TWO controls over one value because the kit has no combobox and this rung needs one: the list is a set of suggestions rather than a whitelist (a value may point at a route param, a signal, or a pointer neither the form nor its host enumerates), so the select offers what the host named and the field accepts anything. Picking a row writes the field; typing a pointer the host never named leaves the select showing it, because a select whose value matches no option synthesises the row rather than lying about what it holds.",
2047
+ tagName: "span",
2048
+ attributes: {
2049
+ part: "pointer-group",
2050
+ role: "none"
2051
+ },
2052
+ children: [
2053
+ {
2054
+ tagName: "jx-textfield",
2055
+ attributes: {
2056
+ part: "pointer"
2057
+ },
2058
+ $props: {
2059
+ label: "${$map.item.label} pointer",
2060
+ value: {
2061
+ $ref: "$map/item/value"
2062
+ },
2063
+ placeholder: "#/state/…",
2064
+ size: "sm",
2065
+ mono: true
2066
+ },
2067
+ onchange: {
2068
+ $prototype: "Function",
2069
+ body: [
2070
+ {
2071
+ operator: "call",
2072
+ target: {
2073
+ $ref: "#/state/commit"
2074
+ },
2075
+ value: [
2076
+ {
2077
+ $ref: "$map/item/key"
2078
+ },
2079
+ {
2080
+ $ref: "event#/target/value"
2081
+ }
2082
+ ]
2083
+ }
2084
+ ]
2085
+ }
2086
+ },
2087
+ {
2088
+ tagName: "span",
2089
+ attributes: {
2090
+ part: "pointer-pick-slot",
2091
+ role: "none"
2092
+ },
2093
+ style: {
2094
+ display: "contents"
2095
+ },
2096
+ $switch: {
2097
+ $ref: "$map/item/hasOptions"
2098
+ },
2099
+ cases: {
2100
+ true: {
2101
+ tagName: "jx-select",
2102
+ attributes: {
2103
+ part: "pointer-pick"
2104
+ },
2105
+ $props: {
2106
+ label: "${$map.item.label} source",
2107
+ options: {
2108
+ $ref: "$map/item/options"
2109
+ },
2110
+ value: {
2111
+ $ref: "$map/item/value"
2112
+ },
2113
+ size: "sm"
2114
+ },
2115
+ onchange: {
2116
+ $prototype: "Function",
2117
+ body: [
2118
+ {
2119
+ operator: "call",
2120
+ target: {
2121
+ $ref: "#/state/commit"
2122
+ },
2123
+ value: [
2124
+ {
2125
+ $ref: "$map/item/key"
2126
+ },
2127
+ {
2128
+ $ref: "event#/target/value"
2129
+ }
2130
+ ]
2131
+ }
2132
+ ]
2133
+ }
2134
+ }
2135
+ }
2136
+ }
2137
+ ]
2138
+ },
2139
+ select: {
2140
+ $description: "A closed set the schema itself declares, or one the host resolved through a `#/$context/…` pointer. The empty row is offered only where the property is optional — a required field has no “none” to choose.",
2141
+ tagName: "jx-select",
2142
+ attributes: {
2143
+ part: "select"
2144
+ },
2145
+ $props: {
2146
+ label: {
2147
+ $ref: "$map/item/label"
2148
+ },
2149
+ options: {
2150
+ $ref: "$map/item/options"
2151
+ },
2152
+ value: {
2153
+ $ref: "$map/item/value"
2154
+ },
2155
+ size: "sm",
2156
+ invalid: {
2157
+ $ref: "$map/item/invalid"
2158
+ }
2159
+ },
2160
+ onchange: {
2161
+ $prototype: "Function",
2162
+ body: [
2163
+ {
2164
+ operator: "call",
2165
+ target: {
2166
+ $ref: "#/state/commit"
2167
+ },
2168
+ value: [
2169
+ {
2170
+ $ref: "$map/item/key"
2171
+ },
2172
+ {
2173
+ $ref: "event#/target/value"
2174
+ }
2175
+ ]
2176
+ }
2177
+ ]
2178
+ }
2179
+ },
2180
+ checkbox: {
2181
+ tagName: "jx-checkbox",
2182
+ attributes: {
2183
+ part: "checkbox"
2184
+ },
2185
+ $props: {
2186
+ label: {
2187
+ $ref: "$map/item/label"
2188
+ },
2189
+ checked: {
2190
+ $ref: "$map/item/checked"
2191
+ },
2192
+ size: "sm"
2193
+ },
2194
+ onchange: {
2195
+ $prototype: "Function",
2196
+ body: [
2197
+ {
2198
+ operator: "call",
2199
+ target: {
2200
+ $ref: "#/state/commitChecked"
2201
+ },
2202
+ value: [
2203
+ {
2204
+ $ref: "$map/item/key"
2205
+ },
2206
+ {
2207
+ $ref: "event#/target/checked"
2208
+ }
2209
+ ]
2210
+ }
2211
+ ]
2212
+ }
2213
+ },
2214
+ number: {
2215
+ $description: "`min`, `max` and `step` come straight off the property schema, so the control refuses out-of-range input before the flow has to say why. The stepper is off: these are configuration values that get typed, not nudged.",
2216
+ tagName: "jx-number-field",
2217
+ attributes: {
2218
+ part: "number"
2219
+ },
2220
+ $props: {
2221
+ label: {
2222
+ $ref: "$map/item/label"
2223
+ },
2224
+ value: {
2225
+ $ref: "$map/item/value"
2226
+ },
2227
+ placeholder: {
2228
+ $ref: "$map/item/placeholder"
2229
+ },
2230
+ min: {
2231
+ $ref: "$map/item/min"
2232
+ },
2233
+ max: {
2234
+ $ref: "$map/item/max"
2235
+ },
2236
+ step: {
2237
+ $ref: "$map/item/step"
2238
+ },
2239
+ size: "sm"
2240
+ },
2241
+ onchange: {
2242
+ $prototype: "Function",
2243
+ body: [
2244
+ {
2245
+ operator: "call",
2246
+ target: {
2247
+ $ref: "#/state/commit"
2248
+ },
2249
+ value: [
2250
+ {
2251
+ $ref: "$map/item/key"
2252
+ },
2253
+ {
2254
+ $ref: "event#/target/value"
2255
+ }
2256
+ ]
2257
+ }
2258
+ ]
2259
+ }
2260
+ },
2261
+ json: {
2262
+ $description: "A value with structure, edited as the JSON it is. The chips above it are a read of the shape currently held — property name and declared type — so the common case (“what is in here?”) is answered without reading the text. The flow parses on a debounce and keeps the last parsable value; a mid-keystroke `{` is not a refusal.",
2263
+ tagName: "span",
2264
+ attributes: {
2265
+ part: "json",
2266
+ role: "none"
2267
+ },
2268
+ children: [
2269
+ {
2270
+ tagName: "span",
2271
+ attributes: {
2272
+ part: "chips-slot",
2273
+ role: "none"
2274
+ },
2275
+ style: {
2276
+ display: "contents"
2277
+ },
2278
+ $switch: {
2279
+ $ref: "$map/item/hasChips"
2280
+ },
2281
+ cases: {
2282
+ true: {
2283
+ tagName: "span",
2284
+ attributes: {
2285
+ part: "chips",
2286
+ role: "none"
2287
+ },
2288
+ children: [
2289
+ {
2290
+ $prototype: "Array",
2291
+ items: {
2292
+ $ref: "$map/item/chips"
2293
+ },
2294
+ key: {
2295
+ $ref: "$map/item/key"
2296
+ },
2297
+ map: {
2298
+ tagName: "span",
2299
+ attributes: {
2300
+ part: "chip"
2301
+ },
2302
+ textContent: "${$map.item.label}"
2303
+ }
2304
+ }
2305
+ ]
2306
+ }
2307
+ }
2308
+ },
2309
+ {
2310
+ tagName: "jx-textfield",
2311
+ attributes: {
2312
+ part: "json-text"
2313
+ },
2314
+ $props: {
2315
+ label: {
2316
+ $ref: "$map/item/label"
2317
+ },
2318
+ value: {
2319
+ $ref: "$map/item/value"
2320
+ },
2321
+ placeholder: {
2322
+ $ref: "$map/item/placeholder"
2323
+ },
2324
+ size: "sm",
2325
+ mono: true,
2326
+ multiline: true,
2327
+ grows: true
2328
+ },
2329
+ oninput: {
2330
+ $prototype: "Function",
2331
+ body: [
2332
+ {
2333
+ operator: "call",
2334
+ target: {
2335
+ $ref: "#/state/editJson"
2336
+ },
2337
+ value: [
2338
+ {
2339
+ $ref: "$map/item/key"
2340
+ },
2341
+ {
2342
+ $ref: "event#/target/value"
2343
+ }
2344
+ ]
2345
+ }
2346
+ ]
2347
+ }
2348
+ }
2349
+ ]
2350
+ },
2351
+ rows: {
2352
+ $description: "An array whose items are objects with a declared shape: one row per item, one cell per declared property, a remove beside each and one add below. The alternative was the raw-JSON editor, which is what an array of anything else still gets — a shape the schema states is a shape the form can draw.",
2353
+ tagName: "span",
2354
+ attributes: {
2355
+ part: "rows",
2356
+ role: "none"
2357
+ },
2358
+ children: [
2359
+ {
2360
+ $prototype: "Array",
2361
+ items: {
2362
+ $ref: "$map/item/rows"
2363
+ },
2364
+ key: {
2365
+ $ref: "$map/item/key"
2366
+ },
2367
+ map: {
2368
+ tagName: "span",
2369
+ attributes: {
2370
+ part: "row",
2371
+ role: "none",
2372
+ "data-row": "${$map.item.key}"
2373
+ },
2374
+ children: [
2375
+ {
2376
+ $prototype: "Array",
2377
+ items: {
2378
+ $ref: "$map/item/cells"
2379
+ },
2380
+ key: {
2381
+ $ref: "$map/item/id"
2382
+ },
2383
+ map: {
2384
+ tagName: "span",
2385
+ attributes: {
2386
+ part: "cell",
2387
+ role: "none"
2388
+ },
2389
+ style: {
2390
+ display: "contents"
2391
+ },
2392
+ $switch: {
2393
+ $ref: "$map/item/kind"
2394
+ },
2395
+ cases: {
2396
+ text: {
2397
+ tagName: "jx-textfield",
2398
+ attributes: {
2399
+ part: "cell-text"
2400
+ },
2401
+ $description: "Committed on input AND on change: a cell showing a pointer is committed when it is left, exactly as the pointer field above is, and a cell showing text follows the canvas while it is typed. The two never disagree — a change after an input carries the same string.",
2402
+ $props: {
2403
+ label: {
2404
+ $ref: "$map/item/label"
2405
+ },
2406
+ value: {
2407
+ $ref: "$map/item/value"
2408
+ },
2409
+ placeholder: {
2410
+ $ref: "$map/item/label"
2411
+ },
2412
+ size: "sm"
2413
+ },
2414
+ oninput: {
2415
+ $prototype: "Function",
2416
+ body: [
2417
+ {
2418
+ operator: "call",
2419
+ target: {
2420
+ $ref: "#/state/editCell"
2421
+ },
2422
+ value: [
2423
+ {
2424
+ $ref: "$map/item/field"
2425
+ },
2426
+ {
2427
+ $ref: "$map/item/row"
2428
+ },
2429
+ {
2430
+ $ref: "$map/item/key"
2431
+ },
2432
+ {
2433
+ $ref: "event#/target/value"
2434
+ }
2435
+ ]
2436
+ }
2437
+ ]
2438
+ },
2439
+ onchange: {
2440
+ $prototype: "Function",
2441
+ body: [
2442
+ {
2443
+ operator: "call",
2444
+ target: {
2445
+ $ref: "#/state/editCell"
2446
+ },
2447
+ value: [
2448
+ {
2449
+ $ref: "$map/item/field"
2450
+ },
2451
+ {
2452
+ $ref: "$map/item/row"
2453
+ },
2454
+ {
2455
+ $ref: "$map/item/key"
2456
+ },
2457
+ {
2458
+ $ref: "event#/target/value"
2459
+ }
2460
+ ]
2461
+ }
2462
+ ]
2463
+ }
2464
+ },
2465
+ select: {
2466
+ tagName: "jx-select",
2467
+ attributes: {
2468
+ part: "cell-select"
2469
+ },
2470
+ $props: {
2471
+ label: {
2472
+ $ref: "$map/item/label"
2473
+ },
2474
+ options: {
2475
+ $ref: "$map/item/options"
2476
+ },
2477
+ value: {
2478
+ $ref: "$map/item/value"
2479
+ },
2480
+ size: "sm"
2481
+ },
2482
+ onchange: {
2483
+ $prototype: "Function",
2484
+ body: [
2485
+ {
2486
+ operator: "call",
2487
+ target: {
2488
+ $ref: "#/state/editCell"
2489
+ },
2490
+ value: [
2491
+ {
2492
+ $ref: "$map/item/field"
2493
+ },
2494
+ {
2495
+ $ref: "$map/item/row"
2496
+ },
2497
+ {
2498
+ $ref: "$map/item/key"
2499
+ },
2500
+ {
2501
+ $ref: "event#/target/value"
2502
+ }
2503
+ ]
2504
+ }
2505
+ ]
2506
+ }
2507
+ },
2508
+ checkbox: {
2509
+ tagName: "jx-checkbox",
2510
+ attributes: {
2511
+ part: "cell-checkbox"
2512
+ },
2513
+ $props: {
2514
+ label: {
2515
+ $ref: "$map/item/label"
2516
+ },
2517
+ checked: {
2518
+ $ref: "$map/item/checked"
2519
+ },
2520
+ size: "sm"
2521
+ },
2522
+ onchange: {
2523
+ $prototype: "Function",
2524
+ body: [
2525
+ {
2526
+ operator: "call",
2527
+ target: {
2528
+ $ref: "#/state/editCellChecked"
2529
+ },
2530
+ value: [
2531
+ {
2532
+ $ref: "$map/item/field"
2533
+ },
2534
+ {
2535
+ $ref: "$map/item/row"
2536
+ },
2537
+ {
2538
+ $ref: "$map/item/key"
2539
+ },
2540
+ {
2541
+ $ref: "event#/target/checked"
2542
+ }
2543
+ ]
2544
+ }
2545
+ ]
2546
+ }
2547
+ },
2548
+ number: {
2549
+ tagName: "jx-number-field",
2550
+ attributes: {
2551
+ part: "cell-number"
2552
+ },
2553
+ $props: {
2554
+ label: {
2555
+ $ref: "$map/item/label"
2556
+ },
2557
+ value: {
2558
+ $ref: "$map/item/value"
2559
+ },
2560
+ size: "sm"
2561
+ },
2562
+ onchange: {
2563
+ $prototype: "Function",
2564
+ body: [
2565
+ {
2566
+ operator: "call",
2567
+ target: {
2568
+ $ref: "#/state/editCell"
2569
+ },
2570
+ value: [
2571
+ {
2572
+ $ref: "$map/item/field"
2573
+ },
2574
+ {
2575
+ $ref: "$map/item/row"
2576
+ },
2577
+ {
2578
+ $ref: "$map/item/key"
2579
+ },
2580
+ {
2581
+ $ref: "event#/target/value"
2582
+ }
2583
+ ]
2584
+ }
2585
+ ]
2586
+ }
2587
+ },
2588
+ control: {
2589
+ $description: "A cell a registered control owns — a relationship to another collection is the one that reaches here. The document draws the empty host and nothing inside it; `ui/schema-form.ts` mounts the control into it, by the same dispatch a FIELD host gets. An extension's control is its own surface rather than part of this one, and the seam between them is this empty node.",
2590
+ tagName: "span",
2591
+ attributes: {
2592
+ part: "cell-control-host",
2593
+ "data-control": "${$map.item.id}",
2594
+ role: "none"
2595
+ }
2596
+ }
2597
+ }
2598
+ }
2599
+ },
2600
+ {
2601
+ tagName: "jx-action-button",
2602
+ attributes: {
2603
+ part: "row-remove"
2604
+ },
2605
+ $props: {
2606
+ icon: "trash",
2607
+ label: "${$map.item.removeLabel}",
2608
+ size: "sm"
2609
+ },
2610
+ onclick: {
2611
+ $prototype: "Function",
2612
+ body: [
2613
+ {
2614
+ operator: "call",
2615
+ target: {
2616
+ $ref: "#/state/removeRow"
2617
+ },
2618
+ value: [
2619
+ {
2620
+ $ref: "$map/item/field"
2621
+ },
2622
+ {
2623
+ $ref: "$map/item/key"
2624
+ }
2625
+ ]
2626
+ }
2627
+ ]
2628
+ }
2629
+ }
2630
+ ]
2631
+ }
2632
+ },
2633
+ {
2634
+ tagName: "jx-action-button",
2635
+ attributes: {
2636
+ part: "row-add"
2637
+ },
2638
+ $props: {
2639
+ icon: "plus",
2640
+ label: "${$map.item.addLabel}",
2641
+ size: "sm"
2642
+ },
2643
+ onclick: {
2644
+ $prototype: "Function",
2645
+ body: [
2646
+ {
2647
+ operator: "call",
2648
+ target: {
2649
+ $ref: "#/state/addRow"
2650
+ },
2651
+ value: [
2652
+ {
2653
+ $ref: "$map/item/key"
2654
+ }
2655
+ ]
2656
+ }
2657
+ ]
2658
+ },
2659
+ children: [
2660
+ {
2661
+ tagName: "span",
2662
+ textContent: "Add"
2663
+ }
2664
+ ]
2665
+ }
2666
+ ]
2667
+ },
2668
+ control: {
2669
+ $description: "A field a registered control owns outright — `schema-builder`, `secret`, or the `reference` picker. The document draws the empty host and the flow MOUNTS the control into it: the registry's contract (specs/extensions.md §9.1) is a control that owns a host, all three built-ins are documents of their own, and a document clears the host it is given — which is why this node is drawn empty and never written to twice.",
2670
+ tagName: "div",
2671
+ attributes: {
2672
+ part: "control-host",
2673
+ "data-control": {
2674
+ $ref: "$map/item/key"
2675
+ },
2676
+ role: "none"
2677
+ }
2678
+ }
2679
+ }
2680
+ }
2681
+ ]
2682
+ },
2683
+ {
2684
+ tagName: "span",
2685
+ attributes: {
2686
+ part: "error-slot",
2687
+ role: "none"
2688
+ },
2689
+ style: {
2690
+ display: "contents"
2691
+ },
2692
+ $switch: {
2693
+ $ref: "$map/item/hasError"
2694
+ },
2695
+ cases: {
2696
+ true: {
2697
+ $description: '§7.1\'s inline tier: why this value is refused, said AT the control that refuses it and for exactly as long as the value stays bad. `role="alert"` so it is announced when it appears, and a repeat counter from two up — “it refused me again” is otherwise indistinguishable from “the field is still red from last time”.',
2698
+ tagName: "p",
2699
+ attributes: {
2700
+ part: "field-error",
2701
+ role: "alert"
2702
+ },
2703
+ children: [
2704
+ {
2705
+ tagName: "span",
2706
+ attributes: {
2707
+ part: "field-error-text"
2708
+ },
2709
+ textContent: "${$map.item.error}"
2710
+ },
2711
+ {
2712
+ tagName: "span",
2713
+ attributes: {
2714
+ part: "error-count-slot",
2715
+ role: "none"
2716
+ },
2717
+ style: {
2718
+ display: "contents"
2719
+ },
2720
+ $switch: {
2721
+ $ref: "$map/item/hasCount"
2722
+ },
2723
+ cases: {
2724
+ true: {
2725
+ tagName: "span",
2726
+ attributes: {
2727
+ part: "field-error-count"
2728
+ },
2729
+ textContent: "${$map.item.errorCount}"
2730
+ }
2731
+ }
2732
+ }
2733
+ ]
2734
+ }
2735
+ }
2736
+ }
2737
+ ]
2738
+ }
3751
2739
  }
3752
- }}
3753
- >
3754
- ${opts.stateDefs.map((name) => html`<sp-menu-item value=${`#/state/${name}`}>${name}</sp-menu-item>`)}
3755
- ${opts.extraSignals?.length ? html`
3756
- <sp-menu-divider></sp-menu-divider>
3757
- ${opts.extraSignals.map((sig) => html`<sp-menu-item value=${sig.value}>${sig.label}</sp-menu-item>`)}
3758
- ` : nothing}
3759
- </sp-picker>
3760
- `;
3761
- }
3762
- function renderTemplateWidget(value, opts) {
3763
- return html`
3764
- <sp-textfield
3765
- size="s"
3766
- style="flex:1;font-family:var(--spectrum-code-font-family, monospace)"
3767
- placeholder="\${state.…}"
3768
- .value=${live(String(value ?? ""))}
3769
- @change=${(e) => opts.onChange(e.target.value)}
3770
- ></sp-textfield>
3771
- `;
3772
- }
3773
- function switchSlotMode(fieldKey, from, to, current, seed) {
3774
- stashSlotValue(fieldKey, from, current);
3775
- setSlotMode(fieldKey, to);
3776
- return hasStashedSlotValue(fieldKey, to) ? recallSlotValue(fieldKey, to) : seed;
3777
- }
3778
- function chipId(fieldKey) {
3779
- return `value-source-${fieldKey.replaceAll(/[^\dA-Za-z]+/g, "-")}`;
3780
- }
3781
- function renderModeChip(mode, caps, opts) {
3782
- const color = mode === "literal" ? "color:var(--spectrum-gray-600, #808080)" : "color:var(--spectrum-accent-content-color-default, #5c9dff)";
3783
- const offered = caps.filter((m) => m !== "ref" || hasRefOptions(opts));
3784
- const label = VALUE_SOURCE_LABELS[mode];
3785
- if (!offered.some((m) => m !== mode)) {
3786
- const hint2 = `Value source: ${label} (no other source available here)`;
3787
- return html`
3788
- <sp-action-button
3789
- size="xs"
3790
- quiet
3791
- disabled
3792
- class="dynamic-slot-mode"
3793
- style=${color}
3794
- title=${hint2}
3795
- aria-label=${hint2}
3796
- >${label}</sp-action-button
3797
- >
3798
- `;
2740
+ ]
2741
+ };
2742
+
2743
+ // src/surfaces/schema-form.ts
2744
+ registerSurface("schema-form", schema_form_default);
2745
+ function controlIdOf(part, state) {
2746
+ const item = state?.["$map"]?.item;
2747
+ const id = part === "control-host" ? item?.["key"] : part === "cell-control-host" ? item?.["id"] : undefined;
2748
+ return typeof id === "string" ? id : "";
2749
+ }
2750
+ function partOf(def) {
2751
+ const part = typeof def === "string" ? undefined : def.attributes?.["part"];
2752
+ return typeof part === "string" ? part : "";
2753
+ }
2754
+ function createSchemaFormSurface(view, actions, onControlHost) {
2755
+ const host = document.createElement("div");
2756
+ host.style.display = "contents";
2757
+ const scope = reactive({
2758
+ ...view,
2759
+ ...actions,
2760
+ templatePlaceholder: "${state.…}"
2761
+ });
2762
+ let handle = null;
2763
+ let mounting = false;
2764
+ let disposed = false;
2765
+ function mount() {
2766
+ mounting = true;
2767
+ mountSurface("schema-form", scope, host, {
2768
+ onNodeCreated: (element, _path, def, state) => {
2769
+ if (!(element instanceof HTMLElement)) {
2770
+ return;
2771
+ }
2772
+ const id = controlIdOf(partOf(def), state);
2773
+ if (id !== "") {
2774
+ onControlHost(id, element);
2775
+ }
2776
+ }
2777
+ }).then((mounted) => {
2778
+ mounting = false;
2779
+ if (disposed) {
2780
+ mounted.dispose();
2781
+ return;
2782
+ }
2783
+ handle = mounted;
2784
+ });
3799
2785
  }
3800
- const id = chipId(opts.fieldKey);
3801
- const hint = `Value source: ${label} — click to change`;
3802
- const choose = (next) => {
3803
- if (next === mode) {
3804
- return;
3805
- }
3806
- if (mode === "ref" && opts.literalDefault !== undefined && !hasStashedSlotValue(opts.fieldKey, "literal")) {
3807
- stashSlotValue(opts.fieldKey, "literal", opts.literalDefault);
3808
- }
3809
- opts.onChange(switchSlotMode(opts.fieldKey, mode, next, cloneValue(opts.value), opts.seedFor?.(next) ?? defaultForSlotMode(next, opts)));
3810
- };
3811
- return html`
3812
- <sp-action-button
3813
- id=${id}
3814
- size="xs"
3815
- quiet
3816
- class="dynamic-slot-mode"
3817
- style=${color}
3818
- title=${hint}
3819
- aria-label=${hint}
3820
- >${label}</sp-action-button
3821
- >
3822
- <sp-overlay trigger="${id}@click" placement="bottom-start" type="auto">
3823
- <sp-popover>
3824
- <sp-menu size="s" label="Value source">
3825
- ${offered.map((m) => html`
3826
- <sp-menu-item data-mode=${m} ?selected=${m === mode} @click=${() => choose(m)}
3827
- >${VALUE_SOURCE_LABELS[m]}
3828
- <span slot="description">${VALUE_SOURCE_HINTS[m]}</span></sp-menu-item
3829
- >
3830
- `)}
3831
- </sp-menu>
3832
- </sp-popover>
3833
- </sp-overlay>
3834
- `;
3835
- }
3836
- function renderDynamicSlot(opts) {
3837
- const caps = slotCaps(opts.caps);
3838
- const mode = effectiveSlotMode(opts.fieldKey, opts.value);
3839
- const widget = mode === "ref" ? renderRefWidget(isRef(opts.value) ? opts.value.$ref : "", opts) : mode === "template" ? renderTemplateWidget(opts.value, opts) : mode === "expression" ? renderExpressionEditor(opts.value.$expression, (node) => opts.onChange({ $expression: node }), {
3840
- allowEventRef: opts.allowEventRef ?? false,
3841
- preview: opts.preview ?? null,
3842
- stateDefs: opts.stateDefs,
3843
- stateEntries: opts.stateEntries ?? null
3844
- }) : opts.staticWidget;
2786
+ mount();
3845
2787
  return {
3846
- modeButton: renderModeChip(mode, caps, opts),
3847
- widget: html`
3848
- <div class="dynamic-slot" style="display:flex;flex:1;min-width:0">
3849
- <div style="flex:1;min-width:0">${widget}</div>
3850
- </div>
3851
- `
2788
+ dispose() {
2789
+ disposed = true;
2790
+ handle?.dispose();
2791
+ handle = null;
2792
+ host.textContent = "";
2793
+ },
2794
+ host,
2795
+ update(next) {
2796
+ scope.fields = next.fields;
2797
+ if (disposed || mounting || handle !== null && host.contains(handle.root)) {
2798
+ return;
2799
+ }
2800
+ handle?.dispose();
2801
+ handle = null;
2802
+ host.textContent = "";
2803
+ mount();
2804
+ }
3852
2805
  };
3853
2806
  }
3854
2807
 
@@ -3857,9 +2810,6 @@ var controlRegistry = new Map;
3857
2810
  function registerFormControl(name, control) {
3858
2811
  controlRegistry.set(name, control);
3859
2812
  }
3860
- function getFormControl(name) {
3861
- return controlRegistry.get(name);
3862
- }
3863
2813
  var CONTENT_REF = /^#\/content\/([^/]+)$/;
3864
2814
  function referenceTarget(schema) {
3865
2815
  const match = typeof schema?.$ref === "string" ? CONTENT_REF.exec(schema.$ref) : null;
@@ -3874,9 +2824,9 @@ function resolveFormEnum(enumDef, ctx, scope) {
3874
2824
  }
3875
2825
  let pointer;
3876
2826
  if (enumDef && typeof enumDef === "object") {
3877
- const ref2 = enumDef.$ref;
3878
- if (typeof ref2 === "string") {
3879
- pointer = ref2;
2827
+ const ref = enumDef.$ref;
2828
+ if (typeof ref === "string") {
2829
+ pointer = ref;
3880
2830
  }
3881
2831
  } else if (typeof enumDef === "string") {
3882
2832
  pointer = enumDef;
@@ -3899,258 +2849,6 @@ function parseNumericField(raw, integer) {
3899
2849
  }
3900
2850
  return integer ? Math.trunc(Number(raw)) : Number(raw);
3901
2851
  }
3902
- function refTextField(key, refVal, onChange) {
3903
- return html`<sp-textfield
3904
- size="s"
3905
- label=${key}
3906
- placeholder=${key}
3907
- .value=${live(refVal)}
3908
- @change=${(e) => {
3909
- const v = e.target.value.trim();
3910
- onChange(v ? { $ref: v } : undefined);
3911
- }}
3912
- ></sp-textfield>`;
3913
- }
3914
- function renderInlineField(key, schema, value, onChange, ctx, scope, rerender) {
3915
- if (isRef(value)) {
3916
- return refTextField(key, value.$ref, onChange);
3917
- }
3918
- const referenceControl = referenceTarget(schema) === null ? undefined : controlRegistry.get("reference");
3919
- if (referenceControl) {
3920
- return referenceControl({
3921
- ctx: ctx ?? NULL_FORM_CONTEXT,
3922
- key,
3923
- onChange,
3924
- rerender,
3925
- schema,
3926
- value
3927
- });
3928
- }
3929
- const enumValues = resolveFormEnum(schema.enum, ctx, scope);
3930
- if (enumValues) {
3931
- return html`<sp-picker
3932
- size="s"
3933
- label=${key}
3934
- value=${value !== undefined ? String(value) : "__none__"}
3935
- @change=${(e) => onChange(e.target.value === "__none__" ? undefined : e.target.value)}
3936
- >
3937
- <sp-menu-item value="__none__">—</sp-menu-item>
3938
- ${enumValues.map((v) => html`<sp-menu-item value=${v}>${v}</sp-menu-item>`)}
3939
- </sp-picker>`;
3940
- }
3941
- if (schema.type === "boolean") {
3942
- return html`<sp-switch
3943
- size="s"
3944
- ?checked=${Boolean(value)}
3945
- @change=${(e) => onChange(e.target.checked)}
3946
- >${key}</sp-switch
3947
- >`;
3948
- }
3949
- if (schema.type === "integer" || schema.type === "number") {
3950
- return html`<sp-number-field
3951
- size="s"
3952
- label=${key}
3953
- .value=${value !== undefined ? value : nothing}
3954
- step=${schema.type === "integer" ? "1" : nothing}
3955
- @change=${(e) => {
3956
- const parsed = parseNumericField(e.target.value, schema.type === "integer");
3957
- onChange(Number.isNaN(parsed) ? undefined : parsed);
3958
- }}
3959
- ></sp-number-field>`;
3960
- }
3961
- return html`<sp-textfield
3962
- size="s"
3963
- label=${key}
3964
- placeholder=${key}
3965
- .value=${value ?? ""}
3966
- @input=${(e) => onChange(e.target.value || undefined)}
3967
- ></sp-textfield>`;
3968
- }
3969
- function renderJsonTextField(currentValue, ps, commit) {
3970
- let debounce;
3971
- return html`<sp-textfield
3972
- multiline
3973
- size="s"
3974
- style="min-height:40px"
3975
- .value=${currentValue !== undefined ? JSON.stringify(currentValue, null, 2) : ""}
3976
- placeholder=${ps.default !== undefined ? JSON.stringify(ps.default) : nothing}
3977
- @input=${(e) => {
3978
- clearTimeout(debounce);
3979
- debounce = setTimeout(() => {
3980
- try {
3981
- commit(JSON.parse(e.target.value));
3982
- } catch {}
3983
- }, 500);
3984
- }}
3985
- ></sp-textfield>`;
3986
- }
3987
- function renderPropertyControl(prop, ps, value, required, opts, ctx) {
3988
- const currentValue = value[prop];
3989
- const commit = (next) => opts.onChange({ [prop]: next });
3990
- const controlArgs = {
3991
- ctx,
3992
- key: prop,
3993
- onChange: commit,
3994
- rerender: opts.rerender,
3995
- schema: ps,
3996
- value: currentValue
3997
- };
3998
- const overrideName = opts.ui?.[prop]?.control;
3999
- if (overrideName) {
4000
- const custom = controlRegistry.get(overrideName);
4001
- if (custom) {
4002
- return custom(controlArgs);
4003
- }
4004
- }
4005
- if (isRef(currentValue) && isBindableField(ps)) {
4006
- return refTextField(prop, currentValue.$ref, commit);
4007
- }
4008
- if (referenceTarget(ps) !== null) {
4009
- const referenceControl = controlRegistry.get("reference");
4010
- if (referenceControl) {
4011
- return referenceControl(controlArgs);
4012
- }
4013
- }
4014
- const enumValues = resolveFormEnum(opts.ui?.[prop]?.enum ?? ps.enum, ctx, value);
4015
- if (enumValues) {
4016
- return html`
4017
- <sp-picker
4018
- size="s"
4019
- value=${currentValue !== undefined ? String(currentValue) : ps.default !== undefined ? String(ps.default) : "__none__"}
4020
- @change=${(e) => commit(e.target.value === "__none__" ? undefined : e.target.value)}
4021
- >
4022
- ${!required.has(prop) ? html`<sp-menu-item value="__none__">—</sp-menu-item>` : nothing}
4023
- ${enumValues.map((val) => html`<sp-menu-item value=${val}>${val}</sp-menu-item>`)}
4024
- </sp-picker>
4025
- `;
4026
- }
4027
- if (ps.type === "boolean") {
4028
- return html`<sp-checkbox
4029
- ?checked=${currentValue ?? ps.default ?? false}
4030
- @change=${(e) => commit(e.target.checked)}
4031
- ></sp-checkbox>`;
4032
- }
4033
- if (ps.type === "integer" || ps.type === "number") {
4034
- let debounce2;
4035
- return html`<sp-number-field
4036
- size="s"
4037
- min=${ifDefined(ps.minimum)}
4038
- max=${ifDefined(ps.maximum)}
4039
- step=${ps.type === "integer" ? "1" : nothing}
4040
- .value=${currentValue !== undefined ? currentValue : nothing}
4041
- placeholder=${ps.default != null ? String(ps.default) : nothing}
4042
- @change=${(e) => {
4043
- clearTimeout(debounce2);
4044
- debounce2 = setTimeout(() => {
4045
- const parsed = parseNumericField(e.target.value, ps.type === "integer");
4046
- commit(Number.isNaN(parsed) ? undefined : parsed);
4047
- }, 400);
4048
- }}
4049
- ></sp-number-field>`;
4050
- }
4051
- if (ps.format === "json-schema") {
4052
- const hasValue = currentValue && typeof currentValue === "object" && Object.keys(currentValue).length > 0;
4053
- const cv = currentValue;
4054
- const isSchemaRef = hasValue && cv.$ref;
4055
- let debounce2;
4056
- return html`
4057
- <div class="schema-param-editor">
4058
- ${hasValue && !isSchemaRef && cv.properties ? html`
4059
- <div style="display:flex;flex-wrap:wrap;gap:3px;margin-bottom:4px">
4060
- ${Object.entries(cv.properties).map(([k, v]) => html`
4061
- <span
4062
- style="background:var(--bg);padding:1px 6px;border-radius:var(--radius);font-size:10px;color:var(--fg-dim)"
4063
- >${k}: ${v.type ?? "any"}</span
4064
- >
4065
- `)}
4066
- </div>
4067
- ` : nothing}
4068
- <sp-textfield
4069
- multiline
4070
- size="s"
4071
- style=${styleMap({
4072
- fontFamily: "monospace",
4073
- fontSize: "11px",
4074
- minHeight: hasValue ? "80px" : "40px"
4075
- })}
4076
- .value=${currentValue !== undefined ? JSON.stringify(currentValue, null, 2) : ""}
4077
- placeholder=${ps.description ?? "JSON Schema defining the data shape…"}
4078
- @input=${(e) => {
4079
- clearTimeout(debounce2);
4080
- debounce2 = setTimeout(() => {
4081
- try {
4082
- commit(JSON.parse(e.target.value));
4083
- } catch {}
4084
- }, 500);
4085
- }}
4086
- ></sp-textfield>
4087
- </div>
4088
- `;
4089
- }
4090
- if (ps.type === "array" && ps.items?.type === "object" && ps.items?.properties) {
4091
- const rows = Array.isArray(currentValue) ? currentValue : [];
4092
- const itemProps = ps.items.properties;
4093
- return html`
4094
- <div class="array-object-field">
4095
- ${rows.map((row, idx) => html`
4096
- <div
4097
- class="array-object-row"
4098
- style="display:flex;gap:4px;align-items:center;margin-bottom:4px"
4099
- >
4100
- ${Object.entries(itemProps).map(([propKey, propSchema]) => renderInlineField(propKey, propSchema, row[propKey], (val) => {
4101
- const updated = [...rows];
4102
- updated[idx] = { ...updated[idx], [propKey]: val };
4103
- commit(updated);
4104
- }, ctx, value, opts.rerender))}
4105
- <sp-action-button
4106
- quiet
4107
- size="s"
4108
- @click=${() => {
4109
- const updated = rows.filter((_, i) => i !== idx);
4110
- commit(updated.length > 0 ? updated : undefined);
4111
- opts.rerender?.();
4112
- }}
4113
- >
4114
- <sp-icon-delete slot="icon"></sp-icon-delete>
4115
- </sp-action-button>
4116
- </div>
4117
- `)}
4118
- <sp-action-button
4119
- quiet
4120
- size="s"
4121
- @click=${(e) => {
4122
- e.stopPropagation();
4123
- const newRow = {};
4124
- for (const [k, v] of Object.entries(itemProps)) {
4125
- if (v.default !== undefined) {
4126
- newRow[k] = v.default;
4127
- }
4128
- }
4129
- commit([...rows, newRow]);
4130
- opts.rerender?.();
4131
- }}
4132
- >+ Add</sp-action-button
4133
- >
4134
- </div>
4135
- `;
4136
- }
4137
- if (ps.type === "array" || ps.type === "object") {
4138
- return renderJsonTextField(currentValue, ps, commit);
4139
- }
4140
- let debounce;
4141
- const ph = ps.default !== undefined ? String(ps.default) : ps.examples?.[0] ?? "";
4142
- return html`<sp-textfield
4143
- size="s"
4144
- style="flex:1"
4145
- .value=${currentValue ?? ""}
4146
- placeholder=${ph || nothing}
4147
- title=${ps.description || nothing}
4148
- @input=${(e) => {
4149
- clearTimeout(debounce);
4150
- debounce = setTimeout(() => commit(e.target.value || undefined), 400);
4151
- }}
4152
- ></sp-textfield>`;
4153
- }
4154
2852
  function isBindableField(ps) {
4155
2853
  return ps.format !== "json-schema" && ps.type !== "object" && ps.type !== "array";
4156
2854
  }
@@ -4160,8 +2858,15 @@ function refSourcesFor(ctx) {
4160
2858
  ...(ctx.params ?? []).map((name) => ({ label: `$params/${name}`, value: `#/$params/${name}` }))
4161
2859
  ];
4162
2860
  }
2861
+ var NONE = "__none__";
2862
+ function asText(value) {
2863
+ if (value === undefined || value === null) {
2864
+ return "";
2865
+ }
2866
+ return typeof value === "object" ? JSON.stringify(value, null, 2) : String(value);
2867
+ }
4163
2868
  function validateFieldValue(schema, value, isRequired) {
4164
- if (isRef(value)) {
2869
+ if (isRef2(value)) {
4165
2870
  return "";
4166
2871
  }
4167
2872
  const empty = value === undefined || value === null || value === "";
@@ -4198,39 +2903,603 @@ function validateFieldValue(schema, value, isRequired) {
4198
2903
  }
4199
2904
  return "";
4200
2905
  }
4201
- function renderForm(schema, value, opts) {
4202
- const required = new Set(schema.required);
4203
- const ctx = opts.context ?? NULL_FORM_CONTEXT;
4204
- const refSources = refSourcesFor(ctx);
4205
- const propertyFields = Object.entries(schema.properties ?? {}).map(([prop, ps]) => {
4206
- const labelText = prop + (required.has(prop) ? " *" : "");
4207
- const error = opts.errors?.[prop] || validateFieldValue(ps, value[prop], Boolean(opts.showRequired) && required.has(prop));
4208
- const count = opts.errorCounts?.[prop];
4209
- const staticWidget = renderPropertyControl(prop, ps, value, required, opts, ctx);
4210
- const laddered = refSources.length > 0 && isBindableField(ps) && !opts.ui?.[prop]?.control ? renderDynamicSlot({
4211
- allowCustomRef: true,
4212
- caps: { schema: configFieldSchema(ps) },
4213
- extraSignals: refSources,
4214
- fieldKey: `${ctx.fieldKeyPrefix ?? ""}.${prop}`,
4215
- onChange: (v) => opts.onChange({ [prop]: v }),
4216
- staticWidget,
4217
- stateDefs: [],
4218
- value: value[prop]
4219
- }) : null;
4220
- return renderFieldRow({
4221
- hasValue: false,
4222
- label: labelText,
4223
- prop: ps.name || prop,
4224
- widget: laddered ? laddered.widget : staticWidget,
4225
- ...laddered ? { labelExtra: laddered.modeButton } : {},
4226
- ...error ? { error } : {},
4227
- ...count === undefined ? {} : { errorCount: count }
2906
+ var JSON_COMMIT_MS = 500;
2907
+ var TEXT_COMMIT_MS = 400;
2908
+ var forms = new Map;
2909
+ function mountSchemaForm(formKey, schema, value, opts) {
2910
+ sweep(formKey);
2911
+ let controller = forms.get(formKey);
2912
+ if (!controller) {
2913
+ controller = createController();
2914
+ forms.set(formKey, controller);
2915
+ }
2916
+ controller.update(schema, value, opts);
2917
+ return controller.host;
2918
+ }
2919
+ function sweep(exceptKey) {
2920
+ for (const [key, controller] of forms) {
2921
+ if (key !== exceptKey && !controller.host.isConnected) {
2922
+ controller.dispose();
2923
+ forms.delete(key);
2924
+ }
2925
+ }
2926
+ }
2927
+ function createController() {
2928
+ let schema = {};
2929
+ let value = {};
2930
+ let opts = { onChange: () => {} };
2931
+ let ctx = NULL_FORM_CONTEXT;
2932
+ const plans = new Map;
2933
+ const timers = new Map;
2934
+ const controlHosts = new Map;
2935
+ const mountedControls = new Map;
2936
+ let sourceMenu = null;
2937
+ const later = (key, ms, run) => {
2938
+ clearTimeout(timers.get(key));
2939
+ if (ms <= 0) {
2940
+ run();
2941
+ return;
2942
+ }
2943
+ timers.set(key, setTimeout(run, ms));
2944
+ };
2945
+ const now = (key, run) => {
2946
+ clearTimeout(timers.get(key));
2947
+ timers.delete(key);
2948
+ run();
2949
+ };
2950
+ const patch = (prop, next) => {
2951
+ opts.onChange({ [prop]: next });
2952
+ };
2953
+ const write = (plan, raw) => {
2954
+ if (plan.commit === "none") {
2955
+ return;
2956
+ }
2957
+ switch (plan.commit) {
2958
+ case "ref": {
2959
+ const trimmed = raw.trim();
2960
+ patch(plan.prop, trimmed ? { $ref: trimmed } : undefined);
2961
+ return;
2962
+ }
2963
+ case "template": {
2964
+ patch(plan.prop, raw);
2965
+ return;
2966
+ }
2967
+ case "select": {
2968
+ patch(plan.prop, raw === NONE ? undefined : raw);
2969
+ return;
2970
+ }
2971
+ case "number": {
2972
+ const parsed = parseNumericField(raw, plan.schema.type === "integer");
2973
+ patch(plan.prop, Number.isNaN(parsed) ? undefined : parsed);
2974
+ return;
2975
+ }
2976
+ case "json": {
2977
+ try {
2978
+ patch(plan.prop, JSON.parse(raw));
2979
+ } catch {}
2980
+ return;
2981
+ }
2982
+ default: {
2983
+ patch(plan.prop, raw || undefined);
2984
+ }
2985
+ }
2986
+ };
2987
+ const cellRows = (plan) => {
2988
+ const copies = [];
2989
+ for (const item of plan.rows ?? []) {
2990
+ copies.push({ ...item });
2991
+ }
2992
+ return copies;
2993
+ };
2994
+ const actions = {
2995
+ addRow: (field) => {
2996
+ const plan = plans.get(field);
2997
+ if (!plan?.itemProps) {
2998
+ return;
2999
+ }
3000
+ const seed = {};
3001
+ for (const [k, v] of Object.entries(plan.itemProps)) {
3002
+ if (v.default !== undefined) {
3003
+ seed[k] = v.default;
3004
+ }
3005
+ }
3006
+ patch(field, [...cellRows(plan), seed]);
3007
+ opts.rerender?.();
3008
+ },
3009
+ commit: (key, raw) => {
3010
+ const plan = plans.get(key);
3011
+ if (plan) {
3012
+ now(key, () => write(plan, raw));
3013
+ }
3014
+ },
3015
+ commitChecked: (key, checked) => {
3016
+ const plan = plans.get(key);
3017
+ if (plan) {
3018
+ patch(plan.prop, checked);
3019
+ }
3020
+ },
3021
+ edit: (key, raw) => {
3022
+ const plan = plans.get(key);
3023
+ if (plan && plan.debounceMs > 0) {
3024
+ later(key, plan.debounceMs, () => write(plan, raw));
3025
+ }
3026
+ },
3027
+ editCell: (field, row, cell, raw) => {
3028
+ writeCell(field, row, cell, raw);
3029
+ },
3030
+ editCellChecked: (field, row, cell, checked) => {
3031
+ writeCell(field, row, cell, checked);
3032
+ },
3033
+ editJson: (key, raw) => {
3034
+ const plan = plans.get(key);
3035
+ if (plan) {
3036
+ later(key, JSON_COMMIT_MS, () => write(plan, raw));
3037
+ }
3038
+ },
3039
+ pickSource: (key, anchor) => {
3040
+ openSourceMenu(key, anchor);
3041
+ },
3042
+ removeRow: (field, row) => {
3043
+ const plan = plans.get(field);
3044
+ if (!plan) {
3045
+ return;
3046
+ }
3047
+ const idx = Number(row);
3048
+ const kept = cellRows(plan).filter((_, i) => i !== idx);
3049
+ patch(field, kept.length > 0 ? kept : undefined);
3050
+ opts.rerender?.();
3051
+ }
3052
+ };
3053
+ function writeCell(field, row, cell, next) {
3054
+ const plan = plans.get(field);
3055
+ if (!plan?.itemProps) {
3056
+ return;
3057
+ }
3058
+ const idx = Number(row);
3059
+ const rows = cellRows(plan);
3060
+ const target = rows[idx];
3061
+ if (!target) {
3062
+ return;
3063
+ }
3064
+ const cellSchema = plan.itemProps[cell];
3065
+ rows[idx] = {
3066
+ ...target,
3067
+ [cell]: coerceCell(cellSchema, next, plan.refCells?.has(`${field}/${row}/${cell}`) ?? false)
3068
+ };
3069
+ patch(field, rows);
3070
+ }
3071
+ function coerceCell(cellSchema, next, isPointer) {
3072
+ if (typeof next === "boolean") {
3073
+ return next;
3074
+ }
3075
+ const raw = String(next);
3076
+ if (isPointer) {
3077
+ const trimmed = raw.trim();
3078
+ return trimmed ? { $ref: trimmed } : undefined;
3079
+ }
3080
+ if (cellSchema?.type === "integer" || cellSchema?.type === "number") {
3081
+ const parsed = parseNumericField(raw, cellSchema.type === "integer");
3082
+ return Number.isNaN(parsed) ? undefined : parsed;
3083
+ }
3084
+ if (raw === NONE || raw === "") {
3085
+ return;
3086
+ }
3087
+ return raw;
3088
+ }
3089
+ function openSourceMenu(key, anchor) {
3090
+ const plan = plans.get(key);
3091
+ if (!plan?.ladder) {
3092
+ return;
3093
+ }
3094
+ const { fieldKey, mode, offered, sources } = plan.ladder;
3095
+ sourceMenu?.close();
3096
+ sourceMenu = openMenu2({
3097
+ label: "Value source",
3098
+ onClosed: () => {
3099
+ sourceMenu = null;
3100
+ },
3101
+ opener: anchor,
3102
+ region: "value-source",
3103
+ rows: offered.map((rung) => ({
3104
+ checked: rung === mode ? "true" : "false",
3105
+ destructive: false,
3106
+ disabled: false,
3107
+ dividerAbove: false,
3108
+ id: rung,
3109
+ title: VALUE_SOURCE_LABELS[rung]
3110
+ })),
3111
+ run: (id) => {
3112
+ const next = id;
3113
+ if (next === mode) {
3114
+ return;
3115
+ }
3116
+ const literalDefault = plan.schema.default;
3117
+ if (mode === "ref" && literalDefault !== undefined && !hasStashedSlotValue(fieldKey, "literal")) {
3118
+ stashSlotValue(fieldKey, "literal", literalDefault);
3119
+ }
3120
+ patch(plan.prop, switchSlotMode(fieldKey, mode, next, cloneValue2(value[plan.prop]), slotModeSeed(next, { extraSignals: sources, literalDefault, stateDefs: [] })));
3121
+ }
4228
3122
  });
3123
+ }
3124
+ function controlFor(id) {
3125
+ const plan = plans.get(id);
3126
+ if (plan?.control) {
3127
+ return {
3128
+ args: {
3129
+ ctx,
3130
+ key: plan.prop,
3131
+ onChange: (next) => patch(plan.prop, next),
3132
+ rerender: opts.rerender,
3133
+ schema: plan.schema,
3134
+ value: value[plan.prop]
3135
+ },
3136
+ control: plan.control
3137
+ };
3138
+ }
3139
+ for (const owner of plans.values()) {
3140
+ const entry = owner.cellControls?.get(id);
3141
+ if (!entry) {
3142
+ continue;
3143
+ }
3144
+ const row = (owner.rows ?? [])[Number(entry.row)] ?? {};
3145
+ return {
3146
+ args: {
3147
+ ctx,
3148
+ key: entry.key,
3149
+ onChange: (next) => writeCell(owner.prop, entry.row, entry.key, next),
3150
+ rerender: opts.rerender,
3151
+ schema: entry.schema,
3152
+ value: row[entry.key]
3153
+ },
3154
+ control: entry.control
3155
+ };
3156
+ }
3157
+ return;
3158
+ }
3159
+ function paintControl(id, host) {
3160
+ const found = controlFor(id);
3161
+ if (!found) {
3162
+ return;
3163
+ }
3164
+ const standing = mountedControls.get(id);
3165
+ if (standing?.host === host) {
3166
+ standing.handle.update(found.args);
3167
+ return;
3168
+ }
3169
+ standing?.handle.dispose();
3170
+ mountedControls.set(id, { handle: found.control.mount(host, found.args), host });
3171
+ }
3172
+ function sweepMountedControls() {
3173
+ for (const [id, standing] of mountedControls) {
3174
+ if (!controlFor(id)) {
3175
+ standing.handle.dispose();
3176
+ mountedControls.delete(id);
3177
+ }
3178
+ }
3179
+ }
3180
+ function paintControls() {
3181
+ sweepMountedControls();
3182
+ for (const [id, host] of controlHosts) {
3183
+ paintControl(id, host);
3184
+ }
3185
+ }
3186
+ const surface = createSchemaFormSurface({ fields: [] }, actions, (id, host) => {
3187
+ controlHosts.set(id, host);
3188
+ paintControl(id, host);
4229
3189
  });
4230
- return html`${propertyFields}`;
3190
+ return {
3191
+ dispose() {
3192
+ for (const timer of timers.values()) {
3193
+ clearTimeout(timer);
3194
+ }
3195
+ timers.clear();
3196
+ sourceMenu?.close();
3197
+ sourceMenu = null;
3198
+ for (const standing of mountedControls.values()) {
3199
+ standing.handle.dispose();
3200
+ }
3201
+ mountedControls.clear();
3202
+ controlHosts.clear();
3203
+ plans.clear();
3204
+ surface.dispose();
3205
+ },
3206
+ host: surface.host,
3207
+ update(nextSchema, nextValue, nextOpts) {
3208
+ schema = nextSchema;
3209
+ value = nextValue;
3210
+ opts = nextOpts;
3211
+ ctx = nextOpts.context ?? NULL_FORM_CONTEXT;
3212
+ plans.clear();
3213
+ surface.update({ fields: deriveFields(schema, value, opts, ctx, plans) });
3214
+ paintControls();
3215
+ }
3216
+ };
3217
+ }
3218
+ function deriveFields(schema, value, opts, ctx, plans) {
3219
+ const required = new Set(schema.required);
3220
+ const sources = refSourcesFor(ctx);
3221
+ return Object.entries(schema.properties ?? {}).map(([prop, ps]) => deriveField(prop, ps, {
3222
+ ctx,
3223
+ opts,
3224
+ plans,
3225
+ required: required.has(prop),
3226
+ sources,
3227
+ value
3228
+ }));
3229
+ }
3230
+ function deriveField(prop, ps, args) {
3231
+ const { ctx, opts, plans, required, sources, value } = args;
3232
+ const current = value[prop];
3233
+ const plan = { commit: "text", debounceMs: TEXT_COMMIT_MS, prop, schema: ps };
3234
+ plans.set(prop, plan);
3235
+ const error = opts.errors?.[prop] || validateFieldValue(ps, current, Boolean(opts.showRequired) && required);
3236
+ const count = opts.errorCounts?.[prop];
3237
+ const overrideName = opts.ui?.[prop]?.control;
3238
+ const override = overrideName ? controlRegistry.get(overrideName) : undefined;
3239
+ const laddered = sources.length > 0 && isBindableField(ps) && !overrideName;
3240
+ const row = {
3241
+ addLabel: `Add ${prop}`,
3242
+ checked: false,
3243
+ chips: [],
3244
+ description: ps.description ?? "",
3245
+ error,
3246
+ errorCount: count !== undefined && count > 1 ? `×${count}` : "",
3247
+ hasChip: false,
3248
+ hasChips: false,
3249
+ hasCount: count !== undefined && count > 1,
3250
+ hasError: error !== "",
3251
+ hasOptions: false,
3252
+ invalid: error !== "",
3253
+ key: prop,
3254
+ kind: "text",
3255
+ label: prop,
3256
+ max: "",
3257
+ min: "",
3258
+ mono: false,
3259
+ multiline: false,
3260
+ options: [],
3261
+ placeholder: "",
3262
+ prop: ps.name || prop,
3263
+ required,
3264
+ rows: [],
3265
+ source: "fixed",
3266
+ sourceHint: "",
3267
+ sourceLabel: "",
3268
+ sourceLocked: false,
3269
+ sourceName: "",
3270
+ step: "",
3271
+ value: ""
3272
+ };
3273
+ if (laddered) {
3274
+ const fieldKey = `${ctx.fieldKeyPrefix ?? ""}.${prop}`;
3275
+ const mode = effectiveSlotMode(fieldKey, current);
3276
+ const offered = slotCaps({ schema: configFieldSchema(ps) });
3277
+ plan.ladder = { fieldKey, mode, offered, sources };
3278
+ row.hasChip = true;
3279
+ row.sourceLabel = VALUE_SOURCE_LABELS[mode];
3280
+ row.sourceHint = VALUE_SOURCE_HINTS[mode];
3281
+ row.source = mode === "literal" ? "fixed" : "bound";
3282
+ row.sourceLocked = !offered.some((m) => m !== mode);
3283
+ row.sourceName = row.sourceLocked ? `Value source: ${row.sourceLabel} (no other source available here)` : `Value source: ${row.sourceLabel} — click to change`;
3284
+ if (mode === "ref") {
3285
+ plan.commit = "ref";
3286
+ plan.debounceMs = 0;
3287
+ row.kind = "pointer";
3288
+ row.value = isRef2(current) ? current.$ref : "";
3289
+ row.options = sources.map((s) => ({ label: s.label, value: s.value }));
3290
+ row.hasOptions = row.options.length > 0;
3291
+ return row;
3292
+ }
3293
+ if (mode === "template") {
3294
+ plan.commit = "template";
3295
+ plan.debounceMs = 0;
3296
+ row.kind = "template";
3297
+ row.value = String(current ?? "");
3298
+ return row;
3299
+ }
3300
+ }
3301
+ if (override) {
3302
+ plan.commit = "none";
3303
+ plan.control = override;
3304
+ row.kind = "control";
3305
+ return row;
3306
+ }
3307
+ if (isRef2(current) && isBindableField(ps)) {
3308
+ plan.commit = "ref";
3309
+ plan.debounceMs = 0;
3310
+ row.kind = "text";
3311
+ row.mono = true;
3312
+ row.value = current.$ref;
3313
+ row.placeholder = prop;
3314
+ return row;
3315
+ }
3316
+ if (referenceTarget(ps) !== null) {
3317
+ const reference = controlRegistry.get("reference");
3318
+ if (reference) {
3319
+ plan.commit = "none";
3320
+ plan.control = reference;
3321
+ row.kind = "control";
3322
+ return row;
3323
+ }
3324
+ }
3325
+ const enumValues = resolveFormEnum(opts.ui?.[prop]?.enum ?? ps.enum, ctx, value);
3326
+ if (enumValues) {
3327
+ plan.commit = "select";
3328
+ plan.debounceMs = 0;
3329
+ row.kind = "select";
3330
+ row.value = current !== undefined ? String(current) : ps.default !== undefined ? String(ps.default) : NONE;
3331
+ row.options = [
3332
+ ...required ? [] : [{ label: "—", value: NONE }],
3333
+ ...enumValues.map((v) => ({ label: v, value: v }))
3334
+ ];
3335
+ return row;
3336
+ }
3337
+ if (ps.type === "boolean") {
3338
+ plan.commit = "none";
3339
+ row.kind = "checkbox";
3340
+ row.checked = Boolean(current ?? ps.default ?? false);
3341
+ return row;
3342
+ }
3343
+ if (ps.type === "integer" || ps.type === "number") {
3344
+ plan.commit = "number";
3345
+ plan.debounceMs = 0;
3346
+ row.kind = "number";
3347
+ row.value = current !== undefined ? String(current) : "";
3348
+ row.placeholder = ps.default != null ? String(ps.default) : "";
3349
+ row.min = ps.minimum !== undefined ? String(ps.minimum) : "";
3350
+ row.max = ps.maximum !== undefined ? String(ps.maximum) : "";
3351
+ row.step = ps.type === "integer" ? "1" : "";
3352
+ return row;
3353
+ }
3354
+ if (ps.format === "json-schema") {
3355
+ plan.commit = "json";
3356
+ row.kind = "json";
3357
+ row.value = current !== undefined ? JSON.stringify(current, null, 2) : "";
3358
+ row.placeholder = ps.description ?? "JSON Schema defining the data shape…";
3359
+ row.chips = shapeChips(current);
3360
+ row.hasChips = row.chips.length > 0;
3361
+ return row;
3362
+ }
3363
+ if (ps.type === "array" && ps.items?.type === "object" && ps.items.properties) {
3364
+ return arrayOfObjects(prop, ps.items.properties, args, plan, row);
3365
+ }
3366
+ if (ps.type === "array" || ps.type === "object") {
3367
+ plan.commit = "json";
3368
+ row.kind = "json";
3369
+ row.value = current !== undefined ? JSON.stringify(current, null, 2) : "";
3370
+ row.placeholder = ps.default !== undefined ? JSON.stringify(ps.default) : "";
3371
+ return row;
3372
+ }
3373
+ row.kind = "text";
3374
+ row.value = asText(current);
3375
+ row.placeholder = ps.default !== undefined ? String(ps.default) : ps.examples?.[0] ?? "";
3376
+ return row;
3377
+ }
3378
+ function shapeChips(current) {
3379
+ if (!current || typeof current !== "object") {
3380
+ return [];
3381
+ }
3382
+ const held = current;
3383
+ if (held.$ref || typeof held.properties !== "object" || held.properties === null) {
3384
+ return [];
3385
+ }
3386
+ return Object.entries(held.properties).map(([key, spec]) => ({ key, label: `${key}: ${String(spec.type ?? "any")}` }));
3387
+ }
3388
+ function arrayOfObjects(prop, itemProps, args, plan, row) {
3389
+ const held = args.value[prop];
3390
+ const rows = Array.isArray(held) ? held : [];
3391
+ plan.commit = "none";
3392
+ plan.itemProps = itemProps;
3393
+ plan.rows = rows;
3394
+ plan.cellControls = new Map;
3395
+ plan.refCells = new Set;
3396
+ row.kind = "rows";
3397
+ row.rows = rows.map((item, idx) => deriveRow(prop, String(idx), item, itemProps, args, plan));
3398
+ return row;
3399
+ }
3400
+ function deriveRow(prop, key, item, itemProps, args, plan) {
3401
+ return {
3402
+ cells: Object.entries(itemProps).map(([cellKey, cellSchema]) => deriveCell(prop, key, cellKey, cellSchema, item[cellKey], args, plan)),
3403
+ field: prop,
3404
+ key,
3405
+ removeLabel: `Remove ${prop} ${Number(key) + 1}`
3406
+ };
3407
+ }
3408
+ function deriveCell(prop, rowKey, cellKey, cellSchema, held, args, plan) {
3409
+ const id = `${prop}/${rowKey}/${cellKey}`;
3410
+ const cell = {
3411
+ checked: false,
3412
+ field: prop,
3413
+ id,
3414
+ key: cellKey,
3415
+ kind: "text",
3416
+ label: cellKey,
3417
+ options: [],
3418
+ row: rowKey,
3419
+ value: ""
3420
+ };
3421
+ if (isRef2(held)) {
3422
+ cell.value = held.$ref;
3423
+ plan.refCells?.add(id);
3424
+ return cell;
3425
+ }
3426
+ if (referenceTarget(cellSchema) !== null) {
3427
+ const reference = controlRegistry.get("reference");
3428
+ if (reference) {
3429
+ cell.kind = "control";
3430
+ plan.cellControls?.set(id, {
3431
+ control: reference,
3432
+ key: cellKey,
3433
+ row: rowKey,
3434
+ schema: cellSchema
3435
+ });
3436
+ return cell;
3437
+ }
3438
+ }
3439
+ const enumValues = resolveFormEnum(cellSchema.enum, args.ctx, args.value);
3440
+ if (enumValues) {
3441
+ cell.kind = "select";
3442
+ cell.value = held !== undefined ? String(held) : NONE;
3443
+ cell.options = [
3444
+ { label: "—", value: NONE },
3445
+ ...enumValues.map((v) => ({ label: v, value: v }))
3446
+ ];
3447
+ return cell;
3448
+ }
3449
+ if (cellSchema.type === "boolean") {
3450
+ cell.kind = "checkbox";
3451
+ cell.checked = Boolean(held);
3452
+ return cell;
3453
+ }
3454
+ if (cellSchema.type === "integer" || cellSchema.type === "number") {
3455
+ cell.kind = "number";
3456
+ cell.value = held !== undefined ? String(held) : "";
3457
+ return cell;
3458
+ }
3459
+ cell.value = asText(held);
3460
+ return cell;
3461
+ }
3462
+
3463
+ // src/services/context-resolver.ts
3464
+ var PARAM_SEGMENT = /^\{@(\w+)\}$/;
3465
+ function resolveContextPointer(pointer, opts) {
3466
+ if (!pointer.startsWith("#/$context/")) {
3467
+ return;
3468
+ }
3469
+ const segments = [];
3470
+ for (const raw of pointer.slice("#/$context/".length).split("/")) {
3471
+ const match = raw.match(PARAM_SEGMENT);
3472
+ if (match) {
3473
+ const value = opts.scope?.[match[1]];
3474
+ if (typeof value !== "string" && typeof value !== "number") {
3475
+ return;
3476
+ }
3477
+ const substituted = String(value);
3478
+ if (!substituted) {
3479
+ return;
3480
+ }
3481
+ segments.push(substituted);
3482
+ } else {
3483
+ segments.push(raw);
3484
+ }
3485
+ }
3486
+ if (segments[0] === "$formats") {
3487
+ if (segments.length !== 1) {
3488
+ return;
3489
+ }
3490
+ return (opts.formats ?? []).map((format) => format.name);
3491
+ }
3492
+ let node = opts.projectConfig;
3493
+ for (const segment of segments) {
3494
+ if (node === null || typeof node !== "object") {
3495
+ return;
3496
+ }
3497
+ node = node[segment];
3498
+ }
3499
+ return node;
4231
3500
  }
4232
3501
 
4233
- export { renderProvenanceChip, countProvenance, renderProvenanceDots, renderFieldRow, renderKvRow, renderStaticKvRow, chipSummary, renderFormulaChips, applyCatalogPick, globalEntries, namedFormulaEntries, formulaCatalog, openFormulaPalette, REDIRECT_STATUSES, VALUE_SOURCE_LABELS, slotMode, capsForPosition, resolveContextPointer, isActionExpression, expressionHint, renderOperandEditor, renderExpressionEditor, switchSlotMode, renderDynamicSlot, registerFormControl, getFormControl, referenceTarget, NULL_FORM_CONTEXT, renderForm };
3502
+ export { REDIRECT_STATUSES, VALUE_SOURCE_LABELS, VALUE_SOURCE_HINTS, slotMode, TAG_EXPRESSION_OPERATORS, capsForPosition, slotCaps, stashSlotValue, hasStashedSlotValue, effectiveSlotMode, slotModeSeed, switchSlotMode, registerFormControl, referenceTarget, mountSchemaForm, resolveContextPointer };
4234
3503
 
4235
- //# debugId=F3BBBF454ADAC7A864756E2164756E21
4236
- //# sourceMappingURL=studio-5q69gfag.js.map
3504
+ //# debugId=427B709FE510BC3D64756E2164756E21
3505
+ //# sourceMappingURL=studio-57mf66w2.js.map