@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.
- package/README.md +1 -1
- package/data/elements-meta.json +5 -15
- package/data/html-meta.json +51 -6
- package/dist/chunks/{2020-n6tfwnda.js → 2020-g79n01v9.js} +14 -14
- package/dist/chunks/action-group-sp6tna8v.js +118 -0
- package/dist/chunks/adapter-nj7tnjfn.js +13 -0
- package/dist/chunks/cf-account-picker-qwkm9zrf.js +495 -0
- package/dist/chunks/cf-settings-71bv5zn9.js +20 -0
- package/dist/chunks/color-area-v17df7b8.js +154 -0
- package/dist/chunks/color-field-anwybnkn.js +208 -0
- package/dist/chunks/color-slider-44ez96fc.js +79 -0
- package/dist/chunks/color-xgg3kfw7.js +35 -0
- package/dist/chunks/combobox-5e7jev23.js +236 -0
- package/dist/chunks/components-vj72gqtq.js +20 -0
- package/dist/chunks/content-source-5mex4x1h.js +46 -0
- package/dist/chunks/convert-to-repeater-n64h8znc.js +639 -0
- package/dist/chunks/dialog-s890b918.js +85 -0
- package/dist/chunks/editor-3qhbg51x.js +35 -0
- package/dist/chunks/entry-commands-234hjnqw.js +42 -0
- package/dist/chunks/extension-sections-3hpcambp.js +651 -0
- package/dist/chunks/file-ops-8yhd83xr.js +47 -0
- package/dist/chunks/files-hnxqxtsg.js +72 -0
- package/dist/chunks/format-host-779sfp2n.js +40 -0
- package/dist/chunks/fs-events-b88z7g4h.js +18 -0
- package/dist/chunks/git-panel-xwj8fpab.js +49 -0
- package/dist/chunks/icons-yy10wk9r.js +402 -0
- package/dist/chunks/iframe-entry-15g3n6xv.js +114 -0
- package/dist/chunks/iframe-entry-b27vq81n.js +203 -0
- package/dist/chunks/iframe-entry-nbyxee1y.js +154 -0
- package/dist/chunks/iframe-host-syr0f9sb.js +115 -0
- package/dist/chunks/{index-wcnt3r9g.js → index-mdmyx6s2.js} +11 -11
- package/dist/chunks/index-n4j7qxyr.js +109 -0
- package/dist/chunks/{javascript-q8kpmfah.js → javascript-xvrfrpkb.js} +8 -9
- package/dist/chunks/{jsonMode-dkr040ga.js → jsonMode-tyx2kj6p.js} +526 -527
- package/dist/chunks/jx-validate-j0wxk72w.js +15 -0
- package/dist/chunks/layers-ff81wthn.js +30 -0
- package/dist/chunks/layers-panel-pnnghw12.js +40 -0
- package/dist/chunks/listbox-6v2ac08j.js +44 -0
- package/dist/chunks/lit-html-m244w9nh.js +17 -0
- package/dist/chunks/media-picker-cqhhxhhe.js +33 -0
- package/dist/chunks/menu-9p2w46gj.js +15 -0
- package/dist/chunks/menu-q99sy9rf.js +285 -0
- package/dist/chunks/{monaco-binding-rj6xpx9y.js → monaco-binding-sw6en4dw.js} +12 -15
- package/dist/chunks/monaco-buffer-bhy3hb6m.js +24 -0
- package/dist/chunks/{monaco-setup-vx9bhp1d.js → monaco-setup-8r62fj6y.js} +27 -37
- package/dist/chunks/notify-628t0anh.js +21 -0
- package/dist/chunks/number-field-5cndyft8.js +72 -0
- package/dist/chunks/parse-0zx14y15.js +10 -0
- package/dist/chunks/platform-ts9cg1a4.js +15 -0
- package/dist/chunks/popover-47z3ytva.js +25 -0
- package/dist/chunks/publish-panel-5zt840sy.js +27 -0
- package/dist/chunks/quick-search-3748564r.js +45 -0
- package/dist/chunks/references-qvvxej7h.js +26 -0
- package/dist/chunks/right-panel-eccz92qt.js +45 -0
- package/dist/chunks/section-registry-5pq32t0q.js +25 -0
- package/dist/chunks/select-2r16ez41.js +49 -0
- package/dist/chunks/split-g7nm1rgb.js +191 -0
- package/dist/chunks/studio-0hqbpbd0.js +62 -0
- package/dist/chunks/{studio-kryvsw6j.js → studio-1k325pwf.js} +48 -48
- package/dist/chunks/{studio-b55y6ewx.js → studio-1knfqgy7.js} +58 -58
- package/dist/chunks/{studio-2b946z26.js → studio-1z1kcjv4.js} +3628 -6095
- package/dist/chunks/studio-3773086z.js +111 -0
- package/dist/chunks/{studio-4r1pv6zf.js → studio-38emantv.js} +22 -5
- package/dist/chunks/{studio-x813zp48.js → studio-3rz5md06.js} +30 -30
- package/dist/chunks/{studio-ynxwkrzd.js → studio-4wbb9ct0.js} +1953 -1785
- package/dist/chunks/studio-5te5tqng.js +81 -0
- package/dist/chunks/studio-5wt9nvhd.js +4763 -0
- package/dist/chunks/{studio-p4ngc046.js → studio-6ewqv390.js} +9 -9
- package/dist/chunks/{studio-9ab8gqsm.js → studio-6nzaktvy.js} +7 -7
- package/dist/chunks/studio-6xa21ej9.js +28 -0
- package/dist/chunks/{studio-5ehhackm.js → studio-727xh0e3.js} +19 -19
- package/dist/chunks/{studio-gcye7qka.js → studio-7e11jpa9.js} +21 -21
- package/dist/chunks/{studio-q7v10cg2.js → studio-7r42c6ca.js} +17 -7
- package/dist/chunks/studio-7xnmgv66.js +13615 -0
- package/dist/chunks/studio-8scdew0e.js +258 -0
- package/dist/chunks/{studio-fbf92ckf.js → studio-93zafjcm.js} +275 -163
- package/dist/chunks/{studio-dg7zyt5x.js → studio-98b3z07n.js} +1 -1
- package/dist/chunks/studio-a79k90n9.js +1162 -0
- package/dist/chunks/studio-aj37eer3.js +422 -0
- package/dist/chunks/studio-b0k5tqgp.js +2432 -0
- package/dist/chunks/studio-b4s9eykd.js +2811 -0
- package/dist/chunks/{studio-f3wqjtk5.js → studio-d0300636.js} +5076 -18
- package/dist/chunks/{studio-b9b6t1dg.js → studio-e1f24eff.js} +5664 -5666
- package/dist/chunks/studio-e1pgga3k.js +839 -0
- package/dist/chunks/{studio-g1wdche0.js → studio-esrxgwh6.js} +80 -80
- package/dist/chunks/{studio-q43a5y9h.js → studio-g3jevyjt.js} +16 -16
- package/dist/chunks/studio-gb13095a.js +2806 -0
- package/dist/chunks/{studio-3n4ndrzh.js → studio-h4nvnjvz.js} +303 -303
- package/dist/chunks/{studio-dm19q43r.js → studio-hhvs4sdg.js} +10 -10
- package/dist/chunks/{studio-x4qkdat5.js → studio-js8nv53n.js} +1749 -2481
- package/dist/chunks/{studio-qjy2z9bv.js → studio-jxp12rnj.js} +32 -137
- package/dist/chunks/{studio-trxrabqf.js → studio-k75n7vhp.js} +527 -283
- package/dist/chunks/{studio-q9jd1a60.js → studio-kcgff4r0.js} +15 -18
- package/dist/chunks/studio-kkqkcp3d.js +17668 -0
- package/dist/chunks/studio-mhhyamxf.js +763 -0
- package/dist/chunks/{studio-cft9r4r7.js → studio-nq5yf5mp.js} +22 -22
- package/dist/chunks/{studio-685s0ytq.js → studio-nqgawfks.js} +572 -572
- package/dist/chunks/studio-nzzpvzx9.js +185 -0
- package/dist/chunks/{studio-xrapxwwc.js → studio-p1dw8wfw.js} +55 -29
- package/dist/chunks/{studio-3bqpr0zh.js → studio-pxbgypmg.js} +299 -110
- package/dist/chunks/studio-q0a3yk4n.js +33 -0
- package/dist/chunks/{studio-wcy8trfx.js → studio-q3x06vmj.js} +23 -22
- package/dist/chunks/{studio-6yw4y3my.js → studio-qhhm6hbt.js} +8 -8
- package/dist/chunks/studio-qk4bfsq2.js +362 -0
- package/dist/chunks/{studio-msd847zn.js → studio-qk90fgqm.js} +361 -90
- package/dist/chunks/{studio-jkftxyvz.js → studio-r6nvacqs.js} +5861 -2154
- package/dist/chunks/{studio-53n8bp04.js → studio-r6xmmtfh.js} +1 -1
- package/dist/chunks/{studio-tvgyw9xw.js → studio-rnyf3f8e.js} +83 -250
- package/dist/chunks/studio-rs76k67r.js +12 -0
- package/dist/chunks/studio-sbjyc8bt.js +954 -0
- package/dist/chunks/{studio-1wagb550.js → studio-syg2tadj.js} +11 -11
- package/dist/chunks/{studio-t5wqwzdh.js → studio-tjjk7jvk.js} +1 -1
- package/dist/chunks/studio-tqgryxwg.js +1171 -0
- package/dist/chunks/studio-try13h8c.js +8448 -0
- package/dist/chunks/studio-vfq8qpjt.js +1219 -0
- package/dist/chunks/studio-vhv5tgxm.js +51 -0
- package/dist/chunks/{studio-ahn3pdgx.js → studio-vw7mzp6z.js} +52 -38
- package/dist/chunks/studio-wqpngpsk.js +174 -0
- package/dist/chunks/studio-wqz6dggm.js +1356 -0
- package/dist/chunks/{studio-92ab2jqe.js → studio-y9zedyjn.js} +14 -14
- package/dist/chunks/studio-yxa81f45.js +12 -0
- package/dist/chunks/studio-zdn2g6dt.js +148 -0
- package/dist/chunks/swatch-group-t0n6h2rn.js +139 -0
- package/dist/chunks/tabs-wbp1cdws.js +156 -0
- package/dist/chunks/textfield-abk6qyee.js +73 -0
- package/dist/chunks/toast-780pvfbv.js +27 -0
- package/dist/chunks/toast-host-3sqv46sn.js +116 -0
- package/dist/chunks/toolbar-xbh5bhtr.js +194 -0
- package/dist/chunks/tooltip-eptb614w.js +369 -0
- package/dist/chunks/tree-a73p0pqm.js +367 -0
- package/dist/chunks/{tsMode-76kr53q0.js → tsMode-jy4m86rv.js} +83 -85
- package/dist/chunks/workspace-t55jdjbx.js +69 -0
- package/dist/chunks/{ws-client-6shc5rze.js → ws-client-ny4zc51r.js} +20 -22
- package/dist/iframe-entry.js +1873 -673
- package/dist/studio.js +52757 -36535
- package/dist/workers/editor.worker.js +1040 -1085
- package/dist/workers/json.worker.js +1946 -1991
- package/dist/workers/ts.worker.js +4960 -5005
- package/index.html +1 -0
- package/package.json +18 -52
- package/src/about/about-modal.ts +72 -123
- package/src/browse/library-commands.ts +20 -49
- package/src/browse/library-layouts.ts +87 -218
- package/src/browse/library-pane.ts +406 -438
- package/src/canvas/canvas-helpers.ts +0 -49
- package/src/canvas/canvas-live-render.ts +25 -2
- package/src/canvas/canvas-render.ts +666 -231
- package/src/canvas/canvas-surface.ts +0 -1
- package/src/canvas/canvas-utils.ts +333 -113
- package/src/canvas/dialog-path.ts +76 -0
- package/src/canvas/dialog-state.ts +67 -0
- package/src/canvas/diff-marks.ts +252 -0
- package/src/canvas/diff-toolbar.ts +349 -0
- package/src/canvas/diff-view.ts +123 -0
- package/src/canvas/edit-width-drag.ts +187 -0
- package/src/canvas/edit-width.ts +150 -0
- package/src/canvas/iframe-entry.ts +125 -8
- package/src/canvas/iframe-host.ts +271 -15
- package/src/canvas/iframe-interaction.ts +133 -13
- package/src/canvas/iframe-patch.ts +7 -1
- package/src/canvas/iframe-position.ts +5 -6
- package/src/canvas/iframe-protocol.ts +106 -5
- package/src/canvas/iframe-render.ts +373 -5
- package/src/canvas/iframe-slash.ts +2 -2
- package/src/canvas/iframe-subtree.ts +5 -8
- package/src/canvas/path-mapping.ts +25 -3
- package/src/canvas/popover-path.ts +106 -0
- package/src/canvas/popover-state.ts +155 -0
- package/src/canvas/surface-registry.ts +21 -10
- package/src/collab/presence-chips.ts +51 -73
- package/src/commands/app-commands.ts +10 -0
- package/src/commands/budget.ts +12 -0
- package/src/commands/command-args.ts +404 -6
- package/src/commands/defaults.ts +44 -14
- package/src/commands/live-context.ts +11 -5
- package/src/commands/reference.ts +12 -2
- package/src/commands/registry.ts +177 -10
- package/src/commands/run-reported.ts +120 -0
- package/src/content/entry-commands.ts +16 -23
- package/src/content/entry-editor.ts +144 -101
- package/src/editor/canvas-slash-bridge.ts +2 -2
- package/src/editor/context-menu.ts +49 -200
- package/src/editor/convert-to-component.ts +42 -82
- package/src/editor/convert-to-repeater.ts +132 -173
- package/src/editor/inline-edit.ts +11 -0
- package/src/editor/shortcuts.ts +76 -49
- package/src/editor/slash-menu.ts +150 -157
- package/src/files/components.ts +39 -0
- package/src/files/file-ops.ts +117 -11
- package/src/files/files.ts +944 -660
- package/src/files/json-layout.ts +474 -0
- package/src/files/serialize-document.ts +8 -1
- package/src/format/convert-file.ts +16 -10
- package/src/format/format-host.ts +50 -1
- package/src/github/github-auth.ts +71 -29
- package/src/github/github-publish.ts +57 -65
- package/src/grid/cell-editors.ts +46 -4
- package/src/grid/cell-popovers.ts +65 -62
- package/src/grid/grid-open.ts +86 -64
- package/src/grid/grid-panel.ts +390 -469
- package/src/grid/redirects-grid.ts +40 -44
- package/src/hosting/layout.ts +2 -0
- package/src/i18n/i18n-commands.ts +137 -62
- package/src/media/media-pane.ts +83 -133
- package/src/new-project/add-repo-modal.ts +125 -176
- package/src/new-project/import-tab.ts +181 -252
- package/src/new-project/location-fields.ts +93 -160
- package/src/new-project/new-project-modal.ts +376 -367
- package/src/packages/jxsuite-update.ts +76 -18
- package/src/packages/pull-package-sync.ts +5 -1
- package/src/panels/activity-panel.ts +137 -104
- package/src/panels/ai-chat/chat-markdown.ts +15 -13
- package/src/panels/ai-chat/chat-view.ts +307 -472
- package/src/panels/ai-chat/composer.ts +175 -163
- package/src/panels/ai-chat/sessions-view.ts +31 -62
- package/src/panels/ai-panel.ts +213 -185
- package/src/panels/block-action-bar.ts +444 -645
- package/src/panels/bottom-dock.ts +112 -47
- package/src/panels/chat-panel.ts +19 -14
- package/src/panels/data-explorer.ts +141 -102
- package/src/panels/data-grid.ts +183 -150
- package/src/panels/dnd.ts +75 -20
- package/src/panels/editors.ts +26 -19
- package/src/panels/elements-panel.ts +243 -186
- package/src/panels/empty-state.ts +26 -42
- package/src/panels/events-panel.ts +950 -729
- package/src/panels/formula-workspace.ts +434 -207
- package/src/panels/frontmatter-fields.ts +185 -153
- package/src/panels/frontmatter-panel.ts +411 -187
- package/src/panels/git-diff-open.ts +148 -0
- package/src/panels/git-panel.ts +679 -605
- package/src/panels/head-panel.ts +617 -435
- package/src/panels/i18n-panel.ts +148 -135
- package/src/panels/imports-panel.ts +388 -414
- package/src/panels/jump-bar.ts +175 -147
- package/src/panels/layers-panel.ts +785 -671
- package/src/panels/left-panel.ts +166 -94
- package/src/panels/navigator-panels.ts +1 -1
- package/src/panels/pane-context.ts +529 -677
- package/src/panels/pane-grid.ts +265 -241
- package/src/panels/panel-registry.ts +15 -4
- package/src/panels/problems-panel.ts +181 -99
- package/src/panels/properties-panel.ts +1525 -966
- package/src/panels/provenance.ts +31 -113
- package/src/panels/quick-search.ts +223 -184
- package/src/panels/right-panel.ts +135 -169
- package/src/panels/seo-modal.ts +346 -167
- package/src/panels/settings-menu.ts +114 -661
- package/src/panels/settings-pane.ts +102 -86
- package/src/panels/signals-panel.ts +1318 -1172
- package/src/panels/statement-editor.ts +556 -428
- package/src/panels/style-panel.ts +1367 -630
- package/src/panels/style-utils.ts +36 -32
- package/src/panels/stylebook-doc.ts +30 -73
- package/src/panels/stylebook-layers-panel.ts +202 -109
- package/src/panels/stylebook-panel.ts +124 -88
- package/src/panels/tab-strip.ts +386 -288
- package/src/platforms/cloud.ts +106 -16
- package/src/platforms/devserver.ts +13 -0
- package/src/publish/deploy-checklist.ts +124 -78
- package/src/publish/publish-commands.ts +4 -12
- package/src/publish/publish-panel.ts +134 -328
- package/src/services/a11y-report.ts +100 -43
- package/src/services/ai-command-tools.ts +538 -0
- package/src/services/ai-project-tools.ts +8 -2
- package/src/services/ai-session-store.ts +6 -0
- package/src/services/ai-system-prompt.ts +112 -24
- package/src/services/ai-tools.ts +36 -212
- package/src/services/ai-write-report.ts +174 -0
- package/src/services/document-assistant.ts +59 -8
- package/src/services/idle.ts +16 -14
- package/src/services/live-surfaces.ts +237 -0
- package/src/services/model-uri.ts +75 -0
- package/src/services/notify.ts +63 -6
- package/src/services/popover-report.ts +325 -0
- package/src/services/surface-registry.ts +56 -0
- package/src/services/tool-executor.ts +6 -0
- package/src/settings/contexts-section.ts +301 -299
- package/src/settings/contributed-section.ts +361 -222
- package/src/settings/css-vars-editor.ts +320 -325
- package/src/settings/defs-editor.ts +572 -535
- package/src/settings/dependencies-editor.ts +79 -107
- package/src/settings/extension-commands.ts +379 -0
- package/src/settings/extension-rows.ts +113 -0
- package/src/settings/extensions-section.ts +287 -0
- package/src/settings/general-settings.ts +130 -181
- package/src/settings/head-editor.ts +10 -305
- package/src/settings/locales-section.ts +146 -196
- package/src/settings/preferences-accounts.ts +1 -1
- package/src/settings/preferences-dialog.ts +350 -330
- package/src/settings/project-sections.ts +108 -181
- package/src/settings/schema-field-ui.ts +11 -399
- package/src/settings/section-registry.ts +8 -1
- package/src/settings/settings-document.ts +31 -29
- package/src/shell/tree.ts +42 -87
- package/src/shell.ts +48 -36
- package/src/studio.ts +104 -69
- package/src/style/token-ref.ts +0 -39
- package/src/surfaces/about.json +263 -0
- package/src/surfaces/about.ts +163 -0
- package/src/surfaces/add-repo.json +486 -0
- package/src/surfaces/add-repo.ts +236 -0
- package/src/surfaces/ai-chat.json +2185 -0
- package/src/surfaces/ai-chat.ts +549 -0
- package/src/surfaces/ai-credentials-form.json +332 -0
- package/src/surfaces/ai-credentials-form.ts +151 -0
- package/src/surfaces/ai-managed-connect.json +108 -0
- package/src/surfaces/ai-managed-connect.ts +123 -0
- package/src/surfaces/ai-model-picker.json +114 -0
- package/src/surfaces/ai-model-picker.ts +139 -0
- package/src/surfaces/block-action-bar.json +640 -0
- package/src/surfaces/block-action-bar.ts +368 -0
- package/src/surfaces/bottom-dock.json +154 -0
- package/src/surfaces/bottom-dock.ts +125 -0
- package/src/surfaces/canvas-stage.json +444 -0
- package/src/surfaces/canvas-stage.ts +253 -0
- package/src/surfaces/cf-account-picker.json +300 -0
- package/src/surfaces/cf-account-picker.ts +214 -0
- package/src/surfaces/commandbar.json +985 -0
- package/src/{panels/toolbar.ts → surfaces/commandbar.ts} +417 -391
- package/src/surfaces/convert-repeater.json +309 -0
- package/src/surfaces/convert-repeater.ts +276 -0
- package/src/surfaces/data-actions.json +157 -0
- package/src/surfaces/data-actions.ts +115 -0
- package/src/surfaces/dialog.json +289 -0
- package/src/surfaces/dialog.ts +343 -0
- package/src/surfaces/diff-toolbar.json +266 -0
- package/src/surfaces/diff-toolbar.ts +139 -0
- package/src/surfaces/doc-header.json +739 -0
- package/src/surfaces/doc-header.ts +223 -0
- package/src/surfaces/empty-state.json +152 -0
- package/src/surfaces/empty-state.ts +173 -0
- package/src/surfaces/entry-editor.json +281 -0
- package/src/surfaces/entry-editor.ts +191 -0
- package/src/surfaces/expression-editor.json +977 -0
- package/src/surfaces/expression-editor.ts +266 -0
- package/src/surfaces/files-panel.json +592 -0
- package/src/surfaces/files-panel.ts +301 -0
- package/src/surfaces/formula-palette.json +364 -0
- package/src/surfaces/formula-palette.ts +340 -0
- package/src/surfaces/git-panel.json +1822 -0
- package/src/surfaces/git-panel.ts +272 -0
- package/src/surfaces/github-auth.json +112 -0
- package/src/surfaces/github-auth.ts +139 -0
- package/src/surfaces/github-publish.json +206 -0
- package/src/surfaces/github-publish.ts +169 -0
- package/src/surfaces/grid-cell.json +218 -0
- package/src/surfaces/grid-cell.ts +120 -0
- package/src/surfaces/grid-open.json +147 -0
- package/src/surfaces/grid-open.ts +87 -0
- package/src/surfaces/grid-panel.json +706 -0
- package/src/surfaces/grid-panel.ts +206 -0
- package/src/surfaces/grid-replace.json +179 -0
- package/src/surfaces/grid-replace.ts +89 -0
- package/src/surfaces/grid-views.json +446 -0
- package/src/surfaces/grid-views.ts +172 -0
- package/src/surfaces/inspector-dock.json +187 -0
- package/src/surfaces/inspector-dock.ts +161 -0
- package/src/surfaces/jump-bar.json +243 -0
- package/src/surfaces/jump-bar.ts +129 -0
- package/src/surfaces/jxsuite-update.json +146 -0
- package/src/surfaces/jxsuite-update.ts +149 -0
- package/src/surfaces/library-pane.json +1509 -0
- package/src/surfaces/library-pane.ts +363 -0
- package/src/surfaces/logic-panel.json +1949 -0
- package/src/surfaces/logic-panel.ts +369 -0
- package/src/surfaces/logic-workspace.json +685 -0
- package/src/surfaces/logic-workspace.ts +238 -0
- package/src/surfaces/media-browser.json +303 -0
- package/src/surfaces/media-browser.ts +145 -0
- package/src/surfaces/media-field.json +163 -0
- package/src/surfaces/media-field.ts +116 -0
- package/src/surfaces/media-pane.json +513 -0
- package/src/surfaces/media-pane.ts +197 -0
- package/src/surfaces/menu.json +232 -0
- package/src/surfaces/menu.ts +375 -0
- package/src/surfaces/navigator-dock.json +113 -0
- package/src/surfaces/navigator-dock.ts +130 -0
- package/src/surfaces/new-project.json +1654 -0
- package/src/surfaces/new-project.ts +411 -0
- package/src/surfaces/palette.json +535 -0
- package/src/surfaces/pane-context.json +1497 -0
- package/src/surfaces/pane-context.ts +513 -0
- package/src/surfaces/pane-grid.json +188 -0
- package/src/surfaces/pane-grid.ts +250 -0
- package/src/surfaces/panel-activity.json +563 -0
- package/src/surfaces/panel-activity.ts +187 -0
- package/src/surfaces/panel-data.json +141 -0
- package/src/surfaces/panel-data.ts +156 -0
- package/src/surfaces/panel-deploy-checklist.json +283 -0
- package/src/surfaces/panel-deploy-checklist.ts +175 -0
- package/src/surfaces/panel-elements.json +367 -0
- package/src/surfaces/panel-elements.ts +175 -0
- package/src/surfaces/panel-i18n.json +569 -0
- package/src/surfaces/panel-i18n.ts +212 -0
- package/src/surfaces/panel-imports.json +780 -0
- package/src/surfaces/panel-imports.ts +209 -0
- package/src/surfaces/panel-outline.json +734 -0
- package/src/surfaces/panel-outline.ts +326 -0
- package/src/surfaces/panel-page.json +1017 -0
- package/src/surfaces/panel-page.ts +240 -0
- package/src/surfaces/panel-problems.json +497 -0
- package/src/surfaces/panel-problems.ts +157 -0
- package/src/surfaces/panel-signals.json +1973 -0
- package/src/surfaces/panel-signals.ts +436 -0
- package/src/surfaces/panel-stylebook-layers.json +317 -0
- package/src/surfaces/panel-stylebook-layers.ts +155 -0
- package/src/surfaces/preferences.json +850 -0
- package/src/surfaces/preferences.ts +332 -0
- package/src/surfaces/progress-modal.json +110 -0
- package/src/surfaces/progress-modal.ts +150 -0
- package/src/surfaces/properties-panel.json +1578 -0
- package/src/surfaces/properties-panel.ts +337 -0
- package/src/surfaces/publish.json +945 -0
- package/src/surfaces/publish.ts +392 -0
- package/src/surfaces/push-plan.json +110 -0
- package/src/surfaces/push-plan.ts +87 -0
- package/src/surfaces/rail.json +255 -0
- package/src/surfaces/rail.ts +206 -0
- package/src/surfaces/reference-field.json +191 -0
- package/src/surfaces/reference-field.ts +168 -0
- package/src/surfaces/schema-builder.json +1011 -0
- package/src/surfaces/schema-builder.ts +181 -0
- package/src/surfaces/schema-form.json +1013 -0
- package/src/surfaces/schema-form.ts +287 -0
- package/src/surfaces/secret-field.json +60 -0
- package/src/surfaces/secret-field.ts +146 -0
- package/src/surfaces/seo.json +932 -0
- package/src/surfaces/seo.ts +334 -0
- package/src/surfaces/settings-contexts.json +538 -0
- package/src/surfaces/settings-contexts.ts +196 -0
- package/src/surfaces/settings-contributed.json +489 -0
- package/src/surfaces/settings-contributed.ts +199 -0
- package/src/surfaces/settings-css-vars.json +820 -0
- package/src/surfaces/settings-css-vars.ts +199 -0
- package/src/surfaces/settings-defs.json +1363 -0
- package/src/surfaces/settings-defs.ts +232 -0
- package/src/surfaces/settings-deploy.json +108 -0
- package/src/surfaces/settings-deploy.ts +135 -0
- package/src/surfaces/settings-extensions.json +486 -0
- package/src/surfaces/settings-extensions.ts +201 -0
- package/src/surfaces/settings-head.json +779 -0
- package/src/surfaces/settings-head.ts +397 -0
- package/src/surfaces/settings-locales.json +420 -0
- package/src/surfaces/settings-locales.ts +137 -0
- package/src/surfaces/settings-overview.json +423 -0
- package/src/surfaces/settings-overview.ts +218 -0
- package/src/surfaces/settings-packages.json +549 -0
- package/src/surfaces/settings-packages.ts +158 -0
- package/src/surfaces/settings-pane.json +147 -0
- package/src/surfaces/settings-pane.ts +150 -0
- package/src/surfaces/settings-rawjson.json +104 -0
- package/src/surfaces/settings-rawjson.ts +107 -0
- package/src/surfaces/shell.json +99 -0
- package/src/surfaces/shell.ts +30 -0
- package/src/surfaces/slash-menu.json +180 -0
- package/src/surfaces/slash-menu.ts +272 -0
- package/src/surfaces/statements.json +607 -0
- package/src/surfaces/statements.ts +238 -0
- package/src/surfaces/statusbar.json +94 -0
- package/src/{panels → surfaces}/statusbar.ts +164 -90
- package/src/surfaces/style-panel.json +1643 -0
- package/src/surfaces/style-panel.ts +338 -0
- package/src/surfaces/stylebook-chrome.json +122 -0
- package/src/surfaces/stylebook-chrome.ts +122 -0
- package/src/surfaces/tab-strip.json +448 -0
- package/src/surfaces/tab-strip.ts +200 -0
- package/src/surfaces/target-line.json +419 -0
- package/src/surfaces/target-line.ts +349 -0
- package/src/surfaces/toasts.json +189 -0
- package/src/surfaces/welcome.json +571 -0
- package/src/surfaces/welcome.ts +338 -0
- package/src/tabs/project-config.ts +111 -21
- package/src/tabs/tab.ts +52 -1
- package/src/types.ts +22 -0
- package/src/ui/ai-credentials-form.ts +104 -100
- package/src/ui/ai-managed-connect.ts +54 -36
- package/src/ui/ai-model-picker.ts +75 -47
- package/src/ui/cf-account-picker.ts +76 -141
- package/src/ui/color-selector.ts +80 -301
- package/src/ui/dynamic-slot.ts +43 -283
- package/src/ui/expression-editor.ts +797 -576
- package/src/ui/field-input.ts +8 -181
- package/src/ui/form-controls.ts +838 -366
- package/src/ui/formula-chips.ts +80 -85
- package/src/ui/kit.ts +33 -0
- package/src/ui/layers.ts +296 -593
- package/src/ui/media-picker.ts +203 -284
- package/src/ui/panel-resize.ts +251 -64
- package/src/ui/popover-surface.ts +153 -0
- package/src/ui/progress-modal.ts +22 -60
- package/src/ui/regions.ts +13 -7
- package/src/ui/schema-form.ts +906 -440
- package/src/ui/surface.ts +122 -0
- package/src/ui/unit-selector.ts +15 -131
- package/src/ui/value-source.ts +14 -0
- package/src/ui/virtual-window.ts +88 -9
- package/src/utils/canvas-media.ts +72 -0
- package/src/utils/edit-display.ts +7 -2
- package/src/utils/element-selectors.ts +69 -0
- package/src/utils/link-target.ts +2 -2
- package/src/utils/studio-utils.ts +0 -10
- package/src/view.ts +15 -0
- package/src/workspace/pane-derive.ts +36 -14
- package/src/workspace/workspace.ts +75 -6
- package/styles/canvas.css +72 -467
- package/styles/forced-colors.css +48 -82
- package/styles/forced-colors.json +41 -0
- package/styles/inspector.css +2 -1301
- package/styles/overlays.css +96 -2823
- package/styles/panels.css +6 -1423
- package/styles/shell-frame.css +272 -0
- package/styles/shell-frame.json +227 -0
- package/styles/shell.css +0 -1568
- package/styles/tokens.css +65 -136
- package/styles/tokens.json +84 -0
- package/dist/chunks/Overlay.dev-etqjwyf9.js +0 -23
- package/dist/chunks/adapter-jkcyhgtr.js +0 -14
- package/dist/chunks/cf-account-picker-en86v7x7.js +0 -163
- package/dist/chunks/cf-settings-45fgyx8y.js +0 -21
- package/dist/chunks/components-zmx10t62.js +0 -21
- package/dist/chunks/content-source-48xg86wj.js +0 -49
- package/dist/chunks/convert-to-repeater-43tfn7cs.js +0 -268
- package/dist/chunks/editor-cptq5jp1.js +0 -37
- package/dist/chunks/entry-commands-e63f1xc0.js +0 -46
- package/dist/chunks/extension-sections-txv08xxg.js +0 -311
- package/dist/chunks/file-ops-wbh4hhsx.js +0 -48
- package/dist/chunks/files-3kfkwhk4.js +0 -93
- package/dist/chunks/flow-qbfgrpeh.js +0 -634
- package/dist/chunks/focus-trap.esm-9t2drp0g.js +0 -1025
- package/dist/chunks/focus-visible-6eg70z18.js +0 -167
- package/dist/chunks/format-host-w16q81tf.js +0 -43
- package/dist/chunks/fs-events-hrrg328g.js +0 -21
- package/dist/chunks/git-panel-532whr1y.js +0 -59
- package/dist/chunks/index-zqxmqxmh.js +0 -109
- package/dist/chunks/jx-validate-8er6gbxy.js +0 -19
- package/dist/chunks/layers-panel-07hm7g5j.js +0 -67
- package/dist/chunks/layers-q75tj2mf.js +0 -45
- package/dist/chunks/lit-html-j055mkh8.js +0 -22
- package/dist/chunks/monaco-buffer-dqw0xxpx.js +0 -25
- package/dist/chunks/notify-yjm79n4s.js +0 -30
- package/dist/chunks/parse-24d0gay3.js +0 -19
- package/dist/chunks/platform-ba1j1gbh.js +0 -16
- package/dist/chunks/publish-panel-v9st96ja.js +0 -29
- package/dist/chunks/quick-search-zccjp6gv.js +0 -66
- package/dist/chunks/references-3qx8fkyp.js +0 -29
- package/dist/chunks/right-panel-vm3b6kwy.js +0 -46
- package/dist/chunks/section-registry-sx1ykvkw.js +0 -30
- package/dist/chunks/sp-overlay.dev-qgvhnxf4.js +0 -18
- package/dist/chunks/sp-popover.dev-n2etmqdp.js +0 -9
- package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +0 -15
- package/dist/chunks/sp-tooltip.dev-4sastdnw.js +0 -15
- package/dist/chunks/sp-tray.dev-tkbscm06.js +0 -199
- package/dist/chunks/studio-0a5qrh01.js +0 -3
- package/dist/chunks/studio-0bq6eh7v.js +0 -12
- package/dist/chunks/studio-18j3bmcz.js +0 -14
- package/dist/chunks/studio-1ngbejft.js +0 -181
- package/dist/chunks/studio-4b5z1x43.js +0 -424
- package/dist/chunks/studio-4n2b5gzb.js +0 -357
- package/dist/chunks/studio-4rzyrtjz.js +0 -426
- package/dist/chunks/studio-5fx3g9jg.js +0 -4325
- package/dist/chunks/studio-5myknm0a.js +0 -1903
- package/dist/chunks/studio-7afd21nw.js +0 -67
- package/dist/chunks/studio-7nte2hj4.js +0 -613
- package/dist/chunks/studio-8jpzqk5s.js +0 -128
- package/dist/chunks/studio-8q517dfr.js +0 -24
- package/dist/chunks/studio-950k0dx6.js +0 -6939
- package/dist/chunks/studio-9n4mrmnb.js +0 -184
- package/dist/chunks/studio-a0tswy0w.js +0 -58
- package/dist/chunks/studio-ahnzd9rg.js +0 -23
- package/dist/chunks/studio-bgxt93ga.js +0 -62
- package/dist/chunks/studio-cmb03bg5.js +0 -13890
- package/dist/chunks/studio-d1ks410h.js +0 -175
- package/dist/chunks/studio-ef49pnzx.js +0 -387
- package/dist/chunks/studio-eqq1w638.js +0 -113
- package/dist/chunks/studio-ew0djmhg.js +0 -341
- package/dist/chunks/studio-gfgb8pe6.js +0 -1652
- package/dist/chunks/studio-h4rxscfc.js +0 -10
- package/dist/chunks/studio-jc994dnr.js +0 -1066
- package/dist/chunks/studio-kyh9avvv.js +0 -28
- package/dist/chunks/studio-m3hwn7y1.js +0 -120
- package/dist/chunks/studio-mtfnaqs9.js +0 -659
- package/dist/chunks/studio-n6j9t10q.js +0 -318
- package/dist/chunks/studio-n8b9wqwy.js +0 -291
- package/dist/chunks/studio-qcy05dp8.js +0 -1743
- package/dist/chunks/studio-qq6jym04.js +0 -53
- package/dist/chunks/studio-rdx8jcxy.js +0 -83
- package/dist/chunks/studio-rn9ta2mz.js +0 -111
- package/dist/chunks/studio-t7qb9db8.js +0 -665
- package/dist/chunks/studio-tmc5emrw.js +0 -8
- package/dist/chunks/studio-z6nw2ek2.js +0 -18
- package/dist/chunks/studio-zczps70q.js +0 -378
- package/dist/chunks/studio-zqazsct2.js +0 -1823
- package/dist/chunks/workspace-fjx52ryv.js +0 -89
- package/src/panels/activity-bar.ts +0 -251
- package/src/panels/panel-scheduler.ts +0 -219
- package/src/panels/style-inputs.ts +0 -237
- package/src/panels/target-line.ts +0 -274
- package/src/panels/welcome-screen.ts +0 -346
- package/src/ui/button-group.ts +0 -102
- package/src/ui/choice-field.ts +0 -72
- package/src/ui/field-row.ts +0 -249
- package/src/ui/formula-palette.ts +0 -213
- package/src/ui/icons.ts +0 -52
- package/src/ui/jx-theme.ts +0 -130
- package/src/ui/spectrum.ts +0 -340
- package/src/ui/value-selector.ts +0 -174
- package/src/ui/widgets.ts +0 -172
- /package/dist/chunks/{javascript-s56a8dpk.css → javascript-4abzjcy9.css} +0 -0
- /package/dist/chunks/{jsonMode-zr9sw0c7.css → jsonMode-vvmpmk34.css} +0 -0
- /package/dist/chunks/{monaco-setup-pav68602.css → monaco-setup-8vbyqnhr.css} +0 -0
- /package/dist/chunks/{tsMode-ef187txx.css → tsMode-hnxxb73h.css} +0 -0
|
@@ -1,13 +1,27 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* The Data panel's flow — what a state entry IS, and every transaction that edits one.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* The markup is `surfaces/panel-signals.json`, mounted by `surfaces/panel-signals.ts`. What is left
|
|
6
|
+
* here is the half a document cannot hold: which category an entry falls in, what its badge and its
|
|
7
|
+
* one-line summary say, WHICH CONTROL each field of a `$prototype` gets, what a commit is worth,
|
|
8
|
+
* what a rename collides with, and the four foreign surfaces that arrive as islands.
|
|
9
|
+
*
|
|
10
|
+
* **The field list is a projection.** A `Request` entry and an `IndexedDB` entry differ only in the
|
|
11
|
+
* rows {@link signalsView} hands over, so a new prototype is a case here and no markup at all —
|
|
12
|
+
* which is why `renderDataSourceFields`, `renderFunctionFields`, `renderParameterEditorTemplate`
|
|
13
|
+
* and `renderEmitsEditorTemplate` are gone rather than moved: each was a template per shape, and
|
|
14
|
+
* there is one shape now.
|
|
15
|
+
*
|
|
16
|
+
* **A control hands back names, and this module looks up what they mean.** Every field is
|
|
17
|
+
* registered as a {@link FieldPlan} under its entry and key while the view is built, so
|
|
18
|
+
* `commitField("$items", "url", "/api")` finds the writer that knows `url` is a `Request`'s URL.
|
|
19
|
+
* Nothing crosses the seam but strings.
|
|
20
|
+
*
|
|
21
|
+
* @docs studio/logic/data
|
|
6
22
|
*/
|
|
7
23
|
|
|
8
|
-
import { html, nothing } from "lit-html";
|
|
9
24
|
import { displayTagName } from "@jxsuite/schema/guards";
|
|
10
|
-
import { classMap } from "lit-html/directives/class-map.js";
|
|
11
25
|
import { dynamicRouteParams } from "../page-params";
|
|
12
26
|
import { projectState } from "../state";
|
|
13
27
|
import type { JsonValue } from "../types";
|
|
@@ -19,31 +33,26 @@ import {
|
|
|
19
33
|
mutateUpdateDef,
|
|
20
34
|
transactDoc,
|
|
21
35
|
} from "../tabs/transact";
|
|
22
|
-
import {
|
|
23
|
-
import { rawTextArea, spTextField } from "../ui/field-input";
|
|
24
|
-
import {
|
|
25
|
-
expressionHint,
|
|
26
|
-
isActionExpression,
|
|
27
|
-
renderExpressionEditor,
|
|
28
|
-
} from "../ui/expression-editor";
|
|
29
|
-
import { renderEmptyState } from "./empty-state";
|
|
36
|
+
import { expressionHint, isActionExpression, mountExpressionEditor } from "../ui/expression-editor";
|
|
30
37
|
import {
|
|
31
38
|
dataTypeLabel,
|
|
32
39
|
expandedDataRows,
|
|
33
40
|
isDataRowExpanded,
|
|
34
|
-
|
|
41
|
+
paintDataTree,
|
|
35
42
|
setDataRowExpanded,
|
|
36
43
|
unwrapSignal,
|
|
37
44
|
} from "./data-explorer";
|
|
45
|
+
import { disposeDetachedDataTrees } from "../surfaces/panel-data";
|
|
38
46
|
import { openLogicTarget } from "./formula-workspace";
|
|
39
47
|
import { bindableSignalNames } from "./properties-panel";
|
|
40
|
-
import {
|
|
48
|
+
import { mountStatementEditor } from "./statement-editor";
|
|
41
49
|
import { NAVIGATOR_STATEMENTS_REGION } from "../ui/regions";
|
|
42
50
|
import { livePreviewExpression } from "../services/live-preview";
|
|
43
|
-
import {
|
|
51
|
+
import { mountMediaPicker } from "../ui/media-picker";
|
|
44
52
|
import { renderOnly } from "../store";
|
|
45
|
-
import {
|
|
53
|
+
import { mountSchemaForm } from "../ui/schema-form";
|
|
46
54
|
import { resolveContextPointer } from "../services/context-resolver";
|
|
55
|
+
import { mountSignalsSurface } from "../surfaces/panel-signals";
|
|
47
56
|
import type { JsonSchema } from "../ui/schema-form";
|
|
48
57
|
import type { TabUi } from "../tabs/tab";
|
|
49
58
|
import type {
|
|
@@ -56,9 +65,21 @@ import type {
|
|
|
56
65
|
import { fetchPluginSchema, pluginSchemaCache } from "../services/code-services";
|
|
57
66
|
import { getExtensions, loadExtensions } from "../format/format-host";
|
|
58
67
|
import { optionalStringArg, stringProperty } from "../commands/command-args";
|
|
59
|
-
import type { TemplateResult } from "lit-html";
|
|
60
68
|
import type { AnyCommand, CommandRegistry } from "../commands/registry";
|
|
61
69
|
import { isMediaFormat } from "../utils/studio-utils";
|
|
70
|
+
import type {
|
|
71
|
+
SignalCategoryView,
|
|
72
|
+
SignalCellRowView,
|
|
73
|
+
SignalCellView,
|
|
74
|
+
SignalChipView,
|
|
75
|
+
SignalFieldKind,
|
|
76
|
+
SignalFieldView,
|
|
77
|
+
SignalOptionGroup,
|
|
78
|
+
SignalRowView,
|
|
79
|
+
SignalsActions,
|
|
80
|
+
SignalsSurfaceHandle,
|
|
81
|
+
SignalsView,
|
|
82
|
+
} from "../surfaces/panel-signals";
|
|
62
83
|
|
|
63
84
|
interface SignalsPanelState {
|
|
64
85
|
document: JxMutableNode;
|
|
@@ -71,13 +92,12 @@ interface SignalsPanelState {
|
|
|
71
92
|
}
|
|
72
93
|
|
|
73
94
|
/**
|
|
74
|
-
* What the
|
|
95
|
+
* What the Data panel's surface needs from its host: a repaint, and the one verb a repaint is not.
|
|
75
96
|
*
|
|
76
97
|
* It carried `renderCanvas` and `updateSession` too, and both are gone for the same reason. The
|
|
77
|
-
* canvas hook lost its last caller when the takeovers went
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
* an invitation to write the wrong thing through it.
|
|
98
|
+
* canvas hook lost its last caller when the takeovers went; the session writer lost its two when
|
|
99
|
+
* the Logic buttons started going through `openLogicTarget`, which addresses the focused tab
|
|
100
|
+
* itself. A ctx field with no reader is an invitation to write the wrong thing through it.
|
|
81
101
|
*/
|
|
82
102
|
interface SignalsPanelCtx {
|
|
83
103
|
renderLeftPanel: () => void;
|
|
@@ -133,7 +153,7 @@ export interface SignalDef {
|
|
|
133
153
|
let renameError: { name: string; message: string } | null = null;
|
|
134
154
|
|
|
135
155
|
/** Track which functions have the advanced param editor open. */
|
|
136
|
-
const advancedParamOpen = new Set();
|
|
156
|
+
const advancedParamOpen = new Set<string>();
|
|
137
157
|
|
|
138
158
|
/** Default templates for creating new signal definitions. */
|
|
139
159
|
const DEF_TEMPLATES = {
|
|
@@ -169,6 +189,9 @@ const STUDIO_RESERVED_KEYS = new Set([
|
|
|
169
189
|
"emits",
|
|
170
190
|
]);
|
|
171
191
|
|
|
192
|
+
/** How long a typed value waits before it is written, where typing commits at all. */
|
|
193
|
+
const DEBOUNCE_MS = 500;
|
|
194
|
+
|
|
172
195
|
// ─── Signals / defs helpers ──────────────────────────────────────────────────
|
|
173
196
|
|
|
174
197
|
/**
|
|
@@ -196,7 +219,7 @@ export function extensionStateClasses(): {
|
|
|
196
219
|
|
|
197
220
|
/**
|
|
198
221
|
* View a state entry through the panel's flattened editing lens. Naked primitive and array entries
|
|
199
|
-
* surface as an empty view — the
|
|
222
|
+
* surface as an empty view — the builders guard every field access.
|
|
200
223
|
*
|
|
201
224
|
* @param {import("@jxsuite/schema/types").JxStateDefinition} def
|
|
202
225
|
* @returns {SignalDef}
|
|
@@ -383,11 +406,11 @@ export function resolveDefaultForCanvas(
|
|
|
383
406
|
}
|
|
384
407
|
// Computed → expression indicator
|
|
385
408
|
if (def.$compute) {
|
|
386
|
-
return
|
|
409
|
+
return `ƒ(${defName})`;
|
|
387
410
|
}
|
|
388
411
|
// Request → URL hint
|
|
389
412
|
if (def.$prototype === "Request") {
|
|
390
|
-
return
|
|
413
|
+
return `⟳ ${def.url || "fetch"}`;
|
|
391
414
|
}
|
|
392
415
|
// Storage → use default or key
|
|
393
416
|
if (def.$prototype === "LocalStorage" || def.$prototype === "SessionStorage") {
|
|
@@ -405,40 +428,6 @@ export function resolveDefaultForCanvas(
|
|
|
405
428
|
return `{${defName}}`;
|
|
406
429
|
}
|
|
407
430
|
|
|
408
|
-
// ─── Simple field row ────────────────────────────────────────────────────────
|
|
409
|
-
|
|
410
|
-
/**
|
|
411
|
-
* Simple field row for signal editors — vertical stacked layout.
|
|
412
|
-
*
|
|
413
|
-
* `error` paints the row invalid and prints the message in a `role="alert"` line, which is how a
|
|
414
|
-
* refused rename says so instead of silently keeping the old name.
|
|
415
|
-
*/
|
|
416
|
-
export function signalFieldRow(
|
|
417
|
-
label: string,
|
|
418
|
-
value: string,
|
|
419
|
-
onChange: (value: string) => void,
|
|
420
|
-
error?: string | undefined,
|
|
421
|
-
) {
|
|
422
|
-
return renderFieldRow({
|
|
423
|
-
prop: label,
|
|
424
|
-
label,
|
|
425
|
-
hasValue: false,
|
|
426
|
-
...(error === undefined ? {} : { error }),
|
|
427
|
-
// CommitMode "blur": signal fields (rename, src, etc.) commit on blur/Enter only — a debounced
|
|
428
|
-
// Mid-typing commit would, e.g., rename the signal on every keystroke pause.
|
|
429
|
-
widget: spTextField(
|
|
430
|
-
`sig:${label}`,
|
|
431
|
-
value,
|
|
432
|
-
(v: string) => {
|
|
433
|
-
if (v !== value) {
|
|
434
|
-
onChange(v);
|
|
435
|
-
}
|
|
436
|
-
},
|
|
437
|
-
{ commitMode: "blur" },
|
|
438
|
-
),
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
|
|
442
431
|
/** Normalize a parameter entry to a CEM object. */
|
|
443
432
|
export function normParam(p: string | CemParameter): CemParameter {
|
|
444
433
|
return typeof p === "string" ? { name: p } : p;
|
|
@@ -455,1092 +444,908 @@ function cemTypeText(type: JsonValue | undefined): string {
|
|
|
455
444
|
return "";
|
|
456
445
|
}
|
|
457
446
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
* by the "+ Add…" picker and the panel's empty state, so both create the same thing.
|
|
463
|
-
*/
|
|
464
|
-
function addTemplateDef(type: string, S: SignalsPanelState, ctx: SignalsPanelCtx) {
|
|
465
|
-
const template = DEF_TEMPLATES[type];
|
|
466
|
-
if (!template) {
|
|
467
|
-
return;
|
|
447
|
+
/** A value as a field shows it: JSON for a structure, the string for anything else. */
|
|
448
|
+
function asText(value: unknown, indent = false): string {
|
|
449
|
+
if (value === undefined || value === null) {
|
|
450
|
+
return "";
|
|
468
451
|
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
let i = 1;
|
|
472
|
-
while (S.document.state && S.document.state[n]) {
|
|
473
|
-
n = nameBase + i;
|
|
474
|
-
i += 1;
|
|
452
|
+
if (typeof value === "object") {
|
|
453
|
+
return indent ? JSON.stringify(value, null, 2) : JSON.stringify(value);
|
|
475
454
|
}
|
|
476
|
-
|
|
477
|
-
mutateAddDef(t, n, structuredClone(template) as Record<string, JsonValue>),
|
|
478
|
-
);
|
|
479
|
-
setDataRowExpanded(n, true);
|
|
480
|
-
ctx.renderLeftPanel();
|
|
455
|
+
return String(value);
|
|
481
456
|
}
|
|
482
457
|
|
|
458
|
+
// ─── The field plans ─────────────────────────────────────────────────────────
|
|
459
|
+
|
|
483
460
|
/**
|
|
484
|
-
*
|
|
485
|
-
* @param {SignalsPanelCtx} ctx
|
|
486
|
-
*/
|
|
487
|
-
/**
|
|
488
|
-
* ONE summary slot per row, and the resolved value wins it as soon as there is one.
|
|
489
|
-
*
|
|
490
|
-
* The row wants to say four things about an entry — its category, its name, how it is defined and
|
|
491
|
-
* what it became — and a 240px Navigator fits three. Eliding both summaries to make room produced
|
|
492
|
-
* "recentproje… C… Array(3)": two truncated descriptions and a truncated identity. So the
|
|
493
|
-
* definition hint holds the slot until the canvas resolves a value and then steps aside for it, and
|
|
494
|
-
* the full definition is one click down in fields — which is what the hint was abbreviating.
|
|
461
|
+
* What one field of one entry can be asked to do.
|
|
495
462
|
*
|
|
496
|
-
* The
|
|
497
|
-
*
|
|
498
|
-
*
|
|
499
|
-
* "pending" for the whole list there would be a fact about the panel dressed up as one about data.
|
|
500
|
-
*
|
|
501
|
-
* ENTRIES THAT CANNOT HOLD A VALUE never get the column. A function and an assignment expression
|
|
502
|
-
* are things the page DOES, not things it knows, and they are absent from the resolved scope for
|
|
503
|
-
* exactly that reason — so the value column called all eight of a component's `setFilter` handlers
|
|
504
|
-
* "pending", which reads as "still loading" for something that will never load.
|
|
463
|
+
* The document hands back the entry's name and this field's key and nothing else, so this is where
|
|
464
|
+
* `url` becomes "a `Request`'s URL". Rebuilt on every projection, which is what keeps a writer
|
|
465
|
+
* closed over the def that is actually on screen.
|
|
505
466
|
*/
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
467
|
+
interface FieldPlan {
|
|
468
|
+
/** What the projection put in the control — a commit equal to it is nothing happening. */
|
|
469
|
+
value: string;
|
|
470
|
+
/** What a KEYSTROKE is worth here: nothing (commit on leaving), a debounce, or a write. */
|
|
471
|
+
live: "none" | "debounce" | "commit";
|
|
472
|
+
write?: (value: string) => void;
|
|
473
|
+
check?: (checked: boolean) => void;
|
|
474
|
+
press?: (action: string) => void;
|
|
475
|
+
cell?: (row: string, cell: string, value: string) => void;
|
|
476
|
+
cellCheck?: (row: string, cell: string, checked: boolean) => void;
|
|
477
|
+
dropRow?: (row: string) => void;
|
|
478
|
+
addRow?: () => void;
|
|
479
|
+
addChip?: (value: string) => void;
|
|
480
|
+
dropChip?: (chip: string) => void;
|
|
519
481
|
}
|
|
520
482
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
const entries = Object.entries(defs);
|
|
524
|
-
// What the canvas actually resolved these to. `S.canvas` is the session's canvas record, so this
|
|
525
|
-
// Costs a property read — the panel already had the scope in hand and rendered it in a second
|
|
526
|
-
// Panel anyway.
|
|
527
|
-
const liveScope = (S.canvas?.scope ?? null) as Record<string, unknown> | null;
|
|
528
|
-
const scope = liveScope ?? {};
|
|
529
|
-
// A Refresh is out and the canvas has not answered. Read off the tab, so it survives the repaints
|
|
530
|
-
// Between the press and the `dataScope` that ends it.
|
|
531
|
-
const refreshing = S.canvas?.refreshing === true;
|
|
483
|
+
/** Every field of every entry the last projection drew, by `<entry>` and `<field key>`. */
|
|
484
|
+
const plans = new Map<string, FieldPlan>();
|
|
532
485
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
void loadExtensions();
|
|
486
|
+
/** Every island the last projection drew, by `<entry>/<slot>`. See {@link paintIsland}. */
|
|
487
|
+
const islands = new Map<string, (host: HTMLElement) => void>();
|
|
536
488
|
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
computed: [],
|
|
540
|
-
data: [],
|
|
541
|
-
expression: [],
|
|
542
|
-
function: [],
|
|
543
|
-
state: [],
|
|
544
|
-
} as Record<string, [string, SignalDef][]>;
|
|
545
|
-
for (const [name, def] of entries) {
|
|
546
|
-
groups[defCategory(def)]!.push([name, asSignalDef(def)]);
|
|
547
|
-
}
|
|
548
|
-
|
|
549
|
-
const categories = [
|
|
550
|
-
{ items: groups.state!, key: "state", label: "State" },
|
|
551
|
-
{ items: groups.computed!, key: "computed", label: "Computed" },
|
|
552
|
-
{ items: groups.data!, key: "data", label: "Data" },
|
|
553
|
-
{ items: groups.expression!, key: "expression", label: "Expressions" },
|
|
554
|
-
{ items: groups.function!, key: "function", label: "Functions" },
|
|
555
|
-
];
|
|
489
|
+
/** Debounced writes in flight, by the same key as {@link plans}. */
|
|
490
|
+
const timers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
556
491
|
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
const catTemplates = categories
|
|
561
|
-
.filter((c) => c.items.length > 0)
|
|
562
|
-
.map(
|
|
563
|
-
({ key, label, items }) => html`
|
|
564
|
-
<sp-accordion-item
|
|
565
|
-
label="${label} (${items.length})"
|
|
566
|
-
?open=${!collapsedCats.has(key)}
|
|
567
|
-
@sp-accordion-item-toggle=${() => {
|
|
568
|
-
if (collapsedCats.has(key)) {
|
|
569
|
-
collapsedCats.delete(key);
|
|
570
|
-
} else {
|
|
571
|
-
collapsedCats.add(key);
|
|
572
|
-
}
|
|
573
|
-
ctx.renderLeftPanel();
|
|
574
|
-
}}
|
|
575
|
-
>
|
|
576
|
-
${items.map(([name, def]) => {
|
|
577
|
-
const isExpanded: boolean = isDataRowExpanded(name);
|
|
578
|
-
const live = scope[name];
|
|
579
|
-
return html`
|
|
580
|
-
<div
|
|
581
|
-
class=${classMap({ expanded: isExpanded, "signal-row": true })}
|
|
582
|
-
@click=${() => {
|
|
583
|
-
setDataRowExpanded(name, !isExpanded);
|
|
584
|
-
ctx.renderLeftPanel();
|
|
585
|
-
}}
|
|
586
|
-
>
|
|
587
|
-
<span class="signal-badge ${defCategory(def)}">${defBadgeLabel(def)}</span>
|
|
588
|
-
<span class="signal-name">${name}</span>
|
|
589
|
-
${summary(name, def, live, liveScope !== null)}
|
|
590
|
-
<sp-action-button
|
|
591
|
-
quiet
|
|
592
|
-
size="xs"
|
|
593
|
-
class="signal-del"
|
|
594
|
-
@click=${(e: Event) => {
|
|
595
|
-
e.stopPropagation();
|
|
596
|
-
transactDoc(activeTab.value, (t) => mutateRemoveDef(t, name));
|
|
597
|
-
}}
|
|
598
|
-
>
|
|
599
|
-
<sp-icon-delete slot="icon"></sp-icon-delete>
|
|
600
|
-
</sp-action-button>
|
|
601
|
-
</div>
|
|
602
|
-
${
|
|
603
|
-
isExpanded
|
|
604
|
-
? html`<div class="signal-editor">
|
|
605
|
-
${renderSignalEditorTemplate(S, name, def, ctx)}
|
|
606
|
-
<div class="signal-live">
|
|
607
|
-
<span class="signal-live-label">Resolved to</span>
|
|
608
|
-
<div class="data-tree">
|
|
609
|
-
${renderDataTreeTemplate(unwrapSignal(live), 0, 5, name)}
|
|
610
|
-
</div>
|
|
611
|
-
</div>
|
|
612
|
-
</div>`
|
|
613
|
-
: nothing
|
|
614
|
-
}
|
|
615
|
-
`;
|
|
616
|
-
})}
|
|
617
|
-
</sp-accordion-item>
|
|
618
|
-
`,
|
|
619
|
-
);
|
|
492
|
+
/** Plugin schemas asked for and not yet answered, by cache key. See {@link externalFields}. */
|
|
493
|
+
const schemaRequests = new Set<string>();
|
|
620
494
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
ctx.refreshData && entries.length > 0
|
|
625
|
-
? html`<div class="data-explorer-toolbar">
|
|
626
|
-
<sp-action-button
|
|
627
|
-
quiet
|
|
628
|
-
size="s"
|
|
629
|
-
class="data-refresh-btn"
|
|
630
|
-
?disabled=${refreshing}
|
|
631
|
-
@click=${() => {
|
|
632
|
-
ctx.refreshData?.();
|
|
633
|
-
ctx.renderLeftPanel();
|
|
634
|
-
}}
|
|
635
|
-
>
|
|
636
|
-
${
|
|
637
|
-
refreshing
|
|
638
|
-
? html`<sp-progress-circle
|
|
639
|
-
slot="icon"
|
|
640
|
-
indeterminate
|
|
641
|
-
size="s"
|
|
642
|
-
label="Refreshing"
|
|
643
|
-
></sp-progress-circle>`
|
|
644
|
-
: html`<sp-icon-refresh slot="icon"></sp-icon-refresh>`
|
|
645
|
-
}
|
|
646
|
-
${refreshing ? "Refreshing…" : "Refresh"}
|
|
647
|
-
</sp-action-button>
|
|
648
|
-
</div>`
|
|
649
|
-
: nothing
|
|
650
|
-
}
|
|
651
|
-
<sp-accordion allow-multiple size="s"> ${catTemplates} </sp-accordion>
|
|
652
|
-
${
|
|
653
|
-
entries.length === 0
|
|
654
|
-
? renderEmptyState({
|
|
655
|
-
actions: [{ label: "Add a value", run: () => addTemplateDef("state", S, ctx) }],
|
|
656
|
-
message:
|
|
657
|
-
"Data lives here — values this page can read, compute or fetch, " +
|
|
658
|
-
"ready to bind to any element.",
|
|
659
|
-
})
|
|
660
|
-
: nothing
|
|
661
|
-
}
|
|
662
|
-
<div class="signals-add">
|
|
663
|
-
<sp-picker
|
|
664
|
-
size="s"
|
|
665
|
-
label="+ Add…"
|
|
666
|
-
placeholder="+ Add…"
|
|
667
|
-
@change=${(e: Event) => {
|
|
668
|
-
const type = (e.target as HTMLInputElement).value;
|
|
669
|
-
if (!type) {
|
|
670
|
-
return;
|
|
671
|
-
}
|
|
495
|
+
/** The panel state and host hooks the last render was given — what an action acts against. */
|
|
496
|
+
let panelState: SignalsPanelState | null = null;
|
|
497
|
+
let panelCtx: SignalsPanelCtx | null = null;
|
|
672
498
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
const cls = extensionStateClasses().find((c) => c.name === protoName);
|
|
678
|
-
let n = `$${protoName.charAt(0).toLowerCase()}${protoName.slice(1)}`;
|
|
679
|
-
let i = 1;
|
|
680
|
-
const base = n;
|
|
681
|
-
while (S.document.state && S.document.state[n]) {
|
|
682
|
-
n = base + i;
|
|
683
|
-
i += 1;
|
|
684
|
-
}
|
|
685
|
-
transactDoc(activeTab.value, (t) =>
|
|
686
|
-
mutateAddDef(t, n, {
|
|
687
|
-
$prototype: protoName,
|
|
688
|
-
...cls?.stateDefaults,
|
|
689
|
-
} as Record<string, JsonValue>),
|
|
690
|
-
);
|
|
691
|
-
setDataRowExpanded(n, true);
|
|
692
|
-
ctx.renderLeftPanel();
|
|
693
|
-
return;
|
|
694
|
-
}
|
|
499
|
+
/** One field's identity, as both maps key it. A state entry's name cannot contain a newline. */
|
|
500
|
+
function planKey(signal: string, fieldKey: string): string {
|
|
501
|
+
return `${signal}\n${fieldKey}`;
|
|
502
|
+
}
|
|
695
503
|
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
return;
|
|
730
|
-
}
|
|
504
|
+
/** Register what a field's controls do, and return the field the document draws. */
|
|
505
|
+
function field(
|
|
506
|
+
spec: Partial<SignalFieldView> & { key: string; kind: SignalFieldKind; signal: string },
|
|
507
|
+
plan?: FieldPlan,
|
|
508
|
+
): SignalFieldView {
|
|
509
|
+
const view: SignalFieldView = {
|
|
510
|
+
addLabel: "",
|
|
511
|
+
buttons: [],
|
|
512
|
+
cellRows: [],
|
|
513
|
+
checked: false,
|
|
514
|
+
chips: [],
|
|
515
|
+
error: "",
|
|
516
|
+
footer: "",
|
|
517
|
+
hasError: false,
|
|
518
|
+
hasFooter: false,
|
|
519
|
+
label: "",
|
|
520
|
+
mono: false,
|
|
521
|
+
options: [],
|
|
522
|
+
placeholder: "",
|
|
523
|
+
prop: spec.prop ?? spec.label ?? spec.key,
|
|
524
|
+
rows: "",
|
|
525
|
+
segments: [],
|
|
526
|
+
slot: "",
|
|
527
|
+
span: false,
|
|
528
|
+
value: "",
|
|
529
|
+
...spec,
|
|
530
|
+
};
|
|
531
|
+
plans.set(planKey(spec.signal, spec.key), {
|
|
532
|
+
...(plan ?? { live: "none", value: "" }),
|
|
533
|
+
value: view.value,
|
|
534
|
+
});
|
|
535
|
+
return view;
|
|
536
|
+
}
|
|
731
537
|
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
<sp-menu-item value="formData">FormData</sp-menu-item>
|
|
746
|
-
<sp-menu-item value="external">From a module…</sp-menu-item>
|
|
747
|
-
${
|
|
748
|
-
projectState?.projectConfig?.imports
|
|
749
|
-
? html`<sp-menu-divider></sp-menu-divider>${Object.keys(
|
|
750
|
-
projectState.projectConfig.imports,
|
|
751
|
-
).map(
|
|
752
|
-
(k: string) => html`<sp-menu-item value="import:${k}">${k}</sp-menu-item>`,
|
|
753
|
-
)}`
|
|
754
|
-
: nothing
|
|
755
|
-
}
|
|
756
|
-
${
|
|
757
|
-
extensionStateClasses().length > 0
|
|
758
|
-
? html`<sp-menu-divider></sp-menu-divider>${extensionStateClasses().map(
|
|
759
|
-
(cls) => html`<sp-menu-item value="ext:${cls.name}">${cls.name}</sp-menu-item>`,
|
|
760
|
-
)}`
|
|
761
|
-
: nothing
|
|
762
|
-
}
|
|
763
|
-
<sp-menu-divider></sp-menu-divider>
|
|
764
|
-
<sp-menu-item value="expression">Expression</sp-menu-item>
|
|
765
|
-
<sp-menu-item value="function">Function</sp-menu-item>
|
|
766
|
-
</sp-picker>
|
|
767
|
-
</div>
|
|
768
|
-
</div>
|
|
769
|
-
`;
|
|
538
|
+
/** A text row committed when the reader leaves it — the shape every `signalFieldRow` had. */
|
|
539
|
+
function textField(
|
|
540
|
+
signal: string,
|
|
541
|
+
key: string,
|
|
542
|
+
label: string,
|
|
543
|
+
value: string,
|
|
544
|
+
write: (value: string) => void,
|
|
545
|
+
extra: Partial<SignalFieldView> = {},
|
|
546
|
+
): SignalFieldView {
|
|
547
|
+
return field(
|
|
548
|
+
{ key, kind: "text", label, signal, value, ...extra },
|
|
549
|
+
{ live: "none", value, write },
|
|
550
|
+
);
|
|
770
551
|
}
|
|
771
552
|
|
|
772
|
-
/**
|
|
773
|
-
function
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
options: string[],
|
|
786
|
-
currentVal: string,
|
|
787
|
-
onChange: (value: string) => void,
|
|
788
|
-
) =>
|
|
789
|
-
renderFieldRow({
|
|
790
|
-
hasValue: false,
|
|
553
|
+
/** A closed set of values, committed on the pick. */
|
|
554
|
+
function selectField(
|
|
555
|
+
signal: string,
|
|
556
|
+
key: string,
|
|
557
|
+
label: string,
|
|
558
|
+
values: string[],
|
|
559
|
+
value: string,
|
|
560
|
+
write: (value: string) => void,
|
|
561
|
+
): SignalFieldView {
|
|
562
|
+
return field(
|
|
563
|
+
{
|
|
564
|
+
key,
|
|
565
|
+
kind: "select",
|
|
791
566
|
label,
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
${options.map((opt: string) => html`<sp-menu-item value=${opt}>${opt}</sp-menu-item>`)}
|
|
800
|
-
</sp-picker>
|
|
801
|
-
`,
|
|
802
|
-
});
|
|
567
|
+
options: values.map((v) => ({ label: v, value: v })),
|
|
568
|
+
signal,
|
|
569
|
+
value,
|
|
570
|
+
},
|
|
571
|
+
{ live: "none", value, write },
|
|
572
|
+
);
|
|
573
|
+
}
|
|
803
574
|
|
|
804
|
-
|
|
805
|
-
// Debounce) so a panel re-render mid-edit can't truncate the in-progress text.
|
|
806
|
-
const textareaRow = (
|
|
807
|
-
label: string,
|
|
808
|
-
value: string,
|
|
809
|
-
onChange: (value: string) => void,
|
|
810
|
-
opts: { minHeight?: string; mono?: boolean } = {},
|
|
811
|
-
) =>
|
|
812
|
-
renderFieldRow({
|
|
813
|
-
hasValue: false,
|
|
814
|
-
label,
|
|
815
|
-
prop: label,
|
|
816
|
-
widget: rawTextArea(`sig:${label}`, value, onChange, {
|
|
817
|
-
debounceMs: 500,
|
|
818
|
-
...(opts.minHeight != null && { minHeight: opts.minHeight }),
|
|
819
|
-
...(opts.mono != null && { mono: opts.mono }),
|
|
820
|
-
}),
|
|
821
|
-
});
|
|
575
|
+
// ─── The editor, field by field ──────────────────────────────────────────────
|
|
822
576
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
577
|
+
/** The rename field, which every category starts with. */
|
|
578
|
+
function nameField(S: SignalsPanelState, name: string): SignalFieldView {
|
|
579
|
+
const message = renameError?.name === name ? renameError.message : "";
|
|
580
|
+
return field(
|
|
581
|
+
{
|
|
582
|
+
error: message,
|
|
583
|
+
hasError: message !== "",
|
|
584
|
+
key: "name",
|
|
585
|
+
kind: "text",
|
|
586
|
+
label: "Name",
|
|
587
|
+
prop: "Name",
|
|
588
|
+
signal: name,
|
|
589
|
+
value: name,
|
|
590
|
+
},
|
|
591
|
+
{
|
|
592
|
+
live: "none",
|
|
593
|
+
value: name,
|
|
594
|
+
/*
|
|
595
|
+
* Every refusal SAYS SO. A collision silently kept the old name while the field showed the
|
|
596
|
+
* new one, so the panel and the document disagreed and only the canvas could tell you which
|
|
597
|
+
* had won — and an empty name did the same. The expansion follows the rename so the editor
|
|
598
|
+
* you are typing in is still the one on screen afterwards.
|
|
599
|
+
*/
|
|
600
|
+
write: (v: string) => {
|
|
601
|
+
const next = v.trim();
|
|
602
|
+
if (next === name) {
|
|
603
|
+
renameError = null;
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
if (!next) {
|
|
607
|
+
renameError = { message: "A name is required.", name };
|
|
608
|
+
} else if (S.document.state?.[next]) {
|
|
609
|
+
renameError = { message: `"${next}" is already defined by this document.`, name };
|
|
610
|
+
} else {
|
|
611
|
+
renameError = null;
|
|
612
|
+
setDataRowExpanded(name, false);
|
|
613
|
+
setDataRowExpanded(next, true);
|
|
614
|
+
transactDoc(activeTab.value, (t) => mutateRenameDef(t, name, next));
|
|
615
|
+
}
|
|
616
|
+
repaint();
|
|
617
|
+
},
|
|
853
618
|
},
|
|
854
|
-
renameError?.name === name ? renameError.message : undefined,
|
|
855
619
|
);
|
|
620
|
+
}
|
|
856
621
|
|
|
857
|
-
|
|
622
|
+
/** A plain value entry: its type, its default, and the CEM facts a custom element adds. */
|
|
623
|
+
function stateFields(S: SignalsPanelState, name: string, def: SignalDef): SignalFieldView[] {
|
|
624
|
+
const patch = (value: Record<string, JsonValue | undefined>): void => {
|
|
625
|
+
transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, value));
|
|
626
|
+
};
|
|
627
|
+
const defaultVal = asText(def.default);
|
|
628
|
+
const out: SignalFieldView[] = [
|
|
629
|
+
selectField(
|
|
630
|
+
name,
|
|
631
|
+
"type",
|
|
632
|
+
"Type",
|
|
633
|
+
["string", "integer", "number", "boolean", "array", "object"],
|
|
634
|
+
def.type || "string",
|
|
635
|
+
(v) => patch({ type: v }),
|
|
636
|
+
),
|
|
637
|
+
];
|
|
638
|
+
if (def.type === "string" || !def.type) {
|
|
639
|
+
out.push(
|
|
640
|
+
selectField(name, "format", "Format", ["", "image", "date", "color"], def.format || "", (v) =>
|
|
641
|
+
patch({ format: v || undefined }),
|
|
642
|
+
),
|
|
643
|
+
);
|
|
644
|
+
}
|
|
645
|
+
if (isMediaFormat(def.format)) {
|
|
646
|
+
out.push(
|
|
647
|
+
field({
|
|
648
|
+
key: "default",
|
|
649
|
+
kind: "slot-field",
|
|
650
|
+
label: "Default",
|
|
651
|
+
prop: "Default",
|
|
652
|
+
signal: name,
|
|
653
|
+
slot: "media",
|
|
654
|
+
}),
|
|
655
|
+
);
|
|
656
|
+
islands.set(`${name}/media`, (host) => {
|
|
657
|
+
mountMediaPicker(host, "default", defaultVal, (v: string) =>
|
|
658
|
+
patch({ default: v || undefined }),
|
|
659
|
+
);
|
|
660
|
+
});
|
|
661
|
+
} else {
|
|
662
|
+
out.push(
|
|
663
|
+
textField(name, "default", "Default", defaultVal, (v: string) => {
|
|
664
|
+
let parsed: unknown = v;
|
|
665
|
+
if (def.type === "integer") {
|
|
666
|
+
parsed = Math.trunc(Number(v)) || 0;
|
|
667
|
+
} else if (def.type === "number") {
|
|
668
|
+
parsed = Number(v) || 0;
|
|
669
|
+
} else if (def.type === "boolean") {
|
|
670
|
+
parsed = v === "true";
|
|
671
|
+
} else if (def.type === "array" || def.type === "object") {
|
|
672
|
+
try {
|
|
673
|
+
parsed = JSON.parse(v);
|
|
674
|
+
} catch {
|
|
675
|
+
parsed = v;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
patch({ default: parsed as JsonValue });
|
|
679
|
+
}),
|
|
680
|
+
);
|
|
681
|
+
}
|
|
682
|
+
out.push(
|
|
683
|
+
textField(name, "description", "Description", def.description || "", (v) =>
|
|
684
|
+
patch({ description: v || undefined }),
|
|
685
|
+
),
|
|
686
|
+
);
|
|
687
|
+
if (isCustomElementDoc(S)) {
|
|
688
|
+
out.push(
|
|
689
|
+
textField(name, "attribute", "Attribute", def.attribute || "", (v) =>
|
|
690
|
+
patch({ attribute: v || undefined }),
|
|
691
|
+
),
|
|
692
|
+
field(
|
|
693
|
+
{
|
|
694
|
+
checked: Boolean(def.reflects),
|
|
695
|
+
key: "reflects",
|
|
696
|
+
kind: "checkbox",
|
|
697
|
+
label: "Reflects",
|
|
698
|
+
prop: "reflects",
|
|
699
|
+
signal: name,
|
|
700
|
+
},
|
|
701
|
+
{
|
|
702
|
+
check: (checked) => patch({ reflects: checked || undefined }),
|
|
703
|
+
live: "none",
|
|
704
|
+
value: "",
|
|
705
|
+
},
|
|
706
|
+
),
|
|
707
|
+
textField(
|
|
708
|
+
name,
|
|
709
|
+
"deprecated",
|
|
710
|
+
"Deprecated",
|
|
711
|
+
typeof def.deprecated === "string" ? def.deprecated : "",
|
|
712
|
+
(v) => patch({ deprecated: v || undefined }),
|
|
713
|
+
),
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
return out;
|
|
717
|
+
}
|
|
858
718
|
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
: ""
|
|
866
|
-
|
|
867
|
-
const cemFields = isCustomElementDoc(S)
|
|
868
|
-
? html`
|
|
869
|
-
${signalFieldRow("Attribute", def.attribute || "", (v: string) =>
|
|
870
|
-
transactDoc(activeTab.value, (t) =>
|
|
871
|
-
mutateUpdateDef(t, name, { attribute: v || undefined }),
|
|
872
|
-
),
|
|
873
|
-
)}
|
|
874
|
-
${renderFieldRow({
|
|
875
|
-
hasValue: false,
|
|
876
|
-
label: "Reflects",
|
|
877
|
-
prop: "reflects",
|
|
878
|
-
widget: html`
|
|
879
|
-
<sp-checkbox
|
|
880
|
-
class="field-check"
|
|
881
|
-
?checked=${Boolean(def.reflects)}
|
|
882
|
-
@change=${(e: Event) =>
|
|
883
|
-
transactDoc(activeTab.value, (t) =>
|
|
884
|
-
mutateUpdateDef(t, name, {
|
|
885
|
-
reflects: (e.target as HTMLInputElement).checked || undefined,
|
|
886
|
-
}),
|
|
887
|
-
)}
|
|
888
|
-
></sp-checkbox>
|
|
889
|
-
`,
|
|
890
|
-
})}
|
|
891
|
-
${signalFieldRow(
|
|
892
|
-
"Deprecated",
|
|
893
|
-
typeof def.deprecated === "string" ? def.deprecated : "",
|
|
894
|
-
(v: string) =>
|
|
895
|
-
transactDoc(activeTab.value, (t) =>
|
|
896
|
-
mutateUpdateDef(t, name, { deprecated: v || undefined }),
|
|
897
|
-
),
|
|
898
|
-
)}
|
|
899
|
-
`
|
|
900
|
-
: nothing;
|
|
901
|
-
|
|
902
|
-
fields = html`
|
|
903
|
-
${pickerRow(
|
|
904
|
-
"Type",
|
|
905
|
-
["string", "integer", "number", "boolean", "array", "object"],
|
|
906
|
-
def.type || "string",
|
|
907
|
-
(v: string) => transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { type: v })),
|
|
908
|
-
)}
|
|
909
|
-
${
|
|
910
|
-
def.type === "string" || !def.type
|
|
911
|
-
? pickerRow("Format", ["", "image", "date", "color"], def.format || "", (v: string) =>
|
|
912
|
-
transactDoc(activeTab.value, (t) =>
|
|
913
|
-
mutateUpdateDef(t, name, { format: v || undefined }),
|
|
914
|
-
),
|
|
915
|
-
)
|
|
916
|
-
: nothing
|
|
917
|
-
}
|
|
918
|
-
${
|
|
919
|
-
isMediaFormat(def.format)
|
|
920
|
-
? renderFieldRow({
|
|
921
|
-
hasValue: false,
|
|
922
|
-
label: "Default",
|
|
923
|
-
prop: "Default",
|
|
924
|
-
widget: renderMediaPicker("default", defaultVal, (v: string) => {
|
|
925
|
-
transactDoc(activeTab.value, (t) =>
|
|
926
|
-
mutateUpdateDef(t, name, { default: v || undefined }),
|
|
927
|
-
);
|
|
928
|
-
}),
|
|
929
|
-
})
|
|
930
|
-
: signalFieldRow("Default", defaultVal, (v: string) => {
|
|
931
|
-
let parsed: unknown = v;
|
|
932
|
-
if (def.type === "integer") {
|
|
933
|
-
parsed = Math.trunc(Number(v)) || 0;
|
|
934
|
-
} else if (def.type === "number") {
|
|
935
|
-
parsed = Number(v) || 0;
|
|
936
|
-
} else if (def.type === "boolean") {
|
|
937
|
-
parsed = v === "true";
|
|
938
|
-
} else if (def.type === "array" || def.type === "object") {
|
|
939
|
-
try {
|
|
940
|
-
parsed = JSON.parse(v);
|
|
941
|
-
} catch {
|
|
942
|
-
parsed = v;
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
transactDoc(activeTab.value, (t) =>
|
|
946
|
-
mutateUpdateDef(t, name, { default: parsed as JsonValue }),
|
|
947
|
-
);
|
|
948
|
-
})
|
|
949
|
-
}
|
|
950
|
-
${signalFieldRow("Description", def.description || "", (v: string) =>
|
|
951
|
-
transactDoc(activeTab.value, (t) =>
|
|
952
|
-
mutateUpdateDef(t, name, { description: v || undefined }),
|
|
953
|
-
),
|
|
954
|
-
)}
|
|
955
|
-
${cemFields}
|
|
956
|
-
`;
|
|
957
|
-
} else if (cat === "computed") {
|
|
958
|
-
let debounce: ReturnType<typeof setTimeout> | undefined;
|
|
959
|
-
fields = html`
|
|
960
|
-
${renderFieldRow({
|
|
961
|
-
hasValue: false,
|
|
719
|
+
/** A derived value: the expression, and the dependencies it was read out of. */
|
|
720
|
+
function computedFields(name: string, def: SignalDef): SignalFieldView[] {
|
|
721
|
+
const expression = def.$compute || "";
|
|
722
|
+
const out: SignalFieldView[] = [
|
|
723
|
+
field(
|
|
724
|
+
{
|
|
725
|
+
key: "expression",
|
|
726
|
+
kind: "multiline",
|
|
962
727
|
label: "Expression",
|
|
963
728
|
prop: "expression",
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
const deps = [...new Set(depMatches)].map((d) => `#/state/${d}`);
|
|
975
|
-
transactDoc(activeTab.value, (t) =>
|
|
976
|
-
mutateUpdateDef(t, name, { $compute: expr, $deps: deps }),
|
|
977
|
-
);
|
|
978
|
-
}, 500);
|
|
979
|
-
}}
|
|
980
|
-
></textarea>
|
|
981
|
-
`,
|
|
982
|
-
})}
|
|
983
|
-
${
|
|
984
|
-
def.$deps && def.$deps.length > 0
|
|
985
|
-
? renderFieldRow({
|
|
986
|
-
hasValue: false,
|
|
987
|
-
label: "Dependencies",
|
|
988
|
-
prop: "dependencies",
|
|
989
|
-
widget: html`
|
|
990
|
-
<span class="signal-hint" style="flex:1;max-width:none"
|
|
991
|
-
>${def.$deps.map((d: string) => d.replace("#/state/", "")).join(", ")}</span
|
|
992
|
-
>
|
|
993
|
-
`,
|
|
994
|
-
})
|
|
995
|
-
: nothing
|
|
996
|
-
}
|
|
997
|
-
`;
|
|
998
|
-
} else if (cat === "data") {
|
|
999
|
-
fields = renderDataSourceFields(S, name, def, textareaRow, pickerRow, ctx);
|
|
1000
|
-
} else if (cat === "function") {
|
|
1001
|
-
fields = renderFunctionFields(S, name, def, textareaRow, ctx);
|
|
1002
|
-
} else if (cat === "expression") {
|
|
1003
|
-
const exprNode = def.$expression || { operator: "=", target: null };
|
|
1004
|
-
fields = html`
|
|
1005
|
-
<div style="display:flex;align-items:center;gap:4px">
|
|
1006
|
-
<span class="field-label" style="flex:1">Expression</span>
|
|
1007
|
-
<sp-action-button
|
|
1008
|
-
size="xs"
|
|
1009
|
-
quiet
|
|
1010
|
-
title="Open in formula workspace"
|
|
1011
|
-
@click=${() => {
|
|
1012
|
-
// One call, because a click is BOTH events: it names the target and it asks for the
|
|
1013
|
-
// Surface. Setting the field alone leaned on the dock's reveal effect, which fires at
|
|
1014
|
-
// Most once per target — so clicking this again after closing the dock was a dead
|
|
1015
|
-
// Click. It also left `editingFunction` set, and that one wins the tie.
|
|
1016
|
-
openLogicTarget({ editing: { defName: name, type: "def" }, surface: "formula" });
|
|
1017
|
-
}}
|
|
1018
|
-
>
|
|
1019
|
-
<sp-icon-align-bottom slot="icon"></sp-icon-align-bottom>
|
|
1020
|
-
</sp-action-button>
|
|
1021
|
-
</div>
|
|
1022
|
-
${renderExpressionEditor(
|
|
1023
|
-
exprNode,
|
|
1024
|
-
(newNode: unknown) =>
|
|
729
|
+
rows: "2",
|
|
730
|
+
signal: name,
|
|
731
|
+
value: expression,
|
|
732
|
+
},
|
|
733
|
+
{
|
|
734
|
+
live: "debounce",
|
|
735
|
+
value: expression,
|
|
736
|
+
write: (expr: string) => {
|
|
737
|
+
const depMatches = expr.match(/\$[a-zA-Z_]\w*/g) || [];
|
|
738
|
+
const deps = [...new Set(depMatches)].map((d) => `#/state/${d}`);
|
|
1025
739
|
transactDoc(activeTab.value, (t) =>
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
),
|
|
1029
|
-
{
|
|
1030
|
-
allowEventRef: false,
|
|
1031
|
-
// Live-context evaluation in the canvas iframe, snapshot fallback (M6). The signals
|
|
1032
|
-
// Panel lives in the left panel — re-render it when a fresh live result lands.
|
|
1033
|
-
preview: livePreviewExpression(activeTab.value, `def:${name}`, exprNode, null, () =>
|
|
1034
|
-
renderOnly("leftPanel"),
|
|
1035
|
-
),
|
|
1036
|
-
onInsertDef: (defName, vendored) =>
|
|
1037
|
-
transactDoc(activeTab.value, (t) =>
|
|
1038
|
-
mutateAddDef(t, defName, vendored as Record<string, JsonValue>),
|
|
1039
|
-
),
|
|
1040
|
-
stateDefs: Object.keys(S.document.state || {}),
|
|
1041
|
-
stateEntries: S.document.state || {},
|
|
740
|
+
mutateUpdateDef(t, name, { $compute: expr, $deps: deps }),
|
|
741
|
+
);
|
|
1042
742
|
},
|
|
1043
|
-
|
|
1044
|
-
|
|
743
|
+
},
|
|
744
|
+
),
|
|
745
|
+
];
|
|
746
|
+
if (def.$deps && def.$deps.length > 0) {
|
|
747
|
+
out.push(
|
|
748
|
+
field({
|
|
749
|
+
key: "dependencies",
|
|
750
|
+
kind: "note",
|
|
751
|
+
label: "Dependencies",
|
|
752
|
+
prop: "dependencies",
|
|
753
|
+
signal: name,
|
|
754
|
+
value: def.$deps.map((d: string) => d.replace("#/state/", "")).join(", "),
|
|
755
|
+
}),
|
|
756
|
+
);
|
|
1045
757
|
}
|
|
1046
|
-
|
|
1047
|
-
return html`${nameField}${fields}`;
|
|
758
|
+
return out;
|
|
1048
759
|
}
|
|
1049
760
|
|
|
1050
|
-
/**
|
|
1051
|
-
function
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
textareaRow: (
|
|
1056
|
-
label: string,
|
|
1057
|
-
value: string,
|
|
1058
|
-
onChange: (value: string) => void,
|
|
1059
|
-
opts?: { minHeight?: string; mono?: boolean },
|
|
1060
|
-
) => TemplateResult,
|
|
1061
|
-
pickerRow: (
|
|
1062
|
-
label: string,
|
|
1063
|
-
options: string[],
|
|
1064
|
-
currentVal: string,
|
|
1065
|
-
onChange: (value: string) => void,
|
|
1066
|
-
) => TemplateResult,
|
|
1067
|
-
ctx: SignalsPanelCtx,
|
|
1068
|
-
) {
|
|
761
|
+
/** A fetched, stored or constructed value — one case per `$prototype`. */
|
|
762
|
+
function dataFields(S: SignalsPanelState, name: string, def: SignalDef): SignalFieldView[] {
|
|
763
|
+
const patch = (value: Record<string, JsonValue | undefined>): void => {
|
|
764
|
+
transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, value));
|
|
765
|
+
};
|
|
1069
766
|
const proto = def.$prototype;
|
|
1070
767
|
|
|
1071
768
|
if (proto === "Request") {
|
|
1072
|
-
return
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
769
|
+
return [
|
|
770
|
+
textField(name, "url", "URL", def.url || "", (v) => patch({ url: v })),
|
|
771
|
+
selectField(
|
|
772
|
+
name,
|
|
773
|
+
"method",
|
|
1077
774
|
"Method",
|
|
1078
775
|
["GET", "POST", "PUT", "DELETE", "PATCH"],
|
|
1079
776
|
def.method || "GET",
|
|
1080
|
-
(v
|
|
1081
|
-
)
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
)
|
|
1085
|
-
|
|
777
|
+
(v) => patch({ method: v }),
|
|
778
|
+
),
|
|
779
|
+
selectField(name, "timing", "Timing", ["client", "server"], def.timing || "client", (v) =>
|
|
780
|
+
patch({ timing: v }),
|
|
781
|
+
),
|
|
782
|
+
];
|
|
1086
783
|
}
|
|
1087
784
|
if (proto === "LocalStorage" || proto === "SessionStorage") {
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
785
|
+
const stored = asText(def.default, true);
|
|
786
|
+
return [
|
|
787
|
+
textField(name, "key", "Key", def.key || "", (v) => patch({ key: v })),
|
|
788
|
+
field(
|
|
789
|
+
{
|
|
790
|
+
key: "default",
|
|
791
|
+
kind: "multiline",
|
|
792
|
+
label: "Default",
|
|
793
|
+
prop: "Default",
|
|
794
|
+
rows: "3",
|
|
795
|
+
signal: name,
|
|
796
|
+
value: stored,
|
|
797
|
+
},
|
|
798
|
+
{
|
|
799
|
+
live: "debounce",
|
|
800
|
+
value: stored,
|
|
801
|
+
write: (v: string) => {
|
|
802
|
+
try {
|
|
803
|
+
// `JSON.parse` answers `any`; the value written is a document default, which is a
|
|
804
|
+
// `JsonValue` by construction — naming it keeps the assignment checked.
|
|
805
|
+
patch({ default: JSON.parse(v) as JsonValue });
|
|
806
|
+
} catch {
|
|
807
|
+
patch({ default: v });
|
|
808
|
+
}
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
),
|
|
812
|
+
];
|
|
1106
813
|
}
|
|
1107
814
|
if (proto === "IndexedDB") {
|
|
1108
|
-
return
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
)
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
${signalFieldRow("Version", String(def.version || 1), (v: string) =>
|
|
1116
|
-
transactDoc(activeTab.value, (t) =>
|
|
1117
|
-
mutateUpdateDef(t, name, { version: Math.trunc(Number(v)) || 1 }),
|
|
1118
|
-
),
|
|
1119
|
-
)}
|
|
1120
|
-
`;
|
|
815
|
+
return [
|
|
816
|
+
textField(name, "database", "Database", def.database || "", (v) => patch({ database: v })),
|
|
817
|
+
textField(name, "store", "Store", def.store || "", (v) => patch({ store: v })),
|
|
818
|
+
textField(name, "version", "Version", String(def.version || 1), (v) =>
|
|
819
|
+
patch({ version: Math.trunc(Number(v)) || 1 }),
|
|
820
|
+
),
|
|
821
|
+
];
|
|
1121
822
|
}
|
|
1122
823
|
if (proto === "Cookie") {
|
|
1123
|
-
return
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
)}
|
|
1130
|
-
`;
|
|
824
|
+
return [
|
|
825
|
+
textField(name, "cookie", "Cookie", def.name || "", (v) => patch({ name: v })),
|
|
826
|
+
textField(name, "default", "Default", String(def.default || ""), (v) =>
|
|
827
|
+
patch({ default: v }),
|
|
828
|
+
),
|
|
829
|
+
];
|
|
1131
830
|
}
|
|
1132
831
|
if (proto === "Set" || proto === "Map" || proto === "FormData") {
|
|
1133
|
-
const
|
|
1134
|
-
const
|
|
1135
|
-
const
|
|
832
|
+
const isForm = proto === "FormData";
|
|
833
|
+
const key = isForm ? "fields" : "default";
|
|
834
|
+
const label = isForm ? "Fields" : "Default";
|
|
835
|
+
const text =
|
|
1136
836
|
def.default !== undefined && def.default !== null
|
|
1137
837
|
? JSON.stringify(def.default, null, 2)
|
|
1138
|
-
:
|
|
838
|
+
: isForm
|
|
1139
839
|
? JSON.stringify(def.fields || {}, null, 2)
|
|
1140
840
|
: "";
|
|
1141
|
-
return
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
841
|
+
return [
|
|
842
|
+
field(
|
|
843
|
+
{ key, kind: "multiline", label, prop: label, rows: "3", signal: name, value: text },
|
|
844
|
+
{
|
|
845
|
+
live: "debounce",
|
|
846
|
+
value: text,
|
|
847
|
+
write: (v: string) => {
|
|
848
|
+
try {
|
|
849
|
+
patch({ [key]: JSON.parse(v) as JsonValue });
|
|
850
|
+
} catch {}
|
|
851
|
+
},
|
|
852
|
+
},
|
|
853
|
+
),
|
|
854
|
+
];
|
|
1148
855
|
}
|
|
1149
|
-
|
|
1150
|
-
return renderExternalPrototypeEditorTemplate(S, name, def, ctx);
|
|
856
|
+
return externalFields(S, name, def);
|
|
1151
857
|
}
|
|
1152
858
|
|
|
1153
|
-
/**
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
const
|
|
1167
|
-
transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { description: v || undefined })),
|
|
1168
|
-
);
|
|
859
|
+
/**
|
|
860
|
+
* An external `$prototype` — its source, and the config form its own schema describes.
|
|
861
|
+
*
|
|
862
|
+
* The schema arrives asynchronously and is cached, so the field list says "Loading schema…" once
|
|
863
|
+
* and the fetch's resolution is what repaints the panel with the form in it.
|
|
864
|
+
*/
|
|
865
|
+
function externalFields(S: SignalsPanelState, name: string, def: SignalDef): SignalFieldView[] {
|
|
866
|
+
const patch = (value: Record<string, JsonValue | undefined>): void => {
|
|
867
|
+
transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, value));
|
|
868
|
+
};
|
|
869
|
+
const importedPath = def.$prototype
|
|
870
|
+
? projectState?.projectConfig?.imports?.[def.$prototype]
|
|
871
|
+
: null;
|
|
872
|
+
const out: SignalFieldView[] = [];
|
|
1169
873
|
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
transactDoc(activeTab.value, (t) =>
|
|
1213
|
-
mutateUpdateDef(t, name, { $export: v || undefined }),
|
|
1214
|
-
),
|
|
1215
|
-
)}
|
|
1216
|
-
`
|
|
1217
|
-
: html`
|
|
1218
|
-
<div style="display:flex;align-items:center;gap:4px">
|
|
1219
|
-
<span class="field-label" style="flex:1">Body</span>
|
|
1220
|
-
${bodyModeToggle}
|
|
1221
|
-
${
|
|
1222
|
-
bodyIsStatements
|
|
1223
|
-
? nothing
|
|
1224
|
-
: html`
|
|
1225
|
-
<sp-action-button
|
|
1226
|
-
size="xs"
|
|
1227
|
-
quiet
|
|
1228
|
-
title="Open in code editor"
|
|
1229
|
-
@click=${() => {
|
|
1230
|
-
// Target AND reveal, in one call. See the formula button above.
|
|
1231
|
-
openLogicTarget({
|
|
1232
|
-
editing: { defName: name, type: "def" },
|
|
1233
|
-
surface: "function",
|
|
1234
|
-
});
|
|
1235
|
-
}}
|
|
1236
|
-
>
|
|
1237
|
-
<sp-icon-code slot="icon"></sp-icon-code>
|
|
1238
|
-
</sp-action-button>
|
|
1239
|
-
`
|
|
874
|
+
if (importedPath) {
|
|
875
|
+
out.push(field({ key: "prototype", kind: "hint", signal: name, value: def.$prototype || "" }));
|
|
876
|
+
} else {
|
|
877
|
+
out.push(
|
|
878
|
+
textField(name, "src", "Source", def.$src || "", (v) => {
|
|
879
|
+
patch({ $src: v || undefined });
|
|
880
|
+
pluginSchemaCache.delete(`${v}::${def.$prototype}`);
|
|
881
|
+
}),
|
|
882
|
+
textField(name, "kind", "Kind", def.$prototype || "", (v) => {
|
|
883
|
+
patch({ $prototype: v || undefined });
|
|
884
|
+
pluginSchemaCache.delete(`${def.$src}::${v}`);
|
|
885
|
+
}),
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
if (def.$export) {
|
|
889
|
+
out.push(
|
|
890
|
+
textField(name, "export", "Export", def.$export || "", (v) =>
|
|
891
|
+
patch({ $export: v || undefined }),
|
|
892
|
+
),
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
const resolvedSrc = def.$src || importedPath;
|
|
897
|
+
if (!resolvedSrc || !def.$prototype) {
|
|
898
|
+
return out;
|
|
899
|
+
}
|
|
900
|
+
const cacheKey = `${resolvedSrc}::${def.$prototype}`;
|
|
901
|
+
if (!pluginSchemaCache.has(cacheKey)) {
|
|
902
|
+
out.push(
|
|
903
|
+
field({ key: "schema-loading", kind: "hint", signal: name, value: "Loading schema…" }),
|
|
904
|
+
);
|
|
905
|
+
/* ONE request per schema, not one per repaint. `fetchPluginSchema` caches what it got but has
|
|
906
|
+
nothing in flight to answer with, so two projections between the ask and the reply sent the
|
|
907
|
+
same request twice — and the Navigator repaints on every document change. */
|
|
908
|
+
if (!schemaRequests.has(cacheKey)) {
|
|
909
|
+
schemaRequests.add(cacheKey);
|
|
910
|
+
void fetchPluginSchema(def, {
|
|
911
|
+
...(S.documentPath != null && { documentPath: S.documentPath }),
|
|
912
|
+
})
|
|
913
|
+
.then((schema) => {
|
|
914
|
+
if (schema) {
|
|
915
|
+
repaint();
|
|
1240
916
|
}
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
917
|
+
})
|
|
918
|
+
.finally(() => schemaRequests.delete(cacheKey));
|
|
919
|
+
}
|
|
920
|
+
return out;
|
|
921
|
+
}
|
|
922
|
+
const schema = pluginSchemaCache.get(cacheKey);
|
|
923
|
+
if (!schema) {
|
|
924
|
+
return out;
|
|
925
|
+
}
|
|
926
|
+
if (schema.description) {
|
|
927
|
+
out.push(field({ key: "schema-note", kind: "hint", signal: name, value: schema.description }));
|
|
928
|
+
}
|
|
929
|
+
out.push(field({ key: "schema", kind: "slot", signal: name, slot: "schema" }));
|
|
930
|
+
islands.set(`${name}/schema`, (host) => {
|
|
931
|
+
placeSchemaForm(host, schema as JsonSchema, def, name, S);
|
|
932
|
+
});
|
|
933
|
+
return out;
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/** Write one of the three CEM text columns, dropping the key when the value is emptied. */
|
|
937
|
+
function writeCemField<T extends CemParameter | CemEvent>(
|
|
938
|
+
entry: T,
|
|
939
|
+
column: string,
|
|
940
|
+
value: string,
|
|
941
|
+
): T {
|
|
942
|
+
if (column === "name") {
|
|
943
|
+
return { ...entry, name: value };
|
|
944
|
+
}
|
|
945
|
+
if (column === "type") {
|
|
946
|
+
const { type: _type, ...rest } = entry;
|
|
947
|
+
return (value ? { ...rest, type: { text: value } } : rest) as T;
|
|
948
|
+
}
|
|
949
|
+
const { description: _description, ...rest } = entry;
|
|
950
|
+
return (value ? { ...rest, description: value } : rest) as T;
|
|
951
|
+
}
|
|
1274
952
|
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
953
|
+
/** One text column of a CEM table. */
|
|
954
|
+
function cemCell(
|
|
955
|
+
key: string,
|
|
956
|
+
placeholder: string,
|
|
957
|
+
value: string,
|
|
958
|
+
grow: string,
|
|
959
|
+
): Omit<SignalCellView, "field" | "row" | "signal"> {
|
|
960
|
+
return { checked: false, grow, key, kind: "text", label: placeholder, placeholder, value };
|
|
1279
961
|
}
|
|
1280
962
|
|
|
1281
|
-
|
|
963
|
+
/** One row of a CEM table, with the identity every cell in it has to carry. */
|
|
964
|
+
function cemRow(
|
|
965
|
+
signal: string,
|
|
966
|
+
fieldKey: string,
|
|
967
|
+
key: string,
|
|
968
|
+
label: string,
|
|
969
|
+
cells: Omit<SignalCellView, "field" | "row" | "signal">[],
|
|
970
|
+
): SignalCellRowView {
|
|
971
|
+
return {
|
|
972
|
+
cells: cells.map((cell) => ({ ...cell, field: fieldKey, row: key, signal })),
|
|
973
|
+
field: fieldKey,
|
|
974
|
+
key,
|
|
975
|
+
removeLabel: `Remove ${label}`,
|
|
976
|
+
signal,
|
|
977
|
+
};
|
|
978
|
+
}
|
|
1282
979
|
|
|
1283
|
-
/**
|
|
1284
|
-
function
|
|
1285
|
-
_S: SignalsPanelState,
|
|
1286
|
-
name: string,
|
|
1287
|
-
def: SignalDef,
|
|
1288
|
-
ctx: SignalsPanelCtx,
|
|
1289
|
-
) {
|
|
980
|
+
/** The CEM parameter editor, in whichever of its two views is open. */
|
|
981
|
+
function parameterField(name: string, def: SignalDef): SignalFieldView {
|
|
1290
982
|
const params = (def.parameters || []).map((p) => normParam(p));
|
|
1291
|
-
const
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
983
|
+
const advanced = advancedParamOpen.has(name);
|
|
984
|
+
const write = (next: CemParameter[]): void => {
|
|
985
|
+
transactDoc(activeTab.value, (t) =>
|
|
986
|
+
mutateUpdateDef(t, name, { parameters: next.length > 0 ? next : undefined }),
|
|
987
|
+
);
|
|
988
|
+
};
|
|
989
|
+
const plan: FieldPlan = {
|
|
990
|
+
addChip: (value: string) => {
|
|
991
|
+
const trimmed = value.trim();
|
|
992
|
+
if (trimmed) {
|
|
993
|
+
write([...params, { name: trimmed }]);
|
|
994
|
+
}
|
|
995
|
+
},
|
|
996
|
+
addRow: () => {
|
|
997
|
+
transactDoc(activeTab.value, (t) =>
|
|
998
|
+
mutateUpdateDef(t, name, { parameters: [...params, { name: "" }] }),
|
|
999
|
+
);
|
|
1000
|
+
},
|
|
1001
|
+
cell: (row: string, cell: string, value: string) => {
|
|
1002
|
+
const next = [...params];
|
|
1003
|
+
const current = next[Number(row)];
|
|
1004
|
+
if (!current) {
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
next[Number(row)] = writeCemField(current, cell, value);
|
|
1008
|
+
write(next);
|
|
1009
|
+
},
|
|
1010
|
+
cellCheck: (row: string, _cell: string, checked: boolean) => {
|
|
1011
|
+
const next = [...params];
|
|
1012
|
+
const current = next[Number(row)];
|
|
1013
|
+
if (!current) {
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
const { optional: _optional, ...rest } = current;
|
|
1017
|
+
next[Number(row)] = checked ? { ...rest, optional: true } : rest;
|
|
1018
|
+
write(next);
|
|
1019
|
+
},
|
|
1020
|
+
dropChip: (chip: string) => write(params.filter((_, j) => j !== Number(chip))),
|
|
1021
|
+
dropRow: (row: string) => write(params.filter((_, j) => j !== Number(row))),
|
|
1022
|
+
live: "none",
|
|
1023
|
+
press: (action: string) => {
|
|
1024
|
+
if (action !== "footer") {
|
|
1025
|
+
return;
|
|
1026
|
+
}
|
|
1027
|
+
if (advanced) {
|
|
1028
|
+
advancedParamOpen.delete(name);
|
|
1029
|
+
} else {
|
|
1030
|
+
advancedParamOpen.add(name);
|
|
1031
|
+
}
|
|
1032
|
+
repaint();
|
|
1033
|
+
},
|
|
1034
|
+
value: "",
|
|
1035
|
+
};
|
|
1036
|
+
|
|
1037
|
+
if (!advanced) {
|
|
1038
|
+
const chips: SignalChipView[] = params.map((p, i) => ({
|
|
1039
|
+
field: "parameters",
|
|
1040
|
+
key: String(i),
|
|
1041
|
+
label: p.name || "?",
|
|
1042
|
+
removeLabel: `Remove ${p.name || "parameter"}`,
|
|
1043
|
+
signal: name,
|
|
1044
|
+
}));
|
|
1045
|
+
return field(
|
|
1046
|
+
{
|
|
1047
|
+
addLabel: "Add a parameter",
|
|
1048
|
+
chips,
|
|
1049
|
+
footer: "▸ Advanced",
|
|
1050
|
+
hasFooter: true,
|
|
1051
|
+
key: "parameters",
|
|
1052
|
+
kind: "chips",
|
|
1053
|
+
label: "Parameters",
|
|
1054
|
+
prop: "parameters",
|
|
1055
|
+
signal: name,
|
|
1056
|
+
},
|
|
1057
|
+
plan,
|
|
1058
|
+
);
|
|
1059
|
+
}
|
|
1060
|
+
return field(
|
|
1061
|
+
{
|
|
1062
|
+
addLabel: "Add parameter",
|
|
1063
|
+
cellRows: params.map((p, i) =>
|
|
1064
|
+
cemRow(name, "parameters", String(i), p.name || "parameter", [
|
|
1065
|
+
cemCell("name", "name", p.name || "", "1"),
|
|
1066
|
+
cemCell("type", "type", cemTypeText(p.type), "1"),
|
|
1067
|
+
cemCell("description", "desc", p.description || "", "2"),
|
|
1068
|
+
{
|
|
1069
|
+
checked: Boolean(p.optional),
|
|
1070
|
+
grow: "0",
|
|
1071
|
+
key: "optional",
|
|
1072
|
+
kind: "checkbox",
|
|
1073
|
+
label: "Optional",
|
|
1074
|
+
placeholder: "",
|
|
1075
|
+
value: "",
|
|
1076
|
+
},
|
|
1077
|
+
]),
|
|
1078
|
+
),
|
|
1079
|
+
footer: "▾ Basic",
|
|
1080
|
+
hasFooter: true,
|
|
1081
|
+
key: "parameters",
|
|
1082
|
+
kind: "rows",
|
|
1297
1083
|
label: "Parameters",
|
|
1298
1084
|
prop: "parameters",
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1085
|
+
signal: name,
|
|
1086
|
+
span: true,
|
|
1087
|
+
},
|
|
1088
|
+
plan,
|
|
1089
|
+
);
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
/** The CEM emits editor — the same table shape, over `emits`. */
|
|
1093
|
+
function emitsField(name: string, def: SignalDef): SignalFieldView {
|
|
1094
|
+
const emits = def.emits || ([] as CemEvent[]);
|
|
1095
|
+
const write = (next: CemEvent[]): void => {
|
|
1096
|
+
transactDoc(activeTab.value, (t) =>
|
|
1097
|
+
mutateUpdateDef(t, name, { emits: next.length > 0 ? next : undefined }),
|
|
1098
|
+
);
|
|
1099
|
+
};
|
|
1100
|
+
return field(
|
|
1101
|
+
{
|
|
1102
|
+
addLabel: "Add event",
|
|
1103
|
+
cellRows: emits.map((e, i) =>
|
|
1104
|
+
cemRow(name, "emits", String(i), e.name || "event", [
|
|
1105
|
+
cemCell("name", "event name", e.name || "", "1"),
|
|
1106
|
+
cemCell("type", "type", cemTypeText(e.type), "1"),
|
|
1107
|
+
cemCell("description", "description", e.description || "", "2"),
|
|
1108
|
+
]),
|
|
1109
|
+
),
|
|
1110
|
+
key: "emits",
|
|
1111
|
+
kind: "rows",
|
|
1112
|
+
label: "Emits",
|
|
1113
|
+
prop: "emits",
|
|
1114
|
+
signal: name,
|
|
1115
|
+
span: true,
|
|
1116
|
+
},
|
|
1117
|
+
{
|
|
1118
|
+
addRow: () => {
|
|
1119
|
+
transactDoc(activeTab.value, (t) =>
|
|
1120
|
+
mutateUpdateDef(t, name, { emits: [...emits, { name: "" }] }),
|
|
1121
|
+
);
|
|
1122
|
+
},
|
|
1123
|
+
cell: (row: string, cell: string, value: string) => {
|
|
1124
|
+
const next = [...emits];
|
|
1125
|
+
const current = next[Number(row)];
|
|
1126
|
+
if (!current) {
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
next[Number(row)] = writeCemField(current, cell, value);
|
|
1130
|
+
write(next);
|
|
1131
|
+
},
|
|
1132
|
+
dropRow: (row: string) => write(emits.filter((_, j) => j !== Number(row))),
|
|
1133
|
+
live: "none",
|
|
1134
|
+
value: "",
|
|
1135
|
+
},
|
|
1136
|
+
);
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
/**
|
|
1140
|
+
* A function: what it is for, what it takes, what it emits, and its body.
|
|
1141
|
+
*
|
|
1142
|
+
* Structured bodies (spec §20) are a body MODE rather than a new entity: "Statements" is the
|
|
1143
|
+
* statement-card editor over `body: JxStatement[]`, "Code" is the text path over `body: string`.
|
|
1144
|
+
* Switching modes replaces the body with the other representation's empty seed — an explicit mode
|
|
1145
|
+
* change, nothing is converted.
|
|
1146
|
+
*/
|
|
1147
|
+
function functionFields(S: SignalsPanelState, name: string, def: SignalDef): SignalFieldView[] {
|
|
1148
|
+
const patch = (value: Record<string, JsonValue | undefined>): void => {
|
|
1149
|
+
transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, value));
|
|
1150
|
+
};
|
|
1151
|
+
const out: SignalFieldView[] = [
|
|
1152
|
+
textField(name, "description", "Description", def.description || "", (v) =>
|
|
1153
|
+
patch({ description: v || undefined }),
|
|
1154
|
+
),
|
|
1155
|
+
parameterField(name, def),
|
|
1156
|
+
];
|
|
1157
|
+
if (isCustomElementDoc(S)) {
|
|
1158
|
+
out.push(emitsField(name, def));
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
if (def.$src) {
|
|
1162
|
+
out.push(
|
|
1163
|
+
textField(name, "src", "Source", def.$src || "", (v) => patch({ $src: v || undefined })),
|
|
1164
|
+
textField(name, "export", "Export", def.$export || "", (v) =>
|
|
1165
|
+
patch({ $export: v || undefined }),
|
|
1166
|
+
),
|
|
1167
|
+
);
|
|
1168
|
+
return out;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
const statements = Array.isArray(def.body);
|
|
1172
|
+
out.push(
|
|
1173
|
+
field(
|
|
1174
|
+
{
|
|
1175
|
+
buttons: statements
|
|
1176
|
+
? []
|
|
1177
|
+
: [
|
|
1178
|
+
{
|
|
1179
|
+
field: "body",
|
|
1180
|
+
icon: "code",
|
|
1181
|
+
key: "editor",
|
|
1182
|
+
label: "Open in code editor",
|
|
1183
|
+
signal: name,
|
|
1184
|
+
},
|
|
1185
|
+
],
|
|
1186
|
+
key: "body",
|
|
1187
|
+
kind: "bar",
|
|
1188
|
+
label: "Body",
|
|
1189
|
+
prop: "Body",
|
|
1190
|
+
segments: [
|
|
1191
|
+
{
|
|
1192
|
+
field: "body",
|
|
1193
|
+
key: "statements",
|
|
1194
|
+
label: "Statements",
|
|
1195
|
+
selected: statements,
|
|
1196
|
+
signal: name,
|
|
1197
|
+
},
|
|
1198
|
+
{ field: "body", key: "code", label: "Code", selected: !statements, signal: name },
|
|
1199
|
+
],
|
|
1200
|
+
signal: name,
|
|
1201
|
+
},
|
|
1202
|
+
{
|
|
1203
|
+
live: "none",
|
|
1204
|
+
press: (action: string) => {
|
|
1205
|
+
if (action === "statements" && !statements) {
|
|
1206
|
+
patch({ body: [] });
|
|
1207
|
+
repaint();
|
|
1208
|
+
} else if (action === "code" && statements) {
|
|
1209
|
+
patch({ body: "" });
|
|
1210
|
+
repaint();
|
|
1211
|
+
} else if (action === "editor") {
|
|
1212
|
+
// Target AND reveal, in one call. See the formula button below.
|
|
1213
|
+
openLogicTarget({ editing: { defName: name, type: "def" }, surface: "function" });
|
|
1214
|
+
}
|
|
1215
|
+
},
|
|
1216
|
+
value: "",
|
|
1217
|
+
},
|
|
1218
|
+
),
|
|
1219
|
+
);
|
|
1220
|
+
|
|
1221
|
+
if (statements) {
|
|
1222
|
+
out.push(field({ key: "statements", kind: "slot", signal: name, slot: "statements" }));
|
|
1223
|
+
islands.set(`${name}/statements`, (host) => {
|
|
1224
|
+
mountStatementEditor(
|
|
1225
|
+
host,
|
|
1226
|
+
def.body as JxStatement[],
|
|
1227
|
+
(next) => {
|
|
1228
|
+
patch({ body: next as unknown as JsonValue });
|
|
1229
|
+
repaint();
|
|
1230
|
+
},
|
|
1231
|
+
{
|
|
1232
|
+
allowEventRef: true,
|
|
1233
|
+
emits: def.emits ?? [],
|
|
1234
|
+
// This editor is in the Navigator's Data panel; the Events tab's is not.
|
|
1235
|
+
region: NAVIGATOR_STATEMENTS_REGION,
|
|
1236
|
+
stateDefs: Object.keys(S.document.state || {}),
|
|
1237
|
+
stateEntries: S.document.state || {},
|
|
1238
|
+
},
|
|
1239
|
+
);
|
|
1347
1240
|
});
|
|
1241
|
+
return out;
|
|
1348
1242
|
}
|
|
1243
|
+
const source = typeof def.body === "string" ? def.body : "";
|
|
1244
|
+
out.push(
|
|
1245
|
+
field(
|
|
1246
|
+
{
|
|
1247
|
+
key: "code",
|
|
1248
|
+
kind: "code",
|
|
1249
|
+
label: "Body",
|
|
1250
|
+
prop: "Body",
|
|
1251
|
+
rows: "3",
|
|
1252
|
+
signal: name,
|
|
1253
|
+
value: source,
|
|
1254
|
+
},
|
|
1255
|
+
{ live: "commit", value: source, write: (v: string) => patch({ body: v }) },
|
|
1256
|
+
),
|
|
1257
|
+
);
|
|
1258
|
+
return out;
|
|
1259
|
+
}
|
|
1349
1260
|
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
title="optional"
|
|
1409
|
-
.checked=${Boolean(p.optional)}
|
|
1410
|
-
@change=${(e: Event) => {
|
|
1411
|
-
const next = [...params];
|
|
1412
|
-
const { checked } = e.target as HTMLInputElement;
|
|
1413
|
-
const { optional: _o, ...rest } = next[i]!;
|
|
1414
|
-
next[i] = checked ? { ...rest, optional: true } : rest;
|
|
1415
|
-
transactDoc(activeTab.value, (t) =>
|
|
1416
|
-
mutateUpdateDef(t, name, { parameters: next }),
|
|
1417
|
-
);
|
|
1418
|
-
}}
|
|
1419
|
-
/>
|
|
1420
|
-
<span
|
|
1421
|
-
style="cursor:pointer;opacity:0.5"
|
|
1422
|
-
@click=${() => {
|
|
1423
|
-
const next = params.filter((_: unknown, j: number) => j !== i);
|
|
1424
|
-
transactDoc(activeTab.value, (t) =>
|
|
1425
|
-
mutateUpdateDef(t, name, {
|
|
1426
|
-
parameters: next.length > 0 ? next : undefined,
|
|
1427
|
-
}),
|
|
1428
|
-
);
|
|
1429
|
-
}}
|
|
1430
|
-
>×</span
|
|
1431
|
-
>
|
|
1432
|
-
</div>
|
|
1433
|
-
`,
|
|
1434
|
-
)}
|
|
1435
|
-
<button
|
|
1436
|
-
class="kv-add"
|
|
1437
|
-
@click=${() =>
|
|
1438
|
-
transactDoc(activeTab.value, (t) =>
|
|
1439
|
-
mutateUpdateDef(t, name, {
|
|
1440
|
-
parameters: [...params, { name: "" }],
|
|
1441
|
-
}),
|
|
1442
|
-
)}
|
|
1443
|
-
>
|
|
1444
|
-
+ Add parameter
|
|
1445
|
-
</button>
|
|
1446
|
-
</div>
|
|
1447
|
-
<span
|
|
1448
|
-
style="font-size:10px;color:var(--fg-dim);cursor:pointer;width:100%;margin-top:2px"
|
|
1449
|
-
@click=${() => {
|
|
1450
|
-
advancedParamOpen.delete(name);
|
|
1451
|
-
ctx.renderLeftPanel();
|
|
1452
|
-
}}
|
|
1453
|
-
>▾ Basic</span
|
|
1454
|
-
>
|
|
1455
|
-
`,
|
|
1261
|
+
/** An assignment or a call the page performs — edited as a formula tree. */
|
|
1262
|
+
function expressionFields(S: SignalsPanelState, name: string, def: SignalDef): SignalFieldView[] {
|
|
1263
|
+
const exprNode = def.$expression || { operator: "=", target: null };
|
|
1264
|
+
const out: SignalFieldView[] = [
|
|
1265
|
+
field(
|
|
1266
|
+
{
|
|
1267
|
+
buttons: [
|
|
1268
|
+
{
|
|
1269
|
+
field: "expression",
|
|
1270
|
+
icon: "align-bottom",
|
|
1271
|
+
key: "formula",
|
|
1272
|
+
label: "Open in formula workspace",
|
|
1273
|
+
signal: name,
|
|
1274
|
+
},
|
|
1275
|
+
],
|
|
1276
|
+
key: "expression",
|
|
1277
|
+
kind: "bar",
|
|
1278
|
+
label: "Expression",
|
|
1279
|
+
prop: "Expression",
|
|
1280
|
+
signal: name,
|
|
1281
|
+
},
|
|
1282
|
+
{
|
|
1283
|
+
live: "none",
|
|
1284
|
+
/* One call, because a click is BOTH events: it names the target and it asks for the
|
|
1285
|
+
surface. Setting the field alone leaned on the dock's reveal effect, which fires at most
|
|
1286
|
+
once per target — so clicking this again after closing the dock was a dead click. It also
|
|
1287
|
+
left `editingFunction` set, and that one wins the tie. */
|
|
1288
|
+
press: () =>
|
|
1289
|
+
openLogicTarget({ editing: { defName: name, type: "def" }, surface: "formula" }),
|
|
1290
|
+
value: "",
|
|
1291
|
+
},
|
|
1292
|
+
),
|
|
1293
|
+
field({ key: "editor", kind: "slot", signal: name, slot: "expression" }),
|
|
1294
|
+
];
|
|
1295
|
+
islands.set(`${name}/expression`, (host) => {
|
|
1296
|
+
mountExpressionEditor(
|
|
1297
|
+
host,
|
|
1298
|
+
exprNode,
|
|
1299
|
+
(newNode: unknown) =>
|
|
1300
|
+
transactDoc(activeTab.value, (t) =>
|
|
1301
|
+
// Expression editors emit JSON expression nodes.
|
|
1302
|
+
mutateUpdateDef(t, name, { $expression: newNode as JsonValue }),
|
|
1303
|
+
),
|
|
1304
|
+
{
|
|
1305
|
+
allowEventRef: false,
|
|
1306
|
+
// Live-context evaluation in the canvas iframe, snapshot fallback (M6). The panel lives
|
|
1307
|
+
// In the Navigator — re-render it when a fresh live result lands.
|
|
1308
|
+
preview: livePreviewExpression(activeTab.value, `def:${name}`, exprNode, null, () =>
|
|
1309
|
+
renderOnly("leftPanel"),
|
|
1310
|
+
),
|
|
1311
|
+
onInsertDef: (defName, vendored) =>
|
|
1312
|
+
transactDoc(activeTab.value, (t) =>
|
|
1313
|
+
mutateAddDef(t, defName, vendored as Record<string, JsonValue>),
|
|
1314
|
+
),
|
|
1315
|
+
stateDefs: Object.keys(S.document.state || {}),
|
|
1316
|
+
stateEntries: S.document.state || {},
|
|
1317
|
+
},
|
|
1318
|
+
);
|
|
1456
1319
|
});
|
|
1320
|
+
return out;
|
|
1457
1321
|
}
|
|
1458
1322
|
|
|
1459
|
-
/**
|
|
1460
|
-
function
|
|
1461
|
-
const
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
(
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
next[i] = {
|
|
1483
|
-
...next[i],
|
|
1484
|
-
name: (e.target as HTMLInputElement).value,
|
|
1485
|
-
};
|
|
1486
|
-
transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { emits: next }));
|
|
1487
|
-
}}
|
|
1488
|
-
/>
|
|
1489
|
-
<input
|
|
1490
|
-
class="field-input"
|
|
1491
|
-
.value=${cemTypeText(ev.type)}
|
|
1492
|
-
placeholder="type"
|
|
1493
|
-
style="flex:1"
|
|
1494
|
-
@change=${(e: Event) => {
|
|
1495
|
-
const next = [...emits];
|
|
1496
|
-
const val = (e.target as HTMLInputElement).value;
|
|
1497
|
-
const { type: _t, ...rest } = next[i]!;
|
|
1498
|
-
next[i] = val ? { ...rest, type: { text: val } } : rest;
|
|
1499
|
-
transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { emits: next }));
|
|
1500
|
-
}}
|
|
1501
|
-
/>
|
|
1502
|
-
<input
|
|
1503
|
-
class="field-input"
|
|
1504
|
-
.value=${ev.description || ""}
|
|
1505
|
-
placeholder="description"
|
|
1506
|
-
style="flex:2"
|
|
1507
|
-
@change=${(e: Event) => {
|
|
1508
|
-
const next = [...emits];
|
|
1509
|
-
const val = (e.target as HTMLInputElement).value;
|
|
1510
|
-
const { description: _d, ...rest } = next[i]!;
|
|
1511
|
-
next[i] = val ? { ...rest, description: val } : rest;
|
|
1512
|
-
transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, { emits: next }));
|
|
1513
|
-
}}
|
|
1514
|
-
/>
|
|
1515
|
-
<span
|
|
1516
|
-
style="cursor:pointer;opacity:0.5"
|
|
1517
|
-
@click=${() => {
|
|
1518
|
-
transactDoc(activeTab.value, (t) =>
|
|
1519
|
-
mutateUpdateDef(t, name, {
|
|
1520
|
-
emits: emits.some((_: unknown, j: number) => j !== i)
|
|
1521
|
-
? emits.filter((_: unknown, j: number) => j !== i)
|
|
1522
|
-
: undefined,
|
|
1523
|
-
}),
|
|
1524
|
-
);
|
|
1525
|
-
}}
|
|
1526
|
-
>×</span
|
|
1527
|
-
>
|
|
1528
|
-
</div>
|
|
1529
|
-
`,
|
|
1530
|
-
)}
|
|
1531
|
-
<button
|
|
1532
|
-
class="kv-add"
|
|
1533
|
-
@click=${() =>
|
|
1534
|
-
transactDoc(activeTab.value, (t) =>
|
|
1535
|
-
mutateUpdateDef(t, name, { emits: [...emits, { name: "" }] }),
|
|
1536
|
-
)}
|
|
1537
|
-
>
|
|
1538
|
-
+ Add event
|
|
1539
|
-
</button>
|
|
1540
|
-
`;
|
|
1323
|
+
/** Every field of one entry's editor, in the order they are read. */
|
|
1324
|
+
function editorFields(S: SignalsPanelState, name: string, def: SignalDef): SignalFieldView[] {
|
|
1325
|
+
const head = nameField(S, name);
|
|
1326
|
+
switch (defCategory(def)) {
|
|
1327
|
+
case "state": {
|
|
1328
|
+
return [head, ...stateFields(S, name, def)];
|
|
1329
|
+
}
|
|
1330
|
+
case "computed": {
|
|
1331
|
+
return [head, ...computedFields(name, def)];
|
|
1332
|
+
}
|
|
1333
|
+
case "data": {
|
|
1334
|
+
return [head, ...dataFields(S, name, def)];
|
|
1335
|
+
}
|
|
1336
|
+
case "function": {
|
|
1337
|
+
return [head, ...functionFields(S, name, def)];
|
|
1338
|
+
}
|
|
1339
|
+
case "expression": {
|
|
1340
|
+
return [head, ...expressionFields(S, name, def)];
|
|
1341
|
+
}
|
|
1342
|
+
default: {
|
|
1343
|
+
return [head];
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1541
1346
|
}
|
|
1542
1347
|
|
|
1543
|
-
// ─── Plugin schema-driven form
|
|
1348
|
+
// ─── Plugin schema-driven form ───────────────────────────────────────────────
|
|
1544
1349
|
|
|
1545
1350
|
/**
|
|
1546
1351
|
* Resolve a schema context pointer for signal config forms — a thin wrapper over the generic
|
|
@@ -1567,120 +1372,461 @@ function resolveSignalsContextPointer(pointer: string, scope?: Record<string, un
|
|
|
1567
1372
|
}
|
|
1568
1373
|
|
|
1569
1374
|
/**
|
|
1570
|
-
*
|
|
1571
|
-
*
|
|
1572
|
-
*
|
|
1375
|
+
* Put the shared schema form into the node the document made for it.
|
|
1376
|
+
*
|
|
1377
|
+
* `ui/schema-form.ts` is a document too, and a document CLEARS the host it is given — so what comes
|
|
1378
|
+
* back is the form's own host element rather than a template, and this function's whole job is to
|
|
1379
|
+
* place it. Calling `mountSchemaForm` again with the same key updates the standing form in place,
|
|
1380
|
+
* which is what keeps the caret in a field across the repaint a commit provokes.
|
|
1381
|
+
*
|
|
1382
|
+
* @param {HTMLElement} host - The empty node the document drew
|
|
1383
|
+
* @param {JsonSchema} schema - The plugin's own schema
|
|
1384
|
+
* @param {SignalDef} def - The entry being configured
|
|
1385
|
+
* @param {string} name - The entry's name
|
|
1386
|
+
* @param {SignalsPanelState} S - The focused document
|
|
1573
1387
|
*/
|
|
1574
|
-
|
|
1575
|
-
|
|
1388
|
+
function placeSchemaForm(
|
|
1389
|
+
host: HTMLElement,
|
|
1390
|
+
schema: JsonSchema,
|
|
1576
1391
|
def: SignalDef,
|
|
1577
1392
|
name: string,
|
|
1578
1393
|
S: SignalsPanelState,
|
|
1579
|
-
|
|
1580
|
-
) {
|
|
1581
|
-
|
|
1582
|
-
return
|
|
1394
|
+
): void {
|
|
1395
|
+
if (!schema.properties) {
|
|
1396
|
+
host.replaceChildren();
|
|
1397
|
+
return;
|
|
1583
1398
|
}
|
|
1584
|
-
|
|
1585
1399
|
const properties = Object.fromEntries(
|
|
1586
1400
|
Object.entries(schema.properties).filter(([prop]) => !STUDIO_RESERVED_KEYS.has(prop)),
|
|
1587
1401
|
);
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1402
|
+
const form = mountSchemaForm(
|
|
1403
|
+
`signal:${name}`,
|
|
1404
|
+
{ ...schema, properties },
|
|
1405
|
+
def as Record<string, unknown>,
|
|
1406
|
+
{
|
|
1407
|
+
context: {
|
|
1408
|
+
fieldKeyPrefix: name,
|
|
1409
|
+
params: dynamicRouteParams(S.documentPath),
|
|
1410
|
+
resolvePointer: resolveSignalsContextPointer,
|
|
1411
|
+
// A config value may point at any signal but this one — a def that reads itself is a cycle.
|
|
1412
|
+
signals: bindableSignalNames(S.document).filter((signal) => signal !== name),
|
|
1413
|
+
},
|
|
1414
|
+
onChange: (patch) => transactDoc(activeTab.value, (t) => mutateUpdateDef(t, name, patch)),
|
|
1415
|
+
rerender: repaint,
|
|
1596
1416
|
},
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1417
|
+
);
|
|
1418
|
+
if (form.parentNode !== host) {
|
|
1419
|
+
host.replaceChildren(form);
|
|
1420
|
+
}
|
|
1600
1421
|
}
|
|
1601
1422
|
|
|
1423
|
+
// ─── The projection ──────────────────────────────────────────────────────────
|
|
1424
|
+
|
|
1602
1425
|
/**
|
|
1603
|
-
*
|
|
1604
|
-
*
|
|
1426
|
+
* ONE summary slot per row, and the resolved value wins it as soon as there is one.
|
|
1427
|
+
*
|
|
1428
|
+
* The row wants to say four things about an entry — its category, its name, how it is defined and
|
|
1429
|
+
* what it became — and a 240px Navigator fits three. Eliding both summaries to make room produced
|
|
1430
|
+
* "recentproje… C… Array(3)": two truncated descriptions and a truncated identity. So the
|
|
1431
|
+
* definition hint holds the slot until the canvas resolves a value and then steps aside for it, and
|
|
1432
|
+
* the full definition is one click down in fields — which is what the hint was abbreviating.
|
|
1433
|
+
*
|
|
1434
|
+
* The switch is whether the canvas has reported a scope AT ALL, not whether this entry appears in
|
|
1435
|
+
* it. An entry the canvas ran and did not produce is "pending", which is a fact about the value; a
|
|
1436
|
+
* panel opened before the canvas has rendered knows nothing about any of them, and guessing
|
|
1437
|
+
* "pending" for the whole list there would be a fact about the panel dressed up as one about data.
|
|
1438
|
+
*
|
|
1439
|
+
* ENTRIES THAT CANNOT HOLD A VALUE never get the column. A function and an assignment expression
|
|
1440
|
+
* are things the page DOES, not things it knows, and they are absent from the resolved scope for
|
|
1441
|
+
* exactly that reason — so the value column called all eight of a component's `setFilter` handlers
|
|
1442
|
+
* "pending", which reads as "still loading" for something that will never load.
|
|
1605
1443
|
*/
|
|
1606
|
-
|
|
1607
|
-
S: SignalsPanelState,
|
|
1444
|
+
function summary(
|
|
1608
1445
|
name: string,
|
|
1609
1446
|
def: SignalDef,
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1447
|
+
live: unknown,
|
|
1448
|
+
resolved: boolean,
|
|
1449
|
+
): { summary: string; summaryTitle: string; summaryTone: string } {
|
|
1450
|
+
const holdsNoValue =
|
|
1451
|
+
defCategory(def) === "function" ||
|
|
1452
|
+
(def.$expression != null && isActionExpression(def.$expression));
|
|
1453
|
+
if (!resolved || holdsNoValue) {
|
|
1454
|
+
const hint = defHint(name, def);
|
|
1455
|
+
return { summary: hint, summaryTitle: hint, summaryTone: "hint" };
|
|
1456
|
+
}
|
|
1457
|
+
return {
|
|
1458
|
+
summary: dataTypeLabel(live),
|
|
1459
|
+
summaryTitle: "What this resolved to on the canvas",
|
|
1460
|
+
summaryTone: unwrapSignal(live) === null ? "pending" : "value",
|
|
1461
|
+
};
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
/** The five categories, in the order the panel lists them. */
|
|
1465
|
+
const CATEGORY_LABELS: readonly { key: string; label: string }[] = [
|
|
1466
|
+
{ key: "state", label: "State" },
|
|
1467
|
+
{ key: "computed", label: "Computed" },
|
|
1468
|
+
{ key: "data", label: "Data" },
|
|
1469
|
+
{ key: "expression", label: "Expressions" },
|
|
1470
|
+
{ key: "function", label: "Functions" },
|
|
1471
|
+
];
|
|
1472
|
+
|
|
1473
|
+
/**
|
|
1474
|
+
* The picker's fixed rows.
|
|
1475
|
+
*
|
|
1476
|
+
* GROUPED rather than divided: `jx-select` draws both halves of a group's delimiter, so the three
|
|
1477
|
+
* unlabelled `sp-menu-divider`s this replaces are named runs a reader can aim at.
|
|
1478
|
+
*/
|
|
1479
|
+
const ADD_GROUPS: readonly SignalOptionGroup[] = [
|
|
1480
|
+
{
|
|
1481
|
+
id: "values",
|
|
1482
|
+
label: "Values",
|
|
1483
|
+
rows: [
|
|
1484
|
+
{ label: "Value", value: "state" },
|
|
1485
|
+
{ label: "Computed", value: "computed" },
|
|
1486
|
+
],
|
|
1487
|
+
},
|
|
1488
|
+
{
|
|
1489
|
+
id: "sources",
|
|
1490
|
+
label: "Data sources",
|
|
1491
|
+
rows: [
|
|
1492
|
+
{ label: "Fetch from a URL", value: "request" },
|
|
1493
|
+
{ label: "LocalStorage", value: "localStorage" },
|
|
1494
|
+
{ label: "SessionStorage", value: "sessionStorage" },
|
|
1495
|
+
{ label: "IndexedDB", value: "indexedDB" },
|
|
1496
|
+
{ label: "Cookie", value: "cookie" },
|
|
1497
|
+
{ label: "Set", value: "set" },
|
|
1498
|
+
{ label: "Map", value: "map" },
|
|
1499
|
+
{ label: "FormData", value: "formData" },
|
|
1500
|
+
{ label: "From a module…", value: "external" },
|
|
1501
|
+
],
|
|
1502
|
+
},
|
|
1503
|
+
{
|
|
1504
|
+
id: "logic",
|
|
1505
|
+
label: "Logic",
|
|
1506
|
+
rows: [
|
|
1507
|
+
{ label: "Expression", value: "expression" },
|
|
1508
|
+
{ label: "Function", value: "function" },
|
|
1509
|
+
],
|
|
1510
|
+
},
|
|
1511
|
+
];
|
|
1512
|
+
|
|
1513
|
+
/** Everything the surface should be showing, from the panel state it was last handed. */
|
|
1514
|
+
export function signalsView(S: SignalsPanelState, ctx: SignalsPanelCtx): SignalsView {
|
|
1515
|
+
plans.clear();
|
|
1516
|
+
islands.clear();
|
|
1517
|
+
|
|
1518
|
+
const defs = S.document.state || {};
|
|
1519
|
+
const entries = Object.entries(defs);
|
|
1520
|
+
/* What the canvas actually resolved these to. `S.canvas` is the session's canvas record, so this
|
|
1521
|
+
costs a property read — the panel already had the scope in hand. */
|
|
1522
|
+
const liveScope = (S.canvas?.scope ?? null) as Record<string, unknown> | null;
|
|
1523
|
+
const scope = liveScope ?? {};
|
|
1524
|
+
/* A Refresh is out and the canvas has not answered. Read off the tab, so it survives the repaints
|
|
1525
|
+
between the press and the `dataScope` that ends it. */
|
|
1526
|
+
const refreshing = S.canvas?.refreshing === true;
|
|
1527
|
+
|
|
1528
|
+
/* Warm the extensions payload so manifest state classes appear in the add picker (the panel
|
|
1529
|
+
re-renders constantly; loadExtensions memoizes, so this is a one-time fetch per project). */
|
|
1530
|
+
void loadExtensions();
|
|
1531
|
+
|
|
1532
|
+
const grouped = new Map<string, [string, SignalDef][]>(
|
|
1533
|
+
CATEGORY_LABELS.map(({ key }) => [key, []]),
|
|
1534
|
+
);
|
|
1535
|
+
for (const [name, raw] of entries) {
|
|
1536
|
+
grouped.get(defCategory(raw))?.push([name, asSignalDef(raw)]);
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
S._collapsedSignalCats ||= new Set();
|
|
1540
|
+
const collapsed = S._collapsedSignalCats;
|
|
1541
|
+
|
|
1542
|
+
const categories: SignalCategoryView[] = [];
|
|
1543
|
+
for (const { key, label } of CATEGORY_LABELS) {
|
|
1544
|
+
const items = grouped.get(key) ?? [];
|
|
1545
|
+
if (items.length === 0) {
|
|
1546
|
+
continue;
|
|
1646
1547
|
}
|
|
1548
|
+
const rows: SignalRowView[] = items.map(([name, def]) => {
|
|
1549
|
+
const expanded = isDataRowExpanded(name);
|
|
1550
|
+
const live = scope[name];
|
|
1551
|
+
if (expanded) {
|
|
1552
|
+
islands.set(`${name}/tree`, (host) => {
|
|
1553
|
+
paintDataTree(host, unwrapSignal(live), name, repaint);
|
|
1554
|
+
});
|
|
1555
|
+
}
|
|
1556
|
+
return {
|
|
1557
|
+
badge: defBadgeLabel(def),
|
|
1558
|
+
category: defCategory(def),
|
|
1559
|
+
categoryLabel: label,
|
|
1560
|
+
deleteLabel: `Delete ${name}`,
|
|
1561
|
+
expanded,
|
|
1562
|
+
fields: expanded ? editorFields(S, name, def) : [],
|
|
1563
|
+
key: name,
|
|
1564
|
+
liveLabel: "Resolved to",
|
|
1565
|
+
name,
|
|
1566
|
+
...summary(name, def, live, liveScope !== null),
|
|
1567
|
+
};
|
|
1568
|
+
});
|
|
1569
|
+
categories.push({ key, label: `${label} (${items.length})`, open: !collapsed.has(key), rows });
|
|
1570
|
+
}
|
|
1571
|
+
|
|
1572
|
+
const groups = [...ADD_GROUPS];
|
|
1573
|
+
const imports = projectState?.projectConfig?.imports;
|
|
1574
|
+
if (imports) {
|
|
1575
|
+
groups.splice(2, 0, {
|
|
1576
|
+
id: "imports",
|
|
1577
|
+
label: "Project imports",
|
|
1578
|
+
rows: Object.keys(imports).map((k) => ({ label: k, value: `import:${k}` })),
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
const classes = extensionStateClasses();
|
|
1582
|
+
if (classes.length > 0) {
|
|
1583
|
+
groups.splice(imports ? 3 : 2, 0, {
|
|
1584
|
+
id: "extensions",
|
|
1585
|
+
label: "Extensions",
|
|
1586
|
+
rows: classes.map((cls) => ({ label: cls.name, value: `ext:${cls.name}` })),
|
|
1587
|
+
});
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
return {
|
|
1591
|
+
addGroups: groups,
|
|
1592
|
+
addOptions: [{ label: "+ Add…", value: "" }],
|
|
1593
|
+
addValue: "",
|
|
1594
|
+
categories,
|
|
1595
|
+
emptyMessage:
|
|
1596
|
+
"Data lives here — values this page can read, compute or fetch, " +
|
|
1597
|
+
"ready to bind to any element.",
|
|
1598
|
+
listState: entries.length === 0 ? "empty" : "listed",
|
|
1599
|
+
refreshState: ctx.refreshData && entries.length > 0 ? (refreshing ? "busy" : "idle") : "none",
|
|
1600
|
+
refreshing,
|
|
1601
|
+
};
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
// ─── Adding an entry ─────────────────────────────────────────────────────────
|
|
1605
|
+
|
|
1606
|
+
/** The first free name of the form `<base>`, `<base>1`, `<base>2`… */
|
|
1607
|
+
function freeName(S: SignalsPanelState, base: string): string {
|
|
1608
|
+
let candidate = base;
|
|
1609
|
+
let i = 1;
|
|
1610
|
+
while (S.document.state && S.document.state[candidate]) {
|
|
1611
|
+
candidate = base + i;
|
|
1612
|
+
i += 1;
|
|
1613
|
+
}
|
|
1614
|
+
return candidate;
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1617
|
+
/**
|
|
1618
|
+
* Add a def from one of the built-in templates under a free name, and expand it for editing. Shared
|
|
1619
|
+
* by the "+ Add…" picker and the panel's empty state, so both create the same thing.
|
|
1620
|
+
*/
|
|
1621
|
+
function addTemplateDef(type: string, S: SignalsPanelState): void {
|
|
1622
|
+
const template = DEF_TEMPLATES[type];
|
|
1623
|
+
if (!template) {
|
|
1624
|
+
return;
|
|
1647
1625
|
}
|
|
1626
|
+
const name = freeName(S, type === "function" ? "newFunction" : "$newSignal");
|
|
1627
|
+
transactDoc(activeTab.value, (t) =>
|
|
1628
|
+
mutateAddDef(t, name, structuredClone(template) as Record<string, JsonValue>),
|
|
1629
|
+
);
|
|
1630
|
+
setDataRowExpanded(name, true);
|
|
1631
|
+
repaint();
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
/** The name a prototype-backed entry is created under: `$contentCollection` for `ContentCollection`. */
|
|
1635
|
+
function prototypeName(S: SignalsPanelState, proto: string): string {
|
|
1636
|
+
return freeName(S, `$${proto.charAt(0).toLowerCase()}${proto.slice(1)}`);
|
|
1637
|
+
}
|
|
1648
1638
|
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1639
|
+
/** Create the entry the picker names — a template, an extension class, or a project import. */
|
|
1640
|
+
function addSignalOfType(type: string): void {
|
|
1641
|
+
const S = panelState;
|
|
1642
|
+
if (!S || !type) {
|
|
1643
|
+
return;
|
|
1644
|
+
}
|
|
1645
|
+
/* Extension-manifest state classes ("ext:Session"): no $src needed — the registry resolves them;
|
|
1646
|
+
the descriptor's stateDefaults seed the def (e.g. timing "client"). */
|
|
1647
|
+
if (type.startsWith("ext:")) {
|
|
1648
|
+
const proto = type.slice(4);
|
|
1649
|
+
const cls = extensionStateClasses().find((c) => c.name === proto);
|
|
1650
|
+
const name = prototypeName(S, proto);
|
|
1651
|
+
transactDoc(activeTab.value, (t) =>
|
|
1652
|
+
mutateAddDef(t, name, {
|
|
1653
|
+
$prototype: proto,
|
|
1654
|
+
...cls?.stateDefaults,
|
|
1655
|
+
} as Record<string, JsonValue>),
|
|
1656
|
+
);
|
|
1657
|
+
setDataRowExpanded(name, true);
|
|
1658
|
+
repaint();
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1661
|
+
/* A project import ("import:ContentCollection") is the prototype name and nothing else — the
|
|
1662
|
+
registry resolves the path. The schema is NOT fetched here: the entry is opened, and an open
|
|
1663
|
+
entry's field list is what asks for it (see {@link externalFields}), which repaints when it
|
|
1664
|
+
lands. Fetching in both places sent the same request twice. */
|
|
1665
|
+
if (type.startsWith("import:")) {
|
|
1666
|
+
const proto = type.slice(7);
|
|
1667
|
+
const name = prototypeName(S, proto);
|
|
1668
|
+
transactDoc(activeTab.value, (t) =>
|
|
1669
|
+
mutateAddDef(t, name, { $prototype: proto } as Record<string, JsonValue>),
|
|
1670
|
+
);
|
|
1671
|
+
setDataRowExpanded(name, true);
|
|
1672
|
+
repaint();
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
addTemplateDef(type, S);
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
// ─── The panel ───────────────────────────────────────────────────────────────
|
|
1679
|
+
|
|
1680
|
+
/** Ask the Navigator to paint again. Every action ends here or in a transaction. */
|
|
1681
|
+
function repaint(): void {
|
|
1682
|
+
panelCtx?.renderLeftPanel();
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
/** The plan a control names, or `undefined` if the projection has moved on without it. */
|
|
1686
|
+
function planFor(signal: string, fieldKey: string): FieldPlan | undefined {
|
|
1687
|
+
return plans.get(planKey(signal, fieldKey));
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1690
|
+
/** Write a value now, cancelling any debounce that was waiting to write the same field. */
|
|
1691
|
+
function commitNow(signal: string, fieldKey: string, value: string): void {
|
|
1692
|
+
const key = planKey(signal, fieldKey);
|
|
1693
|
+
clearTimeout(timers.get(key));
|
|
1694
|
+
timers.delete(key);
|
|
1695
|
+
const plan = plans.get(key);
|
|
1696
|
+
if (!plan?.write || value === plan.value) {
|
|
1697
|
+
return;
|
|
1698
|
+
}
|
|
1699
|
+
plan.write(value);
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
/** Everything a control in the document may ask for. */
|
|
1703
|
+
const ACTIONS: SignalsActions = {
|
|
1704
|
+
addChip: (signal, fieldKey, value) => planFor(signal, fieldKey)?.addChip?.(value),
|
|
1705
|
+
addRow: (signal, fieldKey) => planFor(signal, fieldKey)?.addRow?.(),
|
|
1706
|
+
addSignal: (type) => addSignalOfType(type),
|
|
1707
|
+
checkCell: (signal, fieldKey, row, cell, checked) =>
|
|
1708
|
+
planFor(signal, fieldKey)?.cellCheck?.(row, cell, checked),
|
|
1709
|
+
checkField: (signal, fieldKey, checked) => planFor(signal, fieldKey)?.check?.(checked),
|
|
1710
|
+
commitField: (signal, fieldKey, value) => commitNow(signal, fieldKey, value),
|
|
1711
|
+
dropChip: (signal, fieldKey, chip) => planFor(signal, fieldKey)?.dropChip?.(chip),
|
|
1712
|
+
dropRow: (signal, fieldKey, row) => planFor(signal, fieldKey)?.dropRow?.(row),
|
|
1713
|
+
dropSignal: (name) => transactDoc(activeTab.value, (t) => mutateRemoveDef(t, name)),
|
|
1714
|
+
editCell: (signal, fieldKey, row, cell, value) =>
|
|
1715
|
+
planFor(signal, fieldKey)?.cell?.(row, cell, value),
|
|
1716
|
+
/*
|
|
1717
|
+
* What a KEYSTROKE is worth is the field's own answer, which is why the document asks the same
|
|
1718
|
+
* question of every control and this decides. A rename committed per keystroke would rename the
|
|
1719
|
+
* entry eight times on the way to a nine-letter name.
|
|
1720
|
+
*/
|
|
1721
|
+
inputField: (signal, fieldKey, value) => {
|
|
1722
|
+
const key = planKey(signal, fieldKey);
|
|
1723
|
+
const plan = plans.get(key);
|
|
1724
|
+
if (!plan?.write || plan.live === "none") {
|
|
1725
|
+
return;
|
|
1726
|
+
}
|
|
1727
|
+
if (plan.live === "commit") {
|
|
1728
|
+
plan.write(value);
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1731
|
+
clearTimeout(timers.get(key));
|
|
1732
|
+
timers.set(
|
|
1733
|
+
key,
|
|
1734
|
+
setTimeout(() => {
|
|
1735
|
+
timers.delete(key);
|
|
1736
|
+
plans.get(key)?.write?.(value);
|
|
1737
|
+
}, DEBOUNCE_MS),
|
|
1738
|
+
);
|
|
1739
|
+
},
|
|
1740
|
+
pressField: (signal, fieldKey, action) => planFor(signal, fieldKey)?.press?.(action),
|
|
1741
|
+
refresh: () => {
|
|
1742
|
+
panelCtx?.refreshData?.();
|
|
1743
|
+
repaint();
|
|
1744
|
+
},
|
|
1745
|
+
toggleCategory: (key, open) => {
|
|
1746
|
+
const collapsed = panelState?._collapsedSignalCats;
|
|
1747
|
+
if (!collapsed) {
|
|
1748
|
+
return;
|
|
1672
1749
|
}
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
mutateUpdateDef(t, name, { $export: v || undefined }),
|
|
1678
|
-
),
|
|
1679
|
-
)
|
|
1680
|
-
: nothing
|
|
1750
|
+
if (open) {
|
|
1751
|
+
collapsed.delete(key);
|
|
1752
|
+
} else {
|
|
1753
|
+
collapsed.add(key);
|
|
1681
1754
|
}
|
|
1682
|
-
|
|
1683
|
-
|
|
1755
|
+
repaint();
|
|
1756
|
+
},
|
|
1757
|
+
toggleRow: (name) => {
|
|
1758
|
+
setDataRowExpanded(name, !isDataRowExpanded(name));
|
|
1759
|
+
repaint();
|
|
1760
|
+
},
|
|
1761
|
+
};
|
|
1762
|
+
|
|
1763
|
+
/**
|
|
1764
|
+
* Fill one island: the four foreign surfaces an entry's editor embeds, plus its value tree.
|
|
1765
|
+
*
|
|
1766
|
+
* The painters are closures the projection built, so each is already holding the def that is on
|
|
1767
|
+
* screen; a host whose entry has since gone finds nothing and is emptied rather than being painted
|
|
1768
|
+
* from a stale closure.
|
|
1769
|
+
*
|
|
1770
|
+
* @param {HTMLElement} host - The empty node the document drew
|
|
1771
|
+
* @param {string} signal - The entry it belongs to
|
|
1772
|
+
* @param {string} slot - `expression`, `statements`, `media`, `schema` or `tree`
|
|
1773
|
+
*/
|
|
1774
|
+
function paintIsland(host: HTMLElement, signal: string, slot: string): void {
|
|
1775
|
+
const painter = islands.get(`${signal}/${slot}`);
|
|
1776
|
+
if (painter) {
|
|
1777
|
+
painter(host);
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
host.replaceChildren();
|
|
1781
|
+
}
|
|
1782
|
+
|
|
1783
|
+
/** The mounted surface, and the container it is standing in. */
|
|
1784
|
+
let standing: { handle: SignalsSurfaceHandle; host: HTMLElement } | null = null;
|
|
1785
|
+
|
|
1786
|
+
/**
|
|
1787
|
+
* Draw the Data panel — mounting the document the first time, and projecting into it every time
|
|
1788
|
+
* after.
|
|
1789
|
+
*
|
|
1790
|
+
* The document goes into `.panel-content`, not into the `.panel-body` this is handed, for the
|
|
1791
|
+
* reason `panels/git-panel.ts` states: only one of them is the node lit renders this panel's body
|
|
1792
|
+
* into, and appending to the other would leave the panel drawn under whatever the Navigator paints
|
|
1793
|
+
* next.
|
|
1794
|
+
*
|
|
1795
|
+
* A container that has CHANGED means the Navigator rebuilt the panel, so the panel-local record of
|
|
1796
|
+
* which parameter editor is in its advanced view — and which rename was refused — starts over with
|
|
1797
|
+
* it. Neither belongs to the document, and neither should outlive the panel that was showing it.
|
|
1798
|
+
*
|
|
1799
|
+
* @param {HTMLElement} host - The painted `.panel-body`
|
|
1800
|
+
* @param {SignalsPanelState} S - The focused document, as the Navigator reads it
|
|
1801
|
+
* @param {SignalsPanelCtx} ctx - The repaint, and the Refresh verb
|
|
1802
|
+
*/
|
|
1803
|
+
export function mountSignalsPanel(
|
|
1804
|
+
host: HTMLElement,
|
|
1805
|
+
S: SignalsPanelState,
|
|
1806
|
+
ctx: SignalsPanelCtx,
|
|
1807
|
+
): void {
|
|
1808
|
+
const container = host.querySelector<HTMLElement>(".panel-content") ?? host;
|
|
1809
|
+
panelState = S;
|
|
1810
|
+
panelCtx = ctx;
|
|
1811
|
+
/* A row the reader collapsed took its tree's host out of the page, and the tree registry holds a
|
|
1812
|
+
host by reference — this is the one moment a closed tree can be taken down. */
|
|
1813
|
+
disposeDetachedDataTrees();
|
|
1814
|
+
if (standing && (standing.host !== container || !standing.handle.attached())) {
|
|
1815
|
+
standing.handle.dispose();
|
|
1816
|
+
standing = null;
|
|
1817
|
+
advancedParamOpen.clear();
|
|
1818
|
+
schemaRequests.clear();
|
|
1819
|
+
renameError = null;
|
|
1820
|
+
}
|
|
1821
|
+
const view = signalsView(S, ctx);
|
|
1822
|
+
if (standing) {
|
|
1823
|
+
standing.handle.update(view);
|
|
1824
|
+
return;
|
|
1825
|
+
}
|
|
1826
|
+
standing = {
|
|
1827
|
+
handle: mountSignalsSurface(container, view, ACTIONS, paintIsland),
|
|
1828
|
+
host: container,
|
|
1829
|
+
};
|
|
1684
1830
|
}
|
|
1685
1831
|
|
|
1686
1832
|
// ─── Commands ─────────────────────────────────────────────────────────────────
|
|
@@ -1800,6 +1946,6 @@ export function registerSignalsCommands(registry: CommandRegistry): void {
|
|
|
1800
1946
|
It was registered here `rail: false`, waiting for plan §11.2's merge into Data, and the merge did
|
|
1801
1947
|
not follow: the button was removed and the editor was left reachable only by typing "State" into
|
|
1802
1948
|
the palette. Declaring a state variable — or a component property, which is a state entry with a
|
|
1803
|
-
default — is not an advanced move to hide behind a search box, so
|
|
1804
|
-
|
|
1949
|
+
default — is not an advanced move to hide behind a search box, so the entry list is now the Data
|
|
1950
|
+
panel's own document and there is no second record. A stored `leftTab: "state"` migrates to
|
|
1805
1951
|
`data` in `shell.ts`. */
|