@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
|
@@ -10,19 +10,32 @@
|
|
|
10
10
|
* `studio-ui-guidelines.md` §8.6 is normative about the shape: **ONE shape.** The bar does not
|
|
11
11
|
* rearrange itself when the author starts typing, and a control that cannot act is DISABLED, not
|
|
12
12
|
* removed — a toolbar whose buttons move under the cursor is worse than one with a greyed button.
|
|
13
|
-
* That is why the parent selector, the drag handle and every verb render unconditionally
|
|
14
|
-
* their disabled state (and its one-sentence reason) from the record's own `enablement`.
|
|
13
|
+
* That is why the parent selector, the badge, the drag handle and every verb render unconditionally
|
|
14
|
+
* and take their disabled state (and its one-sentence reason) from the record's own `enablement`.
|
|
15
|
+
*
|
|
16
|
+
* **The markup left this file.** The bar is `surfaces/block-action-bar.json`, a Jx document over
|
|
17
|
+
* the UI kit (studio-ui-guidelines.md §1, §6, §9.3), mounted once by
|
|
18
|
+
* `surfaces/block-action-bar.ts`; what stays here is every DECISION — which records are placed,
|
|
19
|
+
* whether each can act, where the bar belongs against the selection rect, when it steps aside, and
|
|
20
|
+
* what a press does. The toolbar role, the roving caret and the ←/→/Home/End contract are
|
|
21
|
+
* `jx-action-group`'s, so the hand-written `[data-toolbar-item]` ring is gone with the template
|
|
22
|
+
* that needed it; the `⋮` menu is the kit menu every other Studio surface opens (§12.5), so there
|
|
23
|
+
* is no second list of actions.
|
|
15
24
|
*
|
|
16
25
|
* This module is also the single definition site for the structural selection verbs — move
|
|
17
26
|
* up/down/in/out and the component pair — because their implementations live here.
|
|
18
27
|
* {@link registerSelectionCommands} is what a host bootstrap calls to put them in the app-wide
|
|
19
28
|
* registry; until one exists, {@link selectionCommandRegistry} builds the surface's own.
|
|
29
|
+
*
|
|
30
|
+
* A record's `icon` is a name in the UI KIT's manifest, so `commandIconMap` — the lit-side Spectrum
|
|
31
|
+
* alias every button used to be drawn through — is gone with the template that needed it.
|
|
32
|
+
* `jx-action-button` takes the name; nothing looks a glyph up any more.
|
|
33
|
+
*
|
|
34
|
+
* @docs studio/interface/canvas
|
|
20
35
|
*/
|
|
21
36
|
|
|
22
|
-
import { html, render as litRender, nothing } from "lit-html";
|
|
23
37
|
import { displayTagName } from "@jxsuite/schema/guards";
|
|
24
|
-
import {
|
|
25
|
-
import { ref } from "lit-html/directives/ref.js";
|
|
38
|
+
import { focusItem } from "@jxsuite/ui/behaviors/action-group";
|
|
26
39
|
import { draggable } from "@atlaskit/pragmatic-drag-and-drop/element/adapter";
|
|
27
40
|
import { disableNativeDragPreview } from "@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview";
|
|
28
41
|
|
|
@@ -52,13 +65,15 @@ import { projectState } from "../state";
|
|
|
52
65
|
import { componentRegistry } from "../files/components";
|
|
53
66
|
import { convertToComponent } from "../editor/convert-to-component";
|
|
54
67
|
import { getEditBarAnchorRect, getEditSnapshot, postApplyFormat } from "../canvas/iframe-host";
|
|
55
|
-
import { getLayerSlot, isModalOpen
|
|
68
|
+
import { getLayerSlot, isModalOpen } from "../ui/layers";
|
|
56
69
|
import { showSlashMenu } from "../editor/slash-menu";
|
|
57
70
|
import { getConvertTargets } from "../editor/convert-targets";
|
|
58
71
|
import { rectOf } from "../utils/geometry";
|
|
59
72
|
import { createCommandRegistry } from "../commands/registry";
|
|
60
73
|
import { editorKindForMode, makeContext } from "../commands/context";
|
|
61
74
|
import { defaultCommands, noopCommandDeps } from "../commands/defaults";
|
|
75
|
+
import { emptyBlockBarView, mountBlockActionBar } from "../surfaces/block-action-bar";
|
|
76
|
+
import { openMenu } from "../surfaces/menu";
|
|
62
77
|
|
|
63
78
|
import type { AnyCommand, CommandRegistry } from "../commands/registry";
|
|
64
79
|
import type { CommandContext } from "../commands/context";
|
|
@@ -66,8 +81,9 @@ import type { CommandDeps } from "../commands/defaults";
|
|
|
66
81
|
import type { ApplyFormatIntent } from "../canvas/iframe-protocol";
|
|
67
82
|
import type { JxPath } from "../state";
|
|
68
83
|
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
69
|
-
import type { TemplateResult } from "lit-html";
|
|
70
84
|
import type { SlashCommand } from "../editor/convert-targets.js";
|
|
85
|
+
import type { BlockBarSurface, BlockBarTool, BlockBarView } from "../surfaces/block-action-bar";
|
|
86
|
+
import type { MenuHandle, MenuRowProjection } from "../surfaces/menu";
|
|
71
87
|
|
|
72
88
|
/**
|
|
73
89
|
* The plain format commands — everything an action button posts except link/insertData.
|
|
@@ -137,7 +153,7 @@ let _scrollRafPending = false;
|
|
|
137
153
|
* @param {Event} e
|
|
138
154
|
*/
|
|
139
155
|
export function onCanvasScroll(e: Event): void {
|
|
140
|
-
if (!_ctx ||
|
|
156
|
+
if (!_ctx || isLinkPopoverOpen()) {
|
|
141
157
|
return;
|
|
142
158
|
}
|
|
143
159
|
if (activeTab.value?.session.selection.length === 0) {
|
|
@@ -162,27 +178,32 @@ export function onCanvasScroll(e: Event): void {
|
|
|
162
178
|
}
|
|
163
179
|
|
|
164
180
|
/**
|
|
165
|
-
*
|
|
181
|
+
* Position-only fast path: move the standing bar to the anchor's current position (fresh
|
|
166
182
|
* {@link getEditBarAnchorRect} — the iframe's GBCR moves with the scroll). Hides via `visibility`
|
|
167
|
-
* when the anchor left the canvas area so the
|
|
168
|
-
* survives; a bar hidden by the
|
|
183
|
+
* when the anchor left the canvas area so the mounted document — and with it the drag handle's dnd
|
|
184
|
+
* registration and whatever holds the caret — survives; a bar hidden by the `visible` switch is
|
|
185
|
+
* genuinely torn down, which is right for a dismissal and wrong for a scroll that will bring the
|
|
186
|
+
* anchor straight back.
|
|
169
187
|
*/
|
|
170
188
|
function repositionBlockActionBar(): void {
|
|
171
|
-
const
|
|
172
|
-
if (!
|
|
189
|
+
const surface = _surface;
|
|
190
|
+
if (!surface || !_view.visible) {
|
|
173
191
|
renderBlockActionBar();
|
|
174
192
|
return;
|
|
175
193
|
}
|
|
176
194
|
const anchor = getEditBarAnchorRect();
|
|
177
195
|
const pos = anchor ? barPosition(anchor) : null;
|
|
178
196
|
if (!pos) {
|
|
179
|
-
|
|
197
|
+
applyBarView({ ..._view, offscreen: true });
|
|
180
198
|
return;
|
|
181
199
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
200
|
+
applyBarView({ ..._view, barX: pos.left, barY: pos.top, offscreen: false });
|
|
201
|
+
requestAnimationFrame(() => {
|
|
202
|
+
const bar = surface.bar();
|
|
203
|
+
if (bar) {
|
|
204
|
+
clampBarToWindow(bar);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
186
207
|
}
|
|
187
208
|
|
|
188
209
|
/**
|
|
@@ -219,12 +240,25 @@ function barPosition(anchor: {
|
|
|
219
240
|
/**
|
|
220
241
|
* Pull the bar back inside the window's right edge.
|
|
221
242
|
*
|
|
243
|
+
* A measurement and a write of the value it measured, which is why it stays imperative and stays
|
|
244
|
+
* here: the bar's own box is the input, and no projection can carry a number that depends on the
|
|
245
|
+
* layout the projection produced. It writes the same custom property the document's `left` reads.
|
|
246
|
+
*
|
|
247
|
+
* **It releases its own previous write before measuring, and that is load-bearing.** The projected
|
|
248
|
+
* position is set on the document's ROOT and reaches the bar by inheritance, while this override is
|
|
249
|
+
* set on the bar itself — and a value an element carries always beats one it inherits, at every
|
|
250
|
+
* specificity. So a clamp that was never released would pin the bar at the right edge for the rest
|
|
251
|
+
* of the session, and every later `barX` would be projected into a variable nothing reads. Removing
|
|
252
|
+
* it first also makes the measurement the right one: the box is read at the position the projection
|
|
253
|
+
* asked for, rather than at the position the last clamp left it in.
|
|
254
|
+
*
|
|
222
255
|
* @param {HTMLElement} bar
|
|
223
256
|
*/
|
|
224
257
|
function clampBarToWindow(bar: HTMLElement): void {
|
|
258
|
+
bar.style.removeProperty("--jx-bar-x");
|
|
225
259
|
const barRect = rectOf(bar);
|
|
226
260
|
if (barRect.right > window.innerWidth) {
|
|
227
|
-
bar.style.
|
|
261
|
+
bar.style.setProperty("--jx-bar-x", `${Math.max(0, window.innerWidth - barRect.width)}px`);
|
|
228
262
|
}
|
|
229
263
|
}
|
|
230
264
|
|
|
@@ -282,17 +316,17 @@ export function formatCommands(): AnyCommand[] {
|
|
|
282
316
|
});
|
|
283
317
|
|
|
284
318
|
return [
|
|
285
|
-
record("bold", "Bold", "
|
|
286
|
-
record("italic", "Italic", "
|
|
287
|
-
record("underline", "Underline", "
|
|
288
|
-
record("strikethrough", "Strikethrough", "
|
|
289
|
-
record("superscript", "Superscript", "
|
|
290
|
-
record("subscript", "Subscript", "
|
|
291
|
-
record("code", "Code", "
|
|
319
|
+
record("bold", "Bold", "text-b", "mod+b"),
|
|
320
|
+
record("italic", "Italic", "text-italic", "mod+i"),
|
|
321
|
+
record("underline", "Underline", "text-underline", "mod+u"),
|
|
322
|
+
record("strikethrough", "Strikethrough", "text-strikethrough"),
|
|
323
|
+
record("superscript", "Superscript", "text-superscript"),
|
|
324
|
+
record("subscript", "Subscript", "text-subscript"),
|
|
325
|
+
record("code", "Code", "code", "mod+`"),
|
|
292
326
|
{
|
|
293
327
|
category: "Edit",
|
|
294
328
|
group: "6_format",
|
|
295
|
-
icon: "
|
|
329
|
+
icon: "link",
|
|
296
330
|
id: "format.link",
|
|
297
331
|
/* ⌘K, at `caret` scope, beside `palette.open`'s global ⌘K. Two scopes may hold one chord —
|
|
298
332
|
that is what a shadowing ladder is — and the caret's wins only while a caret is in the
|
|
@@ -632,7 +666,7 @@ export function registerSelectionCommands(
|
|
|
632
666
|
category: "Selection",
|
|
633
667
|
enablement: canMoveUp,
|
|
634
668
|
group: "1_move_1",
|
|
635
|
-
icon: "
|
|
669
|
+
icon: "arrow-up",
|
|
636
670
|
id: "selection.moveUp",
|
|
637
671
|
keyScope: "canvas",
|
|
638
672
|
level: "selection",
|
|
@@ -647,7 +681,7 @@ export function registerSelectionCommands(
|
|
|
647
681
|
category: "Selection",
|
|
648
682
|
enablement: canMoveDown,
|
|
649
683
|
group: "1_move_2",
|
|
650
|
-
icon: "
|
|
684
|
+
icon: "arrow-down",
|
|
651
685
|
id: "selection.moveDown",
|
|
652
686
|
keyScope: "canvas",
|
|
653
687
|
level: "selection",
|
|
@@ -662,7 +696,7 @@ export function registerSelectionCommands(
|
|
|
662
696
|
category: "Selection",
|
|
663
697
|
enablement: canMoveIn,
|
|
664
698
|
group: "1_move_3",
|
|
665
|
-
icon: "
|
|
699
|
+
icon: "arrow-right",
|
|
666
700
|
id: "selection.moveIn",
|
|
667
701
|
keyScope: "canvas",
|
|
668
702
|
level: "selection",
|
|
@@ -677,7 +711,7 @@ export function registerSelectionCommands(
|
|
|
677
711
|
category: "Selection",
|
|
678
712
|
enablement: canMoveOut,
|
|
679
713
|
group: "1_move_4",
|
|
680
|
-
icon: "
|
|
714
|
+
icon: "arrow-left",
|
|
681
715
|
id: "selection.moveOut",
|
|
682
716
|
keyScope: "canvas",
|
|
683
717
|
level: "selection",
|
|
@@ -698,7 +732,7 @@ export function registerSelectionCommands(
|
|
|
698
732
|
enablement: (ctx) =>
|
|
699
733
|
Boolean(ctx.selection.kind) && !ctx.selection.isRoot && ctx.editor.kind === "canvas",
|
|
700
734
|
group: "4_component",
|
|
701
|
-
icon: "
|
|
735
|
+
icon: "cube",
|
|
702
736
|
id: "selection.convertToComponent",
|
|
703
737
|
level: "selection",
|
|
704
738
|
// Three surfaces, ONE record. `editor/context-menu.ts` carried a second `convertToComponent`
|
|
@@ -718,7 +752,7 @@ export function registerSelectionCommands(
|
|
|
718
752
|
{
|
|
719
753
|
category: "Selection",
|
|
720
754
|
group: "4_component",
|
|
721
|
-
icon: "
|
|
755
|
+
icon: "pencil-simple",
|
|
722
756
|
id: "selection.editComponent",
|
|
723
757
|
level: "selection",
|
|
724
758
|
menus: ["blockbar", "context/element", "palette"],
|
|
@@ -796,43 +830,6 @@ export function selectionCommandRegistry(): CommandRegistry {
|
|
|
796
830
|
/** How many verb buttons the bar renders before the rest fold into `⋮` (plan §3.2 ⑩). */
|
|
797
831
|
export const BLOCKBAR_MAX_ITEMS = 5;
|
|
798
832
|
|
|
799
|
-
/**
|
|
800
|
-
* Icons for command records, keyed by the record's own `icon`.
|
|
801
|
-
*
|
|
802
|
-
* A record with no icon renders its title as text rather than an unlabelled blank: the surface is
|
|
803
|
-
* allowed to choose how a name is drawn, never what it is.
|
|
804
|
-
*/
|
|
805
|
-
const commandIconMap: Record<string, TemplateResult> = {
|
|
806
|
-
"sp-icon-arrow-down": html`<sp-icon-arrow-down slot="icon"></sp-icon-arrow-down>`,
|
|
807
|
-
"sp-icon-arrow-left": html`<sp-icon-arrow-left slot="icon"></sp-icon-arrow-left>`,
|
|
808
|
-
"sp-icon-arrow-right": html`<sp-icon-arrow-right slot="icon"></sp-icon-arrow-right>`,
|
|
809
|
-
"sp-icon-arrow-up": html`<sp-icon-arrow-up slot="icon"></sp-icon-arrow-up>`,
|
|
810
|
-
"sp-icon-box": html`<sp-icon-box slot="icon" size="xs"></sp-icon-box>`,
|
|
811
|
-
"sp-icon-code": html`<sp-icon-code slot="icon"></sp-icon-code>`,
|
|
812
|
-
"sp-icon-delete": html`<sp-icon-delete slot="icon"></sp-icon-delete>`,
|
|
813
|
-
"sp-icon-duplicate": html`<sp-icon-duplicate slot="icon"></sp-icon-duplicate>`,
|
|
814
|
-
"sp-icon-edit": html`<sp-icon-edit slot="icon" size="xs"></sp-icon-edit>`,
|
|
815
|
-
"sp-icon-link": html`<sp-icon-link slot="icon"></sp-icon-link>`,
|
|
816
|
-
"sp-icon-text-bold": html`<sp-icon-text-bold slot="icon"></sp-icon-text-bold>`,
|
|
817
|
-
"sp-icon-text-italic": html`<sp-icon-text-italic slot="icon"></sp-icon-text-italic>`,
|
|
818
|
-
"sp-icon-text-strikethrough": html`<sp-icon-text-strikethrough
|
|
819
|
-
slot="icon"
|
|
820
|
-
></sp-icon-text-strikethrough>`,
|
|
821
|
-
"sp-icon-text-subscript": html`<sp-icon-text-subscript slot="icon"></sp-icon-text-subscript>`,
|
|
822
|
-
"sp-icon-text-superscript": html`<sp-icon-text-superscript
|
|
823
|
-
slot="icon"
|
|
824
|
-
></sp-icon-text-superscript>`,
|
|
825
|
-
"sp-icon-text-underline": html`<sp-icon-text-underline slot="icon"></sp-icon-text-underline>`,
|
|
826
|
-
};
|
|
827
|
-
|
|
828
|
-
/** The record's icon, or its title drawn as a compact label. */
|
|
829
|
-
export function commandIcon(command: AnyCommand): TemplateResult {
|
|
830
|
-
return (
|
|
831
|
-
(command.icon ? commandIconMap[command.icon] : undefined) ??
|
|
832
|
-
html`<span class="cmd-label">${command.title}</span>`
|
|
833
|
-
);
|
|
834
|
-
}
|
|
835
|
-
|
|
836
833
|
/**
|
|
837
834
|
* The tooltip: the chord when the control can act, the `requires` sentence when it cannot.
|
|
838
835
|
*
|
|
@@ -863,135 +860,141 @@ export function runCommand(registry: CommandRegistry, id: string, target?: JxPat
|
|
|
863
860
|
}
|
|
864
861
|
|
|
865
862
|
/**
|
|
866
|
-
*
|
|
863
|
+
* One button, projected from its record.
|
|
867
864
|
*
|
|
868
|
-
*
|
|
865
|
+
* The surface is told what a button says, never asked to work it out: the glyph is the record's own
|
|
866
|
+
* kit name, the tooltip is {@link commandTooltip}, and `disabled` is the registry's refusal rather
|
|
867
|
+
* than a second opinion this module could hold about the same record.
|
|
869
868
|
*/
|
|
870
|
-
function
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
869
|
+
function toolOf(
|
|
870
|
+
registry: CommandRegistry,
|
|
871
|
+
command: AnyCommand,
|
|
872
|
+
extra: { disabled?: boolean; selected?: boolean; value?: string } = {},
|
|
873
|
+
): BlockBarTool {
|
|
874
|
+
return {
|
|
875
|
+
destructive: command.destructive === true,
|
|
876
|
+
disabled: extra.disabled ?? registry.disabledReason(command.id) !== undefined,
|
|
877
|
+
hasIcon: Boolean(command.icon),
|
|
878
|
+
icon: command.icon ?? "",
|
|
879
|
+
id: command.id,
|
|
880
|
+
selected: extra.selected ?? false,
|
|
881
|
+
title: command.title,
|
|
882
|
+
tooltip: commandTooltip(registry, command),
|
|
883
|
+
value: extra.value ?? "",
|
|
884
|
+
};
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
// ─── The mounted surface ─────────────────────────────────────────────────────
|
|
888
|
+
|
|
889
|
+
/** The bar's surface, mounted on the first render and kept for the life of the window. */
|
|
890
|
+
let _surface: BlockBarSurface | null = null;
|
|
891
|
+
|
|
892
|
+
/** The last projection handed to the surface, so a fast path can vary one field of it. */
|
|
893
|
+
let _view: BlockBarView = emptyBlockBarView();
|
|
894
|
+
|
|
895
|
+
/** Hand the surface a projection, remembering it. */
|
|
896
|
+
function applyBarView(next: BlockBarView): void {
|
|
897
|
+
_view = next;
|
|
898
|
+
_surface?.update(next);
|
|
881
899
|
}
|
|
882
900
|
|
|
883
901
|
/**
|
|
884
|
-
*
|
|
885
|
-
*
|
|
886
|
-
*
|
|
902
|
+
* The layer slot the bar draws into, mounting the document the first time it is asked for.
|
|
903
|
+
*
|
|
904
|
+
* One mount for the life of the window: a document reconciles by assignment, so hiding the bar is a
|
|
905
|
+
* `visible` switch rather than a teardown, and the caret, the drag registration and an open link
|
|
906
|
+
* panel all survive a repaint that would have re-rendered a template.
|
|
887
907
|
*/
|
|
888
|
-
function
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
mutateUpdateProperty(t, selection, "tagName", cmd.tag);
|
|
896
|
-
});
|
|
908
|
+
function ensureSurface(): BlockBarSurface {
|
|
909
|
+
if (!view.blockActionBarEl) {
|
|
910
|
+
view.blockActionBarEl = getLayerSlot("popover", "block-action-bar");
|
|
911
|
+
}
|
|
912
|
+
_surface ??= mountBlockActionBar(view.blockActionBarEl, {
|
|
913
|
+
applyLink: (href) => {
|
|
914
|
+
postApplyFormat({ command: "link", href: href || "" });
|
|
897
915
|
},
|
|
898
|
-
|
|
916
|
+
leaveBar: () => {
|
|
917
|
+
dismissBlockBarOverflow();
|
|
918
|
+
returnFocusToCanvas();
|
|
919
|
+
},
|
|
920
|
+
onDragHandle: attachDragHandle,
|
|
921
|
+
openMergeTags: openMergeTagMenu,
|
|
922
|
+
openOverflow: (anchor) => {
|
|
923
|
+
const registry = selectionCommandRegistry();
|
|
924
|
+
showCommandOverflow(
|
|
925
|
+
anchor,
|
|
926
|
+
registry,
|
|
927
|
+
registry.forPlacement("blockbar").slice(BLOCKBAR_MAX_ITEMS),
|
|
928
|
+
);
|
|
929
|
+
},
|
|
930
|
+
openTagMenu: openConvertMenu,
|
|
931
|
+
removeLink: () => {
|
|
932
|
+
postApplyFormat({ command: "link", href: null });
|
|
933
|
+
},
|
|
934
|
+
run: (id) => runCommand(selectionCommandRegistry(), id),
|
|
935
|
+
selectParent: () => runCommand(selectionCommandRegistry(), "selection.selectParent"),
|
|
899
936
|
});
|
|
937
|
+
return _surface;
|
|
900
938
|
}
|
|
901
939
|
|
|
902
940
|
/**
|
|
903
|
-
*
|
|
941
|
+
* Register the drag handle the document just drew.
|
|
904
942
|
*
|
|
905
|
-
*
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
* `
|
|
943
|
+
* Released before installed, always. The handle's node is re-created every time the bar is hidden
|
|
944
|
+
* and shown again, and two live pragmatic-dnd registrations on one handle is a drag that fires
|
|
945
|
+
* twice — `view.selDragCleanup` is the single field that guarantees there is never more than one.
|
|
946
|
+
* `canDrag` is asked at the PRESS rather than here, so a selection moving to the document root
|
|
947
|
+
* greys the handle without any re-registration at all.
|
|
948
|
+
*
|
|
949
|
+
* @param {HTMLElement} handle
|
|
909
950
|
*/
|
|
910
|
-
function
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
>
|
|
933
|
-
<sp-icon-back slot="icon"></sp-icon-back>
|
|
934
|
-
</sp-action-button>
|
|
935
|
-
`;
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
/** One verb, rendered from its record. */
|
|
939
|
-
function renderCommandButton(registry: CommandRegistry, command: AnyCommand) {
|
|
940
|
-
const disabled = registry.disabledReason(command.id) !== undefined;
|
|
941
|
-
return html`<sp-action-button
|
|
942
|
-
size="xs"
|
|
943
|
-
quiet
|
|
944
|
-
class=${command.destructive ? "bar-cmd bar-cmd--danger" : "bar-cmd"}
|
|
945
|
-
data-toolbar-item
|
|
946
|
-
data-command=${command.id}
|
|
947
|
-
tabindex="-1"
|
|
948
|
-
aria-label=${command.title}
|
|
949
|
-
title=${commandTooltip(registry, command)}
|
|
950
|
-
?disabled=${disabled}
|
|
951
|
-
@mousedown=${(e: MouseEvent) => e.preventDefault()}
|
|
952
|
-
@click=${(e: MouseEvent) => {
|
|
953
|
-
e.stopPropagation();
|
|
954
|
-
runCommand(registry, command.id);
|
|
955
|
-
}}
|
|
956
|
-
>${commandIcon(command)}</sp-action-button
|
|
957
|
-
>`;
|
|
951
|
+
function attachDragHandle(handle: HTMLElement): void {
|
|
952
|
+
view.selDragCleanup?.();
|
|
953
|
+
view.selDragCleanup = draggable({
|
|
954
|
+
canDrag: () => structuralTarget(primarySelection(activeTab.value?.session.selection)) !== null,
|
|
955
|
+
element: handle,
|
|
956
|
+
getInitialData: () => ({
|
|
957
|
+
// Snapshot the selection: the live array is a Vue reactive proxy, which structured clone
|
|
958
|
+
// Rejects when the src crosses postMessage (DataCloneError killed the whole handle drag),
|
|
959
|
+
// And a live reference would also mutate the retained srcData if the selection changed
|
|
960
|
+
// Mid-drag.
|
|
961
|
+
path: [...(primarySelection(activeTab.value?.session.selection) ?? [])],
|
|
962
|
+
type: "tree-node",
|
|
963
|
+
}),
|
|
964
|
+
onGenerateDragPreview: ({
|
|
965
|
+
nativeSetDragImage,
|
|
966
|
+
}: {
|
|
967
|
+
nativeSetDragImage: ((image: Element, x: number, y: number) => void) | null;
|
|
968
|
+
}) => {
|
|
969
|
+
// Suppress the native drag image; the cross-frame ghost is the drag affordance.
|
|
970
|
+
disableNativeDragPreview({ nativeSetDragImage });
|
|
971
|
+
},
|
|
972
|
+
});
|
|
958
973
|
}
|
|
959
974
|
|
|
960
|
-
|
|
961
|
-
function renderOverflowButton(registry: CommandRegistry, commands: readonly AnyCommand[]) {
|
|
962
|
-
return html`<sp-action-button
|
|
963
|
-
size="xs"
|
|
964
|
-
quiet
|
|
965
|
-
class="bar-overflow"
|
|
966
|
-
data-toolbar-item
|
|
967
|
-
tabindex="-1"
|
|
968
|
-
aria-label="More block actions"
|
|
969
|
-
aria-haspopup="menu"
|
|
970
|
-
title="More block actions"
|
|
971
|
-
@mousedown=${(e: MouseEvent) => e.preventDefault()}
|
|
972
|
-
@click=${(e: MouseEvent) => {
|
|
973
|
-
e.stopPropagation();
|
|
974
|
-
showCommandOverflow(e.currentTarget as HTMLElement, registry, commands);
|
|
975
|
-
}}
|
|
976
|
-
>
|
|
977
|
-
<sp-icon-more slot="icon"></sp-icon-more>
|
|
978
|
-
</sp-action-button>`;
|
|
979
|
-
}
|
|
975
|
+
// ─── The `⋮` menu ────────────────────────────────────────────────────────────
|
|
980
976
|
|
|
981
977
|
/** The open overflow menu, so a second press (or a re-render) closes rather than stacks it. */
|
|
982
|
-
let _overflowHandle:
|
|
978
|
+
let _overflowHandle: MenuHandle | null = null;
|
|
983
979
|
|
|
984
980
|
/** Close the block action bar's `⋮` menu if it is open. */
|
|
985
981
|
export function dismissBlockBarOverflow(): void {
|
|
986
|
-
_overflowHandle
|
|
982
|
+
const handle = _overflowHandle;
|
|
987
983
|
_overflowHandle = null;
|
|
984
|
+
handle?.close();
|
|
985
|
+
if (_view.overflowOpen) {
|
|
986
|
+
applyBarView({ ..._view, overflowOpen: false });
|
|
987
|
+
}
|
|
988
988
|
}
|
|
989
989
|
|
|
990
990
|
/**
|
|
991
991
|
* Show the `⋮` menu under `anchor`. Rows carry the same names, chords and refusals as the buttons.
|
|
992
992
|
*
|
|
993
|
-
* Shared by the block action bar and the Outline rows
|
|
994
|
-
* (
|
|
993
|
+
* Shared by the block action bar and the Outline rows, and it is the KIT menu
|
|
994
|
+
* (`surfaces/menu.json`, §12.5) rather than a list of this surface's own: one popover, one roving
|
|
995
|
+
* caret, one typeahead, one Escape. `target` (the Outline row's node) is captured per row and
|
|
996
|
+
* re-applied when a row is chosen, because a row action acts on the row's node and the hovered row
|
|
997
|
+
* is not the selected one.
|
|
995
998
|
*/
|
|
996
999
|
export function showCommandOverflow(
|
|
997
1000
|
anchor: HTMLElement,
|
|
@@ -1000,95 +1003,46 @@ export function showCommandOverflow(
|
|
|
1000
1003
|
target: JxPath | null = null,
|
|
1001
1004
|
): void {
|
|
1002
1005
|
dismissBlockBarOverflow();
|
|
1003
|
-
const rect = rectOf(anchor);
|
|
1004
1006
|
// Resolve every row's name / chord / refusal against the TARGET, not the selection, before the
|
|
1005
|
-
//
|
|
1006
|
-
const project = () =>
|
|
1007
|
-
commands.map((command) =>
|
|
1008
|
-
chord
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1007
|
+
// Rows are handed over — the menu prints what it is given.
|
|
1008
|
+
const project = (): MenuRowProjection[] =>
|
|
1009
|
+
commands.map((command, index) => {
|
|
1010
|
+
const chord = registry.keymap.formatBinding(command.id);
|
|
1011
|
+
const reason = registry.disabledReason(command.id);
|
|
1012
|
+
return {
|
|
1013
|
+
destructive: command.destructive === true,
|
|
1014
|
+
disabled: reason !== undefined,
|
|
1015
|
+
dividerAbove: index > 0 && command.destructive === true,
|
|
1016
|
+
id: command.id,
|
|
1017
|
+
run: () => {
|
|
1018
|
+
runCommand(registry, command.id, target);
|
|
1019
|
+
},
|
|
1020
|
+
title: command.title,
|
|
1021
|
+
...(chord ? { chord } : {}),
|
|
1022
|
+
...(reason === undefined ? {} : { requires: reason }),
|
|
1023
|
+
};
|
|
1024
|
+
});
|
|
1015
1025
|
const rows = target ? withCommandTarget(target, project) : project();
|
|
1016
|
-
_overflowHandle =
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
style=${styleMap({
|
|
1021
|
-
left: `${rect.left}px`,
|
|
1022
|
-
position: "fixed",
|
|
1023
|
-
top: `${rect.bottom + 4}px`,
|
|
1024
|
-
zIndex: "101",
|
|
1025
|
-
})}
|
|
1026
|
-
>
|
|
1027
|
-
<sp-menu>
|
|
1028
|
-
${rows.map(
|
|
1029
|
-
(row) => html`<sp-menu-item
|
|
1030
|
-
?disabled=${row.disabled}
|
|
1031
|
-
data-command=${row.id}
|
|
1032
|
-
title=${row.tooltip}
|
|
1033
|
-
style=${row.destructive ? "color: var(--danger)" : nothing}
|
|
1034
|
-
@click=${() => {
|
|
1035
|
-
dismissBlockBarOverflow();
|
|
1036
|
-
runCommand(registry, row.id, target);
|
|
1037
|
-
}}
|
|
1038
|
-
>${row.title}${
|
|
1039
|
-
row.chord ? html`<kbd slot="value" class="cmd-chord">${row.chord}</kbd>` : nothing
|
|
1040
|
-
}</sp-menu-item
|
|
1041
|
-
>`,
|
|
1042
|
-
)}
|
|
1043
|
-
</sp-menu>
|
|
1044
|
-
</sp-popover>`,
|
|
1045
|
-
{
|
|
1046
|
-
dismissOnOutsideClick: true,
|
|
1047
|
-
onDismiss: () => {
|
|
1026
|
+
_overflowHandle = openMenu({
|
|
1027
|
+
label: "More block actions",
|
|
1028
|
+
onClosed: (closed) => {
|
|
1029
|
+
if (_overflowHandle === closed) {
|
|
1048
1030
|
_overflowHandle = null;
|
|
1049
|
-
|
|
1031
|
+
if (_view.overflowOpen) {
|
|
1032
|
+
applyBarView({ ..._view, overflowOpen: false });
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1050
1035
|
},
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
function toolbarItems(bar: HTMLElement): HTMLElement[] {
|
|
1058
|
-
return [...bar.querySelectorAll<HTMLElement>("[data-toolbar-item]")].filter(
|
|
1059
|
-
(el) => !el.hasAttribute("disabled") && el.getAttribute("aria-disabled") !== "true",
|
|
1060
|
-
);
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
/**
|
|
1064
|
-
* Apply the roving tabindex: exactly one item is in the tab order at a time.
|
|
1065
|
-
*
|
|
1066
|
-
* Called after every render, so the survivor is whichever item still holds focus and otherwise the
|
|
1067
|
-
* first — a toolbar that resets to its first control on every keystroke is not navigable.
|
|
1068
|
-
*/
|
|
1069
|
-
export function applyRovingTabindex(bar: HTMLElement): void {
|
|
1070
|
-
const items = toolbarItems(bar);
|
|
1071
|
-
const focused = items.findIndex(
|
|
1072
|
-
(el) => el === document.activeElement || el.contains(document.activeElement),
|
|
1073
|
-
);
|
|
1074
|
-
const active = focused === -1 ? 0 : focused;
|
|
1075
|
-
for (const [index, el] of items.entries()) {
|
|
1076
|
-
el.tabIndex = index === active ? 0 : -1;
|
|
1036
|
+
opener: anchor,
|
|
1037
|
+
region: "block-actions",
|
|
1038
|
+
rows,
|
|
1039
|
+
});
|
|
1040
|
+
if (!target) {
|
|
1041
|
+
applyBarView({ ..._view, overflowOpen: true });
|
|
1077
1042
|
}
|
|
1078
1043
|
}
|
|
1079
1044
|
|
|
1080
|
-
|
|
1081
|
-
function focusToolbarItem(bar: HTMLElement, index: number): void {
|
|
1082
|
-
const items = toolbarItems(bar);
|
|
1083
|
-
if (items.length === 0) {
|
|
1084
|
-
return;
|
|
1085
|
-
}
|
|
1086
|
-
const wrapped = ((index % items.length) + items.length) % items.length;
|
|
1087
|
-
for (const [i, el] of items.entries()) {
|
|
1088
|
-
el.tabIndex = i === wrapped ? 0 : -1;
|
|
1089
|
-
}
|
|
1090
|
-
items[wrapped]!.focus();
|
|
1091
|
-
}
|
|
1045
|
+
// ─── Keyboard ────────────────────────────────────────────────────────────────
|
|
1092
1046
|
|
|
1093
1047
|
/** Put the keyboard back where it came from — the canvas iframe, else its wrapper. */
|
|
1094
1048
|
function returnFocusToCanvas(): void {
|
|
@@ -1099,32 +1053,6 @@ function returnFocusToCanvas(): void {
|
|
|
1099
1053
|
(iframe ?? stage)?.focus();
|
|
1100
1054
|
}
|
|
1101
1055
|
|
|
1102
|
-
/** `role="toolbar"` navigation: ←/→ between items, Home/End to the ends, Esc back to the canvas. */
|
|
1103
|
-
export function onToolbarKeydown(e: KeyboardEvent): void {
|
|
1104
|
-
const bar = e.currentTarget as HTMLElement;
|
|
1105
|
-
const items = toolbarItems(bar);
|
|
1106
|
-
const current = items.findIndex(
|
|
1107
|
-
(el) => el === document.activeElement || el.contains(document.activeElement),
|
|
1108
|
-
);
|
|
1109
|
-
if (e.key === "ArrowRight") {
|
|
1110
|
-
e.preventDefault();
|
|
1111
|
-
focusToolbarItem(bar, current + 1);
|
|
1112
|
-
} else if (e.key === "ArrowLeft") {
|
|
1113
|
-
e.preventDefault();
|
|
1114
|
-
focusToolbarItem(bar, current - 1);
|
|
1115
|
-
} else if (e.key === "Home") {
|
|
1116
|
-
e.preventDefault();
|
|
1117
|
-
focusToolbarItem(bar, 0);
|
|
1118
|
-
} else if (e.key === "End") {
|
|
1119
|
-
e.preventDefault();
|
|
1120
|
-
focusToolbarItem(bar, items.length - 1);
|
|
1121
|
-
} else if (e.key === "Escape") {
|
|
1122
|
-
e.preventDefault();
|
|
1123
|
-
dismissBlockBarOverflow();
|
|
1124
|
-
returnFocusToCanvas();
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
1056
|
/**
|
|
1129
1057
|
* ⌥↑ enters the bar from the canvas.
|
|
1130
1058
|
*
|
|
@@ -1132,6 +1060,11 @@ export function onToolbarKeydown(e: KeyboardEvent): void {
|
|
|
1132
1060
|
* inside the canvas iframe does not arrive: `canvas/iframe-keys.ts` forwards bare navigation keys
|
|
1133
1061
|
* only when the target is not editable, and the canvas root is permanently `contenteditable`.
|
|
1134
1062
|
* Exported so a test can dispatch it directly.
|
|
1063
|
+
*
|
|
1064
|
+
* The caret it places is the kit's: `jx-action-group` owns the roving tabindex and every arrow key
|
|
1065
|
+
* after this one, so entering the bar is the only part of the toolbar keyboard this module holds.
|
|
1066
|
+
*
|
|
1067
|
+
* @param {KeyboardEvent} e
|
|
1135
1068
|
*/
|
|
1136
1069
|
export function handleBlockBarEntryKey(e: KeyboardEvent): void {
|
|
1137
1070
|
if (e.key !== "ArrowUp" || !e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
|
|
@@ -1140,24 +1073,74 @@ export function handleBlockBarEntryKey(e: KeyboardEvent): void {
|
|
|
1140
1073
|
if (isModalOpen()) {
|
|
1141
1074
|
return;
|
|
1142
1075
|
}
|
|
1143
|
-
const
|
|
1144
|
-
if (!
|
|
1076
|
+
const toolbar = _surface?.toolbar();
|
|
1077
|
+
if (!toolbar) {
|
|
1145
1078
|
return;
|
|
1146
1079
|
}
|
|
1147
1080
|
e.preventDefault();
|
|
1148
|
-
|
|
1081
|
+
focusItem(toolbar, 0);
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// ─── The badge's convert list and the merge-tag list ─────────────────────────
|
|
1085
|
+
|
|
1086
|
+
/**
|
|
1087
|
+
* The tags this node may be converted into — empty for a component instance and for a repeater,
|
|
1088
|
+
* whose content is a `map` template rather than a tag.
|
|
1089
|
+
*
|
|
1090
|
+
* @param {JxMutableNode | null} node
|
|
1091
|
+
*/
|
|
1092
|
+
function convertTargetsFor(node: JxMutableNode | null): SlashCommand[] {
|
|
1093
|
+
if (!node) {
|
|
1094
|
+
return [];
|
|
1095
|
+
}
|
|
1096
|
+
const literal = displayTagName(node.tagName);
|
|
1097
|
+
const isComponent = literal.includes("-") && componentRegistry.some((c) => c.tagName === literal);
|
|
1098
|
+
if (isComponent || node.$prototype === "Array") {
|
|
1099
|
+
return [];
|
|
1100
|
+
}
|
|
1101
|
+
const children = childList(node);
|
|
1102
|
+
const isEmpty =
|
|
1103
|
+
!node.textContent &&
|
|
1104
|
+
(children.length === 0 ||
|
|
1105
|
+
(children.length === 1 && typeof children[0] === "object" && children[0]?.tagName === "br"));
|
|
1106
|
+
return getConvertTargets((literal || "div").toLowerCase(), isEmpty);
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
/**
|
|
1110
|
+
* Open the badge's convert list under it, and retag the node on a choice.
|
|
1111
|
+
*
|
|
1112
|
+
* Resolved at the PRESS rather than captured at projection time: the badge is one node for the life
|
|
1113
|
+
* of the mount, so a closure taken when it was drawn would retag whatever was selected then.
|
|
1114
|
+
*
|
|
1115
|
+
* @param {HTMLElement} anchor
|
|
1116
|
+
*/
|
|
1117
|
+
function openConvertMenu(anchor: HTMLElement): void {
|
|
1118
|
+
const tab = activeTab.value;
|
|
1119
|
+
const selection = primarySelection(tab?.session.selection);
|
|
1120
|
+
const node = tab && selection ? getNodeAtPath(tab.doc.document, selection) : null;
|
|
1121
|
+
const targets = convertTargetsFor(node);
|
|
1122
|
+
if (!selection || targets.length === 0) {
|
|
1123
|
+
return;
|
|
1124
|
+
}
|
|
1125
|
+
showSlashMenu(anchor, "", {
|
|
1126
|
+
commands: targets,
|
|
1127
|
+
onSelect: (cmd) => {
|
|
1128
|
+
transactDoc(activeTab.value, (t) => {
|
|
1129
|
+
mutateUpdateProperty(t, selection, "tagName", cmd.tag);
|
|
1130
|
+
});
|
|
1131
|
+
},
|
|
1132
|
+
showFilter: targets.length > 6,
|
|
1133
|
+
});
|
|
1149
1134
|
}
|
|
1150
1135
|
|
|
1151
1136
|
/**
|
|
1152
1137
|
* Open the merge-tag menu — a searchable list of `${…}` template tokens for the data available in
|
|
1153
|
-
* the current state. Reuses the shared slash-menu
|
|
1138
|
+
* the current state. Reuses the shared slash-menu surface (filter + keyboard nav + dismiss).
|
|
1154
1139
|
* Selecting a token posts an `insertData` intent the iframe applies at its caret.
|
|
1155
1140
|
*
|
|
1156
|
-
* @param {
|
|
1141
|
+
* @param {HTMLElement} anchor
|
|
1157
1142
|
*/
|
|
1158
|
-
function
|
|
1159
|
-
e.stopPropagation();
|
|
1160
|
-
const anchorEl = e.currentTarget as HTMLElement;
|
|
1143
|
+
function openMergeTagMenu(anchor: HTMLElement): void {
|
|
1161
1144
|
const tab = activeTab.value;
|
|
1162
1145
|
const state = (tab?.doc.document.state ?? {}) as Record<string, unknown>;
|
|
1163
1146
|
// The live resolved scope lives inside the iframe realm and is not threaded out, so the parent
|
|
@@ -1169,45 +1152,79 @@ function onMergeTagClick(e: MouseEvent) {
|
|
|
1169
1152
|
// Parent-side from schema, keyed off the selected element's doc path (which carries a `map` segment).
|
|
1170
1153
|
const selPath = getEditSnapshot().snapshot?.path ?? primarySelection(tab?.session.selection);
|
|
1171
1154
|
const arrayNode = tab && selPath ? findEnclosingRepeater(tab.doc.document, selPath) : null;
|
|
1172
|
-
if (arrayNode) {
|
|
1155
|
+
if (arrayNode && tab) {
|
|
1173
1156
|
const tokens = resolveRepeaterItemFields(
|
|
1174
1157
|
arrayNode,
|
|
1175
|
-
tab
|
|
1158
|
+
tab.doc.document.state as Record<string, unknown>,
|
|
1176
1159
|
projectState?.projectConfig,
|
|
1177
1160
|
);
|
|
1178
1161
|
tags.push(...buildRepeaterTagsFromFields(tokens));
|
|
1179
1162
|
}
|
|
1180
1163
|
|
|
1181
|
-
|
|
1182
|
-
description: t.hint,
|
|
1183
|
-
label: t.label,
|
|
1184
|
-
tag: t.token,
|
|
1185
|
-
}));
|
|
1186
|
-
|
|
1187
|
-
showSlashMenu(anchorEl, "", {
|
|
1188
|
-
commands,
|
|
1164
|
+
showSlashMenu(anchor, "", {
|
|
1165
|
+
commands: tags.map((t) => ({ description: t.hint, label: t.label, tag: t.token })),
|
|
1189
1166
|
onSelect: (cmd) => postApplyFormat({ command: "insertData", token: cmd.tag }),
|
|
1190
1167
|
showFilter: true,
|
|
1191
1168
|
});
|
|
1192
1169
|
}
|
|
1193
1170
|
|
|
1171
|
+
// ─── The link panel ──────────────────────────────────────────────────────────
|
|
1172
|
+
|
|
1194
1173
|
/** Dismiss the link popover if open. */
|
|
1195
|
-
export function dismissLinkPopover() {
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1174
|
+
export function dismissLinkPopover(): void {
|
|
1175
|
+
_surface?.closeLink();
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
/** True while the link URL popover is open (so a scroll does not move the bar out from under it). */
|
|
1179
|
+
export function isLinkPopoverOpen(): boolean {
|
|
1180
|
+
return _surface?.linkOpen() === true;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
/**
|
|
1184
|
+
* Open the link popover, anchored to the toolbar's Link button if it is on screen, else the bar.
|
|
1185
|
+
*
|
|
1186
|
+
* The iframe owns the Selection, so the existing-link state comes from the latest selection
|
|
1187
|
+
* snapshot; Apply/Remove post `applyFormat` link intents the iframe applies.
|
|
1188
|
+
*
|
|
1189
|
+
* By RECORD ID, not by rendered text. This queried `sp-action-button[title^="Link"]`, which is the
|
|
1190
|
+
* addressing §13's first rule bans in the screenshot manifest — a match on a title the app DERIVES
|
|
1191
|
+
* (it now reads "Link… (⌘K)", and on Windows it reads something else again). The id is the input
|
|
1192
|
+
* the app accepts, and the button is one the surface ANNOUNCED as it drew it rather than one this
|
|
1193
|
+
* module re-finds.
|
|
1194
|
+
*/
|
|
1195
|
+
export function openLinkPopoverFromShortcut(): void {
|
|
1196
|
+
const surface = _surface;
|
|
1197
|
+
const anchor = surface?.button("format.link") ?? surface?.bar();
|
|
1198
|
+
if (!surface || !anchor) {
|
|
1199
|
+
return;
|
|
1200
|
+
}
|
|
1201
|
+
const link = getEditSnapshot().snapshot?.link ?? { active: false, href: null };
|
|
1202
|
+
surface.openLink(anchor, link.href, link.active);
|
|
1199
1203
|
}
|
|
1200
1204
|
|
|
1205
|
+
// ─── Dismissal and suppression ───────────────────────────────────────────────
|
|
1206
|
+
|
|
1201
1207
|
/** Dismiss the block action bar. */
|
|
1202
|
-
export function dismissBlockActionBar() {
|
|
1208
|
+
export function dismissBlockActionBar(): void {
|
|
1203
1209
|
/* A dismiss is the bar's STRUCTURAL teardown — `canvas/canvas-render.ts` calls it when a stage is
|
|
1204
1210
|
torn down and again on a mode transition — so it also drops any suppression. The bar it would
|
|
1205
1211
|
otherwise be holding down does not exist any more; what comes back is a different one. */
|
|
1206
1212
|
_suppressedFor = null;
|
|
1207
1213
|
dismissBlockBarOverflow();
|
|
1208
|
-
|
|
1209
|
-
|
|
1214
|
+
hideBar();
|
|
1215
|
+
}
|
|
1216
|
+
|
|
1217
|
+
/**
|
|
1218
|
+
* Take the bar off the screen: the `visible` switch, plus the drag registration that goes with the
|
|
1219
|
+
* node the switch is about to remove. The mount itself stays — it is the one thing a repaint must
|
|
1220
|
+
* never rebuild.
|
|
1221
|
+
*/
|
|
1222
|
+
function hideBar(): void {
|
|
1223
|
+
if (_surface) {
|
|
1224
|
+
applyBarView({ ...emptyBlockBarView(), visible: false });
|
|
1210
1225
|
}
|
|
1226
|
+
view.selDragCleanup?.();
|
|
1227
|
+
view.selDragCleanup = null;
|
|
1211
1228
|
}
|
|
1212
1229
|
|
|
1213
1230
|
/**
|
|
@@ -1258,9 +1275,6 @@ function suppressionKey(tab: ActiveTab | null, path: JxPath | null): string {
|
|
|
1258
1275
|
export function suppressBlockActionBar(): void {
|
|
1259
1276
|
// Clears any earlier key as its first act; this selection's replaces it below.
|
|
1260
1277
|
dismissBlockActionBar();
|
|
1261
|
-
if (_linkPopoverOpen) {
|
|
1262
|
-
dismissLinkPopover();
|
|
1263
|
-
}
|
|
1264
1278
|
const tab = activeTab.value;
|
|
1265
1279
|
_suppressedFor = { key: suppressionKey(tab, primarySelection(tab?.session.selection)) };
|
|
1266
1280
|
}
|
|
@@ -1285,22 +1299,13 @@ export function releaseBlockActionBar(): void {
|
|
|
1285
1299
|
}
|
|
1286
1300
|
|
|
1287
1301
|
/**
|
|
1288
|
-
* Whether the
|
|
1289
|
-
*
|
|
1290
|
-
*
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
export function isLinkPopoverOpen(): boolean {
|
|
1296
|
-
return _linkPopoverOpen;
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
/**
|
|
1300
|
-
* Whether `target` sits inside the edit-session chrome — the block action bar, its link popover, or
|
|
1301
|
-
* the slash menu. The parent's pointerdown commit-guard must NOT end the inline-edit session for
|
|
1302
|
-
* clicks on these (they operate ON the session); any other parent-chrome press commits it. This
|
|
1303
|
-
* module owns those popover roots, hence the helper lives here.
|
|
1302
|
+
* Whether `target` sits inside the edit-session chrome — the block action bar, its `⋮` menu, or the
|
|
1303
|
+
* slash menu. The parent's pointerdown commit-guard must NOT end the inline-edit session for clicks
|
|
1304
|
+
* on these (they operate ON the session); any other parent-chrome press commits it. This module
|
|
1305
|
+
* owns those popover roots, hence the helper lives here.
|
|
1306
|
+
*
|
|
1307
|
+
* The link panel is no longer named separately: it is a `jx-popover` inside the bar's own document,
|
|
1308
|
+
* so the bar's host contains it and one root answers for both.
|
|
1304
1309
|
*
|
|
1305
1310
|
* @param {EventTarget | null} target
|
|
1306
1311
|
*/
|
|
@@ -1311,369 +1316,158 @@ export function isEditChromeTarget(target: EventTarget | null): boolean {
|
|
|
1311
1316
|
const roots = [
|
|
1312
1317
|
view.blockActionBarEl,
|
|
1313
1318
|
_overflowHandle?.host ?? null,
|
|
1314
|
-
getLayerSlot("popover", "link-popover"),
|
|
1315
1319
|
getLayerSlot("popover", "slash-menu"),
|
|
1316
1320
|
];
|
|
1317
1321
|
return roots.some((root) => root != null && root.contains(target));
|
|
1318
1322
|
}
|
|
1319
1323
|
|
|
1320
|
-
|
|
1321
|
-
* Show the link URL popover. The iframe owns the Selection, so the existing-link state comes from
|
|
1322
|
-
* the latest selection snapshot; Apply/Remove post `applyFormat` link intents the iframe applies.
|
|
1323
|
-
*
|
|
1324
|
-
* @param {HTMLElement} anchorBtn
|
|
1325
|
-
*/
|
|
1326
|
-
function showLinkPopover(anchorBtn: HTMLElement) {
|
|
1327
|
-
const host = getLayerSlot("popover", "link-popover");
|
|
1328
|
-
litRender(nothing, host);
|
|
1329
|
-
|
|
1330
|
-
const link = getEditSnapshot().snapshot?.link ?? { active: false, href: null };
|
|
1331
|
-
const existing = link.active;
|
|
1332
|
-
|
|
1333
|
-
const rect = rectOf(anchorBtn);
|
|
1334
|
-
|
|
1335
|
-
let _linkField: HTMLInputElement | null = null;
|
|
1336
|
-
|
|
1337
|
-
const close = () => {
|
|
1338
|
-
_linkPopoverOpen = false;
|
|
1339
|
-
litRender(nothing, host);
|
|
1340
|
-
};
|
|
1341
|
-
|
|
1342
|
-
const onApply = () => {
|
|
1343
|
-
const url = _linkField?.value || "";
|
|
1344
|
-
// Apply then let the popover close itself (do not steal focus back into the iframe here).
|
|
1345
|
-
postApplyFormat({ command: "link", href: url || "" });
|
|
1346
|
-
close();
|
|
1347
|
-
};
|
|
1348
|
-
|
|
1349
|
-
const onRemove = () => {
|
|
1350
|
-
postApplyFormat({ command: "link", href: null });
|
|
1351
|
-
close();
|
|
1352
|
-
};
|
|
1353
|
-
|
|
1354
|
-
const onKeydown = (e: KeyboardEvent) => {
|
|
1355
|
-
if (e.key === "Enter") {
|
|
1356
|
-
onApply();
|
|
1357
|
-
} else if (e.key === "Escape") {
|
|
1358
|
-
close();
|
|
1359
|
-
}
|
|
1360
|
-
};
|
|
1361
|
-
|
|
1362
|
-
_linkPopoverOpen = true;
|
|
1363
|
-
litRender(
|
|
1364
|
-
html`
|
|
1365
|
-
<sp-popover
|
|
1366
|
-
class="link-popover"
|
|
1367
|
-
open
|
|
1368
|
-
style=${styleMap({
|
|
1369
|
-
left: `${rect.left}px`,
|
|
1370
|
-
position: "fixed",
|
|
1371
|
-
top: `${rect.bottom + 4}px`,
|
|
1372
|
-
zIndex: "30",
|
|
1373
|
-
})}
|
|
1374
|
-
>
|
|
1375
|
-
<sp-textfield
|
|
1376
|
-
placeholder="https://..."
|
|
1377
|
-
size="s"
|
|
1378
|
-
style="width:200px"
|
|
1379
|
-
value=${link.href || ""}
|
|
1380
|
-
@keydown=${onKeydown}
|
|
1381
|
-
${ref((el) => {
|
|
1382
|
-
_linkField = (el as HTMLInputElement | null) || null;
|
|
1383
|
-
if (el) {
|
|
1384
|
-
requestAnimationFrame(() => (el as HTMLElement).focus());
|
|
1385
|
-
}
|
|
1386
|
-
})}
|
|
1387
|
-
></sp-textfield>
|
|
1388
|
-
<sp-action-button size="xs" @click=${onApply}>
|
|
1389
|
-
${existing ? "Update" : "Apply"}
|
|
1390
|
-
</sp-action-button>
|
|
1391
|
-
${
|
|
1392
|
-
existing
|
|
1393
|
-
? html` <sp-action-button size="xs" @click=${onRemove}>Remove</sp-action-button> `
|
|
1394
|
-
: nothing
|
|
1395
|
-
}
|
|
1396
|
-
</sp-popover>
|
|
1397
|
-
`,
|
|
1398
|
-
host,
|
|
1399
|
-
);
|
|
1400
|
-
}
|
|
1324
|
+
// ─── The projection ──────────────────────────────────────────────────────────
|
|
1401
1325
|
|
|
1402
1326
|
/**
|
|
1403
|
-
*
|
|
1404
|
-
*
|
|
1327
|
+
* What the bar says right now, or `null` when it must not draw at all.
|
|
1328
|
+
*
|
|
1329
|
+
* Every branch that used to end in `litRender(nothing)` ends here in `null`, and the five reasons
|
|
1330
|
+
* are unchanged: no context, no tab, no selection, a canvas mode that is not design or edit, a
|
|
1331
|
+
* selection that resolves to no node, and an anchor the stage no longer shows.
|
|
1405
1332
|
*/
|
|
1406
|
-
|
|
1407
|
-
const bar = view.blockActionBarEl?.querySelector(".block-action-bar") as HTMLElement | null;
|
|
1408
|
-
/* By RECORD ID, not by rendered text. This queried `sp-action-button[title^="Link"]`, which is
|
|
1409
|
-
the addressing §13's first rule bans in the screenshot manifest — a match on a title the app
|
|
1410
|
-
DERIVES (it now reads "Link… (⌘K)", and on Windows it reads something else again). The id is
|
|
1411
|
-
the input the app accepts. */
|
|
1412
|
-
const linkBtn =
|
|
1413
|
-
(bar?.querySelector('[data-command-id="format.link"]') as HTMLElement | null) ?? bar;
|
|
1414
|
-
if (linkBtn) {
|
|
1415
|
-
showLinkPopover(linkBtn);
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
|
|
1419
|
-
/** Render the unified block action bar above the selected element. */
|
|
1420
|
-
export function renderBlockActionBar() {
|
|
1421
|
-
if (!_ctx) {
|
|
1422
|
-
return;
|
|
1423
|
-
}
|
|
1424
|
-
if (!view.blockActionBarEl) {
|
|
1425
|
-
view.blockActionBarEl = getLayerSlot("popover", "block-action-bar");
|
|
1426
|
-
}
|
|
1427
|
-
|
|
1428
|
-
/* A snapshot-driven refresh must not re-mount an open link popover — it would re-create the URL
|
|
1429
|
-
field and lose the caret — so this pass is skipped entirely.
|
|
1430
|
-
|
|
1431
|
-
It sits AHEAD of the drag release below, because it is the one early return that LEAVES THE BAR
|
|
1432
|
-
UP. Releasing and then returning without re-rendering left the ⠿ handle on screen and inert, and
|
|
1433
|
-
nothing re-installs it: `dismissLinkPopover` does not re-render the bar. Any repaint reaches
|
|
1434
|
-
here — `applyTransform` → `renderOnly("overlays")` → this — so a pan, a zoom or a pane resize
|
|
1435
|
-
with the Link popover open silently killed dragging until the next selection change. Every
|
|
1436
|
-
other early return draws `nothing`, so releasing before them is right. */
|
|
1437
|
-
if (_linkPopoverOpen) {
|
|
1438
|
-
return;
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
if (view.selDragCleanup) {
|
|
1442
|
-
view.selDragCleanup();
|
|
1443
|
-
view.selDragCleanup = null;
|
|
1444
|
-
}
|
|
1445
|
-
|
|
1333
|
+
function projectBar(): BlockBarView | null {
|
|
1446
1334
|
const tab = activeTab.value;
|
|
1447
|
-
const canvasMode = _ctx
|
|
1448
|
-
|
|
1335
|
+
const canvasMode = _ctx?.getCanvasMode();
|
|
1449
1336
|
const selection = primarySelection(tab?.session.selection);
|
|
1450
|
-
|
|
1451
|
-
/* Suppressed by a chrome pointerdown, and this pass is the first of the two doors out of it (see
|
|
1452
|
-
{@link _suppressedFor}). A different selection means the click that hid the bar also chose a new
|
|
1453
|
-
target, so the suppression is over; the same selection keeps it, which is what stops the next
|
|
1454
|
-
snapshot- or overlay-driven repaint from flashing the bar back under the author's cursor. */
|
|
1455
|
-
if (_suppressedFor) {
|
|
1456
|
-
if (_suppressedFor.key === suppressionKey(tab, selection)) {
|
|
1457
|
-
litRender(nothing, view.blockActionBarEl);
|
|
1458
|
-
return;
|
|
1459
|
-
}
|
|
1460
|
-
_suppressedFor = null;
|
|
1461
|
-
}
|
|
1462
|
-
|
|
1463
1337
|
if (!tab || !selection || (canvasMode !== "design" && canvasMode !== "edit")) {
|
|
1464
|
-
|
|
1465
|
-
return;
|
|
1338
|
+
return null;
|
|
1466
1339
|
}
|
|
1467
|
-
|
|
1468
1340
|
const node = getNodeAtPath(tab.doc.document, selection);
|
|
1469
1341
|
if (!node) {
|
|
1470
|
-
|
|
1471
|
-
return;
|
|
1342
|
+
return null;
|
|
1472
1343
|
}
|
|
1473
|
-
|
|
1474
1344
|
// Position from the iframe-host's viewport-space anchor (the bar is position:fixed). The parent
|
|
1475
1345
|
// Never reads the iframe DOM, so geometry crosses the bridge as the selection snapshot's rect.
|
|
1476
1346
|
const anchor = getEditBarAnchorRect();
|
|
1477
1347
|
const pos = anchor ? barPosition(anchor) : null;
|
|
1478
1348
|
if (!pos) {
|
|
1479
|
-
|
|
1480
|
-
return;
|
|
1349
|
+
return null;
|
|
1481
1350
|
}
|
|
1482
1351
|
|
|
1352
|
+
const registry = selectionCommandRegistry();
|
|
1483
1353
|
const tag = (displayTagName(node.tagName) || "div").toLowerCase();
|
|
1484
|
-
|
|
1485
|
-
// Inline format state, sourced from the iframe's selection snapshot.
|
|
1486
1354
|
const { editingProp, snapshot } = getEditSnapshot();
|
|
1487
1355
|
const actions = getInlineActions(tag) || [];
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
// A prop-bound block still suppresses it — it edits a single plain string (belt and braces:
|
|
1495
|
-
// Component tags have no $inlineActions, so `actions` is empty there anyway).
|
|
1356
|
+
/* ONE bar, one shape. The format group used to appear only during an "inline edit session", so
|
|
1357
|
+
the toolbar rearranged itself under the author's cursor the moment they started typing. With a
|
|
1358
|
+
document-wide caret there is no session to be in or out of: the group is shown whenever the
|
|
1359
|
+
selected block can carry inline markup, and the buttons enable when there is a range to apply
|
|
1360
|
+
them to. A prop-bound block still suppresses it — it edits a single plain string (belt and
|
|
1361
|
+
braces: component tags have no $inlineActions, so `actions` is empty there anyway). */
|
|
1496
1362
|
const showFormat = !editingProp && actions.length > 0;
|
|
1497
|
-
const activeValues =
|
|
1498
|
-
showFormat && snapshot
|
|
1499
|
-
? actions.filter((a) => snapshot.activeTags.includes(a.tag)).map((a) => a.tag)
|
|
1500
|
-
: [];
|
|
1501
1363
|
// Formatting applies to a RANGE: disabled for a collapsed caret, and for a block selected without
|
|
1502
1364
|
// One at all (from the layers panel, or by a structural edit moving the selection).
|
|
1503
1365
|
const formatDisabled = snapshot?.collapsed ?? true;
|
|
1504
|
-
|
|
1505
|
-
// Conversion targets for badge click
|
|
1506
|
-
const isComponent =
|
|
1507
|
-
displayTagName(node.tagName).includes("-") &&
|
|
1508
|
-
componentRegistry.some((/** @type {{ tagName: string }} */ c) => c.tagName === node.tagName);
|
|
1509
|
-
const children = childList(node);
|
|
1510
|
-
const isEmpty =
|
|
1511
|
-
!node.textContent &&
|
|
1512
|
-
(children.length === 0 ||
|
|
1513
|
-
(children.length === 1 && typeof children[0] === "object" && children[0]?.tagName === "br"));
|
|
1514
|
-
// Repeater ($prototype:"Array") pseudo-elements have no tagName — show the "Repeater → items" label
|
|
1515
|
-
// (not a bare "div") and don't offer tag-conversion targets, which are meaningless for a repeater.
|
|
1516
|
-
const isRepeater = node.$prototype === "Array";
|
|
1517
|
-
const convertTargets = !isComponent && !isRepeater ? getConvertTargets(tag, isEmpty) : [];
|
|
1518
|
-
const badgeInteractive = convertTargets.length > 0;
|
|
1519
|
-
|
|
1520
|
-
// The verb cluster, sliced at the cap. `forPlacement` already dropped the records whose `when` is
|
|
1521
|
-
// False and sorted the rest by group; everything past the cap keeps its name and its chord in the
|
|
1522
|
-
// `⋮` menu rather than being silently unavailable.
|
|
1523
|
-
const registry = selectionCommandRegistry();
|
|
1524
1366
|
/* The SET is the schema's, the VERBS are the registry's (see {@link formatCommands}).
|
|
1525
1367
|
`$inlineActions` says which of the eight this tag accepts and in what order — four on an `<h1>`,
|
|
1526
1368
|
eight on a `<p>` — and each one's name, icon, chord and behaviour come off its record. An action
|
|
1527
1369
|
the registry has no record for is dropped rather than drawn as a button that runs nothing, so
|
|
1528
1370
|
adding a ninth verb to the data file without a record shows up as a missing button and as a red
|
|
1529
1371
|
`tests/block-action-bar.test.ts`, not as a silent no-op. */
|
|
1530
|
-
const
|
|
1372
|
+
const formats = actions.flatMap((action) => {
|
|
1531
1373
|
const command = registry.get(`format.${action.command}`);
|
|
1532
|
-
return command
|
|
1374
|
+
return command
|
|
1375
|
+
? [
|
|
1376
|
+
toolOf(registry, command, {
|
|
1377
|
+
disabled: formatDisabled && action.command !== "link",
|
|
1378
|
+
selected: snapshot?.activeTags.includes(action.tag) === true,
|
|
1379
|
+
value: action.tag,
|
|
1380
|
+
}),
|
|
1381
|
+
]
|
|
1382
|
+
: [];
|
|
1533
1383
|
});
|
|
1384
|
+
|
|
1385
|
+
// The verb cluster, sliced at the cap. `forPlacement` already dropped the records whose `when` is
|
|
1386
|
+
// False and sorted the rest by group; everything past the cap keeps its name and its chord in the
|
|
1387
|
+
// `⋮` menu rather than being silently unavailable.
|
|
1534
1388
|
const placed = registry.forPlacement("blockbar");
|
|
1535
|
-
|
|
1536
|
-
const
|
|
1389
|
+
|
|
1390
|
+
const parentPath = parentElementPath(selection);
|
|
1391
|
+
const parentNode = parentPath ? getNodeAtPath(tab.doc.document, parentPath) : null;
|
|
1392
|
+
const parentCommand = registry.get("selection.selectParent");
|
|
1393
|
+
/* The parent selector renders the `selection.selectParent` record — its name, its chord and its
|
|
1394
|
+
handler — but as fixed chrome rather than from `forPlacement("blockbar")`, because that record
|
|
1395
|
+
does not declare the placement. Its disabled state is "the selection has no parent", which the
|
|
1396
|
+
record's own `enablement` (a bare "there is a selection") cannot express yet. */
|
|
1397
|
+
const parentName = parentCommand?.title ?? "Select Parent";
|
|
1398
|
+
const parentLabel = parentNode ? `Select parent: ${nodeLabel(parentNode)}` : parentName;
|
|
1399
|
+
const parentChord = parentCommand ? registry.keymap.formatBinding(parentCommand.id) : undefined;
|
|
1400
|
+
|
|
1401
|
+
const convertTargets = convertTargetsFor(node);
|
|
1402
|
+
// Repeater ($prototype:"Array") pseudo-elements have no tagName — show the "Repeater → items"
|
|
1403
|
+
// Label (not a bare "div").
|
|
1404
|
+
const tagLabel =
|
|
1405
|
+
(node.$prototype === "Array"
|
|
1406
|
+
? nodeLabel(node)
|
|
1407
|
+
: node.$id || displayTagName(node.tagName) || "div") +
|
|
1408
|
+
(editingProp ? ` · ${editingProp}` : "");
|
|
1537
1409
|
// The handle is chrome, not a verb, so it renders on every selection; only a node that actually
|
|
1538
1410
|
// Sits at a child index can be dragged, and at the root it is a disabled affordance rather than a
|
|
1539
1411
|
// Missing one (§8.6: ONE shape).
|
|
1540
|
-
const
|
|
1541
|
-
|
|
1542
|
-
litRender(
|
|
1543
|
-
html`
|
|
1544
|
-
<div
|
|
1545
|
-
class="block-action-bar"
|
|
1546
|
-
data-jx-region="overlay.menu:block-action-bar"
|
|
1547
|
-
role="toolbar"
|
|
1548
|
-
aria-label="Block actions"
|
|
1549
|
-
aria-orientation="horizontal"
|
|
1550
|
-
style=${styleMap({ left: `${pos.left}px`, top: `${pos.top}px` })}
|
|
1551
|
-
@mousedown=${onBarMousedown}
|
|
1552
|
-
@keydown=${onToolbarKeydown}
|
|
1553
|
-
>
|
|
1554
|
-
${renderParentButton(registry)}
|
|
1555
|
-
|
|
1556
|
-
<span
|
|
1557
|
-
class="bar-tag${badgeInteractive ? " bar-tag--interactive" : ""}"
|
|
1558
|
-
role=${badgeInteractive ? "button" : nothing}
|
|
1559
|
-
data-toolbar-item=${badgeInteractive ? "" : nothing}
|
|
1560
|
-
tabindex=${badgeInteractive ? "-1" : nothing}
|
|
1561
|
-
title=${badgeInteractive ? "Change element type" : nothing}
|
|
1562
|
-
@click=${
|
|
1563
|
-
badgeInteractive
|
|
1564
|
-
? (e: MouseEvent) => onTagBadgeClick(e, convertTargets, selection)
|
|
1565
|
-
: nothing
|
|
1566
|
-
}
|
|
1567
|
-
>${isRepeater ? nodeLabel(node) : node.$id || displayTagName(node.tagName) || "div"}${
|
|
1568
|
-
editingProp ? ` · ${editingProp}` : ""
|
|
1569
|
-
}</span
|
|
1570
|
-
>
|
|
1571
|
-
|
|
1572
|
-
<span
|
|
1573
|
-
class="bar-drag-handle${canDragSelection ? "" : " bar-drag-handle--disabled"}"
|
|
1574
|
-
title=${canDragSelection ? "Drag to reorder" : "Drag to reorder — the document root cannot move"}
|
|
1575
|
-
aria-disabled=${canDragSelection ? nothing : "true"}
|
|
1576
|
-
data-toolbar-item
|
|
1577
|
-
tabindex="-1"
|
|
1578
|
-
${ref((handleEl) => {
|
|
1579
|
-
if (!handleEl || !canDragSelection) {
|
|
1580
|
-
return;
|
|
1581
|
-
}
|
|
1582
|
-
/* No release here: {@link renderBlockActionBar} already released at the top of this
|
|
1583
|
-
pass, and that one — not this — is the load-bearing copy, because it also runs on
|
|
1584
|
-
the passes that return before `litRender` and never reach this ref at all. A second
|
|
1585
|
-
release here could only ever see null, which is why it was dead. */
|
|
1586
|
-
view.selDragCleanup = draggable({
|
|
1587
|
-
element: handleEl as HTMLElement,
|
|
1588
|
-
getInitialData: () => ({
|
|
1589
|
-
// Snapshot the selection: the live array is a Vue reactive proxy, which
|
|
1590
|
-
// Structured clone rejects when the src crosses postMessage (DataCloneError
|
|
1591
|
-
// Killed the whole handle drag), and a live reference would also mutate the
|
|
1592
|
-
// Retained srcData if the selection changed mid-drag.
|
|
1593
|
-
path: [...(primarySelection(activeTab.value?.session.selection) ?? [])],
|
|
1594
|
-
type: "tree-node",
|
|
1595
|
-
}),
|
|
1596
|
-
onGenerateDragPreview: ({
|
|
1597
|
-
nativeSetDragImage,
|
|
1598
|
-
}: {
|
|
1599
|
-
nativeSetDragImage: ((image: Element, x: number, y: number) => void) | null;
|
|
1600
|
-
}) => {
|
|
1601
|
-
// Suppress the native drag image; the cross-frame ghost is the drag affordance.
|
|
1602
|
-
disableNativeDragPreview({ nativeSetDragImage });
|
|
1603
|
-
},
|
|
1604
|
-
});
|
|
1605
|
-
})}
|
|
1606
|
-
>⠿</span
|
|
1607
|
-
>
|
|
1608
|
-
|
|
1609
|
-
<sp-divider size="s" vertical></sp-divider>
|
|
1610
|
-
${shown.map((command) => renderCommandButton(registry, command))}
|
|
1611
|
-
${overflow.length > 0 ? renderOverflowButton(registry, overflow) : nothing}
|
|
1612
|
-
${
|
|
1613
|
-
showFormat
|
|
1614
|
-
? html`
|
|
1615
|
-
<sp-divider size="s" vertical></sp-divider>
|
|
1616
|
-
<sp-action-group
|
|
1617
|
-
size="xs"
|
|
1618
|
-
compact
|
|
1619
|
-
emphasized
|
|
1620
|
-
selects="multiple"
|
|
1621
|
-
selected=${activeValues.length > 0 ? JSON.stringify(activeValues) : nothing}
|
|
1622
|
-
>
|
|
1623
|
-
${formatButtons.map(
|
|
1624
|
-
({ action, command }) => html`
|
|
1625
|
-
<sp-action-button
|
|
1626
|
-
size="xs"
|
|
1627
|
-
value=${action.tag}
|
|
1628
|
-
data-command-id=${command.id}
|
|
1629
|
-
data-toolbar-item
|
|
1630
|
-
tabindex="-1"
|
|
1631
|
-
aria-label=${command.title}
|
|
1632
|
-
title=${commandTooltip(registry, command)}
|
|
1633
|
-
?disabled=${formatDisabled && action.command !== "link"}
|
|
1634
|
-
@mousedown=${(e: MouseEvent) => e.preventDefault()}
|
|
1635
|
-
@click=${(e: MouseEvent) => {
|
|
1636
|
-
e.stopPropagation();
|
|
1637
|
-
runCommand(registry, command.id);
|
|
1638
|
-
}}
|
|
1639
|
-
>
|
|
1640
|
-
${commandIcon(command)}
|
|
1641
|
-
</sp-action-button>
|
|
1642
|
-
`,
|
|
1643
|
-
)}
|
|
1644
|
-
</sp-action-group>
|
|
1645
|
-
<sp-action-button
|
|
1646
|
-
size="xs"
|
|
1647
|
-
quiet
|
|
1648
|
-
data-toolbar-item
|
|
1649
|
-
data-jx-region="overlay.menu:block-action-bar/insertData"
|
|
1650
|
-
tabindex="-1"
|
|
1651
|
-
aria-label="Insert data"
|
|
1652
|
-
title="Insert data"
|
|
1653
|
-
@mousedown=${(e: MouseEvent) => e.preventDefault()}
|
|
1654
|
-
@click=${onMergeTagClick}
|
|
1655
|
-
>
|
|
1656
|
-
<sp-icon-data slot="icon"></sp-icon-data>
|
|
1657
|
-
</sp-action-button>
|
|
1658
|
-
`
|
|
1659
|
-
: nothing
|
|
1660
|
-
}
|
|
1661
|
-
</div>
|
|
1662
|
-
`,
|
|
1663
|
-
view.blockActionBarEl,
|
|
1664
|
-
);
|
|
1412
|
+
const canDrag = structuralTarget(selection) !== null;
|
|
1665
1413
|
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1414
|
+
return {
|
|
1415
|
+
barX: pos.left,
|
|
1416
|
+
barY: pos.top,
|
|
1417
|
+
canDrag,
|
|
1418
|
+
dragHint: canDrag ? "Drag to reorder" : "Drag to reorder — the document root cannot move",
|
|
1419
|
+
formats,
|
|
1420
|
+
hasOverflow: placed.length > BLOCKBAR_MAX_ITEMS,
|
|
1421
|
+
offscreen: false,
|
|
1422
|
+
overflowOpen: _overflowHandle !== null,
|
|
1423
|
+
parentDisabled: !parentPath,
|
|
1424
|
+
parentHint: parentChord ? `${parentLabel} (${parentChord})` : parentLabel,
|
|
1425
|
+
parentLabel,
|
|
1426
|
+
showFormat,
|
|
1427
|
+
tagDisabled: convertTargets.length === 0,
|
|
1428
|
+
tagHint: convertTargets.length > 0 ? "Change element type" : tagLabel,
|
|
1429
|
+
tagLabel,
|
|
1430
|
+
tagPopup: convertTargets.length > 0 ? "menu" : "",
|
|
1431
|
+
verbs: placed.slice(0, BLOCKBAR_MAX_ITEMS).map((command) => toolOf(registry, command)),
|
|
1432
|
+
visible: true,
|
|
1433
|
+
};
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
/** Render the unified block action bar above the selected element. */
|
|
1437
|
+
export function renderBlockActionBar(): void {
|
|
1438
|
+
if (!_ctx) {
|
|
1439
|
+
return;
|
|
1669
1440
|
}
|
|
1441
|
+
ensureSurface();
|
|
1670
1442
|
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1443
|
+
const tab = activeTab.value;
|
|
1444
|
+
const selection = primarySelection(tab?.session.selection);
|
|
1445
|
+
|
|
1446
|
+
/* Suppressed by a chrome pointerdown, and this pass is the first of the two doors out of it (see
|
|
1447
|
+
{@link _suppressedFor}). A different selection means the click that hid the bar also chose a new
|
|
1448
|
+
target, so the suppression is over; the same selection keeps it, which is what stops the next
|
|
1449
|
+
snapshot- or overlay-driven repaint from flashing the bar back under the author's cursor. */
|
|
1450
|
+
if (_suppressedFor) {
|
|
1451
|
+
if (_suppressedFor.key === suppressionKey(tab, selection)) {
|
|
1452
|
+
hideBar();
|
|
1675
1453
|
return;
|
|
1676
1454
|
}
|
|
1677
|
-
|
|
1455
|
+
_suppressedFor = null;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
const next = projectBar();
|
|
1459
|
+
if (!next) {
|
|
1460
|
+
hideBar();
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
applyBarView(next);
|
|
1464
|
+
|
|
1465
|
+
// Post-render side effect: the clamp measures a box that does not exist until the document has
|
|
1466
|
+
// Reconciled, so it waits a frame rather than reading a stale one.
|
|
1467
|
+
requestAnimationFrame(() => {
|
|
1468
|
+
const bar = _surface?.bar();
|
|
1469
|
+
if (bar) {
|
|
1470
|
+
clampBarToWindow(bar);
|
|
1471
|
+
}
|
|
1678
1472
|
});
|
|
1679
1473
|
}
|