From c40d8720c15b5d5d394e77fab636426242f438e8 Mon Sep 17 00:00:00 2001 From: lschirmbrand Date: Mon, 10 Aug 2026 13:19:10 +0200 Subject: [PATCH 01/10] refs #485 - Refactor styling in designer components to use CSS custom properties for theming --- packages/web-component-designer/README.md | 6 + packages/web-component-designer/THEMING.md | 185 ++++++++++++++++++ .../src/elements/controls/ColorEditor.ts | 24 +-- .../elements/controls/DesignerTabControl.ts | 20 +- .../controls/ImageButtonListSelector.ts | 12 +- .../src/elements/controls/MetricsEditor.ts | 12 +- .../elements/controls/NumericStyleInput.ts | 4 +- .../src/elements/controls/PlainScrollbar.ts | 24 +-- .../src/elements/controls/ThicknessEditor.ts | 4 +- .../helper/contextMenu/ContextMenu.ts | 36 ++-- .../propertyEditors/CssPropertyEditor.ts | 2 +- .../elements/widgets/debugView/debug-view.ts | 10 +- .../src/elements/widgets/demoView/demoView.ts | 8 +- .../widgets/designerView/designerCanvas.ts | 18 +- .../widgets/designerView/designerView.ts | 10 +- .../ElementDragTitleExtensionProvider.ts | 2 +- .../extensions/ResizeExtensionProvider.ts | 2 +- .../SelectionDefaultExtensionProvider.ts | 2 +- .../grid/DisplayGridExtensionProvider.ts | 8 +- .../grid/GridChildResizeExtensionProvider.ts | 2 +- .../svg/UnifiedGeometryExtensionProvider.ts | 10 +- .../ProjectiveTransformExtensionProvider.ts | 6 +- .../transforms/RotateExtensionProvider.ts | 4 +- .../RotateGroupExtensionProvider.ts | 6 +- .../transforms/SkewExtensionProvider.ts | 4 +- .../TransformOriginExtensionProvider.ts | 2 +- .../widgets/designerView/overlayLayerView.ts | 16 +- .../tools/toolBar/DesignerToolbarButton.ts | 2 +- .../toolBar/popups/AbstractBaseToolPopup.ts | 10 +- .../popups/BorderRadiusEditorWindow.ts | 46 ++--- .../toolBar/popups/BoxShadowEditorWindow.ts | 52 ++--- .../toolBar/popups/DraggableToolWindow.ts | 16 +- .../toolBar/popups/GradientEditorWindow.ts | 54 ++--- .../toolBar/popups/TextShadowEditorWindow.ts | 46 ++--- .../toolBar/popups/TransformToolPopup.ts | 6 +- .../widgets/layerDepthView/layerDepthView.ts | 10 +- .../widgets/miniatureView/miniatureView.ts | 2 +- .../widgets/paletteView/paletteElements.ts | 6 +- .../widgets/propertyGrid/PropertyGrid.ts | 4 +- .../propertyGrid/PropertyGridPropertyList.ts | 20 +- .../propertyGrid/PropertyGridWithHeader.ts | 12 +- .../src/elements/widgets/treeView/treeView.ts | 20 +- 42 files changed, 468 insertions(+), 277 deletions(-) create mode 100644 packages/web-component-designer/THEMING.md diff --git a/packages/web-component-designer/README.md b/packages/web-component-designer/README.md index 032f328ec..aa9aa3046 100644 --- a/packages/web-component-designer/README.md +++ b/packages/web-component-designer/README.md @@ -39,6 +39,12 @@ https://www.npmjs.com/package/@node-projects/web-component-designer At first you have to setup a service container providing services for history, properties, elements, ... +## Theming + +The designer chrome is styled with CSS custom properties, so it can be restyled from the +outside without patching component styles. See [THEMING.md](THEMING.md) for the token +reference. + ## Code Editor You can select to use one of 3 code editors available (ACE, CodeMirrow, Monaco). diff --git a/packages/web-component-designer/THEMING.md b/packages/web-component-designer/THEMING.md new file mode 100644 index 000000000..379197ada --- /dev/null +++ b/packages/web-component-designer/THEMING.md @@ -0,0 +1,185 @@ +# Theming + +The designer chrome is styled through CSS custom properties. Set them anywhere above +the designer in the DOM — custom properties inherit through shadow boundaries, so a +declaration on `:root` reaches every widget. + +```css +:root { + --wcd-color-surface: #1b1b1f; + --wcd-color-surface-raised: #26262c; + --wcd-color-surface-hover: #32323a; + --wcd-color-text: #e6e6e6; + --wcd-color-accent: #4f9cf9; +} +``` + +These style the **designer's own UI**. To style the document being edited, use +`documentContainer.additionalStyles` / `additionalStylesheets` instead — that is a +separate mechanism that injects stylesheets into the design surface. + +## How a value resolves + +Every themable declaration reads through a fallback chain: + +```css +background: var(--wcd-tab-header-background, /* 1. component token */ + var(--dark-grey, /* 2. legacy alias */ + var(--wcd-color-surface, #232733))); /* 3. global role, 4. built-in default */ +``` + +1. **Component token** — the most specific knob, for one part of one widget. +2. **Legacy alias** — the pre-`--wcd-` variable name, still honoured (see below). +3. **Global role** — the broad knob; set these to reskin the whole designer. +4. **Built-in default** — the value that shipped before theming existed. + +Set nothing and you get exactly the previous appearance. The defaults live only in +`var()` fallbacks — the designer never *declares* a public token internally, so a value +you set on an ancestor is never overridden from inside a shadow root. + +## Global roles + +The complete set. Everything else is a component token. + +| Token | Role | +| --- | --- | +| `--wcd-color-surface` | Panel / window background | +| `--wcd-color-surface-raised` | Controls, inputs and rows sitting on a surface | +| `--wcd-color-surface-hover` | Hover and selected-row background | +| `--wcd-color-text` | Primary text | +| `--wcd-color-text-muted` | Secondary, disabled and unset-value text | +| `--wcd-color-border` | Default border | +| `--wcd-color-border-strong` | High-contrast border | +| `--wcd-color-accent` | Active indicators, selected tab marker | +| `--wcd-color-accent-text` | Text rendered in the accent colour | +| `--wcd-color-accent-border` | Border rendered in the accent colour | +| `--wcd-color-focus` | Focus ring | +| `--wcd-color-error` | Invalid-input border | +| `--wcd-color-error-glow` | Invalid-input glow | +| `--wcd-color-selection` | Selection outline on the canvas | +| `--wcd-color-selection-fill` | Selection rectangle fill | +| `--wcd-color-snapline` | Snapline stroke | +| `--wcd-color-handle` | Geometry handle fill | +| `--wcd-color-handle-stroke` | Geometry handle stroke | +| `--wcd-color-handle-active` | Active geometry handle fill | + +> `--wcd-color-editor-background` is **not** a global role — it is the ColorEditor +> widget's background. The table above is exhaustive. + +### Two default palettes + +The classic chrome (tab control, tree view, palette, property grid) and the tool +windows (border-radius, box-shadow, gradient, text-shadow editors) ship with different +dark palettes. Both read the same global roles but keep their own literal defaults, so +setting `--wcd-color-surface` unifies them while changing nothing by default. + +### Light-by-default components + +`ContextMenu` and `PlainScrollbar` default to a light palette and are deliberately +**not** wired to the global roles — chaining them to the dark chrome roles would invert +them. Theme them through their own tokens (`--wcd-context-menu-*`, +`--wcd-plain-scrollbar-*`). + +## Component tokens + +Named `--wcd---`. + +**Tab control** — `tab-header-background`, `tab-header-hover-background`, +`tab-selected-background`, `tab-selected-indicator-color`, `tab-panel-background` + +**Tree view** — `tree-view-background`, `tree-view-item-hover-background`, +`tree-view-item-selected-background`, `tree-view-id-color`, `tree-view-connector-color` + +**Property grid** — `property-grid-background`, `property-grid-text-color`, +`property-grid-unset-value-color`, `property-grid-group-header-hover-color`, +`property-grid-drop-target-color`, `property-grid-select-option-color`, +`property-grid-header-highlight-color`, `property-grid-tab-indicator-color`, +`property-grid-tab-indicator-inactive-color` + +**Palette** — `palette-item-hover-background` + +**Designer view** — `designer-view-toolbar-background`, `designer-view-statusbar-background`, +`designer-view-tool-selected-background`, `designer-view-tool-hover-background`, +`designer-view-corner-background` + +**Canvas** — `canvas-background`, `screenshot-background`, `canvas-search-background`, +`canvas-search-container-background`, `canvas-search-border-color`, +`canvas-search-button-hover-background`, `canvas-drag-file-outline-color`, +`canvas-loading-background`, `grid-stroke-color`, `grid-fill-color` + +**Overlay layer** — `overlay-toolbar-background`, `overlay-toolbar-button-border-color`, +`overlay-toolbar-button-hover-background` + +**Tool windows / popups** — `tool-popup-background`, `tool-popup-title-background`, +`tool-popup-border-color`, `tool-window-shadow-color`, `tool-window-close-hover-background`, +`toolbar-button-selected-background`, `transform-tool-divider-color`, +`border-radius-editor-preview-background`, `box-shadow-editor-preview-background`, +`box-shadow-editor-preview-box-background`, `gradient-editor-stop-border-color`, +`gradient-editor-stop-selected-border-color`, `gradient-editor-stop-selected-glow-color` + +**Inputs and editors** — `input-background-color`, `input-border-color`, +`color-editor-background`, `numeric-style-input-select-option-color`, +`thickness-editor-background`, `thickness-editor-border-color`, +`metrics-editor-margin-background`, `metrics-editor-border-background`, +`metrics-editor-padding-background`, `metrics-editor-content-background`, +`metrics-editor-label-background`, `image-button-list-selector-property-color`, +`image-button-list-selector-value-color`, `image-button-list-selector-value-set-color`, +`image-button-list-selector-button-background`, +`image-button-list-selector-button-border-color` + +**Other views** — `layer-depth-view-background`, `layer-depth-view-text-color`, +`layer-depth-view-muted-color`, `layer-depth-view-card-background`, +`layer-depth-view-card-border-color`, `miniature-view-border-color`, +`demo-view-background`, `demo-view-toolbar-background`, `demo-view-toolbar-border-color`, +`debug-view-cell-border-color`, `debug-view-header-background`, +`debug-view-row-hover-background`, `debug-view-link-color` + +**Context menu** — `context-menu-background`, `context-menu-color`, +`context-menu-hover-background`, `context-menu-marked-background`, +`context-menu-disabled-color`, `context-menu-divider-color`, `context-menu-border`, +`context-menu-border-radius`, `context-menu-shadow`, `context-menu-padding`, +`context-menu-item-padding-y`, `context-menu-icon-size`, `context-menu-icon-column-width`, +`context-menu-font-family`, `context-menu-font-size`, `context-menu-font-weight` + +**Scrollbar** — `plain-scrollbar-thumb-background-color`, +`plain-scrollbar-thumb-background-color-hover`, `plain-scrollbar-thumb-background-color-active`, +`plain-scrollbar-thumb-border-color`, `plain-scrollbar-thumb-border-width`, +`plain-scrollbar-thumb-border-radius`, `plain-scrollbar-track-background-color`, +`plain-scrollbar-track-border-color`, `plain-scrollbar-button-color`, +`plain-scrollbar-button-color-hover`, `plain-scrollbar-button-color-active`, +`plain-scrollbar-button-size` + +## Legacy variable names + +The variables that were themable before this scheme existed still work and take +precedence over the global roles, so existing setups are unaffected. Prefer the +`--wcd-` names in new code. + +| Legacy | Replacement | +| --- | --- | +| `--dark-grey` | `--wcd-color-surface` | +| `--medium-grey` | `--wcd-color-surface-raised` | +| `--light-grey` | `--wcd-color-surface-hover` | +| `--highlight-pink` | `--wcd-color-accent` | +| `--input-border-color` | `--wcd-input-border-color` | +| `--input-background-color` | `--wcd-input-background-color` | +| `--property-grid-text-color` | `--wcd-property-grid-text-color` | +| `--color-editor-background` | `--wcd-color-editor-background` | +| `--numeric-style-input-select-option-color` | `--wcd-numeric-style-input-select-option-color` | +| `--svg-grid-stroke-color` | `--wcd-grid-stroke-color` | +| `--svg-grid-fill-color` | `--wcd-grid-fill-color` | +| `--node-projects-web-component-designer-background` | `--wcd-canvas-background` | +| `--node-projects-web-component-designer-screenshot-background` | `--wcd-screenshot-background` | +| `--context-menu-*` | `--wcd-context-menu-*` | +| `--plain-scrollbar-*` | `--wcd-plain-scrollbar-*` | + +`--wcd-grid-stroke-color`, `--wcd-grid-fill-color` and +`--wcd-property-grid-tab-indicator-inactive-color` have **no built-in default**, matching +the previous behaviour: leave them unset and the declaration stays invalid, so nothing is +painted. + +## Not themable + +The colour picker's handle rings, focus glows and alpha checkerboards are left as +literals on purpose — they have to stay legible against whatever colour is being +edited, so a theme must not be able to wash them out. diff --git a/packages/web-component-designer/src/elements/controls/ColorEditor.ts b/packages/web-component-designer/src/elements/controls/ColorEditor.ts index 3e81b0539..1ecb483f9 100644 --- a/packages/web-component-designer/src/elements/controls/ColorEditor.ts +++ b/packages/web-component-designer/src/elements/controls/ColorEditor.ts @@ -17,7 +17,7 @@ export class ColorEditor extends BaseCustomWebComponentConstructorAppend { display: block; box-sizing: border-box; width: 280px; - color: var(--property-grid-text-color, #e8edf2); + color: var(--wcd-property-grid-text-color, var(--property-grid-text-color, var(--wcd-color-text, #e8edf2))); font: 12px system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } @@ -28,7 +28,7 @@ export class ColorEditor extends BaseCustomWebComponentConstructorAppend { padding: 12px; border: 1px solid rgba(255, 255, 255, .14); border-radius: 8px; - background: var(--color-editor-background, #20252b); + background: var(--wcd-color-editor-background, var(--color-editor-background, var(--wcd-color-surface, #20252b))); box-shadow: 0 14px 38px rgba(0, 0, 0, .36); } @@ -66,7 +66,7 @@ export class ColorEditor extends BaseCustomWebComponentConstructorAppend { } .slider-row span { - color: rgba(232, 237, 242, .68); + color: var(--wcd-color-text-muted, rgba(232, 237, 242, .68)); font-size: 11px; text-transform: uppercase; } @@ -161,7 +161,7 @@ export class ColorEditor extends BaseCustomWebComponentConstructorAppend { min-width: 0; border: 1px solid rgba(255, 255, 255, .14); border-radius: 5px; - background: #161a1f; + background: var(--wcd-color-surface, #161a1f); color: inherit; font: inherit; outline: none; @@ -177,7 +177,7 @@ export class ColorEditor extends BaseCustomWebComponentConstructorAppend { display: grid; gap: 3px; min-width: 0; - color: rgba(232, 237, 242, .68); + color: var(--wcd-color-text-muted, rgba(232, 237, 242, .68)); font-size: 10px; text-transform: uppercase; } @@ -190,7 +190,7 @@ export class ColorEditor extends BaseCustomWebComponentConstructorAppend { height: 26px; border: 1px solid rgba(255, 255, 255, .14); border-radius: 5px; - background: #161a1f; + background: var(--wcd-color-surface, #161a1f); color: inherit; font: inherit; outline: none; @@ -207,13 +207,13 @@ export class ColorEditor extends BaseCustomWebComponentConstructorAppend { } #text.invalid { - border-color: #e66b6b; - box-shadow: 0 0 0 1px rgba(230, 107, 107, .35); + border-color: var(--wcd-color-error, #e66b6b); + box-shadow: 0 0 0 1px var(--wcd-color-error-glow, rgba(230, 107, 107, .35)); } input:focus, select:focus { - border-color: #79b8ff; + border-color: var(--wcd-color-focus, #79b8ff); box-shadow: 0 0 0 1px rgba(121, 184, 255, .28); } @@ -518,15 +518,15 @@ export class ColorInput extends BaseCustomWebComponentConstructorAppend { width: 100%; height: 100%; padding: 3px; - border: 1px solid var(--input-border-color, #596c7a); + border: 1px solid var(--wcd-input-border-color, var(--input-border-color, var(--wcd-color-border, #596c7a))); border-radius: 4px; - background: var(--input-background-color, #1d2228); + background: var(--wcd-input-background-color, var(--input-background-color, var(--wcd-color-surface, #1d2228))); cursor: pointer; outline: none; } button:focus { - border-color: #79b8ff; + border-color: var(--wcd-color-focus, #79b8ff); box-shadow: 0 0 0 1px rgba(121, 184, 255, .28); } diff --git a/packages/web-component-designer/src/elements/controls/DesignerTabControl.ts b/packages/web-component-designer/src/elements/controls/DesignerTabControl.ts index 6a37775fc..959cff542 100644 --- a/packages/web-component-designer/src/elements/controls/DesignerTabControl.ts +++ b/packages/web-component-designer/src/elements/controls/DesignerTabControl.ts @@ -34,7 +34,7 @@ export class DesignerTabControl extends BaseCustomWebComponentLazyAppend { cursor: pointer; height: 30px; width: calc(100% - 30px); - background-color: var(--dark-grey, #232733); + background-color: var(--wcd-tab-header-background, var(--dark-grey, var(--wcd-color-surface, #232733))); overflow-x: auto; scrollbar-width: none; /* Firefox */ } @@ -43,24 +43,24 @@ export class DesignerTabControl extends BaseCustomWebComponentLazyAppend { top: 0; width: 30px; position: absolute; - color: white; + color: var(--wcd-color-text, white); display: flex; justify-content: center; align-items: center; font-family: math; } .header-more:hover { - background: var(--light-grey, #383f52); + background: var(--wcd-tab-header-hover-background, var(--light-grey, var(--wcd-color-surface-hover, #383f52))); } .more-container { z-index: 1; user-select: none; -webkit-user-select: none; - background-color: var(--dark-grey, #232733); + background-color: var(--wcd-tab-header-background, var(--dark-grey, var(--wcd-color-surface, #232733))); right: 0; top: 30px; position: absolute; - color: white; + color: var(--wcd-color-text, white); display: flex; flex-direction: column; align-items: flex-start; @@ -82,7 +82,7 @@ export class DesignerTabControl extends BaseCustomWebComponentLazyAppend { box-sizing: content-box; padding-left: 5px; padding-right: 5px; - color: white; + color: var(--wcd-color-text, white); font-size: 12px; font-weight: 500; line-height: 1.5; @@ -90,15 +90,15 @@ export class DesignerTabControl extends BaseCustomWebComponentLazyAppend { white-space: nowrap; } .tab-header:hover { - background: var(--light-grey, #383f52); + background: var(--wcd-tab-header-hover-background, var(--light-grey, var(--wcd-color-surface-hover, #383f52))); } .selected { - background: var(--medium-grey, #2f3545); - box-shadow: inset 0 3px 0 var(--highlight-pink, #e91e63); + background: var(--wcd-tab-selected-background, var(--medium-grey, var(--wcd-color-surface-raised, #2f3545))); + box-shadow: inset 0 3px 0 var(--wcd-tab-selected-indicator-color, var(--highlight-pink, var(--wcd-color-accent, #e91e63))); } .panels { z-index: 0; - background: var(--medium-grey, #2f3545); + background: var(--wcd-tab-panel-background, var(--medium-grey, var(--wcd-color-surface-raised, #2f3545))); height: calc(100% - 30px); } `; diff --git a/packages/web-component-designer/src/elements/controls/ImageButtonListSelector.ts b/packages/web-component-designer/src/elements/controls/ImageButtonListSelector.ts index 3c5dbe969..557d6d9fd 100644 --- a/packages/web-component-designer/src/elements/controls/ImageButtonListSelector.ts +++ b/packages/web-component-designer/src/elements/controls/ImageButtonListSelector.ts @@ -5,16 +5,16 @@ export class ImageButtonListSelector extends BaseCustomWebComponentConstructorAp public static override readonly style = css` div { font-size: 10px; - color: white; + color: var(--wcd-color-text, white); } #property { - color: #00aff0; + color: var(--wcd-image-button-list-selector-property-color, #00aff0); } #value { - color: lightgray; + color: var(--wcd-image-button-list-selector-value-color, lightgray); } #value.value-set { - color: wheat; + color: var(--wcd-image-button-list-selector-value-set-color, wheat); } .container { display: flex; @@ -24,8 +24,8 @@ export class ImageButtonListSelector extends BaseCustomWebComponentConstructorAp min-width: 24px; height: 24px; padding: 1px; - background: white; - border: 1px solid lightgray; + background: var(--wcd-image-button-list-selector-button-background, white); + border: 1px solid var(--wcd-image-button-list-selector-button-border-color, lightgray); } `; diff --git a/packages/web-component-designer/src/elements/controls/MetricsEditor.ts b/packages/web-component-designer/src/elements/controls/MetricsEditor.ts index 1ef750c78..3ccdfafa7 100644 --- a/packages/web-component-designer/src/elements/controls/MetricsEditor.ts +++ b/packages/web-component-designer/src/elements/controls/MetricsEditor.ts @@ -66,7 +66,7 @@ export class MetricsEditor extends BaseCustomWebComponentConstructorAppend { display: block; box-sizing: border-box; min-width: 0; - color: var(--property-grid-text-color, white); + color: var(--wcd-property-grid-text-color, var(--property-grid-text-color, var(--wcd-color-text, white))); font: 11px monospace; overflow: hidden; } @@ -111,19 +111,19 @@ export class MetricsEditor extends BaseCustomWebComponentConstructorAppend { #margin { grid-area: margin; - background: #f6c89f; + background: var(--wcd-metrics-editor-margin-background, #f6c89f); } #border { grid-area: inner; - background: #f7dd9c; + background: var(--wcd-metrics-editor-border-background, #f7dd9c); border-style: solid; min-height: 76px; } #padding { grid-area: inner; - background: #c8d08f; + background: var(--wcd-metrics-editor-padding-background, #c8d08f); min-height: 38px; } @@ -138,7 +138,7 @@ export class MetricsEditor extends BaseCustomWebComponentConstructorAppend { height: 100%; min-height: 24px; box-sizing: border-box; - background: #8fb9c3; + background: var(--wcd-metrics-editor-content-background, #8fb9c3); border: 1px solid rgba(0, 0, 0, .65); } @@ -185,7 +185,7 @@ export class MetricsEditor extends BaseCustomWebComponentConstructorAppend { input:hover, input:focus { - background: rgba(255, 255, 255, .72); + background: var(--wcd-metrics-editor-label-background, rgba(255, 255, 255, .72)); box-shadow: 0 0 0 1px rgba(0, 0, 0, .35); } diff --git a/packages/web-component-designer/src/elements/controls/NumericStyleInput.ts b/packages/web-component-designer/src/elements/controls/NumericStyleInput.ts index 85b071faa..1f98b4d9c 100644 --- a/packages/web-component-designer/src/elements/controls/NumericStyleInput.ts +++ b/packages/web-component-designer/src/elements/controls/NumericStyleInput.ts @@ -67,7 +67,7 @@ export class NumericStyleInput extends BaseCustomWebComponentConstructorAppend { box-shadow: none; } #select option{ - color: var(--numeric-style-input-select-option-color, black); + color: var(--wcd-numeric-style-input-select-option-color, var(--numeric-style-input-select-option-color, black)); } #scrubber, @@ -96,7 +96,7 @@ export class NumericStyleInput extends BaseCustomWebComponentConstructorAppend { #stepper button:focus { outline: none; box-shadow: none; - border-color: var(--input-border-color, #596c7a); + border-color: var(--wcd-input-border-color, var(--input-border-color, var(--wcd-color-border, #596c7a))); } #input[disabled], diff --git a/packages/web-component-designer/src/elements/controls/PlainScrollbar.ts b/packages/web-component-designer/src/elements/controls/PlainScrollbar.ts index 90d61a051..28ed12809 100644 --- a/packages/web-component-designer/src/elements/controls/PlainScrollbar.ts +++ b/packages/web-component-designer/src/elements/controls/PlainScrollbar.ts @@ -401,17 +401,17 @@ export class PlainScrollbar extends HTMLElement { //------------------------------------------------------------------------------ const EPS = 1E-9; -const buttonSize = "var(--plain-scrollbar-button-size, 13px)"; +const buttonSize = "var(--wcd-plain-scrollbar-button-size, var(--plain-scrollbar-button-size, 13px))"; const buttonPath = ''; const scrollbarStyle = css` :host { display: block; contain: content; - background-color: #f8f8f8; + background-color: var(--wcd-plain-scrollbar-track-background-color, #f8f8f8); border-style: solid; border-width: 1px; - border-color: #dddddd; + border-color: var(--wcd-plain-scrollbar-track-border-color, #dddddd); } #root { touch-action: none; @@ -439,25 +439,25 @@ const scrollbarStyle = css` position: absolute; width: 100%; height: 100%; - background-color: var(--plain-scrollbar-thumb-background-color, #f0f0f0); + background-color: var(--wcd-plain-scrollbar-thumb-background-color, var(--plain-scrollbar-thumb-background-color, #f0f0f0)); border-style: solid; - border-width: var(--plain-scrollbar-thumb-border-width, 1px); - border-color: var(--plain-scrollbar-thumb-border-color, #b8b8b8); - border-radius: var(--plain-scrollbar-thumb-border-radius, 4px); + border-width: var(--wcd-plain-scrollbar-thumb-border-width, var(--plain-scrollbar-thumb-border-width, 1px)); + border-color: var(--wcd-plain-scrollbar-thumb-border-color, var(--plain-scrollbar-thumb-border-color, #b8b8b8)); + border-radius: var(--wcd-plain-scrollbar-thumb-border-radius, var(--plain-scrollbar-thumb-border-radius, 4px)); transition: background-color 50ms linear; } #thumb:hover { - background-color: var(--plain-scrollbar-thumb-background-color-hover, #e0e0e0); + background-color: var(--wcd-plain-scrollbar-thumb-background-color-hover, var(--plain-scrollbar-thumb-background-color-hover, #e0e0e0)); } #thumb.active { - background-color: var(--plain-scrollbar-thumb-background-color-active, #c0c0c0); + background-color: var(--wcd-plain-scrollbar-thumb-background-color-active, var(--plain-scrollbar-thumb-background-color-active, #c0c0c0)); } #button1, #button2 { box-sizing: border-box; position: absolute; display: block; - fill: var(--plain-scrollbar-button-color, #606060); + fill: var(--wcd-plain-scrollbar-button-color, var(--plain-scrollbar-button-color, #606060)); } #root.vertical #button1 { top: 0; @@ -497,11 +497,11 @@ const scrollbarStyle = css` } #button1:hover, #button2:hover { - background-color: var(--plain-scrollbar-button-color-hover, #e0e0e0); + background-color: var(--wcd-plain-scrollbar-button-color-hover, var(--plain-scrollbar-button-color-hover, #e0e0e0)); } #button1.active, #button2.active { - background-color: var(--plain-scrollbar-button-color-active, #c0c0c0); + background-color: var(--wcd-plain-scrollbar-button-color-active, var(--plain-scrollbar-button-color-active, #c0c0c0)); } `; diff --git a/packages/web-component-designer/src/elements/controls/ThicknessEditor.ts b/packages/web-component-designer/src/elements/controls/ThicknessEditor.ts index 4fade1d69..417fdcce9 100644 --- a/packages/web-component-designer/src/elements/controls/ThicknessEditor.ts +++ b/packages/web-component-designer/src/elements/controls/ThicknessEditor.ts @@ -48,8 +48,8 @@ export class ThicknessEditor extends BaseCustomWebComponentConstructorAppend { } #rect { grid-area: middle; - border: 1px solid black; - background: lightgray; + border: 1px solid var(--wcd-thickness-editor-border-color, black); + background: var(--wcd-thickness-editor-background, lightgray); } `; diff --git a/packages/web-component-designer/src/elements/helper/contextMenu/ContextMenu.ts b/packages/web-component-designer/src/elements/helper/contextMenu/ContextMenu.ts index d576ed079..a27b734eb 100644 --- a/packages/web-component-designer/src/elements/helper/contextMenu/ContextMenu.ts +++ b/packages/web-component-designer/src/elements/helper/contextMenu/ContextMenu.ts @@ -26,10 +26,10 @@ export class ContextMenu implements IContextMenu { transform-origin: top left; padding: 0; z-index: 2147483647; - color: var(--context-menu-color, black); - font-family: var(--context-menu-font-family, inherit); - font-size: var(--context-menu-font-size, inherit); - font-weight: var(--context-menu-font-weight, inherit); + color: var(--wcd-context-menu-color, var(--context-menu-color, black)); + font-family: var(--wcd-context-menu-font-family, var(--context-menu-font-family, inherit)); + font-size: var(--wcd-context-menu-font-size, var(--context-menu-font-size, inherit)); + font-weight: var(--wcd-context-menu-font-weight, var(--context-menu-font-weight, inherit)); } .context_menu.context_menu_display { @@ -48,12 +48,12 @@ export class ContextMenu implements IContextMenu { .context_menu ul { list-style-type: none; - padding: var(--context-menu-padding, 3px); + padding: var(--wcd-context-menu-padding, var(--context-menu-padding, 3px)); margin: 0; - border: var(--context-menu-border, none); - border-radius: var(--context-menu-border-radius, 0); - background-color: var(--context-menu-background, #f5f7f7); - box-shadow: var(--context-menu-shadow, 0 0 5px #333); + border: var(--wcd-context-menu-border, var(--context-menu-border, none)); + border-radius: var(--wcd-context-menu-border-radius, var(--context-menu-border-radius, 0)); + background-color: var(--wcd-context-menu-background, var(--context-menu-background, #f5f7f7)); + box-shadow: var(--wcd-context-menu-shadow, var(--context-menu-shadow, 0 0 5px #333)); max-inline-size: calc(100vw - 8px); max-block-size: calc(100vh - 8px); overflow: auto; @@ -64,12 +64,12 @@ export class ContextMenu implements IContextMenu { .context_menu ul[popover] { inset: auto; margin: 0; - border: var(--context-menu-border, none); - border-radius: var(--context-menu-border-radius, 0); + border: var(--wcd-context-menu-border, var(--context-menu-border, none)); + border-radius: var(--wcd-context-menu-border-radius, var(--context-menu-border-radius, 0)); } .context_menu li { - padding: var(--context-menu-item-padding-y, 0) 1.7em var(--context-menu-item-padding-y, 0) 0; + padding: var(--wcd-context-menu-item-padding-y, var(--context-menu-item-padding-y, 0)) 1.7em var(--wcd-context-menu-item-padding-y, var(--context-menu-item-padding-y, 0)) 0; cursor: pointer; white-space: nowrap; display: flex; @@ -77,18 +77,18 @@ export class ContextMenu implements IContextMenu { } .context_menu li:hover { - background-color: var(--context-menu-hover-background, #bbb); + background-color: var(--wcd-context-menu-hover-background, var(--context-menu-hover-background, #bbb)); } .context_menu li .context_menu_icon_span { - width: var(--context-menu-icon-column-width, 28px); + width: var(--wcd-context-menu-icon-column-width, var(--context-menu-icon-column-width, 28px)); display: inline-flex; align-items: center; justify-content: center; } .context_menu li .context_menu_icon_span img { - height: var(--context-menu-icon-size, 18px); + height: var(--wcd-context-menu-icon-size, var(--context-menu-icon-size, 18px)); } .context_menu li .context_menu_text { @@ -126,7 +126,7 @@ export class ContextMenu implements IContextMenu { } .context_menu li.context_menu_divider { - border-bottom: 1px solid var(--context-menu-divider-color, #aaa); + border-bottom: 1px solid var(--wcd-context-menu-divider-color, var(--context-menu-divider-color, #aaa)); margin: 5px; padding: 0; cursor: default; @@ -137,7 +137,7 @@ export class ContextMenu implements IContextMenu { } .context_menu li[disabled=""] { - color: var(--context-menu-disabled-color, #777); + color: var(--wcd-context-menu-disabled-color, var(--context-menu-disabled-color, #777)); cursor: default; } @@ -146,7 +146,7 @@ export class ContextMenu implements IContextMenu { } .context_menu li.context_menu_marked { - background-color: var(--context-menu-marked-background, #5ebdec); + background-color: var(--wcd-context-menu-marked-background, var(--context-menu-marked-background, #5ebdec)); }`; static count = 0; diff --git a/packages/web-component-designer/src/elements/services/propertiesService/propertyEditors/CssPropertyEditor.ts b/packages/web-component-designer/src/elements/services/propertiesService/propertyEditors/CssPropertyEditor.ts index 92e1b8dd5..88d9a73c2 100644 --- a/packages/web-component-designer/src/elements/services/propertiesService/propertyEditors/CssPropertyEditor.ts +++ b/packages/web-component-designer/src/elements/services/propertiesService/propertyEditors/CssPropertyEditor.ts @@ -33,7 +33,7 @@ export class CssPropertyEditor implements IPropertyEditor { this._importantButton.style.height = '18px'; this._importantButton.style.margin = '-2px 0 0 -2px'; this._importantButton.style.padding = '0'; - this._importantButton.style.border = '1px solid var(--input-border-color, #596c7a)'; + this._importantButton.style.border = '1px solid var(--wcd-input-border-color, var(--input-border-color, var(--wcd-color-border, #596c7a)))'; this._importantButton.style.background = 'transparent'; this._importantButton.style.color = 'lightslategray'; this._importantButton.style.fontWeight = 'bold'; diff --git a/packages/web-component-designer/src/elements/widgets/debugView/debug-view.ts b/packages/web-component-designer/src/elements/widgets/debugView/debug-view.ts index 6665eb7d6..14fbb8df8 100644 --- a/packages/web-component-designer/src/elements/widgets/debugView/debug-view.ts +++ b/packages/web-component-designer/src/elements/widgets/debugView/debug-view.ts @@ -184,22 +184,22 @@ export class DebugView extends BaseCustomWebComponentConstructorAppend { } table td, table th { - border: 1px solid #ddd; + border: 1px solid var(--wcd-debug-view-cell-border-color, #ddd); padding: 2px 4px; } table tr:nth-child(even){background-color: #f2f2f2;} - table tr:hover {background-color: #ddd;} + table tr:hover {background-color: var(--wcd-debug-view-row-hover-background, #ddd);} table th { text-align: left; - background-color: #989898; - color: white; + background-color: var(--wcd-debug-view-header-background, #989898); + color: var(--wcd-color-text, white); } .lnk { - color: blue; + color: var(--wcd-debug-view-link-color, blue); } .lnk:hover { text-decoration: underline; diff --git a/packages/web-component-designer/src/elements/widgets/demoView/demoView.ts b/packages/web-component-designer/src/elements/widgets/demoView/demoView.ts index de28442ab..090a98534 100644 --- a/packages/web-component-designer/src/elements/widgets/demoView/demoView.ts +++ b/packages/web-component-designer/src/elements/widgets/demoView/demoView.ts @@ -15,7 +15,7 @@ export class DemoView extends BaseCustomWebComponentLazyAppend implements IDemoV :host { display: block; overflow: hidden; - background: white; + background: var(--wcd-demo-view-background, white); height: 100%; width: 100%; position: relative; @@ -37,12 +37,12 @@ export class DemoView extends BaseCustomWebComponentLazyAppend implements IDemoV top: 0; height: 100%; width: 24px; - border-right: solid white 1px; + border-right: solid var(--wcd-demo-view-toolbar-border-color, white) 1px; box-sizing: border-box; - background: black; + background: var(--wcd-demo-view-toolbar-background, black); } span { - color: white; + color: var(--wcd-color-text, white); rotate: 270deg; display: block; position: absolute; diff --git a/packages/web-component-designer/src/elements/widgets/designerView/designerCanvas.ts b/packages/web-component-designer/src/elements/widgets/designerView/designerCanvas.ts index 3a6c81450..7853c229a 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/designerCanvas.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/designerCanvas.ts @@ -190,7 +190,7 @@ export class DesignerCanvas extends BaseCustomWebComponentLazyAppend implements touch-action: none; } #node-projects-designer-canvas-canvasContainer { - background: var(--node-projects-web-component-designer-background, border-box fixed 0px 0px repeat url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAFXmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgZXhpZjpQaXhlbFhEaW1lbnNpb249IjIwIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iMjAiCiAgIGV4aWY6Q29sb3JTcGFjZT0iMSIKICAgdGlmZjpJbWFnZVdpZHRoPSIyMCIKICAgdGlmZjpJbWFnZUxlbmd0aD0iMjAiCiAgIHRpZmY6UmVzb2x1dGlvblVuaXQ9IjIiCiAgIHRpZmY6WFJlc29sdXRpb249IjMwMC8xIgogICB0aWZmOllSZXNvbHV0aW9uPSIzMDAvMSIKICAgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIKICAgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIgogICB4bXA6TW9kaWZ5RGF0ZT0iMjAyMi0xMi0wOFQwOToxNTo0OCswMTowMCIKICAgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMi0xMi0wOFQwOToxNTo0OCswMTowMCI+CiAgIDxkYzp0aXRsZT4KICAgIDxyZGY6QWx0PgogICAgIDxyZGY6bGkgeG1sOmxhbmc9IngtZGVmYXVsdCI+QmFja2dyb3VuZGdyaWRfMTBweDwvcmRmOmxpPgogICAgPC9yZGY6QWx0PgogICA8L2RjOnRpdGxlPgogICA8eG1wTU06SGlzdG9yeT4KICAgIDxyZGY6U2VxPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJwcm9kdWNlZCIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWZmaW5pdHkgRGVzaWduZXIgMS4xMC42IgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTEyLTA4VDA5OjE1OjQ4KzAxOjAwIi8+CiAgICA8L3JkZjpTZXE+CiAgIDwveG1wTU06SGlzdG9yeT4KICA8L3JkZjpEZXNjcmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Cjw/eHBhY2tldCBlbmQ9InIiPz4fvgn+AAABgWlDQ1BzUkdCIElFQzYxOTY2LTIuMQAAKJF1kc8rRFEUxz8GjRhRLCiLl7BCftTExmLkV2ExnvJr8+bNvBk1b+b13kiyVbaKEhu/FvwFbJW1UkRKlrImNug5b2ZqJplzO/d87vfec7r3XPCpSd10KnrATGXs8FhImZtfUPwvBKjCTx/Nmu5YUzOjKiXt854yL952ebVKn/vXaqIxR4eyKuEh3bIzwuPCk6sZy+Md4UY9oUWFz4Q7bbmg8J2nR3L86nE8x98e22p4GHz1wkq8iCNFrCdsU1heTpuZXNHz9/FeEoilZmcktoq34BBmjBAKE4wwTJBeBmUO0iX96ZYVJfJ7svnTpCVXl9liDZtl4iTI0CnqilSPSTREj8lIsub1/29fHaO/L1c9EILKZ9d9bwf/Nvxsue7Xkev+HEP5E1ymCvnpQxj4EH2roLUdQN0GnF8VtMguXGxC06Ol2VpWKhf3GQa8nULtPDTcQPVirmf5fU4eQF2Xr7qGvX3okPN1S790cWfsRnax1QAAAAlwSFlzAAAuIwAALiMBeKU/dgAAAC9JREFUOI1jfPr0KQNuICUlhUeWCY8cQTCqeWRoZvz//z8e6WfPntHK5lHNI0MzAMChCNMTuPcnAAAAAElFTkSuQmCC)); + background: var(--wcd-canvas-background, var(--node-projects-web-component-designer-background, border-box fixed 0px 0px repeat url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAFXmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgZXhpZjpQaXhlbFhEaW1lbnNpb249IjIwIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iMjAiCiAgIGV4aWY6Q29sb3JTcGFjZT0iMSIKICAgdGlmZjpJbWFnZVdpZHRoPSIyMCIKICAgdGlmZjpJbWFnZUxlbmd0aD0iMjAiCiAgIHRpZmY6UmVzb2x1dGlvblVuaXQ9IjIiCiAgIHRpZmY6WFJlc29sdXRpb249IjMwMC8xIgogICB0aWZmOllSZXNvbHV0aW9uPSIzMDAvMSIKICAgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIKICAgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIgogICB4bXA6TW9kaWZ5RGF0ZT0iMjAyMi0xMi0wOFQwOToxNTo0OCswMTowMCIKICAgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMi0xMi0wOFQwOToxNTo0OCswMTowMCI+CiAgIDxkYzp0aXRsZT4KICAgIDxyZGY6QWx0PgogICAgIDxyZGY6bGkgeG1sOmxhbmc9IngtZGVmYXVsdCI+QmFja2dyb3VuZGdyaWRfMTBweDwvcmRmOmxpPgogICAgPC9yZGY6QWx0PgogICA8L2RjOnRpdGxlPgogICA8eG1wTU06SGlzdG9yeT4KICAgIDxyZGY6U2VxPgogICAgIDxyZGY6bGkKICAgICAgc3RFdnQ6YWN0aW9uPSJwcm9kdWNlZCIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWZmaW5pdHkgRGVzaWduZXIgMS4xMC42IgogICAgICBzdEV2dDp3aGVuPSIyMDIyLTEyLTA4VDA5OjE1OjQ4KzAxOjAwIi8+CiAgICA8L3JkZjpTZXE+CiAgIDwveG1wTU06SGlzdG9yeT4KICA8L3JkZjpEZXNjcmlwdGlvbj4KIDwvcmRmOlJERj4KPC94OnhtcG1ldGE+Cjw/eHBhY2tldCBlbmQ9InIiPz4fvgn+AAABgWlDQ1BzUkdCIElFQzYxOTY2LTIuMQAAKJF1kc8rRFEUxz8GjRhRLCiLl7BCftTExmLkV2ExnvJr8+bNvBk1b+b13kiyVbaKEhu/FvwFbJW1UkRKlrImNug5b2ZqJplzO/d87vfec7r3XPCpSd10KnrATGXs8FhImZtfUPwvBKjCTx/Nmu5YUzOjKiXt854yL952ebVKn/vXaqIxR4eyKuEh3bIzwuPCk6sZy+Md4UY9oUWFz4Q7bbmg8J2nR3L86nE8x98e22p4GHz1wkq8iCNFrCdsU1heTpuZXNHz9/FeEoilZmcktoq34BBmjBAKE4wwTJBeBmUO0iX96ZYVJfJ7svnTpCVXl9liDZtl4iTI0CnqilSPSTREj8lIsub1/29fHaO/L1c9EILKZ9d9bwf/Nvxsue7Xkev+HEP5E1ymCvnpQxj4EH2roLUdQN0GnF8VtMguXGxC06Ol2VpWKhf3GQa8nULtPDTcQPVirmf5fU4eQF2Xr7qGvX3okPN1S790cWfsRnax1QAAAAlwSFlzAAAuIwAALiMBeKU/dgAAAC9JREFUOI1jfPr0KQNuICUlhUeWCY8cQTCqeWRoZvz//z8e6WfPntHK5lHNI0MzAMChCNMTuPcnAAAAAElFTkSuQmCC))); } #node-projects-designer-canvas-canvas { image-rendering: pixelated; @@ -202,7 +202,7 @@ export class DesignerCanvas extends BaseCustomWebComponentLazyAppend implements } #node-projects-designer-canvas-canvas.dragFileActive { - outline: blue 4px solid; + outline: var(--wcd-canvas-drag-file-outline-color, blue) 4px solid; outline-offset: -4px; } @@ -257,19 +257,19 @@ export class DesignerCanvas extends BaseCustomWebComponentLazyAppend implements width: auto; right: 10px; top: 0; - background:rgb(242, 242, 242); + background: var(--wcd-canvas-search-container-background, rgb(242, 242, 242)); padding: 5px 6px; border-bottom-left-radius: 5px; border-bottom-right-radius: 5px; } #node-projects-designer-search-container > #node-projects-designer-search-bar { - border: 1px solid black; + border: 1px solid var(--wcd-canvas-search-border-color, black); padding: 0; display: grid; grid-template-columns: 1fr auto; align-items: center; - background-color: white; + background-color: var(--wcd-canvas-search-background, white); padding: 1px 2px; } @@ -284,12 +284,12 @@ export class DesignerCanvas extends BaseCustomWebComponentLazyAppend implements #node-projects-designer-search-container > div > #node-projects-designer-search-start { height:22px; border: none; - background-color: white; + background-color: var(--wcd-canvas-search-background, white); font-size: 13px; } #node-projects-designer-search-container > div > #node-projects-designer-search-start:hover { - background-color: #D3D3D3; + background-color: var(--wcd-canvas-search-button-hover-background, #D3D3D3); transition: 0.9s; border-radius: 2px; } @@ -314,7 +314,7 @@ export class DesignerCanvas extends BaseCustomWebComponentLazyAppend implements top: 50%; left: 50%; width: 60%; - background-color: black; + background-color: var(--wcd-canvas-loading-background, black); height: 1px; } @@ -614,7 +614,7 @@ export class DesignerCanvas extends BaseCustomWebComponentLazyAppend implements } public disableBackgroud() { - this._canvasContainer.style.background = 'var(--node-projects-web-component-designer-screenshot-background, white)'; + this._canvasContainer.style.background = 'var(--wcd-screenshot-background, var(--node-projects-web-component-designer-screenshot-background, white))'; } public enableBackground() { diff --git a/packages/web-component-designer/src/elements/widgets/designerView/designerView.ts b/packages/web-component-designer/src/elements/widgets/designerView/designerView.ts index 2619e2ac4..569a976ac 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/designerView.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/designerView.ts @@ -61,7 +61,7 @@ export class DesignerView extends BaseCustomWebComponentConstructorAppend implem } #lowertoolbar { height: 16px; - background: #787f82; + background: var(--wcd-designer-view-statusbar-background, #787f82); display: flex; bottom: 0; position: absolute; @@ -88,10 +88,10 @@ export class DesignerView extends BaseCustomWebComponentConstructorAppend implem align-items: center; } .selected { - background-color: deepskyblue; + background-color: var(--wcd-designer-view-tool-selected-background, deepskyblue); } .toolbar-control:hover { - background-color:rgba(164,206,249,.6); + background-color: var(--wcd-designer-view-tool-hover-background, rgba(164,206,249,.6)); } #outer { user-select: none; @@ -110,7 +110,7 @@ export class DesignerView extends BaseCustomWebComponentConstructorAppend implem width: 24px; height: calc(100% - 32px); position: absolute; - background-color: lightgray; + background-color: var(--wcd-designer-view-toolbar-background, lightgray); } .zoom-in { @@ -149,7 +149,7 @@ export class DesignerView extends BaseCustomWebComponentConstructorAppend implem bottom: 16px; right: 0; position: absolute; - background: #f0f0f0; + background: var(--wcd-designer-view-corner-background, #f0f0f0); }`; static override readonly template = html` diff --git a/packages/web-component-designer/src/elements/widgets/designerView/extensions/ElementDragTitleExtensionProvider.ts b/packages/web-component-designer/src/elements/widgets/designerView/extensions/ElementDragTitleExtensionProvider.ts index a3b57c3a4..738a7abca 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/extensions/ElementDragTitleExtensionProvider.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/extensions/ElementDragTitleExtensionProvider.ts @@ -23,6 +23,6 @@ export class ElementDragTitleExtensionProvider implements IDesignerExtensionProv } static readonly style = css` - .svg-text-primary { stroke: none; color: white; font-family: monospace; } + .svg-text-primary { stroke: none; color: var(--wcd-color-text, white); font-family: monospace; } `; } \ No newline at end of file diff --git a/packages/web-component-designer/src/elements/widgets/designerView/extensions/ResizeExtensionProvider.ts b/packages/web-component-designer/src/elements/widgets/designerView/extensions/ResizeExtensionProvider.ts index fd9db107c..adc01b4de 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/extensions/ResizeExtensionProvider.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/extensions/ResizeExtensionProvider.ts @@ -27,6 +27,6 @@ export class ResizeExtensionProvider implements IDesignerExtensionProvider { } static readonly style = css` - .svg-primary-resizer { stroke: #3899ec; fill: white; pointer-events: auto; } + .svg-primary-resizer { stroke: var(--wcd-color-selection, #3899ec); fill: white; pointer-events: auto; } `; } \ No newline at end of file diff --git a/packages/web-component-designer/src/elements/widgets/designerView/extensions/SelectionDefaultExtensionProvider.ts b/packages/web-component-designer/src/elements/widgets/designerView/extensions/SelectionDefaultExtensionProvider.ts index d700cfd20..af6658a9e 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/extensions/SelectionDefaultExtensionProvider.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/extensions/SelectionDefaultExtensionProvider.ts @@ -17,6 +17,6 @@ export class SelectionDefaultExtensionProvider implements IDesignerExtensionProv } static readonly style = css` - .svg-selection { stroke: #3899ec; fill: transparent; stroke-width: 2; } + .svg-selection { stroke: var(--wcd-color-selection, #3899ec); fill: transparent; stroke-width: 2; } `; } \ No newline at end of file diff --git a/packages/web-component-designer/src/elements/widgets/designerView/extensions/grid/DisplayGridExtensionProvider.ts b/packages/web-component-designer/src/elements/widgets/designerView/extensions/grid/DisplayGridExtensionProvider.ts index dc1fa60c3..56be08ba9 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/extensions/grid/DisplayGridExtensionProvider.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/extensions/grid/DisplayGridExtensionProvider.ts @@ -34,11 +34,11 @@ export class DisplayGridExtensionProvider implements IDesignerExtensionProvider } static readonly style = css` - .svg-grid { stroke: var(--svg-grid-stroke-color); stroke-dasharray: 5; fill: var(--svg-grid-fill-color); } - .svg-grid-current-cell { stroke: var(--svg-grid-stroke-color); stroke-dasharray: 5; fill: #e3ff4722; } + .svg-grid { stroke: var(--wcd-grid-stroke-color, var(--svg-grid-stroke-color)); stroke-dasharray: 5; fill: var(--wcd-grid-fill-color, var(--wcd-grid-fill-color, var(--svg-grid-fill-color))); } + .svg-grid-current-cell { stroke: var(--wcd-grid-stroke-color, var(--svg-grid-stroke-color)); stroke-dasharray: 5; fill: #e3ff4722; } .svg-grid-area { font-size: 8px; } - .svg-grid-gap { stroke: transparent; fill: var(--svg-grid-stroke-color); opacity: 0.3; mask: url(#mask-stripe-grid) } - .svg-grid-header { fill: var(--svg-grid-fill-color); stroke: var(--svg-grid-stroke-color); } + .svg-grid-gap { stroke: transparent; fill: var(--wcd-grid-stroke-color, var(--svg-grid-stroke-color)); opacity: 0.3; mask: url(#mask-stripe-grid) } + .svg-grid-header { fill: var(--wcd-grid-fill-color, var(--wcd-grid-fill-color, var(--svg-grid-fill-color))); stroke: var(--wcd-grid-stroke-color, var(--svg-grid-stroke-color)); } .svg-grid-plus-sign { stroke: black; } `; diff --git a/packages/web-component-designer/src/elements/widgets/designerView/extensions/grid/GridChildResizeExtensionProvider.ts b/packages/web-component-designer/src/elements/widgets/designerView/extensions/grid/GridChildResizeExtensionProvider.ts index 6c47af193..4a88721e2 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/extensions/grid/GridChildResizeExtensionProvider.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/extensions/grid/GridChildResizeExtensionProvider.ts @@ -19,6 +19,6 @@ export class GridChildResizeExtensionProvider implements IDesignerExtensionProvi } static readonly style = css` - .svg-grid-resizer { stroke: #3899ec; fill: white; pointer-events: auto; } + .svg-grid-resizer { stroke: var(--wcd-color-selection, #3899ec); fill: white; pointer-events: auto; } `; } \ No newline at end of file diff --git a/packages/web-component-designer/src/elements/widgets/designerView/extensions/svg/UnifiedGeometryExtensionProvider.ts b/packages/web-component-designer/src/elements/widgets/designerView/extensions/svg/UnifiedGeometryExtensionProvider.ts index 79963a134..f4f07c404 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/extensions/svg/UnifiedGeometryExtensionProvider.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/extensions/svg/UnifiedGeometryExtensionProvider.ts @@ -38,7 +38,7 @@ export class UnifiedGeometryExtensionProvider implements IDesignerExtensionProvi readonly style = css` .svg-control-line { - stroke: #3899ec; + stroke: var(--wcd-color-selection, #3899ec); fill: none; stroke-dasharray: 4; pointer-events: none; @@ -46,14 +46,14 @@ export class UnifiedGeometryExtensionProvider implements IDesignerExtensionProvi } .svg-control-point { - stroke: #3899ec; - fill: white; + stroke: var(--wcd-color-selection, #3899ec); + fill: var(--wcd-color-handle, white); pointer-events: auto; } .svg-path-selected-part { - stroke: black; - fill: lime; + stroke: var(--wcd-color-handle-stroke, black); + fill: var(--wcd-color-handle-active, lime); } `; } diff --git a/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/ProjectiveTransformExtensionProvider.ts b/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/ProjectiveTransformExtensionProvider.ts index 65d23f30c..23af31859 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/ProjectiveTransformExtensionProvider.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/ProjectiveTransformExtensionProvider.ts @@ -22,8 +22,8 @@ export class ProjectiveTransformExtensionProvider implements IDesignerExtensionP } static readonly style = css` - .svg-primary-projective-handle { stroke: #3899ec; fill: white; stroke-width: 1; pointer-events: auto; } - .svg-primary-projective-handle-cross { stroke: #3899ec; fill: none; stroke-width: 1; stroke-linecap: round; pointer-events: none; } - .svg-primary-projective-outline { stroke: #3899ec; fill: none; stroke-width: 1; stroke-dasharray: 4 2; } + .svg-primary-projective-handle { stroke: var(--wcd-color-selection, #3899ec); fill: white; stroke-width: 1; pointer-events: auto; } + .svg-primary-projective-handle-cross { stroke: var(--wcd-color-selection, #3899ec); fill: none; stroke-width: 1; stroke-linecap: round; pointer-events: none; } + .svg-primary-projective-outline { stroke: var(--wcd-color-selection, #3899ec); fill: none; stroke-width: 1; stroke-dasharray: 4 2; } `; } \ No newline at end of file diff --git a/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/RotateExtensionProvider.ts b/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/RotateExtensionProvider.ts index eefcda4c9..6127ba7fe 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/RotateExtensionProvider.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/RotateExtensionProvider.ts @@ -27,8 +27,8 @@ export class RotateExtensionProvider implements IDesignerExtensionProvider { } static readonly style = css` - .svg-primary-rotate { stroke: #3899ec; fill: white; stroke-width: 1; pointer-events: auto; cursor: alias; } + .svg-primary-rotate { stroke: var(--wcd-color-selection, #3899ec); fill: white; stroke-width: 1; pointer-events: auto; cursor: alias; } .svg-primary-rotate-transparent { fill: transparent; pointer-events: auto; cursor: alias; } - .svg-primary-rotate-line { stroke: #3899ec; fill: #3899ec; stroke-width: 1; } + .svg-primary-rotate-line { stroke: var(--wcd-color-selection, #3899ec); fill: #3899ec; stroke-width: 1; } `; } \ No newline at end of file diff --git a/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/RotateGroupExtensionProvider.ts b/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/RotateGroupExtensionProvider.ts index ea5ed7211..171d7accc 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/RotateGroupExtensionProvider.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/RotateGroupExtensionProvider.ts @@ -20,8 +20,8 @@ export class RotateGroupExtensionProvider implements IDesignerExtensionProvider } static readonly style = css` - .svg-primary-rotate { stroke: #3899ec; fill: white; stroke-width: 1; pointer-events: auto; cursor: alias; } - .svg-primary-rotate-line { stroke: #3899ec; fill: #3899ec; stroke-width: 1; } - .svg-rotate-group-rect { stroke: #3899ec; fill: transparent; stroke-width: 2; stroke-dasharray: 5 3; } + .svg-primary-rotate { stroke: var(--wcd-color-selection, #3899ec); fill: white; stroke-width: 1; pointer-events: auto; cursor: alias; } + .svg-primary-rotate-line { stroke: var(--wcd-color-selection, #3899ec); fill: #3899ec; stroke-width: 1; } + .svg-rotate-group-rect { stroke: var(--wcd-color-selection, #3899ec); fill: transparent; stroke-width: 2; stroke-dasharray: 5 3; } `; } diff --git a/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/SkewExtensionProvider.ts b/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/SkewExtensionProvider.ts index 110da5032..8ca5d0d20 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/SkewExtensionProvider.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/SkewExtensionProvider.ts @@ -21,7 +21,7 @@ export class SkewExtensionProvider implements IDesignerExtensionProvider { } static readonly style = css` - .svg-primary-skew { stroke: #3899ec; fill: white; stroke-width: 1; pointer-events: auto; } - .svg-primary-skew-line { stroke: #3899ec; fill: none; stroke-width: 1; } + .svg-primary-skew { stroke: var(--wcd-color-selection, #3899ec); fill: white; stroke-width: 1; pointer-events: auto; } + .svg-primary-skew-line { stroke: var(--wcd-color-selection, #3899ec); fill: none; stroke-width: 1; } `; } \ No newline at end of file diff --git a/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/TransformOriginExtensionProvider.ts b/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/TransformOriginExtensionProvider.ts index fe780d5d2..5dedd4196 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/TransformOriginExtensionProvider.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/TransformOriginExtensionProvider.ts @@ -38,6 +38,6 @@ export class TransformOriginExtensionProvider implements IDesignerExtensionProvi } static readonly style = css` - .svg-transform-origin { stroke: #3899ec; fill: black; pointer-events: auto; } + .svg-transform-origin { stroke: var(--wcd-color-selection, #3899ec); fill: black; pointer-events: auto; } `; } \ No newline at end of file diff --git a/packages/web-component-designer/src/elements/widgets/designerView/overlayLayerView.ts b/packages/web-component-designer/src/elements/widgets/designerView/overlayLayerView.ts index bff29dd63..16715e000 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/overlayLayerView.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/overlayLayerView.ts @@ -21,26 +21,26 @@ export class OverlayLayerView extends BaseCustomWebComponentConstructorAppend { } .svg-invisible { stroke: transparent; fill: transparent; pointer-events: auto; } - .svg-snapline { stroke: purple; stroke-dasharray: 4; fill: transparent; } - .svg-selector { stroke: black; fill: #3899ec55; stroke-width: 1; stroke-dasharray: 2; } - .svg-primary-selection-move { stroke: #3899ec; fill: #3899ec; cursor: move; pointer-events: auto; } + .svg-snapline { stroke: var(--wcd-color-snapline, purple); stroke-dasharray: 4; fill: transparent; } + .svg-selector { stroke: black; fill: var(--wcd-color-selection-fill, #3899ec55); stroke-width: 1; stroke-dasharray: 2; } + .svg-primary-selection-move { stroke: var(--wcd-color-selection, #3899ec); fill: #3899ec; cursor: move; pointer-events: auto; } .svg-position { stroke: black; stroke-dasharray: 2; } - .svg-path { stroke: #3899ec; fill: orange; pointer-events: auto; } - .svg-path-line { stroke: #3899ec; stroke-dasharray: 2; } + .svg-path { stroke: var(--wcd-color-selection, #3899ec); fill: orange; pointer-events: auto; } + .svg-path-line { stroke: var(--wcd-color-selection, #3899ec); stroke-dasharray: 2; } .svg-draw-new-element { stroke: black; fill: transparent; stroke-width: 1; } .svg-toolbar-container { overflow: visible } - .svg-toolbar-container > div { padding: 5px; display: flex; gap: 2px; background: white; border-radius: 4px; box-shadow: 0 2px 10px 0 rgba(19,23,32,.2); align-items: center; } + .svg-toolbar-container > div { padding: 5px; display: flex; gap: 2px; background: var(--wcd-overlay-toolbar-background, white); border-radius: 4px; box-shadow: 0 2px 10px 0 rgba(19,23,32,.2); align-items: center; } node-projects-image-button-list-selector img { height: 16px; - border: 1px solid black; + border: 1px solid var(--wcd-overlay-toolbar-button-border-color, black); border-radius: 4px; box-sizing: border-box; pointer-events: auto; cursor: pointer; } node-projects-image-button-list-selector img:hover { - background: lightgray; + background: var(--wcd-overlay-toolbar-button-hover-background, lightgray); } node-projects-image-button-list-selector img:active { translate: 1px 1px diff --git a/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/DesignerToolbarButton.ts b/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/DesignerToolbarButton.ts index bd55f2dae..5cd6e02e7 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/DesignerToolbarButton.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/DesignerToolbarButton.ts @@ -16,7 +16,7 @@ export class DesignerToolbarButton extends BaseCustomWebComponentConstructorAppe } div:hover { - background-color: darkgray; + background-color: var(--wcd-toolbar-button-selected-background, darkgray); } img { diff --git a/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/AbstractBaseToolPopup.ts b/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/AbstractBaseToolPopup.ts index 655fb9bc9..04cdfd767 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/AbstractBaseToolPopup.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/AbstractBaseToolPopup.ts @@ -7,9 +7,9 @@ export abstract class AbstractBaseToolPopup extends BaseCustomWebComponentConstr .container { width: 120px; min-height: 100px; - color: white; - background-color: rgb(64, 64, 64); - border: 1px solid black; + color: var(--wcd-color-text, white); + background-color: var(--wcd-tool-popup-title-background, rgb(64, 64, 64)); + border: 1px solid var(--wcd-tool-popup-border-color, black); } header { text-align: center; @@ -17,12 +17,12 @@ export abstract class AbstractBaseToolPopup extends BaseCustomWebComponentConstr .tool { height: 32px; width: 32px; - background-color: rgb(255, 255, 255); + background-color: var(--wcd-tool-popup-background, rgb(255, 255, 255)); background-size: 65%; background-repeat: no-repeat; background-position: center center; flex-shrink: 0; - border-bottom: 1px solid black; + border-bottom: 1px solid var(--wcd-tool-popup-border-color, black); } .tools { display: flex; diff --git a/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/BorderRadiusEditorWindow.ts b/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/BorderRadiusEditorWindow.ts index f0e7a130f..8bf498278 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/BorderRadiusEditorWindow.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/BorderRadiusEditorWindow.ts @@ -305,12 +305,12 @@ export class BorderRadiusEditorWindow extends DraggableToolWindow { flex-direction: column; width: 860px; max-width: calc(100vw - 48px); - color: #ddd; + color: var(--wcd-color-text, #ddd); font-family: sans-serif; font-size: 12px; gap: 4px; padding: 6px; - background: #2a2a2a; + background: var(--wcd-color-surface-raised, #2a2a2a); } .editors-row { display: grid; @@ -324,8 +324,8 @@ export class BorderRadiusEditorWindow extends DraggableToolWindow { gap: 4px; } .preview-area { - background: #1a1a1a; - border: 1px solid #444; + background: var(--wcd-color-surface, #1a1a1a); + border: 1px solid var(--wcd-color-border, #444); border-radius: 3px; height: 70px; display: flex; @@ -336,7 +336,7 @@ export class BorderRadiusEditorWindow extends DraggableToolWindow { .preview-box { width: 60px; height: 60px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: var(--wcd-border-radius-editor-preview-background, linear-gradient(135deg, #667eea 0%, #764ba2 100%)); border-radius: 0; } .uniform-row { @@ -351,7 +351,7 @@ export class BorderRadiusEditorWindow extends DraggableToolWindow { gap: 3px; cursor: pointer; user-select: none; - color: #aaa; + color: var(--wcd-color-text-muted, #aaa); } .uniform-row input[type=checkbox] { cursor: pointer; @@ -359,7 +359,7 @@ export class BorderRadiusEditorWindow extends DraggableToolWindow { height: 14px; } .section-header { - color: #999; + color: var(--wcd-color-text-muted, #999); font-size: 10px; font-weight: bold; margin-top: 3px; @@ -377,13 +377,13 @@ export class BorderRadiusEditorWindow extends DraggableToolWindow { gap: 4px; } .corner-section { - background: #1e1e1e; - border: 1px solid #444; + background: var(--wcd-color-surface, #1e1e1e); + border: 1px solid var(--wcd-color-border, #444); border-radius: 2px; padding: 4px; } .corner-header { - color: #888; + color: var(--wcd-color-text-muted, #888); font-size: 10px; margin-bottom: 3px; font-weight: bold; @@ -396,14 +396,14 @@ export class BorderRadiusEditorWindow extends DraggableToolWindow { margin-bottom: 2px; } .corner-row:last-child { margin-bottom: 0; } - .corner-row label { color: #888; font-size: 10px; } + .corner-row label { color: var(--wcd-color-text-muted, #888); font-size: 10px; } .corner-row input[type=range] { width: 100%; height: 16px; } .corner-row input[type=color] { width: 100%; height: 18px; cursor: pointer; } .corner-row .val-label { text-align: right; color: #777; font-size: 9px; } .bre-root select { - background: #2e2e2e; - color: #ddd; - border: 1px solid #555; + background: var(--wcd-color-surface-raised, #2e2e2e); + color: var(--wcd-color-text, #ddd); + border: 1px solid var(--wcd-color-border, #555); border-radius: 2px; padding: 2px 3px; font-size: 10px; @@ -427,14 +427,14 @@ export class BorderRadiusEditorWindow extends DraggableToolWindow { } .separator { height: 1px; - background: #444; + background: var(--wcd-color-surface-hover, #444); margin: 2px 0; } .css-out { - background: #1e1e1e; - border: 1px solid #333; + background: var(--wcd-color-surface, #1e1e1e); + border: 1px solid var(--wcd-color-border, #333); border-radius: 3px; - color: #7ec8e3; + color: var(--wcd-color-accent-text, #7ec8e3); font-family: monospace; font-size: 10px; padding: 4px; @@ -450,17 +450,17 @@ export class BorderRadiusEditorWindow extends DraggableToolWindow { .actions .load-btn { margin-right: auto; } .actions button, .apply-btn { padding: 3px 10px; - background: #3a3a3a; - color: #ddd; - border: 1px solid #555; + background: var(--wcd-color-surface-raised, #3a3a3a); + color: var(--wcd-color-text, #ddd); + border: 1px solid var(--wcd-color-border, #555); border-radius: 2px; cursor: pointer; font-size: 11px; } .actions button:hover, .apply-btn:hover { background: #555; } .apply-btn { - background: #3a6a9a; - border-color: #2a5a8a; + background: var(--wcd-color-accent, #3a6a9a); + border-color: var(--wcd-color-accent-border, #2a5a8a); } .apply-btn:hover { background: #4a7aaa; } `; diff --git a/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/BoxShadowEditorWindow.ts b/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/BoxShadowEditorWindow.ts index 3c88d7fcd..694a56821 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/BoxShadowEditorWindow.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/BoxShadowEditorWindow.ts @@ -155,12 +155,12 @@ export class BoxShadowEditorWindow extends DraggableToolWindow { display: flex; flex-direction: column; width: 380px; - color: #ddd; + color: var(--wcd-color-text, #ddd); font-family: sans-serif; font-size: 12px; padding: 8px; gap: 8px; - background: #2c2c2c; + background: var(--wcd-color-surface-raised, #2c2c2c); } .preview-area { display: flex; @@ -169,12 +169,12 @@ export class BoxShadowEditorWindow extends DraggableToolWindow { height: 110px; background: repeating-conic-gradient(#444 0% 25%, #333 0% 50%) 0 0 / 16px 16px; border-radius: 4px; - border: 1px solid #111; + border: 1px solid var(--wcd-color-border-strong, #111); } .preview-box { width: 70px; height: 70px; - background: #fff; + background: var(--wcd-box-shadow-editor-preview-background, #fff); border-radius: 4px; } .layers-row { @@ -190,8 +190,8 @@ export class BoxShadowEditorWindow extends DraggableToolWindow { min-height: 60px; max-height: 100px; overflow-y: auto; - background: #1e1e1e; - border: 1px solid #111; + background: var(--wcd-color-surface, #1e1e1e); + border: 1px solid var(--wcd-color-border-strong, #111); border-radius: 3px; } .layer-list li { @@ -205,13 +205,13 @@ export class BoxShadowEditorWindow extends DraggableToolWindow { text-overflow: ellipsis; } .layer-list li.selected { - background: #3a3a5a; + background: var(--wcd-box-shadow-editor-preview-box-background, #3a3a5a); } .layer-list li .swatch { width: 12px; height: 12px; border-radius: 2px; - border: 1px solid #555; + border: 1px solid var(--wcd-color-border, #555); flex-shrink: 0; display: inline-block; } @@ -226,9 +226,9 @@ export class BoxShadowEditorWindow extends DraggableToolWindow { height: 24px; padding: 0; font-size: 14px; - background: #444; - color: #ddd; - border: 1px solid #222; + background: var(--wcd-color-surface-hover, #444); + color: var(--wcd-color-text, #ddd); + border: 1px solid var(--wcd-color-border-strong, #222); border-radius: 3px; cursor: pointer; line-height: 1; @@ -240,13 +240,13 @@ export class BoxShadowEditorWindow extends DraggableToolWindow { gap: 4px 6px; align-items: center; } - .controls label { color: #aaa; } + .controls label { color: var(--wcd-color-text-muted, #aaa); } .controls input[type=range] { width: 100%; } .controls input[type=number] { width: 100%; - background: #1e1e1e; - border: 1px solid #444; - color: #ddd; + background: var(--wcd-color-surface, #1e1e1e); + border: 1px solid var(--wcd-color-border, #444); + color: var(--wcd-color-text, #ddd); border-radius: 3px; padding: 2px 4px; } @@ -254,8 +254,8 @@ export class BoxShadowEditorWindow extends DraggableToolWindow { width: 100%; height: 24px; padding: 1px; - background: #1e1e1e; - border: 1px solid #444; + background: var(--wcd-color-surface, #1e1e1e); + border: 1px solid var(--wcd-color-border, #444); border-radius: 3px; cursor: pointer; } @@ -273,10 +273,10 @@ export class BoxShadowEditorWindow extends DraggableToolWindow { user-select: none; } .css-out { - background: #1e1e1e; - border: 1px solid #333; + background: var(--wcd-color-surface, #1e1e1e); + border: 1px solid var(--wcd-color-border, #333); border-radius: 3px; - color: #7ec8e3; + color: var(--wcd-color-accent-text, #7ec8e3); font-family: monospace; font-size: 11px; padding: 6px; @@ -292,20 +292,20 @@ export class BoxShadowEditorWindow extends DraggableToolWindow { .actions .load-btn { margin-right: auto; } .actions button, .apply-btn { padding: 4px 12px; - background: #3a3a3a; - color: #ddd; - border: 1px solid #555; + background: var(--wcd-color-surface-raised, #3a3a3a); + color: var(--wcd-color-text, #ddd); + border: 1px solid var(--wcd-color-border, #555); border-radius: 3px; cursor: pointer; font-size: 12px; } .actions button:hover, .apply-btn:hover { background: #555; } .apply-btn { - background: #3a6a9a; - border-color: #2a5a8a; + background: var(--wcd-color-accent, #3a6a9a); + border-color: var(--wcd-color-accent-border, #2a5a8a); } .apply-btn:hover { background: #4a7aaa; } - .val-label { text-align: right; color: #888; } + .val-label { text-align: right; color: var(--wcd-color-text-muted, #888); } `; } diff --git a/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/DraggableToolWindow.ts b/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/DraggableToolWindow.ts index 993fa0f99..92fbf0e26 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/DraggableToolWindow.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/DraggableToolWindow.ts @@ -22,10 +22,10 @@ export abstract class DraggableToolWindow extends BaseCustomWebComponentConstruc touch-action: none; } .window-frame { - background: #2c2c2c; - border: 1px solid #111; + background: var(--wcd-color-surface-raised, #2c2c2c); + border: 1px solid var(--wcd-color-border-strong, #111); border-radius: 4px; - box-shadow: 0 4px 24px rgba(0,0,0,0.7); + box-shadow: 0 4px 24px var(--wcd-tool-window-shadow-color, rgba(0,0,0,0.7)); display: flex; flex-direction: column; overflow: hidden; @@ -34,7 +34,7 @@ export abstract class DraggableToolWindow extends BaseCustomWebComponentConstruc display: flex; align-items: center; justify-content: space-between; - background: #1a1a1a; + background: var(--wcd-color-surface, #1a1a1a); padding: 2px 5px; cursor: move; flex-shrink: 0; @@ -42,7 +42,7 @@ export abstract class DraggableToolWindow extends BaseCustomWebComponentConstruc box-sizing: border-box; } .title-text { - color: #ccc; + color: var(--wcd-color-text-muted, #ccc); font-size: 11px; font-family: sans-serif; font-weight: 500; @@ -56,7 +56,7 @@ export abstract class DraggableToolWindow extends BaseCustomWebComponentConstruc height: 14px; background: none; border: none; - color: #888; + color: var(--wcd-color-text-muted, #888); font-size: 12px; line-height: 1; cursor: pointer; @@ -68,8 +68,8 @@ export abstract class DraggableToolWindow extends BaseCustomWebComponentConstruc flex-shrink: 0; } .close-btn:hover { - background: #c0392b; - color: #fff; + background: var(--wcd-tool-window-close-hover-background, #c0392b); + color: var(--wcd-color-text, #fff); } .window-content { overflow: auto; diff --git a/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/GradientEditorWindow.ts b/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/GradientEditorWindow.ts index 5f268680e..e550a14dc 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/GradientEditorWindow.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/GradientEditorWindow.ts @@ -254,23 +254,23 @@ export class GradientEditorWindow extends DraggableToolWindow { display: flex; flex-direction: column; width: 380px; - color: #ddd; + color: var(--wcd-color-text, #ddd); font-family: sans-serif; font-size: 12px; gap: 6px; padding: 8px; - background: #2a2a2a; + background: var(--wcd-color-surface-raised, #2a2a2a); } .preview-strip { height: 50px; border-radius: 4px 4px 0 0; - border: 1px solid #444; + border: 1px solid var(--wcd-color-border, #444); border-bottom: none; } .stop-track { height: 20px; background: repeating-conic-gradient(#333 0% 25%, #444 0% 50%) 0 0 / 10px 10px; - border: 1px solid #444; + border: 1px solid var(--wcd-color-border, #444); border-radius: 0 0 4px 4px; position: relative; cursor: crosshair; @@ -282,14 +282,14 @@ export class GradientEditorWindow extends DraggableToolWindow { width: 16px; height: 16px; border-radius: 50%; - border: 2px solid #aaa; + border: 2px solid var(--wcd-gradient-editor-stop-border-color, #aaa); cursor: pointer; transform: translateX(-50%); transition: border-color 0.1s; } .stop-marker.selected { - border-color: #fff; - box-shadow: 0 0 0 2px #3a7ad5; + border-color: var(--wcd-gradient-editor-stop-selected-border-color, #fff); + box-shadow: 0 0 0 2px var(--wcd-gradient-editor-stop-selected-glow-color, #3a7ad5); z-index: 1; } .row { @@ -304,21 +304,21 @@ export class GradientEditorWindow extends DraggableToolWindow { .row.multi { grid-template-columns: 70px 1fr 70px 1fr; } - .row label { color: #aaa; } + .row label { color: var(--wcd-color-text-muted, #aaa); } .row input[type=range] { width: 100%; } .row input[type=color] { width: 100%; height: 22px; padding: 1px; - border: 1px solid #555; + border: 1px solid var(--wcd-color-border, #555); border-radius: 3px; - background: #1e1e1e; + background: var(--wcd-color-surface, #1e1e1e); cursor: pointer; } select { - background: #2e2e2e; - color: #ddd; - border: 1px solid #555; + background: var(--wcd-color-surface-raised, #2e2e2e); + color: var(--wcd-color-text, #ddd); + border: 1px solid var(--wcd-color-border, #555); border-radius: 3px; padding: 2px 4px; font-size: 11px; @@ -326,14 +326,14 @@ export class GradientEditorWindow extends DraggableToolWindow { } .separator { height: 1px; - background: #444; + background: var(--wcd-color-surface-hover, #444); margin: 2px 0; } .stop-header { display: flex; justify-content: space-between; align-items: center; - color: #888; + color: var(--wcd-color-text-muted, #888); font-size: 11px; } .stop-header .stop-actions { @@ -342,20 +342,20 @@ export class GradientEditorWindow extends DraggableToolWindow { } .stop-actions button { padding: 1px 7px; - background: #3a3a3a; - color: #ddd; - border: 1px solid #555; + background: var(--wcd-color-surface-raised, #3a3a3a); + color: var(--wcd-color-text, #ddd); + border: 1px solid var(--wcd-color-border, #555); border-radius: 3px; cursor: pointer; font-size: 12px; } .stop-actions button:hover { background: #555; } - .val-label { text-align: right; color: #888; } + .val-label { text-align: right; color: var(--wcd-color-text-muted, #888); } .css-out { - background: #1e1e1e; - border: 1px solid #333; + background: var(--wcd-color-surface, #1e1e1e); + border: 1px solid var(--wcd-color-border, #333); border-radius: 3px; - color: #7ec8e3; + color: var(--wcd-color-accent-text, #7ec8e3); font-family: monospace; font-size: 11px; padding: 6px; @@ -371,17 +371,17 @@ export class GradientEditorWindow extends DraggableToolWindow { .actions .load-btn { margin-right: auto; } .actions button, .apply-btn { padding: 4px 12px; - background: #3a3a3a; - color: #ddd; - border: 1px solid #555; + background: var(--wcd-color-surface-raised, #3a3a3a); + color: var(--wcd-color-text, #ddd); + border: 1px solid var(--wcd-color-border, #555); border-radius: 3px; cursor: pointer; font-size: 12px; } .actions button:hover, .apply-btn:hover { background: #555; } .apply-btn { - background: #3a6a9a; - border-color: #2a5a8a; + background: var(--wcd-color-accent, #3a6a9a); + border-color: var(--wcd-color-accent-border, #2a5a8a); } .apply-btn:hover { background: #4a7aaa; } `; diff --git a/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/TextShadowEditorWindow.ts b/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/TextShadowEditorWindow.ts index 809389056..f191ae9d1 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/TextShadowEditorWindow.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/TextShadowEditorWindow.ts @@ -117,16 +117,16 @@ export class TextShadowEditorWindow extends DraggableToolWindow { display: flex; flex-direction: column; width: 360px; - color: #ddd; + color: var(--wcd-color-text, #ddd); font-family: sans-serif; font-size: 12px; gap: 6px; padding: 8px; - background: #2a2a2a; + background: var(--wcd-color-surface-raised, #2a2a2a); } .preview-area { - background: #1a1a1a; - border: 1px solid #444; + background: var(--wcd-color-surface, #1a1a1a); + border: 1px solid var(--wcd-color-border, #444); border-radius: 4px; height: 60px; display: flex; @@ -136,7 +136,7 @@ export class TextShadowEditorWindow extends DraggableToolWindow { .preview-text { font-size: 28px; font-weight: bold; - color: #fff; + color: var(--wcd-color-text, #fff); user-select: none; } .layers-row { @@ -148,11 +148,11 @@ export class TextShadowEditorWindow extends DraggableToolWindow { list-style: none; margin: 0; padding: 0; - border: 1px solid #444; + border: 1px solid var(--wcd-color-border, #444); border-radius: 3px; max-height: 80px; overflow-y: auto; - background: #1e1e1e; + background: var(--wcd-color-surface, #1e1e1e); } .layer-list li { display: flex; @@ -167,7 +167,7 @@ export class TextShadowEditorWindow extends DraggableToolWindow { .swatch { width: 12px; height: 12px; border-radius: 2px; - border: 1px solid #555; + border: 1px solid var(--wcd-color-border, #555); flex-shrink: 0; } .disabled-label { opacity: 0.4; } @@ -178,9 +178,9 @@ export class TextShadowEditorWindow extends DraggableToolWindow { } .layer-buttons button { width: 22px; height: 22px; - background: #3a3a3a; - color: #ddd; - border: 1px solid #555; + background: var(--wcd-color-surface-raised, #3a3a3a); + color: var(--wcd-color-text, #ddd); + border: 1px solid var(--wcd-color-border, #555); border-radius: 3px; cursor: pointer; font-size: 13px; @@ -193,15 +193,15 @@ export class TextShadowEditorWindow extends DraggableToolWindow { gap: 3px 6px; align-items: center; } - .controls label { color: #aaa; } + .controls label { color: var(--wcd-color-text-muted, #aaa); } .controls input[type=range] { width: 100%; } .controls input[type=color] { width: 100%; height: 22px; padding: 1px; - border: 1px solid #555; + border: 1px solid var(--wcd-color-border, #555); border-radius: 3px; - background: #1e1e1e; + background: var(--wcd-color-surface, #1e1e1e); cursor: pointer; } .check-row { @@ -217,10 +217,10 @@ export class TextShadowEditorWindow extends DraggableToolWindow { user-select: none; } .css-out { - background: #1e1e1e; - border: 1px solid #333; + background: var(--wcd-color-surface, #1e1e1e); + border: 1px solid var(--wcd-color-border, #333); border-radius: 3px; - color: #7ec8e3; + color: var(--wcd-color-accent-text, #7ec8e3); font-family: monospace; font-size: 11px; padding: 6px; @@ -236,20 +236,20 @@ export class TextShadowEditorWindow extends DraggableToolWindow { .actions .load-btn { margin-right: auto; } .actions button, .apply-btn { padding: 4px 12px; - background: #3a3a3a; - color: #ddd; - border: 1px solid #555; + background: var(--wcd-color-surface-raised, #3a3a3a); + color: var(--wcd-color-text, #ddd); + border: 1px solid var(--wcd-color-border, #555); border-radius: 3px; cursor: pointer; font-size: 12px; } .actions button:hover, .apply-btn:hover { background: #555; } .apply-btn { - background: #3a6a9a; - border-color: #2a5a8a; + background: var(--wcd-color-accent, #3a6a9a); + border-color: var(--wcd-color-accent-border, #2a5a8a); } .apply-btn:hover { background: #4a7aaa; } - .val-label { text-align: right; color: #888; } + .val-label { text-align: right; color: var(--wcd-color-text-muted, #888); } `; } diff --git a/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/TransformToolPopup.ts b/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/TransformToolPopup.ts index a79326a94..8b0bd4539 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/TransformToolPopup.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/tools/toolBar/popups/TransformToolPopup.ts @@ -60,7 +60,7 @@ export class TransformToolPopup extends DraggableToolWindow { grid-row-gap: 2px; font-size: small; margin: 10px; - color: white; + color: var(--wcd-color-text, white); } #button-div{ display: grid; @@ -90,7 +90,7 @@ export class TransformToolPopup extends DraggableToolWindow { #cube-background{ width: 60px; height: 60px; - background: gray; + background: var(--wcd-transform-tool-divider-color, gray); margin-top: 20px; margin-left: 20px; } @@ -102,7 +102,7 @@ export class TransformToolPopup extends DraggableToolWindow { justify-content: center; align-items: center; width: 100%; - color: white; + color: var(--wcd-color-text, white); } `; } diff --git a/packages/web-component-designer/src/elements/widgets/layerDepthView/layerDepthView.ts b/packages/web-component-designer/src/elements/widgets/layerDepthView/layerDepthView.ts index 66cd67629..ca8c5afb8 100644 --- a/packages/web-component-designer/src/elements/widgets/layerDepthView/layerDepthView.ts +++ b/packages/web-component-designer/src/elements/widgets/layerDepthView/layerDepthView.ts @@ -20,8 +20,8 @@ export class LayerDepthView extends BaseCustomWebComponentConstructorAppend impl height: 100%; position: relative; isolation: isolate; - background: #f3f4f6; - color: #111827; + background: var(--wcd-layer-depth-view-background, #f3f4f6); + color: var(--wcd-layer-depth-view-text-color, #111827); user-select: none; -webkit-user-select: none; } @@ -61,7 +61,7 @@ export class LayerDepthView extends BaseCustomWebComponentConstructorAppend impl display: none; align-items: center; justify-content: center; - color: #4b5563; + color: var(--wcd-layer-depth-view-muted-color, #4b5563); font: 13px sans-serif; pointer-events: none; } @@ -77,8 +77,8 @@ export class LayerDepthView extends BaseCustomWebComponentConstructorAppend impl gap: 8px; box-sizing: border-box; padding: 7px 9px; - background: rgba(255, 255, 255, 0.92); - border: 1px solid rgba(17, 24, 39, 0.14); + background: var(--wcd-layer-depth-view-card-background, rgba(255, 255, 255, 0.92)); + border: 1px solid var(--wcd-layer-depth-view-card-border-color, rgba(17, 24, 39, 0.14)); font: 12px sans-serif; pointer-events: auto; z-index: 1; diff --git a/packages/web-component-designer/src/elements/widgets/miniatureView/miniatureView.ts b/packages/web-component-designer/src/elements/widgets/miniatureView/miniatureView.ts index e0912f99a..5c70a506f 100644 --- a/packages/web-component-designer/src/elements/widgets/miniatureView/miniatureView.ts +++ b/packages/web-component-designer/src/elements/widgets/miniatureView/miniatureView.ts @@ -39,7 +39,7 @@ export class MiniatureView extends BaseCustomWebComponentConstructorAppend imple left: 0; width: 100%; height: 100%; - border: 1px solid black; + border: 1px solid var(--wcd-miniature-view-border-color, black); pointer-events: none; }`; diff --git a/packages/web-component-designer/src/elements/widgets/paletteView/paletteElements.ts b/packages/web-component-designer/src/elements/widgets/paletteView/paletteElements.ts index 025f64b12..74b489441 100644 --- a/packages/web-component-designer/src/elements/widgets/paletteView/paletteElements.ts +++ b/packages/web-component-designer/src/elements/widgets/paletteView/paletteElements.ts @@ -16,7 +16,7 @@ export class PaletteElements extends BaseCustomWebComponentLazyAppend { button { background-color: transparent; - color: white; + color: var(--wcd-color-text, white); border: none; font-size: 13px; display: block; @@ -26,7 +26,7 @@ export class PaletteElements extends BaseCustomWebComponentLazyAppend { padding: 8px 14px; } button:hover { - background: var(--light-grey, #383f52); + background: var(--wcd-palette-item-hover-background, var(--light-grey, var(--wcd-color-surface-hover, #383f52))); } table { @@ -34,7 +34,7 @@ export class PaletteElements extends BaseCustomWebComponentLazyAppend { } td { - color: white; + color: var(--wcd-color-text, white); font-size: 13px; } diff --git a/packages/web-component-designer/src/elements/widgets/propertyGrid/PropertyGrid.ts b/packages/web-component-designer/src/elements/widgets/propertyGrid/PropertyGrid.ts index d00331950..f2a8be895 100644 --- a/packages/web-component-designer/src/elements/widgets/propertyGrid/PropertyGrid.ts +++ b/packages/web-component-designer/src/elements/widgets/propertyGrid/PropertyGrid.ts @@ -33,10 +33,10 @@ export class PropertyGrid extends BaseCustomWebComponentLazyAppend { -webkit-user-select: none; } button:hover { - box-shadow: inset 0 3px 0 var(--light-grey); + box-shadow: inset 0 3px 0 var(--wcd-property-grid-tab-indicator-inactive-color, var(--light-grey)); } button:focus { - box-shadow: inset 0 3px 0 var(--highlight-pink, #e91e63); + box-shadow: inset 0 3px 0 var(--wcd-property-grid-tab-indicator-color, var(--highlight-pink, var(--wcd-color-accent, #e91e63))); } `; diff --git a/packages/web-component-designer/src/elements/widgets/propertyGrid/PropertyGridPropertyList.ts b/packages/web-component-designer/src/elements/widgets/propertyGrid/PropertyGridPropertyList.ts index 596be9024..98e00f5d7 100644 --- a/packages/web-component-designer/src/elements/widgets/propertyGrid/PropertyGridPropertyList.ts +++ b/packages/web-component-designer/src/elements/widgets/propertyGrid/PropertyGridPropertyList.ts @@ -47,7 +47,7 @@ export class PropertyGridPropertyList extends BaseCustomWebComponentLazyAppend { } label, input, select { display: inline-block; - color: white; + color: var(--wcd-color-text, white); background: transparent; margin: 2px 0; padding: 0 2px 0 4px; @@ -71,7 +71,7 @@ export class PropertyGridPropertyList extends BaseCustomWebComponentLazyAppend { border: none; } .editor-control, .content-wrapper > input, .content-wrapper > select { - border: 1px solid var(--input-border-color, #596c7a); + border: 1px solid var(--wcd-input-border-color, var(--input-border-color, var(--wcd-color-border, #596c7a))); border-radius: 0; } .editor-control, input, select { @@ -90,22 +90,22 @@ export class PropertyGridPropertyList extends BaseCustomWebComponentLazyAppend { margin-left: 4px; }*/ .editor-control[disabled], input[disabled], select[disabled] { - color: #BDBDBD; + color: var(--wcd-color-text-muted, #BDBDBD); } select { background: transparent; } select:focus option { - color: black; + color: var(--wcd-property-grid-select-option-color, black); } .unset-value { - color: lightslategray + color: var(--wcd-property-grid-unset-value-color, var(--wcd-color-text-muted, lightslategray)) } .unset-value > * { - color: lightslategray + color: var(--wcd-property-grid-unset-value-color, var(--wcd-color-text-muted, lightslategray)) } .unset-value:focus { - color: white + color: var(--wcd-color-text, white) } .group-header { grid-column: 1 / 3; @@ -114,7 +114,7 @@ export class PropertyGridPropertyList extends BaseCustomWebComponentLazyAppend { } .group-header[clickable]:hover { cursor:pointer; - color: orange; + color: var(--wcd-property-grid-group-header-hover-color, orange); text-decoration: underline; } .group-header::after{ @@ -133,11 +133,11 @@ export class PropertyGridPropertyList extends BaseCustomWebComponentLazyAppend { } .group-desc[clickable]:hover { cursor:pointer; - color: orange; + color: var(--wcd-property-grid-group-header-hover-color, orange); text-decoration: underline; } .dragOverProperty { - outline: 2px dashed orange; + outline: 2px dashed var(--wcd-property-grid-drop-target-color, orange); outline-offset: -2px; } `; diff --git a/packages/web-component-designer/src/elements/widgets/propertyGrid/PropertyGridWithHeader.ts b/packages/web-component-designer/src/elements/widgets/propertyGrid/PropertyGridWithHeader.ts index be3728e3e..efb326379 100644 --- a/packages/web-component-designer/src/elements/widgets/propertyGrid/PropertyGridWithHeader.ts +++ b/packages/web-component-designer/src/elements/widgets/propertyGrid/PropertyGridWithHeader.ts @@ -20,8 +20,8 @@ export class PropertyGridWithHeader extends BaseCustomWebComponentLazyAppend { height: 100%; user-select: none; -webkit-user-select: none; - background: var(--medium-grey, #2f3545); - color: white; + background: var(--wcd-property-grid-background, var(--medium-grey, var(--wcd-color-surface-raised, #2f3545))); + color: var(--wcd-color-text, white); } div.root { display: grid; @@ -36,13 +36,13 @@ export class PropertyGridWithHeader extends BaseCustomWebComponentLazyAppend { margin-right: 5px; } input { - background: var(--medium-grey, #2f3545); - border: solid 1px gray; - color: white; + background: var(--wcd-property-grid-background, var(--medium-grey, var(--wcd-color-surface-raised, #2f3545))); + border: solid 1px var(--wcd-color-border, gray); + color: var(--wcd-color-text, white); width: calc(100% - 6px); } #type { - color: wheat; + color: var(--wcd-property-grid-header-highlight-color, wheat); white-space: nowrap; overflow: hidden; font-size: 12px; diff --git a/packages/web-component-designer/src/elements/widgets/treeView/treeView.ts b/packages/web-component-designer/src/elements/widgets/treeView/treeView.ts index b6df575f8..36a18eab3 100644 --- a/packages/web-component-designer/src/elements/widgets/treeView/treeView.ts +++ b/packages/web-component-designer/src/elements/widgets/treeView/treeView.ts @@ -32,7 +32,7 @@ export class TreeView extends BaseCustomWebComponentLazyAppend implements ITreeV position: relative; width: 100%; height: 100%; - background: var(--dark-grey, #232733); + background: var(--wcd-tree-view-background, var(--dark-grey, var(--wcd-color-surface, #232733))); overflow-y: auto; } button { @@ -45,22 +45,22 @@ export class TreeView extends BaseCustomWebComponentLazyAppend implements ITreeV text-align: left; display: inline-block; margin: 0; - background: var(--dark-grey, #232733); + background: var(--wcd-tree-view-background, var(--dark-grey, var(--wcd-color-surface, #232733))); position: relative; - color: white; + color: var(--wcd-color-text, white); } button:hover, button:focus { - background: var(--light-grey, #383f52); + background: var(--wcd-tree-view-item-hover-background, var(--light-grey, var(--wcd-color-surface-hover, #383f52))); } span { margin: 4px; } .id { font-style: italic; - color: var(--highlight-pink, #e91e63); + color: var(--wcd-tree-view-id-color, var(--highlight-pink, var(--wcd-color-accent, #e91e63))); } .selected { - background: var(--light-grey, #383f52); + background: var(--wcd-tree-view-item-selected-background, var(--light-grey, var(--wcd-color-surface-hover, #383f52))); outline: none; } @@ -86,8 +86,8 @@ export class TreeView extends BaseCustomWebComponentLazyAppend implements ITreeV left: calc(var(--horz-shift) - var(--horz-margin)); width: calc(var(--horz-margin) - var(--horz-shift)); height: calc(var(--vert-shift) + var(--vert-margin)); - border-left: 1px solid #ccc; - border-bottom: 1px solid #ccc; + border-left: 1px solid var(--wcd-tree-view-connector-color, #ccc); + border-bottom: 1px solid var(--wcd-tree-view-connector-color, #ccc); border-radius: 0; } @@ -99,8 +99,8 @@ export class TreeView extends BaseCustomWebComponentLazyAppend implements ITreeV left: calc(var(--horz-shift) - var(--horz-margin)); width: calc(var(--horz-margin) - var(--horz-shift)); height: calc(100% - var(--vert-shift)); - border-left: 1px solid #ccc; - border-top: 1px solid #ccc; + border-left: 1px solid var(--wcd-tree-view-connector-color, #ccc); + border-top: 1px solid var(--wcd-tree-view-connector-color, #ccc); border-radius: 0; } From 2549374a066c976383ede50a48ce7c696384556f Mon Sep 17 00:00:00 2001 From: Luca Schirmbrand Date: Mon, 10 Aug 2026 13:42:36 +0200 Subject: [PATCH 02/10] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../src/elements/widgets/designerView/overlayLayerView.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web-component-designer/src/elements/widgets/designerView/overlayLayerView.ts b/packages/web-component-designer/src/elements/widgets/designerView/overlayLayerView.ts index 16715e000..51c10c095 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/overlayLayerView.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/overlayLayerView.ts @@ -23,7 +23,7 @@ export class OverlayLayerView extends BaseCustomWebComponentConstructorAppend { .svg-invisible { stroke: transparent; fill: transparent; pointer-events: auto; } .svg-snapline { stroke: var(--wcd-color-snapline, purple); stroke-dasharray: 4; fill: transparent; } .svg-selector { stroke: black; fill: var(--wcd-color-selection-fill, #3899ec55); stroke-width: 1; stroke-dasharray: 2; } - .svg-primary-selection-move { stroke: var(--wcd-color-selection, #3899ec); fill: #3899ec; cursor: move; pointer-events: auto; } + .svg-primary-selection-move { stroke: var(--wcd-color-selection, #3899ec); fill: var(--wcd-color-selection, #3899ec); cursor: move; pointer-events: auto; } .svg-position { stroke: black; stroke-dasharray: 2; } .svg-path { stroke: var(--wcd-color-selection, #3899ec); fill: orange; pointer-events: auto; } .svg-path-line { stroke: var(--wcd-color-selection, #3899ec); stroke-dasharray: 2; } From 198086bec67eb37b55dd9d32188fac89ac6d5d9c Mon Sep 17 00:00:00 2001 From: Luca Schirmbrand Date: Mon, 10 Aug 2026 13:42:49 +0200 Subject: [PATCH 03/10] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../extensions/transforms/RotateGroupExtensionProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/RotateGroupExtensionProvider.ts b/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/RotateGroupExtensionProvider.ts index 171d7accc..c59767579 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/RotateGroupExtensionProvider.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/RotateGroupExtensionProvider.ts @@ -21,7 +21,7 @@ export class RotateGroupExtensionProvider implements IDesignerExtensionProvider static readonly style = css` .svg-primary-rotate { stroke: var(--wcd-color-selection, #3899ec); fill: white; stroke-width: 1; pointer-events: auto; cursor: alias; } - .svg-primary-rotate-line { stroke: var(--wcd-color-selection, #3899ec); fill: #3899ec; stroke-width: 1; } + .svg-primary-rotate-line { stroke: var(--wcd-color-selection, #3899ec); fill: var(--wcd-color-selection, #3899ec); stroke-width: 1; } .svg-rotate-group-rect { stroke: var(--wcd-color-selection, #3899ec); fill: transparent; stroke-width: 2; stroke-dasharray: 5 3; } `; } From 48959654aad169e3e54e658f34a4faabadd2d055 Mon Sep 17 00:00:00 2001 From: Luca Schirmbrand Date: Mon, 10 Aug 2026 13:44:09 +0200 Subject: [PATCH 04/10] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../extensions/transforms/RotateExtensionProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/RotateExtensionProvider.ts b/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/RotateExtensionProvider.ts index 6127ba7fe..547d3d16b 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/RotateExtensionProvider.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/extensions/transforms/RotateExtensionProvider.ts @@ -29,6 +29,6 @@ export class RotateExtensionProvider implements IDesignerExtensionProvider { static readonly style = css` .svg-primary-rotate { stroke: var(--wcd-color-selection, #3899ec); fill: white; stroke-width: 1; pointer-events: auto; cursor: alias; } .svg-primary-rotate-transparent { fill: transparent; pointer-events: auto; cursor: alias; } - .svg-primary-rotate-line { stroke: var(--wcd-color-selection, #3899ec); fill: #3899ec; stroke-width: 1; } + .svg-primary-rotate-line { stroke: var(--wcd-color-selection, #3899ec); fill: var(--wcd-color-selection, #3899ec); stroke-width: 1; } `; } \ No newline at end of file From 8e7a93d3c12bd9480549fa9be2692fd5222af0dc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 10 Aug 2026 11:45:00 +0000 Subject: [PATCH 05/10] fix: add legacy --plain-scrollbar-* fallback for track background and border color tokens Co-authored-by: lschirmbrand <61965786+lschirmbrand@users.noreply.github.com> --- .../src/elements/controls/PlainScrollbar.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/web-component-designer/src/elements/controls/PlainScrollbar.ts b/packages/web-component-designer/src/elements/controls/PlainScrollbar.ts index 28ed12809..66dc2afe4 100644 --- a/packages/web-component-designer/src/elements/controls/PlainScrollbar.ts +++ b/packages/web-component-designer/src/elements/controls/PlainScrollbar.ts @@ -408,10 +408,10 @@ const scrollbarStyle = css` :host { display: block; contain: content; - background-color: var(--wcd-plain-scrollbar-track-background-color, #f8f8f8); + background-color: var(--wcd-plain-scrollbar-track-background-color, var(--plain-scrollbar-track-background-color, #f8f8f8)); border-style: solid; border-width: 1px; - border-color: var(--wcd-plain-scrollbar-track-border-color, #dddddd); + border-color: var(--wcd-plain-scrollbar-track-border-color, var(--plain-scrollbar-track-border-color, #dddddd)); } #root { touch-action: none; From 64d2a43ab237bdd72bd6f7866d83f73938eb824b Mon Sep 17 00:00:00 2001 From: Luca Schirmbrand Date: Mon, 10 Aug 2026 13:47:12 +0200 Subject: [PATCH 06/10] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../extensions/grid/DisplayGridExtensionProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/web-component-designer/src/elements/widgets/designerView/extensions/grid/DisplayGridExtensionProvider.ts b/packages/web-component-designer/src/elements/widgets/designerView/extensions/grid/DisplayGridExtensionProvider.ts index 56be08ba9..375cfcfff 100644 --- a/packages/web-component-designer/src/elements/widgets/designerView/extensions/grid/DisplayGridExtensionProvider.ts +++ b/packages/web-component-designer/src/elements/widgets/designerView/extensions/grid/DisplayGridExtensionProvider.ts @@ -34,7 +34,7 @@ export class DisplayGridExtensionProvider implements IDesignerExtensionProvider } static readonly style = css` - .svg-grid { stroke: var(--wcd-grid-stroke-color, var(--svg-grid-stroke-color)); stroke-dasharray: 5; fill: var(--wcd-grid-fill-color, var(--wcd-grid-fill-color, var(--svg-grid-fill-color))); } + .svg-grid { stroke: var(--wcd-grid-stroke-color, var(--svg-grid-stroke-color)); stroke-dasharray: 5; fill: var(--wcd-grid-fill-color, var(--svg-grid-fill-color)); } .svg-grid-current-cell { stroke: var(--wcd-grid-stroke-color, var(--svg-grid-stroke-color)); stroke-dasharray: 5; fill: #e3ff4722; } .svg-grid-area { font-size: 8px; } .svg-grid-gap { stroke: transparent; fill: var(--wcd-grid-stroke-color, var(--svg-grid-stroke-color)); opacity: 0.3; mask: url(#mask-stripe-grid) } From 4926c5ea7f3f9772a420e73c89cd63ae8e10bcc5 Mon Sep 17 00:00:00 2001 From: lschirmbrand Date: Mon, 10 Aug 2026 16:04:47 +0200 Subject: [PATCH 07/10] zpl: add measured font metrics and layout helpers for ZPL text/graphics Introduces zplFontMetrics.ts (cap height/advance/scale for font 0 and the bitmap fonts A-H, ^FB line wrapping and justification, ^GB/^GD/^GE size clamping) and zplFontMeasure.ts (canvas-based measurement of the CSS fonts used to approximate them), derived from measuring api.labelary.com output at 8dpmm. Exported from the package entry point and covered by a new unit test suite. Co-Authored-By: Claude Sonnet 5 --- .../web-component-designer-zpl/src/index.ts | 3 + .../src/zplFontMeasure.ts | 103 +++++++ .../src/zplFontMetrics.ts | 262 ++++++++++++++++++ .../tests/zplFontMetrics.test.ts | 229 +++++++++++++++ 4 files changed, 597 insertions(+) create mode 100644 packages/web-component-designer-zpl/src/zplFontMeasure.ts create mode 100644 packages/web-component-designer-zpl/src/zplFontMetrics.ts create mode 100644 packages/web-component-designer-zpl/tests/zplFontMetrics.test.ts diff --git a/packages/web-component-designer-zpl/src/index.ts b/packages/web-component-designer-zpl/src/index.ts index 1418371b2..e679ee6f2 100644 --- a/packages/web-component-designer-zpl/src/index.ts +++ b/packages/web-component-designer-zpl/src/index.ts @@ -14,6 +14,9 @@ export * from "./services/ZplLayoutCopyPasteService.js"; export * from "./services/ZplLayoutPlacementService.js"; export * from "./services/ZplParserService.js"; +export * from "./zplFontMetrics.js"; +export * from "./zplFontMeasure.js"; + export * from "./jsBarcodeOptions.js"; export * from "./qr.js"; export * from "./setupZplServiceContainer.js"; diff --git a/packages/web-component-designer-zpl/src/zplFontMeasure.ts b/packages/web-component-designer-zpl/src/zplFontMeasure.ts new file mode 100644 index 000000000..7e29d507a --- /dev/null +++ b/packages/web-component-designer-zpl/src/zplFontMeasure.ts @@ -0,0 +1,103 @@ +/** + * Browser-side font measurement used to map ZPL font metrics onto CSS text. + * + * Kept separate from zplFontMetrics.ts so that the metric rules themselves stay + * pure and unit testable; everything here needs a DOM. + */ + +/** + * Stand-in for ZPL font 0. The printer font is CG Triumvirate Bold Condensed, + * which we cannot ship, so this is a stack of metric-similar condensed bold + * faces. Glyph shapes will differ slightly from a real printer; the cap height, + * origin and scaling behaviour are exact regardless of which face resolves. + */ +export const ZPL_FONT_0_FAMILY = "'Arial Narrow', 'Liberation Sans Narrow', 'Roboto Condensed', 'Helvetica Neue', sans-serif"; + +/** + * Stand-in for the fixed pitch bitmap font A. Which face resolves does not + * affect layout: the advance is pinned exactly with letter-spacing, so this + * only decides glyph shape. + */ +export const ZPL_FONT_A_FAMILY = "'Courier New', Menlo, Consolas, 'DejaVu Sans Mono', 'Liberation Mono', monospace"; + +export interface IFontRatios { + /** Cap height as a fraction of font size. */ + capRatio: number; + /** Font ascent as a fraction of font size. */ + ascentRatio: number; + /** Font descent as a fraction of font size. */ + descentRatio: number; + /** Advance of a representative glyph as a fraction of font size. */ + advanceRatio: number; +} + +const REFERENCE_SIZE = 100; +const cache = new Map(); +let context: CanvasRenderingContext2D | null = null; + +function getContext(): CanvasRenderingContext2D | null { + if (context === null) { + context = document.createElement('canvas').getContext('2d'); + } + return context; +} + +/** + * Measure a font once at a reference size. Every value scales linearly with + * font size, so callers multiply by their own size. + */ +export function measureFontRatios(fontFamily: string, fontWeight: string): IFontRatios { + const key = fontWeight + ' ' + fontFamily; + const cached = cache.get(key); + if (cached) + return cached; + + // Sensible fallbacks if canvas or the extended TextMetrics are unavailable. + let ratios: IFontRatios = { capRatio: 0.72, ascentRatio: 0.9, descentRatio: 0.21, advanceRatio: 0.5 }; + + const ctx = getContext(); + if (ctx) { + ctx.font = fontWeight + ' ' + REFERENCE_SIZE + 'px ' + fontFamily; + const caps = ctx.measureText('H'); + const full = ctx.measureText('Hg'); + const capHeight = caps.actualBoundingBoxAscent; + const ascent = full.fontBoundingBoxAscent ?? caps.actualBoundingBoxAscent; + const descent = full.fontBoundingBoxDescent ?? 0; + if (capHeight > 0) { + ratios = { + capRatio: capHeight / REFERENCE_SIZE, + ascentRatio: ascent / REFERENCE_SIZE, + descentRatio: descent / REFERENCE_SIZE, + advanceRatio: caps.width / REFERENCE_SIZE, + }; + } + } + + cache.set(key, ratios); + return ratios; +} + +/** + * Advance width of a string at a given size, in px. Used for the proportional + * font 0, whose extent cannot be derived from the ZPL metrics alone. + */ +export function measureTextWidth(text: string, fontSize: number, fontFamily: string, fontWeight: string): number { + if (!text) return 0; + const ctx = getContext(); + if (!ctx) return 0; + ctx.font = fontWeight + ' ' + fontSize + 'px ' + fontFamily; + return ctx.measureText(text).width; +} + +/** + * Distance from the top of a `line-height: 1` line box down to the top of the + * capital letters, in px. The text has to be shifted up by this much so the + * caps land on the ZPL field origin. + */ +export function capTopOffset(fontSize: number, ratios: IFontRatios): number { + const ascent = ratios.ascentRatio * fontSize; + const descent = ratios.descentRatio * fontSize; + const capHeight = ratios.capRatio * fontSize; + const halfLeading = (fontSize - (ascent + descent)) / 2; + return halfLeading + ascent - capHeight; +} diff --git a/packages/web-component-designer-zpl/src/zplFontMetrics.ts b/packages/web-component-designer-zpl/src/zplFontMetrics.ts new file mode 100644 index 000000000..7a21aa2b4 --- /dev/null +++ b/packages/web-component-designer-zpl/src/zplFontMetrics.ts @@ -0,0 +1,262 @@ +/** + * Font and layout metrics for ZPL, derived by measuring the rendered output of + * api.labelary.com at 8dpmm and reading back the pixel bounding boxes. + * + * All values are in dots. The designer renders 1 dot = 1 CSS px, so these are + * also the px values the widgets must produce to match a printed label. + * + * ZPL has two kinds of font: + * + * - font 0 is scalable and proportional. Cap height is 0.75 * h, and the width + * parameter scales it horizontally (w = 0 means w = h). Per-glyph widths + * come from the printer's font and cannot be reproduced exactly in CSS. + * + * - fonts A..H are bitmaps drawn at whole multiples of a base cell, so their + * layout is fully predictable. Measured at the base size (^CF,1): + * + * font cap advance font cap advance + * A 7 6 E 20 20 + * B 11 9 F 21 16 + * C 14 12 G 48 48 + * D 14 12 H 21 19 + * + * The scale is *rounded*, not truncated. Measuring font A over h = 6..30 the + * cap height steps 7 -> 14 at h = 14 and 14 -> 21 at h = 23, which is + * round(h / 9); floor(h / 9) would step at 18 and 27 and mismatches 8 of the 25 + * samples. So ^AAN,20 and ^AAN,17 both print at n = 2. + * + * The key origin rule for every font: the measured dy is 0 at all sizes, so the + * ^FO y coordinate is the top of the capital letters, not the top of the line + * box and not the baseline. + */ + +/** Cap height of the scalable font 0 as a fraction of the declared height. */ +export const FONT_0_CAP_RATIO = 0.75; + +export interface IZplBitmapFont { + /** Base cell width in dots; the width parameter is resolved against this. */ + cellWidth: number; + /** Base cell height in dots; the height parameter is resolved against this. */ + cellHeight: number; + /** Height of a capital letter at scale 1. */ + capHeight: number; + /** Pen advance from one glyph origin to the next at scale 1. */ + advance: number; +} + +/** + * The bitmap fonts. cellWidth/cellHeight are Zebra's documented base cell; + * capHeight and advance are measured. C and D share a cell, as do their metrics. + */ +export const ZPL_BITMAP_FONTS: Readonly> = { + A: { cellWidth: 5, cellHeight: 9, capHeight: 7, advance: 6 }, + B: { cellWidth: 7, cellHeight: 11, capHeight: 11, advance: 9 }, + C: { cellWidth: 10, cellHeight: 18, capHeight: 14, advance: 12 }, + D: { cellWidth: 10, cellHeight: 18, capHeight: 14, advance: 12 }, + E: { cellWidth: 15, cellHeight: 28, capHeight: 20, advance: 20 }, + F: { cellWidth: 13, cellHeight: 26, capHeight: 21, advance: 16 }, + G: { cellWidth: 40, cellHeight: 60, capHeight: 48, advance: 48 }, + H: { cellWidth: 13, cellHeight: 21, capHeight: 21, advance: 19 }, +}; + +export type ZplFontName = '0' | 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H'; + +export interface IZplTextMetrics { + /** Height of a capital letter, in dots. ^FO y is the top of this. */ + capHeight: number; + /** Full character cell height, in dots. */ + cellHeight: number; + /** Horizontal scale to apply relative to the font's natural proportions. */ + scaleX: number; + /** + * Pen advance per character, in dots. Exact for the bitmap fonts; null for + * font 0, which is proportional and has to be measured from rendered text. + */ + advance: number | null; +} + +export function isBitmapFont(fontName: string): boolean { + return !!fontName && Object.prototype.hasOwnProperty.call(ZPL_BITMAP_FONTS, fontName); +} + +/** + * Resolve a requested dot size onto a bitmap font's cell grid. The printer + * rounds to the nearest whole cell and never drops below one. + */ +export function quantizeScale(value: number, cell: number): number { + if (!(value > 0) || !(cell > 0)) return 1; + return Math.max(1, Math.round(value / cell)); +} + +/** + * Resolve the declared width of a ^A / ^CF command. A width of 0 (or omitted) + * means "derive it from the height", which for font 0 yields the font's natural + * aspect and for a bitmap font yields the same scale as the height. + */ +export function resolveFontWidth(height: number, width: number): number { + return width > 0 ? width : height; +} + +/** + * Layout metrics for a ZPL text field. + * + * @param fontName '0' for the scalable font, 'A'..'H' for the bitmap fonts. + * @param height the h parameter of ^A / ^CF, in dots. + * @param width the w parameter of ^A / ^CF, in dots. 0 means "from height". + */ +export function getZplTextMetrics(fontName: ZplFontName, height: number, width: number): IZplTextMetrics { + const h = height > 0 ? height : 0; + + const bitmap = ZPL_BITMAP_FONTS[fontName]; + if (bitmap) { + const n = quantizeScale(h, bitmap.cellHeight); + const nx = width > 0 ? quantizeScale(width, bitmap.cellWidth) : n; + return { + capHeight: bitmap.capHeight * n, + cellHeight: bitmap.cellHeight * n, + scaleX: nx / n, + advance: bitmap.advance * nx, + }; + } + + const w = resolveFontWidth(h, width); + return { + capHeight: h * FONT_0_CAP_RATIO, + cellHeight: h, + // ^A0 scales the glyph cell to w wide; w == h is the natural aspect. + scaleX: h > 0 ? w / h : 1, + advance: null, + }; +} + +/** + * Width of a text field in dots, measured as the pen advance across the string + * (the field's layout extent, which is what the next field must clear). + * + * Exact for the bitmap fonts, which are fixed pitch. Null for font 0, whose + * glyphs are proportional, so its extent has to be measured from rendered text. + */ +export function predictTextWidth(fontName: ZplFontName, height: number, width: number, text: string): number | null { + const m = getZplTextMetrics(fontName, height, width); + if (m.advance == null) return null; + if (!text) return 0; + return text.length * m.advance; +} + +/** Justification of a ^FB field block. Anything else behaves as left. */ +export type ZplJustification = 'L' | 'C' | 'R' | 'J'; + +/** The escape that forces a line break inside ^FD when ^FB is active. */ +export const ZPL_LINE_BREAK = '\\&'; + +/** + * Break field data into the lines a ^FB block would print. + * + * Measured behaviour: wrapping happens at spaces only, greedily; a single word + * wider than the block is truncated rather than broken; and the result is cut + * to maxLines. `\&` forces a break. + * + * @param measure width of a string in dots, so this works for both the fixed + * pitch bitmap fonts and the proportional font 0. + */ +export function wrapZplLines( + text: string, + blockWidth: number, + maxLines: number, + measure: (s: string) => number, +): string[] { + const limit = maxLines > 0 ? maxLines : 1; + if (!text) return ['']; + if (!(blockWidth > 0)) return text.split(ZPL_LINE_BREAK).slice(0, limit); + + const lines: string[] = []; + for (const paragraph of text.split(ZPL_LINE_BREAK)) { + if (lines.length >= limit) break; + const words = paragraph.split(' '); + let line = ''; + for (const word of words) { + const candidate = line ? line + ' ' + word : word; + if (measure(candidate) <= blockWidth) { + line = candidate; + continue; + } + if (line) { + lines.push(line); + if (lines.length >= limit) { line = ''; break; } + } + if (measure(word) <= blockWidth) { + line = word; + continue; + } + // A word wider than the block is not broken: keep what fits. + let cut = word; + while (cut.length > 1 && measure(cut) > blockWidth) cut = cut.slice(0, -1); + lines.push(cut); + line = ''; + if (lines.length >= limit) break; + } + if (line && lines.length < limit) lines.push(line); + } + return lines.length ? lines.slice(0, limit) : ['']; +} + +/** + * Left offset of a line inside a ^FB block, in dots. + * + * Measured at ^CFB,25 (advance 18) in a 300 dot block: + * R: len 1,2,4 -> 282, 264, 228 == blockWidth - len * advance + * C: len 1,2,4 -> 132, 123, 105 == (blockWidth - (len + 1) * advance) / 2 + * so centring behaves as though one further character cell were present. + * For the proportional font 0 there is no cell, so it centres on width alone. + */ +export function justifyOffset( + justification: string, + blockWidth: number, + lineWidth: number, + advance: number | null, +): number { + if (!(blockWidth > 0)) return 0; + switch (justification) { + case 'R': + return Math.max(0, blockWidth - lineWidth); + case 'C': + return Math.max(0, (blockWidth - lineWidth - (advance ?? 0)) / 2); + case 'J': + // Justified text starts flush at the block's left edge, same as L; + // the fill to the right margin is done by stretching inter-word + // gaps (see justifyWordSpacing), not by an offset. + return 0; + default: + return 0; + } +} + +/** + * Extra inter-word spacing, in dots, needed to stretch a line so it fills a + * justified (^FB ...,J,...) block. 0 for anything that doesn't stretch: a + * line with no word gaps, or one already at or past the block width. + * + * ZPL's own J behaviour isn't independently measured against a printed label + * like the rest of this file (Zebra doesn't document the exact algorithm); + * this follows the conventional typesetting rule of spreading the shortfall + * evenly across the gaps, same as every other renderer's "justify". + */ +export function justifyWordSpacing(lineWidth: number, blockWidth: number, gapCount: number): number { + if (gapCount <= 0 || !(blockWidth > lineWidth)) return 0; + return (blockWidth - lineWidth) / gapCount; +} + +/** Baseline to baseline distance of a ^FB block: the cell height plus c. */ +export function blockLineStep(cellHeight: number, lineSpacing: number): number { + return cellHeight + (lineSpacing || 0); +} + +/** + * ^GB / ^GE / ^GD all treat ^FO as the outer top-left and draw the border + * inward, so the painted extent is exactly w x h. ZPL also clamps the box up to + * at least the border thickness: ^GB10,10,20 prints 20x20. + */ +export function clampGraphicSize(size: number, thickness: number): number { + const t = thickness > 0 ? thickness : 1; + return Math.max(size > 0 ? size : 0, t); +} diff --git a/packages/web-component-designer-zpl/tests/zplFontMetrics.test.ts b/packages/web-component-designer-zpl/tests/zplFontMetrics.test.ts new file mode 100644 index 000000000..9fe3486e6 --- /dev/null +++ b/packages/web-component-designer-zpl/tests/zplFontMetrics.test.ts @@ -0,0 +1,229 @@ +import { describe, expect, it } from '@jest/globals'; +import { + ZPL_BITMAP_FONTS, + blockLineStep, + justifyOffset, + justifyWordSpacing, + wrapZplLines, + clampGraphicSize, + getZplTextMetrics, + isBitmapFont, + predictTextWidth, + quantizeScale, + resolveFontWidth, +} from '../src/zplFontMetrics'; + +// Every expectation here is a value read back from a label rendered by +// api.labelary.com at 8dpmm. See the header of zplFontMetrics.ts. + +describe('font 0 (scalable)', () => { + it('has a cap height of 0.75 * h', () => { + expect(getZplTextMetrics('0', 20, 20).capHeight).toBe(15); + expect(getZplTextMetrics('0', 40, 40).capHeight).toBe(30); + expect(getZplTextMetrics('0', 80, 80).capHeight).toBe(60); + }); + + it('treats a width of 0 as "same as height"', () => { + expect(resolveFontWidth(80, 0)).toBe(80); + expect(getZplTextMetrics('0', 80, 0).scaleX).toBe(1); + }); + + it('scales horizontally by w/h', () => { + // ^A0N,40,20 measured exactly half the width of ^A0N,40,40 + expect(getZplTextMetrics('0', 40, 20).scaleX).toBe(0.5); + }); + + it('cannot predict a width, being proportional', () => { + expect(predictTextWidth('0', 40, 40, 'ABC')).toBeNull(); + }); +}); + +describe('bitmap scale quantisation', () => { + // Measured over ^CFA,h for h = 6..30: cap steps 7 -> 14 at h = 14 and + // 14 -> 21 at h = 23. That is round(h/9); floor(h/9) would step at 18/27. + it('rounds to the nearest whole cell', () => { + expect(quantizeScale(13, 9)).toBe(1); + expect(quantizeScale(14, 9)).toBe(2); + expect(quantizeScale(17, 9)).toBe(2); + expect(quantizeScale(22, 9)).toBe(2); + expect(quantizeScale(23, 9)).toBe(3); + }); + + it('never collapses below one cell', () => { + expect(quantizeScale(0, 9)).toBe(1); + expect(quantizeScale(4, 9)).toBe(1); + }); +}); + +describe('font A (bitmap)', () => { + it('has cap height 7n and cell height 9n', () => { + for (const n of [1, 2, 3, 4]) { + const m = getZplTextMetrics('A', n * 9, 0); + expect(m.capHeight).toBe(7 * n); + expect(m.cellHeight).toBe(9 * n); + } + }); + + it('has an advance of exactly 6n', () => { + // derived from measuring "H"/"HH"/"HHH" at n = 1..4 + for (const n of [1, 2, 3, 4]) + expect(getZplTextMetrics('A', n * 9, 0).advance).toBe(6 * n); + }); + + it('resolves the width parameter against the 5-dot cell', () => { + // measured at h=18: w=5 -> n=1, w=10 -> n=2, w=15 -> n=3, w=20 -> n=4 + expect(getZplTextMetrics('A', 18, 5).advance).toBe(6); + expect(getZplTextMetrics('A', 18, 10).advance).toBe(12); + expect(getZplTextMetrics('A', 18, 15).advance).toBe(18); + expect(getZplTextMetrics('A', 18, 20).advance).toBe(24); + expect(getZplTextMetrics('A', 18, 0).advance).toBe(12); + }); +}); + +describe('font B (bitmap)', () => { + // ^CFB,25 is what a real shipping label uses. + it('quantises 25 dots to two 11-dot cells', () => { + const m = getZplTextMetrics('B', 25, 0); + expect(m.capHeight).toBe(22); + expect(m.cellHeight).toBe(22); + expect(m.advance).toBe(18); + }); + + it('predicts field widths exactly', () => { + // verified against Labelary: the pen advance across each of these + // strings at ^CFB,25 is len * 18 + expect(predictTextWidth('B', 25, 0, 'FROM:')).toBe(90); + expect(predictTextWidth('B', 25, 0, 'TO:')).toBe(54); + expect(predictTextWidth('B', 25, 0, 'Test sender')).toBe(198); + expect(predictTextWidth('B', 25, 0, 'TN, COLLIERVILLE, 38017')).toBe(414); + expect(predictTextWidth('B', 25, 0, 'Accounts Payable Dept.')).toBe(396); + }); + + it('scales linearly with n', () => { + for (const n of [1, 2, 3, 4]) { + const m = getZplTextMetrics('B', n * 11, 0); + expect(m.capHeight).toBe(11 * n); + expect(m.advance).toBe(9 * n); + } + }); +}); + +describe('the bitmap font table', () => { + it('recognises every built in bitmap font', () => { + for (const f of ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']) + expect(isBitmapFont(f)).toBe(true); + expect(isBitmapFont('0')).toBe(false); + expect(isBitmapFont('')).toBe(false); + }); + + it('matches the measured base metrics', () => { + // cap height and advance measured at ^CF,1 + const measured: Record = { + A: [7, 6], B: [11, 9], C: [14, 12], D: [14, 12], + E: [20, 20], F: [21, 16], G: [48, 48], H: [21, 19], + }; + for (const [font, [cap, advance]] of Object.entries(measured)) { + expect(ZPL_BITMAP_FONTS[font].capHeight).toBe(cap); + expect(ZPL_BITMAP_FONTS[font].advance).toBe(advance); + } + }); + + it('predicts a width for every bitmap font', () => { + for (const f of ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H']) + expect(predictTextWidth(f, 30, 0, 'AB')).not.toBeNull(); + }); +}); + +describe('graphic primitives', () => { + it('clamps the box up to the border thickness', () => { + // measured: ^GB10,10,20 prints 20x20 + expect(clampGraphicSize(10, 20)).toBe(20); + expect(clampGraphicSize(200, 4)).toBe(200); + }); + + it('keeps a zero dimension as a line of the border thickness', () => { + // ^GB0,100,2 is a 2 x 100 vertical line + expect(clampGraphicSize(0, 2)).toBe(2); + expect(clampGraphicSize(0, 0)).toBe(1); + }); +}); + +describe('^FB field blocks', () => { + // font B at ^CFB,25: advance 18, cell height 22 + const ADV = 18; + const measure = (s: string) => s.length * ADV; + + it('steps lines by the cell height plus c', () => { + // measured: c=0 -> 22, c=10 -> 32, c=-4 -> 18 + expect(blockLineStep(22, 0)).toBe(22); + expect(blockLineStep(22, 10)).toBe(32); + expect(blockLineStep(22, -4)).toBe(18); + }); + + it('right aligns on the pen advance', () => { + // measured in a 300 dot block: len 1,2,4 -> 282, 264, 228 + expect(justifyOffset('R', 300, 1 * ADV, ADV)).toBe(282); + expect(justifyOffset('R', 300, 2 * ADV, ADV)).toBe(264); + expect(justifyOffset('R', 300, 4 * ADV, ADV)).toBe(228); + }); + + it('centres as though one further cell were present', () => { + // measured in a 300 dot block: len 1,2,4,8 -> 132, 123, 105, 69 + expect(justifyOffset('C', 300, 1 * ADV, ADV)).toBe(132); + expect(justifyOffset('C', 300, 2 * ADV, ADV)).toBe(123); + expect(justifyOffset('C', 300, 4 * ADV, ADV)).toBe(105); + expect(justifyOffset('C', 300, 8 * ADV, ADV)).toBe(69); + }); + + it('leaves left aligned text at the block origin', () => { + expect(justifyOffset('L', 300, 2 * ADV, ADV)).toBe(0); + // the label uses N, which is not a documented value; treat as left + expect(justifyOffset('N', 300, 2 * ADV, ADV)).toBe(0); + }); + + it('wraps greedily at spaces', () => { + // measured: ^FB200 fits "AAA BBB CCC" (11 chars = 198) then wraps + expect(wrapZplLines('AAA BBB CCC DDD', 200, 5, measure)) + .toEqual(['AAA BBB CCC', 'DDD']); + }); + + it('truncates to the maximum number of lines', () => { + expect(wrapZplLines('AA\\&BB\\&CC', 300, 2, measure)).toEqual(['AA', 'BB']); + expect(wrapZplLines('AA\\&BB\\&CC', 300, 3, measure)).toEqual(['AA', 'BB', 'CC']); + }); + + it('breaks on the \\& escape', () => { + expect(wrapZplLines('PRODUCT A x 1, PRODUCT B x 4\\&', 692, 5, measure)) + .toEqual(['PRODUCT A x 1, PRODUCT B x 4']); + }); + + it('truncates a word wider than the block rather than breaking it', () => { + // measured: ^FB100 with a 14 character word prints only what fits + expect(wrapZplLines('AAAAAAAAAAAAAA', 100, 5, measure)).toEqual(['AAAAA']); + }); + + it('centres the box counter line from the real label', () => { + // ^FO60,1000^FB692,1,0,C,0^FDBox 1 of 1^FS + const lines = wrapZplLines('Box 1 of 1', 692, 1, measure); + expect(lines).toEqual(['Box 1 of 1']); + expect(justifyOffset('C', 692, measure(lines[0]), ADV)).toBe(247); + }); + + it('starts justified lines flush at the block origin, like L', () => { + expect(justifyOffset('J', 300, 2 * ADV, ADV)).toBe(0); + }); + + it('spreads the shortfall evenly across a justified line\'s word gaps', () => { + // "AAA BBB CCC" is 11 chars * ADV = 198 wide with 2 gaps, in a 300 block + expect(justifyWordSpacing(11 * ADV, 300, 2)).toBe((300 - 11 * ADV) / 2); + }); + + it('does not stretch a justified line with no word gaps', () => { + expect(justifyWordSpacing(3 * ADV, 300, 0)).toBe(0); + }); + + it('does not stretch a justified line already at or past the block width', () => { + expect(justifyWordSpacing(300, 300, 2)).toBe(0); + expect(justifyWordSpacing(320, 300, 2)).toBe(0); + }); +}); From 6c376a60546100511571230a9782d640c4734317 Mon Sep 17 00:00:00 2001 From: lschirmbrand Date: Mon, 10 Aug 2026 16:05:00 +0200 Subject: [PATCH 08/10] zpl: rewrite zpl-text layout to use measured font metrics and support ^FB blocks Replaces the hardcoded font-size/scale guesses with the metrics from zplFontMetrics/zplFontMeasure, so glyph cap height, advance and cap-top offset match a printed label instead of an approximation. Adds ^FB field block support: wrapping, max lines, line spacing, hanging indent and L/C/R/J justification (J stretches inter-word spacing on every wrapped line but the last, which prints ragged). A width of 0 for ^A/^CF now means "derive from the height" throughout, rather than an explicit square scale, so the default element template's font-width is changed to match. Co-Authored-By: Claude Sonnet 5 --- .../src/widgets/elements.json | 2 +- .../src/widgets/zpl-text.ts | 163 +++++++++++++++--- 2 files changed, 141 insertions(+), 24 deletions(-) diff --git a/packages/web-component-designer-zpl/src/widgets/elements.json b/packages/web-component-designer-zpl/src/widgets/elements.json index b34319b17..781e956d7 100644 --- a/packages/web-component-designer-zpl/src/widgets/elements.json +++ b/packages/web-component-designer-zpl/src/widgets/elements.json @@ -67,7 +67,7 @@ "content": "Text...", "font-name": "0", "font-height": "30", - "font-width": "30" + "font-width": "0" } }, { diff --git a/packages/web-component-designer-zpl/src/widgets/zpl-text.ts b/packages/web-component-designer-zpl/src/widgets/zpl-text.ts index 56382bdbb..d90af1067 100644 --- a/packages/web-component-designer-zpl/src/widgets/zpl-text.ts +++ b/packages/web-component-designer-zpl/src/widgets/zpl-text.ts @@ -1,9 +1,25 @@ import { BaseCustomWebComponentConstructorAppend, css, html } from "@node-projects/base-custom-webcomponent"; import { getZplCoordinates } from "../zplHelper.js"; +import { ZplFontName, blockLineStep, getZplTextMetrics, isBitmapFont, justifyOffset, justifyWordSpacing, predictTextWidth, wrapZplLines } from "../zplFontMetrics.js"; +import { ZPL_FONT_0_FAMILY, ZPL_FONT_A_FAMILY, capTopOffset, measureFontRatios, measureTextWidth } from "../zplFontMeasure.js"; + +enum Justifications { + Left = "L", + Center = "C", + Right = "R", + Justified = "J", +} enum FontNames { Font_0 = "0", Font_A = "A", + Font_B = "B", + Font_C = "C", + Font_D = "D", + Font_E = "E", + Font_F = "F", + Font_G = "G", + Font_H = "H", } export class ZplText extends BaseCustomWebComponentConstructorAppend { @@ -24,7 +40,23 @@ export class ZplText extends BaseCustomWebComponentConstructorAppend { public content: string; public fontName: string; public fontHeight: number; - public fontWidth: number; + /** + * The w parameter of ^A / ^CF. 0 means "derive from the height", which + * keeps font 0 at its natural aspect and a bitmap font on a square scale. + * That is the sensible default; an explicit width only ever distorts. + */ + public fontWidth: number = 0; + + /** ^FB block width in dots. 0 means the field is not a block. */ + public blockWidth: number = 0; + /** ^FB maximum number of lines. */ + public maxLines: number = 1; + /** ^FB dots added between lines; may be negative. */ + public lineSpacing: number = 0; + /** ^FB justification: L, C, R or J. */ + public justification: string = 'L'; + /** ^FB hanging indent applied to every line after the first. */ + public hangingIndent: number = 0; private _text: HTMLDivElement; @@ -32,7 +64,12 @@ export class ZplText extends BaseCustomWebComponentConstructorAppend { content: String, fontName: FontNames, fontHeight: Number, - fontWidth: Number + fontWidth: Number, + blockWidth: Number, + maxLines: Number, + lineSpacing: Number, + justification: Justifications, + hangingIndent: Number } constructor() { @@ -43,34 +80,114 @@ export class ZplText extends BaseCustomWebComponentConstructorAppend { async ready() { this._parseAttributesToProperties(); - this._text.innerHTML = this.content; - this._text.style.transformOrigin = "0 0"; - switch (this.fontName) { - case FontNames.Font_0: - this._text.style.fontSize = "9px"; - this._text.style.fontFamily = "RobotoCn, Verdana"; - this._text.style.fontKerning = "none"; - this._text.style.transform = "scaleX(" + this.fontWidth / 9 + ") scaleY(" + this.fontHeight / 9 + ") translate(0px, -3px)"; - break; - case FontNames.Font_A: - this._text.style.fontSize = "9px"; - this._text.style.fontFamily = "monospace"; - this._text.style.transform = "scaleX(" + this.fontWidth / 5 + ") scaleY(" + this.fontHeight / 8 + ") translate(0px, -3px)"; - break; + this._layout(); + } + + /** + * Lay the text out so it occupies the same dots a printer would use. + * + * ZPL puts the top of the capital letters exactly on the ^FO y coordinate, + * so this element's top edge is the cap top and the text is shifted up by + * whatever ascent the CSS font puts above its capitals. See zplFontMetrics + * for the measurements these rules come from. + */ + private _layout() { + const font = (this.fontName || '0'); + const bitmap = isBitmapFont(font); + const metrics = getZplTextMetrics(font, this.fontHeight, this.fontWidth); + const text = this._text; + + const fontFamily = bitmap ? ZPL_FONT_A_FAMILY : ZPL_FONT_0_FAMILY; + const fontWeight = bitmap ? 'normal' : 'bold'; + + text.style.transformOrigin = '0 0'; + text.style.whiteSpace = 'pre'; + text.style.fontKerning = 'none'; + text.style.lineHeight = '1'; + text.style.fontFamily = fontFamily; + text.style.fontWeight = fontWeight; + text.style.letterSpacing = 'normal'; + + const ratios = measureFontRatios(fontFamily, fontWeight); + + // Size the glyphs so their cap height equals the ZPL cap height. + const fontSize = ratios.capRatio > 0 ? metrics.capHeight / ratios.capRatio : metrics.capHeight; + text.style.fontSize = fontSize + 'px'; + + // The bitmap fonts are fixed pitch with an exactly known advance, so + // pin it. letter-spacing is added after each glyph, before the scaleX. + if (bitmap && metrics.advance != null) { + const natural = ratios.advanceRatio * fontSize; + text.style.letterSpacing = ((metrics.advance / metrics.scaleX) - natural) + 'px'; } - this.style.width = ''; - this.style.height = ''; - requestAnimationFrame(() => { - let rect = this._text.getBoundingClientRect() - this.style.width = rect.width + 'px'; - this.style.height = rect.height / 2 + 'px'; + + // Lift the text so the cap top, not the line box top, sits at y = 0. + text.style.marginTop = (-capTopOffset(fontSize, ratios)) + 'px'; + text.style.position = 'relative'; + + // Width of a string in printed dots. Exact for the bitmap fonts; + // measured from the CSS font for the proportional font 0. + const measure = (s: string): number => { + const exact = predictTextWidth(font, this.fontHeight, this.fontWidth, s); + if (exact != null) return exact; + return measureTextWidth(s, fontSize, fontFamily, fontWeight) * metrics.scaleX; + }; + + const content = this.content ?? ''; + const step = blockLineStep(metrics.cellHeight, this.lineSpacing); + const lines = this.blockWidth > 0 + ? wrapZplLines(content, this.blockWidth, this.maxLines, measure) + : [content]; + + // One positioned div per line so each can be justified independently. + text.textContent = ''; + lines.forEach((line, i) => { + const div = document.createElement('div'); + div.textContent = line; + div.style.position = 'absolute'; + div.style.whiteSpace = 'pre'; + div.style.transformOrigin = '0 0'; + div.style.transform = 'scaleX(' + metrics.scaleX + ')'; + div.style.top = (i * step) + 'px'; + const indent = i > 0 ? (this.hangingIndent || 0) : 0; + const lineWidth = measure(line); + // 'J' stretches every wrapped line but the last to fill the block; + // the last line of the field prints ragged, like left-aligned text. + if (this.justification === 'J' && i < lines.length - 1) { + const gapCount = (line.match(/ /g) || []).length; + const extra = justifyWordSpacing(lineWidth, this.blockWidth, gapCount); + // Undo the div's scaleX transform, which would otherwise also + // stretch the spacing itself (same trick as the letter-spacing + // above). + if (extra > 0) div.style.wordSpacing = (extra / metrics.scaleX) + 'px'; + } + div.style.left = (indent + justifyOffset( + this.justification, this.blockWidth, lineWidth, metrics.advance)) + 'px'; + text.appendChild(div); }); + + this.style.height = ((lines.length - 1) * step + metrics.cellHeight) + 'px'; + if (this.blockWidth > 0) { + this.style.width = this.blockWidth + 'px'; + } else { + // measure() already tries predictTextWidth first and only falls + // back to a canvas measurement for the proportional font 0. + this.style.width = measure(content) + 'px'; + } } public createZpl() { let zpl = ""; zpl += getZplCoordinates(this, 0); - zpl += "^CF" + this.fontName + "," + this.fontHeight + "," + this.fontWidth; + // A width of 0 means "derive from the height", which is what omitting + // the parameter does, so leave it off rather than writing an explicit 0. + zpl += "^CF" + this.fontName + "," + this.fontHeight + + (this.fontWidth > 0 ? "," + this.fontWidth : ""); + if (this.blockWidth > 0) { + zpl += "^FB" + this.blockWidth + "," + (this.maxLines || 1) + "," + + (this.lineSpacing || 0) + "," + (this.justification || 'L') + "," + + (this.hangingIndent || 0); + } zpl += "^FD" + this.content zpl += "^FS"; return zpl; From 519b52c5d0ffcf622134b968658d7d14246bd219 Mon Sep 17 00:00:00 2001 From: lschirmbrand Date: Mon, 10 Aug 2026 16:05:07 +0200 Subject: [PATCH 09/10] zpl: parse ^A/^CF font and ^FB block fields for zpl-text Extends font parsing from ^A0/^AA only to all built-in fonts (^A0, ^AA-^AH) and carries the last accepted ^A/^CF height forward when a later command omits it, matching ZPL's "use current default" behaviour instead of defaulting to an invisible 0px font. Adds ^FB parsing (width, max lines, line spacing, justification, hanging indent), consumed by the next ^FD and reset afterwards, and applies it to the zpl-text attributes added in the previous commit. Co-Authored-By: Claude Sonnet 5 --- .../src/services/ZplParserService.ts | 63 +++++++++++-------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/packages/web-component-designer-zpl/src/services/ZplParserService.ts b/packages/web-component-designer-zpl/src/services/ZplParserService.ts index 19429407a..ef874d455 100644 --- a/packages/web-component-designer-zpl/src/services/ZplParserService.ts +++ b/packages/web-component-designer-zpl/src/services/ZplParserService.ts @@ -64,7 +64,9 @@ export class ZplParserService implements IHtmlParserService, IHtmlWriterService } let designItems: IDesignItem[] = []; let fontName: string; - let fontHeight: number; + // ZPL keeps the last accepted ^A/^CF height when a later command omits + // it ("use current default"), so this is carried forward, not reset. + let fontHeight: number = 15; let fontWidth: number; let rotation: string = 'N'; let x: number; @@ -74,6 +76,9 @@ export class ZplParserService implements IHtmlParserService, IHtmlWriterService let bw: number; let bh: number; let br: number; + // ^FB block state, consumed by the next ^FD and then reset + const defaultFieldBlock = { width: 0, maxLines: 1, lineSpacing: 0, justification: 'L', indent: 0 }; + let fieldBlock = defaultFieldBlock; // let bo: string; let barcode: ZplBarcode; @@ -85,6 +90,16 @@ export class ZplParserService implements IHtmlParserService, IHtmlWriterService let fieldString = p.substring(2); let fields = fieldString.split(","); + // ^A,, for any of the built in fonts (0, A..H). + if (command[0] === 'A' && "0ABCDEFGH".includes(command[1])) { + fontName = command[1]; + rotation = getSetValue(fields[0], 'N'); + fontHeight = parseInt(getSetValue(fields[1], fontHeight.toString())) || fontHeight; + // 0 means "derive from the height"; the metrics resolve it. + fontWidth = parseInt(getSetValue(fields[2], '0')) || 0; + continue; + } + switch (command) { case "XA": case "XZ": @@ -97,35 +112,21 @@ export class ZplParserService implements IHtmlParserService, IHtmlWriterService comment.setAttribute("content", fieldString); designItems.push(DesignItem.createDesignItemFromInstance(comment, serviceContainer, instanceServiceContainer)); break; - case "AA": //Ax x=fontname - case "A0": { - fontName = command[1]; - rotation = getSetValue(fields[0], 'N'); - let defaultFontWidth = 15; - switch (fontName) { - case "A": - defaultFontWidth = 15; - break; - case "0": - defaultFontWidth = fontHeight; - } - fontHeight = parseInt(getSetValue(fields[1], defaultFontWidth)); - fontWidth = parseInt(getSetValue(fields[2], defaultFontWidth)); - break; - } case "CF": //we should switch to use A, CF is default font rotation = 'N'; fontName = fields[0]; - fontHeight = parseInt(fields[1]); - let defaultFontWidth = 15; - switch (fontName) { - case "A": - defaultFontWidth = 15; - break; - case "0": - defaultFontWidth = fontHeight; - } - fontWidth = parseInt(getSetValue(fields[2], defaultFontWidth)); + fontHeight = parseInt(getSetValue(fields[1], fontHeight.toString())) || fontHeight; + // 0 means "derive from the height"; the metrics resolve it. + fontWidth = parseInt(getSetValue(fields[2], '0')) || 0; + break; + case "FB": + fieldBlock = { + width: parseInt(getSetValue(fields[0], '0')) || 0, + maxLines: parseInt(getSetValue(fields[1], '1')) || 1, + lineSpacing: parseInt(getSetValue(fields[2], '0')) || 0, + justification: getSetValue(fields[3], 'L'), + indent: parseInt(getSetValue(fields[4], '0')) || 0, + }; break; case "FO": x = parseInt(fields[0]); @@ -220,11 +221,19 @@ export class ZplParserService implements IHtmlParserService, IHtmlWriterService text.setAttribute("font-height", fontHeight.toString()); text.setAttribute("font-width", fontWidth.toString()); text.setAttribute("content", fieldString); + if (fieldBlock.width > 0) { + text.setAttribute("block-width", fieldBlock.width.toString()); + text.setAttribute("max-lines", fieldBlock.maxLines.toString()); + text.setAttribute("line-spacing", fieldBlock.lineSpacing.toString()); + text.setAttribute("justification", fieldBlock.justification); + text.setAttribute("hanging-indent", fieldBlock.indent.toString()); + } if (rotation) { this.createTransform(rotation, text); } designItems.push(DesignItem.createDesignItemFromInstance(text, serviceContainer, instanceServiceContainer)); } + fieldBlock = defaultFieldBlock; break; case "XG": let image = new ZplImage(); From 420c648cfe691fd1ce9ddb5070859e8b4cd7dd28 Mon Sep 17 00:00:00 2001 From: lschirmbrand Date: Mon, 10 Aug 2026 16:05:14 +0200 Subject: [PATCH 10/10] zpl: clamp graphic box/line size for display without corrupting round-tripped ZPL ^GB0,h,t (a vertical line) and other too-small boxes need the printed extent clamped up to the border thickness to be visible, and small boxes/lines need to be filled rather than stroked once the border meets itself. Previously this was done by overwriting the host element's own style.width/height, which corrupted a plain load/save round-trip (createZpl() re-serializes from that mutated style) and could race with the designer's drag-resize handling, which reads the same property. The clamp is now applied only to the internal box-div used for drawing; the host's declared size is left untouched. Also carries cornerRounding into the filled-shape branch, which previously dropped it for any box at or under twice its stroke thickness. Co-Authored-By: Claude Sonnet 5 --- .../src/widgets/zpl-graphic-box.ts | 71 +++++++++++++------ 1 file changed, 51 insertions(+), 20 deletions(-) diff --git a/packages/web-component-designer-zpl/src/widgets/zpl-graphic-box.ts b/packages/web-component-designer-zpl/src/widgets/zpl-graphic-box.ts index 47ae17145..af57559f0 100644 --- a/packages/web-component-designer-zpl/src/widgets/zpl-graphic-box.ts +++ b/packages/web-component-designer-zpl/src/widgets/zpl-graphic-box.ts @@ -1,5 +1,6 @@ import { BaseCustomWebComponentConstructorAppend, css, html } from "@node-projects/base-custom-webcomponent"; import { getZplCoordinates } from "../zplHelper.js"; +import { clampGraphicSize } from "../zplFontMetrics.js"; enum StrokeColor { black = "black", @@ -51,32 +52,62 @@ export class ZplGraphicBox extends BaseCustomWebComponentConstructorAppend { } private _drawSvg() { - let x = this.strokeWidth / 2; - let width = parseInt(this.style.width.replace("px", "")) - this.strokeWidth; - if (width < this.strokeWidth) - width = this.strokeWidth; - let height = parseInt(this.style.height.replace("px", "")) - this.strokeWidth; - if (height < this.strokeWidth) - height = this.strokeWidth; - let smallerLength = width; - if(smallerLength > height) - smallerLength = height; - let radius = (1/8) * this.cornerRounding * smallerLength / 2; + const thickness = this.strokeWidth > 0 ? this.strokeWidth : 1; + // ZPL clamps the box up to at least the border thickness (^GB10,10,20 + // prints 20x20), then draws the border inward, so the painted extent is + // exactly the outer size. Clamp the outer box, not the inner rect. + const outerWidth = clampGraphicSize(parseInt(this.style.width.replace("px", "")), thickness); + const outerHeight = clampGraphicSize(parseInt(this.style.height.replace("px", "")), thickness); + + // Size the inner box to the printed extent, not the host element: ^GB0,100,2 + // is a vertical line, and box-div's "100%" would otherwise clip it away + // entirely. Deliberately never write outerWidth/outerHeight back onto + // this.style: that field is what createZpl() re-serializes and what the + // designer's resize handling reads, so mutating it would corrupt a plain + // load/save round-trip and race with a live drag-resize. + this._box.style.width = outerWidth + "px"; + this._box.style.height = outerHeight + "px"; + let svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); let rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect'); if (this.strokeColor == StrokeColor.black) rect.setAttribute("stroke", "black"); else rect.setAttribute("stroke", "white"); - rect.setAttribute("fill", "white"); - rect.setAttribute("fill-opacity", "0.0"); - rect.setAttribute("stroke-width", this.strokeWidth.toString()); - rect.setAttribute("x", x.toString()); - rect.setAttribute("y", x.toString()); - rect.setAttribute("rx", radius.toString()); - rect.setAttribute("ry", radius.toString()); - rect.setAttribute("width", width.toString()); - rect.setAttribute("height", height.toString()); + + // Once the borders meet in either axis the shape is solid, which is how a + // line (^GB0,h,t) is expressed in ZPL. Draw it filled: a stroked rect with + // a zero inner width is not rendered at all. + if (outerWidth <= 2 * thickness || outerHeight <= 2 * thickness) { + let smallerLength = outerWidth; + if (smallerLength > outerHeight) + smallerLength = outerHeight; + let radius = (1 / 8) * this.cornerRounding * smallerLength / 2; + rect.setAttribute("fill", this.strokeColor == StrokeColor.black ? "black" : "white"); + rect.setAttribute("x", "0"); + rect.setAttribute("y", "0"); + rect.setAttribute("rx", radius.toString()); + rect.setAttribute("ry", radius.toString()); + rect.setAttribute("width", outerWidth.toString()); + rect.setAttribute("height", outerHeight.toString()); + } else { + let x = thickness / 2; + let width = outerWidth - thickness; + let height = outerHeight - thickness; + let smallerLength = width; + if (smallerLength > height) + smallerLength = height; + let radius = (1 / 8) * this.cornerRounding * smallerLength / 2; + rect.setAttribute("fill", "white"); + rect.setAttribute("fill-opacity", "0.0"); + rect.setAttribute("stroke-width", thickness.toString()); + rect.setAttribute("x", x.toString()); + rect.setAttribute("y", x.toString()); + rect.setAttribute("rx", radius.toString()); + rect.setAttribute("ry", radius.toString()); + rect.setAttribute("width", width.toString()); + rect.setAttribute("height", height.toString()); + } svg.style.overflow = "visible"; if (this._box.childElementCount > 0) this._box.removeChild(this._box.children[0]);