@jxsuite/studio 4.0.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/data/elements-meta.json +5 -15
- package/data/html-meta.json +51 -6
- package/dist/chunks/{2020-xp8vnwma.js → 2020-g79n01v9.js} +14 -14
- package/dist/chunks/action-group-sp6tna8v.js +118 -0
- package/dist/chunks/adapter-nj7tnjfn.js +13 -0
- package/dist/chunks/cf-account-picker-fyf63pmy.js +495 -0
- package/dist/chunks/cf-settings-71bv5zn9.js +20 -0
- package/dist/chunks/color-area-v17df7b8.js +154 -0
- package/dist/chunks/color-field-anwybnkn.js +208 -0
- package/dist/chunks/color-slider-44ez96fc.js +79 -0
- package/dist/chunks/color-xgg3kfw7.js +35 -0
- package/dist/chunks/combobox-5e7jev23.js +236 -0
- package/dist/chunks/components-2w1f35qs.js +18 -0
- package/dist/chunks/content-source-3wqeas45.js +46 -0
- package/dist/chunks/convert-to-repeater-d0cgnb6d.js +639 -0
- package/dist/chunks/dialog-s890b918.js +85 -0
- package/dist/chunks/editor-3qhbg51x.js +35 -0
- package/dist/chunks/entry-commands-d6g50j7s.js +42 -0
- package/dist/chunks/extension-sections-fy7q4fe9.js +651 -0
- package/dist/chunks/file-ops-3btj6st0.js +47 -0
- package/dist/chunks/files-ef8fgs3w.js +72 -0
- package/dist/chunks/format-host-3c33nwr8.js +40 -0
- package/dist/chunks/fs-events-13tj6sex.js +18 -0
- package/dist/chunks/git-panel-4wp2rn9b.js +49 -0
- package/dist/chunks/icons-yy10wk9r.js +402 -0
- package/dist/chunks/iframe-entry-15g3n6xv.js +114 -0
- package/dist/chunks/iframe-entry-b27vq81n.js +203 -0
- package/dist/chunks/iframe-entry-nbyxee1y.js +154 -0
- package/dist/chunks/iframe-host-jgesd924.js +115 -0
- package/dist/chunks/{index-nj44jpzj.js → index-mdmyx6s2.js} +11 -11
- package/dist/chunks/index-n4j7qxyr.js +109 -0
- package/dist/chunks/{javascript-q8kpmfah.js → javascript-xvrfrpkb.js} +8 -9
- package/dist/chunks/{jsonMode-dkr040ga.js → jsonMode-tyx2kj6p.js} +526 -527
- package/dist/chunks/jx-validate-j0wxk72w.js +15 -0
- package/dist/chunks/layers-1nq5evsh.js +29 -0
- package/dist/chunks/layers-panel-ar2gh90g.js +40 -0
- package/dist/chunks/listbox-6v2ac08j.js +44 -0
- package/dist/chunks/lit-html-m244w9nh.js +17 -0
- package/dist/chunks/media-picker-2qbetq73.js +33 -0
- package/dist/chunks/menu-q99sy9rf.js +285 -0
- package/dist/chunks/menu-rf7n72mf.js +14 -0
- package/dist/chunks/{monaco-binding-rj6xpx9y.js → monaco-binding-sw6en4dw.js} +12 -15
- package/dist/chunks/monaco-buffer-bhy3hb6m.js +24 -0
- package/dist/chunks/{monaco-setup-rws7k4br.js → monaco-setup-8r62fj6y.js} +27 -37
- package/dist/chunks/notify-628t0anh.js +21 -0
- package/dist/chunks/number-field-5cndyft8.js +72 -0
- package/dist/chunks/parse-0zx14y15.js +10 -0
- package/dist/chunks/platform-ts9cg1a4.js +15 -0
- package/dist/chunks/popover-47z3ytva.js +25 -0
- package/dist/chunks/publish-panel-98f7sr3g.js +27 -0
- package/dist/chunks/quick-search-srzz83k2.js +45 -0
- package/dist/chunks/references-n553f0e1.js +26 -0
- package/dist/chunks/right-panel-rrgx2dn3.js +45 -0
- package/dist/chunks/section-registry-5pq32t0q.js +25 -0
- package/dist/chunks/select-2r16ez41.js +49 -0
- package/dist/chunks/split-g7nm1rgb.js +191 -0
- package/dist/chunks/studio-0hqbpbd0.js +62 -0
- package/dist/chunks/studio-18avnhjp.js +2430 -0
- package/dist/chunks/{studio-kryvsw6j.js → studio-1k325pwf.js} +48 -48
- package/dist/chunks/{studio-hbtp0eh1.js → studio-25hkdrkw.js} +356 -85
- package/dist/chunks/studio-26d7myd1.js +258 -0
- package/dist/chunks/{studio-p8j86wag.js → studio-38emantv.js} +22 -5
- package/dist/chunks/studio-3gxv60tn.js +422 -0
- package/dist/chunks/{studio-8swrjbny.js → studio-3hxkd92t.js} +275 -163
- package/dist/chunks/studio-4m0haseg.js +185 -0
- package/dist/chunks/{studio-ynxwkrzd.js → studio-4wbb9ct0.js} +1953 -1785
- package/dist/chunks/{studio-gv8vvshc.js → studio-5523bpzt.js} +11 -11
- package/dist/chunks/{studio-5q69gfag.js → studio-57mf66w2.js} +1753 -2484
- package/dist/chunks/studio-5jy81t3s.js +17463 -0
- package/dist/chunks/studio-5rt7sqa0.js +1168 -0
- package/dist/chunks/studio-5te5tqng.js +81 -0
- package/dist/chunks/{studio-p4ngc046.js → studio-6ewqv390.js} +9 -9
- package/dist/chunks/{studio-9ab8gqsm.js → studio-6nzaktvy.js} +7 -7
- package/dist/chunks/studio-6xa21ej9.js +28 -0
- package/dist/chunks/{studio-5ehhackm.js → studio-727xh0e3.js} +19 -19
- package/dist/chunks/{studio-eenfkt4e.js → studio-79y5sz51.js} +52 -38
- package/dist/chunks/{studio-4q0wznzf.js → studio-7e11jpa9.js} +461 -145
- package/dist/chunks/{studio-q7v10cg2.js → studio-7r42c6ca.js} +17 -7
- package/dist/chunks/studio-7xptrg0j.js +954 -0
- package/dist/chunks/{studio-cft9r4r7.js → studio-81390n6f.js} +22 -22
- package/dist/chunks/{studio-q43a5y9h.js → studio-8jn5yhyn.js} +16 -16
- package/dist/chunks/studio-8qtrpzr3.js +839 -0
- package/dist/chunks/{studio-dg7zyt5x.js → studio-98b3z07n.js} +1 -1
- package/dist/chunks/studio-9xxz1q69.js +1356 -0
- package/dist/chunks/studio-akbww0j5.js +13615 -0
- package/dist/chunks/studio-arfpe389.js +195 -0
- package/dist/chunks/{studio-2zkz8evn.js → studio-az3zd49t.js} +473 -281
- package/dist/chunks/studio-cpvb2ed1.js +1171 -0
- package/dist/chunks/{studio-9r6j5ev1.js → studio-d0300636.js} +5078 -19
- package/dist/chunks/{studio-bgxt93ga.js → studio-d055c37e.js} +13 -13
- package/dist/chunks/studio-d1ja2x9r.js +362 -0
- package/dist/chunks/{studio-b9b6t1dg.js → studio-e1f24eff.js} +5664 -5666
- package/dist/chunks/{studio-4pqqxbrt.js → studio-em9nw8eh.js} +301 -112
- package/dist/chunks/studio-erdatzvp.js +1190 -0
- package/dist/chunks/{studio-g1wdche0.js → studio-esrxgwh6.js} +80 -80
- package/dist/chunks/studio-fabxk6r9.js +2811 -0
- package/dist/chunks/{studio-3n4ndrzh.js → studio-h4nvnjvz.js} +303 -303
- package/dist/chunks/studio-hhvs4sdg.js +195 -0
- package/dist/chunks/{studio-68n67094.js → studio-kcgff4r0.js} +15 -18
- package/dist/chunks/{studio-685s0ytq.js → studio-nqgawfks.js} +572 -572
- package/dist/chunks/{studio-xrapxwwc.js → studio-p1dw8wfw.js} +55 -29
- package/dist/chunks/studio-pm6hgy7r.js +4763 -0
- package/dist/chunks/studio-q0a3yk4n.js +33 -0
- package/dist/chunks/{studio-wcy8trfx.js → studio-q3x06vmj.js} +23 -22
- package/dist/chunks/studio-qahxvnan.js +111 -0
- package/dist/chunks/{studio-53n8bp04.js → studio-r6xmmtfh.js} +1 -1
- package/dist/chunks/{studio-kjjsjak4.js → studio-rkstz940.js} +3661 -6136
- package/dist/chunks/{studio-tvgyw9xw.js → studio-rnyf3f8e.js} +83 -250
- package/dist/chunks/studio-rpyah2ew.js +2805 -0
- package/dist/chunks/studio-rs76k67r.js +12 -0
- package/dist/chunks/{studio-yxrkyv8a.js → studio-s6z853aq.js} +12108 -11766
- package/dist/chunks/{studio-sf7zjgv1.js → studio-sgdc5pfc.js} +8 -8
- package/dist/chunks/studio-tb3ev01j.js +763 -0
- package/dist/chunks/{studio-t5wqwzdh.js → studio-tjjk7jvk.js} +1 -1
- package/dist/chunks/studio-vhv5tgxm.js +51 -0
- package/dist/chunks/{studio-htsf0w6m.js → studio-vz68yn9e.js} +31 -154
- package/dist/chunks/studio-wqpngpsk.js +174 -0
- package/dist/chunks/{studio-eesa1myv.js → studio-x3rty6tr.js} +58 -58
- package/dist/chunks/studio-xsykpvsh.js +8448 -0
- package/dist/chunks/{studio-92ab2jqe.js → studio-y9zedyjn.js} +14 -14
- package/dist/chunks/studio-yxa81f45.js +12 -0
- package/dist/chunks/swatch-group-t0n6h2rn.js +139 -0
- package/dist/chunks/tabs-wbp1cdws.js +156 -0
- package/dist/chunks/textfield-abk6qyee.js +73 -0
- package/dist/chunks/toast-780pvfbv.js +27 -0
- package/dist/chunks/toast-host-3sqv46sn.js +116 -0
- package/dist/chunks/toolbar-xbh5bhtr.js +194 -0
- package/dist/chunks/tooltip-eptb614w.js +369 -0
- package/dist/chunks/tree-a73p0pqm.js +367 -0
- package/dist/chunks/{tsMode-76kr53q0.js → tsMode-jy4m86rv.js} +83 -85
- package/dist/chunks/workspace-wrew8zk4.js +69 -0
- package/dist/chunks/{ws-client-6shc5rze.js → ws-client-ny4zc51r.js} +20 -22
- package/dist/iframe-entry.js +1812 -698
- package/dist/manifest.json +7 -0
- package/dist/studio.js +52900 -36573
- package/dist/workers/editor.worker.js +1040 -1085
- package/dist/workers/json.worker.js +1946 -1991
- package/dist/workers/ts.worker.js +4960 -5005
- package/favicon.ico +0 -0
- package/index.html +2 -0
- package/package.json +22 -55
- package/src/about/about-modal.ts +73 -123
- package/src/browse/library-commands.ts +20 -49
- package/src/browse/library-layouts.ts +87 -218
- package/src/browse/library-pane.ts +406 -438
- package/src/canvas/canvas-helpers.ts +0 -49
- package/src/canvas/canvas-live-render.ts +25 -2
- package/src/canvas/canvas-render.ts +666 -231
- package/src/canvas/canvas-surface.ts +0 -1
- package/src/canvas/canvas-utils.ts +310 -117
- package/src/canvas/dialog-path.ts +76 -0
- package/src/canvas/dialog-state.ts +67 -0
- package/src/canvas/diff-marks.ts +252 -0
- package/src/canvas/diff-toolbar.ts +349 -0
- package/src/canvas/diff-view.ts +123 -0
- package/src/canvas/edit-width-drag.ts +187 -0
- package/src/canvas/edit-width.ts +150 -0
- package/src/canvas/iframe-entry.ts +125 -8
- package/src/canvas/iframe-host.ts +271 -15
- package/src/canvas/iframe-interaction.ts +133 -13
- package/src/canvas/iframe-patch.ts +7 -1
- package/src/canvas/iframe-position.ts +5 -6
- package/src/canvas/iframe-protocol.ts +106 -5
- package/src/canvas/iframe-render.ts +373 -5
- package/src/canvas/iframe-slash.ts +2 -2
- package/src/canvas/iframe-subtree.ts +5 -8
- package/src/canvas/path-mapping.ts +25 -3
- package/src/canvas/popover-path.ts +106 -0
- package/src/canvas/popover-state.ts +155 -0
- package/src/canvas/surface-registry.ts +21 -10
- package/src/collab/presence-chips.ts +51 -73
- package/src/commands/app-commands.ts +10 -0
- package/src/commands/budget.ts +12 -0
- package/src/commands/command-args.ts +369 -6
- package/src/commands/defaults.ts +44 -14
- package/src/commands/live-context.ts +11 -5
- package/src/commands/reference.ts +12 -2
- package/src/commands/registry.ts +165 -8
- package/src/content/entry-commands.ts +16 -23
- package/src/content/entry-editor.ts +144 -101
- package/src/editor/canvas-slash-bridge.ts +2 -2
- package/src/editor/context-menu.ts +49 -200
- package/src/editor/convert-to-component.ts +42 -82
- package/src/editor/convert-to-repeater.ts +132 -173
- package/src/editor/inline-edit.ts +11 -0
- package/src/editor/shortcuts.ts +65 -26
- package/src/editor/slash-menu.ts +150 -157
- package/src/files/file-ops.ts +117 -11
- package/src/files/files.ts +949 -664
- package/src/files/json-layout.ts +474 -0
- package/src/files/media-paths.ts +13 -42
- package/src/files/media-usage.ts +41 -131
- package/src/files/serialize-document.ts +8 -1
- package/src/format/convert-file.ts +16 -10
- package/src/format/format-host.ts +50 -1
- package/src/github/github-auth.ts +71 -29
- package/src/github/github-publish.ts +57 -65
- package/src/grid/cell-editors.ts +46 -4
- package/src/grid/cell-popovers.ts +65 -62
- package/src/grid/grid-open.ts +86 -64
- package/src/grid/grid-panel.ts +390 -469
- package/src/grid/redirects-grid.ts +40 -44
- package/src/hosting/document.ts +2 -0
- package/src/hosting/layout.ts +10 -0
- package/src/i18n/i18n-commands.ts +127 -62
- package/src/media/media-pane.ts +83 -133
- package/src/new-project/add-repo-modal.ts +125 -176
- package/src/new-project/import-tab.ts +201 -250
- package/src/new-project/location-fields.ts +101 -165
- package/src/new-project/new-project-modal.ts +376 -367
- package/src/packages/jxsuite-update.ts +76 -18
- package/src/panels/activity-panel.ts +137 -104
- package/src/panels/ai-chat/chat-markdown.ts +15 -13
- package/src/panels/ai-chat/chat-view.ts +307 -472
- package/src/panels/ai-chat/composer.ts +175 -153
- package/src/panels/ai-chat/sessions-view.ts +31 -62
- package/src/panels/ai-panel.ts +210 -185
- package/src/panels/block-action-bar.ts +437 -643
- package/src/panels/bottom-dock.ts +112 -47
- package/src/panels/chat-panel.ts +19 -14
- package/src/panels/data-explorer.ts +141 -102
- package/src/panels/data-grid.ts +183 -150
- package/src/panels/dnd.ts +75 -20
- package/src/panels/editors.ts +26 -19
- package/src/panels/elements-panel.ts +243 -186
- package/src/panels/empty-state.ts +26 -42
- package/src/panels/events-panel.ts +950 -729
- package/src/panels/formula-workspace.ts +434 -207
- package/src/panels/frontmatter-fields.ts +185 -153
- package/src/panels/frontmatter-panel.ts +411 -187
- package/src/panels/git-diff-open.ts +148 -0
- package/src/panels/git-panel.ts +679 -605
- package/src/panels/head-panel.ts +617 -435
- package/src/panels/i18n-panel.ts +153 -133
- package/src/panels/imports-panel.ts +388 -414
- package/src/panels/jump-bar.ts +174 -147
- package/src/panels/layers-panel.ts +785 -671
- package/src/panels/left-panel.ts +166 -94
- package/src/panels/navigator-panels.ts +1 -1
- package/src/panels/pane-context.ts +529 -677
- package/src/panels/pane-grid.ts +265 -241
- package/src/panels/panel-registry.ts +15 -4
- package/src/panels/problems-panel.ts +177 -99
- package/src/panels/properties-panel.ts +1525 -966
- package/src/panels/provenance.ts +31 -113
- package/src/panels/quick-search.ts +217 -172
- package/src/panels/right-panel.ts +135 -169
- package/src/panels/seo-modal.ts +346 -167
- package/src/panels/settings-menu.ts +109 -646
- package/src/panels/settings-pane.ts +102 -86
- package/src/panels/signals-panel.ts +1318 -1172
- package/src/panels/statement-editor.ts +556 -428
- package/src/panels/style-panel.ts +1367 -630
- package/src/panels/style-utils.ts +36 -32
- package/src/panels/stylebook-doc.ts +30 -73
- package/src/panels/stylebook-layers-panel.ts +202 -109
- package/src/panels/stylebook-panel.ts +124 -88
- package/src/panels/tab-strip.ts +385 -287
- package/src/platforms/cloud.ts +347 -86
- package/src/platforms/devserver.ts +13 -0
- package/src/publish/deploy-checklist.ts +124 -78
- package/src/publish/publish-commands.ts +4 -12
- package/src/publish/publish-panel.ts +165 -299
- package/src/services/a11y-report.ts +100 -43
- package/src/services/ai-command-tools.ts +468 -0
- package/src/services/ai-import-tools.ts +50 -12
- package/src/services/ai-models.ts +108 -1
- package/src/services/ai-session-store.ts +6 -0
- package/src/services/ai-system-prompt.ts +110 -18
- package/src/services/ai-tools.ts +28 -143
- package/src/services/ai-write-report.ts +174 -0
- package/src/services/context-manager.ts +12 -1
- package/src/services/document-assistant.ts +50 -2
- package/src/services/idle.ts +16 -14
- package/src/services/live-surfaces.ts +237 -0
- package/src/services/model-uri.ts +75 -0
- package/src/services/notify.ts +63 -6
- package/src/services/popover-report.ts +325 -0
- package/src/services/surface-registry.ts +56 -0
- package/src/services/tool-executor.ts +17 -0
- package/src/settings/contexts-section.ts +301 -299
- package/src/settings/contributed-section.ts +361 -222
- package/src/settings/css-vars-editor.ts +320 -325
- package/src/settings/defs-editor.ts +572 -535
- package/src/settings/dependencies-editor.ts +79 -107
- package/src/settings/extension-commands.ts +379 -0
- package/src/settings/extension-rows.ts +113 -0
- package/src/settings/extensions-section.ts +287 -0
- package/src/settings/general-settings.ts +130 -181
- package/src/settings/head-editor.ts +10 -305
- package/src/settings/locales-section.ts +146 -196
- package/src/settings/preferences-accounts.ts +193 -12
- package/src/settings/preferences-dialog.ts +384 -307
- package/src/settings/project-sections.ts +108 -181
- package/src/settings/schema-field-ui.ts +11 -399
- package/src/settings/section-registry.ts +8 -1
- package/src/settings/settings-document.ts +31 -29
- package/src/shell/tree.ts +42 -87
- package/src/shell.ts +48 -36
- package/src/studio.ts +90 -67
- package/src/style/token-ref.ts +0 -39
- package/src/surfaces/about.json +263 -0
- package/src/surfaces/about.ts +163 -0
- package/src/surfaces/add-repo.json +486 -0
- package/src/surfaces/add-repo.ts +236 -0
- package/src/surfaces/ai-chat.json +2185 -0
- package/src/surfaces/ai-chat.ts +549 -0
- package/src/surfaces/ai-credentials-form.json +332 -0
- package/src/surfaces/ai-credentials-form.ts +151 -0
- package/src/surfaces/ai-managed-connect.json +108 -0
- package/src/surfaces/ai-managed-connect.ts +123 -0
- package/src/surfaces/ai-model-picker.json +114 -0
- package/src/surfaces/ai-model-picker.ts +139 -0
- package/src/surfaces/block-action-bar.json +640 -0
- package/src/surfaces/block-action-bar.ts +368 -0
- package/src/surfaces/bottom-dock.json +154 -0
- package/src/surfaces/bottom-dock.ts +125 -0
- package/src/surfaces/canvas-stage.json +444 -0
- package/src/surfaces/canvas-stage.ts +253 -0
- package/src/surfaces/cf-account-picker.json +300 -0
- package/src/surfaces/cf-account-picker.ts +214 -0
- package/src/surfaces/commandbar.json +984 -0
- package/src/{panels/toolbar.ts → surfaces/commandbar.ts} +414 -389
- package/src/surfaces/convert-repeater.json +309 -0
- package/src/surfaces/convert-repeater.ts +276 -0
- package/src/surfaces/data-actions.json +157 -0
- package/src/surfaces/data-actions.ts +115 -0
- package/src/surfaces/dialog.json +289 -0
- package/src/surfaces/dialog.ts +343 -0
- package/src/surfaces/diff-toolbar.json +266 -0
- package/src/surfaces/diff-toolbar.ts +139 -0
- package/src/surfaces/doc-header.json +739 -0
- package/src/surfaces/doc-header.ts +223 -0
- package/src/surfaces/empty-state.json +152 -0
- package/src/surfaces/empty-state.ts +173 -0
- package/src/surfaces/entry-editor.json +281 -0
- package/src/surfaces/entry-editor.ts +191 -0
- package/src/surfaces/expression-editor.json +977 -0
- package/src/surfaces/expression-editor.ts +266 -0
- package/src/surfaces/files-panel.json +592 -0
- package/src/surfaces/files-panel.ts +301 -0
- package/src/surfaces/formula-palette.json +364 -0
- package/src/surfaces/formula-palette.ts +340 -0
- package/src/surfaces/git-panel.json +1822 -0
- package/src/surfaces/git-panel.ts +272 -0
- package/src/surfaces/github-auth.json +112 -0
- package/src/surfaces/github-auth.ts +139 -0
- package/src/surfaces/github-publish.json +206 -0
- package/src/surfaces/github-publish.ts +169 -0
- package/src/surfaces/grid-cell.json +218 -0
- package/src/surfaces/grid-cell.ts +120 -0
- package/src/surfaces/grid-open.json +147 -0
- package/src/surfaces/grid-open.ts +87 -0
- package/src/surfaces/grid-panel.json +706 -0
- package/src/surfaces/grid-panel.ts +206 -0
- package/src/surfaces/grid-replace.json +179 -0
- package/src/surfaces/grid-replace.ts +89 -0
- package/src/surfaces/grid-views.json +446 -0
- package/src/surfaces/grid-views.ts +172 -0
- package/src/surfaces/inspector-dock.json +187 -0
- package/src/surfaces/inspector-dock.ts +161 -0
- package/src/surfaces/jump-bar.json +243 -0
- package/src/surfaces/jump-bar.ts +129 -0
- package/src/surfaces/jxsuite-update.json +146 -0
- package/src/surfaces/jxsuite-update.ts +149 -0
- package/src/surfaces/library-pane.json +1509 -0
- package/src/surfaces/library-pane.ts +363 -0
- package/src/surfaces/logic-panel.json +1949 -0
- package/src/surfaces/logic-panel.ts +369 -0
- package/src/surfaces/logic-workspace.json +685 -0
- package/src/surfaces/logic-workspace.ts +238 -0
- package/src/surfaces/media-browser.json +303 -0
- package/src/surfaces/media-browser.ts +145 -0
- package/src/surfaces/media-field.json +163 -0
- package/src/surfaces/media-field.ts +116 -0
- package/src/surfaces/media-pane.json +513 -0
- package/src/surfaces/media-pane.ts +197 -0
- package/src/surfaces/menu.json +232 -0
- package/src/surfaces/menu.ts +375 -0
- package/src/surfaces/navigator-dock.json +113 -0
- package/src/surfaces/navigator-dock.ts +130 -0
- package/src/surfaces/new-project.json +1654 -0
- package/src/surfaces/new-project.ts +411 -0
- package/src/surfaces/palette.json +535 -0
- package/src/surfaces/pane-context.json +1497 -0
- package/src/surfaces/pane-context.ts +513 -0
- package/src/surfaces/pane-grid.json +188 -0
- package/src/surfaces/pane-grid.ts +250 -0
- package/src/surfaces/panel-activity.json +563 -0
- package/src/surfaces/panel-activity.ts +187 -0
- package/src/surfaces/panel-data.json +141 -0
- package/src/surfaces/panel-data.ts +156 -0
- package/src/surfaces/panel-deploy-checklist.json +283 -0
- package/src/surfaces/panel-deploy-checklist.ts +175 -0
- package/src/surfaces/panel-elements.json +367 -0
- package/src/surfaces/panel-elements.ts +175 -0
- package/src/surfaces/panel-i18n.json +569 -0
- package/src/surfaces/panel-i18n.ts +212 -0
- package/src/surfaces/panel-imports.json +780 -0
- package/src/surfaces/panel-imports.ts +209 -0
- package/src/surfaces/panel-outline.json +734 -0
- package/src/surfaces/panel-outline.ts +326 -0
- package/src/surfaces/panel-page.json +1017 -0
- package/src/surfaces/panel-page.ts +240 -0
- package/src/surfaces/panel-problems.json +497 -0
- package/src/surfaces/panel-problems.ts +157 -0
- package/src/surfaces/panel-signals.json +1973 -0
- package/src/surfaces/panel-signals.ts +436 -0
- package/src/surfaces/panel-stylebook-layers.json +317 -0
- package/src/surfaces/panel-stylebook-layers.ts +155 -0
- package/src/surfaces/preferences.json +850 -0
- package/src/surfaces/preferences.ts +332 -0
- package/src/surfaces/progress-modal.json +110 -0
- package/src/surfaces/progress-modal.ts +150 -0
- package/src/surfaces/properties-panel.json +1578 -0
- package/src/surfaces/properties-panel.ts +337 -0
- package/src/surfaces/publish.json +945 -0
- package/src/surfaces/publish.ts +392 -0
- package/src/surfaces/push-plan.json +110 -0
- package/src/surfaces/push-plan.ts +87 -0
- package/src/surfaces/rail.json +255 -0
- package/src/surfaces/rail.ts +206 -0
- package/src/surfaces/reference-field.json +191 -0
- package/src/surfaces/reference-field.ts +168 -0
- package/src/surfaces/schema-builder.json +1011 -0
- package/src/surfaces/schema-builder.ts +181 -0
- package/src/surfaces/schema-form.json +1013 -0
- package/src/surfaces/schema-form.ts +287 -0
- package/src/surfaces/secret-field.json +60 -0
- package/src/surfaces/secret-field.ts +146 -0
- package/src/surfaces/seo.json +932 -0
- package/src/surfaces/seo.ts +334 -0
- package/src/surfaces/settings-contexts.json +538 -0
- package/src/surfaces/settings-contexts.ts +196 -0
- package/src/surfaces/settings-contributed.json +489 -0
- package/src/surfaces/settings-contributed.ts +199 -0
- package/src/surfaces/settings-css-vars.json +820 -0
- package/src/surfaces/settings-css-vars.ts +199 -0
- package/src/surfaces/settings-defs.json +1363 -0
- package/src/surfaces/settings-defs.ts +232 -0
- package/src/surfaces/settings-deploy.json +108 -0
- package/src/surfaces/settings-deploy.ts +135 -0
- package/src/surfaces/settings-extensions.json +486 -0
- package/src/surfaces/settings-extensions.ts +201 -0
- package/src/surfaces/settings-head.json +779 -0
- package/src/surfaces/settings-head.ts +397 -0
- package/src/surfaces/settings-locales.json +420 -0
- package/src/surfaces/settings-locales.ts +137 -0
- package/src/surfaces/settings-overview.json +423 -0
- package/src/surfaces/settings-overview.ts +218 -0
- package/src/surfaces/settings-packages.json +549 -0
- package/src/surfaces/settings-packages.ts +158 -0
- package/src/surfaces/settings-pane.json +147 -0
- package/src/surfaces/settings-pane.ts +150 -0
- package/src/surfaces/settings-rawjson.json +104 -0
- package/src/surfaces/settings-rawjson.ts +107 -0
- package/src/surfaces/shell.json +99 -0
- package/src/surfaces/shell.ts +30 -0
- package/src/surfaces/slash-menu.json +180 -0
- package/src/surfaces/slash-menu.ts +272 -0
- package/src/surfaces/statements.json +607 -0
- package/src/surfaces/statements.ts +238 -0
- package/src/surfaces/statusbar.json +94 -0
- package/src/{panels → surfaces}/statusbar.ts +163 -90
- package/src/surfaces/style-panel.json +1643 -0
- package/src/surfaces/style-panel.ts +338 -0
- package/src/surfaces/stylebook-chrome.json +122 -0
- package/src/surfaces/stylebook-chrome.ts +122 -0
- package/src/surfaces/tab-strip.json +448 -0
- package/src/surfaces/tab-strip.ts +200 -0
- package/src/surfaces/target-line.json +419 -0
- package/src/surfaces/target-line.ts +349 -0
- package/src/surfaces/toasts.json +189 -0
- package/src/surfaces/welcome.json +571 -0
- package/src/surfaces/welcome.ts +338 -0
- package/src/tabs/project-config.ts +71 -15
- package/src/tabs/tab.ts +52 -1
- package/src/types.ts +49 -3
- package/src/ui/ai-credentials-form.ts +104 -100
- package/src/ui/ai-managed-connect.ts +116 -49
- package/src/ui/ai-model-picker.ts +102 -47
- package/src/ui/cf-account-picker.ts +135 -0
- package/src/ui/color-selector.ts +80 -301
- package/src/ui/dynamic-slot.ts +43 -283
- package/src/ui/expression-editor.ts +797 -576
- package/src/ui/field-input.ts +8 -181
- package/src/ui/form-controls.ts +838 -366
- package/src/ui/formula-chips.ts +80 -85
- package/src/ui/kit.ts +33 -0
- package/src/ui/layers.ts +293 -593
- package/src/ui/media-picker.ts +203 -284
- package/src/ui/panel-resize.ts +251 -64
- package/src/ui/popover-surface.ts +153 -0
- package/src/ui/progress-modal.ts +22 -60
- package/src/ui/regions.ts +13 -7
- package/src/ui/schema-form.ts +906 -440
- package/src/ui/surface.ts +122 -0
- package/src/ui/unit-selector.ts +15 -131
- package/src/ui/value-source.ts +14 -0
- package/src/ui/virtual-window.ts +88 -9
- package/src/utils/canvas-media.ts +72 -0
- package/src/utils/edit-display.ts +7 -2
- package/src/utils/element-selectors.ts +69 -0
- package/src/utils/link-target.ts +2 -2
- package/src/utils/studio-utils.ts +0 -10
- package/src/view.ts +15 -0
- package/src/workspace/pane-derive.ts +36 -14
- package/src/workspace/workspace.ts +15 -6
- package/styles/canvas.css +72 -467
- package/styles/forced-colors.css +48 -82
- package/styles/forced-colors.json +41 -0
- package/styles/inspector.css +2 -1301
- package/styles/overlays.css +96 -2823
- package/styles/panels.css +6 -1423
- package/styles/shell-frame.css +272 -0
- package/styles/shell-frame.json +227 -0
- package/styles/shell.css +0 -1532
- package/styles/tokens.css +65 -136
- package/styles/tokens.json +84 -0
- package/dist/chunks/Overlay.dev-723bv0df.js +0 -23
- package/dist/chunks/adapter-jkcyhgtr.js +0 -14
- package/dist/chunks/cf-settings-45fgyx8y.js +0 -21
- package/dist/chunks/components-zmx10t62.js +0 -21
- package/dist/chunks/content-source-t88vb2dr.js +0 -49
- package/dist/chunks/convert-to-repeater-dhw1nrz9.js +0 -268
- package/dist/chunks/editor-cptq5jp1.js +0 -37
- package/dist/chunks/entry-commands-6z6bhxg0.js +0 -46
- package/dist/chunks/extension-sections-3ydgjbdy.js +0 -311
- package/dist/chunks/file-ops-rskmhxyq.js +0 -48
- package/dist/chunks/files-0cratsr0.js +0 -92
- package/dist/chunks/flow-qbfgrpeh.js +0 -634
- package/dist/chunks/focus-trap.esm-ggawf1hy.js +0 -1024
- package/dist/chunks/focus-visible-6eg70z18.js +0 -167
- package/dist/chunks/format-host-tgcmfs4p.js +0 -43
- package/dist/chunks/fs-events-hrrg328g.js +0 -21
- package/dist/chunks/git-panel-0yywyk76.js +0 -58
- package/dist/chunks/index-zqxmqxmh.js +0 -109
- package/dist/chunks/jx-validate-8q2g05x1.js +0 -19
- package/dist/chunks/layers-panel-z3tqe5cd.js +0 -67
- package/dist/chunks/layers-q75tj2mf.js +0 -45
- package/dist/chunks/lit-html-j055mkh8.js +0 -22
- package/dist/chunks/monaco-buffer-dqw0xxpx.js +0 -25
- package/dist/chunks/notify-yjm79n4s.js +0 -30
- package/dist/chunks/parse-24d0gay3.js +0 -19
- package/dist/chunks/platform-ba1j1gbh.js +0 -16
- package/dist/chunks/publish-panel-54jvhskq.js +0 -28
- package/dist/chunks/quick-search-cgfmjwq5.js +0 -65
- package/dist/chunks/references-3qx8fkyp.js +0 -29
- package/dist/chunks/right-panel-cqp8bc4v.js +0 -46
- package/dist/chunks/section-registry-sx1ykvkw.js +0 -30
- package/dist/chunks/sp-overlay.dev-yztmtrs1.js +0 -18
- package/dist/chunks/sp-popover.dev-n2etmqdp.js +0 -9
- package/dist/chunks/sp-progress-circle.dev-hryhj0es.js +0 -15
- package/dist/chunks/sp-tooltip.dev-xcvpnqr0.js +0 -15
- package/dist/chunks/sp-tray.dev-tkbscm06.js +0 -199
- package/dist/chunks/studio-0a5qrh01.js +0 -3
- package/dist/chunks/studio-18j3bmcz.js +0 -14
- package/dist/chunks/studio-1hbqnt19.js +0 -1823
- package/dist/chunks/studio-4rzyrtjz.js +0 -426
- package/dist/chunks/studio-5rywrkxz.js +0 -1903
- package/dist/chunks/studio-7afd21nw.js +0 -67
- package/dist/chunks/studio-7nte2hj4.js +0 -613
- package/dist/chunks/studio-8jpzqk5s.js +0 -128
- package/dist/chunks/studio-8q517dfr.js +0 -24
- package/dist/chunks/studio-9c4k4vhv.js +0 -357
- package/dist/chunks/studio-9n4mrmnb.js +0 -184
- package/dist/chunks/studio-a0tswy0w.js +0 -58
- package/dist/chunks/studio-ahjfdy6e.js +0 -387
- package/dist/chunks/studio-ahnzd9rg.js +0 -23
- package/dist/chunks/studio-bw5dz6km.js +0 -608
- package/dist/chunks/studio-cb12yz9w.js +0 -15055
- package/dist/chunks/studio-d1ks410h.js +0 -175
- package/dist/chunks/studio-eqq1w638.js +0 -113
- package/dist/chunks/studio-ex8zyp98.js +0 -1813
- package/dist/chunks/studio-h4rxscfc.js +0 -10
- package/dist/chunks/studio-kyh9avvv.js +0 -28
- package/dist/chunks/studio-kyk2v92a.js +0 -659
- package/dist/chunks/studio-m3hwn7y1.js +0 -120
- package/dist/chunks/studio-mmv72a27.js +0 -4344
- package/dist/chunks/studio-n8b9wqwy.js +0 -291
- package/dist/chunks/studio-pdfjntbk.js +0 -6939
- package/dist/chunks/studio-qq6jym04.js +0 -53
- package/dist/chunks/studio-r1r7afxx.js +0 -1066
- package/dist/chunks/studio-rdx8jcxy.js +0 -83
- package/dist/chunks/studio-rn9ta2mz.js +0 -111
- package/dist/chunks/studio-t7qb9db8.js +0 -665
- package/dist/chunks/studio-tmc5emrw.js +0 -8
- package/dist/chunks/studio-txwkm6p9.js +0 -12
- package/dist/chunks/studio-v38hmvx1.js +0 -390
- package/dist/chunks/studio-vadzgkrz.js +0 -318
- package/dist/chunks/studio-wx5609xd.js +0 -341
- package/dist/chunks/studio-z6nw2ek2.js +0 -18
- package/dist/chunks/studio-zczps70q.js +0 -378
- package/dist/chunks/workspace-xpres40v.js +0 -89
- package/src/panels/activity-bar.ts +0 -251
- package/src/panels/panel-scheduler.ts +0 -219
- package/src/panels/style-inputs.ts +0 -237
- package/src/panels/target-line.ts +0 -274
- package/src/panels/welcome-screen.ts +0 -346
- package/src/ui/button-group.ts +0 -102
- package/src/ui/choice-field.ts +0 -72
- package/src/ui/field-row.ts +0 -249
- package/src/ui/formula-palette.ts +0 -213
- package/src/ui/icons.ts +0 -52
- package/src/ui/jx-theme.ts +0 -130
- package/src/ui/spectrum.ts +0 -340
- package/src/ui/value-selector.ts +0 -174
- package/src/ui/widgets.ts +0 -172
- /package/dist/chunks/{javascript-s56a8dpk.css → javascript-4abzjcy9.css} +0 -0
- /package/dist/chunks/{jsonMode-zr9sw0c7.css → jsonMode-vvmpmk34.css} +0 -0
- /package/dist/chunks/{monaco-setup-pav68602.css → monaco-setup-8vbyqnhr.css} +0 -0
- /package/dist/chunks/{tsMode-ef187txx.css → tsMode-hnxxb73h.css} +0 -0
package/src/panels/tab-strip.ts
CHANGED
|
@@ -7,6 +7,20 @@
|
|
|
7
7
|
* addressed by REGION id — `pane.primary/tabs`, `pane.secondary/tabs` — rather than by element id,
|
|
8
8
|
* so the shell can move or rename the divs without touching this file.
|
|
9
9
|
*
|
|
10
|
+
* **This file is the FLOW; the markup is a document.** `surfaces/tab-strip.json` owns the strip's
|
|
11
|
+
* structure, its ARIA, its keyboard and every value in its style, and `surfaces/tab-strip.ts`
|
|
12
|
+
* mounts one per host (studio-ui-guidelines.md §6, §9.3). What stays here is the part that is a
|
|
13
|
+
* decision: which pane a host is drawing, what each tab is called, what its tooltip says, whether
|
|
14
|
+
* closing it would lose work, and what the two menus offer.
|
|
15
|
+
*
|
|
16
|
+
* **The chip is a `jx-tab` now, and three of the things it used to draw by hand are the kit's**:
|
|
17
|
+
* the selected state (`aria-selected` on a real `tab` inside a real `tablist`, where an `.active`
|
|
18
|
+
* class used to be), the close button, and the dirty dot. The three that are Studio's own live in
|
|
19
|
+
* `jx-tab`'s slots — the drill-in marker in `icon`, the draft pill in `status`, the pin toggle in
|
|
20
|
+
* `actions` — which is the gap that kept this strip out of the tab pattern (`ui.md` §5.4). With it
|
|
21
|
+
* closed the strip is one stop in the tab order, the arrows walk it, Home and End reach its ends,
|
|
22
|
+
* and Delete closes a tab: a keyboard contract the × has never had.
|
|
23
|
+
*
|
|
10
24
|
* Six things the strip owes the author:
|
|
11
25
|
*
|
|
12
26
|
* - **A label that identifies the document.** A realistic Jx session has four tabs whose basename is
|
|
@@ -27,11 +41,9 @@
|
|
|
27
41
|
* — reached none of them. See {@link placedTabItems}.
|
|
28
42
|
*/
|
|
29
43
|
|
|
30
|
-
import { html, render as litRender, nothing } from "lit-html";
|
|
31
|
-
import { classMap } from "lit-html/directives/class-map.js";
|
|
32
|
-
import { repeat } from "lit-html/directives/repeat.js";
|
|
33
44
|
import { effect, effectScope } from "../reactivity";
|
|
34
45
|
import {
|
|
46
|
+
PRIMARY_PANE,
|
|
35
47
|
activateTab,
|
|
36
48
|
closeTab,
|
|
37
49
|
focusPane,
|
|
@@ -52,9 +64,18 @@ import { PRESET_LABELS } from "../workspace/pane-derive";
|
|
|
52
64
|
import { localeLabel } from "@jxsuite/schema/locale";
|
|
53
65
|
import { tabOfPane } from "../canvas/canvas-surface";
|
|
54
66
|
import { mediaDisplayName } from "./shared";
|
|
67
|
+
import { mountTabStripSurface } from "../surfaces/tab-strip";
|
|
55
68
|
import type { Pane, PaneDerivation } from "../workspace/workspace";
|
|
56
69
|
import type { Tab } from "../tabs/tab";
|
|
57
|
-
import {
|
|
70
|
+
import type {
|
|
71
|
+
TabChipView,
|
|
72
|
+
TabStripActions,
|
|
73
|
+
TabStripSurface,
|
|
74
|
+
TabStripValues,
|
|
75
|
+
} from "../surfaces/tab-strip";
|
|
76
|
+
import { showConfirmDialog, showSaveDiscardDialog } from "../ui/layers";
|
|
77
|
+
import { openMenu } from "../surfaces/menu";
|
|
78
|
+
import type { MenuHandle, MenuRowProjection } from "../surfaces/menu";
|
|
58
79
|
import { saveFile } from "../files/file-ops";
|
|
59
80
|
import { collabReadOnly } from "../collab/collab-session";
|
|
60
81
|
import { collabState } from "../collab/collab-state";
|
|
@@ -62,7 +83,6 @@ import { rectOf } from "../utils/geometry";
|
|
|
62
83
|
import { resolveRegion } from "../ui/regions";
|
|
63
84
|
import { commitTabBuffers, tabBufferUnsaved } from "../services/monaco-buffer";
|
|
64
85
|
import type { EffectScope } from "@vue/reactivity";
|
|
65
|
-
import type { TemplateResult } from "lit-html";
|
|
66
86
|
|
|
67
87
|
/**
|
|
68
88
|
* The primary pane's host, as handed over by the shell's bootstrap.
|
|
@@ -80,10 +100,23 @@ const _lastActive = new Map<string, string | null>();
|
|
|
80
100
|
/** The host each pane last drew into, so a strip can be blanked when the pane stops owning it. */
|
|
81
101
|
const _hosts = new Map<string, HTMLElement>();
|
|
82
102
|
|
|
103
|
+
/**
|
|
104
|
+
* The mounted document in each host. One mount per HOST, not per pane.
|
|
105
|
+
*
|
|
106
|
+
* Two panes can resolve to the same host while the shell has a single strip, and the focused one
|
|
107
|
+
* wins the tie ({@link render}). Keying the mount by pane would then mount twice into one div;
|
|
108
|
+
* keying it by the host makes the hand-over an assignment instead — the same document keeps
|
|
109
|
+
* standing and starts saying the other pane's tabs.
|
|
110
|
+
*/
|
|
111
|
+
const _strips = new Map<HTMLElement, TabStripSurface>();
|
|
112
|
+
|
|
113
|
+
/** What each host is drawing right now: the pane, and which of the two shapes it is in. */
|
|
114
|
+
const _drawing = new Map<HTMLElement, { paneId: string; mode: TabStripValues["mode"] }>();
|
|
115
|
+
|
|
83
116
|
/** Whether each pane's strip overflows — decides its chevron. Measured after each render. */
|
|
84
117
|
const _overflowing = new Map<string, boolean>();
|
|
85
118
|
|
|
86
|
-
let _overflowHandle:
|
|
119
|
+
let _overflowHandle: MenuHandle | null = null;
|
|
87
120
|
|
|
88
121
|
/** The tab id currently being dragged, or null. */
|
|
89
122
|
let _dragging: string | null = null;
|
|
@@ -117,10 +150,8 @@ function hostFor(pane: Pane): HTMLElement | null {
|
|
|
117
150
|
* @param {HTMLElement} host
|
|
118
151
|
*/
|
|
119
152
|
export function mount(host: HTMLElement) {
|
|
153
|
+
unmount();
|
|
120
154
|
_primaryHost = host;
|
|
121
|
-
_lastActive.clear();
|
|
122
|
-
_overflowing.clear();
|
|
123
|
-
_hosts.clear();
|
|
124
155
|
_scope = effectScope();
|
|
125
156
|
_scope.run(() => {
|
|
126
157
|
effect(() => {
|
|
@@ -131,9 +162,9 @@ export function mount(host: HTMLElement) {
|
|
|
131
162
|
void pane.activeTabId;
|
|
132
163
|
/* NO DERIVATION READS HERE, and the three that were here are the clearest example in the
|
|
133
164
|
package of a tracked input that tracks nothing. `render()` is called from inside this
|
|
134
|
-
effect, synchronously, so every value its
|
|
135
|
-
`
|
|
136
|
-
the chip's label, and `
|
|
165
|
+
effect, synchronously, so every value its projections read IS a dependency —
|
|
166
|
+
`derivationValues` reads `derived.kind`, `derived.preset` and `derived.media` to build
|
|
167
|
+
the chip's label, and `project` reads `pane.derived` to choose the branch. Three
|
|
137
168
|
`void` lines restating them could each be inverted with no test in the suite able to
|
|
138
169
|
tell, because the behaviour they claimed to buy was already bought one function down.
|
|
139
170
|
The loop reads below are a different case and stay: `render()` draws ONE pane per host,
|
|
@@ -163,6 +194,11 @@ export function unmount() {
|
|
|
163
194
|
dismissTabContextMenu();
|
|
164
195
|
_scope?.stop();
|
|
165
196
|
_scope = null;
|
|
197
|
+
for (const strip of _strips.values()) {
|
|
198
|
+
strip.dispose();
|
|
199
|
+
}
|
|
200
|
+
_strips.clear();
|
|
201
|
+
_drawing.clear();
|
|
166
202
|
_primaryHost = null;
|
|
167
203
|
_dragging = null;
|
|
168
204
|
_lastActive.clear();
|
|
@@ -184,13 +220,13 @@ function render() {
|
|
|
184
220
|
}
|
|
185
221
|
const drawn = new Set<string>();
|
|
186
222
|
for (const [host, pane] of claims) {
|
|
187
|
-
|
|
223
|
+
paint(pane, host);
|
|
188
224
|
_hosts.set(pane.id, host);
|
|
189
225
|
drawn.add(pane.id);
|
|
190
226
|
}
|
|
191
227
|
// A pane that has gone away — or that has lost the shared host to the pane now focused — leaves
|
|
192
|
-
// Its last host behind.
|
|
193
|
-
//
|
|
228
|
+
// Its last host behind. Take the document down, unless someone else has just drawn there, so no
|
|
229
|
+
// Strip outlives the pane it belongs to.
|
|
194
230
|
// Deleting the entry the loop is standing on is defined behaviour for a Map iterator.
|
|
195
231
|
for (const [paneId, host] of _hosts) {
|
|
196
232
|
if (drawn.has(paneId)) {
|
|
@@ -200,11 +236,110 @@ function render() {
|
|
|
200
236
|
_lastActive.delete(paneId);
|
|
201
237
|
_overflowing.delete(paneId);
|
|
202
238
|
if (!claims.has(host)) {
|
|
203
|
-
|
|
239
|
+
_strips.get(host)?.dispose();
|
|
240
|
+
_strips.delete(host);
|
|
241
|
+
_drawing.delete(host);
|
|
204
242
|
}
|
|
205
243
|
}
|
|
206
244
|
}
|
|
207
245
|
|
|
246
|
+
/**
|
|
247
|
+
* Put one pane's projection into one host — mounting the document the first time, assigning every
|
|
248
|
+
* time after that.
|
|
249
|
+
*
|
|
250
|
+
* The assignment is the whole reason a strip survives a repaint: a keyed `$map` over `tabs` keeps
|
|
251
|
+
* the node of every chip whose id survived, so the tab the reader is aiming at does not move under
|
|
252
|
+
* them and the strip's scroll offset stays where they left it.
|
|
253
|
+
*/
|
|
254
|
+
function paint(pane: Pane, host: HTMLElement): void {
|
|
255
|
+
const values = project(pane);
|
|
256
|
+
_drawing.set(host, { mode: values.mode, paneId: pane.id });
|
|
257
|
+
const standing = _strips.get(host);
|
|
258
|
+
if (standing) {
|
|
259
|
+
standing.update(values);
|
|
260
|
+
} else {
|
|
261
|
+
_strips.set(host, mountTabStripSurface(host, values, actionsFor(host)));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/* The strip is HELD — `surfaces/tab-strip.ts` takes it as the element is created — so this is an
|
|
265
|
+
imperative USE of a node this module owns rather than a re-find of one it renders
|
|
266
|
+
(studio-ui-guidelines.md §9.4). The chip is asked for by the state the KIT puts on it, which is
|
|
267
|
+
the accessibility tree's own answer to "which tab is current".
|
|
268
|
+
|
|
269
|
+
THE REVEAL IS RECORDED ONLY WHEN THERE WAS A STRIP TO REVEAL IT IN. A mount is asynchronous,
|
|
270
|
+
so the first paint into a fresh host has no document yet — and moving `_lastActive` there
|
|
271
|
+
anyway would consume the transition and leave the chip off-screen with nothing left to notice.
|
|
272
|
+
That is exactly the case a derived pane produces: its strip is created the moment its one tab
|
|
273
|
+
comes back. */
|
|
274
|
+
const strip = _strips.get(host)?.strip();
|
|
275
|
+
if (strip && pane.activeTabId !== _lastActive.get(pane.id)) {
|
|
276
|
+
_lastActive.set(pane.id, pane.activeTabId);
|
|
277
|
+
/* Found by the id the DOCUMENT stamped, not by `aria-selected`. The kit is the single writer
|
|
278
|
+
of a tab's selected state and it writes it on a MutationObserver when the tab SET moved — a
|
|
279
|
+
microtask later — so a strip whose one tab has just come back has no tab marked current at
|
|
280
|
+
the instant this runs. The flow already knows which chip it wants; asking the tree instead
|
|
281
|
+
is asking a question we have the answer to, and getting it one tick early. */
|
|
282
|
+
([...strip.children] as HTMLElement[])
|
|
283
|
+
.find((chip) => chip.dataset.tab === pane.activeTabId)
|
|
284
|
+
?.scrollIntoView({ block: "nearest", inline: "nearest" });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
syncOverflow(pane, host);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** The pane a host is drawing, or null when it is drawing none. */
|
|
291
|
+
function drawnPane(host: HTMLElement): Pane | null {
|
|
292
|
+
const paneId = _drawing.get(host)?.paneId;
|
|
293
|
+
return workspace.panes.find((pane) => pane.id === paneId) ?? null;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* What a gesture on one host's strip asks of the flow.
|
|
298
|
+
*
|
|
299
|
+
* Read ONCE, when that host's document is mounted, so every one of these closes over the host
|
|
300
|
+
* rather than over a pane: the pane a host draws changes, and an action bound to the pane it was
|
|
301
|
+
* mounted for would act on the wrong strip the first time the side pane took the stage.
|
|
302
|
+
*/
|
|
303
|
+
function actionsFor(host: HTMLElement): TabStripActions {
|
|
304
|
+
return {
|
|
305
|
+
activate: (id) => {
|
|
306
|
+
activateTab(id);
|
|
307
|
+
},
|
|
308
|
+
closeTab: (id) => {
|
|
309
|
+
void requestClose(id);
|
|
310
|
+
},
|
|
311
|
+
context: (id, x, y) => {
|
|
312
|
+
const tab = workspace.tabs.get(id);
|
|
313
|
+
if (tab) {
|
|
314
|
+
openTabContextMenu(tab, x, y);
|
|
315
|
+
}
|
|
316
|
+
},
|
|
317
|
+
dragEnd: onDragEnd,
|
|
318
|
+
dragStart: onDragStart,
|
|
319
|
+
drop: (index, transfer) => {
|
|
320
|
+
onDrop(host, index, transfer);
|
|
321
|
+
},
|
|
322
|
+
focusPane: () => {
|
|
323
|
+
focusPane(drawnPane(host)?.id ?? PRIMARY_PANE);
|
|
324
|
+
},
|
|
325
|
+
openTrailing: (anchor) => {
|
|
326
|
+
openTrailing(host, anchor);
|
|
327
|
+
},
|
|
328
|
+
promote: (id) => {
|
|
329
|
+
promoteTab(id);
|
|
330
|
+
},
|
|
331
|
+
togglePin: (id) => {
|
|
332
|
+
const tab = workspace.tabs.get(id);
|
|
333
|
+
if (tab) {
|
|
334
|
+
setTabPinned(id, !tab.pinned);
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
wheel: (_state, event) => {
|
|
338
|
+
onWheel(event);
|
|
339
|
+
},
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
|
|
208
343
|
/**
|
|
209
344
|
* What a lens pane's strip says: the projection, and the document it is a projection OF.
|
|
210
345
|
*
|
|
@@ -212,7 +347,7 @@ function render() {
|
|
|
212
347
|
* runs `pane.unsplit` — the lens's only exit, because Pin is refused for a projection of a document
|
|
213
348
|
* that already has a tab beside it (§14.1).
|
|
214
349
|
*/
|
|
215
|
-
function
|
|
350
|
+
function derivationValues(pane: Pane, derived: PaneDerivation): TabStripValues {
|
|
216
351
|
const registry = activeRegistry();
|
|
217
352
|
/* For a LENS the first read already hops to the source pane. For an unresolved COMPANION it
|
|
218
353
|
answers null — the pane owns no tab yet — and the chip would say "no document" about a
|
|
@@ -227,31 +362,54 @@ function derivationChipTpl(pane: Pane, derived: PaneDerivation): TemplateResult
|
|
|
227
362
|
: derived.kind === "companion" && derived.preset === "locale"
|
|
228
363
|
? `${PRESET_LABELS.locale} ${derived.locale ? localeLabel(derived.locale) : "—"}`
|
|
229
364
|
: PRESET_LABELS[derived.preset];
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
365
|
+
// A chip whose source pane shows nothing SAYS SO: a sentence with its subject missing, in the
|
|
366
|
+
// One row that says what the pane is, is worse than the sentence it replaces.
|
|
367
|
+
const subject = of ? tabLabel(of) : "no document";
|
|
368
|
+
return {
|
|
369
|
+
active: "",
|
|
370
|
+
chipTitle: `${label} · ${of?.documentPath ?? "no document"}`,
|
|
371
|
+
focused: isPaneFocused(pane.id),
|
|
372
|
+
mode: "derivation",
|
|
373
|
+
preset: label,
|
|
374
|
+
stripLabel: stripLabel(pane),
|
|
375
|
+
subject,
|
|
376
|
+
tabs: [],
|
|
377
|
+
trailing: true,
|
|
378
|
+
trailingGlyph: "✕",
|
|
379
|
+
trailingTitle: registry?.get("pane.unsplit")?.title ?? "Close Side Pane",
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* The tablist's accessible name.
|
|
385
|
+
*
|
|
386
|
+
* A `tablist` takes no name from its tabs and no lint can say so, because a bound role switches the
|
|
387
|
+
* naming rules off (`ui.md` §5.4). With two panes on screen there are two tablists, so the name
|
|
388
|
+
* says which — "Open documents" alone would announce the same words over both.
|
|
389
|
+
*/
|
|
390
|
+
function stripLabel(pane: Pane): string {
|
|
391
|
+
return pane.id === PRIMARY_PANE ? "Open documents" : "Open documents in the side pane";
|
|
252
392
|
}
|
|
253
393
|
|
|
254
|
-
|
|
394
|
+
/** A strip with nothing to say: the row is hidden outright rather than left as an empty band. */
|
|
395
|
+
function blankValues(pane: Pane): TabStripValues {
|
|
396
|
+
return {
|
|
397
|
+
active: "",
|
|
398
|
+
chipTitle: "",
|
|
399
|
+
focused: isPaneFocused(pane.id),
|
|
400
|
+
mode: "empty",
|
|
401
|
+
preset: "",
|
|
402
|
+
stripLabel: stripLabel(pane),
|
|
403
|
+
subject: "",
|
|
404
|
+
tabs: [],
|
|
405
|
+
trailing: false,
|
|
406
|
+
trailingGlyph: "",
|
|
407
|
+
trailingTitle: "",
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Everything one host's document draws, decided here so the document decides nothing. */
|
|
412
|
+
function project(pane: Pane): TabStripValues {
|
|
255
413
|
/* A LENS pane has no tabs and must not have an empty strip: its `tabOrder` is `[]` BY DESIGN, so
|
|
256
414
|
the branch below would blank the one row that says what the pane is. It draws a derivation chip
|
|
257
415
|
instead — the preset, what it is a projection of, and the two exits. A COMPANION owns real tabs
|
|
@@ -266,7 +424,7 @@ function renderPane(pane: Pane, host: HTMLElement) {
|
|
|
266
424
|
if (derived && pane.tabOrder.length === 0) {
|
|
267
425
|
/* A COMPANION whose rule has not resolved reaches this too, and it has to. Its `tabOrder` is
|
|
268
426
|
empty for a different reason than a lens's — the document it wants is not open YET, or the
|
|
269
|
-
selection has nothing under it — and the branch below drew
|
|
427
|
+
selection has nothing under it — and the branch below drew nothing: a pane with no chip,
|
|
270
428
|
no ✕ and no way out, which `paneIsEmpty` will not collapse because the derivation counts as
|
|
271
429
|
a subject. The chip is the pane's name and its exit.
|
|
272
430
|
NO `_lastActive` / `_overflowing` RESET HERE, and both were, until each was shown to be a
|
|
@@ -277,142 +435,88 @@ function renderPane(pane: Pane, host: HTMLElement) {
|
|
|
277
435
|
two reactive writes and therefore two renders, and the first of them lands here with
|
|
278
436
|
`tabOrder` full and `activeTabId` still `null` — which resets the field anyway. Both were
|
|
279
437
|
written as bookkeeping and both were unfalsifiable; the branch below owns the answer. */
|
|
280
|
-
|
|
281
|
-
return;
|
|
438
|
+
return derivationValues(pane, derived);
|
|
282
439
|
}
|
|
283
440
|
if (pane.tabOrder.length === 0) {
|
|
284
441
|
_lastActive.set(pane.id, null);
|
|
285
442
|
_overflowing.set(pane.id, false);
|
|
286
|
-
|
|
287
|
-
return;
|
|
443
|
+
return blankValues(pane);
|
|
288
444
|
}
|
|
289
445
|
|
|
290
446
|
const labels = tabLabels(pane);
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
${
|
|
307
|
-
_overflowing.get(pane.id) === true
|
|
308
|
-
? // ONE accessible name (§10): the glyph is hidden, so `title` is both the tooltip and
|
|
309
|
-
// The name — `title` + a matching `aria-label` would announce it twice.
|
|
310
|
-
html`<button
|
|
311
|
-
class="tab-strip-overflow"
|
|
312
|
-
title="Show hidden tabs"
|
|
313
|
-
@click=${(e: MouseEvent) => openOverflowMenu(e, pane, labels)}
|
|
314
|
-
>
|
|
315
|
-
<span aria-hidden="true">⌄</span>
|
|
316
|
-
</button>`
|
|
317
|
-
: nothing
|
|
318
|
-
}
|
|
319
|
-
</div>
|
|
320
|
-
`,
|
|
321
|
-
host,
|
|
322
|
-
);
|
|
323
|
-
|
|
324
|
-
if (pane.activeTabId !== _lastActive.get(pane.id)) {
|
|
325
|
-
_lastActive.set(pane.id, pane.activeTabId);
|
|
326
|
-
host.querySelector(".tab-strip-tab.active")?.scrollIntoView({
|
|
327
|
-
block: "nearest",
|
|
328
|
-
inline: "nearest",
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
syncOverflow(pane, host);
|
|
447
|
+
return {
|
|
448
|
+
active: pane.activeTabId ?? "",
|
|
449
|
+
chipTitle: "",
|
|
450
|
+
focused: isPaneFocused(pane.id),
|
|
451
|
+
mode: "tabs",
|
|
452
|
+
preset: "",
|
|
453
|
+
stripLabel: stripLabel(pane),
|
|
454
|
+
subject: "",
|
|
455
|
+
tabs: pane.tabOrder.flatMap((id, index) => chipView(pane, id, index, labels)),
|
|
456
|
+
trailing: _overflowing.get(pane.id) === true,
|
|
457
|
+
trailingGlyph: "⌄",
|
|
458
|
+
// ONE accessible name (§10): the glyph is hidden, so `title` is both the tooltip and the name
|
|
459
|
+
// — `title` + a matching `aria-label` would announce it twice.
|
|
460
|
+
trailingTitle: "Show hidden tabs",
|
|
461
|
+
};
|
|
333
462
|
}
|
|
334
463
|
|
|
335
464
|
/**
|
|
336
|
-
* One chip.
|
|
465
|
+
* One chip, as a value.
|
|
466
|
+
*
|
|
467
|
+
* Every field is already decided: the document asks no question about a tab, and there is no branch
|
|
468
|
+
* in it a projection has not already taken. A tab that has gone between the pane's order and this
|
|
469
|
+
* call contributes no chip at all, which is what the empty array is for.
|
|
337
470
|
*
|
|
338
471
|
* @param {Pane} pane
|
|
339
472
|
* @param {string} id
|
|
340
473
|
* @param {number} index — the chip's slot in the pane's order, which is the drop target index
|
|
341
474
|
* @param {Map<string, string>} labels
|
|
342
475
|
*/
|
|
343
|
-
function
|
|
476
|
+
function chipView(
|
|
477
|
+
pane: Pane,
|
|
478
|
+
id: string,
|
|
479
|
+
index: number,
|
|
480
|
+
labels: Map<string, string>,
|
|
481
|
+
): TabChipView[] {
|
|
344
482
|
const tab = workspace.tabs.get(id);
|
|
345
483
|
if (!tab) {
|
|
346
|
-
return
|
|
484
|
+
return [];
|
|
347
485
|
}
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
@contextmenu=${(e: MouseEvent) => openTabContextMenu(e, tab)}
|
|
374
|
-
title=${tabTooltip(tab)}
|
|
375
|
-
>
|
|
376
|
-
${origin ? html`<span class="tab-strip-origin" aria-hidden="true">↳</span>` : nothing}
|
|
377
|
-
<span class="tab-strip-label">${label}</span>
|
|
378
|
-
${
|
|
379
|
-
/* The draft pill (§7.6). On the CHIP, not only inside the entry editor: the mistake this
|
|
380
|
-
prevents — publishing something you believed was private — is made while glancing at a
|
|
381
|
-
row of tabs, and it is made about a document that may not even be the active one. */
|
|
382
|
-
entryDraftPill(tab)
|
|
383
|
-
}
|
|
384
|
-
${tab.doc.dirty ? html`<span class="tab-strip-dirty">●</span>` : nothing}
|
|
385
|
-
<button
|
|
386
|
-
class="tab-strip-pin"
|
|
387
|
-
title=${tab.pinned ? "Unpin" : "Pin"}
|
|
388
|
-
@click=${(e: Event) => {
|
|
389
|
-
e.stopPropagation();
|
|
390
|
-
setTabPinned(id, !tab.pinned);
|
|
391
|
-
}}
|
|
392
|
-
>
|
|
393
|
-
<span aria-hidden="true">${tab.pinned ? "◉" : "◎"}</span>
|
|
394
|
-
</button>
|
|
395
|
-
<button
|
|
396
|
-
class="tab-strip-close"
|
|
397
|
-
title="Close"
|
|
398
|
-
@click=${(e: Event) => {
|
|
399
|
-
e.stopPropagation();
|
|
400
|
-
void requestClose(id);
|
|
401
|
-
}}
|
|
402
|
-
>
|
|
403
|
-
×
|
|
404
|
-
</button>
|
|
405
|
-
</div>
|
|
406
|
-
`;
|
|
486
|
+
/* The draft pill (§7.6). On the CHIP, not only inside the entry editor: the mistake this
|
|
487
|
+
prevents — publishing something you believed was private — is made while glancing at a row of
|
|
488
|
+
tabs, and it is made about a document that may not even be the active one. */
|
|
489
|
+
const pill = entryDraftPill(tab);
|
|
490
|
+
return [
|
|
491
|
+
{
|
|
492
|
+
dirty: tab.doc.dirty,
|
|
493
|
+
draft: pill?.draft ?? false,
|
|
494
|
+
dragging: _dragging === id,
|
|
495
|
+
index,
|
|
496
|
+
key: id,
|
|
497
|
+
label: labels.get(id) ?? "Untitled",
|
|
498
|
+
origin: Boolean(tab.session.openedFrom),
|
|
499
|
+
pill: pill?.text ?? "",
|
|
500
|
+
pillTitle: pill?.title ?? "",
|
|
501
|
+
pinGlyph: tab.pinned ? "◉" : "◎",
|
|
502
|
+
pinTitle: tab.pinned ? "Unpin" : "Pin",
|
|
503
|
+
pinned: tab.pinned,
|
|
504
|
+
preview: tab.preview,
|
|
505
|
+
// A control inside a tab is in the tab order only while its tab is the current one, which is
|
|
506
|
+
// What keeps a strip of fifteen documents ONE stop rather than fifteen (`ui.md` §5.4).
|
|
507
|
+
tabindex: id === pane.activeTabId ? "0" : "-1",
|
|
508
|
+
title: tabTooltip(tab),
|
|
509
|
+
},
|
|
510
|
+
];
|
|
407
511
|
}
|
|
408
512
|
|
|
409
513
|
// ─── Drag reorder ─────────────────────────────────────────────────────────────
|
|
410
514
|
|
|
411
|
-
function onDragStart(
|
|
515
|
+
function onDragStart(id: string, transfer: DataTransfer | null) {
|
|
412
516
|
_dragging = id;
|
|
413
|
-
|
|
414
|
-
if (
|
|
415
|
-
|
|
517
|
+
transfer?.setData("text/plain", id);
|
|
518
|
+
if (transfer) {
|
|
519
|
+
transfer.effectAllowed = "move";
|
|
416
520
|
}
|
|
417
521
|
// The ghost is chrome state, not model state, so no effect fires for it.
|
|
418
522
|
render();
|
|
@@ -427,35 +531,57 @@ function onDragEnd() {
|
|
|
427
531
|
* Drop onto the chip at `index`. The model clamps the destination into the region the dragged tab's
|
|
428
532
|
* pinned state allows, so this only has to say where the pointer was.
|
|
429
533
|
*/
|
|
430
|
-
function onDrop(
|
|
431
|
-
|
|
432
|
-
const id = _dragging ??
|
|
534
|
+
function onDrop(host: HTMLElement, index: number, transfer: DataTransfer | null) {
|
|
535
|
+
const pane = drawnPane(host);
|
|
536
|
+
const id = _dragging ?? transfer?.getData("text/plain") ?? null;
|
|
433
537
|
_dragging = null;
|
|
434
|
-
if (!id || !pane.tabOrder.includes(id)) {
|
|
538
|
+
if (!pane || !id || !pane.tabOrder.includes(id)) {
|
|
435
539
|
return;
|
|
436
540
|
}
|
|
437
541
|
moveTab(id, index);
|
|
438
542
|
}
|
|
439
543
|
|
|
440
544
|
/**
|
|
441
|
-
* Re-measure a pane's strip and re-
|
|
545
|
+
* Re-measure a pane's strip and re-project once if the chevron's presence changed.
|
|
442
546
|
*
|
|
443
|
-
* Measurement can only happen after
|
|
444
|
-
*
|
|
445
|
-
* oscillate.
|
|
547
|
+
* Measurement can only happen after the document has written the DOM, so this runs at the tail of
|
|
548
|
+
* {@link paint} and guards its own re-entry on the boolean actually flipping — the chevron cannot
|
|
549
|
+
* oscillate. Before the mount resolves there is no strip to measure and nothing to correct: the
|
|
550
|
+
* next repaint measures it, and a strip that overflows on its first frame has no hidden tab a
|
|
551
|
+
* reader could be looking for yet.
|
|
446
552
|
*/
|
|
447
553
|
function syncOverflow(pane: Pane, host: HTMLElement) {
|
|
448
|
-
const
|
|
449
|
-
|
|
554
|
+
const surface = _strips.get(host);
|
|
555
|
+
const strip = surface?.strip();
|
|
556
|
+
if (!surface || !strip) {
|
|
450
557
|
return;
|
|
451
558
|
}
|
|
452
559
|
const overflowing = strip.scrollWidth > strip.clientWidth;
|
|
453
560
|
if (overflowing !== (_overflowing.get(pane.id) === true)) {
|
|
454
561
|
_overflowing.set(pane.id, overflowing);
|
|
455
|
-
|
|
562
|
+
surface.update(project(pane));
|
|
456
563
|
}
|
|
457
564
|
}
|
|
458
565
|
|
|
566
|
+
/**
|
|
567
|
+
* The trailing button: the overflow chevron, or a derived pane's ✕.
|
|
568
|
+
*
|
|
569
|
+
* One control in one place, so which of the two it is comes from what the host is DRAWING rather
|
|
570
|
+
* than from a second condition re-derived here.
|
|
571
|
+
*/
|
|
572
|
+
function openTrailing(host: HTMLElement, anchor: HTMLElement | null) {
|
|
573
|
+
const pane = drawnPane(host);
|
|
574
|
+
if (!pane) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
if (_drawing.get(host)?.mode === "derivation") {
|
|
578
|
+
const registry = activeRegistry();
|
|
579
|
+
void registry?.run("pane.unsplit");
|
|
580
|
+
return;
|
|
581
|
+
}
|
|
582
|
+
openOverflowMenu(pane, tabLabels(pane), anchor);
|
|
583
|
+
}
|
|
584
|
+
|
|
459
585
|
/**
|
|
460
586
|
* Scroll the strip horizontally from wheel motion so overflowed tabs stay reachable — a plain mouse
|
|
461
587
|
* wheel only emits deltaY, which does nothing in an overflow-x container. The scrollbar is hidden
|
|
@@ -491,14 +617,14 @@ export function hiddenTabIds(paneId: string = workspace.activePaneId): string[]
|
|
|
491
617
|
// The host the pane actually DREW into, not the one it would resolve: a pane with no strip on
|
|
492
618
|
// Screen has no chips out of view, and measuring another pane's strip would invent some.
|
|
493
619
|
const host = _hosts.get(paneId);
|
|
494
|
-
const strip = host
|
|
620
|
+
const strip = host ? (_strips.get(host)?.strip() ?? null) : null;
|
|
495
621
|
if (!pane || !strip) {
|
|
496
622
|
return [];
|
|
497
623
|
}
|
|
498
624
|
const left = strip.scrollLeft;
|
|
499
625
|
const right = left + strip.clientWidth;
|
|
500
626
|
const hidden: string[] = [];
|
|
501
|
-
const chips = [...strip.querySelectorAll("
|
|
627
|
+
const chips = [...strip.querySelectorAll('[part="tab"]')] as HTMLElement[];
|
|
502
628
|
for (const [index, chip] of chips.entries()) {
|
|
503
629
|
const id = pane.tabOrder[index];
|
|
504
630
|
if (id && (chip.offsetLeft < left || chip.offsetLeft + chip.offsetWidth > right)) {
|
|
@@ -509,78 +635,76 @@ export function hiddenTabIds(paneId: string = workspace.activePaneId): string[]
|
|
|
509
635
|
}
|
|
510
636
|
|
|
511
637
|
export function dismissOverflowMenu() {
|
|
512
|
-
_overflowHandle?.
|
|
638
|
+
_overflowHandle?.close();
|
|
513
639
|
_overflowHandle = null;
|
|
514
640
|
}
|
|
515
641
|
|
|
516
642
|
/**
|
|
517
643
|
* List the off-screen tabs. Falls back to every tab when nothing measures as hidden, because an
|
|
518
|
-
* empty menu is a dead control and happy-dom (plus any zero-height layout) measures everything at
|
|
519
|
-
*
|
|
644
|
+
* empty menu is a dead control and happy-dom (plus any zero-height layout) measures everything at 0.
|
|
645
|
+
*
|
|
646
|
+
* **The kit menu, not a popover of this strip's own** (guidelines §8.4). `surfaces/menu.ts` owns
|
|
647
|
+
* the panel, the roving caret, the typeahead, Escape and the light dismissal, and it clamps the
|
|
648
|
+
* panel into the viewport once it has been laid out — so the `right:`/`top:` arithmetic that used
|
|
649
|
+
* to sit in a style attribute here is one `place` callback, measured against the panel's real box
|
|
650
|
+
* instead of guessed from the chevron's.
|
|
651
|
+
*
|
|
652
|
+
* A row states whether it is the tab the pane is already showing. The strip's own chips carry that
|
|
653
|
+
* as `aria-selected`, and a menu row cannot: `checked` on every row is the shape this shell already
|
|
654
|
+
* settled on for one-of-N (`panels/pane-context.ts`'s scheme, media and locale menus), and it is
|
|
655
|
+
* what makes the current tab announce itself rather than merely look different.
|
|
520
656
|
*
|
|
521
|
-
* @param {MouseEvent} e
|
|
522
657
|
* @param {Pane} pane
|
|
523
658
|
* @param {Map<string, string>} labels
|
|
659
|
+
* @param {HTMLElement | null} anchorEl — the chevron that was pressed, as the document hands it
|
|
660
|
+
* over
|
|
524
661
|
*/
|
|
525
|
-
function openOverflowMenu(
|
|
526
|
-
e.stopPropagation();
|
|
662
|
+
function openOverflowMenu(pane: Pane, labels: Map<string, string>, anchorEl: HTMLElement | null) {
|
|
527
663
|
dismissOverflowMenu();
|
|
528
664
|
const hidden = hiddenTabIds(pane.id);
|
|
529
665
|
const ids = hidden.length > 0 ? hidden : [...pane.tabOrder];
|
|
530
|
-
const anchor = rectOf(
|
|
531
|
-
_overflowHandle =
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
)
|
|
538
|
-
>
|
|
539
|
-
<sp-menu>
|
|
540
|
-
${ids.map(
|
|
541
|
-
(id) => html`<sp-menu-item
|
|
542
|
-
?selected=${id === pane.activeTabId}
|
|
543
|
-
@click=${() => {
|
|
544
|
-
dismissOverflowMenu();
|
|
545
|
-
activateTab(id);
|
|
546
|
-
}}
|
|
547
|
-
>${labels.get(id) ?? "Untitled"}</sp-menu-item
|
|
548
|
-
>`,
|
|
549
|
-
)}
|
|
550
|
-
</sp-menu>
|
|
551
|
-
</sp-popover>`,
|
|
552
|
-
{
|
|
553
|
-
dismissOnOutsideClick: true,
|
|
554
|
-
onDismiss: () => {
|
|
666
|
+
const anchor = rectOf(anchorEl ?? document.body);
|
|
667
|
+
_overflowHandle = openMenu({
|
|
668
|
+
/* The trigger says "Show hidden tabs" and this is the panel it opens, so it takes the same
|
|
669
|
+
words: a control and the panel it opens announcing two different things is a menu the
|
|
670
|
+
reader has to re-identify after opening it. */
|
|
671
|
+
label: "Hidden tabs",
|
|
672
|
+
onClosed: (handle) => {
|
|
673
|
+
if (_overflowHandle === handle) {
|
|
555
674
|
_overflowHandle = null;
|
|
556
|
-
}
|
|
675
|
+
}
|
|
557
676
|
},
|
|
558
|
-
|
|
677
|
+
opener: anchorEl,
|
|
678
|
+
/* Right-aligned under the chevron, which is a function of the panel's own WIDTH — so it is a
|
|
679
|
+
`place` callback rather than an `origin`, run once the popover has been laid out. That is
|
|
680
|
+
the fallback; where the platform positions by anchor, `placement` says the same thing. */
|
|
681
|
+
place: (box) => ({ x: Math.max(4, anchor.right - box.width), y: anchor.bottom }),
|
|
682
|
+
placement: "block-end span-inline-start",
|
|
683
|
+
region: "tab-overflow",
|
|
684
|
+
rows: ids.map((id) => ({
|
|
685
|
+
checked: (id === pane.activeTabId ? "true" : "false") as "true" | "false",
|
|
686
|
+
destructive: false,
|
|
687
|
+
disabled: false,
|
|
688
|
+
dividerAbove: false,
|
|
689
|
+
id,
|
|
690
|
+
run: () => {
|
|
691
|
+
activateTab(id);
|
|
692
|
+
},
|
|
693
|
+
title: labels.get(id) ?? "Untitled",
|
|
694
|
+
})),
|
|
695
|
+
});
|
|
559
696
|
}
|
|
560
697
|
|
|
561
698
|
// ─── Context menu ─────────────────────────────────────────────────────────────
|
|
562
699
|
|
|
563
|
-
let _tabCtxHandle:
|
|
700
|
+
let _tabCtxHandle: MenuHandle | null = null;
|
|
564
701
|
|
|
565
702
|
/** Dismiss the tab context menu if open. */
|
|
566
703
|
export function dismissTabContextMenu() {
|
|
567
|
-
_tabCtxHandle?.
|
|
704
|
+
_tabCtxHandle?.close();
|
|
568
705
|
_tabCtxHandle = null;
|
|
569
706
|
}
|
|
570
707
|
|
|
571
|
-
/** One row of the tab menu. Every field on it was read off a command record. */
|
|
572
|
-
interface TabMenuItem {
|
|
573
|
-
label: string;
|
|
574
|
-
action?: () => void;
|
|
575
|
-
disabled?: boolean;
|
|
576
|
-
/** The `requires` sentence, printed under a disabled row. */
|
|
577
|
-
reason?: string;
|
|
578
|
-
/** What is true NOW, for a row whose command names a state — see {@link statedState}. */
|
|
579
|
-
state?: string | undefined;
|
|
580
|
-
/** A group boundary in `forPlacement`'s ordering. */
|
|
581
|
-
dividerAbove?: boolean;
|
|
582
|
-
}
|
|
583
|
-
|
|
584
708
|
/**
|
|
585
709
|
* A tab addresses ONE document, and this is everything the chip can say about it — keyed by the
|
|
586
710
|
* ARGUMENT NAME that asks for it.
|
|
@@ -597,7 +721,7 @@ interface TabMenuItem {
|
|
|
597
721
|
*
|
|
598
722
|
* The VALUE is the state the row would reach, not the state the tab is in: a setter is named for
|
|
599
723
|
* where it lands (`content/draft-state.ts` says why), so the row offers the flip of what is true
|
|
600
|
-
* now and {@link
|
|
724
|
+
* now and {@link statedChecked} reads the current state back out of it for the checkmark.
|
|
601
725
|
*/
|
|
602
726
|
function tabRowFacts(tab: Tab): Record<string, unknown> {
|
|
603
727
|
const facts: Record<string, unknown> = {};
|
|
@@ -608,8 +732,8 @@ function tabRowFacts(tab: Tab): Record<string, unknown> {
|
|
|
608
732
|
}
|
|
609
733
|
|
|
610
734
|
/**
|
|
611
|
-
* The state a row is IN, derived from the state it would reach —
|
|
612
|
-
* says nothing about state.
|
|
735
|
+
* The state a row is IN, derived from the state it would reach — `"true"`, `"false"`, or
|
|
736
|
+
* `undefined` when the record says nothing about state.
|
|
613
737
|
*
|
|
614
738
|
* A menu is the one surface that is READ before it is used, so it is the one surface that can show
|
|
615
739
|
* a boolean instead of asking the author to remember it. That is an argument for the SETTER over
|
|
@@ -620,18 +744,23 @@ function tabRowFacts(tab: Tab): Record<string, unknown> {
|
|
|
620
744
|
* as a plain row here. Its idempotent sibling `document.setPinned {pinned}` would state its own
|
|
621
745
|
* value, with no edit to this file, on the day its record declares `context/tab`; that declaration
|
|
622
746
|
* lives in `workspace/workspace.ts`, which is the only place it can be made.
|
|
747
|
+
*
|
|
748
|
+
* **It is a real checkbox row now, which it could not be under Spectrum.** `sp-menu` reassigned
|
|
749
|
+
* every item's role one frame after connect whenever the menu declared no `selects`, so
|
|
750
|
+
* `role="menuitemcheckbox"` did not survive and the state had to be printed as a sentence ("Draft:
|
|
751
|
+
* no") in the description line instead. `jx-menu-item` derives `role` and `aria-checked` from
|
|
752
|
+
* `checked` and nothing rewrites them, so the row states the boolean the way a screen reader
|
|
753
|
+
* already knows how to announce — and the five rows that carry no state stay plain `menuitem`s,
|
|
754
|
+
* which `selects` on the old menu would have made impossible.
|
|
623
755
|
*/
|
|
624
|
-
function
|
|
756
|
+
function statedChecked(args: Record<string, unknown>): "true" | "false" | undefined {
|
|
625
757
|
const entries = Object.entries(args);
|
|
626
758
|
const only = entries.length === 1 ? entries[0] : undefined;
|
|
627
759
|
if (!only || typeof only[1] !== "boolean") {
|
|
628
760
|
return undefined;
|
|
629
761
|
}
|
|
630
|
-
// The fact is the state the row would REACH, so the state it is in now is the negation.
|
|
631
|
-
|
|
632
|
-
// Where a sentence built around an arbitrary property name does not.
|
|
633
|
-
const key = only[0].charAt(0).toUpperCase() + only[0].slice(1);
|
|
634
|
-
return `${key}: ${only[1] ? "no" : "yes"}`;
|
|
762
|
+
// The fact is the state the row would REACH, so the state it is in now is the negation.
|
|
763
|
+
return only[1] ? "false" : "true";
|
|
635
764
|
}
|
|
636
765
|
|
|
637
766
|
/**
|
|
@@ -643,13 +772,13 @@ function statedState(args: Record<string, unknown>): string | undefined {
|
|
|
643
772
|
* edit to this file — and with no registry published there are no rows at all, because every row
|
|
644
773
|
* there has ever been came from one.
|
|
645
774
|
*/
|
|
646
|
-
function placedTabItems(tab: Tab):
|
|
775
|
+
function placedTabItems(tab: Tab): MenuRowProjection[] {
|
|
647
776
|
const registry = activeRegistry();
|
|
648
777
|
if (!registry) {
|
|
649
778
|
return [];
|
|
650
779
|
}
|
|
651
780
|
const facts = tabRowFacts(tab);
|
|
652
|
-
const items:
|
|
781
|
+
const items: MenuRowProjection[] = [];
|
|
653
782
|
let group: string | undefined;
|
|
654
783
|
for (const command of registry.forPlacement("context/tab")) {
|
|
655
784
|
const schema = command.args as
|
|
@@ -667,17 +796,21 @@ function placedTabItems(tab: Tab): TabMenuItem[] {
|
|
|
667
796
|
const dividerAbove = items.length > 0 && command.group !== group;
|
|
668
797
|
({ group } = command);
|
|
669
798
|
const reason = registry.disabledReason(command.id);
|
|
799
|
+
const checked = statedChecked(args);
|
|
670
800
|
items.push({
|
|
671
|
-
|
|
801
|
+
destructive: command.destructive === true,
|
|
672
802
|
dividerAbove,
|
|
673
|
-
|
|
803
|
+
id: command.id,
|
|
804
|
+
title: command.title,
|
|
805
|
+
...(checked === undefined ? {} : { checked }),
|
|
674
806
|
...(reason === undefined
|
|
675
807
|
? {
|
|
676
|
-
|
|
808
|
+
disabled: false,
|
|
809
|
+
run: () => {
|
|
677
810
|
void registry.run(command.id, args);
|
|
678
811
|
},
|
|
679
812
|
}
|
|
680
|
-
: { disabled: true, reason }),
|
|
813
|
+
: { disabled: true, requires: reason }),
|
|
681
814
|
});
|
|
682
815
|
}
|
|
683
816
|
return items;
|
|
@@ -695,74 +828,39 @@ function placedTabItems(tab: Tab): TabMenuItem[] {
|
|
|
695
828
|
*
|
|
696
829
|
* With nothing declared, no menu opens: an empty popover is a dead control, the same judgement the
|
|
697
830
|
* overflow chevron makes one section up.
|
|
831
|
+
*
|
|
832
|
+
* The `preventDefault` and the `stopPropagation` a right-click needs are the DOCUMENT's, written
|
|
833
|
+
* beside the gesture that needs them (`surfaces/tab-strip.json`), so what arrives here is the tab
|
|
834
|
+
* and the point — which is everything a menu is a function of.
|
|
835
|
+
*
|
|
836
|
+
* **The rows go to the kit menu** (`surfaces/menu.ts`, guidelines §12.5), which is why there is no
|
|
837
|
+
* row template in this file any more. Three things that were written here went with it: the clamp
|
|
838
|
+
* that kept a right-click near the screen edge readable — `onMenuToggle` clamps every panel once it
|
|
839
|
+
* has been laid out, against the panel's real box rather than a guessed one — the `Needs …` line a
|
|
840
|
+
* disabled row prints, and the refusal to dismiss when a disabled row is clicked, which is
|
|
841
|
+
* `jx-menu-item`'s own `onClick` guard.
|
|
698
842
|
*/
|
|
699
|
-
function openTabContextMenu(
|
|
700
|
-
e.preventDefault();
|
|
701
|
-
e.stopPropagation();
|
|
843
|
+
function openTabContextMenu(tab: Tab, clientX: number, clientY: number) {
|
|
702
844
|
dismissTabContextMenu();
|
|
703
845
|
dismissOverflowMenu();
|
|
704
846
|
activateTab(tab.id);
|
|
705
847
|
|
|
706
|
-
const
|
|
707
|
-
if (
|
|
848
|
+
const rows = placedTabItems(tab);
|
|
849
|
+
if (rows.length === 0) {
|
|
708
850
|
return;
|
|
709
851
|
}
|
|
710
852
|
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
_tabCtxHandle = renderPopover(
|
|
717
|
-
html`<sp-popover open style="position:fixed;z-index:10000;left:${x}px;top:${y}px">
|
|
718
|
-
<sp-menu>${items.map((item) => tabMenuItemTemplate(item))}</sp-menu>
|
|
719
|
-
</sp-popover>`,
|
|
720
|
-
{
|
|
721
|
-
dismissOnOutsideClick: true,
|
|
722
|
-
onDismiss: () => {
|
|
853
|
+
_tabCtxHandle = openMenu({
|
|
854
|
+
label: "Tab actions",
|
|
855
|
+
onClosed: (handle) => {
|
|
856
|
+
if (_tabCtxHandle === handle) {
|
|
723
857
|
_tabCtxHandle = null;
|
|
724
|
-
}
|
|
858
|
+
}
|
|
725
859
|
},
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
* The line under a row: why it is disabled, or what is true now.
|
|
731
|
-
*
|
|
732
|
-
* A disabled row prints the record's own `requires` sentence — the same words the palette and the
|
|
733
|
-
* assistant print, never re-worded here. An enabled row whose command names a state prints that
|
|
734
|
-
* state, because a setter is named for where it LANDS and its title alone cannot say where you are
|
|
735
|
-
* now.
|
|
736
|
-
*
|
|
737
|
-
* A description rather than a checkbox, for a mechanical reason: Spectrum's `Menu` reassigns every
|
|
738
|
-
* item's role one frame after connect whenever the menu declares no `selects`, so
|
|
739
|
-
* `role="menuitemcheckbox"` does not survive — verified in a real browser, because happy-dom never
|
|
740
|
-
* runs that reassignment and no test here can see it either way. Declaring `selects` would make all
|
|
741
|
-
* six rows checkboxes, including the five that carry no state.
|
|
742
|
-
*
|
|
743
|
-
* @param {TabMenuItem} item
|
|
744
|
-
*/
|
|
745
|
-
function descriptionTemplate(item: TabMenuItem) {
|
|
746
|
-
const line = item.reason === undefined ? item.state : `Needs ${item.reason}`;
|
|
747
|
-
return line === undefined ? nothing : html`<span slot="description">${line}</span>`;
|
|
748
|
-
}
|
|
749
|
-
|
|
750
|
-
/** One rendered row. A disabled row stays on screen when clicked — it is there to be read. */
|
|
751
|
-
function tabMenuItemTemplate(item: TabMenuItem) {
|
|
752
|
-
return html`${
|
|
753
|
-
item.dividerAbove ? html`<sp-menu-divider role="separator"></sp-menu-divider>` : nothing
|
|
754
|
-
}<sp-menu-item
|
|
755
|
-
?disabled=${item.disabled === true}
|
|
756
|
-
aria-disabled=${item.disabled === true ? "true" : "false"}
|
|
757
|
-
@click=${() => {
|
|
758
|
-
if (item.disabled === true) {
|
|
759
|
-
return;
|
|
760
|
-
}
|
|
761
|
-
dismissTabContextMenu();
|
|
762
|
-
void item.action?.();
|
|
763
|
-
}}
|
|
764
|
-
>${item.label}${descriptionTemplate(item)}</sp-menu-item
|
|
765
|
-
>`;
|
|
860
|
+
origin: { x: clientX, y: clientY },
|
|
861
|
+
region: "tab",
|
|
862
|
+
rows,
|
|
863
|
+
});
|
|
766
864
|
}
|
|
767
865
|
|
|
768
866
|
// ─── Labels ───────────────────────────────────────────────────────────────────
|