@jxsuite/studio 4.1.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (612) 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-n6tfwnda.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-qwkm9zrf.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-vj72gqtq.js +20 -0
  15. package/dist/chunks/content-source-5mex4x1h.js +46 -0
  16. package/dist/chunks/convert-to-repeater-n64h8znc.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-234hjnqw.js +42 -0
  20. package/dist/chunks/extension-sections-3hpcambp.js +651 -0
  21. package/dist/chunks/file-ops-8yhd83xr.js +47 -0
  22. package/dist/chunks/files-hnxqxtsg.js +72 -0
  23. package/dist/chunks/format-host-779sfp2n.js +40 -0
  24. package/dist/chunks/fs-events-b88z7g4h.js +18 -0
  25. package/dist/chunks/git-panel-xwj8fpab.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-syr0f9sb.js +115 -0
  31. package/dist/chunks/{index-wcnt3r9g.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-ff81wthn.js +30 -0
  37. package/dist/chunks/layers-panel-pnnghw12.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-cqhhxhhe.js +33 -0
  41. package/dist/chunks/menu-9p2w46gj.js +15 -0
  42. package/dist/chunks/menu-q99sy9rf.js +285 -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-vx9bhp1d.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-5zt840sy.js +27 -0
  52. package/dist/chunks/quick-search-3748564r.js +45 -0
  53. package/dist/chunks/references-qvvxej7h.js +26 -0
  54. package/dist/chunks/right-panel-eccz92qt.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-kryvsw6j.js → studio-1k325pwf.js} +48 -48
  60. package/dist/chunks/{studio-b55y6ewx.js → studio-1knfqgy7.js} +58 -58
  61. package/dist/chunks/{studio-2b946z26.js → studio-1z1kcjv4.js} +3628 -6095
  62. package/dist/chunks/studio-3773086z.js +111 -0
  63. package/dist/chunks/{studio-4r1pv6zf.js → studio-38emantv.js} +22 -5
  64. package/dist/chunks/{studio-x813zp48.js → studio-3rz5md06.js} +30 -30
  65. package/dist/chunks/{studio-ynxwkrzd.js → studio-4wbb9ct0.js} +1953 -1785
  66. package/dist/chunks/studio-5te5tqng.js +81 -0
  67. package/dist/chunks/studio-5wt9nvhd.js +4763 -0
  68. package/dist/chunks/{studio-p4ngc046.js → studio-6ewqv390.js} +9 -9
  69. package/dist/chunks/{studio-9ab8gqsm.js → studio-6nzaktvy.js} +7 -7
  70. package/dist/chunks/studio-6xa21ej9.js +28 -0
  71. package/dist/chunks/{studio-5ehhackm.js → studio-727xh0e3.js} +19 -19
  72. package/dist/chunks/{studio-gcye7qka.js → studio-7e11jpa9.js} +21 -21
  73. package/dist/chunks/{studio-q7v10cg2.js → studio-7r42c6ca.js} +17 -7
  74. package/dist/chunks/studio-7xnmgv66.js +13615 -0
  75. package/dist/chunks/studio-8scdew0e.js +258 -0
  76. package/dist/chunks/{studio-fbf92ckf.js → studio-93zafjcm.js} +275 -163
  77. package/dist/chunks/{studio-dg7zyt5x.js → studio-98b3z07n.js} +1 -1
  78. package/dist/chunks/studio-a79k90n9.js +1162 -0
  79. package/dist/chunks/studio-aj37eer3.js +422 -0
  80. package/dist/chunks/studio-b0k5tqgp.js +2432 -0
  81. package/dist/chunks/studio-b4s9eykd.js +2811 -0
  82. package/dist/chunks/{studio-f3wqjtk5.js → studio-d0300636.js} +5076 -18
  83. package/dist/chunks/{studio-b9b6t1dg.js → studio-e1f24eff.js} +5664 -5666
  84. package/dist/chunks/studio-e1pgga3k.js +839 -0
  85. package/dist/chunks/{studio-g1wdche0.js → studio-esrxgwh6.js} +80 -80
  86. package/dist/chunks/{studio-q43a5y9h.js → studio-g3jevyjt.js} +16 -16
  87. package/dist/chunks/studio-gb13095a.js +2806 -0
  88. package/dist/chunks/{studio-3n4ndrzh.js → studio-h4nvnjvz.js} +303 -303
  89. package/dist/chunks/{studio-dm19q43r.js → studio-hhvs4sdg.js} +10 -10
  90. package/dist/chunks/{studio-x4qkdat5.js → studio-js8nv53n.js} +1749 -2481
  91. package/dist/chunks/{studio-qjy2z9bv.js → studio-jxp12rnj.js} +32 -137
  92. package/dist/chunks/{studio-trxrabqf.js → studio-k75n7vhp.js} +527 -283
  93. package/dist/chunks/{studio-q9jd1a60.js → studio-kcgff4r0.js} +15 -18
  94. package/dist/chunks/studio-kkqkcp3d.js +17668 -0
  95. package/dist/chunks/studio-mhhyamxf.js +763 -0
  96. package/dist/chunks/{studio-cft9r4r7.js → studio-nq5yf5mp.js} +22 -22
  97. package/dist/chunks/{studio-685s0ytq.js → studio-nqgawfks.js} +572 -572
  98. package/dist/chunks/studio-nzzpvzx9.js +185 -0
  99. package/dist/chunks/{studio-xrapxwwc.js → studio-p1dw8wfw.js} +55 -29
  100. package/dist/chunks/{studio-3bqpr0zh.js → studio-pxbgypmg.js} +299 -110
  101. package/dist/chunks/studio-q0a3yk4n.js +33 -0
  102. package/dist/chunks/{studio-wcy8trfx.js → studio-q3x06vmj.js} +23 -22
  103. package/dist/chunks/{studio-6yw4y3my.js → studio-qhhm6hbt.js} +8 -8
  104. package/dist/chunks/studio-qk4bfsq2.js +362 -0
  105. package/dist/chunks/{studio-msd847zn.js → studio-qk90fgqm.js} +361 -90
  106. package/dist/chunks/{studio-jkftxyvz.js → studio-r6nvacqs.js} +5861 -2154
  107. package/dist/chunks/{studio-53n8bp04.js → studio-r6xmmtfh.js} +1 -1
  108. package/dist/chunks/{studio-tvgyw9xw.js → studio-rnyf3f8e.js} +83 -250
  109. package/dist/chunks/studio-rs76k67r.js +12 -0
  110. package/dist/chunks/studio-sbjyc8bt.js +954 -0
  111. package/dist/chunks/{studio-1wagb550.js → studio-syg2tadj.js} +11 -11
  112. package/dist/chunks/{studio-t5wqwzdh.js → studio-tjjk7jvk.js} +1 -1
  113. package/dist/chunks/studio-tqgryxwg.js +1171 -0
  114. package/dist/chunks/studio-try13h8c.js +8448 -0
  115. package/dist/chunks/studio-vfq8qpjt.js +1219 -0
  116. package/dist/chunks/studio-vhv5tgxm.js +51 -0
  117. package/dist/chunks/{studio-ahn3pdgx.js → studio-vw7mzp6z.js} +52 -38
  118. package/dist/chunks/studio-wqpngpsk.js +174 -0
  119. package/dist/chunks/studio-wqz6dggm.js +1356 -0
  120. package/dist/chunks/{studio-92ab2jqe.js → studio-y9zedyjn.js} +14 -14
  121. package/dist/chunks/studio-yxa81f45.js +12 -0
  122. package/dist/chunks/studio-zdn2g6dt.js +148 -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-t55jdjbx.js +69 -0
  133. package/dist/chunks/{ws-client-6shc5rze.js → ws-client-ny4zc51r.js} +20 -22
  134. package/dist/iframe-entry.js +1873 -673
  135. package/dist/studio.js +52757 -36535
  136. package/dist/workers/editor.worker.js +1040 -1085
  137. package/dist/workers/json.worker.js +1946 -1991
  138. package/dist/workers/ts.worker.js +4960 -5005
  139. package/index.html +1 -0
  140. package/package.json +18 -52
  141. package/src/about/about-modal.ts +72 -123
  142. package/src/browse/library-commands.ts +20 -49
  143. package/src/browse/library-layouts.ts +87 -218
  144. package/src/browse/library-pane.ts +406 -438
  145. package/src/canvas/canvas-helpers.ts +0 -49
  146. package/src/canvas/canvas-live-render.ts +25 -2
  147. package/src/canvas/canvas-render.ts +666 -231
  148. package/src/canvas/canvas-surface.ts +0 -1
  149. package/src/canvas/canvas-utils.ts +333 -113
  150. package/src/canvas/dialog-path.ts +76 -0
  151. package/src/canvas/dialog-state.ts +67 -0
  152. package/src/canvas/diff-marks.ts +252 -0
  153. package/src/canvas/diff-toolbar.ts +349 -0
  154. package/src/canvas/diff-view.ts +123 -0
  155. package/src/canvas/edit-width-drag.ts +187 -0
  156. package/src/canvas/edit-width.ts +150 -0
  157. package/src/canvas/iframe-entry.ts +125 -8
  158. package/src/canvas/iframe-host.ts +271 -15
  159. package/src/canvas/iframe-interaction.ts +133 -13
  160. package/src/canvas/iframe-patch.ts +7 -1
  161. package/src/canvas/iframe-position.ts +5 -6
  162. package/src/canvas/iframe-protocol.ts +106 -5
  163. package/src/canvas/iframe-render.ts +373 -5
  164. package/src/canvas/iframe-slash.ts +2 -2
  165. package/src/canvas/iframe-subtree.ts +5 -8
  166. package/src/canvas/path-mapping.ts +25 -3
  167. package/src/canvas/popover-path.ts +106 -0
  168. package/src/canvas/popover-state.ts +155 -0
  169. package/src/canvas/surface-registry.ts +21 -10
  170. package/src/collab/presence-chips.ts +51 -73
  171. package/src/commands/app-commands.ts +10 -0
  172. package/src/commands/budget.ts +12 -0
  173. package/src/commands/command-args.ts +404 -6
  174. package/src/commands/defaults.ts +44 -14
  175. package/src/commands/live-context.ts +11 -5
  176. package/src/commands/reference.ts +12 -2
  177. package/src/commands/registry.ts +177 -10
  178. package/src/commands/run-reported.ts +120 -0
  179. package/src/content/entry-commands.ts +16 -23
  180. package/src/content/entry-editor.ts +144 -101
  181. package/src/editor/canvas-slash-bridge.ts +2 -2
  182. package/src/editor/context-menu.ts +49 -200
  183. package/src/editor/convert-to-component.ts +42 -82
  184. package/src/editor/convert-to-repeater.ts +132 -173
  185. package/src/editor/inline-edit.ts +11 -0
  186. package/src/editor/shortcuts.ts +76 -49
  187. package/src/editor/slash-menu.ts +150 -157
  188. package/src/files/components.ts +39 -0
  189. package/src/files/file-ops.ts +117 -11
  190. package/src/files/files.ts +944 -660
  191. package/src/files/json-layout.ts +474 -0
  192. package/src/files/serialize-document.ts +8 -1
  193. package/src/format/convert-file.ts +16 -10
  194. package/src/format/format-host.ts +50 -1
  195. package/src/github/github-auth.ts +71 -29
  196. package/src/github/github-publish.ts +57 -65
  197. package/src/grid/cell-editors.ts +46 -4
  198. package/src/grid/cell-popovers.ts +65 -62
  199. package/src/grid/grid-open.ts +86 -64
  200. package/src/grid/grid-panel.ts +390 -469
  201. package/src/grid/redirects-grid.ts +40 -44
  202. package/src/hosting/layout.ts +2 -0
  203. package/src/i18n/i18n-commands.ts +137 -62
  204. package/src/media/media-pane.ts +83 -133
  205. package/src/new-project/add-repo-modal.ts +125 -176
  206. package/src/new-project/import-tab.ts +181 -252
  207. package/src/new-project/location-fields.ts +93 -160
  208. package/src/new-project/new-project-modal.ts +376 -367
  209. package/src/packages/jxsuite-update.ts +76 -18
  210. package/src/packages/pull-package-sync.ts +5 -1
  211. package/src/panels/activity-panel.ts +137 -104
  212. package/src/panels/ai-chat/chat-markdown.ts +15 -13
  213. package/src/panels/ai-chat/chat-view.ts +307 -472
  214. package/src/panels/ai-chat/composer.ts +175 -163
  215. package/src/panels/ai-chat/sessions-view.ts +31 -62
  216. package/src/panels/ai-panel.ts +213 -185
  217. package/src/panels/block-action-bar.ts +444 -645
  218. package/src/panels/bottom-dock.ts +112 -47
  219. package/src/panels/chat-panel.ts +19 -14
  220. package/src/panels/data-explorer.ts +141 -102
  221. package/src/panels/data-grid.ts +183 -150
  222. package/src/panels/dnd.ts +75 -20
  223. package/src/panels/editors.ts +26 -19
  224. package/src/panels/elements-panel.ts +243 -186
  225. package/src/panels/empty-state.ts +26 -42
  226. package/src/panels/events-panel.ts +950 -729
  227. package/src/panels/formula-workspace.ts +434 -207
  228. package/src/panels/frontmatter-fields.ts +185 -153
  229. package/src/panels/frontmatter-panel.ts +411 -187
  230. package/src/panels/git-diff-open.ts +148 -0
  231. package/src/panels/git-panel.ts +679 -605
  232. package/src/panels/head-panel.ts +617 -435
  233. package/src/panels/i18n-panel.ts +148 -135
  234. package/src/panels/imports-panel.ts +388 -414
  235. package/src/panels/jump-bar.ts +175 -147
  236. package/src/panels/layers-panel.ts +785 -671
  237. package/src/panels/left-panel.ts +166 -94
  238. package/src/panels/navigator-panels.ts +1 -1
  239. package/src/panels/pane-context.ts +529 -677
  240. package/src/panels/pane-grid.ts +265 -241
  241. package/src/panels/panel-registry.ts +15 -4
  242. package/src/panels/problems-panel.ts +181 -99
  243. package/src/panels/properties-panel.ts +1525 -966
  244. package/src/panels/provenance.ts +31 -113
  245. package/src/panels/quick-search.ts +223 -184
  246. package/src/panels/right-panel.ts +135 -169
  247. package/src/panels/seo-modal.ts +346 -167
  248. package/src/panels/settings-menu.ts +114 -661
  249. package/src/panels/settings-pane.ts +102 -86
  250. package/src/panels/signals-panel.ts +1318 -1172
  251. package/src/panels/statement-editor.ts +556 -428
  252. package/src/panels/style-panel.ts +1367 -630
  253. package/src/panels/style-utils.ts +36 -32
  254. package/src/panels/stylebook-doc.ts +30 -73
  255. package/src/panels/stylebook-layers-panel.ts +202 -109
  256. package/src/panels/stylebook-panel.ts +124 -88
  257. package/src/panels/tab-strip.ts +386 -288
  258. package/src/platforms/cloud.ts +106 -16
  259. package/src/platforms/devserver.ts +13 -0
  260. package/src/publish/deploy-checklist.ts +124 -78
  261. package/src/publish/publish-commands.ts +4 -12
  262. package/src/publish/publish-panel.ts +134 -328
  263. package/src/services/a11y-report.ts +100 -43
  264. package/src/services/ai-command-tools.ts +538 -0
  265. package/src/services/ai-project-tools.ts +8 -2
  266. package/src/services/ai-session-store.ts +6 -0
  267. package/src/services/ai-system-prompt.ts +112 -24
  268. package/src/services/ai-tools.ts +36 -212
  269. package/src/services/ai-write-report.ts +174 -0
  270. package/src/services/document-assistant.ts +59 -8
  271. package/src/services/idle.ts +16 -14
  272. package/src/services/live-surfaces.ts +237 -0
  273. package/src/services/model-uri.ts +75 -0
  274. package/src/services/notify.ts +63 -6
  275. package/src/services/popover-report.ts +325 -0
  276. package/src/services/surface-registry.ts +56 -0
  277. package/src/services/tool-executor.ts +6 -0
  278. package/src/settings/contexts-section.ts +301 -299
  279. package/src/settings/contributed-section.ts +361 -222
  280. package/src/settings/css-vars-editor.ts +320 -325
  281. package/src/settings/defs-editor.ts +572 -535
  282. package/src/settings/dependencies-editor.ts +79 -107
  283. package/src/settings/extension-commands.ts +379 -0
  284. package/src/settings/extension-rows.ts +113 -0
  285. package/src/settings/extensions-section.ts +287 -0
  286. package/src/settings/general-settings.ts +130 -181
  287. package/src/settings/head-editor.ts +10 -305
  288. package/src/settings/locales-section.ts +146 -196
  289. package/src/settings/preferences-accounts.ts +1 -1
  290. package/src/settings/preferences-dialog.ts +350 -330
  291. package/src/settings/project-sections.ts +108 -181
  292. package/src/settings/schema-field-ui.ts +11 -399
  293. package/src/settings/section-registry.ts +8 -1
  294. package/src/settings/settings-document.ts +31 -29
  295. package/src/shell/tree.ts +42 -87
  296. package/src/shell.ts +48 -36
  297. package/src/studio.ts +104 -69
  298. package/src/style/token-ref.ts +0 -39
  299. package/src/surfaces/about.json +263 -0
  300. package/src/surfaces/about.ts +163 -0
  301. package/src/surfaces/add-repo.json +486 -0
  302. package/src/surfaces/add-repo.ts +236 -0
  303. package/src/surfaces/ai-chat.json +2185 -0
  304. package/src/surfaces/ai-chat.ts +549 -0
  305. package/src/surfaces/ai-credentials-form.json +332 -0
  306. package/src/surfaces/ai-credentials-form.ts +151 -0
  307. package/src/surfaces/ai-managed-connect.json +108 -0
  308. package/src/surfaces/ai-managed-connect.ts +123 -0
  309. package/src/surfaces/ai-model-picker.json +114 -0
  310. package/src/surfaces/ai-model-picker.ts +139 -0
  311. package/src/surfaces/block-action-bar.json +640 -0
  312. package/src/surfaces/block-action-bar.ts +368 -0
  313. package/src/surfaces/bottom-dock.json +154 -0
  314. package/src/surfaces/bottom-dock.ts +125 -0
  315. package/src/surfaces/canvas-stage.json +444 -0
  316. package/src/surfaces/canvas-stage.ts +253 -0
  317. package/src/surfaces/cf-account-picker.json +300 -0
  318. package/src/surfaces/cf-account-picker.ts +214 -0
  319. package/src/surfaces/commandbar.json +985 -0
  320. package/src/{panels/toolbar.ts → surfaces/commandbar.ts} +417 -391
  321. package/src/surfaces/convert-repeater.json +309 -0
  322. package/src/surfaces/convert-repeater.ts +276 -0
  323. package/src/surfaces/data-actions.json +157 -0
  324. package/src/surfaces/data-actions.ts +115 -0
  325. package/src/surfaces/dialog.json +289 -0
  326. package/src/surfaces/dialog.ts +343 -0
  327. package/src/surfaces/diff-toolbar.json +266 -0
  328. package/src/surfaces/diff-toolbar.ts +139 -0
  329. package/src/surfaces/doc-header.json +739 -0
  330. package/src/surfaces/doc-header.ts +223 -0
  331. package/src/surfaces/empty-state.json +152 -0
  332. package/src/surfaces/empty-state.ts +173 -0
  333. package/src/surfaces/entry-editor.json +281 -0
  334. package/src/surfaces/entry-editor.ts +191 -0
  335. package/src/surfaces/expression-editor.json +977 -0
  336. package/src/surfaces/expression-editor.ts +266 -0
  337. package/src/surfaces/files-panel.json +592 -0
  338. package/src/surfaces/files-panel.ts +301 -0
  339. package/src/surfaces/formula-palette.json +364 -0
  340. package/src/surfaces/formula-palette.ts +340 -0
  341. package/src/surfaces/git-panel.json +1822 -0
  342. package/src/surfaces/git-panel.ts +272 -0
  343. package/src/surfaces/github-auth.json +112 -0
  344. package/src/surfaces/github-auth.ts +139 -0
  345. package/src/surfaces/github-publish.json +206 -0
  346. package/src/surfaces/github-publish.ts +169 -0
  347. package/src/surfaces/grid-cell.json +218 -0
  348. package/src/surfaces/grid-cell.ts +120 -0
  349. package/src/surfaces/grid-open.json +147 -0
  350. package/src/surfaces/grid-open.ts +87 -0
  351. package/src/surfaces/grid-panel.json +706 -0
  352. package/src/surfaces/grid-panel.ts +206 -0
  353. package/src/surfaces/grid-replace.json +179 -0
  354. package/src/surfaces/grid-replace.ts +89 -0
  355. package/src/surfaces/grid-views.json +446 -0
  356. package/src/surfaces/grid-views.ts +172 -0
  357. package/src/surfaces/inspector-dock.json +187 -0
  358. package/src/surfaces/inspector-dock.ts +161 -0
  359. package/src/surfaces/jump-bar.json +243 -0
  360. package/src/surfaces/jump-bar.ts +129 -0
  361. package/src/surfaces/jxsuite-update.json +146 -0
  362. package/src/surfaces/jxsuite-update.ts +149 -0
  363. package/src/surfaces/library-pane.json +1509 -0
  364. package/src/surfaces/library-pane.ts +363 -0
  365. package/src/surfaces/logic-panel.json +1949 -0
  366. package/src/surfaces/logic-panel.ts +369 -0
  367. package/src/surfaces/logic-workspace.json +685 -0
  368. package/src/surfaces/logic-workspace.ts +238 -0
  369. package/src/surfaces/media-browser.json +303 -0
  370. package/src/surfaces/media-browser.ts +145 -0
  371. package/src/surfaces/media-field.json +163 -0
  372. package/src/surfaces/media-field.ts +116 -0
  373. package/src/surfaces/media-pane.json +513 -0
  374. package/src/surfaces/media-pane.ts +197 -0
  375. package/src/surfaces/menu.json +232 -0
  376. package/src/surfaces/menu.ts +375 -0
  377. package/src/surfaces/navigator-dock.json +113 -0
  378. package/src/surfaces/navigator-dock.ts +130 -0
  379. package/src/surfaces/new-project.json +1654 -0
  380. package/src/surfaces/new-project.ts +411 -0
  381. package/src/surfaces/palette.json +535 -0
  382. package/src/surfaces/pane-context.json +1497 -0
  383. package/src/surfaces/pane-context.ts +513 -0
  384. package/src/surfaces/pane-grid.json +188 -0
  385. package/src/surfaces/pane-grid.ts +250 -0
  386. package/src/surfaces/panel-activity.json +563 -0
  387. package/src/surfaces/panel-activity.ts +187 -0
  388. package/src/surfaces/panel-data.json +141 -0
  389. package/src/surfaces/panel-data.ts +156 -0
  390. package/src/surfaces/panel-deploy-checklist.json +283 -0
  391. package/src/surfaces/panel-deploy-checklist.ts +175 -0
  392. package/src/surfaces/panel-elements.json +367 -0
  393. package/src/surfaces/panel-elements.ts +175 -0
  394. package/src/surfaces/panel-i18n.json +569 -0
  395. package/src/surfaces/panel-i18n.ts +212 -0
  396. package/src/surfaces/panel-imports.json +780 -0
  397. package/src/surfaces/panel-imports.ts +209 -0
  398. package/src/surfaces/panel-outline.json +734 -0
  399. package/src/surfaces/panel-outline.ts +326 -0
  400. package/src/surfaces/panel-page.json +1017 -0
  401. package/src/surfaces/panel-page.ts +240 -0
  402. package/src/surfaces/panel-problems.json +497 -0
  403. package/src/surfaces/panel-problems.ts +157 -0
  404. package/src/surfaces/panel-signals.json +1973 -0
  405. package/src/surfaces/panel-signals.ts +436 -0
  406. package/src/surfaces/panel-stylebook-layers.json +317 -0
  407. package/src/surfaces/panel-stylebook-layers.ts +155 -0
  408. package/src/surfaces/preferences.json +850 -0
  409. package/src/surfaces/preferences.ts +332 -0
  410. package/src/surfaces/progress-modal.json +110 -0
  411. package/src/surfaces/progress-modal.ts +150 -0
  412. package/src/surfaces/properties-panel.json +1578 -0
  413. package/src/surfaces/properties-panel.ts +337 -0
  414. package/src/surfaces/publish.json +945 -0
  415. package/src/surfaces/publish.ts +392 -0
  416. package/src/surfaces/push-plan.json +110 -0
  417. package/src/surfaces/push-plan.ts +87 -0
  418. package/src/surfaces/rail.json +255 -0
  419. package/src/surfaces/rail.ts +206 -0
  420. package/src/surfaces/reference-field.json +191 -0
  421. package/src/surfaces/reference-field.ts +168 -0
  422. package/src/surfaces/schema-builder.json +1011 -0
  423. package/src/surfaces/schema-builder.ts +181 -0
  424. package/src/surfaces/schema-form.json +1013 -0
  425. package/src/surfaces/schema-form.ts +287 -0
  426. package/src/surfaces/secret-field.json +60 -0
  427. package/src/surfaces/secret-field.ts +146 -0
  428. package/src/surfaces/seo.json +932 -0
  429. package/src/surfaces/seo.ts +334 -0
  430. package/src/surfaces/settings-contexts.json +538 -0
  431. package/src/surfaces/settings-contexts.ts +196 -0
  432. package/src/surfaces/settings-contributed.json +489 -0
  433. package/src/surfaces/settings-contributed.ts +199 -0
  434. package/src/surfaces/settings-css-vars.json +820 -0
  435. package/src/surfaces/settings-css-vars.ts +199 -0
  436. package/src/surfaces/settings-defs.json +1363 -0
  437. package/src/surfaces/settings-defs.ts +232 -0
  438. package/src/surfaces/settings-deploy.json +108 -0
  439. package/src/surfaces/settings-deploy.ts +135 -0
  440. package/src/surfaces/settings-extensions.json +486 -0
  441. package/src/surfaces/settings-extensions.ts +201 -0
  442. package/src/surfaces/settings-head.json +779 -0
  443. package/src/surfaces/settings-head.ts +397 -0
  444. package/src/surfaces/settings-locales.json +420 -0
  445. package/src/surfaces/settings-locales.ts +137 -0
  446. package/src/surfaces/settings-overview.json +423 -0
  447. package/src/surfaces/settings-overview.ts +218 -0
  448. package/src/surfaces/settings-packages.json +549 -0
  449. package/src/surfaces/settings-packages.ts +158 -0
  450. package/src/surfaces/settings-pane.json +147 -0
  451. package/src/surfaces/settings-pane.ts +150 -0
  452. package/src/surfaces/settings-rawjson.json +104 -0
  453. package/src/surfaces/settings-rawjson.ts +107 -0
  454. package/src/surfaces/shell.json +99 -0
  455. package/src/surfaces/shell.ts +30 -0
  456. package/src/surfaces/slash-menu.json +180 -0
  457. package/src/surfaces/slash-menu.ts +272 -0
  458. package/src/surfaces/statements.json +607 -0
  459. package/src/surfaces/statements.ts +238 -0
  460. package/src/surfaces/statusbar.json +94 -0
  461. package/src/{panels → surfaces}/statusbar.ts +164 -90
  462. package/src/surfaces/style-panel.json +1643 -0
  463. package/src/surfaces/style-panel.ts +338 -0
  464. package/src/surfaces/stylebook-chrome.json +122 -0
  465. package/src/surfaces/stylebook-chrome.ts +122 -0
  466. package/src/surfaces/tab-strip.json +448 -0
  467. package/src/surfaces/tab-strip.ts +200 -0
  468. package/src/surfaces/target-line.json +419 -0
  469. package/src/surfaces/target-line.ts +349 -0
  470. package/src/surfaces/toasts.json +189 -0
  471. package/src/surfaces/welcome.json +571 -0
  472. package/src/surfaces/welcome.ts +338 -0
  473. package/src/tabs/project-config.ts +111 -21
  474. package/src/tabs/tab.ts +52 -1
  475. package/src/types.ts +22 -0
  476. package/src/ui/ai-credentials-form.ts +104 -100
  477. package/src/ui/ai-managed-connect.ts +54 -36
  478. package/src/ui/ai-model-picker.ts +75 -47
  479. package/src/ui/cf-account-picker.ts +76 -141
  480. package/src/ui/color-selector.ts +80 -301
  481. package/src/ui/dynamic-slot.ts +43 -283
  482. package/src/ui/expression-editor.ts +797 -576
  483. package/src/ui/field-input.ts +8 -181
  484. package/src/ui/form-controls.ts +838 -366
  485. package/src/ui/formula-chips.ts +80 -85
  486. package/src/ui/kit.ts +33 -0
  487. package/src/ui/layers.ts +296 -593
  488. package/src/ui/media-picker.ts +203 -284
  489. package/src/ui/panel-resize.ts +251 -64
  490. package/src/ui/popover-surface.ts +153 -0
  491. package/src/ui/progress-modal.ts +22 -60
  492. package/src/ui/regions.ts +13 -7
  493. package/src/ui/schema-form.ts +906 -440
  494. package/src/ui/surface.ts +122 -0
  495. package/src/ui/unit-selector.ts +15 -131
  496. package/src/ui/value-source.ts +14 -0
  497. package/src/ui/virtual-window.ts +88 -9
  498. package/src/utils/canvas-media.ts +72 -0
  499. package/src/utils/edit-display.ts +7 -2
  500. package/src/utils/element-selectors.ts +69 -0
  501. package/src/utils/link-target.ts +2 -2
  502. package/src/utils/studio-utils.ts +0 -10
  503. package/src/view.ts +15 -0
  504. package/src/workspace/pane-derive.ts +36 -14
  505. package/src/workspace/workspace.ts +75 -6
  506. package/styles/canvas.css +72 -467
  507. package/styles/forced-colors.css +48 -82
  508. package/styles/forced-colors.json +41 -0
  509. package/styles/inspector.css +2 -1301
  510. package/styles/overlays.css +96 -2823
  511. package/styles/panels.css +6 -1423
  512. package/styles/shell-frame.css +272 -0
  513. package/styles/shell-frame.json +227 -0
  514. package/styles/shell.css +0 -1568
  515. package/styles/tokens.css +65 -136
  516. package/styles/tokens.json +84 -0
  517. package/dist/chunks/Overlay.dev-etqjwyf9.js +0 -23
  518. package/dist/chunks/adapter-jkcyhgtr.js +0 -14
  519. package/dist/chunks/cf-account-picker-en86v7x7.js +0 -163
  520. package/dist/chunks/cf-settings-45fgyx8y.js +0 -21
  521. package/dist/chunks/components-zmx10t62.js +0 -21
  522. package/dist/chunks/content-source-48xg86wj.js +0 -49
  523. package/dist/chunks/convert-to-repeater-43tfn7cs.js +0 -268
  524. package/dist/chunks/editor-cptq5jp1.js +0 -37
  525. package/dist/chunks/entry-commands-e63f1xc0.js +0 -46
  526. package/dist/chunks/extension-sections-txv08xxg.js +0 -311
  527. package/dist/chunks/file-ops-wbh4hhsx.js +0 -48
  528. package/dist/chunks/files-3kfkwhk4.js +0 -93
  529. package/dist/chunks/flow-qbfgrpeh.js +0 -634
  530. package/dist/chunks/focus-trap.esm-9t2drp0g.js +0 -1025
  531. package/dist/chunks/focus-visible-6eg70z18.js +0 -167
  532. package/dist/chunks/format-host-w16q81tf.js +0 -43
  533. package/dist/chunks/fs-events-hrrg328g.js +0 -21
  534. package/dist/chunks/git-panel-532whr1y.js +0 -59
  535. package/dist/chunks/index-zqxmqxmh.js +0 -109
  536. package/dist/chunks/jx-validate-8er6gbxy.js +0 -19
  537. package/dist/chunks/layers-panel-07hm7g5j.js +0 -67
  538. package/dist/chunks/layers-q75tj2mf.js +0 -45
  539. package/dist/chunks/lit-html-j055mkh8.js +0 -22
  540. package/dist/chunks/monaco-buffer-dqw0xxpx.js +0 -25
  541. package/dist/chunks/notify-yjm79n4s.js +0 -30
  542. package/dist/chunks/parse-24d0gay3.js +0 -19
  543. package/dist/chunks/platform-ba1j1gbh.js +0 -16
  544. package/dist/chunks/publish-panel-v9st96ja.js +0 -29
  545. package/dist/chunks/quick-search-zccjp6gv.js +0 -66
  546. package/dist/chunks/references-3qx8fkyp.js +0 -29
  547. package/dist/chunks/right-panel-vm3b6kwy.js +0 -46
  548. package/dist/chunks/section-registry-sx1ykvkw.js +0 -30
  549. package/dist/chunks/sp-overlay.dev-qgvhnxf4.js +0 -18
  550. package/dist/chunks/sp-popover.dev-n2etmqdp.js +0 -9
  551. package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +0 -15
  552. package/dist/chunks/sp-tooltip.dev-4sastdnw.js +0 -15
  553. package/dist/chunks/sp-tray.dev-tkbscm06.js +0 -199
  554. package/dist/chunks/studio-0a5qrh01.js +0 -3
  555. package/dist/chunks/studio-0bq6eh7v.js +0 -12
  556. package/dist/chunks/studio-18j3bmcz.js +0 -14
  557. package/dist/chunks/studio-1ngbejft.js +0 -181
  558. package/dist/chunks/studio-4b5z1x43.js +0 -424
  559. package/dist/chunks/studio-4n2b5gzb.js +0 -357
  560. package/dist/chunks/studio-4rzyrtjz.js +0 -426
  561. package/dist/chunks/studio-5fx3g9jg.js +0 -4325
  562. package/dist/chunks/studio-5myknm0a.js +0 -1903
  563. package/dist/chunks/studio-7afd21nw.js +0 -67
  564. package/dist/chunks/studio-7nte2hj4.js +0 -613
  565. package/dist/chunks/studio-8jpzqk5s.js +0 -128
  566. package/dist/chunks/studio-8q517dfr.js +0 -24
  567. package/dist/chunks/studio-950k0dx6.js +0 -6939
  568. package/dist/chunks/studio-9n4mrmnb.js +0 -184
  569. package/dist/chunks/studio-a0tswy0w.js +0 -58
  570. package/dist/chunks/studio-ahnzd9rg.js +0 -23
  571. package/dist/chunks/studio-bgxt93ga.js +0 -62
  572. package/dist/chunks/studio-cmb03bg5.js +0 -13890
  573. package/dist/chunks/studio-d1ks410h.js +0 -175
  574. package/dist/chunks/studio-ef49pnzx.js +0 -387
  575. package/dist/chunks/studio-eqq1w638.js +0 -113
  576. package/dist/chunks/studio-ew0djmhg.js +0 -341
  577. package/dist/chunks/studio-gfgb8pe6.js +0 -1652
  578. package/dist/chunks/studio-h4rxscfc.js +0 -10
  579. package/dist/chunks/studio-jc994dnr.js +0 -1066
  580. package/dist/chunks/studio-kyh9avvv.js +0 -28
  581. package/dist/chunks/studio-m3hwn7y1.js +0 -120
  582. package/dist/chunks/studio-mtfnaqs9.js +0 -659
  583. package/dist/chunks/studio-n6j9t10q.js +0 -318
  584. package/dist/chunks/studio-n8b9wqwy.js +0 -291
  585. package/dist/chunks/studio-qcy05dp8.js +0 -1743
  586. package/dist/chunks/studio-qq6jym04.js +0 -53
  587. package/dist/chunks/studio-rdx8jcxy.js +0 -83
  588. package/dist/chunks/studio-rn9ta2mz.js +0 -111
  589. package/dist/chunks/studio-t7qb9db8.js +0 -665
  590. package/dist/chunks/studio-tmc5emrw.js +0 -8
  591. package/dist/chunks/studio-z6nw2ek2.js +0 -18
  592. package/dist/chunks/studio-zczps70q.js +0 -378
  593. package/dist/chunks/studio-zqazsct2.js +0 -1823
  594. package/dist/chunks/workspace-fjx52ryv.js +0 -89
  595. package/src/panels/activity-bar.ts +0 -251
  596. package/src/panels/panel-scheduler.ts +0 -219
  597. package/src/panels/style-inputs.ts +0 -237
  598. package/src/panels/target-line.ts +0 -274
  599. package/src/panels/welcome-screen.ts +0 -346
  600. package/src/ui/button-group.ts +0 -102
  601. package/src/ui/choice-field.ts +0 -72
  602. package/src/ui/field-row.ts +0 -249
  603. package/src/ui/formula-palette.ts +0 -213
  604. package/src/ui/icons.ts +0 -52
  605. package/src/ui/jx-theme.ts +0 -130
  606. package/src/ui/spectrum.ts +0 -340
  607. package/src/ui/value-selector.ts +0 -174
  608. package/src/ui/widgets.ts +0 -172
  609. /package/dist/chunks/{javascript-s56a8dpk.css → javascript-4abzjcy9.css} +0 -0
  610. /package/dist/chunks/{jsonMode-zr9sw0c7.css → jsonMode-vvmpmk34.css} +0 -0
  611. /package/dist/chunks/{monaco-setup-pav68602.css → monaco-setup-8vbyqnhr.css} +0 -0
  612. /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-2b946z26.js";
2
+ intlHelpersClause
3
+ } from "./studio-kkqkcp3d.js";
9
4
  import {
10
5
  LANGUAGE_TAG_PATTERN,
11
- activeTab
12
- } from "./studio-trxrabqf.js";
6
+ activeTab2
7
+ } from "./studio-k75n7vhp.js";
13
8
  import {
14
- classMap,
15
- ifDefined,
16
- styleMap
17
- } from "./studio-m3hwn7y1.js";
18
- import {
19
- cloneValue
20
- } from "./studio-5ehhackm.js";
21
- import {
22
- getLayerSlot
23
- } from "./studio-t7qb9db8.js";
24
- import {
25
- live,
26
- ref
27
- } from "./studio-4rzyrtjz.js";
28
- import {
29
- html,
30
- nothing,
31
- render
32
- } from "./studio-kryvsw6j.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
- }
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-vfq8qpjt.js";
20
+ import {
21
+ openMenu2
22
+ } from "./studio-aj37eer3.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
  };
@@ -2862,7 +1520,7 @@ var VALUE_SOURCE_HINTS = {
2862
1520
  template: "Text with ${…} placeholders that fill in from signals."
2863
1521
  };
2864
1522
  function slotMode(value) {
2865
- if (isRef(value)) {
1523
+ if (isRef2(value)) {
2866
1524
  return "ref";
2867
1525
  }
2868
1526
  if (isFunctionDef(value)) {
@@ -2977,6 +1635,7 @@ function deriveSlotCaps(schema) {
2977
1635
  var ELEMENT_TAG_SCHEMA = {
2978
1636
  anyOf: [tagNameSchema, ...elementTagNameSchema.anyOf.slice(1)]
2979
1637
  };
1638
+ var TAG_EXPRESSION_OPERATORS = tagExpressionSchema.anyOf.map((branch) => branch.properties.operator.const);
2980
1639
  var SLOT_POSITION_SCHEMAS = {
2981
1640
  attribute: attributesObjectSchema.additionalProperties,
2982
1641
  componentProp: propsObjectSchema.additionalProperties,
@@ -3008,666 +1667,23 @@ function configFieldSchema(field) {
3008
1667
  return { anyOf: [declared, { $ref: "#/$defs/RefObject" }] };
3009
1668
  }
3010
1669
 
3011
- // src/services/context-resolver.ts
3012
- var PARAM_SEGMENT = /^\{@(\w+)\}$/;
3013
- function resolveContextPointer(pointer, opts) {
3014
- if (!pointer.startsWith("#/$context/")) {
3015
- return;
3016
- }
3017
- const segments = [];
3018
- for (const raw of pointer.slice("#/$context/".length).split("/")) {
3019
- const match = raw.match(PARAM_SEGMENT);
3020
- if (match) {
3021
- const value = opts.scope?.[match[1]];
3022
- if (typeof value !== "string" && typeof value !== "number") {
3023
- return;
3024
- }
3025
- const substituted = String(value);
3026
- if (!substituted) {
3027
- return;
3028
- }
3029
- segments.push(substituted);
3030
- } else {
3031
- segments.push(raw);
3032
- }
3033
- }
3034
- if (segments[0] === "$formats") {
3035
- if (segments.length !== 1) {
3036
- return;
3037
- }
3038
- return (opts.formats ?? []).map((format) => format.name);
3039
- }
3040
- let node = opts.projectConfig;
3041
- for (const segment of segments) {
3042
- if (node === null || typeof node !== "object") {
3043
- return;
3044
- }
3045
- node = node[segment];
3046
- }
3047
- return node;
3048
- }
3049
-
3050
- // src/ui/expression-editor.ts
3051
- var UNARY_OPS = new Set(["!", "-"]);
3052
- var BINARY_OPS = new Set([
3053
- "+",
3054
- "-",
3055
- "*",
3056
- "/",
3057
- "%",
3058
- "===",
3059
- "!==",
3060
- "<",
3061
- "<=",
3062
- ">",
3063
- ">=",
3064
- "&&",
3065
- "||",
3066
- "??"
3067
- ]);
3068
- var ASSIGN_OPS = new Set(["=", "+=", "-=", "*=", "/="]);
3069
- function isActionExpression(node) {
3070
- const op = node?.operator;
3071
- return typeof op === "string" && ASSIGN_OPS.has(op);
3072
- }
3073
- var NO_ARG_OPS = new Set(["pop", "shift"]);
3074
- var ONE_ARG_OPS = new Set(["push", "unshift"]);
3075
- var ZERO_ARG_METHOD_OPS = new Set([
3076
- "flat",
3077
- "normalize",
3078
- "toLocaleLowerCase",
3079
- "toLocaleString",
3080
- "toLocaleUpperCase",
3081
- "toLowerCase",
3082
- "toReversed",
3083
- "toSorted",
3084
- "toUpperCase",
3085
- "trim",
3086
- "trimEnd",
3087
- "trimStart"
3088
- ]);
3089
- var OPERATOR_GROUPS = [
3090
- { label: "Assignment", ops: ["=", "+=", "-=", "*=", "/="] },
3091
- { label: "Unary", ops: ["!", "-"] },
3092
- { label: "Arithmetic", ops: ["+", "-", "*", "/", "%"] },
3093
- { label: "Comparison", ops: ["===", "!==", "<", "<=", ">", ">="] },
3094
- { label: "Logical", ops: ["&&", "||", "??"] },
3095
- { label: "Conditional", ops: ["?:", "switch"] },
3096
- {
3097
- label: "Array methods",
3098
- ops: ["push", "pop", "shift", "unshift", "splice"]
3099
- },
3100
- {
3101
- label: "Pure methods (String)",
3102
- ops: [
3103
- "toUpperCase",
3104
- "toLowerCase",
3105
- "trim",
3106
- "trimStart",
3107
- "trimEnd",
3108
- "split",
3109
- "startsWith",
3110
- "endsWith",
3111
- "padStart",
3112
- "padEnd",
3113
- "replaceAll",
3114
- "repeat",
3115
- "charAt",
3116
- "normalize"
3117
- ]
3118
- },
3119
- {
3120
- label: "Pure methods (Array)",
3121
- ops: [
3122
- "includes",
3123
- "indexOf",
3124
- "lastIndexOf",
3125
- "join",
3126
- "slice",
3127
- "concat",
3128
- "at",
3129
- "flat",
3130
- "toSorted",
3131
- "toReversed",
3132
- "toSpliced",
3133
- "with"
3134
- ]
3135
- },
3136
- { label: "Pure methods (Number)", ops: ["toFixed", "toPrecision", "toLocaleString"] },
3137
- { label: "Aggregate", ops: ["reduce", "map", "filter"] },
3138
- { label: "Function", ops: ["call"] }
3139
- ];
3140
- var INFO_DEFAULTS = {
3141
- callArgs: false,
3142
- needsInitial: false,
3143
- needsValue: false,
3144
- spliceArray: false,
3145
- switchCases: false,
3146
- targetMustBeRef: false,
3147
- valueIsNode: false
3148
- };
3149
- function operatorInfo(op) {
3150
- if (UNARY_OPS.has(op)) {
3151
- return { ...INFO_DEFAULTS };
3152
- }
3153
- if (op === "?:") {
3154
- return { ...INFO_DEFAULTS, needsInitial: true, needsValue: true };
3155
- }
3156
- if (op === "switch") {
3157
- return { ...INFO_DEFAULTS, switchCases: true };
3158
- }
3159
- if (op === "call") {
3160
- return { ...INFO_DEFAULTS, callArgs: true, targetMustBeRef: true };
3161
- }
3162
- if (PURE_METHOD_OPS.has(op)) {
3163
- return { ...INFO_DEFAULTS, needsValue: !ZERO_ARG_METHOD_OPS.has(op) };
3164
- }
3165
- if (BINARY_OPS.has(op)) {
3166
- return { ...INFO_DEFAULTS, needsValue: true };
3167
- }
3168
- if (ASSIGN_OPS.has(op)) {
3169
- return { ...INFO_DEFAULTS, needsValue: true, targetMustBeRef: true };
3170
- }
3171
- if (NO_ARG_OPS.has(op)) {
3172
- return { ...INFO_DEFAULTS, targetMustBeRef: true };
3173
- }
3174
- if (ONE_ARG_OPS.has(op)) {
3175
- return { ...INFO_DEFAULTS, needsValue: true, targetMustBeRef: true };
3176
- }
3177
- if (op === "splice") {
3178
- return { ...INFO_DEFAULTS, needsValue: true, spliceArray: true, targetMustBeRef: true };
3179
- }
3180
- if (op === "reduce") {
3181
- return {
3182
- ...INFO_DEFAULTS,
3183
- needsInitial: true,
3184
- needsValue: true,
3185
- targetMustBeRef: true,
3186
- valueIsNode: true
3187
- };
3188
- }
3189
- if (op === "map" || op === "filter") {
3190
- return { ...INFO_DEFAULTS, needsValue: true, targetMustBeRef: true, valueIsNode: true };
3191
- }
3192
- return { ...INFO_DEFAULTS };
3193
- }
3194
- function operandMode(operand) {
3195
- if (operand && typeof operand === "object") {
3196
- if ("$ref" in operand) {
3197
- return "ref";
3198
- }
3199
- if ("operator" in operand) {
3200
- return "expression";
3201
- }
3202
- }
3203
- return "literal";
3204
- }
3205
- function calleeParamLabels(target, state) {
3206
- const ref2 = isRef(target) ? target.$ref : "";
3207
- const entry = ref2 ? calleeEntry(ref2, state) : undefined;
3208
- return entry?.kind === "formula" ? entry.parameters.map((p) => p.name) : [];
3209
- }
3210
- function defaultForMode(mode) {
3211
- if (mode === "ref") {
3212
- return { $ref: "" };
3213
- }
3214
- if (mode === "expression") {
3215
- return { operator: "!", target: null };
3216
- }
3217
- return null;
3218
- }
3219
- function literalType(val) {
3220
- if (val === null || val === undefined) {
3221
- return "null";
3222
- }
3223
- if (typeof val === "boolean") {
3224
- return "boolean";
3225
- }
3226
- if (typeof val === "number") {
3227
- return "number";
3228
- }
3229
- return "string";
3230
- }
3231
- function defaultForLiteralType(type) {
3232
- if (type === "number") {
3233
- return 0;
3234
- }
3235
- if (type === "boolean") {
3236
- return false;
3237
- }
3238
- if (type === "null") {
3239
- return null;
3240
- }
3241
- return "";
3242
- }
3243
- function expressionHint(node) {
3244
- if (!isJsonObject(node) || typeof node.operator !== "string") {
3245
- return "$expression";
3246
- }
3247
- const expr = node;
3248
- const op = expr.operator;
3249
- const { target } = expr;
3250
- const targetLabel = isRef(target) ? target.$ref.replace("#/state/", "") : isJsonObject(target) && typeof target.operator === "string" ? `(${target.operator}…)` : String(target ?? "?");
3251
- if (ASSIGN_OPS.has(op) || ONE_ARG_OPS.has(op)) {
3252
- return `${op} ${targetLabel}`;
3253
- }
3254
- if (NO_ARG_OPS.has(op)) {
3255
- return `${op}(${targetLabel})`;
3256
- }
3257
- if (op === "splice") {
3258
- return `splice(${targetLabel})`;
3259
- }
3260
- if (op === "call") {
3261
- return `${targetLabel.replace("window#/", "").replaceAll("/", ".")}(…)`;
3262
- }
3263
- if (op === "reduce" || op === "map" || op === "filter" || PURE_METHOD_OPS.has(op)) {
3264
- return `${op}(${targetLabel})`;
3265
- }
3266
- if (op === "?:") {
3267
- return `${targetLabel} ? … : …`;
3268
- }
3269
- if (op === "switch") {
3270
- return `switch(${targetLabel})`;
3271
- }
3272
- if (UNARY_OPS.has(op)) {
3273
- return `${op}${targetLabel}`;
3274
- }
3275
- return `${targetLabel} ${op} …`;
3276
- }
3277
- function renderValueBadge(preview, pathKey) {
3278
- const text = preview?.values.get(pathKey);
3279
- if (text === undefined) {
3280
- return nothing;
3281
- }
3282
- return html`<span class="expr-live-badge" title=${text}>${text}</span>`;
3283
- }
3284
- function renderRefPicker(refVal, onRefChange, opts) {
3285
- const stateRefs = (opts.stateDefs || []).map((k) => `#/state/${k}`);
3286
- const eventRefs = opts.allowEventRef ? ["event#/detail", "event#/target/value"] : [];
3287
- const allRefs = [...stateRefs, ...eventRefs];
3288
- const isCustom = refVal && !allRefs.includes(refVal);
3289
- if (allRefs.length === 0 && !refVal) {
3290
- return renderEmptyState({
3291
- compact: true,
3292
- detail: "Add one in the State panel and it shows up here.",
3293
- message: "A binding points at a value this page holds."
3294
- });
3295
- }
3296
- return html`
3297
- <sp-picker
3298
- size="s"
3299
- class="expr-ref"
3300
- placeholder="Select…"
3301
- .value=${live(isCustom ? "__custom__" : refVal || "")}
3302
- @change=${(e) => {
3303
- const val = e.target.value;
3304
- if (val === "__custom__") {
3305
- return;
3306
- }
3307
- onRefChange(val);
3308
- }}
3309
- >
3310
- ${stateRefs.map((r) => html`<sp-menu-item value=${r}>${r.replace("#/state/", "")}</sp-menu-item>`)}
3311
- ${eventRefs.length > 0 ? html`
3312
- <sp-menu-divider></sp-menu-divider>
3313
- ${eventRefs.map((r) => html`<sp-menu-item value=${r}>${r}</sp-menu-item>`)}
3314
- ` : nothing}
3315
- </sp-picker>
3316
- `;
3317
- }
3318
- function renderLiteralEditor(operand, onChange) {
3319
- const type = literalType(operand);
3320
- return html`
3321
- <div class="expr-literal">
3322
- <sp-picker
3323
- size="s"
3324
- class="expr-literal-type"
3325
- .value=${live(type)}
3326
- @change=${(e) => {
3327
- const newType = e.target.value;
3328
- onChange(defaultForLiteralType(newType));
3329
- }}
3330
- >
3331
- <sp-menu-item value="string">str</sp-menu-item>
3332
- <sp-menu-item value="number">num</sp-menu-item>
3333
- <sp-menu-item value="boolean">bool</sp-menu-item>
3334
- <sp-menu-item value="null">null</sp-menu-item>
3335
- </sp-picker>
3336
- ${type === "string" ? html`<sp-textfield
3337
- size="s"
3338
- class="expr-literal-value"
3339
- .value=${live(String(operand ?? ""))}
3340
- @input=${(e) => onChange(e.target.value)}
3341
- ></sp-textfield>` : type === "number" ? html`<sp-number-field
3342
- size="s"
3343
- class="expr-literal-value"
3344
- .value=${live(Number(operand ?? 0))}
3345
- @change=${(e) => onChange(Number(e.target.value))}
3346
- ></sp-number-field>` : type === "boolean" ? html`<sp-checkbox
3347
- size="s"
3348
- class="expr-literal-bool"
3349
- ?checked=${Boolean(operand)}
3350
- @change=${(e) => onChange(e.target.checked)}
3351
- >true</sp-checkbox
3352
- >` : html`<span class="expr-literal-null">null</span>`}
3353
- </div>
3354
- `;
3355
- }
3356
- function renderOperandEditor(operand, onChange, opts) {
3357
- if (opts.mustBeRef) {
3358
- const refVal = operand?.$ref ?? "";
3359
- return html`
3360
- <div class="expr-operand">${renderRefPicker(refVal, (r) => onChange({ $ref: r }), opts)}</div>
3361
- `;
3362
- }
3363
- const mode = operandMode(operand);
3364
- return html`
3365
- <div class="expr-operand">
3366
- <sp-picker
3367
- size="s"
3368
- class="expr-operand-mode"
3369
- label="Value source"
3370
- .value=${live(mode)}
3371
- @change=${(e) => {
3372
- const newMode = e.target.value;
3373
- onChange(defaultForMode(newMode));
3374
- }}
3375
- >
3376
- <sp-menu-item value="literal">${VALUE_SOURCE_LABELS.literal}</sp-menu-item>
3377
- <sp-menu-item value="ref">${VALUE_SOURCE_LABELS.ref}</sp-menu-item>
3378
- <sp-menu-item value="expression">${VALUE_SOURCE_LABELS.expression}</sp-menu-item>
3379
- </sp-picker>
3380
- ${mode === "literal" ? renderLiteralEditor(operand, onChange) : mode === "ref" ? renderRefPicker(operand?.$ref ?? "", (r) => onChange({ $ref: r }), opts) : renderExpressionEditor(operand, onChange, {
3381
- ...opts,
3382
- depth: opts.depth + 1
3383
- })}
3384
- </div>
3385
- `;
3386
- }
3387
- function renderSpliceArgsEditor(args, onChange, opts, naming = {}) {
3388
- const safeArgs = Array.isArray(args) ? args : [];
3389
- const labels = naming.labels ?? ["start", "del", "item"];
3390
- const fallbackLabel = naming.fallbackLabel ?? "item";
3391
- return html`
3392
- <div class="array-object-field">
3393
- ${safeArgs.map((arg, idx) => html`
3394
- <div class="array-object-row">
3395
- <span class="array-object-label">${labels[idx] ?? fallbackLabel}</span>
3396
- ${renderOperandEditor(arg, (newArg) => {
3397
- const updated = [...safeArgs];
3398
- updated[idx] = newArg;
3399
- onChange(updated);
3400
- }, { ...opts, mustBeRef: false })}
3401
- <sp-action-button
3402
- quiet
3403
- size="xs"
3404
- @click=${() => {
3405
- const updated = safeArgs.filter((_, i) => i !== idx);
3406
- onChange(updated.length > 0 ? updated : [null]);
3407
- }}
3408
- >
3409
- <sp-icon-delete slot="icon"></sp-icon-delete>
3410
- </sp-action-button>
3411
- </div>
3412
- `)}
3413
- <sp-action-button quiet size="s" @click=${() => onChange([...safeArgs, null])}>
3414
- + Add arg
3415
- </sp-action-button>
3416
- </div>
3417
- `;
3418
- }
3419
- var _operatorMenuCache = OPERATOR_GROUPS.map((group, i) => html`
3420
- ${i > 0 ? html`<sp-menu-divider></sp-menu-divider>` : nothing}
3421
- <sp-menu-group>
3422
- <span slot="header">${group.label}</span>
3423
- ${group.ops.map((op) => html`<sp-menu-item value=${op}>${op}</sp-menu-item>`)}
3424
- </sp-menu-group>
3425
- `);
3426
- function renderExpressionEditor(node, onChange, opts) {
3427
- const depth = opts.depth ?? 0;
3428
- const safeNode = node && typeof node === "object" ? node : { operator: "=", target: null };
3429
- const op = safeNode.operator || "=";
3430
- const info = operatorInfo(op);
3431
- const preview = opts.preview ?? null;
3432
- const path = opts.path ?? [];
3433
- const pathKey = path.join("/");
3434
- const sub = (...segs) => [...path, ...segs].join("/");
3435
- const withBadge = (widget, key) => html`<div class="expr-widget">${widget}${renderValueBadge(preview, key)}</div>`;
3436
- const rootBadge = depth === 0 && preview && !preview.mutating ? renderValueBadge(preview, pathKey) : nothing;
3437
- return html`
3438
- <div class=${depth > 0 ? "expression-editor expression-editor--nested" : "expression-editor"}>
3439
- ${depth === 0 ? renderFormulaChips(safeNode, opts.onChipSelect ?? (() => {}), { path, preview }) : nothing}
3440
- ${depth === 0 && preview?.error ? html`<div class="expr-error">${preview.error}</div>` : nothing}
3441
- ${renderFieldRow({
3442
- hasValue: false,
3443
- label: "Operator",
3444
- prop: "operator",
3445
- widget: html`
3446
- <div class="expr-widget">
3447
- <sp-picker
3448
- size="s"
3449
- class="expr-operator"
3450
- .value=${live(op)}
3451
- @change=${(e) => {
3452
- const newOp = e.target.value;
3453
- const newInfo = operatorInfo(newOp);
3454
- const updated = {
3455
- operator: newOp,
3456
- target: safeNode.target
3457
- };
3458
- if (newInfo.targetMustBeRef && operandMode(safeNode.target) !== "ref") {
3459
- updated.target = { $ref: "" };
3460
- }
3461
- if (newInfo.needsValue) {
3462
- if (newInfo.valueIsNode) {
3463
- const val = safeNode.value;
3464
- updated.value = val?.operator ? safeNode.value : { operator: "!", target: null };
3465
- } else if (newInfo.spliceArray) {
3466
- updated.value = Array.isArray(safeNode.value) ? safeNode.value : [null];
3467
- } else {
3468
- updated.value = safeNode.value ?? null;
3469
- }
3470
- }
3471
- if (newInfo.needsInitial) {
3472
- updated.initial = safeNode.initial ?? (newOp === "?:" ? null : 0);
3473
- }
3474
- if (newInfo.switchCases) {
3475
- updated.cases = isJsonObject(safeNode.cases) ? safeNode.cases : {};
3476
- if ("default" in safeNode) {
3477
- updated.default = safeNode.default;
3478
- }
3479
- }
3480
- if (newInfo.callArgs) {
3481
- updated.value = Array.isArray(safeNode.value) ? safeNode.value : [];
3482
- }
3483
- onChange(updated);
3484
- }}
3485
- >
3486
- ${_operatorMenuCache}
3487
- </sp-picker>
3488
- <sp-action-button
3489
- quiet
3490
- size="s"
3491
- class="expr-browse-catalog"
3492
- title="Browse catalog"
3493
- @click=${(e) => openFormulaPalette({
3494
- anchor: e.currentTarget,
3495
- entries: formulaCatalog(opts.stateEntries),
3496
- onPick: (entry) => applyCatalogPick(entry, onChange, {
3497
- onInsertDef: opts.onInsertDef,
3498
- stateEntries: opts.stateEntries
3499
- })
3500
- })}
3501
- >
3502
- <sp-icon-brackets slot="icon"></sp-icon-brackets>
3503
- </sp-action-button>
3504
- ${rootBadge}
3505
- </div>
3506
- `
3507
- })}
3508
- ${renderFieldRow({
3509
- hasValue: false,
3510
- label: op === "?:" ? "If" : op === "switch" ? "On" : op === "call" ? "Callee" : "Target",
3511
- prop: "target",
3512
- widget: withBadge(renderOperandEditor(safeNode.target, (t) => onChange({ ...safeNode, target: t }), {
3513
- ...opts,
3514
- depth,
3515
- mustBeRef: info.targetMustBeRef,
3516
- path: [...path, "target"]
3517
- }), sub("target"))
3518
- })}
3519
- ${info.needsValue && !info.valueIsNode && !info.spliceArray ? renderFieldRow({
3520
- hasValue: false,
3521
- label: op === "?:" ? "Then" : "Value",
3522
- prop: "value",
3523
- widget: withBadge(renderOperandEditor(safeNode.value, (v) => onChange({ ...safeNode, value: v }), {
3524
- ...opts,
3525
- depth,
3526
- mustBeRef: false,
3527
- path: [...path, "value"]
3528
- }), sub("value"))
3529
- }) : nothing}
3530
- ${info.needsValue && info.valueIsNode ? html`
3531
- <div class="expr-subsection">
3532
- ${renderFieldRow({
3533
- hasValue: false,
3534
- label: "Per-item",
3535
- prop: "value",
3536
- widget: nothing
3537
- })}
3538
- ${renderExpressionEditor(safeNode.value?.operator ? safeNode.value : { operator: "!", target: null }, (v) => onChange({ ...safeNode, value: v }), { ...opts, depth: depth + 1, path: [...path, "value"] })}
3539
- </div>
3540
- ` : nothing}
3541
- ${info.spliceArray ? html`
3542
- <div class="expr-subsection">
3543
- ${renderFieldRow({
3544
- hasValue: false,
3545
- label: "Args",
3546
- prop: "value",
3547
- widget: nothing
3548
- })}
3549
- ${renderSpliceArgsEditor(safeNode.value, (v) => onChange({ ...safeNode, value: v }), {
3550
- ...opts,
3551
- depth
3552
- })}
3553
- </div>
3554
- ` : nothing}
3555
- ${info.callArgs ? html`
3556
- <div class="expr-subsection">
3557
- ${renderFieldRow({
3558
- hasValue: false,
3559
- label: "Args",
3560
- prop: "value",
3561
- widget: nothing
3562
- })}
3563
- ${renderSpliceArgsEditor(safeNode.value, (v) => onChange({ ...safeNode, value: v }), { ...opts, depth }, {
3564
- fallbackLabel: "arg",
3565
- labels: calleeParamLabels(safeNode.target, opts.stateEntries)
3566
- })}
3567
- </div>
3568
- ` : nothing}
3569
- ${info.switchCases ? renderSwitchCasesEditor(safeNode, onChange, { ...opts, depth, path }, withBadge) : nothing}
3570
- ${info.needsInitial ? renderFieldRow({
3571
- hasValue: false,
3572
- label: op === "?:" ? "Else" : "Initial",
3573
- prop: "initial",
3574
- widget: withBadge(renderOperandEditor(safeNode.initial, (v) => onChange({ ...safeNode, initial: v }), {
3575
- ...opts,
3576
- depth,
3577
- mustBeRef: false,
3578
- path: [...path, "initial"]
3579
- }), sub("initial"))
3580
- }) : nothing}
3581
- </div>
3582
- `;
3583
- }
3584
- function renderSwitchCasesEditor(safeNode, onChange, opts, withBadge) {
3585
- const cases = isJsonObject(safeNode.cases) ? safeNode.cases : {};
3586
- const entries = Object.entries(cases);
3587
- const setCases = (next) => onChange({ ...safeNode, cases: next });
3588
- return html`
3589
- <div class="switch-cases">
3590
- ${entries.map(([key, operand]) => html`
3591
- <div class="switch-case-row">
3592
- <sp-textfield
3593
- size="s"
3594
- class="switch-case-key"
3595
- placeholder="value"
3596
- .value=${live(key)}
3597
- @change=${(e) => {
3598
- const newKey = e.target.value;
3599
- if (newKey === key) {
3600
- return;
3601
- }
3602
- const next = {};
3603
- for (const [k, v] of entries) {
3604
- next[k === key ? newKey : k] = v;
3605
- }
3606
- setCases(next);
3607
- }}
3608
- ></sp-textfield>
3609
- ${withBadge(renderOperandEditor(operand, (v) => setCases({ ...cases, [key]: v }), {
3610
- ...opts,
3611
- mustBeRef: false,
3612
- path: [...opts.path, "cases", key]
3613
- }), [...opts.path, "cases", key].join("/"))}
3614
- <sp-action-button
3615
- quiet
3616
- size="xs"
3617
- @click=${() => {
3618
- const next = { ...cases };
3619
- delete next[key];
3620
- setCases(next);
3621
- }}
3622
- >
3623
- <sp-icon-delete slot="icon"></sp-icon-delete>
3624
- </sp-action-button>
3625
- </div>
3626
- `)}
3627
- <div class="switch-case-row">
3628
- <span class="switch-case-key switch-case-default">default</span>
3629
- ${withBadge(renderOperandEditor(safeNode.default, (v) => onChange({ ...safeNode, default: v }), {
3630
- ...opts,
3631
- mustBeRef: false,
3632
- path: [...opts.path, "default"]
3633
- }), [...opts.path, "default"].join("/"))}
3634
- </div>
3635
- <sp-action-button
3636
- quiet
3637
- size="s"
3638
- @click=${() => {
3639
- let n = entries.length + 1;
3640
- let key = `case ${n}`;
3641
- while (Object.hasOwn(cases, key)) {
3642
- n += 1;
3643
- key = `case ${n}`;
3644
- }
3645
- setCases({ ...cases, [key]: null });
3646
- }}
3647
- >
3648
- + Add case
3649
- </sp-action-button>
3650
- </div>
3651
- `;
3652
- }
3653
-
3654
1670
  // src/ui/dynamic-slot.ts
3655
1671
  var slotModeMemory = new Map;
3656
1672
  var slotModeRendered = new Map;
3657
1673
  function memoryKey(fieldKey, mode) {
3658
- return `${activeTab.value?.id ?? "-"}|${fieldKey}|${mode}`;
1674
+ return `${activeTab2.value?.id ?? "-"}|${fieldKey}|${mode}`;
3659
1675
  }
3660
1676
  function fieldScopeKey(fieldKey) {
3661
- return `${activeTab.value?.id ?? "-"}|${fieldKey}`;
1677
+ return `${activeTab2.value?.id ?? "-"}|${fieldKey}`;
3662
1678
  }
3663
1679
  function stashSlotValue(fieldKey, mode, value) {
3664
- slotModeMemory.set(memoryKey(fieldKey, mode), cloneValue(value));
1680
+ slotModeMemory.set(memoryKey(fieldKey, mode), cloneValue2(value));
3665
1681
  }
3666
1682
  function hasStashedSlotValue(fieldKey, mode) {
3667
1683
  return slotModeMemory.has(memoryKey(fieldKey, mode));
3668
1684
  }
3669
1685
  function recallSlotValue(fieldKey, mode) {
3670
- return cloneValue(slotModeMemory.get(memoryKey(fieldKey, mode)));
1686
+ return cloneValue2(slotModeMemory.get(memoryKey(fieldKey, mode)));
3671
1687
  }
3672
1688
  function setSlotMode(fieldKey, mode) {
3673
1689
  slotModeRendered.set(fieldScopeKey(fieldKey), mode);
@@ -3683,17 +1699,14 @@ function effectiveSlotMode(fieldKey, value) {
3683
1699
  slotModeRendered.set(key, mode);
3684
1700
  return mode;
3685
1701
  }
3686
- function hasRefOptions(opts) {
3687
- return opts.stateDefs.length > 0 || Boolean(opts.extraSignals?.length);
3688
- }
3689
- function defaultForSlotMode(mode, opts) {
1702
+ function slotModeSeed(mode, sources) {
3690
1703
  switch (mode) {
3691
1704
  case "ref": {
3692
- const [first] = opts.stateDefs;
3693
- 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 ?? "" };
3694
1707
  }
3695
1708
  case "template": {
3696
- const [first] = opts.stateDefs;
1709
+ const [first] = sources.stateDefs;
3697
1710
  return first ? `\${state.${first}}` : "${}";
3698
1711
  }
3699
1712
  case "expression": {
@@ -3703,153 +1716,1092 @@ function defaultForSlotMode(mode, opts) {
3703
1716
  return { $prototype: "Function", body: "", parameters: [] };
3704
1717
  }
3705
1718
  default: {
3706
- return opts.literalDefault;
1719
+ return sources.literalDefault;
3707
1720
  }
3708
1721
  }
3709
1722
  }
3710
- function refOptions(opts) {
3711
- return [
3712
- ...opts.stateDefs.map((name) => ({ label: name, value: `#/state/${name}` })),
3713
- ...opts.extraSignals ?? []
3714
- ];
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;
3715
1727
  }
3716
- function renderCustomRefWidget(refVal, opts) {
3717
- return html`
3718
- <jx-value-selector
3719
- size="s"
3720
- style="flex:1"
3721
- placeholder="#/state/…"
3722
- .value=${refVal}
3723
- .options=${refOptions(opts)}
3724
- @change=${(e) => {
3725
- const v = (e.detail?.value ?? e.target.value ?? "").trim();
3726
- if (v) {
3727
- opts.onChange({ $ref: v });
3728
- } else {
3729
- 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"
3730
1827
  }
3731
- }}
3732
- ></jx-value-selector>
3733
- `;
3734
- }
3735
- function renderRefWidget(refVal, opts) {
3736
- if (opts.allowCustomRef) {
3737
- return renderCustomRefWidget(refVal, opts);
3738
- }
3739
- return html`
3740
- <sp-picker
3741
- size="s"
3742
- quiet
3743
- style="flex:1"
3744
- placeholder="— select signal —"
3745
- value=${refVal || nothing}
3746
- @change=${(e) => {
3747
- const v = e.target.value;
3748
- if (v) {
3749
- opts.onChange({ $ref: v });
3750
- } else {
3751
- 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
+ }
3752
2739
  }
3753
- }}
3754
- >
3755
- ${opts.stateDefs.map((name) => html`<sp-menu-item value=${`#/state/${name}`}>${name}</sp-menu-item>`)}
3756
- ${opts.extraSignals?.length ? html`
3757
- <sp-menu-divider></sp-menu-divider>
3758
- ${opts.extraSignals.map((sig) => html`<sp-menu-item value=${sig.value}>${sig.label}</sp-menu-item>`)}
3759
- ` : nothing}
3760
- </sp-picker>
3761
- `;
3762
- }
3763
- function renderTemplateWidget(value, opts) {
3764
- return html`
3765
- <sp-textfield
3766
- size="s"
3767
- style="flex:1;font-family:var(--spectrum-code-font-family, monospace)"
3768
- placeholder="\${state.…}"
3769
- .value=${live(String(value ?? ""))}
3770
- @change=${(e) => opts.onChange(e.target.value)}
3771
- ></sp-textfield>
3772
- `;
3773
- }
3774
- function switchSlotMode(fieldKey, from, to, current, seed) {
3775
- stashSlotValue(fieldKey, from, current);
3776
- setSlotMode(fieldKey, to);
3777
- return hasStashedSlotValue(fieldKey, to) ? recallSlotValue(fieldKey, to) : seed;
3778
- }
3779
- function chipId(fieldKey) {
3780
- return `value-source-${fieldKey.replaceAll(/[^\dA-Za-z]+/g, "-")}`;
3781
- }
3782
- function renderModeChip(mode, caps, opts) {
3783
- const color = mode === "literal" ? "color:var(--spectrum-gray-600, #808080)" : "color:var(--spectrum-accent-content-color-default, #5c9dff)";
3784
- const offered = caps.filter((m) => m !== "ref" || hasRefOptions(opts));
3785
- const label = VALUE_SOURCE_LABELS[mode];
3786
- if (!offered.some((m) => m !== mode)) {
3787
- const hint2 = `Value source: ${label} (no other source available here)`;
3788
- return html`
3789
- <sp-action-button
3790
- size="xs"
3791
- quiet
3792
- disabled
3793
- class="dynamic-slot-mode"
3794
- style=${color}
3795
- title=${hint2}
3796
- aria-label=${hint2}
3797
- >${label}</sp-action-button
3798
- >
3799
- `;
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
+ });
3800
2785
  }
3801
- const id = chipId(opts.fieldKey);
3802
- const hint = `Value source: ${label} — click to change`;
3803
- const choose = (next) => {
3804
- if (next === mode) {
3805
- return;
3806
- }
3807
- if (mode === "ref" && opts.literalDefault !== undefined && !hasStashedSlotValue(opts.fieldKey, "literal")) {
3808
- stashSlotValue(opts.fieldKey, "literal", opts.literalDefault);
3809
- }
3810
- opts.onChange(switchSlotMode(opts.fieldKey, mode, next, cloneValue(opts.value), opts.seedFor?.(next) ?? defaultForSlotMode(next, opts)));
3811
- };
3812
- return html`
3813
- <sp-action-button
3814
- id=${id}
3815
- size="xs"
3816
- quiet
3817
- class="dynamic-slot-mode"
3818
- style=${color}
3819
- title=${hint}
3820
- aria-label=${hint}
3821
- >${label}</sp-action-button
3822
- >
3823
- <sp-overlay trigger="${id}@click" placement="bottom-start" type="auto">
3824
- <sp-popover>
3825
- <sp-menu size="s" label="Value source">
3826
- ${offered.map((m) => html`
3827
- <sp-menu-item data-mode=${m} ?selected=${m === mode} @click=${() => choose(m)}
3828
- >${VALUE_SOURCE_LABELS[m]}
3829
- <span slot="description">${VALUE_SOURCE_HINTS[m]}</span></sp-menu-item
3830
- >
3831
- `)}
3832
- </sp-menu>
3833
- </sp-popover>
3834
- </sp-overlay>
3835
- `;
3836
- }
3837
- function renderDynamicSlot(opts) {
3838
- const caps = slotCaps(opts.caps);
3839
- const mode = effectiveSlotMode(opts.fieldKey, opts.value);
3840
- 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 }), {
3841
- allowEventRef: opts.allowEventRef ?? false,
3842
- preview: opts.preview ?? null,
3843
- stateDefs: opts.stateDefs,
3844
- stateEntries: opts.stateEntries ?? null
3845
- }) : opts.staticWidget;
2786
+ mount();
3846
2787
  return {
3847
- modeButton: renderModeChip(mode, caps, opts),
3848
- widget: html`
3849
- <div class="dynamic-slot" style="display:flex;flex:1;min-width:0">
3850
- <div style="flex:1;min-width:0">${widget}</div>
3851
- </div>
3852
- `
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
+ }
3853
2805
  };
3854
2806
  }
3855
2807
 
@@ -3858,9 +2810,6 @@ var controlRegistry = new Map;
3858
2810
  function registerFormControl(name, control) {
3859
2811
  controlRegistry.set(name, control);
3860
2812
  }
3861
- function getFormControl(name) {
3862
- return controlRegistry.get(name);
3863
- }
3864
2813
  var CONTENT_REF = /^#\/content\/([^/]+)$/;
3865
2814
  function referenceTarget(schema) {
3866
2815
  const match = typeof schema?.$ref === "string" ? CONTENT_REF.exec(schema.$ref) : null;
@@ -3875,9 +2824,9 @@ function resolveFormEnum(enumDef, ctx, scope) {
3875
2824
  }
3876
2825
  let pointer;
3877
2826
  if (enumDef && typeof enumDef === "object") {
3878
- const ref2 = enumDef.$ref;
3879
- if (typeof ref2 === "string") {
3880
- pointer = ref2;
2827
+ const ref = enumDef.$ref;
2828
+ if (typeof ref === "string") {
2829
+ pointer = ref;
3881
2830
  }
3882
2831
  } else if (typeof enumDef === "string") {
3883
2832
  pointer = enumDef;
@@ -3900,258 +2849,6 @@ function parseNumericField(raw, integer) {
3900
2849
  }
3901
2850
  return integer ? Math.trunc(Number(raw)) : Number(raw);
3902
2851
  }
3903
- function refTextField(key, refVal, onChange) {
3904
- return html`<sp-textfield
3905
- size="s"
3906
- label=${key}
3907
- placeholder=${key}
3908
- .value=${live(refVal)}
3909
- @change=${(e) => {
3910
- const v = e.target.value.trim();
3911
- onChange(v ? { $ref: v } : undefined);
3912
- }}
3913
- ></sp-textfield>`;
3914
- }
3915
- function renderInlineField(key, schema, value, onChange, ctx, scope, rerender) {
3916
- if (isRef(value)) {
3917
- return refTextField(key, value.$ref, onChange);
3918
- }
3919
- const referenceControl = referenceTarget(schema) === null ? undefined : controlRegistry.get("reference");
3920
- if (referenceControl) {
3921
- return referenceControl({
3922
- ctx: ctx ?? NULL_FORM_CONTEXT,
3923
- key,
3924
- onChange,
3925
- rerender,
3926
- schema,
3927
- value
3928
- });
3929
- }
3930
- const enumValues = resolveFormEnum(schema.enum, ctx, scope);
3931
- if (enumValues) {
3932
- return html`<sp-picker
3933
- size="s"
3934
- label=${key}
3935
- value=${value !== undefined ? String(value) : "__none__"}
3936
- @change=${(e) => onChange(e.target.value === "__none__" ? undefined : e.target.value)}
3937
- >
3938
- <sp-menu-item value="__none__">—</sp-menu-item>
3939
- ${enumValues.map((v) => html`<sp-menu-item value=${v}>${v}</sp-menu-item>`)}
3940
- </sp-picker>`;
3941
- }
3942
- if (schema.type === "boolean") {
3943
- return html`<sp-switch
3944
- size="s"
3945
- ?checked=${Boolean(value)}
3946
- @change=${(e) => onChange(e.target.checked)}
3947
- >${key}</sp-switch
3948
- >`;
3949
- }
3950
- if (schema.type === "integer" || schema.type === "number") {
3951
- return html`<sp-number-field
3952
- size="s"
3953
- label=${key}
3954
- .value=${value !== undefined ? value : nothing}
3955
- step=${schema.type === "integer" ? "1" : nothing}
3956
- @change=${(e) => {
3957
- const parsed = parseNumericField(e.target.value, schema.type === "integer");
3958
- onChange(Number.isNaN(parsed) ? undefined : parsed);
3959
- }}
3960
- ></sp-number-field>`;
3961
- }
3962
- return html`<sp-textfield
3963
- size="s"
3964
- label=${key}
3965
- placeholder=${key}
3966
- .value=${value ?? ""}
3967
- @input=${(e) => onChange(e.target.value || undefined)}
3968
- ></sp-textfield>`;
3969
- }
3970
- function renderJsonTextField(currentValue, ps, commit) {
3971
- let debounce;
3972
- return html`<sp-textfield
3973
- multiline
3974
- size="s"
3975
- style="min-height:40px"
3976
- .value=${currentValue !== undefined ? JSON.stringify(currentValue, null, 2) : ""}
3977
- placeholder=${ps.default !== undefined ? JSON.stringify(ps.default) : nothing}
3978
- @input=${(e) => {
3979
- clearTimeout(debounce);
3980
- debounce = setTimeout(() => {
3981
- try {
3982
- commit(JSON.parse(e.target.value));
3983
- } catch {}
3984
- }, 500);
3985
- }}
3986
- ></sp-textfield>`;
3987
- }
3988
- function renderPropertyControl(prop, ps, value, required, opts, ctx) {
3989
- const currentValue = value[prop];
3990
- const commit = (next) => opts.onChange({ [prop]: next });
3991
- const controlArgs = {
3992
- ctx,
3993
- key: prop,
3994
- onChange: commit,
3995
- rerender: opts.rerender,
3996
- schema: ps,
3997
- value: currentValue
3998
- };
3999
- const overrideName = opts.ui?.[prop]?.control;
4000
- if (overrideName) {
4001
- const custom = controlRegistry.get(overrideName);
4002
- if (custom) {
4003
- return custom(controlArgs);
4004
- }
4005
- }
4006
- if (isRef(currentValue) && isBindableField(ps)) {
4007
- return refTextField(prop, currentValue.$ref, commit);
4008
- }
4009
- if (referenceTarget(ps) !== null) {
4010
- const referenceControl = controlRegistry.get("reference");
4011
- if (referenceControl) {
4012
- return referenceControl(controlArgs);
4013
- }
4014
- }
4015
- const enumValues = resolveFormEnum(opts.ui?.[prop]?.enum ?? ps.enum, ctx, value);
4016
- if (enumValues) {
4017
- return html`
4018
- <sp-picker
4019
- size="s"
4020
- value=${currentValue !== undefined ? String(currentValue) : ps.default !== undefined ? String(ps.default) : "__none__"}
4021
- @change=${(e) => commit(e.target.value === "__none__" ? undefined : e.target.value)}
4022
- >
4023
- ${!required.has(prop) ? html`<sp-menu-item value="__none__">—</sp-menu-item>` : nothing}
4024
- ${enumValues.map((val) => html`<sp-menu-item value=${val}>${val}</sp-menu-item>`)}
4025
- </sp-picker>
4026
- `;
4027
- }
4028
- if (ps.type === "boolean") {
4029
- return html`<sp-checkbox
4030
- ?checked=${currentValue ?? ps.default ?? false}
4031
- @change=${(e) => commit(e.target.checked)}
4032
- ></sp-checkbox>`;
4033
- }
4034
- if (ps.type === "integer" || ps.type === "number") {
4035
- let debounce2;
4036
- return html`<sp-number-field
4037
- size="s"
4038
- min=${ifDefined(ps.minimum)}
4039
- max=${ifDefined(ps.maximum)}
4040
- step=${ps.type === "integer" ? "1" : nothing}
4041
- .value=${currentValue !== undefined ? currentValue : nothing}
4042
- placeholder=${ps.default != null ? String(ps.default) : nothing}
4043
- @change=${(e) => {
4044
- clearTimeout(debounce2);
4045
- debounce2 = setTimeout(() => {
4046
- const parsed = parseNumericField(e.target.value, ps.type === "integer");
4047
- commit(Number.isNaN(parsed) ? undefined : parsed);
4048
- }, 400);
4049
- }}
4050
- ></sp-number-field>`;
4051
- }
4052
- if (ps.format === "json-schema") {
4053
- const hasValue = currentValue && typeof currentValue === "object" && Object.keys(currentValue).length > 0;
4054
- const cv = currentValue;
4055
- const isSchemaRef = hasValue && cv.$ref;
4056
- let debounce2;
4057
- return html`
4058
- <div class="schema-param-editor">
4059
- ${hasValue && !isSchemaRef && cv.properties ? html`
4060
- <div style="display:flex;flex-wrap:wrap;gap:3px;margin-bottom:4px">
4061
- ${Object.entries(cv.properties).map(([k, v]) => html`
4062
- <span
4063
- style="background:var(--bg);padding:1px 6px;border-radius:var(--radius);font-size:10px;color:var(--fg-dim)"
4064
- >${k}: ${v.type ?? "any"}</span
4065
- >
4066
- `)}
4067
- </div>
4068
- ` : nothing}
4069
- <sp-textfield
4070
- multiline
4071
- size="s"
4072
- style=${styleMap({
4073
- fontFamily: "monospace",
4074
- fontSize: "11px",
4075
- minHeight: hasValue ? "80px" : "40px"
4076
- })}
4077
- .value=${currentValue !== undefined ? JSON.stringify(currentValue, null, 2) : ""}
4078
- placeholder=${ps.description ?? "JSON Schema defining the data shape…"}
4079
- @input=${(e) => {
4080
- clearTimeout(debounce2);
4081
- debounce2 = setTimeout(() => {
4082
- try {
4083
- commit(JSON.parse(e.target.value));
4084
- } catch {}
4085
- }, 500);
4086
- }}
4087
- ></sp-textfield>
4088
- </div>
4089
- `;
4090
- }
4091
- if (ps.type === "array" && ps.items?.type === "object" && ps.items?.properties) {
4092
- const rows = Array.isArray(currentValue) ? currentValue : [];
4093
- const itemProps = ps.items.properties;
4094
- return html`
4095
- <div class="array-object-field">
4096
- ${rows.map((row, idx) => html`
4097
- <div
4098
- class="array-object-row"
4099
- style="display:flex;gap:4px;align-items:center;margin-bottom:4px"
4100
- >
4101
- ${Object.entries(itemProps).map(([propKey, propSchema]) => renderInlineField(propKey, propSchema, row[propKey], (val) => {
4102
- const updated = [...rows];
4103
- updated[idx] = { ...updated[idx], [propKey]: val };
4104
- commit(updated);
4105
- }, ctx, value, opts.rerender))}
4106
- <sp-action-button
4107
- quiet
4108
- size="s"
4109
- @click=${() => {
4110
- const updated = rows.filter((_, i) => i !== idx);
4111
- commit(updated.length > 0 ? updated : undefined);
4112
- opts.rerender?.();
4113
- }}
4114
- >
4115
- <sp-icon-delete slot="icon"></sp-icon-delete>
4116
- </sp-action-button>
4117
- </div>
4118
- `)}
4119
- <sp-action-button
4120
- quiet
4121
- size="s"
4122
- @click=${(e) => {
4123
- e.stopPropagation();
4124
- const newRow = {};
4125
- for (const [k, v] of Object.entries(itemProps)) {
4126
- if (v.default !== undefined) {
4127
- newRow[k] = v.default;
4128
- }
4129
- }
4130
- commit([...rows, newRow]);
4131
- opts.rerender?.();
4132
- }}
4133
- >+ Add</sp-action-button
4134
- >
4135
- </div>
4136
- `;
4137
- }
4138
- if (ps.type === "array" || ps.type === "object") {
4139
- return renderJsonTextField(currentValue, ps, commit);
4140
- }
4141
- let debounce;
4142
- const ph = ps.default !== undefined ? String(ps.default) : ps.examples?.[0] ?? "";
4143
- return html`<sp-textfield
4144
- size="s"
4145
- style="flex:1"
4146
- .value=${currentValue ?? ""}
4147
- placeholder=${ph || nothing}
4148
- title=${ps.description || nothing}
4149
- @input=${(e) => {
4150
- clearTimeout(debounce);
4151
- debounce = setTimeout(() => commit(e.target.value || undefined), 400);
4152
- }}
4153
- ></sp-textfield>`;
4154
- }
4155
2852
  function isBindableField(ps) {
4156
2853
  return ps.format !== "json-schema" && ps.type !== "object" && ps.type !== "array";
4157
2854
  }
@@ -4161,8 +2858,15 @@ function refSourcesFor(ctx) {
4161
2858
  ...(ctx.params ?? []).map((name) => ({ label: `$params/${name}`, value: `#/$params/${name}` }))
4162
2859
  ];
4163
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
+ }
4164
2868
  function validateFieldValue(schema, value, isRequired) {
4165
- if (isRef(value)) {
2869
+ if (isRef2(value)) {
4166
2870
  return "";
4167
2871
  }
4168
2872
  const empty = value === undefined || value === null || value === "";
@@ -4199,39 +2903,603 @@ function validateFieldValue(schema, value, isRequired) {
4199
2903
  }
4200
2904
  return "";
4201
2905
  }
4202
- function renderForm(schema, value, opts) {
4203
- const required = new Set(schema.required);
4204
- const ctx = opts.context ?? NULL_FORM_CONTEXT;
4205
- const refSources = refSourcesFor(ctx);
4206
- const propertyFields = Object.entries(schema.properties ?? {}).map(([prop, ps]) => {
4207
- const labelText = prop + (required.has(prop) ? " *" : "");
4208
- const error = opts.errors?.[prop] || validateFieldValue(ps, value[prop], Boolean(opts.showRequired) && required.has(prop));
4209
- const count = opts.errorCounts?.[prop];
4210
- const staticWidget = renderPropertyControl(prop, ps, value, required, opts, ctx);
4211
- const laddered = refSources.length > 0 && isBindableField(ps) && !opts.ui?.[prop]?.control ? renderDynamicSlot({
4212
- allowCustomRef: true,
4213
- caps: { schema: configFieldSchema(ps) },
4214
- extraSignals: refSources,
4215
- fieldKey: `${ctx.fieldKeyPrefix ?? ""}.${prop}`,
4216
- onChange: (v) => opts.onChange({ [prop]: v }),
4217
- staticWidget,
4218
- stateDefs: [],
4219
- value: value[prop]
4220
- }) : null;
4221
- return renderFieldRow({
4222
- hasValue: false,
4223
- label: labelText,
4224
- prop: ps.name || prop,
4225
- widget: laddered ? laddered.widget : staticWidget,
4226
- ...laddered ? { labelExtra: laddered.modeButton } : {},
4227
- ...error ? { error } : {},
4228
- ...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
+ }
4229
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);
4230
3189
  });
4231
- 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;
4232
3500
  }
4233
3501
 
4234
- 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 };
4235
3503
 
4236
- //# debugId=DDC68693A8D15CAC64756E2164756E21
4237
- //# sourceMappingURL=studio-x4qkdat5.js.map
3504
+ //# debugId=427B709FE510BC3D64756E2164756E21
3505
+ //# sourceMappingURL=studio-js8nv53n.js.map