@jxsuite/studio 4.1.0 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/data/elements-meta.json +5 -15
- package/data/html-meta.json +51 -6
- package/dist/chunks/{2020-n6tfwnda.js → 2020-g79n01v9.js} +14 -14
- package/dist/chunks/action-group-sp6tna8v.js +118 -0
- package/dist/chunks/adapter-nj7tnjfn.js +13 -0
- package/dist/chunks/cf-account-picker-qwkm9zrf.js +495 -0
- package/dist/chunks/cf-settings-71bv5zn9.js +20 -0
- package/dist/chunks/color-area-v17df7b8.js +154 -0
- package/dist/chunks/color-field-anwybnkn.js +208 -0
- package/dist/chunks/color-slider-44ez96fc.js +79 -0
- package/dist/chunks/color-xgg3kfw7.js +35 -0
- package/dist/chunks/combobox-5e7jev23.js +236 -0
- package/dist/chunks/components-vj72gqtq.js +20 -0
- package/dist/chunks/content-source-5mex4x1h.js +46 -0
- package/dist/chunks/convert-to-repeater-n64h8znc.js +639 -0
- package/dist/chunks/dialog-s890b918.js +85 -0
- package/dist/chunks/editor-3qhbg51x.js +35 -0
- package/dist/chunks/entry-commands-234hjnqw.js +42 -0
- package/dist/chunks/extension-sections-3hpcambp.js +651 -0
- package/dist/chunks/file-ops-8yhd83xr.js +47 -0
- package/dist/chunks/files-hnxqxtsg.js +72 -0
- package/dist/chunks/format-host-779sfp2n.js +40 -0
- package/dist/chunks/fs-events-b88z7g4h.js +18 -0
- package/dist/chunks/git-panel-xwj8fpab.js +49 -0
- package/dist/chunks/icons-yy10wk9r.js +402 -0
- package/dist/chunks/iframe-entry-15g3n6xv.js +114 -0
- package/dist/chunks/iframe-entry-b27vq81n.js +203 -0
- package/dist/chunks/iframe-entry-nbyxee1y.js +154 -0
- package/dist/chunks/iframe-host-syr0f9sb.js +115 -0
- package/dist/chunks/{index-wcnt3r9g.js → index-mdmyx6s2.js} +11 -11
- package/dist/chunks/index-n4j7qxyr.js +109 -0
- package/dist/chunks/{javascript-q8kpmfah.js → javascript-xvrfrpkb.js} +8 -9
- package/dist/chunks/{jsonMode-dkr040ga.js → jsonMode-tyx2kj6p.js} +526 -527
- package/dist/chunks/jx-validate-j0wxk72w.js +15 -0
- package/dist/chunks/layers-ff81wthn.js +30 -0
- package/dist/chunks/layers-panel-pnnghw12.js +40 -0
- package/dist/chunks/listbox-6v2ac08j.js +44 -0
- package/dist/chunks/lit-html-m244w9nh.js +17 -0
- package/dist/chunks/media-picker-cqhhxhhe.js +33 -0
- package/dist/chunks/menu-9p2w46gj.js +15 -0
- package/dist/chunks/menu-q99sy9rf.js +285 -0
- package/dist/chunks/{monaco-binding-rj6xpx9y.js → monaco-binding-sw6en4dw.js} +12 -15
- package/dist/chunks/monaco-buffer-bhy3hb6m.js +24 -0
- package/dist/chunks/{monaco-setup-vx9bhp1d.js → monaco-setup-8r62fj6y.js} +27 -37
- package/dist/chunks/notify-628t0anh.js +21 -0
- package/dist/chunks/number-field-5cndyft8.js +72 -0
- package/dist/chunks/parse-0zx14y15.js +10 -0
- package/dist/chunks/platform-ts9cg1a4.js +15 -0
- package/dist/chunks/popover-47z3ytva.js +25 -0
- package/dist/chunks/publish-panel-5zt840sy.js +27 -0
- package/dist/chunks/quick-search-3748564r.js +45 -0
- package/dist/chunks/references-qvvxej7h.js +26 -0
- package/dist/chunks/right-panel-eccz92qt.js +45 -0
- package/dist/chunks/section-registry-5pq32t0q.js +25 -0
- package/dist/chunks/select-2r16ez41.js +49 -0
- package/dist/chunks/split-g7nm1rgb.js +191 -0
- package/dist/chunks/studio-0hqbpbd0.js +62 -0
- package/dist/chunks/{studio-kryvsw6j.js → studio-1k325pwf.js} +48 -48
- package/dist/chunks/{studio-b55y6ewx.js → studio-1knfqgy7.js} +58 -58
- package/dist/chunks/{studio-2b946z26.js → studio-1z1kcjv4.js} +3628 -6095
- package/dist/chunks/studio-3773086z.js +111 -0
- package/dist/chunks/{studio-4r1pv6zf.js → studio-38emantv.js} +22 -5
- package/dist/chunks/{studio-x813zp48.js → studio-3rz5md06.js} +30 -30
- package/dist/chunks/{studio-ynxwkrzd.js → studio-4wbb9ct0.js} +1953 -1785
- package/dist/chunks/studio-5te5tqng.js +81 -0
- package/dist/chunks/studio-5wt9nvhd.js +4763 -0
- package/dist/chunks/{studio-p4ngc046.js → studio-6ewqv390.js} +9 -9
- package/dist/chunks/{studio-9ab8gqsm.js → studio-6nzaktvy.js} +7 -7
- package/dist/chunks/studio-6xa21ej9.js +28 -0
- package/dist/chunks/{studio-5ehhackm.js → studio-727xh0e3.js} +19 -19
- package/dist/chunks/{studio-gcye7qka.js → studio-7e11jpa9.js} +21 -21
- package/dist/chunks/{studio-q7v10cg2.js → studio-7r42c6ca.js} +17 -7
- package/dist/chunks/studio-7xnmgv66.js +13615 -0
- package/dist/chunks/studio-8scdew0e.js +258 -0
- package/dist/chunks/{studio-fbf92ckf.js → studio-93zafjcm.js} +275 -163
- package/dist/chunks/{studio-dg7zyt5x.js → studio-98b3z07n.js} +1 -1
- package/dist/chunks/studio-a79k90n9.js +1162 -0
- package/dist/chunks/studio-aj37eer3.js +422 -0
- package/dist/chunks/studio-b0k5tqgp.js +2432 -0
- package/dist/chunks/studio-b4s9eykd.js +2811 -0
- package/dist/chunks/{studio-f3wqjtk5.js → studio-d0300636.js} +5076 -18
- package/dist/chunks/{studio-b9b6t1dg.js → studio-e1f24eff.js} +5664 -5666
- package/dist/chunks/studio-e1pgga3k.js +839 -0
- package/dist/chunks/{studio-g1wdche0.js → studio-esrxgwh6.js} +80 -80
- package/dist/chunks/{studio-q43a5y9h.js → studio-g3jevyjt.js} +16 -16
- package/dist/chunks/studio-gb13095a.js +2806 -0
- package/dist/chunks/{studio-3n4ndrzh.js → studio-h4nvnjvz.js} +303 -303
- package/dist/chunks/{studio-dm19q43r.js → studio-hhvs4sdg.js} +10 -10
- package/dist/chunks/{studio-x4qkdat5.js → studio-js8nv53n.js} +1749 -2481
- package/dist/chunks/{studio-qjy2z9bv.js → studio-jxp12rnj.js} +32 -137
- package/dist/chunks/{studio-trxrabqf.js → studio-k75n7vhp.js} +527 -283
- package/dist/chunks/{studio-q9jd1a60.js → studio-kcgff4r0.js} +15 -18
- package/dist/chunks/studio-kkqkcp3d.js +17668 -0
- package/dist/chunks/studio-mhhyamxf.js +763 -0
- package/dist/chunks/{studio-cft9r4r7.js → studio-nq5yf5mp.js} +22 -22
- package/dist/chunks/{studio-685s0ytq.js → studio-nqgawfks.js} +572 -572
- package/dist/chunks/studio-nzzpvzx9.js +185 -0
- package/dist/chunks/{studio-xrapxwwc.js → studio-p1dw8wfw.js} +55 -29
- package/dist/chunks/{studio-3bqpr0zh.js → studio-pxbgypmg.js} +299 -110
- package/dist/chunks/studio-q0a3yk4n.js +33 -0
- package/dist/chunks/{studio-wcy8trfx.js → studio-q3x06vmj.js} +23 -22
- package/dist/chunks/{studio-6yw4y3my.js → studio-qhhm6hbt.js} +8 -8
- package/dist/chunks/studio-qk4bfsq2.js +362 -0
- package/dist/chunks/{studio-msd847zn.js → studio-qk90fgqm.js} +361 -90
- package/dist/chunks/{studio-jkftxyvz.js → studio-r6nvacqs.js} +5861 -2154
- package/dist/chunks/{studio-53n8bp04.js → studio-r6xmmtfh.js} +1 -1
- package/dist/chunks/{studio-tvgyw9xw.js → studio-rnyf3f8e.js} +83 -250
- package/dist/chunks/studio-rs76k67r.js +12 -0
- package/dist/chunks/studio-sbjyc8bt.js +954 -0
- package/dist/chunks/{studio-1wagb550.js → studio-syg2tadj.js} +11 -11
- package/dist/chunks/{studio-t5wqwzdh.js → studio-tjjk7jvk.js} +1 -1
- package/dist/chunks/studio-tqgryxwg.js +1171 -0
- package/dist/chunks/studio-try13h8c.js +8448 -0
- package/dist/chunks/studio-vfq8qpjt.js +1219 -0
- package/dist/chunks/studio-vhv5tgxm.js +51 -0
- package/dist/chunks/{studio-ahn3pdgx.js → studio-vw7mzp6z.js} +52 -38
- package/dist/chunks/studio-wqpngpsk.js +174 -0
- package/dist/chunks/studio-wqz6dggm.js +1356 -0
- package/dist/chunks/{studio-92ab2jqe.js → studio-y9zedyjn.js} +14 -14
- package/dist/chunks/studio-yxa81f45.js +12 -0
- package/dist/chunks/studio-zdn2g6dt.js +148 -0
- package/dist/chunks/swatch-group-t0n6h2rn.js +139 -0
- package/dist/chunks/tabs-wbp1cdws.js +156 -0
- package/dist/chunks/textfield-abk6qyee.js +73 -0
- package/dist/chunks/toast-780pvfbv.js +27 -0
- package/dist/chunks/toast-host-3sqv46sn.js +116 -0
- package/dist/chunks/toolbar-xbh5bhtr.js +194 -0
- package/dist/chunks/tooltip-eptb614w.js +369 -0
- package/dist/chunks/tree-a73p0pqm.js +367 -0
- package/dist/chunks/{tsMode-76kr53q0.js → tsMode-jy4m86rv.js} +83 -85
- package/dist/chunks/workspace-t55jdjbx.js +69 -0
- package/dist/chunks/{ws-client-6shc5rze.js → ws-client-ny4zc51r.js} +20 -22
- package/dist/iframe-entry.js +1873 -673
- package/dist/studio.js +52757 -36535
- package/dist/workers/editor.worker.js +1040 -1085
- package/dist/workers/json.worker.js +1946 -1991
- package/dist/workers/ts.worker.js +4960 -5005
- package/index.html +1 -0
- package/package.json +18 -52
- package/src/about/about-modal.ts +72 -123
- package/src/browse/library-commands.ts +20 -49
- package/src/browse/library-layouts.ts +87 -218
- package/src/browse/library-pane.ts +406 -438
- package/src/canvas/canvas-helpers.ts +0 -49
- package/src/canvas/canvas-live-render.ts +25 -2
- package/src/canvas/canvas-render.ts +666 -231
- package/src/canvas/canvas-surface.ts +0 -1
- package/src/canvas/canvas-utils.ts +333 -113
- package/src/canvas/dialog-path.ts +76 -0
- package/src/canvas/dialog-state.ts +67 -0
- package/src/canvas/diff-marks.ts +252 -0
- package/src/canvas/diff-toolbar.ts +349 -0
- package/src/canvas/diff-view.ts +123 -0
- package/src/canvas/edit-width-drag.ts +187 -0
- package/src/canvas/edit-width.ts +150 -0
- package/src/canvas/iframe-entry.ts +125 -8
- package/src/canvas/iframe-host.ts +271 -15
- package/src/canvas/iframe-interaction.ts +133 -13
- package/src/canvas/iframe-patch.ts +7 -1
- package/src/canvas/iframe-position.ts +5 -6
- package/src/canvas/iframe-protocol.ts +106 -5
- package/src/canvas/iframe-render.ts +373 -5
- package/src/canvas/iframe-slash.ts +2 -2
- package/src/canvas/iframe-subtree.ts +5 -8
- package/src/canvas/path-mapping.ts +25 -3
- package/src/canvas/popover-path.ts +106 -0
- package/src/canvas/popover-state.ts +155 -0
- package/src/canvas/surface-registry.ts +21 -10
- package/src/collab/presence-chips.ts +51 -73
- package/src/commands/app-commands.ts +10 -0
- package/src/commands/budget.ts +12 -0
- package/src/commands/command-args.ts +404 -6
- package/src/commands/defaults.ts +44 -14
- package/src/commands/live-context.ts +11 -5
- package/src/commands/reference.ts +12 -2
- package/src/commands/registry.ts +177 -10
- package/src/commands/run-reported.ts +120 -0
- package/src/content/entry-commands.ts +16 -23
- package/src/content/entry-editor.ts +144 -101
- package/src/editor/canvas-slash-bridge.ts +2 -2
- package/src/editor/context-menu.ts +49 -200
- package/src/editor/convert-to-component.ts +42 -82
- package/src/editor/convert-to-repeater.ts +132 -173
- package/src/editor/inline-edit.ts +11 -0
- package/src/editor/shortcuts.ts +76 -49
- package/src/editor/slash-menu.ts +150 -157
- package/src/files/components.ts +39 -0
- package/src/files/file-ops.ts +117 -11
- package/src/files/files.ts +944 -660
- package/src/files/json-layout.ts +474 -0
- package/src/files/serialize-document.ts +8 -1
- package/src/format/convert-file.ts +16 -10
- package/src/format/format-host.ts +50 -1
- package/src/github/github-auth.ts +71 -29
- package/src/github/github-publish.ts +57 -65
- package/src/grid/cell-editors.ts +46 -4
- package/src/grid/cell-popovers.ts +65 -62
- package/src/grid/grid-open.ts +86 -64
- package/src/grid/grid-panel.ts +390 -469
- package/src/grid/redirects-grid.ts +40 -44
- package/src/hosting/layout.ts +2 -0
- package/src/i18n/i18n-commands.ts +137 -62
- package/src/media/media-pane.ts +83 -133
- package/src/new-project/add-repo-modal.ts +125 -176
- package/src/new-project/import-tab.ts +181 -252
- package/src/new-project/location-fields.ts +93 -160
- package/src/new-project/new-project-modal.ts +376 -367
- package/src/packages/jxsuite-update.ts +76 -18
- package/src/packages/pull-package-sync.ts +5 -1
- package/src/panels/activity-panel.ts +137 -104
- package/src/panels/ai-chat/chat-markdown.ts +15 -13
- package/src/panels/ai-chat/chat-view.ts +307 -472
- package/src/panels/ai-chat/composer.ts +175 -163
- package/src/panels/ai-chat/sessions-view.ts +31 -62
- package/src/panels/ai-panel.ts +213 -185
- package/src/panels/block-action-bar.ts +444 -645
- package/src/panels/bottom-dock.ts +112 -47
- package/src/panels/chat-panel.ts +19 -14
- package/src/panels/data-explorer.ts +141 -102
- package/src/panels/data-grid.ts +183 -150
- package/src/panels/dnd.ts +75 -20
- package/src/panels/editors.ts +26 -19
- package/src/panels/elements-panel.ts +243 -186
- package/src/panels/empty-state.ts +26 -42
- package/src/panels/events-panel.ts +950 -729
- package/src/panels/formula-workspace.ts +434 -207
- package/src/panels/frontmatter-fields.ts +185 -153
- package/src/panels/frontmatter-panel.ts +411 -187
- package/src/panels/git-diff-open.ts +148 -0
- package/src/panels/git-panel.ts +679 -605
- package/src/panels/head-panel.ts +617 -435
- package/src/panels/i18n-panel.ts +148 -135
- package/src/panels/imports-panel.ts +388 -414
- package/src/panels/jump-bar.ts +175 -147
- package/src/panels/layers-panel.ts +785 -671
- package/src/panels/left-panel.ts +166 -94
- package/src/panels/navigator-panels.ts +1 -1
- package/src/panels/pane-context.ts +529 -677
- package/src/panels/pane-grid.ts +265 -241
- package/src/panels/panel-registry.ts +15 -4
- package/src/panels/problems-panel.ts +181 -99
- package/src/panels/properties-panel.ts +1525 -966
- package/src/panels/provenance.ts +31 -113
- package/src/panels/quick-search.ts +223 -184
- package/src/panels/right-panel.ts +135 -169
- package/src/panels/seo-modal.ts +346 -167
- package/src/panels/settings-menu.ts +114 -661
- package/src/panels/settings-pane.ts +102 -86
- package/src/panels/signals-panel.ts +1318 -1172
- package/src/panels/statement-editor.ts +556 -428
- package/src/panels/style-panel.ts +1367 -630
- package/src/panels/style-utils.ts +36 -32
- package/src/panels/stylebook-doc.ts +30 -73
- package/src/panels/stylebook-layers-panel.ts +202 -109
- package/src/panels/stylebook-panel.ts +124 -88
- package/src/panels/tab-strip.ts +386 -288
- package/src/platforms/cloud.ts +106 -16
- package/src/platforms/devserver.ts +13 -0
- package/src/publish/deploy-checklist.ts +124 -78
- package/src/publish/publish-commands.ts +4 -12
- package/src/publish/publish-panel.ts +134 -328
- package/src/services/a11y-report.ts +100 -43
- package/src/services/ai-command-tools.ts +538 -0
- package/src/services/ai-project-tools.ts +8 -2
- package/src/services/ai-session-store.ts +6 -0
- package/src/services/ai-system-prompt.ts +112 -24
- package/src/services/ai-tools.ts +36 -212
- package/src/services/ai-write-report.ts +174 -0
- package/src/services/document-assistant.ts +59 -8
- package/src/services/idle.ts +16 -14
- package/src/services/live-surfaces.ts +237 -0
- package/src/services/model-uri.ts +75 -0
- package/src/services/notify.ts +63 -6
- package/src/services/popover-report.ts +325 -0
- package/src/services/surface-registry.ts +56 -0
- package/src/services/tool-executor.ts +6 -0
- package/src/settings/contexts-section.ts +301 -299
- package/src/settings/contributed-section.ts +361 -222
- package/src/settings/css-vars-editor.ts +320 -325
- package/src/settings/defs-editor.ts +572 -535
- package/src/settings/dependencies-editor.ts +79 -107
- package/src/settings/extension-commands.ts +379 -0
- package/src/settings/extension-rows.ts +113 -0
- package/src/settings/extensions-section.ts +287 -0
- package/src/settings/general-settings.ts +130 -181
- package/src/settings/head-editor.ts +10 -305
- package/src/settings/locales-section.ts +146 -196
- package/src/settings/preferences-accounts.ts +1 -1
- package/src/settings/preferences-dialog.ts +350 -330
- package/src/settings/project-sections.ts +108 -181
- package/src/settings/schema-field-ui.ts +11 -399
- package/src/settings/section-registry.ts +8 -1
- package/src/settings/settings-document.ts +31 -29
- package/src/shell/tree.ts +42 -87
- package/src/shell.ts +48 -36
- package/src/studio.ts +104 -69
- package/src/style/token-ref.ts +0 -39
- package/src/surfaces/about.json +263 -0
- package/src/surfaces/about.ts +163 -0
- package/src/surfaces/add-repo.json +486 -0
- package/src/surfaces/add-repo.ts +236 -0
- package/src/surfaces/ai-chat.json +2185 -0
- package/src/surfaces/ai-chat.ts +549 -0
- package/src/surfaces/ai-credentials-form.json +332 -0
- package/src/surfaces/ai-credentials-form.ts +151 -0
- package/src/surfaces/ai-managed-connect.json +108 -0
- package/src/surfaces/ai-managed-connect.ts +123 -0
- package/src/surfaces/ai-model-picker.json +114 -0
- package/src/surfaces/ai-model-picker.ts +139 -0
- package/src/surfaces/block-action-bar.json +640 -0
- package/src/surfaces/block-action-bar.ts +368 -0
- package/src/surfaces/bottom-dock.json +154 -0
- package/src/surfaces/bottom-dock.ts +125 -0
- package/src/surfaces/canvas-stage.json +444 -0
- package/src/surfaces/canvas-stage.ts +253 -0
- package/src/surfaces/cf-account-picker.json +300 -0
- package/src/surfaces/cf-account-picker.ts +214 -0
- package/src/surfaces/commandbar.json +985 -0
- package/src/{panels/toolbar.ts → surfaces/commandbar.ts} +417 -391
- package/src/surfaces/convert-repeater.json +309 -0
- package/src/surfaces/convert-repeater.ts +276 -0
- package/src/surfaces/data-actions.json +157 -0
- package/src/surfaces/data-actions.ts +115 -0
- package/src/surfaces/dialog.json +289 -0
- package/src/surfaces/dialog.ts +343 -0
- package/src/surfaces/diff-toolbar.json +266 -0
- package/src/surfaces/diff-toolbar.ts +139 -0
- package/src/surfaces/doc-header.json +739 -0
- package/src/surfaces/doc-header.ts +223 -0
- package/src/surfaces/empty-state.json +152 -0
- package/src/surfaces/empty-state.ts +173 -0
- package/src/surfaces/entry-editor.json +281 -0
- package/src/surfaces/entry-editor.ts +191 -0
- package/src/surfaces/expression-editor.json +977 -0
- package/src/surfaces/expression-editor.ts +266 -0
- package/src/surfaces/files-panel.json +592 -0
- package/src/surfaces/files-panel.ts +301 -0
- package/src/surfaces/formula-palette.json +364 -0
- package/src/surfaces/formula-palette.ts +340 -0
- package/src/surfaces/git-panel.json +1822 -0
- package/src/surfaces/git-panel.ts +272 -0
- package/src/surfaces/github-auth.json +112 -0
- package/src/surfaces/github-auth.ts +139 -0
- package/src/surfaces/github-publish.json +206 -0
- package/src/surfaces/github-publish.ts +169 -0
- package/src/surfaces/grid-cell.json +218 -0
- package/src/surfaces/grid-cell.ts +120 -0
- package/src/surfaces/grid-open.json +147 -0
- package/src/surfaces/grid-open.ts +87 -0
- package/src/surfaces/grid-panel.json +706 -0
- package/src/surfaces/grid-panel.ts +206 -0
- package/src/surfaces/grid-replace.json +179 -0
- package/src/surfaces/grid-replace.ts +89 -0
- package/src/surfaces/grid-views.json +446 -0
- package/src/surfaces/grid-views.ts +172 -0
- package/src/surfaces/inspector-dock.json +187 -0
- package/src/surfaces/inspector-dock.ts +161 -0
- package/src/surfaces/jump-bar.json +243 -0
- package/src/surfaces/jump-bar.ts +129 -0
- package/src/surfaces/jxsuite-update.json +146 -0
- package/src/surfaces/jxsuite-update.ts +149 -0
- package/src/surfaces/library-pane.json +1509 -0
- package/src/surfaces/library-pane.ts +363 -0
- package/src/surfaces/logic-panel.json +1949 -0
- package/src/surfaces/logic-panel.ts +369 -0
- package/src/surfaces/logic-workspace.json +685 -0
- package/src/surfaces/logic-workspace.ts +238 -0
- package/src/surfaces/media-browser.json +303 -0
- package/src/surfaces/media-browser.ts +145 -0
- package/src/surfaces/media-field.json +163 -0
- package/src/surfaces/media-field.ts +116 -0
- package/src/surfaces/media-pane.json +513 -0
- package/src/surfaces/media-pane.ts +197 -0
- package/src/surfaces/menu.json +232 -0
- package/src/surfaces/menu.ts +375 -0
- package/src/surfaces/navigator-dock.json +113 -0
- package/src/surfaces/navigator-dock.ts +130 -0
- package/src/surfaces/new-project.json +1654 -0
- package/src/surfaces/new-project.ts +411 -0
- package/src/surfaces/palette.json +535 -0
- package/src/surfaces/pane-context.json +1497 -0
- package/src/surfaces/pane-context.ts +513 -0
- package/src/surfaces/pane-grid.json +188 -0
- package/src/surfaces/pane-grid.ts +250 -0
- package/src/surfaces/panel-activity.json +563 -0
- package/src/surfaces/panel-activity.ts +187 -0
- package/src/surfaces/panel-data.json +141 -0
- package/src/surfaces/panel-data.ts +156 -0
- package/src/surfaces/panel-deploy-checklist.json +283 -0
- package/src/surfaces/panel-deploy-checklist.ts +175 -0
- package/src/surfaces/panel-elements.json +367 -0
- package/src/surfaces/panel-elements.ts +175 -0
- package/src/surfaces/panel-i18n.json +569 -0
- package/src/surfaces/panel-i18n.ts +212 -0
- package/src/surfaces/panel-imports.json +780 -0
- package/src/surfaces/panel-imports.ts +209 -0
- package/src/surfaces/panel-outline.json +734 -0
- package/src/surfaces/panel-outline.ts +326 -0
- package/src/surfaces/panel-page.json +1017 -0
- package/src/surfaces/panel-page.ts +240 -0
- package/src/surfaces/panel-problems.json +497 -0
- package/src/surfaces/panel-problems.ts +157 -0
- package/src/surfaces/panel-signals.json +1973 -0
- package/src/surfaces/panel-signals.ts +436 -0
- package/src/surfaces/panel-stylebook-layers.json +317 -0
- package/src/surfaces/panel-stylebook-layers.ts +155 -0
- package/src/surfaces/preferences.json +850 -0
- package/src/surfaces/preferences.ts +332 -0
- package/src/surfaces/progress-modal.json +110 -0
- package/src/surfaces/progress-modal.ts +150 -0
- package/src/surfaces/properties-panel.json +1578 -0
- package/src/surfaces/properties-panel.ts +337 -0
- package/src/surfaces/publish.json +945 -0
- package/src/surfaces/publish.ts +392 -0
- package/src/surfaces/push-plan.json +110 -0
- package/src/surfaces/push-plan.ts +87 -0
- package/src/surfaces/rail.json +255 -0
- package/src/surfaces/rail.ts +206 -0
- package/src/surfaces/reference-field.json +191 -0
- package/src/surfaces/reference-field.ts +168 -0
- package/src/surfaces/schema-builder.json +1011 -0
- package/src/surfaces/schema-builder.ts +181 -0
- package/src/surfaces/schema-form.json +1013 -0
- package/src/surfaces/schema-form.ts +287 -0
- package/src/surfaces/secret-field.json +60 -0
- package/src/surfaces/secret-field.ts +146 -0
- package/src/surfaces/seo.json +932 -0
- package/src/surfaces/seo.ts +334 -0
- package/src/surfaces/settings-contexts.json +538 -0
- package/src/surfaces/settings-contexts.ts +196 -0
- package/src/surfaces/settings-contributed.json +489 -0
- package/src/surfaces/settings-contributed.ts +199 -0
- package/src/surfaces/settings-css-vars.json +820 -0
- package/src/surfaces/settings-css-vars.ts +199 -0
- package/src/surfaces/settings-defs.json +1363 -0
- package/src/surfaces/settings-defs.ts +232 -0
- package/src/surfaces/settings-deploy.json +108 -0
- package/src/surfaces/settings-deploy.ts +135 -0
- package/src/surfaces/settings-extensions.json +486 -0
- package/src/surfaces/settings-extensions.ts +201 -0
- package/src/surfaces/settings-head.json +779 -0
- package/src/surfaces/settings-head.ts +397 -0
- package/src/surfaces/settings-locales.json +420 -0
- package/src/surfaces/settings-locales.ts +137 -0
- package/src/surfaces/settings-overview.json +423 -0
- package/src/surfaces/settings-overview.ts +218 -0
- package/src/surfaces/settings-packages.json +549 -0
- package/src/surfaces/settings-packages.ts +158 -0
- package/src/surfaces/settings-pane.json +147 -0
- package/src/surfaces/settings-pane.ts +150 -0
- package/src/surfaces/settings-rawjson.json +104 -0
- package/src/surfaces/settings-rawjson.ts +107 -0
- package/src/surfaces/shell.json +99 -0
- package/src/surfaces/shell.ts +30 -0
- package/src/surfaces/slash-menu.json +180 -0
- package/src/surfaces/slash-menu.ts +272 -0
- package/src/surfaces/statements.json +607 -0
- package/src/surfaces/statements.ts +238 -0
- package/src/surfaces/statusbar.json +94 -0
- package/src/{panels → surfaces}/statusbar.ts +164 -90
- package/src/surfaces/style-panel.json +1643 -0
- package/src/surfaces/style-panel.ts +338 -0
- package/src/surfaces/stylebook-chrome.json +122 -0
- package/src/surfaces/stylebook-chrome.ts +122 -0
- package/src/surfaces/tab-strip.json +448 -0
- package/src/surfaces/tab-strip.ts +200 -0
- package/src/surfaces/target-line.json +419 -0
- package/src/surfaces/target-line.ts +349 -0
- package/src/surfaces/toasts.json +189 -0
- package/src/surfaces/welcome.json +571 -0
- package/src/surfaces/welcome.ts +338 -0
- package/src/tabs/project-config.ts +111 -21
- package/src/tabs/tab.ts +52 -1
- package/src/types.ts +22 -0
- package/src/ui/ai-credentials-form.ts +104 -100
- package/src/ui/ai-managed-connect.ts +54 -36
- package/src/ui/ai-model-picker.ts +75 -47
- package/src/ui/cf-account-picker.ts +76 -141
- package/src/ui/color-selector.ts +80 -301
- package/src/ui/dynamic-slot.ts +43 -283
- package/src/ui/expression-editor.ts +797 -576
- package/src/ui/field-input.ts +8 -181
- package/src/ui/form-controls.ts +838 -366
- package/src/ui/formula-chips.ts +80 -85
- package/src/ui/kit.ts +33 -0
- package/src/ui/layers.ts +296 -593
- package/src/ui/media-picker.ts +203 -284
- package/src/ui/panel-resize.ts +251 -64
- package/src/ui/popover-surface.ts +153 -0
- package/src/ui/progress-modal.ts +22 -60
- package/src/ui/regions.ts +13 -7
- package/src/ui/schema-form.ts +906 -440
- package/src/ui/surface.ts +122 -0
- package/src/ui/unit-selector.ts +15 -131
- package/src/ui/value-source.ts +14 -0
- package/src/ui/virtual-window.ts +88 -9
- package/src/utils/canvas-media.ts +72 -0
- package/src/utils/edit-display.ts +7 -2
- package/src/utils/element-selectors.ts +69 -0
- package/src/utils/link-target.ts +2 -2
- package/src/utils/studio-utils.ts +0 -10
- package/src/view.ts +15 -0
- package/src/workspace/pane-derive.ts +36 -14
- package/src/workspace/workspace.ts +75 -6
- package/styles/canvas.css +72 -467
- package/styles/forced-colors.css +48 -82
- package/styles/forced-colors.json +41 -0
- package/styles/inspector.css +2 -1301
- package/styles/overlays.css +96 -2823
- package/styles/panels.css +6 -1423
- package/styles/shell-frame.css +272 -0
- package/styles/shell-frame.json +227 -0
- package/styles/shell.css +0 -1568
- package/styles/tokens.css +65 -136
- package/styles/tokens.json +84 -0
- package/dist/chunks/Overlay.dev-etqjwyf9.js +0 -23
- package/dist/chunks/adapter-jkcyhgtr.js +0 -14
- package/dist/chunks/cf-account-picker-en86v7x7.js +0 -163
- package/dist/chunks/cf-settings-45fgyx8y.js +0 -21
- package/dist/chunks/components-zmx10t62.js +0 -21
- package/dist/chunks/content-source-48xg86wj.js +0 -49
- package/dist/chunks/convert-to-repeater-43tfn7cs.js +0 -268
- package/dist/chunks/editor-cptq5jp1.js +0 -37
- package/dist/chunks/entry-commands-e63f1xc0.js +0 -46
- package/dist/chunks/extension-sections-txv08xxg.js +0 -311
- package/dist/chunks/file-ops-wbh4hhsx.js +0 -48
- package/dist/chunks/files-3kfkwhk4.js +0 -93
- package/dist/chunks/flow-qbfgrpeh.js +0 -634
- package/dist/chunks/focus-trap.esm-9t2drp0g.js +0 -1025
- package/dist/chunks/focus-visible-6eg70z18.js +0 -167
- package/dist/chunks/format-host-w16q81tf.js +0 -43
- package/dist/chunks/fs-events-hrrg328g.js +0 -21
- package/dist/chunks/git-panel-532whr1y.js +0 -59
- package/dist/chunks/index-zqxmqxmh.js +0 -109
- package/dist/chunks/jx-validate-8er6gbxy.js +0 -19
- package/dist/chunks/layers-panel-07hm7g5j.js +0 -67
- package/dist/chunks/layers-q75tj2mf.js +0 -45
- package/dist/chunks/lit-html-j055mkh8.js +0 -22
- package/dist/chunks/monaco-buffer-dqw0xxpx.js +0 -25
- package/dist/chunks/notify-yjm79n4s.js +0 -30
- package/dist/chunks/parse-24d0gay3.js +0 -19
- package/dist/chunks/platform-ba1j1gbh.js +0 -16
- package/dist/chunks/publish-panel-v9st96ja.js +0 -29
- package/dist/chunks/quick-search-zccjp6gv.js +0 -66
- package/dist/chunks/references-3qx8fkyp.js +0 -29
- package/dist/chunks/right-panel-vm3b6kwy.js +0 -46
- package/dist/chunks/section-registry-sx1ykvkw.js +0 -30
- package/dist/chunks/sp-overlay.dev-qgvhnxf4.js +0 -18
- package/dist/chunks/sp-popover.dev-n2etmqdp.js +0 -9
- package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +0 -15
- package/dist/chunks/sp-tooltip.dev-4sastdnw.js +0 -15
- package/dist/chunks/sp-tray.dev-tkbscm06.js +0 -199
- package/dist/chunks/studio-0a5qrh01.js +0 -3
- package/dist/chunks/studio-0bq6eh7v.js +0 -12
- package/dist/chunks/studio-18j3bmcz.js +0 -14
- package/dist/chunks/studio-1ngbejft.js +0 -181
- package/dist/chunks/studio-4b5z1x43.js +0 -424
- package/dist/chunks/studio-4n2b5gzb.js +0 -357
- package/dist/chunks/studio-4rzyrtjz.js +0 -426
- package/dist/chunks/studio-5fx3g9jg.js +0 -4325
- package/dist/chunks/studio-5myknm0a.js +0 -1903
- package/dist/chunks/studio-7afd21nw.js +0 -67
- package/dist/chunks/studio-7nte2hj4.js +0 -613
- package/dist/chunks/studio-8jpzqk5s.js +0 -128
- package/dist/chunks/studio-8q517dfr.js +0 -24
- package/dist/chunks/studio-950k0dx6.js +0 -6939
- package/dist/chunks/studio-9n4mrmnb.js +0 -184
- package/dist/chunks/studio-a0tswy0w.js +0 -58
- package/dist/chunks/studio-ahnzd9rg.js +0 -23
- package/dist/chunks/studio-bgxt93ga.js +0 -62
- package/dist/chunks/studio-cmb03bg5.js +0 -13890
- package/dist/chunks/studio-d1ks410h.js +0 -175
- package/dist/chunks/studio-ef49pnzx.js +0 -387
- package/dist/chunks/studio-eqq1w638.js +0 -113
- package/dist/chunks/studio-ew0djmhg.js +0 -341
- package/dist/chunks/studio-gfgb8pe6.js +0 -1652
- package/dist/chunks/studio-h4rxscfc.js +0 -10
- package/dist/chunks/studio-jc994dnr.js +0 -1066
- package/dist/chunks/studio-kyh9avvv.js +0 -28
- package/dist/chunks/studio-m3hwn7y1.js +0 -120
- package/dist/chunks/studio-mtfnaqs9.js +0 -659
- package/dist/chunks/studio-n6j9t10q.js +0 -318
- package/dist/chunks/studio-n8b9wqwy.js +0 -291
- package/dist/chunks/studio-qcy05dp8.js +0 -1743
- package/dist/chunks/studio-qq6jym04.js +0 -53
- package/dist/chunks/studio-rdx8jcxy.js +0 -83
- package/dist/chunks/studio-rn9ta2mz.js +0 -111
- package/dist/chunks/studio-t7qb9db8.js +0 -665
- package/dist/chunks/studio-tmc5emrw.js +0 -8
- package/dist/chunks/studio-z6nw2ek2.js +0 -18
- package/dist/chunks/studio-zczps70q.js +0 -378
- package/dist/chunks/studio-zqazsct2.js +0 -1823
- package/dist/chunks/workspace-fjx52ryv.js +0 -89
- package/src/panels/activity-bar.ts +0 -251
- package/src/panels/panel-scheduler.ts +0 -219
- package/src/panels/style-inputs.ts +0 -237
- package/src/panels/target-line.ts +0 -274
- package/src/panels/welcome-screen.ts +0 -346
- package/src/ui/button-group.ts +0 -102
- package/src/ui/choice-field.ts +0 -72
- package/src/ui/field-row.ts +0 -249
- package/src/ui/formula-palette.ts +0 -213
- package/src/ui/icons.ts +0 -52
- package/src/ui/jx-theme.ts +0 -130
- package/src/ui/spectrum.ts +0 -340
- package/src/ui/value-selector.ts +0 -174
- package/src/ui/widgets.ts +0 -172
- /package/dist/chunks/{javascript-s56a8dpk.css → javascript-4abzjcy9.css} +0 -0
- /package/dist/chunks/{jsonMode-zr9sw0c7.css → jsonMode-vvmpmk34.css} +0 -0
- /package/dist/chunks/{monaco-setup-pav68602.css → monaco-setup-8vbyqnhr.css} +0 -0
- /package/dist/chunks/{tsMode-ef187txx.css → tsMode-hnxxb73h.css} +0 -0
|
@@ -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,16 @@ 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";
|
|
73
|
+
import { runReported } from "../commands/run-reported";
|
|
60
74
|
import { editorKindForMode, makeContext } from "../commands/context";
|
|
61
75
|
import { defaultCommands, noopCommandDeps } from "../commands/defaults";
|
|
76
|
+
import { emptyBlockBarView, mountBlockActionBar } from "../surfaces/block-action-bar";
|
|
77
|
+
import { openMenu } from "../surfaces/menu";
|
|
62
78
|
|
|
63
79
|
import type { AnyCommand, CommandRegistry } from "../commands/registry";
|
|
64
80
|
import type { CommandContext } from "../commands/context";
|
|
@@ -66,8 +82,9 @@ import type { CommandDeps } from "../commands/defaults";
|
|
|
66
82
|
import type { ApplyFormatIntent } from "../canvas/iframe-protocol";
|
|
67
83
|
import type { JxPath } from "../state";
|
|
68
84
|
import type { JxMutableNode } from "@jxsuite/schema/types";
|
|
69
|
-
import type { TemplateResult } from "lit-html";
|
|
70
85
|
import type { SlashCommand } from "../editor/convert-targets.js";
|
|
86
|
+
import type { BlockBarSurface, BlockBarTool, BlockBarView } from "../surfaces/block-action-bar";
|
|
87
|
+
import type { MenuHandle, MenuRowProjection } from "../surfaces/menu";
|
|
71
88
|
|
|
72
89
|
/**
|
|
73
90
|
* The plain format commands — everything an action button posts except link/insertData.
|
|
@@ -137,7 +154,7 @@ let _scrollRafPending = false;
|
|
|
137
154
|
* @param {Event} e
|
|
138
155
|
*/
|
|
139
156
|
export function onCanvasScroll(e: Event): void {
|
|
140
|
-
if (!_ctx ||
|
|
157
|
+
if (!_ctx || isLinkPopoverOpen()) {
|
|
141
158
|
return;
|
|
142
159
|
}
|
|
143
160
|
if (activeTab.value?.session.selection.length === 0) {
|
|
@@ -162,27 +179,32 @@ export function onCanvasScroll(e: Event): void {
|
|
|
162
179
|
}
|
|
163
180
|
|
|
164
181
|
/**
|
|
165
|
-
*
|
|
182
|
+
* Position-only fast path: move the standing bar to the anchor's current position (fresh
|
|
166
183
|
* {@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
|
|
184
|
+
* when the anchor left the canvas area so the mounted document — and with it the drag handle's dnd
|
|
185
|
+
* registration and whatever holds the caret — survives; a bar hidden by the `visible` switch is
|
|
186
|
+
* genuinely torn down, which is right for a dismissal and wrong for a scroll that will bring the
|
|
187
|
+
* anchor straight back.
|
|
169
188
|
*/
|
|
170
189
|
function repositionBlockActionBar(): void {
|
|
171
|
-
const
|
|
172
|
-
if (!
|
|
190
|
+
const surface = _surface;
|
|
191
|
+
if (!surface || !_view.visible) {
|
|
173
192
|
renderBlockActionBar();
|
|
174
193
|
return;
|
|
175
194
|
}
|
|
176
195
|
const anchor = getEditBarAnchorRect();
|
|
177
196
|
const pos = anchor ? barPosition(anchor) : null;
|
|
178
197
|
if (!pos) {
|
|
179
|
-
|
|
198
|
+
applyBarView({ ..._view, offscreen: true });
|
|
180
199
|
return;
|
|
181
200
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
201
|
+
applyBarView({ ..._view, barX: pos.left, barY: pos.top, offscreen: false });
|
|
202
|
+
requestAnimationFrame(() => {
|
|
203
|
+
const bar = surface.bar();
|
|
204
|
+
if (bar) {
|
|
205
|
+
clampBarToWindow(bar);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
186
208
|
}
|
|
187
209
|
|
|
188
210
|
/**
|
|
@@ -219,12 +241,25 @@ function barPosition(anchor: {
|
|
|
219
241
|
/**
|
|
220
242
|
* Pull the bar back inside the window's right edge.
|
|
221
243
|
*
|
|
244
|
+
* A measurement and a write of the value it measured, which is why it stays imperative and stays
|
|
245
|
+
* here: the bar's own box is the input, and no projection can carry a number that depends on the
|
|
246
|
+
* layout the projection produced. It writes the same custom property the document's `left` reads.
|
|
247
|
+
*
|
|
248
|
+
* **It releases its own previous write before measuring, and that is load-bearing.** The projected
|
|
249
|
+
* position is set on the document's ROOT and reaches the bar by inheritance, while this override is
|
|
250
|
+
* set on the bar itself — and a value an element carries always beats one it inherits, at every
|
|
251
|
+
* specificity. So a clamp that was never released would pin the bar at the right edge for the rest
|
|
252
|
+
* of the session, and every later `barX` would be projected into a variable nothing reads. Removing
|
|
253
|
+
* it first also makes the measurement the right one: the box is read at the position the projection
|
|
254
|
+
* asked for, rather than at the position the last clamp left it in.
|
|
255
|
+
*
|
|
222
256
|
* @param {HTMLElement} bar
|
|
223
257
|
*/
|
|
224
258
|
function clampBarToWindow(bar: HTMLElement): void {
|
|
259
|
+
bar.style.removeProperty("--jx-bar-x");
|
|
225
260
|
const barRect = rectOf(bar);
|
|
226
261
|
if (barRect.right > window.innerWidth) {
|
|
227
|
-
bar.style.
|
|
262
|
+
bar.style.setProperty("--jx-bar-x", `${Math.max(0, window.innerWidth - barRect.width)}px`);
|
|
228
263
|
}
|
|
229
264
|
}
|
|
230
265
|
|
|
@@ -282,17 +317,17 @@ export function formatCommands(): AnyCommand[] {
|
|
|
282
317
|
});
|
|
283
318
|
|
|
284
319
|
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", "
|
|
320
|
+
record("bold", "Bold", "text-b", "mod+b"),
|
|
321
|
+
record("italic", "Italic", "text-italic", "mod+i"),
|
|
322
|
+
record("underline", "Underline", "text-underline", "mod+u"),
|
|
323
|
+
record("strikethrough", "Strikethrough", "text-strikethrough"),
|
|
324
|
+
record("superscript", "Superscript", "text-superscript"),
|
|
325
|
+
record("subscript", "Subscript", "text-subscript"),
|
|
326
|
+
record("code", "Code", "code", "mod+`"),
|
|
292
327
|
{
|
|
293
328
|
category: "Edit",
|
|
294
329
|
group: "6_format",
|
|
295
|
-
icon: "
|
|
330
|
+
icon: "link",
|
|
296
331
|
id: "format.link",
|
|
297
332
|
/* ⌘K, at `caret` scope, beside `palette.open`'s global ⌘K. Two scopes may hold one chord —
|
|
298
333
|
that is what a shadowing ladder is — and the caret's wins only while a caret is in the
|
|
@@ -632,7 +667,7 @@ export function registerSelectionCommands(
|
|
|
632
667
|
category: "Selection",
|
|
633
668
|
enablement: canMoveUp,
|
|
634
669
|
group: "1_move_1",
|
|
635
|
-
icon: "
|
|
670
|
+
icon: "arrow-up",
|
|
636
671
|
id: "selection.moveUp",
|
|
637
672
|
keyScope: "canvas",
|
|
638
673
|
level: "selection",
|
|
@@ -647,7 +682,7 @@ export function registerSelectionCommands(
|
|
|
647
682
|
category: "Selection",
|
|
648
683
|
enablement: canMoveDown,
|
|
649
684
|
group: "1_move_2",
|
|
650
|
-
icon: "
|
|
685
|
+
icon: "arrow-down",
|
|
651
686
|
id: "selection.moveDown",
|
|
652
687
|
keyScope: "canvas",
|
|
653
688
|
level: "selection",
|
|
@@ -662,7 +697,7 @@ export function registerSelectionCommands(
|
|
|
662
697
|
category: "Selection",
|
|
663
698
|
enablement: canMoveIn,
|
|
664
699
|
group: "1_move_3",
|
|
665
|
-
icon: "
|
|
700
|
+
icon: "arrow-right",
|
|
666
701
|
id: "selection.moveIn",
|
|
667
702
|
keyScope: "canvas",
|
|
668
703
|
level: "selection",
|
|
@@ -677,7 +712,7 @@ export function registerSelectionCommands(
|
|
|
677
712
|
category: "Selection",
|
|
678
713
|
enablement: canMoveOut,
|
|
679
714
|
group: "1_move_4",
|
|
680
|
-
icon: "
|
|
715
|
+
icon: "arrow-left",
|
|
681
716
|
id: "selection.moveOut",
|
|
682
717
|
keyScope: "canvas",
|
|
683
718
|
level: "selection",
|
|
@@ -698,7 +733,7 @@ export function registerSelectionCommands(
|
|
|
698
733
|
enablement: (ctx) =>
|
|
699
734
|
Boolean(ctx.selection.kind) && !ctx.selection.isRoot && ctx.editor.kind === "canvas",
|
|
700
735
|
group: "4_component",
|
|
701
|
-
icon: "
|
|
736
|
+
icon: "cube",
|
|
702
737
|
id: "selection.convertToComponent",
|
|
703
738
|
level: "selection",
|
|
704
739
|
// Three surfaces, ONE record. `editor/context-menu.ts` carried a second `convertToComponent`
|
|
@@ -718,7 +753,7 @@ export function registerSelectionCommands(
|
|
|
718
753
|
{
|
|
719
754
|
category: "Selection",
|
|
720
755
|
group: "4_component",
|
|
721
|
-
icon: "
|
|
756
|
+
icon: "pencil-simple",
|
|
722
757
|
id: "selection.editComponent",
|
|
723
758
|
level: "selection",
|
|
724
759
|
menus: ["blockbar", "context/element", "palette"],
|
|
@@ -796,43 +831,6 @@ export function selectionCommandRegistry(): CommandRegistry {
|
|
|
796
831
|
/** How many verb buttons the bar renders before the rest fold into `⋮` (plan §3.2 ⑩). */
|
|
797
832
|
export const BLOCKBAR_MAX_ITEMS = 5;
|
|
798
833
|
|
|
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
834
|
/**
|
|
837
835
|
* The tooltip: the chord when the control can act, the `requires` sentence when it cannot.
|
|
838
836
|
*
|
|
@@ -848,11 +846,15 @@ export function commandTooltip(registry: CommandRegistry, command: AnyCommand):
|
|
|
848
846
|
return chord ? `${command.title} (${chord})` : command.title;
|
|
849
847
|
}
|
|
850
848
|
|
|
851
|
-
/**
|
|
849
|
+
/**
|
|
850
|
+
* Run a command, swallowing the refusal a disabled control should never have produced — and
|
|
851
|
+
* reporting, through `commands/run-reported.ts`, the one an enabled control can still meet: a `run`
|
|
852
|
+
* body that throws or rejects.
|
|
853
|
+
*/
|
|
852
854
|
export function runCommand(registry: CommandRegistry, id: string, target?: JxPath | null): void {
|
|
853
855
|
const invoke = () => {
|
|
854
856
|
if (registry.isEnabled(id)) {
|
|
855
|
-
void registry
|
|
857
|
+
void runReported(registry, id, undefined, "Editor");
|
|
856
858
|
}
|
|
857
859
|
};
|
|
858
860
|
if (target) {
|
|
@@ -863,135 +865,141 @@ export function runCommand(registry: CommandRegistry, id: string, target?: JxPat
|
|
|
863
865
|
}
|
|
864
866
|
|
|
865
867
|
/**
|
|
866
|
-
*
|
|
868
|
+
* One button, projected from its record.
|
|
867
869
|
*
|
|
868
|
-
*
|
|
870
|
+
* The surface is told what a button says, never asked to work it out: the glyph is the record's own
|
|
871
|
+
* kit name, the tooltip is {@link commandTooltip}, and `disabled` is the registry's refusal rather
|
|
872
|
+
* than a second opinion this module could hold about the same record.
|
|
869
873
|
*/
|
|
870
|
-
function
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
874
|
+
function toolOf(
|
|
875
|
+
registry: CommandRegistry,
|
|
876
|
+
command: AnyCommand,
|
|
877
|
+
extra: { disabled?: boolean; selected?: boolean; value?: string } = {},
|
|
878
|
+
): BlockBarTool {
|
|
879
|
+
return {
|
|
880
|
+
destructive: command.destructive === true,
|
|
881
|
+
disabled: extra.disabled ?? registry.disabledReason(command.id) !== undefined,
|
|
882
|
+
hasIcon: Boolean(command.icon),
|
|
883
|
+
icon: command.icon ?? "",
|
|
884
|
+
id: command.id,
|
|
885
|
+
selected: extra.selected ?? false,
|
|
886
|
+
title: command.title,
|
|
887
|
+
tooltip: commandTooltip(registry, command),
|
|
888
|
+
value: extra.value ?? "",
|
|
889
|
+
};
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// ─── The mounted surface ─────────────────────────────────────────────────────
|
|
893
|
+
|
|
894
|
+
/** The bar's surface, mounted on the first render and kept for the life of the window. */
|
|
895
|
+
let _surface: BlockBarSurface | null = null;
|
|
896
|
+
|
|
897
|
+
/** The last projection handed to the surface, so a fast path can vary one field of it. */
|
|
898
|
+
let _view: BlockBarView = emptyBlockBarView();
|
|
899
|
+
|
|
900
|
+
/** Hand the surface a projection, remembering it. */
|
|
901
|
+
function applyBarView(next: BlockBarView): void {
|
|
902
|
+
_view = next;
|
|
903
|
+
_surface?.update(next);
|
|
881
904
|
}
|
|
882
905
|
|
|
883
906
|
/**
|
|
884
|
-
*
|
|
885
|
-
*
|
|
886
|
-
*
|
|
907
|
+
* The layer slot the bar draws into, mounting the document the first time it is asked for.
|
|
908
|
+
*
|
|
909
|
+
* One mount for the life of the window: a document reconciles by assignment, so hiding the bar is a
|
|
910
|
+
* `visible` switch rather than a teardown, and the caret, the drag registration and an open link
|
|
911
|
+
* panel all survive a repaint that would have re-rendered a template.
|
|
887
912
|
*/
|
|
888
|
-
function
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
mutateUpdateProperty(t, selection, "tagName", cmd.tag);
|
|
896
|
-
});
|
|
913
|
+
function ensureSurface(): BlockBarSurface {
|
|
914
|
+
if (!view.blockActionBarEl) {
|
|
915
|
+
view.blockActionBarEl = getLayerSlot("popover", "block-action-bar");
|
|
916
|
+
}
|
|
917
|
+
_surface ??= mountBlockActionBar(view.blockActionBarEl, {
|
|
918
|
+
applyLink: (href) => {
|
|
919
|
+
postApplyFormat({ command: "link", href: href || "" });
|
|
897
920
|
},
|
|
898
|
-
|
|
921
|
+
leaveBar: () => {
|
|
922
|
+
dismissBlockBarOverflow();
|
|
923
|
+
returnFocusToCanvas();
|
|
924
|
+
},
|
|
925
|
+
onDragHandle: attachDragHandle,
|
|
926
|
+
openMergeTags: openMergeTagMenu,
|
|
927
|
+
openOverflow: (anchor) => {
|
|
928
|
+
const registry = selectionCommandRegistry();
|
|
929
|
+
showCommandOverflow(
|
|
930
|
+
anchor,
|
|
931
|
+
registry,
|
|
932
|
+
registry.forPlacement("blockbar").slice(BLOCKBAR_MAX_ITEMS),
|
|
933
|
+
);
|
|
934
|
+
},
|
|
935
|
+
openTagMenu: openConvertMenu,
|
|
936
|
+
removeLink: () => {
|
|
937
|
+
postApplyFormat({ command: "link", href: null });
|
|
938
|
+
},
|
|
939
|
+
run: (id) => runCommand(selectionCommandRegistry(), id),
|
|
940
|
+
selectParent: () => runCommand(selectionCommandRegistry(), "selection.selectParent"),
|
|
899
941
|
});
|
|
942
|
+
return _surface;
|
|
900
943
|
}
|
|
901
944
|
|
|
902
945
|
/**
|
|
903
|
-
*
|
|
946
|
+
* Register the drag handle the document just drew.
|
|
904
947
|
*
|
|
905
|
-
*
|
|
906
|
-
*
|
|
907
|
-
*
|
|
908
|
-
* `
|
|
948
|
+
* Released before installed, always. The handle's node is re-created every time the bar is hidden
|
|
949
|
+
* and shown again, and two live pragmatic-dnd registrations on one handle is a drag that fires
|
|
950
|
+
* twice — `view.selDragCleanup` is the single field that guarantees there is never more than one.
|
|
951
|
+
* `canDrag` is asked at the PRESS rather than here, so a selection moving to the document root
|
|
952
|
+
* greys the handle without any re-registration at all.
|
|
953
|
+
*
|
|
954
|
+
* @param {HTMLElement} handle
|
|
909
955
|
*/
|
|
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
|
-
>`;
|
|
956
|
+
function attachDragHandle(handle: HTMLElement): void {
|
|
957
|
+
view.selDragCleanup?.();
|
|
958
|
+
view.selDragCleanup = draggable({
|
|
959
|
+
canDrag: () => structuralTarget(primarySelection(activeTab.value?.session.selection)) !== null,
|
|
960
|
+
element: handle,
|
|
961
|
+
getInitialData: () => ({
|
|
962
|
+
// Snapshot the selection: the live array is a Vue reactive proxy, which structured clone
|
|
963
|
+
// Rejects when the src crosses postMessage (DataCloneError killed the whole handle drag),
|
|
964
|
+
// And a live reference would also mutate the retained srcData if the selection changed
|
|
965
|
+
// Mid-drag.
|
|
966
|
+
path: [...(primarySelection(activeTab.value?.session.selection) ?? [])],
|
|
967
|
+
type: "tree-node",
|
|
968
|
+
}),
|
|
969
|
+
onGenerateDragPreview: ({
|
|
970
|
+
nativeSetDragImage,
|
|
971
|
+
}: {
|
|
972
|
+
nativeSetDragImage: ((image: Element, x: number, y: number) => void) | null;
|
|
973
|
+
}) => {
|
|
974
|
+
// Suppress the native drag image; the cross-frame ghost is the drag affordance.
|
|
975
|
+
disableNativeDragPreview({ nativeSetDragImage });
|
|
976
|
+
},
|
|
977
|
+
});
|
|
958
978
|
}
|
|
959
979
|
|
|
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
|
-
}
|
|
980
|
+
// ─── The `⋮` menu ────────────────────────────────────────────────────────────
|
|
980
981
|
|
|
981
982
|
/** The open overflow menu, so a second press (or a re-render) closes rather than stacks it. */
|
|
982
|
-
let _overflowHandle:
|
|
983
|
+
let _overflowHandle: MenuHandle | null = null;
|
|
983
984
|
|
|
984
985
|
/** Close the block action bar's `⋮` menu if it is open. */
|
|
985
986
|
export function dismissBlockBarOverflow(): void {
|
|
986
|
-
_overflowHandle
|
|
987
|
+
const handle = _overflowHandle;
|
|
987
988
|
_overflowHandle = null;
|
|
989
|
+
handle?.close();
|
|
990
|
+
if (_view.overflowOpen) {
|
|
991
|
+
applyBarView({ ..._view, overflowOpen: false });
|
|
992
|
+
}
|
|
988
993
|
}
|
|
989
994
|
|
|
990
995
|
/**
|
|
991
996
|
* Show the `⋮` menu under `anchor`. Rows carry the same names, chords and refusals as the buttons.
|
|
992
997
|
*
|
|
993
|
-
* Shared by the block action bar and the Outline rows
|
|
994
|
-
* (
|
|
998
|
+
* Shared by the block action bar and the Outline rows, and it is the KIT menu
|
|
999
|
+
* (`surfaces/menu.json`, §12.5) rather than a list of this surface's own: one popover, one roving
|
|
1000
|
+
* caret, one typeahead, one Escape. `target` (the Outline row's node) is captured per row and
|
|
1001
|
+
* re-applied when a row is chosen, because a row action acts on the row's node and the hovered row
|
|
1002
|
+
* is not the selected one.
|
|
995
1003
|
*/
|
|
996
1004
|
export function showCommandOverflow(
|
|
997
1005
|
anchor: HTMLElement,
|
|
@@ -1000,95 +1008,46 @@ export function showCommandOverflow(
|
|
|
1000
1008
|
target: JxPath | null = null,
|
|
1001
1009
|
): void {
|
|
1002
1010
|
dismissBlockBarOverflow();
|
|
1003
|
-
const rect = rectOf(anchor);
|
|
1004
1011
|
// 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
|
-
|
|
1012
|
+
// Rows are handed over — the menu prints what it is given.
|
|
1013
|
+
const project = (): MenuRowProjection[] =>
|
|
1014
|
+
commands.map((command, index) => {
|
|
1015
|
+
const chord = registry.keymap.formatBinding(command.id);
|
|
1016
|
+
const reason = registry.disabledReason(command.id);
|
|
1017
|
+
return {
|
|
1018
|
+
destructive: command.destructive === true,
|
|
1019
|
+
disabled: reason !== undefined,
|
|
1020
|
+
dividerAbove: index > 0 && command.destructive === true,
|
|
1021
|
+
id: command.id,
|
|
1022
|
+
run: () => {
|
|
1023
|
+
runCommand(registry, command.id, target);
|
|
1024
|
+
},
|
|
1025
|
+
title: command.title,
|
|
1026
|
+
...(chord ? { chord } : {}),
|
|
1027
|
+
...(reason === undefined ? {} : { requires: reason }),
|
|
1028
|
+
};
|
|
1029
|
+
});
|
|
1015
1030
|
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: () => {
|
|
1031
|
+
_overflowHandle = openMenu({
|
|
1032
|
+
label: "More block actions",
|
|
1033
|
+
onClosed: (closed) => {
|
|
1034
|
+
if (_overflowHandle === closed) {
|
|
1048
1035
|
_overflowHandle = null;
|
|
1049
|
-
|
|
1036
|
+
if (_view.overflowOpen) {
|
|
1037
|
+
applyBarView({ ..._view, overflowOpen: false });
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1050
1040
|
},
|
|
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;
|
|
1041
|
+
opener: anchor,
|
|
1042
|
+
region: "block-actions",
|
|
1043
|
+
rows,
|
|
1044
|
+
});
|
|
1045
|
+
if (!target) {
|
|
1046
|
+
applyBarView({ ..._view, overflowOpen: true });
|
|
1077
1047
|
}
|
|
1078
1048
|
}
|
|
1079
1049
|
|
|
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
|
-
}
|
|
1050
|
+
// ─── Keyboard ────────────────────────────────────────────────────────────────
|
|
1092
1051
|
|
|
1093
1052
|
/** Put the keyboard back where it came from — the canvas iframe, else its wrapper. */
|
|
1094
1053
|
function returnFocusToCanvas(): void {
|
|
@@ -1099,32 +1058,6 @@ function returnFocusToCanvas(): void {
|
|
|
1099
1058
|
(iframe ?? stage)?.focus();
|
|
1100
1059
|
}
|
|
1101
1060
|
|
|
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
1061
|
/**
|
|
1129
1062
|
* ⌥↑ enters the bar from the canvas.
|
|
1130
1063
|
*
|
|
@@ -1132,6 +1065,11 @@ export function onToolbarKeydown(e: KeyboardEvent): void {
|
|
|
1132
1065
|
* inside the canvas iframe does not arrive: `canvas/iframe-keys.ts` forwards bare navigation keys
|
|
1133
1066
|
* only when the target is not editable, and the canvas root is permanently `contenteditable`.
|
|
1134
1067
|
* Exported so a test can dispatch it directly.
|
|
1068
|
+
*
|
|
1069
|
+
* The caret it places is the kit's: `jx-action-group` owns the roving tabindex and every arrow key
|
|
1070
|
+
* after this one, so entering the bar is the only part of the toolbar keyboard this module holds.
|
|
1071
|
+
*
|
|
1072
|
+
* @param {KeyboardEvent} e
|
|
1135
1073
|
*/
|
|
1136
1074
|
export function handleBlockBarEntryKey(e: KeyboardEvent): void {
|
|
1137
1075
|
if (e.key !== "ArrowUp" || !e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
|
|
@@ -1140,24 +1078,74 @@ export function handleBlockBarEntryKey(e: KeyboardEvent): void {
|
|
|
1140
1078
|
if (isModalOpen()) {
|
|
1141
1079
|
return;
|
|
1142
1080
|
}
|
|
1143
|
-
const
|
|
1144
|
-
if (!
|
|
1081
|
+
const toolbar = _surface?.toolbar();
|
|
1082
|
+
if (!toolbar) {
|
|
1145
1083
|
return;
|
|
1146
1084
|
}
|
|
1147
1085
|
e.preventDefault();
|
|
1148
|
-
|
|
1086
|
+
focusItem(toolbar, 0);
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// ─── The badge's convert list and the merge-tag list ─────────────────────────
|
|
1090
|
+
|
|
1091
|
+
/**
|
|
1092
|
+
* The tags this node may be converted into — empty for a component instance and for a repeater,
|
|
1093
|
+
* whose content is a `map` template rather than a tag.
|
|
1094
|
+
*
|
|
1095
|
+
* @param {JxMutableNode | null} node
|
|
1096
|
+
*/
|
|
1097
|
+
function convertTargetsFor(node: JxMutableNode | null): SlashCommand[] {
|
|
1098
|
+
if (!node) {
|
|
1099
|
+
return [];
|
|
1100
|
+
}
|
|
1101
|
+
const literal = displayTagName(node.tagName);
|
|
1102
|
+
const isComponent = literal.includes("-") && componentRegistry.some((c) => c.tagName === literal);
|
|
1103
|
+
if (isComponent || node.$prototype === "Array") {
|
|
1104
|
+
return [];
|
|
1105
|
+
}
|
|
1106
|
+
const children = childList(node);
|
|
1107
|
+
const isEmpty =
|
|
1108
|
+
!node.textContent &&
|
|
1109
|
+
(children.length === 0 ||
|
|
1110
|
+
(children.length === 1 && typeof children[0] === "object" && children[0]?.tagName === "br"));
|
|
1111
|
+
return getConvertTargets((literal || "div").toLowerCase(), isEmpty);
|
|
1112
|
+
}
|
|
1113
|
+
|
|
1114
|
+
/**
|
|
1115
|
+
* Open the badge's convert list under it, and retag the node on a choice.
|
|
1116
|
+
*
|
|
1117
|
+
* Resolved at the PRESS rather than captured at projection time: the badge is one node for the life
|
|
1118
|
+
* of the mount, so a closure taken when it was drawn would retag whatever was selected then.
|
|
1119
|
+
*
|
|
1120
|
+
* @param {HTMLElement} anchor
|
|
1121
|
+
*/
|
|
1122
|
+
function openConvertMenu(anchor: HTMLElement): void {
|
|
1123
|
+
const tab = activeTab.value;
|
|
1124
|
+
const selection = primarySelection(tab?.session.selection);
|
|
1125
|
+
const node = tab && selection ? getNodeAtPath(tab.doc.document, selection) : null;
|
|
1126
|
+
const targets = convertTargetsFor(node);
|
|
1127
|
+
if (!selection || targets.length === 0) {
|
|
1128
|
+
return;
|
|
1129
|
+
}
|
|
1130
|
+
showSlashMenu(anchor, "", {
|
|
1131
|
+
commands: targets,
|
|
1132
|
+
onSelect: (cmd) => {
|
|
1133
|
+
transactDoc(activeTab.value, (t) => {
|
|
1134
|
+
mutateUpdateProperty(t, selection, "tagName", cmd.tag);
|
|
1135
|
+
});
|
|
1136
|
+
},
|
|
1137
|
+
showFilter: targets.length > 6,
|
|
1138
|
+
});
|
|
1149
1139
|
}
|
|
1150
1140
|
|
|
1151
1141
|
/**
|
|
1152
1142
|
* 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
|
|
1143
|
+
* the current state. Reuses the shared slash-menu surface (filter + keyboard nav + dismiss).
|
|
1154
1144
|
* Selecting a token posts an `insertData` intent the iframe applies at its caret.
|
|
1155
1145
|
*
|
|
1156
|
-
* @param {
|
|
1146
|
+
* @param {HTMLElement} anchor
|
|
1157
1147
|
*/
|
|
1158
|
-
function
|
|
1159
|
-
e.stopPropagation();
|
|
1160
|
-
const anchorEl = e.currentTarget as HTMLElement;
|
|
1148
|
+
function openMergeTagMenu(anchor: HTMLElement): void {
|
|
1161
1149
|
const tab = activeTab.value;
|
|
1162
1150
|
const state = (tab?.doc.document.state ?? {}) as Record<string, unknown>;
|
|
1163
1151
|
// The live resolved scope lives inside the iframe realm and is not threaded out, so the parent
|
|
@@ -1169,45 +1157,79 @@ function onMergeTagClick(e: MouseEvent) {
|
|
|
1169
1157
|
// Parent-side from schema, keyed off the selected element's doc path (which carries a `map` segment).
|
|
1170
1158
|
const selPath = getEditSnapshot().snapshot?.path ?? primarySelection(tab?.session.selection);
|
|
1171
1159
|
const arrayNode = tab && selPath ? findEnclosingRepeater(tab.doc.document, selPath) : null;
|
|
1172
|
-
if (arrayNode) {
|
|
1160
|
+
if (arrayNode && tab) {
|
|
1173
1161
|
const tokens = resolveRepeaterItemFields(
|
|
1174
1162
|
arrayNode,
|
|
1175
|
-
tab
|
|
1163
|
+
tab.doc.document.state as Record<string, unknown>,
|
|
1176
1164
|
projectState?.projectConfig,
|
|
1177
1165
|
);
|
|
1178
1166
|
tags.push(...buildRepeaterTagsFromFields(tokens));
|
|
1179
1167
|
}
|
|
1180
1168
|
|
|
1181
|
-
|
|
1182
|
-
description: t.hint,
|
|
1183
|
-
label: t.label,
|
|
1184
|
-
tag: t.token,
|
|
1185
|
-
}));
|
|
1186
|
-
|
|
1187
|
-
showSlashMenu(anchorEl, "", {
|
|
1188
|
-
commands,
|
|
1169
|
+
showSlashMenu(anchor, "", {
|
|
1170
|
+
commands: tags.map((t) => ({ description: t.hint, label: t.label, tag: t.token })),
|
|
1189
1171
|
onSelect: (cmd) => postApplyFormat({ command: "insertData", token: cmd.tag }),
|
|
1190
1172
|
showFilter: true,
|
|
1191
1173
|
});
|
|
1192
1174
|
}
|
|
1193
1175
|
|
|
1176
|
+
// ─── The link panel ──────────────────────────────────────────────────────────
|
|
1177
|
+
|
|
1194
1178
|
/** Dismiss the link popover if open. */
|
|
1195
|
-
export function dismissLinkPopover() {
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1179
|
+
export function dismissLinkPopover(): void {
|
|
1180
|
+
_surface?.closeLink();
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
/** True while the link URL popover is open (so a scroll does not move the bar out from under it). */
|
|
1184
|
+
export function isLinkPopoverOpen(): boolean {
|
|
1185
|
+
return _surface?.linkOpen() === true;
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* Open the link popover, anchored to the toolbar's Link button if it is on screen, else the bar.
|
|
1190
|
+
*
|
|
1191
|
+
* The iframe owns the Selection, so the existing-link state comes from the latest selection
|
|
1192
|
+
* snapshot; Apply/Remove post `applyFormat` link intents the iframe applies.
|
|
1193
|
+
*
|
|
1194
|
+
* By RECORD ID, not by rendered text. This queried `sp-action-button[title^="Link"]`, which is the
|
|
1195
|
+
* addressing §13's first rule bans in the screenshot manifest — a match on a title the app DERIVES
|
|
1196
|
+
* (it now reads "Link… (⌘K)", and on Windows it reads something else again). The id is the input
|
|
1197
|
+
* the app accepts, and the button is one the surface ANNOUNCED as it drew it rather than one this
|
|
1198
|
+
* module re-finds.
|
|
1199
|
+
*/
|
|
1200
|
+
export function openLinkPopoverFromShortcut(): void {
|
|
1201
|
+
const surface = _surface;
|
|
1202
|
+
const anchor = surface?.button("format.link") ?? surface?.bar();
|
|
1203
|
+
if (!surface || !anchor) {
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
const link = getEditSnapshot().snapshot?.link ?? { active: false, href: null };
|
|
1207
|
+
surface.openLink(anchor, link.href, link.active);
|
|
1199
1208
|
}
|
|
1200
1209
|
|
|
1210
|
+
// ─── Dismissal and suppression ───────────────────────────────────────────────
|
|
1211
|
+
|
|
1201
1212
|
/** Dismiss the block action bar. */
|
|
1202
|
-
export function dismissBlockActionBar() {
|
|
1213
|
+
export function dismissBlockActionBar(): void {
|
|
1203
1214
|
/* A dismiss is the bar's STRUCTURAL teardown — `canvas/canvas-render.ts` calls it when a stage is
|
|
1204
1215
|
torn down and again on a mode transition — so it also drops any suppression. The bar it would
|
|
1205
1216
|
otherwise be holding down does not exist any more; what comes back is a different one. */
|
|
1206
1217
|
_suppressedFor = null;
|
|
1207
1218
|
dismissBlockBarOverflow();
|
|
1208
|
-
|
|
1209
|
-
|
|
1219
|
+
hideBar();
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
/**
|
|
1223
|
+
* Take the bar off the screen: the `visible` switch, plus the drag registration that goes with the
|
|
1224
|
+
* node the switch is about to remove. The mount itself stays — it is the one thing a repaint must
|
|
1225
|
+
* never rebuild.
|
|
1226
|
+
*/
|
|
1227
|
+
function hideBar(): void {
|
|
1228
|
+
if (_surface) {
|
|
1229
|
+
applyBarView({ ...emptyBlockBarView(), visible: false });
|
|
1210
1230
|
}
|
|
1231
|
+
view.selDragCleanup?.();
|
|
1232
|
+
view.selDragCleanup = null;
|
|
1211
1233
|
}
|
|
1212
1234
|
|
|
1213
1235
|
/**
|
|
@@ -1258,9 +1280,6 @@ function suppressionKey(tab: ActiveTab | null, path: JxPath | null): string {
|
|
|
1258
1280
|
export function suppressBlockActionBar(): void {
|
|
1259
1281
|
// Clears any earlier key as its first act; this selection's replaces it below.
|
|
1260
1282
|
dismissBlockActionBar();
|
|
1261
|
-
if (_linkPopoverOpen) {
|
|
1262
|
-
dismissLinkPopover();
|
|
1263
|
-
}
|
|
1264
1283
|
const tab = activeTab.value;
|
|
1265
1284
|
_suppressedFor = { key: suppressionKey(tab, primarySelection(tab?.session.selection)) };
|
|
1266
1285
|
}
|
|
@@ -1285,22 +1304,13 @@ export function releaseBlockActionBar(): void {
|
|
|
1285
1304
|
}
|
|
1286
1305
|
|
|
1287
1306
|
/**
|
|
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.
|
|
1307
|
+
* Whether `target` sits inside the edit-session chrome — the block action bar, its `⋮` menu, or the
|
|
1308
|
+
* slash menu. The parent's pointerdown commit-guard must NOT end the inline-edit session for clicks
|
|
1309
|
+
* on these (they operate ON the session); any other parent-chrome press commits it. This module
|
|
1310
|
+
* owns those popover roots, hence the helper lives here.
|
|
1311
|
+
*
|
|
1312
|
+
* The link panel is no longer named separately: it is a `jx-popover` inside the bar's own document,
|
|
1313
|
+
* so the bar's host contains it and one root answers for both.
|
|
1304
1314
|
*
|
|
1305
1315
|
* @param {EventTarget | null} target
|
|
1306
1316
|
*/
|
|
@@ -1311,369 +1321,158 @@ export function isEditChromeTarget(target: EventTarget | null): boolean {
|
|
|
1311
1321
|
const roots = [
|
|
1312
1322
|
view.blockActionBarEl,
|
|
1313
1323
|
_overflowHandle?.host ?? null,
|
|
1314
|
-
getLayerSlot("popover", "link-popover"),
|
|
1315
1324
|
getLayerSlot("popover", "slash-menu"),
|
|
1316
1325
|
];
|
|
1317
1326
|
return roots.some((root) => root != null && root.contains(target));
|
|
1318
1327
|
}
|
|
1319
1328
|
|
|
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
|
-
}
|
|
1329
|
+
// ─── The projection ──────────────────────────────────────────────────────────
|
|
1401
1330
|
|
|
1402
1331
|
/**
|
|
1403
|
-
*
|
|
1404
|
-
*
|
|
1332
|
+
* What the bar says right now, or `null` when it must not draw at all.
|
|
1333
|
+
*
|
|
1334
|
+
* Every branch that used to end in `litRender(nothing)` ends here in `null`, and the five reasons
|
|
1335
|
+
* are unchanged: no context, no tab, no selection, a canvas mode that is not design or edit, a
|
|
1336
|
+
* selection that resolves to no node, and an anchor the stage no longer shows.
|
|
1405
1337
|
*/
|
|
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
|
-
|
|
1338
|
+
function projectBar(): BlockBarView | null {
|
|
1446
1339
|
const tab = activeTab.value;
|
|
1447
|
-
const canvasMode = _ctx
|
|
1448
|
-
|
|
1340
|
+
const canvasMode = _ctx?.getCanvasMode();
|
|
1449
1341
|
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
1342
|
if (!tab || !selection || (canvasMode !== "design" && canvasMode !== "edit")) {
|
|
1464
|
-
|
|
1465
|
-
return;
|
|
1343
|
+
return null;
|
|
1466
1344
|
}
|
|
1467
|
-
|
|
1468
1345
|
const node = getNodeAtPath(tab.doc.document, selection);
|
|
1469
1346
|
if (!node) {
|
|
1470
|
-
|
|
1471
|
-
return;
|
|
1347
|
+
return null;
|
|
1472
1348
|
}
|
|
1473
|
-
|
|
1474
1349
|
// Position from the iframe-host's viewport-space anchor (the bar is position:fixed). The parent
|
|
1475
1350
|
// Never reads the iframe DOM, so geometry crosses the bridge as the selection snapshot's rect.
|
|
1476
1351
|
const anchor = getEditBarAnchorRect();
|
|
1477
1352
|
const pos = anchor ? barPosition(anchor) : null;
|
|
1478
1353
|
if (!pos) {
|
|
1479
|
-
|
|
1480
|
-
return;
|
|
1354
|
+
return null;
|
|
1481
1355
|
}
|
|
1482
1356
|
|
|
1357
|
+
const registry = selectionCommandRegistry();
|
|
1483
1358
|
const tag = (displayTagName(node.tagName) || "div").toLowerCase();
|
|
1484
|
-
|
|
1485
|
-
// Inline format state, sourced from the iframe's selection snapshot.
|
|
1486
1359
|
const { editingProp, snapshot } = getEditSnapshot();
|
|
1487
1360
|
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).
|
|
1361
|
+
/* ONE bar, one shape. The format group used to appear only during an "inline edit session", so
|
|
1362
|
+
the toolbar rearranged itself under the author's cursor the moment they started typing. With a
|
|
1363
|
+
document-wide caret there is no session to be in or out of: the group is shown whenever the
|
|
1364
|
+
selected block can carry inline markup, and the buttons enable when there is a range to apply
|
|
1365
|
+
them to. A prop-bound block still suppresses it — it edits a single plain string (belt and
|
|
1366
|
+
braces: component tags have no $inlineActions, so `actions` is empty there anyway). */
|
|
1496
1367
|
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
1368
|
// Formatting applies to a RANGE: disabled for a collapsed caret, and for a block selected without
|
|
1502
1369
|
// One at all (from the layers panel, or by a structural edit moving the selection).
|
|
1503
1370
|
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
1371
|
/* The SET is the schema's, the VERBS are the registry's (see {@link formatCommands}).
|
|
1525
1372
|
`$inlineActions` says which of the eight this tag accepts and in what order — four on an `<h1>`,
|
|
1526
1373
|
eight on a `<p>` — and each one's name, icon, chord and behaviour come off its record. An action
|
|
1527
1374
|
the registry has no record for is dropped rather than drawn as a button that runs nothing, so
|
|
1528
1375
|
adding a ninth verb to the data file without a record shows up as a missing button and as a red
|
|
1529
1376
|
`tests/block-action-bar.test.ts`, not as a silent no-op. */
|
|
1530
|
-
const
|
|
1377
|
+
const formats = actions.flatMap((action) => {
|
|
1531
1378
|
const command = registry.get(`format.${action.command}`);
|
|
1532
|
-
return command
|
|
1379
|
+
return command
|
|
1380
|
+
? [
|
|
1381
|
+
toolOf(registry, command, {
|
|
1382
|
+
disabled: formatDisabled && action.command !== "link",
|
|
1383
|
+
selected: snapshot?.activeTags.includes(action.tag) === true,
|
|
1384
|
+
value: action.tag,
|
|
1385
|
+
}),
|
|
1386
|
+
]
|
|
1387
|
+
: [];
|
|
1533
1388
|
});
|
|
1389
|
+
|
|
1390
|
+
// The verb cluster, sliced at the cap. `forPlacement` already dropped the records whose `when` is
|
|
1391
|
+
// False and sorted the rest by group; everything past the cap keeps its name and its chord in the
|
|
1392
|
+
// `⋮` menu rather than being silently unavailable.
|
|
1534
1393
|
const placed = registry.forPlacement("blockbar");
|
|
1535
|
-
|
|
1536
|
-
const
|
|
1394
|
+
|
|
1395
|
+
const parentPath = parentElementPath(selection);
|
|
1396
|
+
const parentNode = parentPath ? getNodeAtPath(tab.doc.document, parentPath) : null;
|
|
1397
|
+
const parentCommand = registry.get("selection.selectParent");
|
|
1398
|
+
/* The parent selector renders the `selection.selectParent` record — its name, its chord and its
|
|
1399
|
+
handler — but as fixed chrome rather than from `forPlacement("blockbar")`, because that record
|
|
1400
|
+
does not declare the placement. Its disabled state is "the selection has no parent", which the
|
|
1401
|
+
record's own `enablement` (a bare "there is a selection") cannot express yet. */
|
|
1402
|
+
const parentName = parentCommand?.title ?? "Select Parent";
|
|
1403
|
+
const parentLabel = parentNode ? `Select parent: ${nodeLabel(parentNode)}` : parentName;
|
|
1404
|
+
const parentChord = parentCommand ? registry.keymap.formatBinding(parentCommand.id) : undefined;
|
|
1405
|
+
|
|
1406
|
+
const convertTargets = convertTargetsFor(node);
|
|
1407
|
+
// Repeater ($prototype:"Array") pseudo-elements have no tagName — show the "Repeater → items"
|
|
1408
|
+
// Label (not a bare "div").
|
|
1409
|
+
const tagLabel =
|
|
1410
|
+
(node.$prototype === "Array"
|
|
1411
|
+
? nodeLabel(node)
|
|
1412
|
+
: node.$id || displayTagName(node.tagName) || "div") +
|
|
1413
|
+
(editingProp ? ` · ${editingProp}` : "");
|
|
1537
1414
|
// The handle is chrome, not a verb, so it renders on every selection; only a node that actually
|
|
1538
1415
|
// Sits at a child index can be dragged, and at the root it is a disabled affordance rather than a
|
|
1539
1416
|
// Missing one (§8.6: ONE shape).
|
|
1540
|
-
const
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
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
|
-
);
|
|
1417
|
+
const canDrag = structuralTarget(selection) !== null;
|
|
1418
|
+
|
|
1419
|
+
return {
|
|
1420
|
+
barX: pos.left,
|
|
1421
|
+
barY: pos.top,
|
|
1422
|
+
canDrag,
|
|
1423
|
+
dragHint: canDrag ? "Drag to reorder" : "Drag to reorder — the document root cannot move",
|
|
1424
|
+
formats,
|
|
1425
|
+
hasOverflow: placed.length > BLOCKBAR_MAX_ITEMS,
|
|
1426
|
+
offscreen: false,
|
|
1427
|
+
overflowOpen: _overflowHandle !== null,
|
|
1428
|
+
parentDisabled: !parentPath,
|
|
1429
|
+
parentHint: parentChord ? `${parentLabel} (${parentChord})` : parentLabel,
|
|
1430
|
+
parentLabel,
|
|
1431
|
+
showFormat,
|
|
1432
|
+
tagDisabled: convertTargets.length === 0,
|
|
1433
|
+
tagHint: convertTargets.length > 0 ? "Change element type" : tagLabel,
|
|
1434
|
+
tagLabel,
|
|
1435
|
+
tagPopup: convertTargets.length > 0 ? "menu" : "",
|
|
1436
|
+
verbs: placed.slice(0, BLOCKBAR_MAX_ITEMS).map((command) => toolOf(registry, command)),
|
|
1437
|
+
visible: true,
|
|
1438
|
+
};
|
|
1439
|
+
}
|
|
1665
1440
|
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1441
|
+
/** Render the unified block action bar above the selected element. */
|
|
1442
|
+
export function renderBlockActionBar(): void {
|
|
1443
|
+
if (!_ctx) {
|
|
1444
|
+
return;
|
|
1669
1445
|
}
|
|
1446
|
+
ensureSurface();
|
|
1670
1447
|
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1448
|
+
const tab = activeTab.value;
|
|
1449
|
+
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
|
+
hideBar();
|
|
1675
1458
|
return;
|
|
1676
1459
|
}
|
|
1677
|
-
|
|
1460
|
+
_suppressedFor = null;
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
const next = projectBar();
|
|
1464
|
+
if (!next) {
|
|
1465
|
+
hideBar();
|
|
1466
|
+
return;
|
|
1467
|
+
}
|
|
1468
|
+
applyBarView(next);
|
|
1469
|
+
|
|
1470
|
+
// Post-render side effect: the clamp measures a box that does not exist until the document has
|
|
1471
|
+
// Reconciled, so it waits a frame rather than reading a stale one.
|
|
1472
|
+
requestAnimationFrame(() => {
|
|
1473
|
+
const bar = _surface?.bar();
|
|
1474
|
+
if (bar) {
|
|
1475
|
+
clampBarToWindow(bar);
|
|
1476
|
+
}
|
|
1678
1477
|
});
|
|
1679
1478
|
}
|