@jxsuite/studio 4.0.0 → 5.0.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-xp8vnwma.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-fyf63pmy.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-2w1f35qs.js +18 -0
- package/dist/chunks/content-source-3wqeas45.js +46 -0
- package/dist/chunks/convert-to-repeater-d0cgnb6d.js +639 -0
- package/dist/chunks/dialog-s890b918.js +85 -0
- package/dist/chunks/editor-3qhbg51x.js +35 -0
- package/dist/chunks/entry-commands-d6g50j7s.js +42 -0
- package/dist/chunks/extension-sections-fy7q4fe9.js +651 -0
- package/dist/chunks/file-ops-3btj6st0.js +47 -0
- package/dist/chunks/files-ef8fgs3w.js +72 -0
- package/dist/chunks/format-host-3c33nwr8.js +40 -0
- package/dist/chunks/fs-events-13tj6sex.js +18 -0
- package/dist/chunks/git-panel-4wp2rn9b.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-jgesd924.js +115 -0
- package/dist/chunks/{index-nj44jpzj.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-1nq5evsh.js +29 -0
- package/dist/chunks/layers-panel-ar2gh90g.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-2qbetq73.js +33 -0
- package/dist/chunks/menu-q99sy9rf.js +285 -0
- package/dist/chunks/menu-rf7n72mf.js +14 -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-rws7k4br.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-98f7sr3g.js +27 -0
- package/dist/chunks/quick-search-srzz83k2.js +45 -0
- package/dist/chunks/references-n553f0e1.js +26 -0
- package/dist/chunks/right-panel-rrgx2dn3.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-18avnhjp.js +2430 -0
- package/dist/chunks/{studio-kryvsw6j.js → studio-1k325pwf.js} +48 -48
- package/dist/chunks/{studio-hbtp0eh1.js → studio-25hkdrkw.js} +356 -85
- package/dist/chunks/studio-26d7myd1.js +258 -0
- package/dist/chunks/{studio-p8j86wag.js → studio-38emantv.js} +22 -5
- package/dist/chunks/studio-3gxv60tn.js +422 -0
- package/dist/chunks/{studio-8swrjbny.js → studio-3hxkd92t.js} +275 -163
- package/dist/chunks/studio-4m0haseg.js +185 -0
- package/dist/chunks/{studio-ynxwkrzd.js → studio-4wbb9ct0.js} +1953 -1785
- package/dist/chunks/{studio-gv8vvshc.js → studio-5523bpzt.js} +11 -11
- package/dist/chunks/{studio-5q69gfag.js → studio-57mf66w2.js} +1753 -2484
- package/dist/chunks/studio-5jy81t3s.js +17463 -0
- package/dist/chunks/studio-5rt7sqa0.js +1168 -0
- package/dist/chunks/studio-5te5tqng.js +81 -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-eenfkt4e.js → studio-79y5sz51.js} +52 -38
- package/dist/chunks/{studio-4q0wznzf.js → studio-7e11jpa9.js} +461 -145
- package/dist/chunks/{studio-q7v10cg2.js → studio-7r42c6ca.js} +17 -7
- package/dist/chunks/studio-7xptrg0j.js +954 -0
- package/dist/chunks/{studio-cft9r4r7.js → studio-81390n6f.js} +22 -22
- package/dist/chunks/{studio-q43a5y9h.js → studio-8jn5yhyn.js} +16 -16
- package/dist/chunks/studio-8qtrpzr3.js +839 -0
- package/dist/chunks/{studio-dg7zyt5x.js → studio-98b3z07n.js} +1 -1
- package/dist/chunks/studio-9xxz1q69.js +1356 -0
- package/dist/chunks/studio-akbww0j5.js +13615 -0
- package/dist/chunks/studio-arfpe389.js +195 -0
- package/dist/chunks/{studio-2zkz8evn.js → studio-az3zd49t.js} +473 -281
- package/dist/chunks/studio-cpvb2ed1.js +1171 -0
- package/dist/chunks/{studio-9r6j5ev1.js → studio-d0300636.js} +5078 -19
- package/dist/chunks/{studio-bgxt93ga.js → studio-d055c37e.js} +13 -13
- package/dist/chunks/studio-d1ja2x9r.js +362 -0
- package/dist/chunks/{studio-b9b6t1dg.js → studio-e1f24eff.js} +5664 -5666
- package/dist/chunks/{studio-4pqqxbrt.js → studio-em9nw8eh.js} +301 -112
- package/dist/chunks/studio-erdatzvp.js +1190 -0
- package/dist/chunks/{studio-g1wdche0.js → studio-esrxgwh6.js} +80 -80
- package/dist/chunks/studio-fabxk6r9.js +2811 -0
- package/dist/chunks/{studio-3n4ndrzh.js → studio-h4nvnjvz.js} +303 -303
- package/dist/chunks/studio-hhvs4sdg.js +195 -0
- package/dist/chunks/{studio-68n67094.js → studio-kcgff4r0.js} +15 -18
- package/dist/chunks/{studio-685s0ytq.js → studio-nqgawfks.js} +572 -572
- package/dist/chunks/{studio-xrapxwwc.js → studio-p1dw8wfw.js} +55 -29
- package/dist/chunks/studio-pm6hgy7r.js +4763 -0
- package/dist/chunks/studio-q0a3yk4n.js +33 -0
- package/dist/chunks/{studio-wcy8trfx.js → studio-q3x06vmj.js} +23 -22
- package/dist/chunks/studio-qahxvnan.js +111 -0
- package/dist/chunks/{studio-53n8bp04.js → studio-r6xmmtfh.js} +1 -1
- package/dist/chunks/{studio-kjjsjak4.js → studio-rkstz940.js} +3661 -6136
- package/dist/chunks/{studio-tvgyw9xw.js → studio-rnyf3f8e.js} +83 -250
- package/dist/chunks/studio-rpyah2ew.js +2805 -0
- package/dist/chunks/studio-rs76k67r.js +12 -0
- package/dist/chunks/{studio-yxrkyv8a.js → studio-s6z853aq.js} +12108 -11766
- package/dist/chunks/{studio-sf7zjgv1.js → studio-sgdc5pfc.js} +8 -8
- package/dist/chunks/studio-tb3ev01j.js +763 -0
- package/dist/chunks/{studio-t5wqwzdh.js → studio-tjjk7jvk.js} +1 -1
- package/dist/chunks/studio-vhv5tgxm.js +51 -0
- package/dist/chunks/{studio-htsf0w6m.js → studio-vz68yn9e.js} +31 -154
- package/dist/chunks/studio-wqpngpsk.js +174 -0
- package/dist/chunks/{studio-eesa1myv.js → studio-x3rty6tr.js} +58 -58
- package/dist/chunks/studio-xsykpvsh.js +8448 -0
- package/dist/chunks/{studio-92ab2jqe.js → studio-y9zedyjn.js} +14 -14
- package/dist/chunks/studio-yxa81f45.js +12 -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-wrew8zk4.js +69 -0
- package/dist/chunks/{ws-client-6shc5rze.js → ws-client-ny4zc51r.js} +20 -22
- package/dist/iframe-entry.js +1812 -698
- package/dist/manifest.json +7 -0
- package/dist/studio.js +52900 -36573
- 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/favicon.ico +0 -0
- package/index.html +2 -0
- package/package.json +22 -55
- package/src/about/about-modal.ts +73 -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 +310 -117
- 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 +369 -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 +165 -8
- 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 +65 -26
- package/src/editor/slash-menu.ts +150 -157
- package/src/files/file-ops.ts +117 -11
- package/src/files/files.ts +949 -664
- package/src/files/json-layout.ts +474 -0
- package/src/files/media-paths.ts +13 -42
- package/src/files/media-usage.ts +41 -131
- 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/document.ts +2 -0
- package/src/hosting/layout.ts +10 -0
- package/src/i18n/i18n-commands.ts +127 -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 +201 -250
- package/src/new-project/location-fields.ts +101 -165
- package/src/new-project/new-project-modal.ts +376 -367
- package/src/packages/jxsuite-update.ts +76 -18
- 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 -153
- package/src/panels/ai-chat/sessions-view.ts +31 -62
- package/src/panels/ai-panel.ts +210 -185
- package/src/panels/block-action-bar.ts +437 -643
- 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 +153 -133
- package/src/panels/imports-panel.ts +388 -414
- package/src/panels/jump-bar.ts +174 -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 +177 -99
- package/src/panels/properties-panel.ts +1525 -966
- package/src/panels/provenance.ts +31 -113
- package/src/panels/quick-search.ts +217 -172
- package/src/panels/right-panel.ts +135 -169
- package/src/panels/seo-modal.ts +346 -167
- package/src/panels/settings-menu.ts +109 -646
- 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 +385 -287
- package/src/platforms/cloud.ts +347 -86
- 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 +165 -299
- package/src/services/a11y-report.ts +100 -43
- package/src/services/ai-command-tools.ts +468 -0
- package/src/services/ai-import-tools.ts +50 -12
- package/src/services/ai-models.ts +108 -1
- package/src/services/ai-session-store.ts +6 -0
- package/src/services/ai-system-prompt.ts +110 -18
- package/src/services/ai-tools.ts +28 -143
- package/src/services/ai-write-report.ts +174 -0
- package/src/services/context-manager.ts +12 -1
- package/src/services/document-assistant.ts +50 -2
- 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 +17 -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 +193 -12
- package/src/settings/preferences-dialog.ts +384 -307
- 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 +90 -67
- 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 +984 -0
- package/src/{panels/toolbar.ts → surfaces/commandbar.ts} +414 -389
- 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 +163 -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 +71 -15
- package/src/tabs/tab.ts +52 -1
- package/src/types.ts +49 -3
- package/src/ui/ai-credentials-form.ts +104 -100
- package/src/ui/ai-managed-connect.ts +116 -49
- package/src/ui/ai-model-picker.ts +102 -47
- package/src/ui/cf-account-picker.ts +135 -0
- 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 +293 -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 +15 -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 -1532
- package/styles/tokens.css +65 -136
- package/styles/tokens.json +84 -0
- package/dist/chunks/Overlay.dev-723bv0df.js +0 -23
- package/dist/chunks/adapter-jkcyhgtr.js +0 -14
- package/dist/chunks/cf-settings-45fgyx8y.js +0 -21
- package/dist/chunks/components-zmx10t62.js +0 -21
- package/dist/chunks/content-source-t88vb2dr.js +0 -49
- package/dist/chunks/convert-to-repeater-dhw1nrz9.js +0 -268
- package/dist/chunks/editor-cptq5jp1.js +0 -37
- package/dist/chunks/entry-commands-6z6bhxg0.js +0 -46
- package/dist/chunks/extension-sections-3ydgjbdy.js +0 -311
- package/dist/chunks/file-ops-rskmhxyq.js +0 -48
- package/dist/chunks/files-0cratsr0.js +0 -92
- package/dist/chunks/flow-qbfgrpeh.js +0 -634
- package/dist/chunks/focus-trap.esm-ggawf1hy.js +0 -1024
- package/dist/chunks/focus-visible-6eg70z18.js +0 -167
- package/dist/chunks/format-host-tgcmfs4p.js +0 -43
- package/dist/chunks/fs-events-hrrg328g.js +0 -21
- package/dist/chunks/git-panel-0yywyk76.js +0 -58
- package/dist/chunks/index-zqxmqxmh.js +0 -109
- package/dist/chunks/jx-validate-8q2g05x1.js +0 -19
- package/dist/chunks/layers-panel-z3tqe5cd.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-54jvhskq.js +0 -28
- package/dist/chunks/quick-search-cgfmjwq5.js +0 -65
- package/dist/chunks/references-3qx8fkyp.js +0 -29
- package/dist/chunks/right-panel-cqp8bc4v.js +0 -46
- package/dist/chunks/section-registry-sx1ykvkw.js +0 -30
- package/dist/chunks/sp-overlay.dev-yztmtrs1.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-xcvpnqr0.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-18j3bmcz.js +0 -14
- package/dist/chunks/studio-1hbqnt19.js +0 -1823
- package/dist/chunks/studio-4rzyrtjz.js +0 -426
- package/dist/chunks/studio-5rywrkxz.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-9c4k4vhv.js +0 -357
- package/dist/chunks/studio-9n4mrmnb.js +0 -184
- package/dist/chunks/studio-a0tswy0w.js +0 -58
- package/dist/chunks/studio-ahjfdy6e.js +0 -387
- package/dist/chunks/studio-ahnzd9rg.js +0 -23
- package/dist/chunks/studio-bw5dz6km.js +0 -608
- package/dist/chunks/studio-cb12yz9w.js +0 -15055
- package/dist/chunks/studio-d1ks410h.js +0 -175
- package/dist/chunks/studio-eqq1w638.js +0 -113
- package/dist/chunks/studio-ex8zyp98.js +0 -1813
- package/dist/chunks/studio-h4rxscfc.js +0 -10
- package/dist/chunks/studio-kyh9avvv.js +0 -28
- package/dist/chunks/studio-kyk2v92a.js +0 -659
- package/dist/chunks/studio-m3hwn7y1.js +0 -120
- package/dist/chunks/studio-mmv72a27.js +0 -4344
- package/dist/chunks/studio-n8b9wqwy.js +0 -291
- package/dist/chunks/studio-pdfjntbk.js +0 -6939
- package/dist/chunks/studio-qq6jym04.js +0 -53
- package/dist/chunks/studio-r1r7afxx.js +0 -1066
- 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-txwkm6p9.js +0 -12
- package/dist/chunks/studio-v38hmvx1.js +0 -390
- package/dist/chunks/studio-vadzgkrz.js +0 -318
- package/dist/chunks/studio-wx5609xd.js +0 -341
- package/dist/chunks/studio-z6nw2ek2.js +0 -18
- package/dist/chunks/studio-zczps70q.js +0 -378
- package/dist/chunks/workspace-xpres40v.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
|
@@ -2,30 +2,58 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* The expression editor — one operator row plus an operand editor per slot, nested to any depth.
|
|
4
4
|
*
|
|
5
|
-
* **
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
5
|
+
* **This is the flow; `surfaces/expression-editor.json` is the surface.** This module decides what
|
|
6
|
+
* an operator needs, which rung of the value ladder an operand occupies, what a literal's type is,
|
|
7
|
+
* which refs a position may bind, what the live preview says at each node, and what every gesture
|
|
8
|
+
* commits. It draws nothing: {@link flattenExpression} walks the tree once and hands the document a
|
|
9
|
+
* FLAT list of rows that already carry their own indent, and {@link mountExpressionEditor} is the
|
|
10
|
+
* seam between the two.
|
|
11
|
+
*
|
|
12
|
+
* **There is no Monaco here, and never was.** The editor that hosts Monaco is the Logic dock's code
|
|
13
|
+
* surface (`panels/formula-workspace.ts`); this one is a form, and every part of it — the chip
|
|
14
|
+
* strip, the operator picker, the operand ladder, the positional args, the `switch` cases, the live
|
|
15
|
+
* badges — is markup a document can own. So nothing in this conversion is an island, and the four
|
|
16
|
+
* hosts that used to `litRender` into an empty node now mount a document into it instead.
|
|
17
|
+
*
|
|
18
|
+
* **Recursion is a property of the WALK.** An operand may be another expression, an aggregate's
|
|
19
|
+
* per-item body is one, and either may hold more of both — while a document's one repeater is
|
|
20
|
+
* `$prototype: "Array"` over a list. So the walk emits rows in reading order and each says how far
|
|
21
|
+
* in it sits, exactly as `panels/statement-editor.ts` does for the statement tree. A row's key is
|
|
22
|
+
* its node's own path plus the slot it fills, which is unique by construction: two rows of one
|
|
23
|
+
* expression cannot occupy one position in it.
|
|
24
|
+
*
|
|
25
|
+
* **A layout that could not shrink is what sent this editor to a stylesheet, and then to a
|
|
26
|
+
* document.** Every row here used to be an inline `display:flex;…` attribute with a hard
|
|
27
|
+
* `min-width` on the pickers, and no rung of that chain could set `min-width: 0` — so at Inspector
|
|
28
|
+
* width the operand controls refused to shrink and Operator, Target and Value were clipped by the
|
|
29
|
+
* edge of the window. The editor is drawn in the Inspector (~280px), in the Navigator, and in the
|
|
30
|
+
* Bottom dock's Logic tab (very wide), so every row wraps to a stack rather than sizing itself in
|
|
31
|
+
* pixels. The document's `style` block owns that now, and it emits no class at all.
|
|
32
|
+
*
|
|
33
|
+
* @docs studio/logic/formulas
|
|
11
34
|
*/
|
|
12
|
-
import { html, nothing } from "lit-html";
|
|
13
|
-
import { live } from "lit-html/directives/live.js";
|
|
14
35
|
import { PURE_METHOD_OPS } from "@jxsuite/runtime/expression";
|
|
15
36
|
import { isJsonObject, isRef } from "@jxsuite/schema/guards";
|
|
16
|
-
import {
|
|
17
|
-
import { renderFormulaChips } from "./formula-chips";
|
|
37
|
+
import { formulaChipStrip } from "./formula-chips";
|
|
18
38
|
import { applyCatalogPick, calleeEntry, formulaCatalog } from "./formula-catalog";
|
|
19
|
-
import { openFormulaPalette } from "
|
|
39
|
+
import { openFormulaPalette } from "../surfaces/formula-palette";
|
|
20
40
|
import { VALUE_SOURCE_LABELS } from "./value-source";
|
|
21
|
-
import {
|
|
41
|
+
import {
|
|
42
|
+
disposeDetachedExpressionEditors,
|
|
43
|
+
renderExpressionEditorSurface,
|
|
44
|
+
} from "../surfaces/expression-editor";
|
|
22
45
|
|
|
46
|
+
import type {
|
|
47
|
+
ExprOption,
|
|
48
|
+
ExprRowView,
|
|
49
|
+
ExprSelectGroup,
|
|
50
|
+
ExpressionActions,
|
|
51
|
+
} from "../surfaces/expression-editor";
|
|
23
52
|
import type {
|
|
24
53
|
JxExpressionNode,
|
|
25
54
|
JxExpressionOperand,
|
|
26
55
|
JxStateDefinition,
|
|
27
56
|
} from "@jxsuite/schema/types";
|
|
28
|
-
import type { TemplateResult } from "lit-html";
|
|
29
57
|
|
|
30
58
|
// ─── Operator Categories ────────────────────────────────────────────────────
|
|
31
59
|
|
|
@@ -129,6 +157,70 @@ const OPERATOR_GROUPS = [
|
|
|
129
157
|
{ label: "Function", ops: ["call"] },
|
|
130
158
|
];
|
|
131
159
|
|
|
160
|
+
/**
|
|
161
|
+
* The operator picker's rows, in the kit's own `optgroup` shape.
|
|
162
|
+
*
|
|
163
|
+
* Built once: the list is closed, and a delimited run is what the kit draws for it — the label
|
|
164
|
+
* names the group in the accessibility tree and the legend inside it is what a reader sees, which
|
|
165
|
+
* is the pair `<sp-menu-divider>` plus a header span was standing in for.
|
|
166
|
+
*/
|
|
167
|
+
const OPERATOR_SELECT_GROUPS: ExprSelectGroup[] = OPERATOR_GROUPS.map((group) => ({
|
|
168
|
+
id: group.label,
|
|
169
|
+
label: group.label,
|
|
170
|
+
rows: group.ops.map((op) => ({ label: op, value: op })),
|
|
171
|
+
}));
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The same rows, narrowed to the operators a position's grammar admits, with empty groups dropped.
|
|
175
|
+
*
|
|
176
|
+
* The full table is what it filters, rather than a second list written out per position: an
|
|
177
|
+
* operator that gains a group, or moves between two, keeps its label and its neighbours here
|
|
178
|
+
* without anybody remembering to say so twice.
|
|
179
|
+
*/
|
|
180
|
+
function restrictGroups(operators: readonly string[]): ExprSelectGroup[] {
|
|
181
|
+
const allowed = new Set(operators);
|
|
182
|
+
return OPERATOR_SELECT_GROUPS.map((group) => ({
|
|
183
|
+
...group,
|
|
184
|
+
rows: group.rows.filter((row) => allowed.has(row.value)),
|
|
185
|
+
})).filter((group) => group.rows.length > 0);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** The three rungs of the value ladder, spelled the way `ui/value-source.ts` spells them (§6.3). */
|
|
189
|
+
const SOURCE_OPTIONS: ExprOption[] = [
|
|
190
|
+
{ label: VALUE_SOURCE_LABELS.literal, value: "literal" },
|
|
191
|
+
{ label: VALUE_SOURCE_LABELS.ref, value: "ref" },
|
|
192
|
+
{ label: VALUE_SOURCE_LABELS.expression, value: "expression" },
|
|
193
|
+
];
|
|
194
|
+
|
|
195
|
+
/** What a literal may be. Abbreviated because the picker sits beside the value it types. */
|
|
196
|
+
const TYPE_OPTIONS: ExprOption[] = [
|
|
197
|
+
{ label: "str", value: "string" },
|
|
198
|
+
{ label: "num", value: "number" },
|
|
199
|
+
{ label: "bool", value: "boolean" },
|
|
200
|
+
{ label: "null", value: "null" },
|
|
201
|
+
];
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Which control types a literal of each type.
|
|
205
|
+
*
|
|
206
|
+
* A map rather than a chain of ternaries because the two vocabularies are genuinely different: the
|
|
207
|
+
* type is what the value IS, and the control is what edits it — `null` is a type with no control at
|
|
208
|
+
* all, drawn as an inert word.
|
|
209
|
+
*/
|
|
210
|
+
const LITERAL_CONTROLS: Record<string, string> = {
|
|
211
|
+
boolean: "bool",
|
|
212
|
+
null: "null",
|
|
213
|
+
number: "number",
|
|
214
|
+
string: "text",
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/** Where an event handler's operand may bind besides the document's own signals. */
|
|
218
|
+
const EVENT_REFS = ["event#/detail", "event#/target/value"];
|
|
219
|
+
|
|
220
|
+
/** What a slot with nothing to bind to says, in the shell's one empty-state voice. */
|
|
221
|
+
const NO_BINDINGS_MESSAGE = "A binding points at a value this page holds.";
|
|
222
|
+
const NO_BINDINGS_DETAIL = "Add one in the State panel and it shows up here.";
|
|
223
|
+
|
|
132
224
|
interface OperatorInfo {
|
|
133
225
|
needsValue: boolean;
|
|
134
226
|
needsInitial: boolean;
|
|
@@ -329,7 +421,7 @@ export function expressionHint(node: unknown) {
|
|
|
329
421
|
return `${targetLabel} ${op} …`;
|
|
330
422
|
}
|
|
331
423
|
|
|
332
|
-
// ───
|
|
424
|
+
// ─── What the editor is handed ──────────────────────────────────────────────
|
|
333
425
|
|
|
334
426
|
/** Preview data computed by services/preview-eval.ts — display strings keyed by node path. */
|
|
335
427
|
export interface EditorPreview {
|
|
@@ -338,615 +430,744 @@ export interface EditorPreview {
|
|
|
338
430
|
mutating: boolean;
|
|
339
431
|
}
|
|
340
432
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
433
|
+
export interface ExpressionEditorOpts {
|
|
434
|
+
stateDefs: string[];
|
|
435
|
+
allowEventRef: boolean;
|
|
436
|
+
depth?: number;
|
|
437
|
+
preview?: EditorPreview | null;
|
|
438
|
+
path?: (string | number)[];
|
|
439
|
+
/** Full state defs map — resolves named-formula catalog entries (call labels, palette). */
|
|
440
|
+
stateEntries?: Record<string, JxStateDefinition> | null;
|
|
441
|
+
/** Chip-strip click hook (depth 0). No-op when absent. */
|
|
442
|
+
onChipSelect?: (path: (string | number)[]) => void;
|
|
443
|
+
/** Vendors a packaged formula's state entry into the document on catalog pick. */
|
|
444
|
+
onInsertDef?: (name: string, def: JxStateDefinition) => void;
|
|
445
|
+
/**
|
|
446
|
+
* The operators the POSITION's own grammar admits, when it admits fewer than all of them.
|
|
447
|
+
*
|
|
448
|
+
* Most positions take an `ExpressionEntry`, whose grammar is the whole operator table, and leave
|
|
449
|
+
* this absent. An element's `tagName` does not: `defs/tag-expression.schema.ts` admits `?:` and
|
|
450
|
+
* `switch` and nothing else, because every branch there must resolve to a literal tag the
|
|
451
|
+
* pipeline can enumerate without evaluating. The panel used to seed that shape correctly and then
|
|
452
|
+
* hand it to this editor unrestricted — so the next click could reoperate it to `capitalize`, or
|
|
453
|
+
* pick `toUpperCase` out of the formula catalog, and write a document its own validator rejects.
|
|
454
|
+
* A whole-document projection then read `cases` off a node that had none.
|
|
455
|
+
*
|
|
456
|
+
* **Depth 0 only.** The restriction is the position's, and a tag choice's `target` is an ordinary
|
|
457
|
+
* `ExpressionOperand` — the discriminant may be any expression at all. Restricting the nested
|
|
458
|
+
* walk too would have taken that away.
|
|
459
|
+
*/
|
|
460
|
+
operators?: readonly string[];
|
|
347
461
|
}
|
|
348
462
|
|
|
349
|
-
|
|
463
|
+
/** Everything one gesture on one row may ask for. Each row registers only what it offers. */
|
|
464
|
+
interface RowPlan {
|
|
465
|
+
setOperator?: (value: string) => void;
|
|
466
|
+
browse?: (anchor: HTMLElement) => void;
|
|
467
|
+
setSource?: (value: string) => void;
|
|
468
|
+
setLiteralType?: (value: string) => void;
|
|
469
|
+
setText?: (value: string) => void;
|
|
470
|
+
setNumber?: (value: string) => void;
|
|
471
|
+
setBool?: (checked: boolean) => void;
|
|
472
|
+
setRef?: (value: string) => void;
|
|
473
|
+
renameCase?: (value: string) => void;
|
|
474
|
+
remove?: () => void;
|
|
475
|
+
act?: () => void;
|
|
476
|
+
}
|
|
350
477
|
|
|
351
478
|
/**
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
479
|
+
* The whole projection: what the document draws, and what each key in it commits.
|
|
480
|
+
*
|
|
481
|
+
* Exported for its own test — the projection IS the editor now, so what it contains and what a key
|
|
482
|
+
* addresses are the contract rather than an implementation detail of a template.
|
|
356
483
|
*/
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
)
|
|
362
|
-
|
|
363
|
-
const eventRefs = opts.allowEventRef ? ["event#/detail", "event#/target/value"] : [];
|
|
364
|
-
const allRefs = [...stateRefs, ...eventRefs];
|
|
365
|
-
const isCustom = refVal && !allRefs.includes(refVal);
|
|
366
|
-
|
|
367
|
-
// Nothing to pick and nothing already picked: a picker whose only entry is a disabled "No state
|
|
368
|
-
// Defined" is a dead end. Say what a binding is instead, in the shell's one empty-state voice.
|
|
369
|
-
if (allRefs.length === 0 && !refVal) {
|
|
370
|
-
return renderEmptyState({
|
|
371
|
-
compact: true,
|
|
372
|
-
detail: "Add one in the State panel and it shows up here.",
|
|
373
|
-
message: "A binding points at a value this page holds.",
|
|
374
|
-
});
|
|
375
|
-
}
|
|
484
|
+
export interface ExpressionProjection {
|
|
485
|
+
rows: ExprRowView[];
|
|
486
|
+
plans: Map<string, RowPlan>;
|
|
487
|
+
/** A chip's joined path back to the path it stands for. */
|
|
488
|
+
chipPaths: Map<string, (string | number)[]>;
|
|
489
|
+
}
|
|
376
490
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
const val = (e.target as HTMLInputElement).value;
|
|
385
|
-
if (val === "__custom__") {
|
|
386
|
-
return;
|
|
387
|
-
}
|
|
388
|
-
onRefChange(val);
|
|
389
|
-
}}
|
|
390
|
-
>
|
|
391
|
-
${stateRefs.map(
|
|
392
|
-
(r) => html`<sp-menu-item value=${r}>${r.replace("#/state/", "")}</sp-menu-item>`,
|
|
393
|
-
)}
|
|
394
|
-
${
|
|
395
|
-
eventRefs.length > 0
|
|
396
|
-
? html`
|
|
397
|
-
<sp-menu-divider></sp-menu-divider>
|
|
398
|
-
${eventRefs.map((r) => html`<sp-menu-item value=${r}>${r}</sp-menu-item>`)}
|
|
399
|
-
`
|
|
400
|
-
: nothing
|
|
401
|
-
}
|
|
402
|
-
</sp-picker>
|
|
403
|
-
`;
|
|
491
|
+
/** The walk's own state: the options it was given, plus what it has emitted so far. */
|
|
492
|
+
interface Walk {
|
|
493
|
+
opts: ExpressionEditorOpts;
|
|
494
|
+
preview: EditorPreview | null;
|
|
495
|
+
rows: ExprRowView[];
|
|
496
|
+
plans: Map<string, RowPlan>;
|
|
497
|
+
chipPaths: Map<string, (string | number)[]>;
|
|
404
498
|
}
|
|
405
499
|
|
|
406
|
-
|
|
500
|
+
/**
|
|
501
|
+
* A row under construction: every field optional, and an explicit `undefined` allowed.
|
|
502
|
+
*
|
|
503
|
+
* `Partial<ExprRowView>` cannot be it. `exactOptionalPropertyTypes` is on, so a spread of one is
|
|
504
|
+
* typed `string | undefined` at a key the target declares as `string` — which is a real distinction
|
|
505
|
+
* everywhere a key means "absent", and a false one here, where every default is supplied below.
|
|
506
|
+
*/
|
|
507
|
+
type RowPatch = { [K in keyof ExprRowView]?: ExprRowView[K] | undefined };
|
|
407
508
|
|
|
408
509
|
/**
|
|
409
|
-
*
|
|
410
|
-
*
|
|
411
|
-
*
|
|
510
|
+
* A row with every field answered.
|
|
511
|
+
*
|
|
512
|
+
* The document asks no question an answer is missing for: a `$switch` on an absent field resolves
|
|
513
|
+
* to no case at all, so a partial row is a row that silently draws nothing.
|
|
412
514
|
*/
|
|
413
|
-
function
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
: html`<span class="expr-literal-null">null</span>`
|
|
455
|
-
}
|
|
456
|
-
</div>
|
|
457
|
-
`;
|
|
515
|
+
function blankRow(row: RowPatch & { key: string; kind: ExprRowView["kind"] }) {
|
|
516
|
+
return {
|
|
517
|
+
badge: "",
|
|
518
|
+
catalog: false,
|
|
519
|
+
checked: false,
|
|
520
|
+
chips: [],
|
|
521
|
+
chooser: false,
|
|
522
|
+
emptyDetail: "",
|
|
523
|
+
emptyMessage: "",
|
|
524
|
+
groups: [],
|
|
525
|
+
hasBadge: false,
|
|
526
|
+
indent: "0",
|
|
527
|
+
label: "",
|
|
528
|
+
lead: "none",
|
|
529
|
+
leadLabel: "",
|
|
530
|
+
leadValue: "",
|
|
531
|
+
literal: "",
|
|
532
|
+
literalType: "",
|
|
533
|
+
nested: "false",
|
|
534
|
+
prop: "",
|
|
535
|
+
refGroups: [],
|
|
536
|
+
refOptions: [],
|
|
537
|
+
removable: false,
|
|
538
|
+
source: "literal",
|
|
539
|
+
sourceOptions: [],
|
|
540
|
+
title: "",
|
|
541
|
+
typeOptions: [],
|
|
542
|
+
value: "",
|
|
543
|
+
...row,
|
|
544
|
+
} as ExprRowView;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/** The indent one depth reads as. `.expression-editor--nested`'s 4px margin, said per row. */
|
|
548
|
+
function indentAt(depth: number): string {
|
|
549
|
+
return `${depth * 4}px`;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/** The live value at a node path, and whether the preview holds one at all. */
|
|
553
|
+
function badgeAt(walk: Walk, key: string): { badge: string; hasBadge: boolean } {
|
|
554
|
+
const text = walk.preview?.values.get(key);
|
|
555
|
+
return { badge: text ?? "", hasBadge: text !== undefined };
|
|
458
556
|
}
|
|
459
557
|
|
|
460
|
-
// ───
|
|
558
|
+
// ─── The walk ───────────────────────────────────────────────────────────────
|
|
559
|
+
|
|
560
|
+
/** Everything one operand slot needs to know about itself before its rung is decided. */
|
|
561
|
+
interface OperandSlot {
|
|
562
|
+
/** The row's own key, unique within the editor. */
|
|
563
|
+
key: string;
|
|
564
|
+
/** The row's visible label, and what `inspector/field:<prop>` addresses. */
|
|
565
|
+
label: string;
|
|
566
|
+
prop: string;
|
|
567
|
+
depth: number;
|
|
568
|
+
path: (string | number)[];
|
|
569
|
+
/** The position admits a pointer and nothing else — an assignment target, a callee. */
|
|
570
|
+
mustBeRef: boolean;
|
|
571
|
+
/** The node path whose live value annotates this row. Empty asks for no badge. */
|
|
572
|
+
badgeKey: string;
|
|
573
|
+
lead?: ExprRowView["lead"];
|
|
574
|
+
leadLabel?: string;
|
|
575
|
+
leadValue?: string;
|
|
576
|
+
removable?: boolean;
|
|
577
|
+
title?: string;
|
|
578
|
+
/** Renaming a `switch` case: the key IS the matched value. */
|
|
579
|
+
renameCase?: (value: string) => void;
|
|
580
|
+
remove?: () => void;
|
|
581
|
+
}
|
|
461
582
|
|
|
462
583
|
/**
|
|
463
|
-
*
|
|
464
|
-
* statement-position operand slots (spec §20: `if` tests, `$switch` discriminants, `dispatchEvent`
|
|
465
|
-
* detail).
|
|
584
|
+
* Emit one operand row, and — when it holds an expression — the rows of that expression under it.
|
|
466
585
|
*
|
|
467
|
-
* @param
|
|
468
|
-
* @param
|
|
469
|
-
* @param
|
|
470
|
-
*
|
|
471
|
-
* allowEventRef: boolean;
|
|
472
|
-
* depth: number;
|
|
473
|
-
* mustBeRef?: boolean;
|
|
474
|
-
* }} opts
|
|
475
|
-
* @returns {import("lit-html").TemplateResult}
|
|
586
|
+
* @param walk The walk in progress.
|
|
587
|
+
* @param operand What the slot currently holds.
|
|
588
|
+
* @param commit What replacing it means.
|
|
589
|
+
* @param slot Everything about the slot itself.
|
|
476
590
|
*/
|
|
477
|
-
|
|
591
|
+
function walkOperand(
|
|
592
|
+
walk: Walk,
|
|
478
593
|
operand: unknown,
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
594
|
+
commit: (next: unknown) => void,
|
|
595
|
+
slot: OperandSlot,
|
|
596
|
+
): void {
|
|
597
|
+
const { allowEventRef, stateDefs } = walk.opts;
|
|
598
|
+
const stateRefs = (stateDefs || []).map((k) => `#/state/${k}`);
|
|
599
|
+
const refValue = ((operand as Record<string, unknown> | null)?.$ref as string) ?? "";
|
|
600
|
+
const plan: RowPlan = {
|
|
601
|
+
setRef: (value) => commit({ $ref: value }),
|
|
602
|
+
};
|
|
603
|
+
if (slot.renameCase) {
|
|
604
|
+
plan.renameCase = slot.renameCase;
|
|
605
|
+
}
|
|
606
|
+
if (slot.remove) {
|
|
607
|
+
plan.remove = slot.remove;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
const base = {
|
|
611
|
+
...badgeAt(walk, slot.badgeKey),
|
|
612
|
+
indent: indentAt(slot.depth),
|
|
613
|
+
label: slot.label,
|
|
614
|
+
lead: slot.lead ?? "none",
|
|
615
|
+
leadLabel: slot.leadLabel ?? "",
|
|
616
|
+
leadValue: slot.leadValue ?? "",
|
|
617
|
+
nested: slot.depth > 0 ? "true" : "false",
|
|
618
|
+
prop: slot.prop,
|
|
619
|
+
refGroups: allowEventRef
|
|
620
|
+
? [{ id: "event", label: "Event", rows: EVENT_REFS.map((r) => ({ label: r, value: r })) }]
|
|
621
|
+
: [],
|
|
622
|
+
refOptions: stateRefs.map((r) => ({ label: r.replace("#/state/", ""), value: r })),
|
|
623
|
+
removable: Boolean(slot.removable),
|
|
624
|
+
title: slot.title ?? "",
|
|
625
|
+
value: refValue,
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
/* Nothing to pick and nothing already picked: a picker whose only entry is a disabled "No state
|
|
629
|
+
defined" is a dead end. Say what a binding IS instead, in the shell's one empty-state voice. */
|
|
630
|
+
const nothingToBind = stateRefs.length === 0 && !allowEventRef && !refValue;
|
|
631
|
+
|
|
632
|
+
if (slot.mustBeRef) {
|
|
633
|
+
walk.plans.set(slot.key, plan);
|
|
634
|
+
walk.rows.push(
|
|
635
|
+
blankRow({
|
|
636
|
+
...base,
|
|
637
|
+
emptyDetail: nothingToBind ? NO_BINDINGS_DETAIL : "",
|
|
638
|
+
emptyMessage: nothingToBind ? NO_BINDINGS_MESSAGE : "",
|
|
639
|
+
key: slot.key,
|
|
640
|
+
kind: "operand",
|
|
641
|
+
source: nothingToBind ? "empty" : "ref",
|
|
642
|
+
}),
|
|
643
|
+
);
|
|
644
|
+
return;
|
|
495
645
|
}
|
|
496
646
|
|
|
497
647
|
const mode = operandMode(operand);
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
648
|
+
plan.setSource = (value) => commit(defaultForMode(value));
|
|
649
|
+
if (mode === "literal") {
|
|
650
|
+
const type = literalType(operand);
|
|
651
|
+
const asNumber = Number(operand ?? 0);
|
|
652
|
+
plan.setLiteralType = (value) => commit(defaultForLiteralType(value));
|
|
653
|
+
plan.setText = (value) => commit(value);
|
|
654
|
+
plan.setNumber = (value) => commit(Number(value));
|
|
655
|
+
plan.setBool = (checked) => commit(checked);
|
|
656
|
+
walk.plans.set(slot.key, plan);
|
|
657
|
+
walk.rows.push(
|
|
658
|
+
blankRow({
|
|
659
|
+
...base,
|
|
660
|
+
checked: Boolean(operand),
|
|
661
|
+
chooser: true,
|
|
662
|
+
key: slot.key,
|
|
663
|
+
kind: "operand",
|
|
664
|
+
literal: LITERAL_CONTROLS[type],
|
|
665
|
+
literalType: type,
|
|
666
|
+
source: "literal",
|
|
667
|
+
sourceOptions: SOURCE_OPTIONS,
|
|
668
|
+
typeOptions: TYPE_OPTIONS,
|
|
669
|
+
value: type === "number" ? String(asNumber) : String(operand ?? ""),
|
|
670
|
+
}),
|
|
671
|
+
);
|
|
672
|
+
return;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
walk.plans.set(slot.key, plan);
|
|
676
|
+
walk.rows.push(
|
|
677
|
+
blankRow({
|
|
678
|
+
...base,
|
|
679
|
+
chooser: true,
|
|
680
|
+
emptyDetail: mode === "ref" && nothingToBind ? NO_BINDINGS_DETAIL : "",
|
|
681
|
+
emptyMessage: mode === "ref" && nothingToBind ? NO_BINDINGS_MESSAGE : "",
|
|
682
|
+
key: slot.key,
|
|
683
|
+
kind: "operand",
|
|
684
|
+
source: mode === "ref" && nothingToBind ? "empty" : mode,
|
|
685
|
+
sourceOptions: SOURCE_OPTIONS,
|
|
686
|
+
}),
|
|
687
|
+
);
|
|
688
|
+
if (mode === "expression") {
|
|
689
|
+
walkExpression(walk, operand, commit, slot.depth + 1, slot.path);
|
|
690
|
+
}
|
|
530
691
|
}
|
|
531
692
|
|
|
532
|
-
|
|
693
|
+
/** The rows of a positional-args list (`splice`, `call`), plus the row that grows it. */
|
|
694
|
+
function walkArgs(
|
|
695
|
+
walk: Walk,
|
|
696
|
+
args: unknown,
|
|
697
|
+
commit: (next: unknown[]) => void,
|
|
698
|
+
depth: number,
|
|
699
|
+
path: (string | number)[],
|
|
700
|
+
naming: { labels: string[]; fallbackLabel: string },
|
|
701
|
+
): void {
|
|
702
|
+
/* `Array.isArray` narrows an `unknown` to `any[]`, so every spread of it reads as unsafe to
|
|
703
|
+
the type-aware lint. The elements ARE unknown — each is an operand this walk discriminates
|
|
704
|
+
— so saying so restores the check rather than silencing it. */
|
|
705
|
+
const safeArgs: unknown[] = Array.isArray(args) ? args : [];
|
|
706
|
+
for (const [idx, arg] of safeArgs.entries()) {
|
|
707
|
+
const argPath = [...path, "value", idx];
|
|
708
|
+
const label = naming.labels[idx] ?? naming.fallbackLabel;
|
|
709
|
+
walkOperand(
|
|
710
|
+
walk,
|
|
711
|
+
arg,
|
|
712
|
+
(next) => {
|
|
713
|
+
const updated = [...safeArgs];
|
|
714
|
+
updated[idx] = next;
|
|
715
|
+
commit(updated);
|
|
716
|
+
},
|
|
717
|
+
{
|
|
718
|
+
badgeKey: "",
|
|
719
|
+
depth,
|
|
720
|
+
key: argPath.join("/"),
|
|
721
|
+
label,
|
|
722
|
+
leadLabel: label,
|
|
723
|
+
lead: "arg",
|
|
724
|
+
mustBeRef: false,
|
|
725
|
+
path: argPath,
|
|
726
|
+
prop: "value",
|
|
727
|
+
remove: () => {
|
|
728
|
+
const updated = safeArgs.filter((_, i) => i !== idx);
|
|
729
|
+
commit(updated.length > 0 ? updated : [null]);
|
|
730
|
+
},
|
|
731
|
+
removable: true,
|
|
732
|
+
title: `Remove ${label}`,
|
|
733
|
+
},
|
|
734
|
+
);
|
|
735
|
+
}
|
|
736
|
+
const addKey = `${path.join("/")}::add-arg`;
|
|
737
|
+
walk.plans.set(addKey, { act: () => commit([...safeArgs, null]) });
|
|
738
|
+
walk.rows.push(
|
|
739
|
+
blankRow({
|
|
740
|
+
indent: indentAt(depth),
|
|
741
|
+
key: addKey,
|
|
742
|
+
kind: "action",
|
|
743
|
+
label: "+ Add arg",
|
|
744
|
+
nested: depth > 0 ? "true" : "false",
|
|
745
|
+
}),
|
|
746
|
+
);
|
|
747
|
+
}
|
|
533
748
|
|
|
534
|
-
/**
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
):
|
|
547
|
-
const safeArgs = Array.isArray(args) ? args : [];
|
|
548
|
-
const labels = naming.labels ?? ["start", "del", "item"];
|
|
549
|
-
const fallbackLabel = naming.fallbackLabel ?? "item";
|
|
550
|
-
|
|
551
|
-
return html`
|
|
552
|
-
<div class="array-object-field">
|
|
553
|
-
${safeArgs.map(
|
|
554
|
-
(arg, idx) => html`
|
|
555
|
-
<div class="array-object-row">
|
|
556
|
-
<span class="array-object-label">${labels[idx] ?? fallbackLabel}</span>
|
|
557
|
-
${renderOperandEditor(
|
|
558
|
-
arg,
|
|
559
|
-
(newArg) => {
|
|
560
|
-
const updated = [...safeArgs];
|
|
561
|
-
updated[idx] = newArg;
|
|
562
|
-
onChange(updated);
|
|
563
|
-
},
|
|
564
|
-
{ ...opts, mustBeRef: false },
|
|
565
|
-
)}
|
|
566
|
-
<sp-action-button
|
|
567
|
-
quiet
|
|
568
|
-
size="xs"
|
|
569
|
-
@click=${() => {
|
|
570
|
-
const updated = safeArgs.filter((_, i) => i !== idx);
|
|
571
|
-
onChange(updated.length > 0 ? updated : [null]);
|
|
572
|
-
}}
|
|
573
|
-
>
|
|
574
|
-
<sp-icon-delete slot="icon"></sp-icon-delete>
|
|
575
|
-
</sp-action-button>
|
|
576
|
-
</div>
|
|
577
|
-
`,
|
|
578
|
-
)}
|
|
579
|
-
<sp-action-button quiet size="s" @click=${() => onChange([...safeArgs, null])}>
|
|
580
|
-
+ Add arg
|
|
581
|
-
</sp-action-button>
|
|
582
|
-
</div>
|
|
583
|
-
`;
|
|
584
|
-
}
|
|
585
|
-
|
|
586
|
-
// ─── Operator Picker Menu ───────────────────────────────────────────────────
|
|
587
|
-
|
|
588
|
-
const _operatorMenuCache = OPERATOR_GROUPS.map(
|
|
589
|
-
(group, i) => html`
|
|
590
|
-
${i > 0 ? html`<sp-menu-divider></sp-menu-divider>` : nothing}
|
|
591
|
-
<sp-menu-group>
|
|
592
|
-
<span slot="header">${group.label}</span>
|
|
593
|
-
${group.ops.map((op) => html`<sp-menu-item value=${op}>${op}</sp-menu-item>`)}
|
|
594
|
-
</sp-menu-group>
|
|
595
|
-
`,
|
|
596
|
-
);
|
|
597
|
-
|
|
598
|
-
// ─── Main Expression Editor ─────────────────────────────────────────────────
|
|
749
|
+
/** The case rows of a `switch` node: matched value → result operand, the default, and `+ Add case`. */
|
|
750
|
+
function walkSwitchCases(
|
|
751
|
+
walk: Walk,
|
|
752
|
+
safeNode: Record<string, unknown>,
|
|
753
|
+
onChange: (node: unknown) => void,
|
|
754
|
+
depth: number,
|
|
755
|
+
path: (string | number)[],
|
|
756
|
+
): void {
|
|
757
|
+
const cases = isJsonObject(safeNode.cases)
|
|
758
|
+
? (safeNode.cases as Record<string, unknown>)
|
|
759
|
+
: ({} as Record<string, unknown>);
|
|
760
|
+
const entries = Object.entries(cases);
|
|
761
|
+
const setCases = (next: Record<string, unknown>) => onChange({ ...safeNode, cases: next });
|
|
599
762
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
763
|
+
for (const [caseKey, operand] of entries) {
|
|
764
|
+
const casePath = [...path, "cases", caseKey];
|
|
765
|
+
walkOperand(walk, operand, (v) => setCases({ ...cases, [caseKey]: v }), {
|
|
766
|
+
badgeKey: casePath.join("/"),
|
|
767
|
+
depth,
|
|
768
|
+
key: casePath.join("/"),
|
|
769
|
+
label: `Case ${caseKey}`,
|
|
770
|
+
lead: "case",
|
|
771
|
+
leadValue: caseKey,
|
|
772
|
+
mustBeRef: false,
|
|
773
|
+
path: casePath,
|
|
774
|
+
prop: "cases",
|
|
775
|
+
remove: () => {
|
|
776
|
+
const next = { ...cases };
|
|
777
|
+
delete next[caseKey];
|
|
778
|
+
setCases(next);
|
|
779
|
+
},
|
|
780
|
+
removable: true,
|
|
781
|
+
renameCase: (newKey) => {
|
|
782
|
+
if (newKey === caseKey) {
|
|
783
|
+
return;
|
|
784
|
+
}
|
|
785
|
+
const next: Record<string, unknown> = {};
|
|
786
|
+
for (const [k, v] of entries) {
|
|
787
|
+
next[k === caseKey ? newKey : k] = v;
|
|
788
|
+
}
|
|
789
|
+
setCases(next);
|
|
790
|
+
},
|
|
791
|
+
title: `Remove case ${caseKey}`,
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const defaultPath = [...path, "default"];
|
|
796
|
+
walkOperand(walk, safeNode.default, (v) => onChange({ ...safeNode, default: v }), {
|
|
797
|
+
badgeKey: defaultPath.join("/"),
|
|
798
|
+
depth,
|
|
799
|
+
key: defaultPath.join("/"),
|
|
800
|
+
label: "Default",
|
|
801
|
+
lead: "default",
|
|
802
|
+
leadLabel: "default",
|
|
803
|
+
mustBeRef: false,
|
|
804
|
+
path: defaultPath,
|
|
805
|
+
prop: "default",
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
const addKey = `${path.join("/")}::add-case`;
|
|
809
|
+
walk.plans.set(addKey, {
|
|
810
|
+
act: () => {
|
|
811
|
+
let n = entries.length + 1;
|
|
812
|
+
let key = `case ${n}`;
|
|
813
|
+
while (Object.hasOwn(cases, key)) {
|
|
814
|
+
n += 1;
|
|
815
|
+
key = `case ${n}`;
|
|
816
|
+
}
|
|
817
|
+
setCases({ ...cases, [key]: null });
|
|
818
|
+
},
|
|
819
|
+
});
|
|
820
|
+
walk.rows.push(
|
|
821
|
+
blankRow({
|
|
822
|
+
indent: indentAt(depth),
|
|
823
|
+
key: addKey,
|
|
824
|
+
kind: "action",
|
|
825
|
+
label: "+ Add case",
|
|
826
|
+
nested: depth > 0 ? "true" : "false",
|
|
827
|
+
}),
|
|
828
|
+
);
|
|
612
829
|
}
|
|
613
830
|
|
|
614
|
-
/**
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
831
|
+
/** What replacing the operator means for the rest of the node. */
|
|
832
|
+
function reoperate(safeNode: Record<string, unknown>, newOp: string): Record<string, unknown> {
|
|
833
|
+
const newInfo = operatorInfo(newOp);
|
|
834
|
+
const updated: Record<string, unknown> = { operator: newOp, target: safeNode.target };
|
|
835
|
+
if (newInfo.targetMustBeRef && operandMode(safeNode.target) !== "ref") {
|
|
836
|
+
updated.target = { $ref: "" };
|
|
837
|
+
}
|
|
838
|
+
if (newInfo.needsValue) {
|
|
839
|
+
if (newInfo.valueIsNode) {
|
|
840
|
+
const val = safeNode.value as Record<string, unknown> | null;
|
|
841
|
+
updated.value = val?.operator ? safeNode.value : { operator: "!", target: null };
|
|
842
|
+
} else if (newInfo.spliceArray) {
|
|
843
|
+
updated.value = Array.isArray(safeNode.value) ? safeNode.value : [null];
|
|
844
|
+
} else {
|
|
845
|
+
updated.value = safeNode.value ?? null;
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if (newInfo.needsInitial) {
|
|
849
|
+
updated.initial = safeNode.initial ?? (newOp === "?:" ? null : 0);
|
|
850
|
+
}
|
|
851
|
+
if (newInfo.switchCases) {
|
|
852
|
+
updated.cases = isJsonObject(safeNode.cases) ? safeNode.cases : {};
|
|
853
|
+
if ("default" in safeNode) {
|
|
854
|
+
updated.default = safeNode.default;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
if (newInfo.callArgs) {
|
|
858
|
+
updated.value = Array.isArray(safeNode.value) ? safeNode.value : [];
|
|
859
|
+
}
|
|
860
|
+
return updated;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
/** Emit every row one expression node reads as, deepest last. */
|
|
864
|
+
function walkExpression(
|
|
865
|
+
walk: Walk,
|
|
621
866
|
node: unknown,
|
|
622
867
|
onChange: (node: unknown) => void,
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
868
|
+
depth: number,
|
|
869
|
+
path: (string | number)[],
|
|
870
|
+
): void {
|
|
626
871
|
const safeNode: Record<string, unknown> =
|
|
627
872
|
node && typeof node === "object"
|
|
628
873
|
? (node as Record<string, unknown>)
|
|
629
874
|
: { operator: "=", target: null };
|
|
630
875
|
const op = (safeNode.operator as string) || "=";
|
|
631
876
|
const info = operatorInfo(op);
|
|
632
|
-
const preview = opts.preview ?? null;
|
|
633
|
-
const path = opts.path ?? [];
|
|
634
877
|
const pathKey = path.join("/");
|
|
635
878
|
const sub = (...segs: (string | number)[]) => [...path, ...segs].join("/");
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
879
|
+
const { opts, preview } = walk;
|
|
880
|
+
|
|
881
|
+
if (depth === 0) {
|
|
882
|
+
const chips = formulaChipStrip(safeNode, { path, preview });
|
|
883
|
+
if (chips.length > 0) {
|
|
884
|
+
for (const chip of chips) {
|
|
885
|
+
walk.chipPaths.set(chip.key, chip.path);
|
|
886
|
+
}
|
|
887
|
+
walk.rows.push(
|
|
888
|
+
blankRow({
|
|
889
|
+
chips: chips.map((chip) => ({
|
|
890
|
+
badge: chip.badge,
|
|
891
|
+
group: chip.group ? "true" : "false",
|
|
892
|
+
hasBadge: chip.hasBadge,
|
|
893
|
+
key: chip.key,
|
|
894
|
+
label: chip.label,
|
|
895
|
+
})),
|
|
896
|
+
key: "::chips",
|
|
897
|
+
kind: "chips",
|
|
898
|
+
}),
|
|
899
|
+
);
|
|
900
|
+
}
|
|
901
|
+
if (preview?.error) {
|
|
902
|
+
walk.rows.push(blankRow({ key: "::error", kind: "error", label: preview.error }));
|
|
903
|
+
}
|
|
904
|
+
}
|
|
639
905
|
|
|
640
906
|
// The root badge: pure roots show their result; mutating roots' effect shows on target/value.
|
|
641
907
|
const rootBadge =
|
|
642
|
-
depth === 0 && preview && !preview.mutating
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
size="s"
|
|
664
|
-
class="expr-operator"
|
|
665
|
-
.value=${live(op)}
|
|
666
|
-
@change=${(e: Event) => {
|
|
667
|
-
const newOp = (e.target as HTMLInputElement).value;
|
|
668
|
-
const newInfo = operatorInfo(newOp);
|
|
669
|
-
const updated: Record<string, unknown> = {
|
|
670
|
-
operator: newOp,
|
|
671
|
-
target: safeNode.target,
|
|
672
|
-
};
|
|
673
|
-
if (newInfo.targetMustBeRef && operandMode(safeNode.target) !== "ref") {
|
|
674
|
-
updated.target = { $ref: "" };
|
|
675
|
-
}
|
|
676
|
-
if (newInfo.needsValue) {
|
|
677
|
-
if (newInfo.valueIsNode) {
|
|
678
|
-
const val = safeNode.value as Record<string, unknown> | null;
|
|
679
|
-
updated.value = val?.operator
|
|
680
|
-
? safeNode.value
|
|
681
|
-
: { operator: "!", target: null };
|
|
682
|
-
} else if (newInfo.spliceArray) {
|
|
683
|
-
updated.value = Array.isArray(safeNode.value) ? safeNode.value : [null];
|
|
684
|
-
} else {
|
|
685
|
-
updated.value = safeNode.value ?? null;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
if (newInfo.needsInitial) {
|
|
689
|
-
updated.initial = safeNode.initial ?? (newOp === "?:" ? null : 0);
|
|
690
|
-
}
|
|
691
|
-
if (newInfo.switchCases) {
|
|
692
|
-
updated.cases = isJsonObject(safeNode.cases) ? safeNode.cases : {};
|
|
693
|
-
if ("default" in safeNode) {
|
|
694
|
-
updated.default = safeNode.default;
|
|
695
|
-
}
|
|
696
|
-
}
|
|
697
|
-
if (newInfo.callArgs) {
|
|
698
|
-
updated.value = Array.isArray(safeNode.value) ? safeNode.value : [];
|
|
699
|
-
}
|
|
700
|
-
onChange(updated);
|
|
701
|
-
}}
|
|
702
|
-
>
|
|
703
|
-
${_operatorMenuCache}
|
|
704
|
-
</sp-picker>
|
|
705
|
-
<sp-action-button
|
|
706
|
-
quiet
|
|
707
|
-
size="s"
|
|
708
|
-
class="expr-browse-catalog"
|
|
709
|
-
title="Browse catalog"
|
|
710
|
-
@click=${(e: Event) =>
|
|
711
|
-
openFormulaPalette({
|
|
712
|
-
anchor: e.currentTarget as HTMLElement,
|
|
713
|
-
entries: formulaCatalog(opts.stateEntries),
|
|
714
|
-
onPick: (entry) =>
|
|
715
|
-
applyCatalogPick(entry, onChange, {
|
|
716
|
-
onInsertDef: opts.onInsertDef,
|
|
717
|
-
stateEntries: opts.stateEntries,
|
|
718
|
-
}),
|
|
719
|
-
})}
|
|
720
|
-
>
|
|
721
|
-
<sp-icon-brackets slot="icon"></sp-icon-brackets>
|
|
722
|
-
</sp-action-button>
|
|
723
|
-
${rootBadge}
|
|
724
|
-
</div>
|
|
725
|
-
`,
|
|
726
|
-
})}
|
|
727
|
-
${renderFieldRow({
|
|
728
|
-
hasValue: false,
|
|
729
|
-
label: op === "?:" ? "If" : op === "switch" ? "On" : op === "call" ? "Callee" : "Target",
|
|
730
|
-
prop: "target",
|
|
731
|
-
widget: withBadge(
|
|
732
|
-
renderOperandEditor(safeNode.target, (t) => onChange({ ...safeNode, target: t }), {
|
|
733
|
-
...opts,
|
|
734
|
-
depth,
|
|
735
|
-
mustBeRef: info.targetMustBeRef,
|
|
736
|
-
path: [...path, "target"],
|
|
737
|
-
}),
|
|
738
|
-
sub("target"),
|
|
739
|
-
),
|
|
740
|
-
})}
|
|
741
|
-
${
|
|
742
|
-
info.needsValue && !info.valueIsNode && !info.spliceArray
|
|
743
|
-
? renderFieldRow({
|
|
744
|
-
hasValue: false,
|
|
745
|
-
label: op === "?:" ? "Then" : "Value",
|
|
746
|
-
prop: "value",
|
|
747
|
-
widget: withBadge(
|
|
748
|
-
renderOperandEditor(safeNode.value, (v) => onChange({ ...safeNode, value: v }), {
|
|
749
|
-
...opts,
|
|
750
|
-
depth,
|
|
751
|
-
mustBeRef: false,
|
|
752
|
-
path: [...path, "value"],
|
|
908
|
+
depth === 0 && preview && !preview.mutating
|
|
909
|
+
? badgeAt(walk, pathKey)
|
|
910
|
+
: { badge: "", hasBadge: false };
|
|
911
|
+
|
|
912
|
+
/* The position's own grammar, and only at its own root — see `ExpressionEditorOpts.operators`.
|
|
913
|
+
The catalog goes with it: every entry in it inserts a whole node, so a palette offered here
|
|
914
|
+
would be a second door onto the operators the select has just stopped offering. */
|
|
915
|
+
const grammar = depth === 0 ? opts.operators : undefined;
|
|
916
|
+
const operatorKey = `${pathKey}::operator`;
|
|
917
|
+
walk.plans.set(operatorKey, {
|
|
918
|
+
...(grammar
|
|
919
|
+
? {}
|
|
920
|
+
: {
|
|
921
|
+
browse: (anchor: HTMLElement) =>
|
|
922
|
+
openFormulaPalette({
|
|
923
|
+
anchor,
|
|
924
|
+
entries: formulaCatalog(opts.stateEntries),
|
|
925
|
+
onPick: (entry) =>
|
|
926
|
+
applyCatalogPick(entry, onChange, {
|
|
927
|
+
onInsertDef: opts.onInsertDef,
|
|
928
|
+
stateEntries: opts.stateEntries,
|
|
753
929
|
}),
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
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
|
-
|
|
930
|
+
}),
|
|
931
|
+
}),
|
|
932
|
+
setOperator: (newOp) => onChange(reoperate(safeNode, newOp)),
|
|
933
|
+
});
|
|
934
|
+
walk.rows.push(
|
|
935
|
+
blankRow({
|
|
936
|
+
...rootBadge,
|
|
937
|
+
catalog: grammar === undefined,
|
|
938
|
+
groups: grammar ? restrictGroups(grammar) : OPERATOR_SELECT_GROUPS,
|
|
939
|
+
indent: indentAt(depth),
|
|
940
|
+
key: operatorKey,
|
|
941
|
+
kind: "operator",
|
|
942
|
+
label: "Operator",
|
|
943
|
+
nested: depth > 0 ? "true" : "false",
|
|
944
|
+
prop: "operator",
|
|
945
|
+
title: "Browse catalog",
|
|
946
|
+
value: op,
|
|
947
|
+
}),
|
|
948
|
+
);
|
|
949
|
+
|
|
950
|
+
walkOperand(walk, safeNode.target, (t) => onChange({ ...safeNode, target: t }), {
|
|
951
|
+
badgeKey: sub("target"),
|
|
952
|
+
depth,
|
|
953
|
+
key: `${pathKey}::target`,
|
|
954
|
+
label: op === "?:" ? "If" : op === "switch" ? "On" : op === "call" ? "Callee" : "Target",
|
|
955
|
+
mustBeRef: info.targetMustBeRef,
|
|
956
|
+
path: [...path, "target"],
|
|
957
|
+
prop: "target",
|
|
958
|
+
});
|
|
959
|
+
|
|
960
|
+
if (info.needsValue && !info.valueIsNode && !info.spliceArray) {
|
|
961
|
+
walkOperand(walk, safeNode.value, (v) => onChange({ ...safeNode, value: v }), {
|
|
962
|
+
badgeKey: sub("value"),
|
|
963
|
+
depth,
|
|
964
|
+
key: `${pathKey}::value`,
|
|
965
|
+
label: op === "?:" ? "Then" : "Value",
|
|
966
|
+
mustBeRef: false,
|
|
967
|
+
path: [...path, "value"],
|
|
968
|
+
prop: "value",
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
if (info.needsValue && info.valueIsNode) {
|
|
973
|
+
walk.rows.push(
|
|
974
|
+
blankRow({
|
|
975
|
+
indent: indentAt(depth),
|
|
976
|
+
key: `${pathKey}::per-item`,
|
|
977
|
+
kind: "label",
|
|
978
|
+
label: "Per-item",
|
|
979
|
+
nested: depth > 0 ? "true" : "false",
|
|
980
|
+
prop: "value",
|
|
981
|
+
}),
|
|
982
|
+
);
|
|
983
|
+
walkExpression(
|
|
984
|
+
walk,
|
|
985
|
+
(safeNode.value as Record<string, unknown> | null)?.operator
|
|
986
|
+
? safeNode.value
|
|
987
|
+
: { operator: "!", target: null },
|
|
988
|
+
(v) => onChange({ ...safeNode, value: v }),
|
|
989
|
+
depth + 1,
|
|
990
|
+
[...path, "value"],
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
if (info.spliceArray || info.callArgs) {
|
|
995
|
+
walk.rows.push(
|
|
996
|
+
blankRow({
|
|
997
|
+
indent: indentAt(depth),
|
|
998
|
+
key: `${pathKey}::args`,
|
|
999
|
+
kind: "label",
|
|
1000
|
+
label: "Args",
|
|
1001
|
+
nested: depth > 0 ? "true" : "false",
|
|
1002
|
+
prop: "value",
|
|
1003
|
+
}),
|
|
1004
|
+
);
|
|
1005
|
+
walkArgs(
|
|
1006
|
+
walk,
|
|
1007
|
+
safeNode.value,
|
|
1008
|
+
(v) => onChange({ ...safeNode, value: v }),
|
|
1009
|
+
depth,
|
|
1010
|
+
path,
|
|
1011
|
+
info.callArgs
|
|
1012
|
+
? {
|
|
1013
|
+
fallbackLabel: "arg",
|
|
1014
|
+
labels: calleeParamLabels(safeNode.target, opts.stateEntries),
|
|
1015
|
+
}
|
|
1016
|
+
: { fallbackLabel: "item", labels: ["start", "del", "item"] },
|
|
1017
|
+
);
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
if (info.switchCases) {
|
|
1021
|
+
walkSwitchCases(walk, safeNode, onChange, depth, path);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
if (info.needsInitial) {
|
|
1025
|
+
walkOperand(walk, safeNode.initial, (v) => onChange({ ...safeNode, initial: v }), {
|
|
1026
|
+
badgeKey: sub("initial"),
|
|
1027
|
+
depth,
|
|
1028
|
+
key: `${pathKey}::initial`,
|
|
1029
|
+
label: op === "?:" ? "Else" : "Initial",
|
|
1030
|
+
mustBeRef: false,
|
|
1031
|
+
path: [...path, "initial"],
|
|
1032
|
+
prop: "initial",
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/** Start a walk with nothing emitted yet. */
|
|
1038
|
+
function newWalk(opts: ExpressionEditorOpts): Walk {
|
|
1039
|
+
return {
|
|
1040
|
+
chipPaths: new Map(),
|
|
1041
|
+
opts,
|
|
1042
|
+
plans: new Map(),
|
|
1043
|
+
preview: opts.preview ?? null,
|
|
1044
|
+
rows: [],
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/** The projection one expression reads as: the rows, and what each key commits. */
|
|
1049
|
+
export function flattenExpression(
|
|
1050
|
+
node: unknown,
|
|
1051
|
+
onChange: (node: unknown) => void,
|
|
1052
|
+
opts: ExpressionEditorOpts,
|
|
1053
|
+
): ExpressionProjection {
|
|
1054
|
+
const walk = newWalk(opts);
|
|
1055
|
+
walkExpression(walk, node, onChange, opts.depth ?? 0, opts.path ?? []);
|
|
1056
|
+
return { chipPaths: walk.chipPaths, plans: walk.plans, rows: walk.rows };
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
/**
|
|
1060
|
+
* The projection ONE operand slot reads as — a statement's `if` test, a `$switch` discriminant, a
|
|
1061
|
+
* `dispatchEvent` detail (spec §20). The same rows, with no operator row above them.
|
|
1062
|
+
*/
|
|
1063
|
+
export function flattenOperand(
|
|
1064
|
+
operand: unknown,
|
|
1065
|
+
onChange: (next: unknown) => void,
|
|
1066
|
+
opts: ExpressionEditorOpts & { label?: string; prop?: string },
|
|
1067
|
+
): ExpressionProjection {
|
|
1068
|
+
const walk = newWalk(opts);
|
|
1069
|
+
const path = opts.path ?? [];
|
|
1070
|
+
walkOperand(walk, operand, onChange, {
|
|
1071
|
+
badgeKey: path.join("/"),
|
|
1072
|
+
depth: opts.depth ?? 0,
|
|
1073
|
+
key: `${path.join("/")}::operand`,
|
|
1074
|
+
label: opts.label ?? "Value",
|
|
1075
|
+
mustBeRef: false,
|
|
1076
|
+
path,
|
|
1077
|
+
prop: opts.prop ?? "operand",
|
|
1078
|
+
});
|
|
1079
|
+
return { chipPaths: walk.chipPaths, plans: walk.plans, rows: walk.rows };
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// ─── The mount seam ─────────────────────────────────────────────────────────
|
|
1083
|
+
|
|
1084
|
+
/** What one standing editor is currently showing, and what its keys currently commit. */
|
|
1085
|
+
interface Held {
|
|
1086
|
+
projection: ExpressionProjection;
|
|
1087
|
+
opts: ExpressionEditorOpts;
|
|
1088
|
+
}
|
|
1089
|
+
|
|
1090
|
+
/**
|
|
1091
|
+
* The latest projection per host — the indirection that keeps a gesture honest.
|
|
1092
|
+
*
|
|
1093
|
+
* A mount reads its actions ONCE (`surfaces/expression-editor.ts` assigns rows on a repaint and
|
|
1094
|
+
* never rebuilds the scope, which is what keeps a reader's caret in a field). So an action that
|
|
1095
|
+
* closed over the projection it was built beside would go on committing the tree that projection
|
|
1096
|
+
* was walked from, and every edit after the first would be written against a stale node: the Logic
|
|
1097
|
+
* dock selects a sub-node, re-projects, and the operator picker then rewrites the node that used to
|
|
1098
|
+
* be selected. Closing over the HOST instead and reading this map at call time is what makes "which
|
|
1099
|
+
* node does this row address" a question answered when the row is used.
|
|
1100
|
+
*/
|
|
1101
|
+
const held = new Map<HTMLElement, Held>();
|
|
1102
|
+
|
|
1103
|
+
/** The actions for a host, resolved through whatever that host is showing right now. */
|
|
1104
|
+
function actionsFor(host: HTMLElement): ExpressionActions {
|
|
1105
|
+
const plan = (key: string) => held.get(host)?.projection.plans.get(key);
|
|
1106
|
+
return {
|
|
1107
|
+
act: (key) => plan(key)?.act?.(),
|
|
1108
|
+
browse: (key, anchor) => plan(key)?.browse?.(anchor),
|
|
1109
|
+
pickChip: (key) => {
|
|
1110
|
+
const current = held.get(host);
|
|
1111
|
+
const path = current?.projection.chipPaths.get(key);
|
|
1112
|
+
if (path) {
|
|
1113
|
+
current?.opts.onChipSelect?.(path);
|
|
851
1114
|
}
|
|
852
|
-
|
|
853
|
-
|
|
1115
|
+
},
|
|
1116
|
+
remove: (key) => plan(key)?.remove?.(),
|
|
1117
|
+
renameCase: (key, value) => plan(key)?.renameCase?.(value),
|
|
1118
|
+
setBool: (key, checked) => plan(key)?.setBool?.(checked),
|
|
1119
|
+
setLiteralType: (key, value) => plan(key)?.setLiteralType?.(value),
|
|
1120
|
+
setNumber: (key, value) => plan(key)?.setNumber?.(value),
|
|
1121
|
+
setOperator: (key, value) => plan(key)?.setOperator?.(value),
|
|
1122
|
+
setRef: (key, value) => plan(key)?.setRef?.(value),
|
|
1123
|
+
setSource: (key, value) => plan(key)?.setSource?.(value),
|
|
1124
|
+
setText: (key, value) => plan(key)?.setText?.(value),
|
|
1125
|
+
};
|
|
854
1126
|
}
|
|
855
1127
|
|
|
856
|
-
|
|
1128
|
+
/**
|
|
1129
|
+
* Hand a fresh projection to `host` — mounting the document the first time, assigning after that.
|
|
1130
|
+
*
|
|
1131
|
+
* A slot the reader unbound takes its host out of the page and nothing else in the chain hears
|
|
1132
|
+
* about it, because the panel around it simply renders something else. Sweeping here is the one
|
|
1133
|
+
* moment this module is guaranteed to run.
|
|
1134
|
+
*/
|
|
1135
|
+
function paint(host: HTMLElement, projection: ExpressionProjection, opts: ExpressionEditorOpts) {
|
|
1136
|
+
for (const other of held.keys()) {
|
|
1137
|
+
if (!other.isConnected) {
|
|
1138
|
+
held.delete(other);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
disposeDetachedExpressionEditors();
|
|
1142
|
+
held.set(host, { opts, projection });
|
|
1143
|
+
renderExpressionEditorSurface(host, projection.rows, actionsFor(host));
|
|
1144
|
+
}
|
|
857
1145
|
|
|
858
1146
|
/**
|
|
859
|
-
*
|
|
860
|
-
*
|
|
1147
|
+
* Draw one expression editor into `host`, or bring the one already there up to date.
|
|
1148
|
+
*
|
|
1149
|
+
* This is the seam every caller uses. The document CLEARS its host, so the host must be one the
|
|
1150
|
+
* caller left empty — which is what all five of them already drew for the lit version.
|
|
861
1151
|
*/
|
|
862
|
-
function
|
|
863
|
-
|
|
1152
|
+
export function mountExpressionEditor(
|
|
1153
|
+
host: HTMLElement,
|
|
1154
|
+
node: unknown,
|
|
864
1155
|
onChange: (node: unknown) => void,
|
|
865
|
-
opts:
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
preview?: EditorPreview | null;
|
|
870
|
-
path: (string | number)[];
|
|
871
|
-
},
|
|
872
|
-
withBadge: (widget: unknown, key: string) => TemplateResult,
|
|
873
|
-
): TemplateResult {
|
|
874
|
-
const cases = isJsonObject(safeNode.cases)
|
|
875
|
-
? (safeNode.cases as Record<string, unknown>)
|
|
876
|
-
: ({} as Record<string, unknown>);
|
|
877
|
-
const entries = Object.entries(cases);
|
|
878
|
-
const setCases = (next: Record<string, unknown>) => onChange({ ...safeNode, cases: next });
|
|
1156
|
+
opts: ExpressionEditorOpts,
|
|
1157
|
+
): void {
|
|
1158
|
+
paint(host, flattenExpression(node, onChange, opts), opts);
|
|
1159
|
+
}
|
|
879
1160
|
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
if (newKey === key) {
|
|
893
|
-
return;
|
|
894
|
-
}
|
|
895
|
-
const next: Record<string, unknown> = {};
|
|
896
|
-
for (const [k, v] of entries) {
|
|
897
|
-
next[k === key ? newKey : k] = v;
|
|
898
|
-
}
|
|
899
|
-
setCases(next);
|
|
900
|
-
}}
|
|
901
|
-
></sp-textfield>
|
|
902
|
-
${withBadge(
|
|
903
|
-
renderOperandEditor(operand, (v) => setCases({ ...cases, [key]: v }), {
|
|
904
|
-
...opts,
|
|
905
|
-
mustBeRef: false,
|
|
906
|
-
path: [...opts.path, "cases", key],
|
|
907
|
-
}),
|
|
908
|
-
[...opts.path, "cases", key].join("/"),
|
|
909
|
-
)}
|
|
910
|
-
<sp-action-button
|
|
911
|
-
quiet
|
|
912
|
-
size="xs"
|
|
913
|
-
@click=${() => {
|
|
914
|
-
const next = { ...cases };
|
|
915
|
-
delete next[key];
|
|
916
|
-
setCases(next);
|
|
917
|
-
}}
|
|
918
|
-
>
|
|
919
|
-
<sp-icon-delete slot="icon"></sp-icon-delete>
|
|
920
|
-
</sp-action-button>
|
|
921
|
-
</div>
|
|
922
|
-
`,
|
|
923
|
-
)}
|
|
924
|
-
<div class="switch-case-row">
|
|
925
|
-
<span class="switch-case-key switch-case-default">default</span>
|
|
926
|
-
${withBadge(
|
|
927
|
-
renderOperandEditor(safeNode.default, (v) => onChange({ ...safeNode, default: v }), {
|
|
928
|
-
...opts,
|
|
929
|
-
mustBeRef: false,
|
|
930
|
-
path: [...opts.path, "default"],
|
|
931
|
-
}),
|
|
932
|
-
[...opts.path, "default"].join("/"),
|
|
933
|
-
)}
|
|
934
|
-
</div>
|
|
935
|
-
<sp-action-button
|
|
936
|
-
quiet
|
|
937
|
-
size="s"
|
|
938
|
-
@click=${() => {
|
|
939
|
-
let n = entries.length + 1;
|
|
940
|
-
let key = `case ${n}`;
|
|
941
|
-
while (Object.hasOwn(cases, key)) {
|
|
942
|
-
n += 1;
|
|
943
|
-
key = `case ${n}`;
|
|
944
|
-
}
|
|
945
|
-
setCases({ ...cases, [key]: null });
|
|
946
|
-
}}
|
|
947
|
-
>
|
|
948
|
-
+ Add case
|
|
949
|
-
</sp-action-button>
|
|
950
|
-
</div>
|
|
951
|
-
`;
|
|
1161
|
+
/**
|
|
1162
|
+
* Draw ONE operand slot into `host` — statement position, where there is no operator to pick.
|
|
1163
|
+
*
|
|
1164
|
+
* @param opts The editor's options, plus what to call the slot in its own label.
|
|
1165
|
+
*/
|
|
1166
|
+
export function mountOperandEditor(
|
|
1167
|
+
host: HTMLElement,
|
|
1168
|
+
operand: unknown,
|
|
1169
|
+
onChange: (next: unknown) => void,
|
|
1170
|
+
opts: ExpressionEditorOpts & { label?: string; prop?: string },
|
|
1171
|
+
): void {
|
|
1172
|
+
paint(host, flattenOperand(operand, onChange, opts), opts);
|
|
952
1173
|
}
|