@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
|
@@ -1,15 +1,36 @@
|
|
|
1
1
|
/// <reference lib="dom" />
|
|
2
2
|
/**
|
|
3
|
-
* Style panel — CSS property editor
|
|
4
|
-
* accordion, shorthand expand/compress, and filter.
|
|
3
|
+
* Style panel — CSS property editor under the **Target Line** (§6.1), with provenance-coded rows
|
|
4
|
+
* (§6.2), a section accordion, shorthand expand/compress, and a property filter.
|
|
5
|
+
*
|
|
6
|
+
* The panel resolves a compound coordinate before it can commit anything: `(selection, breakpoint,
|
|
7
|
+
* scheme layer, nested selector)`. It has always computed that tuple — it is the per-field key and
|
|
8
|
+
* the five-branch if/else below — and it used to hide it behind three disconnected widgets on two
|
|
9
|
+
* different bars. `target-line.ts` renders it as one sentence instead, and the three widgets are
|
|
10
|
+
* gone: the breakpoint and scheme axes are selected on the pane context bar (region ⑦), and the
|
|
11
|
+
* selector is the one axis this tab owns.
|
|
12
|
+
*
|
|
13
|
+
* Every row then says where its value came from. `provenance.ts` supplies the chip;
|
|
14
|
+
* `computeInheritedSources()` supplies the donor breakpoint the cascade walk always knew and this
|
|
15
|
+
* panel used to throw away.
|
|
5
16
|
*/
|
|
6
17
|
|
|
7
18
|
import { html, nothing } from "lit-html";
|
|
8
19
|
import { getNestedStyle } from "@jxsuite/schema/guards";
|
|
9
20
|
import { live } from "lit-html/directives/live.js";
|
|
10
21
|
import { ifDefined } from "lit-html/directives/if-defined.js";
|
|
11
|
-
import {
|
|
22
|
+
import {
|
|
23
|
+
COMMON_SELECTORS,
|
|
24
|
+
debouncedStyleCommit,
|
|
25
|
+
getNodeAtPath,
|
|
26
|
+
isNestedSelector,
|
|
27
|
+
renderOnly,
|
|
28
|
+
updateUi,
|
|
29
|
+
} from "../store";
|
|
12
30
|
import { activeTab } from "../workspace/workspace";
|
|
31
|
+
import { primarySelection, unifyValues } from "../tabs/selection";
|
|
32
|
+
import { shell } from "../shell";
|
|
33
|
+
import { activeRegistry } from "../commands/active-registry";
|
|
13
34
|
import { selectStylebookTag } from "./stylebook-panel";
|
|
14
35
|
import {
|
|
15
36
|
mutateUpdateMediaNestedStyle,
|
|
@@ -21,13 +42,28 @@ import {
|
|
|
21
42
|
transactDoc,
|
|
22
43
|
} from "../tabs/transact";
|
|
23
44
|
import { inferInputType, propLabel } from "../utils/studio-utils";
|
|
45
|
+
import { stringArg } from "../commands/command-args";
|
|
46
|
+
import type { AnyCommand, CommandRegistry } from "../commands/registry";
|
|
24
47
|
import { renderFieldRow } from "../ui/field-row";
|
|
25
48
|
import { showPromptDialog } from "../ui/layers";
|
|
26
49
|
import { renderDynamicSlot } from "../ui/dynamic-slot";
|
|
27
50
|
import { parseMediaEntries, schemeOfQuery } from "../utils/canvas-media";
|
|
28
51
|
import { getEffectiveMedia, getEffectiveStyle } from "../site-context";
|
|
29
|
-
import {
|
|
52
|
+
import { computeInheritedSources } from "../utils/inherited-style";
|
|
53
|
+
import { loadUsages, peekUsages, usageFiles } from "../services/references";
|
|
30
54
|
import { mediaDisplayName } from "./shared";
|
|
55
|
+
import { countProvenance, renderProvenanceChip, renderProvenanceDots } from "./provenance";
|
|
56
|
+
import {
|
|
57
|
+
openSelectorMenu,
|
|
58
|
+
renderTargetLine,
|
|
59
|
+
resetSelectorTrigger as resetTargetSelector,
|
|
60
|
+
} from "./target-line";
|
|
61
|
+
import {
|
|
62
|
+
clickAnythingTo,
|
|
63
|
+
openPageAction,
|
|
64
|
+
renderEmptyState,
|
|
65
|
+
staleSelectionMessage,
|
|
66
|
+
} from "./empty-state";
|
|
31
67
|
import {
|
|
32
68
|
allConditionsPass,
|
|
33
69
|
autoOpenSections,
|
|
@@ -41,6 +77,8 @@ import {
|
|
|
41
77
|
} from "./style-utils";
|
|
42
78
|
import { widgetForType } from "./style-inputs";
|
|
43
79
|
|
|
80
|
+
import type { FieldProvenance, ProvenanceState } from "./provenance";
|
|
81
|
+
import type { TargetScope, TargetSegment } from "./target-line";
|
|
44
82
|
import type { Tab } from "../tabs/tab";
|
|
45
83
|
import type { JxPath } from "../state";
|
|
46
84
|
import type { JsonValue } from "../types";
|
|
@@ -53,6 +91,9 @@ interface CssLonghand {
|
|
|
53
91
|
|
|
54
92
|
type CssPropertyEntry = Record<string, unknown>;
|
|
55
93
|
|
|
94
|
+
/** The chip a row wears when its caller has nothing more specific to say. */
|
|
95
|
+
const DEFAULT_PROVENANCE: FieldProvenance = { state: "default" };
|
|
96
|
+
|
|
56
97
|
type StyleMutateFn = (
|
|
57
98
|
t: Tab,
|
|
58
99
|
prop: string,
|
|
@@ -88,6 +129,133 @@ function resolveNestedTagStyle(style: JxStyle, tagPath: string): JxStyle {
|
|
|
88
129
|
return obj;
|
|
89
130
|
}
|
|
90
131
|
|
|
132
|
+
/**
|
|
133
|
+
* The style object one compound coordinate addresses.
|
|
134
|
+
*
|
|
135
|
+
* Split out of the panel body because the coordinate has to be resolved TWICE: once against the
|
|
136
|
+
* effective (site-merged) style, which is what the rows display, and once against the document's
|
|
137
|
+
* own, which is what a commit writes. The difference between the two is the "from site tokens"
|
|
138
|
+
* donor — invisible for as long as there was only one resolution.
|
|
139
|
+
*
|
|
140
|
+
* @param {JxStyle} style
|
|
141
|
+
* @param {string | null} activeSelector
|
|
142
|
+
* @param {string | null} editMedia
|
|
143
|
+
* @returns {JxStyle}
|
|
144
|
+
*/
|
|
145
|
+
function resolveContextStyle(
|
|
146
|
+
style: JxStyle,
|
|
147
|
+
activeSelector: string | null,
|
|
148
|
+
editMedia: string | null,
|
|
149
|
+
): JxStyle {
|
|
150
|
+
const base = editMedia ? (getNestedStyle(style, `@${editMedia}`) ?? {}) : style;
|
|
151
|
+
if (activeSelector) {
|
|
152
|
+
return isTagPath(activeSelector)
|
|
153
|
+
? resolveNestedTagStyle(base, activeSelector)
|
|
154
|
+
: (getNestedStyle(base, activeSelector) ?? {});
|
|
155
|
+
}
|
|
156
|
+
const flat: JxStyle = {};
|
|
157
|
+
for (const [p, v] of Object.entries(base)) {
|
|
158
|
+
if (typeof v !== "object") {
|
|
159
|
+
flat[p] = v;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return flat;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The mutation that writes to one compound coordinate — the other half of
|
|
167
|
+
* {@link resolveContextStyle}, and the reason the two now sit side by side: they are one decision
|
|
168
|
+
* expressed twice, and they used to be interleaved in a five-branch `if/else` where a new branch
|
|
169
|
+
* could easily read one coordinate and write another.
|
|
170
|
+
*
|
|
171
|
+
* @param {JxPath} sel
|
|
172
|
+
* @param {string | null} activeSelector
|
|
173
|
+
* @param {string | null} editMedia
|
|
174
|
+
* @returns {StyleMutateFn}
|
|
175
|
+
*/
|
|
176
|
+
function contextMutate(
|
|
177
|
+
sel: JxPath,
|
|
178
|
+
activeSelector: string | null,
|
|
179
|
+
editMedia: string | null,
|
|
180
|
+
): StyleMutateFn {
|
|
181
|
+
if (activeSelector && isTagPath(activeSelector)) {
|
|
182
|
+
const stylePath = activeSelector.split(" ");
|
|
183
|
+
return editMedia
|
|
184
|
+
? (t, prop, val) =>
|
|
185
|
+
mutateUpdateMediaNestedStylePath(
|
|
186
|
+
t,
|
|
187
|
+
sel,
|
|
188
|
+
editMedia,
|
|
189
|
+
stylePath,
|
|
190
|
+
prop,
|
|
191
|
+
val as string | undefined,
|
|
192
|
+
)
|
|
193
|
+
: (t, prop, val) =>
|
|
194
|
+
mutateUpdateNestedStylePath(t, sel, stylePath, prop, val as string | undefined);
|
|
195
|
+
}
|
|
196
|
+
if (activeSelector) {
|
|
197
|
+
return editMedia
|
|
198
|
+
? (t, prop, val) =>
|
|
199
|
+
mutateUpdateMediaNestedStyle(
|
|
200
|
+
t,
|
|
201
|
+
sel,
|
|
202
|
+
editMedia,
|
|
203
|
+
activeSelector,
|
|
204
|
+
prop,
|
|
205
|
+
val as string | undefined,
|
|
206
|
+
)
|
|
207
|
+
: (t, prop, val) =>
|
|
208
|
+
mutateUpdateNestedStyle(t, sel, activeSelector, prop, val as string | undefined);
|
|
209
|
+
}
|
|
210
|
+
return editMedia
|
|
211
|
+
? (t, prop, val) => mutateUpdateMediaStyle(t, sel, editMedia, prop, val as string | undefined)
|
|
212
|
+
: (t, prop, val) => mutateUpdateStyle(t, sel, prop, val as string | undefined);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Which properties the selected elements disagree about, at the coordinate the tab is editing.
|
|
217
|
+
*
|
|
218
|
+
* Resolved through the SAME `resolveContextStyle` the panel reads its own values with, so "mixed"
|
|
219
|
+
* is decided at exactly the breakpoint, scheme layer and selector the Target Line names — two cards
|
|
220
|
+
* that differ only inside `@md` are not mixed while you are editing Base.
|
|
221
|
+
*
|
|
222
|
+
* Returns an empty set for a selection of one, which is the whole reason a single selection renders
|
|
223
|
+
* no Mixed state anywhere: the loop never finds a second value to disagree with.
|
|
224
|
+
*
|
|
225
|
+
* @param {Tab} tab
|
|
226
|
+
* @param {readonly JxPath[]} targets
|
|
227
|
+
* @param {string | null} activeSelector
|
|
228
|
+
* @param {string | null} editMedia
|
|
229
|
+
* @returns {Set<string>}
|
|
230
|
+
*/
|
|
231
|
+
function mixedStyleProps(
|
|
232
|
+
tab: Tab,
|
|
233
|
+
targets: readonly JxPath[],
|
|
234
|
+
activeSelector: string | null,
|
|
235
|
+
editMedia: string | null,
|
|
236
|
+
): Set<string> {
|
|
237
|
+
const mixed = new Set<string>();
|
|
238
|
+
if (targets.length < 2) {
|
|
239
|
+
return mixed;
|
|
240
|
+
}
|
|
241
|
+
const blocks = targets.map((path) => {
|
|
242
|
+
const node = getNodeAtPath(tab.doc.document, path) as JxMutableNode | undefined;
|
|
243
|
+
return resolveContextStyle(node?.style ?? {}, activeSelector, editMedia);
|
|
244
|
+
});
|
|
245
|
+
const props = new Set<string>();
|
|
246
|
+
for (const block of blocks) {
|
|
247
|
+
for (const prop of Object.keys(block)) {
|
|
248
|
+
props.add(prop);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
for (const prop of props) {
|
|
252
|
+
if (unifyValues(blocks.map((block) => block[prop] ?? null)).mixed) {
|
|
253
|
+
mixed.add(prop);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return mixed;
|
|
257
|
+
}
|
|
258
|
+
|
|
91
259
|
// ─── Row renderers ──────────────────────────────────────────────────────────
|
|
92
260
|
|
|
93
261
|
function renderStyleRow(
|
|
@@ -95,131 +263,154 @@ function renderStyleRow(
|
|
|
95
263
|
prop: string,
|
|
96
264
|
value: string,
|
|
97
265
|
onCommit: (v: string | undefined) => void,
|
|
98
|
-
onDelete: () => void,
|
|
99
266
|
isWarning: boolean,
|
|
100
267
|
gridMode: boolean,
|
|
101
268
|
inheritedValue: string | undefined,
|
|
102
269
|
templateSignals: string[] = [],
|
|
103
270
|
fieldKey: string = prop,
|
|
271
|
+
provenance?: FieldProvenance,
|
|
104
272
|
) {
|
|
273
|
+
const chip: FieldProvenance = provenance ?? DEFAULT_PROVENANCE;
|
|
105
274
|
const type = inferInputType(entry);
|
|
106
275
|
const hasVal = value !== undefined && value !== "";
|
|
107
276
|
const placeholder = !hasVal && inheritedValue ? String(inheritedValue) : "";
|
|
108
277
|
const spanVal = gridMode && (entry as Record<string, unknown>).$span === 2 ? 2 : undefined;
|
|
109
|
-
//
|
|
278
|
+
// The rungs come from `StyleObject.additionalProperties`, not from a list here (§6.6 rule 2).
|
|
279
|
+
// That derives literal + ${} template: a CSS value carrying `${…}` IS a signal binding, which
|
|
280
|
+
// `emitStyleString` compiles to a reactive declaration, and `$ref` is not admitted by JxStyle.
|
|
281
|
+
// Naming the POSITION rather than the answer is the point — a schema that later admits another
|
|
282
|
+
// Rung cannot then be contradicted by a hand-written array sitting here.
|
|
110
283
|
const slot = renderDynamicSlot({
|
|
111
|
-
caps:
|
|
284
|
+
caps: "styleProperty",
|
|
112
285
|
fieldKey,
|
|
113
286
|
onChange: (v?: JsonValue) => onCommit(v === undefined || v === "" ? undefined : String(v)),
|
|
114
287
|
staticWidget: widgetForType(type, entry, prop, value, onCommit, { placeholder }),
|
|
115
288
|
stateDefs: templateSignals,
|
|
116
289
|
value,
|
|
117
290
|
});
|
|
291
|
+
// `hasValue` is false because the chip supersedes the row's derived set-dot: the dot could only
|
|
292
|
+
// Say set-or-not, and three of the four states this panel can now answer with were previously
|
|
293
|
+
// Indistinguishable from "unset".
|
|
118
294
|
return renderFieldRow({
|
|
119
295
|
prop,
|
|
120
296
|
label: propLabel(entry, prop),
|
|
121
|
-
hasValue:
|
|
122
|
-
onClear: onDelete,
|
|
297
|
+
hasValue: false,
|
|
123
298
|
widget: slot.widget,
|
|
299
|
+
provenance: chip,
|
|
124
300
|
labelExtra: slot.modeButton,
|
|
125
301
|
...(spanVal != null && { span: spanVal }),
|
|
126
302
|
warning: isWarning,
|
|
127
303
|
});
|
|
128
304
|
}
|
|
129
305
|
|
|
306
|
+
/** What {@link renderShorthandRow} needs to draw one shorthand and its longhand children. */
|
|
307
|
+
interface ShorthandRowOptions {
|
|
308
|
+
prop: string;
|
|
309
|
+
entry: CssPropertyEntry;
|
|
310
|
+
/** The style block at the edited coordinate — the primary element's, as every row reads it. */
|
|
311
|
+
style: Record<string, unknown>;
|
|
312
|
+
/** The write, already fanned across the selection. Called INSIDE a transaction. */
|
|
313
|
+
mutate: StyleMutateFn;
|
|
314
|
+
inherited: Record<string, string | number>;
|
|
315
|
+
ctx: ProvenanceCtx;
|
|
316
|
+
}
|
|
317
|
+
|
|
130
318
|
/**
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
319
|
+
* A shorthand property row: the header field, plus one row per longhand when it is expanded.
|
|
320
|
+
*
|
|
321
|
+
* Two things were wrong with it, and they were the same thing. It took a single-target mutation
|
|
322
|
+
* while every other row in the tab took the selection-wide one, so "set padding on six cards in one
|
|
323
|
+
* decision" — the sentence §6.5 makes, with padding as its example — wrote to one card. And it drew
|
|
324
|
+
* its own `.set-dot` rather than a provenance chip, so a shorthand `mixedStyleProps` had ALREADY
|
|
325
|
+
* flagged as mixed offered a plain "Clear padding" affordance instead of saying so.
|
|
326
|
+
*
|
|
327
|
+
* Both are fixed by taking the same two things every other row takes: `mutate`, which fans across
|
|
328
|
+
* the selection, and `ctx`, which answers where a value came from.
|
|
137
329
|
*/
|
|
138
|
-
function renderShorthandRow(
|
|
139
|
-
|
|
140
|
-
entry
|
|
141
|
-
style
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
) {
|
|
330
|
+
function renderShorthandRow({
|
|
331
|
+
prop: shortProp,
|
|
332
|
+
entry,
|
|
333
|
+
style,
|
|
334
|
+
mutate,
|
|
335
|
+
inherited,
|
|
336
|
+
ctx,
|
|
337
|
+
}: ShorthandRowOptions) {
|
|
146
338
|
const tab = activeTab.value!;
|
|
147
339
|
const longhands = getLonghands(shortProp) as CssLonghand[];
|
|
148
340
|
const shortVal = style[shortProp];
|
|
149
341
|
const hasLonghands = longhands.some((l: CssLonghand) => style[l.name] !== undefined);
|
|
150
342
|
const isExpanded = tab.session.ui.styleShorthands[shortProp] ?? hasLonghands;
|
|
151
|
-
const hasAnyVal =
|
|
152
|
-
shortVal !== undefined || longhands.some((l: CssLonghand) => style[l.name] !== undefined);
|
|
343
|
+
const hasAnyVal = shortVal !== undefined || hasLonghands;
|
|
153
344
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
345
|
+
/**
|
|
346
|
+
* Drop every longhand, then write the shorthand — the one write order this row has.
|
|
347
|
+
*
|
|
348
|
+
* The longhands go unconditionally rather than "the ones the panel can see set", because what the
|
|
349
|
+
* panel can see is the PRIMARY element's style: a second selected element with a `paddingTop` of
|
|
350
|
+
* its own would otherwise keep it and quietly out-rank the shorthand just written over it.
|
|
351
|
+
* Deleting a property an element does not have is a no-op on that element.
|
|
352
|
+
*/
|
|
353
|
+
const writeShorthand = (val?: string | undefined) =>
|
|
354
|
+
transactDoc(activeTab.value, (t) => {
|
|
355
|
+
for (const l of longhands) {
|
|
356
|
+
mutate(t, l.name);
|
|
357
|
+
}
|
|
358
|
+
mutate(t, shortProp, val);
|
|
359
|
+
});
|
|
360
|
+
const clearAll = () =>
|
|
361
|
+
transactDoc(activeTab.value, (t) => {
|
|
362
|
+
mutate(t, shortProp);
|
|
363
|
+
for (const l of longhands) {
|
|
364
|
+
mutate(t, l.name);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
const headerWidget = html`
|
|
369
|
+
<div class="style-shorthand-header">
|
|
370
|
+
<sp-textfield
|
|
371
|
+
size="s"
|
|
372
|
+
.value=${live(shortVal || "")}
|
|
373
|
+
placeholder=${
|
|
374
|
+
!shortVal && hasLonghands
|
|
375
|
+
? longhands.map((l: CssLonghand) => style[l.name] || "0").join(" ")
|
|
376
|
+
: !shortVal && inherited[shortProp]
|
|
377
|
+
? inherited[shortProp]
|
|
378
|
+
: !shortVal && longhands.some((l: CssLonghand) => inherited[l.name])
|
|
379
|
+
? longhands.map((l: CssLonghand) => inherited[l.name] || "0").join(" ")
|
|
380
|
+
: ""
|
|
381
|
+
}
|
|
382
|
+
@input=${debouncedStyleCommit(`short:${shortProp}`, 400, (e: Event) => {
|
|
383
|
+
writeShorthand((e.target as HTMLInputElement).value || undefined);
|
|
384
|
+
})}
|
|
385
|
+
></sp-textfield>
|
|
386
|
+
<sp-action-button
|
|
387
|
+
size="xs"
|
|
388
|
+
quiet
|
|
389
|
+
@click=${(e: Event) => {
|
|
390
|
+
e.stopPropagation();
|
|
391
|
+
activeTab.value!.session.ui.styleShorthands = {
|
|
392
|
+
...activeTab.value!.session.ui.styleShorthands,
|
|
393
|
+
[shortProp]: !isExpanded,
|
|
394
|
+
};
|
|
395
|
+
}}
|
|
396
|
+
>
|
|
157
397
|
${
|
|
158
|
-
|
|
159
|
-
? html`<
|
|
160
|
-
|
|
161
|
-
title="Clear ${shortProp}"
|
|
162
|
-
@click=${(e: Event) => {
|
|
163
|
-
e.stopPropagation();
|
|
164
|
-
transactDoc(activeTab.value, (t) => {
|
|
165
|
-
if (shortVal !== undefined) {
|
|
166
|
-
mutateFn(t, shortProp);
|
|
167
|
-
}
|
|
168
|
-
for (const l of longhands) {
|
|
169
|
-
if (style[l.name] !== undefined) {
|
|
170
|
-
mutateFn(t, l.name);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
});
|
|
174
|
-
}}
|
|
175
|
-
></span>`
|
|
176
|
-
: nothing
|
|
398
|
+
isExpanded
|
|
399
|
+
? html`<sp-icon-chevron-down slot="icon"></sp-icon-chevron-down>`
|
|
400
|
+
: html`<sp-icon-chevron-right slot="icon"></sp-icon-chevron-right>`
|
|
177
401
|
}
|
|
178
|
-
|
|
179
|
-
</div>
|
|
180
|
-
<div class="style-shorthand-header">
|
|
181
|
-
<sp-textfield
|
|
182
|
-
size="s"
|
|
183
|
-
.value=${live(shortVal || "")}
|
|
184
|
-
placeholder=${
|
|
185
|
-
!shortVal && hasLonghands
|
|
186
|
-
? longhands.map((l: CssLonghand) => style[l.name] || "0").join(" ")
|
|
187
|
-
: !shortVal && inherited[shortProp]
|
|
188
|
-
? inherited[shortProp]
|
|
189
|
-
: !shortVal && longhands.some((l: CssLonghand) => inherited[l.name])
|
|
190
|
-
? longhands.map((l: CssLonghand) => inherited[l.name] || "0").join(" ")
|
|
191
|
-
: ""
|
|
192
|
-
}
|
|
193
|
-
@input=${debouncedStyleCommit(`short:${shortProp}`, 400, (e: Event) => {
|
|
194
|
-
transactDoc(activeTab.value, (t) => {
|
|
195
|
-
for (const l of longhands) {
|
|
196
|
-
if (style[l.name] !== undefined) {
|
|
197
|
-
mutateFn(t, l.name);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
mutateFn(t, shortProp, (e.target as HTMLInputElement).value || undefined);
|
|
201
|
-
});
|
|
202
|
-
})}
|
|
203
|
-
></sp-textfield>
|
|
204
|
-
<sp-action-button
|
|
205
|
-
size="xs"
|
|
206
|
-
quiet
|
|
207
|
-
@click=${(e: Event) => {
|
|
208
|
-
e.stopPropagation();
|
|
209
|
-
activeTab.value!.session.ui.styleShorthands = {
|
|
210
|
-
...activeTab.value!.session.ui.styleShorthands,
|
|
211
|
-
[shortProp]: !isExpanded,
|
|
212
|
-
};
|
|
213
|
-
}}
|
|
214
|
-
>
|
|
215
|
-
${
|
|
216
|
-
isExpanded
|
|
217
|
-
? html`<sp-icon-chevron-down slot="icon"></sp-icon-chevron-down>`
|
|
218
|
-
: html`<sp-icon-chevron-right slot="icon"></sp-icon-chevron-right>`
|
|
219
|
-
}
|
|
220
|
-
</sp-action-button>
|
|
221
|
-
</div>
|
|
402
|
+
</sp-action-button>
|
|
222
403
|
</div>
|
|
404
|
+
`;
|
|
405
|
+
|
|
406
|
+
return html`
|
|
407
|
+
${renderFieldRow({
|
|
408
|
+
prop: shortProp,
|
|
409
|
+
label: propLabel(entry, shortProp),
|
|
410
|
+
hasValue: hasAnyVal,
|
|
411
|
+
widget: headerWidget,
|
|
412
|
+
provenance: shorthandProvenance(shortProp, ctx, hasAnyVal ? clearAll : undefined),
|
|
413
|
+
})}
|
|
223
414
|
${
|
|
224
415
|
isExpanded
|
|
225
416
|
? (() => {
|
|
@@ -232,35 +423,27 @@ function renderShorthandRow(
|
|
|
232
423
|
: null;
|
|
233
424
|
const compress = isBorderSide ? compressBorderSide : compressShorthand;
|
|
234
425
|
const emptyVal = isBorderSide ? "" : "0";
|
|
426
|
+
/** This longhand becomes `val`; the others keep whatever they show. */
|
|
427
|
+
const recompress = (idx: number, val: string) =>
|
|
428
|
+
writeShorthand(
|
|
429
|
+
compress(
|
|
430
|
+
longhands.map((l: CssLonghand, i: number) =>
|
|
431
|
+
i === idx ? val : (style[l.name] ?? (expanded ? expanded[i] : emptyVal)),
|
|
432
|
+
) as string[],
|
|
433
|
+
),
|
|
434
|
+
);
|
|
235
435
|
return longhands.map(({ name, entry: lEntry }: CssLonghand, idx: number) => {
|
|
236
436
|
const lVal = style[name] ?? (expanded ? expanded[idx] : "");
|
|
437
|
+
const hasLVal = lVal !== undefined && lVal !== "";
|
|
237
438
|
return html`
|
|
238
439
|
<div class="style-row style-row--child" data-prop=${name}>
|
|
239
440
|
<div class="style-row-label">
|
|
240
|
-
${
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
e.stopPropagation();
|
|
247
|
-
const vals = longhands.map((l: CssLonghand, i: number) =>
|
|
248
|
-
i === idx
|
|
249
|
-
? emptyVal
|
|
250
|
-
: (style[l.name] ?? (expanded ? expanded[i] : emptyVal)),
|
|
251
|
-
);
|
|
252
|
-
transactDoc(activeTab.value, (t) => {
|
|
253
|
-
for (const l of longhands) {
|
|
254
|
-
if (style[l.name] !== undefined) {
|
|
255
|
-
mutateFn(t, l.name);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
mutateFn(t, shortProp, compress(vals as string[]));
|
|
259
|
-
});
|
|
260
|
-
}}
|
|
261
|
-
></span>`
|
|
262
|
-
: nothing
|
|
263
|
-
}
|
|
441
|
+
${renderProvenanceChip(
|
|
442
|
+
name,
|
|
443
|
+
longhandProvenance(name, shortProp, hasLVal, ctx, () =>
|
|
444
|
+
recompress(idx, emptyVal),
|
|
445
|
+
),
|
|
446
|
+
)}
|
|
264
447
|
<sp-field-label size="s" title=${name}
|
|
265
448
|
>${propLabel(lEntry, name)}</sp-field-label
|
|
266
449
|
>
|
|
@@ -270,21 +453,7 @@ function renderShorthandRow(
|
|
|
270
453
|
lEntry,
|
|
271
454
|
name,
|
|
272
455
|
lVal as string,
|
|
273
|
-
(newVal: string) =>
|
|
274
|
-
const vals = longhands.map((l: CssLonghand, i: number) =>
|
|
275
|
-
i === idx
|
|
276
|
-
? newVal || emptyVal
|
|
277
|
-
: (style[l.name] ?? (expanded ? expanded[i] : emptyVal)),
|
|
278
|
-
);
|
|
279
|
-
transactDoc(activeTab.value, (t) => {
|
|
280
|
-
for (const l of longhands) {
|
|
281
|
-
if (style[l.name] !== undefined) {
|
|
282
|
-
mutateFn(t, l.name);
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
mutateFn(t, shortProp, compress(vals as string[]));
|
|
286
|
-
});
|
|
287
|
-
},
|
|
456
|
+
(newVal: string) => recompress(idx, newVal || emptyVal),
|
|
288
457
|
{
|
|
289
458
|
placeholder: !lVal && inherited[name] ? String(inherited[name]) : "",
|
|
290
459
|
},
|
|
@@ -298,23 +467,403 @@ function renderShorthandRow(
|
|
|
298
467
|
`;
|
|
299
468
|
}
|
|
300
469
|
|
|
470
|
+
// ─── The edit target ─────────────────────────────────────────────────────────
|
|
471
|
+
|
|
472
|
+
/** Forget the selector menu's element handle — the Inspector unmounted, or a test starts clean. */
|
|
473
|
+
export function resetSelectorMenu(): void {
|
|
474
|
+
resetTargetSelector();
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Whether the "show affected" list under a project-wide warning band is open.
|
|
479
|
+
*
|
|
480
|
+
* Module-local rather than a `session.ui` field: it is a disclosure on a project-level warning, and
|
|
481
|
+
* a per-document field would make the same warning open in one tab and closed in another.
|
|
482
|
+
*/
|
|
483
|
+
let _showAffected = false;
|
|
484
|
+
|
|
485
|
+
/** Fold the affected list away — the Inspector unmounted, or a test starts clean. */
|
|
486
|
+
export function resetAffectedDisclosure(): void {
|
|
487
|
+
_showAffected = false;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Ask a registered command to do something this panel does not own.
|
|
492
|
+
*
|
|
493
|
+
* The breakpoint and the colour scheme are DEFINED in Project Settings › Contexts and SELECTED on
|
|
494
|
+
* the pane context bar. A Target Line segment that opened its own list would be the third selector
|
|
495
|
+
* §6.4 forbids, so it routes to the definition site through the same command the context bar's
|
|
496
|
+
* "Manage contexts…" footer runs.
|
|
497
|
+
*/
|
|
498
|
+
function runCommand(id: string, args?: Record<string, unknown>): void {
|
|
499
|
+
void activeRegistry()?.run(id, args);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* The tag a stylebook rule restyles, when one is being edited.
|
|
504
|
+
*
|
|
505
|
+
* In Stylebook the "selector" is a bare tag path (`h1`, `table th`) written into the DOCUMENT's
|
|
506
|
+
* root style, so the first segment of the path is the tag whose blast radius the scope chip
|
|
507
|
+
* states.
|
|
508
|
+
*/
|
|
509
|
+
function stylebookTagOf(selector: string | null): string | null {
|
|
510
|
+
return selector && isTagPath(selector) ? (selector.split(" ")[0] ?? null) : null;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Whether this document IS the project's stylesheet.
|
|
515
|
+
*
|
|
516
|
+
* `project.json`'s `style` is not a document's style. The compiler hands it to EVERY route's
|
|
517
|
+
* compile as `projectStyle` (`site/site-build.ts`), and `compileStyles` emits its custom properties
|
|
518
|
+
* as `:root`, its flat properties as `body`, and every bare tag key as a global rule — so `{ "h1":
|
|
519
|
+
* { "color": "red" } }` in project.json becomes `h1 { color: red }` in the stylesheet of every
|
|
520
|
+
* page, reaching inside every component instance too (nothing in the compiler or the runtime
|
|
521
|
+
* attaches a shadow root). Studio shows the same cascade: `getEffectiveStyle` merges the object
|
|
522
|
+
* into every open document.
|
|
523
|
+
*
|
|
524
|
+
* That makes the document Stylebook opens by default the widest blast radius in the app — and it is
|
|
525
|
+
* the one `resolveScope` used to label "in this **document**", the narrowest.
|
|
526
|
+
*
|
|
527
|
+
* Matched by suffix rather than by equality, the way `studio.ts` and `imports-panel.ts` already
|
|
528
|
+
* address this file: a config reached from a sub-directory is still a config, and it still has no
|
|
529
|
+
* elements of its own for "in this document" to be about.
|
|
530
|
+
*/
|
|
531
|
+
function isProjectStylesheet(path: string): boolean {
|
|
532
|
+
return /(?:^|\/)project\.json$/.test(path);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* How wide an edit here reaches, and — for the project case — how wide, in numbers.
|
|
537
|
+
*
|
|
538
|
+
* Three kinds, in the order a user meets them:
|
|
539
|
+
*
|
|
540
|
+
* - **element** — an element is selected; the rule is scoped to it and to nothing else.
|
|
541
|
+
* - **document** — Stylebook on an ordinary document: every `<tag>` on this page.
|
|
542
|
+
* - **project** — the same keystroke restyles files that are not open. Two documents reach this far:
|
|
543
|
+
* a LAYOUT, whose root style is inherited by every page declaring it, and
|
|
544
|
+
* {@link isProjectStylesheet}, which is inherited by all of them.
|
|
545
|
+
*
|
|
546
|
+
* The project stylesheet reaches it with NO tag as well, which no other document does: deselecting
|
|
547
|
+
* in Stylebook leaves the tab editing project.json's own root style — `:root` and `body` on every
|
|
548
|
+
* route — and there is no element anywhere for "this element" to have meant.
|
|
549
|
+
*/
|
|
550
|
+
function resolveScope(tab: Tab, stylebookTag: string | null): TargetScope {
|
|
551
|
+
const path = tab.documentPath ?? "";
|
|
552
|
+
const isProjectStyle = isProjectStylesheet(path);
|
|
553
|
+
if (!stylebookTag) {
|
|
554
|
+
return isProjectStyle
|
|
555
|
+
? projectScope("every page in this project", null)
|
|
556
|
+
: { kind: "element", label: "this element" };
|
|
557
|
+
}
|
|
558
|
+
const shared = isProjectStyle || /^\.?\/?layouts\//.test(path);
|
|
559
|
+
const label = `all <${stylebookTag}> in this ${shared ? "project" : "document"}`;
|
|
560
|
+
return shared ? projectScope(label, stylebookTag) : { kind: "document", label };
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/**
|
|
564
|
+
* The project-wide warning band: its label, its count, and its disclosure.
|
|
565
|
+
*
|
|
566
|
+
* The count is `findReferences`' tag walk, which counts `tagName` matches across the project. Where
|
|
567
|
+
* the host cannot answer — no `findReferences` route, a failed sweep, or no tag to ask about — the
|
|
568
|
+
* band says **unknown** rather than a confident zero, exactly as `services/references.ts` requires
|
|
569
|
+
* of every rendering.
|
|
570
|
+
*
|
|
571
|
+
* @param {string} label
|
|
572
|
+
* @param {string | null} tagName — null for a root-style edit, which has no tag to count.
|
|
573
|
+
* @returns {TargetScope}
|
|
574
|
+
*/
|
|
575
|
+
function projectScope(label: string, tagName: string | null): TargetScope {
|
|
576
|
+
if (tagName === null) {
|
|
577
|
+
// No tag, so no query: `:root`/`body` on every route is not a `tagName` sweep, and how many
|
|
578
|
+
// Routes that is, is a question no surface here can answer. A zero would be the same lie the
|
|
579
|
+
// Band exists to stop, in a smaller font — and with no file list, there is nothing to disclose.
|
|
580
|
+
return { affected: "how many pages that is, is unknown", kind: "project", label };
|
|
581
|
+
}
|
|
582
|
+
const query = { tagName };
|
|
583
|
+
const state = peekUsages(query);
|
|
584
|
+
if (state === null) {
|
|
585
|
+
// First paint for this tag: ask once, and repaint when the answer lands. `loadUsages` joins an
|
|
586
|
+
// In-flight request, so a panel repainting sixty times a second is still one sweep.
|
|
587
|
+
void loadUsages(query).then(() => renderOnly("rightPanel"));
|
|
588
|
+
}
|
|
589
|
+
return {
|
|
590
|
+
kind: "project",
|
|
591
|
+
label,
|
|
592
|
+
affected: affectedSentence(state),
|
|
593
|
+
affectedFiles: state?.status === "ready" ? usageFiles(state.result) : [],
|
|
594
|
+
showAffected: _showAffected,
|
|
595
|
+
onToggleAffected: () => {
|
|
596
|
+
_showAffected = !_showAffected;
|
|
597
|
+
renderOnly("rightPanel");
|
|
598
|
+
},
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/** The affected count, in words — and "unknown" wherever the query cannot answer. */
|
|
603
|
+
function affectedSentence(state: ReturnType<typeof peekUsages>): string {
|
|
604
|
+
if (state === null || state.status === "pending") {
|
|
605
|
+
return "counting the elements it affects…";
|
|
606
|
+
}
|
|
607
|
+
if (state.status !== "ready") {
|
|
608
|
+
return "how many elements that is, is unknown";
|
|
609
|
+
}
|
|
610
|
+
const { files, refsTotal } = state.result;
|
|
611
|
+
if (refsTotal === 0) {
|
|
612
|
+
return "no element in the project uses it yet";
|
|
613
|
+
}
|
|
614
|
+
return `${refsTotal} element${refsTotal === 1 ? "" : "s"} in ${files.length} file${
|
|
615
|
+
files.length === 1 ? "" : "s"
|
|
616
|
+
}`;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/** The two axes the Style tab states but does not own, plus the element it is pointed at. */
|
|
620
|
+
function targetSegments(
|
|
621
|
+
elementLabel: string,
|
|
622
|
+
mediaTab: string | null,
|
|
623
|
+
hasBreakpoints: boolean,
|
|
624
|
+
schemeLayer: string | null,
|
|
625
|
+
): TargetSegment[] {
|
|
626
|
+
const segments: TargetSegment[] = [
|
|
627
|
+
{
|
|
628
|
+
key: "element",
|
|
629
|
+
label: elementLabel,
|
|
630
|
+
title: "What these edits are pointed at — click to find it in the Outline",
|
|
631
|
+
onActivate: () => runCommand("view.setActivity", { tab: "layers" }),
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
key: "media",
|
|
635
|
+
label: hasBreakpoints && mediaTab ? `@${mediaDisplayName(mediaTab)}` : "Base",
|
|
636
|
+
title:
|
|
637
|
+
"The breakpoint these edits land in. Choose it on the pane's Context control; click to " +
|
|
638
|
+
"manage breakpoints.",
|
|
639
|
+
onActivate: () => runCommand("settings.open", { section: "contexts" }),
|
|
640
|
+
},
|
|
641
|
+
];
|
|
642
|
+
if (schemeLayer) {
|
|
643
|
+
segments.push({
|
|
644
|
+
key: "scheme",
|
|
645
|
+
label: `${mediaDisplayName(schemeLayer)} variant`,
|
|
646
|
+
title:
|
|
647
|
+
"Edits land in this colour-scheme variant. Set the pane's Context control back to Auto " +
|
|
648
|
+
"to edit base styles; click to manage schemes.",
|
|
649
|
+
onActivate: () => runCommand("settings.open", { section: "contexts" }),
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
return segments;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
// ─── Provenance ──────────────────────────────────────────────────────────────
|
|
656
|
+
|
|
657
|
+
/** An inherited value, its donor's name, and how to go there. */
|
|
658
|
+
interface InheritedInfo {
|
|
659
|
+
value: string | number;
|
|
660
|
+
donor: string;
|
|
661
|
+
jump?: (() => void) | undefined;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* The signal a `${…}` style value reads, when it reads one.
|
|
666
|
+
*
|
|
667
|
+
* `state.` is optional because both spellings occur in documents and both mean the same entry — the
|
|
668
|
+
* chip names the signal, not the accessor path it was reached by.
|
|
669
|
+
*/
|
|
670
|
+
function templateSignalOf(value: unknown): string | null {
|
|
671
|
+
if (typeof value !== "string") {
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
return /\$\{\s*(?:state\.)?([A-Za-z_$][\w$]*)/.exec(value)?.[1] ?? null;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/** Whether a value is a `${}` template rather than a literal — the `bound` rung for a CSS value. */
|
|
678
|
+
function isTemplateValue(value: unknown): boolean {
|
|
679
|
+
return typeof value === "string" && value.includes("${");
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/** What the Style tab can say about one property's origin. */
|
|
683
|
+
interface ProvenanceCtx {
|
|
684
|
+
own: JxStyle;
|
|
685
|
+
active: JxStyle;
|
|
686
|
+
inherited: Record<string, InheritedInfo>;
|
|
687
|
+
signals: string[];
|
|
688
|
+
clear: (prop: string) => void;
|
|
689
|
+
/**
|
|
690
|
+
* Properties the selected elements DISAGREE about, and how many elements are selected (§6.5).
|
|
691
|
+
*
|
|
692
|
+
* Empty for a selection of one — which is what keeps every other branch of `provenanceOf`
|
|
693
|
+
* reachable exactly as often as it was before the selection became a set.
|
|
694
|
+
*/
|
|
695
|
+
mixed?: { props: Set<string>; count: number } | undefined;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
/**
|
|
699
|
+
* Where this property's value came from.
|
|
700
|
+
*
|
|
701
|
+
* Four answers, and three of them were previously indistinguishable from each other: an inherited
|
|
702
|
+
* value arrived as a placeholder identical to the CSS initial value, a site token arrived looking
|
|
703
|
+
* like a local override, and a `${}` binding arrived looking like a literal with braces in it.
|
|
704
|
+
*/
|
|
705
|
+
function provenanceOf(prop: string, ctx: ProvenanceCtx): FieldProvenance {
|
|
706
|
+
// Mixed is decided FIRST, because it is a fact about the selection rather than about the value:
|
|
707
|
+
// The primary's own value may be perfectly ordinary while five other elements disagree with it,
|
|
708
|
+
// And drawing that as `set` is exactly the lie this state exists to stop.
|
|
709
|
+
if (ctx.mixed?.props.has(prop)) {
|
|
710
|
+
return {
|
|
711
|
+
state: "mixed",
|
|
712
|
+
donor: String(ctx.mixed.count),
|
|
713
|
+
onClick: () => ctx.clear(prop),
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
const value = ctx.active[prop];
|
|
717
|
+
if (value !== undefined && ctx.own[prop] === undefined) {
|
|
718
|
+
// Present in the effective style but not in the document's own: it came from the site style.
|
|
719
|
+
// No jump: the project style document is not something this tab can open.
|
|
720
|
+
return {
|
|
721
|
+
state: "inherited",
|
|
722
|
+
donor: "site tokens",
|
|
723
|
+
title: `Inherited from the project's site tokens — ${prop} is not set on this document`,
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
if (value !== undefined) {
|
|
727
|
+
if (isTemplateValue(value)) {
|
|
728
|
+
const signal = templateSignalOf(value);
|
|
729
|
+
return {
|
|
730
|
+
state: "bound",
|
|
731
|
+
...(signal ? { donor: signal } : {}),
|
|
732
|
+
...(signal && ctx.signals.includes(signal)
|
|
733
|
+
? {
|
|
734
|
+
onClick: () => {
|
|
735
|
+
runCommand("view.setActivity", { tab: "data" });
|
|
736
|
+
runCommand("data.expandRow", { name: signal });
|
|
737
|
+
},
|
|
738
|
+
}
|
|
739
|
+
: {}),
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
return { state: "set", onClick: () => ctx.clear(prop) };
|
|
743
|
+
}
|
|
744
|
+
const inherited = ctx.inherited[prop];
|
|
745
|
+
if (inherited) {
|
|
746
|
+
return {
|
|
747
|
+
state: "inherited",
|
|
748
|
+
donor: inherited.donor,
|
|
749
|
+
...(inherited.jump ? { onClick: inherited.jump } : {}),
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
return { state: "default" };
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
/**
|
|
756
|
+
* Where a SHORTHAND row's value came from.
|
|
757
|
+
*
|
|
758
|
+
* A shorthand is less a value than a name for four of them, so every answer it can give is about
|
|
759
|
+
* five properties at once: `padding` is set when `padding` or any of `paddingTop…Left` is, mixed
|
|
760
|
+
* when the selection disagrees about any of the five, and inherited when the cascade supplies one
|
|
761
|
+
* and this document supplies none. Asking only about the shorthand key is what let the row draw a
|
|
762
|
+
* plain "Clear padding" dot over a property the panel had already computed as mixed.
|
|
763
|
+
*
|
|
764
|
+
* @param {string} shortProp
|
|
765
|
+
* @param {ProvenanceCtx} ctx
|
|
766
|
+
* @param {(() => void) | undefined} [clearAll] — clears the shorthand AND its longhands, which is
|
|
767
|
+
* the only clear this row has ever meant; omitted where there is nothing to clear.
|
|
768
|
+
* @returns {FieldProvenance}
|
|
769
|
+
*/
|
|
770
|
+
function shorthandProvenance(
|
|
771
|
+
shortProp: string,
|
|
772
|
+
ctx: ProvenanceCtx,
|
|
773
|
+
clearAll?: (() => void) | undefined,
|
|
774
|
+
): FieldProvenance {
|
|
775
|
+
const props = [shortProp, ...(getLonghands(shortProp) as CssLonghand[]).map((l) => l.name)];
|
|
776
|
+
const { mixed } = ctx;
|
|
777
|
+
if (mixed && props.some((p) => mixed.props.has(p))) {
|
|
778
|
+
return {
|
|
779
|
+
state: "mixed",
|
|
780
|
+
donor: String(mixed.count),
|
|
781
|
+
...(clearAll ? { onClick: clearAll } : {}),
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
for (const p of props) {
|
|
785
|
+
const prov = provenanceOf(p, ctx);
|
|
786
|
+
if (prov.state === "default") {
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
return prov.state === "set" && clearAll ? { ...prov, onClick: clearAll } : prov;
|
|
790
|
+
}
|
|
791
|
+
return DEFAULT_PROVENANCE;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Where a longhand CHILD row's value came from.
|
|
796
|
+
*
|
|
797
|
+
* A longhand can hold a value it was never given: `padding: 1px 2px` fills four child rows, and
|
|
798
|
+
* clearing one of them recompresses the shorthand rather than deleting a key. So a value derived
|
|
799
|
+
* from the shorthand counts as `set` here, and a selection that disagrees about the shorthand
|
|
800
|
+
* disagrees about every child the shorthand fills.
|
|
801
|
+
*
|
|
802
|
+
* @param {string} name
|
|
803
|
+
* @param {string} shortProp
|
|
804
|
+
* @param {boolean} hasValue — set here, or filled in by the shorthand above.
|
|
805
|
+
* @param {ProvenanceCtx} ctx
|
|
806
|
+
* @param {() => void} clearChild
|
|
807
|
+
* @returns {FieldProvenance}
|
|
808
|
+
*/
|
|
809
|
+
function longhandProvenance(
|
|
810
|
+
name: string,
|
|
811
|
+
shortProp: string,
|
|
812
|
+
hasValue: boolean,
|
|
813
|
+
ctx: ProvenanceCtx,
|
|
814
|
+
clearChild: () => void,
|
|
815
|
+
): FieldProvenance {
|
|
816
|
+
const { mixed } = ctx;
|
|
817
|
+
if (mixed && (mixed.props.has(name) || (hasValue && mixed.props.has(shortProp)))) {
|
|
818
|
+
return { state: "mixed", donor: String(mixed.count), onClick: clearChild };
|
|
819
|
+
}
|
|
820
|
+
const prov = provenanceOf(name, ctx);
|
|
821
|
+
if (prov.state === "set" || (prov.state === "default" && hasValue)) {
|
|
822
|
+
return { state: "set", onClick: clearChild };
|
|
823
|
+
}
|
|
824
|
+
return prov;
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* A section's tally, counting a shorthand as set when any of its longhands is.
|
|
829
|
+
*
|
|
830
|
+
* @param {{ prop: string; entry: CssPropertyEntry }[]} entries
|
|
831
|
+
* @param {ProvenanceCtx} ctx
|
|
832
|
+
*/
|
|
833
|
+
function sectionProvenance(
|
|
834
|
+
entries: { prop: string; entry: CssPropertyEntry }[],
|
|
835
|
+
ctx: ProvenanceCtx,
|
|
836
|
+
): ProvenanceState[] {
|
|
837
|
+
return entries.map(({ prop, entry }) =>
|
|
838
|
+
inferInputType(entry) === "shorthand"
|
|
839
|
+
? shorthandProvenance(prop, ctx).state
|
|
840
|
+
: provenanceOf(prop, ctx).state,
|
|
841
|
+
);
|
|
842
|
+
}
|
|
843
|
+
|
|
301
844
|
// ─── Main template ──────────────────────────────────────────────────────────
|
|
302
845
|
|
|
303
846
|
/**
|
|
304
847
|
* @param {JxMutableNode} node
|
|
305
848
|
* @param {string | null} activeMediaTab
|
|
306
849
|
* @param {string | null} activeSelector
|
|
307
|
-
* @param {JxStyle} [
|
|
850
|
+
* @param {{ effectiveStyle?: JxStyle; stylebookSelector?: string | null }} [opts] —
|
|
851
|
+
* `effectiveStyle` is the site-merged style Stylebook edits against; `stylebookSelector` is the
|
|
852
|
+
* tag catalogue entry being styled, which is what makes the edit tag-wide rather than
|
|
853
|
+
* element-wide and therefore what the scope chip reports.
|
|
308
854
|
*/
|
|
309
855
|
function styleSidebarTemplate(
|
|
310
856
|
node: JxMutableNode,
|
|
311
857
|
activeMediaTab: string | null,
|
|
312
858
|
activeSelector: string | null,
|
|
313
|
-
effectiveStyle?: JxStyle,
|
|
859
|
+
opts: { effectiveStyle?: JxStyle | undefined; stylebookSelector?: string | null } = {},
|
|
314
860
|
) {
|
|
861
|
+
const { effectiveStyle, stylebookSelector = null } = opts;
|
|
315
862
|
const tab = activeTab.value!;
|
|
316
|
-
const sel = tab.session.selection as JxPath;
|
|
317
|
-
|
|
863
|
+
const sel = primarySelection(tab.session.selection) as JxPath;
|
|
864
|
+
/** The document's OWN style — what a commit writes into. */
|
|
865
|
+
const ownRootStyle: JxStyle = node.style || {};
|
|
866
|
+
const style = effectiveStyle ?? ownRootStyle;
|
|
318
867
|
// Signals seeding the ${} template default when a style value escalates (no $ref in JxStyle).
|
|
319
868
|
const templateSignals = Object.entries(tab.doc.document.state || {})
|
|
320
869
|
.filter(
|
|
@@ -341,130 +890,59 @@ function styleSidebarTemplate(
|
|
|
341
890
|
// The media context edits actually target: a valid breakpoint tab, else the scheme layer.
|
|
342
891
|
const editMedia = mediaTab && mediaNames.length > 0 ? mediaTab : schemeLayer;
|
|
343
892
|
|
|
344
|
-
// ──
|
|
345
|
-
const mediaTabsT =
|
|
346
|
-
mediaNames.length > 0
|
|
347
|
-
? html`
|
|
348
|
-
<sp-tabs
|
|
349
|
-
size="s"
|
|
350
|
-
selected=${mediaTab || "base"}
|
|
351
|
-
@change=${(e: Event) => {
|
|
352
|
-
const val = (e.target as HTMLElement & { selected: string }).selected;
|
|
353
|
-
const newMedia = val === "base" ? null : val;
|
|
354
|
-
if (newMedia !== tab.session.ui.activeMedia) {
|
|
355
|
-
tab.session.ui.activeMedia = newMedia;
|
|
356
|
-
}
|
|
357
|
-
}}
|
|
358
|
-
>
|
|
359
|
-
<sp-tab label="Base" value="base"></sp-tab>
|
|
360
|
-
${mediaNames.map(
|
|
361
|
-
(name) => html` <sp-tab label=${mediaDisplayName(name)} value=${name}></sp-tab> `,
|
|
362
|
-
)}
|
|
363
|
-
</sp-tabs>
|
|
364
|
-
`
|
|
365
|
-
: nothing;
|
|
366
|
-
|
|
367
|
-
// ── Selector dropdown ──────────────────────────────────────────────────────
|
|
893
|
+
// ── The selector axis — the one this tab owns ──────────────────────────────
|
|
368
894
|
const contextStyle = editMedia
|
|
369
895
|
? (style[`@${editMedia}`] as Record<string, unknown>) || {}
|
|
370
896
|
: style;
|
|
371
|
-
const
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const _selectorVal = activeSelector || "__base__";
|
|
380
|
-
const selectorT = html`
|
|
381
|
-
<sp-picker
|
|
382
|
-
size="s"
|
|
383
|
-
class="selector-select"
|
|
384
|
-
quiet
|
|
385
|
-
.value=${live(_selectorVal)}
|
|
386
|
-
@change=${(e: Event) => {
|
|
387
|
-
const val = (e.target as HTMLElement & { value: string }).value;
|
|
388
|
-
if (val === "__add_custom__") {
|
|
389
|
-
requestAnimationFrame(() => {
|
|
390
|
-
(e.target as HTMLElement & { value: string }).value = activeSelector || "__base__";
|
|
391
|
-
});
|
|
392
|
-
const picker = e.target as HTMLElement;
|
|
393
|
-
const bar = picker.closest(".style-toolbar") as HTMLElement;
|
|
394
|
-
picker.style.display = "none";
|
|
395
|
-
const inp = document.createElement("input");
|
|
396
|
-
inp.type = "text";
|
|
397
|
-
inp.className = "selector-custom-input";
|
|
398
|
-
inp.placeholder = ":hover, .child, &.active, [attr]";
|
|
399
|
-
bar.append(inp);
|
|
400
|
-
inp.focus();
|
|
401
|
-
let done = false;
|
|
402
|
-
const finish = (accept: boolean) => {
|
|
403
|
-
if (done) {
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
done = true;
|
|
407
|
-
const v = inp.value.trim();
|
|
408
|
-
inp.remove();
|
|
409
|
-
picker.style.display = "";
|
|
410
|
-
if (accept && v && isNestedSelector(v)) {
|
|
411
|
-
activeTab.value!.session.ui.activeSelector = v;
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
inp.addEventListener("keydown", (ev) => {
|
|
415
|
-
if (ev.key === "Enter") {
|
|
416
|
-
finish(true);
|
|
417
|
-
} else if (ev.key === "Escape") {
|
|
418
|
-
finish(false);
|
|
419
|
-
}
|
|
420
|
-
});
|
|
421
|
-
inp.addEventListener("blur", () => finish(inp.value.trim().length > 0));
|
|
422
|
-
return;
|
|
423
|
-
}
|
|
424
|
-
const newSelector = val === "__base__" ? null : val;
|
|
425
|
-
activeTab.value!.session.ui.activeSelector = newSelector;
|
|
426
|
-
}}
|
|
427
|
-
>
|
|
428
|
-
<sp-menu-item value="__base__">(base)</sp-menu-item>
|
|
429
|
-
<sp-menu-divider></sp-menu-divider>
|
|
430
|
-
${COMMON_SELECTORS.map(
|
|
431
|
-
(s) => html`
|
|
432
|
-
<sp-menu-item value=${s}>${existingSet.has(s) ? `${s} \u25CF` : s}</sp-menu-item>
|
|
433
|
-
`,
|
|
434
|
-
)}
|
|
435
|
-
${
|
|
436
|
-
extraSelectors.length > 0
|
|
437
|
-
? html`
|
|
438
|
-
<sp-menu-divider></sp-menu-divider>
|
|
439
|
-
${extraSelectors.map((s) => html` <sp-menu-item value=${s}>${s} ●</sp-menu-item> `)}
|
|
440
|
-
`
|
|
441
|
-
: nothing
|
|
442
|
-
}
|
|
443
|
-
<sp-menu-divider></sp-menu-divider>
|
|
444
|
-
<sp-menu-item value="__add_custom__">+ Add custom…</sp-menu-item>
|
|
445
|
-
</sp-picker>
|
|
446
|
-
`;
|
|
897
|
+
const declaredSelectors = new Set(Object.keys(contextStyle).filter((s) => isNestedSelector(s)));
|
|
898
|
+
const selectorOptions = [
|
|
899
|
+
...new Set([
|
|
900
|
+
...COMMON_SELECTORS,
|
|
901
|
+
...declaredSelectors,
|
|
902
|
+
...(activeSelector ? [activeSelector] : []),
|
|
903
|
+
]),
|
|
904
|
+
];
|
|
447
905
|
|
|
448
|
-
// ──
|
|
449
|
-
//
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
906
|
+
// ── The Target Line (§6.1) ─────────────────────────────────────────────────
|
|
907
|
+
// The three widgets this replaces are gone: the breakpoint `<sp-tabs>` strip, the
|
|
908
|
+
// `.selector-select` picker and the `.style-scheme-badge`. The first two named axes the pane
|
|
909
|
+
// Context bar already selects; the third emitted a class with no CSS rule anywhere in the repo.
|
|
910
|
+
const stylebookTag = stylebookTagOf(stylebookSelector);
|
|
911
|
+
const elementLabel =
|
|
912
|
+
stylebookTag ?? (typeof node.tagName === "string" ? node.tagName : "element");
|
|
913
|
+
const targetLineT = renderTargetLine({
|
|
914
|
+
segments: targetSegments(elementLabel, mediaTab, mediaNames.length > 0, schemeLayer),
|
|
915
|
+
selector: {
|
|
916
|
+
value: activeSelector,
|
|
917
|
+
options: selectorOptions,
|
|
918
|
+
declared: declaredSelectors,
|
|
919
|
+
onSelect: (value) => {
|
|
920
|
+
activeTab.value!.session.ui.activeSelector = value;
|
|
921
|
+
},
|
|
922
|
+
onAddCustom: () => {
|
|
923
|
+
// One flow, through `ui/layers.ts` — the imperative `<input>` this replaces was appended to
|
|
924
|
+
// A bar lit-html owns, bypassing both the renderer and the overlay stack (§11.4).
|
|
925
|
+
void showPromptDialog("Add Selector", {
|
|
926
|
+
confirmLabel: "Use",
|
|
927
|
+
message: "A state or nested rule to edit under this element.",
|
|
928
|
+
placeholder: ":hover, .child, &.active, [attr]",
|
|
929
|
+
validate: (v) =>
|
|
930
|
+
isNestedSelector(v.trim()) ? "" : 'A selector must start with ":", ".", "&" or "[".',
|
|
931
|
+
}).then((value) => {
|
|
932
|
+
if (value) {
|
|
933
|
+
activeTab.value!.session.ui.activeSelector = value.trim();
|
|
934
|
+
renderOnly("rightPanel");
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
},
|
|
938
|
+
},
|
|
939
|
+
scope: resolveScope(tab, stylebookTag),
|
|
940
|
+
});
|
|
466
941
|
|
|
467
942
|
// ── Filter bar ─────────────────────────────────────────────────────────────
|
|
943
|
+
// One control. The "Active" toggle is gone: it existed only because provenance was invisible
|
|
944
|
+
// With a section closed, and a heading that says "3 set here · 2 inherited" answers the same
|
|
945
|
+
// Question without hiding two thirds of the panel to do it (§6.2).
|
|
468
946
|
const filterBarT = html`
|
|
469
947
|
<div class="style-filter-bar">
|
|
470
948
|
<sp-textfield
|
|
@@ -476,85 +954,85 @@ function styleSidebarTemplate(
|
|
|
476
954
|
activeTab.value!.session.ui.styleFilter = (e.target as HTMLInputElement).value;
|
|
477
955
|
}}
|
|
478
956
|
></sp-textfield>
|
|
479
|
-
<sp-action-button
|
|
480
|
-
size="xs"
|
|
481
|
-
class="style-filter-toggle"
|
|
482
|
-
?selected=${tab.session.ui.styleFilterActive}
|
|
483
|
-
@click=${() => {
|
|
484
|
-
activeTab.value!.session.ui.styleFilterActive =
|
|
485
|
-
!activeTab.value!.session.ui.styleFilterActive;
|
|
486
|
-
}}
|
|
487
|
-
>
|
|
488
|
-
Active
|
|
489
|
-
</sp-action-button>
|
|
490
957
|
</div>
|
|
491
958
|
`;
|
|
492
959
|
|
|
493
960
|
// ── Determine the active style object ──────────────────────────────────────
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
961
|
+
const activeStyle = resolveContextStyle(style, activeSelector, editMedia);
|
|
962
|
+
// The SAME coordinate, resolved against the document's own style. In Stylebook `style` is the
|
|
963
|
+
// Site-merged effective style, so a property present here and absent there arrived from the
|
|
964
|
+
// Project's tokens — a donor the panel can now name instead of drawing it as a local override
|
|
965
|
+
// Whose "clear" would silently do nothing.
|
|
966
|
+
const ownStyle =
|
|
967
|
+
style === ownRootStyle
|
|
968
|
+
? activeStyle
|
|
969
|
+
: resolveContextStyle(ownRootStyle, activeSelector, editMedia);
|
|
970
|
+
// ── The selection, and what it means to commit to it (§6.5) ────────────────
|
|
971
|
+
// Every selected element gets the same write, inside ONE `transactDoc` — so setting padding on
|
|
972
|
+
// Six cards is one decision and one undo step. With one element selected this loop runs once
|
|
973
|
+
// Against `sel` and is indistinguishable from the single-target commit it replaces.
|
|
974
|
+
const targets = tab.session.selection.length > 0 ? tab.session.selection : [sel];
|
|
975
|
+
/**
|
|
976
|
+
* ONE write, applied to every selected element — the only write function this tab has.
|
|
977
|
+
*
|
|
978
|
+
* It takes the caller's transaction rather than opening its own, because a row that writes twice
|
|
979
|
+
* (a shorthand and the longhands it supersedes; a renamed custom property and its old key) must
|
|
980
|
+
* still be one undo step. There is no single-target counterpart: every row that had one wrote to
|
|
981
|
+
* the primary element and silently dropped the rest of the selection on the floor.
|
|
982
|
+
*/
|
|
983
|
+
const mutateTargets: StyleMutateFn = (t, prop, val) => {
|
|
984
|
+
for (const target of targets) {
|
|
985
|
+
contextMutate(target, activeSelector, editMedia)(t, prop, val);
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
const commitStyle = (prop: string, val?: string | Record<string, unknown> | undefined) =>
|
|
989
|
+
transactDoc(activeTab.value, (t) => mutateTargets(t, prop, val));
|
|
990
|
+
/** One target's OWN block at this coordinate — what a write that preserves values reads first. */
|
|
991
|
+
const targetStyleOf = (t: Tab, target: JxPath): JxStyle => {
|
|
992
|
+
const targetNode = getNodeAtPath(t.doc.document, target) as JxMutableNode | undefined;
|
|
993
|
+
return resolveContextStyle(targetNode?.style ?? {}, activeSelector, editMedia);
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
// ── Compute inherited style, and NAME THE DONOR ────────────────────────────
|
|
997
|
+
// Scheme layer: the base styles show through as inherited; breakpoint tabs inherit from lower
|
|
998
|
+
// Breakpoints as before. Either way the donor is known at this point and is now kept: it is the
|
|
999
|
+
// Difference between "some value is showing through" and "Base sets this to 16px, go and look".
|
|
1000
|
+
const inheritedInfo: Record<string, InheritedInfo> = {};
|
|
1001
|
+
if (schemeLayer) {
|
|
1002
|
+
const baseBlock = activeSelector ? (getNestedStyle(style, activeSelector) ?? {}) : style;
|
|
1003
|
+
for (const [p, v] of Object.entries(baseBlock)) {
|
|
535
1004
|
if (typeof v !== "object") {
|
|
536
|
-
|
|
1005
|
+
inheritedInfo[p] = {
|
|
1006
|
+
value: (v as string | number) ?? "",
|
|
1007
|
+
donor: "Base",
|
|
1008
|
+
jump: () => updateUi(activeTab.value, "previewColorScheme", "auto"),
|
|
1009
|
+
};
|
|
537
1010
|
}
|
|
538
1011
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
1012
|
+
} else {
|
|
1013
|
+
for (const [p, source] of Object.entries(
|
|
1014
|
+
computeInheritedSources(style, mediaNames, mediaTab, activeSelector),
|
|
1015
|
+
)) {
|
|
1016
|
+
inheritedInfo[p] = {
|
|
1017
|
+
value: source.value,
|
|
1018
|
+
donor: source.donor === null ? "Base" : mediaDisplayName(source.donor),
|
|
1019
|
+
jump: () => updateUi(activeTab.value, "activeMedia", source.donor),
|
|
1020
|
+
};
|
|
1021
|
+
}
|
|
544
1022
|
}
|
|
545
|
-
const
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
1023
|
+
const inheritedStyle: Record<string, string | number> = {};
|
|
1024
|
+
for (const [p, info] of Object.entries(inheritedInfo)) {
|
|
1025
|
+
inheritedStyle[p] = info.value;
|
|
1026
|
+
}
|
|
1027
|
+
const mixedProps = mixedStyleProps(tab, targets, activeSelector, editMedia);
|
|
1028
|
+
const provCtx: ProvenanceCtx = {
|
|
1029
|
+
own: ownStyle,
|
|
1030
|
+
active: activeStyle,
|
|
1031
|
+
inherited: inheritedInfo,
|
|
1032
|
+
signals: templateSignals,
|
|
1033
|
+
clear: (prop: string) => commitStyle(prop),
|
|
1034
|
+
...(mixedProps.size > 0 ? { mixed: { count: targets.length, props: mixedProps } } : {}),
|
|
1035
|
+
};
|
|
558
1036
|
|
|
559
1037
|
// Auto-open sections that have properties
|
|
560
1038
|
const newSections = autoOpenSections({ style: activeStyle }, tab.session.ui.styleSections);
|
|
@@ -606,8 +1084,7 @@ function styleSidebarTemplate(
|
|
|
606
1084
|
|
|
607
1085
|
// ── Filter state ─────────────────────────────────────────────────────────
|
|
608
1086
|
const filterText = (tab.session.ui.styleFilter || "").toLowerCase();
|
|
609
|
-
const
|
|
610
|
-
const isFiltering = filterText.length > 0 || filterActive;
|
|
1087
|
+
const isFiltering = filterText.length > 0;
|
|
611
1088
|
|
|
612
1089
|
// ── Section templates ────────────────────────────────────────────────────
|
|
613
1090
|
const sectionTemplates = cssMeta.$sections
|
|
@@ -629,6 +1106,34 @@ function styleSidebarTemplate(
|
|
|
629
1106
|
},
|
|
630
1107
|
);
|
|
631
1108
|
|
|
1109
|
+
const clearSection = () => {
|
|
1110
|
+
transactDoc(activeTab.value, (t) => {
|
|
1111
|
+
for (const { prop, entry } of sectionActiveProps) {
|
|
1112
|
+
if (activeStyle[prop] !== undefined) {
|
|
1113
|
+
mutateTargets(t, prop);
|
|
1114
|
+
}
|
|
1115
|
+
if (inferInputType(entry) === "shorthand") {
|
|
1116
|
+
for (const l of getLonghands(prop) as CssLonghand[]) {
|
|
1117
|
+
mutateTargets(t, l.name);
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
});
|
|
1122
|
+
};
|
|
1123
|
+
|
|
1124
|
+
// The heading's own answer to "is anything in here set, inherited or bound" — the question
|
|
1125
|
+
// The retired "Active" toggle answered by hiding everything that was not.
|
|
1126
|
+
const counts = countProvenance(sectionProvenance(entries, provCtx));
|
|
1127
|
+
const headingT = html`
|
|
1128
|
+
<span slot="heading" class="style-section-heading">
|
|
1129
|
+
${sec.label}
|
|
1130
|
+
${renderProvenanceDots(counts, {
|
|
1131
|
+
onClearSet: sectionActiveProps.length > 0 ? clearSection : undefined,
|
|
1132
|
+
clearTitle: `Clear all ${sec.label.toLowerCase()} properties`,
|
|
1133
|
+
})}
|
|
1134
|
+
</span>
|
|
1135
|
+
`;
|
|
1136
|
+
|
|
632
1137
|
const isOpen = isFiltering ? true : (tab.session.ui.styleSections[sec.key] ?? false);
|
|
633
1138
|
|
|
634
1139
|
if (!isOpen) {
|
|
@@ -643,37 +1148,7 @@ function styleSidebarTemplate(
|
|
|
643
1148
|
};
|
|
644
1149
|
}}
|
|
645
1150
|
>
|
|
646
|
-
${
|
|
647
|
-
sectionActiveProps.length > 0
|
|
648
|
-
? html`
|
|
649
|
-
<span slot="heading" style="display:flex;align-items:center;gap:6px">
|
|
650
|
-
${sec.label}
|
|
651
|
-
<span
|
|
652
|
-
class="set-dot set-dot--section"
|
|
653
|
-
title="Clear all ${sec.label.toLowerCase()} properties"
|
|
654
|
-
@click=${(e: Event) => {
|
|
655
|
-
e.stopPropagation();
|
|
656
|
-
e.preventDefault();
|
|
657
|
-
transactDoc(activeTab.value, (t) => {
|
|
658
|
-
for (const { prop, entry } of sectionActiveProps) {
|
|
659
|
-
if (activeStyle[prop] !== undefined) {
|
|
660
|
-
commitMutate(t, prop);
|
|
661
|
-
}
|
|
662
|
-
if (inferInputType(entry) === "shorthand") {
|
|
663
|
-
for (const l of getLonghands(prop) as CssLonghand[]) {
|
|
664
|
-
if (activeStyle[l.name] !== undefined) {
|
|
665
|
-
commitMutate(t, l.name);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
}
|
|
669
|
-
}
|
|
670
|
-
});
|
|
671
|
-
}}
|
|
672
|
-
></span>
|
|
673
|
-
</span>
|
|
674
|
-
`
|
|
675
|
-
: nothing
|
|
676
|
-
}
|
|
1151
|
+
${headingT}
|
|
677
1152
|
</sp-accordion-item>
|
|
678
1153
|
`;
|
|
679
1154
|
}
|
|
@@ -694,18 +1169,6 @@ function styleSidebarTemplate(
|
|
|
694
1169
|
continue;
|
|
695
1170
|
}
|
|
696
1171
|
}
|
|
697
|
-
if (filterActive) {
|
|
698
|
-
if (type === "shorthand") {
|
|
699
|
-
const longhands = getLonghands(prop) as CssLonghand[];
|
|
700
|
-
const hasAnySet =
|
|
701
|
-
hasVal || longhands.some((l: CssLonghand) => activeStyle[l.name] !== undefined);
|
|
702
|
-
if (!hasAnySet) {
|
|
703
|
-
continue;
|
|
704
|
-
}
|
|
705
|
-
} else if (!hasVal) {
|
|
706
|
-
continue;
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
1172
|
|
|
710
1173
|
if (type === "shorthand") {
|
|
711
1174
|
const longhands = getLonghands(prop) as CssLonghand[];
|
|
@@ -715,7 +1178,14 @@ function styleSidebarTemplate(
|
|
|
715
1178
|
continue;
|
|
716
1179
|
}
|
|
717
1180
|
rows.push(
|
|
718
|
-
renderShorthandRow(
|
|
1181
|
+
renderShorthandRow({
|
|
1182
|
+
prop,
|
|
1183
|
+
entry,
|
|
1184
|
+
style: activeStyle,
|
|
1185
|
+
mutate: mutateTargets,
|
|
1186
|
+
inherited: inheritedStyle,
|
|
1187
|
+
ctx: provCtx,
|
|
1188
|
+
}),
|
|
719
1189
|
);
|
|
720
1190
|
} else {
|
|
721
1191
|
const isWarning = hasVal && !condMet;
|
|
@@ -726,12 +1196,12 @@ function styleSidebarTemplate(
|
|
|
726
1196
|
prop,
|
|
727
1197
|
(val as string) ?? "",
|
|
728
1198
|
(newVal: string | undefined) => commitStyle(prop, newVal || undefined),
|
|
729
|
-
() => commitStyle(prop),
|
|
730
1199
|
isWarning,
|
|
731
1200
|
sec.$layout === "grid",
|
|
732
1201
|
inheritedStyle[prop] as string | undefined,
|
|
733
1202
|
templateSignals,
|
|
734
1203
|
`style|${sel.join("/")}|${editMedia ?? ""}|${activeSelector ?? ""}|${prop}`,
|
|
1204
|
+
provenanceOf(prop, provCtx),
|
|
735
1205
|
),
|
|
736
1206
|
);
|
|
737
1207
|
}
|
|
@@ -753,37 +1223,7 @@ function styleSidebarTemplate(
|
|
|
753
1223
|
};
|
|
754
1224
|
}}
|
|
755
1225
|
>
|
|
756
|
-
${
|
|
757
|
-
sectionActiveProps.length > 0
|
|
758
|
-
? html`
|
|
759
|
-
<span slot="heading" style="display:flex;align-items:center;gap:6px">
|
|
760
|
-
${sec.label}
|
|
761
|
-
<span
|
|
762
|
-
class="set-dot set-dot--section"
|
|
763
|
-
title="Clear all ${sec.label.toLowerCase()} properties"
|
|
764
|
-
@click=${(e: Event) => {
|
|
765
|
-
e.stopPropagation();
|
|
766
|
-
e.preventDefault();
|
|
767
|
-
transactDoc(activeTab.value, (t) => {
|
|
768
|
-
for (const { prop, entry } of sectionActiveProps) {
|
|
769
|
-
if (activeStyle[prop] !== undefined) {
|
|
770
|
-
commitMutate(t, prop);
|
|
771
|
-
}
|
|
772
|
-
if (inferInputType(entry) === "shorthand") {
|
|
773
|
-
for (const l of getLonghands(prop) as CssLonghand[]) {
|
|
774
|
-
if (activeStyle[l.name] !== undefined) {
|
|
775
|
-
commitMutate(t, l.name);
|
|
776
|
-
}
|
|
777
|
-
}
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
});
|
|
781
|
-
}}
|
|
782
|
-
></span>
|
|
783
|
-
</span>
|
|
784
|
-
`
|
|
785
|
-
: nothing
|
|
786
|
-
}
|
|
1226
|
+
${headingT}
|
|
787
1227
|
<div class=${sec.$layout === "grid" ? "style-section-body--grid" : ""}>${rows}</div>
|
|
788
1228
|
</sp-accordion-item>
|
|
789
1229
|
`;
|
|
@@ -813,12 +1253,26 @@ function styleSidebarTemplate(
|
|
|
813
1253
|
.value=${live(prop)}
|
|
814
1254
|
@change=${(e: Event) => {
|
|
815
1255
|
const newProp = (e.target as HTMLInputElement).value.trim();
|
|
816
|
-
if (newProp
|
|
817
|
-
|
|
818
|
-
commitMutate(t, prop);
|
|
819
|
-
commitMutate(t, newProp, String(activeStyle[prop]));
|
|
820
|
-
});
|
|
1256
|
+
if (!newProp || newProp === prop) {
|
|
1257
|
+
return;
|
|
821
1258
|
}
|
|
1259
|
+
// A rename is the one write that cannot be fanned out with a single value: each
|
|
1260
|
+
// Element keeps ITS OWN value under the new key. The old code batched half of the
|
|
1261
|
+
// Rename (the delete) against the primary and the other half against the primary
|
|
1262
|
+
// Too, while the value cell beside it wrote to the whole selection — so renaming
|
|
1263
|
+
// A property the selection shared moved it on one element and left it on the
|
|
1264
|
+
// Rest.
|
|
1265
|
+
transactDoc(activeTab.value, (t) => {
|
|
1266
|
+
for (const target of targets) {
|
|
1267
|
+
const current = targetStyleOf(t, target)[prop];
|
|
1268
|
+
if (current === undefined) {
|
|
1269
|
+
continue;
|
|
1270
|
+
}
|
|
1271
|
+
const write = contextMutate(target, activeSelector, editMedia);
|
|
1272
|
+
write(t, prop);
|
|
1273
|
+
write(t, newProp, String(current));
|
|
1274
|
+
}
|
|
1275
|
+
});
|
|
822
1276
|
}}
|
|
823
1277
|
></sp-textfield>
|
|
824
1278
|
<sp-textfield
|
|
@@ -917,7 +1371,7 @@ function styleSidebarTemplate(
|
|
|
917
1371
|
|
|
918
1372
|
return html`
|
|
919
1373
|
<div class="style-sidebar">
|
|
920
|
-
${
|
|
1374
|
+
${targetLineT} ${filterBarT}
|
|
921
1375
|
<sp-accordion allow-multiple size="s">
|
|
922
1376
|
${sectionTemplates} ${nestedSectionT} ${customSectionT}
|
|
923
1377
|
</sp-accordion>
|
|
@@ -935,71 +1389,128 @@ function styleSidebarTemplate(
|
|
|
935
1389
|
*/
|
|
936
1390
|
export function renderStylePanelTemplate(ctx: { getCanvasMode: () => string }) {
|
|
937
1391
|
const tab = activeTab.value;
|
|
1392
|
+
const noDocument = () =>
|
|
1393
|
+
renderEmptyState({
|
|
1394
|
+
actions: [openPageAction()],
|
|
1395
|
+
message: "Open a page to style what you click.",
|
|
1396
|
+
});
|
|
938
1397
|
if (!tab) {
|
|
939
|
-
return
|
|
1398
|
+
return noDocument();
|
|
940
1399
|
}
|
|
941
|
-
if (ctx.getCanvasMode() === "stylebook" &&
|
|
1400
|
+
if (ctx.getCanvasMode() === "stylebook" && shell.stylebook.selection) {
|
|
942
1401
|
const node = tab.doc.document;
|
|
943
1402
|
if (!node) {
|
|
944
|
-
return
|
|
1403
|
+
return noDocument();
|
|
945
1404
|
}
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
if (!tab.session.selection) {
|
|
959
|
-
return html`<div class="empty-state">Select an element to style</div>`;
|
|
960
|
-
}
|
|
961
|
-
const node = getNodeAtPath(tab.doc.document, tab.session.selection);
|
|
1405
|
+
// No separate `Styling: <h1>` header. It said the same thing the Target Line's scope chip says,
|
|
1406
|
+
// Except that it said it as a caption, after the fact, and without the blast radius (§6.1).
|
|
1407
|
+
return styleSidebarTemplate(node, tab.session.ui.activeMedia, tab.session.ui.activeSelector, {
|
|
1408
|
+
effectiveStyle: getEffectiveStyle(node.style),
|
|
1409
|
+
stylebookSelector: shell.stylebook.selection,
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
const selected = primarySelection(tab.session.selection);
|
|
1413
|
+
if (!selected) {
|
|
1414
|
+
return renderEmptyState({ message: clickAnythingTo("style it") });
|
|
1415
|
+
}
|
|
1416
|
+
const node = getNodeAtPath(tab.doc.document, selected);
|
|
962
1417
|
if (!node) {
|
|
963
|
-
return
|
|
1418
|
+
return renderEmptyState({ message: staleSelectionMessage() });
|
|
964
1419
|
}
|
|
965
1420
|
return styleSidebarTemplate(node, tab.session.ui.activeMedia, tab.session.ui.activeSelector);
|
|
966
1421
|
}
|
|
967
1422
|
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1423
|
+
// ─── Commands ─────────────────────────────────────────────────────────────────
|
|
1424
|
+
|
|
1425
|
+
/**
|
|
1426
|
+
* The Style tab's selector verbs.
|
|
1427
|
+
*
|
|
1428
|
+
* `style.openSelectorMenu` is the one command here that addresses a CONTROL rather than a state,
|
|
1429
|
+
* and it earns that on purpose: the shot it serves photographs the open menu, so the menu is the
|
|
1430
|
+
* subject. What it does not do is hand a CSS selector back to the runner for a synthetic mouse
|
|
1431
|
+
* press — the element comes from the Target Line's own `ref`, so a refactor of that markup moves
|
|
1432
|
+
* the handle with it. The menu it opens is now a segment of the Target Line rather than a picker on
|
|
1433
|
+
* a retired toolbar; the id, and therefore the shot, is unchanged.
|
|
1434
|
+
*
|
|
1435
|
+
* `style.setSelector` is the state half of the same idea, and it is what a caller that wants a
|
|
1436
|
+
* RESULT rather than a picture should use: the menu exists to choose a selector, and choosing one
|
|
1437
|
+
* is expressible.
|
|
1438
|
+
*
|
|
1439
|
+
* @returns {AnyCommand[]}
|
|
1440
|
+
*/
|
|
1441
|
+
export function styleCommands(): AnyCommand[] {
|
|
1442
|
+
const styleTabOpen = (ctx: { document: { open: boolean }; selection: { count: number } }) =>
|
|
1443
|
+
ctx.document.open && ctx.selection.count > 0;
|
|
1444
|
+
|
|
1445
|
+
return [
|
|
1446
|
+
{
|
|
1447
|
+
category: "View",
|
|
1448
|
+
id: "style.openSelectorMenu",
|
|
1449
|
+
level: "selection",
|
|
1450
|
+
menus: ["palette"],
|
|
1451
|
+
group: "8_style",
|
|
1452
|
+
requires: "the Style tab showing a selected element",
|
|
1453
|
+
when: styleTabOpen,
|
|
1454
|
+
run: () => openSelectorMenu(),
|
|
1455
|
+
title: "Open Selector Menu",
|
|
1456
|
+
},
|
|
1457
|
+
{
|
|
1458
|
+
args: {
|
|
1459
|
+
additionalProperties: false,
|
|
1460
|
+
properties: {
|
|
1461
|
+
selector: pathlessSelectorProperty(),
|
|
1462
|
+
},
|
|
1463
|
+
required: ["selector"],
|
|
1464
|
+
type: "object",
|
|
1465
|
+
},
|
|
1466
|
+
category: "View",
|
|
1467
|
+
id: "style.setSelector",
|
|
1468
|
+
level: "selection",
|
|
1469
|
+
menus: ["palette"],
|
|
1470
|
+
group: "8_style",
|
|
1471
|
+
requires: "an element selection",
|
|
1472
|
+
when: styleTabOpen,
|
|
1473
|
+
run: (_commandCtx, args) => {
|
|
1474
|
+
const { selector } = args as { selector?: unknown };
|
|
1475
|
+
if (selector === null) {
|
|
1476
|
+
const tab = activeTab.value;
|
|
1477
|
+
if (tab) {
|
|
1478
|
+
tab.session.ui.activeSelector = null;
|
|
1479
|
+
}
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
const value = stringArg("style.setSelector", args, "selector");
|
|
1483
|
+
if (!isNestedSelector(value)) {
|
|
1484
|
+
throw new RangeError(
|
|
1485
|
+
`command "style.setSelector" argument "selector": "${value}" is not a nested ` +
|
|
1486
|
+
`selector — it must start with ":", ".", "&" or "["`,
|
|
1487
|
+
);
|
|
1488
|
+
}
|
|
1489
|
+
const tab = activeTab.value;
|
|
1490
|
+
if (!tab) {
|
|
1491
|
+
throw new RangeError(`command "style.setSelector" needs an open document`);
|
|
1492
|
+
}
|
|
1493
|
+
tab.session.ui.activeSelector = value;
|
|
1494
|
+
},
|
|
1495
|
+
title: "Set Style Selector",
|
|
1496
|
+
},
|
|
1497
|
+
];
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
/** The `selector` property's schema: a nested selector, or `null` for the base context. */
|
|
1501
|
+
function pathlessSelectorProperty(): object {
|
|
1502
|
+
return {
|
|
1503
|
+
description:
|
|
1504
|
+
'A nested selector such as ":hover", ".child" or "[open]". null edits the base context.',
|
|
1505
|
+
oneOf: [{ type: "string" }, { type: "null" }],
|
|
980
1506
|
};
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
? html`<sp-checkbox
|
|
991
|
-
?checked=${Boolean(value)}
|
|
992
|
-
@change=${(e: Event) => onChange((e.target as HTMLInputElement).checked)}
|
|
993
|
-
></sp-checkbox>`
|
|
994
|
-
: html`<sp-textfield
|
|
995
|
-
size="s"
|
|
996
|
-
.value=${live(value ?? "")}
|
|
997
|
-
@input=${onInput}
|
|
998
|
-
></sp-textfield>`;
|
|
999
|
-
return html`
|
|
1000
|
-
<div class="field-row">
|
|
1001
|
-
<sp-field-label size="s">${label}</sp-field-label>
|
|
1002
|
-
${inputTpl}
|
|
1003
|
-
</div>
|
|
1004
|
-
`;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
/**
|
|
1510
|
+
* Register the Style tab's selector verbs.
|
|
1511
|
+
*
|
|
1512
|
+
* @param {CommandRegistry} registry
|
|
1513
|
+
*/
|
|
1514
|
+
export function registerStyleCommands(registry: CommandRegistry): void {
|
|
1515
|
+
registry.registerAll(styleCommands());
|
|
1005
1516
|
}
|