@jxsuite/studio 4.1.0 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (612) hide show
  1. package/README.md +1 -1
  2. package/data/elements-meta.json +5 -15
  3. package/data/html-meta.json +51 -6
  4. package/dist/chunks/{2020-n6tfwnda.js → 2020-g79n01v9.js} +14 -14
  5. package/dist/chunks/action-group-sp6tna8v.js +118 -0
  6. package/dist/chunks/adapter-nj7tnjfn.js +13 -0
  7. package/dist/chunks/cf-account-picker-qwkm9zrf.js +495 -0
  8. package/dist/chunks/cf-settings-71bv5zn9.js +20 -0
  9. package/dist/chunks/color-area-v17df7b8.js +154 -0
  10. package/dist/chunks/color-field-anwybnkn.js +208 -0
  11. package/dist/chunks/color-slider-44ez96fc.js +79 -0
  12. package/dist/chunks/color-xgg3kfw7.js +35 -0
  13. package/dist/chunks/combobox-5e7jev23.js +236 -0
  14. package/dist/chunks/components-vj72gqtq.js +20 -0
  15. package/dist/chunks/content-source-5mex4x1h.js +46 -0
  16. package/dist/chunks/convert-to-repeater-n64h8znc.js +639 -0
  17. package/dist/chunks/dialog-s890b918.js +85 -0
  18. package/dist/chunks/editor-3qhbg51x.js +35 -0
  19. package/dist/chunks/entry-commands-234hjnqw.js +42 -0
  20. package/dist/chunks/extension-sections-3hpcambp.js +651 -0
  21. package/dist/chunks/file-ops-8yhd83xr.js +47 -0
  22. package/dist/chunks/files-hnxqxtsg.js +72 -0
  23. package/dist/chunks/format-host-779sfp2n.js +40 -0
  24. package/dist/chunks/fs-events-b88z7g4h.js +18 -0
  25. package/dist/chunks/git-panel-xwj8fpab.js +49 -0
  26. package/dist/chunks/icons-yy10wk9r.js +402 -0
  27. package/dist/chunks/iframe-entry-15g3n6xv.js +114 -0
  28. package/dist/chunks/iframe-entry-b27vq81n.js +203 -0
  29. package/dist/chunks/iframe-entry-nbyxee1y.js +154 -0
  30. package/dist/chunks/iframe-host-syr0f9sb.js +115 -0
  31. package/dist/chunks/{index-wcnt3r9g.js → index-mdmyx6s2.js} +11 -11
  32. package/dist/chunks/index-n4j7qxyr.js +109 -0
  33. package/dist/chunks/{javascript-q8kpmfah.js → javascript-xvrfrpkb.js} +8 -9
  34. package/dist/chunks/{jsonMode-dkr040ga.js → jsonMode-tyx2kj6p.js} +526 -527
  35. package/dist/chunks/jx-validate-j0wxk72w.js +15 -0
  36. package/dist/chunks/layers-ff81wthn.js +30 -0
  37. package/dist/chunks/layers-panel-pnnghw12.js +40 -0
  38. package/dist/chunks/listbox-6v2ac08j.js +44 -0
  39. package/dist/chunks/lit-html-m244w9nh.js +17 -0
  40. package/dist/chunks/media-picker-cqhhxhhe.js +33 -0
  41. package/dist/chunks/menu-9p2w46gj.js +15 -0
  42. package/dist/chunks/menu-q99sy9rf.js +285 -0
  43. package/dist/chunks/{monaco-binding-rj6xpx9y.js → monaco-binding-sw6en4dw.js} +12 -15
  44. package/dist/chunks/monaco-buffer-bhy3hb6m.js +24 -0
  45. package/dist/chunks/{monaco-setup-vx9bhp1d.js → monaco-setup-8r62fj6y.js} +27 -37
  46. package/dist/chunks/notify-628t0anh.js +21 -0
  47. package/dist/chunks/number-field-5cndyft8.js +72 -0
  48. package/dist/chunks/parse-0zx14y15.js +10 -0
  49. package/dist/chunks/platform-ts9cg1a4.js +15 -0
  50. package/dist/chunks/popover-47z3ytva.js +25 -0
  51. package/dist/chunks/publish-panel-5zt840sy.js +27 -0
  52. package/dist/chunks/quick-search-3748564r.js +45 -0
  53. package/dist/chunks/references-qvvxej7h.js +26 -0
  54. package/dist/chunks/right-panel-eccz92qt.js +45 -0
  55. package/dist/chunks/section-registry-5pq32t0q.js +25 -0
  56. package/dist/chunks/select-2r16ez41.js +49 -0
  57. package/dist/chunks/split-g7nm1rgb.js +191 -0
  58. package/dist/chunks/studio-0hqbpbd0.js +62 -0
  59. package/dist/chunks/{studio-kryvsw6j.js → studio-1k325pwf.js} +48 -48
  60. package/dist/chunks/{studio-b55y6ewx.js → studio-1knfqgy7.js} +58 -58
  61. package/dist/chunks/{studio-2b946z26.js → studio-1z1kcjv4.js} +3628 -6095
  62. package/dist/chunks/studio-3773086z.js +111 -0
  63. package/dist/chunks/{studio-4r1pv6zf.js → studio-38emantv.js} +22 -5
  64. package/dist/chunks/{studio-x813zp48.js → studio-3rz5md06.js} +30 -30
  65. package/dist/chunks/{studio-ynxwkrzd.js → studio-4wbb9ct0.js} +1953 -1785
  66. package/dist/chunks/studio-5te5tqng.js +81 -0
  67. package/dist/chunks/studio-5wt9nvhd.js +4763 -0
  68. package/dist/chunks/{studio-p4ngc046.js → studio-6ewqv390.js} +9 -9
  69. package/dist/chunks/{studio-9ab8gqsm.js → studio-6nzaktvy.js} +7 -7
  70. package/dist/chunks/studio-6xa21ej9.js +28 -0
  71. package/dist/chunks/{studio-5ehhackm.js → studio-727xh0e3.js} +19 -19
  72. package/dist/chunks/{studio-gcye7qka.js → studio-7e11jpa9.js} +21 -21
  73. package/dist/chunks/{studio-q7v10cg2.js → studio-7r42c6ca.js} +17 -7
  74. package/dist/chunks/studio-7xnmgv66.js +13615 -0
  75. package/dist/chunks/studio-8scdew0e.js +258 -0
  76. package/dist/chunks/{studio-fbf92ckf.js → studio-93zafjcm.js} +275 -163
  77. package/dist/chunks/{studio-dg7zyt5x.js → studio-98b3z07n.js} +1 -1
  78. package/dist/chunks/studio-a79k90n9.js +1162 -0
  79. package/dist/chunks/studio-aj37eer3.js +422 -0
  80. package/dist/chunks/studio-b0k5tqgp.js +2432 -0
  81. package/dist/chunks/studio-b4s9eykd.js +2811 -0
  82. package/dist/chunks/{studio-f3wqjtk5.js → studio-d0300636.js} +5076 -18
  83. package/dist/chunks/{studio-b9b6t1dg.js → studio-e1f24eff.js} +5664 -5666
  84. package/dist/chunks/studio-e1pgga3k.js +839 -0
  85. package/dist/chunks/{studio-g1wdche0.js → studio-esrxgwh6.js} +80 -80
  86. package/dist/chunks/{studio-q43a5y9h.js → studio-g3jevyjt.js} +16 -16
  87. package/dist/chunks/studio-gb13095a.js +2806 -0
  88. package/dist/chunks/{studio-3n4ndrzh.js → studio-h4nvnjvz.js} +303 -303
  89. package/dist/chunks/{studio-dm19q43r.js → studio-hhvs4sdg.js} +10 -10
  90. package/dist/chunks/{studio-x4qkdat5.js → studio-js8nv53n.js} +1749 -2481
  91. package/dist/chunks/{studio-qjy2z9bv.js → studio-jxp12rnj.js} +32 -137
  92. package/dist/chunks/{studio-trxrabqf.js → studio-k75n7vhp.js} +527 -283
  93. package/dist/chunks/{studio-q9jd1a60.js → studio-kcgff4r0.js} +15 -18
  94. package/dist/chunks/studio-kkqkcp3d.js +17668 -0
  95. package/dist/chunks/studio-mhhyamxf.js +763 -0
  96. package/dist/chunks/{studio-cft9r4r7.js → studio-nq5yf5mp.js} +22 -22
  97. package/dist/chunks/{studio-685s0ytq.js → studio-nqgawfks.js} +572 -572
  98. package/dist/chunks/studio-nzzpvzx9.js +185 -0
  99. package/dist/chunks/{studio-xrapxwwc.js → studio-p1dw8wfw.js} +55 -29
  100. package/dist/chunks/{studio-3bqpr0zh.js → studio-pxbgypmg.js} +299 -110
  101. package/dist/chunks/studio-q0a3yk4n.js +33 -0
  102. package/dist/chunks/{studio-wcy8trfx.js → studio-q3x06vmj.js} +23 -22
  103. package/dist/chunks/{studio-6yw4y3my.js → studio-qhhm6hbt.js} +8 -8
  104. package/dist/chunks/studio-qk4bfsq2.js +362 -0
  105. package/dist/chunks/{studio-msd847zn.js → studio-qk90fgqm.js} +361 -90
  106. package/dist/chunks/{studio-jkftxyvz.js → studio-r6nvacqs.js} +5861 -2154
  107. package/dist/chunks/{studio-53n8bp04.js → studio-r6xmmtfh.js} +1 -1
  108. package/dist/chunks/{studio-tvgyw9xw.js → studio-rnyf3f8e.js} +83 -250
  109. package/dist/chunks/studio-rs76k67r.js +12 -0
  110. package/dist/chunks/studio-sbjyc8bt.js +954 -0
  111. package/dist/chunks/{studio-1wagb550.js → studio-syg2tadj.js} +11 -11
  112. package/dist/chunks/{studio-t5wqwzdh.js → studio-tjjk7jvk.js} +1 -1
  113. package/dist/chunks/studio-tqgryxwg.js +1171 -0
  114. package/dist/chunks/studio-try13h8c.js +8448 -0
  115. package/dist/chunks/studio-vfq8qpjt.js +1219 -0
  116. package/dist/chunks/studio-vhv5tgxm.js +51 -0
  117. package/dist/chunks/{studio-ahn3pdgx.js → studio-vw7mzp6z.js} +52 -38
  118. package/dist/chunks/studio-wqpngpsk.js +174 -0
  119. package/dist/chunks/studio-wqz6dggm.js +1356 -0
  120. package/dist/chunks/{studio-92ab2jqe.js → studio-y9zedyjn.js} +14 -14
  121. package/dist/chunks/studio-yxa81f45.js +12 -0
  122. package/dist/chunks/studio-zdn2g6dt.js +148 -0
  123. package/dist/chunks/swatch-group-t0n6h2rn.js +139 -0
  124. package/dist/chunks/tabs-wbp1cdws.js +156 -0
  125. package/dist/chunks/textfield-abk6qyee.js +73 -0
  126. package/dist/chunks/toast-780pvfbv.js +27 -0
  127. package/dist/chunks/toast-host-3sqv46sn.js +116 -0
  128. package/dist/chunks/toolbar-xbh5bhtr.js +194 -0
  129. package/dist/chunks/tooltip-eptb614w.js +369 -0
  130. package/dist/chunks/tree-a73p0pqm.js +367 -0
  131. package/dist/chunks/{tsMode-76kr53q0.js → tsMode-jy4m86rv.js} +83 -85
  132. package/dist/chunks/workspace-t55jdjbx.js +69 -0
  133. package/dist/chunks/{ws-client-6shc5rze.js → ws-client-ny4zc51r.js} +20 -22
  134. package/dist/iframe-entry.js +1873 -673
  135. package/dist/studio.js +52757 -36535
  136. package/dist/workers/editor.worker.js +1040 -1085
  137. package/dist/workers/json.worker.js +1946 -1991
  138. package/dist/workers/ts.worker.js +4960 -5005
  139. package/index.html +1 -0
  140. package/package.json +18 -52
  141. package/src/about/about-modal.ts +72 -123
  142. package/src/browse/library-commands.ts +20 -49
  143. package/src/browse/library-layouts.ts +87 -218
  144. package/src/browse/library-pane.ts +406 -438
  145. package/src/canvas/canvas-helpers.ts +0 -49
  146. package/src/canvas/canvas-live-render.ts +25 -2
  147. package/src/canvas/canvas-render.ts +666 -231
  148. package/src/canvas/canvas-surface.ts +0 -1
  149. package/src/canvas/canvas-utils.ts +333 -113
  150. package/src/canvas/dialog-path.ts +76 -0
  151. package/src/canvas/dialog-state.ts +67 -0
  152. package/src/canvas/diff-marks.ts +252 -0
  153. package/src/canvas/diff-toolbar.ts +349 -0
  154. package/src/canvas/diff-view.ts +123 -0
  155. package/src/canvas/edit-width-drag.ts +187 -0
  156. package/src/canvas/edit-width.ts +150 -0
  157. package/src/canvas/iframe-entry.ts +125 -8
  158. package/src/canvas/iframe-host.ts +271 -15
  159. package/src/canvas/iframe-interaction.ts +133 -13
  160. package/src/canvas/iframe-patch.ts +7 -1
  161. package/src/canvas/iframe-position.ts +5 -6
  162. package/src/canvas/iframe-protocol.ts +106 -5
  163. package/src/canvas/iframe-render.ts +373 -5
  164. package/src/canvas/iframe-slash.ts +2 -2
  165. package/src/canvas/iframe-subtree.ts +5 -8
  166. package/src/canvas/path-mapping.ts +25 -3
  167. package/src/canvas/popover-path.ts +106 -0
  168. package/src/canvas/popover-state.ts +155 -0
  169. package/src/canvas/surface-registry.ts +21 -10
  170. package/src/collab/presence-chips.ts +51 -73
  171. package/src/commands/app-commands.ts +10 -0
  172. package/src/commands/budget.ts +12 -0
  173. package/src/commands/command-args.ts +404 -6
  174. package/src/commands/defaults.ts +44 -14
  175. package/src/commands/live-context.ts +11 -5
  176. package/src/commands/reference.ts +12 -2
  177. package/src/commands/registry.ts +177 -10
  178. package/src/commands/run-reported.ts +120 -0
  179. package/src/content/entry-commands.ts +16 -23
  180. package/src/content/entry-editor.ts +144 -101
  181. package/src/editor/canvas-slash-bridge.ts +2 -2
  182. package/src/editor/context-menu.ts +49 -200
  183. package/src/editor/convert-to-component.ts +42 -82
  184. package/src/editor/convert-to-repeater.ts +132 -173
  185. package/src/editor/inline-edit.ts +11 -0
  186. package/src/editor/shortcuts.ts +76 -49
  187. package/src/editor/slash-menu.ts +150 -157
  188. package/src/files/components.ts +39 -0
  189. package/src/files/file-ops.ts +117 -11
  190. package/src/files/files.ts +944 -660
  191. package/src/files/json-layout.ts +474 -0
  192. package/src/files/serialize-document.ts +8 -1
  193. package/src/format/convert-file.ts +16 -10
  194. package/src/format/format-host.ts +50 -1
  195. package/src/github/github-auth.ts +71 -29
  196. package/src/github/github-publish.ts +57 -65
  197. package/src/grid/cell-editors.ts +46 -4
  198. package/src/grid/cell-popovers.ts +65 -62
  199. package/src/grid/grid-open.ts +86 -64
  200. package/src/grid/grid-panel.ts +390 -469
  201. package/src/grid/redirects-grid.ts +40 -44
  202. package/src/hosting/layout.ts +2 -0
  203. package/src/i18n/i18n-commands.ts +137 -62
  204. package/src/media/media-pane.ts +83 -133
  205. package/src/new-project/add-repo-modal.ts +125 -176
  206. package/src/new-project/import-tab.ts +181 -252
  207. package/src/new-project/location-fields.ts +93 -160
  208. package/src/new-project/new-project-modal.ts +376 -367
  209. package/src/packages/jxsuite-update.ts +76 -18
  210. package/src/packages/pull-package-sync.ts +5 -1
  211. package/src/panels/activity-panel.ts +137 -104
  212. package/src/panels/ai-chat/chat-markdown.ts +15 -13
  213. package/src/panels/ai-chat/chat-view.ts +307 -472
  214. package/src/panels/ai-chat/composer.ts +175 -163
  215. package/src/panels/ai-chat/sessions-view.ts +31 -62
  216. package/src/panels/ai-panel.ts +213 -185
  217. package/src/panels/block-action-bar.ts +444 -645
  218. package/src/panels/bottom-dock.ts +112 -47
  219. package/src/panels/chat-panel.ts +19 -14
  220. package/src/panels/data-explorer.ts +141 -102
  221. package/src/panels/data-grid.ts +183 -150
  222. package/src/panels/dnd.ts +75 -20
  223. package/src/panels/editors.ts +26 -19
  224. package/src/panels/elements-panel.ts +243 -186
  225. package/src/panels/empty-state.ts +26 -42
  226. package/src/panels/events-panel.ts +950 -729
  227. package/src/panels/formula-workspace.ts +434 -207
  228. package/src/panels/frontmatter-fields.ts +185 -153
  229. package/src/panels/frontmatter-panel.ts +411 -187
  230. package/src/panels/git-diff-open.ts +148 -0
  231. package/src/panels/git-panel.ts +679 -605
  232. package/src/panels/head-panel.ts +617 -435
  233. package/src/panels/i18n-panel.ts +148 -135
  234. package/src/panels/imports-panel.ts +388 -414
  235. package/src/panels/jump-bar.ts +175 -147
  236. package/src/panels/layers-panel.ts +785 -671
  237. package/src/panels/left-panel.ts +166 -94
  238. package/src/panels/navigator-panels.ts +1 -1
  239. package/src/panels/pane-context.ts +529 -677
  240. package/src/panels/pane-grid.ts +265 -241
  241. package/src/panels/panel-registry.ts +15 -4
  242. package/src/panels/problems-panel.ts +181 -99
  243. package/src/panels/properties-panel.ts +1525 -966
  244. package/src/panels/provenance.ts +31 -113
  245. package/src/panels/quick-search.ts +223 -184
  246. package/src/panels/right-panel.ts +135 -169
  247. package/src/panels/seo-modal.ts +346 -167
  248. package/src/panels/settings-menu.ts +114 -661
  249. package/src/panels/settings-pane.ts +102 -86
  250. package/src/panels/signals-panel.ts +1318 -1172
  251. package/src/panels/statement-editor.ts +556 -428
  252. package/src/panels/style-panel.ts +1367 -630
  253. package/src/panels/style-utils.ts +36 -32
  254. package/src/panels/stylebook-doc.ts +30 -73
  255. package/src/panels/stylebook-layers-panel.ts +202 -109
  256. package/src/panels/stylebook-panel.ts +124 -88
  257. package/src/panels/tab-strip.ts +386 -288
  258. package/src/platforms/cloud.ts +106 -16
  259. package/src/platforms/devserver.ts +13 -0
  260. package/src/publish/deploy-checklist.ts +124 -78
  261. package/src/publish/publish-commands.ts +4 -12
  262. package/src/publish/publish-panel.ts +134 -328
  263. package/src/services/a11y-report.ts +100 -43
  264. package/src/services/ai-command-tools.ts +538 -0
  265. package/src/services/ai-project-tools.ts +8 -2
  266. package/src/services/ai-session-store.ts +6 -0
  267. package/src/services/ai-system-prompt.ts +112 -24
  268. package/src/services/ai-tools.ts +36 -212
  269. package/src/services/ai-write-report.ts +174 -0
  270. package/src/services/document-assistant.ts +59 -8
  271. package/src/services/idle.ts +16 -14
  272. package/src/services/live-surfaces.ts +237 -0
  273. package/src/services/model-uri.ts +75 -0
  274. package/src/services/notify.ts +63 -6
  275. package/src/services/popover-report.ts +325 -0
  276. package/src/services/surface-registry.ts +56 -0
  277. package/src/services/tool-executor.ts +6 -0
  278. package/src/settings/contexts-section.ts +301 -299
  279. package/src/settings/contributed-section.ts +361 -222
  280. package/src/settings/css-vars-editor.ts +320 -325
  281. package/src/settings/defs-editor.ts +572 -535
  282. package/src/settings/dependencies-editor.ts +79 -107
  283. package/src/settings/extension-commands.ts +379 -0
  284. package/src/settings/extension-rows.ts +113 -0
  285. package/src/settings/extensions-section.ts +287 -0
  286. package/src/settings/general-settings.ts +130 -181
  287. package/src/settings/head-editor.ts +10 -305
  288. package/src/settings/locales-section.ts +146 -196
  289. package/src/settings/preferences-accounts.ts +1 -1
  290. package/src/settings/preferences-dialog.ts +350 -330
  291. package/src/settings/project-sections.ts +108 -181
  292. package/src/settings/schema-field-ui.ts +11 -399
  293. package/src/settings/section-registry.ts +8 -1
  294. package/src/settings/settings-document.ts +31 -29
  295. package/src/shell/tree.ts +42 -87
  296. package/src/shell.ts +48 -36
  297. package/src/studio.ts +104 -69
  298. package/src/style/token-ref.ts +0 -39
  299. package/src/surfaces/about.json +263 -0
  300. package/src/surfaces/about.ts +163 -0
  301. package/src/surfaces/add-repo.json +486 -0
  302. package/src/surfaces/add-repo.ts +236 -0
  303. package/src/surfaces/ai-chat.json +2185 -0
  304. package/src/surfaces/ai-chat.ts +549 -0
  305. package/src/surfaces/ai-credentials-form.json +332 -0
  306. package/src/surfaces/ai-credentials-form.ts +151 -0
  307. package/src/surfaces/ai-managed-connect.json +108 -0
  308. package/src/surfaces/ai-managed-connect.ts +123 -0
  309. package/src/surfaces/ai-model-picker.json +114 -0
  310. package/src/surfaces/ai-model-picker.ts +139 -0
  311. package/src/surfaces/block-action-bar.json +640 -0
  312. package/src/surfaces/block-action-bar.ts +368 -0
  313. package/src/surfaces/bottom-dock.json +154 -0
  314. package/src/surfaces/bottom-dock.ts +125 -0
  315. package/src/surfaces/canvas-stage.json +444 -0
  316. package/src/surfaces/canvas-stage.ts +253 -0
  317. package/src/surfaces/cf-account-picker.json +300 -0
  318. package/src/surfaces/cf-account-picker.ts +214 -0
  319. package/src/surfaces/commandbar.json +985 -0
  320. package/src/{panels/toolbar.ts → surfaces/commandbar.ts} +417 -391
  321. package/src/surfaces/convert-repeater.json +309 -0
  322. package/src/surfaces/convert-repeater.ts +276 -0
  323. package/src/surfaces/data-actions.json +157 -0
  324. package/src/surfaces/data-actions.ts +115 -0
  325. package/src/surfaces/dialog.json +289 -0
  326. package/src/surfaces/dialog.ts +343 -0
  327. package/src/surfaces/diff-toolbar.json +266 -0
  328. package/src/surfaces/diff-toolbar.ts +139 -0
  329. package/src/surfaces/doc-header.json +739 -0
  330. package/src/surfaces/doc-header.ts +223 -0
  331. package/src/surfaces/empty-state.json +152 -0
  332. package/src/surfaces/empty-state.ts +173 -0
  333. package/src/surfaces/entry-editor.json +281 -0
  334. package/src/surfaces/entry-editor.ts +191 -0
  335. package/src/surfaces/expression-editor.json +977 -0
  336. package/src/surfaces/expression-editor.ts +266 -0
  337. package/src/surfaces/files-panel.json +592 -0
  338. package/src/surfaces/files-panel.ts +301 -0
  339. package/src/surfaces/formula-palette.json +364 -0
  340. package/src/surfaces/formula-palette.ts +340 -0
  341. package/src/surfaces/git-panel.json +1822 -0
  342. package/src/surfaces/git-panel.ts +272 -0
  343. package/src/surfaces/github-auth.json +112 -0
  344. package/src/surfaces/github-auth.ts +139 -0
  345. package/src/surfaces/github-publish.json +206 -0
  346. package/src/surfaces/github-publish.ts +169 -0
  347. package/src/surfaces/grid-cell.json +218 -0
  348. package/src/surfaces/grid-cell.ts +120 -0
  349. package/src/surfaces/grid-open.json +147 -0
  350. package/src/surfaces/grid-open.ts +87 -0
  351. package/src/surfaces/grid-panel.json +706 -0
  352. package/src/surfaces/grid-panel.ts +206 -0
  353. package/src/surfaces/grid-replace.json +179 -0
  354. package/src/surfaces/grid-replace.ts +89 -0
  355. package/src/surfaces/grid-views.json +446 -0
  356. package/src/surfaces/grid-views.ts +172 -0
  357. package/src/surfaces/inspector-dock.json +187 -0
  358. package/src/surfaces/inspector-dock.ts +161 -0
  359. package/src/surfaces/jump-bar.json +243 -0
  360. package/src/surfaces/jump-bar.ts +129 -0
  361. package/src/surfaces/jxsuite-update.json +146 -0
  362. package/src/surfaces/jxsuite-update.ts +149 -0
  363. package/src/surfaces/library-pane.json +1509 -0
  364. package/src/surfaces/library-pane.ts +363 -0
  365. package/src/surfaces/logic-panel.json +1949 -0
  366. package/src/surfaces/logic-panel.ts +369 -0
  367. package/src/surfaces/logic-workspace.json +685 -0
  368. package/src/surfaces/logic-workspace.ts +238 -0
  369. package/src/surfaces/media-browser.json +303 -0
  370. package/src/surfaces/media-browser.ts +145 -0
  371. package/src/surfaces/media-field.json +163 -0
  372. package/src/surfaces/media-field.ts +116 -0
  373. package/src/surfaces/media-pane.json +513 -0
  374. package/src/surfaces/media-pane.ts +197 -0
  375. package/src/surfaces/menu.json +232 -0
  376. package/src/surfaces/menu.ts +375 -0
  377. package/src/surfaces/navigator-dock.json +113 -0
  378. package/src/surfaces/navigator-dock.ts +130 -0
  379. package/src/surfaces/new-project.json +1654 -0
  380. package/src/surfaces/new-project.ts +411 -0
  381. package/src/surfaces/palette.json +535 -0
  382. package/src/surfaces/pane-context.json +1497 -0
  383. package/src/surfaces/pane-context.ts +513 -0
  384. package/src/surfaces/pane-grid.json +188 -0
  385. package/src/surfaces/pane-grid.ts +250 -0
  386. package/src/surfaces/panel-activity.json +563 -0
  387. package/src/surfaces/panel-activity.ts +187 -0
  388. package/src/surfaces/panel-data.json +141 -0
  389. package/src/surfaces/panel-data.ts +156 -0
  390. package/src/surfaces/panel-deploy-checklist.json +283 -0
  391. package/src/surfaces/panel-deploy-checklist.ts +175 -0
  392. package/src/surfaces/panel-elements.json +367 -0
  393. package/src/surfaces/panel-elements.ts +175 -0
  394. package/src/surfaces/panel-i18n.json +569 -0
  395. package/src/surfaces/panel-i18n.ts +212 -0
  396. package/src/surfaces/panel-imports.json +780 -0
  397. package/src/surfaces/panel-imports.ts +209 -0
  398. package/src/surfaces/panel-outline.json +734 -0
  399. package/src/surfaces/panel-outline.ts +326 -0
  400. package/src/surfaces/panel-page.json +1017 -0
  401. package/src/surfaces/panel-page.ts +240 -0
  402. package/src/surfaces/panel-problems.json +497 -0
  403. package/src/surfaces/panel-problems.ts +157 -0
  404. package/src/surfaces/panel-signals.json +1973 -0
  405. package/src/surfaces/panel-signals.ts +436 -0
  406. package/src/surfaces/panel-stylebook-layers.json +317 -0
  407. package/src/surfaces/panel-stylebook-layers.ts +155 -0
  408. package/src/surfaces/preferences.json +850 -0
  409. package/src/surfaces/preferences.ts +332 -0
  410. package/src/surfaces/progress-modal.json +110 -0
  411. package/src/surfaces/progress-modal.ts +150 -0
  412. package/src/surfaces/properties-panel.json +1578 -0
  413. package/src/surfaces/properties-panel.ts +337 -0
  414. package/src/surfaces/publish.json +945 -0
  415. package/src/surfaces/publish.ts +392 -0
  416. package/src/surfaces/push-plan.json +110 -0
  417. package/src/surfaces/push-plan.ts +87 -0
  418. package/src/surfaces/rail.json +255 -0
  419. package/src/surfaces/rail.ts +206 -0
  420. package/src/surfaces/reference-field.json +191 -0
  421. package/src/surfaces/reference-field.ts +168 -0
  422. package/src/surfaces/schema-builder.json +1011 -0
  423. package/src/surfaces/schema-builder.ts +181 -0
  424. package/src/surfaces/schema-form.json +1013 -0
  425. package/src/surfaces/schema-form.ts +287 -0
  426. package/src/surfaces/secret-field.json +60 -0
  427. package/src/surfaces/secret-field.ts +146 -0
  428. package/src/surfaces/seo.json +932 -0
  429. package/src/surfaces/seo.ts +334 -0
  430. package/src/surfaces/settings-contexts.json +538 -0
  431. package/src/surfaces/settings-contexts.ts +196 -0
  432. package/src/surfaces/settings-contributed.json +489 -0
  433. package/src/surfaces/settings-contributed.ts +199 -0
  434. package/src/surfaces/settings-css-vars.json +820 -0
  435. package/src/surfaces/settings-css-vars.ts +199 -0
  436. package/src/surfaces/settings-defs.json +1363 -0
  437. package/src/surfaces/settings-defs.ts +232 -0
  438. package/src/surfaces/settings-deploy.json +108 -0
  439. package/src/surfaces/settings-deploy.ts +135 -0
  440. package/src/surfaces/settings-extensions.json +486 -0
  441. package/src/surfaces/settings-extensions.ts +201 -0
  442. package/src/surfaces/settings-head.json +779 -0
  443. package/src/surfaces/settings-head.ts +397 -0
  444. package/src/surfaces/settings-locales.json +420 -0
  445. package/src/surfaces/settings-locales.ts +137 -0
  446. package/src/surfaces/settings-overview.json +423 -0
  447. package/src/surfaces/settings-overview.ts +218 -0
  448. package/src/surfaces/settings-packages.json +549 -0
  449. package/src/surfaces/settings-packages.ts +158 -0
  450. package/src/surfaces/settings-pane.json +147 -0
  451. package/src/surfaces/settings-pane.ts +150 -0
  452. package/src/surfaces/settings-rawjson.json +104 -0
  453. package/src/surfaces/settings-rawjson.ts +107 -0
  454. package/src/surfaces/shell.json +99 -0
  455. package/src/surfaces/shell.ts +30 -0
  456. package/src/surfaces/slash-menu.json +180 -0
  457. package/src/surfaces/slash-menu.ts +272 -0
  458. package/src/surfaces/statements.json +607 -0
  459. package/src/surfaces/statements.ts +238 -0
  460. package/src/surfaces/statusbar.json +94 -0
  461. package/src/{panels → surfaces}/statusbar.ts +164 -90
  462. package/src/surfaces/style-panel.json +1643 -0
  463. package/src/surfaces/style-panel.ts +338 -0
  464. package/src/surfaces/stylebook-chrome.json +122 -0
  465. package/src/surfaces/stylebook-chrome.ts +122 -0
  466. package/src/surfaces/tab-strip.json +448 -0
  467. package/src/surfaces/tab-strip.ts +200 -0
  468. package/src/surfaces/target-line.json +419 -0
  469. package/src/surfaces/target-line.ts +349 -0
  470. package/src/surfaces/toasts.json +189 -0
  471. package/src/surfaces/welcome.json +571 -0
  472. package/src/surfaces/welcome.ts +338 -0
  473. package/src/tabs/project-config.ts +111 -21
  474. package/src/tabs/tab.ts +52 -1
  475. package/src/types.ts +22 -0
  476. package/src/ui/ai-credentials-form.ts +104 -100
  477. package/src/ui/ai-managed-connect.ts +54 -36
  478. package/src/ui/ai-model-picker.ts +75 -47
  479. package/src/ui/cf-account-picker.ts +76 -141
  480. package/src/ui/color-selector.ts +80 -301
  481. package/src/ui/dynamic-slot.ts +43 -283
  482. package/src/ui/expression-editor.ts +797 -576
  483. package/src/ui/field-input.ts +8 -181
  484. package/src/ui/form-controls.ts +838 -366
  485. package/src/ui/formula-chips.ts +80 -85
  486. package/src/ui/kit.ts +33 -0
  487. package/src/ui/layers.ts +296 -593
  488. package/src/ui/media-picker.ts +203 -284
  489. package/src/ui/panel-resize.ts +251 -64
  490. package/src/ui/popover-surface.ts +153 -0
  491. package/src/ui/progress-modal.ts +22 -60
  492. package/src/ui/regions.ts +13 -7
  493. package/src/ui/schema-form.ts +906 -440
  494. package/src/ui/surface.ts +122 -0
  495. package/src/ui/unit-selector.ts +15 -131
  496. package/src/ui/value-source.ts +14 -0
  497. package/src/ui/virtual-window.ts +88 -9
  498. package/src/utils/canvas-media.ts +72 -0
  499. package/src/utils/edit-display.ts +7 -2
  500. package/src/utils/element-selectors.ts +69 -0
  501. package/src/utils/link-target.ts +2 -2
  502. package/src/utils/studio-utils.ts +0 -10
  503. package/src/view.ts +15 -0
  504. package/src/workspace/pane-derive.ts +36 -14
  505. package/src/workspace/workspace.ts +75 -6
  506. package/styles/canvas.css +72 -467
  507. package/styles/forced-colors.css +48 -82
  508. package/styles/forced-colors.json +41 -0
  509. package/styles/inspector.css +2 -1301
  510. package/styles/overlays.css +96 -2823
  511. package/styles/panels.css +6 -1423
  512. package/styles/shell-frame.css +272 -0
  513. package/styles/shell-frame.json +227 -0
  514. package/styles/shell.css +0 -1568
  515. package/styles/tokens.css +65 -136
  516. package/styles/tokens.json +84 -0
  517. package/dist/chunks/Overlay.dev-etqjwyf9.js +0 -23
  518. package/dist/chunks/adapter-jkcyhgtr.js +0 -14
  519. package/dist/chunks/cf-account-picker-en86v7x7.js +0 -163
  520. package/dist/chunks/cf-settings-45fgyx8y.js +0 -21
  521. package/dist/chunks/components-zmx10t62.js +0 -21
  522. package/dist/chunks/content-source-48xg86wj.js +0 -49
  523. package/dist/chunks/convert-to-repeater-43tfn7cs.js +0 -268
  524. package/dist/chunks/editor-cptq5jp1.js +0 -37
  525. package/dist/chunks/entry-commands-e63f1xc0.js +0 -46
  526. package/dist/chunks/extension-sections-txv08xxg.js +0 -311
  527. package/dist/chunks/file-ops-wbh4hhsx.js +0 -48
  528. package/dist/chunks/files-3kfkwhk4.js +0 -93
  529. package/dist/chunks/flow-qbfgrpeh.js +0 -634
  530. package/dist/chunks/focus-trap.esm-9t2drp0g.js +0 -1025
  531. package/dist/chunks/focus-visible-6eg70z18.js +0 -167
  532. package/dist/chunks/format-host-w16q81tf.js +0 -43
  533. package/dist/chunks/fs-events-hrrg328g.js +0 -21
  534. package/dist/chunks/git-panel-532whr1y.js +0 -59
  535. package/dist/chunks/index-zqxmqxmh.js +0 -109
  536. package/dist/chunks/jx-validate-8er6gbxy.js +0 -19
  537. package/dist/chunks/layers-panel-07hm7g5j.js +0 -67
  538. package/dist/chunks/layers-q75tj2mf.js +0 -45
  539. package/dist/chunks/lit-html-j055mkh8.js +0 -22
  540. package/dist/chunks/monaco-buffer-dqw0xxpx.js +0 -25
  541. package/dist/chunks/notify-yjm79n4s.js +0 -30
  542. package/dist/chunks/parse-24d0gay3.js +0 -19
  543. package/dist/chunks/platform-ba1j1gbh.js +0 -16
  544. package/dist/chunks/publish-panel-v9st96ja.js +0 -29
  545. package/dist/chunks/quick-search-zccjp6gv.js +0 -66
  546. package/dist/chunks/references-3qx8fkyp.js +0 -29
  547. package/dist/chunks/right-panel-vm3b6kwy.js +0 -46
  548. package/dist/chunks/section-registry-sx1ykvkw.js +0 -30
  549. package/dist/chunks/sp-overlay.dev-qgvhnxf4.js +0 -18
  550. package/dist/chunks/sp-popover.dev-n2etmqdp.js +0 -9
  551. package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +0 -15
  552. package/dist/chunks/sp-tooltip.dev-4sastdnw.js +0 -15
  553. package/dist/chunks/sp-tray.dev-tkbscm06.js +0 -199
  554. package/dist/chunks/studio-0a5qrh01.js +0 -3
  555. package/dist/chunks/studio-0bq6eh7v.js +0 -12
  556. package/dist/chunks/studio-18j3bmcz.js +0 -14
  557. package/dist/chunks/studio-1ngbejft.js +0 -181
  558. package/dist/chunks/studio-4b5z1x43.js +0 -424
  559. package/dist/chunks/studio-4n2b5gzb.js +0 -357
  560. package/dist/chunks/studio-4rzyrtjz.js +0 -426
  561. package/dist/chunks/studio-5fx3g9jg.js +0 -4325
  562. package/dist/chunks/studio-5myknm0a.js +0 -1903
  563. package/dist/chunks/studio-7afd21nw.js +0 -67
  564. package/dist/chunks/studio-7nte2hj4.js +0 -613
  565. package/dist/chunks/studio-8jpzqk5s.js +0 -128
  566. package/dist/chunks/studio-8q517dfr.js +0 -24
  567. package/dist/chunks/studio-950k0dx6.js +0 -6939
  568. package/dist/chunks/studio-9n4mrmnb.js +0 -184
  569. package/dist/chunks/studio-a0tswy0w.js +0 -58
  570. package/dist/chunks/studio-ahnzd9rg.js +0 -23
  571. package/dist/chunks/studio-bgxt93ga.js +0 -62
  572. package/dist/chunks/studio-cmb03bg5.js +0 -13890
  573. package/dist/chunks/studio-d1ks410h.js +0 -175
  574. package/dist/chunks/studio-ef49pnzx.js +0 -387
  575. package/dist/chunks/studio-eqq1w638.js +0 -113
  576. package/dist/chunks/studio-ew0djmhg.js +0 -341
  577. package/dist/chunks/studio-gfgb8pe6.js +0 -1652
  578. package/dist/chunks/studio-h4rxscfc.js +0 -10
  579. package/dist/chunks/studio-jc994dnr.js +0 -1066
  580. package/dist/chunks/studio-kyh9avvv.js +0 -28
  581. package/dist/chunks/studio-m3hwn7y1.js +0 -120
  582. package/dist/chunks/studio-mtfnaqs9.js +0 -659
  583. package/dist/chunks/studio-n6j9t10q.js +0 -318
  584. package/dist/chunks/studio-n8b9wqwy.js +0 -291
  585. package/dist/chunks/studio-qcy05dp8.js +0 -1743
  586. package/dist/chunks/studio-qq6jym04.js +0 -53
  587. package/dist/chunks/studio-rdx8jcxy.js +0 -83
  588. package/dist/chunks/studio-rn9ta2mz.js +0 -111
  589. package/dist/chunks/studio-t7qb9db8.js +0 -665
  590. package/dist/chunks/studio-tmc5emrw.js +0 -8
  591. package/dist/chunks/studio-z6nw2ek2.js +0 -18
  592. package/dist/chunks/studio-zczps70q.js +0 -378
  593. package/dist/chunks/studio-zqazsct2.js +0 -1823
  594. package/dist/chunks/workspace-fjx52ryv.js +0 -89
  595. package/src/panels/activity-bar.ts +0 -251
  596. package/src/panels/panel-scheduler.ts +0 -219
  597. package/src/panels/style-inputs.ts +0 -237
  598. package/src/panels/target-line.ts +0 -274
  599. package/src/panels/welcome-screen.ts +0 -346
  600. package/src/ui/button-group.ts +0 -102
  601. package/src/ui/choice-field.ts +0 -72
  602. package/src/ui/field-row.ts +0 -249
  603. package/src/ui/formula-palette.ts +0 -213
  604. package/src/ui/icons.ts +0 -52
  605. package/src/ui/jx-theme.ts +0 -130
  606. package/src/ui/spectrum.ts +0 -340
  607. package/src/ui/value-selector.ts +0 -174
  608. package/src/ui/widgets.ts +0 -172
  609. /package/dist/chunks/{javascript-s56a8dpk.css → javascript-4abzjcy9.css} +0 -0
  610. /package/dist/chunks/{jsonMode-zr9sw0c7.css → jsonMode-vvmpmk34.css} +0 -0
  611. /package/dist/chunks/{monaco-setup-pav68602.css → monaco-setup-8vbyqnhr.css} +0 -0
  612. /package/dist/chunks/{tsMode-ef187txx.css → tsMode-hnxxb73h.css} +0 -0
@@ -1,616 +1,653 @@
1
1
  /// <reference lib="dom" />
2
2
  /**
3
- * Definitions — the visual editor for project-level `$defs` (JSON Schema type definitions).
3
+ * Data Shapes — the editor for project-level `$defs` (JSON Schema type definitions).
4
4
  *
5
5
  * Manages entries in project.json `$defs` — reusable type schemas for external datasets, API
6
6
  * responses, CMS payloads, etc. Same concept as component-level `$defs` but scoped to the entire
7
- * project. The on-disk key stays `$defs` and the section key stays `definitions`.
7
+ * project. The on-disk key stays `$defs` and the section key stays `definitions`; the surface is
8
+ * named for neither, because a surface is named for what it draws.
8
9
  *
9
- * **The reference field type is complete here for the first time.** `schema-field-ui.ts` has always
10
- * been able to draw the target picker, and `ui/form-controls.ts` (the content-types builder) has
11
- * always passed it the available content types — this editor never did, so choosing "reference"
12
- * emitted a bare `#/content/` that pointed at nothing and offered no way to say what it pointed at.
13
- * The list is read straight off the live config's `content` map, which is where a
14
- * `#/content/<type>` pointer resolves.
10
+ * **The reference field type is complete here.** `schema-field-ui.ts` has always been able to draw
11
+ * the target picker, and `ui/form-controls.ts` (the content-types builder) has always passed it the
12
+ * available content types — this editor never did, so choosing "reference" emitted a bare
13
+ * `#/content/` that pointed at nothing and offered no way to say what it pointed at. The list is
14
+ * read straight off the live config's `content` map, which is where a `#/content/<type>` pointer
15
+ * resolves.
16
+ *
17
+ * **The markup left.** The section is the `settings-defs` surface (`surfaces/settings-defs.json`),
18
+ * mounted by `surfaces/settings-defs.ts`; what is here is the section itself — what a shape is,
19
+ * what a field may be renamed to, and what reaches `project.json`. `renderDefsEditor` is unchanged
20
+ * as a contract: the registry hands a container to a `render`, and this one mounts a document into
21
+ * it instead of rendering lit.
22
+ *
23
+ * Three things the conversion settled, and each was a defect rather than a translation:
24
+ *
25
+ * - **The field cards are this section's own now.** They were `schema-field-ui.ts`'s lit templates,
26
+ * shared with the content-types builder, and that sharing is what made every card a Spectrum one.
27
+ * That module is untouched and still draws the builder's cards; this section draws its own out of
28
+ * the kit, so the two surfaces can move one at a time. Nothing was deleted from it.
29
+ * - **The nested add row no longer reads itself.** It found its own name field and its own picker
30
+ * with `closest()` and `querySelector` at click time — a node found by selector is real only
31
+ * until the next render — so what the reader had typed lived in the DOM and nowhere else. Each
32
+ * object field now carries its draft in this section's own state ({@link DefsUi.drafts}), which
33
+ * is what lets a redraw arrive mid-typing without taking the half-typed name away.
34
+ * - **A refused rename snaps back because the scope moves, not because a handler writes to an
35
+ * input.** The lit version assigned `target.value = fieldName` from inside the change handler. A
36
+ * document's binding only writes when the scope value CHANGES, and after a refusal the scope
37
+ * still holds what is on disk — so every setter says what the control now holds first
38
+ * ({@link say}), and the snap-back is then a real move.
39
+ *
40
+ * @docs studio/projects/settings
15
41
  */
16
42
 
17
- import { html, render as litRender } from "lit-html";
18
43
  import { projectState } from "../store";
19
44
  import { commitProjectConfig } from "../tabs/project-config";
20
- import { addFieldFormTpl, detectFieldFormat, fieldCardTpl, schemaForType } from "./schema-field-ui";
21
-
22
- import type { FieldHandlers, SchemaProperty } from "./schema-field-ui.js";
45
+ import { camelToLabel } from "../utils/studio-utils";
46
+ import {
47
+ FIELD_TYPES,
48
+ FORMAT_OPTIONS,
49
+ detectFieldFormat,
50
+ detectFieldType,
51
+ schemaForType,
52
+ } from "./schema-field-ui";
53
+ import { mountDefsSurface } from "../surfaces/settings-defs";
54
+
55
+ import type { SchemaProperty } from "./schema-field-ui";
56
+ import type {
57
+ DefsActions,
58
+ DefsChoice,
59
+ DefsFieldView,
60
+ DefsSurfaceHandle,
61
+ DefsView,
62
+ NestedFieldView,
63
+ } from "../surfaces/settings-defs";
23
64
  import type { ContentTypeSchema, ContentTypeSchemaField } from "@jxsuite/schema/types";
24
65
 
25
- // ─── Module state ─────────────────────────────────────────────────────────────
66
+ // ─── The pickers ──────────────────────────────────────────────────────────────
26
67
 
27
- let selectedDef: string | null = null;
28
- let showAddField = false;
29
- let newFieldState = { format: "", name: "", required: false, type: "string" };
30
- let showNewDef = false;
31
- let newDefName = "";
68
+ /** The field types, as picker rows. One array, made once: an equal write re-runs no binding. */
69
+ const TYPE_ROWS: DefsChoice[] = FIELD_TYPES.map((type) => ({ label: type, value: type }));
32
70
 
33
- // ─── Persistence ──────────────────────────────────────────────────────────────
71
+ /** The formats, with the empty one named: a blank row says nothing about what choosing it does. */
72
+ const FORMAT_ROWS: DefsChoice[] = FORMAT_OPTIONS.map((format) => ({
73
+ label: format || "(none)",
74
+ value: format,
75
+ }));
34
76
 
35
- /**
36
- * Commit the configuration this editor has just mutated in place.
37
- *
38
- * Every handler below edits `projectState.projectConfig.$defs` directly and then says so here. The
39
- * predecessor was this module's own writer — `JSON.stringify(config, null, "\t")` straight to
40
- * `platform.writeFile`, called as `void saveProjectConfig()` at fourteen sites. It re-indented the
41
- * whole file (every `project.json` on disk uses two spaces), it recorded nothing an undo could
42
- * reach, and a rejected write became an unhandled rejection while the form went on showing a data
43
- * shape that was never saved.
44
- *
45
- * `commitProjectConfig` transacts the mutation onto the configuration document and reports its own
46
- * failures as Problems, so there is nothing left here to await or to swallow.
47
- */
48
- function persist(): void {
49
- void commitProjectConfig();
50
- }
77
+ /** What a new field starts as. */
78
+ const BLANK_FIELD = { format: "", name: "", required: false, type: "string" };
51
79
 
52
- // ─── Helpers ─────────────────────────────────────────────────────────────────
80
+ // ─── Per-container state ──────────────────────────────────────────────────────
53
81
 
54
- /** Get the selected $def schema object. */
55
- function getSelectedDef(): ContentTypeSchema | undefined {
56
- const config = projectState?.projectConfig;
57
- return config?.$defs?.[selectedDef as string] as ContentTypeSchema | undefined;
82
+ /** One object field's unsubmitted nested field. */
83
+ interface Draft {
84
+ name: string;
85
+ type: string;
58
86
  }
59
87
 
60
88
  /**
61
- * The content types a `reference` field can point at — the same list the content-types builder
62
- * resolves through `#/$context/content`, read here without a schema-form context to go through.
89
+ * What the reader is part-way through, per rendered container.
63
90
  *
64
- * @returns {string[]}
91
+ * Per container rather than per module, for the reason `general-settings.ts` and
92
+ * `contexts-section.ts` keep their errors that way: Project Settings is a pane document and a pane
93
+ * can be split, so two containers may be showing this section at once. Module state would make the
94
+ * second one take the first one's selection away the moment either was redrawn.
65
95
  */
66
- function contentTypeNames(): string[] {
67
- const content = (projectState?.projectConfig as Record<string, unknown> | null | undefined)
68
- ?.content;
69
- return content && typeof content === "object" && !Array.isArray(content)
70
- ? Object.keys(content)
71
- : [];
96
+ interface DefsUi {
97
+ selected: string | null;
98
+ newOpen: boolean;
99
+ newName: string;
100
+ addOpen: boolean;
101
+ add: { format: string; name: string; required: boolean; type: string };
102
+ /** Each object field's nested add row, by the field's name on disk. */
103
+ drafts: Map<string, Draft>;
104
+ /**
105
+ * The ONE control whose value has parted from what is on disk, and what it holds.
106
+ *
107
+ * One at a time is the whole truth of it: a `change` commits one control, and the decision about
108
+ * it is synchronous, so there is never a second control in flight. See {@link say}.
109
+ */
110
+ echo: { id: string; value: string | boolean } | null;
72
111
  }
73
112
 
74
- // ─── Handlers ─────────────────────────────────────────────────────────────────
75
-
76
- /** @param {() => void} rerender */
77
- function handleNewDef(rerender: () => void) {
78
- const name = newDefName.trim();
79
- if (!name) {
80
- return;
81
- }
82
-
83
- const config = projectState?.projectConfig;
84
- if (!config) {
85
- return;
86
- }
87
- if (!config.$defs) {
88
- config.$defs = {};
113
+ const uis = new WeakMap<HTMLElement, DefsUi>();
114
+
115
+ /** The mounted surface, per container — see {@link renderDefsEditor}. */
116
+ const mounts = new WeakMap<HTMLElement, DefsSurfaceHandle>();
117
+
118
+ /** This container's state, made on first use. */
119
+ function uiFor(container: HTMLElement): DefsUi {
120
+ let ui = uis.get(container);
121
+ if (!ui) {
122
+ ui = {
123
+ add: { ...BLANK_FIELD },
124
+ addOpen: false,
125
+ drafts: new Map(),
126
+ echo: null,
127
+ newName: "",
128
+ newOpen: false,
129
+ selected: null,
130
+ };
131
+ uis.set(container, ui);
89
132
  }
90
- if (config.$defs[name]) {
91
- return;
92
- } // Already exists
93
-
94
- config.$defs[name] = {
95
- properties: {},
96
- required: [],
97
- type: "object",
98
- };
99
-
100
- selectedDef = name;
101
- showNewDef = false;
102
- newDefName = "";
103
- rerender();
104
- persist();
133
+ return ui;
105
134
  }
106
135
 
107
- /** @param {() => void} rerender */
108
- function handleAddField(rerender: () => void) {
109
- const name = newFieldState.name.trim();
110
- if (!name || !selectedDef) {
111
- return;
112
- }
113
-
114
- const def = getSelectedDef();
115
- if (!def) {
116
- return;
117
- }
118
-
119
- if (!def.properties) {
120
- def.properties = {};
121
- }
122
- def.properties[name] = schemaForType(newFieldState.type, newFieldState.format || undefined);
123
-
124
- if (newFieldState.required) {
125
- if (!def.required) {
126
- def.required = [];
127
- }
128
- if (!def.required.includes(name)) {
129
- def.required.push(name);
130
- }
131
- }
132
-
133
- showAddField = false;
134
- newFieldState = { format: "", name: "", required: false, type: "string" };
135
- rerender();
136
- persist();
136
+ /** The id of a field's name control. */
137
+ function nameId(key: string): string {
138
+ return `name:${key}`;
137
139
  }
138
140
 
139
- /**
140
- * @param {string} fieldName
141
- * @param {() => void} rerender
142
- */
143
- function handleDeleteField(fieldName: string, rerender: () => void) {
144
- const def = getSelectedDef();
145
- if (!def?.properties) {
146
- return;
147
- }
148
-
149
- delete def.properties[fieldName];
150
- if (def.required) {
151
- def.required = def.required.filter((r: string) => r !== fieldName);
152
- }
153
-
154
- rerender();
155
- persist();
141
+ /** The id of a field's required switch. */
142
+ function requiredId(key: string): string {
143
+ return `required:${key}`;
156
144
  }
157
145
 
158
- /**
159
- * @param {string} fieldName
160
- * @param {() => void} rerender
161
- */
162
- function handleToggleRequired(fieldName: string, rerender: () => void) {
163
- const def = getSelectedDef();
164
- if (!def) {
165
- return;
166
- }
167
- if (!def.required) {
168
- def.required = [];
169
- }
170
-
171
- const idx = def.required.indexOf(fieldName);
172
- if (idx !== -1) {
173
- def.required.splice(idx, 1);
174
- } else {
175
- def.required.push(fieldName);
176
- }
177
-
178
- rerender();
179
- persist();
146
+ /** A child is addressed by both halves, because two objects may hold the same child name. */
147
+ function nestedId(parent: string, key: string): string {
148
+ return `${parent} ${key}`;
180
149
  }
181
150
 
182
151
  /**
183
- * @param {string} oldName
184
- * @param {string} newName
185
- * @param {() => void} rerender
152
+ * Say what a control now holds, before anything is decided about it.
153
+ *
154
+ * This is what `live()` did for the lit templates. A document's binding writes only when the SCOPE
155
+ * value changes, and after a refusal the scope still holds the value on disk — so without this
156
+ * nothing is written back and the refused text stays in the field. Echoing first makes the
157
+ * projection that follows a real change.
186
158
  */
187
- function handleRenameField(oldName: string, newName: string, rerender: () => void) {
188
- const def = getSelectedDef();
189
- if (!def?.properties || !newName || def.properties[newName]) {
190
- return;
191
- }
192
-
193
- const newProps: Record<string, ContentTypeSchemaField> = {};
194
- for (const [key, val] of Object.entries(def.properties)) {
195
- newProps[key === oldName ? newName : key] = val;
196
- }
197
- def.properties = newProps;
198
-
199
- if (def.required) {
200
- def.required = def.required.map((r: string) => (r === oldName ? newName : r));
201
- }
202
-
203
- rerender();
204
- persist();
159
+ function say(container: HTMLElement, id: string, value: string | boolean): void {
160
+ uiFor(container).echo = { id, value };
161
+ renderDefsEditor(container);
205
162
  }
206
163
 
207
- /**
208
- * @param {string} fieldName
209
- * @param {string} newType
210
- * @param {() => void} rerender
211
- */
212
- function handleChangeType(fieldName: string, newType: string, rerender: () => void) {
213
- const def = getSelectedDef();
214
- if (!def?.properties?.[fieldName]) {
215
- return;
216
- }
217
-
218
- const oldFormat =
219
- newType === "string" || newType === "array"
220
- ? detectFieldFormat(def.properties[fieldName])
221
- : undefined;
222
- def.properties[fieldName] = schemaForType(newType, oldFormat || undefined);
223
- rerender();
224
- persist();
164
+ /** Forget the echo: every control shows what the file says again. */
165
+ function hush(container: HTMLElement): void {
166
+ uiFor(container).echo = null;
225
167
  }
226
168
 
227
- /**
228
- * Point a reference field at a content type. The pointer form is `#/content/<type>` — the same one
229
- * `ui/form-controls.ts` writes, so a reference authored here and one authored in the content-types
230
- * builder are the same value.
231
- *
232
- * @param {string} fieldName
233
- * @param {string} target
234
- * @param {() => void} rerender
235
- */
236
- function handleChangeRefTarget(fieldName: string, target: string, rerender: () => void) {
237
- const def = getSelectedDef();
238
- if (!def?.properties?.[fieldName]) {
239
- return;
240
- }
241
- def.properties[fieldName] = { $ref: `#/content/${target}` } as ContentTypeSchemaField;
242
- rerender();
243
- persist();
169
+ /** What a control shows: what it was last told it holds, or what is on disk. */
170
+ function shown<T extends string | boolean>(ui: DefsUi, id: string, disk: T): T {
171
+ return ui.echo?.id === id ? (ui.echo.value as T) : disk;
244
172
  }
245
173
 
174
+ // ─── Persistence ──────────────────────────────────────────────────────────────
175
+
246
176
  /**
247
- * @param {string} fieldName
248
- * @param {string} format
249
- * @param {() => void} rerender
177
+ * Commit the configuration this editor has just mutated in place.
178
+ *
179
+ * Every handler below edits `projectState.projectConfig.$defs` directly and then says so here. The
180
+ * predecessor was this module's own writer — `JSON.stringify(config, null, "\t")` straight to
181
+ * `platform.writeFile`, called as `void saveProjectConfig()` at fourteen sites. It re-indented the
182
+ * whole file (every `project.json` on disk uses two spaces), it recorded nothing an undo could
183
+ * reach, and a rejected write became an unhandled rejection while the form went on showing a data
184
+ * shape that was never saved.
185
+ *
186
+ * `commitProjectConfig` transacts the mutation onto the configuration document and reports its own
187
+ * failures as Problems, so there is nothing left here to await or to swallow.
250
188
  */
251
- function handleChangeFormat(fieldName: string, format: string, rerender: () => void) {
252
- const def = getSelectedDef();
253
- if (!def?.properties?.[fieldName]) {
254
- return;
255
- }
256
-
257
- const prop = def.properties[fieldName];
258
- const type = prop.type || "string";
259
- def.properties[fieldName] = schemaForType(type, format || undefined);
260
- rerender();
261
- persist();
189
+ function persist(): void {
190
+ void commitProjectConfig();
262
191
  }
263
192
 
264
- // ─── Nested field handlers ───────────────────────────────────────────────────
193
+ // ─── The project, as it stands now ────────────────────────────────────────────
265
194
 
266
195
  /**
267
- * @param {string} parentName
268
- * @param {{ name: string; type: string; required: boolean }} fieldState
269
- * @param {() => void} rerender
196
+ * The live `$defs`, read at the moment it is needed.
197
+ *
198
+ * The lit version closed over the config at render time and rebuilt every handler on every render,
199
+ * so a stale closure was impossible only because there were no long-lived ones. The handlers now
200
+ * outlive a projection, which makes reading through to the store the thing that keeps them honest.
270
201
  */
271
- function handleAddNestedField(
272
- parentName: string,
273
- fieldState: { name: string; type: string; required: boolean },
274
- rerender: () => void,
275
- ) {
276
- const def = getSelectedDef();
277
- const parent = def?.properties?.[parentName];
278
- if (!parent) {
279
- return;
280
- }
281
-
282
- if (!parent.properties) {
283
- parent.properties = {};
284
- }
285
- parent.properties[fieldState.name] = schemaForType(fieldState.type);
286
-
287
- if (fieldState.required) {
288
- if (!parent.required) {
289
- parent.required = [];
290
- }
291
- if (!parent.required.includes(fieldState.name)) {
292
- parent.required.push(fieldState.name);
293
- }
294
- }
202
+ function currentDefs(): Record<string, ContentTypeSchema> | undefined {
203
+ return projectState?.projectConfig?.$defs as Record<string, ContentTypeSchema> | undefined;
204
+ }
295
205
 
296
- rerender();
297
- persist();
206
+ /** The selected shape's schema, or `undefined` when nothing is selected or it has gone. */
207
+ function selectedDef(ui: DefsUi): ContentTypeSchema | undefined {
208
+ return ui.selected === null ? undefined : currentDefs()?.[ui.selected];
298
209
  }
299
210
 
300
211
  /**
301
- * @param {string} parentName
302
- * @param {string} childName
303
- * @param {() => void} rerender
212
+ * The content types a `reference` field can point at — the same list the content-types builder
213
+ * resolves through `#/$context/content`, read here without a schema-form context to go through.
304
214
  */
305
- function handleDeleteNested(parentName: string, childName: string, rerender: () => void) {
306
- const def = getSelectedDef();
307
- const parent = def?.properties?.[parentName];
308
- if (!parent?.properties) {
309
- return;
310
- }
311
-
312
- delete parent.properties[childName];
313
- if (parent.required) {
314
- parent.required = parent.required.filter((r: string) => r !== childName);
315
- }
316
-
317
- rerender();
318
- persist();
215
+ function contentTypeNames(): string[] {
216
+ const content = (projectState?.projectConfig as Record<string, unknown> | null | undefined)
217
+ ?.content;
218
+ return content && typeof content === "object" && !Array.isArray(content)
219
+ ? Object.keys(content)
220
+ : [];
319
221
  }
320
222
 
321
- /**
322
- * @param {string} parentName
323
- * @param {string} childName
324
- * @param {() => void} rerender
325
- */
326
- function handleToggleNestedRequired(parentName: string, childName: string, rerender: () => void) {
327
- const def = getSelectedDef();
328
- const parent = def?.properties?.[parentName];
329
- if (!parent) {
330
- return;
331
- }
332
- if (!parent.required) {
333
- parent.required = [];
334
- }
223
+ /** The last target list, so an unchanged one is the same array and re-runs no binding. */
224
+ let targetRows: DefsChoice[] = [];
225
+ let targetNames = " ";
335
226
 
336
- const idx = parent.required.indexOf(childName);
337
- if (idx !== -1) {
338
- parent.required.splice(idx, 1);
339
- } else {
340
- parent.required.push(childName);
227
+ /** The reference targets, as picker rows. */
228
+ function targets(): DefsChoice[] {
229
+ const names = contentTypeNames();
230
+ const id = names.join(" ");
231
+ if (id !== targetNames) {
232
+ targetNames = id;
233
+ targetRows = names.map((name) => ({ label: name, value: name }));
341
234
  }
342
-
343
- rerender();
344
- persist();
235
+ return targetRows;
345
236
  }
346
237
 
347
- /**
348
- * @param {string} parentName
349
- * @param {string} oldChild
350
- * @param {string} newChild
351
- * @param {() => void} rerender
352
- */
353
- function handleRenameNested(
354
- parentName: string,
355
- oldChild: string,
356
- newChild: string,
357
- rerender: () => void,
358
- ) {
359
- const def = getSelectedDef();
360
- const parent = def?.properties?.[parentName];
361
- if (!parent?.properties || !newChild || parent.properties[newChild]) {
362
- return;
363
- }
238
+ // ─── Projection ───────────────────────────────────────────────────────────────
364
239
 
365
- const newProps: Record<string, ContentTypeSchemaField> = {};
366
- for (const [key, val] of Object.entries(parent.properties)) {
367
- newProps[key === oldChild ? newChild : key] = val;
368
- }
369
- parent.properties = newProps;
370
-
371
- if (parent.required) {
372
- parent.required = parent.required.map((r: string) => (r === oldChild ? newChild : r));
373
- }
240
+ /** Whether a type carries a format at all — only `string` and `array` do. */
241
+ function takesFormat(type: string): boolean {
242
+ return type === "string" || type === "array";
243
+ }
374
244
 
375
- rerender();
376
- persist();
245
+ /** One child of an object field, as the document draws it. */
246
+ function nestedView(
247
+ ui: DefsUi,
248
+ parent: string,
249
+ key: string,
250
+ schema: SchemaProperty,
251
+ required: boolean,
252
+ ): NestedFieldView {
253
+ const id = nestedId(parent, key);
254
+ const type = detectFieldType(schema);
255
+ return {
256
+ format: detectFieldFormat(schema),
257
+ hasFormat: takesFormat(type),
258
+ key,
259
+ name: shown(ui, nameId(id), key),
260
+ parent,
261
+ required: shown(ui, requiredId(id), required),
262
+ type,
263
+ };
377
264
  }
378
265
 
379
- /**
380
- * @param {string} parentName
381
- * @param {string} childName
382
- * @param {string} newType
383
- * @param {() => void} rerender
384
- */
385
- function handleChangeNestedType(
386
- parentName: string,
387
- childName: string,
388
- newType: string,
389
- rerender: () => void,
390
- ) {
391
- const def = getSelectedDef();
392
- const parent = def?.properties?.[parentName];
393
- if (!parent?.properties?.[childName]) {
394
- return;
395
- }
266
+ /** One field of the selected shape, as the document draws it. */
267
+ function fieldView(
268
+ ui: DefsUi,
269
+ key: string,
270
+ schema: SchemaProperty,
271
+ required: boolean,
272
+ hasTargets: boolean,
273
+ ): DefsFieldView {
274
+ const type = detectFieldType(schema);
275
+ const nested = type === "object";
276
+ const draft = ui.drafts.get(key);
277
+ const childRequired = schema.required ?? [];
278
+ return {
279
+ children: nested
280
+ ? Object.entries(schema.properties ?? {}).map(([child, sub]) =>
281
+ nestedView(ui, key, child, sub, childRequired.includes(child)),
282
+ )
283
+ : [],
284
+ draftName: draft?.name ?? "",
285
+ draftType: draft?.type ?? "string",
286
+ /* A reference with nothing to point at draws no picker: a control whose list is empty offers
287
+ the reader no answer, which is why the lit card gated on the same thing. */
288
+ extra: type === "reference" && hasTargets ? "reference" : nested ? "nested" : "none",
289
+ format: detectFieldFormat(schema),
290
+ hasFormat: takesFormat(type),
291
+ key,
292
+ label: camelToLabel(key),
293
+ name: shown(ui, nameId(key), key),
294
+ refTarget: schema.$ref ? schema.$ref.replace(/^#\/[^/]+\//, "") : "",
295
+ required: shown(ui, requiredId(key), required),
296
+ type,
297
+ };
298
+ }
396
299
 
397
- const oldFormat =
398
- newType === "string" || newType === "array"
399
- ? detectFieldFormat(parent.properties[childName])
400
- : undefined;
401
- parent.properties[childName] = schemaForType(newType, oldFormat || undefined);
402
- rerender();
403
- persist();
300
+ /** The whole section, as one value the document can be handed. */
301
+ function project(container: HTMLElement): DefsView {
302
+ const ui = uiFor(container);
303
+ const defs = currentDefs() ?? {};
304
+ const def = selectedDef(ui);
305
+ const rows = targets();
306
+ const properties = def?.properties ?? {};
307
+ const required = def?.required ?? [];
308
+ return {
309
+ addFormat: ui.add.format,
310
+ addHasFormat: takesFormat(ui.add.type),
311
+ addName: ui.add.name,
312
+ addRequired: ui.add.required,
313
+ addState: ui.addOpen && def ? "open" : "closed",
314
+ addType: ui.add.type,
315
+ editorState: def ? "editing" : "empty",
316
+ fields: Object.entries(properties).map(([key, schema]) =>
317
+ fieldView(ui, key, schema as SchemaProperty, required.includes(key), rows.length > 0),
318
+ ),
319
+ formatOptions: FORMAT_ROWS,
320
+ newName: ui.newName,
321
+ newState: ui.newOpen ? "open" : "closed",
322
+ selected: def && ui.selected !== null ? ui.selected : "",
323
+ shapes: Object.keys(defs).map((name) => ({ name, selected: name === ui.selected })),
324
+ targets: rows,
325
+ typeOptions: TYPE_ROWS,
326
+ };
404
327
  }
405
328
 
329
+ // ─── The decisions ────────────────────────────────────────────────────────────
330
+
406
331
  /**
407
- * @param {string} parentName
408
- * @param {string} childName
409
- * @param {string} format
410
- * @param {() => void} rerender
332
+ * What every control in one container does. Built once, when that container's surface is mounted,
333
+ * and read through to the store on every call.
411
334
  */
412
- function handleChangeNestedFormat(
413
- parentName: string,
414
- childName: string,
415
- format: string,
416
- rerender: () => void,
417
- ) {
418
- const def = getSelectedDef();
419
- const parent = def?.properties?.[parentName];
420
- if (!parent?.properties?.[childName]) {
421
- return;
422
- }
335
+ function actionsFor(container: HTMLElement): DefsActions {
336
+ const ui = uiFor(container);
337
+ const redraw = (): void => renderDefsEditor(container);
338
+
339
+ /** The parent object a nested edit is about, or `undefined` when it has gone underneath. */
340
+ const parentOf = (parent: string): SchemaProperty | undefined =>
341
+ selectedDef(ui)?.properties?.[parent] as SchemaProperty | undefined;
342
+
343
+ /** One object field's add-row draft, made on first use. */
344
+ const draftOf = (parent: string): Draft => {
345
+ let draft = ui.drafts.get(parent);
346
+ if (!draft) {
347
+ draft = { name: "", type: "string" };
348
+ ui.drafts.set(parent, draft);
349
+ }
350
+ return draft;
351
+ };
423
352
 
424
- const prop = parent.properties[childName];
425
- const type = prop.type || "string";
426
- parent.properties[childName] = schemaForType(type, format || undefined);
427
- rerender();
428
- persist();
429
- }
353
+ /** A property map with one key renamed, rebuilt in place so a rename never reorders the list. */
354
+ const renameKey = <T>(properties: Record<string, T>, from: string, to: string): void => {
355
+ const next: Record<string, T> = {};
356
+ for (const [key, value] of Object.entries(properties)) {
357
+ next[key === from ? to : key] = value;
358
+ delete properties[key];
359
+ }
360
+ Object.assign(properties, next);
361
+ };
430
362
 
431
- /** @param {() => void} rerender */
432
- function handleDeleteDef(rerender: () => void) {
433
- if (!selectedDef) {
434
- return;
435
- }
436
- const config = projectState?.projectConfig;
437
- if (!config?.$defs?.[selectedDef]) {
438
- return;
439
- }
363
+ /** Add or remove a name from a `required` list, made on first use. */
364
+ const markRequired = (
365
+ holder: { required?: string[] | undefined },
366
+ key: string,
367
+ required: boolean,
368
+ ): void => {
369
+ if (!holder.required) {
370
+ holder.required = [];
371
+ }
372
+ const at = holder.required.indexOf(key);
373
+ if (required && at === -1) {
374
+ holder.required.push(key);
375
+ }
376
+ if (!required && at !== -1) {
377
+ holder.required.splice(at, 1);
378
+ }
379
+ };
380
+
381
+ /** A refusal: the control goes back to what the file says, and nothing is written. */
382
+ const refuse = (): void => {
383
+ hush(container);
384
+ redraw();
385
+ };
440
386
 
441
- delete config.$defs[selectedDef];
442
- selectedDef = null;
387
+ /** A write went through: forget the echo, redraw from the file, and commit it. */
388
+ const done = (): void => {
389
+ hush(container);
390
+ redraw();
391
+ persist();
392
+ };
443
393
 
444
- rerender();
445
- persist();
394
+ return {
395
+ addNested(parent) {
396
+ const draft = draftOf(parent);
397
+ const name = draft.name.trim();
398
+ const holder = parentOf(parent);
399
+ if (!name || !holder) {
400
+ return;
401
+ }
402
+ if (!holder.properties) {
403
+ holder.properties = {};
404
+ }
405
+ holder.properties[name] = schemaForType(draft.type || "string");
406
+ /* The name clears and the type does not: adding three strings in a row is the ordinary case,
407
+ and re-choosing the type each time would be the editor asking what it just heard. */
408
+ draft.name = "";
409
+ done();
410
+ },
411
+ cancelAdd() {
412
+ ui.addOpen = false;
413
+ ui.add = { ...BLANK_FIELD };
414
+ redraw();
415
+ },
416
+ cancelNew() {
417
+ ui.newOpen = false;
418
+ ui.newName = "";
419
+ redraw();
420
+ },
421
+ confirmAdd() {
422
+ const name = ui.add.name.trim();
423
+ const def = selectedDef(ui);
424
+ if (!name || !def) {
425
+ return;
426
+ }
427
+ if (!def.properties) {
428
+ def.properties = {};
429
+ }
430
+ def.properties[name] = schemaForType(ui.add.type, ui.add.format || undefined);
431
+ if (ui.add.required) {
432
+ markRequired(def, name, true);
433
+ }
434
+ ui.addOpen = false;
435
+ ui.add = { ...BLANK_FIELD };
436
+ done();
437
+ },
438
+ createNew() {
439
+ const name = ui.newName.trim();
440
+ const config = projectState?.projectConfig;
441
+ if (!name || !config) {
442
+ return;
443
+ }
444
+ if (!config.$defs) {
445
+ config.$defs = {};
446
+ }
447
+ if (config.$defs[name]) {
448
+ return;
449
+ }
450
+ config.$defs[name] = { properties: {}, required: [], type: "object" };
451
+ ui.selected = name;
452
+ ui.newOpen = false;
453
+ ui.newName = "";
454
+ done();
455
+ },
456
+ editAddFormat(value) {
457
+ ui.add.format = value;
458
+ redraw();
459
+ },
460
+ editAddName(value) {
461
+ ui.add.name = value;
462
+ redraw();
463
+ },
464
+ editAddRequired(required) {
465
+ ui.add.required = required;
466
+ redraw();
467
+ },
468
+ editAddType(value) {
469
+ ui.add.type = value;
470
+ redraw();
471
+ },
472
+ editDraftName(parent, value) {
473
+ draftOf(parent).name = value;
474
+ redraw();
475
+ },
476
+ editDraftType(parent, value) {
477
+ draftOf(parent).type = value;
478
+ redraw();
479
+ },
480
+ editNew(value) {
481
+ ui.newName = value;
482
+ redraw();
483
+ },
484
+ openAdd() {
485
+ ui.addOpen = true;
486
+ ui.add = { ...BLANK_FIELD };
487
+ redraw();
488
+ },
489
+ openNew() {
490
+ ui.newOpen = true;
491
+ ui.newName = "";
492
+ redraw();
493
+ },
494
+ removeField(key) {
495
+ const def = selectedDef(ui);
496
+ if (!def?.properties) {
497
+ return;
498
+ }
499
+ delete def.properties[key];
500
+ if (def.required) {
501
+ def.required = def.required.filter((name) => name !== key);
502
+ }
503
+ done();
504
+ },
505
+ removeNested(parent, key) {
506
+ const holder = parentOf(parent);
507
+ if (!holder?.properties) {
508
+ return;
509
+ }
510
+ delete holder.properties[key];
511
+ if (holder.required) {
512
+ holder.required = holder.required.filter((name) => name !== key);
513
+ }
514
+ done();
515
+ },
516
+ removeShape() {
517
+ const defs = currentDefs();
518
+ if (ui.selected === null || !defs?.[ui.selected]) {
519
+ return;
520
+ }
521
+ delete defs[ui.selected];
522
+ ui.selected = null;
523
+ ui.addOpen = false;
524
+ done();
525
+ },
526
+ renameField(key, value) {
527
+ say(container, nameId(key), value);
528
+ const def = selectedDef(ui);
529
+ const name = value.trim();
530
+ if (!def?.properties || !name || name === key || def.properties[name]) {
531
+ refuse();
532
+ return;
533
+ }
534
+ renameKey(def.properties, key, name);
535
+ if (def.required) {
536
+ def.required = def.required.map((entry) => (entry === key ? name : entry));
537
+ }
538
+ done();
539
+ },
540
+ renameNested(parent, key, value) {
541
+ say(container, nameId(nestedId(parent, key)), value);
542
+ const holder = parentOf(parent);
543
+ const name = value.trim();
544
+ if (!holder?.properties || !name || name === key || holder.properties[name]) {
545
+ refuse();
546
+ return;
547
+ }
548
+ renameKey(holder.properties, key, name);
549
+ if (holder.required) {
550
+ holder.required = holder.required.map((entry) => (entry === key ? name : entry));
551
+ }
552
+ done();
553
+ },
554
+ select(name) {
555
+ ui.selected = name;
556
+ ui.addOpen = false;
557
+ redraw();
558
+ },
559
+ setFormat(key, value) {
560
+ const def = selectedDef(ui);
561
+ const property = def?.properties?.[key];
562
+ if (!def?.properties || !property) {
563
+ return;
564
+ }
565
+ def.properties[key] = schemaForType(property.type || "string", value || undefined);
566
+ done();
567
+ },
568
+ setNestedFormat(parent, key, value) {
569
+ const holder = parentOf(parent);
570
+ const property = holder?.properties?.[key];
571
+ if (!holder?.properties || !property) {
572
+ return;
573
+ }
574
+ holder.properties[key] = schemaForType(property.type || "string", value || undefined);
575
+ done();
576
+ },
577
+ setNestedRequired(parent, key, required) {
578
+ say(container, requiredId(nestedId(parent, key)), required);
579
+ const holder = parentOf(parent);
580
+ if (!holder) {
581
+ refuse();
582
+ return;
583
+ }
584
+ markRequired(holder, key, required);
585
+ done();
586
+ },
587
+ setNestedType(parent, key, value) {
588
+ const holder = parentOf(parent);
589
+ const property = holder?.properties?.[key];
590
+ if (!holder?.properties || !property) {
591
+ return;
592
+ }
593
+ /* A format survives a move between the two types that carry one, and is dropped by any type
594
+ that does not: `schemaForType` is handed the old format only when the new type takes it. */
595
+ const format = takesFormat(value) ? detectFieldFormat(property) : undefined;
596
+ holder.properties[key] = schemaForType(value, format || undefined);
597
+ done();
598
+ },
599
+ setRequired(key, required) {
600
+ say(container, requiredId(key), required);
601
+ const def = selectedDef(ui);
602
+ if (!def) {
603
+ refuse();
604
+ return;
605
+ }
606
+ markRequired(def, key, required);
607
+ done();
608
+ },
609
+ setTarget(key, value) {
610
+ const def = selectedDef(ui);
611
+ if (!def?.properties?.[key]) {
612
+ return;
613
+ }
614
+ /* The pointer form is `#/content/<type>` — the same one `ui/form-controls.ts` writes, so a
615
+ reference authored here and one authored in the content-types builder are the same value. */
616
+ def.properties[key] = { $ref: `#/content/${value}` } as ContentTypeSchemaField;
617
+ done();
618
+ },
619
+ setType(key, value) {
620
+ const def = selectedDef(ui);
621
+ const property = def?.properties?.[key];
622
+ if (!def?.properties || !property) {
623
+ return;
624
+ }
625
+ const format = takesFormat(value) ? detectFieldFormat(property) : undefined;
626
+ def.properties[key] = schemaForType(value, format || undefined);
627
+ done();
628
+ },
629
+ };
446
630
  }
447
631
 
448
632
  // ─── Render ───────────────────────────────────────────────────────────────────
449
633
 
450
634
  /**
451
- * Render the Data Shapes editor.
635
+ * Render Project Settings › Data Shapes into `container`.
636
+ *
637
+ * One mount per container, re-used for every subsequent call. The host calls this again for any
638
+ * change it notices, and a section whose container has meanwhile been given to a DIFFERENT section
639
+ * finds its document gone — `litRender` into the same node replaces everything in it — so the mount
640
+ * is rebuilt exactly when it has actually been evicted, and disposed on the way out rather than
641
+ * left running over detached nodes.
452
642
  *
453
643
  * @param {HTMLElement} container
454
644
  */
455
- export function renderDefsEditor(container: HTMLElement) {
456
- const rerender = () => renderDefsEditor(container);
457
- const config = projectState?.projectConfig;
458
- const defs = config?.$defs || {};
459
- const defNames = Object.keys(defs);
460
-
461
- // Left column — def list
462
- const listTpl = html`
463
- <div class="settings-list-panel">
464
- ${defNames.map(
465
- (name) => html`
466
- <sp-action-button
467
- size="s"
468
- ?selected=${selectedDef === name}
469
- @click=${() => {
470
- selectedDef = name;
471
- showAddField = false;
472
- rerender();
473
- }}
474
- >
475
- ${name}
476
- </sp-action-button>
477
- `,
478
- )}
479
- ${
480
- showNewDef
481
- ? html`
482
- <div class="settings-inline-form">
483
- <sp-textfield
484
- size="s"
485
- placeholder="ProductReview"
486
- .value=${newDefName}
487
- @input=${(e: Event) => {
488
- newDefName = (e.target as HTMLInputElement).value;
489
- }}
490
- @keydown=${(e: KeyboardEvent) => {
491
- if (e.key === "Enter") {
492
- handleNewDef(rerender);
493
- }
494
- if (e.key === "Escape") {
495
- showNewDef = false;
496
- rerender();
497
- }
498
- }}
499
- ></sp-textfield>
500
- <sp-action-button size="s" @click=${() => handleNewDef(rerender)}>
501
- Create
502
- </sp-action-button>
503
- </div>
504
- `
505
- : html`
506
- <sp-action-button
507
- size="s"
508
- quiet
509
- @click=${() => {
510
- showNewDef = true;
511
- rerender();
512
- }}
513
- >
514
- <sp-icon-add slot="icon"></sp-icon-add> New Data Shape
515
- </sp-action-button>
516
- `
517
- }
518
- </div>
519
- `;
520
-
521
- // Right column — schema editor
522
- let editorTpl;
523
- if (!selectedDef || !defs[selectedDef]) {
524
- editorTpl = html`<div class="settings-empty-state">
525
- Pick a data shape on the left, or create one.
526
- </div>`;
527
- } else {
528
- const def = defs[selectedDef] as ContentTypeSchema;
529
- const properties = def.properties || {};
530
- const required = def.required || [];
531
-
532
- const handlers: FieldHandlers = {
533
- onAddNestedField: (p: string, s: { name: string; type: string; required: boolean }) =>
534
- handleAddNestedField(p, s, rerender),
535
- onChangeFormat: (n: string, f: string) => handleChangeFormat(n, f, rerender),
536
- onChangeNestedFormat: (p: string, c: string, f: string) =>
537
- handleChangeNestedFormat(p, c, f, rerender),
538
- onChangeNestedType: (p: string, c: string, t: string) =>
539
- handleChangeNestedType(p, c, t, rerender),
540
- onChangeRefTarget: (n: string, t: string) => handleChangeRefTarget(n, t, rerender),
541
- onChangeType: (n: string, t: string) => handleChangeType(n, t, rerender),
542
- onDelete: (n: string) => handleDeleteField(n, rerender),
543
- onDeleteNested: (p: string, c: string) => handleDeleteNested(p, c, rerender),
544
- onRename: (oldN: string, newN: string) => handleRenameField(oldN, newN, rerender),
545
- onRenameNested: (p: string, o: string, n: string) => handleRenameNested(p, o, n, rerender),
546
- onToggleNestedRequired: (p: string, c: string) => handleToggleNestedRequired(p, c, rerender),
547
- onToggleRequired: (n: string) => handleToggleRequired(n, rerender),
548
- };
549
-
550
- const targets = contentTypeNames();
551
- const fieldCards = Object.entries(properties).map(([name, fieldDef]) =>
552
- fieldCardTpl(name, fieldDef as SchemaProperty, required.includes(name), handlers, targets),
553
- );
554
-
555
- editorTpl = html`
556
- <div class="settings-editor-panel">
557
- <div class="settings-editor-header">
558
- <h3>${selectedDef}</h3>
559
- <sp-action-button
560
- size="xs"
561
- quiet
562
- title="Delete data shape"
563
- @click=${() => handleDeleteDef(rerender)}
564
- >
565
- <sp-icon-delete slot="icon"></sp-icon-delete>
566
- </sp-action-button>
567
- </div>
568
- <div class="schema-field-list">${fieldCards}</div>
569
- ${
570
- showAddField
571
- ? addFieldFormTpl(newFieldState, {
572
- onCancel: () => {
573
- showAddField = false;
574
- newFieldState = {
575
- format: "",
576
- name: "",
577
- required: false,
578
- type: "string",
579
- };
580
- rerender();
581
- },
582
- onConfirm: () => handleAddField(rerender),
583
- onInput: (field, value) => {
584
- newFieldState = { ...newFieldState, [field]: value };
585
- rerender();
586
- },
587
- })
588
- : html`
589
- <sp-action-button
590
- size="s"
591
- quiet
592
- @click=${() => {
593
- showAddField = true;
594
- rerender();
595
- }}
596
- >
597
- <sp-icon-add slot="icon"></sp-icon-add> Add Field
598
- </sp-action-button>
599
- `
600
- }
601
- </div>
602
- `;
603
- }
604
-
605
- // Every section names itself in an <h3> matching its nav entry (Overview, Contexts, Head,
606
- // Packages, Extensions, Deploy, Raw JSON). This one did not, so it was the only section whose
607
- // Body never said what the reader had clicked.
608
- const tpl = html`
609
- <div class="settings-section">
610
- <h3 class="settings-section-title">Data Shapes</h3>
611
- <div class="settings-two-col">${listTpl} ${editorTpl}</div>
612
- </div>
613
- `;
614
-
615
- litRender(tpl, container);
645
+ export function renderDefsEditor(container: HTMLElement): void {
646
+ let handle = mounts.get(container);
647
+ if (!handle?.attached()) {
648
+ handle?.dispose();
649
+ handle = mountDefsSurface(container, actionsFor(container));
650
+ mounts.set(container, handle);
651
+ }
652
+ handle.update(project(container));
616
653
  }