@jxsuite/studio 4.1.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-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-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-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-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-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-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-msd847zn.js → studio-25hkdrkw.js} +357 -86
- package/dist/chunks/studio-26d7myd1.js +258 -0
- package/dist/chunks/{studio-4r1pv6zf.js → studio-38emantv.js} +22 -5
- package/dist/chunks/studio-3gxv60tn.js +422 -0
- package/dist/chunks/{studio-fbf92ckf.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-1wagb550.js → studio-5523bpzt.js} +11 -11
- package/dist/chunks/{studio-x4qkdat5.js → studio-57mf66w2.js} +1749 -2481
- 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-ahn3pdgx.js → studio-79y5sz51.js} +52 -38
- 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-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-x813zp48.js → studio-arfpe389.js} +30 -30
- package/dist/chunks/{studio-trxrabqf.js → studio-az3zd49t.js} +475 -283
- package/dist/chunks/studio-cpvb2ed1.js +1171 -0
- package/dist/chunks/{studio-f3wqjtk5.js → studio-d0300636.js} +5076 -18
- 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-3bqpr0zh.js → studio-em9nw8eh.js} +299 -110
- 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-dm19q43r.js → studio-hhvs4sdg.js} +10 -10
- package/dist/chunks/{studio-q9jd1a60.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-2b946z26.js → studio-rkstz940.js} +3658 -6134
- 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-jkftxyvz.js → studio-s6z853aq.js} +5858 -2152
- package/dist/chunks/{studio-6yw4y3my.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-qjy2z9bv.js → studio-vz68yn9e.js} +32 -137
- package/dist/chunks/studio-wqpngpsk.js +174 -0
- package/dist/chunks/{studio-b55y6ewx.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 +1780 -667
- package/dist/studio.js +52819 -36480
- 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 +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 +943 -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 +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 +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/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 +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 +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 +468 -0
- 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/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 +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 +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 +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 +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 -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-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,1399 +1,28 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
PURE_METHOD_OPS,
|
|
5
|
-
intlHelpersClause,
|
|
6
|
-
rectOf,
|
|
7
|
-
renderEmptyState
|
|
8
|
-
} from "./studio-2b946z26.js";
|
|
2
|
+
intlHelpersClause
|
|
3
|
+
} from "./studio-5jy81t3s.js";
|
|
9
4
|
import {
|
|
10
5
|
LANGUAGE_TAG_PATTERN,
|
|
11
|
-
|
|
12
|
-
} from "./studio-
|
|
6
|
+
activeTab2
|
|
7
|
+
} from "./studio-az3zd49t.js";
|
|
13
8
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} from "./studio-
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
} from "./studio-
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
} from "./studio-
|
|
28
|
-
import {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
render
|
|
32
|
-
} from "./studio-kryvsw6j.js";
|
|
33
|
-
import {
|
|
34
|
-
isFunctionDef,
|
|
35
|
-
isJsonObject,
|
|
36
|
-
isNamedFormulaDef,
|
|
37
|
-
isRef,
|
|
38
|
-
isTemplateString
|
|
39
|
-
} from "./studio-q7v10cg2.js";
|
|
40
|
-
|
|
41
|
-
// src/panels/provenance.ts
|
|
42
|
-
var PROVENANCE_TITLES = {
|
|
43
|
-
bound: "Bound",
|
|
44
|
-
default: "Not set",
|
|
45
|
-
inherited: "Inherited",
|
|
46
|
-
mixed: "Mixed",
|
|
47
|
-
set: "Set here"
|
|
48
|
-
};
|
|
49
|
-
function provenanceText(p) {
|
|
50
|
-
if (p.state === "inherited") {
|
|
51
|
-
return p.donor ? `from ${p.donor}` : "inherited";
|
|
52
|
-
}
|
|
53
|
-
if (p.state === "bound") {
|
|
54
|
-
return p.donor ?? "bound";
|
|
55
|
-
}
|
|
56
|
-
if (p.state === "mixed") {
|
|
57
|
-
return p.donor ? `mixed (${p.donor})` : "mixed";
|
|
58
|
-
}
|
|
59
|
-
return "";
|
|
60
|
-
}
|
|
61
|
-
function provenanceTitle(prop, p) {
|
|
62
|
-
if (p.title) {
|
|
63
|
-
return p.title;
|
|
64
|
-
}
|
|
65
|
-
const lead = PROVENANCE_TITLES[p.state];
|
|
66
|
-
if (p.state === "inherited") {
|
|
67
|
-
return `${lead} from ${p.donor ?? "the cascade"} — click to go there`;
|
|
68
|
-
}
|
|
69
|
-
if (p.state === "bound") {
|
|
70
|
-
return p.donor ? `${lead} to ${p.donor} — click to open it` : lead;
|
|
71
|
-
}
|
|
72
|
-
if (p.state === "set") {
|
|
73
|
-
return `${lead} — click to clear ${prop}`;
|
|
74
|
-
}
|
|
75
|
-
if (p.state === "mixed") {
|
|
76
|
-
const count = p.donor ? `${p.donor} selected elements` : "the selected elements";
|
|
77
|
-
return `${lead} — ${count} have different values for ${prop}; typing sets them all`;
|
|
78
|
-
}
|
|
79
|
-
return `${lead} — ${prop} falls back to the browser default`;
|
|
80
|
-
}
|
|
81
|
-
function renderProvenanceChip(prop, p) {
|
|
82
|
-
if (p.state === "default") {
|
|
83
|
-
return nothing;
|
|
84
|
-
}
|
|
85
|
-
const text = provenanceText(p);
|
|
86
|
-
const title = provenanceTitle(prop, p);
|
|
87
|
-
const isDot = p.state === "set";
|
|
88
|
-
const classes = classMap({
|
|
89
|
-
"provenance-chip": true,
|
|
90
|
-
[`provenance-chip--${p.state}`]: true,
|
|
91
|
-
"set-dot": isDot
|
|
92
|
-
});
|
|
93
|
-
const { onClick } = p;
|
|
94
|
-
if (!onClick) {
|
|
95
|
-
return html`<span class=${classes} title=${title}>${text}</span>`;
|
|
96
|
-
}
|
|
97
|
-
const activate = (e) => {
|
|
98
|
-
e.stopPropagation();
|
|
99
|
-
e.preventDefault();
|
|
100
|
-
onClick();
|
|
101
|
-
};
|
|
102
|
-
return isDot ? html`<span class=${classes} title=${title} @click=${activate}>${text}</span>` : html`<button type="button" class=${classes} title=${title} @click=${activate}>
|
|
103
|
-
${text}
|
|
104
|
-
</button>`;
|
|
105
|
-
}
|
|
106
|
-
function countProvenance(states) {
|
|
107
|
-
const counts = { bound: 0, inherited: 0, mixed: 0, set: 0 };
|
|
108
|
-
for (const state of states) {
|
|
109
|
-
if (state !== "default") {
|
|
110
|
-
counts[state] += 1;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return counts;
|
|
114
|
-
}
|
|
115
|
-
function hasProvenance(counts) {
|
|
116
|
-
return counts.set > 0 || counts.inherited > 0 || counts.bound > 0 || counts.mixed > 0;
|
|
117
|
-
}
|
|
118
|
-
function provenanceSummaryText(counts) {
|
|
119
|
-
const parts = [];
|
|
120
|
-
if (counts.set > 0) {
|
|
121
|
-
parts.push(`${counts.set} set here`);
|
|
122
|
-
}
|
|
123
|
-
if (counts.inherited > 0) {
|
|
124
|
-
parts.push(`${counts.inherited} inherited`);
|
|
125
|
-
}
|
|
126
|
-
if (counts.bound > 0) {
|
|
127
|
-
parts.push(`${counts.bound} bound`);
|
|
128
|
-
}
|
|
129
|
-
if (counts.mixed > 0) {
|
|
130
|
-
parts.push(`${counts.mixed} mixed`);
|
|
131
|
-
}
|
|
132
|
-
return parts.length > 0 ? parts.join(" · ") : "nothing set";
|
|
133
|
-
}
|
|
134
|
-
function renderProvenanceDots(counts, opts = {}) {
|
|
135
|
-
if (!hasProvenance(counts)) {
|
|
136
|
-
return nothing;
|
|
137
|
-
}
|
|
138
|
-
const summary = provenanceSummaryText(counts);
|
|
139
|
-
return html`
|
|
140
|
-
<span class="provenance-dots" title=${summary} aria-label=${summary}>
|
|
141
|
-
${counts.set > 0 ? renderProvenanceChip("section", {
|
|
142
|
-
state: "set",
|
|
143
|
-
title: opts.clearTitle ?? summary,
|
|
144
|
-
...opts.onClearSet ? { onClick: opts.onClearSet } : {}
|
|
145
|
-
}) : nothing}
|
|
146
|
-
${counts.inherited > 0 ? html`<span
|
|
147
|
-
class="provenance-chip provenance-chip--inherited set-dot"
|
|
148
|
-
title=${summary}
|
|
149
|
-
></span>` : nothing}
|
|
150
|
-
${counts.bound > 0 ? html`<span
|
|
151
|
-
class="provenance-chip provenance-chip--bound set-dot"
|
|
152
|
-
title=${summary}
|
|
153
|
-
></span>` : nothing}
|
|
154
|
-
${counts.mixed > 0 ? html`<span
|
|
155
|
-
class="provenance-chip provenance-chip--mixed set-dot"
|
|
156
|
-
title=${summary}
|
|
157
|
-
></span>` : nothing}
|
|
158
|
-
</span>
|
|
159
|
-
`;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// src/ui/field-row.ts
|
|
163
|
-
function renderFieldRow({
|
|
164
|
-
prop,
|
|
165
|
-
label,
|
|
166
|
-
hasValue,
|
|
167
|
-
onClear,
|
|
168
|
-
widget,
|
|
169
|
-
labelExtra,
|
|
170
|
-
span,
|
|
171
|
-
warning,
|
|
172
|
-
provenance,
|
|
173
|
-
error,
|
|
174
|
-
errorCount
|
|
175
|
-
}) {
|
|
176
|
-
const invalid = Boolean(error);
|
|
177
|
-
const chip = provenance ?? (hasValue && onClear ? { onClick: onClear, state: "set", title: `Clear ${prop}` } : undefined);
|
|
178
|
-
return html`
|
|
179
|
-
<div
|
|
180
|
-
class=${classMap({
|
|
181
|
-
"style-row": true,
|
|
182
|
-
"style-row--invalid": invalid,
|
|
183
|
-
"style-row--warning": Boolean(warning) && !invalid
|
|
184
|
-
})}
|
|
185
|
-
data-prop=${prop}
|
|
186
|
-
style=${span === 2 ? "grid-column: 1 / -1" : ""}
|
|
187
|
-
>
|
|
188
|
-
<div class="style-row-label">
|
|
189
|
-
${chip ? renderProvenanceChip(prop, chip) : nothing}
|
|
190
|
-
<sp-field-label size="s" title=${prop}>${label}</sp-field-label>
|
|
191
|
-
${labelExtra ?? nothing}
|
|
192
|
-
</div>
|
|
193
|
-
${widget}
|
|
194
|
-
${invalid ? html`<p class="style-row-error" role="alert">
|
|
195
|
-
${error}${errorCount !== undefined && errorCount > 1 ? html`<span class="style-row-error-count">×${errorCount}</span>` : nothing}
|
|
196
|
-
</p>` : nothing}
|
|
197
|
-
</div>
|
|
198
|
-
`;
|
|
199
|
-
}
|
|
200
|
-
function renderKvRow({
|
|
201
|
-
name,
|
|
202
|
-
value,
|
|
203
|
-
onCommit,
|
|
204
|
-
onDelete,
|
|
205
|
-
placeholderFor,
|
|
206
|
-
debounceMs = 400
|
|
207
|
-
}) {
|
|
208
|
-
let debounce;
|
|
209
|
-
let currentName = name;
|
|
210
|
-
let currentValue = value;
|
|
211
|
-
const commit = () => {
|
|
212
|
-
clearTimeout(debounce);
|
|
213
|
-
debounce = setTimeout(() => onCommit(currentName, currentValue), debounceMs);
|
|
214
|
-
};
|
|
215
|
-
return html`
|
|
216
|
-
<div class="kv-row" data-prop=${name}>
|
|
217
|
-
<sp-textfield
|
|
218
|
-
size="s"
|
|
219
|
-
class="kv-key"
|
|
220
|
-
.value=${name}
|
|
221
|
-
@input=${(e) => {
|
|
222
|
-
currentName = e.target.value;
|
|
223
|
-
commit();
|
|
224
|
-
}}
|
|
225
|
-
@change=${placeholderFor ? (e) => {
|
|
226
|
-
const cell = e.target.closest(".kv-row")?.querySelector(".kv-val");
|
|
227
|
-
cell?.setAttribute("placeholder", placeholderFor(e.target.value));
|
|
228
|
-
} : nothing}
|
|
229
|
-
></sp-textfield>
|
|
230
|
-
<sp-textfield
|
|
231
|
-
size="s"
|
|
232
|
-
class="kv-val"
|
|
233
|
-
.value=${value}
|
|
234
|
-
placeholder=${placeholderFor ? placeholderFor(name) : ""}
|
|
235
|
-
@input=${(e) => {
|
|
236
|
-
currentValue = e.target.value;
|
|
237
|
-
commit();
|
|
238
|
-
}}
|
|
239
|
-
></sp-textfield>
|
|
240
|
-
<sp-action-button size="xs" quiet title="Remove ${name}" @click=${onDelete}>
|
|
241
|
-
<sp-icon-close slot="icon"></sp-icon-close>
|
|
242
|
-
</sp-action-button>
|
|
243
|
-
</div>
|
|
244
|
-
`;
|
|
245
|
-
}
|
|
246
|
-
function renderStaticKvRow({ name, detail, value, tags }) {
|
|
247
|
-
return html`
|
|
248
|
-
<div class="kv-static-row" data-prop=${name}>
|
|
249
|
-
<code class="kv-static-name">${name}</code>
|
|
250
|
-
${detail ? html`<span class="kv-static-detail">${detail}</span>` : nothing}
|
|
251
|
-
${value ? html`<span class="kv-static-value">${value}</span>` : nothing}
|
|
252
|
-
${(tags ?? []).map((tag) => html`<span class="kv-static-tag">${tag}</span>`)}
|
|
253
|
-
</div>
|
|
254
|
-
`;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// src/ui/formula-chips.ts
|
|
258
|
-
function isExprNode(value) {
|
|
259
|
-
return isJsonObject(value) && typeof value.operator === "string";
|
|
260
|
-
}
|
|
261
|
-
function refLabel(ref2) {
|
|
262
|
-
if (ref2.startsWith("#/state/")) {
|
|
263
|
-
return ref2.slice("#/state/".length);
|
|
264
|
-
}
|
|
265
|
-
if (ref2.startsWith("window#/")) {
|
|
266
|
-
return ref2.slice("window#/".length).replaceAll("/", ".");
|
|
267
|
-
}
|
|
268
|
-
return ref2 || "?";
|
|
269
|
-
}
|
|
270
|
-
function operandLabel(value) {
|
|
271
|
-
if (isRef(value)) {
|
|
272
|
-
return refLabel(value.$ref);
|
|
273
|
-
}
|
|
274
|
-
if (isExprNode(value)) {
|
|
275
|
-
return `(${chipSummary(value)})`;
|
|
276
|
-
}
|
|
277
|
-
if (typeof value === "string") {
|
|
278
|
-
return JSON.stringify(value);
|
|
279
|
-
}
|
|
280
|
-
return String(value);
|
|
281
|
-
}
|
|
282
|
-
function targetPath(basePath, hops) {
|
|
283
|
-
return [...basePath, ...Array.from({ length: hops }, () => "target")];
|
|
284
|
-
}
|
|
285
|
-
function unrollChain(node, basePath) {
|
|
286
|
-
const nodes = [];
|
|
287
|
-
let current = node;
|
|
288
|
-
for (;; ) {
|
|
289
|
-
nodes.push(current);
|
|
290
|
-
const next = current.target;
|
|
291
|
-
if (isExprNode(next)) {
|
|
292
|
-
current = next;
|
|
293
|
-
} else {
|
|
294
|
-
break;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
const deepest = nodes.at(-1);
|
|
298
|
-
const head = "target" in deepest || deepest.target !== undefined ? { path: targetPath(basePath, nodes.length), value: deepest.target } : null;
|
|
299
|
-
const rootFirst = nodes.map((n, i) => ({ node: n, path: targetPath(basePath, i) }));
|
|
300
|
-
return { head, links: rootFirst.toReversed() };
|
|
301
|
-
}
|
|
302
|
-
function chipSummary(node) {
|
|
303
|
-
if (!isExprNode(node)) {
|
|
304
|
-
return operandLabel(node);
|
|
305
|
-
}
|
|
306
|
-
const { head, links } = unrollChain(node, []);
|
|
307
|
-
const parts = [];
|
|
308
|
-
if (head) {
|
|
309
|
-
parts.push(operandLabel(head.value));
|
|
310
|
-
}
|
|
311
|
-
for (const link of links) {
|
|
312
|
-
parts.push(String(link.node.operator));
|
|
313
|
-
}
|
|
314
|
-
return parts.join(" › ");
|
|
315
|
-
}
|
|
316
|
-
var CHIP_STYLE = "display:inline-flex;align-items:center;gap:4px;max-width:180px;padding:1px 7px;" + "border:1px solid var(--spectrum-gray-300, #3c3c3c);border-radius:10px;cursor:pointer;" + "background:var(--spectrum-gray-100, #232323);color:var(--spectrum-gray-800, #d0d0d0);" + "font-size:11px;font-family:var(--spectrum-code-font-family, monospace);line-height:18px";
|
|
317
|
-
function renderChipBadge(preview, pathKey) {
|
|
318
|
-
const text = preview?.values.get(pathKey);
|
|
319
|
-
if (text === undefined) {
|
|
320
|
-
return nothing;
|
|
321
|
-
}
|
|
322
|
-
return html`
|
|
323
|
-
<span
|
|
324
|
-
class="expr-live-badge"
|
|
325
|
-
title=${text}
|
|
326
|
-
style="font-family:var(--spectrum-code-font-family, monospace);font-size:10px;line-height:16px;padding:0 5px;border-radius:4px;background:var(--spectrum-gray-200, #323232);color:var(--spectrum-seafoam-900, #35a690);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:120px;flex-shrink:1"
|
|
327
|
-
>${text}</span
|
|
328
|
-
>
|
|
329
|
-
`;
|
|
330
|
-
}
|
|
331
|
-
function renderChip(label, path, onSelect, preview, group = false) {
|
|
332
|
-
return html`
|
|
333
|
-
<button
|
|
334
|
-
type="button"
|
|
335
|
-
class=${group ? "formula-chip formula-chip--group" : "formula-chip"}
|
|
336
|
-
data-path=${path.join("/")}
|
|
337
|
-
style=${CHIP_STYLE}
|
|
338
|
-
title=${label}
|
|
339
|
-
@click=${() => onSelect(path)}
|
|
340
|
-
>
|
|
341
|
-
<span style="white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:110px"
|
|
342
|
-
>${label}</span
|
|
343
|
-
>
|
|
344
|
-
${renderChipBadge(preview, path.join("/"))}
|
|
345
|
-
</button>
|
|
346
|
-
`;
|
|
347
|
-
}
|
|
348
|
-
function groupChips(link, onSelect, preview) {
|
|
349
|
-
const chips = [];
|
|
350
|
-
const add = (operand, path) => {
|
|
351
|
-
if (isExprNode(operand)) {
|
|
352
|
-
chips.push(renderChip(`(${chipSummary(operand)})`, path, onSelect, preview, true));
|
|
353
|
-
}
|
|
354
|
-
};
|
|
355
|
-
const { value, initial, cases } = link.node;
|
|
356
|
-
if (Array.isArray(value)) {
|
|
357
|
-
for (const [i, item] of value.entries()) {
|
|
358
|
-
add(item, [...link.path, "value", i]);
|
|
359
|
-
}
|
|
360
|
-
} else {
|
|
361
|
-
add(value, [...link.path, "value"]);
|
|
362
|
-
}
|
|
363
|
-
add(initial, [...link.path, "initial"]);
|
|
364
|
-
if (isJsonObject(cases)) {
|
|
365
|
-
for (const [key, operand] of Object.entries(cases)) {
|
|
366
|
-
add(operand, [...link.path, "cases", key]);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
add(link.node.default, [...link.path, "default"]);
|
|
370
|
-
return chips;
|
|
371
|
-
}
|
|
372
|
-
function renderFormulaChips(node, onSelect, opts = {}) {
|
|
373
|
-
if (!isExprNode(node)) {
|
|
374
|
-
return html`${nothing}`;
|
|
375
|
-
}
|
|
376
|
-
const preview = opts.preview ?? null;
|
|
377
|
-
const basePath = opts.path ?? [];
|
|
378
|
-
const { head, links } = unrollChain(node, basePath);
|
|
379
|
-
const chips = [];
|
|
380
|
-
if (head) {
|
|
381
|
-
chips.push(renderChip(operandLabel(head.value), head.path, onSelect, preview));
|
|
382
|
-
}
|
|
383
|
-
for (const link of links) {
|
|
384
|
-
chips.push(renderChip(String(link.node.operator), link.path, onSelect, preview), ...groupChips(link, onSelect, preview));
|
|
385
|
-
}
|
|
386
|
-
return html`
|
|
387
|
-
<div
|
|
388
|
-
class="formula-chips"
|
|
389
|
-
style="display:flex;flex-wrap:wrap;gap:4px;align-items:center;padding:2px 0 6px"
|
|
390
|
-
>
|
|
391
|
-
${chips}
|
|
392
|
-
</div>
|
|
393
|
-
`;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
// ../formulas/src/catalog.ts
|
|
397
|
-
var catalog = [
|
|
398
|
-
{
|
|
399
|
-
description: "Arithmetic mean of an array of numbers; 0 for an empty array.",
|
|
400
|
-
expression: {
|
|
401
|
-
operator: "?:",
|
|
402
|
-
target: {
|
|
403
|
-
$ref: "$args/values/length"
|
|
404
|
-
},
|
|
405
|
-
value: {
|
|
406
|
-
operator: "/",
|
|
407
|
-
target: {
|
|
408
|
-
operator: "reduce",
|
|
409
|
-
target: {
|
|
410
|
-
$ref: "$args/values"
|
|
411
|
-
},
|
|
412
|
-
initial: 0,
|
|
413
|
-
value: {
|
|
414
|
-
operator: "+",
|
|
415
|
-
target: {
|
|
416
|
-
$ref: "$reduce/acc"
|
|
417
|
-
},
|
|
418
|
-
value: {
|
|
419
|
-
$ref: "$map/item"
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
},
|
|
423
|
-
value: {
|
|
424
|
-
$ref: "$args/values/length"
|
|
425
|
-
}
|
|
426
|
-
},
|
|
427
|
-
initial: 0
|
|
428
|
-
},
|
|
429
|
-
name: "average",
|
|
430
|
-
parameters: [
|
|
431
|
-
{
|
|
432
|
-
name: "values",
|
|
433
|
-
type: {
|
|
434
|
-
text: "number[]"
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
]
|
|
438
|
-
},
|
|
439
|
-
{
|
|
440
|
-
description: "Uppercase the first character of a string, leaving the rest unchanged (the CSS text-transform: capitalize shape for a single word).",
|
|
441
|
-
expression: {
|
|
442
|
-
operator: "+",
|
|
443
|
-
target: {
|
|
444
|
-
operator: "toUpperCase",
|
|
445
|
-
target: {
|
|
446
|
-
operator: "charAt",
|
|
447
|
-
target: {
|
|
448
|
-
$ref: "$args/text"
|
|
449
|
-
},
|
|
450
|
-
value: 0
|
|
451
|
-
}
|
|
452
|
-
},
|
|
453
|
-
value: {
|
|
454
|
-
operator: "slice",
|
|
455
|
-
target: {
|
|
456
|
-
$ref: "$args/text"
|
|
457
|
-
},
|
|
458
|
-
value: 1
|
|
459
|
-
}
|
|
460
|
-
},
|
|
461
|
-
name: "capitalize",
|
|
462
|
-
parameters: [
|
|
463
|
-
{
|
|
464
|
-
name: "text",
|
|
465
|
-
type: {
|
|
466
|
-
text: "string"
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
]
|
|
470
|
-
},
|
|
471
|
-
{
|
|
472
|
-
description: "Bound a number to the [min, max] range (the TC39 Math.clamp shape): returns min when below, max when above, the value itself otherwise.",
|
|
473
|
-
expression: {
|
|
474
|
-
operator: "call",
|
|
475
|
-
target: {
|
|
476
|
-
$ref: "window#/Math/min"
|
|
477
|
-
},
|
|
478
|
-
value: [
|
|
479
|
-
{
|
|
480
|
-
operator: "call",
|
|
481
|
-
target: {
|
|
482
|
-
$ref: "window#/Math/max"
|
|
483
|
-
},
|
|
484
|
-
value: [
|
|
485
|
-
{
|
|
486
|
-
$ref: "$args/value"
|
|
487
|
-
},
|
|
488
|
-
{
|
|
489
|
-
$ref: "$args/min"
|
|
490
|
-
}
|
|
491
|
-
]
|
|
492
|
-
},
|
|
493
|
-
{
|
|
494
|
-
$ref: "$args/max"
|
|
495
|
-
}
|
|
496
|
-
]
|
|
497
|
-
},
|
|
498
|
-
name: "clamp",
|
|
499
|
-
parameters: [
|
|
500
|
-
{
|
|
501
|
-
name: "value",
|
|
502
|
-
type: {
|
|
503
|
-
text: "number"
|
|
504
|
-
}
|
|
505
|
-
},
|
|
506
|
-
{
|
|
507
|
-
name: "min",
|
|
508
|
-
type: {
|
|
509
|
-
text: "number"
|
|
510
|
-
}
|
|
511
|
-
},
|
|
512
|
-
{
|
|
513
|
-
name: "max",
|
|
514
|
-
type: {
|
|
515
|
-
text: "number"
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
]
|
|
519
|
-
},
|
|
520
|
-
{
|
|
521
|
-
description: "A copy of an array with the falsy elements removed (Array.prototype.filter on truthiness).",
|
|
522
|
-
expression: {
|
|
523
|
-
operator: "filter",
|
|
524
|
-
target: {
|
|
525
|
-
$ref: "$args/values"
|
|
526
|
-
},
|
|
527
|
-
value: {
|
|
528
|
-
operator: "!",
|
|
529
|
-
target: {
|
|
530
|
-
operator: "!",
|
|
531
|
-
target: {
|
|
532
|
-
$ref: "$map/item"
|
|
533
|
-
}
|
|
534
|
-
}
|
|
535
|
-
}
|
|
536
|
-
},
|
|
537
|
-
name: "compact",
|
|
538
|
-
parameters: [
|
|
539
|
-
{
|
|
540
|
-
name: "values",
|
|
541
|
-
type: {
|
|
542
|
-
text: "unknown[]"
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
]
|
|
546
|
-
},
|
|
547
|
-
{
|
|
548
|
-
description: "The length of an array or string, or 0 when the value is missing.",
|
|
549
|
-
expression: {
|
|
550
|
-
operator: "??",
|
|
551
|
-
target: {
|
|
552
|
-
$ref: "$args/values/length"
|
|
553
|
-
},
|
|
554
|
-
value: 0
|
|
555
|
-
},
|
|
556
|
-
name: "count",
|
|
557
|
-
parameters: [
|
|
558
|
-
{
|
|
559
|
-
name: "values",
|
|
560
|
-
type: {
|
|
561
|
-
text: "unknown[] | string"
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
]
|
|
565
|
-
},
|
|
566
|
-
{
|
|
567
|
-
description: "The first element of an array (index 0), or null when empty.",
|
|
568
|
-
expression: {
|
|
569
|
-
operator: "??",
|
|
570
|
-
target: {
|
|
571
|
-
$ref: "$args/values/0"
|
|
572
|
-
},
|
|
573
|
-
value: null
|
|
574
|
-
},
|
|
575
|
-
name: "first",
|
|
576
|
-
parameters: [
|
|
577
|
-
{
|
|
578
|
-
name: "values",
|
|
579
|
-
type: {
|
|
580
|
-
text: "unknown[]"
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
]
|
|
584
|
-
},
|
|
585
|
-
{
|
|
586
|
-
description: 'The uppercase first letters of each whitespace-separated word, joined (e.g. "ada lovelace" → "AL").',
|
|
587
|
-
expression: {
|
|
588
|
-
operator: "join",
|
|
589
|
-
target: {
|
|
590
|
-
operator: "map",
|
|
591
|
-
target: {
|
|
592
|
-
operator: "split",
|
|
593
|
-
target: {
|
|
594
|
-
$ref: "$args/name"
|
|
595
|
-
},
|
|
596
|
-
value: " "
|
|
597
|
-
},
|
|
598
|
-
value: {
|
|
599
|
-
operator: "toUpperCase",
|
|
600
|
-
target: {
|
|
601
|
-
operator: "charAt",
|
|
602
|
-
target: {
|
|
603
|
-
$ref: "$map/item"
|
|
604
|
-
},
|
|
605
|
-
value: 0
|
|
606
|
-
}
|
|
607
|
-
}
|
|
608
|
-
},
|
|
609
|
-
value: ""
|
|
610
|
-
},
|
|
611
|
-
name: "initials",
|
|
612
|
-
parameters: [
|
|
613
|
-
{
|
|
614
|
-
name: "name",
|
|
615
|
-
type: {
|
|
616
|
-
text: "string"
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
]
|
|
620
|
-
},
|
|
621
|
-
{
|
|
622
|
-
description: "True when an array or string has no elements (length 0 — a missing value counts as empty).",
|
|
623
|
-
expression: {
|
|
624
|
-
operator: "===",
|
|
625
|
-
target: {
|
|
626
|
-
operator: "??",
|
|
627
|
-
target: {
|
|
628
|
-
$ref: "$args/value/length"
|
|
629
|
-
},
|
|
630
|
-
value: 0
|
|
631
|
-
},
|
|
632
|
-
value: 0
|
|
633
|
-
},
|
|
634
|
-
name: "isEmpty",
|
|
635
|
-
parameters: [
|
|
636
|
-
{
|
|
637
|
-
name: "value",
|
|
638
|
-
type: {
|
|
639
|
-
text: "unknown[] | string"
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
]
|
|
643
|
-
},
|
|
644
|
-
{
|
|
645
|
-
description: "The last element of an array (Array.prototype.at(-1)), or undefined when empty.",
|
|
646
|
-
expression: {
|
|
647
|
-
operator: "at",
|
|
648
|
-
target: {
|
|
649
|
-
$ref: "$args/values"
|
|
650
|
-
},
|
|
651
|
-
value: -1
|
|
652
|
-
},
|
|
653
|
-
name: "last",
|
|
654
|
-
parameters: [
|
|
655
|
-
{
|
|
656
|
-
name: "values",
|
|
657
|
-
type: {
|
|
658
|
-
text: "unknown[]"
|
|
659
|
-
}
|
|
660
|
-
}
|
|
661
|
-
]
|
|
662
|
-
},
|
|
663
|
-
{
|
|
664
|
-
description: "The largest number in an array (Math.max folded over the values); undefined for an empty array.",
|
|
665
|
-
expression: {
|
|
666
|
-
operator: "reduce",
|
|
667
|
-
target: {
|
|
668
|
-
$ref: "$args/values"
|
|
669
|
-
},
|
|
670
|
-
initial: {
|
|
671
|
-
$ref: "$args/values/0"
|
|
672
|
-
},
|
|
673
|
-
value: {
|
|
674
|
-
operator: "call",
|
|
675
|
-
target: {
|
|
676
|
-
$ref: "window#/Math/max"
|
|
677
|
-
},
|
|
678
|
-
value: [
|
|
679
|
-
{
|
|
680
|
-
$ref: "$reduce/acc"
|
|
681
|
-
},
|
|
682
|
-
{
|
|
683
|
-
$ref: "$map/item"
|
|
684
|
-
}
|
|
685
|
-
]
|
|
686
|
-
}
|
|
687
|
-
},
|
|
688
|
-
name: "max",
|
|
689
|
-
parameters: [
|
|
690
|
-
{
|
|
691
|
-
name: "values",
|
|
692
|
-
type: {
|
|
693
|
-
text: "number[]"
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
]
|
|
697
|
-
},
|
|
698
|
-
{
|
|
699
|
-
description: "The smallest number in an array (Math.min folded over the values); undefined for an empty array.",
|
|
700
|
-
expression: {
|
|
701
|
-
operator: "reduce",
|
|
702
|
-
target: {
|
|
703
|
-
$ref: "$args/values"
|
|
704
|
-
},
|
|
705
|
-
initial: {
|
|
706
|
-
$ref: "$args/values/0"
|
|
707
|
-
},
|
|
708
|
-
value: {
|
|
709
|
-
operator: "call",
|
|
710
|
-
target: {
|
|
711
|
-
$ref: "window#/Math/min"
|
|
712
|
-
},
|
|
713
|
-
value: [
|
|
714
|
-
{
|
|
715
|
-
$ref: "$reduce/acc"
|
|
716
|
-
},
|
|
717
|
-
{
|
|
718
|
-
$ref: "$map/item"
|
|
719
|
-
}
|
|
720
|
-
]
|
|
721
|
-
}
|
|
722
|
-
},
|
|
723
|
-
name: "min",
|
|
724
|
-
parameters: [
|
|
725
|
-
{
|
|
726
|
-
name: "values",
|
|
727
|
-
type: {
|
|
728
|
-
text: "number[]"
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
]
|
|
732
|
-
},
|
|
733
|
-
{
|
|
734
|
-
description: "part / whole as a percentage (0 when the whole is 0 or missing).",
|
|
735
|
-
expression: {
|
|
736
|
-
operator: "?:",
|
|
737
|
-
target: {
|
|
738
|
-
$ref: "$args/whole"
|
|
739
|
-
},
|
|
740
|
-
value: {
|
|
741
|
-
operator: "*",
|
|
742
|
-
target: {
|
|
743
|
-
operator: "/",
|
|
744
|
-
target: {
|
|
745
|
-
$ref: "$args/part"
|
|
746
|
-
},
|
|
747
|
-
value: {
|
|
748
|
-
$ref: "$args/whole"
|
|
749
|
-
}
|
|
750
|
-
},
|
|
751
|
-
value: 100
|
|
752
|
-
},
|
|
753
|
-
initial: 0
|
|
754
|
-
},
|
|
755
|
-
name: "percent",
|
|
756
|
-
parameters: [
|
|
757
|
-
{
|
|
758
|
-
name: "part",
|
|
759
|
-
type: {
|
|
760
|
-
text: "number"
|
|
761
|
-
}
|
|
762
|
-
},
|
|
763
|
-
{
|
|
764
|
-
name: "whole",
|
|
765
|
-
type: {
|
|
766
|
-
text: "number"
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
]
|
|
770
|
-
},
|
|
771
|
-
{
|
|
772
|
-
description: "Round a number to the given decimal places (Math.round against a Math.pow(10, digits) scale).",
|
|
773
|
-
expression: {
|
|
774
|
-
operator: "/",
|
|
775
|
-
target: {
|
|
776
|
-
operator: "call",
|
|
777
|
-
target: {
|
|
778
|
-
$ref: "window#/Math/round"
|
|
779
|
-
},
|
|
780
|
-
value: [
|
|
781
|
-
{
|
|
782
|
-
operator: "*",
|
|
783
|
-
target: {
|
|
784
|
-
$ref: "$args/value"
|
|
785
|
-
},
|
|
786
|
-
value: {
|
|
787
|
-
operator: "call",
|
|
788
|
-
target: {
|
|
789
|
-
$ref: "window#/Math/pow"
|
|
790
|
-
},
|
|
791
|
-
value: [
|
|
792
|
-
10,
|
|
793
|
-
{
|
|
794
|
-
$ref: "$args/digits"
|
|
795
|
-
}
|
|
796
|
-
]
|
|
797
|
-
}
|
|
798
|
-
}
|
|
799
|
-
]
|
|
800
|
-
},
|
|
801
|
-
value: {
|
|
802
|
-
operator: "call",
|
|
803
|
-
target: {
|
|
804
|
-
$ref: "window#/Math/pow"
|
|
805
|
-
},
|
|
806
|
-
value: [
|
|
807
|
-
10,
|
|
808
|
-
{
|
|
809
|
-
$ref: "$args/digits"
|
|
810
|
-
}
|
|
811
|
-
]
|
|
812
|
-
}
|
|
813
|
-
},
|
|
814
|
-
name: "roundTo",
|
|
815
|
-
parameters: [
|
|
816
|
-
{
|
|
817
|
-
name: "value",
|
|
818
|
-
type: {
|
|
819
|
-
text: "number"
|
|
820
|
-
}
|
|
821
|
-
},
|
|
822
|
-
{
|
|
823
|
-
name: "digits",
|
|
824
|
-
type: {
|
|
825
|
-
text: "number"
|
|
826
|
-
},
|
|
827
|
-
default: 0
|
|
828
|
-
}
|
|
829
|
-
]
|
|
830
|
-
},
|
|
831
|
-
{
|
|
832
|
-
description: "Add every number in an array (Array.prototype.reduce with +, seeded at 0).",
|
|
833
|
-
expression: {
|
|
834
|
-
operator: "reduce",
|
|
835
|
-
target: {
|
|
836
|
-
$ref: "$args/values"
|
|
837
|
-
},
|
|
838
|
-
initial: 0,
|
|
839
|
-
value: {
|
|
840
|
-
operator: "+",
|
|
841
|
-
target: {
|
|
842
|
-
$ref: "$reduce/acc"
|
|
843
|
-
},
|
|
844
|
-
value: {
|
|
845
|
-
$ref: "$map/item"
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
},
|
|
849
|
-
name: "sum",
|
|
850
|
-
parameters: [
|
|
851
|
-
{
|
|
852
|
-
name: "values",
|
|
853
|
-
type: {
|
|
854
|
-
text: "number[]"
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
]
|
|
858
|
-
},
|
|
859
|
-
{
|
|
860
|
-
description: "Shorten a string to at most the given length, appending a horizontal ellipsis when cut.",
|
|
861
|
-
expression: {
|
|
862
|
-
operator: "?:",
|
|
863
|
-
target: {
|
|
864
|
-
operator: ">",
|
|
865
|
-
target: {
|
|
866
|
-
$ref: "$args/text/length"
|
|
867
|
-
},
|
|
868
|
-
value: {
|
|
869
|
-
$ref: "$args/length"
|
|
870
|
-
}
|
|
871
|
-
},
|
|
872
|
-
value: {
|
|
873
|
-
operator: "+",
|
|
874
|
-
target: {
|
|
875
|
-
operator: "slice",
|
|
876
|
-
target: {
|
|
877
|
-
$ref: "$args/text"
|
|
878
|
-
},
|
|
879
|
-
value: [
|
|
880
|
-
0,
|
|
881
|
-
{
|
|
882
|
-
$ref: "$args/length"
|
|
883
|
-
}
|
|
884
|
-
]
|
|
885
|
-
},
|
|
886
|
-
value: "…"
|
|
887
|
-
},
|
|
888
|
-
initial: {
|
|
889
|
-
$ref: "$args/text"
|
|
890
|
-
}
|
|
891
|
-
},
|
|
892
|
-
name: "truncate",
|
|
893
|
-
parameters: [
|
|
894
|
-
{
|
|
895
|
-
name: "text",
|
|
896
|
-
type: {
|
|
897
|
-
text: "string"
|
|
898
|
-
}
|
|
899
|
-
},
|
|
900
|
-
{
|
|
901
|
-
name: "length",
|
|
902
|
-
type: {
|
|
903
|
-
text: "number"
|
|
904
|
-
}
|
|
905
|
-
}
|
|
906
|
-
]
|
|
907
|
-
}
|
|
908
|
-
];
|
|
909
|
-
// src/ui/formula-catalog.ts
|
|
910
|
-
function applyCatalogPick(entry, onChange, opts) {
|
|
911
|
-
if (entry.ensure && !opts?.stateEntries?.[entry.ensure.name]) {
|
|
912
|
-
opts?.onInsertDef?.(entry.ensure.name, entry.ensure.def);
|
|
913
|
-
}
|
|
914
|
-
onChange(entry.insert());
|
|
915
|
-
}
|
|
916
|
-
var REF_TARGET = { description: "Writable pointer the operation applies to", name: "target" };
|
|
917
|
-
var LEFT = { description: "Left operand", name: "target" };
|
|
918
|
-
var RIGHT = { description: "Right operand", name: "value" };
|
|
919
|
-
function assignMeta(op, verb) {
|
|
920
|
-
return {
|
|
921
|
-
description: `${verb} assignment operator (ECMA \`${op}\`): stores the result at the target pointer.`,
|
|
922
|
-
group: "Assignment",
|
|
923
|
-
insert: () => ({ operator: op, target: { $ref: "" }, value: null }),
|
|
924
|
-
parameters: [REF_TARGET, { description: "Value assigned to the target", name: "value" }]
|
|
925
|
-
};
|
|
926
|
-
}
|
|
927
|
-
function binaryMeta(group, op, description) {
|
|
928
|
-
return {
|
|
929
|
-
description,
|
|
930
|
-
group,
|
|
931
|
-
insert: () => ({ operator: op, target: null, value: null }),
|
|
932
|
-
parameters: [LEFT, RIGHT]
|
|
933
|
-
};
|
|
934
|
-
}
|
|
935
|
-
function arrayMeta(op, description, withValue) {
|
|
936
|
-
return {
|
|
937
|
-
description,
|
|
938
|
-
group: "Array methods",
|
|
939
|
-
insert: () => withValue ? { operator: op, target: { $ref: "" }, value: null } : { operator: op, target: { $ref: "" } },
|
|
940
|
-
parameters: withValue ? [REF_TARGET, { description: "Element to add", name: "value" }] : [REF_TARGET]
|
|
941
|
-
};
|
|
942
|
-
}
|
|
943
|
-
function aggregateMeta(op, description, withInitial) {
|
|
944
|
-
const node = {
|
|
945
|
-
operator: op,
|
|
946
|
-
target: { $ref: "" },
|
|
947
|
-
value: { operator: "!", target: null }
|
|
948
|
-
};
|
|
949
|
-
return {
|
|
950
|
-
description,
|
|
951
|
-
group: "Aggregate",
|
|
952
|
-
insert: () => withInitial ? { ...node, initial: 0 } : { ...node },
|
|
953
|
-
parameters: [
|
|
954
|
-
{ description: "Array pointer to iterate", name: "target" },
|
|
955
|
-
{ description: "Per-item expression ($map/item, $map/index)", name: "value" },
|
|
956
|
-
...withInitial ? [{ description: "Initial accumulator ($reduce/acc)", name: "initial" }] : []
|
|
957
|
-
]
|
|
958
|
-
};
|
|
959
|
-
}
|
|
960
|
-
var OPERATOR_META = {
|
|
961
|
-
"!": {
|
|
962
|
-
description: "Logical NOT operator (ECMA `!`): coerces the operand to boolean and negates it.",
|
|
963
|
-
group: "Unary",
|
|
964
|
-
insert: () => ({ operator: "!", target: null }),
|
|
965
|
-
parameters: [{ description: "Operand to negate", name: "target" }]
|
|
966
|
-
},
|
|
967
|
-
"!==": binaryMeta("Comparison", "!==", "Strict inequality operator (ECMA `!==`): true when the operands differ without coercion."),
|
|
968
|
-
"%": binaryMeta("Arithmetic", "%", "Remainder operator (ECMA `%`): remainder of dividing the left operand by the right."),
|
|
969
|
-
"&&": binaryMeta("Logical", "&&", "Logical AND operator (ECMA `&&`): yields the right operand when the left is truthy."),
|
|
970
|
-
"*": binaryMeta("Arithmetic", "*", "Multiplication operator (ECMA `*`): numeric product of the operands."),
|
|
971
|
-
"*=": assignMeta("*=", "Multiplication"),
|
|
972
|
-
"+": binaryMeta("Arithmetic", "+", "Addition operator (ECMA `+`): numeric addition, or string concatenation."),
|
|
973
|
-
"+=": assignMeta("+=", "Addition"),
|
|
974
|
-
"-": binaryMeta("Arithmetic", "-", "Subtraction operator (ECMA `-`); with no value operand it is unary negation."),
|
|
975
|
-
"-=": assignMeta("-=", "Subtraction"),
|
|
976
|
-
"/": binaryMeta("Arithmetic", "/", "Division operator (ECMA `/`): quotient of the operands."),
|
|
977
|
-
"/=": assignMeta("/=", "Division"),
|
|
978
|
-
"<": binaryMeta("Comparison", "<", "Less-than operator (ECMA `<`): relational comparison of the operands."),
|
|
979
|
-
"<=": binaryMeta("Comparison", "<=", "Less-than-or-equal operator (ECMA `<=`): relational comparison of the operands."),
|
|
980
|
-
"=": assignMeta("=", "Simple"),
|
|
981
|
-
"===": binaryMeta("Comparison", "===", "Strict equality operator (ECMA `===`): true when the operands are equal without coercion."),
|
|
982
|
-
">": binaryMeta("Comparison", ">", "Greater-than operator (ECMA `>`): relational comparison of the operands."),
|
|
983
|
-
">=": binaryMeta("Comparison", ">=", "Greater-than-or-equal operator (ECMA `>=`): relational comparison of the operands."),
|
|
984
|
-
"??": binaryMeta("Logical", "??", "Nullish coalescing operator (ECMA `??`): yields the right operand when the left is null or undefined."),
|
|
985
|
-
"?:": {
|
|
986
|
-
description: "Conditional operator (ECMA `?:`): evaluates the test in target, yielding value when truthy, initial otherwise.",
|
|
987
|
-
group: "Conditional",
|
|
988
|
-
insert: () => ({ initial: null, operator: "?:", target: null, value: null }),
|
|
989
|
-
parameters: [
|
|
990
|
-
{ description: "Test condition", name: "target" },
|
|
991
|
-
{ description: "Result when the test is truthy", name: "value" },
|
|
992
|
-
{ description: "Result when the test is falsy", name: "initial" }
|
|
993
|
-
]
|
|
994
|
-
},
|
|
995
|
-
"||": binaryMeta("Logical", "||", "Logical OR operator (ECMA `||`): yields the right operand when the left is falsy."),
|
|
996
|
-
call: {
|
|
997
|
-
description: "Function.prototype.call (ECMA): invokes a named formula (#/state/…) or blessed pure global (window#/…) with positional arguments.",
|
|
998
|
-
group: "Function",
|
|
999
|
-
insert: () => ({ operator: "call", target: { $ref: "" }, value: [] }),
|
|
1000
|
-
parameters: [
|
|
1001
|
-
{ description: "Callee pointer: #/state/<formula> or window#/<global>", name: "target" },
|
|
1002
|
-
{ description: "Positional argument list", name: "value" }
|
|
1003
|
-
]
|
|
1004
|
-
},
|
|
1005
|
-
filter: aggregateMeta("filter", "Array.prototype.filter (ECMA): keeps elements whose per-item expression is truthy.", false),
|
|
1006
|
-
map: aggregateMeta("map", "Array.prototype.map (ECMA): transforms each element via the per-item expression.", false),
|
|
1007
|
-
pop: arrayMeta("pop", "Array.prototype.pop (ECMA): removes and returns the last element of the target array.", false),
|
|
1008
|
-
push: arrayMeta("push", "Array.prototype.push (ECMA): appends the value to the target array.", true),
|
|
1009
|
-
reduce: aggregateMeta("reduce", "Array.prototype.reduce (ECMA): folds the array into an accumulator via the per-item expression.", true),
|
|
1010
|
-
shift: arrayMeta("shift", "Array.prototype.shift (ECMA): removes and returns the first element of the target array.", false),
|
|
1011
|
-
splice: {
|
|
1012
|
-
description: "Array.prototype.splice (ECMA): removes and/or inserts elements at an index of the target array.",
|
|
1013
|
-
group: "Array methods",
|
|
1014
|
-
insert: () => ({ operator: "splice", target: { $ref: "" }, value: [null] }),
|
|
1015
|
-
parameters: [
|
|
1016
|
-
REF_TARGET,
|
|
1017
|
-
{ description: "Arguments: start, deleteCount, items…", name: "value" }
|
|
1018
|
-
]
|
|
1019
|
-
},
|
|
1020
|
-
switch: {
|
|
1021
|
-
description: "Switch selection (ECMA `switch` semantics): matches the target's string form against case keys, falling back to default.",
|
|
1022
|
-
group: "Conditional",
|
|
1023
|
-
insert: () => ({ cases: {}, default: null, operator: "switch", target: null }),
|
|
1024
|
-
parameters: [
|
|
1025
|
-
{ description: "Discriminant value", name: "target" },
|
|
1026
|
-
{ description: "Matched value → result operand map", name: "cases" },
|
|
1027
|
-
{ description: "Result when no case matches", name: "default" }
|
|
1028
|
-
]
|
|
1029
|
-
},
|
|
1030
|
-
unshift: arrayMeta("unshift", "Array.prototype.unshift (ECMA): prepends the value to the target array.", true)
|
|
1031
|
-
};
|
|
1032
|
-
function operatorEntries() {
|
|
1033
|
-
const out = [];
|
|
1034
|
-
for (const op of BLESSED_OPERATORS) {
|
|
1035
|
-
const meta = Object.hasOwn(OPERATOR_META, op) ? OPERATOR_META[op] : undefined;
|
|
1036
|
-
if (meta) {
|
|
1037
|
-
out.push({
|
|
1038
|
-
description: meta.description,
|
|
1039
|
-
group: meta.group,
|
|
1040
|
-
insert: meta.insert,
|
|
1041
|
-
kind: "operator",
|
|
1042
|
-
label: op,
|
|
1043
|
-
name: op,
|
|
1044
|
-
parameters: meta.parameters
|
|
1045
|
-
});
|
|
1046
|
-
} else if (PURE_METHOD_OPS.has(op)) {
|
|
1047
|
-
out.push(pureMethodEntry(op));
|
|
1048
|
-
}
|
|
1049
|
-
}
|
|
1050
|
-
return out;
|
|
1051
|
-
}
|
|
1052
|
-
var PURE_METHOD_PROTO = {
|
|
1053
|
-
at: "Array/String",
|
|
1054
|
-
charAt: "String",
|
|
1055
|
-
concat: "Array/String",
|
|
1056
|
-
endsWith: "String",
|
|
1057
|
-
flat: "Array",
|
|
1058
|
-
includes: "Array/String",
|
|
1059
|
-
indexOf: "Array/String",
|
|
1060
|
-
join: "Array",
|
|
1061
|
-
lastIndexOf: "Array/String",
|
|
1062
|
-
normalize: "String",
|
|
1063
|
-
padEnd: "String",
|
|
1064
|
-
padStart: "String",
|
|
1065
|
-
repeat: "String",
|
|
1066
|
-
replaceAll: "String",
|
|
1067
|
-
slice: "Array/String",
|
|
1068
|
-
split: "String",
|
|
1069
|
-
startsWith: "String",
|
|
1070
|
-
toFixed: "Number",
|
|
1071
|
-
toLocaleLowerCase: "String",
|
|
1072
|
-
toLocaleString: "Number",
|
|
1073
|
-
toLocaleUpperCase: "String",
|
|
1074
|
-
toLowerCase: "String",
|
|
1075
|
-
toPrecision: "Number",
|
|
1076
|
-
toReversed: "Array",
|
|
1077
|
-
toSorted: "Array",
|
|
1078
|
-
toSpliced: "Array",
|
|
1079
|
-
toUpperCase: "String",
|
|
1080
|
-
trim: "String",
|
|
1081
|
-
trimEnd: "String",
|
|
1082
|
-
trimStart: "String",
|
|
1083
|
-
with: "Array"
|
|
1084
|
-
};
|
|
1085
|
-
var ZERO_ARG_METHODS = new Set([
|
|
1086
|
-
"flat",
|
|
1087
|
-
"normalize",
|
|
1088
|
-
"toLocaleLowerCase",
|
|
1089
|
-
"toLocaleString",
|
|
1090
|
-
"toLocaleUpperCase",
|
|
1091
|
-
"toLowerCase",
|
|
1092
|
-
"toReversed",
|
|
1093
|
-
"toSorted",
|
|
1094
|
-
"toUpperCase",
|
|
1095
|
-
"trim",
|
|
1096
|
-
"trimEnd",
|
|
1097
|
-
"trimStart"
|
|
1098
|
-
]);
|
|
1099
|
-
function pureMethodEntry(op) {
|
|
1100
|
-
const proto = PURE_METHOD_PROTO[op] ?? "Standard library";
|
|
1101
|
-
return {
|
|
1102
|
-
description: `${proto}.prototype.${op} (ECMA) — pure: never mutates the receiver. Receiver in target; argument (or argument list) in value.`,
|
|
1103
|
-
group: `${proto} methods`,
|
|
1104
|
-
insert: () => ZERO_ARG_METHODS.has(op) ? { operator: op, target: { $ref: "" } } : { operator: op, target: { $ref: "" }, value: null },
|
|
1105
|
-
kind: "operator",
|
|
1106
|
-
label: op,
|
|
1107
|
-
name: op,
|
|
1108
|
-
parameters: []
|
|
1109
|
-
};
|
|
1110
|
-
}
|
|
1111
|
-
var HELPER_DESCRIPTIONS = {
|
|
1112
|
-
"Intl/formatDate": "Intl.formatDate(value, locale?, options?) — blessed helper wrapping new Intl.DateTimeFormat(locale, options).format(new Date(value)).",
|
|
1113
|
-
"Intl/formatNumber": "Intl.formatNumber(value, locale?, options?) — blessed helper wrapping new Intl.NumberFormat(locale, options).format(value).",
|
|
1114
|
-
"Intl/formatRelativeTime": "Intl.formatRelativeTime(value, unit, locale?, options?) — blessed helper wrapping new Intl.RelativeTimeFormat(locale, options).format(value, unit)."
|
|
1115
|
-
};
|
|
1116
|
-
function globalEntries() {
|
|
1117
|
-
const out = [];
|
|
1118
|
-
for (const name of BLESSED_GLOBALS) {
|
|
1119
|
-
const slash = name.indexOf("/");
|
|
1120
|
-
const group = slash === -1 ? "globalThis" : name.slice(0, slash);
|
|
1121
|
-
const label = name.replaceAll("/", ".");
|
|
1122
|
-
out.push({
|
|
1123
|
-
description: HELPER_DESCRIPTIONS[name] ?? `${label} — blessed pure global from the ECMAScript standard library, invoked via the call operator (window#/${name}).`,
|
|
1124
|
-
group,
|
|
1125
|
-
insert: () => ({ operator: "call", target: { $ref: `window#/${name}` }, value: [] }),
|
|
1126
|
-
kind: "global",
|
|
1127
|
-
label,
|
|
1128
|
-
name,
|
|
1129
|
-
parameters: []
|
|
1130
|
-
});
|
|
1131
|
-
}
|
|
1132
|
-
return out;
|
|
1133
|
-
}
|
|
1134
|
-
function toParameterInfo(p) {
|
|
1135
|
-
if (typeof p === "string") {
|
|
1136
|
-
return p ? { name: p } : null;
|
|
1137
|
-
}
|
|
1138
|
-
if (!isJsonObject(p) || typeof p.name !== "string" || !p.name) {
|
|
1139
|
-
return null;
|
|
1140
|
-
}
|
|
1141
|
-
const info = { name: p.name };
|
|
1142
|
-
const { type } = p;
|
|
1143
|
-
if (typeof type === "string") {
|
|
1144
|
-
info.type = type;
|
|
1145
|
-
} else if (isJsonObject(type)) {
|
|
1146
|
-
if (typeof type.text === "string") {
|
|
1147
|
-
info.type = type.text;
|
|
1148
|
-
} else if (typeof type.type === "string") {
|
|
1149
|
-
info.type = type.type;
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
if (typeof p.description === "string") {
|
|
1153
|
-
info.description = p.description;
|
|
1154
|
-
}
|
|
1155
|
-
if ("default" in p && p.default !== undefined) {
|
|
1156
|
-
info.default = p.default;
|
|
1157
|
-
}
|
|
1158
|
-
return info;
|
|
1159
|
-
}
|
|
1160
|
-
function namedFormulaEntries(state) {
|
|
1161
|
-
const out = [];
|
|
1162
|
-
for (const [key, def] of Object.entries(state ?? {})) {
|
|
1163
|
-
if (!isNamedFormulaDef(def)) {
|
|
1164
|
-
continue;
|
|
1165
|
-
}
|
|
1166
|
-
const parameters = def.parameters.map((p) => toParameterInfo(p)).filter((p) => p !== null);
|
|
1167
|
-
const description = typeof def.description === "string" && def.description ? def.description : typeof def.$title === "string" && def.$title ? def.$title : `Named formula "${key}" from document state.`;
|
|
1168
|
-
out.push({
|
|
1169
|
-
description,
|
|
1170
|
-
group: "Formulas",
|
|
1171
|
-
insert: () => ({
|
|
1172
|
-
operator: "call",
|
|
1173
|
-
target: { $ref: `#/state/${key}` },
|
|
1174
|
-
value: parameters.map((p) => p.default ?? null)
|
|
1175
|
-
}),
|
|
1176
|
-
kind: "formula",
|
|
1177
|
-
label: key,
|
|
1178
|
-
name: key,
|
|
1179
|
-
parameters
|
|
1180
|
-
});
|
|
1181
|
-
}
|
|
1182
|
-
return out;
|
|
1183
|
-
}
|
|
1184
|
-
function packagedFormulaEntries(state) {
|
|
1185
|
-
const out = [];
|
|
1186
|
-
for (const formula of catalog) {
|
|
1187
|
-
if (state?.[formula.name]) {
|
|
1188
|
-
continue;
|
|
1189
|
-
}
|
|
1190
|
-
const parameters = formula.parameters.map((p) => toParameterInfo(p)).filter((p) => p !== null);
|
|
1191
|
-
out.push({
|
|
1192
|
-
description: formula.description,
|
|
1193
|
-
ensure: {
|
|
1194
|
-
def: {
|
|
1195
|
-
$description: formula.description,
|
|
1196
|
-
$expression: formula.expression,
|
|
1197
|
-
parameters: formula.parameters
|
|
1198
|
-
},
|
|
1199
|
-
name: formula.name
|
|
1200
|
-
},
|
|
1201
|
-
group: "Formulas library",
|
|
1202
|
-
insert: () => ({
|
|
1203
|
-
operator: "call",
|
|
1204
|
-
target: { $ref: `#/state/${formula.name}` },
|
|
1205
|
-
value: parameters.map((p) => p.default ?? null)
|
|
1206
|
-
}),
|
|
1207
|
-
kind: "formula",
|
|
1208
|
-
label: formula.name,
|
|
1209
|
-
name: formula.name,
|
|
1210
|
-
parameters
|
|
1211
|
-
});
|
|
1212
|
-
}
|
|
1213
|
-
return out;
|
|
1214
|
-
}
|
|
1215
|
-
function formulaCatalog(state) {
|
|
1216
|
-
return [
|
|
1217
|
-
...namedFormulaEntries(state),
|
|
1218
|
-
...packagedFormulaEntries(state),
|
|
1219
|
-
...operatorEntries(),
|
|
1220
|
-
...globalEntries()
|
|
1221
|
-
];
|
|
1222
|
-
}
|
|
1223
|
-
function calleeEntry(ref2, state) {
|
|
1224
|
-
if (ref2.startsWith("window#/")) {
|
|
1225
|
-
const name = ref2.slice("window#/".length);
|
|
1226
|
-
return globalEntries().find((e) => e.name === name);
|
|
1227
|
-
}
|
|
1228
|
-
if (ref2.startsWith("#/state/")) {
|
|
1229
|
-
const key = ref2.slice("#/state/".length);
|
|
1230
|
-
return namedFormulaEntries(state).find((e) => e.name === key);
|
|
1231
|
-
}
|
|
1232
|
-
return;
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
// src/ui/formula-palette.ts
|
|
1236
|
-
var _open = false;
|
|
1237
|
-
var _opts = null;
|
|
1238
|
-
var _query = "";
|
|
1239
|
-
var _selectedIndex = 0;
|
|
1240
|
-
function getContainer() {
|
|
1241
|
-
return getLayerSlot("popover", "formula-palette");
|
|
1242
|
-
}
|
|
1243
|
-
function openFormulaPalette(opts) {
|
|
1244
|
-
_open = true;
|
|
1245
|
-
_opts = opts;
|
|
1246
|
-
_query = "";
|
|
1247
|
-
_selectedIndex = 0;
|
|
1248
|
-
renderOverlay();
|
|
1249
|
-
}
|
|
1250
|
-
function closeFormulaPalette() {
|
|
1251
|
-
_open = false;
|
|
1252
|
-
_opts = null;
|
|
1253
|
-
renderOverlay();
|
|
1254
|
-
}
|
|
1255
|
-
function filteredEntries() {
|
|
1256
|
-
const entries = _opts?.entries ?? [];
|
|
1257
|
-
const needle = _query.trim().toLowerCase();
|
|
1258
|
-
if (!needle) {
|
|
1259
|
-
return entries;
|
|
1260
|
-
}
|
|
1261
|
-
return entries.filter((e) => e.label.toLowerCase().includes(needle) || e.name.toLowerCase().includes(needle) || e.group.toLowerCase().includes(needle) || e.description.toLowerCase().includes(needle));
|
|
1262
|
-
}
|
|
1263
|
-
function groupedEntries(entries) {
|
|
1264
|
-
const groups = new Map;
|
|
1265
|
-
for (const entry of entries) {
|
|
1266
|
-
const bucket = groups.get(entry.group);
|
|
1267
|
-
if (bucket) {
|
|
1268
|
-
bucket.push(entry);
|
|
1269
|
-
} else {
|
|
1270
|
-
groups.set(entry.group, [entry]);
|
|
1271
|
-
}
|
|
1272
|
-
}
|
|
1273
|
-
return [...groups.entries()];
|
|
1274
|
-
}
|
|
1275
|
-
function pickEntry(entry) {
|
|
1276
|
-
const opts = _opts;
|
|
1277
|
-
closeFormulaPalette();
|
|
1278
|
-
opts?.onPick(entry);
|
|
1279
|
-
}
|
|
1280
|
-
function onInput(e) {
|
|
1281
|
-
_query = e.target.value;
|
|
1282
|
-
_selectedIndex = 0;
|
|
1283
|
-
renderOverlay();
|
|
1284
|
-
}
|
|
1285
|
-
function onKeydown(e) {
|
|
1286
|
-
const entries = filteredEntries();
|
|
1287
|
-
switch (e.key) {
|
|
1288
|
-
case "ArrowDown": {
|
|
1289
|
-
e.preventDefault();
|
|
1290
|
-
_selectedIndex = Math.min(_selectedIndex + 1, entries.length - 1);
|
|
1291
|
-
renderOverlay();
|
|
1292
|
-
break;
|
|
1293
|
-
}
|
|
1294
|
-
case "ArrowUp": {
|
|
1295
|
-
e.preventDefault();
|
|
1296
|
-
_selectedIndex = Math.max(_selectedIndex - 1, 0);
|
|
1297
|
-
renderOverlay();
|
|
1298
|
-
break;
|
|
1299
|
-
}
|
|
1300
|
-
case "Enter": {
|
|
1301
|
-
e.preventDefault();
|
|
1302
|
-
if (entries[_selectedIndex]) {
|
|
1303
|
-
pickEntry(entries[_selectedIndex]);
|
|
1304
|
-
}
|
|
1305
|
-
break;
|
|
1306
|
-
}
|
|
1307
|
-
case "Escape": {
|
|
1308
|
-
e.preventDefault();
|
|
1309
|
-
closeFormulaPalette();
|
|
1310
|
-
break;
|
|
1311
|
-
}
|
|
1312
|
-
default: {
|
|
1313
|
-
break;
|
|
1314
|
-
}
|
|
1315
|
-
}
|
|
1316
|
-
}
|
|
1317
|
-
function panelStyle() {
|
|
1318
|
-
const anchor = _opts?.anchor;
|
|
1319
|
-
if (!anchor) {
|
|
1320
|
-
return "";
|
|
1321
|
-
}
|
|
1322
|
-
const rect = rectOf(anchor);
|
|
1323
|
-
if (!rect || rect.top === 0 && rect.left === 0 && rect.width === 0) {
|
|
1324
|
-
return "";
|
|
1325
|
-
}
|
|
1326
|
-
const left = Math.max(8, Math.min(rect.left, (globalThis.innerWidth || 1200) - 568));
|
|
1327
|
-
return `position:fixed;left:${left}px;top:${rect.bottom + 4}px;margin:0`;
|
|
1328
|
-
}
|
|
1329
|
-
function renderOverlay() {
|
|
1330
|
-
const container = getContainer();
|
|
1331
|
-
if (!_open) {
|
|
1332
|
-
render(nothing, container);
|
|
1333
|
-
return;
|
|
1334
|
-
}
|
|
1335
|
-
const entries = filteredEntries();
|
|
1336
|
-
const groups = groupedEntries(entries);
|
|
1337
|
-
const hasQuery = _query.trim().length > 0;
|
|
1338
|
-
const tpl = html`
|
|
1339
|
-
<div class="quick-search-overlay formula-palette-overlay" @click=${closeFormulaPalette}>
|
|
1340
|
-
<div
|
|
1341
|
-
class="quick-search-panel formula-palette"
|
|
1342
|
-
style=${panelStyle()}
|
|
1343
|
-
@click=${(e) => e.stopPropagation()}
|
|
1344
|
-
>
|
|
1345
|
-
<input
|
|
1346
|
-
class="quick-search-input formula-palette-input"
|
|
1347
|
-
type="text"
|
|
1348
|
-
placeholder="Search formulas, operators, globals…"
|
|
1349
|
-
.value=${live(_query)}
|
|
1350
|
-
@input=${onInput}
|
|
1351
|
-
@keydown=${onKeydown}
|
|
1352
|
-
${ref((el) => {
|
|
1353
|
-
if (el) {
|
|
1354
|
-
requestAnimationFrame(() => el.focus());
|
|
1355
|
-
}
|
|
1356
|
-
})}
|
|
1357
|
-
/>
|
|
1358
|
-
<div class="quick-search-results">
|
|
1359
|
-
${entries.length === 0 ? html`<div class="quick-search-empty">
|
|
1360
|
-
${hasQuery ? "No results" : "No entries available"}
|
|
1361
|
-
</div>` : nothing}
|
|
1362
|
-
${groups.map(([group, groupEntries]) => html`
|
|
1363
|
-
<div class="quick-search-section-label">${group}</div>
|
|
1364
|
-
${groupEntries.map((entry) => {
|
|
1365
|
-
const index = entries.indexOf(entry);
|
|
1366
|
-
return html`
|
|
1367
|
-
<div
|
|
1368
|
-
class=${classMap({
|
|
1369
|
-
"quick-search-item": true,
|
|
1370
|
-
selected: index === _selectedIndex
|
|
1371
|
-
})}
|
|
1372
|
-
@click=${() => pickEntry(entry)}
|
|
1373
|
-
@mouseenter=${() => {
|
|
1374
|
-
_selectedIndex = index;
|
|
1375
|
-
renderOverlay();
|
|
1376
|
-
}}
|
|
1377
|
-
>
|
|
1378
|
-
<span
|
|
1379
|
-
class="quick-search-name"
|
|
1380
|
-
style="font-family:var(--spectrum-code-font-family, monospace)"
|
|
1381
|
-
>${entry.label}</span
|
|
1382
|
-
>
|
|
1383
|
-
<span class="quick-search-path" title=${entry.description}
|
|
1384
|
-
>${entry.description}</span
|
|
1385
|
-
>
|
|
1386
|
-
<span class="quick-search-badge">${entry.kind}</span>
|
|
1387
|
-
</div>
|
|
1388
|
-
`;
|
|
1389
|
-
})}
|
|
1390
|
-
`)}
|
|
1391
|
-
</div>
|
|
1392
|
-
</div>
|
|
1393
|
-
</div>
|
|
1394
|
-
`;
|
|
1395
|
-
render(tpl, container);
|
|
1396
|
-
}
|
|
9
|
+
isRef2,
|
|
10
|
+
isFunctionDef,
|
|
11
|
+
isTemplateString
|
|
12
|
+
} from "./studio-7r42c6ca.js";
|
|
13
|
+
import {
|
|
14
|
+
reactive
|
|
15
|
+
} from "./studio-p1dw8wfw.js";
|
|
16
|
+
import {
|
|
17
|
+
registerSurface,
|
|
18
|
+
mountSurface
|
|
19
|
+
} from "./studio-erdatzvp.js";
|
|
20
|
+
import {
|
|
21
|
+
openMenu2
|
|
22
|
+
} from "./studio-3gxv60tn.js";
|
|
23
|
+
import {
|
|
24
|
+
cloneValue2
|
|
25
|
+
} from "./studio-727xh0e3.js";
|
|
1397
26
|
|
|
1398
27
|
// ../schema/defs/tag-expression.schema.ts
|
|
1399
28
|
var tagExpressionSchema = {
|
|
@@ -1530,10 +159,28 @@ var refObjectSchema = {
|
|
|
1530
159
|
// ../schema/defs/style-object.schema.ts
|
|
1531
160
|
var styleObjectSchema = {
|
|
1532
161
|
additionalProperties: {
|
|
1533
|
-
|
|
162
|
+
anyOf: [
|
|
163
|
+
{ type: "string" },
|
|
164
|
+
{ type: "number" },
|
|
165
|
+
{ $ref: "#/$defs/RefObject" },
|
|
166
|
+
{ $ref: "#/$defs/StyleObject" }
|
|
167
|
+
]
|
|
168
|
+
},
|
|
169
|
+
description: "CSS style definition. camelCase property names follow CSSOM convention. " + "Keys starting with :, ., &, or [ are treated as nested CSS selectors. " + "Keys matching $media breakpoint names (or other @-prefixed at-rules) group nested rules. " + "Nesting is recursive: selector and at-rule groups may nest to arbitrary depth " + "(e.g. breakpoint → selector → pseudo-class), mirroring the compiler's recursive emission. " + "An object carrying $ref is a reactive VALUE, and the runtime reads it as one. It also " + "matches the nested-block branch vacuously, since a block admits a string under any key, " + "which is why these branches are anyOf rather than oneOf. " + "A DECLARATION at-rule (@font-face, @property, @position-try, @counter-style) may hold an " + "ARRAY of blocks, which is that key written more than once, in order: an object's keys are " + "unique, so @font-face — the at-rule whose identity is not in its key — could not otherwise " + "state a family's second weight at all.",
|
|
170
|
+
patternProperties: {
|
|
171
|
+
"^@(font-face|property|position-try|counter-style)": {
|
|
172
|
+
anyOf: [
|
|
173
|
+
{ $ref: "#/$defs/StyleObject" },
|
|
174
|
+
{ items: { $ref: "#/$defs/StyleObject" }, minItems: 1, type: "array" }
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
properties: {
|
|
179
|
+
$description: {
|
|
180
|
+
description: "Why this rule exists. Emitted as a comment by static builds, ignored at runtime.",
|
|
181
|
+
type: "string"
|
|
182
|
+
}
|
|
1534
183
|
},
|
|
1535
|
-
description: "CSS style definition. camelCase property names follow CSSOM convention. " + "Keys starting with :, ., &, or [ are treated as nested CSS selectors. " + "Keys matching $media breakpoint names (or other @-prefixed at-rules) group nested rules. " + "Nesting is recursive: selector and at-rule groups may nest to arbitrary depth " + "(e.g. breakpoint → selector → pseudo-class), mirroring the compiler's recursive emission.",
|
|
1536
|
-
properties: {},
|
|
1537
184
|
type: "object"
|
|
1538
185
|
};
|
|
1539
186
|
// ../schema/defs/children-value.schema.ts
|
|
@@ -1546,6 +193,15 @@ var arrayNamespaceSchema = {
|
|
|
1546
193
|
items: {
|
|
1547
194
|
oneOf: [{ $ref: "#/$defs/RefObject" }, { type: "array" }]
|
|
1548
195
|
},
|
|
196
|
+
key: {
|
|
197
|
+
additionalProperties: false,
|
|
198
|
+
description: "The identity of each rendered row: a $map/item pointer read per item. A row whose key " + "survives a change keeps its DOM node and its effects across reorders, insertions and " + "removals; without a key, rows are keyed by index. $map/index is not a key — an index " + "names a position, not a row.",
|
|
199
|
+
properties: {
|
|
200
|
+
$ref: { pattern: "^\\$map/item(/.+)?$", type: "string" }
|
|
201
|
+
},
|
|
202
|
+
required: ["$ref"],
|
|
203
|
+
type: "object"
|
|
204
|
+
},
|
|
1549
205
|
map: { $ref: "#/$defs/ElementDef" },
|
|
1550
206
|
sort: { $ref: "#/$defs/RefObject" }
|
|
1551
207
|
},
|
|
@@ -1613,8 +269,10 @@ var elementPropertyValueSchema = {
|
|
|
1613
269
|
};
|
|
1614
270
|
var switchDefSchema = {
|
|
1615
271
|
additionalProperties: false,
|
|
1616
|
-
description: "Reactive $ref that drives which case to render.",
|
|
1617
|
-
properties: {
|
|
272
|
+
description: "Reactive $ref that drives which case to render: a state entry, or inside a mapped array's template the row's $map/item or $map/index (spec §14.1).",
|
|
273
|
+
properties: {
|
|
274
|
+
$ref: { oneOf: [{ $ref: "#/$defs/StateRef" }, { $ref: "#/$defs/MapRef" }], type: "string" }
|
|
275
|
+
},
|
|
1618
276
|
required: ["$ref"],
|
|
1619
277
|
type: "object"
|
|
1620
278
|
};
|
|
@@ -2862,7 +1520,7 @@ var VALUE_SOURCE_HINTS = {
|
|
|
2862
1520
|
template: "Text with ${…} placeholders that fill in from signals."
|
|
2863
1521
|
};
|
|
2864
1522
|
function slotMode(value) {
|
|
2865
|
-
if (
|
|
1523
|
+
if (isRef2(value)) {
|
|
2866
1524
|
return "ref";
|
|
2867
1525
|
}
|
|
2868
1526
|
if (isFunctionDef(value)) {
|
|
@@ -2977,6 +1635,7 @@ function deriveSlotCaps(schema) {
|
|
|
2977
1635
|
var ELEMENT_TAG_SCHEMA = {
|
|
2978
1636
|
anyOf: [tagNameSchema, ...elementTagNameSchema.anyOf.slice(1)]
|
|
2979
1637
|
};
|
|
1638
|
+
var TAG_EXPRESSION_OPERATORS = tagExpressionSchema.anyOf.map((branch) => branch.properties.operator.const);
|
|
2980
1639
|
var SLOT_POSITION_SCHEMAS = {
|
|
2981
1640
|
attribute: attributesObjectSchema.additionalProperties,
|
|
2982
1641
|
componentProp: propsObjectSchema.additionalProperties,
|
|
@@ -3008,666 +1667,23 @@ function configFieldSchema(field) {
|
|
|
3008
1667
|
return { anyOf: [declared, { $ref: "#/$defs/RefObject" }] };
|
|
3009
1668
|
}
|
|
3010
1669
|
|
|
3011
|
-
// src/services/context-resolver.ts
|
|
3012
|
-
var PARAM_SEGMENT = /^\{@(\w+)\}$/;
|
|
3013
|
-
function resolveContextPointer(pointer, opts) {
|
|
3014
|
-
if (!pointer.startsWith("#/$context/")) {
|
|
3015
|
-
return;
|
|
3016
|
-
}
|
|
3017
|
-
const segments = [];
|
|
3018
|
-
for (const raw of pointer.slice("#/$context/".length).split("/")) {
|
|
3019
|
-
const match = raw.match(PARAM_SEGMENT);
|
|
3020
|
-
if (match) {
|
|
3021
|
-
const value = opts.scope?.[match[1]];
|
|
3022
|
-
if (typeof value !== "string" && typeof value !== "number") {
|
|
3023
|
-
return;
|
|
3024
|
-
}
|
|
3025
|
-
const substituted = String(value);
|
|
3026
|
-
if (!substituted) {
|
|
3027
|
-
return;
|
|
3028
|
-
}
|
|
3029
|
-
segments.push(substituted);
|
|
3030
|
-
} else {
|
|
3031
|
-
segments.push(raw);
|
|
3032
|
-
}
|
|
3033
|
-
}
|
|
3034
|
-
if (segments[0] === "$formats") {
|
|
3035
|
-
if (segments.length !== 1) {
|
|
3036
|
-
return;
|
|
3037
|
-
}
|
|
3038
|
-
return (opts.formats ?? []).map((format) => format.name);
|
|
3039
|
-
}
|
|
3040
|
-
let node = opts.projectConfig;
|
|
3041
|
-
for (const segment of segments) {
|
|
3042
|
-
if (node === null || typeof node !== "object") {
|
|
3043
|
-
return;
|
|
3044
|
-
}
|
|
3045
|
-
node = node[segment];
|
|
3046
|
-
}
|
|
3047
|
-
return node;
|
|
3048
|
-
}
|
|
3049
|
-
|
|
3050
|
-
// src/ui/expression-editor.ts
|
|
3051
|
-
var UNARY_OPS = new Set(["!", "-"]);
|
|
3052
|
-
var BINARY_OPS = new Set([
|
|
3053
|
-
"+",
|
|
3054
|
-
"-",
|
|
3055
|
-
"*",
|
|
3056
|
-
"/",
|
|
3057
|
-
"%",
|
|
3058
|
-
"===",
|
|
3059
|
-
"!==",
|
|
3060
|
-
"<",
|
|
3061
|
-
"<=",
|
|
3062
|
-
">",
|
|
3063
|
-
">=",
|
|
3064
|
-
"&&",
|
|
3065
|
-
"||",
|
|
3066
|
-
"??"
|
|
3067
|
-
]);
|
|
3068
|
-
var ASSIGN_OPS = new Set(["=", "+=", "-=", "*=", "/="]);
|
|
3069
|
-
function isActionExpression(node) {
|
|
3070
|
-
const op = node?.operator;
|
|
3071
|
-
return typeof op === "string" && ASSIGN_OPS.has(op);
|
|
3072
|
-
}
|
|
3073
|
-
var NO_ARG_OPS = new Set(["pop", "shift"]);
|
|
3074
|
-
var ONE_ARG_OPS = new Set(["push", "unshift"]);
|
|
3075
|
-
var ZERO_ARG_METHOD_OPS = new Set([
|
|
3076
|
-
"flat",
|
|
3077
|
-
"normalize",
|
|
3078
|
-
"toLocaleLowerCase",
|
|
3079
|
-
"toLocaleString",
|
|
3080
|
-
"toLocaleUpperCase",
|
|
3081
|
-
"toLowerCase",
|
|
3082
|
-
"toReversed",
|
|
3083
|
-
"toSorted",
|
|
3084
|
-
"toUpperCase",
|
|
3085
|
-
"trim",
|
|
3086
|
-
"trimEnd",
|
|
3087
|
-
"trimStart"
|
|
3088
|
-
]);
|
|
3089
|
-
var OPERATOR_GROUPS = [
|
|
3090
|
-
{ label: "Assignment", ops: ["=", "+=", "-=", "*=", "/="] },
|
|
3091
|
-
{ label: "Unary", ops: ["!", "-"] },
|
|
3092
|
-
{ label: "Arithmetic", ops: ["+", "-", "*", "/", "%"] },
|
|
3093
|
-
{ label: "Comparison", ops: ["===", "!==", "<", "<=", ">", ">="] },
|
|
3094
|
-
{ label: "Logical", ops: ["&&", "||", "??"] },
|
|
3095
|
-
{ label: "Conditional", ops: ["?:", "switch"] },
|
|
3096
|
-
{
|
|
3097
|
-
label: "Array methods",
|
|
3098
|
-
ops: ["push", "pop", "shift", "unshift", "splice"]
|
|
3099
|
-
},
|
|
3100
|
-
{
|
|
3101
|
-
label: "Pure methods (String)",
|
|
3102
|
-
ops: [
|
|
3103
|
-
"toUpperCase",
|
|
3104
|
-
"toLowerCase",
|
|
3105
|
-
"trim",
|
|
3106
|
-
"trimStart",
|
|
3107
|
-
"trimEnd",
|
|
3108
|
-
"split",
|
|
3109
|
-
"startsWith",
|
|
3110
|
-
"endsWith",
|
|
3111
|
-
"padStart",
|
|
3112
|
-
"padEnd",
|
|
3113
|
-
"replaceAll",
|
|
3114
|
-
"repeat",
|
|
3115
|
-
"charAt",
|
|
3116
|
-
"normalize"
|
|
3117
|
-
]
|
|
3118
|
-
},
|
|
3119
|
-
{
|
|
3120
|
-
label: "Pure methods (Array)",
|
|
3121
|
-
ops: [
|
|
3122
|
-
"includes",
|
|
3123
|
-
"indexOf",
|
|
3124
|
-
"lastIndexOf",
|
|
3125
|
-
"join",
|
|
3126
|
-
"slice",
|
|
3127
|
-
"concat",
|
|
3128
|
-
"at",
|
|
3129
|
-
"flat",
|
|
3130
|
-
"toSorted",
|
|
3131
|
-
"toReversed",
|
|
3132
|
-
"toSpliced",
|
|
3133
|
-
"with"
|
|
3134
|
-
]
|
|
3135
|
-
},
|
|
3136
|
-
{ label: "Pure methods (Number)", ops: ["toFixed", "toPrecision", "toLocaleString"] },
|
|
3137
|
-
{ label: "Aggregate", ops: ["reduce", "map", "filter"] },
|
|
3138
|
-
{ label: "Function", ops: ["call"] }
|
|
3139
|
-
];
|
|
3140
|
-
var INFO_DEFAULTS = {
|
|
3141
|
-
callArgs: false,
|
|
3142
|
-
needsInitial: false,
|
|
3143
|
-
needsValue: false,
|
|
3144
|
-
spliceArray: false,
|
|
3145
|
-
switchCases: false,
|
|
3146
|
-
targetMustBeRef: false,
|
|
3147
|
-
valueIsNode: false
|
|
3148
|
-
};
|
|
3149
|
-
function operatorInfo(op) {
|
|
3150
|
-
if (UNARY_OPS.has(op)) {
|
|
3151
|
-
return { ...INFO_DEFAULTS };
|
|
3152
|
-
}
|
|
3153
|
-
if (op === "?:") {
|
|
3154
|
-
return { ...INFO_DEFAULTS, needsInitial: true, needsValue: true };
|
|
3155
|
-
}
|
|
3156
|
-
if (op === "switch") {
|
|
3157
|
-
return { ...INFO_DEFAULTS, switchCases: true };
|
|
3158
|
-
}
|
|
3159
|
-
if (op === "call") {
|
|
3160
|
-
return { ...INFO_DEFAULTS, callArgs: true, targetMustBeRef: true };
|
|
3161
|
-
}
|
|
3162
|
-
if (PURE_METHOD_OPS.has(op)) {
|
|
3163
|
-
return { ...INFO_DEFAULTS, needsValue: !ZERO_ARG_METHOD_OPS.has(op) };
|
|
3164
|
-
}
|
|
3165
|
-
if (BINARY_OPS.has(op)) {
|
|
3166
|
-
return { ...INFO_DEFAULTS, needsValue: true };
|
|
3167
|
-
}
|
|
3168
|
-
if (ASSIGN_OPS.has(op)) {
|
|
3169
|
-
return { ...INFO_DEFAULTS, needsValue: true, targetMustBeRef: true };
|
|
3170
|
-
}
|
|
3171
|
-
if (NO_ARG_OPS.has(op)) {
|
|
3172
|
-
return { ...INFO_DEFAULTS, targetMustBeRef: true };
|
|
3173
|
-
}
|
|
3174
|
-
if (ONE_ARG_OPS.has(op)) {
|
|
3175
|
-
return { ...INFO_DEFAULTS, needsValue: true, targetMustBeRef: true };
|
|
3176
|
-
}
|
|
3177
|
-
if (op === "splice") {
|
|
3178
|
-
return { ...INFO_DEFAULTS, needsValue: true, spliceArray: true, targetMustBeRef: true };
|
|
3179
|
-
}
|
|
3180
|
-
if (op === "reduce") {
|
|
3181
|
-
return {
|
|
3182
|
-
...INFO_DEFAULTS,
|
|
3183
|
-
needsInitial: true,
|
|
3184
|
-
needsValue: true,
|
|
3185
|
-
targetMustBeRef: true,
|
|
3186
|
-
valueIsNode: true
|
|
3187
|
-
};
|
|
3188
|
-
}
|
|
3189
|
-
if (op === "map" || op === "filter") {
|
|
3190
|
-
return { ...INFO_DEFAULTS, needsValue: true, targetMustBeRef: true, valueIsNode: true };
|
|
3191
|
-
}
|
|
3192
|
-
return { ...INFO_DEFAULTS };
|
|
3193
|
-
}
|
|
3194
|
-
function operandMode(operand) {
|
|
3195
|
-
if (operand && typeof operand === "object") {
|
|
3196
|
-
if ("$ref" in operand) {
|
|
3197
|
-
return "ref";
|
|
3198
|
-
}
|
|
3199
|
-
if ("operator" in operand) {
|
|
3200
|
-
return "expression";
|
|
3201
|
-
}
|
|
3202
|
-
}
|
|
3203
|
-
return "literal";
|
|
3204
|
-
}
|
|
3205
|
-
function calleeParamLabels(target, state) {
|
|
3206
|
-
const ref2 = isRef(target) ? target.$ref : "";
|
|
3207
|
-
const entry = ref2 ? calleeEntry(ref2, state) : undefined;
|
|
3208
|
-
return entry?.kind === "formula" ? entry.parameters.map((p) => p.name) : [];
|
|
3209
|
-
}
|
|
3210
|
-
function defaultForMode(mode) {
|
|
3211
|
-
if (mode === "ref") {
|
|
3212
|
-
return { $ref: "" };
|
|
3213
|
-
}
|
|
3214
|
-
if (mode === "expression") {
|
|
3215
|
-
return { operator: "!", target: null };
|
|
3216
|
-
}
|
|
3217
|
-
return null;
|
|
3218
|
-
}
|
|
3219
|
-
function literalType(val) {
|
|
3220
|
-
if (val === null || val === undefined) {
|
|
3221
|
-
return "null";
|
|
3222
|
-
}
|
|
3223
|
-
if (typeof val === "boolean") {
|
|
3224
|
-
return "boolean";
|
|
3225
|
-
}
|
|
3226
|
-
if (typeof val === "number") {
|
|
3227
|
-
return "number";
|
|
3228
|
-
}
|
|
3229
|
-
return "string";
|
|
3230
|
-
}
|
|
3231
|
-
function defaultForLiteralType(type) {
|
|
3232
|
-
if (type === "number") {
|
|
3233
|
-
return 0;
|
|
3234
|
-
}
|
|
3235
|
-
if (type === "boolean") {
|
|
3236
|
-
return false;
|
|
3237
|
-
}
|
|
3238
|
-
if (type === "null") {
|
|
3239
|
-
return null;
|
|
3240
|
-
}
|
|
3241
|
-
return "";
|
|
3242
|
-
}
|
|
3243
|
-
function expressionHint(node) {
|
|
3244
|
-
if (!isJsonObject(node) || typeof node.operator !== "string") {
|
|
3245
|
-
return "$expression";
|
|
3246
|
-
}
|
|
3247
|
-
const expr = node;
|
|
3248
|
-
const op = expr.operator;
|
|
3249
|
-
const { target } = expr;
|
|
3250
|
-
const targetLabel = isRef(target) ? target.$ref.replace("#/state/", "") : isJsonObject(target) && typeof target.operator === "string" ? `(${target.operator}…)` : String(target ?? "?");
|
|
3251
|
-
if (ASSIGN_OPS.has(op) || ONE_ARG_OPS.has(op)) {
|
|
3252
|
-
return `${op} ${targetLabel}`;
|
|
3253
|
-
}
|
|
3254
|
-
if (NO_ARG_OPS.has(op)) {
|
|
3255
|
-
return `${op}(${targetLabel})`;
|
|
3256
|
-
}
|
|
3257
|
-
if (op === "splice") {
|
|
3258
|
-
return `splice(${targetLabel})`;
|
|
3259
|
-
}
|
|
3260
|
-
if (op === "call") {
|
|
3261
|
-
return `${targetLabel.replace("window#/", "").replaceAll("/", ".")}(…)`;
|
|
3262
|
-
}
|
|
3263
|
-
if (op === "reduce" || op === "map" || op === "filter" || PURE_METHOD_OPS.has(op)) {
|
|
3264
|
-
return `${op}(${targetLabel})`;
|
|
3265
|
-
}
|
|
3266
|
-
if (op === "?:") {
|
|
3267
|
-
return `${targetLabel} ? … : …`;
|
|
3268
|
-
}
|
|
3269
|
-
if (op === "switch") {
|
|
3270
|
-
return `switch(${targetLabel})`;
|
|
3271
|
-
}
|
|
3272
|
-
if (UNARY_OPS.has(op)) {
|
|
3273
|
-
return `${op}${targetLabel}`;
|
|
3274
|
-
}
|
|
3275
|
-
return `${targetLabel} ${op} …`;
|
|
3276
|
-
}
|
|
3277
|
-
function renderValueBadge(preview, pathKey) {
|
|
3278
|
-
const text = preview?.values.get(pathKey);
|
|
3279
|
-
if (text === undefined) {
|
|
3280
|
-
return nothing;
|
|
3281
|
-
}
|
|
3282
|
-
return html`<span class="expr-live-badge" title=${text}>${text}</span>`;
|
|
3283
|
-
}
|
|
3284
|
-
function renderRefPicker(refVal, onRefChange, opts) {
|
|
3285
|
-
const stateRefs = (opts.stateDefs || []).map((k) => `#/state/${k}`);
|
|
3286
|
-
const eventRefs = opts.allowEventRef ? ["event#/detail", "event#/target/value"] : [];
|
|
3287
|
-
const allRefs = [...stateRefs, ...eventRefs];
|
|
3288
|
-
const isCustom = refVal && !allRefs.includes(refVal);
|
|
3289
|
-
if (allRefs.length === 0 && !refVal) {
|
|
3290
|
-
return renderEmptyState({
|
|
3291
|
-
compact: true,
|
|
3292
|
-
detail: "Add one in the State panel and it shows up here.",
|
|
3293
|
-
message: "A binding points at a value this page holds."
|
|
3294
|
-
});
|
|
3295
|
-
}
|
|
3296
|
-
return html`
|
|
3297
|
-
<sp-picker
|
|
3298
|
-
size="s"
|
|
3299
|
-
class="expr-ref"
|
|
3300
|
-
placeholder="Select…"
|
|
3301
|
-
.value=${live(isCustom ? "__custom__" : refVal || "")}
|
|
3302
|
-
@change=${(e) => {
|
|
3303
|
-
const val = e.target.value;
|
|
3304
|
-
if (val === "__custom__") {
|
|
3305
|
-
return;
|
|
3306
|
-
}
|
|
3307
|
-
onRefChange(val);
|
|
3308
|
-
}}
|
|
3309
|
-
>
|
|
3310
|
-
${stateRefs.map((r) => html`<sp-menu-item value=${r}>${r.replace("#/state/", "")}</sp-menu-item>`)}
|
|
3311
|
-
${eventRefs.length > 0 ? html`
|
|
3312
|
-
<sp-menu-divider></sp-menu-divider>
|
|
3313
|
-
${eventRefs.map((r) => html`<sp-menu-item value=${r}>${r}</sp-menu-item>`)}
|
|
3314
|
-
` : nothing}
|
|
3315
|
-
</sp-picker>
|
|
3316
|
-
`;
|
|
3317
|
-
}
|
|
3318
|
-
function renderLiteralEditor(operand, onChange) {
|
|
3319
|
-
const type = literalType(operand);
|
|
3320
|
-
return html`
|
|
3321
|
-
<div class="expr-literal">
|
|
3322
|
-
<sp-picker
|
|
3323
|
-
size="s"
|
|
3324
|
-
class="expr-literal-type"
|
|
3325
|
-
.value=${live(type)}
|
|
3326
|
-
@change=${(e) => {
|
|
3327
|
-
const newType = e.target.value;
|
|
3328
|
-
onChange(defaultForLiteralType(newType));
|
|
3329
|
-
}}
|
|
3330
|
-
>
|
|
3331
|
-
<sp-menu-item value="string">str</sp-menu-item>
|
|
3332
|
-
<sp-menu-item value="number">num</sp-menu-item>
|
|
3333
|
-
<sp-menu-item value="boolean">bool</sp-menu-item>
|
|
3334
|
-
<sp-menu-item value="null">null</sp-menu-item>
|
|
3335
|
-
</sp-picker>
|
|
3336
|
-
${type === "string" ? html`<sp-textfield
|
|
3337
|
-
size="s"
|
|
3338
|
-
class="expr-literal-value"
|
|
3339
|
-
.value=${live(String(operand ?? ""))}
|
|
3340
|
-
@input=${(e) => onChange(e.target.value)}
|
|
3341
|
-
></sp-textfield>` : type === "number" ? html`<sp-number-field
|
|
3342
|
-
size="s"
|
|
3343
|
-
class="expr-literal-value"
|
|
3344
|
-
.value=${live(Number(operand ?? 0))}
|
|
3345
|
-
@change=${(e) => onChange(Number(e.target.value))}
|
|
3346
|
-
></sp-number-field>` : type === "boolean" ? html`<sp-checkbox
|
|
3347
|
-
size="s"
|
|
3348
|
-
class="expr-literal-bool"
|
|
3349
|
-
?checked=${Boolean(operand)}
|
|
3350
|
-
@change=${(e) => onChange(e.target.checked)}
|
|
3351
|
-
>true</sp-checkbox
|
|
3352
|
-
>` : html`<span class="expr-literal-null">null</span>`}
|
|
3353
|
-
</div>
|
|
3354
|
-
`;
|
|
3355
|
-
}
|
|
3356
|
-
function renderOperandEditor(operand, onChange, opts) {
|
|
3357
|
-
if (opts.mustBeRef) {
|
|
3358
|
-
const refVal = operand?.$ref ?? "";
|
|
3359
|
-
return html`
|
|
3360
|
-
<div class="expr-operand">${renderRefPicker(refVal, (r) => onChange({ $ref: r }), opts)}</div>
|
|
3361
|
-
`;
|
|
3362
|
-
}
|
|
3363
|
-
const mode = operandMode(operand);
|
|
3364
|
-
return html`
|
|
3365
|
-
<div class="expr-operand">
|
|
3366
|
-
<sp-picker
|
|
3367
|
-
size="s"
|
|
3368
|
-
class="expr-operand-mode"
|
|
3369
|
-
label="Value source"
|
|
3370
|
-
.value=${live(mode)}
|
|
3371
|
-
@change=${(e) => {
|
|
3372
|
-
const newMode = e.target.value;
|
|
3373
|
-
onChange(defaultForMode(newMode));
|
|
3374
|
-
}}
|
|
3375
|
-
>
|
|
3376
|
-
<sp-menu-item value="literal">${VALUE_SOURCE_LABELS.literal}</sp-menu-item>
|
|
3377
|
-
<sp-menu-item value="ref">${VALUE_SOURCE_LABELS.ref}</sp-menu-item>
|
|
3378
|
-
<sp-menu-item value="expression">${VALUE_SOURCE_LABELS.expression}</sp-menu-item>
|
|
3379
|
-
</sp-picker>
|
|
3380
|
-
${mode === "literal" ? renderLiteralEditor(operand, onChange) : mode === "ref" ? renderRefPicker(operand?.$ref ?? "", (r) => onChange({ $ref: r }), opts) : renderExpressionEditor(operand, onChange, {
|
|
3381
|
-
...opts,
|
|
3382
|
-
depth: opts.depth + 1
|
|
3383
|
-
})}
|
|
3384
|
-
</div>
|
|
3385
|
-
`;
|
|
3386
|
-
}
|
|
3387
|
-
function renderSpliceArgsEditor(args, onChange, opts, naming = {}) {
|
|
3388
|
-
const safeArgs = Array.isArray(args) ? args : [];
|
|
3389
|
-
const labels = naming.labels ?? ["start", "del", "item"];
|
|
3390
|
-
const fallbackLabel = naming.fallbackLabel ?? "item";
|
|
3391
|
-
return html`
|
|
3392
|
-
<div class="array-object-field">
|
|
3393
|
-
${safeArgs.map((arg, idx) => html`
|
|
3394
|
-
<div class="array-object-row">
|
|
3395
|
-
<span class="array-object-label">${labels[idx] ?? fallbackLabel}</span>
|
|
3396
|
-
${renderOperandEditor(arg, (newArg) => {
|
|
3397
|
-
const updated = [...safeArgs];
|
|
3398
|
-
updated[idx] = newArg;
|
|
3399
|
-
onChange(updated);
|
|
3400
|
-
}, { ...opts, mustBeRef: false })}
|
|
3401
|
-
<sp-action-button
|
|
3402
|
-
quiet
|
|
3403
|
-
size="xs"
|
|
3404
|
-
@click=${() => {
|
|
3405
|
-
const updated = safeArgs.filter((_, i) => i !== idx);
|
|
3406
|
-
onChange(updated.length > 0 ? updated : [null]);
|
|
3407
|
-
}}
|
|
3408
|
-
>
|
|
3409
|
-
<sp-icon-delete slot="icon"></sp-icon-delete>
|
|
3410
|
-
</sp-action-button>
|
|
3411
|
-
</div>
|
|
3412
|
-
`)}
|
|
3413
|
-
<sp-action-button quiet size="s" @click=${() => onChange([...safeArgs, null])}>
|
|
3414
|
-
+ Add arg
|
|
3415
|
-
</sp-action-button>
|
|
3416
|
-
</div>
|
|
3417
|
-
`;
|
|
3418
|
-
}
|
|
3419
|
-
var _operatorMenuCache = OPERATOR_GROUPS.map((group, i) => html`
|
|
3420
|
-
${i > 0 ? html`<sp-menu-divider></sp-menu-divider>` : nothing}
|
|
3421
|
-
<sp-menu-group>
|
|
3422
|
-
<span slot="header">${group.label}</span>
|
|
3423
|
-
${group.ops.map((op) => html`<sp-menu-item value=${op}>${op}</sp-menu-item>`)}
|
|
3424
|
-
</sp-menu-group>
|
|
3425
|
-
`);
|
|
3426
|
-
function renderExpressionEditor(node, onChange, opts) {
|
|
3427
|
-
const depth = opts.depth ?? 0;
|
|
3428
|
-
const safeNode = node && typeof node === "object" ? node : { operator: "=", target: null };
|
|
3429
|
-
const op = safeNode.operator || "=";
|
|
3430
|
-
const info = operatorInfo(op);
|
|
3431
|
-
const preview = opts.preview ?? null;
|
|
3432
|
-
const path = opts.path ?? [];
|
|
3433
|
-
const pathKey = path.join("/");
|
|
3434
|
-
const sub = (...segs) => [...path, ...segs].join("/");
|
|
3435
|
-
const withBadge = (widget, key) => html`<div class="expr-widget">${widget}${renderValueBadge(preview, key)}</div>`;
|
|
3436
|
-
const rootBadge = depth === 0 && preview && !preview.mutating ? renderValueBadge(preview, pathKey) : nothing;
|
|
3437
|
-
return html`
|
|
3438
|
-
<div class=${depth > 0 ? "expression-editor expression-editor--nested" : "expression-editor"}>
|
|
3439
|
-
${depth === 0 ? renderFormulaChips(safeNode, opts.onChipSelect ?? (() => {}), { path, preview }) : nothing}
|
|
3440
|
-
${depth === 0 && preview?.error ? html`<div class="expr-error">${preview.error}</div>` : nothing}
|
|
3441
|
-
${renderFieldRow({
|
|
3442
|
-
hasValue: false,
|
|
3443
|
-
label: "Operator",
|
|
3444
|
-
prop: "operator",
|
|
3445
|
-
widget: html`
|
|
3446
|
-
<div class="expr-widget">
|
|
3447
|
-
<sp-picker
|
|
3448
|
-
size="s"
|
|
3449
|
-
class="expr-operator"
|
|
3450
|
-
.value=${live(op)}
|
|
3451
|
-
@change=${(e) => {
|
|
3452
|
-
const newOp = e.target.value;
|
|
3453
|
-
const newInfo = operatorInfo(newOp);
|
|
3454
|
-
const updated = {
|
|
3455
|
-
operator: newOp,
|
|
3456
|
-
target: safeNode.target
|
|
3457
|
-
};
|
|
3458
|
-
if (newInfo.targetMustBeRef && operandMode(safeNode.target) !== "ref") {
|
|
3459
|
-
updated.target = { $ref: "" };
|
|
3460
|
-
}
|
|
3461
|
-
if (newInfo.needsValue) {
|
|
3462
|
-
if (newInfo.valueIsNode) {
|
|
3463
|
-
const val = safeNode.value;
|
|
3464
|
-
updated.value = val?.operator ? safeNode.value : { operator: "!", target: null };
|
|
3465
|
-
} else if (newInfo.spliceArray) {
|
|
3466
|
-
updated.value = Array.isArray(safeNode.value) ? safeNode.value : [null];
|
|
3467
|
-
} else {
|
|
3468
|
-
updated.value = safeNode.value ?? null;
|
|
3469
|
-
}
|
|
3470
|
-
}
|
|
3471
|
-
if (newInfo.needsInitial) {
|
|
3472
|
-
updated.initial = safeNode.initial ?? (newOp === "?:" ? null : 0);
|
|
3473
|
-
}
|
|
3474
|
-
if (newInfo.switchCases) {
|
|
3475
|
-
updated.cases = isJsonObject(safeNode.cases) ? safeNode.cases : {};
|
|
3476
|
-
if ("default" in safeNode) {
|
|
3477
|
-
updated.default = safeNode.default;
|
|
3478
|
-
}
|
|
3479
|
-
}
|
|
3480
|
-
if (newInfo.callArgs) {
|
|
3481
|
-
updated.value = Array.isArray(safeNode.value) ? safeNode.value : [];
|
|
3482
|
-
}
|
|
3483
|
-
onChange(updated);
|
|
3484
|
-
}}
|
|
3485
|
-
>
|
|
3486
|
-
${_operatorMenuCache}
|
|
3487
|
-
</sp-picker>
|
|
3488
|
-
<sp-action-button
|
|
3489
|
-
quiet
|
|
3490
|
-
size="s"
|
|
3491
|
-
class="expr-browse-catalog"
|
|
3492
|
-
title="Browse catalog"
|
|
3493
|
-
@click=${(e) => openFormulaPalette({
|
|
3494
|
-
anchor: e.currentTarget,
|
|
3495
|
-
entries: formulaCatalog(opts.stateEntries),
|
|
3496
|
-
onPick: (entry) => applyCatalogPick(entry, onChange, {
|
|
3497
|
-
onInsertDef: opts.onInsertDef,
|
|
3498
|
-
stateEntries: opts.stateEntries
|
|
3499
|
-
})
|
|
3500
|
-
})}
|
|
3501
|
-
>
|
|
3502
|
-
<sp-icon-brackets slot="icon"></sp-icon-brackets>
|
|
3503
|
-
</sp-action-button>
|
|
3504
|
-
${rootBadge}
|
|
3505
|
-
</div>
|
|
3506
|
-
`
|
|
3507
|
-
})}
|
|
3508
|
-
${renderFieldRow({
|
|
3509
|
-
hasValue: false,
|
|
3510
|
-
label: op === "?:" ? "If" : op === "switch" ? "On" : op === "call" ? "Callee" : "Target",
|
|
3511
|
-
prop: "target",
|
|
3512
|
-
widget: withBadge(renderOperandEditor(safeNode.target, (t) => onChange({ ...safeNode, target: t }), {
|
|
3513
|
-
...opts,
|
|
3514
|
-
depth,
|
|
3515
|
-
mustBeRef: info.targetMustBeRef,
|
|
3516
|
-
path: [...path, "target"]
|
|
3517
|
-
}), sub("target"))
|
|
3518
|
-
})}
|
|
3519
|
-
${info.needsValue && !info.valueIsNode && !info.spliceArray ? renderFieldRow({
|
|
3520
|
-
hasValue: false,
|
|
3521
|
-
label: op === "?:" ? "Then" : "Value",
|
|
3522
|
-
prop: "value",
|
|
3523
|
-
widget: withBadge(renderOperandEditor(safeNode.value, (v) => onChange({ ...safeNode, value: v }), {
|
|
3524
|
-
...opts,
|
|
3525
|
-
depth,
|
|
3526
|
-
mustBeRef: false,
|
|
3527
|
-
path: [...path, "value"]
|
|
3528
|
-
}), sub("value"))
|
|
3529
|
-
}) : nothing}
|
|
3530
|
-
${info.needsValue && info.valueIsNode ? html`
|
|
3531
|
-
<div class="expr-subsection">
|
|
3532
|
-
${renderFieldRow({
|
|
3533
|
-
hasValue: false,
|
|
3534
|
-
label: "Per-item",
|
|
3535
|
-
prop: "value",
|
|
3536
|
-
widget: nothing
|
|
3537
|
-
})}
|
|
3538
|
-
${renderExpressionEditor(safeNode.value?.operator ? safeNode.value : { operator: "!", target: null }, (v) => onChange({ ...safeNode, value: v }), { ...opts, depth: depth + 1, path: [...path, "value"] })}
|
|
3539
|
-
</div>
|
|
3540
|
-
` : nothing}
|
|
3541
|
-
${info.spliceArray ? html`
|
|
3542
|
-
<div class="expr-subsection">
|
|
3543
|
-
${renderFieldRow({
|
|
3544
|
-
hasValue: false,
|
|
3545
|
-
label: "Args",
|
|
3546
|
-
prop: "value",
|
|
3547
|
-
widget: nothing
|
|
3548
|
-
})}
|
|
3549
|
-
${renderSpliceArgsEditor(safeNode.value, (v) => onChange({ ...safeNode, value: v }), {
|
|
3550
|
-
...opts,
|
|
3551
|
-
depth
|
|
3552
|
-
})}
|
|
3553
|
-
</div>
|
|
3554
|
-
` : nothing}
|
|
3555
|
-
${info.callArgs ? html`
|
|
3556
|
-
<div class="expr-subsection">
|
|
3557
|
-
${renderFieldRow({
|
|
3558
|
-
hasValue: false,
|
|
3559
|
-
label: "Args",
|
|
3560
|
-
prop: "value",
|
|
3561
|
-
widget: nothing
|
|
3562
|
-
})}
|
|
3563
|
-
${renderSpliceArgsEditor(safeNode.value, (v) => onChange({ ...safeNode, value: v }), { ...opts, depth }, {
|
|
3564
|
-
fallbackLabel: "arg",
|
|
3565
|
-
labels: calleeParamLabels(safeNode.target, opts.stateEntries)
|
|
3566
|
-
})}
|
|
3567
|
-
</div>
|
|
3568
|
-
` : nothing}
|
|
3569
|
-
${info.switchCases ? renderSwitchCasesEditor(safeNode, onChange, { ...opts, depth, path }, withBadge) : nothing}
|
|
3570
|
-
${info.needsInitial ? renderFieldRow({
|
|
3571
|
-
hasValue: false,
|
|
3572
|
-
label: op === "?:" ? "Else" : "Initial",
|
|
3573
|
-
prop: "initial",
|
|
3574
|
-
widget: withBadge(renderOperandEditor(safeNode.initial, (v) => onChange({ ...safeNode, initial: v }), {
|
|
3575
|
-
...opts,
|
|
3576
|
-
depth,
|
|
3577
|
-
mustBeRef: false,
|
|
3578
|
-
path: [...path, "initial"]
|
|
3579
|
-
}), sub("initial"))
|
|
3580
|
-
}) : nothing}
|
|
3581
|
-
</div>
|
|
3582
|
-
`;
|
|
3583
|
-
}
|
|
3584
|
-
function renderSwitchCasesEditor(safeNode, onChange, opts, withBadge) {
|
|
3585
|
-
const cases = isJsonObject(safeNode.cases) ? safeNode.cases : {};
|
|
3586
|
-
const entries = Object.entries(cases);
|
|
3587
|
-
const setCases = (next) => onChange({ ...safeNode, cases: next });
|
|
3588
|
-
return html`
|
|
3589
|
-
<div class="switch-cases">
|
|
3590
|
-
${entries.map(([key, operand]) => html`
|
|
3591
|
-
<div class="switch-case-row">
|
|
3592
|
-
<sp-textfield
|
|
3593
|
-
size="s"
|
|
3594
|
-
class="switch-case-key"
|
|
3595
|
-
placeholder="value"
|
|
3596
|
-
.value=${live(key)}
|
|
3597
|
-
@change=${(e) => {
|
|
3598
|
-
const newKey = e.target.value;
|
|
3599
|
-
if (newKey === key) {
|
|
3600
|
-
return;
|
|
3601
|
-
}
|
|
3602
|
-
const next = {};
|
|
3603
|
-
for (const [k, v] of entries) {
|
|
3604
|
-
next[k === key ? newKey : k] = v;
|
|
3605
|
-
}
|
|
3606
|
-
setCases(next);
|
|
3607
|
-
}}
|
|
3608
|
-
></sp-textfield>
|
|
3609
|
-
${withBadge(renderOperandEditor(operand, (v) => setCases({ ...cases, [key]: v }), {
|
|
3610
|
-
...opts,
|
|
3611
|
-
mustBeRef: false,
|
|
3612
|
-
path: [...opts.path, "cases", key]
|
|
3613
|
-
}), [...opts.path, "cases", key].join("/"))}
|
|
3614
|
-
<sp-action-button
|
|
3615
|
-
quiet
|
|
3616
|
-
size="xs"
|
|
3617
|
-
@click=${() => {
|
|
3618
|
-
const next = { ...cases };
|
|
3619
|
-
delete next[key];
|
|
3620
|
-
setCases(next);
|
|
3621
|
-
}}
|
|
3622
|
-
>
|
|
3623
|
-
<sp-icon-delete slot="icon"></sp-icon-delete>
|
|
3624
|
-
</sp-action-button>
|
|
3625
|
-
</div>
|
|
3626
|
-
`)}
|
|
3627
|
-
<div class="switch-case-row">
|
|
3628
|
-
<span class="switch-case-key switch-case-default">default</span>
|
|
3629
|
-
${withBadge(renderOperandEditor(safeNode.default, (v) => onChange({ ...safeNode, default: v }), {
|
|
3630
|
-
...opts,
|
|
3631
|
-
mustBeRef: false,
|
|
3632
|
-
path: [...opts.path, "default"]
|
|
3633
|
-
}), [...opts.path, "default"].join("/"))}
|
|
3634
|
-
</div>
|
|
3635
|
-
<sp-action-button
|
|
3636
|
-
quiet
|
|
3637
|
-
size="s"
|
|
3638
|
-
@click=${() => {
|
|
3639
|
-
let n = entries.length + 1;
|
|
3640
|
-
let key = `case ${n}`;
|
|
3641
|
-
while (Object.hasOwn(cases, key)) {
|
|
3642
|
-
n += 1;
|
|
3643
|
-
key = `case ${n}`;
|
|
3644
|
-
}
|
|
3645
|
-
setCases({ ...cases, [key]: null });
|
|
3646
|
-
}}
|
|
3647
|
-
>
|
|
3648
|
-
+ Add case
|
|
3649
|
-
</sp-action-button>
|
|
3650
|
-
</div>
|
|
3651
|
-
`;
|
|
3652
|
-
}
|
|
3653
|
-
|
|
3654
1670
|
// src/ui/dynamic-slot.ts
|
|
3655
1671
|
var slotModeMemory = new Map;
|
|
3656
1672
|
var slotModeRendered = new Map;
|
|
3657
1673
|
function memoryKey(fieldKey, mode) {
|
|
3658
|
-
return `${
|
|
1674
|
+
return `${activeTab2.value?.id ?? "-"}|${fieldKey}|${mode}`;
|
|
3659
1675
|
}
|
|
3660
1676
|
function fieldScopeKey(fieldKey) {
|
|
3661
|
-
return `${
|
|
1677
|
+
return `${activeTab2.value?.id ?? "-"}|${fieldKey}`;
|
|
3662
1678
|
}
|
|
3663
1679
|
function stashSlotValue(fieldKey, mode, value) {
|
|
3664
|
-
slotModeMemory.set(memoryKey(fieldKey, mode),
|
|
1680
|
+
slotModeMemory.set(memoryKey(fieldKey, mode), cloneValue2(value));
|
|
3665
1681
|
}
|
|
3666
1682
|
function hasStashedSlotValue(fieldKey, mode) {
|
|
3667
1683
|
return slotModeMemory.has(memoryKey(fieldKey, mode));
|
|
3668
1684
|
}
|
|
3669
1685
|
function recallSlotValue(fieldKey, mode) {
|
|
3670
|
-
return
|
|
1686
|
+
return cloneValue2(slotModeMemory.get(memoryKey(fieldKey, mode)));
|
|
3671
1687
|
}
|
|
3672
1688
|
function setSlotMode(fieldKey, mode) {
|
|
3673
1689
|
slotModeRendered.set(fieldScopeKey(fieldKey), mode);
|
|
@@ -3683,17 +1699,14 @@ function effectiveSlotMode(fieldKey, value) {
|
|
|
3683
1699
|
slotModeRendered.set(key, mode);
|
|
3684
1700
|
return mode;
|
|
3685
1701
|
}
|
|
3686
|
-
function
|
|
3687
|
-
return opts.stateDefs.length > 0 || Boolean(opts.extraSignals?.length);
|
|
3688
|
-
}
|
|
3689
|
-
function defaultForSlotMode(mode, opts) {
|
|
1702
|
+
function slotModeSeed(mode, sources) {
|
|
3690
1703
|
switch (mode) {
|
|
3691
1704
|
case "ref": {
|
|
3692
|
-
const [first] =
|
|
3693
|
-
return first ? { $ref: `#/state/${first}` } : { $ref:
|
|
1705
|
+
const [first] = sources.stateDefs;
|
|
1706
|
+
return first ? { $ref: `#/state/${first}` } : { $ref: sources.extraSignals?.[0]?.value ?? "" };
|
|
3694
1707
|
}
|
|
3695
1708
|
case "template": {
|
|
3696
|
-
const [first] =
|
|
1709
|
+
const [first] = sources.stateDefs;
|
|
3697
1710
|
return first ? `\${state.${first}}` : "${}";
|
|
3698
1711
|
}
|
|
3699
1712
|
case "expression": {
|
|
@@ -3703,153 +1716,1092 @@ function defaultForSlotMode(mode, opts) {
|
|
|
3703
1716
|
return { $prototype: "Function", body: "", parameters: [] };
|
|
3704
1717
|
}
|
|
3705
1718
|
default: {
|
|
3706
|
-
return
|
|
1719
|
+
return sources.literalDefault;
|
|
3707
1720
|
}
|
|
3708
1721
|
}
|
|
3709
1722
|
}
|
|
3710
|
-
function
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
];
|
|
1723
|
+
function switchSlotMode(fieldKey, from, to, current, seed) {
|
|
1724
|
+
stashSlotValue(fieldKey, from, current);
|
|
1725
|
+
setSlotMode(fieldKey, to);
|
|
1726
|
+
return hasStashedSlotValue(fieldKey, to) ? recallSlotValue(fieldKey, to) : seed;
|
|
3715
1727
|
}
|
|
3716
|
-
|
|
3717
|
-
|
|
3718
|
-
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
1728
|
+
// src/surfaces/schema-form.json
|
|
1729
|
+
var schema_form_default = {
|
|
1730
|
+
$schema: "https://jxsuite.com/schema/v1",
|
|
1731
|
+
$id: "StudioSchemaForm",
|
|
1732
|
+
$description: "The shared schema→form engine as a document: one row per property of a JSON Schema, drawn from a projection `ui/schema-form.ts` computes. Nothing here reads a schema. The flow decides which of nine controls a property gets, what its options are, which rungs of the value ladder the position permits, what a commit is worth and whether the value is refused; this document draws the answer. That split is what lets the Entry editor, Project Settings and the Signals panel all embed the same form and get the same row — and it is why the engine hands back a HOST ELEMENT rather than a template: a document clears the host it is given, so a caller that is still lit renders an empty element for it and puts it in its own tree (studio-ui-guidelines.md §9.3, §9.4).",
|
|
1733
|
+
tagName: "div",
|
|
1734
|
+
attributes: {
|
|
1735
|
+
part: "form"
|
|
1736
|
+
},
|
|
1737
|
+
style: {
|
|
1738
|
+
$description: "What `.style-row`, `.style-row-label`, `.style-row--invalid`, `.style-row-error`, `.style-row-error-count`, `.array-object-field`, `.array-object-row` and `.schema-param-editor` said in `styles/inspector.css`, keyed on `part` and re-pointed at the studio token layer. Those rules are DEAD in the stylesheet now rather than shared: `ui/field-row.ts` drew the same row for the inspector panels and `ui/expression-editor.ts` the same array rows, and both converted, so this document's copy is the only emitter left. Every `--spectrum-font-size-*` reference is gone rather than renamed — those names resolve only inside `<sp-theme>`, and a document mounted in a settings pane or an entry editor is outside it, so each would silently have taken its hard-coded fallback.",
|
|
1739
|
+
display: "contents",
|
|
1740
|
+
'& [part="field"]': {
|
|
1741
|
+
padding: "var(--jx-space-1) 0"
|
|
1742
|
+
},
|
|
1743
|
+
'& [part="value"]': {
|
|
1744
|
+
$description: "The control line: the source chip, then whatever the rung draws. `min-width: 0` is load-bearing — without it a monospaced pointer or a long JSON line pushes the row past the pane's right edge instead of scrolling inside its own control.",
|
|
1745
|
+
display: "flex",
|
|
1746
|
+
alignItems: "flex-start",
|
|
1747
|
+
gap: "var(--jx-space-2)",
|
|
1748
|
+
minWidth: "0"
|
|
1749
|
+
},
|
|
1750
|
+
'& [part="value"] > *': {
|
|
1751
|
+
minWidth: "0"
|
|
1752
|
+
},
|
|
1753
|
+
'& [part="widget"] > *': {
|
|
1754
|
+
$description: "The switch wrapper is `display: contents`, so whatever rung it drew is the flex item of the line above rather than a box inside a box — which is what keeps `min-width: 0` reaching the control that actually needs it.",
|
|
1755
|
+
flex: "1",
|
|
1756
|
+
minWidth: "0"
|
|
1757
|
+
},
|
|
1758
|
+
'& [part="source"]': {
|
|
1759
|
+
$description: "Quiet while the value is fixed, accent-tinted the moment it is produced from something else — the one place in the row that says a value is not what it looks like.",
|
|
1760
|
+
flex: "none",
|
|
1761
|
+
color: "var(--fg-dim)",
|
|
1762
|
+
fontSize: "var(--jx-text-sm)"
|
|
1763
|
+
},
|
|
1764
|
+
'& [part="source"][data-source="bound"]': {
|
|
1765
|
+
color: "var(--accent)"
|
|
1766
|
+
},
|
|
1767
|
+
'& [part="pointer-group"]': {
|
|
1768
|
+
display: "flex",
|
|
1769
|
+
flex: "1",
|
|
1770
|
+
gap: "var(--jx-space-2)",
|
|
1771
|
+
minWidth: "0"
|
|
1772
|
+
},
|
|
1773
|
+
'& [part="pointer-group"] > *': {
|
|
1774
|
+
flex: "1",
|
|
1775
|
+
minWidth: "0"
|
|
1776
|
+
},
|
|
1777
|
+
'& [part="json"]': {
|
|
1778
|
+
display: "flex",
|
|
1779
|
+
flexDirection: "column",
|
|
1780
|
+
flex: "1",
|
|
1781
|
+
gap: "var(--jx-space-1)",
|
|
1782
|
+
minWidth: "0"
|
|
1783
|
+
},
|
|
1784
|
+
'& [part="chips"]': {
|
|
1785
|
+
display: "flex",
|
|
1786
|
+
flexWrap: "wrap",
|
|
1787
|
+
gap: "var(--jx-space-1)"
|
|
1788
|
+
},
|
|
1789
|
+
'& [part="chip"]': {
|
|
1790
|
+
padding: "1px var(--jx-space-2)",
|
|
1791
|
+
borderRadius: "var(--radius)",
|
|
1792
|
+
background: "var(--bg)",
|
|
1793
|
+
fontSize: "var(--jx-text-sm)",
|
|
1794
|
+
color: "var(--fg-dim)"
|
|
1795
|
+
},
|
|
1796
|
+
'& [part="rows"]': {
|
|
1797
|
+
display: "flex",
|
|
1798
|
+
flexDirection: "column",
|
|
1799
|
+
flex: "1",
|
|
1800
|
+
gap: "var(--jx-space-1)",
|
|
1801
|
+
minWidth: "0"
|
|
1802
|
+
},
|
|
1803
|
+
'& [part="row"]': {
|
|
1804
|
+
$description: "Wraps rather than scrolls: an item with five declared properties in a 260px inspector is a column of controls, not a horizontal scroller nobody finds the end of.",
|
|
1805
|
+
display: "flex",
|
|
1806
|
+
flexWrap: "wrap",
|
|
1807
|
+
alignItems: "center",
|
|
1808
|
+
gap: "var(--jx-space-1)",
|
|
1809
|
+
minWidth: "0"
|
|
1810
|
+
},
|
|
1811
|
+
'& [part="row-add"]': {
|
|
1812
|
+
alignSelf: "flex-start"
|
|
1813
|
+
},
|
|
1814
|
+
'& [part="field-error"]': {
|
|
1815
|
+
$description: "The left rule is what makes the line read as belonging to the control above it rather than to the row below.",
|
|
1816
|
+
margin: "var(--jx-space-1) 0 0",
|
|
1817
|
+
paddingLeft: "var(--jx-space-2)",
|
|
1818
|
+
borderLeft: "2px solid var(--danger)",
|
|
1819
|
+
fontSize: "var(--jx-text-sm)",
|
|
1820
|
+
lineHeight: "1.35",
|
|
1821
|
+
color: "var(--danger)"
|
|
1822
|
+
},
|
|
1823
|
+
'& [part="field-error-count"]': {
|
|
1824
|
+
marginLeft: "var(--jx-space-1)",
|
|
1825
|
+
fontFamily: "var(--font-mono)",
|
|
1826
|
+
opacity: "0.75"
|
|
3730
1827
|
}
|
|
3731
|
-
}
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
|
|
3735
|
-
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
3742
|
-
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
3749
|
-
|
|
3750
|
-
|
|
3751
|
-
|
|
1828
|
+
},
|
|
1829
|
+
children: [
|
|
1830
|
+
{
|
|
1831
|
+
$prototype: "Array",
|
|
1832
|
+
items: {
|
|
1833
|
+
$ref: "#/state/fields"
|
|
1834
|
+
},
|
|
1835
|
+
key: {
|
|
1836
|
+
$ref: "$map/item/key"
|
|
1837
|
+
},
|
|
1838
|
+
map: {
|
|
1839
|
+
$description: "One property of the schema. `jx-field` owns the label, the required mark, the invalid shade and the association between the label and whatever control ends up in the slot, so none of that is written here — which is the whole of what the four hand-rolled `.style-row` variants used to carry.",
|
|
1840
|
+
tagName: "jx-field",
|
|
1841
|
+
attributes: {
|
|
1842
|
+
part: "field",
|
|
1843
|
+
"data-prop": "${$map.item.prop}"
|
|
1844
|
+
},
|
|
1845
|
+
$props: {
|
|
1846
|
+
label: {
|
|
1847
|
+
$ref: "$map/item/label"
|
|
1848
|
+
},
|
|
1849
|
+
description: {
|
|
1850
|
+
$ref: "$map/item/description"
|
|
1851
|
+
},
|
|
1852
|
+
required: {
|
|
1853
|
+
$ref: "$map/item/required"
|
|
1854
|
+
},
|
|
1855
|
+
invalid: {
|
|
1856
|
+
$ref: "$map/item/invalid"
|
|
1857
|
+
},
|
|
1858
|
+
span: true
|
|
1859
|
+
},
|
|
1860
|
+
children: [
|
|
1861
|
+
{
|
|
1862
|
+
tagName: "div",
|
|
1863
|
+
attributes: {
|
|
1864
|
+
part: "value",
|
|
1865
|
+
role: "none"
|
|
1866
|
+
},
|
|
1867
|
+
children: [
|
|
1868
|
+
{
|
|
1869
|
+
tagName: "span",
|
|
1870
|
+
attributes: {
|
|
1871
|
+
part: "source-slot",
|
|
1872
|
+
role: "none"
|
|
1873
|
+
},
|
|
1874
|
+
style: {
|
|
1875
|
+
display: "contents"
|
|
1876
|
+
},
|
|
1877
|
+
$switch: {
|
|
1878
|
+
$ref: "$map/item/hasChip"
|
|
1879
|
+
},
|
|
1880
|
+
cases: {
|
|
1881
|
+
true: {
|
|
1882
|
+
$description: "The Value Source chip (§6.3): what produces this value, in the same four words every other bindable position in Studio uses. It opens the rung picker rather than cycling, so any rung the position permits is one action away. Drawn only where the host named somewhere a value could come from — a settings form that names none edits fixed values and shows no chip.",
|
|
1883
|
+
tagName: "jx-action-button",
|
|
1884
|
+
attributes: {
|
|
1885
|
+
part: "source",
|
|
1886
|
+
"data-source": "${$map.item.source}"
|
|
1887
|
+
},
|
|
1888
|
+
$props: {
|
|
1889
|
+
label: {
|
|
1890
|
+
$ref: "$map/item/sourceName"
|
|
1891
|
+
},
|
|
1892
|
+
hint: {
|
|
1893
|
+
$ref: "$map/item/sourceHint"
|
|
1894
|
+
},
|
|
1895
|
+
size: "sm",
|
|
1896
|
+
haspopup: "menu",
|
|
1897
|
+
disabled: {
|
|
1898
|
+
$ref: "$map/item/sourceLocked"
|
|
1899
|
+
}
|
|
1900
|
+
},
|
|
1901
|
+
onclick: {
|
|
1902
|
+
$prototype: "Function",
|
|
1903
|
+
body: [
|
|
1904
|
+
{
|
|
1905
|
+
operator: "call",
|
|
1906
|
+
target: {
|
|
1907
|
+
$ref: "#/state/pickSource"
|
|
1908
|
+
},
|
|
1909
|
+
value: [
|
|
1910
|
+
{
|
|
1911
|
+
$ref: "$map/item/key"
|
|
1912
|
+
},
|
|
1913
|
+
{
|
|
1914
|
+
$ref: "event#/currentTarget"
|
|
1915
|
+
}
|
|
1916
|
+
]
|
|
1917
|
+
}
|
|
1918
|
+
]
|
|
1919
|
+
},
|
|
1920
|
+
children: [
|
|
1921
|
+
{
|
|
1922
|
+
tagName: "span",
|
|
1923
|
+
textContent: "${$map.item.sourceLabel}"
|
|
1924
|
+
}
|
|
1925
|
+
]
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
},
|
|
1929
|
+
{
|
|
1930
|
+
tagName: "span",
|
|
1931
|
+
attributes: {
|
|
1932
|
+
part: "widget",
|
|
1933
|
+
role: "none"
|
|
1934
|
+
},
|
|
1935
|
+
style: {
|
|
1936
|
+
display: "contents"
|
|
1937
|
+
},
|
|
1938
|
+
$switch: {
|
|
1939
|
+
$ref: "$map/item/kind"
|
|
1940
|
+
},
|
|
1941
|
+
cases: {
|
|
1942
|
+
text: {
|
|
1943
|
+
$description: "The default control, and the one every scalar without a narrower answer lands on. Commits on input, debounced by the flow, which is what makes the canvas and the preview follow a value being typed rather than waiting for a blur.",
|
|
1944
|
+
tagName: "jx-textfield",
|
|
1945
|
+
attributes: {
|
|
1946
|
+
part: "text"
|
|
1947
|
+
},
|
|
1948
|
+
$props: {
|
|
1949
|
+
label: {
|
|
1950
|
+
$ref: "$map/item/label"
|
|
1951
|
+
},
|
|
1952
|
+
value: {
|
|
1953
|
+
$ref: "$map/item/value"
|
|
1954
|
+
},
|
|
1955
|
+
placeholder: {
|
|
1956
|
+
$ref: "$map/item/placeholder"
|
|
1957
|
+
},
|
|
1958
|
+
size: "sm",
|
|
1959
|
+
invalid: {
|
|
1960
|
+
$ref: "$map/item/invalid"
|
|
1961
|
+
},
|
|
1962
|
+
mono: {
|
|
1963
|
+
$ref: "$map/item/mono"
|
|
1964
|
+
},
|
|
1965
|
+
multiline: {
|
|
1966
|
+
$ref: "$map/item/multiline"
|
|
1967
|
+
}
|
|
1968
|
+
},
|
|
1969
|
+
oninput: {
|
|
1970
|
+
$prototype: "Function",
|
|
1971
|
+
body: [
|
|
1972
|
+
{
|
|
1973
|
+
operator: "call",
|
|
1974
|
+
target: {
|
|
1975
|
+
$ref: "#/state/edit"
|
|
1976
|
+
},
|
|
1977
|
+
value: [
|
|
1978
|
+
{
|
|
1979
|
+
$ref: "$map/item/key"
|
|
1980
|
+
},
|
|
1981
|
+
{
|
|
1982
|
+
$ref: "event#/target/value"
|
|
1983
|
+
}
|
|
1984
|
+
]
|
|
1985
|
+
}
|
|
1986
|
+
]
|
|
1987
|
+
},
|
|
1988
|
+
onchange: {
|
|
1989
|
+
$prototype: "Function",
|
|
1990
|
+
body: [
|
|
1991
|
+
{
|
|
1992
|
+
operator: "call",
|
|
1993
|
+
target: {
|
|
1994
|
+
$ref: "#/state/commit"
|
|
1995
|
+
},
|
|
1996
|
+
value: [
|
|
1997
|
+
{
|
|
1998
|
+
$ref: "$map/item/key"
|
|
1999
|
+
},
|
|
2000
|
+
{
|
|
2001
|
+
$ref: "event#/target/value"
|
|
2002
|
+
}
|
|
2003
|
+
]
|
|
2004
|
+
}
|
|
2005
|
+
]
|
|
2006
|
+
}
|
|
2007
|
+
},
|
|
2008
|
+
template: {
|
|
2009
|
+
$description: "The Mixed text rung: a string with `${…}` holes in it. Monospaced because what is being written is half text and half pointer, and committed on change rather than on input — a half-typed placeholder is not a template anybody meant.",
|
|
2010
|
+
tagName: "jx-textfield",
|
|
2011
|
+
attributes: {
|
|
2012
|
+
part: "template"
|
|
2013
|
+
},
|
|
2014
|
+
$props: {
|
|
2015
|
+
label: {
|
|
2016
|
+
$ref: "$map/item/label"
|
|
2017
|
+
},
|
|
2018
|
+
value: {
|
|
2019
|
+
$ref: "$map/item/value"
|
|
2020
|
+
},
|
|
2021
|
+
placeholder: "${state.templatePlaceholder}",
|
|
2022
|
+
size: "sm",
|
|
2023
|
+
mono: true
|
|
2024
|
+
},
|
|
2025
|
+
onchange: {
|
|
2026
|
+
$prototype: "Function",
|
|
2027
|
+
body: [
|
|
2028
|
+
{
|
|
2029
|
+
operator: "call",
|
|
2030
|
+
target: {
|
|
2031
|
+
$ref: "#/state/commit"
|
|
2032
|
+
},
|
|
2033
|
+
value: [
|
|
2034
|
+
{
|
|
2035
|
+
$ref: "$map/item/key"
|
|
2036
|
+
},
|
|
2037
|
+
{
|
|
2038
|
+
$ref: "event#/target/value"
|
|
2039
|
+
}
|
|
2040
|
+
]
|
|
2041
|
+
}
|
|
2042
|
+
]
|
|
2043
|
+
}
|
|
2044
|
+
},
|
|
2045
|
+
pointer: {
|
|
2046
|
+
$description: "The From data… rung. It is TWO controls over one value because the kit has no combobox and this rung needs one: the list is a set of suggestions rather than a whitelist (a value may point at a route param, a signal, or a pointer neither the form nor its host enumerates), so the select offers what the host named and the field accepts anything. Picking a row writes the field; typing a pointer the host never named leaves the select showing it, because a select whose value matches no option synthesises the row rather than lying about what it holds.",
|
|
2047
|
+
tagName: "span",
|
|
2048
|
+
attributes: {
|
|
2049
|
+
part: "pointer-group",
|
|
2050
|
+
role: "none"
|
|
2051
|
+
},
|
|
2052
|
+
children: [
|
|
2053
|
+
{
|
|
2054
|
+
tagName: "jx-textfield",
|
|
2055
|
+
attributes: {
|
|
2056
|
+
part: "pointer"
|
|
2057
|
+
},
|
|
2058
|
+
$props: {
|
|
2059
|
+
label: "${$map.item.label} pointer",
|
|
2060
|
+
value: {
|
|
2061
|
+
$ref: "$map/item/value"
|
|
2062
|
+
},
|
|
2063
|
+
placeholder: "#/state/…",
|
|
2064
|
+
size: "sm",
|
|
2065
|
+
mono: true
|
|
2066
|
+
},
|
|
2067
|
+
onchange: {
|
|
2068
|
+
$prototype: "Function",
|
|
2069
|
+
body: [
|
|
2070
|
+
{
|
|
2071
|
+
operator: "call",
|
|
2072
|
+
target: {
|
|
2073
|
+
$ref: "#/state/commit"
|
|
2074
|
+
},
|
|
2075
|
+
value: [
|
|
2076
|
+
{
|
|
2077
|
+
$ref: "$map/item/key"
|
|
2078
|
+
},
|
|
2079
|
+
{
|
|
2080
|
+
$ref: "event#/target/value"
|
|
2081
|
+
}
|
|
2082
|
+
]
|
|
2083
|
+
}
|
|
2084
|
+
]
|
|
2085
|
+
}
|
|
2086
|
+
},
|
|
2087
|
+
{
|
|
2088
|
+
tagName: "span",
|
|
2089
|
+
attributes: {
|
|
2090
|
+
part: "pointer-pick-slot",
|
|
2091
|
+
role: "none"
|
|
2092
|
+
},
|
|
2093
|
+
style: {
|
|
2094
|
+
display: "contents"
|
|
2095
|
+
},
|
|
2096
|
+
$switch: {
|
|
2097
|
+
$ref: "$map/item/hasOptions"
|
|
2098
|
+
},
|
|
2099
|
+
cases: {
|
|
2100
|
+
true: {
|
|
2101
|
+
tagName: "jx-select",
|
|
2102
|
+
attributes: {
|
|
2103
|
+
part: "pointer-pick"
|
|
2104
|
+
},
|
|
2105
|
+
$props: {
|
|
2106
|
+
label: "${$map.item.label} source",
|
|
2107
|
+
options: {
|
|
2108
|
+
$ref: "$map/item/options"
|
|
2109
|
+
},
|
|
2110
|
+
value: {
|
|
2111
|
+
$ref: "$map/item/value"
|
|
2112
|
+
},
|
|
2113
|
+
size: "sm"
|
|
2114
|
+
},
|
|
2115
|
+
onchange: {
|
|
2116
|
+
$prototype: "Function",
|
|
2117
|
+
body: [
|
|
2118
|
+
{
|
|
2119
|
+
operator: "call",
|
|
2120
|
+
target: {
|
|
2121
|
+
$ref: "#/state/commit"
|
|
2122
|
+
},
|
|
2123
|
+
value: [
|
|
2124
|
+
{
|
|
2125
|
+
$ref: "$map/item/key"
|
|
2126
|
+
},
|
|
2127
|
+
{
|
|
2128
|
+
$ref: "event#/target/value"
|
|
2129
|
+
}
|
|
2130
|
+
]
|
|
2131
|
+
}
|
|
2132
|
+
]
|
|
2133
|
+
}
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
]
|
|
2138
|
+
},
|
|
2139
|
+
select: {
|
|
2140
|
+
$description: "A closed set the schema itself declares, or one the host resolved through a `#/$context/…` pointer. The empty row is offered only where the property is optional — a required field has no “none” to choose.",
|
|
2141
|
+
tagName: "jx-select",
|
|
2142
|
+
attributes: {
|
|
2143
|
+
part: "select"
|
|
2144
|
+
},
|
|
2145
|
+
$props: {
|
|
2146
|
+
label: {
|
|
2147
|
+
$ref: "$map/item/label"
|
|
2148
|
+
},
|
|
2149
|
+
options: {
|
|
2150
|
+
$ref: "$map/item/options"
|
|
2151
|
+
},
|
|
2152
|
+
value: {
|
|
2153
|
+
$ref: "$map/item/value"
|
|
2154
|
+
},
|
|
2155
|
+
size: "sm",
|
|
2156
|
+
invalid: {
|
|
2157
|
+
$ref: "$map/item/invalid"
|
|
2158
|
+
}
|
|
2159
|
+
},
|
|
2160
|
+
onchange: {
|
|
2161
|
+
$prototype: "Function",
|
|
2162
|
+
body: [
|
|
2163
|
+
{
|
|
2164
|
+
operator: "call",
|
|
2165
|
+
target: {
|
|
2166
|
+
$ref: "#/state/commit"
|
|
2167
|
+
},
|
|
2168
|
+
value: [
|
|
2169
|
+
{
|
|
2170
|
+
$ref: "$map/item/key"
|
|
2171
|
+
},
|
|
2172
|
+
{
|
|
2173
|
+
$ref: "event#/target/value"
|
|
2174
|
+
}
|
|
2175
|
+
]
|
|
2176
|
+
}
|
|
2177
|
+
]
|
|
2178
|
+
}
|
|
2179
|
+
},
|
|
2180
|
+
checkbox: {
|
|
2181
|
+
tagName: "jx-checkbox",
|
|
2182
|
+
attributes: {
|
|
2183
|
+
part: "checkbox"
|
|
2184
|
+
},
|
|
2185
|
+
$props: {
|
|
2186
|
+
label: {
|
|
2187
|
+
$ref: "$map/item/label"
|
|
2188
|
+
},
|
|
2189
|
+
checked: {
|
|
2190
|
+
$ref: "$map/item/checked"
|
|
2191
|
+
},
|
|
2192
|
+
size: "sm"
|
|
2193
|
+
},
|
|
2194
|
+
onchange: {
|
|
2195
|
+
$prototype: "Function",
|
|
2196
|
+
body: [
|
|
2197
|
+
{
|
|
2198
|
+
operator: "call",
|
|
2199
|
+
target: {
|
|
2200
|
+
$ref: "#/state/commitChecked"
|
|
2201
|
+
},
|
|
2202
|
+
value: [
|
|
2203
|
+
{
|
|
2204
|
+
$ref: "$map/item/key"
|
|
2205
|
+
},
|
|
2206
|
+
{
|
|
2207
|
+
$ref: "event#/target/checked"
|
|
2208
|
+
}
|
|
2209
|
+
]
|
|
2210
|
+
}
|
|
2211
|
+
]
|
|
2212
|
+
}
|
|
2213
|
+
},
|
|
2214
|
+
number: {
|
|
2215
|
+
$description: "`min`, `max` and `step` come straight off the property schema, so the control refuses out-of-range input before the flow has to say why. The stepper is off: these are configuration values that get typed, not nudged.",
|
|
2216
|
+
tagName: "jx-number-field",
|
|
2217
|
+
attributes: {
|
|
2218
|
+
part: "number"
|
|
2219
|
+
},
|
|
2220
|
+
$props: {
|
|
2221
|
+
label: {
|
|
2222
|
+
$ref: "$map/item/label"
|
|
2223
|
+
},
|
|
2224
|
+
value: {
|
|
2225
|
+
$ref: "$map/item/value"
|
|
2226
|
+
},
|
|
2227
|
+
placeholder: {
|
|
2228
|
+
$ref: "$map/item/placeholder"
|
|
2229
|
+
},
|
|
2230
|
+
min: {
|
|
2231
|
+
$ref: "$map/item/min"
|
|
2232
|
+
},
|
|
2233
|
+
max: {
|
|
2234
|
+
$ref: "$map/item/max"
|
|
2235
|
+
},
|
|
2236
|
+
step: {
|
|
2237
|
+
$ref: "$map/item/step"
|
|
2238
|
+
},
|
|
2239
|
+
size: "sm"
|
|
2240
|
+
},
|
|
2241
|
+
onchange: {
|
|
2242
|
+
$prototype: "Function",
|
|
2243
|
+
body: [
|
|
2244
|
+
{
|
|
2245
|
+
operator: "call",
|
|
2246
|
+
target: {
|
|
2247
|
+
$ref: "#/state/commit"
|
|
2248
|
+
},
|
|
2249
|
+
value: [
|
|
2250
|
+
{
|
|
2251
|
+
$ref: "$map/item/key"
|
|
2252
|
+
},
|
|
2253
|
+
{
|
|
2254
|
+
$ref: "event#/target/value"
|
|
2255
|
+
}
|
|
2256
|
+
]
|
|
2257
|
+
}
|
|
2258
|
+
]
|
|
2259
|
+
}
|
|
2260
|
+
},
|
|
2261
|
+
json: {
|
|
2262
|
+
$description: "A value with structure, edited as the JSON it is. The chips above it are a read of the shape currently held — property name and declared type — so the common case (“what is in here?”) is answered without reading the text. The flow parses on a debounce and keeps the last parsable value; a mid-keystroke `{` is not a refusal.",
|
|
2263
|
+
tagName: "span",
|
|
2264
|
+
attributes: {
|
|
2265
|
+
part: "json",
|
|
2266
|
+
role: "none"
|
|
2267
|
+
},
|
|
2268
|
+
children: [
|
|
2269
|
+
{
|
|
2270
|
+
tagName: "span",
|
|
2271
|
+
attributes: {
|
|
2272
|
+
part: "chips-slot",
|
|
2273
|
+
role: "none"
|
|
2274
|
+
},
|
|
2275
|
+
style: {
|
|
2276
|
+
display: "contents"
|
|
2277
|
+
},
|
|
2278
|
+
$switch: {
|
|
2279
|
+
$ref: "$map/item/hasChips"
|
|
2280
|
+
},
|
|
2281
|
+
cases: {
|
|
2282
|
+
true: {
|
|
2283
|
+
tagName: "span",
|
|
2284
|
+
attributes: {
|
|
2285
|
+
part: "chips",
|
|
2286
|
+
role: "none"
|
|
2287
|
+
},
|
|
2288
|
+
children: [
|
|
2289
|
+
{
|
|
2290
|
+
$prototype: "Array",
|
|
2291
|
+
items: {
|
|
2292
|
+
$ref: "$map/item/chips"
|
|
2293
|
+
},
|
|
2294
|
+
key: {
|
|
2295
|
+
$ref: "$map/item/key"
|
|
2296
|
+
},
|
|
2297
|
+
map: {
|
|
2298
|
+
tagName: "span",
|
|
2299
|
+
attributes: {
|
|
2300
|
+
part: "chip"
|
|
2301
|
+
},
|
|
2302
|
+
textContent: "${$map.item.label}"
|
|
2303
|
+
}
|
|
2304
|
+
}
|
|
2305
|
+
]
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
},
|
|
2309
|
+
{
|
|
2310
|
+
tagName: "jx-textfield",
|
|
2311
|
+
attributes: {
|
|
2312
|
+
part: "json-text"
|
|
2313
|
+
},
|
|
2314
|
+
$props: {
|
|
2315
|
+
label: {
|
|
2316
|
+
$ref: "$map/item/label"
|
|
2317
|
+
},
|
|
2318
|
+
value: {
|
|
2319
|
+
$ref: "$map/item/value"
|
|
2320
|
+
},
|
|
2321
|
+
placeholder: {
|
|
2322
|
+
$ref: "$map/item/placeholder"
|
|
2323
|
+
},
|
|
2324
|
+
size: "sm",
|
|
2325
|
+
mono: true,
|
|
2326
|
+
multiline: true,
|
|
2327
|
+
grows: true
|
|
2328
|
+
},
|
|
2329
|
+
oninput: {
|
|
2330
|
+
$prototype: "Function",
|
|
2331
|
+
body: [
|
|
2332
|
+
{
|
|
2333
|
+
operator: "call",
|
|
2334
|
+
target: {
|
|
2335
|
+
$ref: "#/state/editJson"
|
|
2336
|
+
},
|
|
2337
|
+
value: [
|
|
2338
|
+
{
|
|
2339
|
+
$ref: "$map/item/key"
|
|
2340
|
+
},
|
|
2341
|
+
{
|
|
2342
|
+
$ref: "event#/target/value"
|
|
2343
|
+
}
|
|
2344
|
+
]
|
|
2345
|
+
}
|
|
2346
|
+
]
|
|
2347
|
+
}
|
|
2348
|
+
}
|
|
2349
|
+
]
|
|
2350
|
+
},
|
|
2351
|
+
rows: {
|
|
2352
|
+
$description: "An array whose items are objects with a declared shape: one row per item, one cell per declared property, a remove beside each and one add below. The alternative was the raw-JSON editor, which is what an array of anything else still gets — a shape the schema states is a shape the form can draw.",
|
|
2353
|
+
tagName: "span",
|
|
2354
|
+
attributes: {
|
|
2355
|
+
part: "rows",
|
|
2356
|
+
role: "none"
|
|
2357
|
+
},
|
|
2358
|
+
children: [
|
|
2359
|
+
{
|
|
2360
|
+
$prototype: "Array",
|
|
2361
|
+
items: {
|
|
2362
|
+
$ref: "$map/item/rows"
|
|
2363
|
+
},
|
|
2364
|
+
key: {
|
|
2365
|
+
$ref: "$map/item/key"
|
|
2366
|
+
},
|
|
2367
|
+
map: {
|
|
2368
|
+
tagName: "span",
|
|
2369
|
+
attributes: {
|
|
2370
|
+
part: "row",
|
|
2371
|
+
role: "none",
|
|
2372
|
+
"data-row": "${$map.item.key}"
|
|
2373
|
+
},
|
|
2374
|
+
children: [
|
|
2375
|
+
{
|
|
2376
|
+
$prototype: "Array",
|
|
2377
|
+
items: {
|
|
2378
|
+
$ref: "$map/item/cells"
|
|
2379
|
+
},
|
|
2380
|
+
key: {
|
|
2381
|
+
$ref: "$map/item/id"
|
|
2382
|
+
},
|
|
2383
|
+
map: {
|
|
2384
|
+
tagName: "span",
|
|
2385
|
+
attributes: {
|
|
2386
|
+
part: "cell",
|
|
2387
|
+
role: "none"
|
|
2388
|
+
},
|
|
2389
|
+
style: {
|
|
2390
|
+
display: "contents"
|
|
2391
|
+
},
|
|
2392
|
+
$switch: {
|
|
2393
|
+
$ref: "$map/item/kind"
|
|
2394
|
+
},
|
|
2395
|
+
cases: {
|
|
2396
|
+
text: {
|
|
2397
|
+
tagName: "jx-textfield",
|
|
2398
|
+
attributes: {
|
|
2399
|
+
part: "cell-text"
|
|
2400
|
+
},
|
|
2401
|
+
$description: "Committed on input AND on change: a cell showing a pointer is committed when it is left, exactly as the pointer field above is, and a cell showing text follows the canvas while it is typed. The two never disagree — a change after an input carries the same string.",
|
|
2402
|
+
$props: {
|
|
2403
|
+
label: {
|
|
2404
|
+
$ref: "$map/item/label"
|
|
2405
|
+
},
|
|
2406
|
+
value: {
|
|
2407
|
+
$ref: "$map/item/value"
|
|
2408
|
+
},
|
|
2409
|
+
placeholder: {
|
|
2410
|
+
$ref: "$map/item/label"
|
|
2411
|
+
},
|
|
2412
|
+
size: "sm"
|
|
2413
|
+
},
|
|
2414
|
+
oninput: {
|
|
2415
|
+
$prototype: "Function",
|
|
2416
|
+
body: [
|
|
2417
|
+
{
|
|
2418
|
+
operator: "call",
|
|
2419
|
+
target: {
|
|
2420
|
+
$ref: "#/state/editCell"
|
|
2421
|
+
},
|
|
2422
|
+
value: [
|
|
2423
|
+
{
|
|
2424
|
+
$ref: "$map/item/field"
|
|
2425
|
+
},
|
|
2426
|
+
{
|
|
2427
|
+
$ref: "$map/item/row"
|
|
2428
|
+
},
|
|
2429
|
+
{
|
|
2430
|
+
$ref: "$map/item/key"
|
|
2431
|
+
},
|
|
2432
|
+
{
|
|
2433
|
+
$ref: "event#/target/value"
|
|
2434
|
+
}
|
|
2435
|
+
]
|
|
2436
|
+
}
|
|
2437
|
+
]
|
|
2438
|
+
},
|
|
2439
|
+
onchange: {
|
|
2440
|
+
$prototype: "Function",
|
|
2441
|
+
body: [
|
|
2442
|
+
{
|
|
2443
|
+
operator: "call",
|
|
2444
|
+
target: {
|
|
2445
|
+
$ref: "#/state/editCell"
|
|
2446
|
+
},
|
|
2447
|
+
value: [
|
|
2448
|
+
{
|
|
2449
|
+
$ref: "$map/item/field"
|
|
2450
|
+
},
|
|
2451
|
+
{
|
|
2452
|
+
$ref: "$map/item/row"
|
|
2453
|
+
},
|
|
2454
|
+
{
|
|
2455
|
+
$ref: "$map/item/key"
|
|
2456
|
+
},
|
|
2457
|
+
{
|
|
2458
|
+
$ref: "event#/target/value"
|
|
2459
|
+
}
|
|
2460
|
+
]
|
|
2461
|
+
}
|
|
2462
|
+
]
|
|
2463
|
+
}
|
|
2464
|
+
},
|
|
2465
|
+
select: {
|
|
2466
|
+
tagName: "jx-select",
|
|
2467
|
+
attributes: {
|
|
2468
|
+
part: "cell-select"
|
|
2469
|
+
},
|
|
2470
|
+
$props: {
|
|
2471
|
+
label: {
|
|
2472
|
+
$ref: "$map/item/label"
|
|
2473
|
+
},
|
|
2474
|
+
options: {
|
|
2475
|
+
$ref: "$map/item/options"
|
|
2476
|
+
},
|
|
2477
|
+
value: {
|
|
2478
|
+
$ref: "$map/item/value"
|
|
2479
|
+
},
|
|
2480
|
+
size: "sm"
|
|
2481
|
+
},
|
|
2482
|
+
onchange: {
|
|
2483
|
+
$prototype: "Function",
|
|
2484
|
+
body: [
|
|
2485
|
+
{
|
|
2486
|
+
operator: "call",
|
|
2487
|
+
target: {
|
|
2488
|
+
$ref: "#/state/editCell"
|
|
2489
|
+
},
|
|
2490
|
+
value: [
|
|
2491
|
+
{
|
|
2492
|
+
$ref: "$map/item/field"
|
|
2493
|
+
},
|
|
2494
|
+
{
|
|
2495
|
+
$ref: "$map/item/row"
|
|
2496
|
+
},
|
|
2497
|
+
{
|
|
2498
|
+
$ref: "$map/item/key"
|
|
2499
|
+
},
|
|
2500
|
+
{
|
|
2501
|
+
$ref: "event#/target/value"
|
|
2502
|
+
}
|
|
2503
|
+
]
|
|
2504
|
+
}
|
|
2505
|
+
]
|
|
2506
|
+
}
|
|
2507
|
+
},
|
|
2508
|
+
checkbox: {
|
|
2509
|
+
tagName: "jx-checkbox",
|
|
2510
|
+
attributes: {
|
|
2511
|
+
part: "cell-checkbox"
|
|
2512
|
+
},
|
|
2513
|
+
$props: {
|
|
2514
|
+
label: {
|
|
2515
|
+
$ref: "$map/item/label"
|
|
2516
|
+
},
|
|
2517
|
+
checked: {
|
|
2518
|
+
$ref: "$map/item/checked"
|
|
2519
|
+
},
|
|
2520
|
+
size: "sm"
|
|
2521
|
+
},
|
|
2522
|
+
onchange: {
|
|
2523
|
+
$prototype: "Function",
|
|
2524
|
+
body: [
|
|
2525
|
+
{
|
|
2526
|
+
operator: "call",
|
|
2527
|
+
target: {
|
|
2528
|
+
$ref: "#/state/editCellChecked"
|
|
2529
|
+
},
|
|
2530
|
+
value: [
|
|
2531
|
+
{
|
|
2532
|
+
$ref: "$map/item/field"
|
|
2533
|
+
},
|
|
2534
|
+
{
|
|
2535
|
+
$ref: "$map/item/row"
|
|
2536
|
+
},
|
|
2537
|
+
{
|
|
2538
|
+
$ref: "$map/item/key"
|
|
2539
|
+
},
|
|
2540
|
+
{
|
|
2541
|
+
$ref: "event#/target/checked"
|
|
2542
|
+
}
|
|
2543
|
+
]
|
|
2544
|
+
}
|
|
2545
|
+
]
|
|
2546
|
+
}
|
|
2547
|
+
},
|
|
2548
|
+
number: {
|
|
2549
|
+
tagName: "jx-number-field",
|
|
2550
|
+
attributes: {
|
|
2551
|
+
part: "cell-number"
|
|
2552
|
+
},
|
|
2553
|
+
$props: {
|
|
2554
|
+
label: {
|
|
2555
|
+
$ref: "$map/item/label"
|
|
2556
|
+
},
|
|
2557
|
+
value: {
|
|
2558
|
+
$ref: "$map/item/value"
|
|
2559
|
+
},
|
|
2560
|
+
size: "sm"
|
|
2561
|
+
},
|
|
2562
|
+
onchange: {
|
|
2563
|
+
$prototype: "Function",
|
|
2564
|
+
body: [
|
|
2565
|
+
{
|
|
2566
|
+
operator: "call",
|
|
2567
|
+
target: {
|
|
2568
|
+
$ref: "#/state/editCell"
|
|
2569
|
+
},
|
|
2570
|
+
value: [
|
|
2571
|
+
{
|
|
2572
|
+
$ref: "$map/item/field"
|
|
2573
|
+
},
|
|
2574
|
+
{
|
|
2575
|
+
$ref: "$map/item/row"
|
|
2576
|
+
},
|
|
2577
|
+
{
|
|
2578
|
+
$ref: "$map/item/key"
|
|
2579
|
+
},
|
|
2580
|
+
{
|
|
2581
|
+
$ref: "event#/target/value"
|
|
2582
|
+
}
|
|
2583
|
+
]
|
|
2584
|
+
}
|
|
2585
|
+
]
|
|
2586
|
+
}
|
|
2587
|
+
},
|
|
2588
|
+
control: {
|
|
2589
|
+
$description: "A cell a registered control owns — a relationship to another collection is the one that reaches here. The document draws the empty host and nothing inside it; `ui/schema-form.ts` mounts the control into it, by the same dispatch a FIELD host gets. An extension's control is its own surface rather than part of this one, and the seam between them is this empty node.",
|
|
2590
|
+
tagName: "span",
|
|
2591
|
+
attributes: {
|
|
2592
|
+
part: "cell-control-host",
|
|
2593
|
+
"data-control": "${$map.item.id}",
|
|
2594
|
+
role: "none"
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2599
|
+
},
|
|
2600
|
+
{
|
|
2601
|
+
tagName: "jx-action-button",
|
|
2602
|
+
attributes: {
|
|
2603
|
+
part: "row-remove"
|
|
2604
|
+
},
|
|
2605
|
+
$props: {
|
|
2606
|
+
icon: "trash",
|
|
2607
|
+
label: "${$map.item.removeLabel}",
|
|
2608
|
+
size: "sm"
|
|
2609
|
+
},
|
|
2610
|
+
onclick: {
|
|
2611
|
+
$prototype: "Function",
|
|
2612
|
+
body: [
|
|
2613
|
+
{
|
|
2614
|
+
operator: "call",
|
|
2615
|
+
target: {
|
|
2616
|
+
$ref: "#/state/removeRow"
|
|
2617
|
+
},
|
|
2618
|
+
value: [
|
|
2619
|
+
{
|
|
2620
|
+
$ref: "$map/item/field"
|
|
2621
|
+
},
|
|
2622
|
+
{
|
|
2623
|
+
$ref: "$map/item/key"
|
|
2624
|
+
}
|
|
2625
|
+
]
|
|
2626
|
+
}
|
|
2627
|
+
]
|
|
2628
|
+
}
|
|
2629
|
+
}
|
|
2630
|
+
]
|
|
2631
|
+
}
|
|
2632
|
+
},
|
|
2633
|
+
{
|
|
2634
|
+
tagName: "jx-action-button",
|
|
2635
|
+
attributes: {
|
|
2636
|
+
part: "row-add"
|
|
2637
|
+
},
|
|
2638
|
+
$props: {
|
|
2639
|
+
icon: "plus",
|
|
2640
|
+
label: "${$map.item.addLabel}",
|
|
2641
|
+
size: "sm"
|
|
2642
|
+
},
|
|
2643
|
+
onclick: {
|
|
2644
|
+
$prototype: "Function",
|
|
2645
|
+
body: [
|
|
2646
|
+
{
|
|
2647
|
+
operator: "call",
|
|
2648
|
+
target: {
|
|
2649
|
+
$ref: "#/state/addRow"
|
|
2650
|
+
},
|
|
2651
|
+
value: [
|
|
2652
|
+
{
|
|
2653
|
+
$ref: "$map/item/key"
|
|
2654
|
+
}
|
|
2655
|
+
]
|
|
2656
|
+
}
|
|
2657
|
+
]
|
|
2658
|
+
},
|
|
2659
|
+
children: [
|
|
2660
|
+
{
|
|
2661
|
+
tagName: "span",
|
|
2662
|
+
textContent: "Add"
|
|
2663
|
+
}
|
|
2664
|
+
]
|
|
2665
|
+
}
|
|
2666
|
+
]
|
|
2667
|
+
},
|
|
2668
|
+
control: {
|
|
2669
|
+
$description: "A field a registered control owns outright — `schema-builder`, `secret`, or the `reference` picker. The document draws the empty host and the flow MOUNTS the control into it: the registry's contract (specs/extensions.md §9.1) is a control that owns a host, all three built-ins are documents of their own, and a document clears the host it is given — which is why this node is drawn empty and never written to twice.",
|
|
2670
|
+
tagName: "div",
|
|
2671
|
+
attributes: {
|
|
2672
|
+
part: "control-host",
|
|
2673
|
+
"data-control": {
|
|
2674
|
+
$ref: "$map/item/key"
|
|
2675
|
+
},
|
|
2676
|
+
role: "none"
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
}
|
|
2681
|
+
]
|
|
2682
|
+
},
|
|
2683
|
+
{
|
|
2684
|
+
tagName: "span",
|
|
2685
|
+
attributes: {
|
|
2686
|
+
part: "error-slot",
|
|
2687
|
+
role: "none"
|
|
2688
|
+
},
|
|
2689
|
+
style: {
|
|
2690
|
+
display: "contents"
|
|
2691
|
+
},
|
|
2692
|
+
$switch: {
|
|
2693
|
+
$ref: "$map/item/hasError"
|
|
2694
|
+
},
|
|
2695
|
+
cases: {
|
|
2696
|
+
true: {
|
|
2697
|
+
$description: '§7.1\'s inline tier: why this value is refused, said AT the control that refuses it and for exactly as long as the value stays bad. `role="alert"` so it is announced when it appears, and a repeat counter from two up — “it refused me again” is otherwise indistinguishable from “the field is still red from last time”.',
|
|
2698
|
+
tagName: "p",
|
|
2699
|
+
attributes: {
|
|
2700
|
+
part: "field-error",
|
|
2701
|
+
role: "alert"
|
|
2702
|
+
},
|
|
2703
|
+
children: [
|
|
2704
|
+
{
|
|
2705
|
+
tagName: "span",
|
|
2706
|
+
attributes: {
|
|
2707
|
+
part: "field-error-text"
|
|
2708
|
+
},
|
|
2709
|
+
textContent: "${$map.item.error}"
|
|
2710
|
+
},
|
|
2711
|
+
{
|
|
2712
|
+
tagName: "span",
|
|
2713
|
+
attributes: {
|
|
2714
|
+
part: "error-count-slot",
|
|
2715
|
+
role: "none"
|
|
2716
|
+
},
|
|
2717
|
+
style: {
|
|
2718
|
+
display: "contents"
|
|
2719
|
+
},
|
|
2720
|
+
$switch: {
|
|
2721
|
+
$ref: "$map/item/hasCount"
|
|
2722
|
+
},
|
|
2723
|
+
cases: {
|
|
2724
|
+
true: {
|
|
2725
|
+
tagName: "span",
|
|
2726
|
+
attributes: {
|
|
2727
|
+
part: "field-error-count"
|
|
2728
|
+
},
|
|
2729
|
+
textContent: "${$map.item.errorCount}"
|
|
2730
|
+
}
|
|
2731
|
+
}
|
|
2732
|
+
}
|
|
2733
|
+
]
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
}
|
|
2737
|
+
]
|
|
2738
|
+
}
|
|
3752
2739
|
}
|
|
3753
|
-
|
|
3754
|
-
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
3759
|
-
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
}
|
|
3763
|
-
function
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
3768
|
-
|
|
3769
|
-
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
}
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
3778
|
-
|
|
3779
|
-
|
|
3780
|
-
|
|
3781
|
-
|
|
3782
|
-
|
|
3783
|
-
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
>
|
|
3799
|
-
`;
|
|
2740
|
+
]
|
|
2741
|
+
};
|
|
2742
|
+
|
|
2743
|
+
// src/surfaces/schema-form.ts
|
|
2744
|
+
registerSurface("schema-form", schema_form_default);
|
|
2745
|
+
function controlIdOf(part, state) {
|
|
2746
|
+
const item = state?.["$map"]?.item;
|
|
2747
|
+
const id = part === "control-host" ? item?.["key"] : part === "cell-control-host" ? item?.["id"] : undefined;
|
|
2748
|
+
return typeof id === "string" ? id : "";
|
|
2749
|
+
}
|
|
2750
|
+
function partOf(def) {
|
|
2751
|
+
const part = typeof def === "string" ? undefined : def.attributes?.["part"];
|
|
2752
|
+
return typeof part === "string" ? part : "";
|
|
2753
|
+
}
|
|
2754
|
+
function createSchemaFormSurface(view, actions, onControlHost) {
|
|
2755
|
+
const host = document.createElement("div");
|
|
2756
|
+
host.style.display = "contents";
|
|
2757
|
+
const scope = reactive({
|
|
2758
|
+
...view,
|
|
2759
|
+
...actions,
|
|
2760
|
+
templatePlaceholder: "${state.…}"
|
|
2761
|
+
});
|
|
2762
|
+
let handle = null;
|
|
2763
|
+
let mounting = false;
|
|
2764
|
+
let disposed = false;
|
|
2765
|
+
function mount() {
|
|
2766
|
+
mounting = true;
|
|
2767
|
+
mountSurface("schema-form", scope, host, {
|
|
2768
|
+
onNodeCreated: (element, _path, def, state) => {
|
|
2769
|
+
if (!(element instanceof HTMLElement)) {
|
|
2770
|
+
return;
|
|
2771
|
+
}
|
|
2772
|
+
const id = controlIdOf(partOf(def), state);
|
|
2773
|
+
if (id !== "") {
|
|
2774
|
+
onControlHost(id, element);
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
}).then((mounted) => {
|
|
2778
|
+
mounting = false;
|
|
2779
|
+
if (disposed) {
|
|
2780
|
+
mounted.dispose();
|
|
2781
|
+
return;
|
|
2782
|
+
}
|
|
2783
|
+
handle = mounted;
|
|
2784
|
+
});
|
|
3800
2785
|
}
|
|
3801
|
-
|
|
3802
|
-
const hint = `Value source: ${label} — click to change`;
|
|
3803
|
-
const choose = (next) => {
|
|
3804
|
-
if (next === mode) {
|
|
3805
|
-
return;
|
|
3806
|
-
}
|
|
3807
|
-
if (mode === "ref" && opts.literalDefault !== undefined && !hasStashedSlotValue(opts.fieldKey, "literal")) {
|
|
3808
|
-
stashSlotValue(opts.fieldKey, "literal", opts.literalDefault);
|
|
3809
|
-
}
|
|
3810
|
-
opts.onChange(switchSlotMode(opts.fieldKey, mode, next, cloneValue(opts.value), opts.seedFor?.(next) ?? defaultForSlotMode(next, opts)));
|
|
3811
|
-
};
|
|
3812
|
-
return html`
|
|
3813
|
-
<sp-action-button
|
|
3814
|
-
id=${id}
|
|
3815
|
-
size="xs"
|
|
3816
|
-
quiet
|
|
3817
|
-
class="dynamic-slot-mode"
|
|
3818
|
-
style=${color}
|
|
3819
|
-
title=${hint}
|
|
3820
|
-
aria-label=${hint}
|
|
3821
|
-
>${label}</sp-action-button
|
|
3822
|
-
>
|
|
3823
|
-
<sp-overlay trigger="${id}@click" placement="bottom-start" type="auto">
|
|
3824
|
-
<sp-popover>
|
|
3825
|
-
<sp-menu size="s" label="Value source">
|
|
3826
|
-
${offered.map((m) => html`
|
|
3827
|
-
<sp-menu-item data-mode=${m} ?selected=${m === mode} @click=${() => choose(m)}
|
|
3828
|
-
>${VALUE_SOURCE_LABELS[m]}
|
|
3829
|
-
<span slot="description">${VALUE_SOURCE_HINTS[m]}</span></sp-menu-item
|
|
3830
|
-
>
|
|
3831
|
-
`)}
|
|
3832
|
-
</sp-menu>
|
|
3833
|
-
</sp-popover>
|
|
3834
|
-
</sp-overlay>
|
|
3835
|
-
`;
|
|
3836
|
-
}
|
|
3837
|
-
function renderDynamicSlot(opts) {
|
|
3838
|
-
const caps = slotCaps(opts.caps);
|
|
3839
|
-
const mode = effectiveSlotMode(opts.fieldKey, opts.value);
|
|
3840
|
-
const widget = mode === "ref" ? renderRefWidget(isRef(opts.value) ? opts.value.$ref : "", opts) : mode === "template" ? renderTemplateWidget(opts.value, opts) : mode === "expression" ? renderExpressionEditor(opts.value.$expression, (node) => opts.onChange({ $expression: node }), {
|
|
3841
|
-
allowEventRef: opts.allowEventRef ?? false,
|
|
3842
|
-
preview: opts.preview ?? null,
|
|
3843
|
-
stateDefs: opts.stateDefs,
|
|
3844
|
-
stateEntries: opts.stateEntries ?? null
|
|
3845
|
-
}) : opts.staticWidget;
|
|
2786
|
+
mount();
|
|
3846
2787
|
return {
|
|
3847
|
-
|
|
3848
|
-
|
|
3849
|
-
|
|
3850
|
-
|
|
3851
|
-
|
|
3852
|
-
|
|
2788
|
+
dispose() {
|
|
2789
|
+
disposed = true;
|
|
2790
|
+
handle?.dispose();
|
|
2791
|
+
handle = null;
|
|
2792
|
+
host.textContent = "";
|
|
2793
|
+
},
|
|
2794
|
+
host,
|
|
2795
|
+
update(next) {
|
|
2796
|
+
scope.fields = next.fields;
|
|
2797
|
+
if (disposed || mounting || handle !== null && host.contains(handle.root)) {
|
|
2798
|
+
return;
|
|
2799
|
+
}
|
|
2800
|
+
handle?.dispose();
|
|
2801
|
+
handle = null;
|
|
2802
|
+
host.textContent = "";
|
|
2803
|
+
mount();
|
|
2804
|
+
}
|
|
3853
2805
|
};
|
|
3854
2806
|
}
|
|
3855
2807
|
|
|
@@ -3858,9 +2810,6 @@ var controlRegistry = new Map;
|
|
|
3858
2810
|
function registerFormControl(name, control) {
|
|
3859
2811
|
controlRegistry.set(name, control);
|
|
3860
2812
|
}
|
|
3861
|
-
function getFormControl(name) {
|
|
3862
|
-
return controlRegistry.get(name);
|
|
3863
|
-
}
|
|
3864
2813
|
var CONTENT_REF = /^#\/content\/([^/]+)$/;
|
|
3865
2814
|
function referenceTarget(schema) {
|
|
3866
2815
|
const match = typeof schema?.$ref === "string" ? CONTENT_REF.exec(schema.$ref) : null;
|
|
@@ -3875,9 +2824,9 @@ function resolveFormEnum(enumDef, ctx, scope) {
|
|
|
3875
2824
|
}
|
|
3876
2825
|
let pointer;
|
|
3877
2826
|
if (enumDef && typeof enumDef === "object") {
|
|
3878
|
-
const
|
|
3879
|
-
if (typeof
|
|
3880
|
-
pointer =
|
|
2827
|
+
const ref = enumDef.$ref;
|
|
2828
|
+
if (typeof ref === "string") {
|
|
2829
|
+
pointer = ref;
|
|
3881
2830
|
}
|
|
3882
2831
|
} else if (typeof enumDef === "string") {
|
|
3883
2832
|
pointer = enumDef;
|
|
@@ -3900,258 +2849,6 @@ function parseNumericField(raw, integer) {
|
|
|
3900
2849
|
}
|
|
3901
2850
|
return integer ? Math.trunc(Number(raw)) : Number(raw);
|
|
3902
2851
|
}
|
|
3903
|
-
function refTextField(key, refVal, onChange) {
|
|
3904
|
-
return html`<sp-textfield
|
|
3905
|
-
size="s"
|
|
3906
|
-
label=${key}
|
|
3907
|
-
placeholder=${key}
|
|
3908
|
-
.value=${live(refVal)}
|
|
3909
|
-
@change=${(e) => {
|
|
3910
|
-
const v = e.target.value.trim();
|
|
3911
|
-
onChange(v ? { $ref: v } : undefined);
|
|
3912
|
-
}}
|
|
3913
|
-
></sp-textfield>`;
|
|
3914
|
-
}
|
|
3915
|
-
function renderInlineField(key, schema, value, onChange, ctx, scope, rerender) {
|
|
3916
|
-
if (isRef(value)) {
|
|
3917
|
-
return refTextField(key, value.$ref, onChange);
|
|
3918
|
-
}
|
|
3919
|
-
const referenceControl = referenceTarget(schema) === null ? undefined : controlRegistry.get("reference");
|
|
3920
|
-
if (referenceControl) {
|
|
3921
|
-
return referenceControl({
|
|
3922
|
-
ctx: ctx ?? NULL_FORM_CONTEXT,
|
|
3923
|
-
key,
|
|
3924
|
-
onChange,
|
|
3925
|
-
rerender,
|
|
3926
|
-
schema,
|
|
3927
|
-
value
|
|
3928
|
-
});
|
|
3929
|
-
}
|
|
3930
|
-
const enumValues = resolveFormEnum(schema.enum, ctx, scope);
|
|
3931
|
-
if (enumValues) {
|
|
3932
|
-
return html`<sp-picker
|
|
3933
|
-
size="s"
|
|
3934
|
-
label=${key}
|
|
3935
|
-
value=${value !== undefined ? String(value) : "__none__"}
|
|
3936
|
-
@change=${(e) => onChange(e.target.value === "__none__" ? undefined : e.target.value)}
|
|
3937
|
-
>
|
|
3938
|
-
<sp-menu-item value="__none__">—</sp-menu-item>
|
|
3939
|
-
${enumValues.map((v) => html`<sp-menu-item value=${v}>${v}</sp-menu-item>`)}
|
|
3940
|
-
</sp-picker>`;
|
|
3941
|
-
}
|
|
3942
|
-
if (schema.type === "boolean") {
|
|
3943
|
-
return html`<sp-switch
|
|
3944
|
-
size="s"
|
|
3945
|
-
?checked=${Boolean(value)}
|
|
3946
|
-
@change=${(e) => onChange(e.target.checked)}
|
|
3947
|
-
>${key}</sp-switch
|
|
3948
|
-
>`;
|
|
3949
|
-
}
|
|
3950
|
-
if (schema.type === "integer" || schema.type === "number") {
|
|
3951
|
-
return html`<sp-number-field
|
|
3952
|
-
size="s"
|
|
3953
|
-
label=${key}
|
|
3954
|
-
.value=${value !== undefined ? value : nothing}
|
|
3955
|
-
step=${schema.type === "integer" ? "1" : nothing}
|
|
3956
|
-
@change=${(e) => {
|
|
3957
|
-
const parsed = parseNumericField(e.target.value, schema.type === "integer");
|
|
3958
|
-
onChange(Number.isNaN(parsed) ? undefined : parsed);
|
|
3959
|
-
}}
|
|
3960
|
-
></sp-number-field>`;
|
|
3961
|
-
}
|
|
3962
|
-
return html`<sp-textfield
|
|
3963
|
-
size="s"
|
|
3964
|
-
label=${key}
|
|
3965
|
-
placeholder=${key}
|
|
3966
|
-
.value=${value ?? ""}
|
|
3967
|
-
@input=${(e) => onChange(e.target.value || undefined)}
|
|
3968
|
-
></sp-textfield>`;
|
|
3969
|
-
}
|
|
3970
|
-
function renderJsonTextField(currentValue, ps, commit) {
|
|
3971
|
-
let debounce;
|
|
3972
|
-
return html`<sp-textfield
|
|
3973
|
-
multiline
|
|
3974
|
-
size="s"
|
|
3975
|
-
style="min-height:40px"
|
|
3976
|
-
.value=${currentValue !== undefined ? JSON.stringify(currentValue, null, 2) : ""}
|
|
3977
|
-
placeholder=${ps.default !== undefined ? JSON.stringify(ps.default) : nothing}
|
|
3978
|
-
@input=${(e) => {
|
|
3979
|
-
clearTimeout(debounce);
|
|
3980
|
-
debounce = setTimeout(() => {
|
|
3981
|
-
try {
|
|
3982
|
-
commit(JSON.parse(e.target.value));
|
|
3983
|
-
} catch {}
|
|
3984
|
-
}, 500);
|
|
3985
|
-
}}
|
|
3986
|
-
></sp-textfield>`;
|
|
3987
|
-
}
|
|
3988
|
-
function renderPropertyControl(prop, ps, value, required, opts, ctx) {
|
|
3989
|
-
const currentValue = value[prop];
|
|
3990
|
-
const commit = (next) => opts.onChange({ [prop]: next });
|
|
3991
|
-
const controlArgs = {
|
|
3992
|
-
ctx,
|
|
3993
|
-
key: prop,
|
|
3994
|
-
onChange: commit,
|
|
3995
|
-
rerender: opts.rerender,
|
|
3996
|
-
schema: ps,
|
|
3997
|
-
value: currentValue
|
|
3998
|
-
};
|
|
3999
|
-
const overrideName = opts.ui?.[prop]?.control;
|
|
4000
|
-
if (overrideName) {
|
|
4001
|
-
const custom = controlRegistry.get(overrideName);
|
|
4002
|
-
if (custom) {
|
|
4003
|
-
return custom(controlArgs);
|
|
4004
|
-
}
|
|
4005
|
-
}
|
|
4006
|
-
if (isRef(currentValue) && isBindableField(ps)) {
|
|
4007
|
-
return refTextField(prop, currentValue.$ref, commit);
|
|
4008
|
-
}
|
|
4009
|
-
if (referenceTarget(ps) !== null) {
|
|
4010
|
-
const referenceControl = controlRegistry.get("reference");
|
|
4011
|
-
if (referenceControl) {
|
|
4012
|
-
return referenceControl(controlArgs);
|
|
4013
|
-
}
|
|
4014
|
-
}
|
|
4015
|
-
const enumValues = resolveFormEnum(opts.ui?.[prop]?.enum ?? ps.enum, ctx, value);
|
|
4016
|
-
if (enumValues) {
|
|
4017
|
-
return html`
|
|
4018
|
-
<sp-picker
|
|
4019
|
-
size="s"
|
|
4020
|
-
value=${currentValue !== undefined ? String(currentValue) : ps.default !== undefined ? String(ps.default) : "__none__"}
|
|
4021
|
-
@change=${(e) => commit(e.target.value === "__none__" ? undefined : e.target.value)}
|
|
4022
|
-
>
|
|
4023
|
-
${!required.has(prop) ? html`<sp-menu-item value="__none__">—</sp-menu-item>` : nothing}
|
|
4024
|
-
${enumValues.map((val) => html`<sp-menu-item value=${val}>${val}</sp-menu-item>`)}
|
|
4025
|
-
</sp-picker>
|
|
4026
|
-
`;
|
|
4027
|
-
}
|
|
4028
|
-
if (ps.type === "boolean") {
|
|
4029
|
-
return html`<sp-checkbox
|
|
4030
|
-
?checked=${currentValue ?? ps.default ?? false}
|
|
4031
|
-
@change=${(e) => commit(e.target.checked)}
|
|
4032
|
-
></sp-checkbox>`;
|
|
4033
|
-
}
|
|
4034
|
-
if (ps.type === "integer" || ps.type === "number") {
|
|
4035
|
-
let debounce2;
|
|
4036
|
-
return html`<sp-number-field
|
|
4037
|
-
size="s"
|
|
4038
|
-
min=${ifDefined(ps.minimum)}
|
|
4039
|
-
max=${ifDefined(ps.maximum)}
|
|
4040
|
-
step=${ps.type === "integer" ? "1" : nothing}
|
|
4041
|
-
.value=${currentValue !== undefined ? currentValue : nothing}
|
|
4042
|
-
placeholder=${ps.default != null ? String(ps.default) : nothing}
|
|
4043
|
-
@change=${(e) => {
|
|
4044
|
-
clearTimeout(debounce2);
|
|
4045
|
-
debounce2 = setTimeout(() => {
|
|
4046
|
-
const parsed = parseNumericField(e.target.value, ps.type === "integer");
|
|
4047
|
-
commit(Number.isNaN(parsed) ? undefined : parsed);
|
|
4048
|
-
}, 400);
|
|
4049
|
-
}}
|
|
4050
|
-
></sp-number-field>`;
|
|
4051
|
-
}
|
|
4052
|
-
if (ps.format === "json-schema") {
|
|
4053
|
-
const hasValue = currentValue && typeof currentValue === "object" && Object.keys(currentValue).length > 0;
|
|
4054
|
-
const cv = currentValue;
|
|
4055
|
-
const isSchemaRef = hasValue && cv.$ref;
|
|
4056
|
-
let debounce2;
|
|
4057
|
-
return html`
|
|
4058
|
-
<div class="schema-param-editor">
|
|
4059
|
-
${hasValue && !isSchemaRef && cv.properties ? html`
|
|
4060
|
-
<div style="display:flex;flex-wrap:wrap;gap:3px;margin-bottom:4px">
|
|
4061
|
-
${Object.entries(cv.properties).map(([k, v]) => html`
|
|
4062
|
-
<span
|
|
4063
|
-
style="background:var(--bg);padding:1px 6px;border-radius:var(--radius);font-size:10px;color:var(--fg-dim)"
|
|
4064
|
-
>${k}: ${v.type ?? "any"}</span
|
|
4065
|
-
>
|
|
4066
|
-
`)}
|
|
4067
|
-
</div>
|
|
4068
|
-
` : nothing}
|
|
4069
|
-
<sp-textfield
|
|
4070
|
-
multiline
|
|
4071
|
-
size="s"
|
|
4072
|
-
style=${styleMap({
|
|
4073
|
-
fontFamily: "monospace",
|
|
4074
|
-
fontSize: "11px",
|
|
4075
|
-
minHeight: hasValue ? "80px" : "40px"
|
|
4076
|
-
})}
|
|
4077
|
-
.value=${currentValue !== undefined ? JSON.stringify(currentValue, null, 2) : ""}
|
|
4078
|
-
placeholder=${ps.description ?? "JSON Schema defining the data shape…"}
|
|
4079
|
-
@input=${(e) => {
|
|
4080
|
-
clearTimeout(debounce2);
|
|
4081
|
-
debounce2 = setTimeout(() => {
|
|
4082
|
-
try {
|
|
4083
|
-
commit(JSON.parse(e.target.value));
|
|
4084
|
-
} catch {}
|
|
4085
|
-
}, 500);
|
|
4086
|
-
}}
|
|
4087
|
-
></sp-textfield>
|
|
4088
|
-
</div>
|
|
4089
|
-
`;
|
|
4090
|
-
}
|
|
4091
|
-
if (ps.type === "array" && ps.items?.type === "object" && ps.items?.properties) {
|
|
4092
|
-
const rows = Array.isArray(currentValue) ? currentValue : [];
|
|
4093
|
-
const itemProps = ps.items.properties;
|
|
4094
|
-
return html`
|
|
4095
|
-
<div class="array-object-field">
|
|
4096
|
-
${rows.map((row, idx) => html`
|
|
4097
|
-
<div
|
|
4098
|
-
class="array-object-row"
|
|
4099
|
-
style="display:flex;gap:4px;align-items:center;margin-bottom:4px"
|
|
4100
|
-
>
|
|
4101
|
-
${Object.entries(itemProps).map(([propKey, propSchema]) => renderInlineField(propKey, propSchema, row[propKey], (val) => {
|
|
4102
|
-
const updated = [...rows];
|
|
4103
|
-
updated[idx] = { ...updated[idx], [propKey]: val };
|
|
4104
|
-
commit(updated);
|
|
4105
|
-
}, ctx, value, opts.rerender))}
|
|
4106
|
-
<sp-action-button
|
|
4107
|
-
quiet
|
|
4108
|
-
size="s"
|
|
4109
|
-
@click=${() => {
|
|
4110
|
-
const updated = rows.filter((_, i) => i !== idx);
|
|
4111
|
-
commit(updated.length > 0 ? updated : undefined);
|
|
4112
|
-
opts.rerender?.();
|
|
4113
|
-
}}
|
|
4114
|
-
>
|
|
4115
|
-
<sp-icon-delete slot="icon"></sp-icon-delete>
|
|
4116
|
-
</sp-action-button>
|
|
4117
|
-
</div>
|
|
4118
|
-
`)}
|
|
4119
|
-
<sp-action-button
|
|
4120
|
-
quiet
|
|
4121
|
-
size="s"
|
|
4122
|
-
@click=${(e) => {
|
|
4123
|
-
e.stopPropagation();
|
|
4124
|
-
const newRow = {};
|
|
4125
|
-
for (const [k, v] of Object.entries(itemProps)) {
|
|
4126
|
-
if (v.default !== undefined) {
|
|
4127
|
-
newRow[k] = v.default;
|
|
4128
|
-
}
|
|
4129
|
-
}
|
|
4130
|
-
commit([...rows, newRow]);
|
|
4131
|
-
opts.rerender?.();
|
|
4132
|
-
}}
|
|
4133
|
-
>+ Add</sp-action-button
|
|
4134
|
-
>
|
|
4135
|
-
</div>
|
|
4136
|
-
`;
|
|
4137
|
-
}
|
|
4138
|
-
if (ps.type === "array" || ps.type === "object") {
|
|
4139
|
-
return renderJsonTextField(currentValue, ps, commit);
|
|
4140
|
-
}
|
|
4141
|
-
let debounce;
|
|
4142
|
-
const ph = ps.default !== undefined ? String(ps.default) : ps.examples?.[0] ?? "";
|
|
4143
|
-
return html`<sp-textfield
|
|
4144
|
-
size="s"
|
|
4145
|
-
style="flex:1"
|
|
4146
|
-
.value=${currentValue ?? ""}
|
|
4147
|
-
placeholder=${ph || nothing}
|
|
4148
|
-
title=${ps.description || nothing}
|
|
4149
|
-
@input=${(e) => {
|
|
4150
|
-
clearTimeout(debounce);
|
|
4151
|
-
debounce = setTimeout(() => commit(e.target.value || undefined), 400);
|
|
4152
|
-
}}
|
|
4153
|
-
></sp-textfield>`;
|
|
4154
|
-
}
|
|
4155
2852
|
function isBindableField(ps) {
|
|
4156
2853
|
return ps.format !== "json-schema" && ps.type !== "object" && ps.type !== "array";
|
|
4157
2854
|
}
|
|
@@ -4161,8 +2858,15 @@ function refSourcesFor(ctx) {
|
|
|
4161
2858
|
...(ctx.params ?? []).map((name) => ({ label: `$params/${name}`, value: `#/$params/${name}` }))
|
|
4162
2859
|
];
|
|
4163
2860
|
}
|
|
2861
|
+
var NONE = "__none__";
|
|
2862
|
+
function asText(value) {
|
|
2863
|
+
if (value === undefined || value === null) {
|
|
2864
|
+
return "";
|
|
2865
|
+
}
|
|
2866
|
+
return typeof value === "object" ? JSON.stringify(value, null, 2) : String(value);
|
|
2867
|
+
}
|
|
4164
2868
|
function validateFieldValue(schema, value, isRequired) {
|
|
4165
|
-
if (
|
|
2869
|
+
if (isRef2(value)) {
|
|
4166
2870
|
return "";
|
|
4167
2871
|
}
|
|
4168
2872
|
const empty = value === undefined || value === null || value === "";
|
|
@@ -4199,39 +2903,603 @@ function validateFieldValue(schema, value, isRequired) {
|
|
|
4199
2903
|
}
|
|
4200
2904
|
return "";
|
|
4201
2905
|
}
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
}
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
2906
|
+
var JSON_COMMIT_MS = 500;
|
|
2907
|
+
var TEXT_COMMIT_MS = 400;
|
|
2908
|
+
var forms = new Map;
|
|
2909
|
+
function mountSchemaForm(formKey, schema, value, opts) {
|
|
2910
|
+
sweep(formKey);
|
|
2911
|
+
let controller = forms.get(formKey);
|
|
2912
|
+
if (!controller) {
|
|
2913
|
+
controller = createController();
|
|
2914
|
+
forms.set(formKey, controller);
|
|
2915
|
+
}
|
|
2916
|
+
controller.update(schema, value, opts);
|
|
2917
|
+
return controller.host;
|
|
2918
|
+
}
|
|
2919
|
+
function sweep(exceptKey) {
|
|
2920
|
+
for (const [key, controller] of forms) {
|
|
2921
|
+
if (key !== exceptKey && !controller.host.isConnected) {
|
|
2922
|
+
controller.dispose();
|
|
2923
|
+
forms.delete(key);
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
}
|
|
2927
|
+
function createController() {
|
|
2928
|
+
let schema = {};
|
|
2929
|
+
let value = {};
|
|
2930
|
+
let opts = { onChange: () => {} };
|
|
2931
|
+
let ctx = NULL_FORM_CONTEXT;
|
|
2932
|
+
const plans = new Map;
|
|
2933
|
+
const timers = new Map;
|
|
2934
|
+
const controlHosts = new Map;
|
|
2935
|
+
const mountedControls = new Map;
|
|
2936
|
+
let sourceMenu = null;
|
|
2937
|
+
const later = (key, ms, run) => {
|
|
2938
|
+
clearTimeout(timers.get(key));
|
|
2939
|
+
if (ms <= 0) {
|
|
2940
|
+
run();
|
|
2941
|
+
return;
|
|
2942
|
+
}
|
|
2943
|
+
timers.set(key, setTimeout(run, ms));
|
|
2944
|
+
};
|
|
2945
|
+
const now = (key, run) => {
|
|
2946
|
+
clearTimeout(timers.get(key));
|
|
2947
|
+
timers.delete(key);
|
|
2948
|
+
run();
|
|
2949
|
+
};
|
|
2950
|
+
const patch = (prop, next) => {
|
|
2951
|
+
opts.onChange({ [prop]: next });
|
|
2952
|
+
};
|
|
2953
|
+
const write = (plan, raw) => {
|
|
2954
|
+
if (plan.commit === "none") {
|
|
2955
|
+
return;
|
|
2956
|
+
}
|
|
2957
|
+
switch (plan.commit) {
|
|
2958
|
+
case "ref": {
|
|
2959
|
+
const trimmed = raw.trim();
|
|
2960
|
+
patch(plan.prop, trimmed ? { $ref: trimmed } : undefined);
|
|
2961
|
+
return;
|
|
2962
|
+
}
|
|
2963
|
+
case "template": {
|
|
2964
|
+
patch(plan.prop, raw);
|
|
2965
|
+
return;
|
|
2966
|
+
}
|
|
2967
|
+
case "select": {
|
|
2968
|
+
patch(plan.prop, raw === NONE ? undefined : raw);
|
|
2969
|
+
return;
|
|
2970
|
+
}
|
|
2971
|
+
case "number": {
|
|
2972
|
+
const parsed = parseNumericField(raw, plan.schema.type === "integer");
|
|
2973
|
+
patch(plan.prop, Number.isNaN(parsed) ? undefined : parsed);
|
|
2974
|
+
return;
|
|
2975
|
+
}
|
|
2976
|
+
case "json": {
|
|
2977
|
+
try {
|
|
2978
|
+
patch(plan.prop, JSON.parse(raw));
|
|
2979
|
+
} catch {}
|
|
2980
|
+
return;
|
|
2981
|
+
}
|
|
2982
|
+
default: {
|
|
2983
|
+
patch(plan.prop, raw || undefined);
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
2986
|
+
};
|
|
2987
|
+
const cellRows = (plan) => {
|
|
2988
|
+
const copies = [];
|
|
2989
|
+
for (const item of plan.rows ?? []) {
|
|
2990
|
+
copies.push({ ...item });
|
|
2991
|
+
}
|
|
2992
|
+
return copies;
|
|
2993
|
+
};
|
|
2994
|
+
const actions = {
|
|
2995
|
+
addRow: (field) => {
|
|
2996
|
+
const plan = plans.get(field);
|
|
2997
|
+
if (!plan?.itemProps) {
|
|
2998
|
+
return;
|
|
2999
|
+
}
|
|
3000
|
+
const seed = {};
|
|
3001
|
+
for (const [k, v] of Object.entries(plan.itemProps)) {
|
|
3002
|
+
if (v.default !== undefined) {
|
|
3003
|
+
seed[k] = v.default;
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
patch(field, [...cellRows(plan), seed]);
|
|
3007
|
+
opts.rerender?.();
|
|
3008
|
+
},
|
|
3009
|
+
commit: (key, raw) => {
|
|
3010
|
+
const plan = plans.get(key);
|
|
3011
|
+
if (plan) {
|
|
3012
|
+
now(key, () => write(plan, raw));
|
|
3013
|
+
}
|
|
3014
|
+
},
|
|
3015
|
+
commitChecked: (key, checked) => {
|
|
3016
|
+
const plan = plans.get(key);
|
|
3017
|
+
if (plan) {
|
|
3018
|
+
patch(plan.prop, checked);
|
|
3019
|
+
}
|
|
3020
|
+
},
|
|
3021
|
+
edit: (key, raw) => {
|
|
3022
|
+
const plan = plans.get(key);
|
|
3023
|
+
if (plan && plan.debounceMs > 0) {
|
|
3024
|
+
later(key, plan.debounceMs, () => write(plan, raw));
|
|
3025
|
+
}
|
|
3026
|
+
},
|
|
3027
|
+
editCell: (field, row, cell, raw) => {
|
|
3028
|
+
writeCell(field, row, cell, raw);
|
|
3029
|
+
},
|
|
3030
|
+
editCellChecked: (field, row, cell, checked) => {
|
|
3031
|
+
writeCell(field, row, cell, checked);
|
|
3032
|
+
},
|
|
3033
|
+
editJson: (key, raw) => {
|
|
3034
|
+
const plan = plans.get(key);
|
|
3035
|
+
if (plan) {
|
|
3036
|
+
later(key, JSON_COMMIT_MS, () => write(plan, raw));
|
|
3037
|
+
}
|
|
3038
|
+
},
|
|
3039
|
+
pickSource: (key, anchor) => {
|
|
3040
|
+
openSourceMenu(key, anchor);
|
|
3041
|
+
},
|
|
3042
|
+
removeRow: (field, row) => {
|
|
3043
|
+
const plan = plans.get(field);
|
|
3044
|
+
if (!plan) {
|
|
3045
|
+
return;
|
|
3046
|
+
}
|
|
3047
|
+
const idx = Number(row);
|
|
3048
|
+
const kept = cellRows(plan).filter((_, i) => i !== idx);
|
|
3049
|
+
patch(field, kept.length > 0 ? kept : undefined);
|
|
3050
|
+
opts.rerender?.();
|
|
3051
|
+
}
|
|
3052
|
+
};
|
|
3053
|
+
function writeCell(field, row, cell, next) {
|
|
3054
|
+
const plan = plans.get(field);
|
|
3055
|
+
if (!plan?.itemProps) {
|
|
3056
|
+
return;
|
|
3057
|
+
}
|
|
3058
|
+
const idx = Number(row);
|
|
3059
|
+
const rows = cellRows(plan);
|
|
3060
|
+
const target = rows[idx];
|
|
3061
|
+
if (!target) {
|
|
3062
|
+
return;
|
|
3063
|
+
}
|
|
3064
|
+
const cellSchema = plan.itemProps[cell];
|
|
3065
|
+
rows[idx] = {
|
|
3066
|
+
...target,
|
|
3067
|
+
[cell]: coerceCell(cellSchema, next, plan.refCells?.has(`${field}/${row}/${cell}`) ?? false)
|
|
3068
|
+
};
|
|
3069
|
+
patch(field, rows);
|
|
3070
|
+
}
|
|
3071
|
+
function coerceCell(cellSchema, next, isPointer) {
|
|
3072
|
+
if (typeof next === "boolean") {
|
|
3073
|
+
return next;
|
|
3074
|
+
}
|
|
3075
|
+
const raw = String(next);
|
|
3076
|
+
if (isPointer) {
|
|
3077
|
+
const trimmed = raw.trim();
|
|
3078
|
+
return trimmed ? { $ref: trimmed } : undefined;
|
|
3079
|
+
}
|
|
3080
|
+
if (cellSchema?.type === "integer" || cellSchema?.type === "number") {
|
|
3081
|
+
const parsed = parseNumericField(raw, cellSchema.type === "integer");
|
|
3082
|
+
return Number.isNaN(parsed) ? undefined : parsed;
|
|
3083
|
+
}
|
|
3084
|
+
if (raw === NONE || raw === "") {
|
|
3085
|
+
return;
|
|
3086
|
+
}
|
|
3087
|
+
return raw;
|
|
3088
|
+
}
|
|
3089
|
+
function openSourceMenu(key, anchor) {
|
|
3090
|
+
const plan = plans.get(key);
|
|
3091
|
+
if (!plan?.ladder) {
|
|
3092
|
+
return;
|
|
3093
|
+
}
|
|
3094
|
+
const { fieldKey, mode, offered, sources } = plan.ladder;
|
|
3095
|
+
sourceMenu?.close();
|
|
3096
|
+
sourceMenu = openMenu2({
|
|
3097
|
+
label: "Value source",
|
|
3098
|
+
onClosed: () => {
|
|
3099
|
+
sourceMenu = null;
|
|
3100
|
+
},
|
|
3101
|
+
opener: anchor,
|
|
3102
|
+
region: "value-source",
|
|
3103
|
+
rows: offered.map((rung) => ({
|
|
3104
|
+
checked: rung === mode ? "true" : "false",
|
|
3105
|
+
destructive: false,
|
|
3106
|
+
disabled: false,
|
|
3107
|
+
dividerAbove: false,
|
|
3108
|
+
id: rung,
|
|
3109
|
+
title: VALUE_SOURCE_LABELS[rung]
|
|
3110
|
+
})),
|
|
3111
|
+
run: (id) => {
|
|
3112
|
+
const next = id;
|
|
3113
|
+
if (next === mode) {
|
|
3114
|
+
return;
|
|
3115
|
+
}
|
|
3116
|
+
const literalDefault = plan.schema.default;
|
|
3117
|
+
if (mode === "ref" && literalDefault !== undefined && !hasStashedSlotValue(fieldKey, "literal")) {
|
|
3118
|
+
stashSlotValue(fieldKey, "literal", literalDefault);
|
|
3119
|
+
}
|
|
3120
|
+
patch(plan.prop, switchSlotMode(fieldKey, mode, next, cloneValue2(value[plan.prop]), slotModeSeed(next, { extraSignals: sources, literalDefault, stateDefs: [] })));
|
|
3121
|
+
}
|
|
4229
3122
|
});
|
|
3123
|
+
}
|
|
3124
|
+
function controlFor(id) {
|
|
3125
|
+
const plan = plans.get(id);
|
|
3126
|
+
if (plan?.control) {
|
|
3127
|
+
return {
|
|
3128
|
+
args: {
|
|
3129
|
+
ctx,
|
|
3130
|
+
key: plan.prop,
|
|
3131
|
+
onChange: (next) => patch(plan.prop, next),
|
|
3132
|
+
rerender: opts.rerender,
|
|
3133
|
+
schema: plan.schema,
|
|
3134
|
+
value: value[plan.prop]
|
|
3135
|
+
},
|
|
3136
|
+
control: plan.control
|
|
3137
|
+
};
|
|
3138
|
+
}
|
|
3139
|
+
for (const owner of plans.values()) {
|
|
3140
|
+
const entry = owner.cellControls?.get(id);
|
|
3141
|
+
if (!entry) {
|
|
3142
|
+
continue;
|
|
3143
|
+
}
|
|
3144
|
+
const row = (owner.rows ?? [])[Number(entry.row)] ?? {};
|
|
3145
|
+
return {
|
|
3146
|
+
args: {
|
|
3147
|
+
ctx,
|
|
3148
|
+
key: entry.key,
|
|
3149
|
+
onChange: (next) => writeCell(owner.prop, entry.row, entry.key, next),
|
|
3150
|
+
rerender: opts.rerender,
|
|
3151
|
+
schema: entry.schema,
|
|
3152
|
+
value: row[entry.key]
|
|
3153
|
+
},
|
|
3154
|
+
control: entry.control
|
|
3155
|
+
};
|
|
3156
|
+
}
|
|
3157
|
+
return;
|
|
3158
|
+
}
|
|
3159
|
+
function paintControl(id, host) {
|
|
3160
|
+
const found = controlFor(id);
|
|
3161
|
+
if (!found) {
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
const standing = mountedControls.get(id);
|
|
3165
|
+
if (standing?.host === host) {
|
|
3166
|
+
standing.handle.update(found.args);
|
|
3167
|
+
return;
|
|
3168
|
+
}
|
|
3169
|
+
standing?.handle.dispose();
|
|
3170
|
+
mountedControls.set(id, { handle: found.control.mount(host, found.args), host });
|
|
3171
|
+
}
|
|
3172
|
+
function sweepMountedControls() {
|
|
3173
|
+
for (const [id, standing] of mountedControls) {
|
|
3174
|
+
if (!controlFor(id)) {
|
|
3175
|
+
standing.handle.dispose();
|
|
3176
|
+
mountedControls.delete(id);
|
|
3177
|
+
}
|
|
3178
|
+
}
|
|
3179
|
+
}
|
|
3180
|
+
function paintControls() {
|
|
3181
|
+
sweepMountedControls();
|
|
3182
|
+
for (const [id, host] of controlHosts) {
|
|
3183
|
+
paintControl(id, host);
|
|
3184
|
+
}
|
|
3185
|
+
}
|
|
3186
|
+
const surface = createSchemaFormSurface({ fields: [] }, actions, (id, host) => {
|
|
3187
|
+
controlHosts.set(id, host);
|
|
3188
|
+
paintControl(id, host);
|
|
4230
3189
|
});
|
|
4231
|
-
return
|
|
3190
|
+
return {
|
|
3191
|
+
dispose() {
|
|
3192
|
+
for (const timer of timers.values()) {
|
|
3193
|
+
clearTimeout(timer);
|
|
3194
|
+
}
|
|
3195
|
+
timers.clear();
|
|
3196
|
+
sourceMenu?.close();
|
|
3197
|
+
sourceMenu = null;
|
|
3198
|
+
for (const standing of mountedControls.values()) {
|
|
3199
|
+
standing.handle.dispose();
|
|
3200
|
+
}
|
|
3201
|
+
mountedControls.clear();
|
|
3202
|
+
controlHosts.clear();
|
|
3203
|
+
plans.clear();
|
|
3204
|
+
surface.dispose();
|
|
3205
|
+
},
|
|
3206
|
+
host: surface.host,
|
|
3207
|
+
update(nextSchema, nextValue, nextOpts) {
|
|
3208
|
+
schema = nextSchema;
|
|
3209
|
+
value = nextValue;
|
|
3210
|
+
opts = nextOpts;
|
|
3211
|
+
ctx = nextOpts.context ?? NULL_FORM_CONTEXT;
|
|
3212
|
+
plans.clear();
|
|
3213
|
+
surface.update({ fields: deriveFields(schema, value, opts, ctx, plans) });
|
|
3214
|
+
paintControls();
|
|
3215
|
+
}
|
|
3216
|
+
};
|
|
3217
|
+
}
|
|
3218
|
+
function deriveFields(schema, value, opts, ctx, plans) {
|
|
3219
|
+
const required = new Set(schema.required);
|
|
3220
|
+
const sources = refSourcesFor(ctx);
|
|
3221
|
+
return Object.entries(schema.properties ?? {}).map(([prop, ps]) => deriveField(prop, ps, {
|
|
3222
|
+
ctx,
|
|
3223
|
+
opts,
|
|
3224
|
+
plans,
|
|
3225
|
+
required: required.has(prop),
|
|
3226
|
+
sources,
|
|
3227
|
+
value
|
|
3228
|
+
}));
|
|
3229
|
+
}
|
|
3230
|
+
function deriveField(prop, ps, args) {
|
|
3231
|
+
const { ctx, opts, plans, required, sources, value } = args;
|
|
3232
|
+
const current = value[prop];
|
|
3233
|
+
const plan = { commit: "text", debounceMs: TEXT_COMMIT_MS, prop, schema: ps };
|
|
3234
|
+
plans.set(prop, plan);
|
|
3235
|
+
const error = opts.errors?.[prop] || validateFieldValue(ps, current, Boolean(opts.showRequired) && required);
|
|
3236
|
+
const count = opts.errorCounts?.[prop];
|
|
3237
|
+
const overrideName = opts.ui?.[prop]?.control;
|
|
3238
|
+
const override = overrideName ? controlRegistry.get(overrideName) : undefined;
|
|
3239
|
+
const laddered = sources.length > 0 && isBindableField(ps) && !overrideName;
|
|
3240
|
+
const row = {
|
|
3241
|
+
addLabel: `Add ${prop}`,
|
|
3242
|
+
checked: false,
|
|
3243
|
+
chips: [],
|
|
3244
|
+
description: ps.description ?? "",
|
|
3245
|
+
error,
|
|
3246
|
+
errorCount: count !== undefined && count > 1 ? `×${count}` : "",
|
|
3247
|
+
hasChip: false,
|
|
3248
|
+
hasChips: false,
|
|
3249
|
+
hasCount: count !== undefined && count > 1,
|
|
3250
|
+
hasError: error !== "",
|
|
3251
|
+
hasOptions: false,
|
|
3252
|
+
invalid: error !== "",
|
|
3253
|
+
key: prop,
|
|
3254
|
+
kind: "text",
|
|
3255
|
+
label: prop,
|
|
3256
|
+
max: "",
|
|
3257
|
+
min: "",
|
|
3258
|
+
mono: false,
|
|
3259
|
+
multiline: false,
|
|
3260
|
+
options: [],
|
|
3261
|
+
placeholder: "",
|
|
3262
|
+
prop: ps.name || prop,
|
|
3263
|
+
required,
|
|
3264
|
+
rows: [],
|
|
3265
|
+
source: "fixed",
|
|
3266
|
+
sourceHint: "",
|
|
3267
|
+
sourceLabel: "",
|
|
3268
|
+
sourceLocked: false,
|
|
3269
|
+
sourceName: "",
|
|
3270
|
+
step: "",
|
|
3271
|
+
value: ""
|
|
3272
|
+
};
|
|
3273
|
+
if (laddered) {
|
|
3274
|
+
const fieldKey = `${ctx.fieldKeyPrefix ?? ""}.${prop}`;
|
|
3275
|
+
const mode = effectiveSlotMode(fieldKey, current);
|
|
3276
|
+
const offered = slotCaps({ schema: configFieldSchema(ps) });
|
|
3277
|
+
plan.ladder = { fieldKey, mode, offered, sources };
|
|
3278
|
+
row.hasChip = true;
|
|
3279
|
+
row.sourceLabel = VALUE_SOURCE_LABELS[mode];
|
|
3280
|
+
row.sourceHint = VALUE_SOURCE_HINTS[mode];
|
|
3281
|
+
row.source = mode === "literal" ? "fixed" : "bound";
|
|
3282
|
+
row.sourceLocked = !offered.some((m) => m !== mode);
|
|
3283
|
+
row.sourceName = row.sourceLocked ? `Value source: ${row.sourceLabel} (no other source available here)` : `Value source: ${row.sourceLabel} — click to change`;
|
|
3284
|
+
if (mode === "ref") {
|
|
3285
|
+
plan.commit = "ref";
|
|
3286
|
+
plan.debounceMs = 0;
|
|
3287
|
+
row.kind = "pointer";
|
|
3288
|
+
row.value = isRef2(current) ? current.$ref : "";
|
|
3289
|
+
row.options = sources.map((s) => ({ label: s.label, value: s.value }));
|
|
3290
|
+
row.hasOptions = row.options.length > 0;
|
|
3291
|
+
return row;
|
|
3292
|
+
}
|
|
3293
|
+
if (mode === "template") {
|
|
3294
|
+
plan.commit = "template";
|
|
3295
|
+
plan.debounceMs = 0;
|
|
3296
|
+
row.kind = "template";
|
|
3297
|
+
row.value = String(current ?? "");
|
|
3298
|
+
return row;
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
if (override) {
|
|
3302
|
+
plan.commit = "none";
|
|
3303
|
+
plan.control = override;
|
|
3304
|
+
row.kind = "control";
|
|
3305
|
+
return row;
|
|
3306
|
+
}
|
|
3307
|
+
if (isRef2(current) && isBindableField(ps)) {
|
|
3308
|
+
plan.commit = "ref";
|
|
3309
|
+
plan.debounceMs = 0;
|
|
3310
|
+
row.kind = "text";
|
|
3311
|
+
row.mono = true;
|
|
3312
|
+
row.value = current.$ref;
|
|
3313
|
+
row.placeholder = prop;
|
|
3314
|
+
return row;
|
|
3315
|
+
}
|
|
3316
|
+
if (referenceTarget(ps) !== null) {
|
|
3317
|
+
const reference = controlRegistry.get("reference");
|
|
3318
|
+
if (reference) {
|
|
3319
|
+
plan.commit = "none";
|
|
3320
|
+
plan.control = reference;
|
|
3321
|
+
row.kind = "control";
|
|
3322
|
+
return row;
|
|
3323
|
+
}
|
|
3324
|
+
}
|
|
3325
|
+
const enumValues = resolveFormEnum(opts.ui?.[prop]?.enum ?? ps.enum, ctx, value);
|
|
3326
|
+
if (enumValues) {
|
|
3327
|
+
plan.commit = "select";
|
|
3328
|
+
plan.debounceMs = 0;
|
|
3329
|
+
row.kind = "select";
|
|
3330
|
+
row.value = current !== undefined ? String(current) : ps.default !== undefined ? String(ps.default) : NONE;
|
|
3331
|
+
row.options = [
|
|
3332
|
+
...required ? [] : [{ label: "—", value: NONE }],
|
|
3333
|
+
...enumValues.map((v) => ({ label: v, value: v }))
|
|
3334
|
+
];
|
|
3335
|
+
return row;
|
|
3336
|
+
}
|
|
3337
|
+
if (ps.type === "boolean") {
|
|
3338
|
+
plan.commit = "none";
|
|
3339
|
+
row.kind = "checkbox";
|
|
3340
|
+
row.checked = Boolean(current ?? ps.default ?? false);
|
|
3341
|
+
return row;
|
|
3342
|
+
}
|
|
3343
|
+
if (ps.type === "integer" || ps.type === "number") {
|
|
3344
|
+
plan.commit = "number";
|
|
3345
|
+
plan.debounceMs = 0;
|
|
3346
|
+
row.kind = "number";
|
|
3347
|
+
row.value = current !== undefined ? String(current) : "";
|
|
3348
|
+
row.placeholder = ps.default != null ? String(ps.default) : "";
|
|
3349
|
+
row.min = ps.minimum !== undefined ? String(ps.minimum) : "";
|
|
3350
|
+
row.max = ps.maximum !== undefined ? String(ps.maximum) : "";
|
|
3351
|
+
row.step = ps.type === "integer" ? "1" : "";
|
|
3352
|
+
return row;
|
|
3353
|
+
}
|
|
3354
|
+
if (ps.format === "json-schema") {
|
|
3355
|
+
plan.commit = "json";
|
|
3356
|
+
row.kind = "json";
|
|
3357
|
+
row.value = current !== undefined ? JSON.stringify(current, null, 2) : "";
|
|
3358
|
+
row.placeholder = ps.description ?? "JSON Schema defining the data shape…";
|
|
3359
|
+
row.chips = shapeChips(current);
|
|
3360
|
+
row.hasChips = row.chips.length > 0;
|
|
3361
|
+
return row;
|
|
3362
|
+
}
|
|
3363
|
+
if (ps.type === "array" && ps.items?.type === "object" && ps.items.properties) {
|
|
3364
|
+
return arrayOfObjects(prop, ps.items.properties, args, plan, row);
|
|
3365
|
+
}
|
|
3366
|
+
if (ps.type === "array" || ps.type === "object") {
|
|
3367
|
+
plan.commit = "json";
|
|
3368
|
+
row.kind = "json";
|
|
3369
|
+
row.value = current !== undefined ? JSON.stringify(current, null, 2) : "";
|
|
3370
|
+
row.placeholder = ps.default !== undefined ? JSON.stringify(ps.default) : "";
|
|
3371
|
+
return row;
|
|
3372
|
+
}
|
|
3373
|
+
row.kind = "text";
|
|
3374
|
+
row.value = asText(current);
|
|
3375
|
+
row.placeholder = ps.default !== undefined ? String(ps.default) : ps.examples?.[0] ?? "";
|
|
3376
|
+
return row;
|
|
3377
|
+
}
|
|
3378
|
+
function shapeChips(current) {
|
|
3379
|
+
if (!current || typeof current !== "object") {
|
|
3380
|
+
return [];
|
|
3381
|
+
}
|
|
3382
|
+
const held = current;
|
|
3383
|
+
if (held.$ref || typeof held.properties !== "object" || held.properties === null) {
|
|
3384
|
+
return [];
|
|
3385
|
+
}
|
|
3386
|
+
return Object.entries(held.properties).map(([key, spec]) => ({ key, label: `${key}: ${String(spec.type ?? "any")}` }));
|
|
3387
|
+
}
|
|
3388
|
+
function arrayOfObjects(prop, itemProps, args, plan, row) {
|
|
3389
|
+
const held = args.value[prop];
|
|
3390
|
+
const rows = Array.isArray(held) ? held : [];
|
|
3391
|
+
plan.commit = "none";
|
|
3392
|
+
plan.itemProps = itemProps;
|
|
3393
|
+
plan.rows = rows;
|
|
3394
|
+
plan.cellControls = new Map;
|
|
3395
|
+
plan.refCells = new Set;
|
|
3396
|
+
row.kind = "rows";
|
|
3397
|
+
row.rows = rows.map((item, idx) => deriveRow(prop, String(idx), item, itemProps, args, plan));
|
|
3398
|
+
return row;
|
|
3399
|
+
}
|
|
3400
|
+
function deriveRow(prop, key, item, itemProps, args, plan) {
|
|
3401
|
+
return {
|
|
3402
|
+
cells: Object.entries(itemProps).map(([cellKey, cellSchema]) => deriveCell(prop, key, cellKey, cellSchema, item[cellKey], args, plan)),
|
|
3403
|
+
field: prop,
|
|
3404
|
+
key,
|
|
3405
|
+
removeLabel: `Remove ${prop} ${Number(key) + 1}`
|
|
3406
|
+
};
|
|
3407
|
+
}
|
|
3408
|
+
function deriveCell(prop, rowKey, cellKey, cellSchema, held, args, plan) {
|
|
3409
|
+
const id = `${prop}/${rowKey}/${cellKey}`;
|
|
3410
|
+
const cell = {
|
|
3411
|
+
checked: false,
|
|
3412
|
+
field: prop,
|
|
3413
|
+
id,
|
|
3414
|
+
key: cellKey,
|
|
3415
|
+
kind: "text",
|
|
3416
|
+
label: cellKey,
|
|
3417
|
+
options: [],
|
|
3418
|
+
row: rowKey,
|
|
3419
|
+
value: ""
|
|
3420
|
+
};
|
|
3421
|
+
if (isRef2(held)) {
|
|
3422
|
+
cell.value = held.$ref;
|
|
3423
|
+
plan.refCells?.add(id);
|
|
3424
|
+
return cell;
|
|
3425
|
+
}
|
|
3426
|
+
if (referenceTarget(cellSchema) !== null) {
|
|
3427
|
+
const reference = controlRegistry.get("reference");
|
|
3428
|
+
if (reference) {
|
|
3429
|
+
cell.kind = "control";
|
|
3430
|
+
plan.cellControls?.set(id, {
|
|
3431
|
+
control: reference,
|
|
3432
|
+
key: cellKey,
|
|
3433
|
+
row: rowKey,
|
|
3434
|
+
schema: cellSchema
|
|
3435
|
+
});
|
|
3436
|
+
return cell;
|
|
3437
|
+
}
|
|
3438
|
+
}
|
|
3439
|
+
const enumValues = resolveFormEnum(cellSchema.enum, args.ctx, args.value);
|
|
3440
|
+
if (enumValues) {
|
|
3441
|
+
cell.kind = "select";
|
|
3442
|
+
cell.value = held !== undefined ? String(held) : NONE;
|
|
3443
|
+
cell.options = [
|
|
3444
|
+
{ label: "—", value: NONE },
|
|
3445
|
+
...enumValues.map((v) => ({ label: v, value: v }))
|
|
3446
|
+
];
|
|
3447
|
+
return cell;
|
|
3448
|
+
}
|
|
3449
|
+
if (cellSchema.type === "boolean") {
|
|
3450
|
+
cell.kind = "checkbox";
|
|
3451
|
+
cell.checked = Boolean(held);
|
|
3452
|
+
return cell;
|
|
3453
|
+
}
|
|
3454
|
+
if (cellSchema.type === "integer" || cellSchema.type === "number") {
|
|
3455
|
+
cell.kind = "number";
|
|
3456
|
+
cell.value = held !== undefined ? String(held) : "";
|
|
3457
|
+
return cell;
|
|
3458
|
+
}
|
|
3459
|
+
cell.value = asText(held);
|
|
3460
|
+
return cell;
|
|
3461
|
+
}
|
|
3462
|
+
|
|
3463
|
+
// src/services/context-resolver.ts
|
|
3464
|
+
var PARAM_SEGMENT = /^\{@(\w+)\}$/;
|
|
3465
|
+
function resolveContextPointer(pointer, opts) {
|
|
3466
|
+
if (!pointer.startsWith("#/$context/")) {
|
|
3467
|
+
return;
|
|
3468
|
+
}
|
|
3469
|
+
const segments = [];
|
|
3470
|
+
for (const raw of pointer.slice("#/$context/".length).split("/")) {
|
|
3471
|
+
const match = raw.match(PARAM_SEGMENT);
|
|
3472
|
+
if (match) {
|
|
3473
|
+
const value = opts.scope?.[match[1]];
|
|
3474
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
3475
|
+
return;
|
|
3476
|
+
}
|
|
3477
|
+
const substituted = String(value);
|
|
3478
|
+
if (!substituted) {
|
|
3479
|
+
return;
|
|
3480
|
+
}
|
|
3481
|
+
segments.push(substituted);
|
|
3482
|
+
} else {
|
|
3483
|
+
segments.push(raw);
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
if (segments[0] === "$formats") {
|
|
3487
|
+
if (segments.length !== 1) {
|
|
3488
|
+
return;
|
|
3489
|
+
}
|
|
3490
|
+
return (opts.formats ?? []).map((format) => format.name);
|
|
3491
|
+
}
|
|
3492
|
+
let node = opts.projectConfig;
|
|
3493
|
+
for (const segment of segments) {
|
|
3494
|
+
if (node === null || typeof node !== "object") {
|
|
3495
|
+
return;
|
|
3496
|
+
}
|
|
3497
|
+
node = node[segment];
|
|
3498
|
+
}
|
|
3499
|
+
return node;
|
|
4232
3500
|
}
|
|
4233
3501
|
|
|
4234
|
-
export {
|
|
3502
|
+
export { REDIRECT_STATUSES, VALUE_SOURCE_LABELS, VALUE_SOURCE_HINTS, slotMode, TAG_EXPRESSION_OPERATORS, capsForPosition, slotCaps, stashSlotValue, hasStashedSlotValue, effectiveSlotMode, slotModeSeed, switchSlotMode, registerFormControl, referenceTarget, mountSchemaForm, resolveContextPointer };
|
|
4235
3503
|
|
|
4236
|
-
//# debugId=
|
|
4237
|
-
//# sourceMappingURL=studio-
|
|
3504
|
+
//# debugId=427B709FE510BC3D64756E2164756E21
|
|
3505
|
+
//# sourceMappingURL=studio-57mf66w2.js.map
|