@jxsuite/studio 10.4.0 → 10.6.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 (346) hide show
  1. package/data/locales/en/elements.json +434 -0
  2. package/data/webdata.json +116 -139
  3. package/dist/chunks/2020-rqm7qrje.js +4 -0
  4. package/dist/chunks/adapter-cyy8vbcx.js +4 -0
  5. package/dist/chunks/add-new-flow-8cgtejv2.js +4 -0
  6. package/dist/chunks/{cf-account-picker-xb8vvt2h.js → cf-account-picker-v3qd2dhw.js} +3 -3
  7. package/dist/chunks/cf-settings-1fnw874z.js +4 -0
  8. package/dist/chunks/{color-area-h8dbasp1.js → color-area-frd9y9q6.js} +2 -2
  9. package/dist/chunks/color-field-rebpbe33.js +4 -0
  10. package/dist/chunks/{color-slider-nz2mrzky.js → color-slider-6bzmk3s7.js} +2 -2
  11. package/dist/chunks/color-t2bnnj8y.js +4 -0
  12. package/dist/chunks/{combobox-6hs00996.js → combobox-c49x7gf1.js} +3 -3
  13. package/dist/chunks/components-y8yhdd7d.js +4 -0
  14. package/dist/chunks/content-source-9e7qvmsf.js +4 -0
  15. package/dist/chunks/convert-to-repeater-pd2w4z21.js +4 -0
  16. package/dist/chunks/editor-mb9jzdrw.js +4 -0
  17. package/dist/chunks/entry-commands-at0wnx3f.js +4 -0
  18. package/dist/chunks/extension-sections-4z9qq0gh.js +4 -0
  19. package/dist/chunks/file-ops-dtdvjmza.js +4 -0
  20. package/dist/chunks/files-8pf70854.js +4 -0
  21. package/dist/chunks/format-host-fd3m2y2a.js +4 -0
  22. package/dist/chunks/fs-events-hrk29vcv.js +4 -0
  23. package/dist/chunks/grid-open-bh74v82z.js +4 -0
  24. package/dist/chunks/{grid-view-2f03wcgw.js → grid-view-a51mgzmq.js} +17 -17
  25. package/dist/chunks/{icons-gb967qc8.js → icons-qp8fn1k8.js} +2 -2
  26. package/dist/chunks/{iframe-entry-btck0e9s.js → iframe-entry-mpwe5pwk.js} +4 -4
  27. package/dist/chunks/{iframe-entry-2x9bn5ng.js → iframe-entry-msd43x9h.js} +4 -4
  28. package/dist/chunks/iframe-entry-rt8jz5t8.js +5 -0
  29. package/dist/chunks/{iframe-entry-b5qgggyp.js → iframe-entry-v40cr813.js} +4 -4
  30. package/dist/chunks/iframe-host-q0s0wwy3.js +4 -0
  31. package/dist/chunks/{image-bake-4afr4y6a.js → image-bake-q9ajdjsj.js} +3 -3
  32. package/dist/chunks/{image-editor-x7t8jbjn.js → image-editor-ke9std9b.js} +3 -3
  33. package/dist/chunks/index-57ang7ma.js +4 -0
  34. package/dist/chunks/index-eytr7av4.js +17 -0
  35. package/dist/chunks/{index-yjrfeks8.js → index-x495reff.js} +3 -3
  36. package/dist/chunks/{javascript-hy2mws8v.js → javascript-qe34bvaz.js} +3 -3
  37. package/dist/chunks/jsonMode-m8rseh31.js +13 -0
  38. package/dist/chunks/jx-validate-bbd1mary.js +4 -0
  39. package/dist/chunks/layers-panel-wcjtdqsa.js +4 -0
  40. package/dist/chunks/layers-yscpys6q.js +4 -0
  41. package/dist/chunks/{listbox-7gka1h86.js → listbox-tktzq5xj.js} +2 -2
  42. package/dist/chunks/lit-html-qm5wbhqz.js +4 -0
  43. package/dist/chunks/media-library-2hfff88g.js +4 -0
  44. package/dist/chunks/media-open-4pb05cne.js +4 -0
  45. package/dist/chunks/media-picker-bzktmmwr.js +4 -0
  46. package/dist/chunks/menu-xc3cbgsd.js +4 -0
  47. package/dist/chunks/{monaco-binding-a51py33c.js → monaco-binding-7t38arh9.js} +3 -3
  48. package/dist/chunks/monaco-buffer-3vz94c9f.js +4 -0
  49. package/dist/chunks/monaco-setup-fstdew02.js +4 -0
  50. package/dist/chunks/notify-gbdcckrh.js +4 -0
  51. package/dist/chunks/parse-fpdzgk4c.js +4 -0
  52. package/dist/chunks/platform-9g72gdqr.js +4 -0
  53. package/dist/chunks/popover-cnmsav6b.js +4 -0
  54. package/dist/chunks/publish-panel-91x1cb5d.js +4 -0
  55. package/dist/chunks/publish-quick-e2dmf7eg.js +4 -0
  56. package/dist/chunks/publish-workspace-1x8mnzrj.js +4 -0
  57. package/dist/chunks/quick-search-2m4y6p1x.js +4 -0
  58. package/dist/chunks/references-yg1g9rwx.js +4 -0
  59. package/dist/chunks/right-panel-7mfyhzrk.js +4 -0
  60. package/dist/chunks/source-control-rrc4jgp5.js +4 -0
  61. package/dist/chunks/{split-m5rwn31z.js → split-d15yencd.js} +2 -2
  62. package/dist/chunks/studio-0jhhznxy.js +9 -0
  63. package/dist/chunks/studio-0nb57pjg.js +7 -0
  64. package/dist/chunks/{studio-s24mhq0z.js → studio-0wa4wv4s.js} +4 -4
  65. package/dist/chunks/studio-0z8kp4xk.js +5 -0
  66. package/dist/chunks/{studio-g0hxtcga.js → studio-18gc5t3v.js} +4 -4
  67. package/dist/chunks/{studio-4fnb5648.js → studio-1b79rpcb.js} +4 -4
  68. package/dist/chunks/studio-211q5j7m.js +4 -0
  69. package/dist/chunks/studio-213pyfxh.js +16 -0
  70. package/dist/chunks/studio-2b1smjvp.js +5 -0
  71. package/dist/chunks/{studio-nwf0c32p.js → studio-2v6tc9jp.js} +4 -4
  72. package/dist/chunks/studio-30j0wz99.js +5 -0
  73. package/dist/chunks/studio-3kmqhwtt.js +5 -0
  74. package/dist/chunks/studio-44drsmhw.js +5 -0
  75. package/dist/chunks/{studio-2pk7kc7z.js → studio-495b6f2r.js} +4 -4
  76. package/dist/chunks/{studio-jma2q8r9.js → studio-4gthb2fd.js} +4 -4
  77. package/dist/chunks/{studio-0gvvqj3h.js → studio-4xadhxj1.js} +4 -4
  78. package/dist/chunks/{studio-nvcn2ykc.js → studio-52mzbxqm.js} +4 -4
  79. package/dist/chunks/{studio-v7a7yb5w.js → studio-56mmnsw8.js} +7 -7
  80. package/dist/chunks/studio-59hz8f4r.js +5 -0
  81. package/dist/chunks/{studio-sce30r7b.js → studio-5k9mfafh.js} +3 -3
  82. package/dist/chunks/{studio-rr00ex23.js → studio-5qez462c.js} +6 -6
  83. package/dist/chunks/studio-6tnpg0vx.js +5 -0
  84. package/dist/chunks/studio-6w8mt734.js +6 -0
  85. package/dist/chunks/studio-7hm04x5z.js +5 -0
  86. package/dist/chunks/studio-8atmfjjm.js +7 -0
  87. package/dist/chunks/{studio-wmjmsw0p.js → studio-8n99z71s.js} +4 -4
  88. package/dist/chunks/studio-8vmad6x2.js +5 -0
  89. package/dist/chunks/studio-93p5k4r4.js +5 -0
  90. package/dist/chunks/{studio-2x972v58.js → studio-9gb0m81w.js} +4 -4
  91. package/dist/chunks/studio-a4ymg4vd.js +5 -0
  92. package/dist/chunks/studio-anc8067x.js +5 -0
  93. package/dist/chunks/{studio-2e4hry72.js → studio-dbdt67kr.js} +5 -5
  94. package/dist/chunks/studio-dbex091t.js +5 -0
  95. package/dist/chunks/{studio-amaz545p.js → studio-dj8ctxey.js} +4 -4
  96. package/dist/chunks/{studio-k2evngrj.js → studio-ed9bz68j.js} +5 -5
  97. package/dist/chunks/{studio-4bgvvzhv.js → studio-fe2d1fp5.js} +4 -4
  98. package/dist/chunks/{studio-45mj4jqe.js → studio-fgp4cyy3.js} +4 -4
  99. package/dist/chunks/studio-ft706q02.js +5 -0
  100. package/dist/chunks/studio-g79s1dyg.js +6 -0
  101. package/dist/chunks/{studio-pj216fmn.js → studio-gahswxp4.js} +4 -4
  102. package/dist/chunks/studio-gbqc12bk.js +5 -0
  103. package/dist/chunks/studio-gxphbetb.js +6 -0
  104. package/dist/chunks/studio-hdf01j7q.js +7 -0
  105. package/dist/chunks/studio-hf259xzd.js +48 -0
  106. package/dist/chunks/studio-jbva4wxw.js +5 -0
  107. package/dist/chunks/studio-jj9gf1qc.js +5 -0
  108. package/dist/chunks/studio-jsjg3mjv.js +5 -0
  109. package/dist/chunks/studio-jtvb8hd7.js +5 -0
  110. package/dist/chunks/{studio-g9brbewz.js → studio-kp4vzrct.js} +4 -4
  111. package/dist/chunks/studio-kpx02gaa.js +7 -0
  112. package/dist/chunks/{studio-3na1r48j.js → studio-kv1728ex.js} +8 -8
  113. package/dist/chunks/studio-m25ry0mf.js +5 -0
  114. package/dist/chunks/studio-mcbwmkyh.js +5 -0
  115. package/dist/chunks/studio-meajkr51.js +5 -0
  116. package/dist/chunks/studio-mkjrjpr2.js +6 -0
  117. package/dist/chunks/{studio-eqcddvc2.js → studio-mr084886.js} +201 -201
  118. package/dist/chunks/studio-mrb10hv3.js +5 -0
  119. package/dist/chunks/{studio-0939t1q1.js → studio-nhpg405j.js} +4 -4
  120. package/dist/chunks/studio-nsec0hxm.js +64 -0
  121. package/dist/chunks/{studio-xx3r3sb0.js → studio-nv0mh0p5.js} +4 -4
  122. package/dist/chunks/studio-pafmb06w.js +5 -0
  123. package/dist/chunks/{studio-pm17wdc4.js → studio-pgftd79b.js} +4 -4
  124. package/dist/chunks/{studio-4zd6an2b.js → studio-pk961xgz.js} +4 -4
  125. package/dist/chunks/studio-pzbr7g05.js +5 -0
  126. package/dist/chunks/studio-qcc5hsw2.js +10 -0
  127. package/dist/chunks/studio-qfq58c8n.js +5 -0
  128. package/dist/chunks/studio-qvb2jr83.js +5 -0
  129. package/dist/chunks/studio-r85zw6ay.js +5 -0
  130. package/dist/chunks/studio-s0kwce5w.js +9 -0
  131. package/dist/chunks/studio-s6ymkf3q.js +14 -0
  132. package/dist/chunks/{studio-0x6f3nk9.js → studio-s7p5ccxa.js} +4 -4
  133. package/dist/chunks/{studio-1410q5dw.js → studio-s9jjap3x.js} +4 -4
  134. package/dist/chunks/{studio-evq47j2z.js → studio-t5hde1ws.js} +4 -4
  135. package/dist/chunks/studio-vj9yv897.js +5 -0
  136. package/dist/chunks/studio-wmak8xjw.js +5 -0
  137. package/dist/chunks/{studio-bnc50f9h.js → studio-wqsmvtqj.js} +4 -4
  138. package/dist/chunks/studio-x3z3rhej.js +5 -0
  139. package/dist/chunks/{studio-bg8pzprg.js → studio-xb55kpmy.js} +4 -4
  140. package/dist/chunks/studio-xxtdajmv.js +12 -0
  141. package/dist/chunks/studio-xxttcym1.js +5 -0
  142. package/dist/chunks/{studio-90zkgne5.js → studio-y2zsnzye.js} +7 -7
  143. package/dist/chunks/{studio-3qr08sxq.js → studio-y3tjh2xb.js} +3 -3
  144. package/dist/chunks/studio-y4f1rdsj.js +5 -0
  145. package/dist/chunks/studio-ybkd5w8k.js +5 -0
  146. package/dist/chunks/{studio-tpxcryf2.js → studio-yetr4gg1.js} +4 -4
  147. package/dist/chunks/studio-z295anhs.js +6 -0
  148. package/dist/chunks/studio-z89e6wpg.js +9 -0
  149. package/dist/chunks/{studio-5tet77de.js → studio-zbwr6prc.js} +3 -3
  150. package/dist/chunks/{studio-daksw8kh.js → studio-zdbsv5q2.js} +4 -4
  151. package/dist/chunks/{studio-abh9kfdh.js → studio-zvthj8hg.js} +4 -4
  152. package/dist/chunks/toast-2kn2vrn4.js +4 -0
  153. package/dist/chunks/{toast-host-g3r2s0kn.js → toast-host-3rxeyj3d.js} +3 -3
  154. package/dist/chunks/{tooltip-dj1pvgz7.js → tooltip-ghkxkasp.js} +3 -3
  155. package/dist/chunks/{tree-j0ebwr5s.js → tree-vw4nc63s.js} +2 -2
  156. package/dist/chunks/tsMode-7gngmamr.js +14 -0
  157. package/dist/chunks/workspace-gv0hxgsm.js +4 -0
  158. package/dist/chunks/workspace-home-j7e0mjy5.js +4 -0
  159. package/dist/chunks/{ws-client-arz2qsy9.js → ws-client-vs11sxc5.js} +3 -3
  160. package/dist/iframe-entry.js +14 -14
  161. package/dist/manifest.json +84 -83
  162. package/dist/studio.js +99 -98
  163. package/dist/workers/ts.worker.js +3 -3
  164. package/package.json +12 -12
  165. package/src/browse/library-layouts.ts +9 -12
  166. package/src/browse/library-pane.ts +22 -16
  167. package/src/browse/library-source.ts +3 -4
  168. package/src/browse/preview-style.ts +2 -1
  169. package/src/canvas/canvas-live-render.ts +2 -5
  170. package/src/canvas/canvas-page-context.ts +5 -0
  171. package/src/canvas/canvas-render.ts +6 -1
  172. package/src/canvas/iframe-drop.ts +84 -1
  173. package/src/canvas/iframe-entry.ts +9 -6
  174. package/src/canvas/iframe-render.ts +13 -0
  175. package/src/collab/collab-session.ts +2 -2
  176. package/src/commands/levels.ts +1 -1
  177. package/src/content/entry-commands.ts +32 -27
  178. package/src/content/entry-editor.ts +46 -44
  179. package/src/content/entry-inspector.ts +15 -12
  180. package/src/content/entry-model.ts +3 -3
  181. package/src/content/entry-summaries.ts +5 -5
  182. package/src/content/hold-state.ts +172 -0
  183. package/src/content/page-summaries.ts +10 -14
  184. package/src/create/add-new-flow.ts +2 -1
  185. package/src/editor/convert-targets.ts +2 -23
  186. package/src/editor/inline-edit.ts +6 -1
  187. package/src/editor/repeater-scope.ts +333 -74
  188. package/src/editor/slash-menu.ts +35 -17
  189. package/src/files/files.ts +18 -0
  190. package/src/format/format-choices.ts +5 -5
  191. package/src/format/format-host.ts +35 -3
  192. package/src/grid/redirects-grid.ts +4 -4
  193. package/src/grid/sources/content-source.ts +42 -66
  194. package/src/locale/element-names.ts +131 -0
  195. package/src/panels/block-action-bar.ts +3 -4
  196. package/src/panels/dnd.ts +3 -1
  197. package/src/panels/elements-panel.ts +93 -36
  198. package/src/panels/events-panel.ts +6 -2
  199. package/src/panels/formula-workspace.ts +9 -0
  200. package/src/panels/imports-panel.ts +3 -2
  201. package/src/panels/navigator-panels.ts +10 -10
  202. package/src/panels/panel-registry.ts +3 -3
  203. package/src/panels/properties-panel.ts +127 -35
  204. package/src/panels/site-panel.ts +2 -1
  205. package/src/panels/statement-editor.ts +4 -0
  206. package/src/panels/style-panel.ts +2 -1
  207. package/src/panels/styles-panel.ts +2 -2
  208. package/src/panels/tab-strip.ts +29 -26
  209. package/src/platforms/cloud.ts +5 -2
  210. package/src/services/ai-command-tools.ts +2 -1
  211. package/src/services/ai-project-tools.ts +21 -8
  212. package/src/services/ai-tools.ts +5 -5
  213. package/src/services/ai-write-report.ts +22 -4
  214. package/src/services/context-manager.ts +110 -2
  215. package/src/services/document-assistant.ts +2 -2
  216. package/src/services/harness/chat-reducer.ts +9 -4
  217. package/src/services/jxn-language.ts +110 -0
  218. package/src/services/model-uri.ts +3 -0
  219. package/src/services/monaco-setup.ts +6 -2
  220. package/src/services/render-critic.ts +234 -12
  221. package/src/services/tool-executor.ts +16 -8
  222. package/src/settings/contributed-section.ts +2 -1
  223. package/src/settings/extension-commands.ts +2 -1
  224. package/src/settings/general-settings.ts +2 -2
  225. package/src/settings/settings-document.ts +5 -5
  226. package/src/shell.ts +1 -1
  227. package/src/studio.ts +28 -11
  228. package/src/style/variables-view.ts +2 -2
  229. package/src/surfaces/collections-panel.json +1 -1
  230. package/src/surfaces/collections-panel.ts +1 -1
  231. package/src/surfaces/entry-editor.json +10 -10
  232. package/src/surfaces/entry-editor.ts +22 -19
  233. package/src/surfaces/entry-inspector.json +11 -11
  234. package/src/surfaces/entry-inspector.ts +17 -10
  235. package/src/surfaces/panel-elements.json +466 -206
  236. package/src/surfaces/panel-elements.ts +61 -25
  237. package/src/surfaces/tab-strip.json +5 -5
  238. package/src/surfaces/tab-strip.ts +2 -2
  239. package/src/tabs/project-config.ts +13 -13
  240. package/src/tabs/tab.ts +7 -2
  241. package/src/types.ts +13 -2
  242. package/src/ui/expression-editor.ts +10 -5
  243. package/src/ui/schema-form.ts +18 -1
  244. package/src/utils/edit-display.ts +27 -1
  245. package/src/view.ts +9 -2
  246. package/src/workspace/tab-workspace.ts +2 -2
  247. package/src/workspace/workspace-back.ts +2 -2
  248. package/dist/chunks/2020-906vx2dq.js +0 -4
  249. package/dist/chunks/adapter-hnvad61x.js +0 -4
  250. package/dist/chunks/add-new-flow-v0pm96mr.js +0 -4
  251. package/dist/chunks/cf-settings-mkbcz4z2.js +0 -4
  252. package/dist/chunks/color-04361ttk.js +0 -4
  253. package/dist/chunks/color-field-qv5r2b16.js +0 -4
  254. package/dist/chunks/components-w5zqwva1.js +0 -4
  255. package/dist/chunks/content-source-g38w0d3f.js +0 -4
  256. package/dist/chunks/convert-to-repeater-r8sfxjjm.js +0 -4
  257. package/dist/chunks/editor-wvf5e6j9.js +0 -4
  258. package/dist/chunks/entry-commands-t13dyw3c.js +0 -4
  259. package/dist/chunks/extension-sections-vjzw46xb.js +0 -4
  260. package/dist/chunks/file-ops-1sghzgcr.js +0 -4
  261. package/dist/chunks/files-zrqtgt41.js +0 -4
  262. package/dist/chunks/format-host-9gy76erh.js +0 -4
  263. package/dist/chunks/fs-events-45rhg3me.js +0 -4
  264. package/dist/chunks/grid-open-t4fq6bbm.js +0 -4
  265. package/dist/chunks/iframe-entry-42hpmwz5.js +0 -5
  266. package/dist/chunks/iframe-host-9aq8ze3q.js +0 -4
  267. package/dist/chunks/index-f9qr7kzh.js +0 -4
  268. package/dist/chunks/jsonMode-vj30te2h.js +0 -13
  269. package/dist/chunks/jx-validate-r4ax11e3.js +0 -4
  270. package/dist/chunks/layers-m49nmwvx.js +0 -4
  271. package/dist/chunks/layers-panel-zr92pwdp.js +0 -4
  272. package/dist/chunks/lit-html-k2mb9k0r.js +0 -4
  273. package/dist/chunks/media-library-j74rt0w4.js +0 -4
  274. package/dist/chunks/media-open-mypr0k6b.js +0 -4
  275. package/dist/chunks/media-picker-pemtbsjv.js +0 -4
  276. package/dist/chunks/menu-d2jxpgma.js +0 -4
  277. package/dist/chunks/monaco-buffer-pa0g6ax7.js +0 -4
  278. package/dist/chunks/monaco-setup-0hw9f835.js +0 -4
  279. package/dist/chunks/notify-bwehrp7q.js +0 -4
  280. package/dist/chunks/parse-hee7ne9b.js +0 -4
  281. package/dist/chunks/platform-kv0k65cy.js +0 -4
  282. package/dist/chunks/popover-9tf4cjrk.js +0 -4
  283. package/dist/chunks/publish-panel-vg1n880b.js +0 -4
  284. package/dist/chunks/publish-quick-4fwngwrw.js +0 -4
  285. package/dist/chunks/publish-workspace-jv4w2bx2.js +0 -4
  286. package/dist/chunks/quick-search-nh29rt8a.js +0 -4
  287. package/dist/chunks/references-vqyr5s9x.js +0 -4
  288. package/dist/chunks/right-panel-3ws9f2w0.js +0 -4
  289. package/dist/chunks/source-control-h3hcs5gy.js +0 -4
  290. package/dist/chunks/studio-0khd8xtx.js +0 -4
  291. package/dist/chunks/studio-1ctnw3xv.js +0 -5
  292. package/dist/chunks/studio-1ks5d9y4.js +0 -5
  293. package/dist/chunks/studio-1y9mab9m.js +0 -5
  294. package/dist/chunks/studio-2kpxm0eh.js +0 -7
  295. package/dist/chunks/studio-2zz4qbv9.js +0 -5
  296. package/dist/chunks/studio-3pz4t5a2.js +0 -5
  297. package/dist/chunks/studio-4f7thq0p.js +0 -5
  298. package/dist/chunks/studio-61rg2cz5.js +0 -5
  299. package/dist/chunks/studio-7rrdbvex.js +0 -5
  300. package/dist/chunks/studio-9c6cbq2c.js +0 -4
  301. package/dist/chunks/studio-9dq7rwc7.js +0 -5
  302. package/dist/chunks/studio-9gr7xs9r.js +0 -6
  303. package/dist/chunks/studio-a88ej93p.js +0 -7
  304. package/dist/chunks/studio-acer0g8w.js +0 -5
  305. package/dist/chunks/studio-az3wvyh2.js +0 -5
  306. package/dist/chunks/studio-brh8d2pr.js +0 -5
  307. package/dist/chunks/studio-c2fa783p.js +0 -9
  308. package/dist/chunks/studio-cfnn70h1.js +0 -5
  309. package/dist/chunks/studio-cqxj4fcy.js +0 -5
  310. package/dist/chunks/studio-cw11h078.js +0 -5
  311. package/dist/chunks/studio-dn56z7sn.js +0 -5
  312. package/dist/chunks/studio-egnjpyde.js +0 -9
  313. package/dist/chunks/studio-ejrypvmq.js +0 -5
  314. package/dist/chunks/studio-frc16j8v.js +0 -10
  315. package/dist/chunks/studio-gnazwtv3.js +0 -5
  316. package/dist/chunks/studio-hfqpfmpk.js +0 -5
  317. package/dist/chunks/studio-hh394phe.js +0 -5
  318. package/dist/chunks/studio-j0zf5x2x.js +0 -5
  319. package/dist/chunks/studio-j5fcp6gj.js +0 -5
  320. package/dist/chunks/studio-jd1qb1st.js +0 -7
  321. package/dist/chunks/studio-k0xzrse2.js +0 -64
  322. package/dist/chunks/studio-k1xqrgm5.js +0 -5
  323. package/dist/chunks/studio-k3we3kqb.js +0 -5
  324. package/dist/chunks/studio-knsjyx7n.js +0 -5
  325. package/dist/chunks/studio-mrjtnm62.js +0 -5
  326. package/dist/chunks/studio-n9qba0gx.js +0 -5
  327. package/dist/chunks/studio-nzemfbkk.js +0 -14
  328. package/dist/chunks/studio-pwctzka3.js +0 -16
  329. package/dist/chunks/studio-q80fg1d6.js +0 -12
  330. package/dist/chunks/studio-q9hx2qp0.js +0 -5
  331. package/dist/chunks/studio-qrzmfaf6.js +0 -5
  332. package/dist/chunks/studio-r6mx7vm5.js +0 -7
  333. package/dist/chunks/studio-swedpd82.js +0 -5
  334. package/dist/chunks/studio-tmfcn347.js +0 -6
  335. package/dist/chunks/studio-v5wktj6f.js +0 -5
  336. package/dist/chunks/studio-vpevkfxw.js +0 -5
  337. package/dist/chunks/studio-wqfaqw4m.js +0 -5
  338. package/dist/chunks/studio-xpx91fby.js +0 -6
  339. package/dist/chunks/studio-xva3hzkv.js +0 -5
  340. package/dist/chunks/studio-z3fztxrp.js +0 -9
  341. package/dist/chunks/studio-z9453ypn.js +0 -6
  342. package/dist/chunks/toast-w11qpkyz.js +0 -4
  343. package/dist/chunks/tsMode-jg1dtphj.js +0 -14
  344. package/dist/chunks/workspace-6ztqcrsh.js +0 -4
  345. package/dist/chunks/workspace-home-rse9h8a0.js +0 -4
  346. package/src/content/draft-state.ts +0 -108
@@ -1,6 +1,9 @@
1
1
  /// <reference lib="dom" />
2
2
  /**
3
- * Elements panel — the Insert palette: the blocks and components a page is built out of.
3
+ * Elements panel — the Insert palette: the blocks and components a page is built out of, on two
4
+ * tabs, Elements and Components (§5.3). Each element card is named in the reader's language
5
+ * (`locale/element-names.ts`), with its tag beside the name, and the categories lead with what a
6
+ * first page is made of: text, lists, media and layout before forms, tables and inline formatting.
4
7
  *
5
8
  * **The body is a Jx document** (`surfaces/panel-elements.json`, mounted by
6
9
  * `surfaces/panel-elements.ts`), so `render` draws nothing and `afterRender` mounts. What stays
@@ -30,6 +33,7 @@ import { mountElementsSurface } from "../surfaces/panel-elements";
30
33
  import { mutateInsertNode, transactDoc } from "../tabs/transact";
31
34
  import { primarySelection } from "../tabs/selection";
32
35
  import { registerPanel } from "./panel-registry";
36
+ import { categoryName, elementDescription, elementName } from "../locale/element-names";
33
37
  import { activeTab } from "../workspace/workspace";
34
38
  import { view } from "../view";
35
39
 
@@ -38,8 +42,10 @@ import type {
38
42
  ComponentCardView,
39
43
  ElementCategoryView,
40
44
  ElementsActions,
45
+ ElementsEmptyState,
41
46
  ElementsSurfaceHandle,
42
47
  ElementsValues,
48
+ InsertTab,
43
49
  } from "../surfaces/panel-elements";
44
50
  import type { JxElement, JxMutableNode } from "@jxsuite/schema/types";
45
51
  import type { NavigatorPanelContext, NavigatorPanelDeps, PanelBody } from "./panel-registry";
@@ -49,8 +55,13 @@ interface ElementsWebdata {
49
55
  elements?: Record<string, { tag: string }[]>;
50
56
  }
51
57
 
52
- /** The Components section's name — its accordion label AND its key in `view.elementsCollapsed`. */
53
- const COMPONENTS = "Components";
58
+ /**
59
+ * The categories that start closed: inline formatting and the advanced remainder (§5.3). Both are
60
+ * long, and neither is where somebody building their first page looks, so a newcomer sees the
61
+ * everyday blocks first and opens these when they want them. A filter opens them too, unless the
62
+ * reader has closed one by hand.
63
+ */
64
+ const CLOSED_AT_FIRST: ReadonlySet<string> = new Set(["inline", "advanced"]);
54
65
 
55
66
  /**
56
67
  * What the palette is built from, held at module scope rather than closed over at mount.
@@ -76,18 +87,37 @@ function needle(): string {
76
87
  return view.elementsFilter.toLowerCase();
77
88
  }
78
89
 
79
- /** The categories that still have something in them, with the filter applied. */
90
+ /** Whether a section is drawn open: the reader's own choice, else its default. */
91
+ function sectionOpen(key: string, filter: string): boolean {
92
+ return view.elementsOpen.get(key) ?? (filter !== "" || !CLOSED_AT_FIRST.has(key));
93
+ }
94
+
95
+ /**
96
+ * The categories that still have something in them, with the filter applied, in the order
97
+ * `webdata.elements` declares them (§5.3). A card matches on its name in the reader's language or
98
+ * on its tag, so "image" and "img" both find the same card.
99
+ */
80
100
  function categoryViews(webdata: ElementsWebdata, filter: string): ElementCategoryView[] {
81
101
  const sections: ElementCategoryView[] = [];
82
- for (const [name, elements] of Object.entries(webdata.elements ?? {})) {
83
- const matched = filter ? elements.filter((entry) => entry.tag.includes(filter)) : elements;
102
+ for (const [key, elements] of Object.entries(webdata.elements ?? {})) {
103
+ const cards = elements.map(({ tag }) => ({
104
+ description: elementDescription(tag),
105
+ name: elementName(tag),
106
+ tag,
107
+ }));
108
+ const matched = filter
109
+ ? cards.filter(
110
+ (card) => card.tag.includes(filter) || card.name.toLowerCase().includes(filter),
111
+ )
112
+ : cards;
84
113
  if (matched.length === 0) {
85
114
  continue;
86
115
  }
87
116
  sections.push({
88
- elements: matched.map(({ tag }) => ({ tag })),
89
- name,
90
- open: !view.elementsCollapsed.has(name),
117
+ elements: matched,
118
+ key,
119
+ name: categoryName(key),
120
+ open: sectionOpen(key, filter),
91
121
  });
92
122
  }
93
123
  return sections;
@@ -145,23 +175,51 @@ function componentViews(filter: string): ComponentCardView[] {
145
175
  }));
146
176
  }
147
177
 
178
+ /** Which body a tab shows: its cards, or why it has none. */
179
+ function emptyState(count: number, filter: string): ElementsEmptyState {
180
+ if (count > 0) {
181
+ return "none";
182
+ }
183
+ return filter ? "filtered" : "empty";
184
+ }
185
+
186
+ /** The other tab, which a filtered-out tab offers to switch to. */
187
+ function otherTab(tab: InsertTab): InsertTab {
188
+ return tab === "elements" ? "components" : "elements";
189
+ }
190
+
191
+ /** "Show 1 matching component", "Show 3 matching elements": the other tab's offer. */
192
+ function otherMatchesLabel(tab: InsertTab, count: number): string {
193
+ const noun = tab === "elements" ? "element" : "component";
194
+ return `Show ${count} matching ${noun}${count === 1 ? "" : "s"}`;
195
+ }
196
+
148
197
  /** Everything the document draws, as one value. */
149
198
  function panelValues(source: NavigatorPanelDeps): ElementsValues {
150
199
  const filter = needle();
200
+ const tab = view.insertTab;
151
201
  /* SAFETY: `webdata` is wired in studio.ts from the bundled data/webdata.json, whose `elements`
152
- maps each category name to an array of `{ tag }` entries. */
202
+ maps each category key to an array of `{ tag }` entries. */
153
203
  const categories = categoryViews(source.webdata as ElementsWebdata, filter);
154
204
  const components = componentViews(filter);
155
- const bare = categories.length === 0 && components.length === 0;
205
+ const elementCount = categories.reduce((sum, section) => sum + section.elements.length, 0);
206
+ const counts: Record<InsertTab, number> = {
207
+ components: components.length,
208
+ elements: elementCount,
209
+ };
210
+ const other = otherTab(tab);
211
+ const showOffer = filter !== "" && counts[tab] === 0 && counts[other] > 0;
156
212
  return {
157
213
  categories,
158
214
  components,
159
- componentsOpen: !view.elementsCollapsed.has(COMPONENTS),
160
- emptyState: bare ? (filter ? "filtered" : "empty") : "none",
215
+ componentsEmpty: emptyState(components.length, filter),
216
+ elementsEmpty: emptyState(elementCount, filter),
161
217
  /* The reader's own text, not `filter`: the field is bound to this, and a scope holding the
162
218
  lower-cased form would rewrite what was typed on every keystroke. */
163
219
  filter: view.elementsFilter,
164
- hasComponents: components.length > 0,
220
+ filterLabel: tab === "elements" ? "Filter elements" : "Filter components",
221
+ otherMatches: showOffer ? otherMatchesLabel(other, counts[other]) : "",
222
+ tab,
165
223
  };
166
224
  }
167
225
 
@@ -207,6 +265,17 @@ function refresh(): void {
207
265
 
208
266
  /** What the document's controls do. Every one of them is a decision this module owns. */
209
267
  const ACTIONS: ElementsActions = {
268
+ /* The strip announces any value its tabs carry; only the two this panel draws are taken. */
269
+ selectTab: (tab) => {
270
+ if (tab === "elements" || tab === "components") {
271
+ view.insertTab = tab;
272
+ refresh();
273
+ }
274
+ },
275
+ showOtherTab: () => {
276
+ view.insertTab = otherTab(view.insertTab);
277
+ refresh();
278
+ },
210
279
  clearFilter: () => {
211
280
  view.elementsFilter = "";
212
281
  refresh();
@@ -223,31 +292,20 @@ const ACTIONS: ElementsActions = {
223
292
  insertAtSelection(panelDeps.defaultDef(tag) as JxMutableNode);
224
293
  }
225
294
  },
226
- setComponentsOpen: (open) => {
227
- setCollapsed(COMPONENTS, !open);
228
- refresh();
229
- },
230
295
  /* The echo: what the field now holds goes into the state the palette is filtered by, before
231
296
  anything is decided about it. */
232
297
  setFilter: (value) => {
233
298
  view.elementsFilter = value;
234
299
  refresh();
235
300
  },
236
- setSectionOpen: (name, open) => {
237
- setCollapsed(name, !open);
301
+ /* Remembered across repaints and panel switches, and from then on it outranks the section's
302
+ default, the filter's included. */
303
+ setSectionOpen: (key, open) => {
304
+ view.elementsOpen.set(key, open);
238
305
  refresh();
239
306
  },
240
307
  };
241
308
 
242
- /** Remember a section's disclosure across repaints and across panel switches. */
243
- function setCollapsed(name: string, collapsed: boolean): void {
244
- if (collapsed) {
245
- view.elementsCollapsed.add(name);
246
- } else {
247
- view.elementsCollapsed.delete(name);
248
- }
249
- }
250
-
251
309
  /**
252
310
  * Draw the panel body — mounting the document the first time, updating it every time after.
253
311
  *
@@ -277,13 +335,13 @@ export function mountElementsPanel(ctx: NavigatorPanelContext, host: HTMLElement
277
335
  }
278
336
 
279
337
  /**
280
- * Contribute the Insert panel — **off the rail** (`rail: false`).
338
+ * Contribute the Insert panel — the first button of the rail's DOCUMENT group (§5.1).
281
339
  *
282
- * §5.1 keeps Insert off the Navigator rail because it is not a view of anything: it is an insert
283
- * palette, and a palette belongs at the caret (slash menu), on the canvas (`+`) and behind ⌘⇧A, all
284
- * of which are the Insert command family. The record survives so the surface stays reachable —
285
- * `view.setActivity {tab:"insert"}`, the palette, and the screenshot pipeline all still address it
286
- * — and giving up its rail slot is what keeps the DOCUMENT group at four.
340
+ * It is the place an author picks an element or component from and drags it onto the canvas, so it
341
+ * stays on screen while they work: a card is a choice to look at, compare and drag, and a palette
342
+ * that has to be summoned by name before it can be seen is one only a reader of the command list
343
+ * finds. The slash menu and the canvas `+` stay the inserts at the caret; this is the one an author
344
+ * browses.
287
345
  *
288
346
  * `level: "document"`, per §13.2's own worked example: it READS the project's component registry
289
347
  * and WRITES the document tree.
@@ -295,7 +353,6 @@ export function registerInsertPanel(): void {
295
353
  level: "document",
296
354
  dock: "navigator",
297
355
  icon: "grid-four",
298
- rail: false,
299
356
  // The body is a document, so lit draws nothing and the mount happens against the painted DOM.
300
357
  // `afterRender` runs on every repaint; {@link mountElementsPanel} is idempotent.
301
358
  render: (): PanelBody => nothing,
@@ -76,6 +76,7 @@ import {
76
76
  bindableSignalNames,
77
77
  defaultAsString,
78
78
  isInspectorSectionOpen,
79
+ mapListSignalsFor,
79
80
  mapSignalsFor,
80
81
  setInspectorSection,
81
82
  } from "./properties-panel";
@@ -348,6 +349,7 @@ function addField(
348
349
  (next) => spec.onChange({ $expression: next } as JsonValue),
349
350
  {
350
351
  allowEventRef: false,
352
+ extraRefs: spec.extraSignals,
351
353
  stateDefs: [...spec.stateDefs],
352
354
  stateEntries: null,
353
355
  },
@@ -452,7 +454,7 @@ export function projectLogicPanel(): LogicView {
452
454
  view.repeaterFields = [
453
455
  addField(fields, `prop|${key}|items`, "items", {
454
456
  caps: "repeaterItems",
455
- extraSignals: null,
457
+ extraSignals: mapListSignalsFor(doc, selection),
456
458
  label: "Items",
457
459
  literalDefault: literalDefaultOf(node.items),
458
460
  onChange: (v?: JsonValue) =>
@@ -475,7 +477,7 @@ export function projectLogicPanel(): LogicView {
475
477
  // `SwitchDef` is a $ref and nothing else, so From data… is the only rung — a $switch is
476
478
  // Inherently dynamic, and de-escalating it would delete the key and demote the node.
477
479
  caps: "switchDiscriminant",
478
- extraSignals: mapSignalsFor(selection),
480
+ extraSignals: mapSignalsFor(doc, selection),
479
481
  label: "Expression",
480
482
  literalDefault: literalDefaultOf(node.$switch),
481
483
  onChange: (v?: JsonValue) =>
@@ -620,6 +622,7 @@ function bindingView(
620
622
  {
621
623
  allowEventRef: true,
622
624
  emits: fn.emits ?? [],
625
+ extraRefs: mapSignalsFor(tab.doc.document, selection),
623
626
  /* The Inspector's copy, named by the handler it edits. It answered to
624
627
  `navigator/statements` until the docks were split, and then to a bare
625
628
  `inspector/statements` — which the binding list stamps once per structured handler, so
@@ -657,6 +660,7 @@ function bindingView(
657
660
  plan.paint = (host) =>
658
661
  mountExpressionEditor(host, expression, commitExpression, {
659
662
  allowEventRef: true,
663
+ extraRefs: mapSignalsFor(tab.doc.document, selection),
660
664
  onInsertDef: insertDef,
661
665
  preview,
662
666
  stateDefs: Object.keys(defs),
@@ -59,6 +59,7 @@ import { livePreviewExpression } from "../services/live-preview";
59
59
  import { closeFunctionEditor, syncFunctionEditor } from "./editors";
60
60
  import { registerPanel } from "./panel-registry";
61
61
  import { dataTypeLabel, paintDataTree, unwrapSignal } from "./data-explorer";
62
+ import { mapSignalsFor } from "./properties-panel";
62
63
  import { disposeDetachedDataTrees } from "../surfaces/panel-data";
63
64
  import { emptyLogicWorkspaceView, mountLogicWorkspace } from "../surfaces/logic-workspace";
64
65
 
@@ -72,6 +73,7 @@ import {
72
73
 
73
74
  import type { AnyCommand, CommandRegistry } from "../commands/registry";
74
75
  import type { PanelBody } from "./panel-registry";
76
+ import type { SurfaceOption } from "../ui/surface";
75
77
  import type { ExpressionPreview } from "../services/preview-eval";
76
78
  import type { FormulaEditDef, FunctionEditDef } from "../types";
77
79
  import type { Tab } from "../tabs/tab";
@@ -314,6 +316,8 @@ interface Resolved {
314
316
  stateEntries: Record<string, JxStateDefinition>;
315
317
  preview: ExpressionPreview | null;
316
318
  allowEventRef: boolean;
319
+ /** The repeater item's fields, for an event handler inside a template. */
320
+ extraRefs: SurfaceOption[] | null;
317
321
  /** Chip key → node path, so a chip click needs no path parsing (a `switch` case may hold "/"). */
318
322
  chipPaths: Map<string, NodePath>;
319
323
  /** Rail entry name → the value its tree draws. */
@@ -475,6 +479,10 @@ export function workspaceView(): LogicWorkspaceView {
475
479
  allowEventRef: editing.type === "event",
476
480
  chipPaths,
477
481
  editing,
482
+ extraRefs:
483
+ editing.type === "event" && editing.path
484
+ ? mapSignalsFor(tab.doc.document, editing.path)
485
+ : null,
478
486
  preview,
479
487
  root,
480
488
  scopeValues: rail.values,
@@ -528,6 +536,7 @@ function paintEditor(): void {
528
536
  mountExpressionEditor(host, held.selected, writeSelected, {
529
537
  allowEventRef: held.allowEventRef,
530
538
  depth: 1,
539
+ extraRefs: held.extraRefs,
531
540
  onInsertDef: insertDef,
532
541
  path: held.selectedPath,
533
542
  preview: held.preview,
@@ -50,6 +50,7 @@ import type {
50
50
  } from "../surfaces/panel-imports";
51
51
  import type { JxMutableNode } from "@jxsuite/schema/types";
52
52
  import type { SurfaceOption } from "../ui/surface";
53
+ import { isProjectConfigPath } from "@jxsuite/schema/project-file";
53
54
 
54
55
  /** What the panel is drawn against. `left-panel.ts`'s context, reduced to what this reads. */
55
56
  export interface ImportsContext {
@@ -204,7 +205,7 @@ function documentView(ctx: ImportsContext): ImportsView {
204
205
 
205
206
  /** What the panel shows right now. `project.json` is the one document with the other mood. */
206
207
  function view(ctx: ImportsContext): ImportsView {
207
- return ctx.documentPath?.endsWith("project.json") ? siteView() : documentView(ctx);
208
+ return isProjectConfigPath(ctx.documentPath) ? siteView() : documentView(ctx);
208
209
  }
209
210
 
210
211
  /** Push the current projection into the standing surface, in this turn. */
@@ -295,7 +296,7 @@ async function onToggleComponent(id: string, checked: boolean): Promise<void> {
295
296
  if (!ctx || !comp) {
296
297
  return;
297
298
  }
298
- if (ctx.documentPath?.endsWith("project.json")) {
299
+ if (isProjectConfigPath(ctx.documentPath)) {
299
300
  const before = siteElements();
300
301
  const updated = checked
301
302
  ? enableElement(before, comp, null)
@@ -8,9 +8,9 @@
8
8
  * the second definition site the registry exists to prevent.
9
9
  *
10
10
  * **Registration order is rail order** within a level group: PROJECT (Site, then Files with the
11
- * developer panels on) above the divider, DOCUMENT (Outline · Data, then Packages with the
12
- * developer panels on) below it (§5.1). Search, Languages and Insert are registered rail-less:
13
- * reachable by `view.setActivity` and the palette, with no button.
11
+ * developer panels on) above the divider, DOCUMENT (Insert · Outline · Data, then Packages with the
12
+ * developer panels on) below it (§5.1). Search and Languages are registered rail-less: reachable by
13
+ * `view.setActivity` and the palette, with no button.
14
14
  *
15
15
  * **Every rail button opens the Navigator.** Problems used to hold the fourth PROJECT slot while
16
16
  * its body was drawn in the Bottom dock, and paying for that took a per-dock branch in three
@@ -84,13 +84,13 @@ export function registerNavigatorPanels(): void {
84
84
  registerI18nPanel();
85
85
 
86
86
  // ── DOCUMENT group ──────────────────────────────────────────────────────────
87
+ /* Insert leads the group: it is where a page's elements and components are picked from and
88
+ dragged onto the canvas, the verb a Design session is mostly made of (§5.1, §5.3). */
89
+ registerInsertPanel();
87
90
  registerLayersPanel();
88
91
  registerDataPanel();
89
92
  registerPackagesPanel();
90
93
 
91
- // ── Off-rail: reachable by command and palette, no rail button ──────────────
92
- registerInsertPanel();
93
-
94
94
  // ── The Bottom dock's tabs (§16.3): Problems · Logic · Activity ──────────────
95
95
  // Composed here, in the one place panel records are gathered, so the shell has ONE composition
96
96
  // Site rather than one per dock. Importing the module is also what attaches the dock to the
@@ -122,10 +122,10 @@ export function navigatorPanelSet(): readonly PanelRecord[] {
122
122
  * The roster `panel.focus.*` is generated from: rail order first, then the rail-less Navigator
123
123
  * panels.
124
124
  *
125
- * Not `railPanelSet()`: Insert and Languages have no rail button and no chord, and they still need
126
- * a `Show …` command and a palette row, which is the whole point of `rail: false` (a surface
127
- * reachable by name, not by number). State used to be the third of them and is gone: its editor is
128
- * the Data panel's now, so there is no second record to roster.
125
+ * Not `railPanelSet()`: Languages has no rail button and no chord, and it still needs a `Show …`
126
+ * command and a palette row, which is the whole point of `rail: false` (a surface reachable by
127
+ * name, not by number). State and Insert were rail-less beside it once: State's editor is the Data
128
+ * panel's now, and Insert is back on the rail (§5.1).
129
129
  *
130
130
  * The Bottom dock's three tabs are deliberately absent — Problems as of this change, and Logic and
131
131
  * Activity all along. Each is `rail: false` AND hosted there, so each is addressed by
@@ -153,9 +153,9 @@ export interface PanelRecord {
153
153
  *
154
154
  * Not a hiding mechanism — a rail-less panel is still reachable by `view.setActivity`, by the
155
155
  * palette and by another surface linking to it. It is how a record survives the interval between
156
- * losing its rail slot and gaining its real home (Insert, reachable from the canvas and palette;
157
- * State, folded into Data — §5.1). Declaring it `false` also removes the panel's `rail/<level>`
158
- * placement, which is what keeps the rail inside its four-per-group budget.
156
+ * losing its rail slot and gaining its real home (State, folded into Data — §5.1). Declaring it
157
+ * `false` also removes the panel's `rail/<level>` placement, which is what keeps the rail inside
158
+ * its four-per-group budget.
159
159
  *
160
160
  * `"developer"` is a rail button only while **Show developer panels** is on (§5.1,
161
161
  * `settings/developer-panels.ts`): Files and Packages, which a site author does not need on the
@@ -41,7 +41,7 @@
41
41
  * @docs studio/design/properties
42
42
  */
43
43
 
44
- import { getNodeAtPath } from "../store";
44
+ import { getNodeAtPath, projectState } from "../store";
45
45
  import { effect, effectScope, reactive } from "../reactivity";
46
46
  import {
47
47
  displayTagName,
@@ -50,6 +50,7 @@ import {
50
50
  isRef,
51
51
  isString,
52
52
  isTagExpression,
53
+ isTemplateString,
53
54
  } from "@jxsuite/schema/guards";
54
55
  import type { JsonValue } from "../types";
55
56
  import {
@@ -114,6 +115,13 @@ import type { LinkKind } from "../utils/link-target";
114
115
  import { clickAnythingTo, openPageAction, staleSelectionMessage } from "./empty-state";
115
116
  import { mountMediaPicker, unmountMediaPicker } from "../ui/media-picker";
116
117
  import { colorTokens, resolvedColor } from "../ui/color-selector";
118
+ import {
119
+ childrenTemplate,
120
+ fieldOfChildrenTemplate,
121
+ mapRefLabel,
122
+ repeaterItemFields,
123
+ } from "../editor/repeater-scope";
124
+ import type { RepeaterField } from "../editor/repeater-scope";
117
125
  import { mountExpressionEditor } from "../ui/expression-editor";
118
126
  import type { ExpressionEditorOpts } from "../ui/expression-editor";
119
127
  import {
@@ -182,20 +190,45 @@ function nodeAtPath(doc: JxMutableNode, path: JxPath): JxMutableNode | undefined
182
190
  // ─── The binding vocabulary, shared with the Logic tab ───────────────────────
183
191
 
184
192
  /**
185
- * The two extra signals a position inside a repeater template can bind to, or `null` outside one.
193
+ * The extra signals a position inside a repeater template can bind to, or `null` outside one: the
194
+ * item, every field of the item the document and project declare, and its index.
186
195
  *
187
196
  * A "map" segment addresses a repeater template (`[…, "children", i, "map", …]`, or the legacy `[…,
188
197
  * "children", "map", …]`), so anything at or below it is inside one.
189
198
  *
190
199
  * Exported because Content and Logic both offer them and there is one right answer: a `$switch`
191
- * inside a `$map` can read `$map/item`, and so can the `alt` attribute two nodes below it.
200
+ * inside a `$map` can read `$map/item/data/title`, and so can the `alt` attribute two nodes below
201
+ * it. `$map/item` stays first, because a fresh binding lands on the first signal offered.
202
+ *
203
+ * `withNodes` adds the fields holding child nodes, for the one position that can draw them.
204
+ */
205
+ export function mapSignalsFor(
206
+ doc: JxMutableNode,
207
+ path: JxPath,
208
+ options: { withNodes?: boolean } = {},
209
+ ): SurfaceOption[] | null {
210
+ if (!path.includes("map")) {
211
+ return null;
212
+ }
213
+ const fields = repeaterItemFields(doc, path, projectState?.projectConfig, options);
214
+ return [
215
+ { label: "item", value: "$map/item" },
216
+ ...fields.map((field) => ({ label: field.token, value: field.pointer })),
217
+ { label: "index", value: "$map/index" },
218
+ ];
219
+ }
220
+
221
+ /**
222
+ * What a repeater's own Items can read from the repeater around it: the lists on the outer item.
223
+ * Only those, because an Items position takes a list, and the index or a text field would render
224
+ * nothing.
192
225
  */
193
- export function mapSignalsFor(path: JxPath): SurfaceOption[] | null {
194
- return path.includes("map")
195
- ? [
196
- { label: "$map/item", value: "$map/item" },
197
- { label: "$map/index", value: "$map/index" },
198
- ]
226
+ export function mapListSignalsFor(doc: JxMutableNode, path: JxPath): SurfaceOption[] | null {
227
+ const lists = repeaterItemFields(doc, path, projectState?.projectConfig).filter(
228
+ (field) => field.isArray,
229
+ );
230
+ return lists.length > 0
231
+ ? lists.map((field) => ({ label: field.token, value: field.pointer }))
199
232
  : null;
200
233
  }
201
234
 
@@ -228,7 +261,7 @@ export function defaultAsString(def: JxStateDefinition | undefined) {
228
261
  function bindingDonor(value: JxDocumentValue | undefined): string {
229
262
  if (isRef(value)) {
230
263
  const ref = value.$ref;
231
- return ref.startsWith("#/state/") ? ref.slice(8) : ref || "nothing yet";
264
+ return ref.startsWith("#/state/") ? ref.slice(8) : mapRefLabel(ref) || ref || "nothing yet";
232
265
  }
233
266
  return isString(value) ? "a template" : "a formula";
234
267
  }
@@ -1098,6 +1131,7 @@ function applyLadder(row: ContentRowView, plan: RowPlan, opts: LadderOpts): void
1098
1131
  plan.draft = undefined;
1099
1132
  const editorOpts: ExpressionEditorOpts = {
1100
1133
  allowEventRef: false,
1134
+ extraRefs: opts.extraSignals,
1101
1135
  preview: null,
1102
1136
  stateDefs: opts.stateDefs,
1103
1137
  stateEntries: null,
@@ -1230,7 +1264,7 @@ function sectionsFor(node: JxMutableNode, path: JxPath): ContentSectionView[] {
1230
1264
  const tagName = displayTagName(node.tagName) || "div";
1231
1265
  const isCustomInstance = displayTagName(node.tagName).includes("-");
1232
1266
  const attrs = node.attributes || {};
1233
- const mapSignals = mapSignalsFor(path);
1267
+ const mapSignals = mapSignalsFor(doc, path);
1234
1268
  const bindableSignals = bindableSignalNames(doc);
1235
1269
 
1236
1270
  // ── Which attributes this tag has, by section ──
@@ -1276,7 +1310,7 @@ function sectionsFor(node: JxMutableNode, path: JxPath): ContentSectionView[] {
1276
1310
  const sections: ContentSectionView[] = [
1277
1311
  sectionView("__element", "Element", isInspectorSectionOpen("__element", false), [], 0),
1278
1312
  ];
1279
- sections[0]!.rows = elementRows(node, path, tagName, mapSignals, bindableSignals);
1313
+ sections[0]!.rows = elementRows(node, path, tagName, bindableSignals);
1280
1314
 
1281
1315
  if (isCustomInstance) {
1282
1316
  const definitionOfThis = isComponentDefinitionOpen(node);
@@ -1366,11 +1400,90 @@ function draftKey(path: JxPath, name: string): string {
1366
1400
  return `prop:${path.join("/")}:${name}`;
1367
1401
  }
1368
1402
 
1403
+ /**
1404
+ * What a write to the content row leaves in `children`: the template to hold, or `null` for none.
1405
+ *
1406
+ * `$children` is a list of nodes, so it cannot be held in `textContent`, which would draw it as
1407
+ * `[object Object]`. Picking it, or typing a template that reads it, moves the binding to the
1408
+ * computed-children form the compiler already expands (spec.md §8.4). A template the row already
1409
+ * held there that names no such field (`${state.entry.$children}`) stays there as it is edited.
1410
+ */
1411
+ function computedChildrenFrom(
1412
+ value: JsonValue | undefined,
1413
+ nodeFields: readonly RepeaterField[],
1414
+ keepsTemplate: boolean,
1415
+ ): string | null {
1416
+ if (isRef(value)) {
1417
+ const field = nodeFields.find((f) => f.pointer === value.$ref);
1418
+ return field ? childrenTemplate(field) : null;
1419
+ }
1420
+ if (!isTemplateString(value)) {
1421
+ return null;
1422
+ }
1423
+ return keepsTemplate || fieldOfChildrenTemplate(value, nodeFields) ? value : null;
1424
+ }
1425
+
1426
+ /**
1427
+ * The Text Content row, which is also where an element's child nodes are bound.
1428
+ *
1429
+ * One row for two properties, because they are one question: what fills this element. A node with
1430
+ * computed children has no text to show, and it used to draw this row empty and on Fixed value, so
1431
+ * the binding could not be seen and typing here wrote text the compiled page never drew.
1432
+ */
1433
+ function contentRow(
1434
+ node: JxMutableNode,
1435
+ path: JxPath,
1436
+ bindableSignals: string[],
1437
+ propertyChip: (name: string, human: string) => FieldProvenance,
1438
+ ): ContentRowView {
1439
+ const doc = activeTab.value!.doc.document;
1440
+ const nodeFields = repeaterItemFields(doc, path, projectState?.projectConfig, {
1441
+ withNodes: true,
1442
+ }).filter((field) => field.holdsNodes);
1443
+ const computed = isTemplateString(node.children) ? node.children : null;
1444
+ const bound = computed === null ? undefined : fieldOfChildrenTemplate(computed, nodeFields);
1445
+ const value = bound ? { $ref: bound.pointer } : (computed ?? node.textContent);
1446
+ const write = (next?: JsonValue): void => {
1447
+ const template = computedChildrenFrom(next, nodeFields, computed !== null && !bound);
1448
+ transactDoc(activeTab.value, (t) => {
1449
+ if (template === null || node.textContent !== undefined) {
1450
+ mutateUpdateProperty(t, path, "textContent", template === null ? next : undefined);
1451
+ }
1452
+ if (template !== computed) {
1453
+ mutateUpdateProperty(t, path, "children", template ?? undefined);
1454
+ }
1455
+ });
1456
+ };
1457
+
1458
+ const textDraft = draft(draftKey(path, "textContent"));
1459
+ const text = blankRow(
1460
+ "prop|textContent",
1461
+ "textContent",
1462
+ computed === null ? "Text Content" : "Content",
1463
+ );
1464
+ text.kind = "textarea";
1465
+ text.value = getFieldValue(textDraft, isString(value) ? value : "");
1466
+ const plan: RowPlan = { commit: write, debounceMs: LIVE_PREVIEW, draft: textDraft };
1467
+ applyLadder(text, plan, {
1468
+ caps: "textProperty",
1469
+ extraSignals: mapSignalsFor(doc, path, { withNodes: true }),
1470
+ fieldKey: `prop|${path.join("/")}|textContent`,
1471
+ onChange: write,
1472
+ stateDefs: bindableSignals,
1473
+ value,
1474
+ });
1475
+ if (computed !== null) {
1476
+ applyChip(text, plan, "textContent", propertyChip("children", "content"));
1477
+ } else if (node.textContent !== undefined) {
1478
+ applyChip(text, plan, "textContent", propertyChip("textContent", "text"));
1479
+ }
1480
+ return withPlan(text, plan);
1481
+ }
1482
+
1369
1483
  function elementRows(
1370
1484
  node: JxMutableNode,
1371
1485
  path: JxPath,
1372
1486
  tagName: string,
1373
- mapSignals: SurfaceOption[] | null,
1374
1487
  bindableSignals: string[],
1375
1488
  ): ContentRowView[] {
1376
1489
  const chosenTag = isTagExpression(node.tagName) ? node.tagName.$expression : null;
@@ -1459,28 +1572,7 @@ function elementRows(
1459
1572
  rows.push(withPlan(cls.row, cls.plan));
1460
1573
 
1461
1574
  if (!Array.isArray(node.children) || node.children.length === 0) {
1462
- const textDraft = draft(draftKey(path, "textContent"));
1463
- const text = blankRow("prop|textContent", "textContent", "Text Content");
1464
- text.kind = "textarea";
1465
- text.value = getFieldValue(textDraft, isString(node.textContent) ? node.textContent : "");
1466
- const plan: RowPlan = {
1467
- commit: writeProperty("textContent"),
1468
- debounceMs: LIVE_PREVIEW,
1469
- draft: textDraft,
1470
- };
1471
- applyLadder(text, plan, {
1472
- caps: "textProperty",
1473
- extraSignals: mapSignals,
1474
- fieldKey: `prop|${path.join("/")}|textContent`,
1475
- onChange: (v) =>
1476
- transactDoc(activeTab.value, (t) => mutateUpdateProperty(t, path, "textContent", v)),
1477
- stateDefs: bindableSignals,
1478
- value: node.textContent,
1479
- });
1480
- if (node.textContent !== undefined) {
1481
- applyChip(text, plan, "textContent", propertyChip("textContent", "text"));
1482
- }
1483
- rows.push(withPlan(text, plan));
1575
+ rows.push(contentRow(node, path, bindableSignals, propertyChip));
1484
1576
  }
1485
1577
 
1486
1578
  const hidden = blankRow("prop|hidden", "hidden", "Hidden");
@@ -71,6 +71,7 @@ import type {
71
71
  SiteSectionState,
72
72
  } from "../surfaces/site-panel";
73
73
  import type { SiteComponentRow, SiteMediaRow, SitePageFact } from "./site-model";
74
+ import { isProjectConfigPath } from "@jxsuite/schema/project-file";
74
75
 
75
76
  // ─── The project index ───────────────────────────────────────────────────────
76
77
 
@@ -213,7 +214,7 @@ export function mayDefineComponent(path: string): boolean {
213
214
  }
214
215
  return (
215
216
  path.endsWith(".json") &&
216
- path !== "project.json" &&
217
+ !isProjectConfigPath(path) &&
217
218
  !NON_COMPONENT_DIRS.some((dir) => path.startsWith(dir))
218
219
  );
219
220
  }