@jxsuite/studio 2.0.1 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (419) hide show
  1. package/canvas.html +21 -0
  2. package/dist/chunks/Overlay.dev-qfjzkms8.js +23 -0
  3. package/dist/chunks/{Overlay.dev-cszx7yn5.js.map → Overlay.dev-qfjzkms8.js.map} +1 -1
  4. package/dist/chunks/{adapter-z8fkmbmv.js → adapter-qfnesn5v.js} +2 -2
  5. package/dist/chunks/{cf-settings-mmyv436d.js → cf-settings-73sh5z4p.js} +3 -3
  6. package/dist/chunks/{components-799jpvhm.js → components-2bpqp4mq.js} +5 -7
  7. package/dist/chunks/{components-799jpvhm.js.map → components-2bpqp4mq.js.map} +1 -1
  8. package/dist/chunks/content-source-34gnkbnn.js +38 -0
  9. package/dist/chunks/content-source-34gnkbnn.js.map +9 -0
  10. package/dist/chunks/convert-to-repeater-t8kxre5s.js +258 -0
  11. package/dist/chunks/convert-to-repeater-t8kxre5s.js.map +10 -0
  12. package/dist/chunks/extension-sections-6mqydz35.js +301 -0
  13. package/dist/chunks/extension-sections-6mqydz35.js.map +11 -0
  14. package/dist/chunks/files-8kn321h1.js +70 -0
  15. package/dist/chunks/files-8kn321h1.js.map +9 -0
  16. package/dist/chunks/{format-host-8jxwts2p.js → format-host-mkwsmt8d.js} +3 -3
  17. package/dist/chunks/git-panel-tjbr191d.js +51 -0
  18. package/dist/chunks/git-panel-tjbr191d.js.map +9 -0
  19. package/dist/chunks/{index-baeg2x3y.js → index-7pxchcce.js} +4 -3
  20. package/dist/chunks/{index-baeg2x3y.js.map → index-7pxchcce.js.map} +3 -3
  21. package/dist/chunks/{jx-validate-qx8degj7.js → jx-validate-v6qgknrs.js} +3 -3
  22. package/dist/chunks/layers-panel-87pb25km.js +58 -0
  23. package/dist/chunks/layers-panel-87pb25km.js.map +9 -0
  24. package/dist/chunks/{monaco-setup-zjdp9zdw.js → monaco-setup-e0yh25j4.js} +4 -75
  25. package/dist/chunks/{monaco-setup-zjdp9zdw.js.map → monaco-setup-e0yh25j4.js.map} +3 -4
  26. package/dist/chunks/publish-panel-m0fcgdrt.js +21 -0
  27. package/dist/chunks/publish-panel-m0fcgdrt.js.map +9 -0
  28. package/dist/chunks/quick-search-rhs6cbsb.js +53 -0
  29. package/dist/chunks/quick-search-rhs6cbsb.js.map +9 -0
  30. package/dist/chunks/right-panel-zpdxd98t.js +37 -0
  31. package/dist/chunks/right-panel-zpdxd98t.js.map +9 -0
  32. package/dist/chunks/section-registry-zmckaxf4.js +30 -0
  33. package/dist/chunks/section-registry-zmckaxf4.js.map +9 -0
  34. package/dist/chunks/sp-overlay.dev-9ran0sfq.js +18 -0
  35. package/dist/chunks/sp-overlay.dev-9ran0sfq.js.map +9 -0
  36. package/dist/chunks/sp-popover.dev-rebmdncr.js +9 -0
  37. package/dist/chunks/sp-popover.dev-rebmdncr.js.map +9 -0
  38. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js +15 -0
  39. package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js.map +9 -0
  40. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js +15 -0
  41. package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js.map +9 -0
  42. package/dist/chunks/{sp-tray.dev-pb2fsd65.js → sp-tray.dev-ey59vfh7.js} +10 -8
  43. package/dist/chunks/{sp-tray.dev-pb2fsd65.js.map → sp-tray.dev-ey59vfh7.js.map} +2 -2
  44. package/dist/chunks/{studio-9qr6fq7p.js → studio-0rqqhrdy.js} +10 -378
  45. package/dist/chunks/{studio-9qr6fq7p.js.map → studio-0rqqhrdy.js.map} +4 -14
  46. package/dist/chunks/{studio-5x6tqqec.js → studio-2sj1jj3v.js} +10 -8
  47. package/dist/chunks/{studio-5x6tqqec.js.map → studio-2sj1jj3v.js.map} +2 -2
  48. package/dist/chunks/{studio-h3fw0wn8.js → studio-3fk1ac9z.js} +6 -4
  49. package/dist/chunks/{studio-h3fw0wn8.js.map → studio-3fk1ac9z.js.map} +2 -2
  50. package/dist/chunks/studio-41exgzhq.js +6690 -0
  51. package/dist/chunks/studio-41exgzhq.js.map +38 -0
  52. package/dist/chunks/{studio-7v2bpweq.js → studio-4mqb9a0j.js} +6 -4
  53. package/dist/chunks/{studio-7v2bpweq.js.map → studio-4mqb9a0j.js.map} +2 -2
  54. package/dist/chunks/studio-50k77cca.js +1787 -0
  55. package/dist/chunks/studio-50k77cca.js.map +18 -0
  56. package/dist/chunks/{studio-56v3zwk6.js → studio-6a0h7sne.js} +10 -8
  57. package/dist/chunks/{studio-56v3zwk6.js.map → studio-6a0h7sne.js.map} +2 -2
  58. package/dist/chunks/studio-6f0am8f2.js +53 -0
  59. package/dist/chunks/studio-6f0am8f2.js.map +10 -0
  60. package/dist/chunks/studio-6nmjc5qk.js +7002 -0
  61. package/dist/chunks/studio-6nmjc5qk.js.map +32 -0
  62. package/dist/chunks/studio-7afd21nw.js +67 -0
  63. package/dist/chunks/studio-7afd21nw.js.map +10 -0
  64. package/dist/chunks/{studio-xze4zrs1.js → studio-7bmdvzne.js} +2 -2
  65. package/dist/chunks/studio-7bmdvzne.js.map +10 -0
  66. package/dist/chunks/studio-8jpzqk5s.js +128 -0
  67. package/dist/chunks/studio-8jpzqk5s.js.map +10 -0
  68. package/dist/chunks/studio-94m77tgk.js +1482 -0
  69. package/dist/chunks/studio-94m77tgk.js.map +13 -0
  70. package/dist/chunks/studio-9je6w5sq.js +1000 -0
  71. package/dist/chunks/studio-9je6w5sq.js.map +16 -0
  72. package/dist/chunks/{studio-w7c6kvk7.js → studio-a7pzveq1.js} +2 -2
  73. package/dist/chunks/{studio-gs6kqdt1.js → studio-adn0z1k3.js} +14 -8
  74. package/dist/chunks/{studio-gs6kqdt1.js.map → studio-adn0z1k3.js.map} +3 -3
  75. package/dist/chunks/{studio-kr88ryr7.js → studio-bsxxx3x2.js} +17 -15
  76. package/dist/chunks/{studio-kr88ryr7.js.map → studio-bsxxx3x2.js.map} +7 -6
  77. package/dist/chunks/studio-bw5dz6km.js +608 -0
  78. package/dist/chunks/studio-bw5dz6km.js.map +21 -0
  79. package/dist/chunks/studio-c6s5getc.js +4232 -0
  80. package/dist/chunks/studio-c6s5getc.js.map +39 -0
  81. package/dist/chunks/studio-cjm7fdjf.js +451 -0
  82. package/dist/chunks/studio-cjm7fdjf.js.map +14 -0
  83. package/dist/chunks/{studio-cx91mnh8.js → studio-ckwz4d3x.js} +53 -53
  84. package/dist/chunks/{studio-cx91mnh8.js.map → studio-ckwz4d3x.js.map} +5 -5
  85. package/dist/chunks/studio-dg7zyt5x.js +28 -0
  86. package/dist/chunks/studio-dg7zyt5x.js.map +10 -0
  87. package/dist/chunks/studio-dzcyvvvz.js +166 -0
  88. package/dist/chunks/studio-dzcyvvvz.js.map +10 -0
  89. package/dist/chunks/{studio-7kqe41sa.js → studio-evkr5d3b.js} +15 -7
  90. package/dist/chunks/{studio-7kqe41sa.js.map → studio-evkr5d3b.js.map} +5 -4
  91. package/dist/chunks/studio-f12h6177.js +411 -0
  92. package/dist/chunks/studio-f12h6177.js.map +13 -0
  93. package/dist/chunks/studio-hbp1s8m9.js +1817 -0
  94. package/dist/chunks/studio-hbp1s8m9.js.map +14 -0
  95. package/dist/chunks/studio-k2sxjph5.js +15295 -0
  96. package/dist/chunks/studio-k2sxjph5.js.map +235 -0
  97. package/dist/chunks/studio-myje51t3.js +385 -0
  98. package/dist/chunks/studio-myje51t3.js.map +12 -0
  99. package/dist/chunks/studio-na6rwzhw.js +353 -0
  100. package/dist/chunks/studio-na6rwzhw.js.map +11 -0
  101. package/dist/chunks/{studio-4k0m4fmp.js → studio-p471gg5n.js} +126 -3
  102. package/dist/chunks/studio-p471gg5n.js.map +14 -0
  103. package/dist/chunks/{studio-9bjwf0d4.js → studio-rgwcpast.js} +5 -46
  104. package/dist/chunks/{studio-9bjwf0d4.js.map → studio-rgwcpast.js.map} +4 -5
  105. package/dist/chunks/{studio-esrewxap.js → studio-s519zc99.js} +3 -3
  106. package/dist/chunks/studio-sas7mt3k.js +75 -0
  107. package/dist/chunks/studio-sas7mt3k.js.map +10 -0
  108. package/dist/chunks/studio-t5wqwzdh.js +74 -0
  109. package/dist/chunks/studio-t5wqwzdh.js.map +10 -0
  110. package/dist/chunks/{studio-9qpspwfn.js → studio-temv5bw4.js} +5 -5
  111. package/dist/chunks/{studio-9qpspwfn.js.map → studio-temv5bw4.js.map} +1 -1
  112. package/dist/chunks/{studio-qkvt37c0.js → studio-tr20m8g8.js} +669 -18
  113. package/dist/chunks/studio-tr20m8g8.js.map +9 -0
  114. package/dist/chunks/{studio-v7jkjqng.js → studio-v96qfnww.js} +5514 -4291
  115. package/dist/chunks/studio-v96qfnww.js.map +54 -0
  116. package/dist/chunks/studio-vcetc91k.js +659 -0
  117. package/dist/chunks/studio-vcetc91k.js.map +10 -0
  118. package/dist/chunks/{studio-jm0z592w.js → studio-vmymjefp.js} +2972 -4937
  119. package/dist/chunks/studio-vmymjefp.js.map +98 -0
  120. package/dist/chunks/{studio-bghqfmea.js → studio-wwseqyw0.js} +2 -2
  121. package/dist/chunks/studio-x9hmy5kr.js +390 -0
  122. package/dist/chunks/studio-x9hmy5kr.js.map +10 -0
  123. package/dist/chunks/studio-xa0ce0n7.js +335 -0
  124. package/dist/chunks/studio-xa0ce0n7.js.map +10 -0
  125. package/dist/chunks/studio-xm3dbxvn.js +378 -0
  126. package/dist/chunks/studio-xm3dbxvn.js.map +19 -0
  127. package/dist/chunks/{studio-nqw433kv.js → studio-xmvbhg31.js} +2 -2
  128. package/dist/chunks/studio-zv37yrfa.js +1054 -0
  129. package/dist/chunks/studio-zv37yrfa.js.map +13 -0
  130. package/dist/chunks/{ws-client-q7hkz3j9.js → ws-client-654ts66j.js} +6 -4
  131. package/dist/chunks/ws-client-654ts66j.js.map +11 -0
  132. package/dist/iframe-entry.js +668 -230
  133. package/dist/studio.js +21080 -32585
  134. package/index.html +35 -4685
  135. package/package.json +17 -15
  136. package/src/about/about-modal.ts +42 -11
  137. package/src/browse/library-commands.ts +240 -0
  138. package/src/browse/library-layouts.ts +369 -0
  139. package/src/browse/library-model.ts +396 -0
  140. package/src/browse/library-pane.ts +1049 -0
  141. package/src/browse/library-preview.ts +252 -0
  142. package/src/browse/library-source.ts +150 -0
  143. package/src/canvas/canvas-helpers.ts +33 -9
  144. package/src/canvas/canvas-live-render.ts +45 -29
  145. package/src/canvas/canvas-patcher.ts +151 -31
  146. package/src/canvas/canvas-perf.ts +46 -7
  147. package/src/canvas/canvas-render.ts +1092 -220
  148. package/src/canvas/canvas-surface.ts +240 -0
  149. package/src/canvas/canvas-utils.ts +1166 -124
  150. package/src/canvas/iframe-drop.ts +2 -1
  151. package/src/canvas/iframe-entry.ts +241 -4
  152. package/src/canvas/iframe-host.ts +1226 -143
  153. package/src/canvas/iframe-inline-edit.ts +31 -0
  154. package/src/canvas/iframe-interaction.ts +76 -16
  155. package/src/canvas/iframe-keys.ts +107 -45
  156. package/src/canvas/iframe-overlay.ts +38 -1
  157. package/src/canvas/iframe-protocol.ts +137 -2
  158. package/src/canvas/iframe-render.ts +70 -2
  159. package/src/canvas/iframe-slash.ts +6 -0
  160. package/src/canvas/path-mapping.ts +56 -6
  161. package/src/canvas/surface-registry.ts +292 -0
  162. package/src/collab/collab-commands.ts +244 -0
  163. package/src/collab/collab-session.ts +198 -32
  164. package/src/collab/collab-state.ts +28 -2
  165. package/src/collab/presence-chips.ts +100 -9
  166. package/src/commands/active-registry.ts +42 -0
  167. package/src/commands/app-commands.ts +176 -0
  168. package/src/commands/budget.ts +158 -0
  169. package/src/commands/command-args.ts +253 -0
  170. package/src/commands/context.ts +387 -0
  171. package/src/commands/defaults.ts +580 -0
  172. package/src/commands/keymap.ts +571 -0
  173. package/src/commands/levels.ts +345 -0
  174. package/src/commands/live-context.ts +193 -0
  175. package/src/commands/reference.ts +227 -0
  176. package/src/commands/registry.ts +293 -0
  177. package/src/component-props.ts +3 -2
  178. package/src/content/draft-state.ts +106 -0
  179. package/src/content/entry-commands.ts +282 -0
  180. package/src/content/entry-editor.ts +327 -0
  181. package/src/content/entry-fields.ts +116 -0
  182. package/src/content/entry-model.ts +175 -0
  183. package/src/editor/canvas-context-menu.ts +17 -11
  184. package/src/editor/canvas-slash-bridge.ts +11 -4
  185. package/src/editor/context-menu.ts +804 -204
  186. package/src/editor/convert-to-component.ts +31 -10
  187. package/src/editor/convert-to-repeater.ts +3 -2
  188. package/src/editor/inline-edit-apply.ts +5 -5
  189. package/src/editor/inline-edit.ts +86 -50
  190. package/src/editor/insert-zone-action.ts +1 -1
  191. package/src/editor/shortcuts.ts +1043 -329
  192. package/src/editor/slash-menu.ts +1 -0
  193. package/src/files/elements.ts +166 -0
  194. package/src/files/file-ops.ts +178 -31
  195. package/src/files/files.ts +898 -268
  196. package/src/files/fs-events.ts +21 -0
  197. package/src/files/media-meta.ts +203 -0
  198. package/src/files/media-paths.ts +141 -0
  199. package/src/files/media-upload.ts +7 -3
  200. package/src/files/media-usage.ts +189 -0
  201. package/src/github/github-auth.ts +235 -33
  202. package/src/github/github-publish.ts +80 -21
  203. package/src/grid/grid-controller.ts +152 -12
  204. package/src/grid/grid-layout.ts +225 -20
  205. package/src/grid/grid-open.ts +120 -0
  206. package/src/grid/grid-panel.ts +554 -39
  207. package/src/grid/grid-source.ts +27 -1
  208. package/src/grid/redirects-grid.ts +599 -0
  209. package/src/grid/redirects.ts +398 -0
  210. package/src/grid/sources/content-source.ts +108 -2
  211. package/src/i18n/i18n-commands.ts +322 -0
  212. package/src/new-project/add-repo-modal.ts +8 -10
  213. package/src/new-project/import-tab.ts +7 -0
  214. package/src/new-project/location-fields.ts +9 -1
  215. package/src/new-project/new-project-modal.ts +221 -245
  216. package/src/packages/jxsuite-update.ts +97 -48
  217. package/src/packages/pull-package-sync.ts +18 -9
  218. package/src/page-params.ts +2 -1
  219. package/src/panels/activity-bar.ts +152 -91
  220. package/src/panels/activity-panel.ts +481 -0
  221. package/src/panels/ai-chat/chat-markdown.ts +8 -3
  222. package/src/panels/ai-chat/chat-view.ts +245 -18
  223. package/src/panels/ai-chat/composer.ts +31 -5
  224. package/src/panels/ai-chat/sessions-view.ts +17 -10
  225. package/src/panels/ai-panel.ts +279 -53
  226. package/src/panels/block-action-bar.ts +1115 -224
  227. package/src/panels/bottom-dock.ts +337 -0
  228. package/src/panels/canvas-dnd-bridge.ts +37 -8
  229. package/src/panels/chat-panel.ts +29 -12
  230. package/src/panels/data-explorer.ts +254 -112
  231. package/src/panels/data-grid.ts +10 -4
  232. package/src/panels/dnd.ts +28 -51
  233. package/src/panels/editors.ts +434 -107
  234. package/src/panels/elements-panel.ts +66 -2
  235. package/src/panels/empty-state.ts +112 -0
  236. package/src/panels/events-panel.ts +596 -82
  237. package/src/panels/formula-workspace.ts +408 -218
  238. package/src/panels/frontmatter-fields.ts +51 -10
  239. package/src/panels/frontmatter-panel.ts +357 -79
  240. package/src/panels/git-panel.ts +375 -164
  241. package/src/panels/head-panel.ts +791 -85
  242. package/src/panels/i18n-panel.ts +592 -0
  243. package/src/panels/imports-panel.ts +95 -85
  244. package/src/panels/jump-bar.ts +584 -0
  245. package/src/panels/layers-panel.ts +1106 -328
  246. package/src/panels/left-panel.ts +109 -204
  247. package/src/panels/navigator-panels.ts +131 -0
  248. package/src/panels/overlays.ts +7 -2
  249. package/src/panels/pane-context.ts +1477 -0
  250. package/src/panels/pane-grid.ts +406 -0
  251. package/src/panels/panel-registry.ts +340 -0
  252. package/src/panels/panel-scheduler.ts +75 -0
  253. package/src/panels/problems-panel.ts +234 -0
  254. package/src/panels/properties-panel.ts +946 -1139
  255. package/src/panels/provenance.ts +258 -0
  256. package/src/panels/quick-search.ts +791 -122
  257. package/src/panels/right-panel.ts +228 -102
  258. package/src/panels/seo-modal.ts +395 -0
  259. package/src/panels/settings-pane.ts +203 -0
  260. package/src/panels/signals-panel.ts +354 -137
  261. package/src/panels/statement-editor.ts +41 -25
  262. package/src/panels/statusbar.ts +342 -106
  263. package/src/panels/style-panel.ts +974 -463
  264. package/src/panels/style-utils.ts +3 -3
  265. package/src/panels/stylebook-doc.ts +4 -2
  266. package/src/panels/stylebook-layers-panel.ts +3 -2
  267. package/src/panels/stylebook-panel.ts +51 -26
  268. package/src/panels/tab-strip.ts +1058 -78
  269. package/src/panels/target-line.ts +274 -0
  270. package/src/panels/toolbar.ts +631 -484
  271. package/src/panels/welcome-screen.ts +301 -198
  272. package/src/platform-errors.ts +26 -3
  273. package/src/platform.ts +96 -2
  274. package/src/platforms/cloud.ts +71 -8
  275. package/src/platforms/devserver.ts +92 -29
  276. package/src/publish/deploy-checklist.ts +383 -0
  277. package/src/publish/pages-service.ts +10 -1
  278. package/src/publish/publish-commands.ts +218 -0
  279. package/src/publish/publish-panel.ts +110 -16
  280. package/src/recent-projects.ts +3 -2
  281. package/src/services/a11y-report.ts +505 -0
  282. package/src/services/ai-models.ts +30 -1
  283. package/src/services/ai-project-tools.ts +29 -10
  284. package/src/services/ai-system-prompt.ts +53 -14
  285. package/src/services/ai-tools.ts +39 -3
  286. package/src/services/ai-writes.ts +154 -0
  287. package/src/services/announce.ts +97 -0
  288. package/src/services/automation.ts +424 -175
  289. package/src/services/cem-export.ts +9 -4
  290. package/src/services/clock.ts +61 -0
  291. package/src/services/document-assistant.ts +36 -8
  292. package/src/services/idle.ts +179 -0
  293. package/src/services/import-client.ts +26 -0
  294. package/src/services/monaco-buffer.ts +627 -0
  295. package/src/services/monaco-lazy.ts +36 -0
  296. package/src/services/notify.ts +271 -0
  297. package/src/services/profile.ts +155 -0
  298. package/src/services/references.ts +0 -0
  299. package/src/services/token-lint.ts +3 -2
  300. package/src/services/tool-executor.ts +53 -7
  301. package/src/services/trusted-types.ts +123 -0
  302. package/src/settings/contexts-section.ts +427 -0
  303. package/src/settings/contributed-section.ts +149 -11
  304. package/src/settings/css-vars-editor.ts +306 -349
  305. package/src/settings/defs-editor.ts +93 -34
  306. package/src/settings/dependencies-editor.ts +7 -7
  307. package/src/settings/extension-sections.ts +37 -5
  308. package/src/settings/general-settings.ts +204 -121
  309. package/src/settings/locales-section.ts +347 -0
  310. package/src/settings/preferences-accounts.ts +140 -0
  311. package/src/settings/preferences-dialog.ts +620 -0
  312. package/src/settings/preferences-keymap.ts +270 -0
  313. package/src/settings/project-sections.ts +260 -0
  314. package/src/settings/section-registry.ts +169 -0
  315. package/src/settings/settings-document.ts +385 -0
  316. package/src/shell.ts +1549 -0
  317. package/src/site-context.ts +45 -20
  318. package/src/state.ts +14 -275
  319. package/src/store.ts +88 -43
  320. package/src/studio.ts +790 -346
  321. package/src/style/live-preview.ts +36 -0
  322. package/src/style/project-styles.ts +349 -0
  323. package/src/style/token-ref.ts +123 -0
  324. package/src/tabs/patch-ops.ts +16 -4
  325. package/src/tabs/project-config.ts +416 -0
  326. package/src/tabs/selection.ts +323 -0
  327. package/src/tabs/tab.ts +182 -47
  328. package/src/tabs/transact.ts +209 -28
  329. package/src/types.ts +68 -13
  330. package/src/ui/ai-credentials-form.ts +39 -35
  331. package/src/ui/ai-managed-connect.ts +7 -1
  332. package/src/ui/dynamic-slot.ts +224 -81
  333. package/src/ui/expression-editor.ts +71 -63
  334. package/src/ui/field-input.ts +4 -6
  335. package/src/ui/field-row.ts +215 -38
  336. package/src/ui/form-controls.ts +194 -7
  337. package/src/ui/layers.ts +492 -69
  338. package/src/ui/media-picker.ts +115 -25
  339. package/src/ui/panel-resize.ts +109 -91
  340. package/src/ui/progress-modal.ts +131 -50
  341. package/src/ui/regions.ts +462 -0
  342. package/src/ui/schema-form.ts +229 -45
  343. package/src/ui/spectrum.ts +24 -8
  344. package/src/ui/timing.ts +49 -0
  345. package/src/ui/value-selector.ts +20 -0
  346. package/src/ui/value-source.ts +348 -0
  347. package/src/ui/virtual-window.ts +400 -0
  348. package/src/utils/edit-display.ts +3 -2
  349. package/src/utils/inherited-style.ts +67 -37
  350. package/src/view.ts +64 -78
  351. package/src/workspace/pane-derive.ts +1258 -0
  352. package/src/workspace/session.ts +317 -0
  353. package/src/workspace/workspace.ts +1345 -23
  354. package/styles/canvas.css +653 -0
  355. package/styles/forced-colors.css +106 -0
  356. package/styles/inspector.css +1454 -0
  357. package/styles/overlays.css +2704 -0
  358. package/styles/panels.css +1701 -0
  359. package/styles/shell.css +1423 -0
  360. package/styles/tokens.css +151 -0
  361. package/dist/chunks/Overlay.dev-cszx7yn5.js +0 -22
  362. package/dist/chunks/extension-sections-qr3drkkw.js +0 -531
  363. package/dist/chunks/extension-sections-qr3drkkw.js.map +0 -12
  364. package/dist/chunks/files-j8a1t06k.js +0 -50
  365. package/dist/chunks/files-j8a1t06k.js.map +0 -9
  366. package/dist/chunks/git-panel-xtq5z81x.js +0 -28
  367. package/dist/chunks/git-panel-xtq5z81x.js.map +0 -9
  368. package/dist/chunks/layers-panel-ancn2wpq.js +0 -26
  369. package/dist/chunks/layers-panel-ancn2wpq.js.map +0 -9
  370. package/dist/chunks/settings-modal-17ttr96g.js +0 -29
  371. package/dist/chunks/settings-modal-17ttr96g.js.map +0 -9
  372. package/dist/chunks/sp-overlay.dev-earn5as8.js +0 -17
  373. package/dist/chunks/sp-overlay.dev-earn5as8.js.map +0 -9
  374. package/dist/chunks/sp-popover.dev-frxtfejn.js +0 -8
  375. package/dist/chunks/sp-popover.dev-frxtfejn.js.map +0 -9
  376. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js +0 -14
  377. package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js.map +0 -9
  378. package/dist/chunks/sp-tooltip.dev-a9fywesm.js +0 -21
  379. package/dist/chunks/sp-tooltip.dev-a9fywesm.js.map +0 -10
  380. package/dist/chunks/studio-0xgs7xrs.js +0 -429
  381. package/dist/chunks/studio-0xgs7xrs.js.map +0 -13
  382. package/dist/chunks/studio-4d8hzcz5.js +0 -3427
  383. package/dist/chunks/studio-4d8hzcz5.js.map +0 -26
  384. package/dist/chunks/studio-4k0m4fmp.js.map +0 -13
  385. package/dist/chunks/studio-9smfzqhc.js +0 -19
  386. package/dist/chunks/studio-9smfzqhc.js.map +0 -10
  387. package/dist/chunks/studio-jm0z592w.js.map +0 -115
  388. package/dist/chunks/studio-m2t4mdg5.js +0 -1854
  389. package/dist/chunks/studio-m2t4mdg5.js.map +0 -18
  390. package/dist/chunks/studio-n4p11dfy.js +0 -853
  391. package/dist/chunks/studio-n4p11dfy.js.map +0 -12
  392. package/dist/chunks/studio-qkvt37c0.js.map +0 -9
  393. package/dist/chunks/studio-sjh1wt1a.js +0 -3744
  394. package/dist/chunks/studio-sjh1wt1a.js.map +0 -22
  395. package/dist/chunks/studio-v7jkjqng.js.map +0 -46
  396. package/dist/chunks/studio-xq0t7n5t.js +0 -140
  397. package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
  398. package/dist/chunks/studio-xze4zrs1.js.map +0 -10
  399. package/dist/chunks/studio-yfcstdzy.js +0 -399
  400. package/dist/chunks/studio-yfcstdzy.js.map +0 -12
  401. package/dist/chunks/studio-zp7jncs8.js +0 -469
  402. package/dist/chunks/studio-zp7jncs8.js.map +0 -14
  403. package/dist/chunks/ws-client-q7hkz3j9.js.map +0 -11
  404. package/src/browse/browse-modal.ts +0 -69
  405. package/src/browse/browse.ts +0 -904
  406. package/src/canvas/canvas-diff.ts +0 -216
  407. package/src/canvas/nested-site-style.ts +0 -58
  408. package/src/new-project/design-fields.ts +0 -264
  409. package/src/new-project/templates.ts +0 -61
  410. package/src/panels/tab-bar.ts +0 -548
  411. package/src/settings/settings-modal.ts +0 -203
  412. /package/dist/chunks/{adapter-z8fkmbmv.js.map → adapter-qfnesn5v.js.map} +0 -0
  413. /package/dist/chunks/{cf-settings-mmyv436d.js.map → cf-settings-73sh5z4p.js.map} +0 -0
  414. /package/dist/chunks/{format-host-8jxwts2p.js.map → format-host-mkwsmt8d.js.map} +0 -0
  415. /package/dist/chunks/{jx-validate-qx8degj7.js.map → jx-validate-v6qgknrs.js.map} +0 -0
  416. /package/dist/chunks/{studio-w7c6kvk7.js.map → studio-a7pzveq1.js.map} +0 -0
  417. /package/dist/chunks/{studio-esrewxap.js.map → studio-s519zc99.js.map} +0 -0
  418. /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
  419. /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
@@ -0,0 +1,4232 @@
1
+ import {
2
+ live
3
+ } from "./studio-xmvbhg31.js";
4
+ import {
5
+ BLESSED_GLOBALS,
6
+ BLESSED_OPERATORS,
7
+ PURE_METHOD_OPS,
8
+ intlHelpersClause,
9
+ rectOf,
10
+ renderEmptyState
11
+ } from "./studio-v96qfnww.js";
12
+ import {
13
+ LANGUAGE_TAG_PATTERN,
14
+ activeTab,
15
+ getLayerSlot
16
+ } from "./studio-41exgzhq.js";
17
+ import {
18
+ classMap,
19
+ ifDefined,
20
+ styleMap
21
+ } from "./studio-ckwz4d3x.js";
22
+ import {
23
+ ref
24
+ } from "./studio-p471gg5n.js";
25
+ import {
26
+ html,
27
+ nothing,
28
+ render
29
+ } from "./studio-5x5774ak.js";
30
+ import {
31
+ isFunctionDef,
32
+ isJsonObject,
33
+ isNamedFormulaDef,
34
+ isRef,
35
+ isTemplateString
36
+ } from "./studio-cjm7fdjf.js";
37
+ import {
38
+ cloneValue
39
+ } from "./studio-5ehhackm.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
+ }
1397
+
1398
+ // ../schema/defs/tag-expression.schema.ts
1399
+ var tagExpressionSchema = {
1400
+ anyOf: [
1401
+ {
1402
+ additionalProperties: false,
1403
+ description: "Two-way: `target` is the test, `value` the tag when it is truthy, `initial` the tag when it is not.",
1404
+ properties: {
1405
+ initial: { $ref: "#/$defs/TagName" },
1406
+ operator: { const: "?:", type: "string" },
1407
+ target: { $ref: "#/$defs/ExpressionOperand" },
1408
+ value: { $ref: "#/$defs/TagName" }
1409
+ },
1410
+ required: ["operator", "target", "value", "initial"],
1411
+ title: "?: — the tag when the test holds, and the tag when it does not"
1412
+ },
1413
+ {
1414
+ additionalProperties: false,
1415
+ description: "Multiway: `target` is the discriminant, matched against case keys by its string form. " + "`default` is required — an element with no tag cannot exist.",
1416
+ properties: {
1417
+ cases: {
1418
+ additionalProperties: { $ref: "#/$defs/TagName" },
1419
+ type: "object"
1420
+ },
1421
+ default: { $ref: "#/$defs/TagName" },
1422
+ operator: { const: "switch", type: "string" },
1423
+ target: { $ref: "#/$defs/ExpressionOperand" }
1424
+ },
1425
+ required: ["operator", "target", "cases", "default"],
1426
+ title: "switch — one tag per discriminant value, with a required fallback"
1427
+ }
1428
+ ],
1429
+ description: "A tag name chosen at element creation. Every branch resolves to a `TagName`, so the candidate " + "set is enumerable without evaluating anything."
1430
+ };
1431
+ var elementTagNameSchema = {
1432
+ anyOf: [
1433
+ { $ref: "#/$defs/TagName" },
1434
+ {
1435
+ additionalProperties: false,
1436
+ properties: { $expression: { $ref: "#/$defs/TagExpression" } },
1437
+ required: ["$expression"],
1438
+ type: "object"
1439
+ }
1440
+ ]
1441
+ };
1442
+ // ../schema/defs/tag-name.schema.ts
1443
+ var tagNameSchema = {
1444
+ description: "HTML element tag name or custom element name (must contain a hyphen per Web Components spec). " + "A NAME, never an expression: no `${…}` position in the pipeline evaluates a tagName. Use " + "`$switch`/`cases` to vary the element.",
1445
+ minLength: 1,
1446
+ pattern: "^[a-zA-Z][a-zA-Z0-9._-]*$",
1447
+ type: "string"
1448
+ };
1449
+ // ../schema/defs/string-or-ref.schema.ts
1450
+ var stringOrRefSchema = {
1451
+ oneOf: [{ type: "string" }, { $ref: "#/$defs/RefObject" }]
1452
+ };
1453
+ // ../schema/defs/head-entry.schema.ts
1454
+ var headEntrySchema = {
1455
+ additionalProperties: true,
1456
+ description: "A page-level <head> entry. Defines meta tags, link tags, script tags, etc.",
1457
+ properties: {
1458
+ attributes: {
1459
+ additionalProperties: {
1460
+ oneOf: [{ type: "string" }, { type: "boolean" }]
1461
+ },
1462
+ type: "object"
1463
+ },
1464
+ children: { items: { $ref: "#/$defs/HeadEntry" }, type: "array" },
1465
+ tagName: { type: "string" },
1466
+ textContent: {
1467
+ description: "Element content. An object is serialized to JSON inside the tag — that is how a " + 'JSON-LD block (`<script type="application/ld+json">`) is authored.',
1468
+ oneOf: [{ type: "string" }, { type: "object" }]
1469
+ }
1470
+ },
1471
+ required: ["tagName"],
1472
+ type: "object"
1473
+ };
1474
+ // ../schema/defs/cem.schema.ts
1475
+ var cemParameterSchema = {
1476
+ description: "A CEM-compatible parameter definition for a function. " + "Follows the Custom Elements Manifest Parameter shape.",
1477
+ properties: {
1478
+ default: { description: "Default value for the parameter." },
1479
+ description: { description: "Parameter documentation.", type: "string" },
1480
+ name: { description: "Parameter name.", type: "string" },
1481
+ optional: { description: "Whether the parameter is optional.", type: "boolean" },
1482
+ type: { description: "Parameter type (JSON Schema or CEM { text } format)." }
1483
+ },
1484
+ required: ["name"],
1485
+ type: "object"
1486
+ };
1487
+ // ../schema/defs/image-config.schema.ts
1488
+ var imageConfigSchema = {
1489
+ description: "Image optimization configuration for the project.",
1490
+ properties: {
1491
+ formats: { items: { type: "string" }, type: "array" },
1492
+ lazyLoad: { type: "boolean" },
1493
+ optimize: { type: "boolean" },
1494
+ picture: {
1495
+ description: "Wrap a multi-format image in a <picture> with one <source> per format (default true). " + "With it off the <img> carries a srcset in the preferred format alone, which browsers " + "that cannot decode that format will still select from.",
1496
+ type: "boolean"
1497
+ },
1498
+ quality: {
1499
+ properties: {
1500
+ avif: { type: "number" },
1501
+ jpeg: { type: "number" },
1502
+ png: { type: "number" },
1503
+ webp: { type: "number" }
1504
+ },
1505
+ type: "object"
1506
+ },
1507
+ remoteDomains: {
1508
+ description: "Hostnames whose remote (https) images are optimized through /cdn-cgi/image transform " + 'URLs (cloudflare service only), e.g. ["drive.usercontent.google.com"]. Remote sources ' + "from other hosts are left untouched.",
1509
+ items: { type: "string" },
1510
+ type: "array"
1511
+ },
1512
+ service: {
1513
+ description: 'How images are optimized: "build" runs Sharp at build time; "cloudflare" emits ' + "/cdn-cgi/image transform URLs served by Cloudflare Image Transformations (requires " + "the feature to be enabled on the serving zone).",
1514
+ enum: ["build", "cloudflare"],
1515
+ type: "string"
1516
+ },
1517
+ sizes: { type: "string" },
1518
+ widths: { items: { type: "number" }, type: "array" }
1519
+ },
1520
+ type: "object"
1521
+ };
1522
+ // ../schema/defs/ref-object.schema.ts
1523
+ var refObjectSchema = {
1524
+ additionalProperties: false,
1525
+ description: "A $ref binding. Resolves to a state entry (reactive) or plain value (static).",
1526
+ properties: { $ref: { $ref: "#/$defs/AnyRef" } },
1527
+ required: ["$ref"],
1528
+ type: "object"
1529
+ };
1530
+ // ../schema/defs/style-object.schema.ts
1531
+ var styleObjectSchema = {
1532
+ additionalProperties: {
1533
+ oneOf: [{ type: "string" }, { type: "number" }, { $ref: "#/$defs/StyleObject" }]
1534
+ },
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
+ type: "object"
1538
+ };
1539
+ // ../schema/defs/children-value.schema.ts
1540
+ var arrayNamespaceSchema = {
1541
+ additionalProperties: false,
1542
+ description: "Dynamic mapped list. Re-renders when the items state entry changes.",
1543
+ properties: {
1544
+ $prototype: { const: "Array", type: "string" },
1545
+ filter: { $ref: "#/$defs/RefObject" },
1546
+ items: {
1547
+ oneOf: [{ $ref: "#/$defs/RefObject" }, { type: "array" }]
1548
+ },
1549
+ map: { $ref: "#/$defs/ElementDef" },
1550
+ sort: { $ref: "#/$defs/RefObject" }
1551
+ },
1552
+ required: ["$prototype", "items", "map"],
1553
+ type: "object"
1554
+ };
1555
+ var childrenValueSchema = {
1556
+ description: "Array of child definitions — elements, text, or Array namespaces (dynamic lists) mixed " + "freely. A bare Array namespace (the whole children slot is one dynamic list) is also " + "accepted for backward compatibility, as is a ${…} template string that resolves at build " + "time to an array of child definitions.",
1557
+ oneOf: [
1558
+ {
1559
+ items: {
1560
+ anyOf: [
1561
+ { $ref: "#/$defs/ElementDef" },
1562
+ { $ref: "#/$defs/SwitchNode" },
1563
+ { $ref: "#/$defs/ArrayNamespace" },
1564
+ { type: "string" },
1565
+ { type: "number" }
1566
+ ]
1567
+ },
1568
+ type: "array"
1569
+ },
1570
+ { $ref: "#/$defs/ArrayNamespace" },
1571
+ {
1572
+ description: "Computed children — a ${…} template that resolves at build time to an array of child " + 'definitions (e.g. "${state.entry.$children}" for parsed content). A bare plain-text ' + "string is NOT a valid children value; text children must be array items.",
1573
+ pattern: "\\$\\{",
1574
+ type: "string"
1575
+ }
1576
+ ]
1577
+ };
1578
+ // ../schema/defs/element-def.schema.ts
1579
+ var attributesObjectSchema = {
1580
+ additionalProperties: {
1581
+ oneOf: [
1582
+ { type: "string" },
1583
+ { type: "number" },
1584
+ { type: "boolean" },
1585
+ { $ref: "#/$defs/RefObject" }
1586
+ ]
1587
+ },
1588
+ description: "HTML attributes and ARIA attributes set via element.setAttribute().",
1589
+ type: "object"
1590
+ };
1591
+ var propsObjectSchema = {
1592
+ additionalProperties: {
1593
+ anyOf: [
1594
+ { type: "string" },
1595
+ { type: "number" },
1596
+ { type: "boolean" },
1597
+ { type: "array" },
1598
+ { type: "object" },
1599
+ { $ref: "#/$defs/RefObject" }
1600
+ ]
1601
+ },
1602
+ description: "Explicit prop passing at a component boundary.",
1603
+ type: "object"
1604
+ };
1605
+ var elementPropertyValueSchema = {
1606
+ oneOf: [
1607
+ { type: "string" },
1608
+ { type: "number" },
1609
+ { type: "boolean" },
1610
+ { type: "null" },
1611
+ { $ref: "#/$defs/RefObject" }
1612
+ ]
1613
+ };
1614
+ var switchDefSchema = {
1615
+ additionalProperties: false,
1616
+ description: "Reactive $ref that drives which case to render.",
1617
+ properties: { $ref: { $ref: "#/$defs/StateRef" } },
1618
+ required: ["$ref"],
1619
+ type: "object"
1620
+ };
1621
+ var elementDefSchema = {
1622
+ additionalProperties: { $ref: "#/$defs/ElementPropertyValue" },
1623
+ description: "A Jx element definition. Maps directly to a DOM element.",
1624
+ properties: {
1625
+ "$map/index": { $ref: "#/$defs/RefObject" },
1626
+ "$map/item": { $ref: "#/$defs/RefObject" },
1627
+ $props: { $ref: "#/$defs/PropsObject" },
1628
+ $ref: { $ref: "#/$defs/ExternalRef" },
1629
+ $switch: { $ref: "#/$defs/SwitchDef" },
1630
+ alt: { $ref: "#/$defs/StringOrRef" },
1631
+ attributes: { $ref: "#/$defs/AttributesObject" },
1632
+ cases: {
1633
+ additionalProperties: {
1634
+ oneOf: [{ $ref: "#/$defs/ElementDef" }, { $ref: "#/$defs/ExternalComponentRef" }]
1635
+ },
1636
+ description: "Switch cases for this element. Maps case values to element definitions or external " + "component refs, rendered according to the reactive $switch discriminant.",
1637
+ type: "object"
1638
+ },
1639
+ checked: { $ref: "#/$defs/BoolOrRef" },
1640
+ children: { $ref: "#/$defs/ChildrenValue" },
1641
+ className: { $ref: "#/$defs/StringOrRef" },
1642
+ dir: { enum: ["ltr", "rtl", "auto"], type: "string" },
1643
+ disabled: { $ref: "#/$defs/BoolOrRef" },
1644
+ hidden: { $ref: "#/$defs/BoolOrRef" },
1645
+ href: { $ref: "#/$defs/StringOrRef" },
1646
+ id: { type: "string" },
1647
+ innerHTML: { $ref: "#/$defs/StringOrRef" },
1648
+ innerText: { $ref: "#/$defs/StringOrRef" },
1649
+ lang: { $ref: "#/$defs/StringOrRef" },
1650
+ name: { $ref: "#/$defs/StringOrRef" },
1651
+ placeholder: { $ref: "#/$defs/StringOrRef" },
1652
+ selected: { $ref: "#/$defs/BoolOrRef" },
1653
+ src: { $ref: "#/$defs/StringOrRef" },
1654
+ style: { $ref: "#/$defs/StyleObject" },
1655
+ tabIndex: { $ref: "#/$defs/NumberOrRef" },
1656
+ tagName: { $ref: "#/$defs/ElementTagName" },
1657
+ textContent: { $ref: "#/$defs/StringOrRef" },
1658
+ title: { $ref: "#/$defs/StringOrRef" },
1659
+ type: { $ref: "#/$defs/StringOrRef" },
1660
+ value: { $ref: "#/$defs/StringOrRef" }
1661
+ },
1662
+ required: ["tagName"],
1663
+ type: "object"
1664
+ };
1665
+ // ../schema/defs/typed-state-def.schema.ts
1666
+ var typedStateDefSchema = {
1667
+ description: "A typed reactive state variable with explicit type and default value. " + "The type property is a JSON Schema or $ref to a $defs type definition. " + "The default property is the initial runtime value.",
1668
+ not: { required: ["$prototype"] },
1669
+ properties: {
1670
+ $ref: { description: "Reference to a shared type definition.", type: "string" },
1671
+ attribute: {
1672
+ description: "Linked HTML attribute name for CEM extraction.",
1673
+ type: "string"
1674
+ },
1675
+ default: { description: "Initial state value." },
1676
+ deprecated: {
1677
+ description: "Deprecation notice for CEM extraction.",
1678
+ oneOf: [{ type: "boolean" }, { type: "string" }]
1679
+ },
1680
+ description: { type: "string" },
1681
+ examples: { type: "array" },
1682
+ reflects: {
1683
+ description: "Whether property changes reflect back to the HTML attribute.",
1684
+ type: "boolean"
1685
+ },
1686
+ type: {
1687
+ description: "JSON Schema type definition, $ref to a $defs type, or JSON Schema type string.",
1688
+ oneOf: [{ type: "string" }, { type: "object" }]
1689
+ }
1690
+ },
1691
+ required: ["default"],
1692
+ type: "object"
1693
+ };
1694
+ // ../schema/defs/function-def.schema.ts
1695
+ var functionDefSchema = {
1696
+ additionalProperties: false,
1697
+ description: 'A function declaration. $prototype must be "Function". ' + "body (inline) and $src (external) are mutually exclusive. " + "First parameter is always state (the reactive scope).",
1698
+ properties: {
1699
+ $export: {
1700
+ description: "Named export in $src module. Defaults to the state key name.",
1701
+ type: "string"
1702
+ },
1703
+ $prototype: { const: "Function", type: "string" },
1704
+ $src: {
1705
+ description: "External module specifier. Mutually exclusive with body.",
1706
+ examples: ["./counter.js", "npm:@myorg/validators"],
1707
+ type: "string"
1708
+ },
1709
+ arguments: {
1710
+ description: "Legacy alias for parameters: bare string parameter names (state may be listed " + "explicitly as the first name). Superseded by parameters.",
1711
+ examples: [["state", "event"]],
1712
+ items: { type: "string" },
1713
+ type: "array"
1714
+ },
1715
+ body: {
1716
+ description: "Inline function body: an opaque JS source string (first implicit parameter is state), or a structured statement array (spec §20) — explicit structured function declaration, analyzable by tooling and editable visually. The escalation ladder within the shape is structured statements → JS string.",
1717
+ examples: [
1718
+ "state.count++",
1719
+ 'state.items.push({ id: Date.now(), text: "", done: false })',
1720
+ 'return state.score >= 90 ? "gold" : "silver"',
1721
+ [
1722
+ { operator: "push", target: { $ref: "#/state/cart" }, value: 1 },
1723
+ { dispatchEvent: "cart-changed", detail: { $ref: "#/state/cart" } }
1724
+ ]
1725
+ ],
1726
+ oneOf: [{ type: "string" }, { $ref: "#/$defs/StatementList" }]
1727
+ },
1728
+ description: { type: "string" },
1729
+ emits: {
1730
+ description: "Array of CEM-compatible Event objects this function dispatches. " + "Used for CEM extraction and studio event discovery.",
1731
+ items: { $ref: "#/$defs/CemEvent" },
1732
+ type: "array"
1733
+ },
1734
+ name: {
1735
+ description: "Explicit function name. Defaults to the state key name.",
1736
+ type: "string"
1737
+ },
1738
+ parameters: {
1739
+ description: "Function parameters (after the implicit state parameter). " + "Accepts CEM-compatible parameter objects or bare string names for backward compatibility.",
1740
+ examples: [
1741
+ ["event"],
1742
+ [{ name: "event", type: { text: "Event" } }],
1743
+ [{ description: "Item identifier", name: "id", type: { text: "number" } }]
1744
+ ],
1745
+ items: {
1746
+ oneOf: [{ type: "string" }, { $ref: "#/$defs/CemParameter" }]
1747
+ },
1748
+ type: "array"
1749
+ },
1750
+ type: {
1751
+ description: "Return type for tooling (JSON Schema or CEM { text } format)."
1752
+ }
1753
+ },
1754
+ required: ["$prototype"],
1755
+ type: "object"
1756
+ };
1757
+ // ../schema/defs/external-class-def.schema.ts
1758
+ var externalClassDefSchema = {
1759
+ description: 'An external class / data source. $prototype is a constructor name (not "Function"). ' + "When $prototype is not in the built-in registry, $src is required. " + "All state entries are reactive by default.",
1760
+ properties: {
1761
+ $export: {
1762
+ description: "Named export in $src module. Defaults to the $prototype value.",
1763
+ type: "string"
1764
+ },
1765
+ $prototype: {
1766
+ description: "Constructor name — built-in Web API class or external class name.",
1767
+ examples: [
1768
+ "Request",
1769
+ "URLSearchParams",
1770
+ "FormData",
1771
+ "LocalStorage",
1772
+ "SessionStorage",
1773
+ "Cookie",
1774
+ "IndexedDB",
1775
+ "Array",
1776
+ "Set",
1777
+ "Map",
1778
+ "Blob",
1779
+ "ReadableStream",
1780
+ "MarkdownCollection",
1781
+ "MyParser"
1782
+ ],
1783
+ not: { const: "Function" },
1784
+ type: "string"
1785
+ },
1786
+ $src: {
1787
+ description: "External module specifier. Required when $prototype is not a built-in.",
1788
+ examples: ["@jxsuite/md", "./lib/my-parser.js", "npm:@myorg/data"],
1789
+ type: "string"
1790
+ },
1791
+ autoIncrement: { type: "boolean" },
1792
+ body: {},
1793
+ database: { type: "string" },
1794
+ debounce: { minimum: 0, type: "integer" },
1795
+ default: {},
1796
+ description: { type: "string" },
1797
+ domain: { type: "string" },
1798
+ filter: {
1799
+ anyOf: [
1800
+ { $ref: "#/$defs/RefObject" },
1801
+ { description: "A single filter object, e.g. an equality shorthand.", type: "object" },
1802
+ {
1803
+ description: 'Ordered filter rules, e.g. [{ field: "Slug", op: "not empty" }].',
1804
+ items: { type: "object" },
1805
+ type: "array"
1806
+ }
1807
+ ],
1808
+ description: "Filter configuration: a reactive $ref binding, a single filter object, or an ordered " + "array of rules."
1809
+ },
1810
+ headers: { additionalProperties: { type: "string" }, type: "object" },
1811
+ indexes: {
1812
+ items: {
1813
+ properties: {
1814
+ keyPath: {
1815
+ oneOf: [{ type: "string" }, { items: { type: "string" }, type: "array" }]
1816
+ },
1817
+ name: { type: "string" },
1818
+ unique: { type: "boolean" }
1819
+ },
1820
+ required: ["name", "keyPath"],
1821
+ type: "object"
1822
+ },
1823
+ type: "array"
1824
+ },
1825
+ items: {},
1826
+ key: { type: "string" },
1827
+ keyPath: { type: "string" },
1828
+ manual: { type: "boolean" },
1829
+ map: { $ref: "#/$defs/ElementDef" },
1830
+ maxAge: { type: "integer" },
1831
+ method: {
1832
+ enum: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
1833
+ type: "string"
1834
+ },
1835
+ name: { type: "string" },
1836
+ path: { type: "string" },
1837
+ responseType: {
1838
+ enum: ["json", "text", "blob", "arraybuffer", "document", ""],
1839
+ type: "string"
1840
+ },
1841
+ sameSite: { enum: ["strict", "lax", "none"], type: "string" },
1842
+ secure: { type: "boolean" },
1843
+ sort: {
1844
+ anyOf: [
1845
+ { $ref: "#/$defs/RefObject" },
1846
+ { description: "A single sort rule, e.g. { field, order }.", type: "object" },
1847
+ {
1848
+ description: 'Ordered sort rules, e.g. [{ field: "date", order: "desc" }].',
1849
+ items: { type: "object" },
1850
+ type: "array"
1851
+ }
1852
+ ],
1853
+ description: "Sort configuration: a reactive $ref binding, a single { field, order } rule, or an " + "ordered array of rules."
1854
+ },
1855
+ src: {
1856
+ description: "Configuration property passed to external class constructor.",
1857
+ type: "string"
1858
+ },
1859
+ store: { type: "string" },
1860
+ timing: { enum: ["compiler", "server", "client"], type: "string" },
1861
+ url: { $ref: "#/$defs/StringOrRef" },
1862
+ version: { minimum: 1, type: "integer" }
1863
+ },
1864
+ required: ["$prototype"],
1865
+ type: "object"
1866
+ };
1867
+ // ../schema/defs/pure-type-def.schema.ts
1868
+ var pureTypeDefSchema = {
1869
+ description: "A reusable JSON Schema type definition for tooling only. " + "No function, no runtime artifact. " + "Referenced from state entries via $ref. " + "Naming convention: PascalCase.",
1870
+ not: {
1871
+ anyOf: [{ required: ["default"] }, { required: ["$prototype"] }]
1872
+ },
1873
+ properties: {
1874
+ description: { type: "string" },
1875
+ enum: { type: "array" },
1876
+ examples: { type: "array" },
1877
+ items: {},
1878
+ maxLength: { minimum: 0, type: "integer" },
1879
+ maximum: { type: "number" },
1880
+ minLength: { minimum: 0, type: "integer" },
1881
+ minimum: { type: "number" },
1882
+ pattern: { type: "string" },
1883
+ properties: { type: "object" },
1884
+ required: { items: { type: "string" }, type: "array" },
1885
+ type: { $ref: "#/$defs/JsonSchemaType" }
1886
+ },
1887
+ required: ["type"],
1888
+ type: "object"
1889
+ };
1890
+ // ../schema/defs/expression-node.schema.ts
1891
+ var expressionNodeSchema = {
1892
+ additionalProperties: false,
1893
+ description: "The core expression node. operator + target, with value gated by arity.",
1894
+ oneOf: [
1895
+ {
1896
+ not: { anyOf: [{ required: ["value"] }, { required: ["initial"] }] },
1897
+ properties: { operator: { $ref: "#/$defs/UnaryOperator" } },
1898
+ required: ["operator"],
1899
+ title: "Unary — target only, no value"
1900
+ },
1901
+ {
1902
+ not: { required: ["initial"] },
1903
+ properties: { operator: { $ref: "#/$defs/BinaryOperator" } },
1904
+ required: ["operator", "value"],
1905
+ title: "Binary — left in target, right in value"
1906
+ },
1907
+ {
1908
+ not: { required: ["initial"] },
1909
+ properties: {
1910
+ operator: { $ref: "#/$defs/AssignmentOperator" },
1911
+ target: { $ref: "#/$defs/ExpressionPointer" }
1912
+ },
1913
+ required: ["operator", "value"],
1914
+ title: "Assignment — writable target, value required"
1915
+ },
1916
+ {
1917
+ not: { anyOf: [{ required: ["value"] }, { required: ["initial"] }] },
1918
+ properties: {
1919
+ operator: { $ref: "#/$defs/NoArgMethod" },
1920
+ target: { $ref: "#/$defs/ExpressionPointer" }
1921
+ },
1922
+ required: ["operator"],
1923
+ title: "pop / shift — array receiver, no value"
1924
+ },
1925
+ {
1926
+ not: { required: ["initial"] },
1927
+ properties: {
1928
+ operator: { $ref: "#/$defs/OneArgMethod" },
1929
+ target: { $ref: "#/$defs/ExpressionPointer" }
1930
+ },
1931
+ required: ["operator", "value"],
1932
+ title: "push / unshift — array receiver, single value argument"
1933
+ },
1934
+ {
1935
+ not: { required: ["initial"] },
1936
+ properties: {
1937
+ operator: { $ref: "#/$defs/SpliceMethod" },
1938
+ target: { $ref: "#/$defs/ExpressionPointer" },
1939
+ value: { items: { $ref: "#/$defs/ExpressionOperand" }, minItems: 1, type: "array" }
1940
+ },
1941
+ required: ["operator", "value"],
1942
+ title: "splice — array receiver, [start, deleteCount, ...items]"
1943
+ },
1944
+ {
1945
+ properties: {
1946
+ operator: { $ref: "#/$defs/ReduceMethod" },
1947
+ target: {
1948
+ anyOf: [{ $ref: "#/$defs/ExpressionPointer" }, { $ref: "#/$defs/ExpressionNode" }]
1949
+ },
1950
+ value: { $ref: "#/$defs/ExpressionNode" }
1951
+ },
1952
+ required: ["operator", "value", "initial"],
1953
+ title: "reduce — pure fold over a pointer or derived array; seed in initial"
1954
+ },
1955
+ {
1956
+ not: { required: ["initial"] },
1957
+ properties: {
1958
+ operator: { $ref: "#/$defs/MapFilterMethod" },
1959
+ target: {
1960
+ anyOf: [{ $ref: "#/$defs/ExpressionPointer" }, { $ref: "#/$defs/ExpressionNode" }]
1961
+ },
1962
+ value: { $ref: "#/$defs/ExpressionNode" }
1963
+ },
1964
+ required: ["operator", "value"],
1965
+ title: "map / filter — pure over a pointer or derived array; per-item expression in value"
1966
+ },
1967
+ {
1968
+ description: "The ECMAScript conditional (ternary) operator, pure: target is the test, value the consequent, initial the alternate (the reduce precedent for repurposing initial). Chain else-if by nesting another ?: node in initial.",
1969
+ properties: { operator: { $ref: "#/$defs/ConditionalOperator" } },
1970
+ required: ["operator", "value", "initial"],
1971
+ title: "?: — pure conditional; test in target, consequent in value, alternate in initial"
1972
+ },
1973
+ {
1974
+ description: "Value-keyed multiway selection, pure: target is the discriminant (matched against case keys by its string form, like element-level $switch), cases maps matched values to result operands, default is the operand when no case matches (result is undefined without it). Condition-chain branching composes from nested ?: nodes instead.",
1975
+ not: { anyOf: [{ required: ["value"] }, { required: ["initial"] }] },
1976
+ properties: {
1977
+ cases: {
1978
+ additionalProperties: { $ref: "#/$defs/ExpressionOperand" },
1979
+ type: "object"
1980
+ },
1981
+ default: { $ref: "#/$defs/ExpressionOperand" },
1982
+ operator: { $ref: "#/$defs/SwitchOperator" }
1983
+ },
1984
+ required: ["operator", "cases"],
1985
+ title: "switch — pure value-keyed selection; discriminant in target, results in cases/default"
1986
+ },
1987
+ {
1988
+ description: "A pure standard-library method call: target is the receiver (any operand — never mutated; change-by-copy names replace the mutating originals), value the optional argument (bare scalar) or argument list (array). A missing receiver or method yields undefined, matching null-safe path reads.",
1989
+ not: { required: ["initial"] },
1990
+ properties: { operator: { $ref: "#/$defs/PureMethod" } },
1991
+ required: ["operator"],
1992
+ title: "Pure method — genuine prototype methods; receiver in target, args in value"
1993
+ },
1994
+ {
1995
+ description: "Invoke a callable, pure in formula position: target is the callee pointer — a named formula entry (#/state/…) or a blessed pure global via window#/ (Math.*, JSON.*, Object.keys/values/entries, and " + `${intlHelpersClause()}) — and value is the positional argument list (the splice args-in-value precedent). Argument order follows the callee's declared parameters.`,
1996
+ not: { required: ["initial"] },
1997
+ properties: {
1998
+ operator: { $ref: "#/$defs/CallOperator" },
1999
+ target: { $ref: "#/$defs/ExpressionPointer" },
2000
+ value: { items: { $ref: "#/$defs/ExpressionOperand" }, type: "array" }
2001
+ },
2002
+ required: ["operator"],
2003
+ title: "call — invoke a named formula or blessed global; callee in target, args in value"
2004
+ }
2005
+ ],
2006
+ properties: {
2007
+ cases: {
2008
+ additionalProperties: { $ref: "#/$defs/ExpressionOperand" },
2009
+ description: "switch only: matched discriminant value (string form) → result operand.",
2010
+ type: "object"
2011
+ },
2012
+ default: {
2013
+ $ref: "#/$defs/ExpressionOperand",
2014
+ description: "switch only: result operand when no case key matches."
2015
+ },
2016
+ initial: { $ref: "#/$defs/ExpressionOperand" },
2017
+ operator: { type: "string" },
2018
+ target: { $ref: "#/$defs/ExpressionOperand" },
2019
+ value: { $ref: "#/$defs/ExpressionOperand" }
2020
+ },
2021
+ required: ["operator", "target"],
2022
+ type: "object"
2023
+ };
2024
+ var expressionEntrySchema = {
2025
+ additionalProperties: false,
2026
+ description: "A declarative expression entry (Shape 5). Used as a state entry or inline event handler. With parameters it is a named formula: a pure, reusable computation invoked via the call operator, its parameters resolved through $args/<name> refs in the body.",
2027
+ properties: {
2028
+ $description: { type: "string" },
2029
+ $expression: { $ref: "#/$defs/ExpressionNode" },
2030
+ $title: { type: "string" },
2031
+ parameters: {
2032
+ description: "Named-formula parameters (CEM convention, as on Function entries): bare names or CEM parameter objects. Present ⇒ the entry is callable, not a computed value.",
2033
+ items: { anyOf: [{ type: "string" }, { $ref: "#/$defs/CemParameter" }] },
2034
+ type: "array"
2035
+ }
2036
+ },
2037
+ required: ["$expression"],
2038
+ type: "object"
2039
+ };
2040
+ // ../schema/defs/state-entry.schema.ts
2041
+ var stateEntrySchema = {
2042
+ description: "A single state entry. Shape is determined by value type: " + "scalar/array → naked reactive property, string with ${} → computed, " + 'object with $prototype: "Function" → function, ' + "object with $prototype: <other> → data source, " + "object with type and default → typed reactive property, " + "plain object → naked object reactive property.",
2043
+ oneOf: [
2044
+ { type: "number" },
2045
+ { type: "boolean" },
2046
+ { type: "null" },
2047
+ { type: "string" },
2048
+ { type: "array" },
2049
+ { $ref: "#/$defs/TypedStateDef" },
2050
+ { $ref: "#/$defs/FunctionDef" },
2051
+ { $ref: "#/$defs/ExternalClassDef" },
2052
+ { $ref: "#/$defs/ExpressionEntry" },
2053
+ {
2054
+ not: {
2055
+ anyOf: [
2056
+ { required: ["$prototype"] },
2057
+ { required: ["default"] },
2058
+ { required: ["type"] },
2059
+ { required: ["$expression"] }
2060
+ ]
2061
+ },
2062
+ type: "object"
2063
+ }
2064
+ ]
2065
+ };
2066
+ var stateMapSchema = {
2067
+ additionalProperties: { $ref: "#/$defs/StateEntry" },
2068
+ description: "Map of runtime variables. Keys are camelCase (public) or #-prefixed (private). " + "All entries are reactive by default.",
2069
+ type: "object"
2070
+ };
2071
+ var defsMapSchema = {
2072
+ additionalProperties: { $ref: "#/$defs/TypeDefEntry" },
2073
+ description: "Map of reusable JSON Schema type definitions. " + "Keys are PascalCase type names. No runtime artifacts are produced.",
2074
+ type: "object"
2075
+ };
2076
+ var typeDefEntrySchema = {
2077
+ description: "A $defs type definition entry. Must be a pure JSON Schema type " + "definition or a class definition (.class.json format).",
2078
+ oneOf: [{ $ref: "#/$defs/PureTypeDef" }, { $ref: "#/$defs/ClassDef" }]
2079
+ };
2080
+ // ../schema/defs/class-def.schema.ts
2081
+ var CLASS_METHOD_ROLES = [
2082
+ "method",
2083
+ "accessor",
2084
+ "parse",
2085
+ "serialize",
2086
+ "discover",
2087
+ "load",
2088
+ "projectData",
2089
+ "resolvePaths",
2090
+ "lower",
2091
+ "emit",
2092
+ "head",
2093
+ "assets",
2094
+ "mount",
2095
+ "dialect",
2096
+ "deploySchema",
2097
+ "bindings",
2098
+ "testConnection"
2099
+ ];
2100
+ var classMethodDefSchema = {
2101
+ description: "A class method or accessor definition. Capability roles (parse, serialize, discover, load) " + "mark static methods that hosts (compiler, server, studio) invoke for format dispatch.",
2102
+ properties: {
2103
+ $prototype: { const: "Function", type: "string" },
2104
+ access: { enum: ["public", "private", "protected"], type: "string" },
2105
+ body: {
2106
+ oneOf: [{ type: "string" }, { items: { type: "string" }, type: "array" }]
2107
+ },
2108
+ description: { type: "string" },
2109
+ getter: {
2110
+ properties: { body: { type: "string" } },
2111
+ type: "object"
2112
+ },
2113
+ identifier: { type: "string" },
2114
+ parameters: {
2115
+ items: {
2116
+ oneOf: [
2117
+ {
2118
+ additionalProperties: false,
2119
+ properties: { $ref: { type: "string" } },
2120
+ required: ["$ref"],
2121
+ type: "object"
2122
+ },
2123
+ { $ref: "#/$defs/ClassParameterDef" }
2124
+ ]
2125
+ },
2126
+ type: "array"
2127
+ },
2128
+ returnType: {},
2129
+ role: {
2130
+ enum: CLASS_METHOD_ROLES,
2131
+ type: "string"
2132
+ },
2133
+ scope: { enum: ["instance", "static"], type: "string" },
2134
+ setter: {
2135
+ properties: {
2136
+ body: { type: "string" },
2137
+ parameters: { type: "array" }
2138
+ },
2139
+ type: "object"
2140
+ },
2141
+ timing: {
2142
+ description: "Execution environments allowed to call this capability directly. " + "Hosts outside the list round-trip through the dev server.",
2143
+ items: { enum: ["compiler", "server", "client"], type: "string" },
2144
+ type: "array"
2145
+ }
2146
+ },
2147
+ type: "object"
2148
+ };
2149
+ var formatDefSchema = {
2150
+ description: "Format participation marker. A class carrying this block is auto-discovered from the " + "project imports map and used for file-extension dispatch by the compiler, server, and studio.",
2151
+ properties: {
2152
+ documentKinds: {
2153
+ description: '"page"/"component" allow the extension in pages/components discovery; ' + '"content" allows it as a content-type source.',
2154
+ items: { enum: ["page", "component", "content"], type: "string" },
2155
+ type: "array"
2156
+ },
2157
+ exportTarget: {
2158
+ description: "When true, site builds emit a serialized sidecar per page in this format.",
2159
+ type: "boolean"
2160
+ },
2161
+ extensions: {
2162
+ description: 'File extensions this format claims, with leading dot (e.g. [".md"]).',
2163
+ items: { pattern: "^\\.", type: "string" },
2164
+ minItems: 1,
2165
+ type: "array"
2166
+ },
2167
+ mediaType: {
2168
+ description: "MIME type for the format (icons, labels, HTTP).",
2169
+ type: "string"
2170
+ },
2171
+ remote: {
2172
+ description: "When true, the load capability accepts http(s) URLs as sources.",
2173
+ type: "boolean"
2174
+ }
2175
+ },
2176
+ required: ["extensions"],
2177
+ type: "object"
2178
+ };
2179
+ var projectBlockDefSchema = {
2180
+ additionalProperties: false,
2181
+ description: "Project-section admission block (specs/extensions.md §9). A class carrying this block owns " + "the project.json section named by `key`; hosts dispatch that section's data through the " + "class's projectData/resolvePaths capabilities.",
2182
+ properties: {
2183
+ description: { type: "string" },
2184
+ key: {
2185
+ description: "The project.json top-level section this class owns (exclusive per project).",
2186
+ type: "string"
2187
+ },
2188
+ referenceable: {
2189
+ description: "Whether section entries can be referenced from documents (e.g. content types).",
2190
+ type: "boolean"
2191
+ },
2192
+ title: { type: "string" }
2193
+ },
2194
+ required: ["key"],
2195
+ type: "object"
2196
+ };
2197
+ var serverBlockDefSchema = {
2198
+ additionalProperties: false,
2199
+ description: "Server-mount admission block (specs/extensions.md §11). A class carrying this block mounts " + "routes under its /_jx/ basePath in the site worker and dev server.",
2200
+ properties: {
2201
+ basePath: {
2202
+ description: "Route subtree this class mounts — must be under /_jx/ (exclusive per project).",
2203
+ examples: ["/_jx/data", "/_jx/auth"],
2204
+ type: "string"
2205
+ },
2206
+ module: {
2207
+ description: "Module specifier exporting the mount handler.",
2208
+ type: "string"
2209
+ },
2210
+ order: {
2211
+ description: "Mount order (ascending; default 100).",
2212
+ type: "number"
2213
+ }
2214
+ },
2215
+ required: ["basePath"],
2216
+ type: "object"
2217
+ };
2218
+ var connectorBlockDefSchema = {
2219
+ description: "Connection-provider admission block (specs/extensions.md §12). A class carrying this block " + "provides database connections of the given provider/kind.",
2220
+ properties: {
2221
+ kind: {
2222
+ description: "SQL dialect family the provider speaks.",
2223
+ enum: ["sqlite", "postgres"],
2224
+ type: "string"
2225
+ },
2226
+ local: {
2227
+ description: "Local-development substitute provider (e.g. sqlite for D1).",
2228
+ type: "string"
2229
+ },
2230
+ module: {
2231
+ description: "Module specifier exporting the provider implementation.",
2232
+ type: "string"
2233
+ },
2234
+ provider: {
2235
+ description: "Provider identifier (e.g. d1, supabase, sqlite).",
2236
+ type: "string"
2237
+ },
2238
+ serve: {
2239
+ description: "Module specifier exporting the serve-time worker binding.",
2240
+ type: "string"
2241
+ }
2242
+ },
2243
+ required: ["provider", "kind"],
2244
+ type: "object"
2245
+ };
2246
+ var studioHintsSchema = {
2247
+ description: "Studio control-surface hints for a format class: editor modes, document-mode rules, " + "templates, and the element/nesting constraints that gate structural editing.",
2248
+ properties: {
2249
+ documentMode: {
2250
+ properties: {
2251
+ componentWhen: {
2252
+ description: "Treat the document as a component when this top-level/frontmatter key matches the regex.",
2253
+ properties: {
2254
+ frontmatterKey: { type: "string" },
2255
+ matches: { type: "string" }
2256
+ },
2257
+ type: "object"
2258
+ },
2259
+ default: { enum: ["content", "component"], type: "string" }
2260
+ },
2261
+ type: "object"
2262
+ },
2263
+ elements: {
2264
+ description: "Element allowlist and nesting constraints for structural editing.",
2265
+ properties: {
2266
+ block: { items: { type: "string" }, type: "array" },
2267
+ inline: { items: { type: "string" }, type: "array" },
2268
+ nesting: {
2269
+ additionalProperties: {
2270
+ properties: {
2271
+ block: { type: "boolean" },
2272
+ directive: { type: "boolean" },
2273
+ inline: { type: "boolean" },
2274
+ only: { items: { type: "string" }, type: "array" }
2275
+ },
2276
+ type: "object"
2277
+ },
2278
+ description: 'Per-parent child rules keyed by tag (or "_root"): ' + "{ block, inline, directive } booleans or { only: [tags] }.",
2279
+ type: "object"
2280
+ },
2281
+ textOnly: { items: { type: "string" }, type: "array" },
2282
+ void: { items: { type: "string" }, type: "array" }
2283
+ },
2284
+ type: "object"
2285
+ },
2286
+ icon: { type: "string" },
2287
+ modes: {
2288
+ description: "Editor modes the studio offers for documents of this format.",
2289
+ items: { type: "string" },
2290
+ type: "array"
2291
+ },
2292
+ newFileTemplate: {
2293
+ description: "Initial source text for newly created files of this format.",
2294
+ type: "string"
2295
+ }
2296
+ },
2297
+ type: "object"
2298
+ };
2299
+ var classDefSchema = {
2300
+ additionalProperties: false,
2301
+ description: 'A .class.json schema-defined class. $prototype must be "Class". ' + "Defines fields, constructor, methods, and type parameters via $defs. " + "Optionally points to a JS module via $implementation for hybrid execution.",
2302
+ properties: {
2303
+ $defs: {
2304
+ description: "Class members: parameters, returnTypes, fields, constructor, methods.",
2305
+ properties: {
2306
+ constructor: { $ref: "#/$defs/ClassConstructorDef" },
2307
+ fields: {
2308
+ additionalProperties: { $ref: "#/$defs/ClassFieldDef" },
2309
+ description: "Class fields with role, access, scope, and type information.",
2310
+ type: "object"
2311
+ },
2312
+ methods: {
2313
+ additionalProperties: { $ref: "#/$defs/ClassMethodDef" },
2314
+ description: "Class methods and accessors.",
2315
+ type: "object"
2316
+ },
2317
+ parameters: {
2318
+ additionalProperties: { $ref: "#/$defs/ClassParameterDef" },
2319
+ description: "Reusable typed parameter schemas, keyed by name.",
2320
+ type: "object"
2321
+ },
2322
+ returnTypes: {
2323
+ additionalProperties: { type: "object" },
2324
+ description: "Output type schemas, keyed by name.",
2325
+ type: "object"
2326
+ }
2327
+ },
2328
+ type: "object"
2329
+ },
2330
+ $id: { type: "string" },
2331
+ $implementation: {
2332
+ description: "Relative path to a JS module containing the actual class implementation.",
2333
+ type: "string"
2334
+ },
2335
+ $prototype: { const: "Class", type: "string" },
2336
+ $schema: { type: "string" },
2337
+ $studio: { $ref: "#/$defs/StudioHints" },
2338
+ connector: { $ref: "#/$defs/ConnectorBlockDef" },
2339
+ description: { type: "string" },
2340
+ extends: {
2341
+ description: "Base class — string name or $ref to another .class.json.",
2342
+ oneOf: [
2343
+ { type: "string" },
2344
+ { properties: { $ref: { type: "string" } }, required: ["$ref"], type: "object" }
2345
+ ]
2346
+ },
2347
+ format: { $ref: "#/$defs/FormatDef" },
2348
+ project: { $ref: "#/$defs/ProjectBlockDef" },
2349
+ server: { $ref: "#/$defs/ServerBlockDef" },
2350
+ title: {
2351
+ description: "PascalCase class name, used as the export name.",
2352
+ type: "string"
2353
+ }
2354
+ },
2355
+ required: ["$prototype", "title"],
2356
+ type: "object"
2357
+ };
2358
+ // ../schema/defs/project-config.schema.ts
2359
+ var REDIRECT_STATUSES = [301, 302, 303, 307, 308];
2360
+ var projectConfigSchema = {
2361
+ description: "Schema for Jx project.json files. " + "A project.json file is the root anchor file for a Jx project, " + "declaring site metadata, default settings, global styles, extensions, " + "and build configuration. Open by design: extension-contributed sections " + "(e.g. `content`) are opaque top-level keys; the generated per-project " + "entry document (project.schema.json) closes the composition.",
2362
+ properties: {
2363
+ $defs: {
2364
+ description: "Global type definitions available to all pages.",
2365
+ type: "object"
2366
+ },
2367
+ $elements: {
2368
+ description: "Global custom element dependencies available to all pages. " + "Items are $ref objects or npm package specifier strings.",
2369
+ items: {
2370
+ oneOf: [
2371
+ {
2372
+ additionalProperties: false,
2373
+ properties: { $ref: { type: "string" } },
2374
+ required: ["$ref"],
2375
+ type: "object"
2376
+ },
2377
+ { type: "string" }
2378
+ ]
2379
+ },
2380
+ type: "array"
2381
+ },
2382
+ $head: {
2383
+ description: "Global <head> entries applied to all pages. " + "Array of element definitions for meta tags, link tags, script tags, etc.",
2384
+ examples: [
2385
+ [
2386
+ { attributes: { href: "/favicon.svg", rel: "icon" }, tagName: "link" },
2387
+ { attributes: { content: "Jx", name: "generator" }, tagName: "meta" }
2388
+ ]
2389
+ ],
2390
+ items: {
2391
+ properties: {
2392
+ attributes: {
2393
+ additionalProperties: { type: "string" },
2394
+ type: "object"
2395
+ },
2396
+ tagName: { type: "string" }
2397
+ },
2398
+ type: "object"
2399
+ },
2400
+ type: "array"
2401
+ },
2402
+ $media: {
2403
+ additionalProperties: { type: "string" },
2404
+ description: "Named media breakpoints following CSS @custom-media convention. " + "Available in all component style objects. Declaring a pure color-scheme " + 'query (e.g. "--dark": "(prefers-color-scheme: dark)") additionally opts the ' + "site into the forced-scheme contract: @--dark style blocks are dual-emitted " + "so a data-color-scheme attribute on <html> overrides the OS preference, " + "color-scheme is declared on :root, and a pre-paint script restores the " + "visitor's persisted choice (spec §9.5).",
2405
+ examples: [
2406
+ {
2407
+ "--dark": "(prefers-color-scheme: dark)",
2408
+ "--lg": "(min-width: 1024px)",
2409
+ "--md": "(min-width: 768px)",
2410
+ "--sm": "(min-width: 640px)"
2411
+ }
2412
+ ],
2413
+ type: "object"
2414
+ },
2415
+ $schema: {
2416
+ description: "Relative path to the generated per-project schema (conventionally " + "./project.schema.json, written by `jx schema`).",
2417
+ type: "string"
2418
+ },
2419
+ build: {
2420
+ description: "Build configuration.",
2421
+ properties: {
2422
+ adapter: {
2423
+ description: "Platform adapter for deployment-specific output.",
2424
+ enum: ["static", "cloudflare-pages", "cloudflare-workers", "node", "bun"],
2425
+ type: "string"
2426
+ },
2427
+ deploy: {
2428
+ additionalProperties: false,
2429
+ description: "Deployment tracking: the hosting project this repo publishes to. Identifiers " + "only (no secrets) — safe to commit; Studio uses it to tell whether the publish " + "workflow already exists. `adapter` says how the build is packaged; `deploy` says " + "where it ships.",
2430
+ properties: {
2431
+ accountId: {
2432
+ description: "Hosting account id (e.g. the Cloudflare account id).",
2433
+ type: "string"
2434
+ },
2435
+ productionUrl: {
2436
+ description: "Production URL of the connected hosting project.",
2437
+ type: "string"
2438
+ },
2439
+ projectName: {
2440
+ description: "Hosting project name (e.g. the Cloudflare Pages project).",
2441
+ pattern: "^[a-z0-9][a-z0-9-]*$",
2442
+ type: "string"
2443
+ },
2444
+ provider: {
2445
+ description: "Hosting provider the project is connected to.",
2446
+ enum: ["cloudflare-pages"],
2447
+ type: "string"
2448
+ }
2449
+ },
2450
+ required: ["provider", "accountId", "projectName"],
2451
+ type: "object"
2452
+ },
2453
+ format: {
2454
+ default: "directory",
2455
+ description: "Reserved; currently unused. Accepted for forward compatibility with single-file output (desktop single-file mode) — the build emits per-route directories regardless.",
2456
+ enum: ["directory", "single"],
2457
+ type: "string"
2458
+ },
2459
+ outDir: {
2460
+ default: "./dist",
2461
+ description: "Output directory for compiled site.",
2462
+ type: "string"
2463
+ },
2464
+ headers: {
2465
+ additionalProperties: false,
2466
+ description: "Emit dist/_headers — the response headers only the build can know. Honored by " + "Cloudflare Pages/Workers assets and Netlify; on the node/bun adapters the file " + "documents what a reverse proxy in front of them must send.",
2467
+ properties: {
2468
+ cache: {
2469
+ default: "auto",
2470
+ description: '"auto" marks the content-addressed image variants immutable (RFC 8246) and ' + 'revalidates everything else (RFC 9111). "off" emits no Cache-Control at all.',
2471
+ enum: ["auto", "off"],
2472
+ type: "string"
2473
+ },
2474
+ enabled: { default: true, type: "boolean" },
2475
+ rules: {
2476
+ additionalProperties: { additionalProperties: { type: "string" }, type: "object" },
2477
+ description: "Verbatim rules, merged after the generated block.",
2478
+ propertyNames: { pattern: "^/" },
2479
+ type: "object"
2480
+ },
2481
+ security: {
2482
+ additionalProperties: false,
2483
+ properties: {
2484
+ contentTypeOptions: { default: true, type: "boolean" },
2485
+ csp: {
2486
+ default: false,
2487
+ description: "CSP Level 3. Off by default: unlike the other security headers this one " + "governs code the build cannot see — a third-party script that loads a " + "second script, a widget that opens a frame — so turning it on is a change " + "to verify in the browser console, not a safe default. `script-src` is " + "derived from the inline scripts and external origins the build emitted; " + "`style-src` keeps 'unsafe-inline', because per-page <style> blocks and " + "per-element style attributes have no site-wide hash.",
2488
+ oneOf: [
2489
+ { type: "boolean" },
2490
+ { const: "report-only" },
2491
+ {
2492
+ additionalProperties: false,
2493
+ properties: {
2494
+ directives: {
2495
+ additionalProperties: {
2496
+ oneOf: [{ type: "string" }, { const: false }]
2497
+ },
2498
+ description: "Replace (string) or remove (false) a computed directive. A " + "replacement is wholesale, so an addition restates the defaults.",
2499
+ type: "object"
2500
+ },
2501
+ mode: { default: "enforce", enum: ["enforce", "report-only"] },
2502
+ reportUri: { format: "uri-reference", type: "string" }
2503
+ },
2504
+ type: "object"
2505
+ }
2506
+ ]
2507
+ },
2508
+ frameOptions: {
2509
+ default: "SAMEORIGIN",
2510
+ oneOf: [{ enum: ["DENY", "SAMEORIGIN"], type: "string" }, { const: false }]
2511
+ },
2512
+ hsts: {
2513
+ default: false,
2514
+ description: "RFC 6797. Off by default: a wrong max-age locks the apex domain to HTTPS " + "for that long, and the mistake is invisible until a certificate lapses. " + "`preload` requires `includeSubDomains` and is a build error without it.",
2515
+ oneOf: [
2516
+ { type: "boolean" },
2517
+ {
2518
+ additionalProperties: false,
2519
+ properties: {
2520
+ includeSubDomains: { default: true, type: "boolean" },
2521
+ maxAge: { default: 31536000, minimum: 0, type: "integer" },
2522
+ preload: { default: false, type: "boolean" }
2523
+ },
2524
+ type: "object"
2525
+ }
2526
+ ]
2527
+ },
2528
+ permissionsPolicy: {
2529
+ default: "camera=(), microphone=(), geolocation=()",
2530
+ oneOf: [{ type: "string" }, { const: false }]
2531
+ },
2532
+ referrerPolicy: {
2533
+ default: "strict-origin-when-cross-origin",
2534
+ oneOf: [
2535
+ {
2536
+ enum: [
2537
+ "no-referrer",
2538
+ "no-referrer-when-downgrade",
2539
+ "origin",
2540
+ "origin-when-cross-origin",
2541
+ "same-origin",
2542
+ "strict-origin",
2543
+ "strict-origin-when-cross-origin",
2544
+ "unsafe-url"
2545
+ ],
2546
+ type: "string"
2547
+ },
2548
+ { const: false }
2549
+ ]
2550
+ }
2551
+ },
2552
+ type: "object"
2553
+ }
2554
+ },
2555
+ type: "object"
2556
+ },
2557
+ sitemap: {
2558
+ default: true,
2559
+ description: "Generate sitemap.xml from the route table (requires `url`). Set false to disable.",
2560
+ type: "boolean"
2561
+ },
2562
+ trailingSlash: {
2563
+ default: "always",
2564
+ description: "Trailing slash behavior for generated URLs.",
2565
+ enum: ["always", "never", "ignore"],
2566
+ type: "string"
2567
+ }
2568
+ },
2569
+ type: "object"
2570
+ },
2571
+ copy: {
2572
+ additionalProperties: { type: "string" },
2573
+ description: "Declarative file copy map. Keys are source paths (relative to project root), values are destination paths (relative to outDir).",
2574
+ type: "object"
2575
+ },
2576
+ defaults: {
2577
+ description: "Default settings applied to all pages unless overridden.",
2578
+ properties: {
2579
+ charset: {
2580
+ default: "utf8",
2581
+ description: "Default charset for the page.",
2582
+ type: "string"
2583
+ },
2584
+ dir: {
2585
+ description: "Base direction for the <html> element. Omitted when unset — a page's `$dir` " + "overrides it. Right-to-left content renders left-to-right without this.",
2586
+ enum: ["ltr", "rtl", "auto"],
2587
+ type: "string"
2588
+ },
2589
+ lang: {
2590
+ default: "en",
2591
+ description: "Default lang attribute for the <html> element. A page's `$lang` overrides it.",
2592
+ type: "string"
2593
+ },
2594
+ layout: {
2595
+ default: null,
2596
+ description: "Default layout file path applied to all pages. " + "Set to null to render pages without a layout.",
2597
+ examples: ["./layouts/base.json"],
2598
+ oneOf: [{ type: "string" }, { type: "null" }]
2599
+ },
2600
+ shadow: {
2601
+ default: false,
2602
+ description: "Default shadow-DOM mode for every component (spec.md §16.6). Off unless set: a " + "shadow root isolates component styles from the page and replaces the <slot> " + "emulation with real slot distribution. A component's own `$shadow` overrides this " + "in both directions.",
2603
+ oneOf: [
2604
+ { enum: ["open", "closed"], type: "string" },
2605
+ { const: false, type: "boolean" }
2606
+ ]
2607
+ }
2608
+ },
2609
+ type: "object"
2610
+ },
2611
+ extensions: {
2612
+ description: "Extension packages: bare package names (resolved project-first through the package " + "exports map) or relative paths. Each must export jx-extension.json.",
2613
+ examples: [["@jxsuite/parser"]],
2614
+ items: { type: "string" },
2615
+ type: "array"
2616
+ },
2617
+ manifest: {
2618
+ additionalProperties: false,
2619
+ description: 'W3C Web App Manifest, emitted as manifest.webmanifest with a <link rel="manifest"> ' + "on every page. Absent means no manifest: it is a claim that a site is meant to be " + "installed, and most are not.",
2620
+ properties: {
2621
+ backgroundColor: { type: "string" },
2622
+ categories: { items: { type: "string" }, type: "array" },
2623
+ description: { type: "string" },
2624
+ dir: { enum: ["ltr", "rtl", "auto"], type: "string" },
2625
+ display: {
2626
+ default: "standalone",
2627
+ enum: ["fullscreen", "standalone", "minimal-ui", "browser"],
2628
+ type: "string"
2629
+ },
2630
+ enabled: { default: true, type: "boolean" },
2631
+ icons: {
2632
+ description: "Icons at 192px and 512px are what browsers require before offering to install " + "the site; the build warns when either is missing.",
2633
+ items: {
2634
+ additionalProperties: false,
2635
+ properties: {
2636
+ purpose: { type: "string" },
2637
+ sizes: { examples: ["192x192"], type: "string" },
2638
+ src: { type: "string" },
2639
+ type: { type: "string" }
2640
+ },
2641
+ required: ["src"],
2642
+ type: "object"
2643
+ },
2644
+ type: "array"
2645
+ },
2646
+ lang: { type: "string" },
2647
+ name: { description: "Defaults to the project name.", type: "string" },
2648
+ orientation: { type: "string" },
2649
+ scope: { type: "string" },
2650
+ shortName: { type: "string" },
2651
+ startUrl: { default: "/", type: "string" },
2652
+ themeColor: { type: "string" }
2653
+ },
2654
+ type: "object"
2655
+ },
2656
+ serviceWorker: {
2657
+ default: false,
2658
+ description: "W3C Service Worker, emitted as sw.js. Off unless declared — a worker is sticky, so it " + "is the one output that can keep breaking visitors after a deploy tries to fix them. " + "**Turning it off means `false`, not deleting the key**: `false` emits a tombstone at " + "the same URL that unregisters the old worker and clears its caches, and a 404 there is " + "not an instruction to stop. HTML is always network-first; only the content-addressed " + "/images/_optimized/* is served cache-first.",
2659
+ oneOf: [
2660
+ { type: "boolean" },
2661
+ {
2662
+ additionalProperties: false,
2663
+ properties: {
2664
+ enabled: { default: true, type: "boolean" },
2665
+ offlineFallback: {
2666
+ description: "Page served for a failed navigation. Added to `precache` if not already there.",
2667
+ type: "string"
2668
+ },
2669
+ precache: {
2670
+ description: "Site-absolute URLs fetched at install time. Each must be a file this build " + "produces; one that is not fails the build, because a single unreachable entry " + "stops the worker installing at all.",
2671
+ items: { pattern: "^/", type: "string" },
2672
+ type: "array"
2673
+ },
2674
+ scope: { default: "/", type: "string" }
2675
+ },
2676
+ type: "object"
2677
+ }
2678
+ ]
2679
+ },
2680
+ securityTxt: {
2681
+ additionalProperties: false,
2682
+ description: "RFC 9116 security.txt, emitted at .well-known/security.txt. `expires` is required by " + "§2.5.5 and an absent or past value is a build error — an expired file advertises a " + "reporting channel while telling the reporter not to trust it. A hand-placed " + "public/.well-known/security.txt shadows this, which is how a clearsigned file ships.",
2683
+ properties: {
2684
+ acknowledgments: { items: { type: "string" }, type: "array" },
2685
+ canonical: { type: "string" },
2686
+ contact: {
2687
+ description: "At least one mailto:, https: or tel: URI (§2.5.3).",
2688
+ items: { type: "string" },
2689
+ type: "array"
2690
+ },
2691
+ enabled: { default: true, type: "boolean" },
2692
+ encryption: { items: { type: "string" }, type: "array" },
2693
+ expires: { format: "date-time", type: "string" },
2694
+ hiring: { items: { type: "string" }, type: "array" },
2695
+ policy: { items: { type: "string" }, type: "array" },
2696
+ preferredLanguages: {
2697
+ description: "BCP 47 tags, validated and canonicalized like i18n.locales.",
2698
+ items: { pattern: LANGUAGE_TAG_PATTERN, type: "string" },
2699
+ type: "array"
2700
+ }
2701
+ },
2702
+ type: "object"
2703
+ },
2704
+ i18n: {
2705
+ description: "Internationalization configuration.",
2706
+ properties: {
2707
+ defaultLocale: {
2708
+ description: "Default locale, as a BCP 47 language tag. The pattern catches shape errors " + "(`en_US`, `en--US`); the build is the authority on well-formedness.",
2709
+ examples: ["en"],
2710
+ pattern: LANGUAGE_TAG_PATTERN,
2711
+ type: "string"
2712
+ },
2713
+ locales: {
2714
+ description: "Available locales, as BCP 47 language tags.",
2715
+ examples: [["en", "fr", "de"]],
2716
+ items: { pattern: LANGUAGE_TAG_PATTERN, type: "string" },
2717
+ type: "array"
2718
+ },
2719
+ routing: {
2720
+ description: "Locale routing strategy.",
2721
+ enum: ["prefix-except-default", "prefix-always"],
2722
+ type: "string"
2723
+ }
2724
+ },
2725
+ type: "object"
2726
+ },
2727
+ images: { $ref: "#/$defs/ImageConfig" },
2728
+ imports: {
2729
+ additionalProperties: { type: "string" },
2730
+ description: "Global import map: $prototype names to .class.json file paths. " + "Makes external classes available by name in all pages.",
2731
+ examples: [
2732
+ {
2733
+ Markdown: "@jxsuite/parser/Markdown.class.json",
2734
+ MarkdownCollection: "@jxsuite/parser/MarkdownCollection.class.json"
2735
+ }
2736
+ ],
2737
+ type: "object"
2738
+ },
2739
+ name: {
2740
+ default: "Jx Site",
2741
+ description: "Human-readable project name.",
2742
+ examples: ["My Portfolio", "Jx Example Site"],
2743
+ type: "string"
2744
+ },
2745
+ redirects: {
2746
+ additionalProperties: {
2747
+ oneOf: [
2748
+ { description: "Destination path or URL; implies a 301.", type: "string" },
2749
+ {
2750
+ additionalProperties: false,
2751
+ description: "A redirect with an explicit RFC 9110 §15.4 status.",
2752
+ properties: {
2753
+ destination: { type: "string" },
2754
+ status: {
2755
+ default: 301,
2756
+ description: "RFC 9110 §15.4 redirection status. 307 and 308 preserve the request method; " + "301 and 302 historically do not.",
2757
+ enum: [...REDIRECT_STATUSES],
2758
+ type: "integer"
2759
+ }
2760
+ },
2761
+ required: ["destination"],
2762
+ type: "object"
2763
+ },
2764
+ {
2765
+ additionalProperties: false,
2766
+ description: "A rewrite (proxy): the destination's content is served AT the source URL, with no " + "redirect. Emitted as status 200 in `_redirects`, which is the host convention — " + "not an HTTP redirection status.",
2767
+ properties: {
2768
+ destination: { type: "string" },
2769
+ rewrite: { const: true, type: "boolean" }
2770
+ },
2771
+ required: ["destination", "rewrite"],
2772
+ type: "object"
2773
+ }
2774
+ ]
2775
+ },
2776
+ description: "Static redirect and rewrite rules. Keys are source paths in URLPattern pathname syntax.",
2777
+ examples: [
2778
+ {
2779
+ "/api/*": { destination: "https://api.example.com/*", rewrite: true },
2780
+ "/legacy/*": { destination: "/archive/*", status: 308 },
2781
+ "/old-about": "/about"
2782
+ }
2783
+ ],
2784
+ propertyNames: { pattern: "^/" },
2785
+ type: "object"
2786
+ },
2787
+ state: {
2788
+ description: "Site-wide reactive state available to all pages.",
2789
+ type: "object"
2790
+ },
2791
+ style: {
2792
+ additionalProperties: {
2793
+ oneOf: [{ type: "string" }, { type: "number" }, { $ref: "#/$defs/StyleObject" }]
2794
+ },
2795
+ description: "Global CSS styles applied to the <body> element. " + "Uses the same camelCase CSSOM convention and recursive selector/at-rule " + "nesting as component styles.",
2796
+ type: "object"
2797
+ },
2798
+ url: {
2799
+ description: "Production URL of the deployed site.",
2800
+ examples: ["https://example.com", "https://jxsuite.com"],
2801
+ type: "string"
2802
+ }
2803
+ },
2804
+ type: "object"
2805
+ };
2806
+ // ../schema/defs/extension-manifest.schema.ts
2807
+ var extensionManifestSchema = {
2808
+ additionalProperties: false,
2809
+ description: "Schema for jx-extension.json manifests. Located at the extension package root and " + "exported so hosts can resolve <package>/jx-extension.json through the exports map.",
2810
+ properties: {
2811
+ $schema: { type: "string" },
2812
+ classes: {
2813
+ additionalProperties: { type: "string" },
2814
+ description: "$prototype-visible class name → class descriptor path, relative to the manifest.",
2815
+ type: "object"
2816
+ },
2817
+ description: { description: "One-line description.", type: "string" },
2818
+ name: { description: "Package name; must match package.json.", type: "string" },
2819
+ schemas: {
2820
+ additionalProperties: false,
2821
+ description: "Schema fragments this package contributes, relative to the manifest.",
2822
+ properties: {
2823
+ document: {
2824
+ description: "Document-schema fragment (positions like $paths values).",
2825
+ type: "string"
2826
+ },
2827
+ fields: {
2828
+ description: "Field-union fragment: its $defs members are unioned into the per-project " + "field-schema resource as extension field extras (specs/extensions.md §5.3).",
2829
+ type: "string"
2830
+ },
2831
+ project: {
2832
+ description: "Project-schema fragment (top-level project.json sections).",
2833
+ type: "string"
2834
+ }
2835
+ },
2836
+ type: "object"
2837
+ },
2838
+ title: { description: "Human-readable name for studio surfaces.", type: "string" }
2839
+ },
2840
+ required: ["name"],
2841
+ type: "object"
2842
+ };
2843
+ // src/ui/value-source.ts
2844
+ var SLOT_MODE_ORDER = ["literal", "ref", "template", "expression", "function"];
2845
+ var VALUE_SOURCE_LABELS = {
2846
+ expression: "Formula",
2847
+ function: "Inline function",
2848
+ literal: "Fixed value",
2849
+ ref: "From data…",
2850
+ template: "Mixed text"
2851
+ };
2852
+ var VALUE_SOURCE_HINTS = {
2853
+ expression: "Computed from other values, built up operator by operator.",
2854
+ function: "Code that runs when this fires.",
2855
+ literal: "A value you type here, the same every time.",
2856
+ ref: "The current value of a signal, picked from a list.",
2857
+ template: "Text with ${…} placeholders that fill in from signals."
2858
+ };
2859
+ function slotMode(value) {
2860
+ if (isRef(value)) {
2861
+ return "ref";
2862
+ }
2863
+ if (isFunctionDef(value)) {
2864
+ return "function";
2865
+ }
2866
+ if (isTemplateString(value)) {
2867
+ return "template";
2868
+ }
2869
+ if (value && typeof value === "object" && "$expression" in value) {
2870
+ return "expression";
2871
+ }
2872
+ return "literal";
2873
+ }
2874
+ var EVENT_HANDLER_SCHEMA = {
2875
+ oneOf: [
2876
+ { $ref: "#/$defs/RefObject" },
2877
+ { $ref: "#/$defs/ExpressionEntry" },
2878
+ { $ref: "#/$defs/FunctionDef" }
2879
+ ]
2880
+ };
2881
+ var REACHABLE_DEFS = {
2882
+ ExpressionEntry: expressionEntrySchema,
2883
+ FunctionDef: functionDefSchema,
2884
+ RefObject: refObjectSchema,
2885
+ StyleObject: styleObjectSchema
2886
+ };
2887
+ function isRefObjectSchema(node) {
2888
+ const { required } = node;
2889
+ return Array.isArray(required) && required.includes("$ref") && Boolean(node.properties?.$ref);
2890
+ }
2891
+ function isExpressionEntrySchema(node) {
2892
+ const { required } = node;
2893
+ return Array.isArray(required) && required.includes("$expression");
2894
+ }
2895
+ function isFunctionDefSchema(node) {
2896
+ const proto = node.properties?.$prototype;
2897
+ return proto?.const === "Function";
2898
+ }
2899
+ function isFreeStringSchema(node) {
2900
+ return node.type === "string" && node.pattern === undefined && node.enum === undefined && node.const === undefined;
2901
+ }
2902
+ var SCALAR_TYPES = new Set(["array", "boolean", "integer", "null", "number", "object", "string"]);
2903
+ function walk(node, state) {
2904
+ if (!node || typeof node !== "object") {
2905
+ return;
2906
+ }
2907
+ const schema = node;
2908
+ if (state.seen.has(schema)) {
2909
+ return;
2910
+ }
2911
+ state.seen.add(schema);
2912
+ const pointer = typeof schema.$ref === "string" ? schema.$ref : undefined;
2913
+ if (pointer !== undefined && Object.keys(schema).length === 1) {
2914
+ const name = pointer.startsWith("#/$defs/") ? pointer.slice("#/$defs/".length) : pointer;
2915
+ const target = REACHABLE_DEFS[name];
2916
+ if (target) {
2917
+ walk(target, state);
2918
+ } else {
2919
+ state.unresolved.add(pointer);
2920
+ }
2921
+ return;
2922
+ }
2923
+ if (isRefObjectSchema(schema)) {
2924
+ state.found.add("ref");
2925
+ return;
2926
+ }
2927
+ if (isExpressionEntrySchema(schema)) {
2928
+ state.found.add("expression");
2929
+ return;
2930
+ }
2931
+ if (isFunctionDefSchema(schema)) {
2932
+ state.found.add("function");
2933
+ return;
2934
+ }
2935
+ let composed = false;
2936
+ for (const key of ["oneOf", "anyOf", "allOf"]) {
2937
+ const branches = schema[key];
2938
+ if (Array.isArray(branches)) {
2939
+ composed = true;
2940
+ for (const branch of branches) {
2941
+ walk(branch, state);
2942
+ }
2943
+ }
2944
+ }
2945
+ if (composed) {
2946
+ return;
2947
+ }
2948
+ if (isFreeStringSchema(schema)) {
2949
+ state.found.add("literal");
2950
+ state.found.add("template");
2951
+ return;
2952
+ }
2953
+ if (schema.enum !== undefined || schema.const !== undefined) {
2954
+ state.found.add("literal");
2955
+ return;
2956
+ }
2957
+ if (typeof schema.type === "string" && SCALAR_TYPES.has(schema.type)) {
2958
+ state.found.add("literal");
2959
+ }
2960
+ }
2961
+ function deriveSlotCapsDetailed(schema) {
2962
+ const state = { found: new Set, seen: new Set, unresolved: new Set };
2963
+ walk(schema, state);
2964
+ return {
2965
+ caps: SLOT_MODE_ORDER.filter((m) => state.found.has(m)),
2966
+ unresolved: [...state.unresolved].toSorted()
2967
+ };
2968
+ }
2969
+ function deriveSlotCaps(schema) {
2970
+ return deriveSlotCapsDetailed(schema).caps;
2971
+ }
2972
+ var ELEMENT_TAG_SCHEMA = {
2973
+ anyOf: [tagNameSchema, ...elementTagNameSchema.anyOf.slice(1)]
2974
+ };
2975
+ var SLOT_POSITION_SCHEMAS = {
2976
+ attribute: attributesObjectSchema.additionalProperties,
2977
+ componentProp: propsObjectSchema.additionalProperties,
2978
+ elementProperty: elementPropertyValueSchema,
2979
+ eventHandler: EVENT_HANDLER_SCHEMA,
2980
+ repeaterFilter: arrayNamespaceSchema.properties.filter,
2981
+ repeaterItems: arrayNamespaceSchema.properties.items,
2982
+ repeaterSort: arrayNamespaceSchema.properties.sort,
2983
+ styleProperty: styleObjectSchema.additionalProperties,
2984
+ switchDiscriminant: switchDefSchema,
2985
+ elementTag: ELEMENT_TAG_SCHEMA,
2986
+ textProperty: stringOrRefSchema
2987
+ };
2988
+ var capsCache = new Map;
2989
+ function capsForPosition(position) {
2990
+ const hit = capsCache.get(position);
2991
+ if (hit) {
2992
+ return hit;
2993
+ }
2994
+ const derived = deriveSlotCaps(SLOT_POSITION_SCHEMAS[position]);
2995
+ capsCache.set(position, derived);
2996
+ return derived;
2997
+ }
2998
+ function slotCaps(source) {
2999
+ return typeof source === "string" ? capsForPosition(source) : deriveSlotCaps(source.schema);
3000
+ }
3001
+ function configFieldSchema(field) {
3002
+ const declared = deriveSlotCaps(field).length > 0 ? field : { type: "string" };
3003
+ return { anyOf: [declared, { $ref: "#/$defs/RefObject" }] };
3004
+ }
3005
+
3006
+ // src/ui/expression-editor.ts
3007
+ var UNARY_OPS = new Set(["!", "-"]);
3008
+ var BINARY_OPS = new Set([
3009
+ "+",
3010
+ "-",
3011
+ "*",
3012
+ "/",
3013
+ "%",
3014
+ "===",
3015
+ "!==",
3016
+ "<",
3017
+ "<=",
3018
+ ">",
3019
+ ">=",
3020
+ "&&",
3021
+ "||",
3022
+ "??"
3023
+ ]);
3024
+ var ASSIGN_OPS = new Set(["=", "+=", "-=", "*=", "/="]);
3025
+ function isActionExpression(node) {
3026
+ const op = node?.operator;
3027
+ return typeof op === "string" && ASSIGN_OPS.has(op);
3028
+ }
3029
+ var NO_ARG_OPS = new Set(["pop", "shift"]);
3030
+ var ONE_ARG_OPS = new Set(["push", "unshift"]);
3031
+ var ZERO_ARG_METHOD_OPS = new Set([
3032
+ "flat",
3033
+ "normalize",
3034
+ "toLocaleLowerCase",
3035
+ "toLocaleString",
3036
+ "toLocaleUpperCase",
3037
+ "toLowerCase",
3038
+ "toReversed",
3039
+ "toSorted",
3040
+ "toUpperCase",
3041
+ "trim",
3042
+ "trimEnd",
3043
+ "trimStart"
3044
+ ]);
3045
+ var OPERATOR_GROUPS = [
3046
+ { label: "Assignment", ops: ["=", "+=", "-=", "*=", "/="] },
3047
+ { label: "Unary", ops: ["!", "-"] },
3048
+ { label: "Arithmetic", ops: ["+", "-", "*", "/", "%"] },
3049
+ { label: "Comparison", ops: ["===", "!==", "<", "<=", ">", ">="] },
3050
+ { label: "Logical", ops: ["&&", "||", "??"] },
3051
+ { label: "Conditional", ops: ["?:", "switch"] },
3052
+ {
3053
+ label: "Array methods",
3054
+ ops: ["push", "pop", "shift", "unshift", "splice"]
3055
+ },
3056
+ {
3057
+ label: "Pure methods (String)",
3058
+ ops: [
3059
+ "toUpperCase",
3060
+ "toLowerCase",
3061
+ "trim",
3062
+ "trimStart",
3063
+ "trimEnd",
3064
+ "split",
3065
+ "startsWith",
3066
+ "endsWith",
3067
+ "padStart",
3068
+ "padEnd",
3069
+ "replaceAll",
3070
+ "repeat",
3071
+ "charAt",
3072
+ "normalize"
3073
+ ]
3074
+ },
3075
+ {
3076
+ label: "Pure methods (Array)",
3077
+ ops: [
3078
+ "includes",
3079
+ "indexOf",
3080
+ "lastIndexOf",
3081
+ "join",
3082
+ "slice",
3083
+ "concat",
3084
+ "at",
3085
+ "flat",
3086
+ "toSorted",
3087
+ "toReversed",
3088
+ "toSpliced",
3089
+ "with"
3090
+ ]
3091
+ },
3092
+ { label: "Pure methods (Number)", ops: ["toFixed", "toPrecision", "toLocaleString"] },
3093
+ { label: "Aggregate", ops: ["reduce", "map", "filter"] },
3094
+ { label: "Function", ops: ["call"] }
3095
+ ];
3096
+ var INFO_DEFAULTS = {
3097
+ callArgs: false,
3098
+ needsInitial: false,
3099
+ needsValue: false,
3100
+ spliceArray: false,
3101
+ switchCases: false,
3102
+ targetMustBeRef: false,
3103
+ valueIsNode: false
3104
+ };
3105
+ function operatorInfo(op) {
3106
+ if (UNARY_OPS.has(op)) {
3107
+ return { ...INFO_DEFAULTS };
3108
+ }
3109
+ if (op === "?:") {
3110
+ return { ...INFO_DEFAULTS, needsInitial: true, needsValue: true };
3111
+ }
3112
+ if (op === "switch") {
3113
+ return { ...INFO_DEFAULTS, switchCases: true };
3114
+ }
3115
+ if (op === "call") {
3116
+ return { ...INFO_DEFAULTS, callArgs: true, targetMustBeRef: true };
3117
+ }
3118
+ if (PURE_METHOD_OPS.has(op)) {
3119
+ return { ...INFO_DEFAULTS, needsValue: !ZERO_ARG_METHOD_OPS.has(op) };
3120
+ }
3121
+ if (BINARY_OPS.has(op)) {
3122
+ return { ...INFO_DEFAULTS, needsValue: true };
3123
+ }
3124
+ if (ASSIGN_OPS.has(op)) {
3125
+ return { ...INFO_DEFAULTS, needsValue: true, targetMustBeRef: true };
3126
+ }
3127
+ if (NO_ARG_OPS.has(op)) {
3128
+ return { ...INFO_DEFAULTS, targetMustBeRef: true };
3129
+ }
3130
+ if (ONE_ARG_OPS.has(op)) {
3131
+ return { ...INFO_DEFAULTS, needsValue: true, targetMustBeRef: true };
3132
+ }
3133
+ if (op === "splice") {
3134
+ return { ...INFO_DEFAULTS, needsValue: true, spliceArray: true, targetMustBeRef: true };
3135
+ }
3136
+ if (op === "reduce") {
3137
+ return {
3138
+ ...INFO_DEFAULTS,
3139
+ needsInitial: true,
3140
+ needsValue: true,
3141
+ targetMustBeRef: true,
3142
+ valueIsNode: true
3143
+ };
3144
+ }
3145
+ if (op === "map" || op === "filter") {
3146
+ return { ...INFO_DEFAULTS, needsValue: true, targetMustBeRef: true, valueIsNode: true };
3147
+ }
3148
+ return { ...INFO_DEFAULTS };
3149
+ }
3150
+ function operandMode(operand) {
3151
+ if (operand && typeof operand === "object") {
3152
+ if ("$ref" in operand) {
3153
+ return "ref";
3154
+ }
3155
+ if ("operator" in operand) {
3156
+ return "expression";
3157
+ }
3158
+ }
3159
+ return "literal";
3160
+ }
3161
+ function calleeParamLabels(target, state) {
3162
+ const ref2 = isRef(target) ? target.$ref : "";
3163
+ const entry = ref2 ? calleeEntry(ref2, state) : undefined;
3164
+ return entry?.kind === "formula" ? entry.parameters.map((p) => p.name) : [];
3165
+ }
3166
+ function defaultForMode(mode) {
3167
+ if (mode === "ref") {
3168
+ return { $ref: "" };
3169
+ }
3170
+ if (mode === "expression") {
3171
+ return { operator: "!", target: null };
3172
+ }
3173
+ return null;
3174
+ }
3175
+ function literalType(val) {
3176
+ if (val === null || val === undefined) {
3177
+ return "null";
3178
+ }
3179
+ if (typeof val === "boolean") {
3180
+ return "boolean";
3181
+ }
3182
+ if (typeof val === "number") {
3183
+ return "number";
3184
+ }
3185
+ return "string";
3186
+ }
3187
+ function defaultForLiteralType(type) {
3188
+ if (type === "number") {
3189
+ return 0;
3190
+ }
3191
+ if (type === "boolean") {
3192
+ return false;
3193
+ }
3194
+ if (type === "null") {
3195
+ return null;
3196
+ }
3197
+ return "";
3198
+ }
3199
+ function expressionHint(node) {
3200
+ if (!isJsonObject(node) || typeof node.operator !== "string") {
3201
+ return "$expression";
3202
+ }
3203
+ const expr = node;
3204
+ const op = expr.operator;
3205
+ const { target } = expr;
3206
+ const targetLabel = isRef(target) ? target.$ref.replace("#/state/", "") : isJsonObject(target) && typeof target.operator === "string" ? `(${target.operator}…)` : String(target ?? "?");
3207
+ if (ASSIGN_OPS.has(op) || ONE_ARG_OPS.has(op)) {
3208
+ return `${op} ${targetLabel}`;
3209
+ }
3210
+ if (NO_ARG_OPS.has(op)) {
3211
+ return `${op}(${targetLabel})`;
3212
+ }
3213
+ if (op === "splice") {
3214
+ return `splice(${targetLabel})`;
3215
+ }
3216
+ if (op === "call") {
3217
+ return `${targetLabel.replace("window#/", "").replaceAll("/", ".")}(…)`;
3218
+ }
3219
+ if (op === "reduce" || op === "map" || op === "filter" || PURE_METHOD_OPS.has(op)) {
3220
+ return `${op}(${targetLabel})`;
3221
+ }
3222
+ if (op === "?:") {
3223
+ return `${targetLabel} ? … : …`;
3224
+ }
3225
+ if (op === "switch") {
3226
+ return `switch(${targetLabel})`;
3227
+ }
3228
+ if (UNARY_OPS.has(op)) {
3229
+ return `${op}${targetLabel}`;
3230
+ }
3231
+ return `${targetLabel} ${op} …`;
3232
+ }
3233
+ function renderValueBadge(preview, pathKey) {
3234
+ const text = preview?.values.get(pathKey);
3235
+ if (text === undefined) {
3236
+ return nothing;
3237
+ }
3238
+ return html`<span class="expr-live-badge" title=${text}>${text}</span>`;
3239
+ }
3240
+ function renderRefPicker(refVal, onRefChange, opts) {
3241
+ const stateRefs = (opts.stateDefs || []).map((k) => `#/state/${k}`);
3242
+ const eventRefs = opts.allowEventRef ? ["event#/detail", "event#/target/value"] : [];
3243
+ const allRefs = [...stateRefs, ...eventRefs];
3244
+ const isCustom = refVal && !allRefs.includes(refVal);
3245
+ if (allRefs.length === 0 && !refVal) {
3246
+ return renderEmptyState({
3247
+ compact: true,
3248
+ detail: "Add one in the State panel and it shows up here.",
3249
+ message: "A binding points at a value this page holds."
3250
+ });
3251
+ }
3252
+ return html`
3253
+ <sp-picker
3254
+ size="s"
3255
+ class="expr-ref"
3256
+ placeholder="Select…"
3257
+ .value=${live(isCustom ? "__custom__" : refVal || "")}
3258
+ @change=${(e) => {
3259
+ const val = e.target.value;
3260
+ if (val === "__custom__") {
3261
+ return;
3262
+ }
3263
+ onRefChange(val);
3264
+ }}
3265
+ >
3266
+ ${stateRefs.map((r) => html`<sp-menu-item value=${r}>${r.replace("#/state/", "")}</sp-menu-item>`)}
3267
+ ${eventRefs.length > 0 ? html`
3268
+ <sp-menu-divider></sp-menu-divider>
3269
+ ${eventRefs.map((r) => html`<sp-menu-item value=${r}>${r}</sp-menu-item>`)}
3270
+ ` : nothing}
3271
+ </sp-picker>
3272
+ `;
3273
+ }
3274
+ function renderLiteralEditor(operand, onChange) {
3275
+ const type = literalType(operand);
3276
+ return html`
3277
+ <div class="expr-literal">
3278
+ <sp-picker
3279
+ size="s"
3280
+ class="expr-literal-type"
3281
+ .value=${live(type)}
3282
+ @change=${(e) => {
3283
+ const newType = e.target.value;
3284
+ onChange(defaultForLiteralType(newType));
3285
+ }}
3286
+ >
3287
+ <sp-menu-item value="string">str</sp-menu-item>
3288
+ <sp-menu-item value="number">num</sp-menu-item>
3289
+ <sp-menu-item value="boolean">bool</sp-menu-item>
3290
+ <sp-menu-item value="null">null</sp-menu-item>
3291
+ </sp-picker>
3292
+ ${type === "string" ? html`<sp-textfield
3293
+ size="s"
3294
+ class="expr-literal-value"
3295
+ .value=${live(String(operand ?? ""))}
3296
+ @input=${(e) => onChange(e.target.value)}
3297
+ ></sp-textfield>` : type === "number" ? html`<sp-number-field
3298
+ size="s"
3299
+ class="expr-literal-value"
3300
+ .value=${live(Number(operand ?? 0))}
3301
+ @change=${(e) => onChange(Number(e.target.value))}
3302
+ ></sp-number-field>` : type === "boolean" ? html`<sp-checkbox
3303
+ size="s"
3304
+ class="expr-literal-bool"
3305
+ ?checked=${Boolean(operand)}
3306
+ @change=${(e) => onChange(e.target.checked)}
3307
+ >true</sp-checkbox
3308
+ >` : html`<span class="expr-literal-null">null</span>`}
3309
+ </div>
3310
+ `;
3311
+ }
3312
+ function renderOperandEditor(operand, onChange, opts) {
3313
+ if (opts.mustBeRef) {
3314
+ const refVal = operand?.$ref ?? "";
3315
+ return html`
3316
+ <div class="expr-operand">${renderRefPicker(refVal, (r) => onChange({ $ref: r }), opts)}</div>
3317
+ `;
3318
+ }
3319
+ const mode = operandMode(operand);
3320
+ return html`
3321
+ <div class="expr-operand">
3322
+ <sp-picker
3323
+ size="s"
3324
+ class="expr-operand-mode"
3325
+ label="Value source"
3326
+ .value=${live(mode)}
3327
+ @change=${(e) => {
3328
+ const newMode = e.target.value;
3329
+ onChange(defaultForMode(newMode));
3330
+ }}
3331
+ >
3332
+ <sp-menu-item value="literal">${VALUE_SOURCE_LABELS.literal}</sp-menu-item>
3333
+ <sp-menu-item value="ref">${VALUE_SOURCE_LABELS.ref}</sp-menu-item>
3334
+ <sp-menu-item value="expression">${VALUE_SOURCE_LABELS.expression}</sp-menu-item>
3335
+ </sp-picker>
3336
+ ${mode === "literal" ? renderLiteralEditor(operand, onChange) : mode === "ref" ? renderRefPicker(operand?.$ref ?? "", (r) => onChange({ $ref: r }), opts) : renderExpressionEditor(operand, onChange, {
3337
+ ...opts,
3338
+ depth: opts.depth + 1
3339
+ })}
3340
+ </div>
3341
+ `;
3342
+ }
3343
+ function renderSpliceArgsEditor(args, onChange, opts, naming = {}) {
3344
+ const safeArgs = Array.isArray(args) ? args : [];
3345
+ const labels = naming.labels ?? ["start", "del", "item"];
3346
+ const fallbackLabel = naming.fallbackLabel ?? "item";
3347
+ return html`
3348
+ <div class="array-object-field">
3349
+ ${safeArgs.map((arg, idx) => html`
3350
+ <div class="array-object-row">
3351
+ <span class="array-object-label">${labels[idx] ?? fallbackLabel}</span>
3352
+ ${renderOperandEditor(arg, (newArg) => {
3353
+ const updated = [...safeArgs];
3354
+ updated[idx] = newArg;
3355
+ onChange(updated);
3356
+ }, { ...opts, mustBeRef: false })}
3357
+ <sp-action-button
3358
+ quiet
3359
+ size="xs"
3360
+ @click=${() => {
3361
+ const updated = safeArgs.filter((_, i) => i !== idx);
3362
+ onChange(updated.length > 0 ? updated : [null]);
3363
+ }}
3364
+ >
3365
+ <sp-icon-delete slot="icon"></sp-icon-delete>
3366
+ </sp-action-button>
3367
+ </div>
3368
+ `)}
3369
+ <sp-action-button quiet size="s" @click=${() => onChange([...safeArgs, null])}>
3370
+ + Add arg
3371
+ </sp-action-button>
3372
+ </div>
3373
+ `;
3374
+ }
3375
+ var _operatorMenuCache = OPERATOR_GROUPS.map((group, i) => html`
3376
+ ${i > 0 ? html`<sp-menu-divider></sp-menu-divider>` : nothing}
3377
+ <sp-menu-group>
3378
+ <span slot="header">${group.label}</span>
3379
+ ${group.ops.map((op) => html`<sp-menu-item value=${op}>${op}</sp-menu-item>`)}
3380
+ </sp-menu-group>
3381
+ `);
3382
+ function renderExpressionEditor(node, onChange, opts) {
3383
+ const depth = opts.depth ?? 0;
3384
+ const safeNode = node && typeof node === "object" ? node : { operator: "=", target: null };
3385
+ const op = safeNode.operator || "=";
3386
+ const info = operatorInfo(op);
3387
+ const preview = opts.preview ?? null;
3388
+ const path = opts.path ?? [];
3389
+ const pathKey = path.join("/");
3390
+ const sub = (...segs) => [...path, ...segs].join("/");
3391
+ const withBadge = (widget, key) => html`<div class="expr-widget">${widget}${renderValueBadge(preview, key)}</div>`;
3392
+ const rootBadge = depth === 0 && preview && !preview.mutating ? renderValueBadge(preview, pathKey) : nothing;
3393
+ return html`
3394
+ <div class=${depth > 0 ? "expression-editor expression-editor--nested" : "expression-editor"}>
3395
+ ${depth === 0 ? renderFormulaChips(safeNode, opts.onChipSelect ?? (() => {}), { path, preview }) : nothing}
3396
+ ${depth === 0 && preview?.error ? html`<div class="expr-error">${preview.error}</div>` : nothing}
3397
+ ${renderFieldRow({
3398
+ hasValue: false,
3399
+ label: "Operator",
3400
+ prop: "operator",
3401
+ widget: html`
3402
+ <div class="expr-widget">
3403
+ <sp-picker
3404
+ size="s"
3405
+ class="expr-operator"
3406
+ .value=${live(op)}
3407
+ @change=${(e) => {
3408
+ const newOp = e.target.value;
3409
+ const newInfo = operatorInfo(newOp);
3410
+ const updated = {
3411
+ operator: newOp,
3412
+ target: safeNode.target
3413
+ };
3414
+ if (newInfo.targetMustBeRef && operandMode(safeNode.target) !== "ref") {
3415
+ updated.target = { $ref: "" };
3416
+ }
3417
+ if (newInfo.needsValue) {
3418
+ if (newInfo.valueIsNode) {
3419
+ const val = safeNode.value;
3420
+ updated.value = val?.operator ? safeNode.value : { operator: "!", target: null };
3421
+ } else if (newInfo.spliceArray) {
3422
+ updated.value = Array.isArray(safeNode.value) ? safeNode.value : [null];
3423
+ } else {
3424
+ updated.value = safeNode.value ?? null;
3425
+ }
3426
+ }
3427
+ if (newInfo.needsInitial) {
3428
+ updated.initial = safeNode.initial ?? (newOp === "?:" ? null : 0);
3429
+ }
3430
+ if (newInfo.switchCases) {
3431
+ updated.cases = isJsonObject(safeNode.cases) ? safeNode.cases : {};
3432
+ if ("default" in safeNode) {
3433
+ updated.default = safeNode.default;
3434
+ }
3435
+ }
3436
+ if (newInfo.callArgs) {
3437
+ updated.value = Array.isArray(safeNode.value) ? safeNode.value : [];
3438
+ }
3439
+ onChange(updated);
3440
+ }}
3441
+ >
3442
+ ${_operatorMenuCache}
3443
+ </sp-picker>
3444
+ <sp-action-button
3445
+ quiet
3446
+ size="s"
3447
+ class="expr-browse-catalog"
3448
+ title="Browse catalog"
3449
+ @click=${(e) => openFormulaPalette({
3450
+ anchor: e.currentTarget,
3451
+ entries: formulaCatalog(opts.stateEntries),
3452
+ onPick: (entry) => applyCatalogPick(entry, onChange, {
3453
+ onInsertDef: opts.onInsertDef,
3454
+ stateEntries: opts.stateEntries
3455
+ })
3456
+ })}
3457
+ >
3458
+ <sp-icon-brackets slot="icon"></sp-icon-brackets>
3459
+ </sp-action-button>
3460
+ ${rootBadge}
3461
+ </div>
3462
+ `
3463
+ })}
3464
+ ${renderFieldRow({
3465
+ hasValue: false,
3466
+ label: op === "?:" ? "If" : op === "switch" ? "On" : op === "call" ? "Callee" : "Target",
3467
+ prop: "target",
3468
+ widget: withBadge(renderOperandEditor(safeNode.target, (t) => onChange({ ...safeNode, target: t }), {
3469
+ ...opts,
3470
+ depth,
3471
+ mustBeRef: info.targetMustBeRef,
3472
+ path: [...path, "target"]
3473
+ }), sub("target"))
3474
+ })}
3475
+ ${info.needsValue && !info.valueIsNode && !info.spliceArray ? renderFieldRow({
3476
+ hasValue: false,
3477
+ label: op === "?:" ? "Then" : "Value",
3478
+ prop: "value",
3479
+ widget: withBadge(renderOperandEditor(safeNode.value, (v) => onChange({ ...safeNode, value: v }), {
3480
+ ...opts,
3481
+ depth,
3482
+ mustBeRef: false,
3483
+ path: [...path, "value"]
3484
+ }), sub("value"))
3485
+ }) : nothing}
3486
+ ${info.needsValue && info.valueIsNode ? html`
3487
+ <div class="expr-subsection">
3488
+ ${renderFieldRow({
3489
+ hasValue: false,
3490
+ label: "Per-item",
3491
+ prop: "value",
3492
+ widget: nothing
3493
+ })}
3494
+ ${renderExpressionEditor(safeNode.value?.operator ? safeNode.value : { operator: "!", target: null }, (v) => onChange({ ...safeNode, value: v }), { ...opts, depth: depth + 1, path: [...path, "value"] })}
3495
+ </div>
3496
+ ` : nothing}
3497
+ ${info.spliceArray ? html`
3498
+ <div class="expr-subsection">
3499
+ ${renderFieldRow({
3500
+ hasValue: false,
3501
+ label: "Args",
3502
+ prop: "value",
3503
+ widget: nothing
3504
+ })}
3505
+ ${renderSpliceArgsEditor(safeNode.value, (v) => onChange({ ...safeNode, value: v }), {
3506
+ ...opts,
3507
+ depth
3508
+ })}
3509
+ </div>
3510
+ ` : nothing}
3511
+ ${info.callArgs ? html`
3512
+ <div class="expr-subsection">
3513
+ ${renderFieldRow({
3514
+ hasValue: false,
3515
+ label: "Args",
3516
+ prop: "value",
3517
+ widget: nothing
3518
+ })}
3519
+ ${renderSpliceArgsEditor(safeNode.value, (v) => onChange({ ...safeNode, value: v }), { ...opts, depth }, {
3520
+ fallbackLabel: "arg",
3521
+ labels: calleeParamLabels(safeNode.target, opts.stateEntries)
3522
+ })}
3523
+ </div>
3524
+ ` : nothing}
3525
+ ${info.switchCases ? renderSwitchCasesEditor(safeNode, onChange, { ...opts, depth, path }, withBadge) : nothing}
3526
+ ${info.needsInitial ? renderFieldRow({
3527
+ hasValue: false,
3528
+ label: op === "?:" ? "Else" : "Initial",
3529
+ prop: "initial",
3530
+ widget: withBadge(renderOperandEditor(safeNode.initial, (v) => onChange({ ...safeNode, initial: v }), {
3531
+ ...opts,
3532
+ depth,
3533
+ mustBeRef: false,
3534
+ path: [...path, "initial"]
3535
+ }), sub("initial"))
3536
+ }) : nothing}
3537
+ </div>
3538
+ `;
3539
+ }
3540
+ function renderSwitchCasesEditor(safeNode, onChange, opts, withBadge) {
3541
+ const cases = isJsonObject(safeNode.cases) ? safeNode.cases : {};
3542
+ const entries = Object.entries(cases);
3543
+ const setCases = (next) => onChange({ ...safeNode, cases: next });
3544
+ return html`
3545
+ <div class="switch-cases">
3546
+ ${entries.map(([key, operand]) => html`
3547
+ <div class="switch-case-row">
3548
+ <sp-textfield
3549
+ size="s"
3550
+ class="switch-case-key"
3551
+ placeholder="value"
3552
+ .value=${live(key)}
3553
+ @change=${(e) => {
3554
+ const newKey = e.target.value;
3555
+ if (newKey === key) {
3556
+ return;
3557
+ }
3558
+ const next = {};
3559
+ for (const [k, v] of entries) {
3560
+ next[k === key ? newKey : k] = v;
3561
+ }
3562
+ setCases(next);
3563
+ }}
3564
+ ></sp-textfield>
3565
+ ${withBadge(renderOperandEditor(operand, (v) => setCases({ ...cases, [key]: v }), {
3566
+ ...opts,
3567
+ mustBeRef: false,
3568
+ path: [...opts.path, "cases", key]
3569
+ }), [...opts.path, "cases", key].join("/"))}
3570
+ <sp-action-button
3571
+ quiet
3572
+ size="xs"
3573
+ @click=${() => {
3574
+ const next = { ...cases };
3575
+ delete next[key];
3576
+ setCases(next);
3577
+ }}
3578
+ >
3579
+ <sp-icon-delete slot="icon"></sp-icon-delete>
3580
+ </sp-action-button>
3581
+ </div>
3582
+ `)}
3583
+ <div class="switch-case-row">
3584
+ <span class="switch-case-key switch-case-default">default</span>
3585
+ ${withBadge(renderOperandEditor(safeNode.default, (v) => onChange({ ...safeNode, default: v }), {
3586
+ ...opts,
3587
+ mustBeRef: false,
3588
+ path: [...opts.path, "default"]
3589
+ }), [...opts.path, "default"].join("/"))}
3590
+ </div>
3591
+ <sp-action-button
3592
+ quiet
3593
+ size="s"
3594
+ @click=${() => {
3595
+ let n = entries.length + 1;
3596
+ let key = `case ${n}`;
3597
+ while (Object.hasOwn(cases, key)) {
3598
+ n += 1;
3599
+ key = `case ${n}`;
3600
+ }
3601
+ setCases({ ...cases, [key]: null });
3602
+ }}
3603
+ >
3604
+ + Add case
3605
+ </sp-action-button>
3606
+ </div>
3607
+ `;
3608
+ }
3609
+
3610
+ // src/ui/dynamic-slot.ts
3611
+ var slotModeMemory = new Map;
3612
+ var slotModeRendered = new Map;
3613
+ function memoryKey(fieldKey, mode) {
3614
+ return `${activeTab.value?.id ?? "-"}|${fieldKey}|${mode}`;
3615
+ }
3616
+ function fieldScopeKey(fieldKey) {
3617
+ return `${activeTab.value?.id ?? "-"}|${fieldKey}`;
3618
+ }
3619
+ function stashSlotValue(fieldKey, mode, value) {
3620
+ slotModeMemory.set(memoryKey(fieldKey, mode), cloneValue(value));
3621
+ }
3622
+ function hasStashedSlotValue(fieldKey, mode) {
3623
+ return slotModeMemory.has(memoryKey(fieldKey, mode));
3624
+ }
3625
+ function recallSlotValue(fieldKey, mode) {
3626
+ return cloneValue(slotModeMemory.get(memoryKey(fieldKey, mode)));
3627
+ }
3628
+ function setSlotMode(fieldKey, mode) {
3629
+ slotModeRendered.set(fieldScopeKey(fieldKey), mode);
3630
+ }
3631
+ function isTextEditRung(mode) {
3632
+ return mode === "literal" || mode === "template";
3633
+ }
3634
+ function effectiveSlotMode(fieldKey, value) {
3635
+ const key = fieldScopeKey(fieldKey);
3636
+ const sniffed = slotMode(value);
3637
+ const previous = slotModeRendered.get(key);
3638
+ const mode = previous !== undefined && isTextEditRung(previous) && isTextEditRung(sniffed) ? previous : sniffed;
3639
+ slotModeRendered.set(key, mode);
3640
+ return mode;
3641
+ }
3642
+ function hasRefOptions(opts) {
3643
+ return opts.stateDefs.length > 0 || Boolean(opts.extraSignals?.length);
3644
+ }
3645
+ function defaultForSlotMode(mode, opts) {
3646
+ switch (mode) {
3647
+ case "ref": {
3648
+ const [first] = opts.stateDefs;
3649
+ return first ? { $ref: `#/state/${first}` } : { $ref: opts.extraSignals?.[0]?.value ?? "" };
3650
+ }
3651
+ case "template": {
3652
+ const [first] = opts.stateDefs;
3653
+ return first ? `\${state.${first}}` : "${}";
3654
+ }
3655
+ case "expression": {
3656
+ return { $expression: { operator: "??", target: null, value: null } };
3657
+ }
3658
+ case "function": {
3659
+ return { $prototype: "Function", body: "", parameters: [] };
3660
+ }
3661
+ default: {
3662
+ return opts.literalDefault;
3663
+ }
3664
+ }
3665
+ }
3666
+ function refOptions(opts) {
3667
+ return [
3668
+ ...opts.stateDefs.map((name) => ({ label: name, value: `#/state/${name}` })),
3669
+ ...opts.extraSignals ?? []
3670
+ ];
3671
+ }
3672
+ function renderCustomRefWidget(refVal, opts) {
3673
+ return html`
3674
+ <jx-value-selector
3675
+ size="s"
3676
+ style="flex:1"
3677
+ placeholder="#/state/…"
3678
+ .value=${refVal}
3679
+ .options=${refOptions(opts)}
3680
+ @change=${(e) => {
3681
+ const v = (e.detail?.value ?? e.target.value ?? "").trim();
3682
+ if (v) {
3683
+ opts.onChange({ $ref: v });
3684
+ } else {
3685
+ opts.onChange();
3686
+ }
3687
+ }}
3688
+ ></jx-value-selector>
3689
+ `;
3690
+ }
3691
+ function renderRefWidget(refVal, opts) {
3692
+ if (opts.allowCustomRef) {
3693
+ return renderCustomRefWidget(refVal, opts);
3694
+ }
3695
+ return html`
3696
+ <sp-picker
3697
+ size="s"
3698
+ quiet
3699
+ style="flex:1"
3700
+ placeholder="— select signal —"
3701
+ value=${refVal || nothing}
3702
+ @change=${(e) => {
3703
+ const v = e.target.value;
3704
+ if (v) {
3705
+ opts.onChange({ $ref: v });
3706
+ } else {
3707
+ opts.onChange();
3708
+ }
3709
+ }}
3710
+ >
3711
+ ${opts.stateDefs.map((name) => html`<sp-menu-item value=${`#/state/${name}`}>${name}</sp-menu-item>`)}
3712
+ ${opts.extraSignals?.length ? html`
3713
+ <sp-menu-divider></sp-menu-divider>
3714
+ ${opts.extraSignals.map((sig) => html`<sp-menu-item value=${sig.value}>${sig.label}</sp-menu-item>`)}
3715
+ ` : nothing}
3716
+ </sp-picker>
3717
+ `;
3718
+ }
3719
+ function renderTemplateWidget(value, opts) {
3720
+ return html`
3721
+ <sp-textfield
3722
+ size="s"
3723
+ style="flex:1;font-family:var(--spectrum-code-font-family, monospace)"
3724
+ placeholder="\${state.…}"
3725
+ .value=${live(String(value ?? ""))}
3726
+ @change=${(e) => opts.onChange(e.target.value)}
3727
+ ></sp-textfield>
3728
+ `;
3729
+ }
3730
+ function switchSlotMode(fieldKey, from, to, current, seed) {
3731
+ stashSlotValue(fieldKey, from, current);
3732
+ setSlotMode(fieldKey, to);
3733
+ return hasStashedSlotValue(fieldKey, to) ? recallSlotValue(fieldKey, to) : seed;
3734
+ }
3735
+ function chipId(fieldKey) {
3736
+ return `value-source-${fieldKey.replaceAll(/[^\dA-Za-z]+/g, "-")}`;
3737
+ }
3738
+ function renderModeChip(mode, caps, opts) {
3739
+ const color = mode === "literal" ? "color:var(--spectrum-gray-600, #808080)" : "color:var(--spectrum-accent-content-color-default, #5c9dff)";
3740
+ const offered = caps.filter((m) => m !== "ref" || hasRefOptions(opts));
3741
+ const label = VALUE_SOURCE_LABELS[mode];
3742
+ if (!offered.some((m) => m !== mode)) {
3743
+ const hint2 = `Value source: ${label} (no other source available here)`;
3744
+ return html`
3745
+ <sp-action-button
3746
+ size="xs"
3747
+ quiet
3748
+ disabled
3749
+ class="dynamic-slot-mode"
3750
+ style=${color}
3751
+ title=${hint2}
3752
+ aria-label=${hint2}
3753
+ >${label}</sp-action-button
3754
+ >
3755
+ `;
3756
+ }
3757
+ const id = chipId(opts.fieldKey);
3758
+ const hint = `Value source: ${label} — click to change`;
3759
+ const choose = (next) => {
3760
+ if (next === mode) {
3761
+ return;
3762
+ }
3763
+ if (mode === "ref" && opts.literalDefault !== undefined && !hasStashedSlotValue(opts.fieldKey, "literal")) {
3764
+ stashSlotValue(opts.fieldKey, "literal", opts.literalDefault);
3765
+ }
3766
+ opts.onChange(switchSlotMode(opts.fieldKey, mode, next, cloneValue(opts.value), opts.seedFor?.(next) ?? defaultForSlotMode(next, opts)));
3767
+ };
3768
+ return html`
3769
+ <sp-action-button
3770
+ id=${id}
3771
+ size="xs"
3772
+ quiet
3773
+ class="dynamic-slot-mode"
3774
+ style=${color}
3775
+ title=${hint}
3776
+ aria-label=${hint}
3777
+ >${label}</sp-action-button
3778
+ >
3779
+ <sp-overlay trigger="${id}@click" placement="bottom-start" type="auto">
3780
+ <sp-popover>
3781
+ <sp-menu size="s" label="Value source">
3782
+ ${offered.map((m) => html`
3783
+ <sp-menu-item data-mode=${m} ?selected=${m === mode} @click=${() => choose(m)}
3784
+ >${VALUE_SOURCE_LABELS[m]}
3785
+ <span slot="description">${VALUE_SOURCE_HINTS[m]}</span></sp-menu-item
3786
+ >
3787
+ `)}
3788
+ </sp-menu>
3789
+ </sp-popover>
3790
+ </sp-overlay>
3791
+ `;
3792
+ }
3793
+ function renderDynamicSlot(opts) {
3794
+ const caps = slotCaps(opts.caps);
3795
+ const mode = effectiveSlotMode(opts.fieldKey, opts.value);
3796
+ 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 }), {
3797
+ allowEventRef: opts.allowEventRef ?? false,
3798
+ preview: opts.preview ?? null,
3799
+ stateDefs: opts.stateDefs,
3800
+ stateEntries: opts.stateEntries ?? null
3801
+ }) : opts.staticWidget;
3802
+ return {
3803
+ modeButton: renderModeChip(mode, caps, opts),
3804
+ widget: html`
3805
+ <div class="dynamic-slot" style="display:flex;flex:1;min-width:0">
3806
+ <div style="flex:1;min-width:0">${widget}</div>
3807
+ </div>
3808
+ `
3809
+ };
3810
+ }
3811
+
3812
+ // src/ui/schema-form.ts
3813
+ var controlRegistry = new Map;
3814
+ function registerFormControl(name, control) {
3815
+ controlRegistry.set(name, control);
3816
+ }
3817
+ function getFormControl(name) {
3818
+ return controlRegistry.get(name);
3819
+ }
3820
+ var CONTENT_REF = /^#\/content\/([^/]+)$/;
3821
+ function referenceTarget(schema) {
3822
+ const match = typeof schema?.$ref === "string" ? CONTENT_REF.exec(schema.$ref) : null;
3823
+ return match ? match[1] : null;
3824
+ }
3825
+ var NULL_FORM_CONTEXT = {
3826
+ resolvePointer: () => {}
3827
+ };
3828
+ function resolveFormEnum(enumDef, ctx, scope) {
3829
+ if (Array.isArray(enumDef)) {
3830
+ return enumDef;
3831
+ }
3832
+ let pointer;
3833
+ if (enumDef && typeof enumDef === "object") {
3834
+ const ref2 = enumDef.$ref;
3835
+ if (typeof ref2 === "string") {
3836
+ pointer = ref2;
3837
+ }
3838
+ } else if (typeof enumDef === "string") {
3839
+ pointer = enumDef;
3840
+ }
3841
+ if (pointer === undefined || !ctx) {
3842
+ return;
3843
+ }
3844
+ const resolved = ctx.resolvePointer(pointer, scope);
3845
+ if (Array.isArray(resolved)) {
3846
+ return resolved.map(String);
3847
+ }
3848
+ if (resolved && typeof resolved === "object") {
3849
+ return Object.keys(resolved);
3850
+ }
3851
+ return;
3852
+ }
3853
+ function parseNumericField(raw, integer) {
3854
+ if (raw.trim() === "") {
3855
+ return Number.NaN;
3856
+ }
3857
+ return integer ? Math.trunc(Number(raw)) : Number(raw);
3858
+ }
3859
+ function refTextField(key, refVal, onChange) {
3860
+ return html`<sp-textfield
3861
+ size="s"
3862
+ label=${key}
3863
+ placeholder=${key}
3864
+ .value=${live(refVal)}
3865
+ @change=${(e) => {
3866
+ const v = e.target.value.trim();
3867
+ onChange(v ? { $ref: v } : undefined);
3868
+ }}
3869
+ ></sp-textfield>`;
3870
+ }
3871
+ function renderInlineField(key, schema, value, onChange, ctx, scope, rerender) {
3872
+ if (isRef(value)) {
3873
+ return refTextField(key, value.$ref, onChange);
3874
+ }
3875
+ const referenceControl = referenceTarget(schema) === null ? undefined : controlRegistry.get("reference");
3876
+ if (referenceControl) {
3877
+ return referenceControl({
3878
+ ctx: ctx ?? NULL_FORM_CONTEXT,
3879
+ key,
3880
+ onChange,
3881
+ rerender,
3882
+ schema,
3883
+ value
3884
+ });
3885
+ }
3886
+ const enumValues = resolveFormEnum(schema.enum, ctx, scope);
3887
+ if (enumValues) {
3888
+ return html`<sp-picker
3889
+ size="s"
3890
+ label=${key}
3891
+ value=${value !== undefined ? String(value) : "__none__"}
3892
+ @change=${(e) => onChange(e.target.value === "__none__" ? undefined : e.target.value)}
3893
+ >
3894
+ <sp-menu-item value="__none__">—</sp-menu-item>
3895
+ ${enumValues.map((v) => html`<sp-menu-item value=${v}>${v}</sp-menu-item>`)}
3896
+ </sp-picker>`;
3897
+ }
3898
+ if (schema.type === "boolean") {
3899
+ return html`<sp-switch
3900
+ size="s"
3901
+ ?checked=${Boolean(value)}
3902
+ @change=${(e) => onChange(e.target.checked)}
3903
+ >${key}</sp-switch
3904
+ >`;
3905
+ }
3906
+ if (schema.type === "integer" || schema.type === "number") {
3907
+ return html`<sp-number-field
3908
+ size="s"
3909
+ label=${key}
3910
+ .value=${value !== undefined ? value : nothing}
3911
+ step=${schema.type === "integer" ? "1" : nothing}
3912
+ @change=${(e) => {
3913
+ const parsed = parseNumericField(e.target.value, schema.type === "integer");
3914
+ onChange(Number.isNaN(parsed) ? undefined : parsed);
3915
+ }}
3916
+ ></sp-number-field>`;
3917
+ }
3918
+ return html`<sp-textfield
3919
+ size="s"
3920
+ label=${key}
3921
+ placeholder=${key}
3922
+ .value=${value ?? ""}
3923
+ @input=${(e) => onChange(e.target.value || undefined)}
3924
+ ></sp-textfield>`;
3925
+ }
3926
+ function renderJsonTextField(currentValue, ps, commit) {
3927
+ let debounce;
3928
+ return html`<sp-textfield
3929
+ multiline
3930
+ size="s"
3931
+ style="min-height:40px"
3932
+ .value=${currentValue !== undefined ? JSON.stringify(currentValue, null, 2) : ""}
3933
+ placeholder=${ps.default !== undefined ? JSON.stringify(ps.default) : nothing}
3934
+ @input=${(e) => {
3935
+ clearTimeout(debounce);
3936
+ debounce = setTimeout(() => {
3937
+ try {
3938
+ commit(JSON.parse(e.target.value));
3939
+ } catch {}
3940
+ }, 500);
3941
+ }}
3942
+ ></sp-textfield>`;
3943
+ }
3944
+ function renderPropertyControl(prop, ps, value, required, opts, ctx) {
3945
+ const currentValue = value[prop];
3946
+ const commit = (next) => opts.onChange({ [prop]: next });
3947
+ const controlArgs = {
3948
+ ctx,
3949
+ key: prop,
3950
+ onChange: commit,
3951
+ rerender: opts.rerender,
3952
+ schema: ps,
3953
+ value: currentValue
3954
+ };
3955
+ const overrideName = opts.ui?.[prop]?.control;
3956
+ if (overrideName) {
3957
+ const custom = controlRegistry.get(overrideName);
3958
+ if (custom) {
3959
+ return custom(controlArgs);
3960
+ }
3961
+ }
3962
+ if (isRef(currentValue) && isBindableField(ps)) {
3963
+ return refTextField(prop, currentValue.$ref, commit);
3964
+ }
3965
+ if (referenceTarget(ps) !== null) {
3966
+ const referenceControl = controlRegistry.get("reference");
3967
+ if (referenceControl) {
3968
+ return referenceControl(controlArgs);
3969
+ }
3970
+ }
3971
+ const enumValues = resolveFormEnum(opts.ui?.[prop]?.enum ?? ps.enum, ctx, value);
3972
+ if (enumValues) {
3973
+ return html`
3974
+ <sp-picker
3975
+ size="s"
3976
+ value=${currentValue !== undefined ? String(currentValue) : ps.default !== undefined ? String(ps.default) : "__none__"}
3977
+ @change=${(e) => commit(e.target.value === "__none__" ? undefined : e.target.value)}
3978
+ >
3979
+ ${!required.has(prop) ? html`<sp-menu-item value="__none__">—</sp-menu-item>` : nothing}
3980
+ ${enumValues.map((val) => html`<sp-menu-item value=${val}>${val}</sp-menu-item>`)}
3981
+ </sp-picker>
3982
+ `;
3983
+ }
3984
+ if (ps.type === "boolean") {
3985
+ return html`<sp-checkbox
3986
+ ?checked=${currentValue ?? ps.default ?? false}
3987
+ @change=${(e) => commit(e.target.checked)}
3988
+ ></sp-checkbox>`;
3989
+ }
3990
+ if (ps.type === "integer" || ps.type === "number") {
3991
+ let debounce2;
3992
+ return html`<sp-number-field
3993
+ size="s"
3994
+ min=${ifDefined(ps.minimum)}
3995
+ max=${ifDefined(ps.maximum)}
3996
+ step=${ps.type === "integer" ? "1" : nothing}
3997
+ .value=${currentValue !== undefined ? currentValue : nothing}
3998
+ placeholder=${ps.default != null ? String(ps.default) : nothing}
3999
+ @change=${(e) => {
4000
+ clearTimeout(debounce2);
4001
+ debounce2 = setTimeout(() => {
4002
+ const parsed = parseNumericField(e.target.value, ps.type === "integer");
4003
+ commit(Number.isNaN(parsed) ? undefined : parsed);
4004
+ }, 400);
4005
+ }}
4006
+ ></sp-number-field>`;
4007
+ }
4008
+ if (ps.format === "json-schema") {
4009
+ const hasValue = currentValue && typeof currentValue === "object" && Object.keys(currentValue).length > 0;
4010
+ const cv = currentValue;
4011
+ const isSchemaRef = hasValue && cv.$ref;
4012
+ let debounce2;
4013
+ return html`
4014
+ <div class="schema-param-editor">
4015
+ ${hasValue && !isSchemaRef && cv.properties ? html`
4016
+ <div style="display:flex;flex-wrap:wrap;gap:3px;margin-bottom:4px">
4017
+ ${Object.entries(cv.properties).map(([k, v]) => html`
4018
+ <span
4019
+ style="background:var(--bg);padding:1px 6px;border-radius:var(--radius);font-size:10px;color:var(--fg-dim)"
4020
+ >${k}: ${v.type ?? "any"}</span
4021
+ >
4022
+ `)}
4023
+ </div>
4024
+ ` : nothing}
4025
+ <sp-textfield
4026
+ multiline
4027
+ size="s"
4028
+ style=${styleMap({
4029
+ fontFamily: "monospace",
4030
+ fontSize: "11px",
4031
+ minHeight: hasValue ? "80px" : "40px"
4032
+ })}
4033
+ .value=${currentValue !== undefined ? JSON.stringify(currentValue, null, 2) : ""}
4034
+ placeholder=${ps.description ?? "JSON Schema defining the data shape…"}
4035
+ @input=${(e) => {
4036
+ clearTimeout(debounce2);
4037
+ debounce2 = setTimeout(() => {
4038
+ try {
4039
+ commit(JSON.parse(e.target.value));
4040
+ } catch {}
4041
+ }, 500);
4042
+ }}
4043
+ ></sp-textfield>
4044
+ </div>
4045
+ `;
4046
+ }
4047
+ if (ps.type === "array" && ps.items?.type === "object" && ps.items?.properties) {
4048
+ const rows = Array.isArray(currentValue) ? currentValue : [];
4049
+ const itemProps = ps.items.properties;
4050
+ return html`
4051
+ <div class="array-object-field">
4052
+ ${rows.map((row, idx) => html`
4053
+ <div
4054
+ class="array-object-row"
4055
+ style="display:flex;gap:4px;align-items:center;margin-bottom:4px"
4056
+ >
4057
+ ${Object.entries(itemProps).map(([propKey, propSchema]) => renderInlineField(propKey, propSchema, row[propKey], (val) => {
4058
+ const updated = [...rows];
4059
+ updated[idx] = { ...updated[idx], [propKey]: val };
4060
+ commit(updated);
4061
+ }, ctx, value, opts.rerender))}
4062
+ <sp-action-button
4063
+ quiet
4064
+ size="s"
4065
+ @click=${() => {
4066
+ const updated = rows.filter((_, i) => i !== idx);
4067
+ commit(updated.length > 0 ? updated : undefined);
4068
+ opts.rerender?.();
4069
+ }}
4070
+ >
4071
+ <sp-icon-delete slot="icon"></sp-icon-delete>
4072
+ </sp-action-button>
4073
+ </div>
4074
+ `)}
4075
+ <sp-action-button
4076
+ quiet
4077
+ size="s"
4078
+ @click=${(e) => {
4079
+ e.stopPropagation();
4080
+ const newRow = {};
4081
+ for (const [k, v] of Object.entries(itemProps)) {
4082
+ if (v.default !== undefined) {
4083
+ newRow[k] = v.default;
4084
+ }
4085
+ }
4086
+ commit([...rows, newRow]);
4087
+ opts.rerender?.();
4088
+ }}
4089
+ >+ Add</sp-action-button
4090
+ >
4091
+ </div>
4092
+ `;
4093
+ }
4094
+ if (ps.type === "array" || ps.type === "object") {
4095
+ return renderJsonTextField(currentValue, ps, commit);
4096
+ }
4097
+ let debounce;
4098
+ const ph = ps.default !== undefined ? String(ps.default) : ps.examples?.[0] ?? "";
4099
+ return html`<sp-textfield
4100
+ size="s"
4101
+ style="flex:1"
4102
+ .value=${currentValue ?? ""}
4103
+ placeholder=${ph || nothing}
4104
+ title=${ps.description || nothing}
4105
+ @input=${(e) => {
4106
+ clearTimeout(debounce);
4107
+ debounce = setTimeout(() => commit(e.target.value || undefined), 400);
4108
+ }}
4109
+ ></sp-textfield>`;
4110
+ }
4111
+ function isBindableField(ps) {
4112
+ return ps.format !== "json-schema" && ps.type !== "object" && ps.type !== "array";
4113
+ }
4114
+ function refSourcesFor(ctx) {
4115
+ return [
4116
+ ...(ctx.signals ?? []).map((name) => ({ label: name, value: `#/state/${name}` })),
4117
+ ...(ctx.params ?? []).map((name) => ({ label: `$params/${name}`, value: `#/$params/${name}` }))
4118
+ ];
4119
+ }
4120
+ function validateFieldValue(schema, value, isRequired) {
4121
+ if (isRef(value)) {
4122
+ return "";
4123
+ }
4124
+ const empty = value === undefined || value === null || value === "";
4125
+ if (empty) {
4126
+ return isRequired ? "Required." : "";
4127
+ }
4128
+ if (Array.isArray(schema.enum) && !schema.enum.includes(value)) {
4129
+ return `Choose one of: ${schema.enum.map(String).join(", ")}.`;
4130
+ }
4131
+ if (schema.type === "number" || schema.type === "integer") {
4132
+ const n = typeof value === "number" ? value : Number(value);
4133
+ if (!Number.isFinite(n)) {
4134
+ return "Enter a number.";
4135
+ }
4136
+ if (schema.type === "integer" && !Number.isInteger(n)) {
4137
+ return "Enter a whole number.";
4138
+ }
4139
+ if (schema.minimum !== undefined && n < schema.minimum) {
4140
+ return `Must be ${schema.minimum} or more.`;
4141
+ }
4142
+ if (schema.maximum !== undefined && n > schema.maximum) {
4143
+ return `Must be ${schema.maximum} or less.`;
4144
+ }
4145
+ return "";
4146
+ }
4147
+ if (schema.type === "boolean" && typeof value !== "boolean") {
4148
+ return "Must be true or false.";
4149
+ }
4150
+ if (schema.type === "array" && !Array.isArray(value)) {
4151
+ return "Must be a list.";
4152
+ }
4153
+ if (schema.type === "object" && (typeof value !== "object" || Array.isArray(value))) {
4154
+ return "Must be an object.";
4155
+ }
4156
+ return "";
4157
+ }
4158
+ function renderForm(schema, value, opts) {
4159
+ const required = new Set(schema.required);
4160
+ const ctx = opts.context ?? NULL_FORM_CONTEXT;
4161
+ const refSources = refSourcesFor(ctx);
4162
+ const propertyFields = Object.entries(schema.properties ?? {}).map(([prop, ps]) => {
4163
+ const labelText = prop + (required.has(prop) ? " *" : "");
4164
+ const error = opts.errors?.[prop] || validateFieldValue(ps, value[prop], Boolean(opts.showRequired) && required.has(prop));
4165
+ const count = opts.errorCounts?.[prop];
4166
+ const staticWidget = renderPropertyControl(prop, ps, value, required, opts, ctx);
4167
+ const laddered = refSources.length > 0 && isBindableField(ps) && !opts.ui?.[prop]?.control ? renderDynamicSlot({
4168
+ allowCustomRef: true,
4169
+ caps: { schema: configFieldSchema(ps) },
4170
+ extraSignals: refSources,
4171
+ fieldKey: `${ctx.fieldKeyPrefix ?? ""}.${prop}`,
4172
+ onChange: (v) => opts.onChange({ [prop]: v }),
4173
+ staticWidget,
4174
+ stateDefs: [],
4175
+ value: value[prop]
4176
+ }) : null;
4177
+ return renderFieldRow({
4178
+ hasValue: false,
4179
+ label: labelText,
4180
+ prop: ps.name || prop,
4181
+ widget: laddered ? laddered.widget : staticWidget,
4182
+ ...laddered ? { labelExtra: laddered.modeButton } : {},
4183
+ ...error ? { error } : {},
4184
+ ...count === undefined ? {} : { errorCount: count }
4185
+ });
4186
+ });
4187
+ return html`${propertyFields}`;
4188
+ }
4189
+
4190
+ // src/services/context-resolver.ts
4191
+ var PARAM_SEGMENT = /^\{@(\w+)\}$/;
4192
+ function resolveContextPointer(pointer, opts) {
4193
+ if (!pointer.startsWith("#/$context/")) {
4194
+ return;
4195
+ }
4196
+ const segments = [];
4197
+ for (const raw of pointer.slice("#/$context/".length).split("/")) {
4198
+ const match = raw.match(PARAM_SEGMENT);
4199
+ if (match) {
4200
+ const value = opts.scope?.[match[1]];
4201
+ if (typeof value !== "string" && typeof value !== "number") {
4202
+ return;
4203
+ }
4204
+ const substituted = String(value);
4205
+ if (!substituted) {
4206
+ return;
4207
+ }
4208
+ segments.push(substituted);
4209
+ } else {
4210
+ segments.push(raw);
4211
+ }
4212
+ }
4213
+ if (segments[0] === "$formats") {
4214
+ if (segments.length !== 1) {
4215
+ return;
4216
+ }
4217
+ return (opts.formats ?? []).map((format) => format.name);
4218
+ }
4219
+ let node = opts.projectConfig;
4220
+ for (const segment of segments) {
4221
+ if (node === null || typeof node !== "object") {
4222
+ return;
4223
+ }
4224
+ node = node[segment];
4225
+ }
4226
+ return node;
4227
+ }
4228
+
4229
+ export { renderProvenanceChip, countProvenance, renderProvenanceDots, renderFieldRow, renderKvRow, renderStaticKvRow, chipSummary, renderFormulaChips, applyCatalogPick, globalEntries, namedFormulaEntries, formulaCatalog, openFormulaPalette, REDIRECT_STATUSES, VALUE_SOURCE_LABELS, slotMode, capsForPosition, isActionExpression, expressionHint, renderOperandEditor, renderExpressionEditor, switchSlotMode, renderDynamicSlot, registerFormControl, getFormControl, referenceTarget, NULL_FORM_CONTEXT, renderForm, resolveContextPointer };
4230
+
4231
+ //# debugId=D548DF8319DDE4D664756E2164756E21
4232
+ //# sourceMappingURL=studio-c6s5getc.js.map