@jxsuite/studio 2.0.1 → 2.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/canvas.html +21 -0
- package/dist/chunks/Overlay.dev-qfjzkms8.js +23 -0
- package/dist/chunks/{Overlay.dev-cszx7yn5.js.map → Overlay.dev-qfjzkms8.js.map} +1 -1
- package/dist/chunks/{adapter-z8fkmbmv.js → adapter-qfnesn5v.js} +2 -2
- package/dist/chunks/{cf-settings-mmyv436d.js → cf-settings-73sh5z4p.js} +3 -3
- package/dist/chunks/{components-799jpvhm.js → components-2bpqp4mq.js} +5 -7
- package/dist/chunks/{components-799jpvhm.js.map → components-2bpqp4mq.js.map} +1 -1
- package/dist/chunks/content-source-34gnkbnn.js +38 -0
- package/dist/chunks/content-source-34gnkbnn.js.map +9 -0
- package/dist/chunks/convert-to-repeater-t8kxre5s.js +258 -0
- package/dist/chunks/convert-to-repeater-t8kxre5s.js.map +10 -0
- package/dist/chunks/extension-sections-6mqydz35.js +301 -0
- package/dist/chunks/extension-sections-6mqydz35.js.map +11 -0
- package/dist/chunks/files-8kn321h1.js +70 -0
- package/dist/chunks/files-8kn321h1.js.map +9 -0
- package/dist/chunks/{format-host-8jxwts2p.js → format-host-mkwsmt8d.js} +3 -3
- package/dist/chunks/git-panel-tjbr191d.js +51 -0
- package/dist/chunks/git-panel-tjbr191d.js.map +9 -0
- package/dist/chunks/{index-baeg2x3y.js → index-7pxchcce.js} +4 -3
- package/dist/chunks/{index-baeg2x3y.js.map → index-7pxchcce.js.map} +3 -3
- package/dist/chunks/{jx-validate-qx8degj7.js → jx-validate-v6qgknrs.js} +3 -3
- package/dist/chunks/layers-panel-87pb25km.js +58 -0
- package/dist/chunks/layers-panel-87pb25km.js.map +9 -0
- package/dist/chunks/{monaco-setup-zjdp9zdw.js → monaco-setup-e0yh25j4.js} +4 -75
- package/dist/chunks/{monaco-setup-zjdp9zdw.js.map → monaco-setup-e0yh25j4.js.map} +3 -4
- package/dist/chunks/publish-panel-m0fcgdrt.js +21 -0
- package/dist/chunks/publish-panel-m0fcgdrt.js.map +9 -0
- package/dist/chunks/quick-search-rhs6cbsb.js +53 -0
- package/dist/chunks/quick-search-rhs6cbsb.js.map +9 -0
- package/dist/chunks/right-panel-zpdxd98t.js +37 -0
- package/dist/chunks/right-panel-zpdxd98t.js.map +9 -0
- package/dist/chunks/section-registry-zmckaxf4.js +30 -0
- package/dist/chunks/section-registry-zmckaxf4.js.map +9 -0
- package/dist/chunks/sp-overlay.dev-9ran0sfq.js +18 -0
- package/dist/chunks/sp-overlay.dev-9ran0sfq.js.map +9 -0
- package/dist/chunks/sp-popover.dev-rebmdncr.js +9 -0
- package/dist/chunks/sp-popover.dev-rebmdncr.js.map +9 -0
- package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js +15 -0
- package/dist/chunks/sp-progress-circle.dev-5vkppfkt.js.map +9 -0
- package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js +15 -0
- package/dist/chunks/sp-tooltip.dev-ykqrg8h5.js.map +9 -0
- package/dist/chunks/{sp-tray.dev-pb2fsd65.js → sp-tray.dev-ey59vfh7.js} +10 -8
- package/dist/chunks/{sp-tray.dev-pb2fsd65.js.map → sp-tray.dev-ey59vfh7.js.map} +2 -2
- package/dist/chunks/{studio-9qr6fq7p.js → studio-0rqqhrdy.js} +10 -378
- package/dist/chunks/{studio-9qr6fq7p.js.map → studio-0rqqhrdy.js.map} +4 -14
- package/dist/chunks/{studio-5x6tqqec.js → studio-2sj1jj3v.js} +10 -8
- package/dist/chunks/{studio-5x6tqqec.js.map → studio-2sj1jj3v.js.map} +2 -2
- package/dist/chunks/{studio-h3fw0wn8.js → studio-3fk1ac9z.js} +6 -4
- package/dist/chunks/{studio-h3fw0wn8.js.map → studio-3fk1ac9z.js.map} +2 -2
- package/dist/chunks/studio-41exgzhq.js +6690 -0
- package/dist/chunks/studio-41exgzhq.js.map +38 -0
- package/dist/chunks/{studio-7v2bpweq.js → studio-4mqb9a0j.js} +6 -4
- package/dist/chunks/{studio-7v2bpweq.js.map → studio-4mqb9a0j.js.map} +2 -2
- package/dist/chunks/studio-50k77cca.js +1787 -0
- package/dist/chunks/studio-50k77cca.js.map +18 -0
- package/dist/chunks/{studio-56v3zwk6.js → studio-6a0h7sne.js} +10 -8
- package/dist/chunks/{studio-56v3zwk6.js.map → studio-6a0h7sne.js.map} +2 -2
- package/dist/chunks/studio-6f0am8f2.js +53 -0
- package/dist/chunks/studio-6f0am8f2.js.map +10 -0
- package/dist/chunks/studio-6nmjc5qk.js +7002 -0
- package/dist/chunks/studio-6nmjc5qk.js.map +32 -0
- package/dist/chunks/studio-7afd21nw.js +67 -0
- package/dist/chunks/studio-7afd21nw.js.map +10 -0
- package/dist/chunks/{studio-xze4zrs1.js → studio-7bmdvzne.js} +2 -2
- package/dist/chunks/studio-7bmdvzne.js.map +10 -0
- package/dist/chunks/studio-8jpzqk5s.js +128 -0
- package/dist/chunks/studio-8jpzqk5s.js.map +10 -0
- package/dist/chunks/studio-94m77tgk.js +1482 -0
- package/dist/chunks/studio-94m77tgk.js.map +13 -0
- package/dist/chunks/studio-9je6w5sq.js +1000 -0
- package/dist/chunks/studio-9je6w5sq.js.map +16 -0
- package/dist/chunks/{studio-w7c6kvk7.js → studio-a7pzveq1.js} +2 -2
- package/dist/chunks/{studio-gs6kqdt1.js → studio-adn0z1k3.js} +14 -8
- package/dist/chunks/{studio-gs6kqdt1.js.map → studio-adn0z1k3.js.map} +3 -3
- package/dist/chunks/{studio-kr88ryr7.js → studio-bsxxx3x2.js} +17 -15
- package/dist/chunks/{studio-kr88ryr7.js.map → studio-bsxxx3x2.js.map} +7 -6
- package/dist/chunks/studio-bw5dz6km.js +608 -0
- package/dist/chunks/studio-bw5dz6km.js.map +21 -0
- package/dist/chunks/studio-c6s5getc.js +4232 -0
- package/dist/chunks/studio-c6s5getc.js.map +39 -0
- package/dist/chunks/studio-cjm7fdjf.js +451 -0
- package/dist/chunks/studio-cjm7fdjf.js.map +14 -0
- package/dist/chunks/{studio-cx91mnh8.js → studio-ckwz4d3x.js} +53 -53
- package/dist/chunks/{studio-cx91mnh8.js.map → studio-ckwz4d3x.js.map} +5 -5
- package/dist/chunks/studio-dg7zyt5x.js +28 -0
- package/dist/chunks/studio-dg7zyt5x.js.map +10 -0
- package/dist/chunks/studio-dzcyvvvz.js +166 -0
- package/dist/chunks/studio-dzcyvvvz.js.map +10 -0
- package/dist/chunks/{studio-7kqe41sa.js → studio-evkr5d3b.js} +15 -7
- package/dist/chunks/{studio-7kqe41sa.js.map → studio-evkr5d3b.js.map} +5 -4
- package/dist/chunks/studio-f12h6177.js +411 -0
- package/dist/chunks/studio-f12h6177.js.map +13 -0
- package/dist/chunks/studio-hbp1s8m9.js +1817 -0
- package/dist/chunks/studio-hbp1s8m9.js.map +14 -0
- package/dist/chunks/studio-k2sxjph5.js +15295 -0
- package/dist/chunks/studio-k2sxjph5.js.map +235 -0
- package/dist/chunks/studio-myje51t3.js +385 -0
- package/dist/chunks/studio-myje51t3.js.map +12 -0
- package/dist/chunks/studio-na6rwzhw.js +353 -0
- package/dist/chunks/studio-na6rwzhw.js.map +11 -0
- package/dist/chunks/{studio-4k0m4fmp.js → studio-p471gg5n.js} +126 -3
- package/dist/chunks/studio-p471gg5n.js.map +14 -0
- package/dist/chunks/{studio-9bjwf0d4.js → studio-rgwcpast.js} +5 -46
- package/dist/chunks/{studio-9bjwf0d4.js.map → studio-rgwcpast.js.map} +4 -5
- package/dist/chunks/{studio-esrewxap.js → studio-s519zc99.js} +3 -3
- package/dist/chunks/studio-sas7mt3k.js +75 -0
- package/dist/chunks/studio-sas7mt3k.js.map +10 -0
- package/dist/chunks/studio-t5wqwzdh.js +74 -0
- package/dist/chunks/studio-t5wqwzdh.js.map +10 -0
- package/dist/chunks/{studio-9qpspwfn.js → studio-temv5bw4.js} +5 -5
- package/dist/chunks/{studio-9qpspwfn.js.map → studio-temv5bw4.js.map} +1 -1
- package/dist/chunks/{studio-qkvt37c0.js → studio-tr20m8g8.js} +669 -18
- package/dist/chunks/studio-tr20m8g8.js.map +9 -0
- package/dist/chunks/{studio-v7jkjqng.js → studio-v96qfnww.js} +5514 -4291
- package/dist/chunks/studio-v96qfnww.js.map +54 -0
- package/dist/chunks/studio-vcetc91k.js +659 -0
- package/dist/chunks/studio-vcetc91k.js.map +10 -0
- package/dist/chunks/{studio-jm0z592w.js → studio-vmymjefp.js} +2972 -4937
- package/dist/chunks/studio-vmymjefp.js.map +98 -0
- package/dist/chunks/{studio-bghqfmea.js → studio-wwseqyw0.js} +2 -2
- package/dist/chunks/studio-x9hmy5kr.js +390 -0
- package/dist/chunks/studio-x9hmy5kr.js.map +10 -0
- package/dist/chunks/studio-xa0ce0n7.js +335 -0
- package/dist/chunks/studio-xa0ce0n7.js.map +10 -0
- package/dist/chunks/studio-xm3dbxvn.js +378 -0
- package/dist/chunks/studio-xm3dbxvn.js.map +19 -0
- package/dist/chunks/{studio-nqw433kv.js → studio-xmvbhg31.js} +2 -2
- package/dist/chunks/studio-zv37yrfa.js +1054 -0
- package/dist/chunks/studio-zv37yrfa.js.map +13 -0
- package/dist/chunks/{ws-client-q7hkz3j9.js → ws-client-654ts66j.js} +6 -4
- package/dist/chunks/ws-client-654ts66j.js.map +11 -0
- package/dist/iframe-entry.js +668 -230
- package/dist/studio.js +21080 -32585
- package/index.html +35 -4685
- package/package.json +17 -15
- package/src/about/about-modal.ts +42 -11
- package/src/browse/library-commands.ts +240 -0
- package/src/browse/library-layouts.ts +369 -0
- package/src/browse/library-model.ts +396 -0
- package/src/browse/library-pane.ts +1049 -0
- package/src/browse/library-preview.ts +252 -0
- package/src/browse/library-source.ts +150 -0
- package/src/canvas/canvas-helpers.ts +33 -9
- package/src/canvas/canvas-live-render.ts +45 -29
- package/src/canvas/canvas-patcher.ts +151 -31
- package/src/canvas/canvas-perf.ts +46 -7
- package/src/canvas/canvas-render.ts +1092 -220
- package/src/canvas/canvas-surface.ts +240 -0
- package/src/canvas/canvas-utils.ts +1166 -124
- package/src/canvas/iframe-drop.ts +2 -1
- package/src/canvas/iframe-entry.ts +241 -4
- package/src/canvas/iframe-host.ts +1226 -143
- package/src/canvas/iframe-inline-edit.ts +31 -0
- package/src/canvas/iframe-interaction.ts +76 -16
- package/src/canvas/iframe-keys.ts +107 -45
- package/src/canvas/iframe-overlay.ts +38 -1
- package/src/canvas/iframe-protocol.ts +137 -2
- package/src/canvas/iframe-render.ts +70 -2
- package/src/canvas/iframe-slash.ts +6 -0
- package/src/canvas/path-mapping.ts +56 -6
- package/src/canvas/surface-registry.ts +292 -0
- package/src/collab/collab-commands.ts +244 -0
- package/src/collab/collab-session.ts +198 -32
- package/src/collab/collab-state.ts +28 -2
- package/src/collab/presence-chips.ts +100 -9
- package/src/commands/active-registry.ts +42 -0
- package/src/commands/app-commands.ts +176 -0
- package/src/commands/budget.ts +158 -0
- package/src/commands/command-args.ts +253 -0
- package/src/commands/context.ts +387 -0
- package/src/commands/defaults.ts +580 -0
- package/src/commands/keymap.ts +571 -0
- package/src/commands/levels.ts +345 -0
- package/src/commands/live-context.ts +193 -0
- package/src/commands/reference.ts +227 -0
- package/src/commands/registry.ts +293 -0
- package/src/component-props.ts +3 -2
- package/src/content/draft-state.ts +106 -0
- package/src/content/entry-commands.ts +282 -0
- package/src/content/entry-editor.ts +327 -0
- package/src/content/entry-fields.ts +116 -0
- package/src/content/entry-model.ts +175 -0
- package/src/editor/canvas-context-menu.ts +17 -11
- package/src/editor/canvas-slash-bridge.ts +11 -4
- package/src/editor/context-menu.ts +804 -204
- package/src/editor/convert-to-component.ts +31 -10
- package/src/editor/convert-to-repeater.ts +3 -2
- package/src/editor/inline-edit-apply.ts +5 -5
- package/src/editor/inline-edit.ts +86 -50
- package/src/editor/insert-zone-action.ts +1 -1
- package/src/editor/shortcuts.ts +1043 -329
- package/src/editor/slash-menu.ts +1 -0
- package/src/files/elements.ts +166 -0
- package/src/files/file-ops.ts +178 -31
- package/src/files/files.ts +898 -268
- package/src/files/fs-events.ts +21 -0
- package/src/files/media-meta.ts +203 -0
- package/src/files/media-paths.ts +141 -0
- package/src/files/media-upload.ts +7 -3
- package/src/files/media-usage.ts +189 -0
- package/src/github/github-auth.ts +235 -33
- package/src/github/github-publish.ts +80 -21
- package/src/grid/grid-controller.ts +152 -12
- package/src/grid/grid-layout.ts +225 -20
- package/src/grid/grid-open.ts +120 -0
- package/src/grid/grid-panel.ts +554 -39
- package/src/grid/grid-source.ts +27 -1
- package/src/grid/redirects-grid.ts +599 -0
- package/src/grid/redirects.ts +398 -0
- package/src/grid/sources/content-source.ts +108 -2
- package/src/i18n/i18n-commands.ts +322 -0
- package/src/new-project/add-repo-modal.ts +8 -10
- package/src/new-project/import-tab.ts +7 -0
- package/src/new-project/location-fields.ts +9 -1
- package/src/new-project/new-project-modal.ts +221 -245
- package/src/packages/jxsuite-update.ts +97 -48
- package/src/packages/pull-package-sync.ts +18 -9
- package/src/page-params.ts +2 -1
- package/src/panels/activity-bar.ts +152 -91
- package/src/panels/activity-panel.ts +481 -0
- package/src/panels/ai-chat/chat-markdown.ts +8 -3
- package/src/panels/ai-chat/chat-view.ts +245 -18
- package/src/panels/ai-chat/composer.ts +31 -5
- package/src/panels/ai-chat/sessions-view.ts +17 -10
- package/src/panels/ai-panel.ts +279 -53
- package/src/panels/block-action-bar.ts +1115 -224
- package/src/panels/bottom-dock.ts +337 -0
- package/src/panels/canvas-dnd-bridge.ts +37 -8
- package/src/panels/chat-panel.ts +29 -12
- package/src/panels/data-explorer.ts +254 -112
- package/src/panels/data-grid.ts +10 -4
- package/src/panels/dnd.ts +28 -51
- package/src/panels/editors.ts +434 -107
- package/src/panels/elements-panel.ts +66 -2
- package/src/panels/empty-state.ts +112 -0
- package/src/panels/events-panel.ts +596 -82
- package/src/panels/formula-workspace.ts +408 -218
- package/src/panels/frontmatter-fields.ts +51 -10
- package/src/panels/frontmatter-panel.ts +357 -79
- package/src/panels/git-panel.ts +375 -164
- package/src/panels/head-panel.ts +791 -85
- package/src/panels/i18n-panel.ts +592 -0
- package/src/panels/imports-panel.ts +95 -85
- package/src/panels/jump-bar.ts +584 -0
- package/src/panels/layers-panel.ts +1106 -328
- package/src/panels/left-panel.ts +109 -204
- package/src/panels/navigator-panels.ts +131 -0
- package/src/panels/overlays.ts +7 -2
- package/src/panels/pane-context.ts +1477 -0
- package/src/panels/pane-grid.ts +406 -0
- package/src/panels/panel-registry.ts +340 -0
- package/src/panels/panel-scheduler.ts +75 -0
- package/src/panels/problems-panel.ts +234 -0
- package/src/panels/properties-panel.ts +946 -1139
- package/src/panels/provenance.ts +258 -0
- package/src/panels/quick-search.ts +791 -122
- package/src/panels/right-panel.ts +228 -102
- package/src/panels/seo-modal.ts +395 -0
- package/src/panels/settings-pane.ts +203 -0
- package/src/panels/signals-panel.ts +354 -137
- package/src/panels/statement-editor.ts +41 -25
- package/src/panels/statusbar.ts +342 -106
- package/src/panels/style-panel.ts +974 -463
- package/src/panels/style-utils.ts +3 -3
- package/src/panels/stylebook-doc.ts +4 -2
- package/src/panels/stylebook-layers-panel.ts +3 -2
- package/src/panels/stylebook-panel.ts +51 -26
- package/src/panels/tab-strip.ts +1058 -78
- package/src/panels/target-line.ts +274 -0
- package/src/panels/toolbar.ts +631 -484
- package/src/panels/welcome-screen.ts +301 -198
- package/src/platform-errors.ts +26 -3
- package/src/platform.ts +96 -2
- package/src/platforms/cloud.ts +71 -8
- package/src/platforms/devserver.ts +92 -29
- package/src/publish/deploy-checklist.ts +383 -0
- package/src/publish/pages-service.ts +10 -1
- package/src/publish/publish-commands.ts +218 -0
- package/src/publish/publish-panel.ts +110 -16
- package/src/recent-projects.ts +3 -2
- package/src/services/a11y-report.ts +505 -0
- package/src/services/ai-models.ts +30 -1
- package/src/services/ai-project-tools.ts +29 -10
- package/src/services/ai-system-prompt.ts +53 -14
- package/src/services/ai-tools.ts +39 -3
- package/src/services/ai-writes.ts +154 -0
- package/src/services/announce.ts +97 -0
- package/src/services/automation.ts +424 -175
- package/src/services/cem-export.ts +9 -4
- package/src/services/clock.ts +61 -0
- package/src/services/document-assistant.ts +36 -8
- package/src/services/idle.ts +179 -0
- package/src/services/import-client.ts +26 -0
- package/src/services/monaco-buffer.ts +627 -0
- package/src/services/monaco-lazy.ts +36 -0
- package/src/services/notify.ts +271 -0
- package/src/services/profile.ts +155 -0
- package/src/services/references.ts +0 -0
- package/src/services/token-lint.ts +3 -2
- package/src/services/tool-executor.ts +53 -7
- package/src/services/trusted-types.ts +123 -0
- package/src/settings/contexts-section.ts +427 -0
- package/src/settings/contributed-section.ts +149 -11
- package/src/settings/css-vars-editor.ts +306 -349
- package/src/settings/defs-editor.ts +93 -34
- package/src/settings/dependencies-editor.ts +7 -7
- package/src/settings/extension-sections.ts +37 -5
- package/src/settings/general-settings.ts +204 -121
- package/src/settings/locales-section.ts +347 -0
- package/src/settings/preferences-accounts.ts +140 -0
- package/src/settings/preferences-dialog.ts +620 -0
- package/src/settings/preferences-keymap.ts +270 -0
- package/src/settings/project-sections.ts +260 -0
- package/src/settings/section-registry.ts +169 -0
- package/src/settings/settings-document.ts +385 -0
- package/src/shell.ts +1549 -0
- package/src/site-context.ts +45 -20
- package/src/state.ts +14 -275
- package/src/store.ts +88 -43
- package/src/studio.ts +790 -346
- package/src/style/live-preview.ts +36 -0
- package/src/style/project-styles.ts +349 -0
- package/src/style/token-ref.ts +123 -0
- package/src/tabs/patch-ops.ts +16 -4
- package/src/tabs/project-config.ts +416 -0
- package/src/tabs/selection.ts +323 -0
- package/src/tabs/tab.ts +182 -47
- package/src/tabs/transact.ts +209 -28
- package/src/types.ts +68 -13
- package/src/ui/ai-credentials-form.ts +39 -35
- package/src/ui/ai-managed-connect.ts +7 -1
- package/src/ui/dynamic-slot.ts +224 -81
- package/src/ui/expression-editor.ts +71 -63
- package/src/ui/field-input.ts +4 -6
- package/src/ui/field-row.ts +215 -38
- package/src/ui/form-controls.ts +194 -7
- package/src/ui/layers.ts +492 -69
- package/src/ui/media-picker.ts +115 -25
- package/src/ui/panel-resize.ts +109 -91
- package/src/ui/progress-modal.ts +131 -50
- package/src/ui/regions.ts +462 -0
- package/src/ui/schema-form.ts +229 -45
- package/src/ui/spectrum.ts +24 -8
- package/src/ui/timing.ts +49 -0
- package/src/ui/value-selector.ts +20 -0
- package/src/ui/value-source.ts +348 -0
- package/src/ui/virtual-window.ts +400 -0
- package/src/utils/edit-display.ts +3 -2
- package/src/utils/inherited-style.ts +67 -37
- package/src/view.ts +64 -78
- package/src/workspace/pane-derive.ts +1258 -0
- package/src/workspace/session.ts +317 -0
- package/src/workspace/workspace.ts +1345 -23
- package/styles/canvas.css +653 -0
- package/styles/forced-colors.css +106 -0
- package/styles/inspector.css +1454 -0
- package/styles/overlays.css +2704 -0
- package/styles/panels.css +1701 -0
- package/styles/shell.css +1423 -0
- package/styles/tokens.css +151 -0
- package/dist/chunks/Overlay.dev-cszx7yn5.js +0 -22
- package/dist/chunks/extension-sections-qr3drkkw.js +0 -531
- package/dist/chunks/extension-sections-qr3drkkw.js.map +0 -12
- package/dist/chunks/files-j8a1t06k.js +0 -50
- package/dist/chunks/files-j8a1t06k.js.map +0 -9
- package/dist/chunks/git-panel-xtq5z81x.js +0 -28
- package/dist/chunks/git-panel-xtq5z81x.js.map +0 -9
- package/dist/chunks/layers-panel-ancn2wpq.js +0 -26
- package/dist/chunks/layers-panel-ancn2wpq.js.map +0 -9
- package/dist/chunks/settings-modal-17ttr96g.js +0 -29
- package/dist/chunks/settings-modal-17ttr96g.js.map +0 -9
- package/dist/chunks/sp-overlay.dev-earn5as8.js +0 -17
- package/dist/chunks/sp-overlay.dev-earn5as8.js.map +0 -9
- package/dist/chunks/sp-popover.dev-frxtfejn.js +0 -8
- package/dist/chunks/sp-popover.dev-frxtfejn.js.map +0 -9
- package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js +0 -14
- package/dist/chunks/sp-progress-circle.dev-7tvw34h8.js.map +0 -9
- package/dist/chunks/sp-tooltip.dev-a9fywesm.js +0 -21
- package/dist/chunks/sp-tooltip.dev-a9fywesm.js.map +0 -10
- package/dist/chunks/studio-0xgs7xrs.js +0 -429
- package/dist/chunks/studio-0xgs7xrs.js.map +0 -13
- package/dist/chunks/studio-4d8hzcz5.js +0 -3427
- package/dist/chunks/studio-4d8hzcz5.js.map +0 -26
- package/dist/chunks/studio-4k0m4fmp.js.map +0 -13
- package/dist/chunks/studio-9smfzqhc.js +0 -19
- package/dist/chunks/studio-9smfzqhc.js.map +0 -10
- package/dist/chunks/studio-jm0z592w.js.map +0 -115
- package/dist/chunks/studio-m2t4mdg5.js +0 -1854
- package/dist/chunks/studio-m2t4mdg5.js.map +0 -18
- package/dist/chunks/studio-n4p11dfy.js +0 -853
- package/dist/chunks/studio-n4p11dfy.js.map +0 -12
- package/dist/chunks/studio-qkvt37c0.js.map +0 -9
- package/dist/chunks/studio-sjh1wt1a.js +0 -3744
- package/dist/chunks/studio-sjh1wt1a.js.map +0 -22
- package/dist/chunks/studio-v7jkjqng.js.map +0 -46
- package/dist/chunks/studio-xq0t7n5t.js +0 -140
- package/dist/chunks/studio-xq0t7n5t.js.map +0 -10
- package/dist/chunks/studio-xze4zrs1.js.map +0 -10
- package/dist/chunks/studio-yfcstdzy.js +0 -399
- package/dist/chunks/studio-yfcstdzy.js.map +0 -12
- package/dist/chunks/studio-zp7jncs8.js +0 -469
- package/dist/chunks/studio-zp7jncs8.js.map +0 -14
- package/dist/chunks/ws-client-q7hkz3j9.js.map +0 -11
- package/src/browse/browse-modal.ts +0 -69
- package/src/browse/browse.ts +0 -904
- package/src/canvas/canvas-diff.ts +0 -216
- package/src/canvas/nested-site-style.ts +0 -58
- package/src/new-project/design-fields.ts +0 -264
- package/src/new-project/templates.ts +0 -61
- package/src/panels/tab-bar.ts +0 -548
- package/src/settings/settings-modal.ts +0 -203
- /package/dist/chunks/{adapter-z8fkmbmv.js.map → adapter-qfnesn5v.js.map} +0 -0
- /package/dist/chunks/{cf-settings-mmyv436d.js.map → cf-settings-73sh5z4p.js.map} +0 -0
- /package/dist/chunks/{format-host-8jxwts2p.js.map → format-host-mkwsmt8d.js.map} +0 -0
- /package/dist/chunks/{jx-validate-qx8degj7.js.map → jx-validate-v6qgknrs.js.map} +0 -0
- /package/dist/chunks/{studio-w7c6kvk7.js.map → studio-a7pzveq1.js.map} +0 -0
- /package/dist/chunks/{studio-esrewxap.js.map → studio-s519zc99.js.map} +0 -0
- /package/dist/chunks/{studio-bghqfmea.js.map → studio-wwseqyw0.js.map} +0 -0
- /package/dist/chunks/{studio-nqw433kv.js.map → studio-xmvbhg31.js.map} +0 -0
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* The one question every write into a Monaco buffer has to ask: **may I write into this buffer?**
|
|
4
|
+
*
|
|
5
|
+
* Studio has two Monaco surfaces — the source view on the canvas stage (`view.monacoEditor`,
|
|
6
|
+
* mounted by `canvas/canvas-render.ts`) and the function body in the Bottom dock's Logic tab
|
|
7
|
+
* (`view.functionEditor`, mounted by `panels/editors.ts`).
|
|
8
|
+
*
|
|
9
|
+
* **The count in this file is of WRITE SITES — places that put text INTO a buffer** — because a
|
|
10
|
+
* write into a buffer is the only thing that has to ask this module's question. (Writes OUT are
|
|
11
|
+
* counted nowhere: there are two, one per surface, they are the debounced commits, and they ask
|
|
12
|
+
* `bufferIsLive` instead. See the note at the end of this header.) There are four, and none of them
|
|
13
|
+
* can see the keyboard:
|
|
14
|
+
*
|
|
15
|
+
* 1. **the serializer resolving with the file's contents**, at mount — `canvas-render.ts`'s
|
|
16
|
+
* `mountSourceEditor`;
|
|
17
|
+
* 2. **a repaint pushing the document's text in** — `canvas-render.ts`'s source fast path;
|
|
18
|
+
* 3. **the dock's re-sync**, showing an edit that arrived from somewhere else — `editors.ts`'s
|
|
19
|
+
* `syncFunctionEditor`;
|
|
20
|
+
* 4. **a code-service round trip landing with a formatted body** — `editors.ts`'s format-on-open.
|
|
21
|
+
*
|
|
22
|
+
* Every one of those writes is computed from a buffer state that may be several keystrokes old by
|
|
23
|
+
* the time it arrives, and `setValue` does not merge — it replaces, and takes the user's unsaved
|
|
24
|
+
* work with it.
|
|
25
|
+
*
|
|
26
|
+
* **And on a co-edited tab there is a FIFTH writer, which is not in that list because it never
|
|
27
|
+
* calls anything here.** y-monaco binds the source model to a shared `Y.Text`, so every peer's
|
|
28
|
+
* keystrokes arrive in the buffer directly and say nothing to anyone. It is also the only writer
|
|
29
|
+
* whose text reaches other people's machines: the binding is two-way, so a local `setValue` over a
|
|
30
|
+
* bound model is a whole-document replace PUBLISHED to every peer. A buffer in that state declares
|
|
31
|
+
* itself with {@link BufferWrites.markShared} and clause 5 refuses every write into it.
|
|
32
|
+
*
|
|
33
|
+
* The rule was already discovered once, in one branch of one fast path:
|
|
34
|
+
*
|
|
35
|
+
* ```js
|
|
36
|
+
* if (!editor.hasTextFocus() && editor.getValue() !== newVal) { … }
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* …and it was never given a name, so the other three write sites each invented a different guard.
|
|
40
|
+
* All three asked **identity** — "is this still the same editor object?" — which answers a real
|
|
41
|
+
* question (a retarget must not write A's body into B) and is not the same question. An editor can
|
|
42
|
+
* keep its identity for minutes while its buffer moves on with every keypress. Identity is
|
|
43
|
+
* necessary and never sufficient.
|
|
44
|
+
*
|
|
45
|
+
* {@link bufferMovedOn} is that rule's one home. A write must be dropped when any of these is true
|
|
46
|
+
* — the first four are "the buffer moved on", and the fifth is the one the co-edited surface
|
|
47
|
+
* added:
|
|
48
|
+
*
|
|
49
|
+
* 1. **It is gone.** `dispose()` detaches the model, and `CodeEditorWidget.getValue()` opens with `if
|
|
50
|
+
* (!this._modelData) return "";` — so a dead editor answers the empty string and a write into it
|
|
51
|
+
* lands nowhere.
|
|
52
|
+
* 2. **The user is in it.** `hasTextFocus()` means their next keystroke is newer than your value.
|
|
53
|
+
* 3. **It holds text the document has not been given.** A repaint that resolves a buffer/document
|
|
54
|
+
* difference in the document's favour discards whatever put the difference there.
|
|
55
|
+
*
|
|
56
|
+
* This clause was first written as `pending()` — "a commit is armed" — which is the same thing for
|
|
57
|
+
* a KEYSTROKE and a different thing for every other write. A programmatic write sets
|
|
58
|
+
* `_ignoreNextChange`, so the change handler returns before it can arm anything: the buffer is
|
|
59
|
+
* ahead with nothing pending, and a timer-shaped clause reported "settled". That is exactly the
|
|
60
|
+
* state format-on-open leaves — it pretty-prints into the buffer and deliberately never commits —
|
|
61
|
+
* so the next repaint reverted it, and **"Format on open" visibly un-formatted itself** within
|
|
62
|
+
* seconds of every open, now that the dock repaints on the Problems badge, on Activity and on a
|
|
63
|
+
* 30-second git poll.
|
|
64
|
+
*
|
|
65
|
+
* So it is a FACT the buffer carries ({@link BufferWrites.ahead}), not an inference from a timer.
|
|
66
|
+
* Whoever writes into a buffer says which side the text came from: text that did not come from the
|
|
67
|
+
* document marks it {@link BufferWrites.markAhead}, and the document taking the buffer's text — or
|
|
68
|
+
* the buffer taking the document's — marks it {@link BufferWrites.markSettled}. A keystroke marks
|
|
69
|
+
* ahead and arms its commit; the commit marks settled.
|
|
70
|
+
* 4. **It is not what the write was computed from.** A caller that read the buffer before an `await`
|
|
71
|
+
* passes what it read as `expected`; a mismatch means the round trip lost the race.
|
|
72
|
+
* 5. **It is not this process's buffer to replace.** The one clause that is not "the buffer moved on"
|
|
73
|
+
* — it is "the buffer is not yours". A co-edited source buffer is bound to a shared `Y.Text` and
|
|
74
|
+
* the CRDT is the source of truth for that text while the source lock is held: the structure
|
|
75
|
+
* tree is DERIVED from it by the source reconciler (`collab/collab-session.ts`'s
|
|
76
|
+
* `sourceParseNow` parses the shared text back into the tree). A repaint serializing that tree
|
|
77
|
+
* over the buffer is therefore a rendering of the text asserting itself over the text — and
|
|
78
|
+
* because the model is bound, it is published to every peer, on every repaint, for every round
|
|
79
|
+
* trip that is not byte-stable. There is no direction in which a co-edited buffer wants the
|
|
80
|
+
* document's serialized text, so this fact is set once at bind ({@link BufferWrites.markShared})
|
|
81
|
+
* and never cleared: a future writer's `markSettled` must not be able to unlock it, which is why
|
|
82
|
+
* it is its own fact and not a use of clause 3.
|
|
83
|
+
*
|
|
84
|
+
* **Ahead is not the same as unsaved, and the gates that warn about lost work need the narrower
|
|
85
|
+
* fact.** Format-on-open leaves a buffer ahead deliberately and never commits — and since
|
|
86
|
+
* `closeFunctionEditor` writes a MINIFIED body, the pretty-printed buffer differs from the document
|
|
87
|
+
* for as long as the editor is open. Counting THAT as unsaved work would put "you have unsaved
|
|
88
|
+
* changes" in front of every author who merely opened a handler. So a keystroke says
|
|
89
|
+
* {@link BufferWrites.markTyped} — ahead, and the text is the user's own — and the two gates that
|
|
90
|
+
* destroy a tab ({@link commitTabBuffers}'s callers) read {@link BufferWrites.typed}, which is
|
|
91
|
+
* exactly the text a close would take with it.
|
|
92
|
+
*
|
|
93
|
+
* {@link commitBufferWrites} reads the same fact from the other end. A gate asks it BEFORE the
|
|
94
|
+
* destruction, to decide whether to prompt; a disposer asks it AFTER its own flush, when the answer
|
|
95
|
+
* can only be "I have just deleted this" — and is the last thing in a position to say so, because
|
|
96
|
+
* the next line detaches the model and `buffersForTab` stops finding the buffer at all.
|
|
97
|
+
*
|
|
98
|
+
* Writes OUT of a buffer — a debounce reading `getValue()` into the document — ask `bufferIsLive`
|
|
99
|
+
* instead, which is clause 1 alone. Deliberately, and it is the only kind of call site in the
|
|
100
|
+
* codebase entitled to a subset: clauses 2 and 3 describe a buffer that is AHEAD of the document,
|
|
101
|
+
* which is the precondition for committing rather than a reason to refuse, and clause 4 has no
|
|
102
|
+
* meaning for a writer whose whole input is the buffer.
|
|
103
|
+
*
|
|
104
|
+
* @docs studio/logic/code
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
import { toRaw } from "../reactivity";
|
|
108
|
+
import { notify } from "./notify";
|
|
109
|
+
import type { Tab } from "../tabs/tab";
|
|
110
|
+
import { view } from "../view";
|
|
111
|
+
import { allCanvasSurfaces } from "../canvas/surface-registry";
|
|
112
|
+
|
|
113
|
+
/** The slice of a Monaco editor this module needs. Both of Studio's Monacos satisfy it. */
|
|
114
|
+
export interface MonacoBuffer {
|
|
115
|
+
getValue: () => string;
|
|
116
|
+
getModel: () => unknown;
|
|
117
|
+
hasTextFocus: () => boolean;
|
|
118
|
+
/** The debounced work armed over THIS buffer. Installed by {@link bufferWrites}. */
|
|
119
|
+
_writes?: BufferWrites;
|
|
120
|
+
/**
|
|
121
|
+
* The tab this buffer was mounted for — **whose buffer this is**, and both surfaces answer it the
|
|
122
|
+
* same way.
|
|
123
|
+
*
|
|
124
|
+
* Only the dock's editor used to carry it. The source editor named its tab in a closure inside
|
|
125
|
+
* `mountSourceEditor`, which is unreachable from outside — so a caller holding a `Tab` could ask
|
|
126
|
+
* the dock "is this yours?" and had no way at all to ask the source view. Every question about a
|
|
127
|
+
* tab's buffers ({@link commitTabBuffers}, {@link tabBufferUnsaved}) needs both answers, and a
|
|
128
|
+
* question that can only be asked of one of two surfaces is answered wrong half the time.
|
|
129
|
+
*/
|
|
130
|
+
_editingTab?: Tab | null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* The debounced work armed over one buffer, with that buffer's exact lifetime.
|
|
135
|
+
*
|
|
136
|
+
* It hangs off the editor rather than off `view` or a module variable because those two spellings
|
|
137
|
+
* are what the two surfaces had: the dock's canceller was an editor property, the canvas's timer
|
|
138
|
+
* was a closure variable inside `mountSourceEditor` that nothing outside could reach — so of the
|
|
139
|
+
* two, only one teardown could cancel, and the canvas's three disposal sites cancelled nothing at
|
|
140
|
+
* all. Whoever disposes an editor now has its canceller in their hand by construction.
|
|
141
|
+
*/
|
|
142
|
+
export interface BufferWrites {
|
|
143
|
+
/**
|
|
144
|
+
* (Re)arm the timer called `key`, replacing whatever was armed under that key.
|
|
145
|
+
*
|
|
146
|
+
* The work may be async — the source view's commit parses through the format host — and
|
|
147
|
+
* {@link BufferWrites.flush} hands that promise back, because a caller that flushes in order to
|
|
148
|
+
* ASK A QUESTION about the result (has this tab unsaved work now?) has to wait for it.
|
|
149
|
+
*/
|
|
150
|
+
arm: (key: string, ms: number, run: () => void | Promise<void>) => void;
|
|
151
|
+
/** Cancel every armed timer. The disposer calls this; see {@link cancelBufferWrites}. */
|
|
152
|
+
cancel: () => void;
|
|
153
|
+
/**
|
|
154
|
+
* Run the work armed under `key` NOW, and drop its timer. A no-op when nothing is armed there.
|
|
155
|
+
*
|
|
156
|
+
* The teardown's half of {@link commitBufferWrites}: the debounce is the only thing that carries
|
|
157
|
+
* the last half-second of typing into the document, so a disposer that merely cancels it deletes
|
|
158
|
+
* that typing — silently, because the document is never marked dirty for work it never received.
|
|
159
|
+
*
|
|
160
|
+
* Returns whatever the work returned, so an async commit can be awaited. The disposers ignore it
|
|
161
|
+
* on purpose: they flush precisely because they are about to detach the model, and every commit
|
|
162
|
+
* reads `getValue()` before its own first `await`.
|
|
163
|
+
*
|
|
164
|
+
* **A commit that already fired counts as armed.** The timer drops its key before running, so a
|
|
165
|
+
* flush arriving mid-run would otherwise find nothing and read that as "there was nothing to
|
|
166
|
+
* carry" — which for the source view, whose commit awaits an IPC round trip to the format host,
|
|
167
|
+
* is a window of tens of milliseconds starting the moment the author stops typing. Exactly when
|
|
168
|
+
* they click another tab. The in-flight promise is returned instead, so the caller waits for the
|
|
169
|
+
* answer rather than inventing one.
|
|
170
|
+
*/
|
|
171
|
+
flush: (key: string) => void | Promise<void>;
|
|
172
|
+
/** Clause 3, as a fact: the buffer holds text the document has not been given. */
|
|
173
|
+
ahead: () => boolean;
|
|
174
|
+
/** Declare that the buffer just took text which did NOT come from the document. */
|
|
175
|
+
markAhead: () => void;
|
|
176
|
+
/**
|
|
177
|
+
* Clause 3, narrowed to the text a close would DESTROY: the user's own keystrokes, not yet in the
|
|
178
|
+
* document. See the header — `ahead` is also true of format-on-open, whose text nobody loses.
|
|
179
|
+
*/
|
|
180
|
+
typed: () => boolean;
|
|
181
|
+
/** Declare a keystroke: {@link BufferWrites.markAhead}, and the text is the user's own. */
|
|
182
|
+
markTyped: () => void;
|
|
183
|
+
/** Declare that buffer and document now hold the same text, whichever way it travelled. */
|
|
184
|
+
markSettled: () => void;
|
|
185
|
+
/** Clause 5, as a fact: this buffer is bound to a shared text that this process does not own. */
|
|
186
|
+
shared: () => boolean;
|
|
187
|
+
/** Declare that the buffer is now bound to a shared text. Permanent — see clause 5. */
|
|
188
|
+
markShared: () => void;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* The key both surfaces arm their document-commit under.
|
|
193
|
+
*
|
|
194
|
+
* A shared constant rather than each surface's own string, because {@link commitBufferWrites} has to
|
|
195
|
+
* be able to flush THE COMMIT and only the commit. A buffer also arms a lint (`"lint"`), and firing
|
|
196
|
+
* a lint request at a buffer that is being disposed is work for an editor that will not exist when
|
|
197
|
+
* the answer arrives.
|
|
198
|
+
*/
|
|
199
|
+
export const BUFFER_COMMIT = "commit";
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Install (and return) the debounce holder for `buffer`.
|
|
203
|
+
*
|
|
204
|
+
* Called once per mount, right after the editor is created, so that the very first keystroke arms
|
|
205
|
+
* its commit through a canceller the teardown can already see.
|
|
206
|
+
*
|
|
207
|
+
* @param {MonacoBuffer} buffer The freshly created editor.
|
|
208
|
+
* @returns {BufferWrites}
|
|
209
|
+
*/
|
|
210
|
+
export function bufferWrites(buffer: MonacoBuffer): BufferWrites {
|
|
211
|
+
const timers = new Map<string, ReturnType<typeof setTimeout>>();
|
|
212
|
+
const runs = new Map<string, () => void | Promise<void>>();
|
|
213
|
+
/** Runs that have started and not yet answered. See {@link BufferWrites.flush}. */
|
|
214
|
+
const inFlight = new Map<string, Promise<void>>();
|
|
215
|
+
let isAhead = false;
|
|
216
|
+
let isTyped = false;
|
|
217
|
+
let isShared = false;
|
|
218
|
+
/**
|
|
219
|
+
* Remember a run while it is answering, so a flush arriving mid-run returns its promise rather
|
|
220
|
+
* than the `undefined` that means "nothing was armed". Self-clearing, and it never rejects — the
|
|
221
|
+
* caller inspects the settled state through `typed()`, and a rejection is the commit's own to
|
|
222
|
+
* report.
|
|
223
|
+
*/
|
|
224
|
+
function track(key: string, result: void | Promise<void>): void | Promise<void> {
|
|
225
|
+
if (typeof (result as Promise<void> | undefined)?.then !== "function") {
|
|
226
|
+
return result;
|
|
227
|
+
}
|
|
228
|
+
const promise = (result as Promise<void>).finally(() => {
|
|
229
|
+
if (inFlight.get(key) === promise) {
|
|
230
|
+
inFlight.delete(key);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
inFlight.set(key, promise);
|
|
234
|
+
return promise;
|
|
235
|
+
}
|
|
236
|
+
const writes: BufferWrites = {
|
|
237
|
+
arm(key, ms, run) {
|
|
238
|
+
clearTimeout(timers.get(key));
|
|
239
|
+
runs.set(key, run);
|
|
240
|
+
timers.set(
|
|
241
|
+
key,
|
|
242
|
+
setTimeout(() => {
|
|
243
|
+
// Dropped BEFORE the body runs: a callback that re-arms its own key must not have that
|
|
244
|
+
// Arm cleared out from under it, and a flush of a key already executing must find nothing.
|
|
245
|
+
timers.delete(key);
|
|
246
|
+
runs.delete(key);
|
|
247
|
+
/* Discarded deliberately: a timer that fires on its own has no caller to answer to.
|
|
248
|
+
`track` has already put the promise in `inFlight`, which is where a flush arriving
|
|
249
|
+
mid-run collects it — and per the contract above, `run` reports its own failures. */
|
|
250
|
+
void track(key, run());
|
|
251
|
+
}, ms),
|
|
252
|
+
);
|
|
253
|
+
},
|
|
254
|
+
cancel() {
|
|
255
|
+
for (const timer of timers.values()) {
|
|
256
|
+
clearTimeout(timer);
|
|
257
|
+
}
|
|
258
|
+
timers.clear();
|
|
259
|
+
runs.clear();
|
|
260
|
+
},
|
|
261
|
+
flush(key) {
|
|
262
|
+
const run = runs.get(key);
|
|
263
|
+
if (!run) {
|
|
264
|
+
// Nothing armed — but something may be RUNNING. Its answer is the honest one.
|
|
265
|
+
return inFlight.get(key);
|
|
266
|
+
}
|
|
267
|
+
clearTimeout(timers.get(key));
|
|
268
|
+
timers.delete(key);
|
|
269
|
+
runs.delete(key);
|
|
270
|
+
return track(key, run());
|
|
271
|
+
},
|
|
272
|
+
ahead: () => isAhead,
|
|
273
|
+
markAhead() {
|
|
274
|
+
isAhead = true;
|
|
275
|
+
},
|
|
276
|
+
typed: () => isTyped,
|
|
277
|
+
markTyped() {
|
|
278
|
+
isAhead = true;
|
|
279
|
+
isTyped = true;
|
|
280
|
+
},
|
|
281
|
+
markSettled() {
|
|
282
|
+
isAhead = false;
|
|
283
|
+
isTyped = false;
|
|
284
|
+
},
|
|
285
|
+
shared: () => isShared,
|
|
286
|
+
markShared() {
|
|
287
|
+
isShared = true;
|
|
288
|
+
},
|
|
289
|
+
};
|
|
290
|
+
buffer._writes = writes;
|
|
291
|
+
return writes;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* The two Monaco surfaces, named so a teardown that loses text can say what the author was looking
|
|
296
|
+
* at when it went. Both spellings are this module's, not the caller's, because the sentence has to
|
|
297
|
+
* read the same whichever disposer produced it.
|
|
298
|
+
*/
|
|
299
|
+
export type BufferSurface = "logic" | "source";
|
|
300
|
+
|
|
301
|
+
const SURFACE: Record<BufferSurface, { holds: string; reached: string; source: string }> = {
|
|
302
|
+
logic: { holds: "The handler you were typing", reached: "written into", source: "Logic" },
|
|
303
|
+
source: { holds: "The source you were typing", reached: "parsed into", source: "Editor" },
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Say that a teardown destroyed the author's typing, and answer whether it did.
|
|
308
|
+
*
|
|
309
|
+
* Called once per {@link commitBufferWrites}, at the moment the answer is known — synchronously for
|
|
310
|
+
* the dock's body write, and from the promise's continuation for the source view's parse, which
|
|
311
|
+
* cannot answer before the disposer has returned.
|
|
312
|
+
*
|
|
313
|
+
* **A toast rather than a Problem, and the difference is whether anything can be done.** A commit
|
|
314
|
+
* that THREW leaves the text in the buffer, so {@link commitTabBuffers} files a Problem — the
|
|
315
|
+
* author can still go and rescue it, minutes later. Here the buffer is being detached in the same
|
|
316
|
+
* breath: there is nothing left to fix, and a record the app promises to keep until somebody fixes
|
|
317
|
+
* it is the wrong host for a loss that is already final.
|
|
318
|
+
*
|
|
319
|
+
* **It does not ask whether a gate already said this.** A close that destroys the TAB runs
|
|
320
|
+
* `commitTabBuffers` and its dialog first, so an author who chose "Close Without Saving" hears it
|
|
321
|
+
* twice; "is that tab still open?" is `workspace`'s to answer, and `workspace` imports this module.
|
|
322
|
+
* One extra keyed toast confirming a choice the author just made is the cheaper of the two costs.
|
|
323
|
+
*
|
|
324
|
+
* @param {MonacoBuffer} buffer
|
|
325
|
+
* @param {BufferSurface} surface
|
|
326
|
+
* @param {{ error: unknown } | null} failure What the commit threw, BOXED — a commit is free to
|
|
327
|
+
* throw `undefined`, and "did it throw" must not become "was what it threw interesting".
|
|
328
|
+
* @returns {boolean} True when there was nothing to lose.
|
|
329
|
+
*/
|
|
330
|
+
function reportDiscarded(
|
|
331
|
+
buffer: MonacoBuffer,
|
|
332
|
+
surface: BufferSurface,
|
|
333
|
+
failure: { error: unknown } | null,
|
|
334
|
+
): boolean {
|
|
335
|
+
const writes = buffer._writes;
|
|
336
|
+
if (writes?.typed() !== true) {
|
|
337
|
+
return true;
|
|
338
|
+
}
|
|
339
|
+
const tab = buffer._editingTab ?? null;
|
|
340
|
+
const where = tab?.documentPath ?? "Untitled";
|
|
341
|
+
const { holds, reached, source } = SURFACE[surface];
|
|
342
|
+
notify.warn(`${holds} was discarded — it was never ${reached} "${where}".`, {
|
|
343
|
+
...(failure ? { detail: describeCommitFailure(failure.error) } : {}),
|
|
344
|
+
key: `buffer-discarded:${surface}:${tab?.id ?? "none"}`,
|
|
345
|
+
source,
|
|
346
|
+
...(tab?.documentPath ? { path: tab.documentPath } : {}),
|
|
347
|
+
});
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** What a failed commit is worth showing: the stack when there is one, the value otherwise. */
|
|
352
|
+
function describeCommitFailure(error: unknown): string {
|
|
353
|
+
return error instanceof Error ? (error.stack ?? error.message) : String(error);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Carry what the buffer is holding into the document, then drop everything armed over it.
|
|
358
|
+
*
|
|
359
|
+
* **The teardown's obligation, and it is two obligations rather than one.** A disposer that only
|
|
360
|
+
* cancels stops a dead buffer from writing `""` over a handler — the round-3 defect — by throwing
|
|
361
|
+
* away the last half-second of typing instead. That is still lost work, and it is worse in one
|
|
362
|
+
* respect: `doc.dirty` stays `false`, so nothing on screen says anything went missing. Five ways
|
|
363
|
+
* out of the code surface lost an edit that way (switch dock tab, open another target, open a
|
|
364
|
+
* formula through `openLogicTarget`, collapse the dock, switch to a tab whose target string
|
|
365
|
+
* matches), and each measured as "the pre-typing body, quietly".
|
|
366
|
+
*
|
|
367
|
+
* **Two more ways out are not a disposer's to fix, and this function cannot reach them.** ⌘W / the
|
|
368
|
+
* tab × and quitting DESTROY THE TAB: `closeTab` deletes it from `workspace.tabs` before anything
|
|
369
|
+
* disposes an editor, so by the time a dock repaint reaches `disposeFunctionEditor` the commit
|
|
370
|
+
* flushed here finds `tabIsLive(tab) === false` and writes nothing. Seven ways out, and the last
|
|
371
|
+
* two are fixed where the decision is made — {@link commitTabBuffers} runs the commit before the
|
|
372
|
+
* gate reads the tab, and {@link tabBufferUnsaved} is what the gate reads when the commit could not
|
|
373
|
+
* land it.
|
|
374
|
+
*
|
|
375
|
+
* The order is the whole point: flush WHILE THE BUFFER IS STILL ALIVE, then cancel, then dispose.
|
|
376
|
+
* Reversed, or deferred by so much as a microtask, the flush reads the empty string off a detached
|
|
377
|
+
* model and writes that — which is the defect this replaces, not a fix for it. The flushed callback
|
|
378
|
+
* must therefore read `getValue()` before its own first `await`, which both surfaces' commits do.
|
|
379
|
+
*
|
|
380
|
+
* Cancel-without-flush ({@link cancelBufferWrites}) stays correct in exactly one place, and only
|
|
381
|
+
* because the commit has already happened by other means: `closeFunctionEditor` reads the buffer
|
|
382
|
+
* itself and writes a MINIFIED body through the same writer, so the armed commit holds nothing but
|
|
383
|
+
* a raw copy of what was just written, and replaying it afterwards would undo the minify. Every
|
|
384
|
+
* other disposal path — `disposeFunctionEditor`, `disposeSourceEditor` and therefore
|
|
385
|
+
* `resetCanvasView`, the model-URI swap and the mode transition — flushes.
|
|
386
|
+
*
|
|
387
|
+
* **AND THE FLUSH IS ALLOWED TO FAIL, which is what the answer is for.** `bodyWriter` returns
|
|
388
|
+
* whether the body reached the document, and it returns `false` whenever the collab gate refuses
|
|
389
|
+
* the write, the tab is gone, or the element the handler hangs off has been deleted; the source
|
|
390
|
+
* view's parse simply declines to settle when the text does not parse. Throwing that answer away
|
|
391
|
+
* here made five exits — dock tab switch, dock collapse, ⌘, opening another Logic target, switching
|
|
392
|
+
* to another tab, plus the source view's model-URI swap and its mode transition — into silent
|
|
393
|
+
* deletions: the buffer holding the only copy is detached on the next line, and with it goes
|
|
394
|
+
* `buffersForTab`'s knowledge that it ever existed, so {@link tabBufferUnsaved} and every gate
|
|
395
|
+
* built on it go back to answering "nothing to lose" about text that has just been destroyed.
|
|
396
|
+
*
|
|
397
|
+
* `closeFunctionEditor` shows the other shape — a refused write keeps the surface, so the text
|
|
398
|
+
* stays on screen and the author can act. **A disposer has no such option.** Every one of its call
|
|
399
|
+
* sites is a repaint or a mode transition that has already replaced the container, and refusing
|
|
400
|
+
* would leave a live Monaco bound to detached DOM, which is the leak this module's teardown exists
|
|
401
|
+
* to prevent. So the disposers all choose the other answer: they cannot keep the text, and they say
|
|
402
|
+
* that it is gone ({@link reportDiscarded}).
|
|
403
|
+
*
|
|
404
|
+
* **The source view's commit answers late, and the report goes with it.** Its parse is an `await`,
|
|
405
|
+
* so the disposer is long finished by the time "did it land" has a value; the promise's
|
|
406
|
+
* continuation reports, and the synchronous return says only what is known at return time. A
|
|
407
|
+
* `false` therefore always means the loss is certain and has been announced.
|
|
408
|
+
*
|
|
409
|
+
* @param {MonacoBuffer | null | undefined} buffer
|
|
410
|
+
* @param {BufferSurface} surface Which editor is being destroyed — names the toast.
|
|
411
|
+
* @returns {boolean} True when the teardown carried everything the author typed, or there was
|
|
412
|
+
* nothing of theirs to carry. False when it destroyed the author's text, which is reported.
|
|
413
|
+
*/
|
|
414
|
+
export function commitBufferWrites(
|
|
415
|
+
buffer: MonacoBuffer | null | undefined,
|
|
416
|
+
surface: BufferSurface,
|
|
417
|
+
): boolean {
|
|
418
|
+
const writes = buffer?._writes;
|
|
419
|
+
if (!buffer || !writes) {
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
/* THE FLUSH IS GUARDED AND ITS TWIN'S WAS NOT, which is the whole difference between a failed
|
|
423
|
+
commit and a broken repaint. `bodyWriter`'s event branch resolves `editing.path` in the live
|
|
424
|
+
tree, and a collaborator's delete — or a local undo — makes that path resolve to nothing. The
|
|
425
|
+
throw escaped into `disposeFunctionEditor`, into `syncFunctionEditor`, and out of the dock
|
|
426
|
+
panel's `afterRender`: the repaint aborted mid-way, `cancel()` never ran, `dispose()` never
|
|
427
|
+
ran, and a live 500ms timer was left over an editor whose container lit was about to replace.
|
|
428
|
+
A timer reading `""` off a detached model is the exact defect this module was created to
|
|
429
|
+
prevent, so the teardown below must happen whatever the commit does. */
|
|
430
|
+
let pending: Promise<void> | undefined;
|
|
431
|
+
try {
|
|
432
|
+
// Fire-and-forget on purpose: the disposer is synchronous, and the commit read `getValue()`
|
|
433
|
+
// Before its own first `await`. What is left to await is the write INTO the document, which
|
|
434
|
+
// Does not need this editor to still exist.
|
|
435
|
+
pending = writes.flush(BUFFER_COMMIT) as Promise<void> | undefined;
|
|
436
|
+
} catch (error) {
|
|
437
|
+
writes.cancel();
|
|
438
|
+
return reportDiscarded(buffer, surface, { error });
|
|
439
|
+
}
|
|
440
|
+
writes.cancel();
|
|
441
|
+
if (typeof pending?.then === "function") {
|
|
442
|
+
void pending.then(
|
|
443
|
+
() => reportDiscarded(buffer, surface, null),
|
|
444
|
+
(error: unknown) => reportDiscarded(buffer, surface, { error }),
|
|
445
|
+
);
|
|
446
|
+
return true;
|
|
447
|
+
}
|
|
448
|
+
return reportDiscarded(buffer, surface, null);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Cancel the debounced work armed over `buffer`. Safe on null, and on a buffer that never armed any
|
|
453
|
+
* — which is why every disposal site can call it unconditionally.
|
|
454
|
+
*
|
|
455
|
+
* **This is not housekeeping, it is the correctness of the teardown.** A surviving timer reads
|
|
456
|
+
* `getValue()` off a disposed editor, gets `""`, and writes that into the document: for the dock it
|
|
457
|
+
* deletes a handler half a second after the surface closed, and for the source view it replaces a
|
|
458
|
+
* whole page body with an empty parse 600ms after the user left Code view.
|
|
459
|
+
*
|
|
460
|
+
* @param {MonacoBuffer | null | undefined} buffer
|
|
461
|
+
*/
|
|
462
|
+
export function cancelBufferWrites(buffer: MonacoBuffer | null | undefined): void {
|
|
463
|
+
buffer?._writes?.cancel();
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Whether `buffer` still has a model — i.e. whether there is a buffer at all.
|
|
468
|
+
*
|
|
469
|
+
* The question a write OUT of the buffer asks. `dispose()` runs `_detachModel()`, so this is false
|
|
470
|
+
* for a disposed editor and its `getValue()` is the empty string rather than the text it held.
|
|
471
|
+
*
|
|
472
|
+
* @param {MonacoBuffer | null | undefined} buffer
|
|
473
|
+
* @returns {boolean}
|
|
474
|
+
*/
|
|
475
|
+
export function bufferIsLive(buffer: MonacoBuffer | null | undefined): boolean {
|
|
476
|
+
return Boolean(buffer?.getModel());
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Whether this write must be dropped — the buffer moved on, or was never this write's to make. The
|
|
481
|
+
* five clauses are stated in this module's header.
|
|
482
|
+
*
|
|
483
|
+
* @param {MonacoBuffer} buffer The editor about to be written into.
|
|
484
|
+
* @param {string} [expected] What the caller read out of the buffer before its `await`, when it
|
|
485
|
+
* made one. Omitted by callers that computed their value from the DOCUMENT rather than from the
|
|
486
|
+
* buffer, for which clauses 1–3 and 5 are the whole question.
|
|
487
|
+
* @returns {boolean} True when the write must be dropped.
|
|
488
|
+
*/
|
|
489
|
+
export function bufferMovedOn(buffer: MonacoBuffer, expected?: string): boolean {
|
|
490
|
+
if (
|
|
491
|
+
!bufferIsLive(buffer) ||
|
|
492
|
+
buffer.hasTextFocus() ||
|
|
493
|
+
buffer._writes?.ahead() ||
|
|
494
|
+
buffer._writes?.shared()
|
|
495
|
+
) {
|
|
496
|
+
return true;
|
|
497
|
+
}
|
|
498
|
+
return expected !== undefined && buffer.getValue() !== expected;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* The Monaco buffers Studio currently has mounted for `tab`, and nothing else.
|
|
503
|
+
*
|
|
504
|
+
* Both surfaces are asked the same question through `_editingTab`, and both are asked clause 1
|
|
505
|
+
* first: a buffer whose model is detached answers `""` to everything and has no work left to hold.
|
|
506
|
+
*
|
|
507
|
+
* **The order is the SOURCE view first and the dock second, and it is load-bearing** — see
|
|
508
|
+
* {@link commitTabBuffers}, which commits them in this order one at a time.
|
|
509
|
+
*
|
|
510
|
+
* @param {Tab} tab
|
|
511
|
+
* @returns {MonacoBuffer[]}
|
|
512
|
+
*/
|
|
513
|
+
function buffersForTab(tab: Tab): MonacoBuffer[] {
|
|
514
|
+
const raw = toRaw(tab as object);
|
|
515
|
+
/* Every stage's source buffer, plus the dock's one function editor.
|
|
516
|
+
The source view is per-PANE now: two Code panes are two Monaco instances over two documents,
|
|
517
|
+
and a close gate that asked only "the" source editor would have missed whichever one it was
|
|
518
|
+
not holding — which is the same half-answer that let ⌘W close a tab over unprompted typing. */
|
|
519
|
+
const mounted: (MonacoBuffer | null)[] = [
|
|
520
|
+
...allCanvasSurfaces().map((surface) => surface.monacoEditor as MonacoBuffer | null),
|
|
521
|
+
view.functionEditor,
|
|
522
|
+
];
|
|
523
|
+
return mounted.filter(
|
|
524
|
+
(buffer): buffer is MonacoBuffer =>
|
|
525
|
+
buffer != null &&
|
|
526
|
+
buffer._editingTab != null &&
|
|
527
|
+
toRaw(buffer._editingTab as object) === raw &&
|
|
528
|
+
bufferIsLive(buffer),
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
/**
|
|
533
|
+
* Carry what `tab`'s buffers are holding into the document, leaving the buffers standing.
|
|
534
|
+
*
|
|
535
|
+
* **The close path is a WRITE before it is a question.** `shouldWarnOnClose` and `hasUnsavedTabs`
|
|
536
|
+
* both read `tab.doc.dirty`, and nothing makes a buffer's armed commit dirty a document it has not
|
|
537
|
+
* reached yet — so typing the last character of a handler and pressing ⌘W closed the tab with no
|
|
538
|
+
* prompt at all and took the last 500ms (dock) / 600ms (source) with it. No disposer can cover
|
|
539
|
+
* this: `closeTab` deletes the tab first, and every commit checks `tabIsLive` precisely so that it
|
|
540
|
+
* will not write into a tab nobody can read. The flush therefore has to happen while the tab is
|
|
541
|
+
* still open, which is here, before the gate.
|
|
542
|
+
*
|
|
543
|
+
* **The two commits run ONE AT A TIME, source first, because they are not independent.** Both
|
|
544
|
+
* surfaces can be open on one tab — the dock's Logic editor is not a canvas mode, so a handler body
|
|
545
|
+
* and the page source are editable side by side — and their two commits write at different
|
|
546
|
+
* granularities. The dock's writes ONE body at `editing.path`; the source view's parses the whole
|
|
547
|
+
* buffer and assigns a whole new `tab.doc.document`. Started together, the dock's synchronous write
|
|
548
|
+
* lands first and the source's post-await assign — computed from buffer text that predates it —
|
|
549
|
+
* replaces the entire tree on top of it, so the body the author just typed is gone with no trace.
|
|
550
|
+
*
|
|
551
|
+
* Reversed, both survive: `bodyWriter` re-reads `tab.doc.document` and re-resolves `editing.path`
|
|
552
|
+
* at call time, so the dock's targeted write applies to whatever tree is there when it runs. The
|
|
553
|
+
* whole-document assign is the destructive direction and therefore goes first; the body write goes
|
|
554
|
+
* last and is the only one that can be the last word. (Two independent timers could already race
|
|
555
|
+
* this either way — but a race that used to be a coin toss is not a licence to make the losing side
|
|
556
|
+
* certain.)
|
|
557
|
+
*
|
|
558
|
+
* **It is always a promise, and callers `await` it.** It used to return `Promise<void> | void` so
|
|
559
|
+
* that a tab with nothing armed closed without spending a microtask. Nothing a user can perceive
|
|
560
|
+
* distinguishes zero microtasks from one; the entire cost was five synchronous assertions in
|
|
561
|
+
* `tests/shortcuts.test.ts`, and the entire price was a three-line `const committing = …; if
|
|
562
|
+
* (committing) await committing;` dance in every present and future caller — which is exactly the
|
|
563
|
+
* shape that let {@link commitTabBuffers}'s one existing caller forget to re-check its tab across
|
|
564
|
+
* the await.
|
|
565
|
+
*
|
|
566
|
+
* Deliberately NOT {@link commitBufferWrites}: that one cancels afterwards, which is right for a
|
|
567
|
+
* disposer and wrong here. The tab may well survive this call — the author can press Cancel on the
|
|
568
|
+
* dialog the flush just caused to appear — and its buffers must still have their lint armed.
|
|
569
|
+
*
|
|
570
|
+
* **AND IT NEVER REJECTS, because its callers are gestures.** `requestClose` awaits this before it
|
|
571
|
+
* asks anything, and a rejection there propagated out of an `async` function nobody awaits: ⌘W did
|
|
572
|
+
* nothing — no prompt, no close, no message, an unhandled rejection in the console. The other two
|
|
573
|
+
* callers (`openTab`'s preview replacement, the project switch) do not even await it, so a
|
|
574
|
+
* rejection is invisible by construction. A commit is also not all-or-nothing: the source view's
|
|
575
|
+
* commit and the dock's write to DIFFERENT places, so the first one throwing must not cost the
|
|
576
|
+
* second its turn — which is why the guard is per buffer rather than around the loop.
|
|
577
|
+
*
|
|
578
|
+
* What a failed commit leaves behind is the honest state, not a lost one: `markSettled` never ran,
|
|
579
|
+
* so the buffer is still `typed()`, {@link tabBufferUnsaved} still answers yes, and the gate above
|
|
580
|
+
* the close still prompts. This function's remaining job is to make sure somebody is told, which is
|
|
581
|
+
* a problem rather than a toast: an author who reads it minutes later still needs to know that one
|
|
582
|
+
* document's editor holds text the file does not.
|
|
583
|
+
*
|
|
584
|
+
* @param {Tab} tab
|
|
585
|
+
* @returns {Promise<void>} Resolves when every commit this flush started has finished or failed.
|
|
586
|
+
*/
|
|
587
|
+
export async function commitTabBuffers(tab: Tab): Promise<void> {
|
|
588
|
+
for (const buffer of buffersForTab(tab)) {
|
|
589
|
+
/* Clause 1, RE-ASKED, because the list was taken before the previous commit's await. Assigning
|
|
590
|
+
a new document repaints the dock, and a repaint can dispose the function editor — whose
|
|
591
|
+
`getValue()` then answers `""`. Committing that is the deletion this module exists to
|
|
592
|
+
prevent. (The ordinary teardown already flushed and cancelled, so this usually finds nothing
|
|
593
|
+
armed; "usually" is not a guard.) */
|
|
594
|
+
if (!bufferIsLive(buffer)) {
|
|
595
|
+
continue;
|
|
596
|
+
}
|
|
597
|
+
try {
|
|
598
|
+
await buffer._writes?.flush(BUFFER_COMMIT);
|
|
599
|
+
} catch (error) {
|
|
600
|
+
notify.error(
|
|
601
|
+
`Could not save the editor's contents into "${tab.documentPath ?? "Untitled"}".`,
|
|
602
|
+
{
|
|
603
|
+
detail: error instanceof Error ? (error.stack ?? error.message) : String(error),
|
|
604
|
+
key: `buffer-commit:${tab.id}`,
|
|
605
|
+
source: "Editor",
|
|
606
|
+
...(tab.documentPath ? { path: tab.documentPath } : {}),
|
|
607
|
+
},
|
|
608
|
+
);
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Whether a buffer of `tab`'s holds the user's own typing that the document has not been given.
|
|
615
|
+
*
|
|
616
|
+
* What the unsaved-work gates ask AFTER {@link commitTabBuffers}, because a commit is allowed to
|
|
617
|
+
* fail to land: unparseable source deliberately leaves the buffer ahead rather than resyncing over
|
|
618
|
+
* a half-typed heading, and that text exists nowhere but the buffer. `typed()` rather than
|
|
619
|
+
* `ahead()` — the header says why counting format-on-open as unsaved work would warn every author
|
|
620
|
+
* who merely opened a handler.
|
|
621
|
+
*
|
|
622
|
+
* @param {Tab} tab
|
|
623
|
+
* @returns {boolean}
|
|
624
|
+
*/
|
|
625
|
+
export function tabBufferUnsaved(tab: Tab): boolean {
|
|
626
|
+
return buffersForTab(tab).some((buffer) => buffer._writes?.typed() === true);
|
|
627
|
+
}
|
|
@@ -109,6 +109,42 @@ export function loadedMonaco(): Monaco | null {
|
|
|
109
109
|
return _monaco;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
/**
|
|
113
|
+
* Whether the mount started before {@link loadMonaco} is still the one the app wants.
|
|
114
|
+
*
|
|
115
|
+
* **The question you owe on the far side of the load, which is why it lives beside the load.** A
|
|
116
|
+
* cold `loadMonaco()` is a 12.6 MB dynamic import; the user has hundreds of milliseconds inside it
|
|
117
|
+
* to close the surface, retarget it, or leave the mode — and a second render has the same window to
|
|
118
|
+
* start a mount of its own. Both surfaces need it and each had its own spelling of it, so the
|
|
119
|
+
* source view simply did without: `renderCanvasImpl` assigns `surface.prevCanvasMode` BEFORE it
|
|
120
|
+
* reaches the mount, so a second synchronous `renderCanvas()` during the load sees `modeChanged ===
|
|
121
|
+
* false` and a null `view.monacoEditor`, skips the fast path and mounts again. The second mount
|
|
122
|
+
* then calls `createModel` with a URI the first already registered, which real Monaco throws on
|
|
123
|
+
* ("ERR: Another model with the same URI"), and `store.ts`'s `render()`/`renderOnly()` coalesce
|
|
124
|
+
* nothing.
|
|
125
|
+
*
|
|
126
|
+
* Returning false means creating NOTHING — no model, no editor, no `automaticLayout` observer. That
|
|
127
|
+
* is strictly better than creating and disposing: there is no window in which a doomed editor is
|
|
128
|
+
* attached to anything, and no URI briefly claimed by a model nobody holds.
|
|
129
|
+
*
|
|
130
|
+
* @param {Element | null | undefined} container Where the editor was going to be created.
|
|
131
|
+
* @param {unknown} existing The surface's editor handle (`view.monacoEditor` /
|
|
132
|
+
* `view.functionEditor`). Non-null means another mount already won this race.
|
|
133
|
+
* @param {() => boolean} stillTargeted Whether the app still wants exactly THIS mount — the same
|
|
134
|
+
* tab, the same mode, the same editing target. Lazy: the two cheap clauses above answer first.
|
|
135
|
+
* @returns {boolean}
|
|
136
|
+
*/
|
|
137
|
+
export function mountStillWanted(
|
|
138
|
+
container: Element | null | undefined,
|
|
139
|
+
existing: unknown,
|
|
140
|
+
stillTargeted: () => boolean,
|
|
141
|
+
): boolean {
|
|
142
|
+
if (!container?.isConnected || existing) {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
return stillTargeted();
|
|
146
|
+
}
|
|
147
|
+
|
|
112
148
|
/** Whether Monaco has been loaded (tests, and the odd diagnostic). */
|
|
113
149
|
export function isMonacoLoaded(): boolean {
|
|
114
150
|
return _monaco !== null;
|