@jxsuite/studio 2.0.0 → 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-0hzentwq.js → components-2bpqp4mq.js} +5 -7
- package/dist/chunks/{components-0hzentwq.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-2c188n41.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-97qcgvdn.js → studio-adn0z1k3.js} +14 -8
- package/dist/chunks/{studio-97qcgvdn.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-hhj92gcr.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-ygzav7z5.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-n9zmpxjg.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 +23711 -35185
- 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 +34 -9
- package/src/new-project/location-fields.ts +9 -1
- package/src/new-project/new-project-modal.ts +270 -262
- 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 -120
- 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 +75 -2
- 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 +113 -0
- 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-qct0gt8f.js +0 -531
- package/dist/chunks/extension-sections-qct0gt8f.js.map +0 -12
- package/dist/chunks/files-02zn5y24.js +0 -50
- package/dist/chunks/files-02zn5y24.js.map +0 -9
- package/dist/chunks/git-panel-mbb0y0hz.js +0 -28
- package/dist/chunks/git-panel-mbb0y0hz.js.map +0 -9
- package/dist/chunks/layers-panel-9qekmqk1.js +0 -26
- package/dist/chunks/layers-panel-9qekmqk1.js.map +0 -9
- package/dist/chunks/settings-modal-gj0hrah4.js +0 -29
- package/dist/chunks/settings-modal-gj0hrah4.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-4k0m4fmp.js.map +0 -13
- package/dist/chunks/studio-568nx4t5.js +0 -469
- package/dist/chunks/studio-568nx4t5.js.map +0 -14
- package/dist/chunks/studio-9smfzqhc.js +0 -19
- package/dist/chunks/studio-9smfzqhc.js.map +0 -10
- package/dist/chunks/studio-asvf0j9e.js +0 -429
- package/dist/chunks/studio-asvf0j9e.js.map +0 -13
- package/dist/chunks/studio-d77qm3pk.js +0 -3427
- package/dist/chunks/studio-d77qm3pk.js.map +0 -26
- package/dist/chunks/studio-j6gqyjf4.js +0 -1854
- package/dist/chunks/studio-j6gqyjf4.js.map +0 -18
- package/dist/chunks/studio-n9zmpxjg.js.map +0 -115
- package/dist/chunks/studio-qkvt37c0.js.map +0 -9
- package/dist/chunks/studio-rq0vxb19.js +0 -3744
- package/dist/chunks/studio-rq0vxb19.js.map +0 -22
- package/dist/chunks/studio-wd6ewdvd.js +0 -853
- package/dist/chunks/studio-wd6ewdvd.js.map +0 -12
- 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-ygzav7z5.js.map +0 -46
- package/dist/chunks/studio-zmy7241e.js +0 -399
- package/dist/chunks/studio-zmy7241e.js.map +0 -12
- 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-2c188n41.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-hhj92gcr.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,427 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
/**
|
|
3
|
+
* Contexts — the ONE place a project's rendering contexts are defined (plan §4.2, §2 principle 5).
|
|
4
|
+
*
|
|
5
|
+
* A rendering context is anything the document can be resolved _under_: a size breakpoint, a colour
|
|
6
|
+
* scheme, a feature query. All three are the same thing on disk — one entry in `project.json`'s
|
|
7
|
+
* `$media` map — and until this section existed they were **defined in four places**, none of which
|
|
8
|
+
* said "context" and only one of which said "breakpoint":
|
|
9
|
+
*
|
|
10
|
+
* 1. The New Project wizard's four breakpoint presets, described by media-query direction;
|
|
11
|
+
* 2. Settings › General's "Breakpoints" field;
|
|
12
|
+
* 3. Properties › Media, which renders **only when the document root is selected** — so adding a
|
|
13
|
+
* breakpoint cost you your element selection, the Style tab's scope, and your place;
|
|
14
|
+
* 4. The CSS Variables editor's "Enable dark scheme" button, which appended `'--dark':
|
|
15
|
+
* '(prefers-color-scheme: dark)'` to `$media` **without ever using the word breakpoint**, so the
|
|
16
|
+
* one control that created a colour scheme was filed under variables.
|
|
17
|
+
*
|
|
18
|
+
* All four are gone. §2 principle 5 is why: **definition and selection are different levels.** The
|
|
19
|
+
* pane context bar's Rendering-context control _selects_ among these; its popover's footer is
|
|
20
|
+
* "Manage contexts…", which opens this section. Nothing here selects, and nothing there defines.
|
|
21
|
+
*
|
|
22
|
+
* **This section writes the project, not the document.** Per-document `$media` overlays still merge
|
|
23
|
+
* at render time (`site-context.ts`'s `getEffectiveMedia`) and the on-disk format is unchanged —
|
|
24
|
+
* what moved is the authoring surface, from a panel that required a selection to a settings section
|
|
25
|
+
* that requires only a project.
|
|
26
|
+
*
|
|
27
|
+
* Failures are surfaced, not swallowed: every write runs through {@link persistMedia}, which
|
|
28
|
+
* schema-validates the candidate `project.json` with `jx-validate` first (the human editing this
|
|
29
|
+
* file used to get **no validation at all** — that was wired to the AI's `write_project_config`
|
|
30
|
+
* alone), parks any rejection under the control that caused it, and re-renders. §7.1's third tier:
|
|
31
|
+
* a bad value belongs at its control, not in a toast that expires.
|
|
32
|
+
*
|
|
33
|
+
* @docs studio/projects/pages-layouts-components
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import { html, render as litRender, nothing } from "lit-html";
|
|
37
|
+
import { live } from "lit-html/directives/live.js";
|
|
38
|
+
import { errorMessage } from "@jxsuite/schema/parse";
|
|
39
|
+
import { projectState } from "../store";
|
|
40
|
+
import { updateSiteConfig } from "../site-context";
|
|
41
|
+
import { validateProjectConfig } from "../services/jx-validate";
|
|
42
|
+
import { isSchemeQuery, schemeOfQuery } from "../utils/canvas-media";
|
|
43
|
+
import { mediaDisplayName } from "../panels/shared";
|
|
44
|
+
|
|
45
|
+
import type { ProjectConfig } from "@jxsuite/schema/types";
|
|
46
|
+
|
|
47
|
+
/** The `$media` key the base width lives under. Not a query — a number of CSS pixels. */
|
|
48
|
+
const BASE_KEY = "--";
|
|
49
|
+
|
|
50
|
+
/** What a new size breakpoint starts as. Narrow-first, because `--` is the widest canvas. */
|
|
51
|
+
const NEW_BREAKPOINT_QUERY = "(max-width: 768px)";
|
|
52
|
+
|
|
53
|
+
/** What a new feature query starts as — a real query, so the row is valid the moment it exists. */
|
|
54
|
+
const NEW_FEATURE_QUERY = "(prefers-reduced-motion: reduce)";
|
|
55
|
+
|
|
56
|
+
/** The three kinds of context, in the order they are defined and rendered. */
|
|
57
|
+
export type ContextKind = "size" | "scheme" | "feature";
|
|
58
|
+
|
|
59
|
+
/** One `$media` entry, classified. */
|
|
60
|
+
export interface ContextEntry {
|
|
61
|
+
key: string;
|
|
62
|
+
query: string;
|
|
63
|
+
kind: ContextKind;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Which kind of context an entry is.
|
|
68
|
+
*
|
|
69
|
+
* The classification is the same one the canvas makes (`utils/canvas-media.ts`) so a row defined
|
|
70
|
+
* here lands in the group the pane context bar will show it in — a size breakpoint gets a canvas
|
|
71
|
+
* width, a scheme query drives the Auto/Light/Dark segment, and everything else is a plain toggle.
|
|
72
|
+
* Deriving it from the query rather than storing it is what keeps the on-disk format unchanged.
|
|
73
|
+
*
|
|
74
|
+
* @param {string} query
|
|
75
|
+
* @returns {ContextKind}
|
|
76
|
+
*/
|
|
77
|
+
export function contextKindOf(query: string): ContextKind {
|
|
78
|
+
if (isSchemeQuery(query)) {
|
|
79
|
+
return "scheme";
|
|
80
|
+
}
|
|
81
|
+
return /(?:min|max)-width:/.test(query) ? "size" : "feature";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Split a `$media` map into the base width and the three context groups.
|
|
86
|
+
*
|
|
87
|
+
* @param {Record<string, string> | undefined} media
|
|
88
|
+
* @returns {{ base: string; entries: ContextEntry[] }}
|
|
89
|
+
*/
|
|
90
|
+
export function splitContexts(media?: Record<string, string> | undefined): {
|
|
91
|
+
base: string;
|
|
92
|
+
entries: ContextEntry[];
|
|
93
|
+
} {
|
|
94
|
+
const source = media ?? {};
|
|
95
|
+
const entries: ContextEntry[] = [];
|
|
96
|
+
for (const [key, raw] of Object.entries(source)) {
|
|
97
|
+
if (key === BASE_KEY) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const query = String(raw);
|
|
101
|
+
entries.push({ key, kind: contextKindOf(query), query });
|
|
102
|
+
}
|
|
103
|
+
return { base: String(source[BASE_KEY] ?? ""), entries };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The `$media` key a typed name becomes: `Wide screen` → `--wide-screen`.
|
|
108
|
+
*
|
|
109
|
+
* Typing the leading dashes is allowed but never required — the dashes are a storage detail of the
|
|
110
|
+
* CSS custom-property namespace `$media` shares, and §8.4 says a definition surface may not make
|
|
111
|
+
* the user spell one.
|
|
112
|
+
*
|
|
113
|
+
* @param {string} name
|
|
114
|
+
* @returns {string}
|
|
115
|
+
*/
|
|
116
|
+
export function contextKeyOf(name: string): string {
|
|
117
|
+
const slug = name
|
|
118
|
+
.trim()
|
|
119
|
+
.replace(/^-+/, "")
|
|
120
|
+
.toLowerCase()
|
|
121
|
+
.replaceAll(/[^a-z0-9]+/g, "-")
|
|
122
|
+
.replaceAll(/^-+|-+$/g, "");
|
|
123
|
+
return slug ? `--${slug}` : "";
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ─── Per-container error state ────────────────────────────────────────────────
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Where an error belongs. A `$media` key addresses the row it came from; `"base"` is the base-width
|
|
130
|
+
* field and `"section"` is a whole-file write failure with no single guilty control.
|
|
131
|
+
*/
|
|
132
|
+
type ErrorTarget = string;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* The last failure, per rendered container.
|
|
136
|
+
*
|
|
137
|
+
* Keyed by container so two mounted copies (and two tests) never share a message — the same reason
|
|
138
|
+
* `general-settings.ts` does it. One at a time is deliberate: settings persist on change, so there
|
|
139
|
+
* is exactly one write in flight and exactly one thing that just went wrong.
|
|
140
|
+
*/
|
|
141
|
+
const errors = new WeakMap<HTMLElement, { target: ErrorTarget; message: string }>();
|
|
142
|
+
|
|
143
|
+
/** Read the current failure for a container — exported for tests and for re-render helpers. */
|
|
144
|
+
export function contextsError(container: HTMLElement): { target: string; message: string } | null {
|
|
145
|
+
return errors.get(container) ?? null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ─── Render ───────────────────────────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Render Project Settings › Contexts into `container`.
|
|
152
|
+
*
|
|
153
|
+
* @param {HTMLElement} container
|
|
154
|
+
*/
|
|
155
|
+
export function renderContextsSection(container: HTMLElement): void {
|
|
156
|
+
const config = (projectState?.projectConfig || {}) as ProjectConfig;
|
|
157
|
+
const media = (config.$media || {}) as Record<string, string>;
|
|
158
|
+
const shown = errors.get(container);
|
|
159
|
+
const { base, entries } = splitContexts(media);
|
|
160
|
+
|
|
161
|
+
/** Park a failure under one control without writing anything. */
|
|
162
|
+
const reject = (target: ErrorTarget, message: string) => {
|
|
163
|
+
errors.set(container, { message, target });
|
|
164
|
+
renderContextsSection(container);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Write a whole `$media` map, validating it first and surfacing whatever refuses it.
|
|
169
|
+
*
|
|
170
|
+
* Both failure modes are real and both used to be invisible here: the schema can refuse the
|
|
171
|
+
* shape, and the disk can refuse the write. The predecessor surfaces (`Properties › Media` and
|
|
172
|
+
* the CSS-variables "Enable dark scheme") did `void updateSiteConfig(...)` and dropped the
|
|
173
|
+
* rejection, so a failed write read as the field snapping back for no reason.
|
|
174
|
+
*/
|
|
175
|
+
const persistMedia = async (next: Record<string, string>, target: ErrorTarget) => {
|
|
176
|
+
const candidate = { ...config, $media: next } as unknown;
|
|
177
|
+
let schemaErrors: string[] = [];
|
|
178
|
+
try {
|
|
179
|
+
schemaErrors = await validateProjectConfig(candidate);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
/* A validator that cannot compile must not block the edit — but it must not be silent
|
|
182
|
+
either, so the message rides the same inline slot the schema errors would have used. */
|
|
183
|
+
reject(target, `Could not validate project.json — ${errorMessage(error)}`);
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (schemaErrors.length > 0) {
|
|
187
|
+
reject(target, schemaErrors.join("; "));
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
await updateSiteConfig({ $media: next });
|
|
192
|
+
errors.delete(container);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
errors.set(container, {
|
|
195
|
+
message: `Could not save project.json — ${errorMessage(error)}`,
|
|
196
|
+
target,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
renderContextsSection(container);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/** The error line for one control, when that is where the current failure belongs. */
|
|
203
|
+
const errorFor = (target: ErrorTarget) =>
|
|
204
|
+
shown?.target === target
|
|
205
|
+
? html`<p class="settings-field-error" role="alert">${shown.message}</p>`
|
|
206
|
+
: nothing;
|
|
207
|
+
|
|
208
|
+
// ─── Base width ────────────────────────────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
const onBaseChange = (e: Event) => {
|
|
211
|
+
const value = (e.target as HTMLInputElement).value.trim();
|
|
212
|
+
if (value && !/^\d+px$/.test(value)) {
|
|
213
|
+
reject("base", "Enter a width in pixels, like 1280px.");
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
const next = { ...media };
|
|
217
|
+
if (value) {
|
|
218
|
+
next[BASE_KEY] = value;
|
|
219
|
+
} else {
|
|
220
|
+
delete next[BASE_KEY];
|
|
221
|
+
}
|
|
222
|
+
void persistMedia(next, "base");
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// ─── Row editing ───────────────────────────────────────────────────────────
|
|
226
|
+
|
|
227
|
+
const onRename = (oldKey: string) => (e: Event) => {
|
|
228
|
+
const newKey = contextKeyOf((e.target as HTMLInputElement).value);
|
|
229
|
+
if (!newKey) {
|
|
230
|
+
reject(oldKey, "A context needs a name.");
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
if (newKey === oldKey) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
if (newKey in media) {
|
|
237
|
+
reject(oldKey, `"${mediaDisplayName(newKey)}" is already defined.`);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
/* Rebuild in place so renaming never reorders the list — the order is the order the pane
|
|
241
|
+
context bar offers them in, and a rename is not a reordering. */
|
|
242
|
+
const next: Record<string, string> = {};
|
|
243
|
+
for (const [key, value] of Object.entries(media)) {
|
|
244
|
+
next[key === oldKey ? newKey : key] = value;
|
|
245
|
+
}
|
|
246
|
+
void persistMedia(next, newKey);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const onQueryChange = (key: string) => (e: Event) => {
|
|
250
|
+
const query = (e.target as HTMLInputElement).value.trim();
|
|
251
|
+
if (!query) {
|
|
252
|
+
reject(key, "A context needs a media query, like (max-width: 768px).");
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
void persistMedia({ ...media, [key]: query }, key);
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const onRemove = (key: string) => () => {
|
|
259
|
+
const next = { ...media };
|
|
260
|
+
delete next[key];
|
|
261
|
+
void persistMedia(next, "section");
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
/** Add an entry under the first free `--<stem>` / `--<stem>-2` … name. */
|
|
265
|
+
const add = (stem: string, query: string) => () => {
|
|
266
|
+
let key = `--${stem}`;
|
|
267
|
+
let n = 2;
|
|
268
|
+
while (key in media) {
|
|
269
|
+
key = `--${stem}-${n}`;
|
|
270
|
+
n += 1;
|
|
271
|
+
}
|
|
272
|
+
void persistMedia({ ...media, [key]: query }, key);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
/** The scheme picker writes the canonical query, so a scheme is never mistyped into a feature. */
|
|
276
|
+
const onSchemeChange = (key: string) => (e: Event) => {
|
|
277
|
+
const scheme = (e.target as HTMLInputElement).value;
|
|
278
|
+
void persistMedia({ ...media, [key]: `(prefers-color-scheme: ${scheme})` }, key);
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
// ─── Group templates ───────────────────────────────────────────────────────
|
|
282
|
+
|
|
283
|
+
const removeButton = (key: string) => html`
|
|
284
|
+
<sp-action-button
|
|
285
|
+
size="s"
|
|
286
|
+
quiet
|
|
287
|
+
data-remove=${key}
|
|
288
|
+
title="Remove ${mediaDisplayName(key)}"
|
|
289
|
+
@click=${onRemove(key)}
|
|
290
|
+
>
|
|
291
|
+
×
|
|
292
|
+
</sp-action-button>
|
|
293
|
+
`;
|
|
294
|
+
|
|
295
|
+
const nameField = (key: string) => html`
|
|
296
|
+
<sp-textfield
|
|
297
|
+
size="s"
|
|
298
|
+
class="settings-media-name"
|
|
299
|
+
.value=${live(key.replace(/^--/, ""))}
|
|
300
|
+
placeholder="name"
|
|
301
|
+
?invalid=${shown?.target === key}
|
|
302
|
+
@change=${onRename(key)}
|
|
303
|
+
></sp-textfield>
|
|
304
|
+
`;
|
|
305
|
+
|
|
306
|
+
const queryRow = (entry: ContextEntry) => html`
|
|
307
|
+
<div class="settings-media-row" data-context=${entry.key}>
|
|
308
|
+
${nameField(entry.key)}
|
|
309
|
+
<sp-textfield
|
|
310
|
+
size="s"
|
|
311
|
+
class="settings-media-value"
|
|
312
|
+
.value=${live(entry.query)}
|
|
313
|
+
placeholder=${NEW_BREAKPOINT_QUERY}
|
|
314
|
+
?invalid=${shown?.target === entry.key}
|
|
315
|
+
@change=${onQueryChange(entry.key)}
|
|
316
|
+
></sp-textfield>
|
|
317
|
+
${removeButton(entry.key)}
|
|
318
|
+
</div>
|
|
319
|
+
${errorFor(entry.key)}
|
|
320
|
+
`;
|
|
321
|
+
|
|
322
|
+
const schemeRow = (entry: ContextEntry) => html`
|
|
323
|
+
<div class="settings-media-row" data-context=${entry.key}>
|
|
324
|
+
${nameField(entry.key)}
|
|
325
|
+
<sp-picker
|
|
326
|
+
size="s"
|
|
327
|
+
class="settings-media-value"
|
|
328
|
+
label="Scheme"
|
|
329
|
+
.value=${schemeOfQuery(entry.query) ?? "dark"}
|
|
330
|
+
@change=${onSchemeChange(entry.key)}
|
|
331
|
+
>
|
|
332
|
+
<sp-menu-item value="light">Light</sp-menu-item>
|
|
333
|
+
<sp-menu-item value="dark">Dark</sp-menu-item>
|
|
334
|
+
</sp-picker>
|
|
335
|
+
${removeButton(entry.key)}
|
|
336
|
+
</div>
|
|
337
|
+
${errorFor(entry.key)}
|
|
338
|
+
`;
|
|
339
|
+
|
|
340
|
+
const group = (
|
|
341
|
+
kind: ContextKind,
|
|
342
|
+
label: string,
|
|
343
|
+
desc: string,
|
|
344
|
+
row: (entry: ContextEntry) => unknown,
|
|
345
|
+
addLabel: string,
|
|
346
|
+
onAdd: () => void,
|
|
347
|
+
empty: string,
|
|
348
|
+
) => {
|
|
349
|
+
const rows = entries.filter((entry) => entry.kind === kind);
|
|
350
|
+
return html`
|
|
351
|
+
<div class="settings-field" data-context-group=${kind}>
|
|
352
|
+
<label class="settings-field-label">${label}</label>
|
|
353
|
+
<p class="settings-field-desc">${desc}</p>
|
|
354
|
+
<div class="settings-media-list">
|
|
355
|
+
${
|
|
356
|
+
rows.length === 0
|
|
357
|
+
? html`<p class="settings-field-desc">${empty}</p>`
|
|
358
|
+
: rows.map((entry) => row(entry))
|
|
359
|
+
}
|
|
360
|
+
</div>
|
|
361
|
+
<sp-action-button size="s" data-add=${kind} @click=${onAdd}>${addLabel}</sp-action-button>
|
|
362
|
+
</div>
|
|
363
|
+
`;
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
const tpl = html`
|
|
367
|
+
<div class="settings-section">
|
|
368
|
+
<h3 class="settings-section-title">Contexts</h3>
|
|
369
|
+
<p class="settings-field-desc">
|
|
370
|
+
The conditions this project's pages are rendered under. Define them once here; choose
|
|
371
|
+
between them on the pane's context control while you edit.
|
|
372
|
+
</p>
|
|
373
|
+
${errorFor("section")}
|
|
374
|
+
|
|
375
|
+
<div class="settings-field">
|
|
376
|
+
<label class="settings-field-label">Base width</label>
|
|
377
|
+
<p class="settings-field-desc">
|
|
378
|
+
How wide the canvas is when no other context applies. Styles written here apply
|
|
379
|
+
everywhere.
|
|
380
|
+
</p>
|
|
381
|
+
<div class="settings-media-row">
|
|
382
|
+
<span class="settings-media-name-fixed">Base</span>
|
|
383
|
+
<sp-textfield
|
|
384
|
+
size="s"
|
|
385
|
+
class="settings-media-value"
|
|
386
|
+
data-context="base"
|
|
387
|
+
placeholder="1280px"
|
|
388
|
+
.value=${live(base)}
|
|
389
|
+
?invalid=${shown?.target === "base"}
|
|
390
|
+
@change=${onBaseChange}
|
|
391
|
+
></sp-textfield>
|
|
392
|
+
</div>
|
|
393
|
+
${errorFor("base")}
|
|
394
|
+
</div>
|
|
395
|
+
|
|
396
|
+
${group(
|
|
397
|
+
"size",
|
|
398
|
+
"Size breakpoints",
|
|
399
|
+
"Screen widths that get their own canvas and their own style values.",
|
|
400
|
+
queryRow,
|
|
401
|
+
"+ Add breakpoint",
|
|
402
|
+
add("breakpoint", NEW_BREAKPOINT_QUERY),
|
|
403
|
+
"No breakpoints yet — every width uses the base styles.",
|
|
404
|
+
)}
|
|
405
|
+
${group(
|
|
406
|
+
"scheme",
|
|
407
|
+
"Colour schemes",
|
|
408
|
+
"Light and dark renderings. Defining one turns on the Auto / Light / Dark control.",
|
|
409
|
+
schemeRow,
|
|
410
|
+
"+ Add colour scheme",
|
|
411
|
+
add("dark", "(prefers-color-scheme: dark)"),
|
|
412
|
+
"No colour schemes yet — the project renders one way.",
|
|
413
|
+
)}
|
|
414
|
+
${group(
|
|
415
|
+
"feature",
|
|
416
|
+
"Feature queries",
|
|
417
|
+
"Anything else a media query can ask: reduced motion, print, hover, orientation.",
|
|
418
|
+
queryRow,
|
|
419
|
+
"+ Add feature query",
|
|
420
|
+
add("feature", NEW_FEATURE_QUERY),
|
|
421
|
+
"No feature queries yet.",
|
|
422
|
+
)}
|
|
423
|
+
</div>
|
|
424
|
+
`;
|
|
425
|
+
|
|
426
|
+
litRender(tpl, container);
|
|
427
|
+
}
|
|
@@ -9,11 +9,17 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { html, nothing, render as litRender } from "lit-html";
|
|
12
|
+
import { errorMessage } from "@jxsuite/schema/parse";
|
|
12
13
|
import { getPlatform } from "../platform";
|
|
14
|
+
import { commitProjectConfig } from "../tabs/project-config";
|
|
13
15
|
import { projectState } from "../store";
|
|
14
16
|
import { renderForm } from "../ui/schema-form";
|
|
15
17
|
import { resolveContextPointer } from "../services/context-resolver";
|
|
16
18
|
import { deriveSecretEnvName } from "../services/data-service";
|
|
19
|
+
import { validateProjectConfig } from "../services/jx-validate";
|
|
20
|
+
import { notify } from "../services/notify";
|
|
21
|
+
import { paneRegion } from "../ui/regions";
|
|
22
|
+
import { paneOfContainer } from "../canvas/canvas-surface";
|
|
17
23
|
|
|
18
24
|
import type { TemplateResult } from "lit-html";
|
|
19
25
|
import type { JsonSchema, SchemaFormContext } from "../ui/schema-form";
|
|
@@ -77,12 +83,134 @@ export function resetContributedSectionState(): void {
|
|
|
77
83
|
newEntryNames.clear();
|
|
78
84
|
}
|
|
79
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Select an entry of a map-layout section by key — the addressable form of clicking an entry row.
|
|
88
|
+
*
|
|
89
|
+
* Five screenshot steps used to reach these rows by typing an empty string into a hand-stamped
|
|
90
|
+
* region id, carrying `unstable: { reason: "\"settings.selectEntry\" has no command record", until:
|
|
91
|
+
* "P6.2" }`. This is P6.2 and the record is `settings.open {section, entry}`: an entry is a KEY in
|
|
92
|
+
* the section's own map, so naming it is naming state rather than reproducing a gesture.
|
|
93
|
+
*
|
|
94
|
+
* @param {string} sectionKey
|
|
95
|
+
* @param {string} entryKey
|
|
96
|
+
* @returns {string[] | null} `null` once selected; the section's actual entry keys when it has no
|
|
97
|
+
* such entry, so the caller's refusal can name both sides.
|
|
98
|
+
*/
|
|
99
|
+
export function selectContributedEntry(sectionKey: string, entryKey: string): string[] | null {
|
|
100
|
+
const section = (projectState?.projectConfig as Record<string, unknown> | null | undefined)?.[
|
|
101
|
+
sectionKey
|
|
102
|
+
];
|
|
103
|
+
const entries =
|
|
104
|
+
section && typeof section === "object" && !Array.isArray(section)
|
|
105
|
+
? (section as Record<string, unknown>)
|
|
106
|
+
: {};
|
|
107
|
+
if (!Object.hasOwn(entries, entryKey)) {
|
|
108
|
+
return Object.keys(entries);
|
|
109
|
+
}
|
|
110
|
+
selectedEntries.set(sectionKey, entryKey);
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ─── Validation (§7.1 inline tier, §7.2 Problems) ─────────────────────────────
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The last validator run's messages, keyed by the JSON-pointer base they were rendered under.
|
|
118
|
+
*
|
|
119
|
+
* `jx-validate` validates the WHOLE `project.json` against the project's generated entry document,
|
|
120
|
+
* so its output has to be routed back to individual controls. Until now it was wired to exactly one
|
|
121
|
+
* caller — the AI's `write_project_config` — which meant the model's edits to this file were
|
|
122
|
+
* schema-checked and a human's edits through this very form were not.
|
|
123
|
+
*/
|
|
124
|
+
const diagnostics = new Map<string, Record<string, string>>();
|
|
125
|
+
|
|
126
|
+
/** Drop every cached diagnostic (test hook, and the "project closed" path). */
|
|
127
|
+
export function resetContributedDiagnostics(): void {
|
|
128
|
+
diagnostics.clear();
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Route `jx-validate` messages ("/search/index: must be string") to the field they are about.
|
|
133
|
+
*
|
|
134
|
+
* Only the messages one level under `base` become field errors: `/search/index` belongs to the
|
|
135
|
+
* `index` control, and `/search/index/0/kind` belongs to it too — the deepest control that exists
|
|
136
|
+
* on this form is the one that can be corrected. A message ABOUT the base itself has no field to
|
|
137
|
+
* live at and is returned separately, for the section line.
|
|
138
|
+
*
|
|
139
|
+
* @param {string} base - JSON pointer of the record the form is editing, e.g. `/search`
|
|
140
|
+
* @param {string[]} messages
|
|
141
|
+
* @returns {{ fields: Record<string, string>; section: string[] }}
|
|
142
|
+
*/
|
|
143
|
+
export function routeDiagnostics(
|
|
144
|
+
base: string,
|
|
145
|
+
messages: string[],
|
|
146
|
+
): { fields: Record<string, string>; section: string[] } {
|
|
147
|
+
const fields: Record<string, string> = {};
|
|
148
|
+
const section: string[] = [];
|
|
149
|
+
for (const message of messages) {
|
|
150
|
+
const at = message.indexOf(": ");
|
|
151
|
+
const pointer = at === -1 ? "" : message.slice(0, at);
|
|
152
|
+
const text = at === -1 ? message : message.slice(at + 2);
|
|
153
|
+
if (pointer === base) {
|
|
154
|
+
section.push(text);
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (!pointer.startsWith(`${base}/`)) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const [prop] = pointer.slice(base.length + 1).split("/");
|
|
161
|
+
if (prop && fields[prop] === undefined) {
|
|
162
|
+
fields[prop] = text;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return { fields, section };
|
|
166
|
+
}
|
|
167
|
+
|
|
80
168
|
// ─── Persistence ──────────────────────────────────────────────────────────────
|
|
81
169
|
|
|
82
|
-
|
|
83
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Commit `project.json`, then say what is wrong with it.
|
|
172
|
+
*
|
|
173
|
+
* The write itself now belongs to `tabs/project-config.ts` — one serialisation, one error path, and
|
|
174
|
+
* a transaction the author can undo. This module used to own a second writer, at
|
|
175
|
+
* `JSON.stringify(config, null, "\t")`, which re-indented the whole file on any edit; and before
|
|
176
|
+
* that it was `void saveProjectConfig()` at five call sites with no validation at all. §7.2 files a
|
|
177
|
+
* failed config write as a Problem, because it must be fixed and it is about a named file; the
|
|
178
|
+
* chokepoint files it, so a failure here only has to stop.
|
|
179
|
+
*
|
|
180
|
+
* **The write comes first, and the order is the honest one.** This form mutates
|
|
181
|
+
* `projectState.projectConfig` in place before calling here, so the value is already live in the
|
|
182
|
+
* editor and the canvas; a validator run in front of the write could not have prevented anything,
|
|
183
|
+
* it would only have delayed persisting what the user can already see. This surface REPORTS. The
|
|
184
|
+
* one that gates is `contexts-section.ts`, which builds a candidate map and validates it before
|
|
185
|
+
* anything is applied.
|
|
186
|
+
*
|
|
187
|
+
* @param {string} base - JSON pointer of the record being edited, for routing field errors
|
|
188
|
+
* @param {() => void} rerender
|
|
189
|
+
*/
|
|
190
|
+
async function saveProjectConfig(base: string, rerender: () => void) {
|
|
84
191
|
const config = (projectState as { projectConfig: ProjectConfig }).projectConfig;
|
|
85
|
-
|
|
192
|
+
|
|
193
|
+
const commit = await commitProjectConfig();
|
|
194
|
+
if (!commit.ok) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let messages: string[] = [];
|
|
199
|
+
try {
|
|
200
|
+
messages = await validateProjectConfig(config);
|
|
201
|
+
} catch (error) {
|
|
202
|
+
/* A validator that will not compile must not be silent about it — but it is not the edit's
|
|
203
|
+
fault, so it is a problem of its own rather than an error parked on the user's field. */
|
|
204
|
+
notify.warn(`Could not validate project.json — ${errorMessage(error)}`, {
|
|
205
|
+
key: "settings:validator",
|
|
206
|
+
path: "project.json",
|
|
207
|
+
source: "Settings",
|
|
208
|
+
tier: "problem",
|
|
209
|
+
});
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
diagnostics.set(base, routeDiagnostics(base, messages).fields);
|
|
213
|
+
rerender();
|
|
86
214
|
}
|
|
87
215
|
|
|
88
216
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
@@ -181,8 +309,13 @@ function sectionValue(key: string): Record<string, unknown> | null {
|
|
|
181
309
|
|
|
182
310
|
// ─── Render ───────────────────────────────────────────────────────────────────
|
|
183
311
|
|
|
312
|
+
/* There is no `paneOfContainer` here. It moved to `canvas/canvas-surface.ts`, beside the other
|
|
313
|
+
"which pane is this about" answers, because it is not a settings idea: any stage content handed
|
|
314
|
+
only a host needs it, and `settings/general-settings.ts` and `settings/project-sections.ts` are
|
|
315
|
+
two more that do — both write a canvas mode, and both used to write the FOCUSED pane's. */
|
|
316
|
+
|
|
184
317
|
/**
|
|
185
|
-
* Render a contributed settings section into
|
|
318
|
+
* Render a contributed settings section into the settings document's content area.
|
|
186
319
|
*
|
|
187
320
|
* @param {HTMLElement} container
|
|
188
321
|
* @param {SettingsContribution} contribution
|
|
@@ -199,7 +332,7 @@ export function renderContributedSection(
|
|
|
199
332
|
|
|
200
333
|
const body =
|
|
201
334
|
layout === "map"
|
|
202
|
-
? renderMapLayout(contribution, opts, rerender)
|
|
335
|
+
? renderMapLayout(contribution, opts, rerender, paneOfContainer(container))
|
|
203
336
|
: renderFormLayout(contribution, opts, rerender);
|
|
204
337
|
|
|
205
338
|
const selected = layout === "map" ? (selectedEntries.get(contribution.key) ?? null) : null;
|
|
@@ -225,11 +358,13 @@ function renderFormLayout(
|
|
|
225
358
|
): TemplateResult {
|
|
226
359
|
const value = sectionValue(contribution.key) ?? {};
|
|
227
360
|
const ui = contribution.settings.entry?.ui;
|
|
361
|
+
const base = `/${contribution.key}`;
|
|
228
362
|
|
|
229
363
|
return html`
|
|
230
364
|
<div class="settings-form-panel">
|
|
231
365
|
${renderForm(contribution.entrySchema, value, {
|
|
232
366
|
context: buildContext(contribution.key, opts, null),
|
|
367
|
+
errors: diagnostics.get(base) ?? {},
|
|
233
368
|
onChange: (patch) => {
|
|
234
369
|
const target = sectionValue(contribution.key);
|
|
235
370
|
if (!target) {
|
|
@@ -237,7 +372,7 @@ function renderFormLayout(
|
|
|
237
372
|
}
|
|
238
373
|
applyPatch(target, patch);
|
|
239
374
|
rerender();
|
|
240
|
-
void saveProjectConfig();
|
|
375
|
+
void saveProjectConfig(base, rerender);
|
|
241
376
|
},
|
|
242
377
|
rerender,
|
|
243
378
|
...(ui !== undefined && { ui }),
|
|
@@ -251,6 +386,7 @@ function renderMapLayout(
|
|
|
251
386
|
contribution: SettingsContribution,
|
|
252
387
|
opts: ContributedSectionOptions,
|
|
253
388
|
rerender: () => void,
|
|
389
|
+
paneId: string,
|
|
254
390
|
): TemplateResult {
|
|
255
391
|
const { key: sectionKey } = contribution;
|
|
256
392
|
const entries = sectionValue(sectionKey) ?? {};
|
|
@@ -269,7 +405,7 @@ function renderMapLayout(
|
|
|
269
405
|
newEntryOpen.delete(sectionKey);
|
|
270
406
|
newEntryNames.delete(sectionKey);
|
|
271
407
|
rerender();
|
|
272
|
-
void saveProjectConfig();
|
|
408
|
+
void saveProjectConfig(`/${sectionKey}`, rerender);
|
|
273
409
|
};
|
|
274
410
|
|
|
275
411
|
const handleDelete = () => {
|
|
@@ -280,7 +416,7 @@ function renderMapLayout(
|
|
|
280
416
|
delete target[selected];
|
|
281
417
|
selectedEntries.set(sectionKey, null);
|
|
282
418
|
rerender();
|
|
283
|
-
void saveProjectConfig();
|
|
419
|
+
void saveProjectConfig(`/${sectionKey}`, rerender);
|
|
284
420
|
};
|
|
285
421
|
|
|
286
422
|
const handleRename = (newName: string) => {
|
|
@@ -298,7 +434,7 @@ function renderMapLayout(
|
|
|
298
434
|
Object.assign(target, next);
|
|
299
435
|
selectedEntries.set(sectionKey, slug);
|
|
300
436
|
rerender();
|
|
301
|
-
void saveProjectConfig();
|
|
437
|
+
void saveProjectConfig(`/${sectionKey}`, rerender);
|
|
302
438
|
};
|
|
303
439
|
|
|
304
440
|
// Left column — entry key list
|
|
@@ -308,6 +444,7 @@ function renderMapLayout(
|
|
|
308
444
|
(name) => html`
|
|
309
445
|
<sp-action-button
|
|
310
446
|
size="s"
|
|
447
|
+
data-jx-region=${paneRegion(paneId, `entry:${name}`)}
|
|
311
448
|
?selected=${selected === name}
|
|
312
449
|
@click=${() => {
|
|
313
450
|
selectedEntries.set(sectionKey, name);
|
|
@@ -363,7 +500,7 @@ function renderMapLayout(
|
|
|
363
500
|
const editorTpl: TemplateResult =
|
|
364
501
|
selected && selectedEntry && typeof selectedEntry === "object"
|
|
365
502
|
? html`
|
|
366
|
-
<div class="settings-editor-panel">
|
|
503
|
+
<div class="settings-editor-panel" data-jx-region=${paneRegion(paneId, "editor")}>
|
|
367
504
|
<div class="settings-editor-header">
|
|
368
505
|
<sp-textfield
|
|
369
506
|
size="s"
|
|
@@ -392,10 +529,11 @@ function renderMapLayout(
|
|
|
392
529
|
</div>
|
|
393
530
|
${renderForm(contribution.entrySchema, selectedEntry as Record<string, unknown>, {
|
|
394
531
|
context: buildContext(sectionKey, opts, selected),
|
|
532
|
+
errors: diagnostics.get(`/${sectionKey}/${selected}`) ?? {},
|
|
395
533
|
onChange: (patch) => {
|
|
396
534
|
applyPatch(selectedEntry as Record<string, unknown>, patch);
|
|
397
535
|
rerender();
|
|
398
|
-
void saveProjectConfig();
|
|
536
|
+
void saveProjectConfig(`/${sectionKey}/${selected}`, rerender);
|
|
399
537
|
},
|
|
400
538
|
rerender,
|
|
401
539
|
...(ui !== undefined && { ui }),
|