From d00550435c0fcf9162180da21da7b3eb3c51d342 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Mon, 7 Apr 2025 16:49:41 +0300 Subject: [PATCH 01/66] Prototype CSS injection mixin based on Stylable --- dev/lumo-injection/link-body.html | 36 ++++ dev/lumo-injection/link-head.html | 35 ++++ dev/lumo-injection/shadow-host.js | 10 + dev/lumo-injection/style-head.html | 36 ++++ dev/lumo-injection/styles/item.css | 89 +++++++++ .../component-base/src/css-injection-mixin.js | 44 +++++ .../component-base/src/css-injection-utils.js | 186 ++++++++++++++++++ packages/item/src/vaadin-lit-item.js | 3 +- 8 files changed, 438 insertions(+), 1 deletion(-) create mode 100644 dev/lumo-injection/link-body.html create mode 100644 dev/lumo-injection/link-head.html create mode 100644 dev/lumo-injection/shadow-host.js create mode 100644 dev/lumo-injection/style-head.html create mode 100644 dev/lumo-injection/styles/item.css create mode 100644 packages/component-base/src/css-injection-mixin.js create mode 100644 packages/component-base/src/css-injection-utils.js diff --git a/dev/lumo-injection/link-body.html b/dev/lumo-injection/link-body.html new file mode 100644 index 00000000000..8144e0c5948 --- /dev/null +++ b/dev/lumo-injection/link-body.html @@ -0,0 +1,36 @@ + + + + + + + Link in body + + + + + + + Basic item + Focused item + Selected item + + + + + + + + diff --git a/dev/lumo-injection/link-head.html b/dev/lumo-injection/link-head.html new file mode 100644 index 00000000000..4f68fce202d --- /dev/null +++ b/dev/lumo-injection/link-head.html @@ -0,0 +1,35 @@ + + + + + + + Link in head + + + + + + Basic item + Focused item + Selected item + + + + + + + + diff --git a/dev/lumo-injection/shadow-host.js b/dev/lumo-injection/shadow-host.js new file mode 100644 index 00000000000..f22f19b6aba --- /dev/null +++ b/dev/lumo-injection/shadow-host.js @@ -0,0 +1,10 @@ +customElements.define( + 'shadow-host', + class extends HTMLElement { + constructor() { + super(); + + this.attachShadow({ mode: 'open' }); + } + }, +); diff --git a/dev/lumo-injection/style-head.html b/dev/lumo-injection/style-head.html new file mode 100644 index 00000000000..d1230ad67c5 --- /dev/null +++ b/dev/lumo-injection/style-head.html @@ -0,0 +1,36 @@ + + + + + + + Style in head + + + + + Basic item + Focused item + Selected item + + + + + + + + diff --git a/dev/lumo-injection/styles/item.css b/dev/lumo-injection/styles/item.css new file mode 100644 index 00000000000..a1aa3e9940b --- /dev/null +++ b/dev/lumo-injection/styles/item.css @@ -0,0 +1,89 @@ +/* +This file should presumably import files with custom CSS properties; +- import '@vaadin/vaadin-lumo-styles/font-icons.js'; +- import '@vaadin/vaadin-lumo-styles/sizing.js'; +- import '@vaadin/vaadin-lumo-styles/spacing.js'; +- import '@vaadin/vaadin-lumo-styles/style.js'; +- import '@vaadin/vaadin-lumo-styles/typography.js'; +*/ + +@media vaadin-item { + :host { + display: flex; + align-items: center; + box-sizing: border-box; + font-family: var(--lumo-font-family); + font-size: var(--lumo-font-size-m); + line-height: var(--lumo-line-height-xs); + padding: 0.5em calc(var(--lumo-space-l) + var(--lumo-border-radius-m) / 4) 0.5em + var(--_lumo-list-box-item-padding-left, calc(var(--lumo-border-radius-m) / 4)); + min-height: var(--lumo-size-m); + outline: none; + border-radius: var(--lumo-border-radius-m); + cursor: var(--lumo-clickable-cursor); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-tap-highlight-color: var(--lumo-primary-color-10pct); + --_focus-ring-color: var(--vaadin-focus-ring-color, var(--lumo-primary-color-50pct)); + --_focus-ring-width: var(--vaadin-focus-ring-width, 2px); + --_selection-color-text: var(--vaadin-selection-color-text, var(--lumo-primary-text-color)); + } + + /* Checkmark */ + [part='checkmark']::before { + display: var(--_lumo-item-selected-icon-display, none); + content: var(--lumo-icons-checkmark); + font-family: lumo-icons; + font-size: var(--lumo-icon-size-m); + line-height: 1; + font-weight: normal; + width: 1em; + height: 1em; + margin: calc((1 - var(--lumo-line-height-xs)) * var(--lumo-font-size-m) / 2) 0; + color: var(--_selection-color-text); + flex: none; + opacity: 0; + transition: + transform 0.2s cubic-bezier(0.12, 0.32, 0.54, 2), + opacity 0.1s; + } + + :host([selected]) [part='checkmark']::before { + opacity: 1; + } + + :host([active]:not([selected])) [part='checkmark']::before { + transform: scale(0.8); + opacity: 0; + transition-duration: 0s; + } + + [part='content'] { + flex: auto; + } + + /* Disabled */ + :host([disabled]) { + color: var(--lumo-disabled-text-color); + cursor: default; + pointer-events: none; + } + + /* TODO a workaround until we have "focus-follows-mouse". After that, use the hover style for focus-ring as well */ + @media (any-hover: hover) { + :host(:hover:not([disabled])) { + background-color: var(--lumo-primary-color-10pct); + } + } + + :host([focus-ring]:not([disabled])) { + box-shadow: inset 0 0 0 var(--_focus-ring-width) var(--_focus-ring-color); + } + + /* RTL specific styles */ + :host([dir='rtl']) { + padding-left: calc(var(--lumo-space-l) + var(--lumo-border-radius-m) / 4); + padding-right: var(--_lumo-list-box-item-padding-left, calc(var(--lumo-border-radius-m) / 4)); + } + +} diff --git a/packages/component-base/src/css-injection-mixin.js b/packages/component-base/src/css-injection-mixin.js new file mode 100644 index 00000000000..0a5c92a20ce --- /dev/null +++ b/packages/component-base/src/css-injection-mixin.js @@ -0,0 +1,44 @@ +/** + * @license + * Copyright (c) 2021 - 2025 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ +import { gatherMatchingStyleRules } from './css-injection-utils.js'; + +/** + * Mixin for internal use only. Do not use it in custom components. + * + * @polymerMixin + */ +export const CssInjectionMixin = (superClass) => + class CssInjectionMixinClass extends superClass { + /** @protected */ + async connectedCallback() { + super.connectedCallback(); + + const rules = await gatherMatchingStyleRules(this); + + if (rules.length > 0) { + this.__injectedStyleSheet = new CSSStyleSheet(); + + rules.forEach((ruleList) => { + for (const rule of ruleList) { + this.__injectedStyleSheet.insertRule(rule.cssText); + } + }); + + this.shadowRoot.adoptedStyleSheets.push(this.__injectedStyleSheet); + } + } + + /** @protected */ + disconnectedCallback() { + super.disconnectedCallback(); + + if (this.__injectedStyleSheet) { + this.shadowRoot.adoptedStyleSheets.splice( + this.shadowRoot.adoptedStyleSheets.indexOf(this.__injectedStyleSheet, 1), + ); + } + } + }; diff --git a/packages/component-base/src/css-injection-utils.js b/packages/component-base/src/css-injection-utils.js new file mode 100644 index 00000000000..482c68f4b36 --- /dev/null +++ b/packages/component-base/src/css-injection-utils.js @@ -0,0 +1,186 @@ +/** + * @license + * Copyright (c) 2021 - 2025 Vaadin Ltd. + * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ + */ + +// Based on https://github.com/jouni/j-elements/blob/main/test/old-components/Stylable.js + +/** + * Check if the media query is a non-standard "element scoped selector", i.e. it does not contain any media feature queries, only media type. + * @param {MediaList} media + * @return {Boolean} True if the media query only contains a media type query + */ +function isElementMedia(media) { + // TODO account for the 'and' combinator? + // e.g. screen and (orientation: portrait) and (max-width: 400px) and x-foo + // x-foo and x-bar + return media && media.match(/^[\w]+-[\w.()[\]"'=~*^$]+/u); +} + +/** + * Check if an element matches a given selector + * @param {HTMLElement} el The element which might match the selector + * @param {String} selector The selector to match against + * @return {Boolean} undefined if the selector is not a valid CSS selector. True|false whether the element matches the selector or not. + */ +function matches(el, selector) { + try { + return el.matches(selector); + } catch (_) { + // Not a valid selector (such as an empty string) + return undefined; + } +} + +/** + * Check if the media type string matches the given element + * @param {HTMLElement} el The element which might match the given media type string + * @param {MediaList} media MediaList object to match against + * @return {Boolean} undefined if the media type string is not a valid CSS selector or a standard media features query. True|false whether the element matches the selector or not. + */ +function matchesElement(el, media) { + // Firefox parses the escaping backward slash into a double backward slash: \ -> \\ + media = media.replace(/\\/gmu, ''); + if (isElementMedia(media)) { + return matches(el, media); + } + + return undefined; +} + +// Recursively process a style sheet for matching rules +function extractMatchingStyleRules(styleSheet, element, collectorFunc) { + let media = ''; + + if (styleSheet.ownerRule) { + if (styleSheet.ownerRule.type === 3) { + // @import + // Need this awkward workaround since Firefox (sometimes?) blocks the access to the MediaList + // object for some reason in imported stylesheets + const importRule = styleSheet.ownerRule.cssText.split(' '); + if (importRule.length > 2) { + media = importRule.slice(2).join(' ').replace(';', ''); + } + } + } else if (styleSheet.ownerNode) { + media = styleSheet.ownerNode.media; + } else if (styleSheet.media) { + media = styleSheet.media.mediaText; + } + + // TODO @import sheets should be inserted as the first ones in the results + // Now they can end up in the middle of other rules and be ignored + + const match = matchesElement(element, media); + if (match !== undefined) { + // Not a standard media query (no media features specified, only media type) + if (match) { + // Media type matches the element + collectorFunc(styleSheet.cssRules); + } + } else { + // Either no media specified or a standard media query + for (const rule of styleSheet.cssRules) { + if (rule.type === 3) { + // @import + extractMatchingStyleRules(rule.styleSheet, element, collectorFunc); + } else if (rule.type === 4) { + // @media + if (matchesElement(element, rule.media.mediaText)) { + collectorFunc(rule.cssRules); + } + } + } + } +} + +function linkOrStylePromise(linkOrStyle) { + // - + Basic item Focused item diff --git a/dev/lumo-injection/link-head.css b/dev/lumo-injection/link-head.css new file mode 100644 index 00000000000..ee2b8d84842 --- /dev/null +++ b/dev/lumo-injection/link-head.css @@ -0,0 +1 @@ +@import '@vaadin/vaadin-lumo-styles/components/item.css'; diff --git a/dev/lumo-injection/link-head.html b/dev/lumo-injection/link-head.html index 0fcf959479b..9218dc2f6ba 100644 --- a/dev/lumo-injection/link-head.html +++ b/dev/lumo-injection/link-head.html @@ -5,7 +5,8 @@ Link in head - + + - + Basic item Focused item diff --git a/dev/lumo-injection/link-head-chunks.html b/dev/lumo-injection/link-head-chunks.html new file mode 100644 index 00000000000..dd249c3f883 --- /dev/null +++ b/dev/lumo-injection/link-head-chunks.html @@ -0,0 +1,23 @@ + + + + + + + Multiple links in head + + + + + Button + + Basic item + Focused item + Selected item + + + + diff --git a/dev/lumo-injection/link-head.css b/dev/lumo-injection/link-head.css deleted file mode 100644 index ee2b8d84842..00000000000 --- a/dev/lumo-injection/link-head.css +++ /dev/null @@ -1 +0,0 @@ -@import '@vaadin/vaadin-lumo-styles/components/item.css'; diff --git a/dev/lumo-injection/link-head.html b/dev/lumo-injection/link-head.html index 9218dc2f6ba..246e046f8f2 100644 --- a/dev/lumo-injection/link-head.html +++ b/dev/lumo-injection/link-head.html @@ -5,8 +5,8 @@ Link in head - - + + + + + Basic item + Focused item + Selected item + + + + + + + diff --git a/packages/component-base/package.json b/packages/component-base/package.json index 11aca6fd1c1..0b36dde1c1f 100644 --- a/packages/component-base/package.json +++ b/packages/component-base/package.json @@ -35,7 +35,8 @@ "@polymer/polymer": "^3.0.0", "@vaadin/vaadin-development-mode-detector": "^2.0.0", "@vaadin/vaadin-usage-statistics": "^2.1.0", - "lit": "^3.0.0" + "lit": "^3.0.0", + "style-observer": "^0.0.5" }, "devDependencies": { "@vaadin/chai-plugins": "24.8.0-alpha13", diff --git a/packages/component-base/src/css-injection-mixin.js b/packages/component-base/src/css-injection-mixin.js index 0a5c92a20ce..3681a2d57e6 100644 --- a/packages/component-base/src/css-injection-mixin.js +++ b/packages/component-base/src/css-injection-mixin.js @@ -3,8 +3,86 @@ * Copyright (c) 2021 - 2025 Vaadin Ltd. * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ */ +import StyleObserver from 'style-observer'; import { gatherMatchingStyleRules } from './css-injection-utils.js'; +const injectedClasses = new Set(); + +const injectableInstances = new Set(); + +async function injectInstanceStyles(el) { + const rules = await gatherMatchingStyleRules(el); + + if (rules.length > 0) { + el.__injectedStyleSheet = new CSSStyleSheet(); + + rules.forEach((ruleList) => { + for (const rule of ruleList) { + el.__injectedStyleSheet.insertRule(rule.cssText); + } + }); + + el.shadowRoot.adoptedStyleSheets.push(el.__injectedStyleSheet); + } +} + +function cleanupInstanceStyles(el) { + if (el.__injectedStyleSheet) { + el.shadowRoot.adoptedStyleSheets.splice(el.shadowRoot.adoptedStyleSheets.indexOf(el.__injectedStyleSheet, 1)); + } +} + +async function injectClassInstanceStyles(componentClass) { + const promises = []; + + injectableInstances.forEach((ref) => { + const instance = ref.deref(); + if (instance instanceof componentClass) { + promises.push(injectInstanceStyles(instance)); + } else if (!instance) { + // Clean up the weak reference to a GC'd instance + injectableInstances.delete(ref); + } + }); + + await Promise.all(promises); +} + +function cleanupClassInstanceStyles(componentClass) { + injectableInstances.forEach((ref) => { + const instance = ref.deref(); + if (instance instanceof componentClass) { + cleanupInstanceStyles(instance); + } else if (!instance) { + // Clean up the weak reference to a GC'd instance + injectableInstances.delete(ref); + } + }); +} + +const observer = new StyleObserver((records) => { + records.forEach((record) => { + const { property, value, oldValue } = record; + + const tagName = property.slice(2).replace('-css-inject', ''); + const componentClass = customElements.get(tagName); + + if (componentClass) { + if (value === '1') { + // Allow future instances inject own styles + injectedClasses.add(componentClass); + // Inject styles for already existing instances + injectClassInstanceStyles(componentClass); + } else if (oldValue === '1') { + // Disallow future instances inject own styles + injectedClasses.delete(componentClass); + // Cleanup styles for already existing instances + cleanupClassInstanceStyles(componentClass); + } + } + }); +}); + /** * Mixin for internal use only. Do not use it in custom components. * @@ -12,33 +90,52 @@ import { gatherMatchingStyleRules } from './css-injection-utils.js'; */ export const CssInjectionMixin = (superClass) => class CssInjectionMixinClass extends superClass { - /** @protected */ - async connectedCallback() { - super.connectedCallback(); + static finalize() { + super.finalize(); + + if (this.is) { + const propName = `--${this.is}-css-inject`; + + // If styles for custom property are already loaded, store this class + // in a registry so that evert instance of it would auto-inject styles + const value = getComputedStyle(document.documentElement).getPropertyValue(propName); + if (value === '1') { + injectedClasses.add(this); + } + + // Initialize custom property for this class with 0 as default + // so that changing it to 1 would inject styles to instances + const styleTag = document.createElement('style'); + styleTag.id = `${this.is}-css-inject-initial`; + styleTag.textContent = `:where(html) { --${this.is}-css-inject: 0 }`; + document.head.insertAdjacentElement('afterbegin', styleTag); - const rules = await gatherMatchingStyleRules(this); + // Observe custom property that would trigger injection for this class + observer.observe(document.documentElement, propName); + } + } - if (rules.length > 0) { - this.__injectedStyleSheet = new CSSStyleSheet(); + constructor() { + super(); + // Store a weak reference to the instance + injectableInstances.add(new WeakRef(this)); + } - rules.forEach((ruleList) => { - for (const rule of ruleList) { - this.__injectedStyleSheet.insertRule(rule.cssText); - } - }); + /** @protected */ + async connectedCallback() { + super.connectedCallback(); - this.shadowRoot.adoptedStyleSheets.push(this.__injectedStyleSheet); + if (!injectedClasses.has(this.constructor)) { + return; } + + await injectInstanceStyles(this); } /** @protected */ disconnectedCallback() { super.disconnectedCallback(); - if (this.__injectedStyleSheet) { - this.shadowRoot.adoptedStyleSheets.splice( - this.shadowRoot.adoptedStyleSheets.indexOf(this.__injectedStyleSheet, 1), - ); - } + cleanupInstanceStyles(this); } }; diff --git a/packages/component-base/src/css-injection-utils.js b/packages/component-base/src/css-injection-utils.js index 482c68f4b36..e1dd32f94bb 100644 --- a/packages/component-base/src/css-injection-utils.js +++ b/packages/component-base/src/css-injection-utils.js @@ -160,6 +160,10 @@ export async function gatherMatchingStyleRules(instance) { // NOTE: original code used deprecated `performance.timing.loadEventEnd` const perfEntries = performance.getEntriesByType('navigation'); + // TODO: also process `document.adoptedStyleSheets` to support importing + // CSS files from JS: `import '@vaadin/lumo/lumo.css' with { type: 'css' }` + // This would be convenient in some cases e.g. for Lumo visual tests + if (perfEntries[0].loadEventEnd) { // Page has already loaded, document.styleSheets is populated processSheetsArray(document.styleSheets, instance, matchingStyleRules); diff --git a/packages/vaadin-lumo-styles/components/item.css b/packages/vaadin-lumo-styles/components/item.css index c562009bffb..afe6853163a 100644 --- a/packages/vaadin-lumo-styles/components/item.css +++ b/packages/vaadin-lumo-styles/components/item.css @@ -9,6 +9,10 @@ This file should presumably import files with custom CSS properties; @import '../sizing.css'; +html { + --vaadin-item-css-inject: 1; +} + @media vaadin-item { :host { display: flex; diff --git a/web-dev-server.config.js b/web-dev-server.config.js index 96794378fb6..a7c38c75e3c 100644 --- a/web-dev-server.config.js +++ b/web-dev-server.config.js @@ -26,23 +26,6 @@ function generatedLitTestsPlugin() { }; } -const preventFouc = ` - - - -`; - module.exports = { plugins: [ { @@ -51,9 +34,6 @@ module.exports = { if (context.response.is('html')) { let body = context.body; - // Fouc prevention - body = body.replace(/<\/body>/u, `${preventFouc}\n`); - // Index page listing if (['/dev/index.html', '/dev', '/dev/'].includes(context.path)) { const listing = ` diff --git a/yarn.lock b/yarn.lock index a21393aa42c..451733fc209 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11962,6 +11962,11 @@ strong-log-transformer@^2.1.0: minimist "^1.2.0" through "^2.3.4" +style-observer@^0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/style-observer/-/style-observer-0.0.5.tgz#2efbac9a6ebb4bf926c18862ef61f1cd0873a1bb" + integrity sha512-d5ieqbpC6URZmZlemdYDUcGSh6hCqIODWppBCGUfJDylVprE20hqAo9aHetb43kCrHc/1kNaikrQgbE5+kBWkA== + stylelint-config-vaadin@^1.0.0-alpha.2: version "1.0.0-alpha.2" resolved "https://registry.yarnpkg.com/stylelint-config-vaadin/-/stylelint-config-vaadin-1.0.0-alpha.2.tgz#995372f0d31b8b941c8320414cbeb9f2737e616c" From dce08e26ae8039d7878d3488e1352dc3a87447fd Mon Sep 17 00:00:00 2001 From: web-padawan Date: Tue, 8 Apr 2025 15:19:59 +0300 Subject: [PATCH 08/66] Do not wait for all stylesheets to be loaded --- .../component-base/src/css-injection-mixin.js | 8 +-- .../component-base/src/css-injection-utils.js | 66 +------------------ 2 files changed, 7 insertions(+), 67 deletions(-) diff --git a/packages/component-base/src/css-injection-mixin.js b/packages/component-base/src/css-injection-mixin.js index 3681a2d57e6..f3157fbc210 100644 --- a/packages/component-base/src/css-injection-mixin.js +++ b/packages/component-base/src/css-injection-mixin.js @@ -10,8 +10,8 @@ const injectedClasses = new Set(); const injectableInstances = new Set(); -async function injectInstanceStyles(el) { - const rules = await gatherMatchingStyleRules(el); +function injectInstanceStyles(el) { + const rules = gatherMatchingStyleRules(el); if (rules.length > 0) { el.__injectedStyleSheet = new CSSStyleSheet(); @@ -122,14 +122,14 @@ export const CssInjectionMixin = (superClass) => } /** @protected */ - async connectedCallback() { + connectedCallback() { super.connectedCallback(); if (!injectedClasses.has(this.constructor)) { return; } - await injectInstanceStyles(this); + injectInstanceStyles(this); } /** @protected */ diff --git a/packages/component-base/src/css-injection-utils.js b/packages/component-base/src/css-injection-utils.js index e1dd32f94bb..ced71fe1a78 100644 --- a/packages/component-base/src/css-injection-utils.js +++ b/packages/component-base/src/css-injection-utils.js @@ -95,57 +95,6 @@ function extractMatchingStyleRules(styleSheet, element, collectorFunc) { } } -function linkOrStylePromise(linkOrStyle) { - // + - Basic item - Focused item - Selected item + + Basic item + Focused item + Selected item + - + - + + - Basic item - Focused item - Selected item + + Basic item + Focused item + Selected item + @@ -25,6 +23,7 @@ - - diff --git a/dev/lumo-injection/link-head.html b/dev/lumo-injection/link-head.html index 246e046f8f2..ae6e53bba0f 100644 --- a/dev/lumo-injection/link-head.html +++ b/dev/lumo-injection/link-head.html @@ -7,16 +7,14 @@ Link in head - + - Basic item - Focused item - Selected item + + Basic item + Focused item + Selected item + @@ -24,11 +22,12 @@ diff --git a/dev/lumo-injection/style-head.html b/dev/lumo-injection/style-head.html index fbb3374d11f..25766828ba5 100644 --- a/dev/lumo-injection/style-head.html +++ b/dev/lumo-injection/style-head.html @@ -5,19 +5,18 @@ Style in head - + - Basic item - Focused item - Selected item + + Basic item + Focused item + Selected item + @@ -25,11 +24,12 @@ diff --git a/dev/lumo-injection/styles/button.css b/dev/lumo-injection/styles/button.css deleted file mode 100644 index 560499919dd..00000000000 --- a/dev/lumo-injection/styles/button.css +++ /dev/null @@ -1 +0,0 @@ -@import '@vaadin/vaadin-lumo-styles/components/button.css'; diff --git a/dev/lumo-injection/styles/common.css b/dev/lumo-injection/styles/common.css new file mode 100644 index 00000000000..effa94e47aa --- /dev/null +++ b/dev/lumo-injection/styles/common.css @@ -0,0 +1,2 @@ +@import url("item.css"); +@import url("list-box.css"); diff --git a/dev/lumo-injection/styles/list-box.css b/dev/lumo-injection/styles/list-box.css new file mode 100644 index 00000000000..bf98a21158b --- /dev/null +++ b/dev/lumo-injection/styles/list-box.css @@ -0,0 +1 @@ +@import '@vaadin/vaadin-lumo-styles/components/list-box.css'; diff --git a/packages/button/src/vaadin-lit-button.js b/packages/button/src/vaadin-lit-button.js index 249ced06d11..a33eb79d66c 100644 --- a/packages/button/src/vaadin-lit-button.js +++ b/packages/button/src/vaadin-lit-button.js @@ -4,7 +4,6 @@ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ */ import { html, LitElement } from 'lit'; -import { CssInjectionMixin } from '@vaadin/component-base/src/css-injection-mixin.js'; import { defineCustomElement } from '@vaadin/component-base/src/define.js'; import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; @@ -22,7 +21,7 @@ import { ButtonMixin } from './vaadin-button-mixin.js'; * There is no ETA regarding specific Vaadin version where it'll land. * Feel free to try this code in your apps as per Apache 2.0 license. */ -class Button extends ButtonMixin(CssInjectionMixin(ElementMixin(ThemableMixin(PolylitMixin(LitElement))))) { +class Button extends ButtonMixin(ElementMixin(ThemableMixin(PolylitMixin(LitElement)))) { static get is() { return 'vaadin-button'; } diff --git a/packages/list-box/src/vaadin-lit-list-box.js b/packages/list-box/src/vaadin-lit-list-box.js index b4f2ecde92b..9b07e055e74 100644 --- a/packages/list-box/src/vaadin-lit-list-box.js +++ b/packages/list-box/src/vaadin-lit-list-box.js @@ -4,6 +4,7 @@ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ */ import { css, html, LitElement } from 'lit'; +import { CssInjectionMixin } from '@vaadin/component-base/src/css-injection-mixin.js'; import { defineCustomElement } from '@vaadin/component-base/src/define.js'; import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; @@ -20,7 +21,7 @@ import { MultiSelectListMixin } from './vaadin-multi-select-list-mixin.js'; * There is no ETA regarding specific Vaadin version where it'll land. * Feel free to try this code in your apps as per Apache 2.0 license. */ -class ListBox extends ElementMixin(MultiSelectListMixin(ThemableMixin(PolylitMixin(LitElement)))) { +class ListBox extends CssInjectionMixin(ElementMixin(MultiSelectListMixin(ThemableMixin(PolylitMixin(LitElement))))) { static get is() { return 'vaadin-list-box'; } diff --git a/packages/vaadin-lumo-styles/color.css b/packages/vaadin-lumo-styles/color.css new file mode 100644 index 00000000000..732ae634b4b --- /dev/null +++ b/packages/vaadin-lumo-styles/color.css @@ -0,0 +1,84 @@ +:root { + /* Base (background) */ + --lumo-base-color: #fff; + + /* Tint */ + --lumo-tint-5pct: hsla(0, 0%, 100%, 0.3); + --lumo-tint-10pct: hsla(0, 0%, 100%, 0.37); + --lumo-tint-20pct: hsla(0, 0%, 100%, 0.44); + --lumo-tint-30pct: hsla(0, 0%, 100%, 0.5); + --lumo-tint-40pct: hsla(0, 0%, 100%, 0.57); + --lumo-tint-50pct: hsla(0, 0%, 100%, 0.64); + --lumo-tint-60pct: hsla(0, 0%, 100%, 0.7); + --lumo-tint-70pct: hsla(0, 0%, 100%, 0.77); + --lumo-tint-80pct: hsla(0, 0%, 100%, 0.84); + --lumo-tint-90pct: hsla(0, 0%, 100%, 0.9); + --lumo-tint: #fff; + + /* Shade */ + --lumo-shade-5pct: hsla(214, 61%, 25%, 0.05); + --lumo-shade-10pct: hsla(214, 57%, 24%, 0.1); + --lumo-shade-20pct: hsla(214, 53%, 23%, 0.16); + --lumo-shade-30pct: hsla(214, 50%, 22%, 0.26); + --lumo-shade-40pct: hsla(214, 47%, 21%, 0.38); + --lumo-shade-50pct: hsla(214, 45%, 20%, 0.52); + --lumo-shade-60pct: hsla(214, 43%, 19%, 0.6); + --lumo-shade-70pct: hsla(214, 42%, 18%, 0.69); + --lumo-shade-80pct: hsla(214, 41%, 17%, 0.83); + --lumo-shade-90pct: hsla(214, 40%, 16%, 0.94); + --lumo-shade: hsl(214, 35%, 15%); + + /* Contrast */ + --lumo-contrast-5pct: var(--lumo-shade-5pct); + --lumo-contrast-10pct: var(--lumo-shade-10pct); + --lumo-contrast-20pct: var(--lumo-shade-20pct); + --lumo-contrast-30pct: var(--lumo-shade-30pct); + --lumo-contrast-40pct: var(--lumo-shade-40pct); + --lumo-contrast-50pct: var(--lumo-shade-50pct); + --lumo-contrast-60pct: var(--lumo-shade-60pct); + --lumo-contrast-70pct: var(--lumo-shade-70pct); + --lumo-contrast-80pct: var(--lumo-shade-80pct); + --lumo-contrast-90pct: var(--lumo-shade-90pct); + --lumo-contrast: var(--lumo-shade); + + /* Text */ + --lumo-header-text-color: var(--lumo-contrast); + --lumo-body-text-color: var(--lumo-contrast-90pct); + --lumo-secondary-text-color: var(--lumo-contrast-70pct); + --lumo-tertiary-text-color: var(--lumo-contrast-50pct); + --lumo-disabled-text-color: var(--lumo-contrast-30pct); + + /* Primary */ + --lumo-primary-color: hsl(214, 100%, 48%); + --lumo-primary-color-50pct: hsla(214, 100%, 49%, 0.76); + --lumo-primary-color-10pct: hsla(214, 100%, 60%, 0.13); + --lumo-primary-text-color: hsl(214, 100%, 43%); + --lumo-primary-contrast-color: #fff; + + /* Error */ + --lumo-error-color: hsl(3, 85%, 48%); + --lumo-error-color-50pct: hsla(3, 85%, 49%, 0.5); + --lumo-error-color-10pct: hsla(3, 85%, 49%, 0.1); + --lumo-error-text-color: hsl(3, 89%, 42%); + --lumo-error-contrast-color: #fff; + + /* Success */ + --lumo-success-color: hsl(145, 72%, 30%); + --lumo-success-color-50pct: hsla(145, 72%, 31%, 0.5); + --lumo-success-color-10pct: hsla(145, 72%, 31%, 0.1); + --lumo-success-text-color: hsl(145, 85%, 25%); + --lumo-success-contrast-color: #fff; + + /* Warning */ + --lumo-warning-color: hsl(48, 100%, 50%); + --lumo-warning-color-10pct: hsla(48, 100%, 50%, 0.25); + --lumo-warning-text-color: hsl(32, 100%, 30%); + --lumo-warning-contrast-color: var(--lumo-shade-90pct); +} + +/* forced-colors mode adjustments */ +@media (forced-colors: active) { + :root { + --lumo-disabled-text-color: GrayText; + } +} diff --git a/packages/vaadin-lumo-styles/components/button.css b/packages/vaadin-lumo-styles/components/button.css deleted file mode 100644 index 4499bd2fa10..00000000000 --- a/packages/vaadin-lumo-styles/components/button.css +++ /dev/null @@ -1,14 +0,0 @@ -/* -This file should presumably import files with custom CSS properties; -- import '@vaadin/vaadin-lumo-styles/font-icons.js'; -- import '@vaadin/vaadin-lumo-styles/sizing.js'; -- import '@vaadin/vaadin-lumo-styles/spacing.js'; -- import '@vaadin/vaadin-lumo-styles/style.js'; -- import '@vaadin/vaadin-lumo-styles/typography.js'; -*/ - -@media vaadin-button { - :host { - background: red; - } -} diff --git a/packages/vaadin-lumo-styles/components/item.css b/packages/vaadin-lumo-styles/components/item.css index afe6853163a..b8f3f4cc16a 100644 --- a/packages/vaadin-lumo-styles/components/item.css +++ b/packages/vaadin-lumo-styles/components/item.css @@ -1,13 +1,13 @@ /* This file should presumably import files with custom CSS properties; - import '@vaadin/vaadin-lumo-styles/font-icons.js'; -- import '@vaadin/vaadin-lumo-styles/sizing.js'; -- import '@vaadin/vaadin-lumo-styles/spacing.js'; -- import '@vaadin/vaadin-lumo-styles/style.js'; -- import '@vaadin/vaadin-lumo-styles/typography.js'; */ - +@import '../color.css'; +@import '../font-icons.css'; @import '../sizing.css'; +@import '../spacing.css'; +@import '../style.css'; +@import '../typography.css'; html { --vaadin-item-css-inject: 1; diff --git a/packages/vaadin-lumo-styles/components/list-box.css b/packages/vaadin-lumo-styles/components/list-box.css new file mode 100644 index 00000000000..8ce1e47ebd0 --- /dev/null +++ b/packages/vaadin-lumo-styles/components/list-box.css @@ -0,0 +1,23 @@ +@import '../color.css'; +@import '../spacing.css'; +@import '../style.css'; + +html { + --vaadin-list-box-css-inject: 1; +} + +@media vaadin-list-box { + :host { + -webkit-tap-highlight-color: transparent; + --_lumo-item-selected-icon-display: var(--_lumo-list-box-item-selected-icon-display, block); + } + + /* Dividers */ + [part='items'] ::slotted(hr) { + height: 1px; + border: 0; + padding: 0; + margin: var(--lumo-space-s) var(--lumo-border-radius-m); + background-color: var(--lumo-contrast-10pct); + } +} diff --git a/packages/vaadin-lumo-styles/font-icons.css b/packages/vaadin-lumo-styles/font-icons.css new file mode 100644 index 00000000000..1abcf9916cf --- /dev/null +++ b/packages/vaadin-lumo-styles/font-icons.css @@ -0,0 +1,54 @@ +@font-face { + font-family: 'lumo-icons'; + src: url(data:application/font-woff;charset=utf-8;base64,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) + format('woff'); + font-weight: normal; + font-style: normal; +} + +html { + --lumo-icons-align-center: '\ea01'; + --lumo-icons-align-left: '\ea02'; + --lumo-icons-align-right: '\ea03'; + --lumo-icons-angle-down: '\ea04'; + --lumo-icons-angle-left: '\ea05'; + --lumo-icons-angle-right: '\ea06'; + --lumo-icons-angle-up: '\ea07'; + --lumo-icons-arrow-down: '\ea08'; + --lumo-icons-arrow-left: '\ea09'; + --lumo-icons-arrow-right: '\ea0a'; + --lumo-icons-arrow-up: '\ea0b'; + --lumo-icons-bar-chart: '\ea0c'; + --lumo-icons-bell: '\ea0d'; + --lumo-icons-calendar: '\ea0e'; + --lumo-icons-checkmark: '\ea0f'; + --lumo-icons-chevron-down: '\ea10'; + --lumo-icons-chevron-left: '\ea11'; + --lumo-icons-chevron-right: '\ea12'; + --lumo-icons-chevron-up: '\ea13'; + --lumo-icons-clock: '\ea14'; + --lumo-icons-cog: '\ea15'; + --lumo-icons-cross: '\ea16'; + --lumo-icons-download: '\ea17'; + --lumo-icons-drag-handle: '\ea18'; + --lumo-icons-dropdown: '\ea19'; + --lumo-icons-edit: '\ea1a'; + --lumo-icons-error: '\ea1b'; + --lumo-icons-eye: '\ea1c'; + --lumo-icons-eye-disabled: '\ea1d'; + --lumo-icons-menu: '\ea1e'; + --lumo-icons-minus: '\ea1f'; + --lumo-icons-ordered-list: '\ea20'; + --lumo-icons-phone: '\ea21'; + --lumo-icons-photo: '\ea22'; + --lumo-icons-play: '\ea23'; + --lumo-icons-plus: '\ea24'; + --lumo-icons-redo: '\ea25'; + --lumo-icons-reload: '\ea26'; + --lumo-icons-resize-handle: '\ea27'; + --lumo-icons-search: '\ea28'; + --lumo-icons-undo: '\ea29'; + --lumo-icons-unordered-list: '\ea2a'; + --lumo-icons-upload: '\ea2b'; + --lumo-icons-user: '\ea2c'; +} diff --git a/packages/vaadin-lumo-styles/spacing.css b/packages/vaadin-lumo-styles/spacing.css new file mode 100644 index 00000000000..1e176129a7b --- /dev/null +++ b/packages/vaadin-lumo-styles/spacing.css @@ -0,0 +1,22 @@ +:root { + /* Square */ + --lumo-space-xs: 0.25rem; + --lumo-space-s: 0.5rem; + --lumo-space-m: 1rem; + --lumo-space-l: 1.5rem; + --lumo-space-xl: 2.5rem; + + /* Wide */ + --lumo-space-wide-xs: calc(var(--lumo-space-xs) / 2) var(--lumo-space-xs); + --lumo-space-wide-s: calc(var(--lumo-space-s) / 2) var(--lumo-space-s); + --lumo-space-wide-m: calc(var(--lumo-space-m) / 2) var(--lumo-space-m); + --lumo-space-wide-l: calc(var(--lumo-space-l) / 2) var(--lumo-space-l); + --lumo-space-wide-xl: calc(var(--lumo-space-xl) / 2) var(--lumo-space-xl); + + /* Tall */ + --lumo-space-tall-xs: var(--lumo-space-xs) calc(var(--lumo-space-xs) / 2); + --lumo-space-tall-s: var(--lumo-space-s) calc(var(--lumo-space-s) / 2); + --lumo-space-tall-m: var(--lumo-space-m) calc(var(--lumo-space-m) / 2); + --lumo-space-tall-l: var(--lumo-space-l) calc(var(--lumo-space-l) / 2); + --lumo-space-tall-xl: var(--lumo-space-xl) calc(var(--lumo-space-xl) / 2); +} diff --git a/packages/vaadin-lumo-styles/style.css b/packages/vaadin-lumo-styles/style.css new file mode 100644 index 00000000000..c5c447c01b7 --- /dev/null +++ b/packages/vaadin-lumo-styles/style.css @@ -0,0 +1,16 @@ +:root { + /* Border radius */ + --lumo-border-radius-s: 0.25em; /* Checkbox, badge, date-picker year indicator, etc */ + --lumo-border-radius-m: var(--lumo-border-radius, 0.25em); /* Button, text field, menu overlay, etc */ + --lumo-border-radius-l: 0.5em; /* Dialog, notification, etc */ + + /* Shadow */ + --lumo-box-shadow-xs: 0 1px 4px -1px var(--lumo-shade-50pct); + --lumo-box-shadow-s: 0 2px 4px -1px var(--lumo-shade-20pct), 0 3px 12px -1px var(--lumo-shade-30pct); + --lumo-box-shadow-m: 0 2px 6px -1px var(--lumo-shade-20pct), 0 8px 24px -4px var(--lumo-shade-40pct); + --lumo-box-shadow-l: 0 3px 18px -2px var(--lumo-shade-20pct), 0 12px 48px -6px var(--lumo-shade-40pct); + --lumo-box-shadow-xl: 0 4px 24px -3px var(--lumo-shade-20pct), 0 18px 64px -8px var(--lumo-shade-40pct); + + /* Clickable element cursor */ + --lumo-clickable-cursor: default; +} diff --git a/packages/vaadin-lumo-styles/typography.css b/packages/vaadin-lumo-styles/typography.css new file mode 100644 index 00000000000..2375744c694 --- /dev/null +++ b/packages/vaadin-lumo-styles/typography.css @@ -0,0 +1,19 @@ +:root { + /* prettier-ignore */ + --lumo-font-family: -apple-system, BlinkMacSystemFont, 'Roboto', 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + + /* Font sizes */ + --lumo-font-size-xxs: 0.75rem; + --lumo-font-size-xs: 0.8125rem; + --lumo-font-size-s: 0.875rem; + --lumo-font-size-m: 1rem; + --lumo-font-size-l: 1.125rem; + --lumo-font-size-xl: 1.375rem; + --lumo-font-size-xxl: 1.75rem; + --lumo-font-size-xxxl: 2.5rem; + + /* Line heights */ + --lumo-line-height-xs: 1.25; + --lumo-line-height-s: 1.375; + --lumo-line-height-m: 1.625; +} From 4e48330fa0f5f39f37fd66e411342f4345dad547 Mon Sep 17 00:00:00 2001 From: Sergey Vinogradov Date: Tue, 8 Apr 2025 17:15:15 +0400 Subject: [PATCH 14/66] clean up css injection code --- .../component-base/src/css-injection-utils.js | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/component-base/src/css-injection-utils.js b/packages/component-base/src/css-injection-utils.js index ced71fe1a78..8a843af6dff 100644 --- a/packages/component-base/src/css-injection-utils.js +++ b/packages/component-base/src/css-injection-utils.js @@ -95,23 +95,25 @@ function extractMatchingStyleRules(styleSheet, element, collectorFunc) { } } -function processSheetsArray(sheets, element, matchingStyleRules) { +function processSheetsArray(sheets, element) { + const matchingRules = []; + for (const sheet of sheets) { - extractMatchingStyleRules(sheet, element, (rules) => { - matchingStyleRules.push(rules); - }); + extractMatchingStyleRules(sheet, element, (rules) => matchingRules.push(rules)); } + + return matchingRules; } export function gatherMatchingStyleRules(instance) { - const matchingStyleRules = []; + const matchingRules = []; // TODO: also process `document.adoptedStyleSheets` to support importing // CSS files from JS: `import '@vaadin/lumo/lumo.css' with { type: 'css' }` // This would be convenient in some cases e.g. for Lumo visual tests // Page has already loaded, document.styleSheets is populated - processSheetsArray(document.styleSheets, instance, matchingStyleRules); + matchingRules.push(...processSheetsArray(document.styleSheets, instance)); // Scoped stylesheets @@ -121,10 +123,10 @@ export function gatherMatchingStyleRules(instance) { const root = instance.getRootNode(); if (root !== document) { if (root.adoptedStyleSheets) { - processSheetsArray(root.adoptedStyleSheets, instance, matchingStyleRules); + matchingRules.push(...processSheetsArray(root.adoptedStyleSheets, instance)); } - processSheetsArray(root.styleSheets, instance, matchingStyleRules); + matchingRules.push(...processSheetsArray(root.styleSheets, instance)); } - return matchingStyleRules; + return matchingRules; } From 38a10cfd128ab0279419e83e6fa9c89d69f35475 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Tue, 8 Apr 2025 16:22:13 +0300 Subject: [PATCH 15/66] Revert unintended changes to dev-server config --- web-dev-server.config.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/web-dev-server.config.js b/web-dev-server.config.js index a7c38c75e3c..96794378fb6 100644 --- a/web-dev-server.config.js +++ b/web-dev-server.config.js @@ -26,6 +26,23 @@ function generatedLitTestsPlugin() { }; } +const preventFouc = ` + + + +`; + module.exports = { plugins: [ { @@ -34,6 +51,9 @@ module.exports = { if (context.response.is('html')) { let body = context.body; + // Fouc prevention + body = body.replace(/<\/body>/u, `${preventFouc}\n`); + // Index page listing if (['/dev/index.html', '/dev', '/dev/'].includes(context.path)) { const listing = ` From 31e6ef7574a9f7308aca50ba3795231d0fd18735 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Wed, 9 Apr 2025 10:07:56 +0300 Subject: [PATCH 16/66] Use html selector for consistency --- packages/vaadin-lumo-styles/color.css | 4 ++-- packages/vaadin-lumo-styles/sizing.css | 2 +- packages/vaadin-lumo-styles/spacing.css | 2 +- packages/vaadin-lumo-styles/style.css | 2 +- packages/vaadin-lumo-styles/typography.css | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/vaadin-lumo-styles/color.css b/packages/vaadin-lumo-styles/color.css index 732ae634b4b..1bbab3ce0ea 100644 --- a/packages/vaadin-lumo-styles/color.css +++ b/packages/vaadin-lumo-styles/color.css @@ -1,4 +1,4 @@ -:root { +html { /* Base (background) */ --lumo-base-color: #fff; @@ -78,7 +78,7 @@ /* forced-colors mode adjustments */ @media (forced-colors: active) { - :root { + html { --lumo-disabled-text-color: GrayText; } } diff --git a/packages/vaadin-lumo-styles/sizing.css b/packages/vaadin-lumo-styles/sizing.css index ef83fd803f0..ee163f09873 100644 --- a/packages/vaadin-lumo-styles/sizing.css +++ b/packages/vaadin-lumo-styles/sizing.css @@ -1,4 +1,4 @@ -:root { +html { --lumo-size-xs: 1.625rem; --lumo-size-s: 1.875rem; --lumo-size-m: 2.25rem; diff --git a/packages/vaadin-lumo-styles/spacing.css b/packages/vaadin-lumo-styles/spacing.css index 1e176129a7b..4d9c4a8af6b 100644 --- a/packages/vaadin-lumo-styles/spacing.css +++ b/packages/vaadin-lumo-styles/spacing.css @@ -1,4 +1,4 @@ -:root { +html { /* Square */ --lumo-space-xs: 0.25rem; --lumo-space-s: 0.5rem; diff --git a/packages/vaadin-lumo-styles/style.css b/packages/vaadin-lumo-styles/style.css index c5c447c01b7..98b4835a084 100644 --- a/packages/vaadin-lumo-styles/style.css +++ b/packages/vaadin-lumo-styles/style.css @@ -1,4 +1,4 @@ -:root { +html { /* Border radius */ --lumo-border-radius-s: 0.25em; /* Checkbox, badge, date-picker year indicator, etc */ --lumo-border-radius-m: var(--lumo-border-radius, 0.25em); /* Button, text field, menu overlay, etc */ diff --git a/packages/vaadin-lumo-styles/typography.css b/packages/vaadin-lumo-styles/typography.css index 2375744c694..1b928166f65 100644 --- a/packages/vaadin-lumo-styles/typography.css +++ b/packages/vaadin-lumo-styles/typography.css @@ -1,4 +1,4 @@ -:root { +html { /* prettier-ignore */ --lumo-font-family: -apple-system, BlinkMacSystemFont, 'Roboto', 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; From bea39efdb85b5ad1da35a1f83b782ed7e2276272 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Wed, 9 Apr 2025 11:42:04 +0300 Subject: [PATCH 17/66] Rewrite item and list-box to use shared styles --- .../vaadin-lumo-styles/components/item.css | 86 +--------------- .../components/list-box.css | 17 +--- .../components/shared/item.css | 97 +++++++++++++++++++ .../components/shared/list-box.css | 32 ++++++ 4 files changed, 131 insertions(+), 101 deletions(-) create mode 100644 packages/vaadin-lumo-styles/components/shared/item.css create mode 100644 packages/vaadin-lumo-styles/components/shared/list-box.css diff --git a/packages/vaadin-lumo-styles/components/item.css b/packages/vaadin-lumo-styles/components/item.css index b8f3f4cc16a..28e20996cd3 100644 --- a/packages/vaadin-lumo-styles/components/item.css +++ b/packages/vaadin-lumo-styles/components/item.css @@ -1,95 +1,11 @@ -/* -This file should presumably import files with custom CSS properties; -- import '@vaadin/vaadin-lumo-styles/font-icons.js'; -*/ @import '../color.css'; @import '../font-icons.css'; @import '../sizing.css'; @import '../spacing.css'; @import '../style.css'; @import '../typography.css'; +@import './shared/item.css' vaadin-item; html { --vaadin-item-css-inject: 1; } - -@media vaadin-item { - :host { - display: flex; - align-items: center; - box-sizing: border-box; - font-family: var(--lumo-font-family); - font-size: var(--lumo-font-size-m); - line-height: var(--lumo-line-height-xs); - padding: 0.5em calc(var(--lumo-space-l) + var(--lumo-border-radius-m) / 4) 0.5em - var(--_lumo-list-box-item-padding-left, calc(var(--lumo-border-radius-m) / 4)); - min-height: var(--lumo-size-m); - outline: none; - border-radius: var(--lumo-border-radius-m); - cursor: var(--lumo-clickable-cursor); - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-tap-highlight-color: var(--lumo-primary-color-10pct); - --_focus-ring-color: var(--vaadin-focus-ring-color, var(--lumo-primary-color-50pct)); - --_focus-ring-width: var(--vaadin-focus-ring-width, 2px); - --_selection-color-text: var(--vaadin-selection-color-text, var(--lumo-primary-text-color)); - } - - /* Checkmark */ - [part='checkmark']::before { - display: var(--_lumo-item-selected-icon-display, none); - content: var(--lumo-icons-checkmark); - font-family: lumo-icons; - font-size: var(--lumo-icon-size-m); - line-height: 1; - font-weight: normal; - width: 1em; - height: 1em; - margin: calc((1 - var(--lumo-line-height-xs)) * var(--lumo-font-size-m) / 2) 0; - color: var(--_selection-color-text); - flex: none; - opacity: 0; - transition: - transform 0.2s cubic-bezier(0.12, 0.32, 0.54, 2), - opacity 0.1s; - } - - :host([selected]) [part='checkmark']::before { - opacity: 1; - } - - :host([active]:not([selected])) [part='checkmark']::before { - transform: scale(0.8); - opacity: 0; - transition-duration: 0s; - } - - [part='content'] { - flex: auto; - } - - /* Disabled */ - :host([disabled]) { - color: var(--lumo-disabled-text-color); - cursor: default; - pointer-events: none; - } - - /* TODO a workaround until we have "focus-follows-mouse". After that, use the hover style for focus-ring as well */ - @media (any-hover: hover) { - :host(:hover:not([disabled])) { - background-color: var(--lumo-primary-color-10pct); - } - } - - :host([focus-ring]:not([disabled])) { - box-shadow: inset 0 0 0 var(--_focus-ring-width) var(--_focus-ring-color); - } - - /* RTL specific styles */ - :host([dir='rtl']) { - padding-left: calc(var(--lumo-space-l) + var(--lumo-border-radius-m) / 4); - padding-right: var(--_lumo-list-box-item-padding-left, calc(var(--lumo-border-radius-m) / 4)); - } - -} diff --git a/packages/vaadin-lumo-styles/components/list-box.css b/packages/vaadin-lumo-styles/components/list-box.css index 8ce1e47ebd0..de87173e659 100644 --- a/packages/vaadin-lumo-styles/components/list-box.css +++ b/packages/vaadin-lumo-styles/components/list-box.css @@ -1,23 +1,8 @@ @import '../color.css'; @import '../spacing.css'; @import '../style.css'; +@import './shared/list-box.css' vaadin-list-box; html { --vaadin-list-box-css-inject: 1; } - -@media vaadin-list-box { - :host { - -webkit-tap-highlight-color: transparent; - --_lumo-item-selected-icon-display: var(--_lumo-list-box-item-selected-icon-display, block); - } - - /* Dividers */ - [part='items'] ::slotted(hr) { - height: 1px; - border: 0; - padding: 0; - margin: var(--lumo-space-s) var(--lumo-border-radius-m); - background-color: var(--lumo-contrast-10pct); - } -} diff --git a/packages/vaadin-lumo-styles/components/shared/item.css b/packages/vaadin-lumo-styles/components/shared/item.css new file mode 100644 index 00000000000..f061d1cf33f --- /dev/null +++ b/packages/vaadin-lumo-styles/components/shared/item.css @@ -0,0 +1,97 @@ +/** + * Shared styles used by the following components: + * + * - `vaadin-item` + * - `vaadin-combo-box-item` + * - `vaadin-context-menu-item` + * - `vaadin-menu-bar-item` + * - `vaadin-select-item` + * - `vaadin-avatar-group-menu-item` + * + * Import this file only with proper media. Example: + * + * ``` + * @import './shared/item.css' vaadin-item; + * ``` + * + * NOTE: this file should not import dependencies e.g. `color.css` etc. + * Instead these should be loaded from corresponding components files. + */ + +:host { + display: flex; + align-items: center; + box-sizing: border-box; + font-family: var(--lumo-font-family); + font-size: var(--lumo-font-size-m); + line-height: var(--lumo-line-height-xs); + padding: 0.5em calc(var(--lumo-space-l) + var(--lumo-border-radius-m) / 4) 0.5em + var(--_lumo-list-box-item-padding-left, calc(var(--lumo-border-radius-m) / 4)); + min-height: var(--lumo-size-m); + outline: none; + border-radius: var(--lumo-border-radius-m); + cursor: var(--lumo-clickable-cursor); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-tap-highlight-color: var(--lumo-primary-color-10pct); + --_focus-ring-color: var(--vaadin-focus-ring-color, var(--lumo-primary-color-50pct)); + --_focus-ring-width: var(--vaadin-focus-ring-width, 2px); + --_selection-color-text: var(--vaadin-selection-color-text, var(--lumo-primary-text-color)); +} + +/* Checkmark */ +[part='checkmark']::before { + display: var(--_lumo-item-selected-icon-display, none); + content: var(--lumo-icons-checkmark); + font-family: lumo-icons; + font-size: var(--lumo-icon-size-m); + line-height: 1; + font-weight: normal; + width: 1em; + height: 1em; + margin: calc((1 - var(--lumo-line-height-xs)) * var(--lumo-font-size-m) / 2) 0; + color: var(--_selection-color-text); + flex: none; + opacity: 0; + transition: + transform 0.2s cubic-bezier(0.12, 0.32, 0.54, 2), + opacity 0.1s; +} + +:host([selected]) [part='checkmark']::before { + opacity: 1; +} + +:host([active]:not([selected])) [part='checkmark']::before { + transform: scale(0.8); + opacity: 0; + transition-duration: 0s; +} + +[part='content'] { + flex: auto; +} + +/* Disabled */ +:host([disabled]) { + color: var(--lumo-disabled-text-color); + cursor: default; + pointer-events: none; +} + +/* TODO a workaround until we have "focus-follows-mouse". After that, use the hover style for focus-ring as well */ +@media (any-hover: hover) { + :host(:hover:not([disabled])) { + background-color: var(--lumo-primary-color-10pct); + } +} + +:host([focus-ring]:not([disabled])) { + box-shadow: inset 0 0 0 var(--_focus-ring-width) var(--_focus-ring-color); +} + +/* RTL specific styles */ +:host([dir='rtl']) { + padding-left: calc(var(--lumo-space-l) + var(--lumo-border-radius-m) / 4); + padding-right: var(--_lumo-list-box-item-padding-left, calc(var(--lumo-border-radius-m) / 4)); +} diff --git a/packages/vaadin-lumo-styles/components/shared/list-box.css b/packages/vaadin-lumo-styles/components/shared/list-box.css new file mode 100644 index 00000000000..3d6b171bc91 --- /dev/null +++ b/packages/vaadin-lumo-styles/components/shared/list-box.css @@ -0,0 +1,32 @@ +/** + * Shared styles used by the following components: + * + * - `vaadin-list-box` + * - `vaadin-context-menu-list-box` + * - `vaadin-menu-bar-list-box` + * - `vaadin-select-list-box` + * - `vaadin-avatar-group-menu` + * + * Import this file only with proper media. Example: + * + * ``` + * @import './shared/list-box.css' vaadin-list-box; + * ``` + * + * NOTE: this file should not import dependencies e.g. `color.css` etc. + * Instead these should be loaded from corresponding components files. + */ + +:host { + -webkit-tap-highlight-color: transparent; + --_lumo-item-selected-icon-display: var(--_lumo-list-box-item-selected-icon-display, block); +} + +/* Dividers */ +[part='items'] ::slotted(hr) { + height: 1px; + border: 0; + padding: 0; + margin: var(--lumo-space-s) var(--lumo-border-radius-m); + background-color: var(--lumo-contrast-10pct); +} From 8726dc5a120b0b6ad4be84bb62402511f5d34703 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Wed, 9 Apr 2025 12:16:17 +0300 Subject: [PATCH 18/66] Adapt injecting logic to work with registerStyles API --- dev/lumo-injection/custom-styles-post.html | 39 +++++++++++++++++++ dev/lumo-injection/custom-styles-pre.html | 38 ++++++++++++++++++ .../component-base/src/css-injection-mixin.js | 4 +- packages/item/src/vaadin-lit-item.js | 14 ++++--- packages/list-box/src/vaadin-lit-list-box.js | 24 ++++++------ .../vaadin-themable-mixin.js | 9 ++++- 6 files changed, 109 insertions(+), 19 deletions(-) create mode 100644 dev/lumo-injection/custom-styles-post.html create mode 100644 dev/lumo-injection/custom-styles-pre.html diff --git a/dev/lumo-injection/custom-styles-post.html b/dev/lumo-injection/custom-styles-post.html new file mode 100644 index 00000000000..b26d130fa7e --- /dev/null +++ b/dev/lumo-injection/custom-styles-post.html @@ -0,0 +1,39 @@ + + + + + + + Custom styles post finalize + + + + + + + Basic item + Focused item + Selected item + + + + + diff --git a/dev/lumo-injection/custom-styles-pre.html b/dev/lumo-injection/custom-styles-pre.html new file mode 100644 index 00000000000..8edcabae215 --- /dev/null +++ b/dev/lumo-injection/custom-styles-pre.html @@ -0,0 +1,38 @@ + + + + + + + Custom styles pre finalize + + + + + + + Basic item + Focused item + Selected item + + + + + + + diff --git a/packages/component-base/src/css-injection-mixin.js b/packages/component-base/src/css-injection-mixin.js index 895bffac357..585581de01a 100644 --- a/packages/component-base/src/css-injection-mixin.js +++ b/packages/component-base/src/css-injection-mixin.js @@ -22,7 +22,9 @@ function injectInstanceStyles(el) { } }); - el.shadowRoot.adoptedStyleSheets.push(el.__injectedStyleSheet); + // Insert injected stylesheet as the first one to ensure it applies + // before any custom styles applied with `registerStyles()` API + el.shadowRoot.adoptedStyleSheets.unshift(el.__injectedStyleSheet); } } diff --git a/packages/item/src/vaadin-lit-item.js b/packages/item/src/vaadin-lit-item.js index bbea3a2fc8c..dbc7530bd30 100644 --- a/packages/item/src/vaadin-lit-item.js +++ b/packages/item/src/vaadin-lit-item.js @@ -27,12 +27,14 @@ class Item extends ItemMixin(CssInjectionMixin(ThemableMixin(DirMixin(PolylitMix static get styles() { return css` - :host { - display: inline-block; - } - - :host([hidden]) { - display: none !important; + @layer base { + :host { + display: inline-block; + } + + :host([hidden]) { + display: none !important; + } } `; } diff --git a/packages/list-box/src/vaadin-lit-list-box.js b/packages/list-box/src/vaadin-lit-list-box.js index 9b07e055e74..e37c8e75494 100644 --- a/packages/list-box/src/vaadin-lit-list-box.js +++ b/packages/list-box/src/vaadin-lit-list-box.js @@ -28,19 +28,21 @@ class ListBox extends CssInjectionMixin(ElementMixin(MultiSelectListMixin(Themab static get styles() { return css` - :host { - display: flex; - } + @layer base { + :host { + display: flex; + } - :host([hidden]) { - display: none !important; - } + :host([hidden]) { + display: none !important; + } - [part='items'] { - height: 100%; - width: 100%; - overflow-y: auto; - -webkit-overflow-scrolling: touch; + [part='items'] { + height: 100%; + width: 100%; + overflow-y: auto; + -webkit-overflow-scrolling: touch; + } } `; } diff --git a/packages/vaadin-themable-mixin/vaadin-themable-mixin.js b/packages/vaadin-themable-mixin/vaadin-themable-mixin.js index 3ff7e70e4a2..06e11fa5f16 100644 --- a/packages/vaadin-themable-mixin/vaadin-themable-mixin.js +++ b/packages/vaadin-themable-mixin/vaadin-themable-mixin.js @@ -120,8 +120,15 @@ function updateInstanceStyles(instance) { // Remove them first to avoid duplicates. [...instance.shadowRoot.querySelectorAll('style')].forEach((style) => style.remove()); + // There are some styles injected, we need to retain them and place + // before any custom styles. Note that `src` styles will end up after + // injected ones but those should use `@layer` for lower specificity. + const styles = instance.__injectedStyleSheet + ? [instance.__injectedStyleSheet, ...componentClass.elementStyles] + : componentClass.elementStyles; + // Adopt the updated styles - adoptStyles(instance.shadowRoot, componentClass.elementStyles); + adoptStyles(instance.shadowRoot, styles); } else { // PolymerElement From 1e25e8e2c5c4db53f382015ee7df2c2e1443f508 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Wed, 9 Apr 2025 14:03:58 +0300 Subject: [PATCH 19/66] Support document.adoptedStyleSheets --- dev/lumo-injection/dynamic.html | 1 - dev/lumo-injection/import-css.html | 36 +++++++++++++++++++ dev/lumo-injection/link-body.html | 2 +- .../component-base/src/css-injection-utils.js | 8 ++--- 4 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 dev/lumo-injection/import-css.html diff --git a/dev/lumo-injection/dynamic.html b/dev/lumo-injection/dynamic.html index 067e5ef3607..0535153cb66 100644 --- a/dev/lumo-injection/dynamic.html +++ b/dev/lumo-injection/dynamic.html @@ -20,7 +20,6 @@ + + diff --git a/dev/lumo-injection/link-body.html b/dev/lumo-injection/link-body.html index ea84a231298..10231bb9aaa 100644 --- a/dev/lumo-injection/link-body.html +++ b/dev/lumo-injection/link-body.html @@ -28,7 +28,7 @@ document.querySelector('button').addEventListener('click', () => { const shadow = document.querySelector('shadow-host').shadowRoot; - shadow.appendChild(document.querySelector('vaadin-item[selected]')); + shadow.appendChild(document.querySelector('vaadin-list-box]')); }); diff --git a/packages/component-base/src/css-injection-utils.js b/packages/component-base/src/css-injection-utils.js index 8a843af6dff..11d27211f19 100644 --- a/packages/component-base/src/css-injection-utils.js +++ b/packages/component-base/src/css-injection-utils.js @@ -108,11 +108,11 @@ function processSheetsArray(sheets, element) { export function gatherMatchingStyleRules(instance) { const matchingRules = []; - // TODO: also process `document.adoptedStyleSheets` to support importing - // CSS files from JS: `import '@vaadin/lumo/lumo.css' with { type: 'css' }` - // This would be convenient in some cases e.g. for Lumo visual tests + // Global stylesheets + if (document.adoptedStyleSheets) { + matchingRules.push(...processSheetsArray(document.adoptedStyleSheets, instance)); + } - // Page has already loaded, document.styleSheets is populated matchingRules.push(...processSheetsArray(document.styleSheets, instance)); // Scoped stylesheets From 581b30364be66b18207fca22d5247b66c66040b2 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Wed, 9 Apr 2025 14:20:34 +0300 Subject: [PATCH 20/66] Simplify code, add JSDoc comments --- .../component-base/src/css-injection-utils.js | 49 +++++++++++++------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/packages/component-base/src/css-injection-utils.js b/packages/component-base/src/css-injection-utils.js index 11d27211f19..edde132f80d 100644 --- a/packages/component-base/src/css-injection-utils.js +++ b/packages/component-base/src/css-injection-utils.js @@ -49,7 +49,13 @@ function matchesElement(el, media) { return undefined; } -// Recursively process a style sheet for matching rules +/** + * Recursively process a style sheet for matching rules + * + * @param {CSSStyleSheet} styleSheet + * @param {HTMLElement} element + * @param {Function} collectorFunc + */ function extractMatchingStyleRules(styleSheet, element, collectorFunc) { let media = ''; @@ -95,6 +101,11 @@ function extractMatchingStyleRules(styleSheet, element, collectorFunc) { } } +/** + * @param {StyleSheetList} sheets + * @param {HTMLElement} element + * @return {CSSRuleList[]} + */ function processSheetsArray(sheets, element) { const matchingRules = []; @@ -105,27 +116,37 @@ function processSheetsArray(sheets, element) { return matchingRules; } -export function gatherMatchingStyleRules(instance) { +/** + * @param {HTMLElement} instance + * @param {DocumentOrShadowRoot} root + * @return {CSSRuleList[]} + */ +function getMatchingCssRules(instance, root) { const matchingRules = []; - // Global stylesheets - if (document.adoptedStyleSheets) { - matchingRules.push(...processSheetsArray(document.adoptedStyleSheets, instance)); + if (root.adoptedStyleSheets) { + matchingRules.push(...processSheetsArray(root.adoptedStyleSheets, instance)); } - matchingRules.push(...processSheetsArray(document.styleSheets, instance)); + matchingRules.push(...processSheetsArray(root.styleSheets, instance)); - // Scoped stylesheets + return matchingRules; +} + +/** + * @param {HTMLElement} instance + * @return {CSSRuleList[]} + */ +export function gatherMatchingStyleRules(instance) { + const matchingRules = []; + + // Global stylesheets + matchingRules.push(...getMatchingCssRules(instance, document)); - // NOTE: can be needed for embedded components where Lumo will only - // be loaded in a shadow root but should not leak to the host page. - // See e.g. https://github.com/vaadin/flow/issues/12704 + // Scoped stylesheets const root = instance.getRootNode(); if (root !== document) { - if (root.adoptedStyleSheets) { - matchingRules.push(...processSheetsArray(root.adoptedStyleSheets, instance)); - } - matchingRules.push(...processSheetsArray(root.styleSheets, instance)); + matchingRules.push(...getMatchingCssRules(instance, root)); } return matchingRules; From 49e2f13502765751bdda20437f54bbbbef2bb36e Mon Sep 17 00:00:00 2001 From: web-padawan Date: Wed, 9 Apr 2025 14:47:03 +0300 Subject: [PATCH 21/66] Update to use element for consistency --- .../component-base/src/css-injection-utils.js | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/component-base/src/css-injection-utils.js b/packages/component-base/src/css-injection-utils.js index edde132f80d..40e052af03c 100644 --- a/packages/component-base/src/css-injection-utils.js +++ b/packages/component-base/src/css-injection-utils.js @@ -117,36 +117,36 @@ function processSheetsArray(sheets, element) { } /** - * @param {HTMLElement} instance + * @param {HTMLElement} element * @param {DocumentOrShadowRoot} root * @return {CSSRuleList[]} */ -function getMatchingCssRules(instance, root) { +function getMatchingCssRules(element, root) { const matchingRules = []; if (root.adoptedStyleSheets) { - matchingRules.push(...processSheetsArray(root.adoptedStyleSheets, instance)); + matchingRules.push(...processSheetsArray(root.adoptedStyleSheets, element)); } - matchingRules.push(...processSheetsArray(root.styleSheets, instance)); + matchingRules.push(...processSheetsArray(root.styleSheets, element)); return matchingRules; } /** - * @param {HTMLElement} instance + * @param {HTMLElement} element * @return {CSSRuleList[]} */ -export function gatherMatchingStyleRules(instance) { +export function gatherMatchingStyleRules(element) { const matchingRules = []; // Global stylesheets - matchingRules.push(...getMatchingCssRules(instance, document)); + matchingRules.push(...getMatchingCssRules(element, document)); // Scoped stylesheets - const root = instance.getRootNode(); + const root = element.getRootNode(); if (root !== document) { - matchingRules.push(...getMatchingCssRules(instance, root)); + matchingRules.push(...getMatchingCssRules(element, root)); } return matchingRules; From f151b26c4c8ec7503e3cd2fb2aa5a381914c8c07 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Wed, 9 Apr 2025 14:51:02 +0300 Subject: [PATCH 22/66] Add more JSDoc annotations --- .../component-base/src/css-injection-mixin.js | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/packages/component-base/src/css-injection-mixin.js b/packages/component-base/src/css-injection-mixin.js index 585581de01a..38a2e56fb81 100644 --- a/packages/component-base/src/css-injection-mixin.js +++ b/packages/component-base/src/css-injection-mixin.js @@ -10,30 +10,42 @@ const injectedClasses = new Set(); const injectableInstances = new Set(); -function injectInstanceStyles(el) { - const rules = gatherMatchingStyleRules(el); +/** + * @param {HTMLElement} element + */ +function injectInstanceStyles(element) { + const rules = gatherMatchingStyleRules(element); if (rules.length > 0) { - el.__injectedStyleSheet = new CSSStyleSheet(); + element.__injectedStyleSheet = new CSSStyleSheet(); rules.forEach((ruleList) => { for (const rule of ruleList) { - el.__injectedStyleSheet.insertRule(rule.cssText, el.__injectedStyleSheet.cssRules.length); + element.__injectedStyleSheet.insertRule(rule.cssText, element.__injectedStyleSheet.cssRules.length); } }); // Insert injected stylesheet as the first one to ensure it applies // before any custom styles applied with `registerStyles()` API - el.shadowRoot.adoptedStyleSheets.unshift(el.__injectedStyleSheet); + element.shadowRoot.adoptedStyleSheets.unshift(element.__injectedStyleSheet); } } -function cleanupInstanceStyles(el) { - if (el.__injectedStyleSheet) { - el.shadowRoot.adoptedStyleSheets.splice(el.shadowRoot.adoptedStyleSheets.indexOf(el.__injectedStyleSheet, 1)); +/** + * @param {HTMLElement} element + */ +function cleanupInstanceStyles(element) { + if (element.__injectedStyleSheet) { + element.shadowRoot.adoptedStyleSheets.splice( + element.shadowRoot.adoptedStyleSheets.indexOf(element.__injectedStyleSheet, 1), + ); } } +/** + * Dynamically injects styles to the instances matching the given component type. + * @param {Function} componentClass + */ function injectClassInstanceStyles(componentClass) { injectableInstances.forEach((ref) => { const instance = ref.deref(); @@ -46,6 +58,10 @@ function injectClassInstanceStyles(componentClass) { }); } +/** + * Removes styles from the instances matching the given component type. + * @param {Function} componentClass + */ function cleanupClassInstanceStyles(componentClass) { injectableInstances.forEach((ref) => { const instance = ref.deref(); From df918b7e72465d2644065e2e5e02d9f3ca302979 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Wed, 9 Apr 2025 15:41:52 +0300 Subject: [PATCH 23/66] Fix cleanup instance styles logic --- packages/component-base/src/css-injection-mixin.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/component-base/src/css-injection-mixin.js b/packages/component-base/src/css-injection-mixin.js index 38a2e56fb81..bf4b172a0ed 100644 --- a/packages/component-base/src/css-injection-mixin.js +++ b/packages/component-base/src/css-injection-mixin.js @@ -37,7 +37,8 @@ function injectInstanceStyles(element) { function cleanupInstanceStyles(element) { if (element.__injectedStyleSheet) { element.shadowRoot.adoptedStyleSheets.splice( - element.shadowRoot.adoptedStyleSheets.indexOf(element.__injectedStyleSheet, 1), + element.shadowRoot.adoptedStyleSheets.indexOf(element.__injectedStyleSheet), + 1, ); } } From 3bb8873e2a40f883b41270127d31865fdd817a1c Mon Sep 17 00:00:00 2001 From: web-padawan Date: Wed, 9 Apr 2025 15:42:42 +0300 Subject: [PATCH 24/66] Store tag names instead of component classes --- .../component-base/src/css-injection-mixin.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/component-base/src/css-injection-mixin.js b/packages/component-base/src/css-injection-mixin.js index bf4b172a0ed..7f8eb987d9f 100644 --- a/packages/component-base/src/css-injection-mixin.js +++ b/packages/component-base/src/css-injection-mixin.js @@ -6,8 +6,14 @@ import StyleObserver from 'style-observer'; import { gatherMatchingStyleRules } from './css-injection-utils.js'; -const injectedClasses = new Set(); +/** + * @type {string[]} + */ +const injectableTagNames = new Set(); +/** + * @type {WeakRef[]} + */ const injectableInstances = new Set(); /** @@ -85,12 +91,12 @@ const observer = new StyleObserver((records) => { if (componentClass) { if (value === '1') { // Allow future instances inject own styles - injectedClasses.add(componentClass); + injectableTagNames.add(tagName); // Inject styles for already existing instances injectClassInstanceStyles(componentClass); } else if (oldValue === '1') { // Disallow future instances inject own styles - injectedClasses.delete(componentClass); + injectableTagNames.delete(tagName); // Cleanup styles for already existing instances cleanupClassInstanceStyles(componentClass); } @@ -115,7 +121,7 @@ export const CssInjectionMixin = (superClass) => // in a registry so that evert instance of it would auto-inject styles const value = getComputedStyle(document.documentElement).getPropertyValue(propName); if (value === '1') { - injectedClasses.add(this); + injectableTagNames.add(this.is); } // Initialize custom property for this class with 0 as default @@ -142,7 +148,7 @@ export const CssInjectionMixin = (superClass) => connectedCallback() { super.connectedCallback(); - if (!injectedClasses.has(this.constructor)) { + if (!injectableTagNames.has(this.constructor.is)) { return; } From c1d8c43d82665b9cbefab1d7c89034710890feb6 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Wed, 9 Apr 2025 15:47:07 +0300 Subject: [PATCH 25/66] Move CSS injection logic to ThemableMixin package --- packages/component-base/package.json | 3 +-- packages/item/src/vaadin-lit-item.js | 2 +- packages/list-box/src/vaadin-lit-list-box.js | 2 +- .../src => vaadin-themable-mixin}/css-injection-mixin.js | 0 .../src => vaadin-themable-mixin}/css-injection-utils.js | 0 packages/vaadin-themable-mixin/package.json | 3 ++- 6 files changed, 5 insertions(+), 5 deletions(-) rename packages/{component-base/src => vaadin-themable-mixin}/css-injection-mixin.js (100%) rename packages/{component-base/src => vaadin-themable-mixin}/css-injection-utils.js (100%) diff --git a/packages/component-base/package.json b/packages/component-base/package.json index 0b36dde1c1f..11aca6fd1c1 100644 --- a/packages/component-base/package.json +++ b/packages/component-base/package.json @@ -35,8 +35,7 @@ "@polymer/polymer": "^3.0.0", "@vaadin/vaadin-development-mode-detector": "^2.0.0", "@vaadin/vaadin-usage-statistics": "^2.1.0", - "lit": "^3.0.0", - "style-observer": "^0.0.5" + "lit": "^3.0.0" }, "devDependencies": { "@vaadin/chai-plugins": "24.8.0-alpha13", diff --git a/packages/item/src/vaadin-lit-item.js b/packages/item/src/vaadin-lit-item.js index dbc7530bd30..e6749114d27 100644 --- a/packages/item/src/vaadin-lit-item.js +++ b/packages/item/src/vaadin-lit-item.js @@ -4,10 +4,10 @@ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ */ import { css, html, LitElement } from 'lit'; -import { CssInjectionMixin } from '@vaadin/component-base/src/css-injection-mixin.js'; import { defineCustomElement } from '@vaadin/component-base/src/define.js'; import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; +import { CssInjectionMixin } from '@vaadin/vaadin-themable-mixin/css-injection-mixin.js'; import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; import { ItemMixin } from './vaadin-item-mixin.js'; diff --git a/packages/list-box/src/vaadin-lit-list-box.js b/packages/list-box/src/vaadin-lit-list-box.js index e37c8e75494..0d8fa9c6f27 100644 --- a/packages/list-box/src/vaadin-lit-list-box.js +++ b/packages/list-box/src/vaadin-lit-list-box.js @@ -4,11 +4,11 @@ * This program is available under Apache License Version 2.0, available at https://vaadin.com/license/ */ import { css, html, LitElement } from 'lit'; -import { CssInjectionMixin } from '@vaadin/component-base/src/css-injection-mixin.js'; import { defineCustomElement } from '@vaadin/component-base/src/define.js'; import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js'; +import { CssInjectionMixin } from '@vaadin/vaadin-themable-mixin/css-injection-mixin.js'; import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; import { MultiSelectListMixin } from './vaadin-multi-select-list-mixin.js'; diff --git a/packages/component-base/src/css-injection-mixin.js b/packages/vaadin-themable-mixin/css-injection-mixin.js similarity index 100% rename from packages/component-base/src/css-injection-mixin.js rename to packages/vaadin-themable-mixin/css-injection-mixin.js diff --git a/packages/component-base/src/css-injection-utils.js b/packages/vaadin-themable-mixin/css-injection-utils.js similarity index 100% rename from packages/component-base/src/css-injection-utils.js rename to packages/vaadin-themable-mixin/css-injection-utils.js diff --git a/packages/vaadin-themable-mixin/package.json b/packages/vaadin-themable-mixin/package.json index 6f3f2d5dae6..abdf6da111e 100644 --- a/packages/vaadin-themable-mixin/package.json +++ b/packages/vaadin-themable-mixin/package.json @@ -32,7 +32,8 @@ ], "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", - "lit": "^3.0.0" + "lit": "^3.0.0", + "style-observer": "^0.0.5" }, "devDependencies": { "@polymer/polymer": "^3.0.0", From ad4cfcd61f247240290cc65f69e6fe1ab81b9fad Mon Sep 17 00:00:00 2001 From: web-padawan Date: Wed, 9 Apr 2025 15:57:30 +0300 Subject: [PATCH 26/66] Add comments about web-dev-server --- dev/lumo-injection/style-head.html | 4 ++-- dev/lumo-injection/styles/item.css | 2 ++ dev/lumo-injection/styles/list-box.css | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dev/lumo-injection/style-head.html b/dev/lumo-injection/style-head.html index 25766828ba5..f563cced154 100644 --- a/dev/lumo-injection/style-head.html +++ b/dev/lumo-injection/style-head.html @@ -7,8 +7,8 @@ Style in head diff --git a/dev/lumo-injection/styles/item.css b/dev/lumo-injection/styles/item.css index ee2b8d84842..b8963ce6d7b 100644 --- a/dev/lumo-injection/styles/item.css +++ b/dev/lumo-injection/styles/item.css @@ -1 +1,3 @@ @import '@vaadin/vaadin-lumo-styles/components/item.css'; +/* For web-dev-server use the following instead */ +/* @import '../../../packages/vaadin-lumo-styles/components/item.css'; */ diff --git a/dev/lumo-injection/styles/list-box.css b/dev/lumo-injection/styles/list-box.css index bf98a21158b..4c2dc27465f 100644 --- a/dev/lumo-injection/styles/list-box.css +++ b/dev/lumo-injection/styles/list-box.css @@ -1 +1,3 @@ @import '@vaadin/vaadin-lumo-styles/components/list-box.css'; +/* For web-dev-server use the following instead */ +/* @import '../../../packages/vaadin-lumo-styles/components/list-box.css'; */ From 35dea3219d5f4b7c5d7000795b1cdb134e6817f7 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Wed, 9 Apr 2025 16:56:26 +0300 Subject: [PATCH 27/66] Do not use incorrect import syntax --- dev/lumo-injection/styles/common.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dev/lumo-injection/styles/common.css b/dev/lumo-injection/styles/common.css index effa94e47aa..927c47bb8c1 100644 --- a/dev/lumo-injection/styles/common.css +++ b/dev/lumo-injection/styles/common.css @@ -1,2 +1,2 @@ -@import url("item.css"); -@import url("list-box.css"); +@import './item.css'; +@import './list-box.css'; From d9fe1a8fa4af3055b3164b92386df933ac863e34 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Wed, 9 Apr 2025 17:11:29 +0300 Subject: [PATCH 28/66] Add web-dev-server plugin to inline CSS imports --- dev/lumo-injection/styles/item.css | 3 +-- dev/lumo-injection/styles/list-box.css | 3 +-- package.json | 2 ++ web-dev-server.config.js | 24 ++++++++++++++++++++++++ yarn.lock | 18 +++++++++++++++++- 5 files changed, 45 insertions(+), 5 deletions(-) diff --git a/dev/lumo-injection/styles/item.css b/dev/lumo-injection/styles/item.css index b8963ce6d7b..254c14ba9ce 100644 --- a/dev/lumo-injection/styles/item.css +++ b/dev/lumo-injection/styles/item.css @@ -1,3 +1,2 @@ +/* The following will be inlined using postcss-import */ @import '@vaadin/vaadin-lumo-styles/components/item.css'; -/* For web-dev-server use the following instead */ -/* @import '../../../packages/vaadin-lumo-styles/components/item.css'; */ diff --git a/dev/lumo-injection/styles/list-box.css b/dev/lumo-injection/styles/list-box.css index 4c2dc27465f..4b39085c3ef 100644 --- a/dev/lumo-injection/styles/list-box.css +++ b/dev/lumo-injection/styles/list-box.css @@ -1,3 +1,2 @@ +/* The following will be inlined using postcss-import */ @import '@vaadin/vaadin-lumo-styles/components/list-box.css'; -/* For web-dev-server use the following instead */ -/* @import '../../../packages/vaadin-lumo-styles/components/list-box.css'; */ diff --git a/package.json b/package.json index c693acf3bfc..fc8ba550a30 100644 --- a/package.json +++ b/package.json @@ -60,6 +60,8 @@ "lint-staged": "^15.2.9", "npm-run-all": "^4.1.5", "patch-package": "^7.0.0", + "postcss": "^8.5.3", + "postcss-import": "^16.1.0", "postcss-lit": "^1.1.0", "prettier": "^3.1.0", "prettier-plugin-package": "^1.3.0", diff --git a/web-dev-server.config.js b/web-dev-server.config.js index 96794378fb6..dd4b36b39d2 100644 --- a/web-dev-server.config.js +++ b/web-dev-server.config.js @@ -2,6 +2,9 @@ const fs = require('fs'); const { esbuildPlugin } = require('@web/dev-server-esbuild'); const path = require('path'); +const { getRequestFilePath } = require('@web/dev-server-core'); +const postcss = require('postcss'); +const atImport = require('postcss-import'); /** @return {import('@web/test-runner').TestRunnerPlugin} */ function generatedLitTestsPlugin() { @@ -26,6 +29,26 @@ function generatedLitTestsPlugin() { }; } +function inlineCssImportsPlugin() { + let rootDir; + + return { + name: 'inline-css-imports', + + serverStart(args) { + ({ rootDir } = args.config); + }, + + async transform(context) { + if (context.response.is('css') && context.body.includes('@import')) { + const filePath = getRequestFilePath(context.url, rootDir); + const { css } = await postcss([atImport()]).process(context.body, { from: filePath }); + return { body: css }; + } + }, + }; +} + const preventFouc = ` diff --git a/dev/lumo-injection/styles/base.css b/dev/lumo-injection/styles/base.css new file mode 100644 index 00000000000..4eaedaf0cd4 --- /dev/null +++ b/dev/lumo-injection/styles/base.css @@ -0,0 +1 @@ +@import '@vaadin/vaadin-lumo-styles/base/base.css'; diff --git a/dev/lumo-injection/styles/common.css b/dev/lumo-injection/styles/components.css similarity index 100% rename from dev/lumo-injection/styles/common.css rename to dev/lumo-injection/styles/components.css diff --git a/dev/lumo-injection/styles/item.css b/dev/lumo-injection/styles/item.css index 254c14ba9ce..ee2b8d84842 100644 --- a/dev/lumo-injection/styles/item.css +++ b/dev/lumo-injection/styles/item.css @@ -1,2 +1 @@ -/* The following will be inlined using postcss-import */ @import '@vaadin/vaadin-lumo-styles/components/item.css'; diff --git a/dev/lumo-injection/styles/list-box.css b/dev/lumo-injection/styles/list-box.css index 4b39085c3ef..bf98a21158b 100644 --- a/dev/lumo-injection/styles/list-box.css +++ b/dev/lumo-injection/styles/list-box.css @@ -1,2 +1 @@ -/* The following will be inlined using postcss-import */ @import '@vaadin/vaadin-lumo-styles/components/list-box.css'; diff --git a/packages/vaadin-lumo-styles/base/base.css b/packages/vaadin-lumo-styles/base/base.css new file mode 100644 index 00000000000..2982f72d2fa --- /dev/null +++ b/packages/vaadin-lumo-styles/base/base.css @@ -0,0 +1,6 @@ +@import './color.css'; +@import './font-icons.css'; +@import './sizing.css'; +@import './spacing.css'; +@import './style.css'; +@import './typography.css'; diff --git a/packages/vaadin-lumo-styles/color.css b/packages/vaadin-lumo-styles/base/color.css similarity index 100% rename from packages/vaadin-lumo-styles/color.css rename to packages/vaadin-lumo-styles/base/color.css diff --git a/packages/vaadin-lumo-styles/font-icons.css b/packages/vaadin-lumo-styles/base/font-icons.css similarity index 100% rename from packages/vaadin-lumo-styles/font-icons.css rename to packages/vaadin-lumo-styles/base/font-icons.css diff --git a/packages/vaadin-lumo-styles/sizing.css b/packages/vaadin-lumo-styles/base/sizing.css similarity index 100% rename from packages/vaadin-lumo-styles/sizing.css rename to packages/vaadin-lumo-styles/base/sizing.css diff --git a/packages/vaadin-lumo-styles/spacing.css b/packages/vaadin-lumo-styles/base/spacing.css similarity index 100% rename from packages/vaadin-lumo-styles/spacing.css rename to packages/vaadin-lumo-styles/base/spacing.css diff --git a/packages/vaadin-lumo-styles/style.css b/packages/vaadin-lumo-styles/base/style.css similarity index 100% rename from packages/vaadin-lumo-styles/style.css rename to packages/vaadin-lumo-styles/base/style.css diff --git a/packages/vaadin-lumo-styles/typography.css b/packages/vaadin-lumo-styles/base/typography.css similarity index 100% rename from packages/vaadin-lumo-styles/typography.css rename to packages/vaadin-lumo-styles/base/typography.css diff --git a/packages/vaadin-lumo-styles/components/item.css b/packages/vaadin-lumo-styles/components/item.css index 28e20996cd3..765e0f5eb9d 100644 --- a/packages/vaadin-lumo-styles/components/item.css +++ b/packages/vaadin-lumo-styles/components/item.css @@ -1,9 +1,3 @@ -@import '../color.css'; -@import '../font-icons.css'; -@import '../sizing.css'; -@import '../spacing.css'; -@import '../style.css'; -@import '../typography.css'; @import './shared/item.css' vaadin-item; html { diff --git a/packages/vaadin-lumo-styles/components/list-box.css b/packages/vaadin-lumo-styles/components/list-box.css index de87173e659..774f8ca3944 100644 --- a/packages/vaadin-lumo-styles/components/list-box.css +++ b/packages/vaadin-lumo-styles/components/list-box.css @@ -1,6 +1,3 @@ -@import '../color.css'; -@import '../spacing.css'; -@import '../style.css'; @import './shared/list-box.css' vaadin-list-box; html { From 24310c7887175898bd673d192d64ce45e8415627 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Thu, 10 Apr 2025 11:58:11 +0300 Subject: [PATCH 30/66] Implement observing shadow root hosts --- dev/lumo-injection/link-body.html | 17 ++- dev/lumo-injection/link-head.html | 17 ++- dev/lumo-injection/shadow.html | 48 ++++++++ dev/lumo-injection/style-head.html | 17 ++- .../vaadin-lumo-styles/components/item.css | 2 +- .../components/list-box.css | 2 +- .../css-injection-mixin.js | 111 +++++++++++------- 7 files changed, 159 insertions(+), 55 deletions(-) create mode 100644 dev/lumo-injection/shadow.html diff --git a/dev/lumo-injection/link-body.html b/dev/lumo-injection/link-body.html index 769650e63cf..d41336fc290 100644 --- a/dev/lumo-injection/link-body.html +++ b/dev/lumo-injection/link-body.html @@ -20,16 +20,25 @@ - +
+ + + diff --git a/dev/lumo-injection/link-head.html b/dev/lumo-injection/link-head.html index a98ad66a74c..51ec1f282ad 100644 --- a/dev/lumo-injection/link-head.html +++ b/dev/lumo-injection/link-head.html @@ -19,16 +19,25 @@ - +
+ + + diff --git a/dev/lumo-injection/shadow.html b/dev/lumo-injection/shadow.html new file mode 100644 index 00000000000..8f8624198b6 --- /dev/null +++ b/dev/lumo-injection/shadow.html @@ -0,0 +1,48 @@ + + + + + + + Shadow styles + + + + + + Basic item + Focused item + Selected item + + + + +
+ + + + + + + diff --git a/dev/lumo-injection/style-head.html b/dev/lumo-injection/style-head.html index a76af948496..c300266f92a 100644 --- a/dev/lumo-injection/style-head.html +++ b/dev/lumo-injection/style-head.html @@ -21,16 +21,25 @@ - +
+ + + diff --git a/packages/vaadin-lumo-styles/components/item.css b/packages/vaadin-lumo-styles/components/item.css index 765e0f5eb9d..2ba48baa7e4 100644 --- a/packages/vaadin-lumo-styles/components/item.css +++ b/packages/vaadin-lumo-styles/components/item.css @@ -1,5 +1,5 @@ @import './shared/item.css' vaadin-item; -html { +html, :host { --vaadin-item-css-inject: 1; } diff --git a/packages/vaadin-lumo-styles/components/list-box.css b/packages/vaadin-lumo-styles/components/list-box.css index 774f8ca3944..e48436d12c9 100644 --- a/packages/vaadin-lumo-styles/components/list-box.css +++ b/packages/vaadin-lumo-styles/components/list-box.css @@ -1,5 +1,5 @@ @import './shared/list-box.css' vaadin-list-box; -html { +html, :host { --vaadin-list-box-css-inject: 1; } diff --git a/packages/vaadin-themable-mixin/css-injection-mixin.js b/packages/vaadin-themable-mixin/css-injection-mixin.js index 7f8eb987d9f..b4082d2b74e 100644 --- a/packages/vaadin-themable-mixin/css-injection-mixin.js +++ b/packages/vaadin-themable-mixin/css-injection-mixin.js @@ -7,14 +7,26 @@ import StyleObserver from 'style-observer'; import { gatherMatchingStyleRules } from './css-injection-utils.js'; /** - * @type {string[]} + * @type {WeakMap>} */ -const injectableTagNames = new Set(); +const observedHosts = new WeakMap(); /** - * @type {WeakRef[]} + * Gets or creates an object with the stored values for root. + * + * @param {HTMLElement} host reference to the document or shadow root host + * + * @return {WeakMap} a weak map with the stored values for the elements being controlled by the helper */ -const injectableInstances = new Set(); +function getHostMap(host) { + if (!observedHosts.has(host)) { + observedHosts.set(host, { + tagNames: new Set(), + instances: new Set(), + }); + } + return observedHosts.get(host); +} /** * @param {HTMLElement} element @@ -52,15 +64,12 @@ function cleanupInstanceStyles(element) { /** * Dynamically injects styles to the instances matching the given component type. * @param {Function} componentClass + * @param {Set} instances */ -function injectClassInstanceStyles(componentClass) { - injectableInstances.forEach((ref) => { - const instance = ref.deref(); +function injectClassInstanceStyles(componentClass, instances) { + instances.forEach((instance) => { if (instance instanceof componentClass) { injectInstanceStyles(instance); - } else if (!instance) { - // Clean up the weak reference to a GC'd instance - injectableInstances.delete(ref); } }); } @@ -68,42 +77,58 @@ function injectClassInstanceStyles(componentClass) { /** * Removes styles from the instances matching the given component type. * @param {Function} componentClass + * @param {Set} instances */ -function cleanupClassInstanceStyles(componentClass) { - injectableInstances.forEach((ref) => { - const instance = ref.deref(); +function cleanupClassInstanceStyles(componentClass, instances) { + instances.forEach((instance) => { if (instance instanceof componentClass) { cleanupInstanceStyles(instance); - } else if (!instance) { - // Clean up the weak reference to a GC'd instance - injectableInstances.delete(ref); } }); } const observer = new StyleObserver((records) => { records.forEach((record) => { - const { property, value, oldValue } = record; + const { property, value, oldValue, target } = record; const tagName = property.slice(2).replace('-css-inject', ''); const componentClass = customElements.get(tagName); if (componentClass) { + // Only apply styles changes to given host + const hostMap = getHostMap(target); + if (value === '1') { // Allow future instances inject own styles - injectableTagNames.add(tagName); + hostMap.tagNames.add(tagName); // Inject styles for already existing instances - injectClassInstanceStyles(componentClass); + injectClassInstanceStyles(componentClass, hostMap.instances); } else if (oldValue === '1') { // Disallow future instances inject own styles - injectableTagNames.delete(tagName); + hostMap.tagNames.delete(tagName); // Cleanup styles for already existing instances - cleanupClassInstanceStyles(componentClass); + cleanupClassInstanceStyles(componentClass, hostMap.instances); } } }); }); +function observeHost(componentClass, host) { + const { cssInjectPropName, is } = componentClass; + + const hostMap = getHostMap(host); + + // If styles for custom property are already loaded for this root, + // store corresponding tag name so that we can inject styles + const value = getComputedStyle(host).getPropertyValue(cssInjectPropName); + if (value === '1') { + hostMap.tagNames.add(is); + } + + // Observe custom property that would trigger injection for this class + observer.observe(host, cssInjectPropName); +} + /** * Mixin for internal use only. Do not use it in custom components. * @@ -115,50 +140,54 @@ export const CssInjectionMixin = (superClass) => super.finalize(); if (this.is) { - const propName = `--${this.is}-css-inject`; - - // If styles for custom property are already loaded, store this class - // in a registry so that evert instance of it would auto-inject styles - const value = getComputedStyle(document.documentElement).getPropertyValue(propName); - if (value === '1') { - injectableTagNames.add(this.is); - } + const propName = this.cssInjectPropName; // Initialize custom property for this class with 0 as default // so that changing it to 1 would inject styles to instances + // Use `inherits: true` so that property defined on `` + // would apply to components instances within shadow roots CSS.registerProperty({ name: propName, syntax: '', - inherits: false, + inherits: true, initialValue: '0', }); - - // Observe custom property that would trigger injection for this class - observer.observe(document.documentElement, propName); } } - constructor() { - super(); - // Store a weak reference to the instance - injectableInstances.add(new WeakRef(this)); + static get cssInjectPropName() { + return `--${this.is}-css-inject`; } /** @protected */ connectedCallback() { super.connectedCallback(); - if (!injectableTagNames.has(this.constructor.is)) { - return; - } + // Detect if we are in a document or shadow root + const root = this.getRootNode(); + const host = root === document ? root.documentElement : root.host; - injectInstanceStyles(this); + // Store this instance in the map for given host + this.__storedHost = host; + getHostMap(host).instances.add(this); + + // Observe host for custom CSS property injection + observeHost(this.constructor, host); + + // If custom CSS property is already set, inject styles + if (getHostMap(host).tagNames.has(this.constructor.is)) { + injectInstanceStyles(this); + } } /** @protected */ disconnectedCallback() { super.disconnectedCallback(); + // Cleanup instance from the previous host + getHostMap(this.__storedHost).instances.delete(this); + this.__storedHost = undefined; + cleanupInstanceStyles(this); } }; From f0c627475a0a5c21a6a898b5cc9793644c07fcd1 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Thu, 10 Apr 2025 12:02:37 +0300 Subject: [PATCH 31/66] Fix dynamic loading dev page --- dev/lumo-injection/dynamic.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/dev/lumo-injection/dynamic.html b/dev/lumo-injection/dynamic.html index 8f21235a3a7..8c371db5375 100644 --- a/dev/lumo-injection/dynamic.html +++ b/dev/lumo-injection/dynamic.html @@ -22,15 +22,15 @@ import '@vaadin/item/src/vaadin-lit-item.js'; import '@vaadin/list-box/src/vaadin-lit-list-box.js'; + const link = document.createElement('link'); + link.href = './styles/components.css'; + link.rel = 'stylesheet'; + document.querySelector('#unload').addEventListener('click', () => { - const link = document.head.querySelector('link[rel="stylesheet"]'); - link?.remove(); + link.remove(); }); document.querySelector('#load').addEventListener('click', () => { - const link = document.createElement('link'); - link.href = './styles/components.css'; - link.rel = 'stylesheet'; document.head.appendChild(link); }); From 94f9205a39b6b86161b2ad883d3529f42829d61f Mon Sep 17 00:00:00 2001 From: web-padawan Date: Thu, 10 Apr 2025 12:09:31 +0300 Subject: [PATCH 32/66] Update style-observer to latest version --- packages/vaadin-themable-mixin/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/vaadin-themable-mixin/package.json b/packages/vaadin-themable-mixin/package.json index abdf6da111e..64024801ef8 100644 --- a/packages/vaadin-themable-mixin/package.json +++ b/packages/vaadin-themable-mixin/package.json @@ -33,7 +33,7 @@ "dependencies": { "@open-wc/dedupe-mixin": "^1.3.0", "lit": "^3.0.0", - "style-observer": "^0.0.5" + "style-observer": "^0.0.6" }, "devDependencies": { "@polymer/polymer": "^3.0.0", diff --git a/yarn.lock b/yarn.lock index 0754232e1e1..c999021b686 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11978,10 +11978,10 @@ strong-log-transformer@^2.1.0: minimist "^1.2.0" through "^2.3.4" -style-observer@^0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/style-observer/-/style-observer-0.0.5.tgz#2efbac9a6ebb4bf926c18862ef61f1cd0873a1bb" - integrity sha512-d5ieqbpC6URZmZlemdYDUcGSh6hCqIODWppBCGUfJDylVprE20hqAo9aHetb43kCrHc/1kNaikrQgbE5+kBWkA== +style-observer@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/style-observer/-/style-observer-0.0.6.tgz#a5276994a33b582d7f596f4c25d6c7aeedc07737" + integrity sha512-fHEcwwuH2s93YHM1byMCxp7cGakEh1ad/SPJQjcgaLyjANa7QNt/xZgv153OLBoaRGnqJ+b4Vfrf64s8ORExzw== stylelint-config-vaadin@^1.0.0-alpha.2: version "1.0.0-alpha.2" From d23fb4a8bd654332e9a8c3aab5a80534b2dfd0cc Mon Sep 17 00:00:00 2001 From: web-padawan Date: Thu, 10 Apr 2025 14:16:39 +0300 Subject: [PATCH 33/66] Fix JSDoc annotations for host map --- packages/vaadin-themable-mixin/css-injection-mixin.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/vaadin-themable-mixin/css-injection-mixin.js b/packages/vaadin-themable-mixin/css-injection-mixin.js index b4082d2b74e..869fffa2551 100644 --- a/packages/vaadin-themable-mixin/css-injection-mixin.js +++ b/packages/vaadin-themable-mixin/css-injection-mixin.js @@ -7,16 +7,15 @@ import StyleObserver from 'style-observer'; import { gatherMatchingStyleRules } from './css-injection-utils.js'; /** - * @type {WeakMap>} + * @type {WeakMap} */ const observedHosts = new WeakMap(); /** - * Gets or creates an object with the stored values for root. + * Gets or creates an object with the stored values for given host. * - * @param {HTMLElement} host reference to the document or shadow root host - * - * @return {WeakMap} a weak map with the stored values for the elements being controlled by the helper + * @param {HTMLElement} host reference to the `` element or shadow root host + * @return {object} an object with tag names and instances for given host */ function getHostMap(host) { if (!observedHosts.has(host)) { From 7ecfee1082616a4fc726defa926692346dbdc49a Mon Sep 17 00:00:00 2001 From: web-padawan Date: Thu, 10 Apr 2025 15:20:40 +0300 Subject: [PATCH 34/66] Add text-field and API to observe parent root --- dev/lumo-injection/dynamic.html | 9 +- dev/lumo-injection/link-body.html | 30 +-- dev/lumo-injection/link-head.html | 30 +-- dev/lumo-injection/style-head.html | 30 +-- dev/lumo-injection/styles/components.css | 1 + dev/lumo-injection/styles/text-field.css | 1 + .../src/vaadin-lit-input-container.js | 13 +- .../text-field/src/vaadin-lit-text-field.js | 5 +- .../components/input-container.css | 5 + .../components/shared/field-button.css | 40 ++++ .../components/shared/helper.css | 67 ++++++ .../components/shared/input-container.css | 190 ++++++++++++++++++ .../components/shared/input-field.css | 171 ++++++++++++++++ .../components/shared/required-field.css | 113 +++++++++++ .../components/text-field.css | 10 + .../css-injection-mixin.js | 28 ++- 16 files changed, 700 insertions(+), 43 deletions(-) create mode 100644 dev/lumo-injection/styles/text-field.css create mode 100644 packages/vaadin-lumo-styles/components/input-container.css create mode 100644 packages/vaadin-lumo-styles/components/shared/field-button.css create mode 100644 packages/vaadin-lumo-styles/components/shared/helper.css create mode 100644 packages/vaadin-lumo-styles/components/shared/input-container.css create mode 100644 packages/vaadin-lumo-styles/components/shared/input-field.css create mode 100644 packages/vaadin-lumo-styles/components/shared/required-field.css create mode 100644 packages/vaadin-lumo-styles/components/text-field.css diff --git a/dev/lumo-injection/dynamic.html b/dev/lumo-injection/dynamic.html index 8c371db5375..09a95c1a3e6 100644 --- a/dev/lumo-injection/dynamic.html +++ b/dev/lumo-injection/dynamic.html @@ -15,12 +15,17 @@ Selected item - - + + +

+ + +

diff --git a/dev/lumo-injection/link-head.html b/dev/lumo-injection/link-head.html index 51ec1f282ad..b0601851c78 100644 --- a/dev/lumo-injection/link-head.html +++ b/dev/lumo-injection/link-head.html @@ -9,35 +9,41 @@ + - - Basic item - Focused item - Selected item - +
+ + Basic item + Focused item + Selected item + - + +
-
+ - - +

+ + +

diff --git a/dev/lumo-injection/style-head.html b/dev/lumo-injection/style-head.html index c300266f92a..7fc009bebf9 100644 --- a/dev/lumo-injection/style-head.html +++ b/dev/lumo-injection/style-head.html @@ -10,36 +10,42 @@ @import './styles/base.css'; @import './styles/item.css'; @import './styles/list-box.css'; + @import './styles/text-field.css'; - - Basic item - Focused item - Selected item - +
+ + Basic item + Focused item + Selected item + - + +
-
+ - - +

+ + +

diff --git a/dev/lumo-injection/styles/components.css b/dev/lumo-injection/styles/components.css index 927c47bb8c1..ae010435d85 100644 --- a/dev/lumo-injection/styles/components.css +++ b/dev/lumo-injection/styles/components.css @@ -1,2 +1,3 @@ @import './item.css'; @import './list-box.css'; +@import './text-field.css'; diff --git a/dev/lumo-injection/styles/text-field.css b/dev/lumo-injection/styles/text-field.css new file mode 100644 index 00000000000..252a09b9964 --- /dev/null +++ b/dev/lumo-injection/styles/text-field.css @@ -0,0 +1 @@ +@import '@vaadin/vaadin-lumo-styles/components/text-field.css'; diff --git a/packages/input-container/src/vaadin-lit-input-container.js b/packages/input-container/src/vaadin-lit-input-container.js index 41683f73158..8d5b9d4b47c 100644 --- a/packages/input-container/src/vaadin-lit-input-container.js +++ b/packages/input-container/src/vaadin-lit-input-container.js @@ -7,6 +7,7 @@ import { html, LitElement } from 'lit'; import { defineCustomElement } from '@vaadin/component-base/src/define.js'; import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; +import { CssInjectionMixin } from '@vaadin/vaadin-themable-mixin/css-injection-mixin.js'; import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; import { InputContainerMixin } from './vaadin-input-container-mixin.js'; import { inputContainerStyles } from './vaadin-input-container-styles.js'; @@ -25,7 +26,9 @@ import { inputContainerStyles } from './vaadin-input-container-styles.js'; * @mixes DirMixin * @mixes InputContainerMixin */ -export class InputContainer extends InputContainerMixin(ThemableMixin(DirMixin(PolylitMixin(LitElement)))) { +export class InputContainer extends CssInjectionMixin( + InputContainerMixin(ThemableMixin(DirMixin(PolylitMixin(LitElement)))), +) { static get is() { return 'vaadin-input-container'; } @@ -34,6 +37,14 @@ export class InputContainer extends InputContainerMixin(ThemableMixin(DirMixin(P return inputContainerStyles; } + /** + * Override to indicate that this component should use its parent + * root host for detecting style injection custom CSS properties. + */ + static get cssInjectParentRoot() { + return true; + } + /** @protected */ render() { return html` diff --git a/packages/text-field/src/vaadin-lit-text-field.js b/packages/text-field/src/vaadin-lit-text-field.js index c3df7dba145..9f020f2f3d3 100644 --- a/packages/text-field/src/vaadin-lit-text-field.js +++ b/packages/text-field/src/vaadin-lit-text-field.js @@ -11,6 +11,7 @@ import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js'; import { inputFieldShared } from '@vaadin/field-base/src/styles/input-field-shared-styles.js'; +import { CssInjectionMixin } from '@vaadin/vaadin-themable-mixin/css-injection-mixin.js'; import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; import { TextFieldMixin } from './vaadin-text-field-mixin.js'; @@ -23,7 +24,9 @@ import { TextFieldMixin } from './vaadin-text-field-mixin.js'; * There is no ETA regarding specific Vaadin version where it'll land. * Feel free to try this code in your apps as per Apache 2.0 license. */ -export class TextField extends TextFieldMixin(ThemableMixin(ElementMixin(PolylitMixin(LitElement)))) { +export class TextField extends CssInjectionMixin( + TextFieldMixin(ThemableMixin(ElementMixin(PolylitMixin(LitElement)))), +) { static get is() { return 'vaadin-text-field'; } diff --git a/packages/vaadin-lumo-styles/components/input-container.css b/packages/vaadin-lumo-styles/components/input-container.css new file mode 100644 index 00000000000..2d5885d3b96 --- /dev/null +++ b/packages/vaadin-lumo-styles/components/input-container.css @@ -0,0 +1,5 @@ +@import './shared/input-container.css' vaadin-input-container; + +html, :host { + --vaadin-input-container-css-inject: 1; +} diff --git a/packages/vaadin-lumo-styles/components/shared/field-button.css b/packages/vaadin-lumo-styles/components/shared/field-button.css new file mode 100644 index 00000000000..ad9e642780c --- /dev/null +++ b/packages/vaadin-lumo-styles/components/shared/field-button.css @@ -0,0 +1,40 @@ +/** + * Shared styles used by all field components that have icon buttons. + * Example: `toggle-button` and `clear-button` parts in combo-box. + * + * Import this file only with proper media. Example: + * + * ``` + * @import './shared/field-button.css' vaadin-text-field; + * ``` + * + * NOTE: this file should not import dependencies e.g. `color.css` etc. + * Instead these should be loaded from corresponding components files. + */ + +[part$='button'] { + flex: none; + width: 1em; + height: 1em; + line-height: 1; + font-size: var(--lumo-icon-size-m); + text-align: center; + color: var(--lumo-contrast-60pct); + transition: 0.2s color; + cursor: var(--lumo-clickable-cursor); +} + +[part$='button']:hover { + color: var(--lumo-contrast-90pct); +} + +:host([disabled]) [part$='button'], +:host([readonly]) [part$='button'] { + color: var(--lumo-contrast-20pct); + cursor: default; +} + +[part$='button']::before { + font-family: 'lumo-icons'; + display: block; +} diff --git a/packages/vaadin-lumo-styles/components/shared/helper.css b/packages/vaadin-lumo-styles/components/shared/helper.css new file mode 100644 index 00000000000..840f82e7a30 --- /dev/null +++ b/packages/vaadin-lumo-styles/components/shared/helper.css @@ -0,0 +1,67 @@ +/** + * Shared styles used by all field components that support helper text. + * Import this file only with proper media. Example: + * + * ``` + * @import './shared/helper.css' vaadin-text-field; + * ``` + * + * NOTE: this file should not import dependencies e.g. `color.css` etc. + * Instead these should be loaded from corresponding components files. + */ + +:host { + --_helper-spacing: var(--vaadin-input-field-helper-spacing, 0.4em); +} + +:host([has-helper]) [part='helper-text']::before { + content: ''; + display: block; + height: var(--_helper-spacing); +} + +[part='helper-text'] { + display: block; + color: var(--vaadin-input-field-helper-color, var(--lumo-secondary-text-color)); + font-size: var(--vaadin-input-field-helper-font-size, var(--lumo-font-size-xs)); + line-height: var(--lumo-line-height-xs); + font-weight: var(--vaadin-input-field-helper-font-weight, 400); + margin-left: calc(var(--lumo-border-radius-m) / 4); + transition: color 0.2s; +} + +:host(:hover:not([readonly])) [part='helper-text'] { + color: var(--lumo-body-text-color); +} + +:host([disabled]) [part='helper-text'] { + color: var(--lumo-disabled-text-color); + -webkit-text-fill-color: var(--lumo-disabled-text-color); +} + +:host([has-helper][theme~='helper-above-field']) [part='helper-text']::before { + display: none; +} + +:host([has-helper][theme~='helper-above-field']) [part='helper-text']::after { + content: ''; + display: block; + height: var(--_helper-spacing); +} + +:host([has-helper][theme~='helper-above-field']) [part='label'] { + order: 0; + padding-bottom: var(--_helper-spacing); +} + +:host([has-helper][theme~='helper-above-field']) [part='helper-text'] { + order: 1; +} + +:host([has-helper][theme~='helper-above-field']) [part='label'] + * { + order: 2; +} + +:host([has-helper][theme~='helper-above-field']) [part='error-message'] { + order: 3; +} diff --git a/packages/vaadin-lumo-styles/components/shared/input-container.css b/packages/vaadin-lumo-styles/components/shared/input-container.css new file mode 100644 index 00000000000..4162260c0fc --- /dev/null +++ b/packages/vaadin-lumo-styles/components/shared/input-container.css @@ -0,0 +1,190 @@ +/** + * Shared styles used by all field components that use input container. + * + * Import this file only with proper media. Example: + * + * ``` + * @import './shared/input-container.css' vaadin-input-container; + * ``` + * + * NOTE: this file should not import dependencies e.g. `color.css` etc. + * Instead these should be loaded from corresponding components files. + */ + +:host { + background: var(--_background); + padding: 0 calc(0.375em + var(--_input-container-radius) / 4 - 1px); + font-weight: var(--vaadin-input-field-value-font-weight, 500); + line-height: 1; + position: relative; + cursor: text; + box-sizing: border-box; + border-radius: + /* See https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius#syntax */ + var(--vaadin-input-field-top-start-radius, var(--_input-container-radius)) + var(--vaadin-input-field-top-end-radius, var(--_input-container-radius)) + var(--vaadin-input-field-bottom-end-radius, var(--_input-container-radius)) + var(--vaadin-input-field-bottom-start-radius, var(--_input-container-radius)); + /* Fallback */ + --_input-container-radius: var(--vaadin-input-field-border-radius, var(--lumo-border-radius-m)); + --_input-height: var(--lumo-text-field-size, var(--lumo-size-m)); + /* Default values */ + --_background: var(--vaadin-input-field-background, var(--lumo-contrast-10pct)); + --_hover-highlight: var(--vaadin-input-field-hover-highlight, var(--lumo-contrast-50pct)); + --_input-border-color: var(--vaadin-input-field-border-color, var(--lumo-contrast-50pct)); + --_icon-color: var(--vaadin-input-field-icon-color, var(--lumo-contrast-60pct)); + --_icon-size: var(--vaadin-input-field-icon-size, var(--lumo-icon-size-m)); + --_invalid-background: var(--vaadin-input-field-invalid-background, var(--lumo-error-color-10pct)); + --_invalid-hover-highlight: var(--vaadin-input-field-invalid-hover-highlight, var(--lumo-error-color-50pct)); + --_disabled-background: var(--vaadin-input-field-disabled-background, var(--lumo-contrast-5pct)); + --_disabled-value-color: var(--vaadin-input-field-disabled-value-color, var(--lumo-disabled-text-color)); +} + +:host([dir='rtl']) { + border-radius: + /* Don't use logical props, see https://github.com/vaadin/vaadin-time-picker/issues/145 */ + var(--vaadin-input-field-top-end-radius, var(--_input-container-radius)) + var(--vaadin-input-field-top-start-radius, var(--_input-container-radius)) + var(--vaadin-input-field-bottom-start-radius, var(--_input-container-radius)) + var(--vaadin-input-field-bottom-end-radius, var(--_input-container-radius)); +} + +/* Used for hover and activation effects */ +:host::after { + content: ''; + position: absolute; + inset: 0; + border-radius: inherit; + pointer-events: none; + background: var(--_hover-highlight); + opacity: 0; + transition: + transform 0.15s, + opacity 0.2s; + transform-origin: 100% 0; +} + +::slotted(:not([slot$='fix'])) { + cursor: inherit; + min-height: var(--vaadin-input-field-height, var(--_input-height)); + padding: 0 0.25em; + --_lumo-text-field-overflow-mask-image: linear-gradient(to left, transparent, #000 1.25em); + -webkit-mask-image: var(--_lumo-text-field-overflow-mask-image); + mask-image: var(--_lumo-text-field-overflow-mask-image); +} + +/* Read-only */ +:host([readonly]) { + color: var(--lumo-secondary-text-color); + background-color: transparent; + cursor: default; +} + +:host([readonly])::after { + background-color: transparent; + opacity: 1; + border: var(--vaadin-input-field-readonly-border, 1px dashed var(--lumo-contrast-30pct)); +} + +/* Disabled */ +:host([disabled]) { + background: var(--_disabled-background); +} + +:host([disabled]) ::slotted(:not([slot$='fix'])) { + -webkit-text-fill-color: var(--_disabled-value-color); + color: var(--_disabled-value-color); +} + +/* Invalid */ +:host([invalid]) { + background: var(--_invalid-background); +} + +:host([invalid]:not([readonly]))::after { + background: var(--_invalid-hover-highlight); +} + +/* Slotted icons */ +::slotted(vaadin-icon) { + color: var(--_icon-color); + width: var(--_icon-size); + height: var(--_icon-size); +} + +/* Vaadin icons are based on a 16x16 grid (unlike Lumo and Material icons with 24x24), so they look too big by default */ +::slotted(vaadin-icon[icon^='vaadin:']) { + padding: 0.25em; + box-sizing: border-box !important; +} + +/* Text align */ +:host([dir='rtl']) ::slotted(:not([slot$='fix'])) { + --_lumo-text-field-overflow-mask-image: linear-gradient(to right, transparent, #000 1.25em); +} + +@-moz-document url-prefix() { + :host([dir='rtl']) ::slotted(:not([slot$='fix'])) { + mask-image: var(--_lumo-text-field-overflow-mask-image); + } +} + +:host([theme~='align-left']) ::slotted(:not([slot$='fix'])) { + text-align: start; + --_lumo-text-field-overflow-mask-image: none; +} + +:host([theme~='align-center']) ::slotted(:not([slot$='fix'])) { + text-align: center; + --_lumo-text-field-overflow-mask-image: none; +} + +:host([theme~='align-right']) ::slotted(:not([slot$='fix'])) { + text-align: end; + --_lumo-text-field-overflow-mask-image: none; +} + +@-moz-document url-prefix() { + /* Firefox is smart enough to align overflowing text to right */ + :host([theme~='align-right']) ::slotted(:not([slot$='fix'])) { + --_lumo-text-field-overflow-mask-image: linear-gradient(to right, transparent 0.25em, #000 1.5em); + } +} + +@-moz-document url-prefix() { + /* Firefox is smart enough to align overflowing text to right */ + :host([theme~='align-left']) ::slotted(:not([slot$='fix'])) { + --_lumo-text-field-overflow-mask-image: linear-gradient(to left, transparent 0.25em, #000 1.5em); + } +} + +/* RTL specific styles */ +:host([dir='rtl'])::after { + transform-origin: 0% 0; +} + +:host([theme~='align-left'][dir='rtl']) ::slotted(:not([slot$='fix'])) { + --_lumo-text-field-overflow-mask-image: none; +} + +:host([theme~='align-center'][dir='rtl']) ::slotted(:not([slot$='fix'])) { + --_lumo-text-field-overflow-mask-image: none; +} + +:host([theme~='align-right'][dir='rtl']) ::slotted(:not([slot$='fix'])) { + --_lumo-text-field-overflow-mask-image: none; +} + +@-moz-document url-prefix() { + /* Firefox is smart enough to align overflowing text to right */ + :host([theme~='align-right'][dir='rtl']) ::slotted(:not([slot$='fix'])) { + --_lumo-text-field-overflow-mask-image: linear-gradient(to right, transparent 0.25em, #000 1.5em); + } +} + +@-moz-document url-prefix() { + /* Firefox is smart enough to align overflowing text to right */ + :host([theme~='align-left'][dir='rtl']) ::slotted(:not([slot$='fix'])) { + --_lumo-text-field-overflow-mask-image: linear-gradient(to left, transparent 0.25em, #000 1.5em); + } +} diff --git a/packages/vaadin-lumo-styles/components/shared/input-field.css b/packages/vaadin-lumo-styles/components/shared/input-field.css new file mode 100644 index 00000000000..d8df38add5d --- /dev/null +++ b/packages/vaadin-lumo-styles/components/shared/input-field.css @@ -0,0 +1,171 @@ +/** + * Shared styles used by the following components: + * + * - `vaadin-text-field` + * - `vaadin-text-area` + * - `vaadin-password-field` + * - `vaadin-number-field` + * - `vaadin-integer-field` + * - `vaadin-email-field` + * - `vaadin-combo-box` + * - `vaadin-date-picker` + * - `vaadin-time-picker` + * - `vaadin-multi-select-combo-box` + * + * Import this file only with proper media. Example: + * + * ``` + * @import './shared/input-field.css' vaadin-text-field; + * ``` + * + * NOTE: this file should not import dependencies e.g. `color.css` etc. + * Instead these should be loaded from corresponding components files. + */ + +:host { + --lumo-text-field-size: var(--lumo-size-m); + color: var(--vaadin-input-field-value-color, var(--lumo-body-text-color)); + font-size: var(--vaadin-input-field-value-font-size, var(--lumo-font-size-m)); + font-family: var(--lumo-font-family); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-tap-highlight-color: transparent; + padding: var(--lumo-space-xs) 0; + --_focus-ring-color: var(--vaadin-focus-ring-color, var(--lumo-primary-color-50pct)); + --_focus-ring-width: var(--vaadin-focus-ring-width, 2px); + --_input-height: var(--vaadin-input-field-height, var(--lumo-text-field-size)); + --_disabled-value-color: var(--vaadin-input-field-disabled-value-color, var(--lumo-disabled-text-color)); +} + +:host::before { + height: var(--_input-height); + box-sizing: border-box; + display: inline-flex; + align-items: center; +} + +:host([focused]) [part='input-field'] ::slotted(:is(input, textarea)) { + -webkit-mask-image: none; + mask-image: none; +} + +::slotted(:is(input, textarea):placeholder-shown) { + color: var(--vaadin-input-field-placeholder-color, var(--lumo-secondary-text-color)); +} + +/* Hover */ +:host(:hover:not([readonly]):not([focused]):not([disabled])) [part='input-field']::after { + opacity: var(--vaadin-input-field-hover-highlight-opacity, 0.1); +} + +/* Touch device adjustment */ +@media (pointer: coarse) { + :host(:hover:not([readonly]):not([focused]):not([disabled])) [part='input-field']::after { + opacity: 0; + } + + :host(:active:not([readonly]):not([focused]):not([disabled])) [part='input-field']::after { + opacity: 0.2; + } +} + +/* Trigger when not focusing using the keyboard */ +:host([focused]:not([focus-ring]):not([readonly])) [part='input-field']::after { + transform: scaleX(0); + transition-duration: 0.15s, 1s; +} + +/* Opt-in focus-ring when using pointer devices */ +/* This applies a focus-ring as box-shadow when the element is focused, but + the ring is only visible / has a width when the respective CSS property is + "enabled" using a value of 1 */ +:host([focused]) [part='input-field'] { + /* Borders are implemented using box-shadows as well. To avoid overriding + the border on focus, even if the pointer focus-ring is disabled, we need to: + - Duplicate the border box shadow for this rule + - Remove the border (by using width of 0) when the focus-ring is visible, + which is the same behavior as for the keyboard focus-ring below + - Apply the border when the focus ring is not visible + */ + --_pointer-focus-visible: clamp(0, var(--lumo-input-field-pointer-focus-visible, 0), 1); + --_conditional-border-width: calc(calc(1 - var(--_pointer-focus-visible)) * var(--_input-border-width)); + --_conditional-focus-ring-width: calc(var(--_pointer-focus-visible) * var(--_focus-ring-width)); + box-shadow: + inset 0 0 0 var(--_conditional-border-width) var(--_input-border-color), + 0 0 0 var(--_conditional-focus-ring-width) var(--_focus-ring-color); +} + +/* Focus-ring when using keyboard navigation */ +:host([focus-ring]) [part='input-field'] { + box-shadow: 0 0 0 var(--_focus-ring-width) var(--_focus-ring-color); +} + +/* Read-only and disabled */ +:host(:is([readonly], [disabled])) ::slotted(:is(input, textarea):placeholder-shown) { + opacity: 0; +} + +/* Read-only style */ +:host([readonly]) { + --vaadin-input-field-border-color: transparent; +} + +/* Disabled style */ +:host([disabled]) { + pointer-events: none; + --vaadin-input-field-border-color: var(--lumo-contrast-20pct); +} + +:host([disabled]) [part='label'], +:host([disabled]) [part='input-field'] ::slotted([slot$='fix']) { + color: var(--lumo-disabled-text-color); + -webkit-text-fill-color: var(--lumo-disabled-text-color); +} + +:host([disabled]) [part='input-field'] ::slotted(:not([slot$='fix'])) { + color: var(--_disabled-value-color); + -webkit-text-fill-color: var(--_disabled-value-color); +} + +/* Invalid style */ +:host([invalid]) { + --vaadin-input-field-border-color: var(--lumo-error-color); + --_focus-ring-color: var(--lumo-error-color-50pct); +} + +:host([input-prevented]) [part='input-field'] { + animation: shake 0.15s infinite; +} + +@keyframes shake { + 25% { + transform: translateX(4px); + } + 75% { + transform: translateX(-4px); + } +} + +/* Small theme */ +:host([theme~='small']) { + font-size: var(--lumo-font-size-s); + --lumo-text-field-size: var(--lumo-size-s); +} + +:host([theme~='small']) [part='label'] { + font-size: var(--lumo-font-size-xs); +} + +:host([theme~='small']) [part='error-message'] { + font-size: var(--lumo-font-size-xxs); +} + +/* Slotted content */ +[part='input-field'] ::slotted(:not(vaadin-icon):not(input):not(textarea)) { + color: var(--lumo-secondary-text-color); + font-weight: 400; +} + +[part='clear-button']::before { + content: var(--lumo-icons-cross); +} diff --git a/packages/vaadin-lumo-styles/components/shared/required-field.css b/packages/vaadin-lumo-styles/components/shared/required-field.css new file mode 100644 index 00000000000..c759f2b05ed --- /dev/null +++ b/packages/vaadin-lumo-styles/components/shared/required-field.css @@ -0,0 +1,113 @@ +/** + * Shared styles used by all field components that support validation. + * Import this file only with proper media. Example: + * + * ``` + * @import './shared/required-field.css' vaadin-text-field; + * ``` + * + * NOTE: this file should not import dependencies e.g. `color.css` etc. + * Instead these should be loaded from corresponding components files. + */ + +[part='label'] { + align-self: flex-start; + color: var(--vaadin-input-field-label-color, var(--lumo-secondary-text-color)); + font-weight: var(--vaadin-input-field-label-font-weight, 500); + font-size: var(--vaadin-input-field-label-font-size, var(--lumo-font-size-s)); + transition: color 0.2s; + line-height: 1; + padding-inline-start: calc(var(--lumo-border-radius-m) / 4); + padding-inline-end: 1em; + padding-bottom: 0.5em; + /* As a workaround for diacritics being cut off, add a top padding and a + negative margin to compensate */ + padding-top: 0.25em; + margin-top: -0.25em; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + position: relative; + max-width: 100%; + box-sizing: border-box; +} + +:host([focused]:not([readonly])) [part='label'] { + color: var(--vaadin-input-field-focused-label-color, var(--lumo-primary-text-color)); +} + +:host(:hover:not([readonly]):not([focused])) [part='label'] { + color: var(--vaadin-input-field-hovered-label-color, var(--lumo-body-text-color)); +} + +/* Touch device adjustment */ +@media (pointer: coarse) { + :host(:hover:not([readonly]):not([focused])) [part='label'] { + color: var(--vaadin-input-field-label-color, var(--lumo-secondary-text-color)); + } +} + +:host([has-label])::before { + margin-top: calc(var(--lumo-font-size-s) * 1.5); +} + +:host([has-label][theme~='small'])::before { + margin-top: calc(var(--lumo-font-size-xs) * 1.5); +} + +:host([has-label]) { + padding-top: var(--lumo-space-m); +} + +:host([has-label]) ::slotted([slot='tooltip']) { + --vaadin-tooltip-offset-bottom: calc((var(--lumo-space-m) - var(--lumo-space-xs)) * -1); +} + +:host([required]) [part='required-indicator']::after { + content: var(--lumo-required-field-indicator, '\\2022'); + transition: opacity 0.2s; + color: var(--lumo-required-field-indicator-color, var(--lumo-primary-text-color)); + position: absolute; + right: 0; + width: 1em; + text-align: center; +} + +:host([invalid]) [part='required-indicator']::after { + color: var(--lumo-required-field-indicator-color, var(--lumo-error-text-color)); +} + +[part='error-message'] { + margin-left: calc(var(--lumo-border-radius-m) / 4); + font-size: var(--vaadin-input-field-error-font-size, var(--lumo-font-size-xs)); + line-height: var(--lumo-line-height-xs); + font-weight: var(--vaadin-input-field-error-font-weight, 400); + color: var(--vaadin-input-field-error-color, var(--lumo-error-text-color)); + will-change: max-height; + transition: 0.4s max-height; + max-height: 5em; +} + +:host([has-error-message]) [part='error-message']::before, +:host([has-error-message]) [part='error-message']::after { + content: ''; + display: block; + height: 0.4em; +} + +:host(:not([invalid])) [part='error-message'] { + max-height: 0; + overflow: hidden; +} + +/* RTL specific styles */ + +:host([dir='rtl']) [part='required-indicator']::after { + right: auto; + left: 0; +} + +:host([dir='rtl']) [part='error-message'] { + margin-left: 0; + margin-right: calc(var(--lumo-border-radius-m) / 4); +} diff --git a/packages/vaadin-lumo-styles/components/text-field.css b/packages/vaadin-lumo-styles/components/text-field.css new file mode 100644 index 00000000000..aa90eaf5264 --- /dev/null +++ b/packages/vaadin-lumo-styles/components/text-field.css @@ -0,0 +1,10 @@ +@import './input-container.css'; + +@import './shared/field-button.css' vaadin-text-field; +@import './shared/helper.css' vaadin-text-field; +@import './shared/input-field.css' vaadin-text-field; +@import './shared/required-field.css' vaadin-text-field; + +html, :host { + --vaadin-text-field-css-inject: 1; +} diff --git a/packages/vaadin-themable-mixin/css-injection-mixin.js b/packages/vaadin-themable-mixin/css-injection-mixin.js index 869fffa2551..b7305f0997a 100644 --- a/packages/vaadin-themable-mixin/css-injection-mixin.js +++ b/packages/vaadin-themable-mixin/css-injection-mixin.js @@ -128,6 +128,22 @@ function observeHost(componentClass, host) { observer.observe(host, cssInjectPropName); } +/** + * Detect whether the element should use its own host, or rely on its parent + * (which is the case for components that are always used in shadow roots). + * + * @param {HTMLElement} element + * @return {HTMLElement} + */ +function findHost(element) { + const root = element.getRootNode(); + const host = root === document ? root.documentElement : root.host; + + // If this element has flag indicating it should be nested, use its parent + // Example: `vaadin-input-container` using its parent `vaadin-text-field`. + return element.constructor.cssInjectParentRoot ? findHost(host) : host; +} + /** * Mixin for internal use only. Do not use it in custom components. * @@ -158,13 +174,19 @@ export const CssInjectionMixin = (superClass) => return `--${this.is}-css-inject`; } + /** + * Override for components + */ + static get cssInjectParentRoot() { + return false; + } + /** @protected */ connectedCallback() { super.connectedCallback(); - // Detect if we are in a document or shadow root - const root = this.getRootNode(); - const host = root === document ? root.documentElement : root.host; + // Find proper host element to observe + const host = findHost(this); // Store this instance in the map for given host this.__storedHost = host; From cc0d6bb82350c61cd9bcc20bf1a272353bf13aa2 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Thu, 10 Apr 2025 16:06:33 +0300 Subject: [PATCH 35/66] Find proper root for gathering matching styles --- dev/lumo-injection/shadow.html | 29 ++++---- .../src/vaadin-lit-input-container.js | 8 --- .../css-injection-mixin.js | 70 +++++++++++-------- .../css-injection-utils.js | 3 +- 4 files changed, 60 insertions(+), 50 deletions(-) diff --git a/dev/lumo-injection/shadow.html b/dev/lumo-injection/shadow.html index 8f8624198b6..96af8700d4f 100644 --- a/dev/lumo-injection/shadow.html +++ b/dev/lumo-injection/shadow.html @@ -9,33 +9,38 @@ - - Basic item - Focused item - Selected item - +
+ + Basic item + Focused item + Selected item + - + +
-
+ - - +

+ + +

- - diff --git a/dev/lumo-injection/custom-styles-pre.html b/dev/lumo-injection/custom-styles-pre.html deleted file mode 100644 index db069721dd6..00000000000 --- a/dev/lumo-injection/custom-styles-pre.html +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - Custom styles pre finalize - - - - - - - - Basic item - Focused item - Selected item - - - - - - - diff --git a/dev/lumo-injection/dynamic.html b/dev/lumo-injection/dynamic.html deleted file mode 100644 index 09a95c1a3e6..00000000000 --- a/dev/lumo-injection/dynamic.html +++ /dev/null @@ -1,43 +0,0 @@ - - - - - - - Dynamic loading - - - - - - Basic item - Focused item - Selected item - - - - -

- - -

- - - - diff --git a/dev/lumo-injection/import-css.html b/dev/lumo-injection/import-css.html deleted file mode 100644 index 1f797060334..00000000000 --- a/dev/lumo-injection/import-css.html +++ /dev/null @@ -1,37 +0,0 @@ - - - - - - - Import CSS - - - - - - Basic item - Focused item - Selected item - - - - - - - - - diff --git a/dev/lumo-injection/link-body.html b/dev/lumo-injection/link-body.html deleted file mode 100644 index d7ffe29bb8a..00000000000 --- a/dev/lumo-injection/link-body.html +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - Link in body - - - - - - - - -
- - Basic item - Focused item - Selected item - - - -
- - - -

- - -

- - - - diff --git a/dev/lumo-injection/link-head.html b/dev/lumo-injection/link-head.html deleted file mode 100644 index b0601851c78..00000000000 --- a/dev/lumo-injection/link-head.html +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - Link in head - - - - - - - -
- - Basic item - Focused item - Selected item - - - -
- - - -

- - -

- - - - diff --git a/dev/lumo-injection/shadow-host.js b/dev/lumo-injection/shadow-host.js deleted file mode 100644 index f22f19b6aba..00000000000 --- a/dev/lumo-injection/shadow-host.js +++ /dev/null @@ -1,10 +0,0 @@ -customElements.define( - 'shadow-host', - class extends HTMLElement { - constructor() { - super(); - - this.attachShadow({ mode: 'open' }); - } - }, -); diff --git a/dev/lumo-injection/shadow.html b/dev/lumo-injection/shadow.html deleted file mode 100644 index 96af8700d4f..00000000000 --- a/dev/lumo-injection/shadow.html +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - Shadow styles - - - - -
- - Basic item - Focused item - Selected item - - - -
- - - -

- - -

- - - - diff --git a/dev/lumo-injection/style-head.html b/dev/lumo-injection/style-head.html deleted file mode 100644 index 7fc009bebf9..00000000000 --- a/dev/lumo-injection/style-head.html +++ /dev/null @@ -1,52 +0,0 @@ - - - - - - - Style in head - - - - -
- - Basic item - Focused item - Selected item - - - -
- - - -

- - -

- - - - diff --git a/dev/lumo-injection/styles/base.css b/dev/lumo-injection/styles/base.css deleted file mode 100644 index 4eaedaf0cd4..00000000000 --- a/dev/lumo-injection/styles/base.css +++ /dev/null @@ -1 +0,0 @@ -@import '@vaadin/vaadin-lumo-styles/base/base.css'; diff --git a/dev/lumo-injection/styles/components.css b/dev/lumo-injection/styles/components.css deleted file mode 100644 index ae010435d85..00000000000 --- a/dev/lumo-injection/styles/components.css +++ /dev/null @@ -1,3 +0,0 @@ -@import './item.css'; -@import './list-box.css'; -@import './text-field.css'; diff --git a/dev/lumo-injection/styles/item.css b/dev/lumo-injection/styles/item.css deleted file mode 100644 index ee2b8d84842..00000000000 --- a/dev/lumo-injection/styles/item.css +++ /dev/null @@ -1 +0,0 @@ -@import '@vaadin/vaadin-lumo-styles/components/item.css'; diff --git a/dev/lumo-injection/styles/list-box.css b/dev/lumo-injection/styles/list-box.css deleted file mode 100644 index bf98a21158b..00000000000 --- a/dev/lumo-injection/styles/list-box.css +++ /dev/null @@ -1 +0,0 @@ -@import '@vaadin/vaadin-lumo-styles/components/list-box.css'; diff --git a/dev/lumo-injection/styles/text-field.css b/dev/lumo-injection/styles/text-field.css deleted file mode 100644 index 252a09b9964..00000000000 --- a/dev/lumo-injection/styles/text-field.css +++ /dev/null @@ -1 +0,0 @@ -@import '@vaadin/vaadin-lumo-styles/components/text-field.css'; diff --git a/package.json b/package.json index fc8ba550a30..31db2d9ea10 100644 --- a/package.json +++ b/package.json @@ -28,9 +28,7 @@ "test:webkit": "yarn test --config web-test-runner-webkit.config.js", "update:lumo": "TEST_ENV=update yarn test --config web-test-runner-lumo.config.js", "update:material": "TEST_ENV=update yarn test --config web-test-runner-material.config.js", - "update:snapshots": "yarn test --config web-test-runner-snapshots.config.js --update-snapshots", - "vite:start": "vite dev", - "vite:build": "vite build && vite preview" + "update:snapshots": "yarn test --config web-test-runner-snapshots.config.js --update-snapshots" }, "devDependencies": { "@fontsource/roboto": "^4.5.8", @@ -54,14 +52,12 @@ "eslint-plugin-no-only-tests": "^3.1.0", "eslint-plugin-prettier": "^5.1.3", "eslint-plugin-simple-import-sort": "^12.0.0", - "glob": "^11.0.1", + "glob": "^8.0.3", "husky": "^9.1.4", "lerna": "^4.0.0", "lint-staged": "^15.2.9", "npm-run-all": "^4.1.5", "patch-package": "^7.0.0", - "postcss": "^8.5.3", - "postcss-import": "^16.1.0", "postcss-lit": "^1.1.0", "prettier": "^3.1.0", "prettier-plugin-package": "^1.3.0", @@ -70,8 +66,7 @@ "rollup": "^4.4.0", "stylelint": "^16.10.0", "stylelint-config-vaadin": "^1.0.0-alpha.2", - "typescript": "^5.7.3", - "vite": "^6.2.5" + "typescript": "^5.7.3" }, "resolutions": { "playwright": "^1.52.0" diff --git a/packages/input-container/src/vaadin-lit-input-container.js b/packages/input-container/src/vaadin-lit-input-container.js index abf0612203d..41683f73158 100644 --- a/packages/input-container/src/vaadin-lit-input-container.js +++ b/packages/input-container/src/vaadin-lit-input-container.js @@ -7,7 +7,6 @@ import { html, LitElement } from 'lit'; import { defineCustomElement } from '@vaadin/component-base/src/define.js'; import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; -import { CssInjectionMixin } from '@vaadin/vaadin-themable-mixin/css-injection-mixin.js'; import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; import { InputContainerMixin } from './vaadin-input-container-mixin.js'; import { inputContainerStyles } from './vaadin-input-container-styles.js'; @@ -26,9 +25,7 @@ import { inputContainerStyles } from './vaadin-input-container-styles.js'; * @mixes DirMixin * @mixes InputContainerMixin */ -export class InputContainer extends CssInjectionMixin( - InputContainerMixin(ThemableMixin(DirMixin(PolylitMixin(LitElement)))), -) { +export class InputContainer extends InputContainerMixin(ThemableMixin(DirMixin(PolylitMixin(LitElement)))) { static get is() { return 'vaadin-input-container'; } diff --git a/packages/item/src/vaadin-lit-item.js b/packages/item/src/vaadin-lit-item.js index e6749114d27..f1bb479f561 100644 --- a/packages/item/src/vaadin-lit-item.js +++ b/packages/item/src/vaadin-lit-item.js @@ -7,7 +7,6 @@ import { css, html, LitElement } from 'lit'; import { defineCustomElement } from '@vaadin/component-base/src/define.js'; import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; -import { CssInjectionMixin } from '@vaadin/vaadin-themable-mixin/css-injection-mixin.js'; import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; import { ItemMixin } from './vaadin-item-mixin.js'; @@ -20,21 +19,19 @@ import { ItemMixin } from './vaadin-item-mixin.js'; * There is no ETA regarding specific Vaadin version where it'll land. * Feel free to try this code in your apps as per Apache 2.0 license. */ -class Item extends ItemMixin(CssInjectionMixin(ThemableMixin(DirMixin(PolylitMixin(LitElement))))) { +class Item extends ItemMixin(ThemableMixin(DirMixin(PolylitMixin(LitElement)))) { static get is() { return 'vaadin-item'; } static get styles() { return css` - @layer base { - :host { - display: inline-block; - } - - :host([hidden]) { - display: none !important; - } + :host { + display: inline-block; + } + + :host([hidden]) { + display: none !important; } `; } diff --git a/packages/list-box/src/vaadin-lit-list-box.js b/packages/list-box/src/vaadin-lit-list-box.js index 0d8fa9c6f27..b4f2ecde92b 100644 --- a/packages/list-box/src/vaadin-lit-list-box.js +++ b/packages/list-box/src/vaadin-lit-list-box.js @@ -8,7 +8,6 @@ import { defineCustomElement } from '@vaadin/component-base/src/define.js'; import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js'; -import { CssInjectionMixin } from '@vaadin/vaadin-themable-mixin/css-injection-mixin.js'; import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; import { MultiSelectListMixin } from './vaadin-multi-select-list-mixin.js'; @@ -21,28 +20,26 @@ import { MultiSelectListMixin } from './vaadin-multi-select-list-mixin.js'; * There is no ETA regarding specific Vaadin version where it'll land. * Feel free to try this code in your apps as per Apache 2.0 license. */ -class ListBox extends CssInjectionMixin(ElementMixin(MultiSelectListMixin(ThemableMixin(PolylitMixin(LitElement))))) { +class ListBox extends ElementMixin(MultiSelectListMixin(ThemableMixin(PolylitMixin(LitElement)))) { static get is() { return 'vaadin-list-box'; } static get styles() { return css` - @layer base { - :host { - display: flex; - } + :host { + display: flex; + } - :host([hidden]) { - display: none !important; - } + :host([hidden]) { + display: none !important; + } - [part='items'] { - height: 100%; - width: 100%; - overflow-y: auto; - -webkit-overflow-scrolling: touch; - } + [part='items'] { + height: 100%; + width: 100%; + overflow-y: auto; + -webkit-overflow-scrolling: touch; } `; } diff --git a/packages/text-field/src/vaadin-lit-text-field.js b/packages/text-field/src/vaadin-lit-text-field.js index 9f020f2f3d3..c3df7dba145 100644 --- a/packages/text-field/src/vaadin-lit-text-field.js +++ b/packages/text-field/src/vaadin-lit-text-field.js @@ -11,7 +11,6 @@ import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js'; import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; import { TooltipController } from '@vaadin/component-base/src/tooltip-controller.js'; import { inputFieldShared } from '@vaadin/field-base/src/styles/input-field-shared-styles.js'; -import { CssInjectionMixin } from '@vaadin/vaadin-themable-mixin/css-injection-mixin.js'; import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js'; import { TextFieldMixin } from './vaadin-text-field-mixin.js'; @@ -24,9 +23,7 @@ import { TextFieldMixin } from './vaadin-text-field-mixin.js'; * There is no ETA regarding specific Vaadin version where it'll land. * Feel free to try this code in your apps as per Apache 2.0 license. */ -export class TextField extends CssInjectionMixin( - TextFieldMixin(ThemableMixin(ElementMixin(PolylitMixin(LitElement)))), -) { +export class TextField extends TextFieldMixin(ThemableMixin(ElementMixin(PolylitMixin(LitElement)))) { static get is() { return 'vaadin-text-field'; } diff --git a/packages/vaadin-lumo-styles/base/base.css b/packages/vaadin-lumo-styles/base/base.css deleted file mode 100644 index 2982f72d2fa..00000000000 --- a/packages/vaadin-lumo-styles/base/base.css +++ /dev/null @@ -1,6 +0,0 @@ -@import './color.css'; -@import './font-icons.css'; -@import './sizing.css'; -@import './spacing.css'; -@import './style.css'; -@import './typography.css'; diff --git a/packages/vaadin-lumo-styles/base/color.css b/packages/vaadin-lumo-styles/base/color.css deleted file mode 100644 index 1bbab3ce0ea..00000000000 --- a/packages/vaadin-lumo-styles/base/color.css +++ /dev/null @@ -1,84 +0,0 @@ -html { - /* Base (background) */ - --lumo-base-color: #fff; - - /* Tint */ - --lumo-tint-5pct: hsla(0, 0%, 100%, 0.3); - --lumo-tint-10pct: hsla(0, 0%, 100%, 0.37); - --lumo-tint-20pct: hsla(0, 0%, 100%, 0.44); - --lumo-tint-30pct: hsla(0, 0%, 100%, 0.5); - --lumo-tint-40pct: hsla(0, 0%, 100%, 0.57); - --lumo-tint-50pct: hsla(0, 0%, 100%, 0.64); - --lumo-tint-60pct: hsla(0, 0%, 100%, 0.7); - --lumo-tint-70pct: hsla(0, 0%, 100%, 0.77); - --lumo-tint-80pct: hsla(0, 0%, 100%, 0.84); - --lumo-tint-90pct: hsla(0, 0%, 100%, 0.9); - --lumo-tint: #fff; - - /* Shade */ - --lumo-shade-5pct: hsla(214, 61%, 25%, 0.05); - --lumo-shade-10pct: hsla(214, 57%, 24%, 0.1); - --lumo-shade-20pct: hsla(214, 53%, 23%, 0.16); - --lumo-shade-30pct: hsla(214, 50%, 22%, 0.26); - --lumo-shade-40pct: hsla(214, 47%, 21%, 0.38); - --lumo-shade-50pct: hsla(214, 45%, 20%, 0.52); - --lumo-shade-60pct: hsla(214, 43%, 19%, 0.6); - --lumo-shade-70pct: hsla(214, 42%, 18%, 0.69); - --lumo-shade-80pct: hsla(214, 41%, 17%, 0.83); - --lumo-shade-90pct: hsla(214, 40%, 16%, 0.94); - --lumo-shade: hsl(214, 35%, 15%); - - /* Contrast */ - --lumo-contrast-5pct: var(--lumo-shade-5pct); - --lumo-contrast-10pct: var(--lumo-shade-10pct); - --lumo-contrast-20pct: var(--lumo-shade-20pct); - --lumo-contrast-30pct: var(--lumo-shade-30pct); - --lumo-contrast-40pct: var(--lumo-shade-40pct); - --lumo-contrast-50pct: var(--lumo-shade-50pct); - --lumo-contrast-60pct: var(--lumo-shade-60pct); - --lumo-contrast-70pct: var(--lumo-shade-70pct); - --lumo-contrast-80pct: var(--lumo-shade-80pct); - --lumo-contrast-90pct: var(--lumo-shade-90pct); - --lumo-contrast: var(--lumo-shade); - - /* Text */ - --lumo-header-text-color: var(--lumo-contrast); - --lumo-body-text-color: var(--lumo-contrast-90pct); - --lumo-secondary-text-color: var(--lumo-contrast-70pct); - --lumo-tertiary-text-color: var(--lumo-contrast-50pct); - --lumo-disabled-text-color: var(--lumo-contrast-30pct); - - /* Primary */ - --lumo-primary-color: hsl(214, 100%, 48%); - --lumo-primary-color-50pct: hsla(214, 100%, 49%, 0.76); - --lumo-primary-color-10pct: hsla(214, 100%, 60%, 0.13); - --lumo-primary-text-color: hsl(214, 100%, 43%); - --lumo-primary-contrast-color: #fff; - - /* Error */ - --lumo-error-color: hsl(3, 85%, 48%); - --lumo-error-color-50pct: hsla(3, 85%, 49%, 0.5); - --lumo-error-color-10pct: hsla(3, 85%, 49%, 0.1); - --lumo-error-text-color: hsl(3, 89%, 42%); - --lumo-error-contrast-color: #fff; - - /* Success */ - --lumo-success-color: hsl(145, 72%, 30%); - --lumo-success-color-50pct: hsla(145, 72%, 31%, 0.5); - --lumo-success-color-10pct: hsla(145, 72%, 31%, 0.1); - --lumo-success-text-color: hsl(145, 85%, 25%); - --lumo-success-contrast-color: #fff; - - /* Warning */ - --lumo-warning-color: hsl(48, 100%, 50%); - --lumo-warning-color-10pct: hsla(48, 100%, 50%, 0.25); - --lumo-warning-text-color: hsl(32, 100%, 30%); - --lumo-warning-contrast-color: var(--lumo-shade-90pct); -} - -/* forced-colors mode adjustments */ -@media (forced-colors: active) { - html { - --lumo-disabled-text-color: GrayText; - } -} diff --git a/packages/vaadin-lumo-styles/base/font-icons.css b/packages/vaadin-lumo-styles/base/font-icons.css deleted file mode 100644 index 1abcf9916cf..00000000000 --- a/packages/vaadin-lumo-styles/base/font-icons.css +++ /dev/null @@ -1,54 +0,0 @@ -@font-face { - font-family: 'lumo-icons'; - src: url(data:application/font-woff;charset=utf-8;base64,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) - format('woff'); - font-weight: normal; - font-style: normal; -} - -html { - --lumo-icons-align-center: '\ea01'; - --lumo-icons-align-left: '\ea02'; - --lumo-icons-align-right: '\ea03'; - --lumo-icons-angle-down: '\ea04'; - --lumo-icons-angle-left: '\ea05'; - --lumo-icons-angle-right: '\ea06'; - --lumo-icons-angle-up: '\ea07'; - --lumo-icons-arrow-down: '\ea08'; - --lumo-icons-arrow-left: '\ea09'; - --lumo-icons-arrow-right: '\ea0a'; - --lumo-icons-arrow-up: '\ea0b'; - --lumo-icons-bar-chart: '\ea0c'; - --lumo-icons-bell: '\ea0d'; - --lumo-icons-calendar: '\ea0e'; - --lumo-icons-checkmark: '\ea0f'; - --lumo-icons-chevron-down: '\ea10'; - --lumo-icons-chevron-left: '\ea11'; - --lumo-icons-chevron-right: '\ea12'; - --lumo-icons-chevron-up: '\ea13'; - --lumo-icons-clock: '\ea14'; - --lumo-icons-cog: '\ea15'; - --lumo-icons-cross: '\ea16'; - --lumo-icons-download: '\ea17'; - --lumo-icons-drag-handle: '\ea18'; - --lumo-icons-dropdown: '\ea19'; - --lumo-icons-edit: '\ea1a'; - --lumo-icons-error: '\ea1b'; - --lumo-icons-eye: '\ea1c'; - --lumo-icons-eye-disabled: '\ea1d'; - --lumo-icons-menu: '\ea1e'; - --lumo-icons-minus: '\ea1f'; - --lumo-icons-ordered-list: '\ea20'; - --lumo-icons-phone: '\ea21'; - --lumo-icons-photo: '\ea22'; - --lumo-icons-play: '\ea23'; - --lumo-icons-plus: '\ea24'; - --lumo-icons-redo: '\ea25'; - --lumo-icons-reload: '\ea26'; - --lumo-icons-resize-handle: '\ea27'; - --lumo-icons-search: '\ea28'; - --lumo-icons-undo: '\ea29'; - --lumo-icons-unordered-list: '\ea2a'; - --lumo-icons-upload: '\ea2b'; - --lumo-icons-user: '\ea2c'; -} diff --git a/packages/vaadin-lumo-styles/base/sizing.css b/packages/vaadin-lumo-styles/base/sizing.css deleted file mode 100644 index ee163f09873..00000000000 --- a/packages/vaadin-lumo-styles/base/sizing.css +++ /dev/null @@ -1,14 +0,0 @@ -html { - --lumo-size-xs: 1.625rem; - --lumo-size-s: 1.875rem; - --lumo-size-m: 2.25rem; - --lumo-size-l: 2.75rem; - --lumo-size-xl: 3.5rem; - - /* Icons */ - --lumo-icon-size-s: 1.25em; - --lumo-icon-size-m: 1.5em; - --lumo-icon-size-l: 2.25em; - /* For backwards compatibility */ - --lumo-icon-size: var(--lumo-icon-size-m); -} diff --git a/packages/vaadin-lumo-styles/base/spacing.css b/packages/vaadin-lumo-styles/base/spacing.css deleted file mode 100644 index 4d9c4a8af6b..00000000000 --- a/packages/vaadin-lumo-styles/base/spacing.css +++ /dev/null @@ -1,22 +0,0 @@ -html { - /* Square */ - --lumo-space-xs: 0.25rem; - --lumo-space-s: 0.5rem; - --lumo-space-m: 1rem; - --lumo-space-l: 1.5rem; - --lumo-space-xl: 2.5rem; - - /* Wide */ - --lumo-space-wide-xs: calc(var(--lumo-space-xs) / 2) var(--lumo-space-xs); - --lumo-space-wide-s: calc(var(--lumo-space-s) / 2) var(--lumo-space-s); - --lumo-space-wide-m: calc(var(--lumo-space-m) / 2) var(--lumo-space-m); - --lumo-space-wide-l: calc(var(--lumo-space-l) / 2) var(--lumo-space-l); - --lumo-space-wide-xl: calc(var(--lumo-space-xl) / 2) var(--lumo-space-xl); - - /* Tall */ - --lumo-space-tall-xs: var(--lumo-space-xs) calc(var(--lumo-space-xs) / 2); - --lumo-space-tall-s: var(--lumo-space-s) calc(var(--lumo-space-s) / 2); - --lumo-space-tall-m: var(--lumo-space-m) calc(var(--lumo-space-m) / 2); - --lumo-space-tall-l: var(--lumo-space-l) calc(var(--lumo-space-l) / 2); - --lumo-space-tall-xl: var(--lumo-space-xl) calc(var(--lumo-space-xl) / 2); -} diff --git a/packages/vaadin-lumo-styles/base/style.css b/packages/vaadin-lumo-styles/base/style.css deleted file mode 100644 index 98b4835a084..00000000000 --- a/packages/vaadin-lumo-styles/base/style.css +++ /dev/null @@ -1,16 +0,0 @@ -html { - /* Border radius */ - --lumo-border-radius-s: 0.25em; /* Checkbox, badge, date-picker year indicator, etc */ - --lumo-border-radius-m: var(--lumo-border-radius, 0.25em); /* Button, text field, menu overlay, etc */ - --lumo-border-radius-l: 0.5em; /* Dialog, notification, etc */ - - /* Shadow */ - --lumo-box-shadow-xs: 0 1px 4px -1px var(--lumo-shade-50pct); - --lumo-box-shadow-s: 0 2px 4px -1px var(--lumo-shade-20pct), 0 3px 12px -1px var(--lumo-shade-30pct); - --lumo-box-shadow-m: 0 2px 6px -1px var(--lumo-shade-20pct), 0 8px 24px -4px var(--lumo-shade-40pct); - --lumo-box-shadow-l: 0 3px 18px -2px var(--lumo-shade-20pct), 0 12px 48px -6px var(--lumo-shade-40pct); - --lumo-box-shadow-xl: 0 4px 24px -3px var(--lumo-shade-20pct), 0 18px 64px -8px var(--lumo-shade-40pct); - - /* Clickable element cursor */ - --lumo-clickable-cursor: default; -} diff --git a/packages/vaadin-lumo-styles/base/typography.css b/packages/vaadin-lumo-styles/base/typography.css deleted file mode 100644 index 1b928166f65..00000000000 --- a/packages/vaadin-lumo-styles/base/typography.css +++ /dev/null @@ -1,19 +0,0 @@ -html { - /* prettier-ignore */ - --lumo-font-family: -apple-system, BlinkMacSystemFont, 'Roboto', 'Segoe UI', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; - - /* Font sizes */ - --lumo-font-size-xxs: 0.75rem; - --lumo-font-size-xs: 0.8125rem; - --lumo-font-size-s: 0.875rem; - --lumo-font-size-m: 1rem; - --lumo-font-size-l: 1.125rem; - --lumo-font-size-xl: 1.375rem; - --lumo-font-size-xxl: 1.75rem; - --lumo-font-size-xxxl: 2.5rem; - - /* Line heights */ - --lumo-line-height-xs: 1.25; - --lumo-line-height-s: 1.375; - --lumo-line-height-m: 1.625; -} diff --git a/packages/vaadin-lumo-styles/components/input-container.css b/packages/vaadin-lumo-styles/components/input-container.css deleted file mode 100644 index 2d5885d3b96..00000000000 --- a/packages/vaadin-lumo-styles/components/input-container.css +++ /dev/null @@ -1,5 +0,0 @@ -@import './shared/input-container.css' vaadin-input-container; - -html, :host { - --vaadin-input-container-css-inject: 1; -} diff --git a/packages/vaadin-lumo-styles/components/item.css b/packages/vaadin-lumo-styles/components/item.css deleted file mode 100644 index 2ba48baa7e4..00000000000 --- a/packages/vaadin-lumo-styles/components/item.css +++ /dev/null @@ -1,5 +0,0 @@ -@import './shared/item.css' vaadin-item; - -html, :host { - --vaadin-item-css-inject: 1; -} diff --git a/packages/vaadin-lumo-styles/components/list-box.css b/packages/vaadin-lumo-styles/components/list-box.css deleted file mode 100644 index e48436d12c9..00000000000 --- a/packages/vaadin-lumo-styles/components/list-box.css +++ /dev/null @@ -1,5 +0,0 @@ -@import './shared/list-box.css' vaadin-list-box; - -html, :host { - --vaadin-list-box-css-inject: 1; -} diff --git a/packages/vaadin-lumo-styles/components/shared/field-button.css b/packages/vaadin-lumo-styles/components/shared/field-button.css deleted file mode 100644 index ad9e642780c..00000000000 --- a/packages/vaadin-lumo-styles/components/shared/field-button.css +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Shared styles used by all field components that have icon buttons. - * Example: `toggle-button` and `clear-button` parts in combo-box. - * - * Import this file only with proper media. Example: - * - * ``` - * @import './shared/field-button.css' vaadin-text-field; - * ``` - * - * NOTE: this file should not import dependencies e.g. `color.css` etc. - * Instead these should be loaded from corresponding components files. - */ - -[part$='button'] { - flex: none; - width: 1em; - height: 1em; - line-height: 1; - font-size: var(--lumo-icon-size-m); - text-align: center; - color: var(--lumo-contrast-60pct); - transition: 0.2s color; - cursor: var(--lumo-clickable-cursor); -} - -[part$='button']:hover { - color: var(--lumo-contrast-90pct); -} - -:host([disabled]) [part$='button'], -:host([readonly]) [part$='button'] { - color: var(--lumo-contrast-20pct); - cursor: default; -} - -[part$='button']::before { - font-family: 'lumo-icons'; - display: block; -} diff --git a/packages/vaadin-lumo-styles/components/shared/helper.css b/packages/vaadin-lumo-styles/components/shared/helper.css deleted file mode 100644 index 840f82e7a30..00000000000 --- a/packages/vaadin-lumo-styles/components/shared/helper.css +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Shared styles used by all field components that support helper text. - * Import this file only with proper media. Example: - * - * ``` - * @import './shared/helper.css' vaadin-text-field; - * ``` - * - * NOTE: this file should not import dependencies e.g. `color.css` etc. - * Instead these should be loaded from corresponding components files. - */ - -:host { - --_helper-spacing: var(--vaadin-input-field-helper-spacing, 0.4em); -} - -:host([has-helper]) [part='helper-text']::before { - content: ''; - display: block; - height: var(--_helper-spacing); -} - -[part='helper-text'] { - display: block; - color: var(--vaadin-input-field-helper-color, var(--lumo-secondary-text-color)); - font-size: var(--vaadin-input-field-helper-font-size, var(--lumo-font-size-xs)); - line-height: var(--lumo-line-height-xs); - font-weight: var(--vaadin-input-field-helper-font-weight, 400); - margin-left: calc(var(--lumo-border-radius-m) / 4); - transition: color 0.2s; -} - -:host(:hover:not([readonly])) [part='helper-text'] { - color: var(--lumo-body-text-color); -} - -:host([disabled]) [part='helper-text'] { - color: var(--lumo-disabled-text-color); - -webkit-text-fill-color: var(--lumo-disabled-text-color); -} - -:host([has-helper][theme~='helper-above-field']) [part='helper-text']::before { - display: none; -} - -:host([has-helper][theme~='helper-above-field']) [part='helper-text']::after { - content: ''; - display: block; - height: var(--_helper-spacing); -} - -:host([has-helper][theme~='helper-above-field']) [part='label'] { - order: 0; - padding-bottom: var(--_helper-spacing); -} - -:host([has-helper][theme~='helper-above-field']) [part='helper-text'] { - order: 1; -} - -:host([has-helper][theme~='helper-above-field']) [part='label'] + * { - order: 2; -} - -:host([has-helper][theme~='helper-above-field']) [part='error-message'] { - order: 3; -} diff --git a/packages/vaadin-lumo-styles/components/shared/input-container.css b/packages/vaadin-lumo-styles/components/shared/input-container.css deleted file mode 100644 index 4162260c0fc..00000000000 --- a/packages/vaadin-lumo-styles/components/shared/input-container.css +++ /dev/null @@ -1,190 +0,0 @@ -/** - * Shared styles used by all field components that use input container. - * - * Import this file only with proper media. Example: - * - * ``` - * @import './shared/input-container.css' vaadin-input-container; - * ``` - * - * NOTE: this file should not import dependencies e.g. `color.css` etc. - * Instead these should be loaded from corresponding components files. - */ - -:host { - background: var(--_background); - padding: 0 calc(0.375em + var(--_input-container-radius) / 4 - 1px); - font-weight: var(--vaadin-input-field-value-font-weight, 500); - line-height: 1; - position: relative; - cursor: text; - box-sizing: border-box; - border-radius: - /* See https://developer.mozilla.org/en-US/docs/Web/CSS/border-radius#syntax */ - var(--vaadin-input-field-top-start-radius, var(--_input-container-radius)) - var(--vaadin-input-field-top-end-radius, var(--_input-container-radius)) - var(--vaadin-input-field-bottom-end-radius, var(--_input-container-radius)) - var(--vaadin-input-field-bottom-start-radius, var(--_input-container-radius)); - /* Fallback */ - --_input-container-radius: var(--vaadin-input-field-border-radius, var(--lumo-border-radius-m)); - --_input-height: var(--lumo-text-field-size, var(--lumo-size-m)); - /* Default values */ - --_background: var(--vaadin-input-field-background, var(--lumo-contrast-10pct)); - --_hover-highlight: var(--vaadin-input-field-hover-highlight, var(--lumo-contrast-50pct)); - --_input-border-color: var(--vaadin-input-field-border-color, var(--lumo-contrast-50pct)); - --_icon-color: var(--vaadin-input-field-icon-color, var(--lumo-contrast-60pct)); - --_icon-size: var(--vaadin-input-field-icon-size, var(--lumo-icon-size-m)); - --_invalid-background: var(--vaadin-input-field-invalid-background, var(--lumo-error-color-10pct)); - --_invalid-hover-highlight: var(--vaadin-input-field-invalid-hover-highlight, var(--lumo-error-color-50pct)); - --_disabled-background: var(--vaadin-input-field-disabled-background, var(--lumo-contrast-5pct)); - --_disabled-value-color: var(--vaadin-input-field-disabled-value-color, var(--lumo-disabled-text-color)); -} - -:host([dir='rtl']) { - border-radius: - /* Don't use logical props, see https://github.com/vaadin/vaadin-time-picker/issues/145 */ - var(--vaadin-input-field-top-end-radius, var(--_input-container-radius)) - var(--vaadin-input-field-top-start-radius, var(--_input-container-radius)) - var(--vaadin-input-field-bottom-start-radius, var(--_input-container-radius)) - var(--vaadin-input-field-bottom-end-radius, var(--_input-container-radius)); -} - -/* Used for hover and activation effects */ -:host::after { - content: ''; - position: absolute; - inset: 0; - border-radius: inherit; - pointer-events: none; - background: var(--_hover-highlight); - opacity: 0; - transition: - transform 0.15s, - opacity 0.2s; - transform-origin: 100% 0; -} - -::slotted(:not([slot$='fix'])) { - cursor: inherit; - min-height: var(--vaadin-input-field-height, var(--_input-height)); - padding: 0 0.25em; - --_lumo-text-field-overflow-mask-image: linear-gradient(to left, transparent, #000 1.25em); - -webkit-mask-image: var(--_lumo-text-field-overflow-mask-image); - mask-image: var(--_lumo-text-field-overflow-mask-image); -} - -/* Read-only */ -:host([readonly]) { - color: var(--lumo-secondary-text-color); - background-color: transparent; - cursor: default; -} - -:host([readonly])::after { - background-color: transparent; - opacity: 1; - border: var(--vaadin-input-field-readonly-border, 1px dashed var(--lumo-contrast-30pct)); -} - -/* Disabled */ -:host([disabled]) { - background: var(--_disabled-background); -} - -:host([disabled]) ::slotted(:not([slot$='fix'])) { - -webkit-text-fill-color: var(--_disabled-value-color); - color: var(--_disabled-value-color); -} - -/* Invalid */ -:host([invalid]) { - background: var(--_invalid-background); -} - -:host([invalid]:not([readonly]))::after { - background: var(--_invalid-hover-highlight); -} - -/* Slotted icons */ -::slotted(vaadin-icon) { - color: var(--_icon-color); - width: var(--_icon-size); - height: var(--_icon-size); -} - -/* Vaadin icons are based on a 16x16 grid (unlike Lumo and Material icons with 24x24), so they look too big by default */ -::slotted(vaadin-icon[icon^='vaadin:']) { - padding: 0.25em; - box-sizing: border-box !important; -} - -/* Text align */ -:host([dir='rtl']) ::slotted(:not([slot$='fix'])) { - --_lumo-text-field-overflow-mask-image: linear-gradient(to right, transparent, #000 1.25em); -} - -@-moz-document url-prefix() { - :host([dir='rtl']) ::slotted(:not([slot$='fix'])) { - mask-image: var(--_lumo-text-field-overflow-mask-image); - } -} - -:host([theme~='align-left']) ::slotted(:not([slot$='fix'])) { - text-align: start; - --_lumo-text-field-overflow-mask-image: none; -} - -:host([theme~='align-center']) ::slotted(:not([slot$='fix'])) { - text-align: center; - --_lumo-text-field-overflow-mask-image: none; -} - -:host([theme~='align-right']) ::slotted(:not([slot$='fix'])) { - text-align: end; - --_lumo-text-field-overflow-mask-image: none; -} - -@-moz-document url-prefix() { - /* Firefox is smart enough to align overflowing text to right */ - :host([theme~='align-right']) ::slotted(:not([slot$='fix'])) { - --_lumo-text-field-overflow-mask-image: linear-gradient(to right, transparent 0.25em, #000 1.5em); - } -} - -@-moz-document url-prefix() { - /* Firefox is smart enough to align overflowing text to right */ - :host([theme~='align-left']) ::slotted(:not([slot$='fix'])) { - --_lumo-text-field-overflow-mask-image: linear-gradient(to left, transparent 0.25em, #000 1.5em); - } -} - -/* RTL specific styles */ -:host([dir='rtl'])::after { - transform-origin: 0% 0; -} - -:host([theme~='align-left'][dir='rtl']) ::slotted(:not([slot$='fix'])) { - --_lumo-text-field-overflow-mask-image: none; -} - -:host([theme~='align-center'][dir='rtl']) ::slotted(:not([slot$='fix'])) { - --_lumo-text-field-overflow-mask-image: none; -} - -:host([theme~='align-right'][dir='rtl']) ::slotted(:not([slot$='fix'])) { - --_lumo-text-field-overflow-mask-image: none; -} - -@-moz-document url-prefix() { - /* Firefox is smart enough to align overflowing text to right */ - :host([theme~='align-right'][dir='rtl']) ::slotted(:not([slot$='fix'])) { - --_lumo-text-field-overflow-mask-image: linear-gradient(to right, transparent 0.25em, #000 1.5em); - } -} - -@-moz-document url-prefix() { - /* Firefox is smart enough to align overflowing text to right */ - :host([theme~='align-left'][dir='rtl']) ::slotted(:not([slot$='fix'])) { - --_lumo-text-field-overflow-mask-image: linear-gradient(to left, transparent 0.25em, #000 1.5em); - } -} diff --git a/packages/vaadin-lumo-styles/components/shared/input-field.css b/packages/vaadin-lumo-styles/components/shared/input-field.css deleted file mode 100644 index d8df38add5d..00000000000 --- a/packages/vaadin-lumo-styles/components/shared/input-field.css +++ /dev/null @@ -1,171 +0,0 @@ -/** - * Shared styles used by the following components: - * - * - `vaadin-text-field` - * - `vaadin-text-area` - * - `vaadin-password-field` - * - `vaadin-number-field` - * - `vaadin-integer-field` - * - `vaadin-email-field` - * - `vaadin-combo-box` - * - `vaadin-date-picker` - * - `vaadin-time-picker` - * - `vaadin-multi-select-combo-box` - * - * Import this file only with proper media. Example: - * - * ``` - * @import './shared/input-field.css' vaadin-text-field; - * ``` - * - * NOTE: this file should not import dependencies e.g. `color.css` etc. - * Instead these should be loaded from corresponding components files. - */ - -:host { - --lumo-text-field-size: var(--lumo-size-m); - color: var(--vaadin-input-field-value-color, var(--lumo-body-text-color)); - font-size: var(--vaadin-input-field-value-font-size, var(--lumo-font-size-m)); - font-family: var(--lumo-font-family); - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-tap-highlight-color: transparent; - padding: var(--lumo-space-xs) 0; - --_focus-ring-color: var(--vaadin-focus-ring-color, var(--lumo-primary-color-50pct)); - --_focus-ring-width: var(--vaadin-focus-ring-width, 2px); - --_input-height: var(--vaadin-input-field-height, var(--lumo-text-field-size)); - --_disabled-value-color: var(--vaadin-input-field-disabled-value-color, var(--lumo-disabled-text-color)); -} - -:host::before { - height: var(--_input-height); - box-sizing: border-box; - display: inline-flex; - align-items: center; -} - -:host([focused]) [part='input-field'] ::slotted(:is(input, textarea)) { - -webkit-mask-image: none; - mask-image: none; -} - -::slotted(:is(input, textarea):placeholder-shown) { - color: var(--vaadin-input-field-placeholder-color, var(--lumo-secondary-text-color)); -} - -/* Hover */ -:host(:hover:not([readonly]):not([focused]):not([disabled])) [part='input-field']::after { - opacity: var(--vaadin-input-field-hover-highlight-opacity, 0.1); -} - -/* Touch device adjustment */ -@media (pointer: coarse) { - :host(:hover:not([readonly]):not([focused]):not([disabled])) [part='input-field']::after { - opacity: 0; - } - - :host(:active:not([readonly]):not([focused]):not([disabled])) [part='input-field']::after { - opacity: 0.2; - } -} - -/* Trigger when not focusing using the keyboard */ -:host([focused]:not([focus-ring]):not([readonly])) [part='input-field']::after { - transform: scaleX(0); - transition-duration: 0.15s, 1s; -} - -/* Opt-in focus-ring when using pointer devices */ -/* This applies a focus-ring as box-shadow when the element is focused, but - the ring is only visible / has a width when the respective CSS property is - "enabled" using a value of 1 */ -:host([focused]) [part='input-field'] { - /* Borders are implemented using box-shadows as well. To avoid overriding - the border on focus, even if the pointer focus-ring is disabled, we need to: - - Duplicate the border box shadow for this rule - - Remove the border (by using width of 0) when the focus-ring is visible, - which is the same behavior as for the keyboard focus-ring below - - Apply the border when the focus ring is not visible - */ - --_pointer-focus-visible: clamp(0, var(--lumo-input-field-pointer-focus-visible, 0), 1); - --_conditional-border-width: calc(calc(1 - var(--_pointer-focus-visible)) * var(--_input-border-width)); - --_conditional-focus-ring-width: calc(var(--_pointer-focus-visible) * var(--_focus-ring-width)); - box-shadow: - inset 0 0 0 var(--_conditional-border-width) var(--_input-border-color), - 0 0 0 var(--_conditional-focus-ring-width) var(--_focus-ring-color); -} - -/* Focus-ring when using keyboard navigation */ -:host([focus-ring]) [part='input-field'] { - box-shadow: 0 0 0 var(--_focus-ring-width) var(--_focus-ring-color); -} - -/* Read-only and disabled */ -:host(:is([readonly], [disabled])) ::slotted(:is(input, textarea):placeholder-shown) { - opacity: 0; -} - -/* Read-only style */ -:host([readonly]) { - --vaadin-input-field-border-color: transparent; -} - -/* Disabled style */ -:host([disabled]) { - pointer-events: none; - --vaadin-input-field-border-color: var(--lumo-contrast-20pct); -} - -:host([disabled]) [part='label'], -:host([disabled]) [part='input-field'] ::slotted([slot$='fix']) { - color: var(--lumo-disabled-text-color); - -webkit-text-fill-color: var(--lumo-disabled-text-color); -} - -:host([disabled]) [part='input-field'] ::slotted(:not([slot$='fix'])) { - color: var(--_disabled-value-color); - -webkit-text-fill-color: var(--_disabled-value-color); -} - -/* Invalid style */ -:host([invalid]) { - --vaadin-input-field-border-color: var(--lumo-error-color); - --_focus-ring-color: var(--lumo-error-color-50pct); -} - -:host([input-prevented]) [part='input-field'] { - animation: shake 0.15s infinite; -} - -@keyframes shake { - 25% { - transform: translateX(4px); - } - 75% { - transform: translateX(-4px); - } -} - -/* Small theme */ -:host([theme~='small']) { - font-size: var(--lumo-font-size-s); - --lumo-text-field-size: var(--lumo-size-s); -} - -:host([theme~='small']) [part='label'] { - font-size: var(--lumo-font-size-xs); -} - -:host([theme~='small']) [part='error-message'] { - font-size: var(--lumo-font-size-xxs); -} - -/* Slotted content */ -[part='input-field'] ::slotted(:not(vaadin-icon):not(input):not(textarea)) { - color: var(--lumo-secondary-text-color); - font-weight: 400; -} - -[part='clear-button']::before { - content: var(--lumo-icons-cross); -} diff --git a/packages/vaadin-lumo-styles/components/shared/item.css b/packages/vaadin-lumo-styles/components/shared/item.css deleted file mode 100644 index f061d1cf33f..00000000000 --- a/packages/vaadin-lumo-styles/components/shared/item.css +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Shared styles used by the following components: - * - * - `vaadin-item` - * - `vaadin-combo-box-item` - * - `vaadin-context-menu-item` - * - `vaadin-menu-bar-item` - * - `vaadin-select-item` - * - `vaadin-avatar-group-menu-item` - * - * Import this file only with proper media. Example: - * - * ``` - * @import './shared/item.css' vaadin-item; - * ``` - * - * NOTE: this file should not import dependencies e.g. `color.css` etc. - * Instead these should be loaded from corresponding components files. - */ - -:host { - display: flex; - align-items: center; - box-sizing: border-box; - font-family: var(--lumo-font-family); - font-size: var(--lumo-font-size-m); - line-height: var(--lumo-line-height-xs); - padding: 0.5em calc(var(--lumo-space-l) + var(--lumo-border-radius-m) / 4) 0.5em - var(--_lumo-list-box-item-padding-left, calc(var(--lumo-border-radius-m) / 4)); - min-height: var(--lumo-size-m); - outline: none; - border-radius: var(--lumo-border-radius-m); - cursor: var(--lumo-clickable-cursor); - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-tap-highlight-color: var(--lumo-primary-color-10pct); - --_focus-ring-color: var(--vaadin-focus-ring-color, var(--lumo-primary-color-50pct)); - --_focus-ring-width: var(--vaadin-focus-ring-width, 2px); - --_selection-color-text: var(--vaadin-selection-color-text, var(--lumo-primary-text-color)); -} - -/* Checkmark */ -[part='checkmark']::before { - display: var(--_lumo-item-selected-icon-display, none); - content: var(--lumo-icons-checkmark); - font-family: lumo-icons; - font-size: var(--lumo-icon-size-m); - line-height: 1; - font-weight: normal; - width: 1em; - height: 1em; - margin: calc((1 - var(--lumo-line-height-xs)) * var(--lumo-font-size-m) / 2) 0; - color: var(--_selection-color-text); - flex: none; - opacity: 0; - transition: - transform 0.2s cubic-bezier(0.12, 0.32, 0.54, 2), - opacity 0.1s; -} - -:host([selected]) [part='checkmark']::before { - opacity: 1; -} - -:host([active]:not([selected])) [part='checkmark']::before { - transform: scale(0.8); - opacity: 0; - transition-duration: 0s; -} - -[part='content'] { - flex: auto; -} - -/* Disabled */ -:host([disabled]) { - color: var(--lumo-disabled-text-color); - cursor: default; - pointer-events: none; -} - -/* TODO a workaround until we have "focus-follows-mouse". After that, use the hover style for focus-ring as well */ -@media (any-hover: hover) { - :host(:hover:not([disabled])) { - background-color: var(--lumo-primary-color-10pct); - } -} - -:host([focus-ring]:not([disabled])) { - box-shadow: inset 0 0 0 var(--_focus-ring-width) var(--_focus-ring-color); -} - -/* RTL specific styles */ -:host([dir='rtl']) { - padding-left: calc(var(--lumo-space-l) + var(--lumo-border-radius-m) / 4); - padding-right: var(--_lumo-list-box-item-padding-left, calc(var(--lumo-border-radius-m) / 4)); -} diff --git a/packages/vaadin-lumo-styles/components/shared/list-box.css b/packages/vaadin-lumo-styles/components/shared/list-box.css deleted file mode 100644 index 3d6b171bc91..00000000000 --- a/packages/vaadin-lumo-styles/components/shared/list-box.css +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Shared styles used by the following components: - * - * - `vaadin-list-box` - * - `vaadin-context-menu-list-box` - * - `vaadin-menu-bar-list-box` - * - `vaadin-select-list-box` - * - `vaadin-avatar-group-menu` - * - * Import this file only with proper media. Example: - * - * ``` - * @import './shared/list-box.css' vaadin-list-box; - * ``` - * - * NOTE: this file should not import dependencies e.g. `color.css` etc. - * Instead these should be loaded from corresponding components files. - */ - -:host { - -webkit-tap-highlight-color: transparent; - --_lumo-item-selected-icon-display: var(--_lumo-list-box-item-selected-icon-display, block); -} - -/* Dividers */ -[part='items'] ::slotted(hr) { - height: 1px; - border: 0; - padding: 0; - margin: var(--lumo-space-s) var(--lumo-border-radius-m); - background-color: var(--lumo-contrast-10pct); -} diff --git a/packages/vaadin-lumo-styles/components/shared/required-field.css b/packages/vaadin-lumo-styles/components/shared/required-field.css deleted file mode 100644 index c759f2b05ed..00000000000 --- a/packages/vaadin-lumo-styles/components/shared/required-field.css +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Shared styles used by all field components that support validation. - * Import this file only with proper media. Example: - * - * ``` - * @import './shared/required-field.css' vaadin-text-field; - * ``` - * - * NOTE: this file should not import dependencies e.g. `color.css` etc. - * Instead these should be loaded from corresponding components files. - */ - -[part='label'] { - align-self: flex-start; - color: var(--vaadin-input-field-label-color, var(--lumo-secondary-text-color)); - font-weight: var(--vaadin-input-field-label-font-weight, 500); - font-size: var(--vaadin-input-field-label-font-size, var(--lumo-font-size-s)); - transition: color 0.2s; - line-height: 1; - padding-inline-start: calc(var(--lumo-border-radius-m) / 4); - padding-inline-end: 1em; - padding-bottom: 0.5em; - /* As a workaround for diacritics being cut off, add a top padding and a - negative margin to compensate */ - padding-top: 0.25em; - margin-top: -0.25em; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - position: relative; - max-width: 100%; - box-sizing: border-box; -} - -:host([focused]:not([readonly])) [part='label'] { - color: var(--vaadin-input-field-focused-label-color, var(--lumo-primary-text-color)); -} - -:host(:hover:not([readonly]):not([focused])) [part='label'] { - color: var(--vaadin-input-field-hovered-label-color, var(--lumo-body-text-color)); -} - -/* Touch device adjustment */ -@media (pointer: coarse) { - :host(:hover:not([readonly]):not([focused])) [part='label'] { - color: var(--vaadin-input-field-label-color, var(--lumo-secondary-text-color)); - } -} - -:host([has-label])::before { - margin-top: calc(var(--lumo-font-size-s) * 1.5); -} - -:host([has-label][theme~='small'])::before { - margin-top: calc(var(--lumo-font-size-xs) * 1.5); -} - -:host([has-label]) { - padding-top: var(--lumo-space-m); -} - -:host([has-label]) ::slotted([slot='tooltip']) { - --vaadin-tooltip-offset-bottom: calc((var(--lumo-space-m) - var(--lumo-space-xs)) * -1); -} - -:host([required]) [part='required-indicator']::after { - content: var(--lumo-required-field-indicator, '\\2022'); - transition: opacity 0.2s; - color: var(--lumo-required-field-indicator-color, var(--lumo-primary-text-color)); - position: absolute; - right: 0; - width: 1em; - text-align: center; -} - -:host([invalid]) [part='required-indicator']::after { - color: var(--lumo-required-field-indicator-color, var(--lumo-error-text-color)); -} - -[part='error-message'] { - margin-left: calc(var(--lumo-border-radius-m) / 4); - font-size: var(--vaadin-input-field-error-font-size, var(--lumo-font-size-xs)); - line-height: var(--lumo-line-height-xs); - font-weight: var(--vaadin-input-field-error-font-weight, 400); - color: var(--vaadin-input-field-error-color, var(--lumo-error-text-color)); - will-change: max-height; - transition: 0.4s max-height; - max-height: 5em; -} - -:host([has-error-message]) [part='error-message']::before, -:host([has-error-message]) [part='error-message']::after { - content: ''; - display: block; - height: 0.4em; -} - -:host(:not([invalid])) [part='error-message'] { - max-height: 0; - overflow: hidden; -} - -/* RTL specific styles */ - -:host([dir='rtl']) [part='required-indicator']::after { - right: auto; - left: 0; -} - -:host([dir='rtl']) [part='error-message'] { - margin-left: 0; - margin-right: calc(var(--lumo-border-radius-m) / 4); -} diff --git a/packages/vaadin-lumo-styles/components/text-field.css b/packages/vaadin-lumo-styles/components/text-field.css deleted file mode 100644 index aa90eaf5264..00000000000 --- a/packages/vaadin-lumo-styles/components/text-field.css +++ /dev/null @@ -1,10 +0,0 @@ -@import './input-container.css'; - -@import './shared/field-button.css' vaadin-text-field; -@import './shared/helper.css' vaadin-text-field; -@import './shared/input-field.css' vaadin-text-field; -@import './shared/required-field.css' vaadin-text-field; - -html, :host { - --vaadin-text-field-css-inject: 1; -} diff --git a/vite.config.mts b/vite.config.mts deleted file mode 100644 index aafbb32bea9..00000000000 --- a/vite.config.mts +++ /dev/null @@ -1,25 +0,0 @@ -/* eslint-env node */ -import { globSync } from 'glob'; -import { defineConfig } from 'vite'; - -export default defineConfig({ - server: { - port: 8000, - }, - preview: { - port: 8000, - }, - optimizeDeps: { - noDiscovery: true, - }, - build: { - target: 'esnext', - rollupOptions: { - input: globSync('dev/lumo-injection/*.html'), - output: { - format: 'es', - dir: 'dist', - }, - }, - }, -}); diff --git a/web-dev-server.config.js b/web-dev-server.config.js index dd4b36b39d2..96794378fb6 100644 --- a/web-dev-server.config.js +++ b/web-dev-server.config.js @@ -2,9 +2,6 @@ const fs = require('fs'); const { esbuildPlugin } = require('@web/dev-server-esbuild'); const path = require('path'); -const { getRequestFilePath } = require('@web/dev-server-core'); -const postcss = require('postcss'); -const atImport = require('postcss-import'); /** @return {import('@web/test-runner').TestRunnerPlugin} */ function generatedLitTestsPlugin() { @@ -29,26 +26,6 @@ function generatedLitTestsPlugin() { }; } -function inlineCssImportsPlugin() { - let rootDir; - - return { - name: 'inline-css-imports', - - serverStart(args) { - ({ rootDir } = args.config); - }, - - async transform(context) { - if (context.response.is('css') && context.body.includes('@import')) { - const filePath = getRequestFilePath(context.url, rootDir); - const { css } = await postcss([atImport()]).process(context.body, { from: filePath }); - return { body: css }; - } - }, - }; -} - const preventFouc = ` + `); + + { + const rules = extractTagScopedCSSRules(document, 'test-button'); + expect(rules).to.have.length(1); + expect(rules[0].cssText).to.equal(':host { color: black; }'); + } + { + const rules = extractTagScopedCSSRules(document, 'test-text-field'); + expect(rules).to.have.length(3); + expect(rules[0].cssText).to.equal('#label { color: black; }'); + expect(rules[1].cssText).to.equal('#error-message { color: black; }'); + expect(rules[2].cssText).to.equal('#error-message { color: red; }'); + } + }); + + it('should extract rules from tag-scoped @import at-rules ', async () => { + const style = fixtureSync(` + + `); + await oneEvent(style, 'load'); + + { + const rules = extractTagScopedCSSRules(document, 'test-button'); + expect(rules).to.have.length(1); + expect(rules[0].cssText).to.equal(':host { color: black; }'); + } + { + const rules = extractTagScopedCSSRules(document, 'test-text-field'); + expect(rules).to.have.length(2); + expect(rules[0].cssText).to.equal('#label { color: black; }'); + expect(rules[1].cssText).to.equal('#error-message { color: black; }'); + } + }); + + it('should extract rules from tag-scoped @media and @import at-rules inside an imported stylesheet', async () => { + const style = fixtureSync(` + + `); + await oneEvent(style, 'load'); + + { + const rules = extractTagScopedCSSRules(document, 'test-button'); + expect(rules).to.have.length(1); + expect(rules[0].cssText).to.equal(':host { color: black; }'); + } + { + const rules = extractTagScopedCSSRules(document, 'test-text-field'); + expect(rules).to.have.length(3); + expect(rules[0].cssText).to.equal('#label { color: black; }'); + expect(rules[1].cssText).to.equal('#error-message { color: black; }'); + expect(rules[2].cssText).to.equal('#error-message { color: red; }'); + } + }); +}); From 640ce1ff10d8270ea09e79bcc9b6d85f65e0e110 Mon Sep 17 00:00:00 2001 From: web-padawan Date: Mon, 14 Apr 2025 14:32:24 +0300 Subject: [PATCH 61/66] Do not register same custom CSS property twice --- .../css-injection-mixin.js | 14 ++++++-- .../test/css-injection-mixin-lit.test.js | 34 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/vaadin-themable-mixin/css-injection-mixin.js b/packages/vaadin-themable-mixin/css-injection-mixin.js index cbfcf76e6c2..5dc4dd55ea3 100644 --- a/packages/vaadin-themable-mixin/css-injection-mixin.js +++ b/packages/vaadin-themable-mixin/css-injection-mixin.js @@ -5,6 +5,11 @@ */ import { CSSInjector } from './src/css-injector.js'; +/** + * @type {string[]} + */ +const registeredProperties = new Set(); + /** * Find enclosing root for given element to gather style rules from. * @@ -31,8 +36,13 @@ export const CSSInjectionMixin = (superClass) => static finalize() { super.finalize(); - if (this.is) { - const propName = this.cssInjectPropName; + const propName = this.cssInjectPropName; + + // Prevent registering same property twice when a class extends + // another class using this mixin, since `finalize()` is called + // by LitElement for all superclasses in the prototype chain. + if (this.is && !registeredProperties.has(propName)) { + registeredProperties.add(propName); // Initialize custom property for this class with 0 as default // so that changing it to 1 would inject styles to instances diff --git a/packages/vaadin-themable-mixin/test/css-injection-mixin-lit.test.js b/packages/vaadin-themable-mixin/test/css-injection-mixin-lit.test.js index 3f158fe69d8..4d83aa88c0f 100644 --- a/packages/vaadin-themable-mixin/test/css-injection-mixin-lit.test.js +++ b/packages/vaadin-themable-mixin/test/css-injection-mixin-lit.test.js @@ -41,6 +41,18 @@ class TestBar extends CSSInjectionMixin(LitElement) { customElements.define(TestBar.is, TestBar); +class TestBaz extends TestFoo { + static get is() { + return 'test-baz'; + } + + render() { + return html`
Baz Content
`; + } +} + +customElements.define(TestBaz.is, TestBaz); + const TEST_FOO_STYLES = ` html, :host { --test-foo-css-inject: 1; @@ -409,6 +421,28 @@ describe('CSS injection', () => { }); }); + describe('extending class', () => { + beforeEach(async () => { + element = fixtureSync(''); + await nextRender(); + content = element.shadowRoot.querySelector('[part="content"]'); + }); + + it('should inject matching styles for the extending component', async () => { + const style = document.createElement('style'); + style.textContent = TEST_FOO_STYLES.replaceAll('foo', 'baz'); + document.head.appendChild(style); + + await contentTransition(); + assertInjectedStyle(); + + style.remove(); + + await contentTransition(); + assertBaseStyle(); + }); + }); + describe('registerStyles', () => { let style; From 8c8ae89b445da496afed6dc3247e804bff0ef0fb Mon Sep 17 00:00:00 2001 From: Sergey Vinogradov Date: Mon, 14 Apr 2025 15:52:55 +0400 Subject: [PATCH 62/66] add more tests --- .../test/css-rules-extraction.test.js | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/packages/vaadin-themable-mixin/test/css-rules-extraction.test.js b/packages/vaadin-themable-mixin/test/css-rules-extraction.test.js index 9dc94d861dc..affe1e2506b 100644 --- a/packages/vaadin-themable-mixin/test/css-rules-extraction.test.js +++ b/packages/vaadin-themable-mixin/test/css-rules-extraction.test.js @@ -89,4 +89,48 @@ describe('CSS rules extraction', () => { expect(rules[2].cssText).to.equal('#error-message { color: red; }'); } }); + + describe('adoptedStyleSheets', () => { + afterEach(() => { + document.adoptedStyleSheets = []; + }); + + it('should extract rules from tag-scoped @media at-rules', () => { + const style = new CSSStyleSheet(); + style.replaceSync(` + @media test-button { + :host { + color: black; + } + } + `); + + document.adoptedStyleSheets = [style]; + + const rules = extractTagScopedCSSRules(document, 'test-button'); + expect(rules).to.have.length(1); + expect(rules[0].cssText).to.equal(':host { color: black; }'); + }); + }); + + describe('shadowRoot', () => { + it('should extract rules from tag-scoped @media at-rules inside shadowRoot', () => { + const style = new CSSStyleSheet(); + style.replaceSync(` + @media test-button { + :host { + color: black; + } + } + `); + + const root = document.createElement('div'); + root.attachShadow({ mode: 'open' }); + root.shadowRoot.adoptedStyleSheets = [style]; + + const rules = extractTagScopedCSSRules(root.shadowRoot, 'test-button'); + expect(rules).to.have.length(1); + expect(rules[0].cssText).to.equal(':host { color: black; }'); + }); + }); }); From 25f4555fd4c0ab953d532ec137723498cdac4890 Mon Sep 17 00:00:00 2001 From: Sergey Vinogradov Date: Mon, 14 Apr 2025 17:22:11 +0400 Subject: [PATCH 63/66] fix duplicate stylesheets, add more tests --- .../vaadin-themable-mixin/src/css-rules.js | 9 ++- .../test/css-rules-extraction.test.js | 56 ++++++++++++++----- 2 files changed, 48 insertions(+), 17 deletions(-) diff --git a/packages/vaadin-themable-mixin/src/css-rules.js b/packages/vaadin-themable-mixin/src/css-rules.js index e4bc1c3ffd1..7a6522415d2 100644 --- a/packages/vaadin-themable-mixin/src/css-rules.js +++ b/packages/vaadin-themable-mixin/src/css-rules.js @@ -81,7 +81,10 @@ function extractStyleSheetTagScopedCSSRules(styleSheet, tagName) { * @return {CSSRule[]} */ export function extractTagScopedCSSRules(root, tagName) { - return [...root.adoptedStyleSheets, ...root.styleSheets].flatMap((styleSheet) => - extractStyleSheetTagScopedCSSRules(styleSheet, tagName), - ); + const styleSheets = new Set([...root.styleSheets]); + const adoptedStyleSheets = new Set([...root.adoptedStyleSheets]); + + return [...styleSheets.difference(adoptedStyleSheets), ...adoptedStyleSheets].flatMap((styleSheet) => { + return extractStyleSheetTagScopedCSSRules(styleSheet, tagName); + }); } diff --git a/packages/vaadin-themable-mixin/test/css-rules-extraction.test.js b/packages/vaadin-themable-mixin/test/css-rules-extraction.test.js index affe1e2506b..816b8f93d5d 100644 --- a/packages/vaadin-themable-mixin/test/css-rules-extraction.test.js +++ b/packages/vaadin-themable-mixin/test/css-rules-extraction.test.js @@ -5,7 +5,7 @@ import { extractTagScopedCSSRules } from '../src/css-rules.js'; const BASE_PATH = import.meta.url.split('/').slice(0, -1).join('/'); describe('CSS rules extraction', () => { - it('should extract rules from tag-scoped @media at-rules', () => { + it('should extract rules from tag-scoped @media', () => { fixtureSync(` + + `); + + const style = new CSSStyleSheet(); + style.replaceSync(` + @media test-button { + :host { + color: red; + } + } + `); + document.adoptedStyleSheets = [style]; + + const rules = extractTagScopedCSSRules(document, 'test-button'); + expect(rules).to.have.lengthOf(2); expect(rules[0].cssText).to.equal(':host { color: black; }'); + expect(rules[1].cssText).to.equal(':host { color: red; }'); }); }); describe('shadowRoot', () => { - it('should extract rules from tag-scoped @media at-rules inside shadowRoot', () => { + it('should extract rules from tag-scoped @media inside shadowRoot', () => { const style = new CSSStyleSheet(); style.replaceSync(` @media test-button { @@ -129,7 +157,7 @@ describe('CSS rules extraction', () => { root.shadowRoot.adoptedStyleSheets = [style]; const rules = extractTagScopedCSSRules(root.shadowRoot, 'test-button'); - expect(rules).to.have.length(1); + expect(rules).to.have.lengthOf(1); expect(rules[0].cssText).to.equal(':host { color: black; }'); }); }); From 6c9aeefa98af1fd1b9d0788b29ee33b7100f7d6a Mon Sep 17 00:00:00 2001 From: web-padawan Date: Mon, 14 Apr 2025 16:41:54 +0300 Subject: [PATCH 64/66] Remove extra whitespace --- .../vaadin-themable-mixin/test/css-rules-extraction.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vaadin-themable-mixin/test/css-rules-extraction.test.js b/packages/vaadin-themable-mixin/test/css-rules-extraction.test.js index 816b8f93d5d..a47a47914da 100644 --- a/packages/vaadin-themable-mixin/test/css-rules-extraction.test.js +++ b/packages/vaadin-themable-mixin/test/css-rules-extraction.test.js @@ -47,7 +47,7 @@ describe('CSS rules extraction', () => { } }); - it('should extract rules from tag-scoped @import ', async () => { + it('should extract rules from tag-scoped @import', async () => { const style = fixtureSync(` - `); - const style = new CSSStyleSheet(); style.replaceSync(` @media test-button { :host { - color: red; + color: black; } } `); document.adoptedStyleSheets = [style]; + fixtureSync(` + + `); + const rules = extractTagScopedCSSRules(document, 'test-button'); expect(rules).to.have.lengthOf(2); - expect(rules[0].cssText).to.equal(':host { color: black; }'); - expect(rules[1].cssText).to.equal(':host { color: red; }'); + expect(rules[0].cssText).to.equal(':host { color: red; }'); + expect(rules[1].cssText).to.equal(':host { color: black; }'); }); }); describe('shadowRoot', () => { + let root; + + beforeEach(() => { + root = fixtureSync('
'); + root.attachShadow({ mode: 'open' }); + }); + it('should extract rules from tag-scoped @media inside shadowRoot', () => { - const style = new CSSStyleSheet(); - style.replaceSync(` + const adoptedStyleSheet = new CSSStyleSheet(); + adoptedStyleSheet.replaceSync(` @media test-button { :host { color: black; } } `); + root.shadowRoot.adoptedStyleSheets = [adoptedStyleSheet]; - const root = document.createElement('div'); - root.attachShadow({ mode: 'open' }); - root.shadowRoot.adoptedStyleSheets = [style]; + root.shadowRoot.innerHTML = ` + + `; const rules = extractTagScopedCSSRules(root.shadowRoot, 'test-button'); - expect(rules).to.have.lengthOf(1); - expect(rules[0].cssText).to.equal(':host { color: black; }'); + expect(rules).to.have.lengthOf(2); + expect(rules[0].cssText).to.equal(':host { color: red; }'); + expect(rules[1].cssText).to.equal(':host { color: black; }'); }); }); }); From 1b998dce48d9abbb53a0fa0b9bf8169dbf0994de Mon Sep 17 00:00:00 2001 From: web-padawan Date: Fri, 18 Apr 2025 12:21:31 +0300 Subject: [PATCH 66/66] Use correct type in JSDoc --- packages/vaadin-themable-mixin/css-injection-mixin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vaadin-themable-mixin/css-injection-mixin.js b/packages/vaadin-themable-mixin/css-injection-mixin.js index 5dc4dd55ea3..24e0af545a8 100644 --- a/packages/vaadin-themable-mixin/css-injection-mixin.js +++ b/packages/vaadin-themable-mixin/css-injection-mixin.js @@ -6,7 +6,7 @@ import { CSSInjector } from './src/css-injector.js'; /** - * @type {string[]} + * @type {Set} */ const registeredProperties = new Set();