From d09973b5716fbdd1e8824bffa5d0a7b1dff3b568 Mon Sep 17 00:00:00 2001 From: Kelsey Jackson Date: Thu, 30 Oct 2025 09:22:39 -0500 Subject: [PATCH 01/59] updated sidebar (#1687) * updated sidebar * Update packages/webawesome/docs/_includes/sidebar.njk Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fixed pr --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- packages/webawesome/docs/_includes/sidebar.njk | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/packages/webawesome/docs/_includes/sidebar.njk b/packages/webawesome/docs/_includes/sidebar.njk index c6d0fd1cd..04cea0171 100644 --- a/packages/webawesome/docs/_includes/sidebar.njk +++ b/packages/webawesome/docs/_includes/sidebar.njk @@ -29,6 +29,8 @@
  • Contributing
  • Changelog
  • Visual Tests
  • + + @@ -143,12 +145,8 @@
  • Tag
  • Textarea
  • Tooltip
  • -
  • - Tree - -
  • +
  • Tree
  • +
  • Tree Item
  • Zoomable Frame
  • {# PLOP_NEW_COMPONENT_PLACEHOLDER #} @@ -367,9 +365,6 @@
  • Ecommerce
  • -
  • - App -
  • Blog
  • From 9c7b34188939e7494acd4d4ac9af87c0b988164e Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Thu, 30 Oct 2025 10:41:50 -0400 Subject: [PATCH 02/59] adding planned stretch goal-based components to sidebar.njk (#1682) --- packages/webawesome/docs/_includes/sidebar.njk | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/webawesome/docs/_includes/sidebar.njk b/packages/webawesome/docs/_includes/sidebar.njk index 04cea0171..ee902cd5d 100644 --- a/packages/webawesome/docs/_includes/sidebar.njk +++ b/packages/webawesome/docs/_includes/sidebar.njk @@ -29,7 +29,7 @@
  • Contributing
  • Changelog
  • Visual Tests
  • - + @@ -101,6 +101,7 @@
  • Dropdown Item
  • +
  • Format Bytes
  • Format Date
  • Format Number
  • @@ -109,6 +110,7 @@
  • Input
  • Intersection Observer
  • Mutation Observer
  • +
  • Popover
  • Popup
  • Progress Bar
  • @@ -144,9 +146,15 @@
  • Tag
  • Textarea
  • +
  • Tooltip
  • -
  • Tree
  • -
  • Tree Item
  • +
  • + Tree + +
  • +
  • Zoomable Frame
  • {# PLOP_NEW_COMPONENT_PLACEHOLDER #} From 479fff4b614cee840062cb6b35e33e225a121708 Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Thu, 30 Oct 2025 11:06:47 -0400 Subject: [PATCH 03/59] remove unsupported property; closes #1677 (#1689) --- .../webawesome/src/components/button-group/button-group.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/webawesome/src/components/button-group/button-group.ts b/packages/webawesome/src/components/button-group/button-group.ts index df776767b..0a491c3ad 100644 --- a/packages/webawesome/src/components/button-group/button-group.ts +++ b/packages/webawesome/src/components/button-group/button-group.ts @@ -3,7 +3,6 @@ import { html } from 'lit'; import { customElement, property, query, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import variantStyles from '../../styles/utilities/variants.css'; import type WaButton from '../button/button.js'; import styles from './button-group.css'; @@ -19,7 +18,7 @@ import styles from './button-group.css'; */ @customElement('wa-button-group') export default class WaButtonGroup extends WebAwesomeElement { - static css = [variantStyles, styles]; + static css = [styles]; @query('slot') defaultSlot: HTMLSlotElement; @@ -35,9 +34,6 @@ export default class WaButtonGroup extends WebAwesomeElement { /** The button group's orientation. */ @property({ reflect: true }) orientation: 'horizontal' | 'vertical' = 'horizontal'; - /** The button group's theme variant. Defaults to `neutral` if not within another element with a variant. */ - @property({ reflect: true }) variant: 'neutral' | 'brand' | 'success' | 'warning' | 'danger' = 'neutral'; - updated(changedProperties: PropertyValues) { super.updated(changedProperties); From eb2410ff8881528ebd0f31aaf77dc6d4dd3b0670 Mon Sep 17 00:00:00 2001 From: Cory LaViska Date: Thu, 30 Oct 2025 11:29:30 -0400 Subject: [PATCH 04/59] fix links (#1691) --- packages/webawesome/docs/docs/utilities/cluster.md | 4 ++-- packages/webawesome/docs/docs/utilities/flank.md | 4 ++-- packages/webawesome/docs/docs/utilities/frame.md | 2 +- packages/webawesome/docs/docs/utilities/grid.md | 2 +- packages/webawesome/docs/docs/utilities/split.md | 4 ++-- packages/webawesome/docs/docs/utilities/stack.md | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/webawesome/docs/docs/utilities/cluster.md b/packages/webawesome/docs/docs/utilities/cluster.md index 0a8adc013..e17d226fe 100644 --- a/packages/webawesome/docs/docs/utilities/cluster.md +++ b/packages/webawesome/docs/docs/utilities/cluster.md @@ -84,7 +84,7 @@ Clusters are great for inline lists and aligning items of varying sizes. ## Align Items -By default, items are centered in the block direction of the `wa-cluster` container. You can add any of the following [`wa-align-items-*`](/docs/style-utilities/align-items) classes to an element with `wa-cluster` to specify how items are aligned in the block direction: +By default, items are centered in the block direction of the `wa-cluster` container. You can add any of the following [`wa-align-items-*`](/docs/utilities/align-items) classes to an element with `wa-cluster` to specify how items are aligned in the block direction: - `wa-align-items-start` - `wa-align-items-end` @@ -119,7 +119,7 @@ By default, items are centered in the block direction of the `wa-cluster` contai ## Gap -By default, the gap between cluster items uses `--wa-space-m` from your theme. You can add any of the following [`wa-gap-*`](/docs/style-utilities/gap) classes to an element with `wa-cluster` to specify the gap between items: +By default, the gap between cluster items uses `--wa-space-m` from your theme. You can add any of the following [`wa-gap-*`](/docs/utilities/gap) classes to an element with `wa-cluster` to specify the gap between items: - `wa-gap-0` - `wa-gap-3xs` diff --git a/packages/webawesome/docs/docs/utilities/flank.md b/packages/webawesome/docs/docs/utilities/flank.md index ded9aa341..81abd2b5d 100644 --- a/packages/webawesome/docs/docs/utilities/flank.md +++ b/packages/webawesome/docs/docs/utilities/flank.md @@ -137,7 +137,7 @@ The main content fills the remaining inline space of the container. By default, ## Align Items -By default, items are centered in the block direction of the `wa-flank` container. You can add any of the following [`wa-align-items-*`](/docs/style-utilities/align-items) classes to an element with `wa-flank` to specify how items are aligned in the block direction: +By default, items are centered in the block direction of the `wa-flank` container. You can add any of the following [`wa-align-items-*`](/docs/utilities/align-items) classes to an element with `wa-flank` to specify how items are aligned in the block direction: - `wa-align-items-start` - `wa-align-items-end` @@ -168,7 +168,7 @@ By default, items are centered in the block direction of the `wa-flank` containe ## Gap -By default, the gap between flank items uses `--wa-space-m` from your theme. You can add any of the following [`wa-gap-*`](/docs/style-utilities/gap) classes to an element with `wa-flank` to specify the gap between items: +By default, the gap between flank items uses `--wa-space-m` from your theme. You can add any of the following [`wa-gap-*`](/docs/utilities/gap) classes to an element with `wa-flank` to specify the gap between items: - `wa-gap-0` - `wa-gap-3xs` diff --git a/packages/webawesome/docs/docs/utilities/frame.md b/packages/webawesome/docs/docs/utilities/frame.md index 4ee7f99fa..0473d32f8 100644 --- a/packages/webawesome/docs/docs/utilities/frame.md +++ b/packages/webawesome/docs/docs/utilities/frame.md @@ -139,7 +139,7 @@ Frames have a square aspect ratio by default. You can append `:square` (1 / 1), ## Border Radius -Frames have a square border radius by default. You can add any of the following [`wa-border-radius-*`](/docs/style-utilities/border-radius) classes to an element with `wa-frame` to specify the border radius: +Frames have a square border radius by default. You can add any of the following [`wa-border-radius-*`](/docs/utilities/border-radius) classes to an element with `wa-frame` to specify the border radius: - `wa-border-radius-s` - `wa-border-radius-m` diff --git a/packages/webawesome/docs/docs/utilities/grid.md b/packages/webawesome/docs/docs/utilities/grid.md index 356ab2128..3fec0aea2 100644 --- a/packages/webawesome/docs/docs/utilities/grid.md +++ b/packages/webawesome/docs/docs/utilities/grid.md @@ -169,7 +169,7 @@ By default, grid items will wrap when the grid's column size is less than `20ch` ## Gap -By default, the gap between grid items uses `--wa-space-m` from your theme. You can add any of the following [`wa-gap-*`](/docs/style-utilities/gap) classes to an element with `wa-grid` to specify the gap between items: +By default, the gap between grid items uses `--wa-space-m` from your theme. You can add any of the following [`wa-gap-*`](/docs/utilities/gap) classes to an element with `wa-grid` to specify the gap between items: - `wa-gap-0` - `wa-gap-3xs` diff --git a/packages/webawesome/docs/docs/utilities/split.md b/packages/webawesome/docs/docs/utilities/split.md index 1b23680a3..cd5dc8d18 100644 --- a/packages/webawesome/docs/docs/utilities/split.md +++ b/packages/webawesome/docs/docs/utilities/split.md @@ -106,7 +106,7 @@ Items can be split across a row or a column by appending `:row` or `:column` to ## Align Items -By default, items are centered on the cross axis of the `wa-split` container. You can add any of the following [`wa-align-items-*`](/docs/style-utilities/align-items) classes to an element with `wa-split` to specify how items are aligned: +By default, items are centered on the cross axis of the `wa-split` container. You can add any of the following [`wa-align-items-*`](/docs/utilities/align-items) classes to an element with `wa-split` to specify how items are aligned: - `wa-align-items-start` - `wa-align-items-end` @@ -139,7 +139,7 @@ These modifiers specify how items are aligned in the block direction for `wa-spl ## Gap -A split's gap determines how close items can be before they wrap. By default, the gap between split items uses `--wa-space-m` from your theme. You can add any of the following [`wa-gap-*`](/docs/style-utilities/gap) classes to an element with `wa-split` to specify the gap between items: +A split's gap determines how close items can be before they wrap. By default, the gap between split items uses `--wa-space-m` from your theme. You can add any of the following [`wa-gap-*`](/docs/utilities/gap) classes to an element with `wa-split` to specify the gap between items: - `wa-gap-0` - `wa-gap-3xs` diff --git a/packages/webawesome/docs/docs/utilities/stack.md b/packages/webawesome/docs/docs/utilities/stack.md index 561955807..cb93ef3aa 100644 --- a/packages/webawesome/docs/docs/utilities/stack.md +++ b/packages/webawesome/docs/docs/utilities/stack.md @@ -62,7 +62,7 @@ Stacks are well suited for forms, text, and ensuring consistent spacing between ## Align Items -By default, items stretch to fill the inline size of the `wa-stack` container. You can add any of the following [`wa-align-items-*`](/docs/style-utilities/align-items) classes to an element with `wa-stack` to specify how items are aligned in the inline direction: +By default, items stretch to fill the inline size of the `wa-stack` container. You can add any of the following [`wa-align-items-*`](/docs/utilities/align-items) classes to an element with `wa-stack` to specify how items are aligned in the inline direction: - `wa-align-items-start` - `wa-align-items-end` @@ -92,7 +92,7 @@ By default, items stretch to fill the inline size of the `wa-stack` container. Y ## Gap -By default, the gap between stack items uses `--wa-space-m` from your theme. You can add any of the following [`wa-gap-*`](/docs/style-utilities/gap) classes to an element with `wa-stack` to specify the gap between items: +By default, the gap between stack items uses `--wa-space-m` from your theme. You can add any of the following [`wa-gap-*`](/docs/utilities/gap) classes to an element with `wa-stack` to specify the gap between items: - `wa-gap-0` - `wa-gap-3xs` From 0f601df74afa29cdc84d6df0c6397baa8a240adf Mon Sep 17 00:00:00 2001 From: Brian Talbot Date: Fri, 31 Oct 2025 14:03:39 -0400 Subject: [PATCH 05/59] removing page title suffixes from search results (#1697) * Use for search indexing but strip trailing site suffix at build time * centralize title logic with shared composePageTitle() and stripSiteTitleSuffix() helpers * introduce single source of truth for site name and separators * keep client search rendering simple; no post-processing in the browser * preserve existing page/OG title behavior while improving search relevance --- packages/webawesome/docs/.eleventy.js | 40 +++++++++++++++++++++------ 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/packages/webawesome/docs/.eleventy.js b/packages/webawesome/docs/.eleventy.js index eda89ac3e..7580daa0c 100644 --- a/packages/webawesome/docs/.eleventy.js +++ b/packages/webawesome/docs/.eleventy.js @@ -102,6 +102,31 @@ export default async function (eleventyConfig) { image: 'https://webawesome.com/assets/images/open-graph/default.png', }; + // Title composition/stripping config - single source of truth + const SITE_NAME = siteMetadata.name; + const SITE_TITLE_SEPARATORS = ['|']; + + // Helper to escape user-provided strings for safe use inside RegExp sources + const escapeRegExp = string => (string + '').replace(/[.*+?^${}()|[\\]\\]/g, '\\$&'); + + // Precompute a reusable regex to strip a trailing site name suffix from titles, e.g. " | Web Awesome" + // Supports configured separators and flexible whitespace. This keeps search titles clean and improves Lunr scoring + const siteNameEscapedForRegex = escapeRegExp(SITE_NAME); + const separatorsEscaped = SITE_TITLE_SEPARATORS.map(s => escapeRegExp(s)).join(''); + const siteTitleSuffixPattern = new RegExp(`\\s*[${separatorsEscaped}]\\s*${siteNameEscapedForRegex}$`); + + // Helper to remove the site suffix from a page title. Keep this in sync with how page titles + // are composed (see eleventyComputed.pageTitle) so search indexing stays consistent + const stripSiteTitleSuffix = title => (title || '').replace(siteTitleSuffixPattern, ''); + + // Helper to compose a full page title with site suffix when appropriate + // Uses the same separator set as the stripping logic for consistency + const composePageTitle = baseTitle => { + const title = baseTitle || SITE_NAME; + const preferredSeparator = SITE_TITLE_SEPARATORS[0] || '|'; + return title !== SITE_NAME ? `${title} ${preferredSeparator} ${SITE_NAME}` : title; + }; + eleventyConfig.addGlobalData('siteMetadata', siteMetadata); // Template filters - {{ content | filter }} @@ -183,15 +208,9 @@ export default async function (eleventyConfig) { eleventyConfig.addGlobalData('eleventyComputed', { lastUpdatedISO: data => getLastModifiedISO(data.page?.inputPath, data.lastUpdated), // Page title with smart + default site name formatting - pageTitle: data => { - const title = data.title || siteMetadata.name; - return title !== siteMetadata.name ? `${title} | ${siteMetadata.name}` : title; - }, + pageTitle: data => composePageTitle(data.title), // Open Graph title with smart + default site name formatting - ogTitle: data => { - const ogTitle = data.ogTitle || data.title || siteMetadata.name; - return ogTitle !== siteMetadata.name ? `${ogTitle} | ${siteMetadata.name}` : ogTitle; - }, + ogTitle: data => composePageTitle(data.ogTitle || data.title), ogDescription: data => data.ogDescription || data.description, ogImage: data => data.ogImage || siteMetadata.image, ogUrl: data => { @@ -354,6 +373,11 @@ export default async function (eleventyConfig) { searchPlugin({ filename: '', selectorsToIgnore: ['code.example'], + // Use <title> but strip a trailing site name suffix for cleaner search results + getTitle: doc => { + const raw = doc.querySelector('title')?.textContent ?? ''; + return stripSiteTitleSuffix(raw); + }, getContent: doc => doc.querySelector('#content')?.textContent ?? '', }), ); From 46812e08c5daa6285cb5cc500bda90a0bab7a877 Mon Sep 17 00:00:00 2001 From: Cory LaViska <cory@abeautifulsite.net> Date: Mon, 3 Nov 2025 09:43:21 -0500 Subject: [PATCH 06/59] fix slider on touch; fixes #1703 (#1709) --- packages/webawesome/docs/docs/resources/changelog.md | 6 +++++- packages/webawesome/src/internal/drag.ts | 12 ++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index f6fb18584..e4e2c3847 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -10,6 +10,10 @@ Web Awesome follows [Semantic Versioning](https://semver.org/). Breaking changes Components with the <wa-badge variant="warning">Experimental</wa-badge> badge should not be used in production. They are made available as release candidates for development and testing purposes. As such, changes to experimental components will not be subject to semantic versioning. +## Next + +- Fixed a bug in `<wa-slider>` that caused some touch devices to end up with the incorrect value [issue:1703] + ## 3.0.0 - 🚨 BREAKING: Changed `appearance="filled outlined"` to `appearance="filled-outlined"` in the following elements [issue:1127] @@ -492,4 +496,4 @@ Many of these changes and improvements were the direct result of feedback from u </details> -Did we miss something? [Let us know!](https://github.com/shoelace-style/webawesome/discussions) \ No newline at end of file +Did we miss something? [Let us know!](https://github.com/shoelace-style/webawesome/discussions) diff --git a/packages/webawesome/src/internal/drag.ts b/packages/webawesome/src/internal/drag.ts index a1fe23e4b..10c249884 100644 --- a/packages/webawesome/src/internal/drag.ts +++ b/packages/webawesome/src/internal/drag.ts @@ -84,8 +84,8 @@ export class DraggableElement { } private handleDragStart = (event: PointerEvent | TouchEvent) => { - const clientX = supportsTouch && 'touches' in event ? event.touches[0].clientX : (event as PointerEvent).clientX; - const clientY = supportsTouch && 'touches' in event ? event.touches[0].clientY : (event as PointerEvent).clientY; + const clientX = 'touches' in event ? event.touches[0].clientX : (event as PointerEvent).clientX; + const clientY = 'touches' in event ? event.touches[0].clientY : (event as PointerEvent).clientY; if ( this.isDragging || @@ -105,8 +105,8 @@ export class DraggableElement { }; private handleDragStop = (event: PointerEvent | TouchEvent) => { - const clientX = supportsTouch && 'touches' in event ? event.touches[0].clientX : (event as PointerEvent).clientX; - const clientY = supportsTouch && 'touches' in event ? event.touches[0].clientY : (event as PointerEvent).clientY; + const clientX = 'touches' in event ? event.touches[0].clientX : (event as PointerEvent).clientX; + const clientY = 'touches' in event ? event.touches[0].clientY : (event as PointerEvent).clientY; this.isDragging = false; document.removeEventListener('pointerup', this.handleDragStop); @@ -117,8 +117,8 @@ export class DraggableElement { }; private handleDragMove = (event: PointerEvent | TouchEvent) => { - const clientX = supportsTouch && 'touches' in event ? event.touches[0].clientX : (event as PointerEvent).clientX; - const clientY = supportsTouch && 'touches' in event ? event.touches[0].clientY : (event as PointerEvent).clientY; + const clientX = 'touches' in event ? event.touches[0].clientX : (event as PointerEvent).clientX; + const clientY = 'touches' in event ? event.touches[0].clientY : (event as PointerEvent).clientY; // Prevent text selection while dragging window.getSelection()?.removeAllRanges(); From e2c7286ccc550384080027b86d4b9b41dd9d5959 Mon Sep 17 00:00:00 2001 From: Brian Talbot <hi.talbs@gmail.com> Date: Tue, 4 Nov 2025 14:10:59 -0500 Subject: [PATCH 07/59] Control 11ty-Generated Headings' Display via Front Matter (#1715) * controlling 11ty generated headings' display via frontmatter * addressing PR feedback * addressing additional PR feedback --- packages/webawesome/docs/_includes/base.njk | 9 +++++---- packages/webawesome/docs/assets/styles/utils.css | 1 - 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/webawesome/docs/_includes/base.njk b/packages/webawesome/docs/_includes/base.njk index 1a28284d0..45e1d609d 100644 --- a/packages/webawesome/docs/_includes/base.njk +++ b/packages/webawesome/docs/_includes/base.njk @@ -27,9 +27,8 @@ </script> </head> <body class="layout-{{ layout | stripExtension }} page-{{ pageClass or page.fileSlug or 'home' }}{{ ' page-wide' if wide }}"> - {% if hasBanner == undefined %} - {% set hasBanner = true %} - {% endif %} + {% if hasBanner == undefined %}{% set hasBanner = true %}{% endif %} + {% if hasGeneratedTitle == undefined %}{% set hasGeneratedTitle = true %}{% endif %} {% set defaultWaPageAttributes = defaultWaPageAttributes or { view: 'desktop', 'disable-navigation-toggle': true, 'mobile-breakpoint': 1180, 'disable-sticky': 'banner' } %} {% set waPageAttributes = waPageAttributes or {} %} @@ -120,7 +119,9 @@ <div id="flashes">{% server "flashes" %}</div> {% block header %} - <h1 class="title">{{ title }}</h1> + {% if hasGeneratedTitle %} + <h1 class="title">{{ title }}</h1> + {% endif %} {% endblock %} {% block beforeContent %}{% endblock %} diff --git a/packages/webawesome/docs/assets/styles/utils.css b/packages/webawesome/docs/assets/styles/utils.css index 8a2a4762e..52e56a948 100644 --- a/packages/webawesome/docs/assets/styles/utils.css +++ b/packages/webawesome/docs/assets/styles/utils.css @@ -122,7 +122,6 @@ --secondary-color: var(--wa-color-neutral-30); } } - /* #endregion */ /* #region funsies + cosmetics */ From 9ca3e6f39c1d946c2132675e249b16ab1107b0f2 Mon Sep 17 00:00:00 2001 From: Christian Oliff <christianoliff@pm.me> Date: Wed, 5 Nov 2025 21:25:13 +0900 Subject: [PATCH 08/59] Fix typo in 'Github' heading to 'GitHub' (#1720) --- packages/webawesome/docs/docs/resources/support.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webawesome/docs/docs/resources/support.md b/packages/webawesome/docs/docs/resources/support.md index 8f61a3bdd..ed079e447 100644 --- a/packages/webawesome/docs/docs/resources/support.md +++ b/packages/webawesome/docs/docs/resources/support.md @@ -11,7 +11,7 @@ layout: page <div class="wa-stack wa-gap-xl"> <div class="wa-stack wa-align-items-start"> <div class="wa-stack wa-gap-s"> - <h2 class="anchor-heading wa-cluster wa-gap-xs" data-no-anchor><wa-icon name="github" family="brands" style="font-size: 2ch;"></wa-icon>Github</h2> + <h2 class="anchor-heading wa-cluster wa-gap-xs" data-no-anchor><wa-icon name="github" family="brands" style="font-size: 2ch;"></wa-icon>GitHub</h2> <span class="wa-heading-m">Feature requests & bugs</span> <p class="wa-caption-l">Notice a bug or have an idea? Open an issue on GitHub so we can triage, track, and ship fixes.</p> </div> From 0bead172fe84e5af613e41e00eaa75e2cf19d67e Mon Sep 17 00:00:00 2001 From: Lindsay M <126139086+lindsaym-fa@users.noreply.github.com> Date: Wed, 5 Nov 2025 13:31:40 -0500 Subject: [PATCH 09/59] Tweak `<wa-radio appearance="button">` focus styles (#1695) --- packages/webawesome/src/components/radio/radio.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webawesome/src/components/radio/radio.css b/packages/webawesome/src/components/radio/radio.css index 617b297ad..159237479 100644 --- a/packages/webawesome/src/components/radio/radio.css +++ b/packages/webawesome/src/components/radio/radio.css @@ -174,7 +174,7 @@ } :host([appearance='button']:state(checked):focus-visible) { - outline: var(--wa-focus-ring-style) var(--wa-focus-ring-width) var(--wa-color-brand-border-loud); + outline: var(--wa-focus-ring); outline-offset: var(--wa-focus-ring-offset); } From 3d4b3ada272326f1b57294bb2996c2f914935ab2 Mon Sep 17 00:00:00 2001 From: Brian Talbot <hi.talbs@gmail.com> Date: Wed, 5 Nov 2025 14:06:56 -0500 Subject: [PATCH 10/59] Allow Setting `data-no-anchor` at the View-Level (#1718) * adding option to set 11ty's `data-no-anchor` at view-level * moving all `base.njk` initializations to before `<html>` * updating anchor-headings.js transformer logic --- packages/webawesome/docs/_includes/base.njk | 7 ++++--- .../docs/_transformers/anchor-headings.js | 18 ++++++++++++++++-- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/packages/webawesome/docs/_includes/base.njk b/packages/webawesome/docs/_includes/base.njk index 45e1d609d..ebab009ef 100644 --- a/packages/webawesome/docs/_includes/base.njk +++ b/packages/webawesome/docs/_includes/base.njk @@ -1,5 +1,8 @@ <!DOCTYPE html> -<html lang="en" data-fa-kit-code="38c11e3f20" data-version="{{ package.version }}" class="wa-cloak"> +{% if hasAnchors == undefined %}{% set hasAnchors = true %}{% endif %} +{% if hasBanner == undefined %}{% set hasBanner = true %}{% endif %} +{% if hasGeneratedTitle == undefined %}{% set hasGeneratedTitle = true %}{% endif %} +<html lang="en" data-fa-kit-code="38c11e3f20" data-version="{{ package.version }}" class="wa-cloak"{% if hasAnchors == false %} data-no-anchor{% endif %}> <head> {% include 'head.njk' %} <meta name="theme-color" content="#f36944"> @@ -27,8 +30,6 @@ </script> </head> <body class="layout-{{ layout | stripExtension }} page-{{ pageClass or page.fileSlug or 'home' }}{{ ' page-wide' if wide }}"> - {% if hasBanner == undefined %}{% set hasBanner = true %}{% endif %} - {% if hasGeneratedTitle == undefined %}{% set hasGeneratedTitle = true %}{% endif %} {% set defaultWaPageAttributes = defaultWaPageAttributes or { view: 'desktop', 'disable-navigation-toggle': true, 'mobile-breakpoint': 1180, 'disable-sticky': 'banner' } %} {% set waPageAttributes = waPageAttributes or {} %} diff --git a/packages/webawesome/docs/_transformers/anchor-headings.js b/packages/webawesome/docs/_transformers/anchor-headings.js index 12b150ff7..5b17f6674 100644 --- a/packages/webawesome/docs/_transformers/anchor-headings.js +++ b/packages/webawesome/docs/_transformers/anchor-headings.js @@ -35,9 +35,23 @@ export function anchorHeadingsTransformer(options = {}) { return doc; } - // Look for headings - let selector = `:is(${options.headingSelector}):not([data-no-anchor], [data-no-anchor] *)`; + // Check if the document or container has data-no-anchor (view-level) + const hasNoAnchorOnDocument = doc.querySelector('html')?.hasAttribute('data-no-anchor') || false; + const hasNoAnchorOnContainer = container.closest('[data-no-anchor]') !== null; + + // If view-level data-no-anchor is set, skip processing all headings + if (hasNoAnchorOnDocument || hasNoAnchorOnContainer) { + return doc; + } + + // Look for headings (selector excludes headings with data-no-anchor attribute) + let selector = `:is(${options.headingSelector}):not([data-no-anchor])`; container.querySelectorAll(selector).forEach(heading => { + // Skip if heading is a descendant of an element with data-no-anchor + // (selector already excludes headings with the attribute directly) + if (heading.closest('[data-no-anchor]') !== null) { + return; + } const hasAnchor = heading.querySelector('a'); const existingId = heading.getAttribute('id'); const clone = parse(heading.outerHTML); From 20628a98eb21b88b8847cb00da888014845a2b5a Mon Sep 17 00:00:00 2001 From: Brian Talbot <hi.talbs@gmail.com> Date: Thu, 6 Nov 2025 11:13:54 -0500 Subject: [PATCH 11/59] =?UTF-8?q?=F0=9F=A7=BC=20Anchor=20Link=20Clean=20Up?= =?UTF-8?q?=20(#1732)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * using wa-icon in generated anchors * moving anchor-heading styling from docs.css to utils.css * improving anchor-heading styling * adding subtle transition * using --wa-space-3xs custom property for padding --- .../docs/_transformers/anchor-headings.js | 2 +- .../webawesome/docs/assets/styles/docs.css | 20 ---------------- .../webawesome/docs/assets/styles/utils.css | 23 +++++++++++++++++++ 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/packages/webawesome/docs/_transformers/anchor-headings.js b/packages/webawesome/docs/_transformers/anchor-headings.js index 5b17f6674..27547f97a 100644 --- a/packages/webawesome/docs/_transformers/anchor-headings.js +++ b/packages/webawesome/docs/_transformers/anchor-headings.js @@ -79,7 +79,7 @@ export function anchorHeadingsTransformer(options = {}) { const anchor = parse(` <a href="#${encodeURIComponent(id)}"> <span class="wa-visually-hidden"></span> - <span aria-hidden="true">#</span> + <wa-icon variant="regular" name="hashtag" class="icon-shrink"></wa-icon> </a> `); anchor.querySelector('.wa-visually-hidden').textContent = options.anchorLabel; diff --git a/packages/webawesome/docs/assets/styles/docs.css b/packages/webawesome/docs/assets/styles/docs.css index ce4f633ec..43b7e60ae 100644 --- a/packages/webawesome/docs/assets/styles/docs.css +++ b/packages/webawesome/docs/assets/styles/docs.css @@ -351,26 +351,6 @@ h1.title { } } -/* Anchor headings */ -.anchor-heading a { - visibility: hidden; - text-decoration: none; -} - -@media (hover: hover) { - .anchor-heading:hover a { - visibility: visible; - padding: 0 0.125em; - } -} - -@media print { - /* Show URLs for printed links */ - a:not(.anchor-heading)[href]::after { - content: ' (' attr(href) ')'; - } -} - /* Callouts */ .callout { display: flex; diff --git a/packages/webawesome/docs/assets/styles/utils.css b/packages/webawesome/docs/assets/styles/utils.css index 52e56a948..2fe92fb0b 100644 --- a/packages/webawesome/docs/assets/styles/utils.css +++ b/packages/webawesome/docs/assets/styles/utils.css @@ -122,6 +122,29 @@ --secondary-color: var(--wa-color-neutral-30); } } + + /* anchor headings */ + .anchor-heading a { + opacity: 0; + visibility: hidden; + text-decoration: none; + transition: opacity var(--wa-transition-normal) var(--wa-transition-easing); + } + + @media (hover: hover) { + .anchor-heading:hover a { + opacity: 1; + visibility: visible; + padding: var(--wa-space-3xs); + } + } + + @media print { + /* show URLs for printed links */ + a:not(.anchor-heading)[href]::after { + content: ' (' attr(href) ')'; + } + } /* #endregion */ /* #region funsies + cosmetics */ From 34512554549a8e786751a789b8f3f385aa921cbb Mon Sep 17 00:00:00 2001 From: Brian Talbot <hi.talbs@gmail.com> Date: Thu, 6 Nov 2025 13:12:57 -0500 Subject: [PATCH 12/59] Docs: Add Free Badge (#1696) * adding Free Badge macro * using semantic color value for Pro Badge's `color` * removing unncessary `border-color` from Pro Badge's custom CSS * adding `data-pro-badge` attr to Pro Badge * using freeBadge and proBadge macros in themes.njk --- .../webawesome/docs/_includes/free-badge.njk | 7 +++++++ packages/webawesome/docs/_includes/pro-badge.njk | 2 +- packages/webawesome/docs/assets/styles/utils.css | 11 +++++++++-- packages/webawesome/docs/docs/themes.njk | 16 ++++++++++++---- 4 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 packages/webawesome/docs/_includes/free-badge.njk diff --git a/packages/webawesome/docs/_includes/free-badge.njk b/packages/webawesome/docs/_includes/free-badge.njk new file mode 100644 index 000000000..5ec8823f8 --- /dev/null +++ b/packages/webawesome/docs/_includes/free-badge.njk @@ -0,0 +1,7 @@ +{% macro freeBadge(params) %} + {% set description = params.description or "This feature is available in the free version of Web Awesome" %} + {% set badgeId = params.id or ("free-badge-" + ("" | uniqueId(8))) %} + <wa-badge appearance="filled" variant="neutral" pill class="free" id="{{ badgeId }}" data-free-badge>Free</wa-badge> + <wa-tooltip for="{{ badgeId }}">{{ description }}</wa-tooltip> +{% endmacro %} + diff --git a/packages/webawesome/docs/_includes/pro-badge.njk b/packages/webawesome/docs/_includes/pro-badge.njk index d67d37372..6c5b782db 100644 --- a/packages/webawesome/docs/_includes/pro-badge.njk +++ b/packages/webawesome/docs/_includes/pro-badge.njk @@ -1,6 +1,6 @@ {% macro proBadge(params) %} {% set description = params.description or "This requires access to Web Awesome Pro" %} {% set badgeId = params.id or ("pro-badge-" + ("" | uniqueId(8))) %} - <wa-badge appearance="accent" pill class="pro" id="{{ badgeId }}">Pro</wa-badge> + <wa-badge appearance="accent" pill class="pro" id="{{ badgeId }}" data-pro-badge>Pro</wa-badge> <wa-tooltip for="{{ badgeId }}">{{ description }}</wa-tooltip> {% endmacro %} diff --git a/packages/webawesome/docs/assets/styles/utils.css b/packages/webawesome/docs/assets/styles/utils.css index 2fe92fb0b..deb71148e 100644 --- a/packages/webawesome/docs/assets/styles/utils.css +++ b/packages/webawesome/docs/assets/styles/utils.css @@ -70,9 +70,8 @@ /* #region shared UI */ /* pro badge */ wa-badge.pro { - color: white; + color: var(--wa-color-brand-on-loud); background-color: var(--wa-brand-orange); - border-color: var(--wa-brand-orange); + wa-tooltip { font-size: var(--wa-font-size-xs); @@ -80,6 +79,14 @@ } } + /* free badge */ + wa-badge.free { + + wa-tooltip { + font-size: var(--wa-font-size-xs); + --max-width: unset; + } + } + /* planned badge */ wa-badge.planned { background-color: var(--wa-color-neutral-fill-quiet); diff --git a/packages/webawesome/docs/docs/themes.njk b/packages/webawesome/docs/docs/themes.njk index be6e20eae..103ecdb14 100644 --- a/packages/webawesome/docs/docs/themes.njk +++ b/packages/webawesome/docs/docs/themes.njk @@ -4,6 +4,9 @@ description: Style (and restyle) your website at will with any of Web Awesome's layout: page --- +{% from "pro-badge.njk" import proBadge %} +{% from "free-badge.njk" import freeBadge %} + <h1>{{ title }}</h1> <p>Themes are collections of <a href="/docs/tokens">design tokens</a> that give a cohesive look and feel to the entire Web Awesome library. Style and restyle your website at will by loading any pre-built theme.</p> @@ -64,8 +67,9 @@ to create a project with any one of these themes. <header class="wa-stack"> <div class="wa-cluster"> <h2 data-theme-name="name">Theme</h2> - <wa-badge data-free-badge appearance="outlined" variant="neutral" hidden>FREE</wa-badge> - <wa-badge data-pro-badge appearance="accent" hidden>PRO</wa-badge> + {{ freeBadge({ id: "free-badge", description: "This theme is available in the free version of Web Awesome." }) }} + {{ proBadge({ id: "pro-badge", description: "This theme requires access to Web Awesome Pro." }) }} + </div> <p data-theme-description>Description</p> </header> @@ -162,8 +166,12 @@ Then apply the following classes to the `<html>` element: const beforeFrame = document.querySelector('wa-zoomable-frame[slot="before"]'); const nameElement = document.querySelector('[data-theme-name]'); const descriptionElement = document.querySelector('[data-theme-description]'); - const freeBadge = document.querySelector('[data-free-badge]'); - const proBadge = document.querySelector('[data-pro-badge]'); + const freeBadge = document.getElementById('free-badge'); + const proBadge = document.getElementById('pro-badge'); + + // Hide both badges initially + if (freeBadge) freeBadge.hidden = true; + if (proBadge) proBadge.hidden = true; function updateFrames(selectedValue, title, description, isPro, palette, brand) { // Update theme classes on both frames From 9d6ded49b9fcd456cddb0c4df1782ea61cee820e Mon Sep 17 00:00:00 2001 From: Brian Talbot <hi.talbs@gmail.com> Date: Fri, 7 Nov 2025 13:35:09 -0500 Subject: [PATCH 13/59] Fix Broken Docs Links (#1740) * fixing broken links in docs/resources/contributing.md * removing broken links in docs/resources/changelog.md * fixing broken link in docs/components/button-group.md * fixing broken link in docs/components/comparison.md * fixing broken link in docs/components/divider.md * fixing broken link in docs/components/radio-group.md * fixing broken link in docs/utilities/frame.md * fixing broken link in docs/utilities/gap.md * fixing broken link in docs/utilities/rounding.md * Apply suggestions from code review Co-authored-by: Lindsay M <126139086+lindsaym-fa@users.noreply.github.com> * addressing PR feedback --------- Co-authored-by: Lindsay M <126139086+lindsaym-fa@users.noreply.github.com> --- .../docs/docs/components/button-group.md | 2 +- .../docs/docs/components/comparison.md | 2 +- .../docs/docs/components/divider.md | 4 ++-- .../docs/docs/components/radio-group.md | 4 ++-- .../docs/docs/resources/changelog.md | 22 +++++++++---------- .../docs/docs/resources/contributing.md | 4 ++-- .../webawesome/docs/docs/utilities/frame.md | 2 +- .../webawesome/docs/docs/utilities/gap.md | 2 +- .../docs/docs/utilities/rounding.md | 2 +- 9 files changed, 22 insertions(+), 22 deletions(-) diff --git a/packages/webawesome/docs/docs/components/button-group.md b/packages/webawesome/docs/docs/components/button-group.md index b84debe74..ce4c5ebf4 100644 --- a/packages/webawesome/docs/docs/components/button-group.md +++ b/packages/webawesome/docs/docs/components/button-group.md @@ -141,7 +141,7 @@ Dropdowns can be placed into button groups. ### Split Buttons -Create a split button using a button and a dropdown. Use a [visually hidden](/docs/components/visually-hidden) label to ensure the dropdown is accessible to users with assistive devices. +Create a split button using a button and a dropdown. Use a [visually hidden](/docs/utilities/visually-hidden) label to ensure the dropdown is accessible to users with assistive devices. ```html {.example} <wa-button-group label="Example Button Group"> diff --git a/packages/webawesome/docs/docs/components/comparison.md b/packages/webawesome/docs/docs/components/comparison.md index 9dfda62b9..c009ede7d 100644 --- a/packages/webawesome/docs/docs/components/comparison.md +++ b/packages/webawesome/docs/docs/components/comparison.md @@ -5,7 +5,7 @@ layout: component category: Imagery --- -This is especially useful for comparing images, but can be used for comparing any type of content (for an example of using it to compare entire UIs, check out our [theme pages](/docs/themes/default/)). +This is especially useful for comparing images, but can be used for comparing any type of content (for an example of using it to compare entire UIs, check out our [theme page](/docs/themes)). For best results, use content that shares the same dimensions. The slider can be controlled by dragging or pressing the left and right arrow keys. (Tip: press shift + arrows to move the slider in larger intervals, or home + end to jump to the beginning or end.) diff --git a/packages/webawesome/docs/docs/components/divider.md b/packages/webawesome/docs/docs/components/divider.md index 05b2f4ea9..37ced5e72 100644 --- a/packages/webawesome/docs/docs/components/divider.md +++ b/packages/webawesome/docs/docs/components/divider.md @@ -53,9 +53,9 @@ The default orientation for dividers is `horizontal`. Set `orientation` attribut </div> ``` -### Menu Dividers +### Dropdown Dividers -Use dividers in [menus](/docs/components/menu) to visually group menu items. +Use dividers in [dropdowns](/docs/components/dropdown) to visually group dropdown items. ```html {.example} <wa-dropdown style="max-width: 200px;"> diff --git a/packages/webawesome/docs/docs/components/radio-group.md b/packages/webawesome/docs/docs/components/radio-group.md index 57528e6e3..f5d6cfde8 100644 --- a/packages/webawesome/docs/docs/components/radio-group.md +++ b/packages/webawesome/docs/docs/components/radio-group.md @@ -102,7 +102,7 @@ The default orientation for radio items is `vertical`. Set the `orientation` to ### Sizing Options -The size of [Radios](/docs/components/radio) will be determined by the Radio Group's `size` attribute. +The size of radios will be determined by the Radio Group's `size` attribute. ```html {.example} <wa-radio-group label="Select an option" size="medium" value="medium" onchange="this.size = this.value"> @@ -113,7 +113,7 @@ The size of [Radios](/docs/components/radio) will be determined by the Radio Gro ``` :::info -[Radios](/docs/components/radio) and [Radio Buttons](/docs/components/radio-button) also have a `size` attribute, +[Radios](/docs/components/radio) also have a `size` attribute, which will override the inherited size when used. ::: diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index e4e2c3847..42210b8a9 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -249,7 +249,7 @@ Many of these changes and improvements were the direct result of feedback from u - 🚨 BREAKING: Renamed `<image-comparer>` to `<wa-comparison>` and improved compatibility for non-image content - 🚨 BREAKING: Added slot detection to `<wa-dialog>` and `<wa-drawer>` so you don't need to specify `with-header` and `with-footer`; headers are on by default now, but you can use the `without-header` attribute to turn them off - 🚨 BREAKING: Renamed the `image` slot to `media` for a more appropriate naming convention -- Added [a theme builder](/docs/themes/edit/) to create your own themes +- Added Theme Builder to create your own themes - Added a new Blog & News pattern category - Added a new free component: `<wa-scroller>` (#1 of 14 per stretch goals) - Added support for Duotone Thin, Light, and Regular styles and the Sharp Duotone family of styles to `<wa-icon>` @@ -266,7 +266,7 @@ Many of these changes and improvements were the direct result of feedback from u ### Enhancements {data-no-outline} -- Added `appearance` to [`<wa-details>`](/docs/components/details) and [`<wa-card>`](/docs/components/card) and support for the [appearance utilities](/docs/utilities/appearance/) in the [`<details>` native styles](/docs/utilities/native/details). +- Added `appearance` to [`<wa-details>`](/docs/components/details) and [`<wa-card>`](/docs/components/card) and support for the appearance utilities in the [`<details>` native styles](/docs/utilities/native/#details). - Added an `orange` scale to all color palettes - Added the [`.wa-cloak` utility](/docs/utilities/fouce) to prevent FOUCE - Added the [`allDefined()` utility](/docs/usage/#all-defined) for awaiting component registration @@ -291,7 +291,7 @@ Many of these changes and improvements were the direct result of feedback from u - Revert `<wa-dialog>` structure and CSS to fix clipped content in dialogs (WA-A #123) and light dismiss in iOS Safari (WA-A #201) - Fixed a bug in `<wa-color-picker>` that prevented light dismiss from working when clicking immediately above the color picker dropdown - Fixed a bug in `<wa-progress>` that prevented Safari from animation progress changes -- Fixed the missing indeterminate icon in [native checkbox styles](/docs/utilities/native/checkbox) +- Fixed the missing indeterminate icon in [native checkbox styles](/docs/utilities/native/#form-controls) - Fixed a bug in `<wa-radio>` where elements would stack instead of display inline - Docs fixes: - Fixed the search dialog's styles so it doesn't jump around as you search @@ -438,13 +438,13 @@ Many of these changes and improvements were the direct result of feedback from u ### Native styles {data-no-outline} - Added native styles for - [buttons](/docs/utilities/native/button), - [input fields](/docs/utilities/native/input), - [dialogs](/docs/utilities/native/dialog), - [details](/docs/utilities/native/details), - [tables](/docs/utilities/native/table), - [lists](/docs/utilities/native/lists), - and most [content elements](/docs/utilities/native/content). + [buttons](/docs/utilities/native/#buttons), + [input fields](/docs/utilities/native/#form-controls), + [dialogs](/docs/utilities/native/#dialog), + [details](/docs/utilities/native/#details), + [tables](/docs/utilities/native/#tables), + [lists](/docs/utilities/native/#lists), + and most [content elements](/docs/utilities/native/#typography). ### Style utilities {data-no-outline} @@ -452,7 +452,7 @@ Many of these changes and improvements were the direct result of feedback from u - Added [appearance utilities](/docs/utilities/appearance/) - Added [size utilities](/docs/utilities/size/) - Added [layout utilities](/docs/layout/#utilities) -- Added [`.wa-visually hidden`](/docs/utilities/a11y/#visually-hidden) utility +- Added [`.wa-visually hidden`](/docs/utilities/visually-hidden) utility - Added [`<wa-page>`](/docs/components/page/#styles) native styles and utilities ### Components {data-no-outline} diff --git a/packages/webawesome/docs/docs/resources/contributing.md b/packages/webawesome/docs/docs/resources/contributing.md index 9af29f930..14434d585 100644 --- a/packages/webawesome/docs/docs/resources/contributing.md +++ b/packages/webawesome/docs/docs/resources/contributing.md @@ -164,7 +164,7 @@ This section can be a lot to digest in one sitting, so don't feel like you need Web Awesome is built with accessibility in mind. Creating generic components that are fully accessible to users with varying capabilities across a multitude of circumstances is a daunting challenge. Oftentimes, the solution to an a11y problem is not written in black and white and, therefore, we may not get it right the first time around. There are, however, guidelines we can follow in our effort to make Web Awesome an accessible foundation from which applications and websites can be built. -We take this commitment seriously, so please ensure your contributions have this goal in mind. If you need help with anything a11y-related, please [reach out to the community](/resources/community) for assistance. If you discover an accessibility concern within the library, please file a bug on the [issue tracker](https://github.com/shoelace-style/webawesome/issues). +We take this commitment seriously, so please ensure your contributions have this goal in mind. If you need help with anything a11y-related, please reach out on the [community chat](https://discord.gg/mg8f26C) for assistance. If you discover an accessibility concern within the library, please file a bug on the [issue tracker](https://github.com/shoelace-style/webawesome/issues). It's important to remember that, although accessibility starts with foundational components, it doesn't end with them. It everyone's responsibility to encourage best practices and ensure we're providing an optimal experience for all of our users. @@ -364,7 +364,7 @@ Form controls should support submission and validation through the following con ### System Icons -Avoid inlining SVG icons inside of templates. If a component requires an icon, make sure `<wa-icon>` is a dependency of the component and use the [system library](/components/icon#customizing-the-system-library): +Avoid inlining SVG icons inside of templates. If a component requires an icon, make sure `<wa-icon>` is a dependency of the component and use the [system library](/docs/components/icon#customizing-the-system-library): ```html <wa-icon library="system" name="..." variant="..."></wa-icon> diff --git a/packages/webawesome/docs/docs/utilities/frame.md b/packages/webawesome/docs/docs/utilities/frame.md index 0473d32f8..d93ec763c 100644 --- a/packages/webawesome/docs/docs/utilities/frame.md +++ b/packages/webawesome/docs/docs/utilities/frame.md @@ -139,7 +139,7 @@ Frames have a square aspect ratio by default. You can append `:square` (1 / 1), ## Border Radius -Frames have a square border radius by default. You can add any of the following [`wa-border-radius-*`](/docs/utilities/border-radius) classes to an element with `wa-frame` to specify the border radius: +Frames have a square border radius by default. You can add any of the following [`wa-border-radius-*`](/docs/utilities/rounding) classes to an element with `wa-frame` to specify the border radius: - `wa-border-radius-s` - `wa-border-radius-m` diff --git a/packages/webawesome/docs/docs/utilities/gap.md b/packages/webawesome/docs/docs/utilities/gap.md index 061930786..332348ed9 100644 --- a/packages/webawesome/docs/docs/utilities/gap.md +++ b/packages/webawesome/docs/docs/utilities/gap.md @@ -14,7 +14,7 @@ tags: layoutUtilities } </style> -Web Awesome includes classes to set the `gap` property of flex and grid containers. They can be used alongside other Web Awesome layout utilities, like [cluster](/docs/layout/cluster) and [stack](/docs/layout/stack), to change the space between items. +Web Awesome includes classes to set the `gap` property of flex and grid containers. They can be used alongside other Web Awesome layout utilities, like [cluster](/docs/utilities/cluster) and [stack](/docs/utilities/stack), to change the space between items. Or even by themselves — all gap properties also set `display: flex` with a specificity of 0 so that it can be trivially overridden. Besides `wa-gap-0`, which sets `gap` to zero, each class corresponds to one of the [`--wa-space-*`](/docs/tokens/space) tokens in your theme. diff --git a/packages/webawesome/docs/docs/utilities/rounding.md b/packages/webawesome/docs/docs/utilities/rounding.md index b0637d501..2495f4141 100644 --- a/packages/webawesome/docs/docs/utilities/rounding.md +++ b/packages/webawesome/docs/docs/utilities/rounding.md @@ -12,7 +12,7 @@ tags: styleUtilities } </style> -Web Awesome includes classes to set an element's `border-radius` property. They can be used alongside Web Awesome layout utilities, like [frame](/docs/layout/frame), to round all corners of an element. +Web Awesome includes classes to set an element's `border-radius` property. They can be used alongside Web Awesome layout utilities, like [frame](/docs/utilities/frame), to round all corners of an element. Each class corresponds to one of the [`--wa-border-radius-*`](/docs/tokens/borders/#radius) tokens in your theme. From dfaa9599512be10c168067819cf0060d86444016 Mon Sep 17 00:00:00 2001 From: Brian Talbot <hi.talbs@gmail.com> Date: Wed, 12 Nov 2025 14:46:04 -0500 Subject: [PATCH 14/59] Add Launch Sale Dialog (#1739) * adding site-wide dialog to base.njk * adding logic to control dialog rendering * adding sale-based site-wide dialog * overriding docs brand props for `#dialog-site` * adding plausible event tracking to sale-based site-wide dialog --- .../docs/_includes/_dialog-wa-launch.njk | 97 +++++++++++++++++++ packages/webawesome/docs/_includes/base.njk | 10 ++ .../webawesome/docs/assets/styles/docs.css | 18 ++++ 3 files changed, 125 insertions(+) create mode 100644 packages/webawesome/docs/_includes/_dialog-wa-launch.njk diff --git a/packages/webawesome/docs/_includes/_dialog-wa-launch.njk b/packages/webawesome/docs/_includes/_dialog-wa-launch.njk new file mode 100644 index 000000000..8ecb35a4e --- /dev/null +++ b/packages/webawesome/docs/_includes/_dialog-wa-launch.njk @@ -0,0 +1,97 @@ +<wa-dialog id="dialog-site" light-dismiss without-header> + + <div class="background-wa-pattern" style="justify-content: center; margin-inline: calc(var(--spacing) * -1); margin-block-start: calc(var(--spacing) * -1); margin-block-end: var(--spacing); background: linear-gradient(to bottom, var(--wa-color-brand), var(--wa-color-brand-50)); color: var(--wa-color-brand-on-loud); padding: var(--wa-space-3xl) var(--spacing); --background-pattern-opacity: 0.2; --background-pattern-image: url('/assets/images/bg-wa-pattern.svg');"> + + <div class="wa-stack wa-align-items-center" style="text-align: center;"> + <wa-icon name="party-horn" family="duotone" variant="solid" style="font-size: var(--wa-font-size-3xl); --secondary-color: var(--wa-color-brand-on-quiet); --secondary-opacity: 1.0;"></wa-icon> + <h2 class="wa-heading-2xl brand-font">Get a lifetime discount on Web Awesome Pro!</h2> + </div> + </div> + + <div class="wa-stack wa-gap-l"> + <p>Celebrate our official launch with a 20% discount on a Web Awesome Pro plan…<span class="appearance-underlined variant-drawn" style="--underline-color: var(--wa-color-brand);">for life</span>! But hurry, this lifetime discount is only available for a limited time.</p> + + <div class="wa-split"> + <wa-button type="button" appearance="plain" data-dialog="close" class="plausible-event-name=launch_dialog:close_button_click">Maybe Later</wa-button> + <wa-button variant="neutral" appearance="accent" href="/purchase" class="brand-font plausible-event-name=launch_dialog:pro_purchase_button_click"> + <wa-icon slot="start" variant="regular" name="rocket-launch"></wa-icon> + Get Pro + Save 20% + </wa-button> + </div> + </div> +</wa-dialog> + +<script type="module"> + (function() { + const SITE_DIALOG_DISMISSED_KEY = 'dialog-wa-launch-sale-dismissed'; + + // Early exit if user has dismissed the dialog + try { + if (localStorage.getItem(SITE_DIALOG_DISMISSED_KEY) === 'true') { + return; + } + } catch (e) { + // localStorage may be disabled or unavailable + return; + } + + // Wait for DOM and dialog element + const dialog = document.getElementById('dialog-site'); + if (!dialog) { + return; + } + + // Helper function to safely track Plausible events + const trackEvent = (eventName) => { + if (typeof plausible !== 'undefined') { + plausible(eventName); + } + }; + + // Initialize dialog functionality + let initCalled = false; + const initDialog = () => { + // Prevent double initialization + if (initCalled) { + return; + } + initCalled = true; + + // Track when dialog is shown + dialog.addEventListener('wa-show', () => { + trackEvent('launch_dialog:view'); + }, { once: true }); + + // Track when dialog is dismissed + dialog.addEventListener('wa-hide', (event) => { + // Track overlay click or Escape key dismissal + // Button clicks are tracked via CSS classes, so we only track non-button dismissals + if (event.detail?.source === dialog) { + trackEvent('launch_dialog:overlay_click'); + } + + // Save dismissal state to localStorage + try { + localStorage.setItem(SITE_DIALOG_DISMISSED_KEY, 'true'); + } catch (e) { + // localStorage may be disabled or unavailable + } + }, { once: true }); + + // Show dialog after a short delay to ensure page is loaded + setTimeout(() => { + dialog.open = true; + }, 500); + }; + + // Initialize when ready + if (customElements.get('wa-dialog')) { + initDialog(); + } else { + document.addEventListener('wa-discovery-complete', initDialog, { once: true }); + // Fallback timeout in case wa-discovery-complete doesn't fire + setTimeout(initDialog, 100); + } + })(); +</script> + diff --git a/packages/webawesome/docs/_includes/base.njk b/packages/webawesome/docs/_includes/base.njk index ebab009ef..96b6f1094 100644 --- a/packages/webawesome/docs/_includes/base.njk +++ b/packages/webawesome/docs/_includes/base.njk @@ -1,6 +1,7 @@ <!DOCTYPE html> {% if hasAnchors == undefined %}{% set hasAnchors = true %}{% endif %} {% if hasBanner == undefined %}{% set hasBanner = true %}{% endif %} +{% if hasSiteDialog == undefined %}{% set hasSiteDialog = true %}{% endif %} {% if hasGeneratedTitle == undefined %}{% set hasGeneratedTitle = true %}{% endif %} <html lang="en" data-fa-kit-code="38c11e3f20" data-version="{{ package.version }}" class="wa-cloak"{% if hasAnchors == false %} data-no-anchor{% endif %}> <head> @@ -136,6 +137,15 @@ {% include 'search.njk' %} + {#- Site-Wide Dialog -#} + {% if hasSiteDialog %} + {% raw %} + {%- if not currentUser.hasPro -%} + {% include "_dialog-wa-launch.njk" ignore missing %} + {%- endif -%} + {% endraw %} + {% endif %} + {# Footer #} {% block pageFooter %}{% endblock %} </wa-page> diff --git a/packages/webawesome/docs/assets/styles/docs.css b/packages/webawesome/docs/assets/styles/docs.css index 43b7e60ae..914a77783 100644 --- a/packages/webawesome/docs/assets/styles/docs.css +++ b/packages/webawesome/docs/assets/styles/docs.css @@ -64,6 +64,24 @@ wa-page > [slot='banner'] { } } +/* Site-Wide Dialog */ +#dialog-site { + /* custom brand colors carrried over from theme-site for the banner */ + --wa-color-brand-95: #fef0ec; + --wa-color-brand-90: #fce0d8; + --wa-color-brand-80: #f8bcac; + --wa-color-brand-70: #fa9378; + --wa-color-brand-60: #f46a45; + --wa-color-brand-50: #cb4b27; + --wa-color-brand-40: #9d371a; + --wa-color-brand-30: #7c2a13; + --wa-color-brand-20: #5d1d0b; + --wa-color-brand-10: #3b0f05; + --wa-color-brand-05: #270802; + --wa-color-brand: var(--wa-color-brand-60); + --wa-color-brand-on: var(--wa-color-brand-10); +} + /* Header */ wa-page::part(header) { background-color: var(--wa-color-surface-default); From 9a3fc59e6a93906fff56ba267f5487be6a964bbe Mon Sep 17 00:00:00 2001 From: Brian Talbot <hi.talbs@gmail.com> Date: Wed, 12 Nov 2025 15:21:28 -0500 Subject: [PATCH 15/59] forcing brand color on launch sale dialog (#1764) * to override any docs-based brand --- packages/webawesome/docs/_includes/_dialog-wa-launch.njk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webawesome/docs/_includes/_dialog-wa-launch.njk b/packages/webawesome/docs/_includes/_dialog-wa-launch.njk index 8ecb35a4e..a33276141 100644 --- a/packages/webawesome/docs/_includes/_dialog-wa-launch.njk +++ b/packages/webawesome/docs/_includes/_dialog-wa-launch.njk @@ -3,7 +3,7 @@ <div class="background-wa-pattern" style="justify-content: center; margin-inline: calc(var(--spacing) * -1); margin-block-start: calc(var(--spacing) * -1); margin-block-end: var(--spacing); background: linear-gradient(to bottom, var(--wa-color-brand), var(--wa-color-brand-50)); color: var(--wa-color-brand-on-loud); padding: var(--wa-space-3xl) var(--spacing); --background-pattern-opacity: 0.2; --background-pattern-image: url('/assets/images/bg-wa-pattern.svg');"> <div class="wa-stack wa-align-items-center" style="text-align: center;"> - <wa-icon name="party-horn" family="duotone" variant="solid" style="font-size: var(--wa-font-size-3xl); --secondary-color: var(--wa-color-brand-on-quiet); --secondary-opacity: 1.0;"></wa-icon> + <wa-icon name="party-horn" family="duotone" variant="solid" style="font-size: var(--wa-font-size-3xl); --secondary-color: var(--wa-color-brand-40); --secondary-opacity: 1.0;"></wa-icon> <h2 class="wa-heading-2xl brand-font">Get a lifetime discount on Web Awesome Pro!</h2> </div> </div> From e695b54dcd42b6287dfc75425352350710a585ec Mon Sep 17 00:00:00 2001 From: Brian Talbot <hi.talbs@gmail.com> Date: Thu, 13 Nov 2025 11:25:29 -0500 Subject: [PATCH 16/59] Revert Custom Last Modified Utility (#1750) * reverting custom `getLastModifiedISO` utility * using 11ty's `git Last Modified` on docs/resources/changelog * dumbing down "Last updated" date to be static --- packages/webawesome/docs/.eleventy.js | 96 ------------------- .../docs/docs/resources/changelog.md | 3 +- 2 files changed, 2 insertions(+), 97 deletions(-) diff --git a/packages/webawesome/docs/.eleventy.js b/packages/webawesome/docs/.eleventy.js index 7580daa0c..8420ec379 100644 --- a/packages/webawesome/docs/.eleventy.js +++ b/packages/webawesome/docs/.eleventy.js @@ -1,6 +1,5 @@ import { nanoid } from 'nanoid'; import { parse as HTMLParse } from 'node-html-parser'; -import { execFileSync } from 'node:child_process'; import * as fs from 'node:fs'; import * as path from 'node:path'; import { anchorHeadingsTransformer } from './_transformers/anchor-headings.js'; @@ -21,7 +20,6 @@ import { replaceTextPlugin } from './_plugins/replace-text.js'; import { searchPlugin } from './_plugins/search.js'; const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); const isDev = process.argv.includes('--develop'); -const ignoreGit = process.env.ELEVENTY_IGNORE_GIT === 'true'; const passThroughExtensions = ['js', 'css', 'png', 'svg', 'jpg', 'mp4']; async function getPackageData() { @@ -60,15 +58,6 @@ export default async function (eleventyConfig) { if (updateComponentData) { allComponents = getComponents(); } - - // Invalidate last-modified cache for changed content files during watch - if (Array.isArray(changedFiles)) { - for (const file of changedFiles) { - if (/\.(md|njk|html)$/i.test(file)) { - lastModCache.delete(file); - } - } - } }); /** @@ -135,91 +124,6 @@ export default async function (eleventyConfig) { eleventyConfig.addFilter('stripExtension', string => path.parse(string + '').name); eleventyConfig.addFilter('stripPrefix', content => content.replace(/^wa-/, '')); eleventyConfig.addFilter('uniqueId', (_value, length = 8) => nanoid(length)); - // Returns last modified date as ISO 8601 (UTC, Z-suffixed) - // Fallback order: front matter override -> Git last commit date -> filesystem mtime -> now - // Caching: in-memory per inputPath during one build/dev session - // Override: pass a Date or string: {{ page.inputPath | gitLastModifiedISO(lastUpdated) }} - const lastModCache = new Map(); - let repoRoot = null; // lazily resolved; null => not resolved, undefined => failed - - function getLastModifiedISO(inputPath, overrideDate) { - if (overrideDate instanceof Date) { - return overrideDate.toISOString(); - } - if (typeof overrideDate === 'string' && overrideDate) { - const parsed = new Date(overrideDate); - if (!isNaN(parsed.getTime())) return parsed.toISOString(); - } - if (!inputPath) return new Date().toISOString(); - if (lastModCache.has(inputPath)) return lastModCache.get(inputPath); - - // Try Git (ISO via %cI). Use a repo-root-relative path for portability. - if (!ignoreGit) { - try { - if (repoRoot === null) { - try { - repoRoot = execFileSync('git', ['rev-parse', '--show-toplevel'], { - stdio: ['ignore', 'pipe', 'ignore'], - cwd: __dirname, - }) - .toString() - .trim(); - } catch (_) { - repoRoot = undefined; - } - } - - const gitPath = repoRoot ? path.relative(repoRoot, inputPath) : inputPath; - const args = ['log', '-1', '--format=%cI', '--follow', '--', gitPath]; - const result = execFileSync('git', args, { - stdio: ['ignore', 'pipe', 'ignore'], - cwd: repoRoot || path.dirname(inputPath), - }) - .toString() - .trim(); - if (result) { - const iso = new Date(result).toISOString(); - lastModCache.set(inputPath, iso); - return iso; - } - } catch (_) { - // continue to fs fallback - } - } - - // Fallback to filesystem mtime - try { - const stats = fs.statSync(inputPath); - const iso = new Date(stats.mtime).toISOString(); - lastModCache.set(inputPath, iso); - return iso; - } catch (_) { - const now = new Date().toISOString(); - lastModCache.set(inputPath, now); - return now; - } - } - - eleventyConfig.addFilter('gitLastModifiedISO', function (inputPath, overrideDate) { - return getLastModifiedISO(inputPath, overrideDate); - }); - - // Attach lastUpdatedISO to page data so templates can use {{ lastUpdatedISO }} directly - eleventyConfig.addGlobalData('eleventyComputed', { - lastUpdatedISO: data => getLastModifiedISO(data.page?.inputPath, data.lastUpdated), - // Page title with smart + default site name formatting - pageTitle: data => composePageTitle(data.title), - // Open Graph title with smart + default site name formatting - ogTitle: data => composePageTitle(data.ogTitle || data.title), - ogDescription: data => data.ogDescription || data.description, - ogImage: data => data.ogImage || siteMetadata.image, - ogUrl: data => { - if (data.ogUrl) return data.ogUrl; - const url = data.page?.url || ''; - return url ? `${siteMetadata.url}${url}` : siteMetadata.url; - }, - ogType: data => data.ogType || 'website', - }); // Trims whitespace and pipes from the start and end of a string. Useful for CEM types, which can be pipe-delimited. // With Prettier 3, this means a leading pipe will exist be present when the line wraps. eleventyConfig.addFilter('trimPipes', content => { diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index 42210b8a9..8e31fd659 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -1,10 +1,11 @@ --- title: Changelog +dateLastUpdated: 2025-11-07 description: Changes to each version of the project are documented here. layout: page-outline --- -<p class="wa-caption-s">Last updated: <wa-format-date month="long" day="numeric" year="numeric" date="{{ lastUpdatedISO }}"></wa-format-date></p> +<p class="wa-caption-s">Last updated: <wa-format-date month="long" day="numeric" year="numeric" date="{{ dateLastUpdated }}"></wa-format-date></p> Web Awesome follows [Semantic Versioning](https://semver.org/). Breaking changes in components with the <wa-badge variant="brand">Stable</wa-badge> badge will not be accepted until the next major version. As such, all contributions must consider the project's roadmap and take this into consideration. Features that are deemed no longer necessary will be deprecated but not removed. From 5a6ce1c4d397e5e64c9ed513e2fc2d18fe142184 Mon Sep 17 00:00:00 2001 From: Brian Talbot <hi.talbs@gmail.com> Date: Thu, 13 Nov 2025 11:46:55 -0500 Subject: [PATCH 17/59] Gating Launch Sale-Based Banner and Dialog (#1769) * gating wa-launch banner and dialog based on coupon state * addressing PR feedback * Update packages/webawesome/docs/_includes/_dialog-wa-launch.njk Co-authored-by: Konnor Rogers <konnor5456@gmail.com> --------- Co-authored-by: Konnor Rogers <konnor5456@gmail.com> --- .../docs/_includes/_banner-wa-launch.njk | 2 +- .../docs/_includes/_dialog-wa-launch.njk | 166 +++++++++--------- packages/webawesome/docs/_includes/base.njk | 6 +- 3 files changed, 84 insertions(+), 90 deletions(-) diff --git a/packages/webawesome/docs/_includes/_banner-wa-launch.njk b/packages/webawesome/docs/_includes/_banner-wa-launch.njk index 50e771eee..02dd3f387 100644 --- a/packages/webawesome/docs/_includes/_banner-wa-launch.njk +++ b/packages/webawesome/docs/_includes/_banner-wa-launch.njk @@ -1,5 +1,5 @@ {% raw %} - {%- if not currentUser.hasPro -%} + {%- if req.stripe.discount.active and not currentUser.hasPro -%} <div slot="banner" class="banner-wa-launch wa-dark"> <div class="banner-content wa-split"> <div class="wa-cluster wa-gap-s"> diff --git a/packages/webawesome/docs/_includes/_dialog-wa-launch.njk b/packages/webawesome/docs/_includes/_dialog-wa-launch.njk index a33276141..e1ddbeb19 100644 --- a/packages/webawesome/docs/_includes/_dialog-wa-launch.njk +++ b/packages/webawesome/docs/_includes/_dialog-wa-launch.njk @@ -1,97 +1,95 @@ -<wa-dialog id="dialog-site" light-dismiss without-header> +{% raw %} + {%- if req.stripe.discount.active and not currentUser.hasPro -%} + <wa-dialog id="dialog-site" light-dismiss without-header> - <div class="background-wa-pattern" style="justify-content: center; margin-inline: calc(var(--spacing) * -1); margin-block-start: calc(var(--spacing) * -1); margin-block-end: var(--spacing); background: linear-gradient(to bottom, var(--wa-color-brand), var(--wa-color-brand-50)); color: var(--wa-color-brand-on-loud); padding: var(--wa-space-3xl) var(--spacing); --background-pattern-opacity: 0.2; --background-pattern-image: url('/assets/images/bg-wa-pattern.svg');"> + <div class="background-wa-pattern" style="justify-content: center; margin-inline: calc(var(--spacing) * -1); margin-block-start: calc(var(--spacing) * -1); margin-block-end: var(--spacing); background: linear-gradient(to bottom, var(--wa-color-brand), var(--wa-color-brand-50)); color: var(--wa-color-brand-on-loud); padding: var(--wa-space-3xl) var(--spacing); --background-pattern-opacity: 0.2; --background-pattern-image: url('/assets/images/bg-wa-pattern.svg');"> - <div class="wa-stack wa-align-items-center" style="text-align: center;"> - <wa-icon name="party-horn" family="duotone" variant="solid" style="font-size: var(--wa-font-size-3xl); --secondary-color: var(--wa-color-brand-40); --secondary-opacity: 1.0;"></wa-icon> - <h2 class="wa-heading-2xl brand-font">Get a lifetime discount on Web Awesome Pro!</h2> - </div> - </div> + <div class="wa-stack wa-align-items-center" style="text-align: center;"> + <wa-icon name="party-horn" family="duotone" variant="solid" style="font-size: var(--wa-font-size-3xl); --secondary-color: var(--wa-color-brand-40); --secondary-opacity: 1.0;"></wa-icon> + <h2 class="wa-heading-2xl brand-font">Get a lifetime discount on Web Awesome Pro!</h2> + </div> + </div> - <div class="wa-stack wa-gap-l"> - <p>Celebrate our official launch with a 20% discount on a Web Awesome Pro plan…<span class="appearance-underlined variant-drawn" style="--underline-color: var(--wa-color-brand);">for life</span>! But hurry, this lifetime discount is only available for a limited time.</p> + <div class="wa-stack wa-gap-l"> + <p>Celebrate our official launch with a 20% discount on a Web Awesome Pro plan…<span class="appearance-underlined variant-drawn" style="--underline-color: var(--wa-color-brand);">for life</span>! But hurry, this lifetime discount is only available for a limited time.</p> - <div class="wa-split"> - <wa-button type="button" appearance="plain" data-dialog="close" class="plausible-event-name=launch_dialog:close_button_click">Maybe Later</wa-button> - <wa-button variant="neutral" appearance="accent" href="/purchase" class="brand-font plausible-event-name=launch_dialog:pro_purchase_button_click"> - <wa-icon slot="start" variant="regular" name="rocket-launch"></wa-icon> - Get Pro + Save 20% - </wa-button> - </div> - </div> -</wa-dialog> + <div class="wa-split"> + <wa-button type="button" appearance="plain" data-dialog="close" class="plausible-event-name=launch_dialog:close_button_click">Maybe Later</wa-button> + <wa-button variant="neutral" appearance="accent" href="/purchase" class="brand-font plausible-event-name=launch_dialog:pro_purchase_button_click"> + <wa-icon slot="start" variant="regular" name="rocket-launch"></wa-icon> + Get Pro + Save 20% + </wa-button> + </div> + </div> + </wa-dialog> -<script type="module"> - (function() { - const SITE_DIALOG_DISMISSED_KEY = 'dialog-wa-launch-sale-dismissed'; + <script type="module"> + (function() { + const SITE_DIALOG_DISMISSED_KEY = 'dialog-wa-launch-sale-dismissed'; - // Early exit if user has dismissed the dialog - try { - if (localStorage.getItem(SITE_DIALOG_DISMISSED_KEY) === 'true') { - return; - } - } catch (e) { - // localStorage may be disabled or unavailable - return; - } - - // Wait for DOM and dialog element - const dialog = document.getElementById('dialog-site'); - if (!dialog) { - return; - } - - // Helper function to safely track Plausible events - const trackEvent = (eventName) => { - if (typeof plausible !== 'undefined') { - plausible(eventName); - } - }; - - // Initialize dialog functionality - let initCalled = false; - const initDialog = () => { - // Prevent double initialization - if (initCalled) { - return; - } - initCalled = true; - - // Track when dialog is shown - dialog.addEventListener('wa-show', () => { - trackEvent('launch_dialog:view'); - }, { once: true }); - - // Track when dialog is dismissed - dialog.addEventListener('wa-hide', (event) => { - // Track overlay click or Escape key dismissal - // Button clicks are tracked via CSS classes, so we only track non-button dismissals - if (event.detail?.source === dialog) { - trackEvent('launch_dialog:overlay_click'); - } - - // Save dismissal state to localStorage + // Early exit if user has dismissed the dialog try { - localStorage.setItem(SITE_DIALOG_DISMISSED_KEY, 'true'); + if (localStorage.getItem(SITE_DIALOG_DISMISSED_KEY) === 'true') { + return; + } } catch (e) { // localStorage may be disabled or unavailable + return; } - }, { once: true }); - // Show dialog after a short delay to ensure page is loaded - setTimeout(() => { - dialog.open = true; - }, 500); - }; + // Wait for DOM and dialog element + const dialog = document.getElementById('dialog-site'); + if (!dialog) { + return; + } - // Initialize when ready - if (customElements.get('wa-dialog')) { - initDialog(); - } else { - document.addEventListener('wa-discovery-complete', initDialog, { once: true }); - // Fallback timeout in case wa-discovery-complete doesn't fire - setTimeout(initDialog, 100); - } - })(); -</script> + // Helper function to safely track Plausible events + const trackEvent = (eventName) => { + if (typeof plausible !== 'undefined') { + plausible(eventName); + } + }; + // Initialize dialog functionality + let initCalled = false; + const initDialog = () => { + // Prevent double initialization + if (initCalled) { + return; + } + initCalled = true; + + // Track when dialog is shown + dialog.addEventListener('wa-show', () => { + trackEvent('launch_dialog:view'); + }, { once: true }); + + // Track when dialog is dismissed + dialog.addEventListener('wa-hide', (event) => { + // Track overlay click or Escape key dismissal + // Button clicks are tracked via CSS classes, so we only track non-button dismissals + if (event.detail?.source === dialog) { + trackEvent('launch_dialog:overlay_click'); + } + + // Save dismissal state to localStorage + try { + localStorage.setItem(SITE_DIALOG_DISMISSED_KEY, 'true'); + } catch (e) { + // localStorage may be disabled or unavailable + } + }, { once: true }); + + // Show dialog after a short delay to ensure page is loaded + setTimeout(() => { + dialog.open = true; + }, 500); + }; + + customElements.whenDefined("wa-dialog").then(() => { + initDialog() + }) + })(); + </script> + {%- endif -%} +{% endraw %} diff --git a/packages/webawesome/docs/_includes/base.njk b/packages/webawesome/docs/_includes/base.njk index 96b6f1094..26b50c2b1 100644 --- a/packages/webawesome/docs/_includes/base.njk +++ b/packages/webawesome/docs/_includes/base.njk @@ -139,11 +139,7 @@ {#- Site-Wide Dialog -#} {% if hasSiteDialog %} - {% raw %} - {%- if not currentUser.hasPro -%} - {% include "_dialog-wa-launch.njk" ignore missing %} - {%- endif -%} - {% endraw %} + {% include "_dialog-wa-launch.njk" ignore missing %} {% endif %} {# Footer #} From 8c51c5b9336725a9c92e801ce3dc43f7ee73b3f7 Mon Sep 17 00:00:00 2001 From: Owarie <82327125+0warie@users.noreply.github.com> Date: Mon, 17 Nov 2025 22:47:14 +0800 Subject: [PATCH 18/59] fix: re-add page metadata (#1776) --- packages/webawesome/docs/.eleventy.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/webawesome/docs/.eleventy.js b/packages/webawesome/docs/.eleventy.js index 8420ec379..8446b2111 100644 --- a/packages/webawesome/docs/.eleventy.js +++ b/packages/webawesome/docs/.eleventy.js @@ -124,6 +124,22 @@ export default async function (eleventyConfig) { eleventyConfig.addFilter('stripExtension', string => path.parse(string + '').name); eleventyConfig.addFilter('stripPrefix', content => content.replace(/^wa-/, '')); eleventyConfig.addFilter('uniqueId', (_value, length = 8) => nanoid(length)); + + eleventyConfig.addGlobalData('eleventyComputed', { + // Page title with smart + default site name formatting + pageTitle: data => composePageTitle(data.title), + // Open Graph title with smart + default site name formatting + ogTitle: data => composePageTitle(data.ogTitle || data.title), + ogDescription: data => data.ogDescription || data.description, + ogImage: data => data.ogImage || siteMetadata.image, + ogUrl: data => { + if (data.ogUrl) return data.ogUrl; + const url = data.page?.url || ''; + return url ? `${siteMetadata.url}${url}` : siteMetadata.url; + }, + ogType: data => data.ogType || 'website', + }); + // Trims whitespace and pipes from the start and end of a string. Useful for CEM types, which can be pipe-delimited. // With Prettier 3, this means a leading pipe will exist be present when the line wraps. eleventyConfig.addFilter('trimPipes', content => { From 4069360242773b57c04116fa4d35af38d604abfb Mon Sep 17 00:00:00 2001 From: Cory LaViska <cory@abeautifulsite.net> Date: Mon, 17 Nov 2025 09:51:11 -0500 Subject: [PATCH 19/59] fix card slot detection (#1779) --- packages/webawesome/docs/docs/resources/changelog.md | 1 + packages/webawesome/src/components/card/card.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index 8e31fd659..b86419fc9 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -14,6 +14,7 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh ## Next - Fixed a bug in `<wa-slider>` that caused some touch devices to end up with the incorrect value [issue:1703] +- Fixed a bug in `<wa-card>` that prevented some slots from being detected correctly [discuss:1450] ## 3.0.0 diff --git a/packages/webawesome/src/components/card/card.ts b/packages/webawesome/src/components/card/card.ts index 86062d673..94a4212e7 100644 --- a/packages/webawesome/src/components/card/card.ts +++ b/packages/webawesome/src/components/card/card.ts @@ -30,7 +30,15 @@ import styles from './card.css'; export default class WaCard extends WebAwesomeElement { static css = [sizeStyles, styles]; - private readonly hasSlotController = new HasSlotController(this, 'footer', 'header', 'media'); + private readonly hasSlotController = new HasSlotController( + this, + 'footer', + 'header', + 'media', + 'header-actions', + 'footer-actions', + 'actions', + ); /** The card's visual appearance. */ @property({ reflect: true }) From ab0f2a141163e066f86e87199ba9329496eff908 Mon Sep 17 00:00:00 2001 From: Konnor Rogers <konnor5456@gmail.com> Date: Mon, 17 Nov 2025 11:31:26 -0500 Subject: [PATCH 20/59] add manifest json file copying (#1780) * add manifest json file copying * update comment * prettier --- packages/webawesome/docs/.eleventy.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/webawesome/docs/.eleventy.js b/packages/webawesome/docs/.eleventy.js index 8446b2111..dfdff37a0 100644 --- a/packages/webawesome/docs/.eleventy.js +++ b/packages/webawesome/docs/.eleventy.js @@ -313,18 +313,19 @@ export default async function (eleventyConfig) { // This needs to happen in "eleventy.after" otherwise incremental builds never update. eleventyConfig.on('eleventy.after', function () { - let assetsDir = path.join(process.env.BASE_DIR || 'docs', 'assets'); + const baseDir = process.env.BASE_DIR || 'docs'; + let assetsDir = path.join(baseDir, 'assets'); const siteAssetsDir = path.join(eleventyConfig.directories.output, 'assets'); fs.cpSync(assetsDir, siteAssetsDir, { recursive: true }); + + // Passthrough copy for manifest.json (PWA manifest file) + fs.cpSync(path.join(baseDir, 'manifest.json'), path.join(eleventyConfig.directories.output, 'manifest.json')); }); for (let glob of passThrough) { eleventyConfig.addPassthroughCopy(glob); } - // Passthrough copy for manifest.json (PWA manifest file) - eleventyConfig.addPassthroughCopy('manifest.json'); - // // SSR plugin // if (!isDev) { // // From 17fd3d238c71250cbc998694cd0050fdbce831a6 Mon Sep 17 00:00:00 2001 From: Cory LaViska <cory@abeautifulsite.net> Date: Mon, 17 Nov 2025 14:38:40 -0500 Subject: [PATCH 21/59] fix typo; closes #1713 --- packages/webawesome/docs/docs/utilities/fouce.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webawesome/docs/docs/utilities/fouce.md b/packages/webawesome/docs/docs/utilities/fouce.md index cfe22e8dc..8bb731bba 100644 --- a/packages/webawesome/docs/docs/utilities/fouce.md +++ b/packages/webawesome/docs/docs/utilities/fouce.md @@ -22,7 +22,7 @@ As soon as all elements are registered _or_ after two seconds have elapsed, the :::details Are you using Turbo in your app? -If you're using [Turbo](https://turbo.hotwired.dev/) to serve a multi-page application (MPA) as a single page application (SPA), you might notice FOUCE when navigating from page to page. This is because Turbo renders the new page's content before the autoloader has a change to register new components. +If you're using [Turbo](https://turbo.hotwired.dev/) to serve a multi-page application (MPA) as a single page application (SPA), you might notice FOUCE when navigating from page to page. This is because Turbo renders the new page's content before the autoloader has a chance to register new components. The following function acts as a middleware to ensure components are registered _before_ the page shows, eliminating FOUCE for page-to-page navigation with Turbo. From 634828d89a45c5a1641204304efaeb4e18ddac19 Mon Sep 17 00:00:00 2001 From: Dimitri <dima@panchenko.net> Date: Mon, 17 Nov 2025 20:49:32 +0100 Subject: [PATCH 22/59] fix(wa-icon): add waitUntilFirstUpdate: true option to the setIcon() watch decorator (#1738) --- packages/webawesome/src/components/icon/icon.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webawesome/src/components/icon/icon.ts b/packages/webawesome/src/components/icon/icon.ts index 785289805..d9e8d9760 100644 --- a/packages/webawesome/src/components/icon/icon.ts +++ b/packages/webawesome/src/components/icon/icon.ts @@ -187,7 +187,7 @@ export default class WaIcon extends WebAwesomeElement { } } - @watch(['family', 'name', 'library', 'variant', 'src', 'autoWidth', 'swapOpacity']) + @watch(['family', 'name', 'library', 'variant', 'src', 'autoWidth', 'swapOpacity'], { waitUntilFirstUpdate: true }) async setIcon() { const { url, fromLibrary } = this.getIconSource(); const library = fromLibrary ? getIconLibrary(this.library) : undefined; From 411d385d654e641598f490e36372fe75ae247616 Mon Sep 17 00:00:00 2001 From: Cory LaViska <cory@abeautifulsite.net> Date: Mon, 17 Nov 2025 14:49:45 -0500 Subject: [PATCH 23/59] improve icon perf; closes #1729 (#1783) --- packages/webawesome/docs/docs/resources/changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index b86419fc9..fddd7d024 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -15,6 +15,7 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh - Fixed a bug in `<wa-slider>` that caused some touch devices to end up with the incorrect value [issue:1703] - Fixed a bug in `<wa-card>` that prevented some slots from being detected correctly [discuss:1450] +- Improved performance of `<wa-icon>` so initial rendering occurs faster, especially with multiple icons on the page [issue:1729] ## 3.0.0 From 2491ca45ac0c3ff26de55a10ab80952110212670 Mon Sep 17 00:00:00 2001 From: Kelsey Jackson <kelseythej@gmail.com> Date: Mon, 17 Nov 2025 14:12:23 -0600 Subject: [PATCH 24/59] Fix `appearance="filled-outlined"` in `<wa-card>` (#1694) * upated filled-outlined attribute for cards * updated selector * updated changelog * updated heading * updated css * remove final `~=` selector (thanks @Copilot) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Lindsay M <126139086+lindsaym-fa@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../webawesome/docs/docs/resources/changelog.md | 2 ++ packages/webawesome/src/components/card/card.css | 13 +++++++------ packages/webawesome/src/components/card/card.ts | 2 +- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index fddd7d024..3526e0628 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -13,6 +13,7 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh ## Next +- 🚨 BREAKING: Changed `appearance="filled outlined"` to `appearance="filled-outlined"` in `<wa-card>` [issue:1671] - Fixed a bug in `<wa-slider>` that caused some touch devices to end up with the incorrect value [issue:1703] - Fixed a bug in `<wa-card>` that prevented some slots from being detected correctly [discuss:1450] - Improved performance of `<wa-icon>` so initial rendering occurs faster, especially with multiple icons on the page [issue:1729] @@ -23,6 +24,7 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh - `<wa-badge>` - `<wa-button>` - `<wa-callout>` + - `<wa-card>` - `<wa-details>` - `<wa-input>` - `<wa-select>` diff --git a/packages/webawesome/src/components/card/card.css b/packages/webawesome/src/components/card/card.css index 5156511df..cb06f19f6 100644 --- a/packages/webawesome/src/components/card/card.css +++ b/packages/webawesome/src/components/card/card.css @@ -16,27 +16,28 @@ } /* Appearance modifiers */ -:host([appearance~='plain']) { +:host([appearance='plain']) { background-color: transparent; border-color: transparent; box-shadow: none; } -:host([appearance~='outlined']) { +:host([appearance='outlined']) { background-color: var(--wa-color-surface-default); border-color: var(--wa-color-surface-border); } -:host([appearance~='filled']) { +:host([appearance='filled']) { background-color: var(--wa-color-neutral-fill-quiet); border-color: transparent; } -:host([appearance~='filled'][appearance~='outlined']) { - border-color: var(--wa-color-neutral-border-quiet); +:host([appearance='filled-outlined']) { + background-color: var(--wa-color-neutral-fill-quiet); + border-color: var(--wa-color-surface-border); } -:host([appearance~='accent']) { +:host([appearance='accent']) { color: var(--wa-color-neutral-on-loud); background-color: var(--wa-color-neutral-fill-loud); border-color: transparent; diff --git a/packages/webawesome/src/components/card/card.ts b/packages/webawesome/src/components/card/card.ts index 94a4212e7..58c072852 100644 --- a/packages/webawesome/src/components/card/card.ts +++ b/packages/webawesome/src/components/card/card.ts @@ -42,7 +42,7 @@ export default class WaCard extends WebAwesomeElement { /** The card's visual appearance. */ @property({ reflect: true }) - appearance: 'accent' | 'filled' | 'outlined' | 'plain' = 'outlined'; + appearance: 'accent' | 'filled' | 'outlined' | 'filled-outlined' | 'plain' = 'outlined'; /** Renders the card with a header. Only needed for SSR, otherwise is automatically added. */ @property({ attribute: 'with-header', type: Boolean, reflect: true }) withHeader = false; From 8fb521d9ef8bcc21ce2452426d455bce99c8ee7f Mon Sep 17 00:00:00 2001 From: Fred <37804453+fmercille@users.noreply.github.com> Date: Mon, 17 Nov 2025 17:15:18 -0500 Subject: [PATCH 25/59] Adding a `reflect` property option to the `name` property (#1716) --- packages/webawesome/src/components/select/select.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webawesome/src/components/select/select.ts b/packages/webawesome/src/components/select/select.ts index cfedcdd6d..2e1459e1c 100644 --- a/packages/webawesome/src/components/select/select.ts +++ b/packages/webawesome/src/components/select/select.ts @@ -119,7 +119,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement { @state() optionValues: Set<string | null> | undefined; /** The name of the select, submitted as a name/value pair with form data. */ - @property() name = ''; + @property({ reflect: true }) name = ''; private _defaultValue: null | string | string[] = null; From 86293cc3e14531ae7ed5c4c89decbd36c6ea8d50 Mon Sep 17 00:00:00 2001 From: Christian Oliff <christian_oliff@trimble.com> Date: Wed, 19 Nov 2025 00:03:29 +0900 Subject: [PATCH 26/59] Remove width property from select component (#1736) Deleted the duplicate 'width: 100%' style from the select component (already defined on line 56). Also fixed a typo in a comment in the docs CSS. You should setup Stylelint to prevent these issues :-) --- packages/webawesome/docs/assets/styles/docs.css | 2 +- packages/webawesome/src/components/select/select.css | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/webawesome/docs/assets/styles/docs.css b/packages/webawesome/docs/assets/styles/docs.css index 914a77783..7ceba5c9a 100644 --- a/packages/webawesome/docs/assets/styles/docs.css +++ b/packages/webawesome/docs/assets/styles/docs.css @@ -38,7 +38,7 @@ wa-page > [slot='banner'] { } &.banner-wa-launch { - /* custom brand colors carrried over from theme-site for the banner */ + /* custom brand colors carried over from theme-site for the banner */ --wa-color-brand-95: #fef0ec; --wa-color-brand-90: #fce0d8; --wa-color-brand-80: #f8bcac; diff --git a/packages/webawesome/src/components/select/select.css b/packages/webawesome/src/components/select/select.css index a01654461..6318fb590 100644 --- a/packages/webawesome/src/components/select/select.css +++ b/packages/webawesome/src/components/select/select.css @@ -74,7 +74,6 @@ padding: 0 var(--wa-form-control-padding-inline); position: relative; vertical-align: middle; - width: 100%; transition: background-color var(--wa-transition-normal), border var(--wa-transition-normal), From e2f2a8a0d30f1e743855235f0b2da67727a182fa Mon Sep 17 00:00:00 2001 From: Jesse Jurman <j.r.jurman@gmail.com> Date: Tue, 18 Nov 2025 14:07:53 -0500 Subject: [PATCH 27/59] Fix: Allow wa-hide to be cancelled in WaDropdown (#1774) * Fix: Allow wa-hide to be cancelled in WaDropdown * add tests around open attribute / property * update changelog --- .../docs/docs/resources/changelog.md | 1 + .../src/components/dropdown/dropdown.test.ts | 112 +++++++++++++++++- .../src/components/dropdown/dropdown.ts | 18 +++ 3 files changed, 130 insertions(+), 1 deletion(-) diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index 3526e0628..f67dea039 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -16,6 +16,7 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh - 🚨 BREAKING: Changed `appearance="filled outlined"` to `appearance="filled-outlined"` in `<wa-card>` [issue:1671] - Fixed a bug in `<wa-slider>` that caused some touch devices to end up with the incorrect value [issue:1703] - Fixed a bug in `<wa-card>` that prevented some slots from being detected correctly [discuss:1450] +- Fixed a bug in `<wa-dropdown>` that caused the browser to hang when cancelling the `wa-hide` event [issue:1483] - Improved performance of `<wa-icon>` so initial rendering occurs faster, especially with multiple icons on the page [issue:1729] ## 3.0.0 diff --git a/packages/webawesome/src/components/dropdown/dropdown.test.ts b/packages/webawesome/src/components/dropdown/dropdown.test.ts index 0ec43f66a..f557189d8 100644 --- a/packages/webawesome/src/components/dropdown/dropdown.test.ts +++ b/packages/webawesome/src/components/dropdown/dropdown.test.ts @@ -1,4 +1,6 @@ -import { expect, fixture, html } from '@open-wc/testing'; +import { aTimeout, expect, fixture, html, waitUntil } from '@open-wc/testing'; +import sinon from 'sinon'; +import type WaDropdown from './dropdown.js'; describe('<wa-dropdown>', () => { it('should render a component', async () => { @@ -6,4 +8,112 @@ describe('<wa-dropdown>', () => { expect(el).to.exist; }); + + it('should respect the open attribute when included', async () => { + const el = await fixture<WaDropdown>(html` + <wa-dropdown open> + <wa-button slot="trigger">Dropdown</wa-button> + <wa-dropdown-item>One</wa-dropdown-item> + </wa-dropdown> + `); + + await el.updateComplete; + await aTimeout(200); + + expect(el.open).to.be.true; + }); + + it('should fire a single show/after-show and hide/after-hide in normal open/close flow', async () => { + const el = await fixture<WaDropdown>(html` + <wa-dropdown> + <wa-button slot="trigger">Dropdown</wa-button> + <wa-dropdown-item>One</wa-dropdown-item> + <wa-dropdown-item>Two</wa-dropdown-item> + </wa-dropdown> + `); + + // setup spies to track how often we see different show/hide events + const showSpy = sinon.spy(); + const afterShowSpy = sinon.spy(); + const hideSpy = sinon.spy(); + const afterHideSpy = sinon.spy(); + + el.addEventListener('wa-show', showSpy); + el.addEventListener('wa-after-show', afterShowSpy); + el.addEventListener('wa-hide', hideSpy); + el.addEventListener('wa-after-hide', afterHideSpy); + + // open the dropdown by triggering a click on the trigger + const trigger = el.querySelector<HTMLElement>('[slot="trigger"]')!; + trigger.click(); + + await waitUntil(() => showSpy.calledOnce); + await waitUntil(() => afterShowSpy.calledOnce); + + expect(showSpy.callCount).to.equal(1); + expect(afterShowSpy.callCount).to.equal(1); + + expect(el.open).to.be.true; + + // close the dropdown by clicking the trigger again + trigger.click(); + + await waitUntil(() => hideSpy.calledOnce); + await waitUntil(() => afterHideSpy.calledOnce); + + expect(hideSpy.callCount).to.equal(1); + expect(afterHideSpy.callCount).to.equal(1); + + expect(el.open).to.be.false; + }); + + it('should fire a single show/after-show and hide/after-hide when wa-hide event is cancelled', async () => { + const el = await fixture<WaDropdown>(html` + <wa-dropdown> + <wa-button slot="trigger">Dropdown</wa-button> + <wa-dropdown-item>One</wa-dropdown-item> + <wa-dropdown-item>Two</wa-dropdown-item> + </wa-dropdown> + `); + + // setup spies to track how often we see different show/hide events + const showSpy = sinon.spy(); + const afterShowSpy = sinon.spy(); + const hideSpy = sinon.spy(); + const afterHideSpy = sinon.spy(); + + el.addEventListener('wa-show', showSpy); + el.addEventListener('wa-after-show', afterShowSpy); + + // Intercept wa-hide and prevent it + el.addEventListener('wa-hide', event => { + event.preventDefault(); + hideSpy(event); + }); + + el.addEventListener('wa-after-hide', afterHideSpy); + + // open the dropdown by triggering a click on the trigger + const trigger = el.querySelector<HTMLElement>('[slot="trigger"]')!; + trigger.click(); + + await waitUntil(() => showSpy.calledOnce); + await waitUntil(() => afterShowSpy.calledOnce); + + expect(showSpy.callCount).to.equal(1); + expect(afterShowSpy.callCount).to.equal(1); + + expect(el.open).to.be.true; + + // click on the trigger (which should do nothing to the open state) + trigger.click(); + + await waitUntil(() => hideSpy.calledOnce); + + expect(hideSpy.callCount).to.equal(1); + // after-hide should not have been called if hide is cancelled + expect(afterHideSpy.callCount).to.equal(0); + + expect(el.open).to.be.true; + }); }); diff --git a/packages/webawesome/src/components/dropdown/dropdown.ts b/packages/webawesome/src/components/dropdown/dropdown.ts index 2b21643d0..a8d03f4fc 100644 --- a/packages/webawesome/src/components/dropdown/dropdown.ts +++ b/packages/webawesome/src/components/dropdown/dropdown.ts @@ -109,6 +109,18 @@ export default class WaDropdown extends WebAwesomeElement { async updated(changedProperties: PropertyValues) { if (changedProperties.has('open')) { + const previousOpen = changedProperties.get('open'); + // check if the previous value is the same + // (if they are, do not trigger menu showing / hiding) + if (previousOpen === this.open) { + return; + } + // check if we are changing from undefined to false + // (if we are, we can skip menu hiding) + if (previousOpen === undefined && this.open === false) { + return; + } + this.customStates.set('open', this.open); if (this.open) { @@ -227,6 +239,12 @@ export default class WaDropdown extends WebAwesomeElement { return; } + // if this dropdown is already open, do nothing + // (this can happen when wa-hide was cancelled) + if (this.popup.active) { + return; + } + openDropdowns.forEach(dropdown => (dropdown.open = false)); this.popup.active = true; // Use wa-popup's active property instead of showPopover From bb0a9617844902718a75e13d65c2207a5e74d0ed Mon Sep 17 00:00:00 2001 From: Cory LaViska <cory@abeautifulsite.net> Date: Tue, 18 Nov 2025 15:29:57 -0500 Subject: [PATCH 28/59] remove offending property; fixes #1614 (#1792) --- packages/webawesome/docs/assets/styles/search.css | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/webawesome/docs/assets/styles/search.css b/packages/webawesome/docs/assets/styles/search.css index 8e0f37771..a40834b07 100644 --- a/packages/webawesome/docs/assets/styles/search.css +++ b/packages/webawesome/docs/assets/styles/search.css @@ -5,7 +5,6 @@ border-radius: var(--wa-border-radius-l); padding: 0; margin: 0 auto; - overflow: hidden; &::part(dialog) { margin-block-start: 10vh; From 9706964511e91b42e759044dda87988f0c854ec2 Mon Sep 17 00:00:00 2001 From: Cory LaViska <cory@abeautifulsite.net> Date: Tue, 18 Nov 2025 15:31:51 -0500 Subject: [PATCH 29/59] Fix spinner when `<wa-tree-item>` is lazy loaded (#1786) * fix spinner when lazy; closes #1678 * remove redundant entry --- packages/webawesome/docs/docs/resources/changelog.md | 3 +-- .../webawesome/src/components/tree-item/tree-item.css | 8 ++++++-- packages/webawesome/src/components/tree-item/tree-item.ts | 5 ++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index f67dea039..c9a2bf7ad 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -13,16 +13,15 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh ## Next -- 🚨 BREAKING: Changed `appearance="filled outlined"` to `appearance="filled-outlined"` in `<wa-card>` [issue:1671] - Fixed a bug in `<wa-slider>` that caused some touch devices to end up with the incorrect value [issue:1703] - Fixed a bug in `<wa-card>` that prevented some slots from being detected correctly [discuss:1450] +- Fixed a bug in `<wa-tree-item>` that caused the spinner to not show when lazy loading [issue:1678] - Fixed a bug in `<wa-dropdown>` that caused the browser to hang when cancelling the `wa-hide` event [issue:1483] - Improved performance of `<wa-icon>` so initial rendering occurs faster, especially with multiple icons on the page [issue:1729] ## 3.0.0 - 🚨 BREAKING: Changed `appearance="filled outlined"` to `appearance="filled-outlined"` in the following elements [issue:1127] - - `<wa-badge>` - `<wa-button>` - `<wa-callout>` - `<wa-card>` diff --git a/packages/webawesome/src/components/tree-item/tree-item.css b/packages/webawesome/src/components/tree-item/tree-item.css index 11b3c9bb8..a34ed4d3d 100644 --- a/packages/webawesome/src/components/tree-item/tree-item.css +++ b/packages/webawesome/src/components/tree-item/tree-item.css @@ -73,12 +73,16 @@ slot:not([name])::slotted(wa-icon) { rotate: -90deg; } -.tree-item-expanded slot[name='expand-icon'], +.tree-item-expanded:not(.tree-item-loading) slot[name='expand-icon'], .tree-item:not(.tree-item-expanded) slot[name='collapse-icon'] { display: none; } -.tree-item:not(.tree-item-has-expand-button) .expand-icon-slot { +.tree-item:not(.tree-item-has-expand-button):not(.tree-item-loading) .expand-icon-slot { + display: none; +} + +.tree-item-loading .expand-icon-slot wa-icon { display: none; } diff --git a/packages/webawesome/src/components/tree-item/tree-item.ts b/packages/webawesome/src/components/tree-item/tree-item.ts index 5c96b5629..dc15888d6 100644 --- a/packages/webawesome/src/components/tree-item/tree-item.ts +++ b/packages/webawesome/src/components/tree-item/tree-item.ts @@ -254,6 +254,7 @@ export default class WaTreeItem extends WebAwesomeElement { 'tree-item-expanded': this.expanded, 'tree-item-selected': this.selected, 'tree-item-leaf': this.isLeaf, + 'tree-item-loading': this.loading, 'tree-item-has-expand-button': showExpandButton, })}" > @@ -272,8 +273,10 @@ export default class WaTreeItem extends WebAwesomeElement { ${when( this.loading, () => html` <wa-spinner part="spinner" exportparts="base:spinner__base"></wa-spinner> `, + () => html` + <wa-icon name=${isRtl ? 'chevron-left' : 'chevron-right'} library="system" variant="solid"></wa-icon> + `, )} - <wa-icon name=${isRtl ? 'chevron-left' : 'chevron-right'} library="system" variant="solid"></wa-icon> </slot> <slot class="expand-icon-slot" name="collapse-icon"> <wa-icon name=${isRtl ? 'chevron-left' : 'chevron-right'} library="system" variant="solid"></wa-icon> From 1429095eed39be077453e90af126889720e98a3d Mon Sep 17 00:00:00 2001 From: Cory LaViska <cory@abeautifulsite.net> Date: Wed, 19 Nov 2025 07:33:56 -0500 Subject: [PATCH 30/59] Fix SVG fill in `<wa-icon>` (#1784) * fix svg fill; closes #1733 * remove fill --- packages/webawesome/docs/docs/resources/changelog.md | 1 + packages/webawesome/src/components/icon/icon.css | 1 - packages/webawesome/src/components/icon/icon.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index c9a2bf7ad..61085fd7c 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -15,6 +15,7 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh - Fixed a bug in `<wa-slider>` that caused some touch devices to end up with the incorrect value [issue:1703] - Fixed a bug in `<wa-card>` that prevented some slots from being detected correctly [discuss:1450] +- Fixed a bug in `<wa-icon>` that caused some icon libraries to render with the incorrect SVG fill [issue:1733] - Fixed a bug in `<wa-tree-item>` that caused the spinner to not show when lazy loading [issue:1678] - Fixed a bug in `<wa-dropdown>` that caused the browser to hang when cancelling the `wa-hide` event [issue:1483] - Improved performance of `<wa-icon>` so initial rendering occurs faster, especially with multiple icons on the page [issue:1729] diff --git a/packages/webawesome/src/components/icon/icon.css b/packages/webawesome/src/components/icon/icon.css index bac395ab3..9a90c004f 100644 --- a/packages/webawesome/src/components/icon/icon.css +++ b/packages/webawesome/src/components/icon/icon.css @@ -25,7 +25,6 @@ svg { height: 1em; - fill: currentColor; overflow: visible; /* Duotone colors with path-specific opacity fallback */ diff --git a/packages/webawesome/src/components/icon/icon.ts b/packages/webawesome/src/components/icon/icon.ts index d9e8d9760..b30ac07f6 100644 --- a/packages/webawesome/src/components/icon/icon.ts +++ b/packages/webawesome/src/components/icon/icon.ts @@ -249,7 +249,7 @@ export default class WaIcon extends WebAwesomeElement { return this.svg; } - return html`<svg part="svg" fill="currentColor" width="16" height="16"></svg>`; + return html`<svg part="svg" width="16" height="16"></svg>`; } } From 200b02f023c9f3e5d2ffd09bb1550b22eff16428 Mon Sep 17 00:00:00 2001 From: Cory LaViska <cory@abeautifulsite.net> Date: Wed, 19 Nov 2025 07:34:22 -0500 Subject: [PATCH 31/59] fix scroller styles; closes #1724 (#1785) --- .../docs/docs/resources/changelog.md | 1 + .../src/components/scroller/scroller.css | 30 ++++++++----------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index 61085fd7c..513432106 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -15,6 +15,7 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh - Fixed a bug in `<wa-slider>` that caused some touch devices to end up with the incorrect value [issue:1703] - Fixed a bug in `<wa-card>` that prevented some slots from being detected correctly [discuss:1450] +- Fixed a z-index bug in `<wa-scroller>` styles [issue:1724] - Fixed a bug in `<wa-icon>` that caused some icon libraries to render with the incorrect SVG fill [issue:1733] - Fixed a bug in `<wa-tree-item>` that caused the spinner to not show when lazy loading [issue:1678] - Fixed a bug in `<wa-dropdown>` that caused the browser to hang when cancelling the `wa-hide` event [issue:1483] diff --git a/packages/webawesome/src/components/scroller/scroller.css b/packages/webawesome/src/components/scroller/scroller.css index 2dac32094..3b4080c9e 100644 --- a/packages/webawesome/src/components/scroller/scroller.css +++ b/packages/webawesome/src/components/scroller/scroller.css @@ -53,6 +53,19 @@ overflow-y: auto; } +#start-shadow, +#end-shadow { + z-index: 2; +} + +#start-shadow { + opacity: var(--start-shadow-opacity); +} + +#end-shadow { + opacity: var(--end-shadow-opacity); +} + /* Horizontal shadows */ :host([orientation='horizontal']) { #start-shadow, @@ -64,14 +77,6 @@ pointer-events: none; } - #start-shadow { - opacity: var(--start-shadow-opacity); - } - - #end-shadow { - opacity: var(--end-shadow-opacity); - } - #start-shadow { &:dir(ltr) { left: 0; @@ -102,21 +107,12 @@ #start-shadow, #end-shadow { position: absolute; - z-index: 2; right: 0; left: 0; height: var(--shadow-size); pointer-events: none; } - #start-shadow { - opacity: var(--start-shadow-opacity); - } - - #end-shadow { - opacity: var(--end-shadow-opacity); - } - #start-shadow { top: 0; background: linear-gradient(to bottom, var(--shadow-color), transparent 100%); From d6fe9f78190cc3b48c26b8e76e25674a03c70bce Mon Sep 17 00:00:00 2001 From: Lindsay M <126139086+lindsaym-fa@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:43:55 -0500 Subject: [PATCH 32/59] Define and import cascade layers (#1793) * define cascade layers * invoke cascade layers in kitchen sink styles * invoke cascade layers in utilities and native styles * invoke cascade layers in palettes + consolidate shared palette rules * invoke cascade layers in themes * add changelog * prettier demands whitespace --- packages/webawesome/docs/docs/resources/changelog.md | 1 + packages/webawesome/src/styles/color/palettes/base.css | 4 ++++ packages/webawesome/src/styles/color/palettes/bright.css | 1 - packages/webawesome/src/styles/color/palettes/default.css | 1 - packages/webawesome/src/styles/color/palettes/shoelace.css | 1 - packages/webawesome/src/styles/layers.css | 2 ++ packages/webawesome/src/styles/native.css | 3 +++ packages/webawesome/src/styles/themes/awesome.css | 1 + packages/webawesome/src/styles/themes/default.css | 1 + packages/webawesome/src/styles/themes/shoelace.css | 1 + packages/webawesome/src/styles/utilities.css | 2 ++ packages/webawesome/src/styles/webawesome.css | 7 ++++--- 12 files changed, 19 insertions(+), 6 deletions(-) create mode 100644 packages/webawesome/src/styles/layers.css diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index 513432106..8bd78fecd 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -13,6 +13,7 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh ## Next +- Added `layers.css` to define cascade layer order and updated palettes, themes, native styles, and utilities to import the new rule for more fail-safe modularity [pr:1793] - Fixed a bug in `<wa-slider>` that caused some touch devices to end up with the incorrect value [issue:1703] - Fixed a bug in `<wa-card>` that prevented some slots from being detected correctly [discuss:1450] - Fixed a z-index bug in `<wa-scroller>` styles [issue:1724] diff --git a/packages/webawesome/src/styles/color/palettes/base.css b/packages/webawesome/src/styles/color/palettes/base.css index 88cc20294..7e8151185 100644 --- a/packages/webawesome/src/styles/color/palettes/base.css +++ b/packages/webawesome/src/styles/color/palettes/base.css @@ -1,3 +1,7 @@ +/* Rules shared by all palettes */ +@import url('../../layers.css'); +@import url('../variants.css'); + /* Generates --wa-color-{hue}-on tokens for pairing with any palette's key colors */ :where(:root), :host { diff --git a/packages/webawesome/src/styles/color/palettes/bright.css b/packages/webawesome/src/styles/color/palettes/bright.css index 120196805..a23c70560 100644 --- a/packages/webawesome/src/styles/color/palettes/bright.css +++ b/packages/webawesome/src/styles/color/palettes/bright.css @@ -1,5 +1,4 @@ @import url('base.css'); -@import url('../variants.css'); @layer wa-color-palette { .wa-palette-bright { diff --git a/packages/webawesome/src/styles/color/palettes/default.css b/packages/webawesome/src/styles/color/palettes/default.css index af9d6ffc3..87c5b9dc2 100644 --- a/packages/webawesome/src/styles/color/palettes/default.css +++ b/packages/webawesome/src/styles/color/palettes/default.css @@ -1,5 +1,4 @@ @import url('base.css'); -@import url('../variants.css'); @layer wa-color-palette { :where(:root), diff --git a/packages/webawesome/src/styles/color/palettes/shoelace.css b/packages/webawesome/src/styles/color/palettes/shoelace.css index c0ff6257f..c41cdc135 100644 --- a/packages/webawesome/src/styles/color/palettes/shoelace.css +++ b/packages/webawesome/src/styles/color/palettes/shoelace.css @@ -1,5 +1,4 @@ @import url('base.css'); -@import url('../variants.css'); @layer wa-color-palette { .wa-palette-shoelace, diff --git a/packages/webawesome/src/styles/layers.css b/packages/webawesome/src/styles/layers.css new file mode 100644 index 000000000..e6bcef7f0 --- /dev/null +++ b/packages/webawesome/src/styles/layers.css @@ -0,0 +1,2 @@ +/* Order of precedence for all cascade layers in Web Awesome */ +@layer wa-native, wa-utilities, wa-color-palette, wa-color-variant, wa-theme, wa-theme-dimension, wa-theme-overrides; diff --git a/packages/webawesome/src/styles/native.css b/packages/webawesome/src/styles/native.css index ae1d6d6d0..70d1ff509 100644 --- a/packages/webawesome/src/styles/native.css +++ b/packages/webawesome/src/styles/native.css @@ -1,3 +1,6 @@ +@import url('layers.css'); + +/* Required utilities */ @import url('utilities/size.css'); @import url('utilities/variants.css'); diff --git a/packages/webawesome/src/styles/themes/awesome.css b/packages/webawesome/src/styles/themes/awesome.css index b0bd11d83..47ce9a4c2 100644 --- a/packages/webawesome/src/styles/themes/awesome.css +++ b/packages/webawesome/src/styles/themes/awesome.css @@ -1,3 +1,4 @@ +@import url('../layers.css'); @import url('../color/palettes/bright.css'); /* To use this palette, add class="wa-palette-bright" to the <html> element */ @import url('https://fonts.bunny.net/css2?family=Crimson+Pro:ital,wght@0,200..900;1,200..900&family=Quicksand:wght@300..700&display=swap'); diff --git a/packages/webawesome/src/styles/themes/default.css b/packages/webawesome/src/styles/themes/default.css index 8cd6b15c9..0cdc87885 100644 --- a/packages/webawesome/src/styles/themes/default.css +++ b/packages/webawesome/src/styles/themes/default.css @@ -1,3 +1,4 @@ +@import url('../layers.css'); @import url('../color/palettes/default.css'); @layer wa-theme { diff --git a/packages/webawesome/src/styles/themes/shoelace.css b/packages/webawesome/src/styles/themes/shoelace.css index a9ef60a0b..50c72ab51 100644 --- a/packages/webawesome/src/styles/themes/shoelace.css +++ b/packages/webawesome/src/styles/themes/shoelace.css @@ -1,3 +1,4 @@ +@import url('../layers.css'); @import url('../color/palettes/shoelace.css'); /* To use this palette, add class="wa-palette-shoelace" to the <html> element */ @layer wa-theme { diff --git a/packages/webawesome/src/styles/utilities.css b/packages/webawesome/src/styles/utilities.css index 8fdea0f71..d80c3d18e 100644 --- a/packages/webawesome/src/styles/utilities.css +++ b/packages/webawesome/src/styles/utilities.css @@ -1,3 +1,5 @@ +@import url('layers.css'); + @import url('utilities/fouce.css'); @import url('utilities/visually-hidden.css'); @import url('utilities/scroll-lock.css'); diff --git a/packages/webawesome/src/styles/webawesome.css b/packages/webawesome/src/styles/webawesome.css index 0ff8dc0ac..8c1d3a189 100644 --- a/packages/webawesome/src/styles/webawesome.css +++ b/packages/webawesome/src/styles/webawesome.css @@ -1,9 +1,10 @@ -@layer wa-native, wa-utilities, wa-color-palette, wa-color-variant, wa-theme, wa-theme-dimension, wa-theme-overrides; +/* Cascade layers */ +@import url('layers.css'); -/* Native Styles */ +/* Native styles */ @import url('native.css'); -/* CSS Utilities */ +/* CSS utilities */ @import url('utilities.css'); /* Theme */ From 09c8f02f794c05852115b6c1c095011c971256ed Mon Sep 17 00:00:00 2001 From: Lindsay M <126139086+lindsaym-fa@users.noreply.github.com> Date: Wed, 19 Nov 2025 17:50:47 -0500 Subject: [PATCH 33/59] Use tokens for `<wa-dropdown-item>` transitions (#1693) * use tokens for `<wa-dropdown-item>` transitions * add changelog --- packages/webawesome/docs/docs/resources/changelog.md | 1 + .../webawesome/src/components/dropdown-item/dropdown-item.css | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index 8bd78fecd..a6f2c5e36 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -21,6 +21,7 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh - Fixed a bug in `<wa-tree-item>` that caused the spinner to not show when lazy loading [issue:1678] - Fixed a bug in `<wa-dropdown>` that caused the browser to hang when cancelling the `wa-hide` event [issue:1483] - Improved performance of `<wa-icon>` so initial rendering occurs faster, especially with multiple icons on the page [issue:1729] +- Modified the default `transition` styles of `<wa-dropdown-item>` to use design tokens [pr:1693] ## 3.0.0 diff --git a/packages/webawesome/src/components/dropdown-item/dropdown-item.css b/packages/webawesome/src/components/dropdown-item/dropdown-item.css index 300b658fb..f663f0191 100644 --- a/packages/webawesome/src/components/dropdown-item/dropdown-item.css +++ b/packages/webawesome/src/components/dropdown-item/dropdown-item.css @@ -9,8 +9,8 @@ line-height: var(--wa-line-height-condensed); cursor: pointer; transition: - 100ms background-color ease, - 100ms color ease; + var(--wa-transition-fast) background-color var(--wa-transition-easing), + var(--wa-transition-fast) color var(--wa-transition-easing); } @media (hover: hover) { From ed3cb6d78210d8146ae9615e9ee90049973f9922 Mon Sep 17 00:00:00 2001 From: Brian Talbot <hi.talbs@gmail.com> Date: Mon, 24 Nov 2025 12:31:15 -0500 Subject: [PATCH 34/59] marking planned pro components in sidebar (#1819) --- packages/webawesome/docs/_includes/sidebar.njk | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/webawesome/docs/_includes/sidebar.njk b/packages/webawesome/docs/_includes/sidebar.njk index ee902cd5d..0d629660c 100644 --- a/packages/webawesome/docs/_includes/sidebar.njk +++ b/packages/webawesome/docs/_includes/sidebar.njk @@ -78,10 +78,10 @@ </li> </ul> </li> - <li><span class="is-planned wa-split">Charts <a href="https://github.com/shoelace-style/webawesome/issues/1073" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a></span></li> + <li><span class="is-planned wa-split">Charts <span><a href="https://github.com/shoelace-style/webawesome/issues/1073" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge({ description: "This will require access to Web Awesome Pro" }) }}</span></span></li> <li><a href="/docs/components/checkbox/">Checkbox</a></li> <li><a href="/docs/components/color-picker/">Color Picker</a></li> - <li><span class="is-planned wa-split">Combobox <a href="https://github.com/shoelace-style/webawesome/issues/1074" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a></span></li> + <li><span class="is-planned wa-split">Combobox <span><a href="https://github.com/shoelace-style/webawesome/issues/1074" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge({ description: "This will require access to Web Awesome Pro" }) }}</span></span></li> <li><a href="/docs/components/comparison/">Comparison</a></li> <li> <a class="wa-cluster wa-gap-xs" href="/docs/components/copy-button/"> @@ -89,8 +89,8 @@ <wa-icon name="flask" aria-hidden="true" class="icon-shrink"></wa-icon> </a> </li> - <li><span class="is-planned wa-split">Data Grid <a href="https://github.com/shoelace-style/webawesome/issues/1072" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a></span></li> - <li><span class="is-planned wa-split">Datepicker <a href="https://github.com/shoelace-style/webawesome/issues/1075" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a></span></li> + <li><span class="is-planned wa-split">Data Grid <span><a href="https://github.com/shoelace-style/webawesome/issues/1072" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge({ description: "This will require access to Web Awesome Pro" }) }}</span></span></li> + <li><span class="is-planned wa-split">Datepicker <span><a href="https://github.com/shoelace-style/webawesome/issues/1075" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge({ description: "This will require access to Web Awesome Pro" }) }}</span></span></li> <li><a href="/docs/components/details/">Details</a></li> <li><a href="/docs/components/dialog/">Dialog</a></li> <li><a href="/docs/components/divider/">Divider</a></li> @@ -101,7 +101,7 @@ <li><a href="/docs/components/dropdown-item">Dropdown Item</a></li> </ul> </li> - <li><span class="is-planned wa-split">File Input <a href="https://github.com/shoelace-style/webawesome/issues/1240" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a></span></li> + <li><span class="is-planned wa-split">File Input <span><a href="https://github.com/shoelace-style/webawesome/issues/1240" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge({ description: "This will require access to Web Awesome Pro" }) }}</span></span></li> <li><a href="/docs/components/format-bytes/">Format Bytes</a></li> <li><a href="/docs/components/format-date/">Format Date</a></li> <li><a href="/docs/components/format-number/">Format Number</a></li> @@ -146,7 +146,7 @@ </li> <li><a href="/docs/components/tag/">Tag</a></li> <li><a href="/docs/components/textarea/">Textarea</a></li> - <li><span class="is-planned wa-split">Toast <a href="https://github.com/shoelace-style/webawesome/issues/105" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a></span></li> + <li><span class="is-planned wa-split">Toast <span><a href="https://github.com/shoelace-style/webawesome/issues/105" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge( { description: "This will require access to Web Awesome Pro" }) }}</span></span></li> <li><a href="/docs/components/tooltip/">Tooltip</a></li> <li> <a href="/docs/components/tree/">Tree</a> @@ -154,7 +154,7 @@ <li><a href="/docs/components/tree-item/">Tree Item</a></li> </ul> </li> - <li><span class="is-planned wa-split">Video <a href="https://github.com/shoelace-style/webawesome/issues/985" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a></span></li> + <li><span class="is-planned wa-split">Video <span><a href="https://github.com/shoelace-style/webawesome/issues/985" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge( { description: "This will require access to Web Awesome Pro" }) }}</span></span></li> <li><a href="/docs/components/zoomable-frame">Zoomable Frame</a></li> {# PLOP_NEW_COMPONENT_PLACEHOLDER #} </ul> From fa558eeda054f11087063e22e88447aed29c852f Mon Sep 17 00:00:00 2001 From: Cory LaViska <cory@abeautifulsite.net> Date: Mon, 24 Nov 2025 15:58:08 -0500 Subject: [PATCH 35/59] update npm-check-updates and set a cooldown (#1820) --- package-lock.json | 19 +++++++++++++++++-- packages/webawesome/package.json | 5 +++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index f82819c0d..311fb0b01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14029,7 +14029,8 @@ "devDependencies": { "@wc-toolkit/jsx-types": "^1.3.0", "eleventy-plugin-git-commit-date": "^0.1.3", - "esbuild": "^0.25.11" + "esbuild": "^0.25.11", + "npm-check-updates": "^19.1.2" }, "engines": { "node": ">=14.17.0" @@ -14037,7 +14038,7 @@ }, "packages/webawesome-pro": { "name": "@shoelace-style/webawesome-pro", - "version": "3.0.0-beta.6", + "version": "3.0.0", "dependencies": { "@ctrl/tinycolor": "4.1.0", "@floating-ui/dom": "^1.6.13", @@ -14966,6 +14967,20 @@ "engines": { "node": "^18 || >=20" } + }, + "packages/webawesome/node_modules/npm-check-updates": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-19.1.2.tgz", + "integrity": "sha512-FNeFCVgPOj0fz89hOpGtxP2rnnRHR7hD2E8qNU8SMWfkyDZXA/xpgjsL3UMLSo3F/K13QvJDnbxPngulNDDo/g==", + "dev": true, + "bin": { + "ncu": "build/cli.js", + "npm-check-updates": "build/cli.js" + }, + "engines": { + "node": ">=20.0.0", + "npm": ">=8.12.1" + } } } } diff --git a/packages/webawesome/package.json b/packages/webawesome/package.json index 176b36f6d..151ae51df 100644 --- a/packages/webawesome/package.json +++ b/packages/webawesome/package.json @@ -64,7 +64,7 @@ "spellcheck": "cspell \"**/*.{js,ts,json,html,css,md}\" --no-progress --config=\"../../cspell.json\"", "verify": "npm run prettier && npm run build && npm run test", "prepublishOnly": "npm run verify", - "check-updates": "npx npm-check-updates --interactive --format group", + "check-updates": "npm-check-updates --cooldown 7 --interactive --format group", "print-version": "echo $npm_package_version", "tag-version": "git tag -a \"v$(npm run print-version | tail -n1)\" -m \"tag v$(npm run print-version | tail -n1)\"", "postversion": "npm run tag-version" @@ -91,6 +91,7 @@ "devDependencies": { "@wc-toolkit/jsx-types": "^1.3.0", "eleventy-plugin-git-commit-date": "^0.1.3", - "esbuild": "^0.25.11" + "esbuild": "^0.25.11", + "npm-check-updates": "^19.1.2" } } From 5fb6faf527d893ac3cf9be30d29c4c5867e22942 Mon Sep 17 00:00:00 2001 From: Konnor Rogers <konnor5456@gmail.com> Date: Tue, 25 Nov 2025 12:09:56 -0500 Subject: [PATCH 36/59] update webawesome simulation to be closer to the app. (#1789) * update webawesome simulation to be closer to the app. * prettier --- .../docs/_utils/simulate-webawesome-app.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/webawesome/docs/_utils/simulate-webawesome-app.js b/packages/webawesome/docs/_utils/simulate-webawesome-app.js index 0c07a25c7..f24682521 100644 --- a/packages/webawesome/docs/_utils/simulate-webawesome-app.js +++ b/packages/webawesome/docs/_utils/simulate-webawesome-app.js @@ -1,10 +1,20 @@ +import * as path from 'node:path'; import nunjucks from 'nunjucks'; +const baseDir = process.env.BASE_DIR || 'docs'; + +const views = [path.join(baseDir), path.join(baseDir, '_layouts'), path.join(baseDir, '_includes')]; + +const nunjucksEnv = new nunjucks.Environment(new nunjucks.FileSystemLoader(views), { + autoescape: true, + noCache: process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test', +}); + /** * This function simulates what a server would do running "on top" of eleventy. */ export function SimulateWebAwesomeApp(str) { - return nunjucks.renderString(str, { + return nunjucksEnv.renderString(str, { // Stub the server EJS shortcodes. currentUser: { hasPro: false, From 8ae90ef2b231d5f0e840eeb4354ec131e8844550 Mon Sep 17 00:00:00 2001 From: Cory LaViska <cory@abeautifulsite.net> Date: Tue, 25 Nov 2025 16:35:31 -0500 Subject: [PATCH 37/59] add missing icon dep (#1828) --- packages/webawesome/docs/docs/resources/changelog.md | 1 + .../webawesome/src/components/dropdown-item/dropdown-item.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index a6f2c5e36..bc4553f9f 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -20,6 +20,7 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh - Fixed a bug in `<wa-icon>` that caused some icon libraries to render with the incorrect SVG fill [issue:1733] - Fixed a bug in `<wa-tree-item>` that caused the spinner to not show when lazy loading [issue:1678] - Fixed a bug in `<wa-dropdown>` that caused the browser to hang when cancelling the `wa-hide` event [issue:1483] +- Fixed a bug in `<wa-dropdown-item>` that prevented the icon dependency from being imported [issue:1825] - Improved performance of `<wa-icon>` so initial rendering occurs faster, especially with multiple icons on the page [issue:1729] - Modified the default `transition` styles of `<wa-dropdown-item>` to use design tokens [pr:1693] diff --git a/packages/webawesome/src/components/dropdown-item/dropdown-item.ts b/packages/webawesome/src/components/dropdown-item/dropdown-item.ts index 4e43f5d6f..c01b7fcb9 100644 --- a/packages/webawesome/src/components/dropdown-item/dropdown-item.ts +++ b/packages/webawesome/src/components/dropdown-item/dropdown-item.ts @@ -4,6 +4,7 @@ import { customElement, property, query, state } from 'lit/decorators.js'; import { animateWithClass } from '../../internal/animate.js'; import { HasSlotController } from '../../internal/slot.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; +import '../icon/icon.js'; import styles from './dropdown-item.css'; /** From c0ced13f066f7904afe1cb0c3a354577b3d106bc Mon Sep 17 00:00:00 2001 From: Patrick McDougle <patrick@patrickmcdougle.com> Date: Tue, 2 Dec 2025 10:29:04 -0600 Subject: [PATCH 38/59] Add Intersection Observer component to the Utilities Category (#1837) --- .../webawesome/docs/docs/components/intersection-observer.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/webawesome/docs/docs/components/intersection-observer.md b/packages/webawesome/docs/docs/components/intersection-observer.md index 1252b2bcd..c2fd70e25 100644 --- a/packages/webawesome/docs/docs/components/intersection-observer.md +++ b/packages/webawesome/docs/docs/components/intersection-observer.md @@ -2,6 +2,7 @@ title: Intersection Observer description: Tracks immediate child elements and fires events as they move in and out of view. layout: component +category: Utilities --- This component leverages the [IntersectionObserver API](https://developer.mozilla.org/en-US/docs/Web/API/IntersectionObserver) to track when its direct children enter or leave a designated root element. The `wa-intersect` event fires whenever elements cross the visibility threshold. From 2baebca2307397c40677534c4a172a32a1424538 Mon Sep 17 00:00:00 2001 From: Cory LaViska <cory@abeautifulsite.net> Date: Mon, 8 Dec 2025 13:37:00 -0500 Subject: [PATCH 39/59] remove old and unsupported example (#1842) --- .../docs/docs/components/button-group.md | 54 ------------------- 1 file changed, 54 deletions(-) diff --git a/packages/webawesome/docs/docs/components/button-group.md b/packages/webawesome/docs/docs/components/button-group.md index ce4c5ebf4..5a95cd281 100644 --- a/packages/webawesome/docs/docs/components/button-group.md +++ b/packages/webawesome/docs/docs/components/button-group.md @@ -40,60 +40,6 @@ Set the `orientation` attribute to `vertical` to make a vertical button group. </wa-button-group> ``` -### Theme Buttons - -Theme buttons are supported through the button group's `variant` attribute. - -```html {.example} -<wa-button-group label="Alignment" variant="brand"> - <wa-button>Left</wa-button> - <wa-button>Center</wa-button> - <wa-button>Right</wa-button> -</wa-button-group> - -<br /><br /> - -<wa-button-group label="Alignment" variant="success"> - <wa-button>Left</wa-button> - <wa-button>Center</wa-button> - <wa-button>Right</wa-button> -</wa-button-group> - -<br /><br /> - -<wa-button-group label="Alignment"> - <wa-button>Left</wa-button> - <wa-button>Center</wa-button> - <wa-button>Right</wa-button> -</wa-button-group> - -<br /><br /> - -<wa-button-group label="Alignment" variant="warning"> - <wa-button>Left</wa-button> - <wa-button>Center</wa-button> - <wa-button>Right</wa-button> -</wa-button-group> - -<br /><br /> - -<wa-button-group label="Alignment" variant="danger"> - <wa-button>Left</wa-button> - <wa-button>Center</wa-button> - <wa-button>Right</wa-button> -</wa-button-group> -``` - -You can still use the buttons’ own `variant` attribute to override the inherited variant. - -```html {.example} -<wa-button-group label="Alignment" variant="brand"> - <wa-button>Left</wa-button> - <wa-button>Center</wa-button> - <wa-button variant="neutral">Right</wa-button> -</wa-button-group> -``` - ### Pill Buttons Pill buttons are supported through the button's `pill` attribute. From 579e86fc4999cd2f7194823696267457a5c21aa3 Mon Sep 17 00:00:00 2001 From: David Enke <david@enke.dev> Date: Mon, 8 Dec 2025 19:49:22 +0100 Subject: [PATCH 40/59] fix(wa-button): add missing css transition property (#1850) fixes #1844 --- packages/webawesome/src/components/button/button.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webawesome/src/components/button/button.css b/packages/webawesome/src/components/button/button.css index 7660f5c0e..ac7ddc3f2 100644 --- a/packages/webawesome/src/components/button/button.css +++ b/packages/webawesome/src/components/button/button.css @@ -26,7 +26,7 @@ -webkit-user-select: none; white-space: nowrap; vertical-align: middle; - transition-property: background, border, box-shadow, color; + transition-property: background, border, box-shadow, color, opacity; transition-duration: var(--wa-transition-fast); transition-timing-function: var(--wa-transition-easing); cursor: pointer; From fa6ccc1ee26c7d91f55ee71161ccdfe439e0df42 Mon Sep 17 00:00:00 2001 From: Brian Talbot <hi.talbs@gmail.com> Date: Tue, 9 Dec 2025 12:10:24 -0500 Subject: [PATCH 41/59] adding cookie consent dialog to base layout (#1840) --- packages/webawesome/docs/_includes/base.njk | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/webawesome/docs/_includes/base.njk b/packages/webawesome/docs/_includes/base.njk index 26b50c2b1..d7fafbed0 100644 --- a/packages/webawesome/docs/_includes/base.njk +++ b/packages/webawesome/docs/_includes/base.njk @@ -142,6 +142,9 @@ {% include "_dialog-wa-launch.njk" ignore missing %} {% endif %} + {#- Cookie Consent Dialog -#} + {% include "cookie-consent.njk" ignore missing %} + {# Footer #} {% block pageFooter %}{% endblock %} </wa-page> From 079c09b9a3fdb0536229d394aa8d4fb86df38676 Mon Sep 17 00:00:00 2001 From: Brian Talbot <hi.talbs@gmail.com> Date: Wed, 10 Dec 2025 09:38:46 -0500 Subject: [PATCH 42/59] Space Junk Analytics: Remove Plausible Code + Details (#1801) * removing plausible-based code --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../docs/_includes/_dialog-wa-launch.njk | 24 +++---------------- packages/webawesome/docs/_includes/base.njk | 1 - 2 files changed, 3 insertions(+), 22 deletions(-) diff --git a/packages/webawesome/docs/_includes/_dialog-wa-launch.njk b/packages/webawesome/docs/_includes/_dialog-wa-launch.njk index e1ddbeb19..e1326ccbc 100644 --- a/packages/webawesome/docs/_includes/_dialog-wa-launch.njk +++ b/packages/webawesome/docs/_includes/_dialog-wa-launch.njk @@ -14,8 +14,8 @@ <p>Celebrate our official launch with a 20% discount on a Web Awesome Pro plan…<span class="appearance-underlined variant-drawn" style="--underline-color: var(--wa-color-brand);">for life</span>! But hurry, this lifetime discount is only available for a limited time.</p> <div class="wa-split"> - <wa-button type="button" appearance="plain" data-dialog="close" class="plausible-event-name=launch_dialog:close_button_click">Maybe Later</wa-button> - <wa-button variant="neutral" appearance="accent" href="/purchase" class="brand-font plausible-event-name=launch_dialog:pro_purchase_button_click"> + <wa-button type="button" appearance="plain" data-dialog="close">Maybe Later</wa-button> + <wa-button variant="neutral" appearance="accent" href="/purchase" class="brand-font"> <wa-icon slot="start" variant="regular" name="rocket-launch"></wa-icon> Get Pro + Save 20% </wa-button> @@ -43,13 +43,6 @@ return; } - // Helper function to safely track Plausible events - const trackEvent = (eventName) => { - if (typeof plausible !== 'undefined') { - plausible(eventName); - } - }; - // Initialize dialog functionality let initCalled = false; const initDialog = () => { @@ -59,19 +52,8 @@ } initCalled = true; - // Track when dialog is shown - dialog.addEventListener('wa-show', () => { - trackEvent('launch_dialog:view'); - }, { once: true }); - - // Track when dialog is dismissed + // Save dismissal state when dialog is hidden dialog.addEventListener('wa-hide', (event) => { - // Track overlay click or Escape key dismissal - // Button clicks are tracked via CSS classes, so we only track non-button dismissals - if (event.detail?.source === dialog) { - trackEvent('launch_dialog:overlay_click'); - } - // Save dismissal state to localStorage try { localStorage.setItem(SITE_DIALOG_DISMISSED_KEY, 'true'); diff --git a/packages/webawesome/docs/_includes/base.njk b/packages/webawesome/docs/_includes/base.njk index d7fafbed0..eb7d837ec 100644 --- a/packages/webawesome/docs/_includes/base.njk +++ b/packages/webawesome/docs/_includes/base.njk @@ -17,7 +17,6 @@ <script type="module" src="/assets/scripts/color-scheme.js"></script> <script type="module" src="/assets/scripts/theme.js"></script> {% if hasSidebar %}<script type="module" src="/assets/scripts/sidebar.js"></script>{% endif %} - <script defer data-domain="webawesome.com" src="https://plausible.io/js/script.js"></script> {% block head %} <link rel="stylesheet" href="/assets/styles/docs.css" /> From 895fb304e0eab7f7d4a7d2173b63091b6327f072 Mon Sep 17 00:00:00 2001 From: Cory LaViska <cory@abeautifulsite.net> Date: Thu, 11 Dec 2025 11:32:51 -0500 Subject: [PATCH 43/59] Revert `.css` => `.styles.ts` (#1865) * revert .css => .styles.ts; fixes #1812 * fix native styles and vh util * remove layers in component CSS * add layer --- .../docs/docs/resources/changelog.md | 1 + packages/webawesome/scripts/build.js | 3 - .../animated-image/animated-image.css | 65 --- .../animated-image/animated-image.styles.ts | 69 ++++ .../animated-image/animated-image.ts | 2 +- .../src/components/animation/animation.css | 3 - .../components/animation/animation.styles.ts | 7 + .../src/components/animation/animation.ts | 2 +- .../src/components/avatar/avatar.css | 53 --- .../src/components/avatar/avatar.styles.ts | 57 +++ .../src/components/avatar/avatar.ts | 2 +- .../webawesome/src/components/badge/badge.css | 104 ----- .../src/components/badge/badge.styles.ts | 108 +++++ .../webawesome/src/components/badge/badge.ts | 4 +- .../breadcrumb-item/breadcrumb-item.css | 81 ---- .../breadcrumb-item/breadcrumb-item.styles.ts | 85 ++++ .../breadcrumb-item/breadcrumb-item.ts | 2 +- .../src/components/breadcrumb/breadcrumb.css | 5 - .../breadcrumb/breadcrumb.styles.ts | 9 + .../src/components/breadcrumb/breadcrumb.ts | 2 +- .../components/button-group/button-group.css | 44 -- .../button-group/button-group.styles.ts | 48 +++ .../components/button-group/button-group.ts | 2 +- .../src/components/button/button.css | 372 ----------------- .../src/components/button/button.styles.ts | 376 ++++++++++++++++++ .../src/components/button/button.ts | 6 +- .../src/components/callout/callout.css | 60 --- .../src/components/callout/callout.styles.ts | 64 +++ .../src/components/callout/callout.ts | 6 +- .../webawesome/src/components/card/card.css | 141 ------- .../src/components/card/card.styles.ts | 145 +++++++ .../webawesome/src/components/card/card.ts | 4 +- .../carousel-item/carousel-item.css | 19 - .../carousel-item/carousel-item.styles.ts | 23 ++ .../components/carousel-item/carousel-item.ts | 2 +- .../src/components/carousel/carousel.css | 154 ------- .../components/carousel/carousel.styles.ts | 158 ++++++++ .../src/components/carousel/carousel.ts | 2 +- .../src/components/checkbox/checkbox.css | 100 ----- .../components/checkbox/checkbox.styles.ts | 104 +++++ .../src/components/checkbox/checkbox.ts | 6 +- .../components/color-picker/color-picker.css | 341 ---------------- .../color-picker/color-picker.styles.ts | 345 ++++++++++++++++ .../components/color-picker/color-picker.ts | 8 +- .../src/components/comparison/comparison.css | 76 ---- .../comparison/comparison.styles.ts | 80 ++++ .../src/components/comparison/comparison.ts | 2 +- .../components/copy-button/copy-button.css | 75 ---- .../copy-button/copy-button.styles.ts | 79 ++++ .../src/components/copy-button/copy-button.ts | 4 +- .../src/components/details/details.css | 122 ------ .../src/components/details/details.styles.ts | 126 ++++++ .../src/components/details/details.ts | 2 +- .../src/components/dialog/dialog.css | 183 --------- .../src/components/dialog/dialog.styles.ts | 187 +++++++++ .../src/components/dialog/dialog.ts | 2 +- .../src/components/divider/divider.css | 19 - .../src/components/divider/divider.styles.ts | 23 ++ .../src/components/divider/divider.ts | 2 +- .../src/components/drawer/drawer.css | 290 -------------- .../src/components/drawer/drawer.styles.ts | 294 ++++++++++++++ .../src/components/drawer/drawer.ts | 2 +- .../dropdown-item/dropdown-item.css | 227 ----------- .../dropdown-item/dropdown-item.styles.ts | 231 +++++++++++ .../components/dropdown-item/dropdown-item.ts | 2 +- .../src/components/dropdown/dropdown.css | 93 ----- .../components/dropdown/dropdown.styles.ts | 97 +++++ .../src/components/dropdown/dropdown.ts | 4 +- .../webawesome/src/components/icon/icon.css | 40 -- .../src/components/icon/icon.styles.ts | 44 ++ .../webawesome/src/components/icon/icon.ts | 2 +- .../src/components/include/include.css | 3 - .../src/components/include/include.styles.ts | 7 + .../src/components/include/include.ts | 2 +- .../webawesome/src/components/input/input.css | 227 ----------- .../src/components/input/input.styles.ts | 231 +++++++++++ .../webawesome/src/components/input/input.ts | 6 +- .../intersection-observer.css | 3 - .../intersection-observer.styles.ts | 7 + .../intersection-observer.ts | 2 +- .../mutation-observer/mutation-observer.css | 3 - .../mutation-observer.styles.ts | 7 + .../mutation-observer/mutation-observer.ts | 2 +- .../src/components/option/option.css | 80 ---- .../src/components/option/option.styles.ts | 84 ++++ .../src/components/option/option.ts | 2 +- .../src/components/popover/popover.css | 91 ----- .../src/components/popover/popover.styles.ts | 95 +++++ .../src/components/popover/popover.ts | 2 +- .../webawesome/src/components/popup/popup.css | 121 ------ .../src/components/popup/popup.styles.ts | 125 ++++++ .../webawesome/src/components/popup/popup.ts | 2 +- .../components/progress-bar/progress-bar.css | 66 --- .../progress-bar/progress-bar.styles.ts | 70 ++++ .../components/progress-bar/progress-bar.ts | 2 +- .../progress-ring/progress-ring.css | 64 --- .../progress-ring/progress-ring.styles.ts | 68 ++++ .../components/progress-ring/progress-ring.ts | 2 +- .../src/components/qr-code/qr-code.css | 12 - .../src/components/qr-code/qr-code.styles.ts | 16 + .../src/components/qr-code/qr-code.ts | 2 +- .../components/radio-group/radio-group.css | 36 -- .../radio-group/radio-group.styles.ts | 40 ++ .../src/components/radio-group/radio-group.ts | 6 +- .../webawesome/src/components/radio/radio.css | 199 --------- .../src/components/radio/radio.styles.ts | 203 ++++++++++ .../webawesome/src/components/radio/radio.ts | 6 +- .../src/components/rating/rating.css | 85 ---- .../src/components/rating/rating.styles.ts | 89 +++++ .../src/components/rating/rating.ts | 4 +- .../resize-observer/resize-observer.css | 3 - .../resize-observer/resize-observer.styles.ts | 7 + .../resize-observer/resize-observer.ts | 2 +- .../src/components/scroller/scroller.css | 125 ------ .../components/scroller/scroller.styles.ts | 129 ++++++ .../src/components/scroller/scroller.ts | 2 +- .../src/components/select/select.css | 272 ------------- .../src/components/select/select.styles.ts | 276 +++++++++++++ .../src/components/select/select.ts | 6 +- .../src/components/skeleton/skeleton.css | 54 --- .../components/skeleton/skeleton.styles.ts | 58 +++ .../src/components/skeleton/skeleton.ts | 2 +- .../src/components/slider/slider.css | 229 ----------- .../src/components/slider/slider.styles.ts | 233 +++++++++++ .../src/components/slider/slider.ts | 6 +- .../src/components/spinner/spinner.css | 59 --- .../src/components/spinner/spinner.styles.ts | 64 +++ .../src/components/spinner/spinner.ts | 2 +- .../components/split-panel/split-panel.css | 73 ---- .../split-panel/split-panel.styles.ts | 77 ++++ .../src/components/split-panel/split-panel.ts | 2 +- .../src/components/switch/switch.css | 98 ----- .../src/components/switch/switch.styles.ts | 102 +++++ .../src/components/switch/switch.ts | 6 +- .../src/components/tab-group/tab-group.css | 224 ----------- .../components/tab-group/tab-group.styles.ts | 228 +++++++++++ .../src/components/tab-group/tab-group.ts | 2 +- .../src/components/tab-panel/tab-panel.css | 14 - .../components/tab-panel/tab-panel.styles.ts | 18 + .../src/components/tab-panel/tab-panel.ts | 2 +- .../webawesome/src/components/tab/tab.css | 56 --- .../src/components/tab/tab.styles.ts | 60 +++ packages/webawesome/src/components/tab/tab.ts | 2 +- .../webawesome/src/components/tag/tag.css | 78 ---- .../src/components/tag/tag.styles.ts | 82 ++++ packages/webawesome/src/components/tag/tag.ts | 6 +- .../src/components/textarea/textarea.css | 120 ------ .../components/textarea/textarea.styles.ts | 124 ++++++ .../src/components/textarea/textarea.ts | 6 +- .../src/components/tooltip/tooltip.css | 56 --- .../src/components/tooltip/tooltip.styles.ts | 60 +++ .../src/components/tooltip/tooltip.ts | 2 +- .../src/components/tree-item/tree-item.css | 150 ------- .../components/tree-item/tree-item.styles.ts | 154 +++++++ .../src/components/tree-item/tree-item.ts | 2 +- .../tree/{tree.css => tree.styles.ts} | 26 +- .../webawesome/src/components/tree/tree.ts | 2 +- .../zoomable-frame/zoomable-frame.css | 82 ---- .../zoomable-frame/zoomable-frame.styles.ts | 86 ++++ .../zoomable-frame/zoomable-frame.ts | 2 +- .../src/internal/webawesome-element.ts | 23 +- .../src/styles/component/form-control.css | 34 -- .../styles/component/form-control.styles.ts | 38 ++ .../webawesome/src/styles/component/host.css | 13 - .../src/styles/component/host.styles.ts | 17 + .../src/styles/component/size.styles.ts | 18 + .../src/styles/component/variants.styles.ts | 69 ++++ .../component/visually-hidden.styles.ts | 18 + .../webawesome/src/styles/utilities/size.css | 3 - .../src/styles/utilities/variants.css | 15 +- 170 files changed, 5831 insertions(+), 5522 deletions(-) delete mode 100644 packages/webawesome/src/components/animated-image/animated-image.css create mode 100644 packages/webawesome/src/components/animated-image/animated-image.styles.ts delete mode 100644 packages/webawesome/src/components/animation/animation.css create mode 100644 packages/webawesome/src/components/animation/animation.styles.ts delete mode 100644 packages/webawesome/src/components/avatar/avatar.css create mode 100644 packages/webawesome/src/components/avatar/avatar.styles.ts delete mode 100644 packages/webawesome/src/components/badge/badge.css create mode 100644 packages/webawesome/src/components/badge/badge.styles.ts delete mode 100644 packages/webawesome/src/components/breadcrumb-item/breadcrumb-item.css create mode 100644 packages/webawesome/src/components/breadcrumb-item/breadcrumb-item.styles.ts delete mode 100644 packages/webawesome/src/components/breadcrumb/breadcrumb.css create mode 100644 packages/webawesome/src/components/breadcrumb/breadcrumb.styles.ts delete mode 100644 packages/webawesome/src/components/button-group/button-group.css create mode 100644 packages/webawesome/src/components/button-group/button-group.styles.ts delete mode 100644 packages/webawesome/src/components/button/button.css create mode 100644 packages/webawesome/src/components/button/button.styles.ts delete mode 100644 packages/webawesome/src/components/callout/callout.css create mode 100644 packages/webawesome/src/components/callout/callout.styles.ts delete mode 100644 packages/webawesome/src/components/card/card.css create mode 100644 packages/webawesome/src/components/card/card.styles.ts delete mode 100644 packages/webawesome/src/components/carousel-item/carousel-item.css create mode 100644 packages/webawesome/src/components/carousel-item/carousel-item.styles.ts delete mode 100644 packages/webawesome/src/components/carousel/carousel.css create mode 100644 packages/webawesome/src/components/carousel/carousel.styles.ts delete mode 100644 packages/webawesome/src/components/checkbox/checkbox.css create mode 100644 packages/webawesome/src/components/checkbox/checkbox.styles.ts delete mode 100644 packages/webawesome/src/components/color-picker/color-picker.css create mode 100644 packages/webawesome/src/components/color-picker/color-picker.styles.ts delete mode 100644 packages/webawesome/src/components/comparison/comparison.css create mode 100644 packages/webawesome/src/components/comparison/comparison.styles.ts delete mode 100644 packages/webawesome/src/components/copy-button/copy-button.css create mode 100644 packages/webawesome/src/components/copy-button/copy-button.styles.ts delete mode 100644 packages/webawesome/src/components/details/details.css create mode 100644 packages/webawesome/src/components/details/details.styles.ts delete mode 100644 packages/webawesome/src/components/dialog/dialog.css create mode 100644 packages/webawesome/src/components/dialog/dialog.styles.ts delete mode 100644 packages/webawesome/src/components/divider/divider.css create mode 100644 packages/webawesome/src/components/divider/divider.styles.ts delete mode 100644 packages/webawesome/src/components/drawer/drawer.css create mode 100644 packages/webawesome/src/components/drawer/drawer.styles.ts delete mode 100644 packages/webawesome/src/components/dropdown-item/dropdown-item.css create mode 100644 packages/webawesome/src/components/dropdown-item/dropdown-item.styles.ts delete mode 100644 packages/webawesome/src/components/dropdown/dropdown.css create mode 100644 packages/webawesome/src/components/dropdown/dropdown.styles.ts delete mode 100644 packages/webawesome/src/components/icon/icon.css create mode 100644 packages/webawesome/src/components/icon/icon.styles.ts delete mode 100644 packages/webawesome/src/components/include/include.css create mode 100644 packages/webawesome/src/components/include/include.styles.ts delete mode 100644 packages/webawesome/src/components/input/input.css create mode 100644 packages/webawesome/src/components/input/input.styles.ts delete mode 100644 packages/webawesome/src/components/intersection-observer/intersection-observer.css create mode 100644 packages/webawesome/src/components/intersection-observer/intersection-observer.styles.ts delete mode 100644 packages/webawesome/src/components/mutation-observer/mutation-observer.css create mode 100644 packages/webawesome/src/components/mutation-observer/mutation-observer.styles.ts delete mode 100644 packages/webawesome/src/components/option/option.css create mode 100644 packages/webawesome/src/components/option/option.styles.ts delete mode 100644 packages/webawesome/src/components/popover/popover.css create mode 100644 packages/webawesome/src/components/popover/popover.styles.ts delete mode 100644 packages/webawesome/src/components/popup/popup.css create mode 100644 packages/webawesome/src/components/popup/popup.styles.ts delete mode 100644 packages/webawesome/src/components/progress-bar/progress-bar.css create mode 100644 packages/webawesome/src/components/progress-bar/progress-bar.styles.ts delete mode 100644 packages/webawesome/src/components/progress-ring/progress-ring.css create mode 100644 packages/webawesome/src/components/progress-ring/progress-ring.styles.ts delete mode 100644 packages/webawesome/src/components/qr-code/qr-code.css create mode 100644 packages/webawesome/src/components/qr-code/qr-code.styles.ts delete mode 100644 packages/webawesome/src/components/radio-group/radio-group.css create mode 100644 packages/webawesome/src/components/radio-group/radio-group.styles.ts delete mode 100644 packages/webawesome/src/components/radio/radio.css create mode 100644 packages/webawesome/src/components/radio/radio.styles.ts delete mode 100644 packages/webawesome/src/components/rating/rating.css create mode 100644 packages/webawesome/src/components/rating/rating.styles.ts delete mode 100644 packages/webawesome/src/components/resize-observer/resize-observer.css create mode 100644 packages/webawesome/src/components/resize-observer/resize-observer.styles.ts delete mode 100644 packages/webawesome/src/components/scroller/scroller.css create mode 100644 packages/webawesome/src/components/scroller/scroller.styles.ts delete mode 100644 packages/webawesome/src/components/select/select.css create mode 100644 packages/webawesome/src/components/select/select.styles.ts delete mode 100644 packages/webawesome/src/components/skeleton/skeleton.css create mode 100644 packages/webawesome/src/components/skeleton/skeleton.styles.ts delete mode 100644 packages/webawesome/src/components/slider/slider.css create mode 100644 packages/webawesome/src/components/slider/slider.styles.ts delete mode 100644 packages/webawesome/src/components/spinner/spinner.css create mode 100644 packages/webawesome/src/components/spinner/spinner.styles.ts delete mode 100644 packages/webawesome/src/components/split-panel/split-panel.css create mode 100644 packages/webawesome/src/components/split-panel/split-panel.styles.ts delete mode 100644 packages/webawesome/src/components/switch/switch.css create mode 100644 packages/webawesome/src/components/switch/switch.styles.ts delete mode 100644 packages/webawesome/src/components/tab-group/tab-group.css create mode 100644 packages/webawesome/src/components/tab-group/tab-group.styles.ts delete mode 100644 packages/webawesome/src/components/tab-panel/tab-panel.css create mode 100644 packages/webawesome/src/components/tab-panel/tab-panel.styles.ts delete mode 100644 packages/webawesome/src/components/tab/tab.css create mode 100644 packages/webawesome/src/components/tab/tab.styles.ts delete mode 100644 packages/webawesome/src/components/tag/tag.css create mode 100644 packages/webawesome/src/components/tag/tag.styles.ts delete mode 100644 packages/webawesome/src/components/textarea/textarea.css create mode 100644 packages/webawesome/src/components/textarea/textarea.styles.ts delete mode 100644 packages/webawesome/src/components/tooltip/tooltip.css create mode 100644 packages/webawesome/src/components/tooltip/tooltip.styles.ts delete mode 100644 packages/webawesome/src/components/tree-item/tree-item.css create mode 100644 packages/webawesome/src/components/tree-item/tree-item.styles.ts rename packages/webawesome/src/components/tree/{tree.css => tree.styles.ts} (53%) delete mode 100644 packages/webawesome/src/components/zoomable-frame/zoomable-frame.css create mode 100644 packages/webawesome/src/components/zoomable-frame/zoomable-frame.styles.ts delete mode 100644 packages/webawesome/src/styles/component/form-control.css create mode 100644 packages/webawesome/src/styles/component/form-control.styles.ts delete mode 100644 packages/webawesome/src/styles/component/host.css create mode 100644 packages/webawesome/src/styles/component/host.styles.ts create mode 100644 packages/webawesome/src/styles/component/size.styles.ts create mode 100644 packages/webawesome/src/styles/component/variants.styles.ts create mode 100644 packages/webawesome/src/styles/component/visually-hidden.styles.ts diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index bc4553f9f..aa9cfb0ba 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -22,6 +22,7 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh - Fixed a bug in `<wa-dropdown>` that caused the browser to hang when cancelling the `wa-hide` event [issue:1483] - Fixed a bug in `<wa-dropdown-item>` that prevented the icon dependency from being imported [issue:1825] - Improved performance of `<wa-icon>` so initial rendering occurs faster, especially with multiple icons on the page [issue:1729] +- Improved performance of all components by fixing how CSS is imported and reused [issue:1812] - Modified the default `transition` styles of `<wa-dropdown-item>` to use design tokens [pr:1693] ## 3.0.0 diff --git a/packages/webawesome/scripts/build.js b/packages/webawesome/scripts/build.js index f9c6112ef..4bfe15c27 100644 --- a/packages/webawesome/scripts/build.js +++ b/packages/webawesome/scripts/build.js @@ -230,9 +230,6 @@ export async function build(options = {}) { js: `/*! Copyright ${currentYear} Fonticons, Inc. - https://webawesome.com/license */`, }, plugins: [replace({ __WEBAWESOME_VERSION__: await getVersion() })], - loader: { - '.css': 'text', - }, }; const unbundledConfig = { diff --git a/packages/webawesome/src/components/animated-image/animated-image.css b/packages/webawesome/src/components/animated-image/animated-image.css deleted file mode 100644 index 59f659044..000000000 --- a/packages/webawesome/src/components/animated-image/animated-image.css +++ /dev/null @@ -1,65 +0,0 @@ -:host { - --control-box-size: 3rem; - --icon-size: calc(var(--control-box-size) * 0.625); - - display: inline-flex; - position: relative; - cursor: pointer; -} - -img { - display: block; - width: 100%; - height: 100%; -} - -img[aria-hidden='true'] { - display: none; -} - -.control-box { - display: flex; - position: absolute; - align-items: center; - justify-content: center; - top: calc(50% - var(--control-box-size) / 2); - right: calc(50% - var(--control-box-size) / 2); - width: var(--control-box-size); - height: var(--control-box-size); - font-size: calc(var(--icon-size) * 0.75); - background: none; - border: solid var(--wa-border-width-s) currentColor; - background-color: rgb(0 0 0 / 50%); - border-radius: var(--wa-border-radius-circle); - color: white; - pointer-events: none; - transition: opacity var(--wa-transition-normal) var(--wa-transition-easing); -} - -@media (hover: hover) { - :host([play]:hover) .control-box { - opacity: 1; - } -} - -:where(:host([play]:not(:hover))) .control-box { - opacity: 0; -} - -:host([play]) slot[name='play-icon'], -:host(:not([play])) slot[name='pause-icon'] { - display: none; -} - -/* Show control box on keyboard focus */ -.animated-image { - &:focus { - outline: none; - } - - &:focus-visible .control-box { - opacity: 1; - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); - } -} diff --git a/packages/webawesome/src/components/animated-image/animated-image.styles.ts b/packages/webawesome/src/components/animated-image/animated-image.styles.ts new file mode 100644 index 000000000..94675b814 --- /dev/null +++ b/packages/webawesome/src/components/animated-image/animated-image.styles.ts @@ -0,0 +1,69 @@ +import { css } from 'lit'; + +export default css` + :host { + --control-box-size: 3rem; + --icon-size: calc(var(--control-box-size) * 0.625); + + display: inline-flex; + position: relative; + cursor: pointer; + } + + img { + display: block; + width: 100%; + height: 100%; + } + + img[aria-hidden='true'] { + display: none; + } + + .control-box { + display: flex; + position: absolute; + align-items: center; + justify-content: center; + top: calc(50% - var(--control-box-size) / 2); + right: calc(50% - var(--control-box-size) / 2); + width: var(--control-box-size); + height: var(--control-box-size); + font-size: calc(var(--icon-size) * 0.75); + background: none; + border: solid var(--wa-border-width-s) currentColor; + background-color: rgb(0 0 0 / 50%); + border-radius: var(--wa-border-radius-circle); + color: white; + pointer-events: none; + transition: opacity var(--wa-transition-normal) var(--wa-transition-easing); + } + + @media (hover: hover) { + :host([play]:hover) .control-box { + opacity: 1; + } + } + + :where(:host([play]:not(:hover))) .control-box { + opacity: 0; + } + + :host([play]) slot[name='play-icon'], + :host(:not([play])) slot[name='pause-icon'] { + display: none; + } + + /* Show control box on keyboard focus */ + .animated-image { + &:focus { + outline: none; + } + + &:focus-visible .control-box { + opacity: 1; + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + } +`; diff --git a/packages/webawesome/src/components/animated-image/animated-image.ts b/packages/webawesome/src/components/animated-image/animated-image.ts index f12ad4f06..c041841fa 100644 --- a/packages/webawesome/src/components/animated-image/animated-image.ts +++ b/packages/webawesome/src/components/animated-image/animated-image.ts @@ -6,7 +6,7 @@ import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import { LocalizeController } from '../../utilities/localize.js'; import '../icon/icon.js'; -import styles from './animated-image.css'; +import styles from './animated-image.styles.js'; /** * @summary A component for displaying animated GIFs and WEBPs that play and pause on interaction. diff --git a/packages/webawesome/src/components/animation/animation.css b/packages/webawesome/src/components/animation/animation.css deleted file mode 100644 index 92d692cdd..000000000 --- a/packages/webawesome/src/components/animation/animation.css +++ /dev/null @@ -1,3 +0,0 @@ -:host { - display: contents; -} diff --git a/packages/webawesome/src/components/animation/animation.styles.ts b/packages/webawesome/src/components/animation/animation.styles.ts new file mode 100644 index 000000000..1ef4bf6f3 --- /dev/null +++ b/packages/webawesome/src/components/animation/animation.styles.ts @@ -0,0 +1,7 @@ +import { css } from 'lit'; + +export default css` + :host { + display: contents; + } +`; diff --git a/packages/webawesome/src/components/animation/animation.ts b/packages/webawesome/src/components/animation/animation.ts index e77ee92aa..7a0e9b06d 100644 --- a/packages/webawesome/src/components/animation/animation.ts +++ b/packages/webawesome/src/components/animation/animation.ts @@ -5,7 +5,7 @@ import { WaFinishEvent } from '../../events/finish.js'; import { WaStartEvent } from '../../events/start.js'; import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import styles from './animation.css'; +import styles from './animation.styles.js'; import { animations } from './animations.js'; /** diff --git a/packages/webawesome/src/components/avatar/avatar.css b/packages/webawesome/src/components/avatar/avatar.css deleted file mode 100644 index 2c071cb5c..000000000 --- a/packages/webawesome/src/components/avatar/avatar.css +++ /dev/null @@ -1,53 +0,0 @@ -:host { - --size: 3rem; - - display: inline-flex; - align-items: center; - justify-content: center; - position: relative; - width: var(--size); - height: var(--size); - color: var(--wa-color-neutral-on-normal); - font: inherit; - font-size: calc(var(--size) * 0.4); - vertical-align: middle; - background-color: var(--wa-color-neutral-fill-normal); - border-radius: var(--wa-border-radius-circle); - user-select: none; - -webkit-user-select: none; -} - -:host([shape='square']) { - border-radius: 0; -} - -:host([shape='rounded']) { - border-radius: var(--wa-border-radius-m); -} - -.icon { - display: flex; - align-items: center; - justify-content: center; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; -} - -.initials { - line-height: 1; - text-transform: uppercase; -} - -.image { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - object-fit: cover; - overflow: hidden; - border-radius: inherit; -} diff --git a/packages/webawesome/src/components/avatar/avatar.styles.ts b/packages/webawesome/src/components/avatar/avatar.styles.ts new file mode 100644 index 000000000..98d8c5ae4 --- /dev/null +++ b/packages/webawesome/src/components/avatar/avatar.styles.ts @@ -0,0 +1,57 @@ +import { css } from 'lit'; + +export default css` + :host { + --size: 3rem; + + display: inline-flex; + align-items: center; + justify-content: center; + position: relative; + width: var(--size); + height: var(--size); + color: var(--wa-color-neutral-on-normal); + font: inherit; + font-size: calc(var(--size) * 0.4); + vertical-align: middle; + background-color: var(--wa-color-neutral-fill-normal); + border-radius: var(--wa-border-radius-circle); + user-select: none; + -webkit-user-select: none; + } + + :host([shape='square']) { + border-radius: 0; + } + + :host([shape='rounded']) { + border-radius: var(--wa-border-radius-m); + } + + .icon { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + } + + .initials { + line-height: 1; + text-transform: uppercase; + } + + .image { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + overflow: hidden; + border-radius: inherit; + } +`; diff --git a/packages/webawesome/src/components/avatar/avatar.ts b/packages/webawesome/src/components/avatar/avatar.ts index 7dc7a1530..4b2c983c9 100644 --- a/packages/webawesome/src/components/avatar/avatar.ts +++ b/packages/webawesome/src/components/avatar/avatar.ts @@ -4,7 +4,7 @@ import { WaErrorEvent } from '../../events/error.js'; import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import '../icon/icon.js'; -import styles from './avatar.css'; +import styles from './avatar.styles.js'; /** * @summary Avatars are used to represent a person or object. diff --git a/packages/webawesome/src/components/badge/badge.css b/packages/webawesome/src/components/badge/badge.css deleted file mode 100644 index 6fceaac75..000000000 --- a/packages/webawesome/src/components/badge/badge.css +++ /dev/null @@ -1,104 +0,0 @@ -:host { - --pulse-color: var(--wa-color-fill-loud, var(--wa-color-brand-fill-loud)); - - display: inline-flex; - align-items: center; - justify-content: center; - padding: 0.375em 0.625em; - color: var(--wa-color-on-loud, var(--wa-color-brand-on-loud)); - font-size: max(var(--wa-font-size-2xs), 0.75em); - font-weight: var(--wa-font-weight-semibold); - line-height: 1; - white-space: nowrap; - background-color: var(--wa-color-fill-loud, var(--wa-color-brand-fill-loud)); - border-color: transparent; - border-radius: var(--wa-border-radius-s); - border-style: var(--wa-border-style); - border-width: var(--wa-border-width-s); - user-select: none; - -webkit-user-select: none; - cursor: inherit; -} - -/* Appearance modifiers */ -:host([appearance='outlined']) { - --pulse-color: var(--wa-color-border-loud, var(--wa-color-brand-border-loud)); - - color: var(--wa-color-on-quiet, var(--wa-color-brand-on-quiet)); - background-color: transparent; - border-color: var(--wa-color-border-loud, var(--wa-color-brand-border-loud)); -} - -:host([appearance='filled']) { - --pulse-color: var(--wa-color-fill-normal, var(--wa-color-brand-fill-normal)); - - color: var(--wa-color-on-normal, var(--wa-color-brand-on-normal)); - background-color: var(--wa-color-fill-normal, var(--wa-color-brand-fill-normal)); - border-color: transparent; -} - -:host([appearance='filled-outlined']) { - --pulse-color: var(--wa-color-border-normal, var(--wa-color-brand-border-normal)); - - color: var(--wa-color-on-normal, var(--wa-color-brand-on-normal)); - background-color: var(--wa-color-fill-normal, var(--wa-color-brand-fill-normal)); - border-color: var(--wa-color-border-normal, var(--wa-color-brand-border-normal)); -} - -:host([appearance='accent']) { - --pulse-color: var(--wa-color-fill-loud, var(--wa-color-brand-fill-loud)); - - color: var(--wa-color-on-loud, var(--wa-color-brand-on-loud)); - background-color: var(--wa-color-fill-loud, var(--wa-color-brand-fill-loud)); - border-color: transparent; -} - -/* Pill modifier */ -:host([pill]) { - border-radius: var(--wa-border-radius-pill); -} - -/* Pulse attention */ -:host([attention='pulse']) { - animation: pulse 1.5s infinite; -} - -@keyframes pulse { - 0% { - box-shadow: 0 0 0 0 var(--pulse-color); - } - 70% { - box-shadow: 0 0 0 0.5rem transparent; - } - 100% { - box-shadow: 0 0 0 0 transparent; - } -} - -/* Bounce attention */ -:host([attention='bounce']) { - animation: bounce 1s cubic-bezier(0.28, 0.84, 0.42, 1) infinite; -} - -@keyframes bounce { - 0%, - 20%, - 50%, - 80%, - 100% { - transform: translateY(0); - } - 40% { - transform: translateY(-5px); - } - 60% { - transform: translateY(-2px); - } -} - -::slotted(wa-icon) { - margin-inline-end: var(--wa-space-2xs, 0.25em); - opacity: 90%; - line-height: 1; - height: 0.85em; -} diff --git a/packages/webawesome/src/components/badge/badge.styles.ts b/packages/webawesome/src/components/badge/badge.styles.ts new file mode 100644 index 000000000..14147861e --- /dev/null +++ b/packages/webawesome/src/components/badge/badge.styles.ts @@ -0,0 +1,108 @@ +import { css } from 'lit'; + +export default css` + :host { + --pulse-color: var(--wa-color-fill-loud, var(--wa-color-brand-fill-loud)); + + display: inline-flex; + align-items: center; + justify-content: center; + padding: 0.375em 0.625em; + color: var(--wa-color-on-loud, var(--wa-color-brand-on-loud)); + font-size: max(var(--wa-font-size-2xs), 0.75em); + font-weight: var(--wa-font-weight-semibold); + line-height: 1; + white-space: nowrap; + background-color: var(--wa-color-fill-loud, var(--wa-color-brand-fill-loud)); + border-color: transparent; + border-radius: var(--wa-border-radius-s); + border-style: var(--wa-border-style); + border-width: var(--wa-border-width-s); + user-select: none; + -webkit-user-select: none; + cursor: inherit; + } + + /* Appearance modifiers */ + :host([appearance='outlined']) { + --pulse-color: var(--wa-color-border-loud, var(--wa-color-brand-border-loud)); + + color: var(--wa-color-on-quiet, var(--wa-color-brand-on-quiet)); + background-color: transparent; + border-color: var(--wa-color-border-loud, var(--wa-color-brand-border-loud)); + } + + :host([appearance='filled']) { + --pulse-color: var(--wa-color-fill-normal, var(--wa-color-brand-fill-normal)); + + color: var(--wa-color-on-normal, var(--wa-color-brand-on-normal)); + background-color: var(--wa-color-fill-normal, var(--wa-color-brand-fill-normal)); + border-color: transparent; + } + + :host([appearance='filled-outlined']) { + --pulse-color: var(--wa-color-border-normal, var(--wa-color-brand-border-normal)); + + color: var(--wa-color-on-normal, var(--wa-color-brand-on-normal)); + background-color: var(--wa-color-fill-normal, var(--wa-color-brand-fill-normal)); + border-color: var(--wa-color-border-normal, var(--wa-color-brand-border-normal)); + } + + :host([appearance='accent']) { + --pulse-color: var(--wa-color-fill-loud, var(--wa-color-brand-fill-loud)); + + color: var(--wa-color-on-loud, var(--wa-color-brand-on-loud)); + background-color: var(--wa-color-fill-loud, var(--wa-color-brand-fill-loud)); + border-color: transparent; + } + + /* Pill modifier */ + :host([pill]) { + border-radius: var(--wa-border-radius-pill); + } + + /* Pulse attention */ + :host([attention='pulse']) { + animation: pulse 1.5s infinite; + } + + @keyframes pulse { + 0% { + box-shadow: 0 0 0 0 var(--pulse-color); + } + 70% { + box-shadow: 0 0 0 0.5rem transparent; + } + 100% { + box-shadow: 0 0 0 0 transparent; + } + } + + /* Bounce attention */ + :host([attention='bounce']) { + animation: bounce 1s cubic-bezier(0.28, 0.84, 0.42, 1) infinite; + } + + @keyframes bounce { + 0%, + 20%, + 50%, + 80%, + 100% { + transform: translateY(0); + } + 40% { + transform: translateY(-5px); + } + 60% { + transform: translateY(-2px); + } + } + + ::slotted(wa-icon) { + margin-inline-end: var(--wa-space-2xs, 0.25em); + opacity: 90%; + line-height: 1; + height: 0.85em; + } +`; diff --git a/packages/webawesome/src/components/badge/badge.ts b/packages/webawesome/src/components/badge/badge.ts index 13c3fa375..892eadb27 100644 --- a/packages/webawesome/src/components/badge/badge.ts +++ b/packages/webawesome/src/components/badge/badge.ts @@ -1,8 +1,8 @@ import { html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import variantStyles from '../../styles/utilities/variants.css'; -import styles from './badge.css'; +import variantStyles from '../../styles/component/variants.styles.js'; +import styles from './badge.styles.js'; /** * @summary Badges are used to draw attention and display statuses or counts. diff --git a/packages/webawesome/src/components/breadcrumb-item/breadcrumb-item.css b/packages/webawesome/src/components/breadcrumb-item/breadcrumb-item.css deleted file mode 100644 index 4784fa017..000000000 --- a/packages/webawesome/src/components/breadcrumb-item/breadcrumb-item.css +++ /dev/null @@ -1,81 +0,0 @@ -:host { - color: var(--wa-color-text-link); - display: inline-flex; - align-items: center; - font: inherit; - font-weight: var(--wa-font-weight-action); - line-height: var(--wa-line-height-normal); - white-space: nowrap; -} - -:host(:last-of-type) { - color: var(--wa-color-text-quiet); -} - -.label { - display: inline-block; - font: inherit; - text-decoration: none; - color: currentColor; - background: none; - border: none; - border-radius: var(--wa-border-radius-m); - padding: 0; - margin: 0; - cursor: pointer; - transition: color var(--wa-transition-normal) var(--wa-transition-easing); -} - -@media (hover: hover) { - :host(:not(:last-of-type)) .label:hover { - color: color-mix(in oklab, currentColor, var(--wa-color-mix-hover)); - } -} - -:host(:not(:last-of-type)) .label:active { - color: color-mix(in oklab, currentColor, var(--wa-color-mix-active)); -} - -.label:focus { - outline: none; -} - -.label:focus-visible { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); -} - -.start, -.end { - display: none; - flex: 0 0 auto; - display: flex; - align-items: center; -} - -.start, -.end { - display: inline-flex; - color: var(--wa-color-text-quiet); -} - -::slotted([slot='start']) { - margin-inline-end: var(--wa-space-s); -} - -::slotted([slot='end']) { - margin-inline-start: var(--wa-space-s); -} - -:host(:last-of-type) .separator { - display: none; -} - -.separator { - color: var(--wa-color-text-quiet); - display: inline-flex; - align-items: center; - margin: 0 var(--wa-space-s); - user-select: none; - -webkit-user-select: none; -} diff --git a/packages/webawesome/src/components/breadcrumb-item/breadcrumb-item.styles.ts b/packages/webawesome/src/components/breadcrumb-item/breadcrumb-item.styles.ts new file mode 100644 index 000000000..b7370f626 --- /dev/null +++ b/packages/webawesome/src/components/breadcrumb-item/breadcrumb-item.styles.ts @@ -0,0 +1,85 @@ +import { css } from 'lit'; + +export default css` + :host { + color: var(--wa-color-text-link); + display: inline-flex; + align-items: center; + font: inherit; + font-weight: var(--wa-font-weight-action); + line-height: var(--wa-line-height-normal); + white-space: nowrap; + } + + :host(:last-of-type) { + color: var(--wa-color-text-quiet); + } + + .label { + display: inline-block; + font: inherit; + text-decoration: none; + color: currentColor; + background: none; + border: none; + border-radius: var(--wa-border-radius-m); + padding: 0; + margin: 0; + cursor: pointer; + transition: color var(--wa-transition-normal) var(--wa-transition-easing); + } + + @media (hover: hover) { + :host(:not(:last-of-type)) .label:hover { + color: color-mix(in oklab, currentColor, var(--wa-color-mix-hover)); + } + } + + :host(:not(:last-of-type)) .label:active { + color: color-mix(in oklab, currentColor, var(--wa-color-mix-active)); + } + + .label:focus { + outline: none; + } + + .label:focus-visible { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + + .start, + .end { + display: none; + flex: 0 0 auto; + display: flex; + align-items: center; + } + + .start, + .end { + display: inline-flex; + color: var(--wa-color-text-quiet); + } + + ::slotted([slot='start']) { + margin-inline-end: var(--wa-space-s); + } + + ::slotted([slot='end']) { + margin-inline-start: var(--wa-space-s); + } + + :host(:last-of-type) .separator { + display: none; + } + + .separator { + color: var(--wa-color-text-quiet); + display: inline-flex; + align-items: center; + margin: 0 var(--wa-space-s); + user-select: none; + -webkit-user-select: none; + } +`; diff --git a/packages/webawesome/src/components/breadcrumb-item/breadcrumb-item.ts b/packages/webawesome/src/components/breadcrumb-item/breadcrumb-item.ts index ff9c8b29d..83ec368c5 100644 --- a/packages/webawesome/src/components/breadcrumb-item/breadcrumb-item.ts +++ b/packages/webawesome/src/components/breadcrumb-item/breadcrumb-item.ts @@ -3,7 +3,7 @@ import { customElement, property, query, state } from 'lit/decorators.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import styles from './breadcrumb-item.css'; +import styles from './breadcrumb-item.styles.js'; /** * @summary Breadcrumb Items are used inside breadcrumbs to represent different links. diff --git a/packages/webawesome/src/components/breadcrumb/breadcrumb.css b/packages/webawesome/src/components/breadcrumb/breadcrumb.css deleted file mode 100644 index a45d4f776..000000000 --- a/packages/webawesome/src/components/breadcrumb/breadcrumb.css +++ /dev/null @@ -1,5 +0,0 @@ -.breadcrumb { - display: flex; - align-items: center; - flex-wrap: wrap; -} diff --git a/packages/webawesome/src/components/breadcrumb/breadcrumb.styles.ts b/packages/webawesome/src/components/breadcrumb/breadcrumb.styles.ts new file mode 100644 index 000000000..86c0e16e0 --- /dev/null +++ b/packages/webawesome/src/components/breadcrumb/breadcrumb.styles.ts @@ -0,0 +1,9 @@ +import { css } from 'lit'; + +export default css` + .breadcrumb { + display: flex; + align-items: center; + flex-wrap: wrap; + } +`; diff --git a/packages/webawesome/src/components/breadcrumb/breadcrumb.ts b/packages/webawesome/src/components/breadcrumb/breadcrumb.ts index be3e24a24..0f2f77093 100644 --- a/packages/webawesome/src/components/breadcrumb/breadcrumb.ts +++ b/packages/webawesome/src/components/breadcrumb/breadcrumb.ts @@ -4,7 +4,7 @@ import WebAwesomeElement from '../../internal/webawesome-element.js'; import { LocalizeController } from '../../utilities/localize.js'; import type WaBreadcrumbItem from '../breadcrumb-item/breadcrumb-item.js'; import '../icon/icon.js'; -import styles from './breadcrumb.css'; +import styles from './breadcrumb.styles.js'; /** * @summary Breadcrumbs provide a group of links so users can easily navigate a website's hierarchy. diff --git a/packages/webawesome/src/components/button-group/button-group.css b/packages/webawesome/src/components/button-group/button-group.css deleted file mode 100644 index ee7e152c0..000000000 --- a/packages/webawesome/src/components/button-group/button-group.css +++ /dev/null @@ -1,44 +0,0 @@ -:host { - display: inline-flex; -} - -.button-group { - display: flex; - position: relative; - isolation: isolate; - flex-wrap: wrap; - gap: 1px; - - @media (hover: hover) { - > :hover, - &::slotted(:hover) { - z-index: 1; - } - } - - /* Focus and checked are always on top */ - > :focus, - &::slotted(:focus), - > [aria-checked='true'], - &::slotted([aria-checked='true']), - > [checked], - &::slotted([checked]) { - z-index: 2 !important; - } -} -:host([orientation='vertical']) .button-group { - flex-direction: column; -} - -/* Button groups with at least one outlined button will not have a gap and instead have borders overlap */ -.button-group.has-outlined { - gap: 0; - - &:not([aria-orientation='vertical']):not(.button-group-vertical)::slotted(:not(:first-child)) { - margin-inline-start: calc(-1 * var(--border-width)); - } - - &:is([aria-orientation='vertical'], .button-group-vertical)::slotted(:not(:first-child)) { - margin-block-start: calc(-1 * var(--border-width)); - } -} diff --git a/packages/webawesome/src/components/button-group/button-group.styles.ts b/packages/webawesome/src/components/button-group/button-group.styles.ts new file mode 100644 index 000000000..dfa4d85c9 --- /dev/null +++ b/packages/webawesome/src/components/button-group/button-group.styles.ts @@ -0,0 +1,48 @@ +import { css } from 'lit'; + +export default css` + :host { + display: inline-flex; + } + + .button-group { + display: flex; + position: relative; + isolation: isolate; + flex-wrap: wrap; + gap: 1px; + + @media (hover: hover) { + > :hover, + &::slotted(:hover) { + z-index: 1; + } + } + + /* Focus and checked are always on top */ + > :focus, + &::slotted(:focus), + > [aria-checked='true'], + &::slotted([aria-checked='true']), + > [checked], + &::slotted([checked]) { + z-index: 2 !important; + } + } + :host([orientation='vertical']) .button-group { + flex-direction: column; + } + + /* Button groups with at least one outlined button will not have a gap and instead have borders overlap */ + .button-group.has-outlined { + gap: 0; + + &:not([aria-orientation='vertical']):not(.button-group-vertical)::slotted(:not(:first-child)) { + margin-inline-start: calc(-1 * var(--border-width)); + } + + &:is([aria-orientation='vertical'], .button-group-vertical)::slotted(:not(:first-child)) { + margin-block-start: calc(-1 * var(--border-width)); + } + } +`; diff --git a/packages/webawesome/src/components/button-group/button-group.ts b/packages/webawesome/src/components/button-group/button-group.ts index 0a491c3ad..cda7123c5 100644 --- a/packages/webawesome/src/components/button-group/button-group.ts +++ b/packages/webawesome/src/components/button-group/button-group.ts @@ -4,7 +4,7 @@ import { customElement, property, query, state } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import type WaButton from '../button/button.js'; -import styles from './button-group.css'; +import styles from './button-group.styles.js'; /** * @summary Button groups can be used to group related buttons into sections. diff --git a/packages/webawesome/src/components/button/button.css b/packages/webawesome/src/components/button/button.css deleted file mode 100644 index ac7ddc3f2..000000000 --- a/packages/webawesome/src/components/button/button.css +++ /dev/null @@ -1,372 +0,0 @@ -@layer wa-component { - :host { - display: inline-block; - - /* Workaround because Chrome doesn't like :host(:has()) below - * https://issues.chromium.org/issues/40062355 - * Firefox doesn't like this nested rule, so both are needed */ - &:has(wa-badge) { - position: relative; - } - } - - /* Apply relative positioning only when needed to position wa-badge - * This avoids creating a new stacking context for every button */ - :host(:has(wa-badge)) { - position: relative; - } -} - -.button { - display: inline-flex; - align-items: center; - justify-content: center; - text-decoration: none; - user-select: none; - -webkit-user-select: none; - white-space: nowrap; - vertical-align: middle; - transition-property: background, border, box-shadow, color, opacity; - transition-duration: var(--wa-transition-fast); - transition-timing-function: var(--wa-transition-easing); - cursor: pointer; - padding: 0 var(--wa-form-control-padding-inline); - font-family: inherit; - font-size: inherit; - font-weight: var(--wa-font-weight-action); - line-height: calc(var(--wa-form-control-height) - var(--border-width) * 2); - height: var(--wa-form-control-height); - width: 100%; - - background-color: var(--wa-color-fill-loud, var(--wa-color-neutral-fill-loud)); - border-color: transparent; - color: var(--wa-color-on-loud, var(--wa-color-neutral-on-loud)); - border-radius: var(--wa-form-control-border-radius); - border-style: var(--wa-border-style); - border-width: var(--wa-border-width-s); -} - -/* Appearance modifiers */ -:host([appearance='plain']) { - .button { - color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); - background-color: transparent; - border-color: transparent; - } - @media (hover: hover) { - .button:not(.disabled):not(.loading):hover { - color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); - background-color: var(--wa-color-fill-quiet, var(--wa-color-neutral-fill-quiet)); - } - } - .button:not(.disabled):not(.loading):active { - color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); - background-color: color-mix( - in oklab, - var(--wa-color-fill-quiet, var(--wa-color-neutral-fill-quiet)), - var(--wa-color-mix-active) - ); - } -} - -:host([appearance='outlined']) { - .button { - color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); - background-color: transparent; - border-color: var(--wa-color-border-loud, var(--wa-color-neutral-border-loud)); - } - @media (hover: hover) { - .button:not(.disabled):not(.loading):hover { - color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); - background-color: var(--wa-color-fill-quiet, var(--wa-color-neutral-fill-quiet)); - } - } - .button:not(.disabled):not(.loading):active { - color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); - background-color: color-mix( - in oklab, - var(--wa-color-fill-quiet, var(--wa-color-neutral-fill-quiet)), - var(--wa-color-mix-active) - ); - } -} - -:host([appearance='filled']) { - .button { - color: var(--wa-color-on-normal, var(--wa-color-neutral-on-normal)); - background-color: var(--wa-color-fill-normal, var(--wa-color-neutral-fill-normal)); - border-color: transparent; - } - @media (hover: hover) { - .button:not(.disabled):not(.loading):hover { - color: var(--wa-color-on-normal, var(--wa-color-neutral-on-normal)); - background-color: color-mix( - in oklab, - var(--wa-color-fill-normal, var(--wa-color-neutral-fill-normal)), - var(--wa-color-mix-hover) - ); - } - } - .button:not(.disabled):not(.loading):active { - color: var(--wa-color-on-normal, var(--wa-color-neutral-on-normal)); - background-color: color-mix( - in oklab, - var(--wa-color-fill-normal, var(--wa-color-neutral-fill-normal)), - var(--wa-color-mix-active) - ); - } -} - -:host([appearance='filled-outlined']) { - .button { - color: var(--wa-color-on-normal, var(--wa-color-neutral-on-normal)); - background-color: var(--wa-color-fill-normal, var(--wa-color-neutral-fill-normal)); - border-color: var(--wa-color-border-normal, var(--wa-color-neutral-border-normal)); - } - @media (hover: hover) { - .button:not(.disabled):not(.loading):hover { - color: var(--wa-color-on-normal, var(--wa-color-neutral-on-normal)); - background-color: color-mix( - in oklab, - var(--wa-color-fill-normal, var(--wa-color-neutral-fill-normal)), - var(--wa-color-mix-hover) - ); - } - } - .button:not(.disabled):not(.loading):active { - color: var(--wa-color-on-normal, var(--wa-color-neutral-on-normal)); - background-color: color-mix( - in oklab, - var(--wa-color-fill-normal, var(--wa-color-neutral-fill-normal)), - var(--wa-color-mix-active) - ); - } -} - -:host([appearance='accent']) { - .button { - color: var(--wa-color-on-loud, var(--wa-color-neutral-on-loud)); - background-color: var(--wa-color-fill-loud, var(--wa-color-neutral-fill-loud)); - border-color: transparent; - } - @media (hover: hover) { - .button:not(.disabled):not(.loading):hover { - background-color: color-mix( - in oklab, - var(--wa-color-fill-loud, var(--wa-color-neutral-fill-loud)), - var(--wa-color-mix-hover) - ); - } - } - .button:not(.disabled):not(.loading):active { - background-color: color-mix( - in oklab, - var(--wa-color-fill-loud, var(--wa-color-neutral-fill-loud)), - var(--wa-color-mix-active) - ); - } -} - -/* Focus states */ -.button:focus { - outline: none; -} - -.button:focus-visible { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); -} - -/* Disabled state */ -.button.disabled { - opacity: 0.5; - cursor: not-allowed; -} - -/* When disabled, prevent mouse events from bubbling up from children */ -.button.disabled * { - pointer-events: none; -} - -/* Keep it last so Safari doesn't stop parsing this block */ -.button::-moz-focus-inner { - border: 0; -} - -/* Icon buttons */ -.button.is-icon-button { - outline-offset: 2px; - width: var(--wa-form-control-height); - aspect-ratio: 1; -} - -.button.is-icon-button:has(wa-icon) { - width: auto; -} - -/* Pill modifier */ -:host([pill]) .button { - border-radius: var(--wa-border-radius-pill); -} - -/* - * Label - */ - -.start, -.end { - flex: 0 0 auto; - display: flex; - align-items: center; - pointer-events: none; -} - -.label { - display: inline-block; -} - -.is-icon-button .label { - display: flex; -} - -.label::slotted(wa-icon) { - align-self: center; -} - -/* - * Caret modifier - */ - -wa-icon[part='caret'] { - display: flex; - align-self: center; - align-items: center; - - &::part(svg) { - width: 0.875em; - height: 0.875em; - } - - .button:has(&) .end { - display: none; - } -} - -/* - * Loading modifier - */ - -.loading { - position: relative; - cursor: wait; - - .start, - .label, - .end, - .caret { - visibility: hidden; - } - - wa-spinner { - --indicator-color: currentColor; - --track-color: color-mix(in oklab, currentColor, transparent 90%); - - position: absolute; - font-size: 1em; - height: 1em; - width: 1em; - top: calc(50% - 0.5em); - left: calc(50% - 0.5em); - } -} - -/* - * Badges - */ - -.button ::slotted(wa-badge) { - border-color: var(--wa-color-surface-default); - position: absolute; - inset-block-start: 0; - inset-inline-end: 0; - translate: 50% -50%; - pointer-events: none; -} - -:host(:dir(rtl)) ::slotted(wa-badge) { - translate: -50% -50%; -} - -/* -* Button spacing -*/ - -slot[name='start']::slotted(*) { - margin-inline-end: 0.75em; -} - -slot[name='end']::slotted(*), -.button:not(.visually-hidden-label) [part='caret'] { - margin-inline-start: 0.75em; -} - -/* - * Button group border radius modifications - */ - -/* Remove border radius from all grouped buttons by default */ -:host(.wa-button-group__button) .button { - border-radius: 0; -} - -/* Horizontal orientation */ -:host(.wa-button-group__horizontal.wa-button-group__button-first) .button { - border-start-start-radius: var(--wa-form-control-border-radius); - border-end-start-radius: var(--wa-form-control-border-radius); -} - -:host(.wa-button-group__horizontal.wa-button-group__button-last) .button { - border-start-end-radius: var(--wa-form-control-border-radius); - border-end-end-radius: var(--wa-form-control-border-radius); -} - -/* Vertical orientation */ -:host(.wa-button-group__vertical) { - flex: 1 1 auto; -} - -:host(.wa-button-group__vertical) .button { - width: 100%; - justify-content: start; -} - -:host(.wa-button-group__vertical.wa-button-group__button-first) .button { - border-start-start-radius: var(--wa-form-control-border-radius); - border-start-end-radius: var(--wa-form-control-border-radius); -} - -:host(.wa-button-group__vertical.wa-button-group__button-last) .button { - border-end-start-radius: var(--wa-form-control-border-radius); - border-end-end-radius: var(--wa-form-control-border-radius); -} - -/* Handle pill modifier for button groups */ -:host([pill].wa-button-group__horizontal.wa-button-group__button-first) .button { - border-start-start-radius: var(--wa-border-radius-pill); - border-end-start-radius: var(--wa-border-radius-pill); -} - -:host([pill].wa-button-group__horizontal.wa-button-group__button-last) .button { - border-start-end-radius: var(--wa-border-radius-pill); - border-end-end-radius: var(--wa-border-radius-pill); -} - -:host([pill].wa-button-group__vertical.wa-button-group__button-first) .button { - border-start-start-radius: var(--wa-border-radius-pill); - border-start-end-radius: var(--wa-border-radius-pill); -} - -:host([pill].wa-button-group__vertical.wa-button-group__button-last) .button { - border-end-start-radius: var(--wa-border-radius-pill); - border-end-end-radius: var(--wa-border-radius-pill); -} diff --git a/packages/webawesome/src/components/button/button.styles.ts b/packages/webawesome/src/components/button/button.styles.ts new file mode 100644 index 000000000..2cd955897 --- /dev/null +++ b/packages/webawesome/src/components/button/button.styles.ts @@ -0,0 +1,376 @@ +import { css } from 'lit'; + +export default css` + @layer wa-component { + :host { + display: inline-block; + + /* Workaround because Chrome doesn't like :host(:has()) below + * https://issues.chromium.org/issues/40062355 + * Firefox doesn't like this nested rule, so both are needed */ + &:has(wa-badge) { + position: relative; + } + } + + /* Apply relative positioning only when needed to position wa-badge + * This avoids creating a new stacking context for every button */ + :host(:has(wa-badge)) { + position: relative; + } + } + + .button { + display: inline-flex; + align-items: center; + justify-content: center; + text-decoration: none; + user-select: none; + -webkit-user-select: none; + white-space: nowrap; + vertical-align: middle; + transition-property: background, border, box-shadow, color, opacity; + transition-duration: var(--wa-transition-fast); + transition-timing-function: var(--wa-transition-easing); + cursor: pointer; + padding: 0 var(--wa-form-control-padding-inline); + font-family: inherit; + font-size: inherit; + font-weight: var(--wa-font-weight-action); + line-height: calc(var(--wa-form-control-height) - var(--border-width) * 2); + height: var(--wa-form-control-height); + width: 100%; + + background-color: var(--wa-color-fill-loud, var(--wa-color-neutral-fill-loud)); + border-color: transparent; + color: var(--wa-color-on-loud, var(--wa-color-neutral-on-loud)); + border-radius: var(--wa-form-control-border-radius); + border-style: var(--wa-border-style); + border-width: var(--wa-border-width-s); + } + + /* Appearance modifiers */ + :host([appearance='plain']) { + .button { + color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); + background-color: transparent; + border-color: transparent; + } + @media (hover: hover) { + .button:not(.disabled):not(.loading):hover { + color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); + background-color: var(--wa-color-fill-quiet, var(--wa-color-neutral-fill-quiet)); + } + } + .button:not(.disabled):not(.loading):active { + color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); + background-color: color-mix( + in oklab, + var(--wa-color-fill-quiet, var(--wa-color-neutral-fill-quiet)), + var(--wa-color-mix-active) + ); + } + } + + :host([appearance='outlined']) { + .button { + color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); + background-color: transparent; + border-color: var(--wa-color-border-loud, var(--wa-color-neutral-border-loud)); + } + @media (hover: hover) { + .button:not(.disabled):not(.loading):hover { + color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); + background-color: var(--wa-color-fill-quiet, var(--wa-color-neutral-fill-quiet)); + } + } + .button:not(.disabled):not(.loading):active { + color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); + background-color: color-mix( + in oklab, + var(--wa-color-fill-quiet, var(--wa-color-neutral-fill-quiet)), + var(--wa-color-mix-active) + ); + } + } + + :host([appearance='filled']) { + .button { + color: var(--wa-color-on-normal, var(--wa-color-neutral-on-normal)); + background-color: var(--wa-color-fill-normal, var(--wa-color-neutral-fill-normal)); + border-color: transparent; + } + @media (hover: hover) { + .button:not(.disabled):not(.loading):hover { + color: var(--wa-color-on-normal, var(--wa-color-neutral-on-normal)); + background-color: color-mix( + in oklab, + var(--wa-color-fill-normal, var(--wa-color-neutral-fill-normal)), + var(--wa-color-mix-hover) + ); + } + } + .button:not(.disabled):not(.loading):active { + color: var(--wa-color-on-normal, var(--wa-color-neutral-on-normal)); + background-color: color-mix( + in oklab, + var(--wa-color-fill-normal, var(--wa-color-neutral-fill-normal)), + var(--wa-color-mix-active) + ); + } + } + + :host([appearance='filled-outlined']) { + .button { + color: var(--wa-color-on-normal, var(--wa-color-neutral-on-normal)); + background-color: var(--wa-color-fill-normal, var(--wa-color-neutral-fill-normal)); + border-color: var(--wa-color-border-normal, var(--wa-color-neutral-border-normal)); + } + @media (hover: hover) { + .button:not(.disabled):not(.loading):hover { + color: var(--wa-color-on-normal, var(--wa-color-neutral-on-normal)); + background-color: color-mix( + in oklab, + var(--wa-color-fill-normal, var(--wa-color-neutral-fill-normal)), + var(--wa-color-mix-hover) + ); + } + } + .button:not(.disabled):not(.loading):active { + color: var(--wa-color-on-normal, var(--wa-color-neutral-on-normal)); + background-color: color-mix( + in oklab, + var(--wa-color-fill-normal, var(--wa-color-neutral-fill-normal)), + var(--wa-color-mix-active) + ); + } + } + + :host([appearance='accent']) { + .button { + color: var(--wa-color-on-loud, var(--wa-color-neutral-on-loud)); + background-color: var(--wa-color-fill-loud, var(--wa-color-neutral-fill-loud)); + border-color: transparent; + } + @media (hover: hover) { + .button:not(.disabled):not(.loading):hover { + background-color: color-mix( + in oklab, + var(--wa-color-fill-loud, var(--wa-color-neutral-fill-loud)), + var(--wa-color-mix-hover) + ); + } + } + .button:not(.disabled):not(.loading):active { + background-color: color-mix( + in oklab, + var(--wa-color-fill-loud, var(--wa-color-neutral-fill-loud)), + var(--wa-color-mix-active) + ); + } + } + + /* Focus states */ + .button:focus { + outline: none; + } + + .button:focus-visible { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + + /* Disabled state */ + .button.disabled { + opacity: 0.5; + cursor: not-allowed; + } + + /* When disabled, prevent mouse events from bubbling up from children */ + .button.disabled * { + pointer-events: none; + } + + /* Keep it last so Safari doesn't stop parsing this block */ + .button::-moz-focus-inner { + border: 0; + } + + /* Icon buttons */ + .button.is-icon-button { + outline-offset: 2px; + width: var(--wa-form-control-height); + aspect-ratio: 1; + } + + .button.is-icon-button:has(wa-icon) { + width: auto; + } + + /* Pill modifier */ + :host([pill]) .button { + border-radius: var(--wa-border-radius-pill); + } + + /* + * Label + */ + + .start, + .end { + flex: 0 0 auto; + display: flex; + align-items: center; + pointer-events: none; + } + + .label { + display: inline-block; + } + + .is-icon-button .label { + display: flex; + } + + .label::slotted(wa-icon) { + align-self: center; + } + + /* + * Caret modifier + */ + + wa-icon[part='caret'] { + display: flex; + align-self: center; + align-items: center; + + &::part(svg) { + width: 0.875em; + height: 0.875em; + } + + .button:has(&) .end { + display: none; + } + } + + /* + * Loading modifier + */ + + .loading { + position: relative; + cursor: wait; + + .start, + .label, + .end, + .caret { + visibility: hidden; + } + + wa-spinner { + --indicator-color: currentColor; + --track-color: color-mix(in oklab, currentColor, transparent 90%); + + position: absolute; + font-size: 1em; + height: 1em; + width: 1em; + top: calc(50% - 0.5em); + left: calc(50% - 0.5em); + } + } + + /* + * Badges + */ + + .button ::slotted(wa-badge) { + border-color: var(--wa-color-surface-default); + position: absolute; + inset-block-start: 0; + inset-inline-end: 0; + translate: 50% -50%; + pointer-events: none; + } + + :host(:dir(rtl)) ::slotted(wa-badge) { + translate: -50% -50%; + } + + /* + * Button spacing + */ + + slot[name='start']::slotted(*) { + margin-inline-end: 0.75em; + } + + slot[name='end']::slotted(*), + .button:not(.visually-hidden-label) [part='caret'] { + margin-inline-start: 0.75em; + } + + /* + * Button group border radius modifications + */ + + /* Remove border radius from all grouped buttons by default */ + :host(.wa-button-group__button) .button { + border-radius: 0; + } + + /* Horizontal orientation */ + :host(.wa-button-group__horizontal.wa-button-group__button-first) .button { + border-start-start-radius: var(--wa-form-control-border-radius); + border-end-start-radius: var(--wa-form-control-border-radius); + } + + :host(.wa-button-group__horizontal.wa-button-group__button-last) .button { + border-start-end-radius: var(--wa-form-control-border-radius); + border-end-end-radius: var(--wa-form-control-border-radius); + } + + /* Vertical orientation */ + :host(.wa-button-group__vertical) { + flex: 1 1 auto; + } + + :host(.wa-button-group__vertical) .button { + width: 100%; + justify-content: start; + } + + :host(.wa-button-group__vertical.wa-button-group__button-first) .button { + border-start-start-radius: var(--wa-form-control-border-radius); + border-start-end-radius: var(--wa-form-control-border-radius); + } + + :host(.wa-button-group__vertical.wa-button-group__button-last) .button { + border-end-start-radius: var(--wa-form-control-border-radius); + border-end-end-radius: var(--wa-form-control-border-radius); + } + + /* Handle pill modifier for button groups */ + :host([pill].wa-button-group__horizontal.wa-button-group__button-first) .button { + border-start-start-radius: var(--wa-border-radius-pill); + border-end-start-radius: var(--wa-border-radius-pill); + } + + :host([pill].wa-button-group__horizontal.wa-button-group__button-last) .button { + border-start-end-radius: var(--wa-border-radius-pill); + border-end-end-radius: var(--wa-border-radius-pill); + } + + :host([pill].wa-button-group__vertical.wa-button-group__button-first) .button { + border-start-start-radius: var(--wa-border-radius-pill); + border-start-end-radius: var(--wa-border-radius-pill); + } + + :host([pill].wa-button-group__vertical.wa-button-group__button-last) .button { + border-end-start-radius: var(--wa-border-radius-pill); + border-end-end-radius: var(--wa-border-radius-pill); + } +`; diff --git a/packages/webawesome/src/components/button/button.ts b/packages/webawesome/src/components/button/button.ts index 6c1225559..19a3dc6cf 100644 --- a/packages/webawesome/src/components/button/button.ts +++ b/packages/webawesome/src/components/button/button.ts @@ -7,13 +7,13 @@ import { HasSlotController } from '../../internal/slot.js'; import { MirrorValidator } from '../../internal/validators/mirror-validator.js'; import { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js'; -import sizeStyles from '../../styles/utilities/size.css'; -import variantStyles from '../../styles/utilities/variants.css'; +import sizeStyles from '../../styles/component/size.styles.js'; +import variantStyles from '../../styles/component/variants.styles.js'; import { LocalizeController } from '../../utilities/localize.js'; import '../icon/icon.js'; import type WaIcon from '../icon/icon.js'; import '../spinner/spinner.js'; -import styles from './button.css'; +import styles from './button.styles.js'; /** * @summary Buttons represent actions that are available to the user. diff --git a/packages/webawesome/src/components/callout/callout.css b/packages/webawesome/src/components/callout/callout.css deleted file mode 100644 index b7118c2e4..000000000 --- a/packages/webawesome/src/components/callout/callout.css +++ /dev/null @@ -1,60 +0,0 @@ -:host { - display: flex; - position: relative; - align-items: stretch; - border-radius: var(--wa-panel-border-radius); - background-color: var(--wa-color-fill-quiet, var(--wa-color-brand-fill-quiet)); - border-color: var(--wa-color-border-quiet, var(--wa-color-brand-border-quiet)); - border-style: var(--wa-panel-border-style); - border-width: var(--wa-panel-border-width); - color: var(--wa-color-text-normal); - padding: 1em; -} - -/* Appearance modifiers */ -:host([appearance~='plain']) { - background-color: transparent; - border-color: transparent; -} - -:host([appearance~='outlined']) { - background-color: transparent; - border-color: var(--wa-color-border-loud, var(--wa-color-brand-border-loud)); -} - -:host([appearance~='filled']) { - background-color: var(--wa-color-fill-quiet, var(--wa-color-brand-fill-quiet)); - border-color: transparent; -} - -:host([appearance~='filled-outlined']) { - border-color: var(--wa-color-border-quiet, var(--wa-color-brand-border-quiet)); -} - -:host([appearance~='accent']) { - color: var(--wa-color-on-loud, var(--wa-color-brand-on-loud)); - background-color: var(--wa-color-fill-loud, var(--wa-color-brand-fill-loud)); - border-color: transparent; - - [part~='icon'] { - color: currentColor; - } -} - -[part~='icon'] { - flex: 0 0 auto; - display: flex; - align-items: center; - color: var(--wa-color-on-quiet); - font-size: 1.25em; -} - -::slotted([slot='icon']) { - margin-inline-end: var(--wa-form-control-padding-inline); -} - -[part~='message'] { - flex: 1 1 auto; - display: block; - overflow: hidden; -} diff --git a/packages/webawesome/src/components/callout/callout.styles.ts b/packages/webawesome/src/components/callout/callout.styles.ts new file mode 100644 index 000000000..9b3b59a54 --- /dev/null +++ b/packages/webawesome/src/components/callout/callout.styles.ts @@ -0,0 +1,64 @@ +import { css } from 'lit'; + +export default css` + :host { + display: flex; + position: relative; + align-items: stretch; + border-radius: var(--wa-panel-border-radius); + background-color: var(--wa-color-fill-quiet, var(--wa-color-brand-fill-quiet)); + border-color: var(--wa-color-border-quiet, var(--wa-color-brand-border-quiet)); + border-style: var(--wa-panel-border-style); + border-width: var(--wa-panel-border-width); + color: var(--wa-color-text-normal); + padding: 1em; + } + + /* Appearance modifiers */ + :host([appearance~='plain']) { + background-color: transparent; + border-color: transparent; + } + + :host([appearance~='outlined']) { + background-color: transparent; + border-color: var(--wa-color-border-loud, var(--wa-color-brand-border-loud)); + } + + :host([appearance~='filled']) { + background-color: var(--wa-color-fill-quiet, var(--wa-color-brand-fill-quiet)); + border-color: transparent; + } + + :host([appearance~='filled-outlined']) { + border-color: var(--wa-color-border-quiet, var(--wa-color-brand-border-quiet)); + } + + :host([appearance~='accent']) { + color: var(--wa-color-on-loud, var(--wa-color-brand-on-loud)); + background-color: var(--wa-color-fill-loud, var(--wa-color-brand-fill-loud)); + border-color: transparent; + + [part~='icon'] { + color: currentColor; + } + } + + [part~='icon'] { + flex: 0 0 auto; + display: flex; + align-items: center; + color: var(--wa-color-on-quiet); + font-size: 1.25em; + } + + ::slotted([slot='icon']) { + margin-inline-end: var(--wa-form-control-padding-inline); + } + + [part~='message'] { + flex: 1 1 auto; + display: block; + overflow: hidden; + } +`; diff --git a/packages/webawesome/src/components/callout/callout.ts b/packages/webawesome/src/components/callout/callout.ts index d33f6600b..f8a2a55aa 100644 --- a/packages/webawesome/src/components/callout/callout.ts +++ b/packages/webawesome/src/components/callout/callout.ts @@ -1,9 +1,9 @@ import { html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import sizeStyles from '../../styles/utilities/size.css'; -import variantStyles from '../../styles/utilities/variants.css'; -import styles from './callout.css'; +import sizeStyles from '../../styles/component/size.styles.js'; +import variantStyles from '../../styles/component/variants.styles.js'; +import styles from './callout.styles.js'; /** * @summary Callouts are used to display important messages inline. diff --git a/packages/webawesome/src/components/card/card.css b/packages/webawesome/src/components/card/card.css deleted file mode 100644 index cb06f19f6..000000000 --- a/packages/webawesome/src/components/card/card.css +++ /dev/null @@ -1,141 +0,0 @@ -:host { - --spacing: var(--wa-space-l); - - /* Internal calculated properties */ - --inner-border-radius: calc(var(--wa-panel-border-radius) - var(--wa-panel-border-width)); - - display: flex; - flex-direction: column; - background-color: var(--wa-color-surface-default); - border-color: var(--wa-color-surface-border); - border-radius: var(--wa-panel-border-radius); - border-style: var(--wa-panel-border-style); - box-shadow: var(--wa-shadow-s); - border-width: var(--wa-panel-border-width); - color: var(--wa-color-text-normal); -} - -/* Appearance modifiers */ -:host([appearance='plain']) { - background-color: transparent; - border-color: transparent; - box-shadow: none; -} - -:host([appearance='outlined']) { - background-color: var(--wa-color-surface-default); - border-color: var(--wa-color-surface-border); -} - -:host([appearance='filled']) { - background-color: var(--wa-color-neutral-fill-quiet); - border-color: transparent; -} - -:host([appearance='filled-outlined']) { - background-color: var(--wa-color-neutral-fill-quiet); - border-color: var(--wa-color-surface-border); -} - -:host([appearance='accent']) { - color: var(--wa-color-neutral-on-loud); - background-color: var(--wa-color-neutral-fill-loud); - border-color: transparent; -} - -/* Take care of top and bottom radii */ -.media, -:host(:not([with-media])) .header, -:host(:not([with-media], [with-header])) .body { - border-start-start-radius: var(--inner-border-radius); - border-start-end-radius: var(--inner-border-radius); -} - -:host(:not([with-footer])) .body, -.footer { - border-end-start-radius: var(--inner-border-radius); - border-end-end-radius: var(--inner-border-radius); -} - -.media { - display: flex; - overflow: hidden; - - &::slotted(*) { - display: block; - width: 100%; - border-radius: 0 !important; - } -} - -/* Round all corners for plain appearance */ -:host([appearance='plain']) .media { - border-radius: var(--inner-border-radius); - - &::slotted(*) { - border-radius: inherit !important; - } -} - -.header { - display: block; - border-block-end-style: inherit; - border-block-end-color: var(--wa-color-surface-border); - border-block-end-width: var(--wa-panel-border-width); - padding: calc(var(--spacing) / 2) var(--spacing); -} - -.body { - display: block; - padding: var(--spacing); -} - -.footer { - display: block; - border-block-start-style: inherit; - border-block-start-color: var(--wa-color-surface-border); - border-block-start-width: var(--wa-panel-border-width); - padding: var(--spacing); -} - -/* Push slots to sides when the action slots renders */ -.has-actions { - display: flex; - align-items: center; - justify-content: space-between; -} - -:host(:not([with-header])) .header, -:host(:not([with-footer])) .footer, -:host(:not([with-media])) .media { - display: none; -} - -/* Orientation Styles */ -:host([orientation='horizontal']) { - flex-direction: row; - - .media { - border-start-start-radius: var(--inner-border-radius); - border-end-start-radius: var(--inner-border-radius); - border-start-end-radius: 0; - - &::slotted(*) { - block-size: 100%; - inline-size: 100%; - object-fit: cover; - } - } -} - -:host([orientation='horizontal']) ::slotted([slot='body']) { - display: block; - height: 100%; - margin: 0; -} - -:host([orientation='horizontal']) ::slotted([slot='actions']) { - display: flex; - align-items: center; - padding: var(--spacing); -} diff --git a/packages/webawesome/src/components/card/card.styles.ts b/packages/webawesome/src/components/card/card.styles.ts new file mode 100644 index 000000000..3c8ed1ce0 --- /dev/null +++ b/packages/webawesome/src/components/card/card.styles.ts @@ -0,0 +1,145 @@ +import { css } from 'lit'; + +export default css` + :host { + --spacing: var(--wa-space-l); + + /* Internal calculated properties */ + --inner-border-radius: calc(var(--wa-panel-border-radius) - var(--wa-panel-border-width)); + + display: flex; + flex-direction: column; + background-color: var(--wa-color-surface-default); + border-color: var(--wa-color-surface-border); + border-radius: var(--wa-panel-border-radius); + border-style: var(--wa-panel-border-style); + box-shadow: var(--wa-shadow-s); + border-width: var(--wa-panel-border-width); + color: var(--wa-color-text-normal); + } + + /* Appearance modifiers */ + :host([appearance='plain']) { + background-color: transparent; + border-color: transparent; + box-shadow: none; + } + + :host([appearance='outlined']) { + background-color: var(--wa-color-surface-default); + border-color: var(--wa-color-surface-border); + } + + :host([appearance='filled']) { + background-color: var(--wa-color-neutral-fill-quiet); + border-color: transparent; + } + + :host([appearance='filled-outlined']) { + background-color: var(--wa-color-neutral-fill-quiet); + border-color: var(--wa-color-surface-border); + } + + :host([appearance='accent']) { + color: var(--wa-color-neutral-on-loud); + background-color: var(--wa-color-neutral-fill-loud); + border-color: transparent; + } + + /* Take care of top and bottom radii */ + .media, + :host(:not([with-media])) .header, + :host(:not([with-media], [with-header])) .body { + border-start-start-radius: var(--inner-border-radius); + border-start-end-radius: var(--inner-border-radius); + } + + :host(:not([with-footer])) .body, + .footer { + border-end-start-radius: var(--inner-border-radius); + border-end-end-radius: var(--inner-border-radius); + } + + .media { + display: flex; + overflow: hidden; + + &::slotted(*) { + display: block; + width: 100%; + border-radius: 0 !important; + } + } + + /* Round all corners for plain appearance */ + :host([appearance='plain']) .media { + border-radius: var(--inner-border-radius); + + &::slotted(*) { + border-radius: inherit !important; + } + } + + .header { + display: block; + border-block-end-style: inherit; + border-block-end-color: var(--wa-color-surface-border); + border-block-end-width: var(--wa-panel-border-width); + padding: calc(var(--spacing) / 2) var(--spacing); + } + + .body { + display: block; + padding: var(--spacing); + } + + .footer { + display: block; + border-block-start-style: inherit; + border-block-start-color: var(--wa-color-surface-border); + border-block-start-width: var(--wa-panel-border-width); + padding: var(--spacing); + } + + /* Push slots to sides when the action slots renders */ + .has-actions { + display: flex; + align-items: center; + justify-content: space-between; + } + + :host(:not([with-header])) .header, + :host(:not([with-footer])) .footer, + :host(:not([with-media])) .media { + display: none; + } + + /* Orientation Styles */ + :host([orientation='horizontal']) { + flex-direction: row; + + .media { + border-start-start-radius: var(--inner-border-radius); + border-end-start-radius: var(--inner-border-radius); + border-start-end-radius: 0; + + &::slotted(*) { + block-size: 100%; + inline-size: 100%; + object-fit: cover; + } + } + } + + :host([orientation='horizontal']) ::slotted([slot='body']) { + display: block; + height: 100%; + margin: 0; + } + + :host([orientation='horizontal']) ::slotted([slot='actions']) { + display: flex; + align-items: center; + padding: var(--spacing); + } +`; diff --git a/packages/webawesome/src/components/card/card.ts b/packages/webawesome/src/components/card/card.ts index 58c072852..b4e0ce8e5 100644 --- a/packages/webawesome/src/components/card/card.ts +++ b/packages/webawesome/src/components/card/card.ts @@ -2,8 +2,8 @@ import { html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { HasSlotController } from '../../internal/slot.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import sizeStyles from '../../styles/utilities/size.css'; -import styles from './card.css'; +import sizeStyles from '../../styles/component/size.styles.js'; +import styles from './card.styles.js'; /** * @summary Cards can be used to group related subjects in a container. diff --git a/packages/webawesome/src/components/carousel-item/carousel-item.css b/packages/webawesome/src/components/carousel-item/carousel-item.css deleted file mode 100644 index abc65e317..000000000 --- a/packages/webawesome/src/components/carousel-item/carousel-item.css +++ /dev/null @@ -1,19 +0,0 @@ -:host { - --aspect-ratio: inherit; - - display: flex; - align-items: center; - justify-content: center; - flex-direction: column; - width: 100%; - max-height: 100%; - aspect-ratio: var(--aspect-ratio); - scroll-snap-align: start; - scroll-snap-stop: always; -} - -::slotted(img) { - width: 100% !important; - height: 100% !important; - object-fit: cover; -} diff --git a/packages/webawesome/src/components/carousel-item/carousel-item.styles.ts b/packages/webawesome/src/components/carousel-item/carousel-item.styles.ts new file mode 100644 index 000000000..11e07af3c --- /dev/null +++ b/packages/webawesome/src/components/carousel-item/carousel-item.styles.ts @@ -0,0 +1,23 @@ +import { css } from 'lit'; + +export default css` + :host { + --aspect-ratio: inherit; + + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + width: 100%; + max-height: 100%; + aspect-ratio: var(--aspect-ratio); + scroll-snap-align: start; + scroll-snap-stop: always; + } + + ::slotted(img) { + width: 100% !important; + height: 100% !important; + object-fit: cover; + } +`; diff --git a/packages/webawesome/src/components/carousel-item/carousel-item.ts b/packages/webawesome/src/components/carousel-item/carousel-item.ts index a655b317c..8b80fd536 100644 --- a/packages/webawesome/src/components/carousel-item/carousel-item.ts +++ b/packages/webawesome/src/components/carousel-item/carousel-item.ts @@ -1,7 +1,7 @@ import { html } from 'lit'; import { customElement } from 'lit/decorators.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import styles from './carousel-item.css'; +import styles from './carousel-item.styles.js'; /** * @summary A carousel item represent a slide within a carousel. diff --git a/packages/webawesome/src/components/carousel/carousel.css b/packages/webawesome/src/components/carousel/carousel.css deleted file mode 100644 index 004d30a0a..000000000 --- a/packages/webawesome/src/components/carousel/carousel.css +++ /dev/null @@ -1,154 +0,0 @@ -:host { - --aspect-ratio: 16 / 9; - --scroll-hint: 0px; - --slide-gap: var(--wa-space-m, 1rem); /* fallback value is necessary */ - - display: flex; -} - -.carousel { - display: grid; - grid-template-columns: min-content 1fr min-content; - grid-template-rows: 1fr min-content; - grid-template-areas: - '. slides .' - '. pagination .'; - gap: var(--wa-space-m); - align-items: center; - min-height: 100%; - min-width: 100%; - position: relative; -} - -.pagination { - grid-area: pagination; - display: flex; - flex-wrap: wrap; - justify-content: center; - gap: var(--wa-space-s); -} - -.slides { - grid-area: slides; - - display: grid; - height: 100%; - width: 100%; - align-items: center; - justify-items: center; - overflow: auto; - overscroll-behavior-x: contain; - scrollbar-width: none; - aspect-ratio: calc(var(--aspect-ratio) * var(--slides-per-page)); - border-radius: var(--wa-border-radius-m); - - --slide-size: calc((100% - (var(--slides-per-page) - 1) * var(--slide-gap)) / var(--slides-per-page)); -} - -@media (prefers-reduced-motion) { - :where(.slides) { - scroll-behavior: auto; - } -} - -.slides-horizontal { - grid-auto-flow: column; - grid-auto-columns: var(--slide-size); - grid-auto-rows: 100%; - column-gap: var(--slide-gap); - scroll-snap-type: x mandatory; - scroll-padding-inline: var(--scroll-hint); - padding-inline: var(--scroll-hint); - overflow-y: hidden; -} - -.slides-vertical { - grid-auto-flow: row; - grid-auto-columns: 100%; - grid-auto-rows: var(--slide-size); - row-gap: var(--slide-gap); - scroll-snap-type: y mandatory; - scroll-padding-block: var(--scroll-hint); - padding-block: var(--scroll-hint); - overflow-x: hidden; -} - -.slides-dragging, -.slides-dropping { - scroll-snap-type: unset; -} - -:host([vertical]) ::slotted(wa-carousel-item) { - height: 100%; -} - -.slides::-webkit-scrollbar { - display: none; -} - -.navigation { - grid-area: navigation; - display: contents; - font-size: var(--wa-font-size-l); -} - -.navigation-button { - flex: 0 0 auto; - display: flex; - align-items: center; - background: none; - border: none; - border-radius: var(--wa-border-radius-m); - font-size: inherit; - color: var(--wa-color-text-quiet); - padding: var(--wa-space-xs); - cursor: pointer; - transition: var(--wa-transition-normal) color; - appearance: none; -} - -.navigation-button-disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.navigation-button-disabled::part(base) { - pointer-events: none; -} - -.navigation-button-previous { - grid-column: 1; - grid-row: 1; -} - -.navigation-button-next { - grid-column: 3; - grid-row: 1; -} - -.pagination-item { - display: block; - cursor: pointer; - background: none; - border: 0; - border-radius: var(--wa-border-radius-circle); - width: var(--wa-space-s); - height: var(--wa-space-s); - background-color: var(--wa-color-neutral-fill-normal); - padding: 0; - margin: 0; - transition: transform var(--wa-transition-slow); -} - -.pagination-item-active { - background-color: var(--wa-form-control-activated-color); - transform: scale(1.25); -} - -/* Focus styles */ -.slides:focus-visible, -.navigation-button:focus-visible, -.pagination-item:focus-visible { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); -} diff --git a/packages/webawesome/src/components/carousel/carousel.styles.ts b/packages/webawesome/src/components/carousel/carousel.styles.ts new file mode 100644 index 000000000..7882b603c --- /dev/null +++ b/packages/webawesome/src/components/carousel/carousel.styles.ts @@ -0,0 +1,158 @@ +import { css } from 'lit'; + +export default css` + :host { + --aspect-ratio: 16 / 9; + --scroll-hint: 0px; + --slide-gap: var(--wa-space-m, 1rem); /* fallback value is necessary */ + + display: flex; + } + + .carousel { + display: grid; + grid-template-columns: min-content 1fr min-content; + grid-template-rows: 1fr min-content; + grid-template-areas: + '. slides .' + '. pagination .'; + gap: var(--wa-space-m); + align-items: center; + min-height: 100%; + min-width: 100%; + position: relative; + } + + .pagination { + grid-area: pagination; + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: var(--wa-space-s); + } + + .slides { + grid-area: slides; + + display: grid; + height: 100%; + width: 100%; + align-items: center; + justify-items: center; + overflow: auto; + overscroll-behavior-x: contain; + scrollbar-width: none; + aspect-ratio: calc(var(--aspect-ratio) * var(--slides-per-page)); + border-radius: var(--wa-border-radius-m); + + --slide-size: calc((100% - (var(--slides-per-page) - 1) * var(--slide-gap)) / var(--slides-per-page)); + } + + @media (prefers-reduced-motion) { + :where(.slides) { + scroll-behavior: auto; + } + } + + .slides-horizontal { + grid-auto-flow: column; + grid-auto-columns: var(--slide-size); + grid-auto-rows: 100%; + column-gap: var(--slide-gap); + scroll-snap-type: x mandatory; + scroll-padding-inline: var(--scroll-hint); + padding-inline: var(--scroll-hint); + overflow-y: hidden; + } + + .slides-vertical { + grid-auto-flow: row; + grid-auto-columns: 100%; + grid-auto-rows: var(--slide-size); + row-gap: var(--slide-gap); + scroll-snap-type: y mandatory; + scroll-padding-block: var(--scroll-hint); + padding-block: var(--scroll-hint); + overflow-x: hidden; + } + + .slides-dragging, + .slides-dropping { + scroll-snap-type: unset; + } + + :host([vertical]) ::slotted(wa-carousel-item) { + height: 100%; + } + + .slides::-webkit-scrollbar { + display: none; + } + + .navigation { + grid-area: navigation; + display: contents; + font-size: var(--wa-font-size-l); + } + + .navigation-button { + flex: 0 0 auto; + display: flex; + align-items: center; + background: none; + border: none; + border-radius: var(--wa-border-radius-m); + font-size: inherit; + color: var(--wa-color-text-quiet); + padding: var(--wa-space-xs); + cursor: pointer; + transition: var(--wa-transition-normal) color; + appearance: none; + } + + .navigation-button-disabled { + opacity: 0.5; + cursor: not-allowed; + } + + .navigation-button-disabled::part(base) { + pointer-events: none; + } + + .navigation-button-previous { + grid-column: 1; + grid-row: 1; + } + + .navigation-button-next { + grid-column: 3; + grid-row: 1; + } + + .pagination-item { + display: block; + cursor: pointer; + background: none; + border: 0; + border-radius: var(--wa-border-radius-circle); + width: var(--wa-space-s); + height: var(--wa-space-s); + background-color: var(--wa-color-neutral-fill-normal); + padding: 0; + margin: 0; + transition: transform var(--wa-transition-slow); + } + + .pagination-item-active { + background-color: var(--wa-form-control-activated-color); + transform: scale(1.25); + } + + /* Focus styles */ + .slides:focus-visible, + .navigation-button:focus-visible, + .pagination-item:focus-visible { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } +`; diff --git a/packages/webawesome/src/components/carousel/carousel.ts b/packages/webawesome/src/components/carousel/carousel.ts index a3f369dc7..b8353dd95 100644 --- a/packages/webawesome/src/components/carousel/carousel.ts +++ b/packages/webawesome/src/components/carousel/carousel.ts @@ -16,7 +16,7 @@ import { LocalizeController } from '../../utilities/localize.js'; import type WaCarouselItem from '../carousel-item/carousel-item.js'; import '../icon/icon.js'; import { AutoplayController } from './autoplay-controller.js'; -import styles from './carousel.css'; +import styles from './carousel.styles.js'; /** * @summary Carousels display an arbitrary number of content slides along a horizontal or vertical axis. diff --git a/packages/webawesome/src/components/checkbox/checkbox.css b/packages/webawesome/src/components/checkbox/checkbox.css deleted file mode 100644 index ae45abbd6..000000000 --- a/packages/webawesome/src/components/checkbox/checkbox.css +++ /dev/null @@ -1,100 +0,0 @@ -:host { - --checked-icon-color: var(--wa-color-brand-on-loud); - --checked-icon-scale: 0.8; - - display: inline-flex; - color: var(--wa-form-control-value-color); - font-family: inherit; - font-weight: var(--wa-form-control-value-font-weight); - line-height: var(--wa-form-control-value-line-height); - user-select: none; - -webkit-user-select: none; -} - -[part~='control'] { - display: inline-flex; - flex: 0 0 auto; - position: relative; - align-items: center; - justify-content: center; - width: var(--wa-form-control-toggle-size); - height: var(--wa-form-control-toggle-size); - border-color: var(--wa-form-control-border-color); - border-radius: min( - calc(var(--wa-form-control-toggle-size) * 0.375), - var(--wa-border-radius-s) - ); /* min prevents entirely circular checkbox */ - border-style: var(--wa-border-style); - border-width: var(--wa-form-control-border-width); - background-color: var(--wa-form-control-background-color); - transition: - background var(--wa-transition-normal), - border-color var(--wa-transition-fast), - box-shadow var(--wa-transition-fast), - color var(--wa-transition-fast); - transition-timing-function: var(--wa-transition-easing); - - margin-inline-end: 0.5em; -} - -[part~='base'] { - display: flex; - align-items: flex-start; - position: relative; - color: currentColor; - vertical-align: middle; - cursor: pointer; -} - -[part~='label'] { - display: inline; -} - -/* Checked */ -[part~='control']:has(:checked, :indeterminate) { - color: var(--checked-icon-color); - border-color: var(--wa-form-control-activated-color); - background-color: var(--wa-form-control-activated-color); -} - -/* Focus */ -[part~='control']:has(> input:focus-visible:not(:disabled)) { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); -} - -/* Disabled */ -:host [part~='base']:has(input:disabled) { - opacity: 0.5; - cursor: not-allowed; -} - -input { - position: absolute; - padding: 0; - margin: 0; - height: 100%; - width: 100%; - opacity: 0; - pointer-events: none; -} - -[part~='icon'] { - display: flex; - scale: var(--checked-icon-scale); - - /* Without this, Safari renders the icon slightly to the left */ - &::part(svg) { - translate: 0.0009765625em; - } - - input:not(:checked, :indeterminate) + & { - visibility: hidden; - } -} - -:host([required]) [part~='label']::after { - content: var(--wa-form-control-required-content); - color: var(--wa-form-control-required-content-color); - margin-inline-start: var(--wa-form-control-required-content-offset); -} diff --git a/packages/webawesome/src/components/checkbox/checkbox.styles.ts b/packages/webawesome/src/components/checkbox/checkbox.styles.ts new file mode 100644 index 000000000..77fe0c11b --- /dev/null +++ b/packages/webawesome/src/components/checkbox/checkbox.styles.ts @@ -0,0 +1,104 @@ +import { css } from 'lit'; + +export default css` + :host { + --checked-icon-color: var(--wa-color-brand-on-loud); + --checked-icon-scale: 0.8; + + display: inline-flex; + color: var(--wa-form-control-value-color); + font-family: inherit; + font-weight: var(--wa-form-control-value-font-weight); + line-height: var(--wa-form-control-value-line-height); + user-select: none; + -webkit-user-select: none; + } + + [part~='control'] { + display: inline-flex; + flex: 0 0 auto; + position: relative; + align-items: center; + justify-content: center; + width: var(--wa-form-control-toggle-size); + height: var(--wa-form-control-toggle-size); + border-color: var(--wa-form-control-border-color); + border-radius: min( + calc(var(--wa-form-control-toggle-size) * 0.375), + var(--wa-border-radius-s) + ); /* min prevents entirely circular checkbox */ + border-style: var(--wa-border-style); + border-width: var(--wa-form-control-border-width); + background-color: var(--wa-form-control-background-color); + transition: + background var(--wa-transition-normal), + border-color var(--wa-transition-fast), + box-shadow var(--wa-transition-fast), + color var(--wa-transition-fast); + transition-timing-function: var(--wa-transition-easing); + + margin-inline-end: 0.5em; + } + + [part~='base'] { + display: flex; + align-items: flex-start; + position: relative; + color: currentColor; + vertical-align: middle; + cursor: pointer; + } + + [part~='label'] { + display: inline; + } + + /* Checked */ + [part~='control']:has(:checked, :indeterminate) { + color: var(--checked-icon-color); + border-color: var(--wa-form-control-activated-color); + background-color: var(--wa-form-control-activated-color); + } + + /* Focus */ + [part~='control']:has(> input:focus-visible:not(:disabled)) { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + + /* Disabled */ + :host [part~='base']:has(input:disabled) { + opacity: 0.5; + cursor: not-allowed; + } + + input { + position: absolute; + padding: 0; + margin: 0; + height: 100%; + width: 100%; + opacity: 0; + pointer-events: none; + } + + [part~='icon'] { + display: flex; + scale: var(--checked-icon-scale); + + /* Without this, Safari renders the icon slightly to the left */ + &::part(svg) { + translate: 0.0009765625em; + } + + input:not(:checked, :indeterminate) + & { + visibility: hidden; + } + } + + :host([required]) [part~='label']::after { + content: var(--wa-form-control-required-content); + color: var(--wa-form-control-required-content-color); + margin-inline-start: var(--wa-form-control-required-content-offset); + } +`; diff --git a/packages/webawesome/src/components/checkbox/checkbox.ts b/packages/webawesome/src/components/checkbox/checkbox.ts index 1f1decf62..444472b1d 100644 --- a/packages/webawesome/src/components/checkbox/checkbox.ts +++ b/packages/webawesome/src/components/checkbox/checkbox.ts @@ -8,10 +8,10 @@ import { HasSlotController } from '../../internal/slot.js'; import { RequiredValidator } from '../../internal/validators/required-validator.js'; import { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js'; -import formControlStyles from '../../styles/component/form-control.css'; -import sizeStyles from '../../styles/utilities/size.css'; +import formControlStyles from '../../styles/component/form-control.styles.js'; +import sizeStyles from '../../styles/component/size.styles.js'; import '../icon/icon.js'; -import styles from './checkbox.css'; +import styles from './checkbox.styles.js'; /** * @summary Checkboxes allow the user to toggle an option on or off. diff --git a/packages/webawesome/src/components/color-picker/color-picker.css b/packages/webawesome/src/components/color-picker/color-picker.css deleted file mode 100644 index f9cd5283e..000000000 --- a/packages/webawesome/src/components/color-picker/color-picker.css +++ /dev/null @@ -1,341 +0,0 @@ -:host { - --grid-width: 17em; - --grid-height: 12em; - --grid-handle-size: 1.25em; - --slider-height: 1em; - --slider-handle-size: calc(var(--slider-height) + 0.25em); -} - -.color-picker { - background-color: var(--wa-color-surface-raised); - border-radius: var(--wa-border-radius-m); - border-style: var(--wa-border-style); - border-width: var(--wa-border-width-s); - border-color: var(--wa-color-surface-border); - box-shadow: var(--wa-shadow-m); - color: var(--color); - font: inherit; - font-size: inherit; - user-select: none; - width: var(--grid-width); - -webkit-user-select: none; -} - -.grid { - position: relative; - height: var(--grid-height); - background-image: - linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 100%), - linear-gradient(to right, #fff 0%, rgba(255, 255, 255, 0) 100%); - border-top-left-radius: calc(var(--wa-border-radius-m) - var(--wa-border-width-s)); - border-top-right-radius: calc(var(--wa-border-radius-m) - var(--wa-border-width-s)); - cursor: crosshair; - forced-color-adjust: none; -} - -.grid-handle { - position: absolute; - width: var(--grid-handle-size); - height: var(--grid-handle-size); - border-radius: var(--wa-border-radius-circle); - box-shadow: 0 0 0 0.0625rem rgba(0, 0, 0, 0.2); - border: solid 0.125rem white; - margin-top: calc(var(--grid-handle-size) / -2); - margin-left: calc(var(--grid-handle-size) / -2); - transition: scale var(--wa-transition-normal) var(--wa-transition-easing); -} - -.grid-handle-dragging { - cursor: none; - scale: 1.5; -} - -.grid-handle:focus-visible { - outline: var(--wa-focus-ring); -} - -.controls { - padding: 0.75em; - display: flex; - align-items: center; -} - -.sliders { - flex: 1 1 auto; -} - -.slider { - position: relative; - height: var(--slider-height); - border-radius: var(--wa-border-radius-s); - box-shadow: inset 0 0 0 0.0625rem rgba(0, 0, 0, 0.2); - forced-color-adjust: none; -} - -.slider:not(:last-of-type) { - margin-bottom: 0.75em; -} - -.slider-handle { - position: absolute; - top: calc(50% - var(--slider-handle-size) / 2); - width: var(--slider-handle-size); - height: var(--slider-handle-size); - border-radius: var(--wa-border-radius-circle); - border: solid 0.125rem white; - box-shadow: 0 0 0 0.0625rem rgba(0, 0, 0, 0.2); - margin-left: calc(var(--slider-handle-size) / -2); -} - -.slider-handle:focus-visible { - outline: var(--wa-focus-ring); -} - -.hue { - background-image: linear-gradient( - to right, - rgb(255, 0, 0) 0%, - rgb(255, 255, 0) 17%, - rgb(0, 255, 0) 33%, - rgb(0, 255, 255) 50%, - rgb(0, 0, 255) 67%, - rgb(255, 0, 255) 83%, - rgb(255, 0, 0) 100% - ); -} - -.alpha .alpha-gradient { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border-radius: inherit; -} - -.preview { - flex: 0 0 auto; - display: inline-flex; - align-items: center; - justify-content: center; - position: relative; - width: 3em; - height: 3em; - border: none; - border-radius: var(--wa-border-radius-circle); - background: none; - font-size: inherit; - margin-inline-start: 0.75em; - cursor: copy; - forced-color-adjust: none; -} - -.preview:before { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border-radius: inherit; - box-shadow: inset 0 0 0 0.0625rem rgba(0, 0, 0, 0.2); - - /* We use a custom property in lieu of currentColor because of https://bugs.webkit.org/show_bug.cgi?id=216780 */ - background-color: var(--preview-color); -} - -.preview:focus-visible { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); -} - -.preview-color { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: solid 0.0625rem rgba(0, 0, 0, 0.125); -} - -.preview-color-copied { - animation: pulse 850ms; -} - -@keyframes pulse { - 0% { - box-shadow: 0 0 0 0 var(--wa-color-brand-fill-loud); - } - 70% { - box-shadow: 0 0 0 0.5rem transparent; - } - 100% { - box-shadow: 0 0 0 0 transparent; - } -} - -.user-input { - display: flex; - align-items: center; - padding: 0 0.75em 0.75em 0.75em; -} - -.user-input wa-input { - min-width: 0; /* fix input width in Safari */ - flex: 1 1 auto; - - &::part(form-control-label) { - /* Visually hidden */ - position: absolute !important; - width: 1px !important; - height: 1px !important; - clip: rect(0 0 0 0) !important; - clip-path: inset(50%) !important; - border: none !important; - overflow: hidden !important; - white-space: nowrap !important; - padding: 0 !important; - } -} - -.user-input wa-button-group { - margin-inline-start: 0.75em; - - &::part(base) { - flex-wrap: nowrap; - } -} - -.user-input wa-button:first-of-type { - min-width: 3em; - max-width: 3em; -} - -.swatches { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(min(1.5em, 100%), 1fr)); - grid-gap: 0.5em; - justify-items: center; - border-block-start: var(--wa-form-control-border-style) var(--wa-form-control-border-width) - var(--wa-color-surface-border); - padding: 0.5em; - forced-color-adjust: none; -} - -.swatch { - position: relative; - aspect-ratio: 1 / 1; - width: 100%; - border-radius: var(--wa-border-radius-s); -} - -.swatch .swatch-color { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border: solid 0.0625rem rgba(0, 0, 0, 0.125); - border-radius: inherit; - cursor: pointer; -} - -.swatch:focus-visible { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); -} - -.transparent-bg { - background-image: - linear-gradient(45deg, var(--wa-color-neutral-fill-normal) 25%, transparent 25%), - linear-gradient(45deg, transparent 75%, var(--wa-color-neutral-fill-normal) 75%), - linear-gradient(45deg, transparent 75%, var(--wa-color-neutral-fill-normal) 75%), - linear-gradient(45deg, var(--wa-color-neutral-fill-normal) 25%, transparent 25%); - background-size: 0.5rem 0.5rem; - background-position: - 0 0, - 0 0, - -0.25rem -0.25rem, - 0.25rem 0.25rem; -} - -:host([disabled]) { - opacity: 0.5; - cursor: not-allowed; - - .grid, - .grid-handle, - .slider, - .slider-handle, - .preview, - .swatch, - .swatch-color { - pointer-events: none; - } -} - -/* - * Color dropdown - */ - -.color-dropdown { - display: contents; -} - -.color-dropdown::part(panel) { - max-height: none; - background-color: var(--wa-color-surface-raised); - border: var(--wa-border-style) var(--wa-border-width-s) var(--wa-color-surface-border); - border-radius: var(--wa-border-radius-m); - overflow: visible; -} - -.trigger { - display: block; - position: relative; - background-color: transparent; - border: none; - cursor: pointer; - font-size: inherit; - forced-color-adjust: none; - width: var(--wa-form-control-height); - height: var(--wa-form-control-height); - border-radius: var(--wa-form-control-border-radius); -} - -.trigger:before { - content: ''; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - border-radius: inherit; - background-color: currentColor; - box-shadow: - inset 0 0 0 var(--wa-form-control-border-width) var(--wa-form-control-border-color), - inset 0 0 0 calc(var(--wa-form-control-border-width) * 3) var(--wa-color-surface-default); -} - -.trigger-empty:before { - background-color: transparent; -} - -.trigger:focus-visible { - outline: none; -} - -.trigger:focus-visible:not(.trigger:disabled) { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); -} - -:host([disabled]) :is(.label, .trigger) { - opacity: 0.5; - cursor: not-allowed; -} - -.form-control.form-control-has-label .label { - cursor: pointer; - display: inline-block; -} diff --git a/packages/webawesome/src/components/color-picker/color-picker.styles.ts b/packages/webawesome/src/components/color-picker/color-picker.styles.ts new file mode 100644 index 000000000..d2eedca15 --- /dev/null +++ b/packages/webawesome/src/components/color-picker/color-picker.styles.ts @@ -0,0 +1,345 @@ +import { css } from 'lit'; + +export default css` + :host { + --grid-width: 17em; + --grid-height: 12em; + --grid-handle-size: 1.25em; + --slider-height: 1em; + --slider-handle-size: calc(var(--slider-height) + 0.25em); + } + + .color-picker { + background-color: var(--wa-color-surface-raised); + border-radius: var(--wa-border-radius-m); + border-style: var(--wa-border-style); + border-width: var(--wa-border-width-s); + border-color: var(--wa-color-surface-border); + box-shadow: var(--wa-shadow-m); + color: var(--color); + font: inherit; + font-size: inherit; + user-select: none; + width: var(--grid-width); + -webkit-user-select: none; + } + + .grid { + position: relative; + height: var(--grid-height); + background-image: + linear-gradient(to bottom, rgba(0, 0, 0, 0) 0%, rgba(0, 0, 0, 1) 100%), + linear-gradient(to right, #fff 0%, rgba(255, 255, 255, 0) 100%); + border-top-left-radius: calc(var(--wa-border-radius-m) - var(--wa-border-width-s)); + border-top-right-radius: calc(var(--wa-border-radius-m) - var(--wa-border-width-s)); + cursor: crosshair; + forced-color-adjust: none; + } + + .grid-handle { + position: absolute; + width: var(--grid-handle-size); + height: var(--grid-handle-size); + border-radius: var(--wa-border-radius-circle); + box-shadow: 0 0 0 0.0625rem rgba(0, 0, 0, 0.2); + border: solid 0.125rem white; + margin-top: calc(var(--grid-handle-size) / -2); + margin-left: calc(var(--grid-handle-size) / -2); + transition: scale var(--wa-transition-normal) var(--wa-transition-easing); + } + + .grid-handle-dragging { + cursor: none; + scale: 1.5; + } + + .grid-handle:focus-visible { + outline: var(--wa-focus-ring); + } + + .controls { + padding: 0.75em; + display: flex; + align-items: center; + } + + .sliders { + flex: 1 1 auto; + } + + .slider { + position: relative; + height: var(--slider-height); + border-radius: var(--wa-border-radius-s); + box-shadow: inset 0 0 0 0.0625rem rgba(0, 0, 0, 0.2); + forced-color-adjust: none; + } + + .slider:not(:last-of-type) { + margin-bottom: 0.75em; + } + + .slider-handle { + position: absolute; + top: calc(50% - var(--slider-handle-size) / 2); + width: var(--slider-handle-size); + height: var(--slider-handle-size); + border-radius: var(--wa-border-radius-circle); + border: solid 0.125rem white; + box-shadow: 0 0 0 0.0625rem rgba(0, 0, 0, 0.2); + margin-left: calc(var(--slider-handle-size) / -2); + } + + .slider-handle:focus-visible { + outline: var(--wa-focus-ring); + } + + .hue { + background-image: linear-gradient( + to right, + rgb(255, 0, 0) 0%, + rgb(255, 255, 0) 17%, + rgb(0, 255, 0) 33%, + rgb(0, 255, 255) 50%, + rgb(0, 0, 255) 67%, + rgb(255, 0, 255) 83%, + rgb(255, 0, 0) 100% + ); + } + + .alpha .alpha-gradient { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: inherit; + } + + .preview { + flex: 0 0 auto; + display: inline-flex; + align-items: center; + justify-content: center; + position: relative; + width: 3em; + height: 3em; + border: none; + border-radius: var(--wa-border-radius-circle); + background: none; + font-size: inherit; + margin-inline-start: 0.75em; + cursor: copy; + forced-color-adjust: none; + } + + .preview:before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: inherit; + box-shadow: inset 0 0 0 0.0625rem rgba(0, 0, 0, 0.2); + + /* We use a custom property in lieu of currentColor because of https://bugs.webkit.org/show_bug.cgi?id=216780 */ + background-color: var(--preview-color); + } + + .preview:focus-visible { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + + .preview-color { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: solid 0.0625rem rgba(0, 0, 0, 0.125); + } + + .preview-color-copied { + animation: pulse 850ms; + } + + @keyframes pulse { + 0% { + box-shadow: 0 0 0 0 var(--wa-color-brand-fill-loud); + } + 70% { + box-shadow: 0 0 0 0.5rem transparent; + } + 100% { + box-shadow: 0 0 0 0 transparent; + } + } + + .user-input { + display: flex; + align-items: center; + padding: 0 0.75em 0.75em 0.75em; + } + + .user-input wa-input { + min-width: 0; /* fix input width in Safari */ + flex: 1 1 auto; + + &::part(form-control-label) { + /* Visually hidden */ + position: absolute !important; + width: 1px !important; + height: 1px !important; + clip: rect(0 0 0 0) !important; + clip-path: inset(50%) !important; + border: none !important; + overflow: hidden !important; + white-space: nowrap !important; + padding: 0 !important; + } + } + + .user-input wa-button-group { + margin-inline-start: 0.75em; + + &::part(base) { + flex-wrap: nowrap; + } + } + + .user-input wa-button:first-of-type { + min-width: 3em; + max-width: 3em; + } + + .swatches { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(min(1.5em, 100%), 1fr)); + grid-gap: 0.5em; + justify-items: center; + border-block-start: var(--wa-form-control-border-style) var(--wa-form-control-border-width) + var(--wa-color-surface-border); + padding: 0.5em; + forced-color-adjust: none; + } + + .swatch { + position: relative; + aspect-ratio: 1 / 1; + width: 100%; + border-radius: var(--wa-border-radius-s); + } + + .swatch .swatch-color { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border: solid 0.0625rem rgba(0, 0, 0, 0.125); + border-radius: inherit; + cursor: pointer; + } + + .swatch:focus-visible { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + + .transparent-bg { + background-image: + linear-gradient(45deg, var(--wa-color-neutral-fill-normal) 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, var(--wa-color-neutral-fill-normal) 75%), + linear-gradient(45deg, transparent 75%, var(--wa-color-neutral-fill-normal) 75%), + linear-gradient(45deg, var(--wa-color-neutral-fill-normal) 25%, transparent 25%); + background-size: 0.5rem 0.5rem; + background-position: + 0 0, + 0 0, + -0.25rem -0.25rem, + 0.25rem 0.25rem; + } + + :host([disabled]) { + opacity: 0.5; + cursor: not-allowed; + + .grid, + .grid-handle, + .slider, + .slider-handle, + .preview, + .swatch, + .swatch-color { + pointer-events: none; + } + } + + /* + * Color dropdown + */ + + .color-dropdown { + display: contents; + } + + .color-dropdown::part(panel) { + max-height: none; + background-color: var(--wa-color-surface-raised); + border: var(--wa-border-style) var(--wa-border-width-s) var(--wa-color-surface-border); + border-radius: var(--wa-border-radius-m); + overflow: visible; + } + + .trigger { + display: block; + position: relative; + background-color: transparent; + border: none; + cursor: pointer; + font-size: inherit; + forced-color-adjust: none; + width: var(--wa-form-control-height); + height: var(--wa-form-control-height); + border-radius: var(--wa-form-control-border-radius); + } + + .trigger:before { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + border-radius: inherit; + background-color: currentColor; + box-shadow: + inset 0 0 0 var(--wa-form-control-border-width) var(--wa-form-control-border-color), + inset 0 0 0 calc(var(--wa-form-control-border-width) * 3) var(--wa-color-surface-default); + } + + .trigger-empty:before { + background-color: transparent; + } + + .trigger:focus-visible { + outline: none; + } + + .trigger:focus-visible:not(.trigger:disabled) { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + + :host([disabled]) :is(.label, .trigger) { + opacity: 0.5; + cursor: not-allowed; + } + + .form-control.form-control-has-label .label { + cursor: pointer; + display: inline-block; + } +`; diff --git a/packages/webawesome/src/components/color-picker/color-picker.ts b/packages/webawesome/src/components/color-picker/color-picker.ts index 25b94b9ec..c039f5034 100644 --- a/packages/webawesome/src/components/color-picker/color-picker.ts +++ b/packages/webawesome/src/components/color-picker/color-picker.ts @@ -14,9 +14,9 @@ import { HasSlotController } from '../../internal/slot.js'; import { RequiredValidator } from '../../internal/validators/required-validator.js'; import { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js'; -import formControlStyles from '../../styles/component/form-control.css'; -import sizeStyles from '../../styles/utilities/size.css'; -import visuallyHidden from '../../styles/utilities/visually-hidden.css'; +import formControlStyles from '../../styles/component/form-control.styles.js'; +import sizeStyles from '../../styles/component/size.styles.js'; +import visuallyHidden from '../../styles/component/visually-hidden.styles.js'; import { LocalizeController } from '../../utilities/localize.js'; import '../button-group/button-group.js'; import '../button/button.js'; @@ -25,7 +25,7 @@ import '../input/input.js'; import type WaInput from '../input/input.js'; import '../popup/popup.js'; import type WaPopup from '../popup/popup.js'; -import styles from './color-picker.css'; +import styles from './color-picker.styles.js'; interface EyeDropperConstructor { new (): EyeDropperInterface; diff --git a/packages/webawesome/src/components/comparison/comparison.css b/packages/webawesome/src/components/comparison/comparison.css deleted file mode 100644 index 139cb3dd9..000000000 --- a/packages/webawesome/src/components/comparison/comparison.css +++ /dev/null @@ -1,76 +0,0 @@ -:host { - --divider-width: 0.125rem; - --handle-size: 2.5rem; - - display: block; - position: relative; - max-width: 100%; - max-height: 100%; - overflow: hidden; -} - -.before, -.after { - display: block; - - &::slotted(img), - &::slotted(svg) { - display: block; - max-width: 100% !important; - height: auto; - } - - &::slotted(:not(img, svg)) { - isolation: isolate; - } -} - -.after { - position: absolute; - top: 0; - left: 0; - height: 100%; - width: 100%; -} - -/* Disable pointer-events while dragging. This is especially important for iframes. */ -:host(:state(dragging)) { - .before, - .after { - pointer-events: none; - } -} - -.divider { - display: flex; - align-items: center; - justify-content: center; - position: absolute; - top: 0; - width: var(--divider-width); - height: 100%; - background-color: var(--wa-color-surface-default); - translate: calc(var(--divider-width) / -2); - cursor: ew-resize; -} - -.handle { - display: flex; - align-items: center; - justify-content: center; - position: absolute; - top: calc(50% - (var(--handle-size) / 2)); - width: var(--handle-size); - height: var(--handle-size); - background-color: var(--wa-color-surface-default); - border-radius: var(--wa-border-radius-circle); - font-size: calc(var(--handle-size) * 0.4); - color: var(--wa-color-neutral-on-quiet); - cursor: inherit; - z-index: 10; -} - -.handle:focus-visible { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); -} diff --git a/packages/webawesome/src/components/comparison/comparison.styles.ts b/packages/webawesome/src/components/comparison/comparison.styles.ts new file mode 100644 index 000000000..192aee4e4 --- /dev/null +++ b/packages/webawesome/src/components/comparison/comparison.styles.ts @@ -0,0 +1,80 @@ +import { css } from 'lit'; + +export default css` + :host { + --divider-width: 0.125rem; + --handle-size: 2.5rem; + + display: block; + position: relative; + max-width: 100%; + max-height: 100%; + overflow: hidden; + } + + .before, + .after { + display: block; + + &::slotted(img), + &::slotted(svg) { + display: block; + max-width: 100% !important; + height: auto; + } + + &::slotted(:not(img, svg)) { + isolation: isolate; + } + } + + .after { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + } + + /* Disable pointer-events while dragging. This is especially important for iframes. */ + :host(:state(dragging)) { + .before, + .after { + pointer-events: none; + } + } + + .divider { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + top: 0; + width: var(--divider-width); + height: 100%; + background-color: var(--wa-color-surface-default); + translate: calc(var(--divider-width) / -2); + cursor: ew-resize; + } + + .handle { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + top: calc(50% - (var(--handle-size) / 2)); + width: var(--handle-size); + height: var(--handle-size); + background-color: var(--wa-color-surface-default); + border-radius: var(--wa-border-radius-circle); + font-size: calc(var(--handle-size) * 0.4); + color: var(--wa-color-neutral-on-quiet); + cursor: inherit; + z-index: 10; + } + + .handle:focus-visible { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } +`; diff --git a/packages/webawesome/src/components/comparison/comparison.ts b/packages/webawesome/src/components/comparison/comparison.ts index 12f458fb9..8500931b3 100644 --- a/packages/webawesome/src/components/comparison/comparison.ts +++ b/packages/webawesome/src/components/comparison/comparison.ts @@ -7,7 +7,7 @@ import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import { LocalizeController } from '../../utilities/localize.js'; import '../icon/icon.js'; -import styles from './comparison.css'; +import styles from './comparison.styles.js'; /** * @summary Compare visual differences between similar content with a sliding panel. diff --git a/packages/webawesome/src/components/copy-button/copy-button.css b/packages/webawesome/src/components/copy-button/copy-button.css deleted file mode 100644 index 7d7be5126..000000000 --- a/packages/webawesome/src/components/copy-button/copy-button.css +++ /dev/null @@ -1,75 +0,0 @@ -:host { - display: inline-block; - color: var(--wa-color-neutral-on-quiet); -} - -.button { - flex: 0 0 auto; - display: flex; - align-items: center; - background-color: transparent; - border: none; - border-radius: var(--wa-form-control-border-radius); - color: inherit; - font-size: inherit; - padding: 0.5em; - cursor: pointer; - transition: color var(--wa-transition-fast) var(--wa-transition-easing); -} - -@media (hover: hover) { - .button:hover:not([disabled]) { - background-color: var(--wa-color-neutral-fill-quiet); - color: color-mix(in oklab, currentColor, var(--wa-color-mix-hover)); - } -} - -.button:focus-visible:not([disabled]) { - background-color: var(--wa-color-neutral-fill-quiet); - color: color-mix(in oklab, currentColor, var(--wa-color-mix-hover)); -} - -.button:active:not([disabled]) { - color: color-mix(in oklab, currentColor, var(--wa-color-mix-active)); -} - -slot[name='success-icon'] { - color: var(--wa-color-success-on-quiet); -} - -slot[name='error-icon'] { - color: var(--wa-color-danger-on-quiet); -} - -.button:focus-visible { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); -} - -.button[disabled] { - opacity: 0.5; - cursor: not-allowed !important; -} - -slot { - display: inline-flex; -} - -.show { - animation: show 100ms ease; -} - -.hide { - animation: show 100ms ease reverse; -} - -@keyframes show { - from { - scale: 0.25; - opacity: 0.25; - } - to { - scale: 1; - opacity: 1; - } -} diff --git a/packages/webawesome/src/components/copy-button/copy-button.styles.ts b/packages/webawesome/src/components/copy-button/copy-button.styles.ts new file mode 100644 index 000000000..97082278c --- /dev/null +++ b/packages/webawesome/src/components/copy-button/copy-button.styles.ts @@ -0,0 +1,79 @@ +import { css } from 'lit'; + +export default css` + :host { + display: inline-block; + color: var(--wa-color-neutral-on-quiet); + } + + .button { + flex: 0 0 auto; + display: flex; + align-items: center; + background-color: transparent; + border: none; + border-radius: var(--wa-form-control-border-radius); + color: inherit; + font-size: inherit; + padding: 0.5em; + cursor: pointer; + transition: color var(--wa-transition-fast) var(--wa-transition-easing); + } + + @media (hover: hover) { + .button:hover:not([disabled]) { + background-color: var(--wa-color-neutral-fill-quiet); + color: color-mix(in oklab, currentColor, var(--wa-color-mix-hover)); + } + } + + .button:focus-visible:not([disabled]) { + background-color: var(--wa-color-neutral-fill-quiet); + color: color-mix(in oklab, currentColor, var(--wa-color-mix-hover)); + } + + .button:active:not([disabled]) { + color: color-mix(in oklab, currentColor, var(--wa-color-mix-active)); + } + + slot[name='success-icon'] { + color: var(--wa-color-success-on-quiet); + } + + slot[name='error-icon'] { + color: var(--wa-color-danger-on-quiet); + } + + .button:focus-visible { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + + .button[disabled] { + opacity: 0.5; + cursor: not-allowed !important; + } + + slot { + display: inline-flex; + } + + .show { + animation: show 100ms ease; + } + + .hide { + animation: show 100ms ease reverse; + } + + @keyframes show { + from { + scale: 0.25; + opacity: 0.25; + } + to { + scale: 1; + opacity: 1; + } + } +`; diff --git a/packages/webawesome/src/components/copy-button/copy-button.ts b/packages/webawesome/src/components/copy-button/copy-button.ts index b29967fd6..533bb61c7 100644 --- a/packages/webawesome/src/components/copy-button/copy-button.ts +++ b/packages/webawesome/src/components/copy-button/copy-button.ts @@ -5,12 +5,12 @@ import { WaCopyEvent } from '../../events/copy.js'; import { WaErrorEvent } from '../../events/error.js'; import { animateWithClass } from '../../internal/animate.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import visuallyHidden from '../../styles/utilities/visually-hidden.css'; +import visuallyHidden from '../../styles/component/visually-hidden.styles.js'; import { LocalizeController } from '../../utilities/localize.js'; import '../icon/icon.js'; import '../tooltip/tooltip.js'; import type WaTooltip from '../tooltip/tooltip.js'; -import styles from './copy-button.css'; +import styles from './copy-button.styles.js'; /** * @summary Copies text data to the clipboard when the user clicks the trigger. diff --git a/packages/webawesome/src/components/details/details.css b/packages/webawesome/src/components/details/details.css deleted file mode 100644 index 858fb0a89..000000000 --- a/packages/webawesome/src/components/details/details.css +++ /dev/null @@ -1,122 +0,0 @@ -:host { - --spacing: var(--wa-space-m); - --show-duration: 200ms; - --hide-duration: 200ms; - - display: block; -} - -details { - display: block; - overflow-anchor: none; - border: var(--wa-panel-border-width) var(--wa-color-surface-border) var(--wa-panel-border-style); - background-color: var(--wa-color-surface-default); - border-radius: var(--wa-panel-border-radius); - color: var(--wa-color-text-normal); - - /* Print styles */ - @media print { - background: none; - border: solid var(--wa-border-width-s) var(--wa-color-surface-border); - - summary { - list-style: none; - } - } -} - -/* Appearance modifiers */ -:host([appearance='plain']) details { - background-color: transparent; - border-color: transparent; - border-radius: 0; -} - -:host([appearance='outlined']) details { - background-color: var(--wa-color-surface-default); - border-color: var(--wa-color-surface-border); -} - -:host([appearance='filled']) details { - background-color: var(--wa-color-neutral-fill-quiet); - border-color: transparent; -} - -:host([appearance='filled-outlined']) details { - background-color: var(--wa-color-neutral-fill-quiet); - border-color: var(--wa-color-neutral-border-quiet); -} - -:host([disabled]) details { - opacity: 0.5; - cursor: not-allowed; -} - -summary { - display: flex; - align-items: center; - justify-content: space-between; - gap: var(--spacing); - padding: var(--spacing); /* Add padding here */ - border-radius: calc(var(--wa-panel-border-radius) - var(--wa-panel-border-width)); - user-select: none; - -webkit-user-select: none; - cursor: pointer; - - &::marker, - &::-webkit-details-marker { - display: none; - } - - &:focus { - outline: none; - } - - &:focus-visible { - outline: var(--wa-focus-ring); - outline-offset: calc(var(--wa-panel-border-width) + var(--wa-focus-ring-offset)); - } -} - -:host([open]) summary { - border-end-start-radius: 0; - border-end-end-radius: 0; -} - -/* 'Start' icon placement */ -:host([icon-placement='start']) summary { - flex-direction: row-reverse; - justify-content: start; -} - -[part~='icon'] { - flex: 0 0 auto; - display: flex; - align-items: center; - color: var(--wa-color-text-quiet); - transition: rotate var(--wa-transition-normal) var(--wa-transition-easing); -} - -:host([open]) [part~='icon'] { - rotate: 90deg; -} - -:host([open]:dir(rtl)) [part~='icon'] { - rotate: -90deg; -} - -:host([open]) slot[name='expand-icon'], -:host(:not([open])) slot[name='collapse-icon'] { - display: none; -} - -.body.animating { - overflow: hidden; -} - -.content { - display: block; - padding-block-start: var(--spacing); - padding-inline: var(--spacing); /* Add horizontal padding */ - padding-block-end: var(--spacing); /* Add bottom padding */ -} diff --git a/packages/webawesome/src/components/details/details.styles.ts b/packages/webawesome/src/components/details/details.styles.ts new file mode 100644 index 000000000..70c4af31e --- /dev/null +++ b/packages/webawesome/src/components/details/details.styles.ts @@ -0,0 +1,126 @@ +import { css } from 'lit'; + +export default css` + :host { + --spacing: var(--wa-space-m); + --show-duration: 200ms; + --hide-duration: 200ms; + + display: block; + } + + details { + display: block; + overflow-anchor: none; + border: var(--wa-panel-border-width) var(--wa-color-surface-border) var(--wa-panel-border-style); + background-color: var(--wa-color-surface-default); + border-radius: var(--wa-panel-border-radius); + color: var(--wa-color-text-normal); + + /* Print styles */ + @media print { + background: none; + border: solid var(--wa-border-width-s) var(--wa-color-surface-border); + + summary { + list-style: none; + } + } + } + + /* Appearance modifiers */ + :host([appearance='plain']) details { + background-color: transparent; + border-color: transparent; + border-radius: 0; + } + + :host([appearance='outlined']) details { + background-color: var(--wa-color-surface-default); + border-color: var(--wa-color-surface-border); + } + + :host([appearance='filled']) details { + background-color: var(--wa-color-neutral-fill-quiet); + border-color: transparent; + } + + :host([appearance='filled-outlined']) details { + background-color: var(--wa-color-neutral-fill-quiet); + border-color: var(--wa-color-neutral-border-quiet); + } + + :host([disabled]) details { + opacity: 0.5; + cursor: not-allowed; + } + + summary { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--spacing); + padding: var(--spacing); /* Add padding here */ + border-radius: calc(var(--wa-panel-border-radius) - var(--wa-panel-border-width)); + user-select: none; + -webkit-user-select: none; + cursor: pointer; + + &::marker, + &::-webkit-details-marker { + display: none; + } + + &:focus { + outline: none; + } + + &:focus-visible { + outline: var(--wa-focus-ring); + outline-offset: calc(var(--wa-panel-border-width) + var(--wa-focus-ring-offset)); + } + } + + :host([open]) summary { + border-end-start-radius: 0; + border-end-end-radius: 0; + } + + /* 'Start' icon placement */ + :host([icon-placement='start']) summary { + flex-direction: row-reverse; + justify-content: start; + } + + [part~='icon'] { + flex: 0 0 auto; + display: flex; + align-items: center; + color: var(--wa-color-text-quiet); + transition: rotate var(--wa-transition-normal) var(--wa-transition-easing); + } + + :host([open]) [part~='icon'] { + rotate: 90deg; + } + + :host([open]:dir(rtl)) [part~='icon'] { + rotate: -90deg; + } + + :host([open]) slot[name='expand-icon'], + :host(:not([open])) slot[name='collapse-icon'] { + display: none; + } + + .body.animating { + overflow: hidden; + } + + .content { + display: block; + padding-block-start: var(--spacing); + padding-inline: var(--spacing); /* Add horizontal padding */ + padding-block-end: var(--spacing); /* Add bottom padding */ + } +`; diff --git a/packages/webawesome/src/components/details/details.ts b/packages/webawesome/src/components/details/details.ts index 416f746a7..42ccc8c34 100644 --- a/packages/webawesome/src/components/details/details.ts +++ b/packages/webawesome/src/components/details/details.ts @@ -13,7 +13,7 @@ import WebAwesomeElement from '../../internal/webawesome-element.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js'; import { LocalizeController } from '../../utilities/localize.js'; import '../icon/icon.js'; -import styles from './details.css'; +import styles from './details.styles.js'; /** * @summary Details show a brief summary and expand to show additional content. diff --git a/packages/webawesome/src/components/dialog/dialog.css b/packages/webawesome/src/components/dialog/dialog.css deleted file mode 100644 index cb6e4f7de..000000000 --- a/packages/webawesome/src/components/dialog/dialog.css +++ /dev/null @@ -1,183 +0,0 @@ -:host { - --width: 31rem; - --spacing: var(--wa-space-l); - --show-duration: 200ms; - --hide-duration: 200ms; - - display: none; -} - -:host([open]) { - display: block; -} - -.dialog { - display: flex; - flex-direction: column; - top: 0; - right: 0; - bottom: 0; - left: 0; - width: var(--width); - max-width: calc(100% - var(--wa-space-2xl)); - max-height: calc(100% - var(--wa-space-2xl)); - background-color: var(--wa-color-surface-raised); - border-radius: var(--wa-panel-border-radius); - border: none; - box-shadow: var(--wa-shadow-l); - padding: 0; - margin: auto; - - &.show { - animation: show-dialog var(--show-duration) ease; - - &::backdrop { - animation: show-backdrop var(--show-duration, 200ms) ease; - } - } - - &.hide { - animation: show-dialog var(--hide-duration) ease reverse; - - &::backdrop { - animation: show-backdrop var(--hide-duration, 200ms) ease reverse; - } - } - - &.pulse { - animation: pulse 250ms ease; - } -} - -.dialog:focus { - outline: none; -} - -/* Ensure there's enough vertical padding for phones that don't update vh when chrome appears (e.g. iPhone) */ -@media screen and (max-width: 420px) { - .dialog { - max-height: 80vh; - } -} - -.open { - display: flex; - opacity: 1; -} - -.header { - flex: 0 0 auto; - display: flex; - flex-wrap: nowrap; - - padding-inline-start: var(--spacing); - padding-block-end: 0; - - /* Subtract the close button's padding so that the X is visually aligned with the edges of the dialog content */ - padding-inline-end: calc(var(--spacing) - var(--wa-form-control-padding-block)); - padding-block-start: calc(var(--spacing) - var(--wa-form-control-padding-block)); -} - -.title { - align-self: center; - flex: 1 1 auto; - font-family: inherit; - font-size: var(--wa-font-size-l); - font-weight: var(--wa-font-weight-heading); - line-height: var(--wa-line-height-condensed); - margin: 0; -} - -.header-actions { - align-self: start; - display: flex; - flex-shrink: 0; - flex-wrap: wrap; - justify-content: end; - gap: var(--wa-space-2xs); - padding-inline-start: var(--spacing); -} - -.header-actions wa-button, -.header-actions ::slotted(wa-button) { - flex: 0 0 auto; - display: flex; - align-items: center; -} - -.body { - flex: 1 1 auto; - display: block; - padding: var(--spacing); - overflow: auto; - -webkit-overflow-scrolling: touch; - - &:focus { - outline: none; - } - - &:focus-visible { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); - } -} - -.footer { - flex: 0 0 auto; - display: flex; - flex-wrap: wrap; - gap: var(--wa-space-xs); - justify-content: end; - padding: var(--spacing); - padding-block-start: 0; -} - -.footer ::slotted(wa-button:not(:first-of-type)) { - margin-inline-start: var(--wa-spacing-xs); -} - -.dialog::backdrop { - /* - NOTE: the ::backdrop element doesn't inherit properly in Safari yet, but it will in 17.4! At that time, we can - remove the fallback values here. - */ - background-color: var(--wa-color-overlay-modal, rgb(0 0 0 / 0.25)); -} - -@keyframes pulse { - 0% { - scale: 1; - } - 50% { - scale: 1.02; - } - 100% { - scale: 1; - } -} - -@keyframes show-dialog { - from { - opacity: 0; - scale: 0.8; - } - to { - opacity: 1; - scale: 1; - } -} - -@keyframes show-backdrop { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -@media (forced-colors: active) { - .dialog { - border: solid 1px white; - } -} diff --git a/packages/webawesome/src/components/dialog/dialog.styles.ts b/packages/webawesome/src/components/dialog/dialog.styles.ts new file mode 100644 index 000000000..1d0b730b2 --- /dev/null +++ b/packages/webawesome/src/components/dialog/dialog.styles.ts @@ -0,0 +1,187 @@ +import { css } from 'lit'; + +export default css` + :host { + --width: 31rem; + --spacing: var(--wa-space-l); + --show-duration: 200ms; + --hide-duration: 200ms; + + display: none; + } + + :host([open]) { + display: block; + } + + .dialog { + display: flex; + flex-direction: column; + top: 0; + right: 0; + bottom: 0; + left: 0; + width: var(--width); + max-width: calc(100% - var(--wa-space-2xl)); + max-height: calc(100% - var(--wa-space-2xl)); + background-color: var(--wa-color-surface-raised); + border-radius: var(--wa-panel-border-radius); + border: none; + box-shadow: var(--wa-shadow-l); + padding: 0; + margin: auto; + + &.show { + animation: show-dialog var(--show-duration) ease; + + &::backdrop { + animation: show-backdrop var(--show-duration, 200ms) ease; + } + } + + &.hide { + animation: show-dialog var(--hide-duration) ease reverse; + + &::backdrop { + animation: show-backdrop var(--hide-duration, 200ms) ease reverse; + } + } + + &.pulse { + animation: pulse 250ms ease; + } + } + + .dialog:focus { + outline: none; + } + + /* Ensure there's enough vertical padding for phones that don't update vh when chrome appears (e.g. iPhone) */ + @media screen and (max-width: 420px) { + .dialog { + max-height: 80vh; + } + } + + .open { + display: flex; + opacity: 1; + } + + .header { + flex: 0 0 auto; + display: flex; + flex-wrap: nowrap; + + padding-inline-start: var(--spacing); + padding-block-end: 0; + + /* Subtract the close button's padding so that the X is visually aligned with the edges of the dialog content */ + padding-inline-end: calc(var(--spacing) - var(--wa-form-control-padding-block)); + padding-block-start: calc(var(--spacing) - var(--wa-form-control-padding-block)); + } + + .title { + align-self: center; + flex: 1 1 auto; + font-family: inherit; + font-size: var(--wa-font-size-l); + font-weight: var(--wa-font-weight-heading); + line-height: var(--wa-line-height-condensed); + margin: 0; + } + + .header-actions { + align-self: start; + display: flex; + flex-shrink: 0; + flex-wrap: wrap; + justify-content: end; + gap: var(--wa-space-2xs); + padding-inline-start: var(--spacing); + } + + .header-actions wa-button, + .header-actions ::slotted(wa-button) { + flex: 0 0 auto; + display: flex; + align-items: center; + } + + .body { + flex: 1 1 auto; + display: block; + padding: var(--spacing); + overflow: auto; + -webkit-overflow-scrolling: touch; + + &:focus { + outline: none; + } + + &:focus-visible { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + } + + .footer { + flex: 0 0 auto; + display: flex; + flex-wrap: wrap; + gap: var(--wa-space-xs); + justify-content: end; + padding: var(--spacing); + padding-block-start: 0; + } + + .footer ::slotted(wa-button:not(:first-of-type)) { + margin-inline-start: var(--wa-spacing-xs); + } + + .dialog::backdrop { + /* + NOTE: the ::backdrop element doesn't inherit properly in Safari yet, but it will in 17.4! At that time, we can + remove the fallback values here. + */ + background-color: var(--wa-color-overlay-modal, rgb(0 0 0 / 0.25)); + } + + @keyframes pulse { + 0% { + scale: 1; + } + 50% { + scale: 1.02; + } + 100% { + scale: 1; + } + } + + @keyframes show-dialog { + from { + opacity: 0; + scale: 0.8; + } + to { + opacity: 1; + scale: 1; + } + } + + @keyframes show-backdrop { + from { + opacity: 0; + } + to { + opacity: 1; + } + } + + @media (forced-colors: active) { + .dialog { + border: solid 1px white; + } + } +`; diff --git a/packages/webawesome/src/components/dialog/dialog.ts b/packages/webawesome/src/components/dialog/dialog.ts index 4d0259a99..95993da1c 100644 --- a/packages/webawesome/src/components/dialog/dialog.ts +++ b/packages/webawesome/src/components/dialog/dialog.ts @@ -13,7 +13,7 @@ import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import { LocalizeController } from '../../utilities/localize.js'; import '../button/button.js'; -import styles from './dialog.css'; +import styles from './dialog.styles.js'; /** * @summary Dialogs, sometimes called "modals", appear above the page and require the user's immediate attention. diff --git a/packages/webawesome/src/components/divider/divider.css b/packages/webawesome/src/components/divider/divider.css deleted file mode 100644 index 8eeb3a599..000000000 --- a/packages/webawesome/src/components/divider/divider.css +++ /dev/null @@ -1,19 +0,0 @@ -:host { - --color: var(--wa-color-surface-border); - --width: var(--wa-border-width-s); - --spacing: var(--wa-space-m); -} - -:host(:not([orientation='vertical'])) { - display: block; - border-top: solid var(--width) var(--color); - margin: var(--spacing) 0; -} - -:host([orientation='vertical']) { - display: inline-block; - height: 100%; - border-inline-start: solid var(--width) var(--color); - margin: 0 var(--spacing); - min-block-size: 1lh; -} diff --git a/packages/webawesome/src/components/divider/divider.styles.ts b/packages/webawesome/src/components/divider/divider.styles.ts new file mode 100644 index 000000000..0f4d34a3b --- /dev/null +++ b/packages/webawesome/src/components/divider/divider.styles.ts @@ -0,0 +1,23 @@ +import { css } from 'lit'; + +export default css` + :host { + --color: var(--wa-color-surface-border); + --width: var(--wa-border-width-s); + --spacing: var(--wa-space-m); + } + + :host(:not([orientation='vertical'])) { + display: block; + border-top: solid var(--width) var(--color); + margin: var(--spacing) 0; + } + + :host([orientation='vertical']) { + display: inline-block; + height: 100%; + border-inline-start: solid var(--width) var(--color); + margin: 0 var(--spacing); + min-block-size: 1lh; + } +`; diff --git a/packages/webawesome/src/components/divider/divider.ts b/packages/webawesome/src/components/divider/divider.ts index 7d24696ac..30c181a58 100644 --- a/packages/webawesome/src/components/divider/divider.ts +++ b/packages/webawesome/src/components/divider/divider.ts @@ -1,7 +1,7 @@ import { customElement, property } from 'lit/decorators.js'; import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import styles from './divider.css'; +import styles from './divider.styles.js'; /** * @summary Dividers are used to visually separate or group elements. diff --git a/packages/webawesome/src/components/drawer/drawer.css b/packages/webawesome/src/components/drawer/drawer.css deleted file mode 100644 index b380caebf..000000000 --- a/packages/webawesome/src/components/drawer/drawer.css +++ /dev/null @@ -1,290 +0,0 @@ -:host { - --size: 25rem; - --spacing: var(--wa-space-l); - --show-duration: 200ms; - --hide-duration: 200ms; - - display: none; -} - -:host([open]) { - display: block; -} - -.drawer { - display: flex; - flex-direction: column; - top: 0; - inset-inline-start: 0; - width: 100%; - height: 100%; - max-width: 100%; - max-height: 100%; - overflow: hidden; - background-color: var(--wa-color-surface-raised); - border: none; - box-shadow: var(--wa-shadow-l); - overflow: auto; - padding: 0; - margin: 0; - animation-duration: var(--show-duration); - animation-timing-function: ease; - - &.show::backdrop { - animation: show-backdrop var(--show-duration, 200ms) ease; - } - - &.hide::backdrop { - animation: show-backdrop var(--hide-duration, 200ms) ease reverse; - } - - &.show.top { - animation: show-drawer-from-top var(--show-duration) ease; - } - - &.hide.top { - animation: show-drawer-from-top var(--hide-duration) ease reverse; - } - - &.show.end { - animation: show-drawer-from-end var(--show-duration) ease; - - &:dir(rtl) { - animation-name: show-drawer-from-start; - } - } - - &.hide.end { - animation: show-drawer-from-end var(--hide-duration) ease reverse; - - &:dir(rtl) { - animation-name: show-drawer-from-start; - } - } - - &.show.bottom { - animation: show-drawer-from-bottom var(--show-duration) ease; - } - - &.hide.bottom { - animation: show-drawer-from-bottom var(--hide-duration) ease reverse; - } - - &.show.start { - animation: show-drawer-from-start var(--show-duration) ease; - - &:dir(rtl) { - animation-name: show-drawer-from-end; - } - } - - &.hide.start { - animation: show-drawer-from-start var(--hide-duration) ease reverse; - - &:dir(rtl) { - animation-name: show-drawer-from-end; - } - } - - &.pulse { - animation: pulse 250ms ease; - } -} - -.drawer:focus { - outline: none; -} - -.top { - top: 0; - inset-inline-end: auto; - bottom: auto; - inset-inline-start: 0; - width: 100%; - height: var(--size); -} - -.end { - top: 0; - inset-inline-end: 0; - bottom: auto; - inset-inline-start: auto; - width: var(--size); - height: 100%; -} - -.bottom { - top: auto; - inset-inline-end: auto; - bottom: 0; - inset-inline-start: 0; - width: 100%; - height: var(--size); -} - -.start { - top: 0; - inset-inline-end: auto; - bottom: auto; - inset-inline-start: 0; - width: var(--size); - height: 100%; -} - -.header { - display: flex; - flex-wrap: nowrap; - padding-inline-start: var(--spacing); - padding-block-end: 0; - - /* Subtract the close button's padding so that the X is visually aligned with the edges of the dialog content */ - padding-inline-end: calc(var(--spacing) - var(--wa-form-control-padding-block)); - padding-block-start: calc(var(--spacing) - var(--wa-form-control-padding-block)); -} - -.title { - align-self: center; - flex: 1 1 auto; - font: inherit; - font-size: var(--wa-font-size-l); - font-weight: var(--wa-font-weight-heading); - line-height: var(--wa-line-height-condensed); - margin: 0; -} - -.header-actions { - align-self: start; - display: flex; - flex-shrink: 0; - flex-wrap: wrap; - justify-content: end; - gap: var(--wa-space-2xs); - padding-inline-start: var(--spacing); -} - -.header-actions wa-button, -.header-actions ::slotted(wa-button) { - flex: 0 0 auto; - display: flex; - align-items: center; -} - -.body { - flex: 1 1 auto; - display: block; - padding: var(--spacing); - overflow: auto; - -webkit-overflow-scrolling: touch; - - &:focus { - outline: none; - } - - &:focus-visible { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); - } -} - -.footer { - display: flex; - flex-wrap: wrap; - gap: var(--wa-space-xs); - justify-content: end; - padding: var(--spacing); - padding-block-start: 0; -} - -.footer ::slotted(wa-button:not(:last-of-type)) { - margin-inline-end: var(--wa-spacing-xs); -} - -.drawer::backdrop { - /* - NOTE: the ::backdrop element doesn't inherit properly in Safari yet, but it will in 17.4! At that time, we can - remove the fallback values here. - */ - background-color: var(--wa-color-overlay-modal, rgb(0 0 0 / 0.25)); -} - -@keyframes pulse { - 0% { - scale: 1; - } - 50% { - scale: 1.01; - } - 100% { - scale: 1; - } -} - -@keyframes show-drawer { - from { - opacity: 0; - scale: 0.8; - } - to { - opacity: 1; - scale: 1; - } -} - -@keyframes show-drawer-from-top { - from { - opacity: 0; - translate: 0 -100%; - } - to { - opacity: 1; - translate: 0 0; - } -} - -@keyframes show-drawer-from-end { - from { - opacity: 0; - translate: 100%; - } - to { - opacity: 1; - translate: 0 0; - } -} - -@keyframes show-drawer-from-bottom { - from { - opacity: 0; - translate: 0 100%; - } - to { - opacity: 1; - translate: 0 0; - } -} - -@keyframes show-drawer-from-start { - from { - opacity: 0; - translate: -100% 0; - } - to { - opacity: 1; - translate: 0 0; - } -} - -@keyframes show-backdrop { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -@media (forced-colors: active) { - .drawer { - border: solid 1px white; - } -} diff --git a/packages/webawesome/src/components/drawer/drawer.styles.ts b/packages/webawesome/src/components/drawer/drawer.styles.ts new file mode 100644 index 000000000..f2bfcf4dc --- /dev/null +++ b/packages/webawesome/src/components/drawer/drawer.styles.ts @@ -0,0 +1,294 @@ +import { css } from 'lit'; + +export default css` + :host { + --size: 25rem; + --spacing: var(--wa-space-l); + --show-duration: 200ms; + --hide-duration: 200ms; + + display: none; + } + + :host([open]) { + display: block; + } + + .drawer { + display: flex; + flex-direction: column; + top: 0; + inset-inline-start: 0; + width: 100%; + height: 100%; + max-width: 100%; + max-height: 100%; + overflow: hidden; + background-color: var(--wa-color-surface-raised); + border: none; + box-shadow: var(--wa-shadow-l); + overflow: auto; + padding: 0; + margin: 0; + animation-duration: var(--show-duration); + animation-timing-function: ease; + + &.show::backdrop { + animation: show-backdrop var(--show-duration, 200ms) ease; + } + + &.hide::backdrop { + animation: show-backdrop var(--hide-duration, 200ms) ease reverse; + } + + &.show.top { + animation: show-drawer-from-top var(--show-duration) ease; + } + + &.hide.top { + animation: show-drawer-from-top var(--hide-duration) ease reverse; + } + + &.show.end { + animation: show-drawer-from-end var(--show-duration) ease; + + &:dir(rtl) { + animation-name: show-drawer-from-start; + } + } + + &.hide.end { + animation: show-drawer-from-end var(--hide-duration) ease reverse; + + &:dir(rtl) { + animation-name: show-drawer-from-start; + } + } + + &.show.bottom { + animation: show-drawer-from-bottom var(--show-duration) ease; + } + + &.hide.bottom { + animation: show-drawer-from-bottom var(--hide-duration) ease reverse; + } + + &.show.start { + animation: show-drawer-from-start var(--show-duration) ease; + + &:dir(rtl) { + animation-name: show-drawer-from-end; + } + } + + &.hide.start { + animation: show-drawer-from-start var(--hide-duration) ease reverse; + + &:dir(rtl) { + animation-name: show-drawer-from-end; + } + } + + &.pulse { + animation: pulse 250ms ease; + } + } + + .drawer:focus { + outline: none; + } + + .top { + top: 0; + inset-inline-end: auto; + bottom: auto; + inset-inline-start: 0; + width: 100%; + height: var(--size); + } + + .end { + top: 0; + inset-inline-end: 0; + bottom: auto; + inset-inline-start: auto; + width: var(--size); + height: 100%; + } + + .bottom { + top: auto; + inset-inline-end: auto; + bottom: 0; + inset-inline-start: 0; + width: 100%; + height: var(--size); + } + + .start { + top: 0; + inset-inline-end: auto; + bottom: auto; + inset-inline-start: 0; + width: var(--size); + height: 100%; + } + + .header { + display: flex; + flex-wrap: nowrap; + padding-inline-start: var(--spacing); + padding-block-end: 0; + + /* Subtract the close button's padding so that the X is visually aligned with the edges of the dialog content */ + padding-inline-end: calc(var(--spacing) - var(--wa-form-control-padding-block)); + padding-block-start: calc(var(--spacing) - var(--wa-form-control-padding-block)); + } + + .title { + align-self: center; + flex: 1 1 auto; + font: inherit; + font-size: var(--wa-font-size-l); + font-weight: var(--wa-font-weight-heading); + line-height: var(--wa-line-height-condensed); + margin: 0; + } + + .header-actions { + align-self: start; + display: flex; + flex-shrink: 0; + flex-wrap: wrap; + justify-content: end; + gap: var(--wa-space-2xs); + padding-inline-start: var(--spacing); + } + + .header-actions wa-button, + .header-actions ::slotted(wa-button) { + flex: 0 0 auto; + display: flex; + align-items: center; + } + + .body { + flex: 1 1 auto; + display: block; + padding: var(--spacing); + overflow: auto; + -webkit-overflow-scrolling: touch; + + &:focus { + outline: none; + } + + &:focus-visible { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + } + + .footer { + display: flex; + flex-wrap: wrap; + gap: var(--wa-space-xs); + justify-content: end; + padding: var(--spacing); + padding-block-start: 0; + } + + .footer ::slotted(wa-button:not(:last-of-type)) { + margin-inline-end: var(--wa-spacing-xs); + } + + .drawer::backdrop { + /* + NOTE: the ::backdrop element doesn't inherit properly in Safari yet, but it will in 17.4! At that time, we can + remove the fallback values here. + */ + background-color: var(--wa-color-overlay-modal, rgb(0 0 0 / 0.25)); + } + + @keyframes pulse { + 0% { + scale: 1; + } + 50% { + scale: 1.01; + } + 100% { + scale: 1; + } + } + + @keyframes show-drawer { + from { + opacity: 0; + scale: 0.8; + } + to { + opacity: 1; + scale: 1; + } + } + + @keyframes show-drawer-from-top { + from { + opacity: 0; + translate: 0 -100%; + } + to { + opacity: 1; + translate: 0 0; + } + } + + @keyframes show-drawer-from-end { + from { + opacity: 0; + translate: 100%; + } + to { + opacity: 1; + translate: 0 0; + } + } + + @keyframes show-drawer-from-bottom { + from { + opacity: 0; + translate: 0 100%; + } + to { + opacity: 1; + translate: 0 0; + } + } + + @keyframes show-drawer-from-start { + from { + opacity: 0; + translate: -100% 0; + } + to { + opacity: 1; + translate: 0 0; + } + } + + @keyframes show-backdrop { + from { + opacity: 0; + } + to { + opacity: 1; + } + } + + @media (forced-colors: active) { + .drawer { + border: solid 1px white; + } + } +`; diff --git a/packages/webawesome/src/components/drawer/drawer.ts b/packages/webawesome/src/components/drawer/drawer.ts index 15131dccd..502b1561c 100644 --- a/packages/webawesome/src/components/drawer/drawer.ts +++ b/packages/webawesome/src/components/drawer/drawer.ts @@ -13,7 +13,7 @@ import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import { LocalizeController } from '../../utilities/localize.js'; import '../button/button.js'; -import styles from './drawer.css'; +import styles from './drawer.styles.js'; /** * @summary Drawers slide in from a container to expose additional options and information. diff --git a/packages/webawesome/src/components/dropdown-item/dropdown-item.css b/packages/webawesome/src/components/dropdown-item/dropdown-item.css deleted file mode 100644 index f663f0191..000000000 --- a/packages/webawesome/src/components/dropdown-item/dropdown-item.css +++ /dev/null @@ -1,227 +0,0 @@ -:host { - display: flex; - position: relative; - align-items: center; - padding: 0.5em 1em; - border-radius: var(--wa-border-radius-s); - isolation: isolate; - color: var(--wa-color-text-normal); - line-height: var(--wa-line-height-condensed); - cursor: pointer; - transition: - var(--wa-transition-fast) background-color var(--wa-transition-easing), - var(--wa-transition-fast) color var(--wa-transition-easing); -} - -@media (hover: hover) { - :host(:hover:not(:state(disabled))) { - background-color: var(--wa-color-neutral-fill-normal); - } -} - -:host(:focus-visible) { - z-index: 1; - outline: var(--wa-focus-ring); - background-color: var(--wa-color-neutral-fill-normal); -} - -:host(:state(disabled)) { - opacity: 0.5; - cursor: not-allowed; -} - -/* Danger variant */ -:host([variant='danger']), -:host([variant='danger']) #details { - color: var(--wa-color-danger-on-quiet); -} - -@media (hover: hover) { - :host([variant='danger']:hover) { - background-color: var(--wa-color-danger-fill-normal); - color: var(--wa-color-danger-on-normal); - } -} - -:host([variant='danger']:focus-visible) { - background-color: var(--wa-color-danger-fill-normal); - color: var(--wa-color-danger-on-normal); -} - -:host([checkbox-adjacent]) { - padding-inline-start: 2em; -} - -/* Only add padding when item actually has a submenu */ -:host([submenu-adjacent]:not(:state(has-submenu))) #details { - padding-inline-end: 0; -} - -:host(:state(has-submenu)[submenu-adjacent]) #details { - padding-inline-end: 1.75em; -} - -#check { - visibility: hidden; - margin-inline-start: -1.5em; - margin-inline-end: 0.5em; - font-size: var(--wa-font-size-smaller); -} - -:host(:state(checked)) #check { - visibility: visible; -} - -#icon ::slotted(*) { - display: flex; - flex: 0 0 auto; - align-items: center; - margin-inline-end: 0.75em !important; - font-size: var(--wa-font-size-smaller); -} - -#label { - flex: 1 1 auto; - min-width: 0; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -#details { - display: flex; - flex: 0 0 auto; - align-items: center; - justify-content: end; - color: var(--wa-color-text-quiet); - font-size: var(--wa-font-size-smaller) !important; -} - -#details ::slotted(*) { - margin-inline-start: 2em !important; -} - -/* Submenu indicator icon */ -#submenu-indicator { - position: absolute; - inset-inline-end: 1em; - color: var(--wa-color-neutral-on-quiet); - font-size: var(--wa-font-size-smaller); -} - -/* Flip chevron icon when RTL */ -:host(:dir(rtl)) #submenu-indicator { - transform: scaleX(-1); -} - -/* Submenu styles */ -#submenu { - display: flex; - z-index: 10; - position: absolute; - top: 0; - left: 0; - flex-direction: column; - width: max-content; - margin: 0; - padding: 0.25em; - border: var(--wa-border-style) var(--wa-border-width-s) var(--wa-color-surface-border); - border-radius: var(--wa-border-radius-m); - background-color: var(--wa-color-surface-raised); - box-shadow: var(--wa-shadow-m); - color: var(--wa-color-text-normal); - text-align: start; - user-select: none; - - /* Override default popover styles */ - &[popover] { - margin: 0; - inset: auto; - padding: 0.25em; - overflow: visible; - border-radius: var(--wa-border-radius-m); - } - - &.show { - animation: submenu-show var(--show-duration, 50ms) ease; - } - - &.hide { - animation: submenu-show var(--show-duration, 50ms) ease reverse; - } - - /* Submenu placement transform origins */ - &[data-placement^='top'] { - transform-origin: bottom; - } - - &[data-placement^='bottom'] { - transform-origin: top; - } - - &[data-placement^='left'] { - transform-origin: right; - } - - &[data-placement^='right'] { - transform-origin: left; - } - - &[data-placement='left-start'] { - transform-origin: right top; - } - - &[data-placement='left-end'] { - transform-origin: right bottom; - } - - &[data-placement='right-start'] { - transform-origin: left top; - } - - &[data-placement='right-end'] { - transform-origin: left bottom; - } - - /* Safe triangle styling */ - &::before { - display: none; - z-index: 9; - position: fixed; - top: 0; - right: 0; - bottom: 0; - left: 0; - background-color: transparent; - content: ''; - clip-path: polygon( - var(--safe-triangle-cursor-x, 0) var(--safe-triangle-cursor-y, 0), - var(--safe-triangle-submenu-start-x, 0) var(--safe-triangle-submenu-start-y, 0), - var(--safe-triangle-submenu-end-x, 0) var(--safe-triangle-submenu-end-y, 0) - ); - pointer-events: auto; /* Enable mouse events on the triangle */ - } - - &[data-visible]::before { - display: block; - } -} - -::slotted(wa-dropdown-item) { - font-size: inherit; -} - -::slotted(wa-divider) { - --spacing: 0.25em; -} - -@keyframes submenu-show { - from { - scale: 0.9; - opacity: 0; - } - to { - scale: 1; - opacity: 1; - } -} diff --git a/packages/webawesome/src/components/dropdown-item/dropdown-item.styles.ts b/packages/webawesome/src/components/dropdown-item/dropdown-item.styles.ts new file mode 100644 index 000000000..f88aa9d03 --- /dev/null +++ b/packages/webawesome/src/components/dropdown-item/dropdown-item.styles.ts @@ -0,0 +1,231 @@ +import { css } from 'lit'; + +export default css` + :host { + display: flex; + position: relative; + align-items: center; + padding: 0.5em 1em; + border-radius: var(--wa-border-radius-s); + isolation: isolate; + color: var(--wa-color-text-normal); + line-height: var(--wa-line-height-condensed); + cursor: pointer; + transition: + var(--wa-transition-fast) background-color var(--wa-transition-easing), + var(--wa-transition-fast) color var(--wa-transition-easing); + } + + @media (hover: hover) { + :host(:hover:not(:state(disabled))) { + background-color: var(--wa-color-neutral-fill-normal); + } + } + + :host(:focus-visible) { + z-index: 1; + outline: var(--wa-focus-ring); + background-color: var(--wa-color-neutral-fill-normal); + } + + :host(:state(disabled)) { + opacity: 0.5; + cursor: not-allowed; + } + + /* Danger variant */ + :host([variant='danger']), + :host([variant='danger']) #details { + color: var(--wa-color-danger-on-quiet); + } + + @media (hover: hover) { + :host([variant='danger']:hover) { + background-color: var(--wa-color-danger-fill-normal); + color: var(--wa-color-danger-on-normal); + } + } + + :host([variant='danger']:focus-visible) { + background-color: var(--wa-color-danger-fill-normal); + color: var(--wa-color-danger-on-normal); + } + + :host([checkbox-adjacent]) { + padding-inline-start: 2em; + } + + /* Only add padding when item actually has a submenu */ + :host([submenu-adjacent]:not(:state(has-submenu))) #details { + padding-inline-end: 0; + } + + :host(:state(has-submenu)[submenu-adjacent]) #details { + padding-inline-end: 1.75em; + } + + #check { + visibility: hidden; + margin-inline-start: -1.5em; + margin-inline-end: 0.5em; + font-size: var(--wa-font-size-smaller); + } + + :host(:state(checked)) #check { + visibility: visible; + } + + #icon ::slotted(*) { + display: flex; + flex: 0 0 auto; + align-items: center; + margin-inline-end: 0.75em !important; + font-size: var(--wa-font-size-smaller); + } + + #label { + flex: 1 1 auto; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + #details { + display: flex; + flex: 0 0 auto; + align-items: center; + justify-content: end; + color: var(--wa-color-text-quiet); + font-size: var(--wa-font-size-smaller) !important; + } + + #details ::slotted(*) { + margin-inline-start: 2em !important; + } + + /* Submenu indicator icon */ + #submenu-indicator { + position: absolute; + inset-inline-end: 1em; + color: var(--wa-color-neutral-on-quiet); + font-size: var(--wa-font-size-smaller); + } + + /* Flip chevron icon when RTL */ + :host(:dir(rtl)) #submenu-indicator { + transform: scaleX(-1); + } + + /* Submenu styles */ + #submenu { + display: flex; + z-index: 10; + position: absolute; + top: 0; + left: 0; + flex-direction: column; + width: max-content; + margin: 0; + padding: 0.25em; + border: var(--wa-border-style) var(--wa-border-width-s) var(--wa-color-surface-border); + border-radius: var(--wa-border-radius-m); + background-color: var(--wa-color-surface-raised); + box-shadow: var(--wa-shadow-m); + color: var(--wa-color-text-normal); + text-align: start; + user-select: none; + + /* Override default popover styles */ + &[popover] { + margin: 0; + inset: auto; + padding: 0.25em; + overflow: visible; + border-radius: var(--wa-border-radius-m); + } + + &.show { + animation: submenu-show var(--show-duration, 50ms) ease; + } + + &.hide { + animation: submenu-show var(--show-duration, 50ms) ease reverse; + } + + /* Submenu placement transform origins */ + &[data-placement^='top'] { + transform-origin: bottom; + } + + &[data-placement^='bottom'] { + transform-origin: top; + } + + &[data-placement^='left'] { + transform-origin: right; + } + + &[data-placement^='right'] { + transform-origin: left; + } + + &[data-placement='left-start'] { + transform-origin: right top; + } + + &[data-placement='left-end'] { + transform-origin: right bottom; + } + + &[data-placement='right-start'] { + transform-origin: left top; + } + + &[data-placement='right-end'] { + transform-origin: left bottom; + } + + /* Safe triangle styling */ + &::before { + display: none; + z-index: 9; + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: transparent; + content: ''; + clip-path: polygon( + var(--safe-triangle-cursor-x, 0) var(--safe-triangle-cursor-y, 0), + var(--safe-triangle-submenu-start-x, 0) var(--safe-triangle-submenu-start-y, 0), + var(--safe-triangle-submenu-end-x, 0) var(--safe-triangle-submenu-end-y, 0) + ); + pointer-events: auto; /* Enable mouse events on the triangle */ + } + + &[data-visible]::before { + display: block; + } + } + + ::slotted(wa-dropdown-item) { + font-size: inherit; + } + + ::slotted(wa-divider) { + --spacing: 0.25em; + } + + @keyframes submenu-show { + from { + scale: 0.9; + opacity: 0; + } + to { + scale: 1; + opacity: 1; + } + } +`; diff --git a/packages/webawesome/src/components/dropdown-item/dropdown-item.ts b/packages/webawesome/src/components/dropdown-item/dropdown-item.ts index c01b7fcb9..b773662bb 100644 --- a/packages/webawesome/src/components/dropdown-item/dropdown-item.ts +++ b/packages/webawesome/src/components/dropdown-item/dropdown-item.ts @@ -5,7 +5,7 @@ import { animateWithClass } from '../../internal/animate.js'; import { HasSlotController } from '../../internal/slot.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import '../icon/icon.js'; -import styles from './dropdown-item.css'; +import styles from './dropdown-item.styles.js'; /** * @summary Represents an individual item within a dropdown menu, supporting standard items, checkboxes, and submenus. diff --git a/packages/webawesome/src/components/dropdown/dropdown.css b/packages/webawesome/src/components/dropdown/dropdown.css deleted file mode 100644 index 693bdc013..000000000 --- a/packages/webawesome/src/components/dropdown/dropdown.css +++ /dev/null @@ -1,93 +0,0 @@ -:host { - --show-duration: 50ms; - --hide-duration: 50ms; - display: contents; -} - -#menu { - display: flex; - flex-direction: column; - width: max-content; - margin: 0; - padding: 0.25em; - border: var(--wa-border-style) var(--wa-border-width-s) var(--wa-color-surface-border); - border-radius: var(--wa-border-radius-m); - background-color: var(--wa-color-surface-raised); - box-shadow: var(--wa-shadow-m); - color: var(--wa-color-text-normal); - text-align: start; - user-select: none; - overflow: auto; - max-width: var(--auto-size-available-width) !important; - max-height: var(--auto-size-available-height) !important; - - &.show { - animation: show var(--show-duration) ease; - } - - &.hide { - animation: show var(--hide-duration) ease reverse; - } - - ::slotted(h1), - ::slotted(h2), - ::slotted(h3), - ::slotted(h4), - ::slotted(h5), - ::slotted(h6) { - display: block !important; - margin: 0.25em 0 !important; - padding: 0.25em 0.75em !important; - color: var(--wa-color-text-quiet) !important; - font-family: var(--wa-font-family-body) !important; - font-weight: var(--wa-font-weight-semibold) !important; - font-size: var(--wa-font-size-smaller) !important; - } - - ::slotted(wa-divider) { - --spacing: 0.25em; /* Component-specific, left as-is */ - } -} - -wa-popup[data-current-placement^='top'] #menu { - transform-origin: bottom; -} - -wa-popup[data-current-placement^='bottom'] #menu { - transform-origin: top; -} - -wa-popup[data-current-placement^='left'] #menu { - transform-origin: right; -} - -wa-popup[data-current-placement^='right'] #menu { - transform-origin: left; -} - -wa-popup[data-current-placement='left-start'] #menu { - transform-origin: right top; -} - -wa-popup[data-current-placement='left-end'] #menu { - transform-origin: right bottom; -} - -wa-popup[data-current-placement='right-start'] #menu { - transform-origin: left top; -} - -wa-popup[data-current-placement='right-end'] #menu { - transform-origin: left bottom; -} - -@keyframes show { - from { - scale: 0.9; - opacity: 0; - } - to { - scale: 1; - opacity: 1; - } -} diff --git a/packages/webawesome/src/components/dropdown/dropdown.styles.ts b/packages/webawesome/src/components/dropdown/dropdown.styles.ts new file mode 100644 index 000000000..11d247602 --- /dev/null +++ b/packages/webawesome/src/components/dropdown/dropdown.styles.ts @@ -0,0 +1,97 @@ +import { css } from 'lit'; + +export default css` + :host { + --show-duration: 50ms; + --hide-duration: 50ms; + display: contents; + } + + #menu { + display: flex; + flex-direction: column; + width: max-content; + margin: 0; + padding: 0.25em; + border: var(--wa-border-style) var(--wa-border-width-s) var(--wa-color-surface-border); + border-radius: var(--wa-border-radius-m); + background-color: var(--wa-color-surface-raised); + box-shadow: var(--wa-shadow-m); + color: var(--wa-color-text-normal); + text-align: start; + user-select: none; + overflow: auto; + max-width: var(--auto-size-available-width) !important; + max-height: var(--auto-size-available-height) !important; + + &.show { + animation: show var(--show-duration) ease; + } + + &.hide { + animation: show var(--hide-duration) ease reverse; + } + + ::slotted(h1), + ::slotted(h2), + ::slotted(h3), + ::slotted(h4), + ::slotted(h5), + ::slotted(h6) { + display: block !important; + margin: 0.25em 0 !important; + padding: 0.25em 0.75em !important; + color: var(--wa-color-text-quiet) !important; + font-family: var(--wa-font-family-body) !important; + font-weight: var(--wa-font-weight-semibold) !important; + font-size: var(--wa-font-size-smaller) !important; + } + + ::slotted(wa-divider) { + --spacing: 0.25em; /* Component-specific, left as-is */ + } + } + + wa-popup[data-current-placement^='top'] #menu { + transform-origin: bottom; + } + + wa-popup[data-current-placement^='bottom'] #menu { + transform-origin: top; + } + + wa-popup[data-current-placement^='left'] #menu { + transform-origin: right; + } + + wa-popup[data-current-placement^='right'] #menu { + transform-origin: left; + } + + wa-popup[data-current-placement='left-start'] #menu { + transform-origin: right top; + } + + wa-popup[data-current-placement='left-end'] #menu { + transform-origin: right bottom; + } + + wa-popup[data-current-placement='right-start'] #menu { + transform-origin: left top; + } + + wa-popup[data-current-placement='right-end'] #menu { + transform-origin: left bottom; + } + + @keyframes show { + from { + scale: 0.9; + opacity: 0; + } + to { + scale: 1; + opacity: 1; + } + } +`; diff --git a/packages/webawesome/src/components/dropdown/dropdown.ts b/packages/webawesome/src/components/dropdown/dropdown.ts index a8d03f4fc..b3a1a70e9 100644 --- a/packages/webawesome/src/components/dropdown/dropdown.ts +++ b/packages/webawesome/src/components/dropdown/dropdown.ts @@ -12,13 +12,13 @@ import { activeElements } from '../../internal/active-elements.js'; import { animateWithClass } from '../../internal/animate.js'; import { uniqueId } from '../../internal/math.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import sizeStyles from '../../styles/utilities/size.css'; +import sizeStyles from '../../styles/component/size.styles.js'; import { LocalizeController } from '../../utilities/localize.js'; import type WaButton from '../button/button.js'; import '../dropdown-item/dropdown-item.js'; import type WaDropdownItem from '../dropdown-item/dropdown-item.js'; import WaPopup from '../popup/popup.js'; // Added import for wa-popup -import styles from './dropdown.css'; +import styles from './dropdown.styles.js'; const openDropdowns = new Set<WaDropdown>(); diff --git a/packages/webawesome/src/components/icon/icon.css b/packages/webawesome/src/components/icon/icon.css deleted file mode 100644 index 9a90c004f..000000000 --- a/packages/webawesome/src/components/icon/icon.css +++ /dev/null @@ -1,40 +0,0 @@ -:host { - --primary-color: currentColor; - --primary-opacity: 1; - --secondary-color: currentColor; - --secondary-opacity: 0.4; - - box-sizing: content-box; - display: inline-flex; - align-items: center; - justify-content: center; - vertical-align: -0.125em; -} - -/* Standard */ -:host(:not([auto-width])) { - width: 1.25em; - height: 1em; -} - -/* Auto-width */ -:host([auto-width]) { - width: auto; - height: 1em; -} - -svg { - height: 1em; - overflow: visible; - - /* Duotone colors with path-specific opacity fallback */ - path[data-duotone-primary] { - color: var(--primary-color); - opacity: var(--path-opacity, var(--primary-opacity)); - } - - path[data-duotone-secondary] { - color: var(--secondary-color); - opacity: var(--path-opacity, var(--secondary-opacity)); - } -} diff --git a/packages/webawesome/src/components/icon/icon.styles.ts b/packages/webawesome/src/components/icon/icon.styles.ts new file mode 100644 index 000000000..30c656645 --- /dev/null +++ b/packages/webawesome/src/components/icon/icon.styles.ts @@ -0,0 +1,44 @@ +import { css } from 'lit'; + +export default css` + :host { + --primary-color: currentColor; + --primary-opacity: 1; + --secondary-color: currentColor; + --secondary-opacity: 0.4; + + box-sizing: content-box; + display: inline-flex; + align-items: center; + justify-content: center; + vertical-align: -0.125em; + } + + /* Standard */ + :host(:not([auto-width])) { + width: 1.25em; + height: 1em; + } + + /* Auto-width */ + :host([auto-width]) { + width: auto; + height: 1em; + } + + svg { + height: 1em; + overflow: visible; + + /* Duotone colors with path-specific opacity fallback */ + path[data-duotone-primary] { + color: var(--primary-color); + opacity: var(--path-opacity, var(--primary-opacity)); + } + + path[data-duotone-secondary] { + color: var(--secondary-color); + opacity: var(--path-opacity, var(--secondary-opacity)); + } + } +`; diff --git a/packages/webawesome/src/components/icon/icon.ts b/packages/webawesome/src/components/icon/icon.ts index b30ac07f6..85dbe554d 100644 --- a/packages/webawesome/src/components/icon/icon.ts +++ b/packages/webawesome/src/components/icon/icon.ts @@ -5,7 +5,7 @@ import { WaErrorEvent } from '../../events/error.js'; import { WaLoadEvent } from '../../events/load.js'; import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import styles from './icon.css'; +import styles from './icon.styles.js'; import { getDefaultIconFamily, getIconLibrary, unwatchIcon, watchIcon, type IconLibrary } from './library.js'; import type { HTMLTemplateResult, PropertyValues } from 'lit'; diff --git a/packages/webawesome/src/components/include/include.css b/packages/webawesome/src/components/include/include.css deleted file mode 100644 index 5d4e87f30..000000000 --- a/packages/webawesome/src/components/include/include.css +++ /dev/null @@ -1,3 +0,0 @@ -:host { - display: block; -} diff --git a/packages/webawesome/src/components/include/include.styles.ts b/packages/webawesome/src/components/include/include.styles.ts new file mode 100644 index 000000000..940a1557b --- /dev/null +++ b/packages/webawesome/src/components/include/include.styles.ts @@ -0,0 +1,7 @@ +import { css } from 'lit'; + +export default css` + :host { + display: block; + } +`; diff --git a/packages/webawesome/src/components/include/include.ts b/packages/webawesome/src/components/include/include.ts index 267047801..8f9c661f8 100644 --- a/packages/webawesome/src/components/include/include.ts +++ b/packages/webawesome/src/components/include/include.ts @@ -4,7 +4,7 @@ import { WaIncludeErrorEvent } from '../../events/include-error.js'; import { WaLoadEvent } from '../../events/load.js'; import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import styles from './include.css'; +import styles from './include.styles.js'; import { requestInclude } from './request.js'; /** diff --git a/packages/webawesome/src/components/input/input.css b/packages/webawesome/src/components/input/input.css deleted file mode 100644 index be657138a..000000000 --- a/packages/webawesome/src/components/input/input.css +++ /dev/null @@ -1,227 +0,0 @@ -:host { - border-width: 0; -} - -.text-field { - flex: auto; - display: flex; - align-items: stretch; - justify-content: start; - position: relative; - transition: inherit; - height: var(--wa-form-control-height); - border-color: var(--wa-form-control-border-color); - border-radius: var(--wa-form-control-border-radius); - border-style: var(--wa-form-control-border-style); - border-width: var(--wa-form-control-border-width); - cursor: text; - color: var(--wa-form-control-value-color); - font-size: var(--wa-form-control-value-font-size); - font-family: inherit; - font-weight: var(--wa-form-control-value-font-weight); - line-height: var(--wa-form-control-value-line-height); - vertical-align: middle; - width: 100%; - transition: - background-color var(--wa-transition-normal), - border var(--wa-transition-normal), - outline var(--wa-transition-fast); - transition-timing-function: var(--wa-transition-easing); - background-color: var(--wa-form-control-background-color); - box-shadow: var(--box-shadow); - padding: 0 var(--wa-form-control-padding-inline); - - &:focus-within { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); - } - - /* Style disabled inputs */ - &:has(:disabled) { - cursor: not-allowed; - opacity: 0.5; - } -} - -/* Appearance modifiers */ -:host([appearance='outlined']) .text-field { - background-color: var(--wa-form-control-background-color); - border-color: var(--wa-form-control-border-color); -} - -:host([appearance='filled']) .text-field { - background-color: var(--wa-color-neutral-fill-quiet); - border-color: var(--wa-color-neutral-fill-quiet); -} - -:host([appearance='filled-outlined']) .text-field { - background-color: var(--wa-color-neutral-fill-quiet); - border-color: var(--wa-form-control-border-color); -} - -:host([pill]) .text-field { - border-radius: var(--wa-border-radius-pill) !important; -} - -.text-field { - /* Show autofill styles over the entire text field, not just the native <input> */ - &:has(:autofill), - &:has(:-webkit-autofill) { - background-color: var(--wa-color-brand-fill-quiet) !important; - } - - input, - textarea { - /* - Fixes an alignment issue with placeholders. - https://github.com/shoelace-style/webawesome/issues/342 - */ - height: 100%; - - padding: 0; - border: none; - outline: none; - box-shadow: none; - margin: 0; - cursor: inherit; - -webkit-appearance: none; - font: inherit; - - /* Turn off Safari's autofill styles */ - &:-webkit-autofill, - &:-webkit-autofill:hover, - &:-webkit-autofill:focus, - &:-webkit-autofill:active { - -webkit-background-clip: text; - background-color: transparent; - -webkit-text-fill-color: inherit; - } - } -} - -input { - flex: 1 1 auto; - min-width: 0; - height: 100%; - transition: inherit; - - /* prettier-ignore */ - background-color: rgb(118 118 118 / 0); /* ensures proper placeholder styles in webkit's date input */ - height: calc(var(--wa-form-control-height) - var(--border-width) * 2); - padding-block: 0; - color: inherit; - - &:autofill { - &, - &:hover, - &:focus, - &:active { - box-shadow: none; - caret-color: var(--wa-form-control-value-color); - } - } - - &::placeholder { - color: var(--wa-form-control-placeholder-color); - user-select: none; - -webkit-user-select: none; - } - - &::-webkit-search-decoration, - &::-webkit-search-cancel-button, - &::-webkit-search-results-button, - &::-webkit-search-results-decoration { - -webkit-appearance: none; - } - - &:focus { - outline: none; - } -} - -textarea { - &:autofill { - &, - &:hover, - &:focus, - &:active { - box-shadow: none; - caret-color: var(--wa-form-control-value-color); - } - } - - &::placeholder { - color: var(--wa-form-control-placeholder-color); - user-select: none; - -webkit-user-select: none; - } -} - -.start, -.end { - display: inline-flex; - flex: 0 0 auto; - align-items: center; - cursor: default; - - &::slotted(wa-icon) { - color: var(--wa-color-neutral-on-quiet); - } -} - -.start::slotted(*) { - margin-inline-end: var(--wa-form-control-padding-inline); -} - -.end::slotted(*) { - margin-inline-start: var(--wa-form-control-padding-inline); -} - -/* - * Clearable + Password Toggle - */ - -.clear, -.password-toggle { - display: inline-flex; - align-items: center; - justify-content: center; - font-size: inherit; - color: var(--wa-color-neutral-on-quiet); - border: none; - background: none; - padding: 0; - transition: var(--wa-transition-normal) color; - cursor: pointer; - margin-inline-start: var(--wa-form-control-padding-inline); - - @media (hover: hover) { - &:hover { - color: color-mix(in oklab, currentColor, var(--wa-color-mix-hover)); - } - } - - &:active { - color: color-mix(in oklab, currentColor, var(--wa-color-mix-active)); - } - - &:focus { - outline: none; - } -} - -/* Don't show the browser's password toggle in Edge */ -::-ms-reveal { - display: none; -} - -/* Hide the built-in number spinner */ -:host([without-spin-buttons]) input[type='number'] { - -moz-appearance: textfield; - - &::-webkit-outer-spin-button, - &::-webkit-inner-spin-button { - -webkit-appearance: none; - display: none; - } -} diff --git a/packages/webawesome/src/components/input/input.styles.ts b/packages/webawesome/src/components/input/input.styles.ts new file mode 100644 index 000000000..5332a7d69 --- /dev/null +++ b/packages/webawesome/src/components/input/input.styles.ts @@ -0,0 +1,231 @@ +import { css } from 'lit'; + +export default css` + :host { + border-width: 0; + } + + .text-field { + flex: auto; + display: flex; + align-items: stretch; + justify-content: start; + position: relative; + transition: inherit; + height: var(--wa-form-control-height); + border-color: var(--wa-form-control-border-color); + border-radius: var(--wa-form-control-border-radius); + border-style: var(--wa-form-control-border-style); + border-width: var(--wa-form-control-border-width); + cursor: text; + color: var(--wa-form-control-value-color); + font-size: var(--wa-form-control-value-font-size); + font-family: inherit; + font-weight: var(--wa-form-control-value-font-weight); + line-height: var(--wa-form-control-value-line-height); + vertical-align: middle; + width: 100%; + transition: + background-color var(--wa-transition-normal), + border var(--wa-transition-normal), + outline var(--wa-transition-fast); + transition-timing-function: var(--wa-transition-easing); + background-color: var(--wa-form-control-background-color); + box-shadow: var(--box-shadow); + padding: 0 var(--wa-form-control-padding-inline); + + &:focus-within { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + + /* Style disabled inputs */ + &:has(:disabled) { + cursor: not-allowed; + opacity: 0.5; + } + } + + /* Appearance modifiers */ + :host([appearance='outlined']) .text-field { + background-color: var(--wa-form-control-background-color); + border-color: var(--wa-form-control-border-color); + } + + :host([appearance='filled']) .text-field { + background-color: var(--wa-color-neutral-fill-quiet); + border-color: var(--wa-color-neutral-fill-quiet); + } + + :host([appearance='filled-outlined']) .text-field { + background-color: var(--wa-color-neutral-fill-quiet); + border-color: var(--wa-form-control-border-color); + } + + :host([pill]) .text-field { + border-radius: var(--wa-border-radius-pill) !important; + } + + .text-field { + /* Show autofill styles over the entire text field, not just the native <input> */ + &:has(:autofill), + &:has(:-webkit-autofill) { + background-color: var(--wa-color-brand-fill-quiet) !important; + } + + input, + textarea { + /* + Fixes an alignment issue with placeholders. + https://github.com/shoelace-style/webawesome/issues/342 + */ + height: 100%; + + padding: 0; + border: none; + outline: none; + box-shadow: none; + margin: 0; + cursor: inherit; + -webkit-appearance: none; + font: inherit; + + /* Turn off Safari's autofill styles */ + &:-webkit-autofill, + &:-webkit-autofill:hover, + &:-webkit-autofill:focus, + &:-webkit-autofill:active { + -webkit-background-clip: text; + background-color: transparent; + -webkit-text-fill-color: inherit; + } + } + } + + input { + flex: 1 1 auto; + min-width: 0; + height: 100%; + transition: inherit; + + /* prettier-ignore */ + background-color: rgb(118 118 118 / 0); /* ensures proper placeholder styles in webkit's date input */ + height: calc(var(--wa-form-control-height) - var(--border-width) * 2); + padding-block: 0; + color: inherit; + + &:autofill { + &, + &:hover, + &:focus, + &:active { + box-shadow: none; + caret-color: var(--wa-form-control-value-color); + } + } + + &::placeholder { + color: var(--wa-form-control-placeholder-color); + user-select: none; + -webkit-user-select: none; + } + + &::-webkit-search-decoration, + &::-webkit-search-cancel-button, + &::-webkit-search-results-button, + &::-webkit-search-results-decoration { + -webkit-appearance: none; + } + + &:focus { + outline: none; + } + } + + textarea { + &:autofill { + &, + &:hover, + &:focus, + &:active { + box-shadow: none; + caret-color: var(--wa-form-control-value-color); + } + } + + &::placeholder { + color: var(--wa-form-control-placeholder-color); + user-select: none; + -webkit-user-select: none; + } + } + + .start, + .end { + display: inline-flex; + flex: 0 0 auto; + align-items: center; + cursor: default; + + &::slotted(wa-icon) { + color: var(--wa-color-neutral-on-quiet); + } + } + + .start::slotted(*) { + margin-inline-end: var(--wa-form-control-padding-inline); + } + + .end::slotted(*) { + margin-inline-start: var(--wa-form-control-padding-inline); + } + + /* + * Clearable + Password Toggle + */ + + .clear, + .password-toggle { + display: inline-flex; + align-items: center; + justify-content: center; + font-size: inherit; + color: var(--wa-color-neutral-on-quiet); + border: none; + background: none; + padding: 0; + transition: var(--wa-transition-normal) color; + cursor: pointer; + margin-inline-start: var(--wa-form-control-padding-inline); + + @media (hover: hover) { + &:hover { + color: color-mix(in oklab, currentColor, var(--wa-color-mix-hover)); + } + } + + &:active { + color: color-mix(in oklab, currentColor, var(--wa-color-mix-active)); + } + + &:focus { + outline: none; + } + } + + /* Don't show the browser's password toggle in Edge */ + ::-ms-reveal { + display: none; + } + + /* Hide the built-in number spinner */ + :host([without-spin-buttons]) input[type='number'] { + -moz-appearance: textfield; + + &::-webkit-outer-spin-button, + &::-webkit-inner-spin-button { + -webkit-appearance: none; + display: none; + } + } +`; diff --git a/packages/webawesome/src/components/input/input.ts b/packages/webawesome/src/components/input/input.ts index 4be80e1b0..3dfa32771 100644 --- a/packages/webawesome/src/components/input/input.ts +++ b/packages/webawesome/src/components/input/input.ts @@ -9,11 +9,11 @@ import { submitOnEnter } from '../../internal/submit-on-enter.js'; import { MirrorValidator } from '../../internal/validators/mirror-validator.js'; import { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js'; -import formControlStyles from '../../styles/component/form-control.css'; -import sizeStyles from '../../styles/utilities/size.css'; +import formControlStyles from '../../styles/component/form-control.styles.js'; +import sizeStyles from '../../styles/component/size.styles.js'; import { LocalizeController } from '../../utilities/localize.js'; import '../icon/icon.js'; -import styles from './input.css'; +import styles from './input.styles.js'; /** * @summary Inputs collect data from the user. diff --git a/packages/webawesome/src/components/intersection-observer/intersection-observer.css b/packages/webawesome/src/components/intersection-observer/intersection-observer.css deleted file mode 100644 index 92d692cdd..000000000 --- a/packages/webawesome/src/components/intersection-observer/intersection-observer.css +++ /dev/null @@ -1,3 +0,0 @@ -:host { - display: contents; -} diff --git a/packages/webawesome/src/components/intersection-observer/intersection-observer.styles.ts b/packages/webawesome/src/components/intersection-observer/intersection-observer.styles.ts new file mode 100644 index 000000000..1ef4bf6f3 --- /dev/null +++ b/packages/webawesome/src/components/intersection-observer/intersection-observer.styles.ts @@ -0,0 +1,7 @@ +import { css } from 'lit'; + +export default css` + :host { + display: contents; + } +`; diff --git a/packages/webawesome/src/components/intersection-observer/intersection-observer.ts b/packages/webawesome/src/components/intersection-observer/intersection-observer.ts index e703be0d0..5dbdb1eae 100644 --- a/packages/webawesome/src/components/intersection-observer/intersection-observer.ts +++ b/packages/webawesome/src/components/intersection-observer/intersection-observer.ts @@ -5,7 +5,7 @@ import { clamp } from '../../internal/math.js'; import { parseSpaceDelimitedTokens } from '../../internal/parse.js'; import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import styles from './intersection-observer.css'; +import styles from './intersection-observer.styles.js'; /** * @summary Tracks immediate child elements and fires events as they move in and out of view. diff --git a/packages/webawesome/src/components/mutation-observer/mutation-observer.css b/packages/webawesome/src/components/mutation-observer/mutation-observer.css deleted file mode 100644 index 92d692cdd..000000000 --- a/packages/webawesome/src/components/mutation-observer/mutation-observer.css +++ /dev/null @@ -1,3 +0,0 @@ -:host { - display: contents; -} diff --git a/packages/webawesome/src/components/mutation-observer/mutation-observer.styles.ts b/packages/webawesome/src/components/mutation-observer/mutation-observer.styles.ts new file mode 100644 index 000000000..1ef4bf6f3 --- /dev/null +++ b/packages/webawesome/src/components/mutation-observer/mutation-observer.styles.ts @@ -0,0 +1,7 @@ +import { css } from 'lit'; + +export default css` + :host { + display: contents; + } +`; diff --git a/packages/webawesome/src/components/mutation-observer/mutation-observer.ts b/packages/webawesome/src/components/mutation-observer/mutation-observer.ts index 5c0ad0c6b..28fe4b97d 100644 --- a/packages/webawesome/src/components/mutation-observer/mutation-observer.ts +++ b/packages/webawesome/src/components/mutation-observer/mutation-observer.ts @@ -3,7 +3,7 @@ import { customElement, property } from 'lit/decorators.js'; import { WaMutationEvent } from '../../events/mutation.js'; import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import styles from './mutation-observer.css'; +import styles from './mutation-observer.styles.js'; /** * @summary The Mutation Observer component offers a thin, declarative interface to the [`MutationObserver API`](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver). diff --git a/packages/webawesome/src/components/option/option.css b/packages/webawesome/src/components/option/option.css deleted file mode 100644 index 31ec2647a..000000000 --- a/packages/webawesome/src/components/option/option.css +++ /dev/null @@ -1,80 +0,0 @@ -:host { - display: block; - color: var(--wa-color-text-normal); - -webkit-user-select: none; - user-select: none; - - position: relative; - display: flex; - align-items: center; - font: inherit; - padding: 0.5em 1em 0.5em 0.25em; - line-height: var(--wa-line-height-condensed); - transition: fill var(--wa-transition-normal) var(--wa-transition-easing); - cursor: pointer; -} - -:host(:focus) { - outline: none; -} - -@media (hover: hover) { - :host(:not([disabled], :state(current)):is(:state(hover), :hover)) { - background-color: var(--wa-color-neutral-fill-normal); - color: var(--wa-color-neutral-on-normal); - } -} - -:host(:state(current)), -:host([disabled]:state(current)) { - background-color: var(--wa-color-brand-fill-loud); - color: var(--wa-color-brand-on-loud); - opacity: 1; -} - -:host([disabled]) { - outline: none; - opacity: 0.5; - cursor: not-allowed; -} - -.label { - flex: 1 1 auto; - display: inline-block; -} - -.check { - flex: 0 0 auto; - display: flex; - align-items: center; - justify-content: center; - font-size: var(--wa-font-size-smaller); - visibility: hidden; - width: 2em; -} - -:host(:state(selected)) .check { - visibility: visible; -} - -.start, -.end { - flex: 0 0 auto; - display: flex; - align-items: center; -} - -.start::slotted(*) { - margin-inline-end: 0.5em; -} - -.end::slotted(*) { - margin-inline-start: 0.5em; -} - -@media (forced-colors: active) { - :host(:hover:not([aria-disabled='true'])) { - outline: dashed 1px SelectedItem; - outline-offset: -1px; - } -} diff --git a/packages/webawesome/src/components/option/option.styles.ts b/packages/webawesome/src/components/option/option.styles.ts new file mode 100644 index 000000000..a0779c251 --- /dev/null +++ b/packages/webawesome/src/components/option/option.styles.ts @@ -0,0 +1,84 @@ +import { css } from 'lit'; + +export default css` + :host { + display: block; + color: var(--wa-color-text-normal); + -webkit-user-select: none; + user-select: none; + + position: relative; + display: flex; + align-items: center; + font: inherit; + padding: 0.5em 1em 0.5em 0.25em; + line-height: var(--wa-line-height-condensed); + transition: fill var(--wa-transition-normal) var(--wa-transition-easing); + cursor: pointer; + } + + :host(:focus) { + outline: none; + } + + @media (hover: hover) { + :host(:not([disabled], :state(current)):is(:state(hover), :hover)) { + background-color: var(--wa-color-neutral-fill-normal); + color: var(--wa-color-neutral-on-normal); + } + } + + :host(:state(current)), + :host([disabled]:state(current)) { + background-color: var(--wa-color-brand-fill-loud); + color: var(--wa-color-brand-on-loud); + opacity: 1; + } + + :host([disabled]) { + outline: none; + opacity: 0.5; + cursor: not-allowed; + } + + .label { + flex: 1 1 auto; + display: inline-block; + } + + .check { + flex: 0 0 auto; + display: flex; + align-items: center; + justify-content: center; + font-size: var(--wa-font-size-smaller); + visibility: hidden; + width: 2em; + } + + :host(:state(selected)) .check { + visibility: visible; + } + + .start, + .end { + flex: 0 0 auto; + display: flex; + align-items: center; + } + + .start::slotted(*) { + margin-inline-end: 0.5em; + } + + .end::slotted(*) { + margin-inline-start: 0.5em; + } + + @media (forced-colors: active) { + :host(:hover:not([aria-disabled='true'])) { + outline: dashed 1px SelectedItem; + outline-offset: -1px; + } + } +`; diff --git a/packages/webawesome/src/components/option/option.ts b/packages/webawesome/src/components/option/option.ts index 5c0f10957..852957ade 100644 --- a/packages/webawesome/src/components/option/option.ts +++ b/packages/webawesome/src/components/option/option.ts @@ -5,7 +5,7 @@ import getText from '../../internal/get-text.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import { LocalizeController } from '../../utilities/localize.js'; import '../icon/icon.js'; -import styles from './option.css'; +import styles from './option.styles.js'; /** * @summary Options define the selectable items within a select component. diff --git a/packages/webawesome/src/components/popover/popover.css b/packages/webawesome/src/components/popover/popover.css deleted file mode 100644 index 1afa32574..000000000 --- a/packages/webawesome/src/components/popover/popover.css +++ /dev/null @@ -1,91 +0,0 @@ -:host { - --arrow-size: 0.375rem; - --max-width: 25rem; - --show-duration: 100ms; - --hide-duration: 100ms; - - /* Internal calculated properties */ - --arrow-diagonal-size: calc((var(--arrow-size) * sin(45deg))); - - display: contents; - - /** Defaults for inherited CSS properties */ - font-size: var(--wa-font-size-m); - line-height: var(--wa-line-height-normal); - text-align: start; - white-space: normal; -} - -/* The native dialog element */ -.dialog { - display: none; - position: fixed; - inset: 0; - width: 100%; - height: 100%; - margin: 0; - padding: 0; - border: none; - background: transparent; - overflow: visible; - pointer-events: none; - - &:focus { - outline: none; - } - - &[open] { - display: block; - } -} - -/* The <wa-popup> element */ -.popover { - --arrow-size: inherit; - --show-duration: inherit; - --hide-duration: inherit; - - pointer-events: auto; - - &::part(arrow) { - background-color: var(--wa-color-surface-default); - border-top: none; - border-left: none; - border-bottom: solid var(--wa-panel-border-width) var(--wa-color-surface-border); - border-right: solid var(--wa-panel-border-width) var(--wa-color-surface-border); - box-shadow: none; - } -} - -.popover[placement^='top']::part(popup) { - transform-origin: bottom; -} - -.popover[placement^='bottom']::part(popup) { - transform-origin: top; -} - -.popover[placement^='left']::part(popup) { - transform-origin: right; -} - -.popover[placement^='right']::part(popup) { - transform-origin: left; -} - -/* Body */ -.body { - display: flex; - flex-direction: column; - width: max-content; - max-width: var(--max-width); - padding: var(--wa-space-l); - background-color: var(--wa-color-surface-default); - border: var(--wa-panel-border-width) solid var(--wa-color-surface-border); - border-radius: var(--wa-panel-border-radius); - border-style: var(--wa-panel-border-style); - box-shadow: var(--wa-shadow-l); - color: var(--wa-color-text-normal); - user-select: none; - -webkit-user-select: none; -} diff --git a/packages/webawesome/src/components/popover/popover.styles.ts b/packages/webawesome/src/components/popover/popover.styles.ts new file mode 100644 index 000000000..f2163e8e2 --- /dev/null +++ b/packages/webawesome/src/components/popover/popover.styles.ts @@ -0,0 +1,95 @@ +import { css } from 'lit'; + +export default css` + :host { + --arrow-size: 0.375rem; + --max-width: 25rem; + --show-duration: 100ms; + --hide-duration: 100ms; + + /* Internal calculated properties */ + --arrow-diagonal-size: calc((var(--arrow-size) * sin(45deg))); + + display: contents; + + /** Defaults for inherited CSS properties */ + font-size: var(--wa-font-size-m); + line-height: var(--wa-line-height-normal); + text-align: start; + white-space: normal; + } + + /* The native dialog element */ + .dialog { + display: none; + position: fixed; + inset: 0; + width: 100%; + height: 100%; + margin: 0; + padding: 0; + border: none; + background: transparent; + overflow: visible; + pointer-events: none; + + &:focus { + outline: none; + } + + &[open] { + display: block; + } + } + + /* The <wa-popup> element */ + .popover { + --arrow-size: inherit; + --show-duration: inherit; + --hide-duration: inherit; + + pointer-events: auto; + + &::part(arrow) { + background-color: var(--wa-color-surface-default); + border-top: none; + border-left: none; + border-bottom: solid var(--wa-panel-border-width) var(--wa-color-surface-border); + border-right: solid var(--wa-panel-border-width) var(--wa-color-surface-border); + box-shadow: none; + } + } + + .popover[placement^='top']::part(popup) { + transform-origin: bottom; + } + + .popover[placement^='bottom']::part(popup) { + transform-origin: top; + } + + .popover[placement^='left']::part(popup) { + transform-origin: right; + } + + .popover[placement^='right']::part(popup) { + transform-origin: left; + } + + /* Body */ + .body { + display: flex; + flex-direction: column; + width: max-content; + max-width: var(--max-width); + padding: var(--wa-space-l); + background-color: var(--wa-color-surface-default); + border: var(--wa-panel-border-width) solid var(--wa-color-surface-border); + border-radius: var(--wa-panel-border-radius); + border-style: var(--wa-panel-border-style); + box-shadow: var(--wa-shadow-l); + color: var(--wa-color-text-normal); + user-select: none; + -webkit-user-select: none; + } +`; diff --git a/packages/webawesome/src/components/popover/popover.ts b/packages/webawesome/src/components/popover/popover.ts index 63ed3a14a..60eaeba84 100644 --- a/packages/webawesome/src/components/popover/popover.ts +++ b/packages/webawesome/src/components/popover/popover.ts @@ -12,7 +12,7 @@ import { uniqueId } from '../../internal/math.js'; import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import WaPopup from '../popup/popup.js'; -import styles from './popover.css'; +import styles from './popover.styles.js'; const openPopovers = new Set<WaPopover>(); diff --git a/packages/webawesome/src/components/popup/popup.css b/packages/webawesome/src/components/popup/popup.css deleted file mode 100644 index 7db983610..000000000 --- a/packages/webawesome/src/components/popup/popup.css +++ /dev/null @@ -1,121 +0,0 @@ -:host { - --arrow-color: black; - --arrow-size: var(--wa-tooltip-arrow-size); - --show-duration: 100ms; - --hide-duration: 100ms; - - /* - * These properties are computed to account for the arrow's dimensions after being rotated 45º. The constant - * 0.7071 is derived from sin(45), which is the diagonal size of the arrow's container after rotating. - */ - --arrow-size-diagonal: calc(var(--arrow-size) * 0.7071); - --arrow-padding-offset: calc(var(--arrow-size-diagonal) - var(--arrow-size)); - - display: contents; -} - -.popup { - position: absolute; - isolation: isolate; - max-width: var(--auto-size-available-width, none); - max-height: var(--auto-size-available-height, none); - - /* Clear UA styles for [popover] */ - :where(&) { - inset: unset; - padding: unset; - margin: unset; - width: unset; - height: unset; - color: unset; - background: unset; - border: unset; - overflow: unset; - } -} - -.popup-fixed { - position: fixed; -} - -.popup:not(.popup-active) { - display: none; -} - -.arrow { - position: absolute; - width: calc(var(--arrow-size-diagonal) * 2); - height: calc(var(--arrow-size-diagonal) * 2); - rotate: 45deg; - background: var(--arrow-color); - z-index: 3; -} - -:host([data-current-placement~='left']) .arrow { - rotate: -45deg; -} - -:host([data-current-placement~='right']) .arrow { - rotate: 135deg; -} - -:host([data-current-placement~='bottom']) .arrow { - rotate: 225deg; -} - -/* Hover bridge */ -.popup-hover-bridge:not(.popup-hover-bridge-visible) { - display: none; -} - -.popup-hover-bridge { - position: fixed; - z-index: 899; - top: 0; - right: 0; - bottom: 0; - left: 0; - clip-path: polygon( - var(--hover-bridge-top-left-x, 0) var(--hover-bridge-top-left-y, 0), - var(--hover-bridge-top-right-x, 0) var(--hover-bridge-top-right-y, 0), - var(--hover-bridge-bottom-right-x, 0) var(--hover-bridge-bottom-right-y, 0), - var(--hover-bridge-bottom-left-x, 0) var(--hover-bridge-bottom-left-y, 0) - ); -} - -/* Built-in animations */ -.show { - animation: show var(--show-duration) ease; -} - -.hide { - animation: show var(--hide-duration) ease reverse; -} - -@keyframes show { - from { - opacity: 0; - } - to { - opacity: 1; - } -} - -.show-with-scale { - animation: show-with-scale var(--show-duration) ease; -} - -.hide-with-scale { - animation: show-with-scale var(--hide-duration) ease reverse; -} - -@keyframes show-with-scale { - from { - opacity: 0; - scale: 0.8; - } - to { - opacity: 1; - scale: 1; - } -} diff --git a/packages/webawesome/src/components/popup/popup.styles.ts b/packages/webawesome/src/components/popup/popup.styles.ts new file mode 100644 index 000000000..92932f04c --- /dev/null +++ b/packages/webawesome/src/components/popup/popup.styles.ts @@ -0,0 +1,125 @@ +import { css } from 'lit'; + +export default css` + :host { + --arrow-color: black; + --arrow-size: var(--wa-tooltip-arrow-size); + --show-duration: 100ms; + --hide-duration: 100ms; + + /* + * These properties are computed to account for the arrow's dimensions after being rotated 45º. The constant + * 0.7071 is derived from sin(45), which is the diagonal size of the arrow's container after rotating. + */ + --arrow-size-diagonal: calc(var(--arrow-size) * 0.7071); + --arrow-padding-offset: calc(var(--arrow-size-diagonal) - var(--arrow-size)); + + display: contents; + } + + .popup { + position: absolute; + isolation: isolate; + max-width: var(--auto-size-available-width, none); + max-height: var(--auto-size-available-height, none); + + /* Clear UA styles for [popover] */ + :where(&) { + inset: unset; + padding: unset; + margin: unset; + width: unset; + height: unset; + color: unset; + background: unset; + border: unset; + overflow: unset; + } + } + + .popup-fixed { + position: fixed; + } + + .popup:not(.popup-active) { + display: none; + } + + .arrow { + position: absolute; + width: calc(var(--arrow-size-diagonal) * 2); + height: calc(var(--arrow-size-diagonal) * 2); + rotate: 45deg; + background: var(--arrow-color); + z-index: 3; + } + + :host([data-current-placement~='left']) .arrow { + rotate: -45deg; + } + + :host([data-current-placement~='right']) .arrow { + rotate: 135deg; + } + + :host([data-current-placement~='bottom']) .arrow { + rotate: 225deg; + } + + /* Hover bridge */ + .popup-hover-bridge:not(.popup-hover-bridge-visible) { + display: none; + } + + .popup-hover-bridge { + position: fixed; + z-index: 899; + top: 0; + right: 0; + bottom: 0; + left: 0; + clip-path: polygon( + var(--hover-bridge-top-left-x, 0) var(--hover-bridge-top-left-y, 0), + var(--hover-bridge-top-right-x, 0) var(--hover-bridge-top-right-y, 0), + var(--hover-bridge-bottom-right-x, 0) var(--hover-bridge-bottom-right-y, 0), + var(--hover-bridge-bottom-left-x, 0) var(--hover-bridge-bottom-left-y, 0) + ); + } + + /* Built-in animations */ + .show { + animation: show var(--show-duration) ease; + } + + .hide { + animation: show var(--hide-duration) ease reverse; + } + + @keyframes show { + from { + opacity: 0; + } + to { + opacity: 1; + } + } + + .show-with-scale { + animation: show-with-scale var(--show-duration) ease; + } + + .hide-with-scale { + animation: show-with-scale var(--hide-duration) ease reverse; + } + + @keyframes show-with-scale { + from { + opacity: 0; + scale: 0.8; + } + to { + opacity: 1; + scale: 1; + } + } +`; diff --git a/packages/webawesome/src/components/popup/popup.ts b/packages/webawesome/src/components/popup/popup.ts index 80ec2f254..cd95780e5 100644 --- a/packages/webawesome/src/components/popup/popup.ts +++ b/packages/webawesome/src/components/popup/popup.ts @@ -17,7 +17,7 @@ import { classMap } from 'lit/directives/class-map.js'; import { WaRepositionEvent } from '../../events/reposition.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import { LocalizeController } from '../../utilities/localize.js'; -import styles from './popup.css'; +import styles from './popup.styles.js'; export interface VirtualElement { getBoundingClientRect: () => DOMRect; diff --git a/packages/webawesome/src/components/progress-bar/progress-bar.css b/packages/webawesome/src/components/progress-bar/progress-bar.css deleted file mode 100644 index 821320cc4..000000000 --- a/packages/webawesome/src/components/progress-bar/progress-bar.css +++ /dev/null @@ -1,66 +0,0 @@ -:host { - --track-height: 1rem; - --track-color: var(--wa-color-neutral-fill-normal); - --indicator-color: var(--wa-color-brand-fill-loud); - - display: flex; -} - -.progress-bar { - flex: 1 1 auto; - display: flex; - position: relative; - overflow: hidden; - height: var(--track-height); - border-radius: var(--wa-border-radius-pill); - background-color: var(--track-color); - color: var(--wa-color-brand-on-loud); - font-size: var(--wa-font-size-s); -} - -.indicator { - width: var(--percentage); - display: flex; - align-items: center; - justify-content: center; - background-color: var(--indicator-color); - text-align: center; - white-space: nowrap; - overflow: hidden; - line-height: 1; - font-weight: var(--wa-font-weight-semibold); - transition: all var(--wa-transition-slow, 200ms) var(--wa-transition-easing, ease); - user-select: none; - -webkit-user-select: none; -} - -/* Indeterminate */ -:host([indeterminate]) .indicator { - position: absolute; - inset-block: 0; - inline-size: 50%; - animation: wa-progress-indeterminate 2.5s infinite cubic-bezier(0.37, 0, 0.63, 1); -} - -@media (forced-colors: active) { - .progress-bar { - outline: solid 1px SelectedItem; - background-color: var(--wa-color-surface-default); - } - - .indicator { - outline: solid 1px SelectedItem; - background-color: SelectedItem; - } -} - -@keyframes wa-progress-indeterminate { - 0% { - inset-inline-start: -50%; - } - - 75%, - 100% { - inset-inline-start: 100%; - } -} diff --git a/packages/webawesome/src/components/progress-bar/progress-bar.styles.ts b/packages/webawesome/src/components/progress-bar/progress-bar.styles.ts new file mode 100644 index 000000000..b2ed39885 --- /dev/null +++ b/packages/webawesome/src/components/progress-bar/progress-bar.styles.ts @@ -0,0 +1,70 @@ +import { css } from 'lit'; + +export default css` + :host { + --track-height: 1rem; + --track-color: var(--wa-color-neutral-fill-normal); + --indicator-color: var(--wa-color-brand-fill-loud); + + display: flex; + } + + .progress-bar { + flex: 1 1 auto; + display: flex; + position: relative; + overflow: hidden; + height: var(--track-height); + border-radius: var(--wa-border-radius-pill); + background-color: var(--track-color); + color: var(--wa-color-brand-on-loud); + font-size: var(--wa-font-size-s); + } + + .indicator { + width: var(--percentage); + display: flex; + align-items: center; + justify-content: center; + background-color: var(--indicator-color); + text-align: center; + white-space: nowrap; + overflow: hidden; + line-height: 1; + font-weight: var(--wa-font-weight-semibold); + transition: all var(--wa-transition-slow, 200ms) var(--wa-transition-easing, ease); + user-select: none; + -webkit-user-select: none; + } + + /* Indeterminate */ + :host([indeterminate]) .indicator { + position: absolute; + inset-block: 0; + inline-size: 50%; + animation: wa-progress-indeterminate 2.5s infinite cubic-bezier(0.37, 0, 0.63, 1); + } + + @media (forced-colors: active) { + .progress-bar { + outline: solid 1px SelectedItem; + background-color: var(--wa-color-surface-default); + } + + .indicator { + outline: solid 1px SelectedItem; + background-color: SelectedItem; + } + } + + @keyframes wa-progress-indeterminate { + 0% { + inset-inline-start: -50%; + } + + 75%, + 100% { + inset-inline-start: 100%; + } + } +`; diff --git a/packages/webawesome/src/components/progress-bar/progress-bar.ts b/packages/webawesome/src/components/progress-bar/progress-bar.ts index 7ef5a3dce..708c72f3a 100644 --- a/packages/webawesome/src/components/progress-bar/progress-bar.ts +++ b/packages/webawesome/src/components/progress-bar/progress-bar.ts @@ -5,7 +5,7 @@ import { ifDefined } from 'lit/directives/if-defined.js'; import { clamp } from '../../internal/math.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import { LocalizeController } from '../../utilities/localize.js'; -import styles from './progress-bar.css'; +import styles from './progress-bar.styles.js'; /** * @summary Progress bars are used to show the status of an ongoing operation. diff --git a/packages/webawesome/src/components/progress-ring/progress-ring.css b/packages/webawesome/src/components/progress-ring/progress-ring.css deleted file mode 100644 index 19f95b903..000000000 --- a/packages/webawesome/src/components/progress-ring/progress-ring.css +++ /dev/null @@ -1,64 +0,0 @@ -:host { - --size: 8rem; - --track-width: 0.25em; /* avoid using rems here */ - --track-color: var(--wa-color-neutral-fill-normal); - --indicator-width: var(--track-width); - --indicator-color: var(--wa-color-brand-fill-loud); - --indicator-transition-duration: 0.35s; - - display: inline-flex; -} - -.progress-ring { - display: inline-flex; - align-items: center; - justify-content: center; - position: relative; -} - -.image { - width: var(--size); - height: var(--size); - rotate: -90deg; - transform-origin: 50% 50%; -} - -.track, -.indicator { - --radius: calc(var(--size) / 2 - max(var(--track-width), var(--indicator-width)) * 0.5); - --circumference: calc(var(--radius) * 2 * 3.141592654); - - fill: none; - r: var(--radius); - cx: calc(var(--size) / 2); - cy: calc(var(--size) / 2); -} - -.track { - stroke: var(--track-color); - stroke-width: var(--track-width); -} - -.indicator { - stroke: var(--indicator-color); - stroke-width: var(--indicator-width); - stroke-linecap: round; - transition-property: stroke-dashoffset; - transition-duration: var(--indicator-transition-duration); - stroke-dasharray: var(--circumference) var(--circumference); - stroke-dashoffset: calc(var(--circumference) - var(--percentage) * var(--circumference)); -} - -.label { - display: flex; - align-items: center; - justify-content: center; - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - text-align: center; - user-select: none; - -webkit-user-select: none; -} diff --git a/packages/webawesome/src/components/progress-ring/progress-ring.styles.ts b/packages/webawesome/src/components/progress-ring/progress-ring.styles.ts new file mode 100644 index 000000000..9af84ea77 --- /dev/null +++ b/packages/webawesome/src/components/progress-ring/progress-ring.styles.ts @@ -0,0 +1,68 @@ +import { css } from 'lit'; + +export default css` + :host { + --size: 8rem; + --track-width: 0.25em; /* avoid using rems here */ + --track-color: var(--wa-color-neutral-fill-normal); + --indicator-width: var(--track-width); + --indicator-color: var(--wa-color-brand-fill-loud); + --indicator-transition-duration: 0.35s; + + display: inline-flex; + } + + .progress-ring { + display: inline-flex; + align-items: center; + justify-content: center; + position: relative; + } + + .image { + width: var(--size); + height: var(--size); + rotate: -90deg; + transform-origin: 50% 50%; + } + + .track, + .indicator { + --radius: calc(var(--size) / 2 - max(var(--track-width), var(--indicator-width)) * 0.5); + --circumference: calc(var(--radius) * 2 * 3.141592654); + + fill: none; + r: var(--radius); + cx: calc(var(--size) / 2); + cy: calc(var(--size) / 2); + } + + .track { + stroke: var(--track-color); + stroke-width: var(--track-width); + } + + .indicator { + stroke: var(--indicator-color); + stroke-width: var(--indicator-width); + stroke-linecap: round; + transition-property: stroke-dashoffset; + transition-duration: var(--indicator-transition-duration); + stroke-dasharray: var(--circumference) var(--circumference); + stroke-dashoffset: calc(var(--circumference) - var(--percentage) * var(--circumference)); + } + + .label { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + text-align: center; + user-select: none; + -webkit-user-select: none; + } +`; diff --git a/packages/webawesome/src/components/progress-ring/progress-ring.ts b/packages/webawesome/src/components/progress-ring/progress-ring.ts index 53ef182a8..93187de19 100644 --- a/packages/webawesome/src/components/progress-ring/progress-ring.ts +++ b/packages/webawesome/src/components/progress-ring/progress-ring.ts @@ -3,7 +3,7 @@ import { html } from 'lit'; import { customElement, property, query, state } from 'lit/decorators.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import { LocalizeController } from '../../utilities/localize.js'; -import styles from './progress-ring.css'; +import styles from './progress-ring.styles.js'; /** * @summary Progress rings are used to show the progress of a determinate operation in a circular fashion. diff --git a/packages/webawesome/src/components/qr-code/qr-code.css b/packages/webawesome/src/components/qr-code/qr-code.css deleted file mode 100644 index 2777d853f..000000000 --- a/packages/webawesome/src/components/qr-code/qr-code.css +++ /dev/null @@ -1,12 +0,0 @@ -:host { - --size: 128px; - display: inline-block; -} - -:host, -canvas { - max-width: var(--size); - max-height: var(--size); - width: var(--size); - height: var(--size); -} diff --git a/packages/webawesome/src/components/qr-code/qr-code.styles.ts b/packages/webawesome/src/components/qr-code/qr-code.styles.ts new file mode 100644 index 000000000..b790cc3b0 --- /dev/null +++ b/packages/webawesome/src/components/qr-code/qr-code.styles.ts @@ -0,0 +1,16 @@ +import { css } from 'lit'; + +export default css` + :host { + --size: 128px; + display: inline-block; + } + + :host, + canvas { + max-width: var(--size); + max-height: var(--size); + width: var(--size); + height: var(--size); + } +`; diff --git a/packages/webawesome/src/components/qr-code/qr-code.ts b/packages/webawesome/src/components/qr-code/qr-code.ts index dcc211857..a4247d394 100644 --- a/packages/webawesome/src/components/qr-code/qr-code.ts +++ b/packages/webawesome/src/components/qr-code/qr-code.ts @@ -4,7 +4,7 @@ import { customElement, property, query, state } from 'lit/decorators.js'; import type _QrCreator from 'qr-creator'; import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import styles from './qr-code.css'; +import styles from './qr-code.styles.js'; let QrCreator: _QrCreator.default; diff --git a/packages/webawesome/src/components/radio-group/radio-group.css b/packages/webawesome/src/components/radio-group/radio-group.css deleted file mode 100644 index 8b60d8ab2..000000000 --- a/packages/webawesome/src/components/radio-group/radio-group.css +++ /dev/null @@ -1,36 +0,0 @@ -:host { - display: block; -} - -.form-control { - position: relative; - border: none; - padding: 0; - margin: 0; -} - -.label { - padding: 0; -} - -.radio-group-required .label::after { - content: var(--wa-form-control-required-content); - margin-inline-start: var(--wa-form-control-required-content-offset); -} - -[part~='form-control-input'] { - display: flex; - flex-direction: column; - flex-wrap: wrap; - gap: 0; /* Radios handle their own spacing */ -} - -/* Horizontal */ -:host([orientation='horizontal']) [part~='form-control-input'] { - flex-direction: row; -} - -/* Help text */ -[part~='hint'] { - margin-block-start: 0.5em; -} diff --git a/packages/webawesome/src/components/radio-group/radio-group.styles.ts b/packages/webawesome/src/components/radio-group/radio-group.styles.ts new file mode 100644 index 000000000..e06ca4067 --- /dev/null +++ b/packages/webawesome/src/components/radio-group/radio-group.styles.ts @@ -0,0 +1,40 @@ +import { css } from 'lit'; + +export default css` + :host { + display: block; + } + + .form-control { + position: relative; + border: none; + padding: 0; + margin: 0; + } + + .label { + padding: 0; + } + + .radio-group-required .label::after { + content: var(--wa-form-control-required-content); + margin-inline-start: var(--wa-form-control-required-content-offset); + } + + [part~='form-control-input'] { + display: flex; + flex-direction: column; + flex-wrap: wrap; + gap: 0; /* Radios handle their own spacing */ + } + + /* Horizontal */ + :host([orientation='horizontal']) [part~='form-control-input'] { + flex-direction: row; + } + + /* Help text */ + [part~='hint'] { + margin-block-start: 0.5em; + } +`; diff --git a/packages/webawesome/src/components/radio-group/radio-group.ts b/packages/webawesome/src/components/radio-group/radio-group.ts index 7e39a0872..b202a226d 100644 --- a/packages/webawesome/src/components/radio-group/radio-group.ts +++ b/packages/webawesome/src/components/radio-group/radio-group.ts @@ -6,11 +6,11 @@ import { uniqueId } from '../../internal/math.js'; import { HasSlotController } from '../../internal/slot.js'; import { RequiredValidator } from '../../internal/validators/required-validator.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js'; -import formControlStyles from '../../styles/component/form-control.css'; -import sizeStyles from '../../styles/utilities/size.css'; +import formControlStyles from '../../styles/component/form-control.styles.js'; +import sizeStyles from '../../styles/component/size.styles.js'; import '../radio/radio.js'; import type WaRadio from '../radio/radio.js'; -import styles from './radio-group.css'; +import styles from './radio-group.styles.js'; /** * @summary Radio groups are used to group multiple [radios](/docs/components/radio) so they function as a single form control. diff --git a/packages/webawesome/src/components/radio/radio.css b/packages/webawesome/src/components/radio/radio.css deleted file mode 100644 index 159237479..000000000 --- a/packages/webawesome/src/components/radio/radio.css +++ /dev/null @@ -1,199 +0,0 @@ -:host { - --checked-icon-color: var(--wa-form-control-activated-color); - --checked-icon-scale: 0.7; - - color: var(--wa-form-control-value-color); - display: inline-flex; - flex-direction: row; - align-items: top; - font-family: inherit; - font-weight: var(--wa-form-control-value-font-weight); - line-height: var(--wa-form-control-value-line-height); - cursor: pointer; - user-select: none; - -webkit-user-select: none; -} - -:host(:focus) { - outline: none; -} - -/* When the control isn't checked, hide the circle for Windows High Contrast mode a11y */ -:host(:not(:state(checked))) svg circle { - opacity: 0; -} - -[part~='label'] { - display: inline; -} - -[part~='hint'] { - margin-block-start: 0.5em; -} - -/* Default spacing for default appearance radios */ -:host([appearance='default']) { - margin-block: 0.375em; /* Half of the original 0.75em gap on each side */ -} - -:host([appearance='default'][data-wa-radio-horizontal]) { - margin-block: 0; - margin-inline: 0.5em; /* Half of the original 1em gap on each side */ -} - -/* Remove margin from first/last items to prevent extra space */ -:host([appearance='default'][data-wa-radio-first]) { - margin-block-start: 0; - margin-inline-start: 0; -} - -:host([appearance='default'][data-wa-radio-last]) { - margin-block-end: 0; - margin-inline-end: 0; -} - -/* Button appearance have no spacing, they get handled by the overlap margins below */ -:host([appearance='button']) { - margin: 0; - align-items: center; - min-height: var(--wa-form-control-height); - background-color: var(--wa-color-surface-default); - border: var(--wa-form-control-border-width) var(--wa-form-control-border-style) var(--wa-form-control-border-color); - border-radius: var(--wa-border-radius-m); - padding: 0 var(--wa-form-control-padding-inline); - transition: - background-color var(--wa-transition-fast), - border-color var(--wa-transition-fast); -} - -/* Default appearance */ -:host([appearance='default']) { - .control { - flex: 0 0 auto; - position: relative; - display: inline-flex; - align-items: center; - justify-content: center; - width: var(--wa-form-control-toggle-size); - height: var(--wa-form-control-toggle-size); - border-color: var(--wa-form-control-border-color); - border-radius: 50%; - border-style: var(--wa-form-control-border-style); - border-width: var(--wa-form-control-border-width); - background-color: var(--wa-form-control-background-color); - color: transparent; - transition: - background var(--wa-transition-normal), - border-color var(--wa-transition-fast), - box-shadow var(--wa-transition-fast), - color var(--wa-transition-fast); - transition-timing-function: var(--wa-transition-easing); - - margin-inline-end: 0.5em; - } - - .checked-icon { - display: flex; - fill: currentColor; - width: var(--wa-form-control-toggle-size); - height: var(--wa-form-control-toggle-size); - scale: var(--checked-icon-scale); - } -} - -/* Button appearance */ -:host([appearance='button']) { - .control { - display: none; - } -} - -/* Checked */ -:host(:state(checked)) .control { - color: var(--checked-icon-color); - border-color: var(--wa-form-control-activated-color); - background-color: var(--wa-form-control-background-color); -} - -/* Focus */ -:host(:focus-visible) .control { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); -} - -/* Disabled */ -:host(:state(disabled)) { - opacity: 0.5; - cursor: not-allowed; -} - -/* Horizontal grouping - remove inner border radius */ -:host([appearance='button'][data-wa-radio-horizontal][data-wa-radio-inner]) { - border-radius: 0; -} - -:host([appearance='button'][data-wa-radio-horizontal][data-wa-radio-first]) { - border-start-end-radius: 0; - border-end-end-radius: 0; -} - -:host([appearance='button'][data-wa-radio-horizontal][data-wa-radio-last]) { - border-start-start-radius: 0; - border-end-start-radius: 0; -} - -/* Vertical grouping - remove inner border radius */ -:host([appearance='button'][data-wa-radio-vertical][data-wa-radio-inner]) { - border-radius: 0; -} - -:host([appearance='button'][data-wa-radio-vertical][data-wa-radio-first]) { - border-end-start-radius: 0; - border-end-end-radius: 0; -} - -:host([appearance='button'][data-wa-radio-vertical][data-wa-radio-last]) { - border-start-start-radius: 0; - border-start-end-radius: 0; -} - -@media (hover: hover) { - :host([appearance='button']:hover:not(:state(disabled), :state(checked))) { - background-color: color-mix(in srgb, var(--wa-color-surface-default) 95%, var(--wa-color-mix-hover)); - } -} - -:host([appearance='button']:focus-visible) { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); -} - -:host([appearance='button']:state(checked)) { - border-color: var(--wa-form-control-activated-color); - background-color: var(--wa-color-brand-fill-quiet); -} - -:host([appearance='button']:state(checked):focus-visible) { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); -} - -/* Button overlap margins */ -:host([appearance='button'][data-wa-radio-horizontal]:not([data-wa-radio-first])) { - margin-inline-start: calc(-1 * var(--wa-form-control-border-width)); -} - -:host([appearance='button'][data-wa-radio-vertical]:not([data-wa-radio-first])) { - margin-block-start: calc(-1 * var(--wa-form-control-border-width)); -} - -/* Ensure interactive states are visible above adjacent buttons */ -:host([appearance='button']:hover), -:host([appearance='button']:state(checked)) { - position: relative; - z-index: 1; -} - -:host([appearance='button']:focus-visible) { - z-index: 2; -} diff --git a/packages/webawesome/src/components/radio/radio.styles.ts b/packages/webawesome/src/components/radio/radio.styles.ts new file mode 100644 index 000000000..2ec3bcfdc --- /dev/null +++ b/packages/webawesome/src/components/radio/radio.styles.ts @@ -0,0 +1,203 @@ +import { css } from 'lit'; + +export default css` + :host { + --checked-icon-color: var(--wa-form-control-activated-color); + --checked-icon-scale: 0.7; + + color: var(--wa-form-control-value-color); + display: inline-flex; + flex-direction: row; + align-items: top; + font-family: inherit; + font-weight: var(--wa-form-control-value-font-weight); + line-height: var(--wa-form-control-value-line-height); + cursor: pointer; + user-select: none; + -webkit-user-select: none; + } + + :host(:focus) { + outline: none; + } + + /* When the control isn't checked, hide the circle for Windows High Contrast mode a11y */ + :host(:not(:state(checked))) svg circle { + opacity: 0; + } + + [part~='label'] { + display: inline; + } + + [part~='hint'] { + margin-block-start: 0.5em; + } + + /* Default spacing for default appearance radios */ + :host([appearance='default']) { + margin-block: 0.375em; /* Half of the original 0.75em gap on each side */ + } + + :host([appearance='default'][data-wa-radio-horizontal]) { + margin-block: 0; + margin-inline: 0.5em; /* Half of the original 1em gap on each side */ + } + + /* Remove margin from first/last items to prevent extra space */ + :host([appearance='default'][data-wa-radio-first]) { + margin-block-start: 0; + margin-inline-start: 0; + } + + :host([appearance='default'][data-wa-radio-last]) { + margin-block-end: 0; + margin-inline-end: 0; + } + + /* Button appearance have no spacing, they get handled by the overlap margins below */ + :host([appearance='button']) { + margin: 0; + align-items: center; + min-height: var(--wa-form-control-height); + background-color: var(--wa-color-surface-default); + border: var(--wa-form-control-border-width) var(--wa-form-control-border-style) var(--wa-form-control-border-color); + border-radius: var(--wa-border-radius-m); + padding: 0 var(--wa-form-control-padding-inline); + transition: + background-color var(--wa-transition-fast), + border-color var(--wa-transition-fast); + } + + /* Default appearance */ + :host([appearance='default']) { + .control { + flex: 0 0 auto; + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + width: var(--wa-form-control-toggle-size); + height: var(--wa-form-control-toggle-size); + border-color: var(--wa-form-control-border-color); + border-radius: 50%; + border-style: var(--wa-form-control-border-style); + border-width: var(--wa-form-control-border-width); + background-color: var(--wa-form-control-background-color); + color: transparent; + transition: + background var(--wa-transition-normal), + border-color var(--wa-transition-fast), + box-shadow var(--wa-transition-fast), + color var(--wa-transition-fast); + transition-timing-function: var(--wa-transition-easing); + + margin-inline-end: 0.5em; + } + + .checked-icon { + display: flex; + fill: currentColor; + width: var(--wa-form-control-toggle-size); + height: var(--wa-form-control-toggle-size); + scale: var(--checked-icon-scale); + } + } + + /* Button appearance */ + :host([appearance='button']) { + .control { + display: none; + } + } + + /* Checked */ + :host(:state(checked)) .control { + color: var(--checked-icon-color); + border-color: var(--wa-form-control-activated-color); + background-color: var(--wa-form-control-background-color); + } + + /* Focus */ + :host(:focus-visible) .control { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + + /* Disabled */ + :host(:state(disabled)) { + opacity: 0.5; + cursor: not-allowed; + } + + /* Horizontal grouping - remove inner border radius */ + :host([appearance='button'][data-wa-radio-horizontal][data-wa-radio-inner]) { + border-radius: 0; + } + + :host([appearance='button'][data-wa-radio-horizontal][data-wa-radio-first]) { + border-start-end-radius: 0; + border-end-end-radius: 0; + } + + :host([appearance='button'][data-wa-radio-horizontal][data-wa-radio-last]) { + border-start-start-radius: 0; + border-end-start-radius: 0; + } + + /* Vertical grouping - remove inner border radius */ + :host([appearance='button'][data-wa-radio-vertical][data-wa-radio-inner]) { + border-radius: 0; + } + + :host([appearance='button'][data-wa-radio-vertical][data-wa-radio-first]) { + border-end-start-radius: 0; + border-end-end-radius: 0; + } + + :host([appearance='button'][data-wa-radio-vertical][data-wa-radio-last]) { + border-start-start-radius: 0; + border-start-end-radius: 0; + } + + @media (hover: hover) { + :host([appearance='button']:hover:not(:state(disabled), :state(checked))) { + background-color: color-mix(in srgb, var(--wa-color-surface-default) 95%, var(--wa-color-mix-hover)); + } + } + + :host([appearance='button']:focus-visible) { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + + :host([appearance='button']:state(checked)) { + border-color: var(--wa-form-control-activated-color); + background-color: var(--wa-color-brand-fill-quiet); + } + + :host([appearance='button']:state(checked):focus-visible) { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + + /* Button overlap margins */ + :host([appearance='button'][data-wa-radio-horizontal]:not([data-wa-radio-first])) { + margin-inline-start: calc(-1 * var(--wa-form-control-border-width)); + } + + :host([appearance='button'][data-wa-radio-vertical]:not([data-wa-radio-first])) { + margin-block-start: calc(-1 * var(--wa-form-control-border-width)); + } + + /* Ensure interactive states are visible above adjacent buttons */ + :host([appearance='button']:hover), + :host([appearance='button']:state(checked)) { + position: relative; + z-index: 1; + } + + :host([appearance='button']:focus-visible) { + z-index: 2; + } +`; diff --git a/packages/webawesome/src/components/radio/radio.ts b/packages/webawesome/src/components/radio/radio.ts index 3898b3b20..d2b5377f4 100644 --- a/packages/webawesome/src/components/radio/radio.ts +++ b/packages/webawesome/src/components/radio/radio.ts @@ -2,10 +2,10 @@ import type { PropertyValues } from 'lit'; import { html, isServer } from 'lit'; import { customElement, property, state } from 'lit/decorators.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js'; -import formControlStyles from '../../styles/component/form-control.css'; -import sizeStyles from '../../styles/utilities/size.css'; +import formControlStyles from '../../styles/component/form-control.styles.js'; +import sizeStyles from '../../styles/component/size.styles.js'; import '../icon/icon.js'; -import styles from './radio.css'; +import styles from './radio.styles.js'; /** * @summary Radios allow the user to select a single option from a group. diff --git a/packages/webawesome/src/components/rating/rating.css b/packages/webawesome/src/components/rating/rating.css deleted file mode 100644 index 606a2f263..000000000 --- a/packages/webawesome/src/components/rating/rating.css +++ /dev/null @@ -1,85 +0,0 @@ -:host { - --symbol-color: var(--wa-color-neutral-on-quiet); - --symbol-color-active: var(--wa-color-yellow-70); - --symbol-spacing: 0.125em; - - display: inline-flex; -} - -.rating { - position: relative; - display: inline-flex; - border-radius: var(--wa-border-radius-m); - vertical-align: middle; -} - -.rating:focus { - outline: none; -} - -.rating:focus-visible { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); -} - -.symbols { - display: inline-flex; - gap: 0.125em; - position: relative; - line-height: 0; - color: var(--symbol-color); - white-space: nowrap; - cursor: pointer; -} - -.symbols > * { - padding: var(--symbol-spacing); -} - -.symbol-active, -.partial-filled { - color: var(--symbol-color-active); -} - -.partial-symbol-container { - position: relative; -} - -.partial-filled { - position: absolute; - top: var(--symbol-spacing); - left: var(--symbol-spacing); -} - -.symbol { - transition: scale var(--wa-transition-normal) var(--wa-transition-easing); - pointer-events: none; -} - -.symbol-hover { - scale: 1.2; -} - -.rating-readonly .symbols { - cursor: default; -} - -:host([disabled]) .symbol-hover, -.rating-readonly .symbol-hover { - scale: none; -} - -:host([disabled]) { - opacity: 0.5; -} - -:host([disabled]) .symbols { - cursor: not-allowed; -} - -/* Forced colors mode */ -@media (forced-colors: active) { - .symbol-active { - color: SelectedItem; - } -} diff --git a/packages/webawesome/src/components/rating/rating.styles.ts b/packages/webawesome/src/components/rating/rating.styles.ts new file mode 100644 index 000000000..7c6eeecab --- /dev/null +++ b/packages/webawesome/src/components/rating/rating.styles.ts @@ -0,0 +1,89 @@ +import { css } from 'lit'; + +export default css` + :host { + --symbol-color: var(--wa-color-neutral-on-quiet); + --symbol-color-active: var(--wa-color-yellow-70); + --symbol-spacing: 0.125em; + + display: inline-flex; + } + + .rating { + position: relative; + display: inline-flex; + border-radius: var(--wa-border-radius-m); + vertical-align: middle; + } + + .rating:focus { + outline: none; + } + + .rating:focus-visible { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + + .symbols { + display: inline-flex; + gap: 0.125em; + position: relative; + line-height: 0; + color: var(--symbol-color); + white-space: nowrap; + cursor: pointer; + } + + .symbols > * { + padding: var(--symbol-spacing); + } + + .symbol-active, + .partial-filled { + color: var(--symbol-color-active); + } + + .partial-symbol-container { + position: relative; + } + + .partial-filled { + position: absolute; + top: var(--symbol-spacing); + left: var(--symbol-spacing); + } + + .symbol { + transition: scale var(--wa-transition-normal) var(--wa-transition-easing); + pointer-events: none; + } + + .symbol-hover { + scale: 1.2; + } + + .rating-readonly .symbols { + cursor: default; + } + + :host([disabled]) .symbol-hover, + .rating-readonly .symbol-hover { + scale: none; + } + + :host([disabled]) { + opacity: 0.5; + } + + :host([disabled]) .symbols { + cursor: not-allowed; + } + + /* Forced colors mode */ + @media (forced-colors: active) { + .symbol-active { + color: SelectedItem; + } + } +`; diff --git a/packages/webawesome/src/components/rating/rating.ts b/packages/webawesome/src/components/rating/rating.ts index d8615dd58..ebbbe3de4 100644 --- a/packages/webawesome/src/components/rating/rating.ts +++ b/packages/webawesome/src/components/rating/rating.ts @@ -7,10 +7,10 @@ import { WaHoverEvent } from '../../events/hover.js'; import { clamp } from '../../internal/math.js'; import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import sizeStyles from '../../styles/utilities/size.css'; +import sizeStyles from '../../styles/component/size.styles.js'; import { LocalizeController } from '../../utilities/localize.js'; import '../icon/icon.js'; -import styles from './rating.css'; +import styles from './rating.styles.js'; /** * @summary Ratings give users a way to quickly view and provide feedback. diff --git a/packages/webawesome/src/components/resize-observer/resize-observer.css b/packages/webawesome/src/components/resize-observer/resize-observer.css deleted file mode 100644 index 92d692cdd..000000000 --- a/packages/webawesome/src/components/resize-observer/resize-observer.css +++ /dev/null @@ -1,3 +0,0 @@ -:host { - display: contents; -} diff --git a/packages/webawesome/src/components/resize-observer/resize-observer.styles.ts b/packages/webawesome/src/components/resize-observer/resize-observer.styles.ts new file mode 100644 index 000000000..1ef4bf6f3 --- /dev/null +++ b/packages/webawesome/src/components/resize-observer/resize-observer.styles.ts @@ -0,0 +1,7 @@ +import { css } from 'lit'; + +export default css` + :host { + display: contents; + } +`; diff --git a/packages/webawesome/src/components/resize-observer/resize-observer.ts b/packages/webawesome/src/components/resize-observer/resize-observer.ts index b26d0e5e9..e77727abb 100644 --- a/packages/webawesome/src/components/resize-observer/resize-observer.ts +++ b/packages/webawesome/src/components/resize-observer/resize-observer.ts @@ -3,7 +3,7 @@ import { customElement, property } from 'lit/decorators.js'; import { WaResizeEvent } from '../../events/resize.js'; import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import styles from './resize-observer.css'; +import styles from './resize-observer.styles.js'; /** * @summary The Resize Observer component offers a thin, declarative interface to the [`ResizeObserver API`](https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver). diff --git a/packages/webawesome/src/components/scroller/scroller.css b/packages/webawesome/src/components/scroller/scroller.css deleted file mode 100644 index 3b4080c9e..000000000 --- a/packages/webawesome/src/components/scroller/scroller.css +++ /dev/null @@ -1,125 +0,0 @@ -:host { - --shadow-color: var(--wa-color-surface-default); - --shadow-size: 2rem; - - /* private (defined dynamically) */ - --start-shadow-opacity: 0; - --end-shadow-opacity: 0; - - display: block; - position: relative; - max-width: 100%; - isolation: isolate; -} - -:host([orientation='vertical']) { - display: flex; - flex-direction: column; - height: 100%; -} - -#content { - z-index: 1; /* below shadows */ - border-radius: inherit; - scroll-behavior: smooth; - scrollbar-width: thin; - - /* Prevent text in mobile Safari from being larger when the container width larger than the viewport */ - -webkit-text-size-adjust: 100%; - - &:focus { - outline: none; - } - - &:focus-visible { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); - } -} - -:host([without-scrollbar]) #content { - scrollbar-width: none; -} - -:host([orientation='horizontal']) #content { - overflow-x: auto; - overflow-y: hidden; -} - -:host([orientation='vertical']) #content { - flex: 1 1 auto; - min-height: 0; /* This is crucial for flex children to respect overflow */ - overflow-x: hidden; - overflow-y: auto; -} - -#start-shadow, -#end-shadow { - z-index: 2; -} - -#start-shadow { - opacity: var(--start-shadow-opacity); -} - -#end-shadow { - opacity: var(--end-shadow-opacity); -} - -/* Horizontal shadows */ -:host([orientation='horizontal']) { - #start-shadow, - #end-shadow { - position: absolute; - top: 0; - bottom: 0; - width: var(--shadow-size); - pointer-events: none; - } - - #start-shadow { - &:dir(ltr) { - left: 0; - background: linear-gradient(to right, var(--shadow-color), transparent 100%); - } - - &:dir(rtl) { - right: 0; - background: linear-gradient(to left, var(--shadow-color), transparent 100%); - } - } - - #end-shadow { - &:dir(ltr) { - right: 0; - background: linear-gradient(to left, var(--shadow-color), transparent 100%); - } - - &:dir(rtl) { - left: 0; - background: linear-gradient(to right, var(--shadow-color), transparent 100%); - } - } -} - -/* Vertical shadows */ -:host([orientation='vertical']) { - #start-shadow, - #end-shadow { - position: absolute; - right: 0; - left: 0; - height: var(--shadow-size); - pointer-events: none; - } - - #start-shadow { - top: 0; - background: linear-gradient(to bottom, var(--shadow-color), transparent 100%); - } - - #end-shadow { - bottom: 0; - background: linear-gradient(to top, var(--shadow-color), transparent 100%); - } -} diff --git a/packages/webawesome/src/components/scroller/scroller.styles.ts b/packages/webawesome/src/components/scroller/scroller.styles.ts new file mode 100644 index 000000000..a57493291 --- /dev/null +++ b/packages/webawesome/src/components/scroller/scroller.styles.ts @@ -0,0 +1,129 @@ +import { css } from 'lit'; + +export default css` + :host { + --shadow-color: var(--wa-color-surface-default); + --shadow-size: 2rem; + + /* private (defined dynamically) */ + --start-shadow-opacity: 0; + --end-shadow-opacity: 0; + + display: block; + position: relative; + max-width: 100%; + isolation: isolate; + } + + :host([orientation='vertical']) { + display: flex; + flex-direction: column; + height: 100%; + } + + #content { + z-index: 1; /* below shadows */ + border-radius: inherit; + scroll-behavior: smooth; + scrollbar-width: thin; + + /* Prevent text in mobile Safari from being larger when the container width larger than the viewport */ + -webkit-text-size-adjust: 100%; + + &:focus { + outline: none; + } + + &:focus-visible { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + } + + :host([without-scrollbar]) #content { + scrollbar-width: none; + } + + :host([orientation='horizontal']) #content { + overflow-x: auto; + overflow-y: hidden; + } + + :host([orientation='vertical']) #content { + flex: 1 1 auto; + min-height: 0; /* This is crucial for flex children to respect overflow */ + overflow-x: hidden; + overflow-y: auto; + } + + #start-shadow, + #end-shadow { + z-index: 2; + } + + #start-shadow { + opacity: var(--start-shadow-opacity); + } + + #end-shadow { + opacity: var(--end-shadow-opacity); + } + + /* Horizontal shadows */ + :host([orientation='horizontal']) { + #start-shadow, + #end-shadow { + position: absolute; + top: 0; + bottom: 0; + width: var(--shadow-size); + pointer-events: none; + } + + #start-shadow { + &:dir(ltr) { + left: 0; + background: linear-gradient(to right, var(--shadow-color), transparent 100%); + } + + &:dir(rtl) { + right: 0; + background: linear-gradient(to left, var(--shadow-color), transparent 100%); + } + } + + #end-shadow { + &:dir(ltr) { + right: 0; + background: linear-gradient(to left, var(--shadow-color), transparent 100%); + } + + &:dir(rtl) { + left: 0; + background: linear-gradient(to right, var(--shadow-color), transparent 100%); + } + } + } + + /* Vertical shadows */ + :host([orientation='vertical']) { + #start-shadow, + #end-shadow { + position: absolute; + right: 0; + left: 0; + height: var(--shadow-size); + pointer-events: none; + } + + #start-shadow { + top: 0; + background: linear-gradient(to bottom, var(--shadow-color), transparent 100%); + } + + #end-shadow { + bottom: 0; + background: linear-gradient(to top, var(--shadow-color), transparent 100%); + } + } +`; diff --git a/packages/webawesome/src/components/scroller/scroller.ts b/packages/webawesome/src/components/scroller/scroller.ts index 6f0db2e5b..8c5f7dff8 100644 --- a/packages/webawesome/src/components/scroller/scroller.ts +++ b/packages/webawesome/src/components/scroller/scroller.ts @@ -2,7 +2,7 @@ import { html } from 'lit'; import { customElement, eventOptions, property, query, state } from 'lit/decorators.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import { LocalizeController } from '../../utilities/localize.js'; -import styles from './scroller.css'; +import styles from './scroller.styles.js'; /** * @summary Scrollers create an accessible container while providing visual cues that help users identify and navigate diff --git a/packages/webawesome/src/components/select/select.css b/packages/webawesome/src/components/select/select.css deleted file mode 100644 index 6318fb590..000000000 --- a/packages/webawesome/src/components/select/select.css +++ /dev/null @@ -1,272 +0,0 @@ -:host { - --tag-max-size: 10ch; - --show-duration: 100ms; - --hide-duration: 100ms; -} - -/* Add ellipses to multi select options */ -:host wa-tag::part(content) { - display: initial; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - max-width: var(--tag-max-size); -} - -:host .disabled [part~='combobox'] { - opacity: 0.5; - cursor: not-allowed; - outline: none; -} - -:host .enabled:is(.open, :focus-within) [part~='combobox'] { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); -} - -/** The popup */ -.select { - flex: 1 1 auto; - display: inline-flex; - width: 100%; - position: relative; - vertical-align: middle; - - /* Pass through from select to the popup */ - --show-duration: inherit; - --hide-duration: inherit; - - &::part(popup) { - z-index: 900; - } - - &[data-current-placement^='top']::part(popup) { - transform-origin: bottom; - } - - &[data-current-placement^='bottom']::part(popup) { - transform-origin: top; - } -} - -/* Combobox */ -.combobox { - flex: 1; - display: flex; - width: 100%; - min-width: 0; - align-items: center; - justify-content: start; - - min-height: var(--wa-form-control-height); - - background-color: var(--wa-form-control-background-color); - border-color: var(--wa-form-control-border-color); - border-radius: var(--wa-form-control-border-radius); - border-style: var(--wa-form-control-border-style); - border-width: var(--wa-form-control-border-width); - color: var(--wa-form-control-value-color); - cursor: pointer; - font-family: inherit; - font-weight: var(--wa-form-control-value-font-weight); - line-height: var(--wa-form-control-value-line-height); - overflow: hidden; - padding: 0 var(--wa-form-control-padding-inline); - position: relative; - vertical-align: middle; - transition: - background-color var(--wa-transition-normal), - border var(--wa-transition-normal), - outline var(--wa-transition-fast); - transition-timing-function: var(--wa-transition-easing); - - :host([multiple]) .select:not(.placeholder-visible) & { - padding-inline-start: 0; - padding-block: calc(var(--wa-form-control-height) * 0.1 - var(--wa-form-control-border-width)); - } - - /* Pills */ - :host([pill]) & { - border-radius: var(--wa-border-radius-pill); - } -} - -/* Appearance modifiers */ -:host([appearance='outlined']) .combobox { - background-color: var(--wa-form-control-background-color); - border-color: var(--wa-form-control-border-color); -} - -:host([appearance='filled']) .combobox { - background-color: var(--wa-color-neutral-fill-quiet); - border-color: var(--wa-color-neutral-fill-quiet); -} - -:host([appearance='filled-outlined']) .combobox { - background-color: var(--wa-color-neutral-fill-quiet); - border-color: var(--wa-form-control-border-color); -} - -.display-input { - position: relative; - width: 100%; - font: inherit; - border: none; - background: none; - line-height: var(--wa-form-control-value-line-height); - color: var(--wa-form-control-value-color); - cursor: inherit; - overflow: hidden; - padding: 0; - margin: 0; - -webkit-appearance: none; - - &:focus { - outline: none; - } - - &::placeholder { - color: var(--wa-form-control-placeholder-color); - } -} - -/* Visually hide the display input when multiple is enabled */ -:host([multiple]) .select:not(.placeholder-visible) .display-input { - position: absolute; - z-index: -1; - top: 0; - left: 0; - width: 100%; - height: 100%; - opacity: 0; -} - -.value-input { - position: absolute; - z-index: -1; - top: 0; - left: 0; - width: 100%; - height: 100%; - opacity: 0; - padding: 0; - margin: 0; -} - -.tags { - display: flex; - flex: 1; - align-items: center; - flex-wrap: wrap; - margin-inline-start: 0.25em; - gap: 0.25em; - - &::slotted(wa-tag) { - cursor: pointer !important; - } - - .disabled &, - .disabled &::slotted(wa-tag) { - cursor: not-allowed !important; - } -} - -/* Start and End */ - -.start, -.end { - flex: 0; - display: inline-flex; - align-items: center; - color: var(--wa-color-neutral-on-quiet); -} - -.end::slotted(*) { - margin-inline-start: var(--wa-form-control-padding-inline); -} - -.start::slotted(*) { - margin-inline-end: var(--wa-form-control-padding-inline); -} - -:host([multiple]) .start::slotted(*) { - margin-inline: var(--wa-form-control-padding-inline); -} - -/* Clear button */ -[part~='clear-button'] { - display: inline-flex; - align-items: center; - justify-content: center; - font-size: inherit; - color: var(--wa-color-neutral-on-quiet); - border: none; - background: none; - padding: 0; - transition: color var(--wa-transition-normal); - cursor: pointer; - margin-inline-start: var(--wa-form-control-padding-inline); - - &:focus { - outline: none; - } - - @media (hover: hover) { - &:hover { - color: color-mix(in oklab, currentColor, var(--wa-color-mix-hover)); - } - } - - &:active { - color: color-mix(in oklab, currentColor, var(--wa-color-mix-active)); - } -} - -/* Expand icon */ -.expand-icon { - flex: 0 0 auto; - display: flex; - align-items: center; - color: var(--wa-color-neutral-on-quiet); - transition: rotate var(--wa-transition-slow) ease; - rotate: 0deg; - margin-inline-start: var(--wa-form-control-padding-inline); - - .open & { - rotate: -180deg; - } -} - -/* Listbox */ -.listbox { - display: block; - position: relative; - font: inherit; - box-shadow: var(--wa-shadow-m); - background: var(--wa-color-surface-raised); - border-color: var(--wa-color-surface-border); - border-radius: var(--wa-border-radius-m); - border-style: var(--wa-border-style); - border-width: var(--wa-border-width-s); - padding-block: 0.5em; - padding-inline: 0; - overflow: auto; - overscroll-behavior: none; - - /* Make sure it adheres to the popup's auto size */ - max-width: var(--auto-size-available-width); - max-height: var(--auto-size-available-height); - - &::slotted(wa-divider) { - --spacing: 0.5em; - } -} - -slot:not([name])::slotted(small) { - display: block; - font-size: var(--wa-font-size-smaller); - font-weight: var(--wa-font-weight-semibold); - color: var(--wa-color-text-quiet); - padding-block: 0.5em; - padding-inline: 2.25em; -} diff --git a/packages/webawesome/src/components/select/select.styles.ts b/packages/webawesome/src/components/select/select.styles.ts new file mode 100644 index 000000000..b837ebd5b --- /dev/null +++ b/packages/webawesome/src/components/select/select.styles.ts @@ -0,0 +1,276 @@ +import { css } from 'lit'; + +export default css` + :host { + --tag-max-size: 10ch; + --show-duration: 100ms; + --hide-duration: 100ms; + } + + /* Add ellipses to multi select options */ + :host wa-tag::part(content) { + display: initial; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + max-width: var(--tag-max-size); + } + + :host .disabled [part~='combobox'] { + opacity: 0.5; + cursor: not-allowed; + outline: none; + } + + :host .enabled:is(.open, :focus-within) [part~='combobox'] { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + + /** The popup */ + .select { + flex: 1 1 auto; + display: inline-flex; + width: 100%; + position: relative; + vertical-align: middle; + + /* Pass through from select to the popup */ + --show-duration: inherit; + --hide-duration: inherit; + + &::part(popup) { + z-index: 900; + } + + &[data-current-placement^='top']::part(popup) { + transform-origin: bottom; + } + + &[data-current-placement^='bottom']::part(popup) { + transform-origin: top; + } + } + + /* Combobox */ + .combobox { + flex: 1; + display: flex; + width: 100%; + min-width: 0; + align-items: center; + justify-content: start; + + min-height: var(--wa-form-control-height); + + background-color: var(--wa-form-control-background-color); + border-color: var(--wa-form-control-border-color); + border-radius: var(--wa-form-control-border-radius); + border-style: var(--wa-form-control-border-style); + border-width: var(--wa-form-control-border-width); + color: var(--wa-form-control-value-color); + cursor: pointer; + font-family: inherit; + font-weight: var(--wa-form-control-value-font-weight); + line-height: var(--wa-form-control-value-line-height); + overflow: hidden; + padding: 0 var(--wa-form-control-padding-inline); + position: relative; + vertical-align: middle; + transition: + background-color var(--wa-transition-normal), + border var(--wa-transition-normal), + outline var(--wa-transition-fast); + transition-timing-function: var(--wa-transition-easing); + + :host([multiple]) .select:not(.placeholder-visible) & { + padding-inline-start: 0; + padding-block: calc(var(--wa-form-control-height) * 0.1 - var(--wa-form-control-border-width)); + } + + /* Pills */ + :host([pill]) & { + border-radius: var(--wa-border-radius-pill); + } + } + + /* Appearance modifiers */ + :host([appearance='outlined']) .combobox { + background-color: var(--wa-form-control-background-color); + border-color: var(--wa-form-control-border-color); + } + + :host([appearance='filled']) .combobox { + background-color: var(--wa-color-neutral-fill-quiet); + border-color: var(--wa-color-neutral-fill-quiet); + } + + :host([appearance='filled-outlined']) .combobox { + background-color: var(--wa-color-neutral-fill-quiet); + border-color: var(--wa-form-control-border-color); + } + + .display-input { + position: relative; + width: 100%; + font: inherit; + border: none; + background: none; + line-height: var(--wa-form-control-value-line-height); + color: var(--wa-form-control-value-color); + cursor: inherit; + overflow: hidden; + padding: 0; + margin: 0; + -webkit-appearance: none; + + &:focus { + outline: none; + } + + &::placeholder { + color: var(--wa-form-control-placeholder-color); + } + } + + /* Visually hide the display input when multiple is enabled */ + :host([multiple]) .select:not(.placeholder-visible) .display-input { + position: absolute; + z-index: -1; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0; + } + + .value-input { + position: absolute; + z-index: -1; + top: 0; + left: 0; + width: 100%; + height: 100%; + opacity: 0; + padding: 0; + margin: 0; + } + + .tags { + display: flex; + flex: 1; + align-items: center; + flex-wrap: wrap; + margin-inline-start: 0.25em; + gap: 0.25em; + + &::slotted(wa-tag) { + cursor: pointer !important; + } + + .disabled &, + .disabled &::slotted(wa-tag) { + cursor: not-allowed !important; + } + } + + /* Start and End */ + + .start, + .end { + flex: 0; + display: inline-flex; + align-items: center; + color: var(--wa-color-neutral-on-quiet); + } + + .end::slotted(*) { + margin-inline-start: var(--wa-form-control-padding-inline); + } + + .start::slotted(*) { + margin-inline-end: var(--wa-form-control-padding-inline); + } + + :host([multiple]) .start::slotted(*) { + margin-inline: var(--wa-form-control-padding-inline); + } + + /* Clear button */ + [part~='clear-button'] { + display: inline-flex; + align-items: center; + justify-content: center; + font-size: inherit; + color: var(--wa-color-neutral-on-quiet); + border: none; + background: none; + padding: 0; + transition: color var(--wa-transition-normal); + cursor: pointer; + margin-inline-start: var(--wa-form-control-padding-inline); + + &:focus { + outline: none; + } + + @media (hover: hover) { + &:hover { + color: color-mix(in oklab, currentColor, var(--wa-color-mix-hover)); + } + } + + &:active { + color: color-mix(in oklab, currentColor, var(--wa-color-mix-active)); + } + } + + /* Expand icon */ + .expand-icon { + flex: 0 0 auto; + display: flex; + align-items: center; + color: var(--wa-color-neutral-on-quiet); + transition: rotate var(--wa-transition-slow) ease; + rotate: 0deg; + margin-inline-start: var(--wa-form-control-padding-inline); + + .open & { + rotate: -180deg; + } + } + + /* Listbox */ + .listbox { + display: block; + position: relative; + font: inherit; + box-shadow: var(--wa-shadow-m); + background: var(--wa-color-surface-raised); + border-color: var(--wa-color-surface-border); + border-radius: var(--wa-border-radius-m); + border-style: var(--wa-border-style); + border-width: var(--wa-border-width-s); + padding-block: 0.5em; + padding-inline: 0; + overflow: auto; + overscroll-behavior: none; + + /* Make sure it adheres to the popup's auto size */ + max-width: var(--auto-size-available-width); + max-height: var(--auto-size-available-height); + + &::slotted(wa-divider) { + --spacing: 0.5em; + } + } + + slot:not([name])::slotted(small) { + display: block; + font-size: var(--wa-font-size-smaller); + font-weight: var(--wa-font-weight-semibold); + color: var(--wa-color-text-quiet); + padding-block: 0.5em; + padding-inline: 2.25em; + } +`; diff --git a/packages/webawesome/src/components/select/select.ts b/packages/webawesome/src/components/select/select.ts index 2e1459e1c..37f0c0d02 100644 --- a/packages/webawesome/src/components/select/select.ts +++ b/packages/webawesome/src/components/select/select.ts @@ -16,8 +16,8 @@ import { HasSlotController } from '../../internal/slot.js'; import { RequiredValidator } from '../../internal/validators/required-validator.js'; import { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js'; -import formControlStyles from '../../styles/component/form-control.css'; -import sizeStyles from '../../styles/utilities/size.css'; +import formControlStyles from '../../styles/component/form-control.styles.js'; +import sizeStyles from '../../styles/component/size.styles.js'; import { LocalizeController } from '../../utilities/localize.js'; import '../icon/icon.js'; import '../option/option.js'; @@ -25,7 +25,7 @@ import type WaOption from '../option/option.js'; import '../popup/popup.js'; import type WaPopup from '../popup/popup.js'; import '../tag/tag.js'; -import styles from './select.css'; +import styles from './select.styles.js'; /** * @summary Selects allow you to choose items from a menu of predefined options. diff --git a/packages/webawesome/src/components/skeleton/skeleton.css b/packages/webawesome/src/components/skeleton/skeleton.css deleted file mode 100644 index 65a7f794a..000000000 --- a/packages/webawesome/src/components/skeleton/skeleton.css +++ /dev/null @@ -1,54 +0,0 @@ -:host { - --color: var(--wa-color-neutral-fill-normal); - --sheen-color: color-mix(in oklab, var(--color), var(--wa-color-surface-raised)); - - display: flex; - position: relative; - width: 100%; - height: 100%; - min-height: 1rem; -} - -.indicator { - flex: 1 1 auto; - background: var(--color); - border-radius: var(--wa-border-radius-pill); -} - -:host([effect='sheen']) .indicator { - background: linear-gradient(270deg, var(--sheen-color), var(--color), var(--color), var(--sheen-color)); - background-size: 400% 100%; - animation: sheen 8s ease-in-out infinite; -} - -:host([effect='pulse']) .indicator { - animation: pulse 2s ease-in-out 0.5s infinite; -} - -/* Forced colors mode */ -@media (forced-colors: active) { - :host { - --color: GrayText; - } -} - -@keyframes sheen { - 0% { - background-position: 200% 0; - } - to { - background-position: -200% 0; - } -} - -@keyframes pulse { - 0% { - opacity: 1; - } - 50% { - opacity: 0.4; - } - 100% { - opacity: 1; - } -} diff --git a/packages/webawesome/src/components/skeleton/skeleton.styles.ts b/packages/webawesome/src/components/skeleton/skeleton.styles.ts new file mode 100644 index 000000000..9fd766ede --- /dev/null +++ b/packages/webawesome/src/components/skeleton/skeleton.styles.ts @@ -0,0 +1,58 @@ +import { css } from 'lit'; + +export default css` + :host { + --color: var(--wa-color-neutral-fill-normal); + --sheen-color: color-mix(in oklab, var(--color), var(--wa-color-surface-raised)); + + display: flex; + position: relative; + width: 100%; + height: 100%; + min-height: 1rem; + } + + .indicator { + flex: 1 1 auto; + background: var(--color); + border-radius: var(--wa-border-radius-pill); + } + + :host([effect='sheen']) .indicator { + background: linear-gradient(270deg, var(--sheen-color), var(--color), var(--color), var(--sheen-color)); + background-size: 400% 100%; + animation: sheen 8s ease-in-out infinite; + } + + :host([effect='pulse']) .indicator { + animation: pulse 2s ease-in-out 0.5s infinite; + } + + /* Forced colors mode */ + @media (forced-colors: active) { + :host { + --color: GrayText; + } + } + + @keyframes sheen { + 0% { + background-position: 200% 0; + } + to { + background-position: -200% 0; + } + } + + @keyframes pulse { + 0% { + opacity: 1; + } + 50% { + opacity: 0.4; + } + 100% { + opacity: 1; + } + } +`; diff --git a/packages/webawesome/src/components/skeleton/skeleton.ts b/packages/webawesome/src/components/skeleton/skeleton.ts index b22178fd9..2b4723d77 100644 --- a/packages/webawesome/src/components/skeleton/skeleton.ts +++ b/packages/webawesome/src/components/skeleton/skeleton.ts @@ -1,7 +1,7 @@ import { html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import styles from './skeleton.css'; +import styles from './skeleton.styles.js'; /** * @summary Skeletons are used to provide a visual representation of where content will eventually be drawn. diff --git a/packages/webawesome/src/components/slider/slider.css b/packages/webawesome/src/components/slider/slider.css deleted file mode 100644 index efb206130..000000000 --- a/packages/webawesome/src/components/slider/slider.css +++ /dev/null @@ -1,229 +0,0 @@ -:host { - --track-size: 0.5em; - --thumb-width: 1.4em; - --thumb-height: 1.4em; - --marker-width: 0.1875em; - --marker-height: 0.1875em; -} - -:host([orientation='vertical']) { - width: auto; -} - -#label:has(~ .vertical) { - display: block; - order: 2; - max-width: none; - text-align: center; -} - -#description:has(~ .vertical) { - order: 3; - text-align: center; -} - -/* Add extra space between slider and label, when present */ -#label:has(*:not(:empty)) ~ #slider { - &.horizontal { - margin-block-start: 0.5em; - } - &.vertical { - margin-block-end: 0.5em; - } -} - -#slider { - touch-action: none; - - &:focus { - outline: none; - } - - &:focus-visible:not(.disabled) #thumb, - &:focus-visible:not(.disabled) #thumb-min, - &:focus-visible:not(.disabled) #thumb-max { - outline: var(--wa-focus-ring); - /* intentionally no offset due to border */ - } -} - -#track { - position: relative; - border-radius: 9999px; - background: var(--wa-color-neutral-fill-normal); - isolation: isolate; -} - -/* Orientation */ -.horizontal #track { - height: var(--track-size); -} - -.vertical #track { - order: 1; - width: var(--track-size); - height: 200px; -} - -/* Disabled */ -.disabled #track { - cursor: not-allowed; - opacity: 0.5; -} - -/* Indicator */ -#indicator { - position: absolute; - border-radius: inherit; - background-color: var(--wa-form-control-activated-color); - - &:dir(ltr) { - right: calc(100% - max(var(--start), var(--end))); - left: min(var(--start), var(--end)); - } - - &:dir(rtl) { - right: min(var(--start), var(--end)); - left: calc(100% - max(var(--start), var(--end))); - } -} - -.horizontal #indicator { - top: 0; - height: 100%; -} - -.vertical #indicator { - top: calc(100% - var(--end)); - bottom: var(--start); - left: 0; - width: 100%; -} - -/* Thumbs */ -#thumb, -#thumb-min, -#thumb-max { - z-index: 3; - position: absolute; - width: var(--thumb-width); - height: var(--thumb-height); - border: solid 0.125em var(--wa-color-surface-default); - border-radius: 50%; - background-color: var(--wa-form-control-activated-color); - cursor: pointer; -} - -.disabled #thumb, -.disabled #thumb-min, -.disabled #thumb-max { - cursor: inherit; -} - -.horizontal #thumb, -.horizontal #thumb-min, -.horizontal #thumb-max { - top: calc(50% - var(--thumb-height) / 2); - - &:dir(ltr) { - right: auto; - left: calc(var(--position) - var(--thumb-width) / 2); - } - - &:dir(rtl) { - right: calc(var(--position) - var(--thumb-width) / 2); - left: auto; - } -} - -.vertical #thumb, -.vertical #thumb-min, -.vertical #thumb-max { - bottom: calc(var(--position) - var(--thumb-height) / 2); - left: calc(50% - var(--thumb-width) / 2); -} - -/* Range-specific thumb styles */ -:host([range]) { - #thumb-min:focus-visible, - #thumb-max:focus-visible { - z-index: 4; /* Ensure focused thumb appears on top */ - outline: var(--wa-focus-ring); - /* intentionally no offset due to border */ - } -} - -/* Markers */ -#markers { - pointer-events: none; -} - -.marker { - z-index: 2; - position: absolute; - width: var(--marker-width); - height: var(--marker-height); - border-radius: 50%; - background-color: var(--wa-color-surface-default); -} - -.marker:first-of-type, -.marker:last-of-type { - display: none; -} - -.horizontal .marker { - top: calc(50% - var(--marker-height) / 2); - left: calc(var(--position) - var(--marker-width) / 2); -} - -.vertical .marker { - top: calc(var(--position) - var(--marker-height) / 2); - left: calc(50% - var(--marker-width) / 2); -} - -/* Marker labels */ -#references { - position: relative; - - slot { - display: flex; - justify-content: space-between; - height: 100%; - } - - ::slotted(*) { - color: var(--wa-color-text-quiet); - font-size: 0.875em; - line-height: 1; - } -} - -.horizontal { - #references { - margin-block-start: 0.5em; - } -} - -.vertical { - display: flex; - margin-inline: auto; - - #track { - order: 1; - } - - #references { - order: 2; - width: min-content; - margin-inline-start: 0.75em; - - slot { - flex-direction: column; - } - } -} - -.vertical #references slot { - flex-direction: column; -} diff --git a/packages/webawesome/src/components/slider/slider.styles.ts b/packages/webawesome/src/components/slider/slider.styles.ts new file mode 100644 index 000000000..5e3b5a6c7 --- /dev/null +++ b/packages/webawesome/src/components/slider/slider.styles.ts @@ -0,0 +1,233 @@ +import { css } from 'lit'; + +export default css` + :host { + --track-size: 0.5em; + --thumb-width: 1.4em; + --thumb-height: 1.4em; + --marker-width: 0.1875em; + --marker-height: 0.1875em; + } + + :host([orientation='vertical']) { + width: auto; + } + + #label:has(~ .vertical) { + display: block; + order: 2; + max-width: none; + text-align: center; + } + + #description:has(~ .vertical) { + order: 3; + text-align: center; + } + + /* Add extra space between slider and label, when present */ + #label:has(*:not(:empty)) ~ #slider { + &.horizontal { + margin-block-start: 0.5em; + } + &.vertical { + margin-block-end: 0.5em; + } + } + + #slider { + touch-action: none; + + &:focus { + outline: none; + } + + &:focus-visible:not(.disabled) #thumb, + &:focus-visible:not(.disabled) #thumb-min, + &:focus-visible:not(.disabled) #thumb-max { + outline: var(--wa-focus-ring); + /* intentionally no offset due to border */ + } + } + + #track { + position: relative; + border-radius: 9999px; + background: var(--wa-color-neutral-fill-normal); + isolation: isolate; + } + + /* Orientation */ + .horizontal #track { + height: var(--track-size); + } + + .vertical #track { + order: 1; + width: var(--track-size); + height: 200px; + } + + /* Disabled */ + .disabled #track { + cursor: not-allowed; + opacity: 0.5; + } + + /* Indicator */ + #indicator { + position: absolute; + border-radius: inherit; + background-color: var(--wa-form-control-activated-color); + + &:dir(ltr) { + right: calc(100% - max(var(--start), var(--end))); + left: min(var(--start), var(--end)); + } + + &:dir(rtl) { + right: min(var(--start), var(--end)); + left: calc(100% - max(var(--start), var(--end))); + } + } + + .horizontal #indicator { + top: 0; + height: 100%; + } + + .vertical #indicator { + top: calc(100% - var(--end)); + bottom: var(--start); + left: 0; + width: 100%; + } + + /* Thumbs */ + #thumb, + #thumb-min, + #thumb-max { + z-index: 3; + position: absolute; + width: var(--thumb-width); + height: var(--thumb-height); + border: solid 0.125em var(--wa-color-surface-default); + border-radius: 50%; + background-color: var(--wa-form-control-activated-color); + cursor: pointer; + } + + .disabled #thumb, + .disabled #thumb-min, + .disabled #thumb-max { + cursor: inherit; + } + + .horizontal #thumb, + .horizontal #thumb-min, + .horizontal #thumb-max { + top: calc(50% - var(--thumb-height) / 2); + + &:dir(ltr) { + right: auto; + left: calc(var(--position) - var(--thumb-width) / 2); + } + + &:dir(rtl) { + right: calc(var(--position) - var(--thumb-width) / 2); + left: auto; + } + } + + .vertical #thumb, + .vertical #thumb-min, + .vertical #thumb-max { + bottom: calc(var(--position) - var(--thumb-height) / 2); + left: calc(50% - var(--thumb-width) / 2); + } + + /* Range-specific thumb styles */ + :host([range]) { + #thumb-min:focus-visible, + #thumb-max:focus-visible { + z-index: 4; /* Ensure focused thumb appears on top */ + outline: var(--wa-focus-ring); + /* intentionally no offset due to border */ + } + } + + /* Markers */ + #markers { + pointer-events: none; + } + + .marker { + z-index: 2; + position: absolute; + width: var(--marker-width); + height: var(--marker-height); + border-radius: 50%; + background-color: var(--wa-color-surface-default); + } + + .marker:first-of-type, + .marker:last-of-type { + display: none; + } + + .horizontal .marker { + top: calc(50% - var(--marker-height) / 2); + left: calc(var(--position) - var(--marker-width) / 2); + } + + .vertical .marker { + top: calc(var(--position) - var(--marker-height) / 2); + left: calc(50% - var(--marker-width) / 2); + } + + /* Marker labels */ + #references { + position: relative; + + slot { + display: flex; + justify-content: space-between; + height: 100%; + } + + ::slotted(*) { + color: var(--wa-color-text-quiet); + font-size: 0.875em; + line-height: 1; + } + } + + .horizontal { + #references { + margin-block-start: 0.5em; + } + } + + .vertical { + display: flex; + margin-inline: auto; + + #track { + order: 1; + } + + #references { + order: 2; + width: min-content; + margin-inline-start: 0.75em; + + slot { + flex-direction: column; + } + } + } + + .vertical #references slot { + flex-direction: column; + } +`; diff --git a/packages/webawesome/src/components/slider/slider.ts b/packages/webawesome/src/components/slider/slider.ts index 4a4817aad..55e7df338 100644 --- a/packages/webawesome/src/components/slider/slider.ts +++ b/packages/webawesome/src/components/slider/slider.ts @@ -8,12 +8,12 @@ import { HasSlotController } from '../../internal/slot.js'; import { submitOnEnter } from '../../internal/submit-on-enter.js'; import { SliderValidator } from '../../internal/validators/slider-validator.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js'; -import formControlStyles from '../../styles/component/form-control.css'; -import sizeStyles from '../../styles/utilities/size.css'; +import formControlStyles from '../../styles/component/form-control.styles.js'; +import sizeStyles from '../../styles/component/size.styles.js'; import { LocalizeController } from '../../utilities/localize.js'; import '../tooltip/tooltip.js'; import type WaTooltip from '../tooltip/tooltip.js'; -import styles from './slider.css'; +import styles from './slider.styles.js'; /** * <wa-slider> diff --git a/packages/webawesome/src/components/spinner/spinner.css b/packages/webawesome/src/components/spinner/spinner.css deleted file mode 100644 index cc28a04c8..000000000 --- a/packages/webawesome/src/components/spinner/spinner.css +++ /dev/null @@ -1,59 +0,0 @@ -:host { - --track-width: 2px; - --track-color: var(--wa-color-neutral-fill-normal); - --indicator-color: var(--wa-color-brand-fill-loud); - --speed: 2s; - - /* Resizing a spinner element using anything but font-size will break the animation because the animation uses em units. - Therefore, if a spinner is used in a flex container without `flex: none` applied, the spinner can grow/shrink and - break the animation. The use of `flex: none` on the host element prevents this by always having the spinner sized - according to its actual dimensions. - */ - flex: none; - display: inline-flex; - width: 1em; - height: 1em; -} - -svg { - width: 100%; - height: 100%; - aspect-ratio: 1; - animation: spin var(--speed) linear infinite; -} - -.track { - stroke: var(--track-color); -} - -.indicator { - stroke: var(--indicator-color); - stroke-dasharray: 75, 100; - stroke-dashoffset: -5; - animation: dash 1.5s ease-in-out infinite; - stroke-linecap: round; -} - -@keyframes spin { - 0% { - transform: rotate(0deg); - } - 100% { - transform: rotate(360deg); - } -} - -@keyframes dash { - 0% { - stroke-dasharray: 1, 150; - stroke-dashoffset: 0; - } - 50% { - stroke-dasharray: 90, 150; - stroke-dashoffset: -35; - } - 100% { - stroke-dasharray: 90, 150; - stroke-dashoffset: -124; - } -} diff --git a/packages/webawesome/src/components/spinner/spinner.styles.ts b/packages/webawesome/src/components/spinner/spinner.styles.ts new file mode 100644 index 000000000..f965c6fd9 --- /dev/null +++ b/packages/webawesome/src/components/spinner/spinner.styles.ts @@ -0,0 +1,64 @@ +import { css } from 'lit'; + +export default css` + :host { + --track-width: 2px; + --track-color: var(--wa-color-neutral-fill-normal); + --indicator-color: var(--wa-color-brand-fill-loud); + --speed: 2s; + + /* + Resizing a spinner element using anything but font-size will break the animation because the animation uses em + units. Therefore, if a spinner is used in a flex container without \`flex: none\` applied, the spinner can + grow/shrink and break the animation. The use of \`flex: none\` on the host element prevents this by always having + the spinner sized according to its actual dimensions. + */ + flex: none; + display: inline-flex; + width: 1em; + height: 1em; + } + + svg { + width: 100%; + height: 100%; + aspect-ratio: 1; + animation: spin var(--speed) linear infinite; + } + + .track { + stroke: var(--track-color); + } + + .indicator { + stroke: var(--indicator-color); + stroke-dasharray: 75, 100; + stroke-dashoffset: -5; + animation: dash 1.5s ease-in-out infinite; + stroke-linecap: round; + } + + @keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } + } + + @keyframes dash { + 0% { + stroke-dasharray: 1, 150; + stroke-dashoffset: 0; + } + 50% { + stroke-dasharray: 90, 150; + stroke-dashoffset: -35; + } + 100% { + stroke-dasharray: 90, 150; + stroke-dashoffset: -124; + } + } +`; diff --git a/packages/webawesome/src/components/spinner/spinner.ts b/packages/webawesome/src/components/spinner/spinner.ts index ca3d2b45d..9985a492c 100644 --- a/packages/webawesome/src/components/spinner/spinner.ts +++ b/packages/webawesome/src/components/spinner/spinner.ts @@ -2,7 +2,7 @@ import { html } from 'lit'; import { customElement } from 'lit/decorators.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import { LocalizeController } from '../../utilities/localize.js'; -import styles from './spinner.css'; +import styles from './spinner.styles.js'; /** * @summary Spinners are used to show the progress of an indeterminate operation. diff --git a/packages/webawesome/src/components/split-panel/split-panel.css b/packages/webawesome/src/components/split-panel/split-panel.css deleted file mode 100644 index 4c9187b7e..000000000 --- a/packages/webawesome/src/components/split-panel/split-panel.css +++ /dev/null @@ -1,73 +0,0 @@ -:host { - --divider-width: 0.25rem; - --divider-hit-area: 0.75rem; - --min: 0%; - --max: 100%; - - display: grid; -} - -.start, -.end { - overflow: hidden; -} - -.divider { - flex: 0 0 var(--divider-width); - display: flex; - position: relative; - align-items: center; - justify-content: center; - background-color: var(--wa-color-neutral-border-normal); - color: var(--wa-color-neutral-on-normal); - z-index: 1; -} - -.divider:focus { - outline: none; -} - -:host(:not([disabled])) .divider:focus-visible { - outline: var(--wa-focus-ring); -} - -:host([disabled]) .divider { - cursor: not-allowed; -} - -/* Horizontal */ -:host(:not([orientation='vertical'], [disabled])) .divider { - cursor: col-resize; -} - -:host(:not([orientation='vertical'])) .divider::after { - display: flex; - content: ''; - position: absolute; - height: 100%; - left: calc(var(--divider-hit-area) / -2 + var(--divider-width) / 2); - width: var(--divider-hit-area); -} - -/* Vertical */ -:host([orientation='vertical']) { - flex-direction: column; -} - -:host([orientation='vertical']:not([disabled])) .divider { - cursor: row-resize; -} - -:host([orientation='vertical']) .divider::after { - content: ''; - position: absolute; - width: 100%; - top: calc(var(--divider-hit-area) / -2 + var(--divider-width) / 2); - height: var(--divider-hit-area); -} - -@media (forced-colors: active) { - .divider { - outline: solid 1px transparent; - } -} diff --git a/packages/webawesome/src/components/split-panel/split-panel.styles.ts b/packages/webawesome/src/components/split-panel/split-panel.styles.ts new file mode 100644 index 000000000..aada89211 --- /dev/null +++ b/packages/webawesome/src/components/split-panel/split-panel.styles.ts @@ -0,0 +1,77 @@ +import { css } from 'lit'; + +export default css` + :host { + --divider-width: 0.25rem; + --divider-hit-area: 0.75rem; + --min: 0%; + --max: 100%; + + display: grid; + } + + .start, + .end { + overflow: hidden; + } + + .divider { + flex: 0 0 var(--divider-width); + display: flex; + position: relative; + align-items: center; + justify-content: center; + background-color: var(--wa-color-neutral-border-normal); + color: var(--wa-color-neutral-on-normal); + z-index: 1; + } + + .divider:focus { + outline: none; + } + + :host(:not([disabled])) .divider:focus-visible { + outline: var(--wa-focus-ring); + } + + :host([disabled]) .divider { + cursor: not-allowed; + } + + /* Horizontal */ + :host(:not([orientation='vertical'], [disabled])) .divider { + cursor: col-resize; + } + + :host(:not([orientation='vertical'])) .divider::after { + display: flex; + content: ''; + position: absolute; + height: 100%; + left: calc(var(--divider-hit-area) / -2 + var(--divider-width) / 2); + width: var(--divider-hit-area); + } + + /* Vertical */ + :host([orientation='vertical']) { + flex-direction: column; + } + + :host([orientation='vertical']:not([disabled])) .divider { + cursor: row-resize; + } + + :host([orientation='vertical']) .divider::after { + content: ''; + position: absolute; + width: 100%; + top: calc(var(--divider-hit-area) / -2 + var(--divider-width) / 2); + height: var(--divider-hit-area); + } + + @media (forced-colors: active) { + .divider { + outline: solid 1px transparent; + } + } +`; diff --git a/packages/webawesome/src/components/split-panel/split-panel.ts b/packages/webawesome/src/components/split-panel/split-panel.ts index 7e83bb90b..e3930db79 100644 --- a/packages/webawesome/src/components/split-panel/split-panel.ts +++ b/packages/webawesome/src/components/split-panel/split-panel.ts @@ -7,7 +7,7 @@ import { clamp } from '../../internal/math.js'; import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import { LocalizeController } from '../../utilities/localize.js'; -import styles from './split-panel.css'; +import styles from './split-panel.styles.js'; /** * @summary Split panels display two adjacent panels, allowing the user to reposition them. diff --git a/packages/webawesome/src/components/switch/switch.css b/packages/webawesome/src/components/switch/switch.css deleted file mode 100644 index 941bf017c..000000000 --- a/packages/webawesome/src/components/switch/switch.css +++ /dev/null @@ -1,98 +0,0 @@ -:host { - --height: var(--wa-form-control-toggle-size); - --width: calc(var(--height) * 1.75); - --thumb-size: 0.75em; - - display: inline-flex; - line-height: var(--wa-form-control-value-line-height); -} - -label { - position: relative; - display: flex; - align-items: center; - font: inherit; - color: var(--wa-form-control-value-color); - vertical-align: middle; - cursor: pointer; -} - -.switch { - flex: 0 0 auto; - position: relative; - display: flex; - align-items: center; - justify-content: center; - width: var(--width); - height: var(--height); - background-color: var(--wa-form-control-background-color); - border-color: var(--wa-form-control-border-color); - border-radius: var(--height); - border-style: var(--wa-form-control-border-style); - border-width: var(--wa-form-control-border-width); - transition-property: translate, background, border-color, box-shadow; - transition-duration: var(--wa-transition-normal); - transition-timing-function: var(--wa-transition-easing); -} - -.switch .thumb { - aspect-ratio: 1 / 1; - width: var(--thumb-size); - height: var(--thumb-size); - background-color: var(--wa-form-control-border-color); - border-radius: 50%; - translate: calc((var(--width) - var(--height)) / -2); - transition: inherit; -} - -.input { - position: absolute; - opacity: 0; - padding: 0; - margin: 0; - pointer-events: none; -} - -/* Focus */ -label:not(.disabled) .input:focus-visible ~ .switch .thumb { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); -} - -/* Checked */ -.checked .switch { - background-color: var(--wa-form-control-activated-color); - border-color: var(--wa-form-control-activated-color); -} - -.checked .switch .thumb { - background-color: var(--wa-color-surface-default); - translate: calc((var(--width) - var(--height)) / 2); -} - -/* Disabled */ -label:has(> :disabled) { - opacity: 0.5; - cursor: not-allowed; -} - -[part~='label'] { - display: inline-block; - line-height: var(--height); - margin-inline-start: 0.5em; - user-select: none; - -webkit-user-select: none; -} - -:host([required]) [part~='label']::after { - content: var(--wa-form-control-required-content); - color: var(--wa-form-control-required-content-color); - margin-inline-start: var(--wa-form-control-required-content-offset); -} - -@media (forced-colors: active) { - :checked:enabled + .switch:hover .thumb, - :checked + .switch .thumb { - background-color: ButtonText; - } -} diff --git a/packages/webawesome/src/components/switch/switch.styles.ts b/packages/webawesome/src/components/switch/switch.styles.ts new file mode 100644 index 000000000..f1b689e57 --- /dev/null +++ b/packages/webawesome/src/components/switch/switch.styles.ts @@ -0,0 +1,102 @@ +import { css } from 'lit'; + +export default css` + :host { + --height: var(--wa-form-control-toggle-size); + --width: calc(var(--height) * 1.75); + --thumb-size: 0.75em; + + display: inline-flex; + line-height: var(--wa-form-control-value-line-height); + } + + label { + position: relative; + display: flex; + align-items: center; + font: inherit; + color: var(--wa-form-control-value-color); + vertical-align: middle; + cursor: pointer; + } + + .switch { + flex: 0 0 auto; + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: var(--width); + height: var(--height); + background-color: var(--wa-form-control-background-color); + border-color: var(--wa-form-control-border-color); + border-radius: var(--height); + border-style: var(--wa-form-control-border-style); + border-width: var(--wa-form-control-border-width); + transition-property: translate, background, border-color, box-shadow; + transition-duration: var(--wa-transition-normal); + transition-timing-function: var(--wa-transition-easing); + } + + .switch .thumb { + aspect-ratio: 1 / 1; + width: var(--thumb-size); + height: var(--thumb-size); + background-color: var(--wa-form-control-border-color); + border-radius: 50%; + translate: calc((var(--width) - var(--height)) / -2); + transition: inherit; + } + + .input { + position: absolute; + opacity: 0; + padding: 0; + margin: 0; + pointer-events: none; + } + + /* Focus */ + label:not(.disabled) .input:focus-visible ~ .switch .thumb { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + + /* Checked */ + .checked .switch { + background-color: var(--wa-form-control-activated-color); + border-color: var(--wa-form-control-activated-color); + } + + .checked .switch .thumb { + background-color: var(--wa-color-surface-default); + translate: calc((var(--width) - var(--height)) / 2); + } + + /* Disabled */ + label:has(> :disabled) { + opacity: 0.5; + cursor: not-allowed; + } + + [part~='label'] { + display: inline-block; + line-height: var(--height); + margin-inline-start: 0.5em; + user-select: none; + -webkit-user-select: none; + } + + :host([required]) [part~='label']::after { + content: var(--wa-form-control-required-content); + color: var(--wa-form-control-required-content-color); + margin-inline-start: var(--wa-form-control-required-content-offset); + } + + @media (forced-colors: active) { + :checked:enabled + .switch:hover .thumb, + :checked + .switch .thumb { + background-color: ButtonText; + } + } +`; diff --git a/packages/webawesome/src/components/switch/switch.ts b/packages/webawesome/src/components/switch/switch.ts index a78807122..14f06084e 100644 --- a/packages/webawesome/src/components/switch/switch.ts +++ b/packages/webawesome/src/components/switch/switch.ts @@ -8,9 +8,9 @@ import { HasSlotController } from '../../internal/slot.js'; import { MirrorValidator } from '../../internal/validators/mirror-validator.js'; import { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js'; -import formControlStyles from '../../styles/component/form-control.css'; -import sizeStyles from '../../styles/utilities/size.css'; -import styles from './switch.css'; +import formControlStyles from '../../styles/component/form-control.styles.js'; +import sizeStyles from '../../styles/component/size.styles.js'; +import styles from './switch.styles.js'; /** * @summary Switches allow the user to toggle an option on or off. diff --git a/packages/webawesome/src/components/tab-group/tab-group.css b/packages/webawesome/src/components/tab-group/tab-group.css deleted file mode 100644 index 6606fa163..000000000 --- a/packages/webawesome/src/components/tab-group/tab-group.css +++ /dev/null @@ -1,224 +0,0 @@ -:host { - --indicator-color: var(--wa-color-brand-fill-loud); - --track-color: var(--wa-color-neutral-fill-normal); - --track-width: 0.125rem; - - display: block; -} - -.tab-group { - display: flex; - border-radius: 0; -} - -.tabs { - display: flex; - position: relative; -} - -.indicator { - position: absolute; -} - -.tab-group-has-scroll-controls .nav-container { - position: relative; - padding: 0 1.5em; -} - -.body { - display: block; -} - -.scroll-button { - display: flex; - align-items: center; - justify-content: center; - position: absolute; - top: 0; - bottom: 0; - width: 1.5em; -} - -.scroll-button-start { - inset-inline-start: 0; -} - -.scroll-button-end { - inset-inline-end: 0; -} - -/* - * Top - */ - -.tab-group-top { - flex-direction: column; -} - -.tab-group-top .nav-container { - order: 1; -} - -.tab-group-top .nav { - display: flex; - overflow-x: auto; - - /* Hide scrollbar in Firefox */ - scrollbar-width: none; -} - -/* Hide scrollbar in Chrome/Safari */ -.tab-group-top .nav::-webkit-scrollbar { - width: 0; - height: 0; -} - -.tab-group-top .tabs { - flex: 1 1 auto; - position: relative; - flex-direction: row; - border-bottom: solid var(--track-width) var(--track-color); -} - -.tab-group-top .indicator { - bottom: calc(-1 * var(--track-width)); - border-bottom: solid var(--track-width) var(--indicator-color); -} - -.tab-group-top .body { - order: 2; -} - -.tab-group-top ::slotted(wa-tab[active]) { - border-block-end: solid var(--track-width) var(--indicator-color); - margin-block-end: calc(-1 * var(--track-width)); -} - -.tab-group-top ::slotted(wa-tab-panel) { - --padding: var(--wa-space-xl) 0; -} - -/* - * Bottom - */ - -.tab-group-bottom { - flex-direction: column; -} - -.tab-group-bottom .nav-container { - order: 2; -} - -.tab-group-bottom .nav { - display: flex; - overflow-x: auto; - - /* Hide scrollbar in Firefox */ - scrollbar-width: none; -} - -/* Hide scrollbar in Chrome/Safari */ -.tab-group-bottom .nav::-webkit-scrollbar { - width: 0; - height: 0; -} - -.tab-group-bottom .tabs { - flex: 1 1 auto; - position: relative; - flex-direction: row; - border-top: solid var(--track-width) var(--track-color); -} - -.tab-group-bottom .indicator { - top: calc(-1 * var(--track-width)); - border-top: solid var(--track-width) var(--indicator-color); -} - -.tab-group-bottom .body { - order: 1; -} - -.tab-group-bottom ::slotted(wa-tab[active]) { - border-block-start: solid var(--track-width) var(--indicator-color); - margin-block-start: calc(-1 * var(--track-width)); -} - -.tab-group-bottom ::slotted(wa-tab-panel) { - --padding: var(--wa-space-xl) 0; -} - -/* - * Start - */ - -.tab-group-start { - flex-direction: row; -} - -.tab-group-start .nav-container { - order: 1; -} - -.tab-group-start .tabs { - flex: 0 0 auto; - flex-direction: column; - border-inline-end: solid var(--track-width) var(--track-color); -} - -.tab-group-start .indicator { - inset-inline-end: calc(-1 * var(--track-width)); - border-right: solid var(--track-width) var(--indicator-color); -} - -.tab-group-start .body { - flex: 1 1 auto; - order: 2; -} - -.tab-group-start ::slotted(wa-tab[active]) { - border-inline-end: solid var(--track-width) var(--indicator-color); - margin-inline-end: calc(-1 * var(--track-width)); -} - -.tab-group-start ::slotted(wa-tab-panel) { - --padding: 0 var(--wa-space-xl); -} - -/* - * End - */ - -.tab-group-end { - flex-direction: row; -} - -.tab-group-end .nav-container { - order: 2; -} - -.tab-group-end .tabs { - flex: 0 0 auto; - flex-direction: column; - border-left: solid var(--track-width) var(--track-color); -} - -.tab-group-end .indicator { - inset-inline-start: calc(-1 * var(--track-width)); - border-inline-start: solid var(--track-width) var(--indicator-color); -} - -.tab-group-end .body { - flex: 1 1 auto; - order: 1; -} - -.tab-group-end ::slotted(wa-tab[active]) { - border-inline-start: solid var(--track-width) var(--indicator-color); - margin-inline-start: calc(-1 * var(--track-width)); -} - -.tab-group-end ::slotted(wa-tab-panel) { - --padding: 0 var(--wa-space-xl); -} diff --git a/packages/webawesome/src/components/tab-group/tab-group.styles.ts b/packages/webawesome/src/components/tab-group/tab-group.styles.ts new file mode 100644 index 000000000..ce98145fb --- /dev/null +++ b/packages/webawesome/src/components/tab-group/tab-group.styles.ts @@ -0,0 +1,228 @@ +import { css } from 'lit'; + +export default css` + :host { + --indicator-color: var(--wa-color-brand-fill-loud); + --track-color: var(--wa-color-neutral-fill-normal); + --track-width: 0.125rem; + + display: block; + } + + .tab-group { + display: flex; + border-radius: 0; + } + + .tabs { + display: flex; + position: relative; + } + + .indicator { + position: absolute; + } + + .tab-group-has-scroll-controls .nav-container { + position: relative; + padding: 0 1.5em; + } + + .body { + display: block; + } + + .scroll-button { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + top: 0; + bottom: 0; + width: 1.5em; + } + + .scroll-button-start { + inset-inline-start: 0; + } + + .scroll-button-end { + inset-inline-end: 0; + } + + /* + * Top + */ + + .tab-group-top { + flex-direction: column; + } + + .tab-group-top .nav-container { + order: 1; + } + + .tab-group-top .nav { + display: flex; + overflow-x: auto; + + /* Hide scrollbar in Firefox */ + scrollbar-width: none; + } + + /* Hide scrollbar in Chrome/Safari */ + .tab-group-top .nav::-webkit-scrollbar { + width: 0; + height: 0; + } + + .tab-group-top .tabs { + flex: 1 1 auto; + position: relative; + flex-direction: row; + border-bottom: solid var(--track-width) var(--track-color); + } + + .tab-group-top .indicator { + bottom: calc(-1 * var(--track-width)); + border-bottom: solid var(--track-width) var(--indicator-color); + } + + .tab-group-top .body { + order: 2; + } + + .tab-group-top ::slotted(wa-tab[active]) { + border-block-end: solid var(--track-width) var(--indicator-color); + margin-block-end: calc(-1 * var(--track-width)); + } + + .tab-group-top ::slotted(wa-tab-panel) { + --padding: var(--wa-space-xl) 0; + } + + /* + * Bottom + */ + + .tab-group-bottom { + flex-direction: column; + } + + .tab-group-bottom .nav-container { + order: 2; + } + + .tab-group-bottom .nav { + display: flex; + overflow-x: auto; + + /* Hide scrollbar in Firefox */ + scrollbar-width: none; + } + + /* Hide scrollbar in Chrome/Safari */ + .tab-group-bottom .nav::-webkit-scrollbar { + width: 0; + height: 0; + } + + .tab-group-bottom .tabs { + flex: 1 1 auto; + position: relative; + flex-direction: row; + border-top: solid var(--track-width) var(--track-color); + } + + .tab-group-bottom .indicator { + top: calc(-1 * var(--track-width)); + border-top: solid var(--track-width) var(--indicator-color); + } + + .tab-group-bottom .body { + order: 1; + } + + .tab-group-bottom ::slotted(wa-tab[active]) { + border-block-start: solid var(--track-width) var(--indicator-color); + margin-block-start: calc(-1 * var(--track-width)); + } + + .tab-group-bottom ::slotted(wa-tab-panel) { + --padding: var(--wa-space-xl) 0; + } + + /* + * Start + */ + + .tab-group-start { + flex-direction: row; + } + + .tab-group-start .nav-container { + order: 1; + } + + .tab-group-start .tabs { + flex: 0 0 auto; + flex-direction: column; + border-inline-end: solid var(--track-width) var(--track-color); + } + + .tab-group-start .indicator { + inset-inline-end: calc(-1 * var(--track-width)); + border-right: solid var(--track-width) var(--indicator-color); + } + + .tab-group-start .body { + flex: 1 1 auto; + order: 2; + } + + .tab-group-start ::slotted(wa-tab[active]) { + border-inline-end: solid var(--track-width) var(--indicator-color); + margin-inline-end: calc(-1 * var(--track-width)); + } + + .tab-group-start ::slotted(wa-tab-panel) { + --padding: 0 var(--wa-space-xl); + } + + /* + * End + */ + + .tab-group-end { + flex-direction: row; + } + + .tab-group-end .nav-container { + order: 2; + } + + .tab-group-end .tabs { + flex: 0 0 auto; + flex-direction: column; + border-left: solid var(--track-width) var(--track-color); + } + + .tab-group-end .indicator { + inset-inline-start: calc(-1 * var(--track-width)); + border-inline-start: solid var(--track-width) var(--indicator-color); + } + + .tab-group-end .body { + flex: 1 1 auto; + order: 1; + } + + .tab-group-end ::slotted(wa-tab[active]) { + border-inline-start: solid var(--track-width) var(--indicator-color); + margin-inline-start: calc(-1 * var(--track-width)); + } + + .tab-group-end ::slotted(wa-tab-panel) { + --padding: 0 var(--wa-space-xl); + } +`; diff --git a/packages/webawesome/src/components/tab-group/tab-group.ts b/packages/webawesome/src/components/tab-group/tab-group.ts index abd684f9f..2421cd96c 100644 --- a/packages/webawesome/src/components/tab-group/tab-group.ts +++ b/packages/webawesome/src/components/tab-group/tab-group.ts @@ -12,7 +12,7 @@ import '../tab-panel/tab-panel.js'; import type WaTabPanel from '../tab-panel/tab-panel.js'; import '../tab/tab.js'; import type WaTab from '../tab/tab.js'; -import styles from './tab-group.css'; +import styles from './tab-group.styles.js'; /** * @summary Tab groups organize content into a container that shows one section at a time. diff --git a/packages/webawesome/src/components/tab-panel/tab-panel.css b/packages/webawesome/src/components/tab-panel/tab-panel.css deleted file mode 100644 index cc74c9f1c..000000000 --- a/packages/webawesome/src/components/tab-panel/tab-panel.css +++ /dev/null @@ -1,14 +0,0 @@ -:host { - --padding: 0; - - display: none; -} - -:host([active]) { - display: block; -} - -.tab-panel { - display: block; - padding: var(--padding); -} diff --git a/packages/webawesome/src/components/tab-panel/tab-panel.styles.ts b/packages/webawesome/src/components/tab-panel/tab-panel.styles.ts new file mode 100644 index 000000000..e0c9f2142 --- /dev/null +++ b/packages/webawesome/src/components/tab-panel/tab-panel.styles.ts @@ -0,0 +1,18 @@ +import { css } from 'lit'; + +export default css` + :host { + --padding: 0; + + display: none; + } + + :host([active]) { + display: block; + } + + .tab-panel { + display: block; + padding: var(--padding); + } +`; diff --git a/packages/webawesome/src/components/tab-panel/tab-panel.ts b/packages/webawesome/src/components/tab-panel/tab-panel.ts index 1974f92f8..53d9b239d 100644 --- a/packages/webawesome/src/components/tab-panel/tab-panel.ts +++ b/packages/webawesome/src/components/tab-panel/tab-panel.ts @@ -3,7 +3,7 @@ import { customElement, property } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import styles from './tab-panel.css'; +import styles from './tab-panel.styles.js'; let id = 0; diff --git a/packages/webawesome/src/components/tab/tab.css b/packages/webawesome/src/components/tab/tab.css deleted file mode 100644 index ef87dd807..000000000 --- a/packages/webawesome/src/components/tab/tab.css +++ /dev/null @@ -1,56 +0,0 @@ -:host { - display: inline-block; - color: var(--wa-color-neutral-on-quiet); - font-weight: var(--wa-font-weight-action); -} - -.tab { - display: inline-flex; - align-items: center; - font: inherit; - padding: 1em 1.5em; - white-space: nowrap; - user-select: none; - -webkit-user-select: none; - cursor: pointer; - transition: color var(--wa-transition-fast) var(--wa-transition-easing); - - ::slotted(wa-icon:first-child) { - margin-inline-end: 0.5em; - } - - ::slotted(wa-icon:last-child) { - margin-inline-start: 0.5em; - } -} - -@media (hover: hover) { - :host(:hover:not([disabled])) .tab { - color: currentColor; - } -} - -:host(:focus) { - outline: transparent; -} - -:host(:focus-visible) .tab { - outline: var(--wa-focus-ring); - outline-offset: calc(-1 * var(--wa-border-width-l) - var(--wa-focus-ring-offset)); -} - -:host([active]:not([disabled])) { - color: var(--wa-color-brand-on-quiet); -} - -:host([disabled]) .tab { - opacity: 0.5; - cursor: not-allowed; -} - -@media (forced-colors: active) { - :host([active]:not([disabled])) { - outline: solid 1px transparent; - outline-offset: -3px; - } -} diff --git a/packages/webawesome/src/components/tab/tab.styles.ts b/packages/webawesome/src/components/tab/tab.styles.ts new file mode 100644 index 000000000..61977cff2 --- /dev/null +++ b/packages/webawesome/src/components/tab/tab.styles.ts @@ -0,0 +1,60 @@ +import { css } from 'lit'; + +export default css` + :host { + display: inline-block; + color: var(--wa-color-neutral-on-quiet); + font-weight: var(--wa-font-weight-action); + } + + .tab { + display: inline-flex; + align-items: center; + font: inherit; + padding: 1em 1.5em; + white-space: nowrap; + user-select: none; + -webkit-user-select: none; + cursor: pointer; + transition: color var(--wa-transition-fast) var(--wa-transition-easing); + + ::slotted(wa-icon:first-child) { + margin-inline-end: 0.5em; + } + + ::slotted(wa-icon:last-child) { + margin-inline-start: 0.5em; + } + } + + @media (hover: hover) { + :host(:hover:not([disabled])) .tab { + color: currentColor; + } + } + + :host(:focus) { + outline: transparent; + } + + :host(:focus-visible) .tab { + outline: var(--wa-focus-ring); + outline-offset: calc(-1 * var(--wa-border-width-l) - var(--wa-focus-ring-offset)); + } + + :host([active]:not([disabled])) { + color: var(--wa-color-brand-on-quiet); + } + + :host([disabled]) .tab { + opacity: 0.5; + cursor: not-allowed; + } + + @media (forced-colors: active) { + :host([active]:not([disabled])) { + outline: solid 1px transparent; + outline-offset: -3px; + } + } +`; diff --git a/packages/webawesome/src/components/tab/tab.ts b/packages/webawesome/src/components/tab/tab.ts index aedb2f988..546d41a45 100644 --- a/packages/webawesome/src/components/tab/tab.ts +++ b/packages/webawesome/src/components/tab/tab.ts @@ -3,7 +3,7 @@ import { customElement, property, query } from 'lit/decorators.js'; import { classMap } from 'lit/directives/class-map.js'; import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import styles from './tab.css'; +import styles from './tab.styles.js'; let id = 0; diff --git a/packages/webawesome/src/components/tag/tag.css b/packages/webawesome/src/components/tag/tag.css deleted file mode 100644 index 6289ad822..000000000 --- a/packages/webawesome/src/components/tag/tag.css +++ /dev/null @@ -1,78 +0,0 @@ -@layer wa-component { - :host { - display: inline-flex; - gap: 0.5em; - border-radius: var(--wa-border-radius-m); - align-items: center; - background-color: var(--wa-color-fill-quiet, var(--wa-color-neutral-fill-quiet)); - border-color: var(--wa-color-border-normal, var(--wa-color-neutral-border-normal)); - border-style: var(--wa-border-style); - border-width: var(--wa-border-width-s); - color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); - font-size: inherit; - line-height: 1; - white-space: nowrap; - user-select: none; - -webkit-user-select: none; - height: calc(var(--wa-form-control-height) * 0.8); - line-height: calc(var(--wa-form-control-height) - var(--wa-form-control-border-width) * 2); - padding: 0 0.75em; - } - - /* Appearance modifiers */ - :host([appearance='outlined']) { - color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); - background-color: transparent; - border-color: var(--wa-color-border-loud, var(--wa-color-neutral-border-loud)); - } - - :host([appearance='filled']) { - color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); - background-color: var(--wa-color-fill-quiet, var(--wa-color-neutral-fill-quiet)); - border-color: transparent; - } - - :host([appearance='filled-outlined']) { - color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); - background-color: var(--wa-color-fill-quiet, var(--wa-color-neutral-fill-quiet)); - border-color: var(--wa-color-border-normal, var(--wa-color-neutral-border-normal)); - } - - :host([appearance='accent']) { - color: var(--wa-color-on-loud, var(--wa-color-neutral-on-loud)); - background-color: var(--wa-color-fill-loud, var(--wa-color-neutral-fill-loud)); - border-color: transparent; - } -} - -.content { - font-size: var(--wa-font-size-smaller); -} - -[part='remove-button'] { - color: inherit; - line-height: 1; -} - -[part='remove-button']::part(base) { - padding: 0; - height: 1em; - width: 1em; -} - -@media (hover: hover) { - :host(:hover) > [part='remove-button']::part(base) { - color: color-mix(in oklab, currentColor, var(--wa-color-mix-hover)); - } -} - -:host(:active) > [part='remove-button']::part(base) { - color: color-mix(in oklab, currentColor, var(--wa-color-mix-active)); -} - -/* - * Pill modifier - */ -:host([pill]) { - border-radius: var(--wa-border-radius-pill); -} diff --git a/packages/webawesome/src/components/tag/tag.styles.ts b/packages/webawesome/src/components/tag/tag.styles.ts new file mode 100644 index 000000000..17d91c99c --- /dev/null +++ b/packages/webawesome/src/components/tag/tag.styles.ts @@ -0,0 +1,82 @@ +import { css } from 'lit'; + +export default css` + @layer wa-component { + :host { + display: inline-flex; + gap: 0.5em; + border-radius: var(--wa-border-radius-m); + align-items: center; + background-color: var(--wa-color-fill-quiet, var(--wa-color-neutral-fill-quiet)); + border-color: var(--wa-color-border-normal, var(--wa-color-neutral-border-normal)); + border-style: var(--wa-border-style); + border-width: var(--wa-border-width-s); + color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); + font-size: inherit; + line-height: 1; + white-space: nowrap; + user-select: none; + -webkit-user-select: none; + height: calc(var(--wa-form-control-height) * 0.8); + line-height: calc(var(--wa-form-control-height) - var(--wa-form-control-border-width) * 2); + padding: 0 0.75em; + } + + /* Appearance modifiers */ + :host([appearance='outlined']) { + color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); + background-color: transparent; + border-color: var(--wa-color-border-loud, var(--wa-color-neutral-border-loud)); + } + + :host([appearance='filled']) { + color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); + background-color: var(--wa-color-fill-quiet, var(--wa-color-neutral-fill-quiet)); + border-color: transparent; + } + + :host([appearance='filled-outlined']) { + color: var(--wa-color-on-quiet, var(--wa-color-neutral-on-quiet)); + background-color: var(--wa-color-fill-quiet, var(--wa-color-neutral-fill-quiet)); + border-color: var(--wa-color-border-normal, var(--wa-color-neutral-border-normal)); + } + + :host([appearance='accent']) { + color: var(--wa-color-on-loud, var(--wa-color-neutral-on-loud)); + background-color: var(--wa-color-fill-loud, var(--wa-color-neutral-fill-loud)); + border-color: transparent; + } + } + + .content { + font-size: var(--wa-font-size-smaller); + } + + [part='remove-button'] { + color: inherit; + line-height: 1; + } + + [part='remove-button']::part(base) { + padding: 0; + height: 1em; + width: 1em; + } + + @media (hover: hover) { + :host(:hover) > [part='remove-button']::part(base) { + color: color-mix(in oklab, currentColor, var(--wa-color-mix-hover)); + } + } + + :host(:active) > [part='remove-button']::part(base) { + color: color-mix(in oklab, currentColor, var(--wa-color-mix-active)); + } + + /* + * Pill modifier + */ + :host([pill]) { + border-radius: var(--wa-border-radius-pill); + } +`; diff --git a/packages/webawesome/src/components/tag/tag.ts b/packages/webawesome/src/components/tag/tag.ts index d2d1fbbe2..f72bf5b14 100644 --- a/packages/webawesome/src/components/tag/tag.ts +++ b/packages/webawesome/src/components/tag/tag.ts @@ -2,11 +2,11 @@ import { html } from 'lit'; import { customElement, property } from 'lit/decorators.js'; import { WaRemoveEvent } from '../../events/remove.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; -import sizeStyles from '../../styles/utilities/size.css'; -import variantStyles from '../../styles/utilities/variants.css'; +import sizeStyles from '../../styles/component/size.styles.js'; +import variantStyles from '../../styles/component/variants.styles.js'; import { LocalizeController } from '../../utilities/localize.js'; import '../button/button.js'; -import styles from './tag.css'; +import styles from './tag.styles.js'; /** * @summary Tags are used as labels to organize things or to indicate a selection. diff --git a/packages/webawesome/src/components/textarea/textarea.css b/packages/webawesome/src/components/textarea/textarea.css deleted file mode 100644 index da7091971..000000000 --- a/packages/webawesome/src/components/textarea/textarea.css +++ /dev/null @@ -1,120 +0,0 @@ -:host { - border-width: 0; -} - -.textarea { - display: grid; - align-items: center; - margin: 0; - border: none; - outline: none; - cursor: inherit; - font: inherit; - background-color: var(--wa-form-control-background-color); - border-color: var(--wa-form-control-border-color); - border-radius: var(--wa-form-control-border-radius); - border-style: var(--wa-form-control-border-style); - border-width: var(--wa-form-control-border-width); - -webkit-appearance: none; - - &:focus-within { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); - } -} - -/* Appearance modifiers */ -:host([appearance='outlined']) .textarea { - background-color: var(--wa-form-control-background-color); - border-color: var(--wa-form-control-border-color); -} - -:host([appearance='filled']) .textarea { - background-color: var(--wa-color-neutral-fill-quiet); - border-color: var(--wa-color-neutral-fill-quiet); -} - -:host([appearance='filled-outlined']) .textarea { - background-color: var(--wa-color-neutral-fill-quiet); - border-color: var(--wa-form-control-border-color); -} - -textarea { - display: block; - width: 100%; - border: none; - background: transparent; - font: inherit; - color: inherit; - padding: calc(var(--wa-form-control-padding-block) - ((1lh - 1em) / 2)) var(--wa-form-control-padding-inline); /* accounts for the larger line height of textarea content */ - min-height: calc(var(--wa-form-control-height) - var(--border-width) * 2); - box-shadow: none; - margin: 0; - - &::placeholder { - color: var(--wa-form-control-placeholder-color); - user-select: none; - -webkit-user-select: none; - } - - &:autofill { - &, - &:hover, - &:focus, - &:active { - box-shadow: none; - caret-color: var(--wa-form-control-value-color); - } - } - - &:focus { - outline: none; - } -} - -/* Shared textarea and size-adjuster positioning */ -.control, -.size-adjuster { - grid-area: 1 / 1 / 2 / 2; -} - -.size-adjuster { - visibility: hidden; - pointer-events: none; - opacity: 0; - padding: 0; -} - -textarea::-webkit-search-decoration, -textarea::-webkit-search-cancel-button, -textarea::-webkit-search-results-button, -textarea::-webkit-search-results-decoration { - -webkit-appearance: none; -} - -/* - * Resize types - */ - -:host([resize='none']) textarea { - resize: none; -} - -textarea, -:host([resize='vertical']) textarea { - resize: vertical; -} - -:host([resize='horizontal']) textarea { - resize: horizontal; -} - -:host([resize='both']) textarea { - resize: both; -} - -:host([resize='auto']) textarea { - height: auto; - resize: none; - overflow-y: hidden; -} diff --git a/packages/webawesome/src/components/textarea/textarea.styles.ts b/packages/webawesome/src/components/textarea/textarea.styles.ts new file mode 100644 index 000000000..4db3e6e16 --- /dev/null +++ b/packages/webawesome/src/components/textarea/textarea.styles.ts @@ -0,0 +1,124 @@ +import { css } from 'lit'; + +export default css` + :host { + border-width: 0; + } + + .textarea { + display: grid; + align-items: center; + margin: 0; + border: none; + outline: none; + cursor: inherit; + font: inherit; + background-color: var(--wa-form-control-background-color); + border-color: var(--wa-form-control-border-color); + border-radius: var(--wa-form-control-border-radius); + border-style: var(--wa-form-control-border-style); + border-width: var(--wa-form-control-border-width); + -webkit-appearance: none; + + &:focus-within { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + } + + /* Appearance modifiers */ + :host([appearance='outlined']) .textarea { + background-color: var(--wa-form-control-background-color); + border-color: var(--wa-form-control-border-color); + } + + :host([appearance='filled']) .textarea { + background-color: var(--wa-color-neutral-fill-quiet); + border-color: var(--wa-color-neutral-fill-quiet); + } + + :host([appearance='filled-outlined']) .textarea { + background-color: var(--wa-color-neutral-fill-quiet); + border-color: var(--wa-form-control-border-color); + } + + textarea { + display: block; + width: 100%; + border: none; + background: transparent; + font: inherit; + color: inherit; + padding: calc(var(--wa-form-control-padding-block) - ((1lh - 1em) / 2)) var(--wa-form-control-padding-inline); /* accounts for the larger line height of textarea content */ + min-height: calc(var(--wa-form-control-height) - var(--border-width) * 2); + box-shadow: none; + margin: 0; + + &::placeholder { + color: var(--wa-form-control-placeholder-color); + user-select: none; + -webkit-user-select: none; + } + + &:autofill { + &, + &:hover, + &:focus, + &:active { + box-shadow: none; + caret-color: var(--wa-form-control-value-color); + } + } + + &:focus { + outline: none; + } + } + + /* Shared textarea and size-adjuster positioning */ + .control, + .size-adjuster { + grid-area: 1 / 1 / 2 / 2; + } + + .size-adjuster { + visibility: hidden; + pointer-events: none; + opacity: 0; + padding: 0; + } + + textarea::-webkit-search-decoration, + textarea::-webkit-search-cancel-button, + textarea::-webkit-search-results-button, + textarea::-webkit-search-results-decoration { + -webkit-appearance: none; + } + + /* + * Resize types + */ + + :host([resize='none']) textarea { + resize: none; + } + + textarea, + :host([resize='vertical']) textarea { + resize: vertical; + } + + :host([resize='horizontal']) textarea { + resize: horizontal; + } + + :host([resize='both']) textarea { + resize: both; + } + + :host([resize='auto']) textarea { + height: auto; + resize: none; + overflow-y: hidden; + } +`; diff --git a/packages/webawesome/src/components/textarea/textarea.ts b/packages/webawesome/src/components/textarea/textarea.ts index 954b325be..5f63a5e50 100644 --- a/packages/webawesome/src/components/textarea/textarea.ts +++ b/packages/webawesome/src/components/textarea/textarea.ts @@ -8,9 +8,9 @@ import { HasSlotController } from '../../internal/slot.js'; import { MirrorValidator } from '../../internal/validators/mirror-validator.js'; import { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js'; -import formControlStyles from '../../styles/component/form-control.css'; -import sizeStyles from '../../styles/utilities/size.css'; -import styles from './textarea.css'; +import formControlStyles from '../../styles/component/form-control.styles.js'; +import sizeStyles from '../../styles/component/size.styles.js'; +import styles from './textarea.styles.js'; /** * @summary Textareas collect data from the user and allow multiple lines of text. diff --git a/packages/webawesome/src/components/tooltip/tooltip.css b/packages/webawesome/src/components/tooltip/tooltip.css deleted file mode 100644 index 6dc3d2ebc..000000000 --- a/packages/webawesome/src/components/tooltip/tooltip.css +++ /dev/null @@ -1,56 +0,0 @@ -:host { - --max-width: 30ch; - - /** These styles are added so we don't interfere in the DOM. */ - display: inline-block; - position: absolute; - - /** Defaults for inherited CSS properties */ - color: var(--wa-tooltip-content-color); - font-size: var(--wa-tooltip-font-size); - line-height: var(--wa-tooltip-line-height); - text-align: start; - white-space: normal; -} - -.tooltip { - --arrow-size: var(--wa-tooltip-arrow-size); - --arrow-color: var(--wa-tooltip-background-color); -} - -.tooltip::part(popup) { - z-index: 1000; -} - -.tooltip[placement^='top']::part(popup) { - transform-origin: bottom; -} - -.tooltip[placement^='bottom']::part(popup) { - transform-origin: top; -} - -.tooltip[placement^='left']::part(popup) { - transform-origin: right; -} - -.tooltip[placement^='right']::part(popup) { - transform-origin: left; -} - -.body { - display: block; - width: max-content; - max-width: var(--max-width); - border-radius: var(--wa-tooltip-border-radius); - background-color: var(--wa-tooltip-background-color); - border: var(--wa-tooltip-border-width) var(--wa-tooltip-border-style) var(--wa-tooltip-border-color); - padding: 0.25em 0.5em; - user-select: none; - -webkit-user-select: none; -} - -.tooltip::part(arrow) { - border-bottom: var(--wa-tooltip-border-width) var(--wa-tooltip-border-style) var(--wa-tooltip-border-color); - border-right: var(--wa-tooltip-border-width) var(--wa-tooltip-border-style) var(--wa-tooltip-border-color); -} diff --git a/packages/webawesome/src/components/tooltip/tooltip.styles.ts b/packages/webawesome/src/components/tooltip/tooltip.styles.ts new file mode 100644 index 000000000..6bb5be759 --- /dev/null +++ b/packages/webawesome/src/components/tooltip/tooltip.styles.ts @@ -0,0 +1,60 @@ +import { css } from 'lit'; + +export default css` + :host { + --max-width: 30ch; + + /** These styles are added so we don't interfere in the DOM. */ + display: inline-block; + position: absolute; + + /** Defaults for inherited CSS properties */ + color: var(--wa-tooltip-content-color); + font-size: var(--wa-tooltip-font-size); + line-height: var(--wa-tooltip-line-height); + text-align: start; + white-space: normal; + } + + .tooltip { + --arrow-size: var(--wa-tooltip-arrow-size); + --arrow-color: var(--wa-tooltip-background-color); + } + + .tooltip::part(popup) { + z-index: 1000; + } + + .tooltip[placement^='top']::part(popup) { + transform-origin: bottom; + } + + .tooltip[placement^='bottom']::part(popup) { + transform-origin: top; + } + + .tooltip[placement^='left']::part(popup) { + transform-origin: right; + } + + .tooltip[placement^='right']::part(popup) { + transform-origin: left; + } + + .body { + display: block; + width: max-content; + max-width: var(--max-width); + border-radius: var(--wa-tooltip-border-radius); + background-color: var(--wa-tooltip-background-color); + border: var(--wa-tooltip-border-width) var(--wa-tooltip-border-style) var(--wa-tooltip-border-color); + padding: 0.25em 0.5em; + user-select: none; + -webkit-user-select: none; + } + + .tooltip::part(arrow) { + border-bottom: var(--wa-tooltip-border-width) var(--wa-tooltip-border-style) var(--wa-tooltip-border-color); + border-right: var(--wa-tooltip-border-width) var(--wa-tooltip-border-style) var(--wa-tooltip-border-color); + } +`; diff --git a/packages/webawesome/src/components/tooltip/tooltip.ts b/packages/webawesome/src/components/tooltip/tooltip.ts index abbbcfd85..db5d6bebd 100644 --- a/packages/webawesome/src/components/tooltip/tooltip.ts +++ b/packages/webawesome/src/components/tooltip/tooltip.ts @@ -11,7 +11,7 @@ import { uniqueId } from '../../internal/math.js'; import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import WaPopup from '../popup/popup.js'; -import styles from './tooltip.css'; +import styles from './tooltip.styles.js'; /** * @summary Tooltips display additional information based on a specific action. diff --git a/packages/webawesome/src/components/tree-item/tree-item.css b/packages/webawesome/src/components/tree-item/tree-item.css deleted file mode 100644 index a34ed4d3d..000000000 --- a/packages/webawesome/src/components/tree-item/tree-item.css +++ /dev/null @@ -1,150 +0,0 @@ -:host { - --show-duration: 200ms; - --hide-duration: 200ms; - - display: block; - color: var(--wa-color-text-normal); - outline: 0; - z-index: 0; -} - -:host(:focus) { - outline: none; -} - -slot:not([name])::slotted(wa-icon) { - margin-inline-end: var(--wa-space-xs); -} - -.tree-item { - position: relative; - display: flex; - align-items: stretch; - flex-direction: column; - cursor: default; - user-select: none; - -webkit-user-select: none; -} - -.checkbox { - line-height: var(--wa-form-control-value-line-height); - pointer-events: none; -} - -.expand-button, -.checkbox, -.label { - font-family: inherit; - font-size: var(--wa-font-size-m); - font-weight: inherit; -} - -.checkbox::part(base) { - display: flex; - align-items: center; -} - -.indentation { - display: block; - width: 1em; - flex-shrink: 0; -} - -.expand-button { - display: flex; - align-items: center; - justify-content: center; - color: var(--wa-color-text-quiet); - width: 2em; - height: 2em; - flex-shrink: 0; - cursor: pointer; -} - -.expand-button { - transition: rotate var(--wa-transition-normal) var(--wa-transition-easing); -} - -.tree-item-expanded .expand-button { - rotate: 90deg; -} - -.tree-item-expanded:dir(rtl) .expand-button { - rotate: -90deg; -} - -.tree-item-expanded:not(.tree-item-loading) slot[name='expand-icon'], -.tree-item:not(.tree-item-expanded) slot[name='collapse-icon'] { - display: none; -} - -.tree-item:not(.tree-item-has-expand-button):not(.tree-item-loading) .expand-icon-slot { - display: none; -} - -.tree-item-loading .expand-icon-slot wa-icon { - display: none; -} - -.expand-button-visible { - cursor: pointer; -} - -.item { - display: flex; - align-items: center; - border-inline-start: solid 3px transparent; -} - -:host([disabled]) .item { - opacity: 0.5; - outline: none; - cursor: not-allowed; -} - -:host(:focus-visible) .item { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); - z-index: 2; -} - -:host(:not([aria-disabled='true'])) .tree-item-selected .item { - background-color: var(--wa-color-neutral-fill-quiet); - border-inline-start-color: var(--wa-color-brand-fill-loud); -} - -:host(:not([aria-disabled='true'])) .expand-button { - color: var(--wa-color-text-quiet); -} - -.label { - display: flex; - align-items: center; - transition: color var(--wa-transition-normal) var(--wa-transition-easing); -} - -.children { - display: block; - font-size: calc(1em + var(--indent-size, var(--wa-space-m))); -} - -/* Indentation lines */ -.children { - position: relative; -} - -.children::before { - content: ''; - position: absolute; - top: var(--indent-guide-offset); - bottom: var(--indent-guide-offset); - inset-inline-start: calc(1em - (var(--indent-guide-width) / 2) - 1px); - border-inline-end: var(--indent-guide-width) var(--indent-guide-style) var(--indent-guide-color); - z-index: 1; -} - -@media (forced-colors: active) { - :host(:not([aria-disabled='true'])) .tree-item-selected .item { - outline: dashed 1px SelectedItem; - } -} diff --git a/packages/webawesome/src/components/tree-item/tree-item.styles.ts b/packages/webawesome/src/components/tree-item/tree-item.styles.ts new file mode 100644 index 000000000..e2c49c6fc --- /dev/null +++ b/packages/webawesome/src/components/tree-item/tree-item.styles.ts @@ -0,0 +1,154 @@ +import { css } from 'lit'; + +export default css` + :host { + --show-duration: 200ms; + --hide-duration: 200ms; + + display: block; + color: var(--wa-color-text-normal); + outline: 0; + z-index: 0; + } + + :host(:focus) { + outline: none; + } + + slot:not([name])::slotted(wa-icon) { + margin-inline-end: var(--wa-space-xs); + } + + .tree-item { + position: relative; + display: flex; + align-items: stretch; + flex-direction: column; + cursor: default; + user-select: none; + -webkit-user-select: none; + } + + .checkbox { + line-height: var(--wa-form-control-value-line-height); + pointer-events: none; + } + + .expand-button, + .checkbox, + .label { + font-family: inherit; + font-size: var(--wa-font-size-m); + font-weight: inherit; + } + + .checkbox::part(base) { + display: flex; + align-items: center; + } + + .indentation { + display: block; + width: 1em; + flex-shrink: 0; + } + + .expand-button { + display: flex; + align-items: center; + justify-content: center; + color: var(--wa-color-text-quiet); + width: 2em; + height: 2em; + flex-shrink: 0; + cursor: pointer; + } + + .expand-button { + transition: rotate var(--wa-transition-normal) var(--wa-transition-easing); + } + + .tree-item-expanded .expand-button { + rotate: 90deg; + } + + .tree-item-expanded:dir(rtl) .expand-button { + rotate: -90deg; + } + + .tree-item-expanded:not(.tree-item-loading) slot[name='expand-icon'], + .tree-item:not(.tree-item-expanded) slot[name='collapse-icon'] { + display: none; + } + + .tree-item:not(.tree-item-has-expand-button):not(.tree-item-loading) .expand-icon-slot { + display: none; + } + + .tree-item-loading .expand-icon-slot wa-icon { + display: none; + } + + .expand-button-visible { + cursor: pointer; + } + + .item { + display: flex; + align-items: center; + border-inline-start: solid 3px transparent; + } + + :host([disabled]) .item { + opacity: 0.5; + outline: none; + cursor: not-allowed; + } + + :host(:focus-visible) .item { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + z-index: 2; + } + + :host(:not([aria-disabled='true'])) .tree-item-selected .item { + background-color: var(--wa-color-neutral-fill-quiet); + border-inline-start-color: var(--wa-color-brand-fill-loud); + } + + :host(:not([aria-disabled='true'])) .expand-button { + color: var(--wa-color-text-quiet); + } + + .label { + display: flex; + align-items: center; + transition: color var(--wa-transition-normal) var(--wa-transition-easing); + } + + .children { + display: block; + font-size: calc(1em + var(--indent-size, var(--wa-space-m))); + } + + /* Indentation lines */ + .children { + position: relative; + } + + .children::before { + content: ''; + position: absolute; + top: var(--indent-guide-offset); + bottom: var(--indent-guide-offset); + inset-inline-start: calc(1em - (var(--indent-guide-width) / 2) - 1px); + border-inline-end: var(--indent-guide-width) var(--indent-guide-style) var(--indent-guide-color); + z-index: 1; + } + + @media (forced-colors: active) { + :host(:not([aria-disabled='true'])) .tree-item-selected .item { + outline: dashed 1px SelectedItem; + } + } +`; diff --git a/packages/webawesome/src/components/tree-item/tree-item.ts b/packages/webawesome/src/components/tree-item/tree-item.ts index dc15888d6..2a1ff4cf0 100644 --- a/packages/webawesome/src/components/tree-item/tree-item.ts +++ b/packages/webawesome/src/components/tree-item/tree-item.ts @@ -17,7 +17,7 @@ import { LocalizeController } from '../../utilities/localize.js'; import '../checkbox/checkbox.js'; import '../icon/icon.js'; import '../spinner/spinner.js'; -import styles from './tree-item.css'; +import styles from './tree-item.styles.js'; /** * @summary A tree item serves as a hierarchical node that lives inside a [tree](/docs/components/tree). diff --git a/packages/webawesome/src/components/tree/tree.css b/packages/webawesome/src/components/tree/tree.styles.ts similarity index 53% rename from packages/webawesome/src/components/tree/tree.css rename to packages/webawesome/src/components/tree/tree.styles.ts index b5ca3878e..666100ae2 100644 --- a/packages/webawesome/src/components/tree/tree.css +++ b/packages/webawesome/src/components/tree/tree.styles.ts @@ -1,19 +1,23 @@ -:host { - /* +import { css } from 'lit'; + +export default css` + :host { + /* * These are actually used by tree item, but we define them here so they can more easily be set and all tree items * stay consistent. */ - --indent-guide-color: var(--wa-color-surface-border); - --indent-guide-offset: 0; - --indent-guide-style: solid; - --indent-guide-width: 0; - --indent-size: var(--wa-space-l); + --indent-guide-color: var(--wa-color-surface-border); + --indent-guide-offset: 0; + --indent-guide-style: solid; + --indent-guide-width: 0; + --indent-size: var(--wa-space-l); - display: block; + display: block; - /* + /* * Tree item indentation uses the "em" unit to increment its width on each level, so setting the font size to zero * here removes the indentation for all the nodes on the first level. */ - font-size: 0; -} + font-size: 0; + } +`; diff --git a/packages/webawesome/src/components/tree/tree.ts b/packages/webawesome/src/components/tree/tree.ts index e82dab664..762cb7d44 100644 --- a/packages/webawesome/src/components/tree/tree.ts +++ b/packages/webawesome/src/components/tree/tree.ts @@ -6,7 +6,7 @@ import { watch } from '../../internal/watch.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import { LocalizeController } from '../../utilities/localize.js'; import WaTreeItem from '../tree-item/tree-item.js'; -import styles from './tree.css'; +import styles from './tree.styles.js'; function syncCheckboxes(changedTreeItem: WaTreeItem, initialSync = false) { function syncParentItem(treeItem: WaTreeItem) { diff --git a/packages/webawesome/src/components/zoomable-frame/zoomable-frame.css b/packages/webawesome/src/components/zoomable-frame/zoomable-frame.css deleted file mode 100644 index fbd1af03b..000000000 --- a/packages/webawesome/src/components/zoomable-frame/zoomable-frame.css +++ /dev/null @@ -1,82 +0,0 @@ -:host { - display: block; - position: relative; - aspect-ratio: 16 / 9; - width: 100%; - overflow: hidden; - border-radius: var(--wa-border-radius-m); -} - -#frame-container { - position: absolute; - top: 0; - left: 0; - width: calc(100% / var(--zoom)); - height: calc(100% / var(--zoom)); - transform: scale(var(--zoom)); - transform-origin: 0 0; -} - -#iframe { - width: 100%; - height: 100%; - border: none; - border-radius: inherit; - /* Prevent the iframe from being selected, e.g. by a double click. Doesn't affect selection withing the iframe. */ - user-select: none; - -webkit-user-select: none; -} - -#controls { - display: flex; - position: absolute; - bottom: 0.5em; - align-items: center; - font-weight: var(--wa-font-weight-semibold); - padding: 0.25em 0.5em; - gap: 0.5em; - border-radius: var(--wa-border-radius-s); - background: #000b; - color: white; - font-size: min(12px, 0.75em); - user-select: none; - -webkit-user-select: none; - - &:dir(ltr) { - right: 0.5em; - } - - &:dir(rtl) { - left: 0.5em; - } - - button { - display: flex; - align-items: center; - padding: 0.25em; - border: none; - background: none; - color: inherit; - cursor: pointer; - - &:focus { - outline: none; - } - - &:focus-visible { - outline: var(--wa-focus-ring); - outline-offset: var(--wa-focus-ring-offset); - } - - &:disabled { - cursor: not-allowed; - opacity: 0.5; - } - } - - span { - min-width: 4.5ch; /* extra space so numbers don't shift */ - font-variant-numeric: tabular-nums; - text-align: center; - } -} diff --git a/packages/webawesome/src/components/zoomable-frame/zoomable-frame.styles.ts b/packages/webawesome/src/components/zoomable-frame/zoomable-frame.styles.ts new file mode 100644 index 000000000..a40151dec --- /dev/null +++ b/packages/webawesome/src/components/zoomable-frame/zoomable-frame.styles.ts @@ -0,0 +1,86 @@ +import { css } from 'lit'; + +export default css` + :host { + display: block; + position: relative; + aspect-ratio: 16 / 9; + width: 100%; + overflow: hidden; + border-radius: var(--wa-border-radius-m); + } + + #frame-container { + position: absolute; + top: 0; + left: 0; + width: calc(100% / var(--zoom)); + height: calc(100% / var(--zoom)); + transform: scale(var(--zoom)); + transform-origin: 0 0; + } + + #iframe { + width: 100%; + height: 100%; + border: none; + border-radius: inherit; + /* Prevent the iframe from being selected, e.g. by a double click. Doesn't affect selection withing the iframe. */ + user-select: none; + -webkit-user-select: none; + } + + #controls { + display: flex; + position: absolute; + bottom: 0.5em; + align-items: center; + font-weight: var(--wa-font-weight-semibold); + padding: 0.25em 0.5em; + gap: 0.5em; + border-radius: var(--wa-border-radius-s); + background: #000b; + color: white; + font-size: min(12px, 0.75em); + user-select: none; + -webkit-user-select: none; + + &:dir(ltr) { + right: 0.5em; + } + + &:dir(rtl) { + left: 0.5em; + } + + button { + display: flex; + align-items: center; + padding: 0.25em; + border: none; + background: none; + color: inherit; + cursor: pointer; + + &:focus { + outline: none; + } + + &:focus-visible { + outline: var(--wa-focus-ring); + outline-offset: var(--wa-focus-ring-offset); + } + + &:disabled { + cursor: not-allowed; + opacity: 0.5; + } + } + + span { + min-width: 4.5ch; /* extra space so numbers don't shift */ + font-variant-numeric: tabular-nums; + text-align: center; + } + } +`; diff --git a/packages/webawesome/src/components/zoomable-frame/zoomable-frame.ts b/packages/webawesome/src/components/zoomable-frame/zoomable-frame.ts index 6414d6313..1b2854e87 100644 --- a/packages/webawesome/src/components/zoomable-frame/zoomable-frame.ts +++ b/packages/webawesome/src/components/zoomable-frame/zoomable-frame.ts @@ -5,7 +5,7 @@ import { ifDefined } from 'lit/directives/if-defined.js'; import { parseSpaceDelimitedTokens } from '../../internal/parse.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import { LocalizeController } from '../../utilities/localize.js'; -import styles from './zoomable-frame.css'; +import styles from './zoomable-frame.styles.js'; /** * @summary Zoomable frames render iframe content with zoom and interaction controls. diff --git a/packages/webawesome/src/internal/webawesome-element.ts b/packages/webawesome/src/internal/webawesome-element.ts index 68faf36e1..ef0e54fd5 100644 --- a/packages/webawesome/src/internal/webawesome-element.ts +++ b/packages/webawesome/src/internal/webawesome-element.ts @@ -1,13 +1,13 @@ -import type { CSSResult, CSSResultGroup, PropertyValues } from 'lit'; -import { LitElement, isServer, unsafeCSS } from 'lit'; +import type { CSSResultGroup, PropertyValues } from 'lit'; +import { LitElement, isServer } from 'lit'; import { property } from 'lit/decorators.js'; -import hostStyles from '../styles/component/host.css'; +import hostStyles from '../styles/component/host.styles.js'; // Augment Lit's module declare module 'lit' { interface PropertyDeclaration { /** - * Specifies the property’s default value + * Specifies the property's default value */ default?: any; initial?: any; @@ -15,20 +15,13 @@ declare module 'lit' { } export default class WebAwesomeElement extends LitElement { - /** - * One or more CSS files to include in the component's shadow root. Host styles are automatically prepended. We use - * this instead of Lit's styles property because we're importing CSS files as strings and need to convert them using - * unsafeCSS. - */ - static css?: CSSResultGroup | CSSResult | string | (CSSResult | string)[]; + /** One or more CSSResultGroup to include in the component's shadow root. Host styles are automatically prepended. */ + static css?: CSSResultGroup; - /** - * Override the default styles property to fetch and convert string CSS files. Components can override this behavior - * by setting their own `static styles = []` property. - */ + /** Prepends host styles to the component's styles. */ static get styles(): CSSResultGroup { const styles = Array.isArray(this.css) ? this.css : this.css ? [this.css] : []; - return [hostStyles, ...styles].map(style => (typeof style === 'string' ? unsafeCSS(style) : style)); + return [hostStyles, ...styles]; } #hasRecordedInitialProperties = false; diff --git a/packages/webawesome/src/styles/component/form-control.css b/packages/webawesome/src/styles/component/form-control.css deleted file mode 100644 index f9e1efb77..000000000 --- a/packages/webawesome/src/styles/component/form-control.css +++ /dev/null @@ -1,34 +0,0 @@ -:host { - display: flex; - flex-direction: column; -} - -/* Label */ -:is([part~='form-control-label'], [part~='label']):has(*:not(:empty)) { - display: inline-flex; - color: var(--wa-form-control-label-color); - font-weight: var(--wa-form-control-label-font-weight); - line-height: var(--wa-form-control-label-line-height); - margin-block-end: 0.5em; -} - -:host([required]) :is([part~='form-control-label'], [part~='label'])::after { - content: var(--wa-form-control-required-content); - margin-inline-start: var(--wa-form-control-required-content-offset); - color: var(--wa-form-control-required-content-color); -} - -/* Help text */ -[part~='hint'] { - display: block; - color: var(--wa-form-control-hint-color); - font-weight: var(--wa-form-control-hint-font-weight); - line-height: var(--wa-form-control-hint-line-height); - margin-block-start: 0.5em; - font-size: var(--wa-font-size-smaller); - line-height: var(--wa-form-control-label-line-height); - - &:not(.has-slotted) { - display: none; - } -} diff --git a/packages/webawesome/src/styles/component/form-control.styles.ts b/packages/webawesome/src/styles/component/form-control.styles.ts new file mode 100644 index 000000000..461d732e5 --- /dev/null +++ b/packages/webawesome/src/styles/component/form-control.styles.ts @@ -0,0 +1,38 @@ +import { css } from 'lit'; + +export default css` + :host { + display: flex; + flex-direction: column; + } + + /* Label */ + :is([part~='form-control-label'], [part~='label']):has(*:not(:empty)) { + display: inline-flex; + color: var(--wa-form-control-label-color); + font-weight: var(--wa-form-control-label-font-weight); + line-height: var(--wa-form-control-label-line-height); + margin-block-end: 0.5em; + } + + :host([required]) :is([part~='form-control-label'], [part~='label'])::after { + content: var(--wa-form-control-required-content); + margin-inline-start: var(--wa-form-control-required-content-offset); + color: var(--wa-form-control-required-content-color); + } + + /* Help text */ + [part~='hint'] { + display: block; + color: var(--wa-form-control-hint-color); + font-weight: var(--wa-form-control-hint-font-weight); + line-height: var(--wa-form-control-hint-line-height); + margin-block-start: 0.5em; + font-size: var(--wa-font-size-smaller); + line-height: var(--wa-form-control-label-line-height); + + &:not(.has-slotted) { + display: none; + } + } +`; diff --git a/packages/webawesome/src/styles/component/host.css b/packages/webawesome/src/styles/component/host.css deleted file mode 100644 index 7239526e1..000000000 --- a/packages/webawesome/src/styles/component/host.css +++ /dev/null @@ -1,13 +0,0 @@ -:host { - box-sizing: border-box !important; -} - -:host *, -:host *::before, -:host *::after { - box-sizing: inherit !important; -} - -[hidden] { - display: none !important; -} diff --git a/packages/webawesome/src/styles/component/host.styles.ts b/packages/webawesome/src/styles/component/host.styles.ts new file mode 100644 index 000000000..510b37dbd --- /dev/null +++ b/packages/webawesome/src/styles/component/host.styles.ts @@ -0,0 +1,17 @@ +import { css } from 'lit'; + +export default css` + :host { + box-sizing: border-box !important; + } + + :host *, + :host *::before, + :host *::after { + box-sizing: inherit !important; + } + + [hidden] { + display: none !important; + } +`; diff --git a/packages/webawesome/src/styles/component/size.styles.ts b/packages/webawesome/src/styles/component/size.styles.ts new file mode 100644 index 000000000..2b06e5862 --- /dev/null +++ b/packages/webawesome/src/styles/component/size.styles.ts @@ -0,0 +1,18 @@ +import { css } from 'lit'; + +export default css` + :host([size='small']), + .wa-size-s { + font-size: var(--wa-font-size-s); + } + + :host([size='medium']), + .wa-size-m { + font-size: var(--wa-font-size-m); + } + + :host([size='large']), + .wa-size-l { + font-size: var(--wa-font-size-l); + } +`; diff --git a/packages/webawesome/src/styles/component/variants.styles.ts b/packages/webawesome/src/styles/component/variants.styles.ts new file mode 100644 index 000000000..2498610ad --- /dev/null +++ b/packages/webawesome/src/styles/component/variants.styles.ts @@ -0,0 +1,69 @@ +import { css } from 'lit'; + +export default css` + :where(:root), + .wa-neutral, + :host([variant='neutral']) { + --wa-color-fill-loud: var(--wa-color-neutral-fill-loud); + --wa-color-fill-normal: var(--wa-color-neutral-fill-normal); + --wa-color-fill-quiet: var(--wa-color-neutral-fill-quiet); + --wa-color-border-loud: var(--wa-color-neutral-border-loud); + --wa-color-border-normal: var(--wa-color-neutral-border-normal); + --wa-color-border-quiet: var(--wa-color-neutral-border-quiet); + --wa-color-on-loud: var(--wa-color-neutral-on-loud); + --wa-color-on-normal: var(--wa-color-neutral-on-normal); + --wa-color-on-quiet: var(--wa-color-neutral-on-quiet); + } + + .wa-brand, + :host([variant='brand']) { + --wa-color-fill-loud: var(--wa-color-brand-fill-loud); + --wa-color-fill-normal: var(--wa-color-brand-fill-normal); + --wa-color-fill-quiet: var(--wa-color-brand-fill-quiet); + --wa-color-border-loud: var(--wa-color-brand-border-loud); + --wa-color-border-normal: var(--wa-color-brand-border-normal); + --wa-color-border-quiet: var(--wa-color-brand-border-quiet); + --wa-color-on-loud: var(--wa-color-brand-on-loud); + --wa-color-on-normal: var(--wa-color-brand-on-normal); + --wa-color-on-quiet: var(--wa-color-brand-on-quiet); + } + + .wa-success, + :host([variant='success']) { + --wa-color-fill-loud: var(--wa-color-success-fill-loud); + --wa-color-fill-normal: var(--wa-color-success-fill-normal); + --wa-color-fill-quiet: var(--wa-color-success-fill-quiet); + --wa-color-border-loud: var(--wa-color-success-border-loud); + --wa-color-border-normal: var(--wa-color-success-border-normal); + --wa-color-border-quiet: var(--wa-color-success-border-quiet); + --wa-color-on-loud: var(--wa-color-success-on-loud); + --wa-color-on-normal: var(--wa-color-success-on-normal); + --wa-color-on-quiet: var(--wa-color-success-on-quiet); + } + + .wa-warning, + :host([variant='warning']) { + --wa-color-fill-loud: var(--wa-color-warning-fill-loud); + --wa-color-fill-normal: var(--wa-color-warning-fill-normal); + --wa-color-fill-quiet: var(--wa-color-warning-fill-quiet); + --wa-color-border-loud: var(--wa-color-warning-border-loud); + --wa-color-border-normal: var(--wa-color-warning-border-normal); + --wa-color-border-quiet: var(--wa-color-warning-border-quiet); + --wa-color-on-loud: var(--wa-color-warning-on-loud); + --wa-color-on-normal: var(--wa-color-warning-on-normal); + --wa-color-on-quiet: var(--wa-color-warning-on-quiet); + } + + .wa-danger, + :host([variant='danger']) { + --wa-color-fill-loud: var(--wa-color-danger-fill-loud); + --wa-color-fill-normal: var(--wa-color-danger-fill-normal); + --wa-color-fill-quiet: var(--wa-color-danger-fill-quiet); + --wa-color-border-loud: var(--wa-color-danger-border-loud); + --wa-color-border-normal: var(--wa-color-danger-border-normal); + --wa-color-border-quiet: var(--wa-color-danger-border-quiet); + --wa-color-on-loud: var(--wa-color-danger-on-loud); + --wa-color-on-normal: var(--wa-color-danger-on-normal); + --wa-color-on-quiet: var(--wa-color-danger-on-quiet); + } +`; diff --git a/packages/webawesome/src/styles/component/visually-hidden.styles.ts b/packages/webawesome/src/styles/component/visually-hidden.styles.ts new file mode 100644 index 000000000..866bae7a7 --- /dev/null +++ b/packages/webawesome/src/styles/component/visually-hidden.styles.ts @@ -0,0 +1,18 @@ +import { css } from 'lit'; + +export default css` + .wa-visually-hidden:not(:focus-within), + .wa-visually-hidden-force, + .wa-visually-hidden-hint::part(hint), + .wa-visually-hidden-label::part(label) { + position: absolute !important; + width: 1px !important; + height: 1px !important; + clip: rect(0 0 0 0) !important; + clip-path: inset(50%) !important; + border: none !important; + overflow: hidden !important; + white-space: nowrap !important; + padding: 0 !important; + } +`; diff --git a/packages/webawesome/src/styles/utilities/size.css b/packages/webawesome/src/styles/utilities/size.css index ca8a43f36..6ad984091 100644 --- a/packages/webawesome/src/styles/utilities/size.css +++ b/packages/webawesome/src/styles/utilities/size.css @@ -1,15 +1,12 @@ @layer wa-utilities { - :host([size='small']), .wa-size-s { font-size: var(--wa-font-size-s); } - :host([size='medium']), .wa-size-m { font-size: var(--wa-font-size-m); } - :host([size='large']), .wa-size-l { font-size: var(--wa-font-size-l); } diff --git a/packages/webawesome/src/styles/utilities/variants.css b/packages/webawesome/src/styles/utilities/variants.css index df7eb08ec..b93da35a3 100644 --- a/packages/webawesome/src/styles/utilities/variants.css +++ b/packages/webawesome/src/styles/utilities/variants.css @@ -1,7 +1,6 @@ @layer wa-utilities { :where(:root), - .wa-neutral, - :host([variant='neutral']) { + .wa-neutral { --wa-color-fill-loud: var(--wa-color-neutral-fill-loud); --wa-color-fill-normal: var(--wa-color-neutral-fill-normal); --wa-color-fill-quiet: var(--wa-color-neutral-fill-quiet); @@ -13,8 +12,7 @@ --wa-color-on-quiet: var(--wa-color-neutral-on-quiet); } - .wa-brand, - :host([variant='brand']) { + .wa-brand { --wa-color-fill-loud: var(--wa-color-brand-fill-loud); --wa-color-fill-normal: var(--wa-color-brand-fill-normal); --wa-color-fill-quiet: var(--wa-color-brand-fill-quiet); @@ -26,8 +24,7 @@ --wa-color-on-quiet: var(--wa-color-brand-on-quiet); } - .wa-success, - :host([variant='success']) { + .wa-success { --wa-color-fill-loud: var(--wa-color-success-fill-loud); --wa-color-fill-normal: var(--wa-color-success-fill-normal); --wa-color-fill-quiet: var(--wa-color-success-fill-quiet); @@ -39,8 +36,7 @@ --wa-color-on-quiet: var(--wa-color-success-on-quiet); } - .wa-warning, - :host([variant='warning']) { + .wa-warning { --wa-color-fill-loud: var(--wa-color-warning-fill-loud); --wa-color-fill-normal: var(--wa-color-warning-fill-normal); --wa-color-fill-quiet: var(--wa-color-warning-fill-quiet); @@ -52,8 +48,7 @@ --wa-color-on-quiet: var(--wa-color-warning-on-quiet); } - .wa-danger, - :host([variant='danger']) { + .wa-danger { --wa-color-fill-loud: var(--wa-color-danger-fill-loud); --wa-color-fill-normal: var(--wa-color-danger-fill-normal); --wa-color-fill-quiet: var(--wa-color-danger-fill-quiet); From e0f6ff11ecc4e2df5410fd0e1b8b4253d7174191 Mon Sep 17 00:00:00 2001 From: Cory LaViska <cory@abeautifulsite.net> Date: Thu, 11 Dec 2025 13:04:42 -0500 Subject: [PATCH 44/59] Combobox (supporting contributions in free) (#1838) * hold the mustard! let's make it yellower * words * update sidebar * update changelog * fix icon cropping * add combobox support * preserve user-selected order * add quick pro flag * move import to the top * fix custom tag example --- cspell.json | 4 ++ .../webawesome/docs/_includes/sidebar.njk | 12 +++- .../webawesome/docs/_layouts/component.njk | 68 ++++++++++--------- .../webawesome/docs/docs/components/select.md | 9 ++- .../docs/docs/resources/changelog.md | 3 + .../src/components/option/option.ts | 16 ++++- .../src/components/select/select.ts | 55 +++++++++++---- 7 files changed, 114 insertions(+), 53 deletions(-) diff --git a/cspell.json b/cspell.json index e5b28277e..6c381b4df 100644 --- a/cspell.json +++ b/cspell.json @@ -105,6 +105,7 @@ "keydown", "keyframes", "keymaker", + "Kickstarter", "Konnor", "Kool", "labelledby", @@ -117,6 +118,7 @@ "lowercasing", "Lucide", "maxlength", + "mdash", "Menlo", "menuitemcheckbox", "menuitemradio", @@ -130,6 +132,7 @@ "mouseout", "mouseup", "multiselectable", + "nbsp", "nextjs", "nocheck", "noindex", @@ -179,6 +182,7 @@ "shadowrootmode", "Shortcode", "Shortcodes", + "signup", "sitedir", "slotchange", "smartquotes", diff --git a/packages/webawesome/docs/_includes/sidebar.njk b/packages/webawesome/docs/_includes/sidebar.njk index 0d629660c..1f85fb8c9 100644 --- a/packages/webawesome/docs/_includes/sidebar.njk +++ b/packages/webawesome/docs/_includes/sidebar.njk @@ -81,7 +81,15 @@ <li><span class="is-planned wa-split">Charts <span><a href="https://github.com/shoelace-style/webawesome/issues/1073" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge({ description: "This will require access to Web Awesome Pro" }) }}</span></span></li> <li><a href="/docs/components/checkbox/">Checkbox</a></li> <li><a href="/docs/components/color-picker/">Color Picker</a></li> - <li><span class="is-planned wa-split">Combobox <span><a href="https://github.com/shoelace-style/webawesome/issues/1074" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge({ description: "This will require access to Web Awesome Pro" }) }}</span></span></li> + <li> + <span class="wa-split"> + <span> + <a href="/docs/components/combobox">Combobox</a> + <wa-icon name="flask" aria-hidden="true" class="icon-shrink"></wa-icon> + </span> + {{ proBadge() }} + </span> + </li> <li><a href="/docs/components/comparison/">Comparison</a></li> <li> <a class="wa-cluster wa-gap-xs" href="/docs/components/copy-button/"> @@ -90,7 +98,7 @@ </a> </li> <li><span class="is-planned wa-split">Data Grid <span><a href="https://github.com/shoelace-style/webawesome/issues/1072" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge({ description: "This will require access to Web Awesome Pro" }) }}</span></span></li> - <li><span class="is-planned wa-split">Datepicker <span><a href="https://github.com/shoelace-style/webawesome/issues/1075" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge({ description: "This will require access to Web Awesome Pro" }) }}</span></span></li> + <li><span class="is-planned wa-split">Date Picker <span><a href="https://github.com/shoelace-style/webawesome/issues/1075" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge({ description: "This will require access to Web Awesome Pro" }) }}</span></span></li> <li><a href="/docs/components/details/">Details</a></li> <li><a href="/docs/components/dialog/">Dialog</a></li> <li><a href="/docs/components/divider/">Divider</a></li> diff --git a/packages/webawesome/docs/_layouts/component.njk b/packages/webawesome/docs/_layouts/component.njk index 4c260f17c..b6dd8d18d 100644 --- a/packages/webawesome/docs/_layouts/component.njk +++ b/packages/webawesome/docs/_layouts/component.njk @@ -8,10 +8,13 @@ <wa-badge variant="neutral">Since {{ component.since }}</wa-badge> <wa-badge {% if component.status == 'stable' %}variant="brand"{% endif %} - {% if component.status == 'experimental' %}variant="warning"{% endif %} + {% if component.status == 'experimental' %}variant="warning" appearance="filled"{% endif %} > {{ component.status }} </wa-badge> + {% if isProComponent %} + <wa-badge class="pro">Pro</wa-badge> + {% endif %} </div> <p class="component-summary"> {{ component.summary | inlineMarkdown | safe }} @@ -20,6 +23,37 @@ {# Component API #} {% block afterContent %} + {# Importing #} + <h2>Importing</h2> + <p> + Autoloading components via <a href="/docs/#using-a-project">projects</a> is the recommended way to import components. If you prefer to do it manually, use one of the following code snippets. + </p> + + {% set componentName = component.tagName | stripPrefix %} + {% set componentPath = ["components/", componentName, "/", componentName, ".js"] | join("") %} + <wa-tab-group label="How would you like to import this component?"> + <wa-tab panel="cdn">CDN</wa-tab> + <wa-tab panel="npm">npm</wa-tab> + <wa-tab panel="react">React</wa-tab> + <wa-tab-panel name="cdn"> + <p> + Let your project code do the work! <a href="/signup">Sign up for free</a> to use a project with your very own CDN — it's the fastest and easiest way to use Web Awesome. + </p> + </wa-tab-panel> + <wa-tab-panel name="npm"> + <p> + To manually import this component from NPM, use the following code. + </p> + <pre><code class="language-js">import '@awesome.me/webawesome/dist/{{ componentPath }}';</code></pre> + </wa-tab-panel> + <wa-tab-panel name="react"> + <p> + To manually import this component from React, use the following code. + </p> + <pre><code class="language-js">import {{ component.name }} from '@awesome.me/webawesome/dist/react/{{ componentName }}';</code></pre> + </wa-tab-panel> + </wa-tab-group> + {# Slots #} {% if component.slots.length %} <h2>Slots</h2> @@ -270,38 +304,6 @@ </ul> {% endif %} - {# Importing #} - <h2>Importing</h2> - <p> - Autoloading components via <a href="/docs/#using-a-project">projects</a> is the recommended way to import components. If you prefer to do it manually, use one of the following code snippets. - </p> - - - {% set componentName = component.tagName | stripPrefix %} - {% set componentPath = ["components/", componentName, "/", componentName, ".js"] | join("") %} - <wa-tab-group label="How would you like to import this component?"> - <wa-tab panel="cdn">CDN</wa-tab> - <wa-tab panel="npm">npm</wa-tab> - <wa-tab panel="react">React</wa-tab> - <wa-tab-panel name="cdn"> - <p> - Let your project code do the work! <a href="/signup">Sign up for free</a> to use a project with your very own CDN — it's the fastest and easiest way to use Web Awesome. - </p> - </wa-tab-panel> - <wa-tab-panel name="npm"> - <p> - To manually import this component from NPM, use the following code. - </p> - <pre><code class="language-js">import '@awesome.me/webawesome/dist/{{ componentPath }}';</code></pre> - </wa-tab-panel> - <wa-tab-panel name="react"> - <p> - To manually import this component from React, use the following code. - </p> - <pre><code class="language-js">import {{ component.name }} from '@awesome.me/webawesome/dist/react/{{ componentName }}';</code></pre> - </wa-tab-panel> - </wa-tab-group> - <wa-divider></wa-divider> <div class="component-help"> diff --git a/packages/webawesome/docs/docs/components/select.md b/packages/webawesome/docs/docs/components/select.md index ff7135e8a..2878fc00b 100644 --- a/packages/webawesome/docs/docs/components/select.md +++ b/packages/webawesome/docs/docs/components/select.md @@ -285,9 +285,10 @@ Remember that custom tags are rendered in a shadow root. To style them, you can const name = option.querySelector('wa-icon[slot="start"]').name; // You can return a string, a Lit Template, or an HTMLElement here + // Important: include data-value so the tag can be removed properly! return ` - <wa-tag with-remove> - <wa-icon name="${name}" style="padding-inline-end: .5rem;"></wa-icon> + <wa-tag with-remove data-value="${option.value}"> + <wa-icon name="${name}"></wa-icon> ${option.label} </wa-tag> `; @@ -299,6 +300,10 @@ Remember that custom tags are rendered in a shadow root. To style them, you can Be sure you trust the content you are outputting! Passing unsanitized user input to `getTag()` can result in XSS vulnerabilities. ::: +:::info +When using custom tags with `with-remove`, you must include the `data-value` attribute set to the option's value. This allows the select to identify which option to deselect when the tag's remove button is clicked. +::: + ### Lazy loading options Lazy loading options works similarly to native `<select>` elements. The select component handles various scenarios intelligently: diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index aa9cfb0ba..28668cb69 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -13,6 +13,7 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh ## Next +- Added `<wa-combobox>` as an experimental pro component [issue:1074] - Added `layers.css` to define cascade layer order and updated palettes, themes, native styles, and utilities to import the new rule for more fail-safe modularity [pr:1793] - Fixed a bug in `<wa-slider>` that caused some touch devices to end up with the incorrect value [issue:1703] - Fixed a bug in `<wa-card>` that prevented some slots from being detected correctly [discuss:1450] @@ -21,6 +22,8 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh - Fixed a bug in `<wa-tree-item>` that caused the spinner to not show when lazy loading [issue:1678] - Fixed a bug in `<wa-dropdown>` that caused the browser to hang when cancelling the `wa-hide` event [issue:1483] - Fixed a bug in `<wa-dropdown-item>` that prevented the icon dependency from being imported [issue:1825] +- Fixed a bug in `<wa-select>` that prevented clicks on the tag's remove button from removing options in multiple mode +- Fixed a bug in `<wa-select>` that caused tags to appear in alphabetical order instead of selection order when using `multiple` - Improved performance of `<wa-icon>` so initial rendering occurs faster, especially with multiple icons on the page [issue:1729] - Improved performance of all components by fixing how CSS is imported and reused [issue:1812] - Modified the default `transition` styles of `<wa-dropdown-item>` to use design tokens [pr:1693] diff --git a/packages/webawesome/src/components/option/option.ts b/packages/webawesome/src/components/option/option.ts index 852957ade..2bbf69242 100644 --- a/packages/webawesome/src/components/option/option.ts +++ b/packages/webawesome/src/components/option/option.ts @@ -5,6 +5,7 @@ import getText from '../../internal/get-text.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import { LocalizeController } from '../../utilities/localize.js'; import '../icon/icon.js'; +import type WaSelect from '../select/select.js'; import styles from './option.styles.js'; /** @@ -109,7 +110,7 @@ export default class WaOption extends WebAwesomeElement { this.updateDefaultLabel(); if (this.isInitialized) { - // When the label changes, tell the controller to update + // When the label changes, tell the parent <wa-select> to update customElements.whenDefined('wa-select').then(() => { const controller = this.closest('wa-select'); if (controller) { @@ -117,6 +118,16 @@ export default class WaOption extends WebAwesomeElement { controller.selectionChanged?.(); } }); + + // When the label changes, tell the parent <wa-combobox> to update + customElements.whenDefined('wa-combobox').then(() => { + // We cast to <wa-select> because it shares the same API as combobox + const controller = this.closest<WaSelect>('wa-combobox'); + if (controller) { + controller.handleDefaultSlotChange(); + controller.selectionChanged?.(); + } + }); } else { this.isInitialized = true; } @@ -134,7 +145,8 @@ export default class WaOption extends WebAwesomeElement { protected willUpdate(changedProperties: PropertyValues<this>): void { if (changedProperties.has('defaultSelected')) { - if (!this.closest('wa-select')?.hasInteracted) { + // We cast to <wa-select> because it shares the same API as combobox + if (!this.closest<WaSelect>('wa-combobox, wa-select')?.hasInteracted) { const oldVal = this.selected; this.selected = this.defaultSelected; this.requestUpdate('selected', oldVal); diff --git a/packages/webawesome/src/components/select/select.ts b/packages/webawesome/src/components/select/select.ts index 37f0c0d02..ddc51ce33 100644 --- a/packages/webawesome/src/components/select/select.ts +++ b/packages/webawesome/src/components/select/select.ts @@ -7,7 +7,7 @@ import { WaAfterHideEvent } from '../../events/after-hide.js'; import { WaAfterShowEvent } from '../../events/after-show.js'; import { WaClearEvent } from '../../events/clear.js'; import { WaHideEvent } from '../../events/hide.js'; -import type { WaRemoveEvent } from '../../events/remove.js'; +import { WaRemoveEvent } from '../../events/remove.js'; import { WaShowEvent } from '../../events/show.js'; import { animateWithClass } from '../../internal/animate.js'; import { waitForEvent } from '../../internal/event.js'; @@ -99,6 +99,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement { private readonly hasSlotController = new HasSlotController(this, 'hint', 'label'); private readonly localize = new LocalizeController(this); + private selectionOrder: Map<string, number> = new Map(); private typeToSelectString = ''; private typeToSelectTimeout: number; @@ -285,6 +286,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement { ?pill=${this.pill} size=${this.size} with-remove + data-value=${option.value} + @wa-remove=${(event: WaRemoveEvent) => this.handleTagRemove(event, option)} > ${option.label} </wa-tag> @@ -520,6 +523,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement { event.stopPropagation(); if (this.value !== null) { + this.selectionOrder.clear(); this.setSelectedOptions([]); this.displayInput.focus({ preventScroll: true }); @@ -603,24 +607,20 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement { if (this.disabled) return; + // Mark as interacted so selectionChanged() uses the correct filter logic + this.hasInteracted = true; + this.valueHasChanged = true; + // Use the directly provided option if available (from getTag method) let option = directOption; - // If no direct option was provided, find the option from the event path + // If no direct option was provided, find the option from the data-value attribute if (!option) { - const tagElement = (event.target as Element).closest('wa-tag[part~=tag]'); + const tagElement = (event.target as Element).closest('wa-tag[data-value]') as HTMLElement | null; if (tagElement) { - // Find the index of this tag among all tags - const tagsContainer = this.shadowRoot?.querySelector('[part="tags"]'); - if (tagsContainer) { - const allTags = Array.from(tagsContainer.children); - const index = allTags.indexOf(tagElement as HTMLElement); - - if (index >= 0 && index < this.selectedOptions.length) { - option = this.selectedOptions[index]; - } - } + const value = tagElement.dataset.value; + option = this.selectedOptions.find(opt => opt.value === value); } } @@ -707,7 +707,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement { const options = this.getAllOptions(); // Update selected options cache - this.selectedOptions = options.filter(el => { + const newSelectedOptions = options.filter(el => { if (!this.hasInteracted && !this.valueHasChanged) { const defaultValue = this.defaultValue; const defaultValues = Array.isArray(defaultValue) ? defaultValue : [defaultValue]; @@ -717,6 +717,32 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement { return el.selected; }); + // Update the selection order map + const newSelectedValues = new Set(newSelectedOptions.map(el => el.value)); + + // Remove deselected options from the order map + for (const value of this.selectionOrder.keys()) { + if (!newSelectedValues.has(value)) { + this.selectionOrder.delete(value); + } + } + + // Add newly selected options + const maxOrder = this.selectionOrder.size > 0 ? Math.max(...this.selectionOrder.values()) : -1; + let nextOrder = maxOrder + 1; + for (const option of newSelectedOptions) { + if (!this.selectionOrder.has(option.value)) { + this.selectionOrder.set(option.value, nextOrder++); + } + } + + // Sort options by selection order + this.selectedOptions = newSelectedOptions.sort((a, b) => { + const orderA = this.selectionOrder.get(a.value) ?? 0; + const orderB = this.selectionOrder.get(b.value) ?? 0; + return orderA - orderB; + }); + let selectedValues = new Set(this.selectedOptions.map(el => el.value)); // Toggle values present in the DOM from this.value, while preserving options NOT present in the DOM (for lazy loading) @@ -888,6 +914,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement { } formResetCallback() { + this.selectionOrder.clear(); this.value = this.defaultValue; super.formResetCallback(); this.handleValueChange(); From fa073e8d21cca1ed65109f315f94e41a0e7e71b1 Mon Sep 17 00:00:00 2001 From: Kelsey Jackson <kelseythej@gmail.com> Date: Mon, 15 Dec 2025 14:01:32 -0600 Subject: [PATCH 45/59] updated sidebar (#1873) * updated sidebar * added video item * fixed formatting * update --------- Co-authored-by: Cory LaViska <cory@abeautifulsite.net> --- .../webawesome/docs/_includes/sidebar.njk | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/webawesome/docs/_includes/sidebar.njk b/packages/webawesome/docs/_includes/sidebar.njk index 1f85fb8c9..fe26602c6 100644 --- a/packages/webawesome/docs/_includes/sidebar.njk +++ b/packages/webawesome/docs/_includes/sidebar.njk @@ -162,7 +162,25 @@ <li><a href="/docs/components/tree-item/">Tree Item</a></li> </ul> </li> - <li><span class="is-planned wa-split">Video <span><a href="https://github.com/shoelace-style/webawesome/issues/985" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge( { description: "This will require access to Web Awesome Pro" }) }}</span></span></li> + <li> + <span class="wa-split"> + <span> + <a href="/docs/components/video">Video</a> + <wa-icon name="flask" aria-hidden="true" class="icon-shrink"></wa-icon> + </span> + {{ proBadge() }} + <ul style="margin-block-start: var(--wa-space-3xs);"> + <li> + <span class="wa-split"> + <span> + <a href="/docs/components/video-item/">Video Item</a> + </li> + <wa-icon name="flask" aria-hidden="true" class="icon-shrink"></wa-icon> + </span> + </span> + </ul> + </span> + </li> <li><a href="/docs/components/zoomable-frame">Zoomable Frame</a></li> {# PLOP_NEW_COMPONENT_PLACEHOLDER #} </ul> From b6b82fb0ac1bf19809c443e77275dc4bbb7ab18f Mon Sep 17 00:00:00 2001 From: Cory LaViska <cory@abeautifulsite.net> Date: Mon, 15 Dec 2025 15:59:38 -0500 Subject: [PATCH 46/59] Revert "updated sidebar (#1873)" (#1875) This reverts commit fa073e8d21cca1ed65109f315f94e41a0e7e71b1. --- .../webawesome/docs/_includes/sidebar.njk | 20 +------------------ 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/packages/webawesome/docs/_includes/sidebar.njk b/packages/webawesome/docs/_includes/sidebar.njk index fe26602c6..1f85fb8c9 100644 --- a/packages/webawesome/docs/_includes/sidebar.njk +++ b/packages/webawesome/docs/_includes/sidebar.njk @@ -162,25 +162,7 @@ <li><a href="/docs/components/tree-item/">Tree Item</a></li> </ul> </li> - <li> - <span class="wa-split"> - <span> - <a href="/docs/components/video">Video</a> - <wa-icon name="flask" aria-hidden="true" class="icon-shrink"></wa-icon> - </span> - {{ proBadge() }} - <ul style="margin-block-start: var(--wa-space-3xs);"> - <li> - <span class="wa-split"> - <span> - <a href="/docs/components/video-item/">Video Item</a> - </li> - <wa-icon name="flask" aria-hidden="true" class="icon-shrink"></wa-icon> - </span> - </span> - </ul> - </span> - </li> + <li><span class="is-planned wa-split">Video <span><a href="https://github.com/shoelace-style/webawesome/issues/985" target="_blank">{{ plannedBadge("A Web Awesome Kickstarter stretch goal!") }}</a>{{ proBadge( { description: "This will require access to Web Awesome Pro" }) }}</span></span></li> <li><a href="/docs/components/zoomable-frame">Zoomable Frame</a></li> {# PLOP_NEW_COMPONENT_PLACEHOLDER #} </ul> From a50648c6e893d5f4f6de64d4960b236419d67cf2 Mon Sep 17 00:00:00 2001 From: Lindsay M <126139086+lindsaym-fa@users.noreply.github.com> Date: Mon, 15 Dec 2025 17:49:56 -0500 Subject: [PATCH 47/59] Update changelog with leftover items (#1874) * add leftover logs * remove video component log --- packages/webawesome/docs/docs/resources/changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index 28668cb69..8b5763c0b 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -14,6 +14,8 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh ## Next - Added `<wa-combobox>` as an experimental pro component [issue:1074] +- Added version 2.0.0 of the [official Web Awesome Figma Design Kit](/docs/resources/figma) +- Added npm support for Web Awesome Pro - Added `layers.css` to define cascade layer order and updated palettes, themes, native styles, and utilities to import the new rule for more fail-safe modularity [pr:1793] - Fixed a bug in `<wa-slider>` that caused some touch devices to end up with the incorrect value [issue:1703] - Fixed a bug in `<wa-card>` that prevented some slots from being detected correctly [discuss:1450] From b3844ef77f87f0ad90d0cf7c377401749f8ac0b6 Mon Sep 17 00:00:00 2001 From: Konnor Rogers <konnor5456@gmail.com> Date: Tue, 16 Dec 2025 10:29:26 -0500 Subject: [PATCH 48/59] fixing deploy scripts, and updating root version (#1857) * fixing deploy scripts * update root version script * prettier --- package-lock.json | 18 ++++++++++++- package.json | 4 +-- packages/webawesome/package.json | 2 +- .../webawesome/scripts/update-root-version.js | 27 +++++++++++++++++++ 4 files changed, 47 insertions(+), 4 deletions(-) create mode 100755 packages/webawesome/scripts/update-root-version.js diff --git a/package-lock.json b/package-lock.json index 311fb0b01..dd58b9bd2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14053,7 +14053,8 @@ "devDependencies": { "@wc-toolkit/jsx-types": "^1.3.0", "eleventy-plugin-git-commit-date": "^0.1.3", - "esbuild": "^0.25.11" + "esbuild": "^0.25.11", + "npm-check-updates": "^19.1.2" }, "engines": { "node": ">=14.17.0" @@ -14526,6 +14527,21 @@ "node": "^18 || >=20" } }, + "packages/webawesome-pro/node_modules/npm-check-updates": { + "version": "19.1.2", + "resolved": "https://registry.npmjs.org/npm-check-updates/-/npm-check-updates-19.1.2.tgz", + "integrity": "sha512-FNeFCVgPOj0fz89hOpGtxP2rnnRHR7hD2E8qNU8SMWfkyDZXA/xpgjsL3UMLSo3F/K13QvJDnbxPngulNDDo/g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "ncu": "build/cli.js", + "npm-check-updates": "build/cli.js" + }, + "engines": { + "node": ">=20.0.0", + "npm": ">=8.12.1" + } + }, "packages/webawesome/node_modules/@esbuild/aix-ppc64": { "version": "0.25.11", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", diff --git a/package.json b/package.json index 3393e2f48..2f2b446cc 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@webawesome/monorepo", "private": true, "description": "A forward-thinking library of web components.", - "version": "3.0.0-alpha.13", + "version": "3.0.0", "homepage": "https://webawesome.com/", "author": "Web Awesome", "license": "MIT", @@ -85,4 +85,4 @@ "prettier --write" ] } -} +} \ No newline at end of file diff --git a/packages/webawesome/package.json b/packages/webawesome/package.json index 151ae51df..11c9a106e 100644 --- a/packages/webawesome/package.json +++ b/packages/webawesome/package.json @@ -67,7 +67,7 @@ "check-updates": "npm-check-updates --cooldown 7 --interactive --format group", "print-version": "echo $npm_package_version", "tag-version": "git tag -a \"v$(npm run print-version | tail -n1)\" -m \"tag v$(npm run print-version | tail -n1)\"", - "postversion": "npm run tag-version" + "postversion": "npm run tag-version && node ./scripts/update-root-version.js" }, "engines": { "node": ">=14.17.0" diff --git a/packages/webawesome/scripts/update-root-version.js b/packages/webawesome/scripts/update-root-version.js new file mode 100755 index 000000000..20490af94 --- /dev/null +++ b/packages/webawesome/scripts/update-root-version.js @@ -0,0 +1,27 @@ +#!/usr/bin/env node + +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import * as url from 'url'; + +const __dirname = url.fileURLToPath(new URL('.', import.meta.url)); + +const monorepoRoot = path.resolve(__dirname, '..', '..', '..'); +const rootPackageJSONFile = path.join(monorepoRoot, 'package.json'); +const webawesomePackageJSONFile = path.join(path.resolve(__dirname, '..'), 'package.json'); + +const rootPackageJSON = JSON.parse(fs.readFileSync(rootPackageJSONFile)); +const webawesomePackageJSON = JSON.parse(fs.readFileSync(webawesomePackageJSONFile)); + +const currentVersion = webawesomePackageJSON.version; +rootPackageJSON.version = currentVersion; + +fs.writeFileSync(rootPackageJSONFile, JSON.stringify(rootPackageJSON, null, 2)); + +const versionsFile = path.join(monorepoRoot, 'VERSIONS.txt'); +const versions = fs.readFileSync(versions).split(/\r?\n/); + +// TODO: Make this smart and understand semver and "insert" in the correct spot instead of appending. +if (!versions.includes(currentVersion)) { + fs.appendFileSync(webawesomePackageJSON.version); +} From 3d395653fcd7113d0e22b4efc5bf7f101ec8d68f Mon Sep 17 00:00:00 2001 From: Konnor Rogers <konnor5456@gmail.com> Date: Tue, 16 Dec 2025 10:59:40 -0500 Subject: [PATCH 49/59] Bug fix: el.form = 'foo' no longer sets the el.form property (#1815) * Bug fix: el.form = 'foo' no longer sets the el.form property * add changelog entry * add changelog entry * prettier * fix form * skip style attr for button * prettier --- .../docs/docs/resources/changelog.md | 4 ++- .../src/components/button/button.ts | 28 ++++++++++--------- .../src/components/checkbox/checkbox.ts | 7 ----- .../components/color-picker/color-picker.ts | 7 ----- .../webawesome/src/components/input/input.ts | 7 ----- .../webawesome/src/components/radio/radio.ts | 5 ---- .../src/components/select/select.ts | 7 ----- .../src/components/slider/slider.ts | 6 ---- .../src/components/switch/switch.ts | 7 ----- .../src/components/textarea/textarea.ts | 7 ----- .../webawesome-form-associated-element.ts | 20 ++++++++++++- 11 files changed, 37 insertions(+), 68 deletions(-) diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index 8b5763c0b..0e2eb6ef7 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -27,6 +27,8 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh - Fixed a bug in `<wa-select>` that prevented clicks on the tag's remove button from removing options in multiple mode - Fixed a bug in `<wa-select>` that caused tags to appear in alphabetical order instead of selection order when using `multiple` - Improved performance of `<wa-icon>` so initial rendering occurs faster, especially with multiple icons on the page [issue:1729] +- Fixed a bug in Web Awesome form controls that caused `<wa-input form="foo">` to set the form property to equal `"foo"` instead of returning an `HTMLFormElement` breaking platform expectations. [pr:1815] +- Fixed a bug in `<wa-button>` causing it to not copy over attributes for form submissions. [pr:1815] - Improved performance of all components by fixing how CSS is imported and reused [issue:1812] - Modified the default `transition` styles of `<wa-dropdown-item>` to use design tokens [pr:1693] @@ -512,4 +514,4 @@ Many of these changes and improvements were the direct result of feedback from u </details> -Did we miss something? [Let us know!](https://github.com/shoelace-style/webawesome/discussions) +Did we miss something? [Let us know!](https://github.com/shoelace-style/webawesome/discussions) \ No newline at end of file diff --git a/packages/webawesome/src/components/button/button.ts b/packages/webawesome/src/components/button/button.ts index 19a3dc6cf..146fdb83d 100644 --- a/packages/webawesome/src/components/button/button.ts +++ b/packages/webawesome/src/components/button/button.ts @@ -115,7 +115,6 @@ export default class WaButton extends WebAwesomeFormAssociatedElement { * The "form owner" to associate the button with. If omitted, the closest containing form will be used instead. The * value of this attribute must be an id of a form in the same document or shadow root as the button. */ - @property({ reflect: true }) form: string | null = null; /** Used to override the form owner's `action` attribute. */ @property({ attribute: 'formaction' }) formAction: string; @@ -135,24 +134,27 @@ export default class WaButton extends WebAwesomeFormAssociatedElement { private constructLightDOMButton() { const button = document.createElement('button'); + + for (const attribute of this.attributes) { + if (attribute.name === 'style') { + // Skip style attributes as they *shouldn't* be necessary + continue; + } + button.setAttribute(attribute.name, attribute.value); + } + button.type = this.type; - button.style.position = 'absolute'; - button.style.width = '0'; - button.style.height = '0'; - button.style.clipPath = 'inset(50%)'; - button.style.overflow = 'hidden'; - button.style.whiteSpace = 'nowrap'; + button.style.position = 'absolute !important'; + button.style.width = '0 !important'; + button.style.height = '0 !important'; + button.style.clipPath = 'inset(50%) !important'; + button.style.overflow = 'hidden !important'; + button.style.whiteSpace = 'nowrap !important'; if (this.name) { button.name = this.name; } button.value = this.value || ''; - ['form', 'formaction', 'formenctype', 'formmethod', 'formnovalidate', 'formtarget'].forEach(attr => { - if (this.hasAttribute(attr)) { - button.setAttribute(attr, this.getAttribute(attr)!); - } - }); - return button; } diff --git a/packages/webawesome/src/components/checkbox/checkbox.ts b/packages/webawesome/src/components/checkbox/checkbox.ts index 444472b1d..9af6d20f1 100644 --- a/packages/webawesome/src/components/checkbox/checkbox.ts +++ b/packages/webawesome/src/components/checkbox/checkbox.ts @@ -108,13 +108,6 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement { @property({ type: Boolean, reflect: true, attribute: 'checked' }) defaultChecked: boolean = this.hasAttribute('checked'); - /** - * By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you - * to place the form control outside of a form and associate it with the form that has this `id`. The form must be in - * the same document or shadow root for this to work. - */ - @property({ reflect: true }) form = null; - /** Makes the checkbox a required field. */ @property({ type: Boolean, reflect: true }) required = false; diff --git a/packages/webawesome/src/components/color-picker/color-picker.ts b/packages/webawesome/src/components/color-picker/color-picker.ts index c039f5034..ce897d69e 100644 --- a/packages/webawesome/src/components/color-picker/color-picker.ts +++ b/packages/webawesome/src/components/color-picker/color-picker.ts @@ -220,13 +220,6 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement { */ @property() swatches: string | string[] = ''; - /** - * By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you - * to place the form control outside of a form and associate it with the form that has this `id`. The form must be in - * the same document or shadow root for this to work. - */ - @property({ reflect: true }) form = null; - /** Makes the color picker a required field. */ @property({ type: Boolean, reflect: true }) required = false; diff --git a/packages/webawesome/src/components/input/input.ts b/packages/webawesome/src/components/input/input.ts index 3dfa32771..930b85ee7 100644 --- a/packages/webawesome/src/components/input/input.ts +++ b/packages/webawesome/src/components/input/input.ts @@ -140,13 +140,6 @@ export default class WaInput extends WebAwesomeFormAssociatedElement { /** Hides the browser's built-in increment/decrement spin buttons for number inputs. */ @property({ attribute: 'without-spin-buttons', type: Boolean }) withoutSpinButtons = false; - /** - * By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you - * to place the form control outside of a form and associate it with the form that has this `id`. The form must be in - * the same document or shadow root for this to work. - */ - @property({ reflect: true }) form = null; - /** Makes the input a required field. */ @property({ type: Boolean, reflect: true }) required = false; diff --git a/packages/webawesome/src/components/radio/radio.ts b/packages/webawesome/src/components/radio/radio.ts index d2b5377f4..0f7d6f991 100644 --- a/packages/webawesome/src/components/radio/radio.ts +++ b/packages/webawesome/src/components/radio/radio.ts @@ -39,11 +39,6 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement { /** @internal Used by radio group to force disable radios while preserving their original disabled state. */ @state() forceDisabled = false; - /** - * The string pointing to a form's id. - */ - @property({ reflect: true }) form: string | null = null; - /** The radio's value. When selected, the radio group will receive this value. */ @property({ reflect: true }) value: string; diff --git a/packages/webawesome/src/components/select/select.ts b/packages/webawesome/src/components/select/select.ts index ddc51ce33..46d1b8cf6 100644 --- a/packages/webawesome/src/components/select/select.ts +++ b/packages/webawesome/src/components/select/select.ts @@ -257,13 +257,6 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement { */ @property({ attribute: 'with-hint', type: Boolean }) withHint = false; - /** - * By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you - * to place the form control outside of a form and associate it with the form that has this `id`. The form must be in - * the same document or shadow root for this to work. - */ - @property({ reflect: true }) form = null; - /** The select's required attribute. */ @property({ type: Boolean, reflect: true }) required = false; diff --git a/packages/webawesome/src/components/slider/slider.ts b/packages/webawesome/src/components/slider/slider.ts index 55e7df338..e896e6174 100644 --- a/packages/webawesome/src/components/slider/slider.ts +++ b/packages/webawesome/src/components/slider/slider.ts @@ -167,12 +167,6 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement { /** The starting value from which to draw the slider's fill, which is based on its current value. */ @property({ attribute: 'indicator-offset', type: Number }) indicatorOffset: number; - /** - * The form to associate this control with. If omitted, the closest containing `<form>` will be used. The value of - * this attribute must be an ID of a form in the same document or shadow root. - */ - @property({ reflect: true }) form = null; - /** The minimum value allowed. */ @property({ type: Number }) min: number = 0; diff --git a/packages/webawesome/src/components/switch/switch.ts b/packages/webawesome/src/components/switch/switch.ts index 14f06084e..99e3b8de0 100644 --- a/packages/webawesome/src/components/switch/switch.ts +++ b/packages/webawesome/src/components/switch/switch.ts @@ -80,13 +80,6 @@ export default class WaSwitch extends WebAwesomeFormAssociatedElement { @property({ type: Boolean, attribute: 'checked', reflect: true }) defaultChecked: boolean = this.hasAttribute('checked'); - /** - * By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you - * to place the form control outside of a form and associate it with the form that has this `id`. The form must be in - * the same document or shadow root for this to work. - */ - @property({ reflect: true }) form = null; - /** Makes the switch a required field. */ @property({ type: Boolean, reflect: true }) required = false; diff --git a/packages/webawesome/src/components/textarea/textarea.ts b/packages/webawesome/src/components/textarea/textarea.ts index 5f63a5e50..3e7870f7d 100644 --- a/packages/webawesome/src/components/textarea/textarea.ts +++ b/packages/webawesome/src/components/textarea/textarea.ts @@ -107,13 +107,6 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement { /** Makes the textarea readonly. */ @property({ type: Boolean, reflect: true }) readonly = false; - /** - * By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you - * to place the form control outside of a form and associate it with the form that has this `id`. The form must be in - * the same document or shadow root for this to work. - */ - @property({ reflect: true }) form = null; - /** Makes the textarea a required field. */ @property({ type: Boolean, reflect: true }) required = false; diff --git a/packages/webawesome/src/internal/webawesome-form-associated-element.ts b/packages/webawesome/src/internal/webawesome-form-associated-element.ts index f84116325..440f13aee 100644 --- a/packages/webawesome/src/internal/webawesome-form-associated-element.ts +++ b/packages/webawesome/src/internal/webawesome-form-associated-element.ts @@ -23,7 +23,8 @@ export interface WebAwesomeFormControl extends WebAwesomeElement { checked?: boolean; defaultSelected?: boolean; selected?: boolean; - form?: string | null; + get form(): HTMLFormElement | null; + set form(val: string); value?: unknown; @@ -203,6 +204,23 @@ export class WebAwesomeFormAssociatedElement return this.internals.form; } + /** + * By default, form controls are associated with the nearest containing `<form>` element. This attribute allows you + * to place the form control outside of a form and associate it with the form that has this `id`. The form must be in + * the same document or shadow root for this to work. + */ + set form(val: string) { + if (val) { + this.setAttribute('form', val); + } else { + this.removeAttribute('form'); + } + } + + get form(): HTMLFormElement | null { + return this.internals.form; + } + @property({ attribute: false, state: true, type: Object }) get validity() { return this.internals.validity; From 00b8150f3e8b8be6d6864c22c000c7a753b7aaa2 Mon Sep 17 00:00:00 2001 From: Cory LaViska <cory@abeautifulsite.net> Date: Tue, 16 Dec 2025 11:06:27 -0500 Subject: [PATCH 50/59] ensure min/max/step are numbers; fixes #1823 (#1832) Co-authored-by: konnorrogers <konnor5456@gmail.com> --- packages/webawesome/docs/docs/resources/changelog.md | 1 + packages/webawesome/src/components/slider/slider.ts | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index 0e2eb6ef7..ae2a2a9d1 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -27,6 +27,7 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh - Fixed a bug in `<wa-select>` that prevented clicks on the tag's remove button from removing options in multiple mode - Fixed a bug in `<wa-select>` that caused tags to appear in alphabetical order instead of selection order when using `multiple` - Improved performance of `<wa-icon>` so initial rendering occurs faster, especially with multiple icons on the page [issue:1729] +- Improved `<wa-slider>` to not throw an error when string values are passed to the `min`, `max`, and `step` properties [issue:1823] - Fixed a bug in Web Awesome form controls that caused `<wa-input form="foo">` to set the form property to equal `"foo"` instead of returning an `HTMLFormElement` breaking platform expectations. [pr:1815] - Fixed a bug in `<wa-button>` causing it to not copy over attributes for form submissions. [pr:1815] - Improved performance of all components by fixing how CSS is imported and reused [issue:1812] diff --git a/packages/webawesome/src/components/slider/slider.ts b/packages/webawesome/src/components/slider/slider.ts index e896e6174..dab5b6eb7 100644 --- a/packages/webawesome/src/components/slider/slider.ts +++ b/packages/webawesome/src/components/slider/slider.ts @@ -431,8 +431,14 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement { /** Clamps a number to min/max while ensuring it's a valid step interval. */ private clampAndRoundToStep(value: number) { const stepPrecision = (String(this.step).split('.')[1] || '').replace(/0+$/g, '').length; - value = Math.round(value / this.step) * this.step; - value = clamp(value, this.min, this.max); + + // Ensure we're working with numbers (in case the user passes strings to the respective properties) + const step = Number(this.step); + const min = Number(this.min); + const max = Number(this.max); + + value = Math.round(value / step) * step; + value = clamp(value, min, max); return parseFloat(value.toFixed(stepPrecision)); } From 1e6d4689597147145d9f96d6bb3d66e65febeec0 Mon Sep 17 00:00:00 2001 From: Konnor Rogers <konnor5456@gmail.com> Date: Tue, 16 Dec 2025 11:46:01 -0500 Subject: [PATCH 51/59] add changelog entry (#1877) --- packages/webawesome/docs/docs/resources/changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index ae2a2a9d1..db3e0a757 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -17,6 +17,7 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh - Added version 2.0.0 of the [official Web Awesome Figma Design Kit](/docs/resources/figma) - Added npm support for Web Awesome Pro - Added `layers.css` to define cascade layer order and updated palettes, themes, native styles, and utilities to import the new rule for more fail-safe modularity [pr:1793] +- [PRO]: Fixed a few sizing bugs in `<wa-page>` and `slot="footer"` no longer will always "overflow" the container. - Fixed a bug in `<wa-slider>` that caused some touch devices to end up with the incorrect value [issue:1703] - Fixed a bug in `<wa-card>` that prevented some slots from being detected correctly [discuss:1450] - Fixed a z-index bug in `<wa-scroller>` styles [issue:1724] From 4dca4185e73d52c5f8f729ae5d4521bbbfca2415 Mon Sep 17 00:00:00 2001 From: konnorrogers <konnor5456@gmail.com> Date: Tue, 16 Dec 2025 11:48:29 -0500 Subject: [PATCH 52/59] Bump changelog --- packages/webawesome/docs/docs/resources/changelog.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/webawesome/docs/docs/resources/changelog.md b/packages/webawesome/docs/docs/resources/changelog.md index db3e0a757..e8588a568 100644 --- a/packages/webawesome/docs/docs/resources/changelog.md +++ b/packages/webawesome/docs/docs/resources/changelog.md @@ -11,7 +11,7 @@ Web Awesome follows [Semantic Versioning](https://semver.org/). Breaking changes Components with the <wa-badge variant="warning">Experimental</wa-badge> badge should not be used in production. They are made available as release candidates for development and testing purposes. As such, changes to experimental components will not be subject to semantic versioning. -## Next +## 3.1.0 - Added `<wa-combobox>` as an experimental pro component [issue:1074] - Added version 2.0.0 of the [official Web Awesome Figma Design Kit](/docs/resources/figma) From d1b6628d595858f58682591435dabfd9a523e55e Mon Sep 17 00:00:00 2001 From: konnorrogers <konnor5456@gmail.com> Date: Tue, 16 Dec 2025 11:48:56 -0500 Subject: [PATCH 53/59] update package lock --- package-lock.json | 179 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 171 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd58b9bd2..bbed21ad0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@webawesome/monorepo", - "version": "3.0.0-alpha.13", + "version": "3.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@webawesome/monorepo", - "version": "3.0.0-alpha.13", + "version": "3.0.0", "license": "MIT", "workspaces": [ "packages/*" @@ -593,6 +593,10 @@ "resolved": "packages/webawesome", "link": true }, + "node_modules/@awesome.me/webawesome-pro": { + "resolved": "packages/webawesome-pro", + "link": true + }, "node_modules/@babel/code-frame": { "version": "7.23.5", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", @@ -2519,10 +2523,6 @@ "resolved": "https://registry.npmjs.org/@shoelace-style/localize/-/localize-3.2.1.tgz", "integrity": "sha512-r4C9C/5kSfMBIr0D9imvpRdCNXtUNgyYThc4YlS6K5Hchv1UyxNQ9mxwj+BTRH2i1Neits260sR3OjKMnplsFA==" }, - "node_modules/@shoelace-style/webawesome-pro": { - "resolved": "packages/webawesome-pro", - "link": true - }, "node_modules/@sindresorhus/is": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", @@ -12139,6 +12139,16 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-typescript-paths": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-typescript-paths/-/rollup-plugin-typescript-paths-1.5.0.tgz", + "integrity": "sha512-zly2aiGNjYJNq5YUi6eyGrQnCYUQ8b5czOtHZIGriwG9U3Ba2F9hlSklafXCdsNulK/IlNmE0Kzj0h+fVV32pA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">=3.4" + } + }, "node_modules/run-async": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", @@ -14037,7 +14047,7 @@ } }, "packages/webawesome-pro": { - "name": "@shoelace-style/webawesome-pro", + "name": "@awesome.me/webawesome-pro", "version": "3.0.0", "dependencies": { "@ctrl/tinycolor": "4.1.0", @@ -14052,9 +14062,11 @@ }, "devDependencies": { "@wc-toolkit/jsx-types": "^1.3.0", + "@web/dev-server-rollup": "^0.6.4", "eleventy-plugin-git-commit-date": "^0.1.3", "esbuild": "^0.25.11", - "npm-check-updates": "^19.1.2" + "npm-check-updates": "^19.1.2", + "rollup-plugin-typescript-paths": "^1.5.0" }, "engines": { "node": ">=14.17.0" @@ -14468,6 +14480,84 @@ "node": ">=18" } }, + "packages/webawesome-pro/node_modules/@web/dev-server-core": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@web/dev-server-core/-/dev-server-core-0.7.5.tgz", + "integrity": "sha512-Da65zsiN6iZPMRuj4Oa6YPwvsmZmo5gtPWhW2lx3GTUf5CAEapjVpZVlUXnKPL7M7zRuk72jSsIl8lo+XpTCtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/koa": "^2.11.6", + "@types/ws": "^7.4.0", + "@web/parse5-utils": "^2.1.0", + "chokidar": "^4.0.1", + "clone": "^2.1.2", + "es-module-lexer": "^1.0.0", + "get-stream": "^6.0.0", + "is-stream": "^2.0.0", + "isbinaryfile": "^5.0.0", + "koa": "^2.13.0", + "koa-etag": "^4.0.0", + "koa-send": "^5.0.1", + "koa-static": "^5.0.0", + "lru-cache": "^8.0.4", + "mime-types": "^2.1.27", + "parse5": "^6.0.1", + "picomatch": "^2.2.2", + "ws": "^7.5.10" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/webawesome-pro/node_modules/@web/dev-server-rollup": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@web/dev-server-rollup/-/dev-server-rollup-0.6.4.tgz", + "integrity": "sha512-sJZfTGCCrdku5xYnQQG51odGI092hKY9YFM0X3Z0tRY3iXKXcYRaLZrErw5KfCxr6g0JRuhe4BBhqXTA5Q2I3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-node-resolve": "^15.0.1", + "@web/dev-server-core": "^0.7.2", + "nanocolors": "^0.2.1", + "parse5": "^6.0.1", + "rollup": "^4.4.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/webawesome-pro/node_modules/@web/parse5-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@web/parse5-utils/-/parse5-utils-2.1.0.tgz", + "integrity": "sha512-GzfK5disEJ6wEjoPwx8AVNwUe9gYIiwc+x//QYxYDAFKUp4Xb1OJAGLc2l2gVrSQmtPGLKrTRcW90Hv4pEq1qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/parse5": "^6.0.1", + "parse5": "^6.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/webawesome-pro/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "packages/webawesome-pro/node_modules/esbuild": { "version": "0.25.11", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", @@ -14510,6 +14600,16 @@ "@esbuild/win32-x64": "0.25.11" } }, + "packages/webawesome-pro/node_modules/lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16.14" + } + }, "packages/webawesome-pro/node_modules/nanoid": { "version": "5.1.5", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", @@ -14542,6 +14642,69 @@ "npm": ">=8.12.1" } }, + "packages/webawesome-pro/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "packages/webawesome-pro/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "packages/webawesome-pro/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "packages/webawesome-pro/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "packages/webawesome/node_modules/@esbuild/aix-ppc64": { "version": "0.25.11", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", From 2b69fa74a57f00bb11a07e2393fb7adf9737f869 Mon Sep 17 00:00:00 2001 From: konnorrogers <konnor5456@gmail.com> Date: Tue, 16 Dec 2025 13:21:23 -0500 Subject: [PATCH 54/59] Bump package.json version --- packages/webawesome/package.json | 4 ++-- packages/webawesome/scripts/update-root-version.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/webawesome/package.json b/packages/webawesome/package.json index 11c9a106e..36f9bf483 100644 --- a/packages/webawesome/package.json +++ b/packages/webawesome/package.json @@ -4,7 +4,7 @@ "access": "public" }, "description": "A forward-thinking library of web components.", - "version": "3.0.0", + "version": "3.1.0", "homepage": "https://webawesome.com/", "author": "Web Awesome", "license": "MIT", @@ -67,7 +67,7 @@ "check-updates": "npm-check-updates --cooldown 7 --interactive --format group", "print-version": "echo $npm_package_version", "tag-version": "git tag -a \"v$(npm run print-version | tail -n1)\" -m \"tag v$(npm run print-version | tail -n1)\"", - "postversion": "npm run tag-version && node ./scripts/update-root-version.js" + "postversion": "node ./scripts/update-root-version.js" }, "engines": { "node": ">=14.17.0" diff --git a/packages/webawesome/scripts/update-root-version.js b/packages/webawesome/scripts/update-root-version.js index 20490af94..696750bac 100755 --- a/packages/webawesome/scripts/update-root-version.js +++ b/packages/webawesome/scripts/update-root-version.js @@ -10,8 +10,8 @@ const monorepoRoot = path.resolve(__dirname, '..', '..', '..'); const rootPackageJSONFile = path.join(monorepoRoot, 'package.json'); const webawesomePackageJSONFile = path.join(path.resolve(__dirname, '..'), 'package.json'); -const rootPackageJSON = JSON.parse(fs.readFileSync(rootPackageJSONFile)); -const webawesomePackageJSON = JSON.parse(fs.readFileSync(webawesomePackageJSONFile)); +const rootPackageJSON = JSON.parse(fs.readFileSync(rootPackageJSONFile, { encoding: "utf8" })); +const webawesomePackageJSON = JSON.parse(fs.readFileSync(webawesomePackageJSONFile, { encoding: "utf8" })); const currentVersion = webawesomePackageJSON.version; rootPackageJSON.version = currentVersion; @@ -19,9 +19,9 @@ rootPackageJSON.version = currentVersion; fs.writeFileSync(rootPackageJSONFile, JSON.stringify(rootPackageJSON, null, 2)); const versionsFile = path.join(monorepoRoot, 'VERSIONS.txt'); -const versions = fs.readFileSync(versions).split(/\r?\n/); +const versions = fs.readFileSync(versionsFile, { encoding: "utf8" }).split(/\r?\n/); // TODO: Make this smart and understand semver and "insert" in the correct spot instead of appending. if (!versions.includes(currentVersion)) { - fs.appendFileSync(webawesomePackageJSON.version); + fs.appendFileSync(versionsFile, webawesomePackageJSON.version); } From da206a87873e3ab43ded7466a05005225aa50e69 Mon Sep 17 00:00:00 2001 From: konnorrogers <konnor5456@gmail.com> Date: Tue, 16 Dec 2025 13:21:46 -0500 Subject: [PATCH 55/59] update versions --- VERSIONS.txt | 1 + package-lock.json | 6 +++--- package.json | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/VERSIONS.txt b/VERSIONS.txt index 4a36342fc..32e65834f 100644 --- a/VERSIONS.txt +++ b/VERSIONS.txt @@ -1 +1,2 @@ 3.0.0 +3.1.0 diff --git a/package-lock.json b/package-lock.json index bbed21ad0..afa2dae53 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@webawesome/monorepo", - "version": "3.0.0", + "version": "3.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@webawesome/monorepo", - "version": "3.0.0", + "version": "3.1.0", "license": "MIT", "workspaces": [ "packages/*" @@ -14023,7 +14023,7 @@ }, "packages/webawesome": { "name": "@awesome.me/webawesome", - "version": "3.0.0", + "version": "3.1.0", "license": "MIT", "dependencies": { "@ctrl/tinycolor": "4.1.0", diff --git a/package.json b/package.json index 2f2b446cc..06d4143c3 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "@webawesome/monorepo", "private": true, "description": "A forward-thinking library of web components.", - "version": "3.0.0", + "version": "3.1.0", "homepage": "https://webawesome.com/", "author": "Web Awesome", "license": "MIT", From 5fb3625ee549bb6e9637690d4256e4e916d30957 Mon Sep 17 00:00:00 2001 From: konnorrogers <konnor5456@gmail.com> Date: Tue, 16 Dec 2025 13:22:32 -0500 Subject: [PATCH 56/59] fix script --- packages/webawesome/scripts/update-root-version.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/webawesome/scripts/update-root-version.js b/packages/webawesome/scripts/update-root-version.js index 696750bac..0dc3d8275 100755 --- a/packages/webawesome/scripts/update-root-version.js +++ b/packages/webawesome/scripts/update-root-version.js @@ -10,8 +10,8 @@ const monorepoRoot = path.resolve(__dirname, '..', '..', '..'); const rootPackageJSONFile = path.join(monorepoRoot, 'package.json'); const webawesomePackageJSONFile = path.join(path.resolve(__dirname, '..'), 'package.json'); -const rootPackageJSON = JSON.parse(fs.readFileSync(rootPackageJSONFile, { encoding: "utf8" })); -const webawesomePackageJSON = JSON.parse(fs.readFileSync(webawesomePackageJSONFile, { encoding: "utf8" })); +const rootPackageJSON = JSON.parse(fs.readFileSync(rootPackageJSONFile, { encoding: 'utf8' })); +const webawesomePackageJSON = JSON.parse(fs.readFileSync(webawesomePackageJSONFile, { encoding: 'utf8' })); const currentVersion = webawesomePackageJSON.version; rootPackageJSON.version = currentVersion; @@ -19,7 +19,7 @@ rootPackageJSON.version = currentVersion; fs.writeFileSync(rootPackageJSONFile, JSON.stringify(rootPackageJSON, null, 2)); const versionsFile = path.join(monorepoRoot, 'VERSIONS.txt'); -const versions = fs.readFileSync(versionsFile, { encoding: "utf8" }).split(/\r?\n/); +const versions = fs.readFileSync(versionsFile, { encoding: 'utf8' }).split(/\r?\n/); // TODO: Make this smart and understand semver and "insert" in the correct spot instead of appending. if (!versions.includes(currentVersion)) { From f739121aaf780deb5278a1240dc594ebc9ae9b98 Mon Sep 17 00:00:00 2001 From: konnorrogers <konnor5456@gmail.com> Date: Tue, 16 Dec 2025 13:30:16 -0500 Subject: [PATCH 57/59] updae lockfile --- package-lock.json | 167 +--------------------------------------------- 1 file changed, 2 insertions(+), 165 deletions(-) diff --git a/package-lock.json b/package-lock.json index afa2dae53..dac94783c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12139,16 +12139,6 @@ "fsevents": "~2.3.2" } }, - "node_modules/rollup-plugin-typescript-paths": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-typescript-paths/-/rollup-plugin-typescript-paths-1.5.0.tgz", - "integrity": "sha512-zly2aiGNjYJNq5YUi6eyGrQnCYUQ8b5czOtHZIGriwG9U3Ba2F9hlSklafXCdsNulK/IlNmE0Kzj0h+fVV32pA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "typescript": ">=3.4" - } - }, "node_modules/run-async": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", @@ -14048,7 +14038,7 @@ }, "packages/webawesome-pro": { "name": "@awesome.me/webawesome-pro", - "version": "3.0.0", + "version": "3.1.0", "dependencies": { "@ctrl/tinycolor": "4.1.0", "@floating-ui/dom": "^1.6.13", @@ -14062,11 +14052,9 @@ }, "devDependencies": { "@wc-toolkit/jsx-types": "^1.3.0", - "@web/dev-server-rollup": "^0.6.4", "eleventy-plugin-git-commit-date": "^0.1.3", "esbuild": "^0.25.11", - "npm-check-updates": "^19.1.2", - "rollup-plugin-typescript-paths": "^1.5.0" + "npm-check-updates": "^19.1.2" }, "engines": { "node": ">=14.17.0" @@ -14480,84 +14468,6 @@ "node": ">=18" } }, - "packages/webawesome-pro/node_modules/@web/dev-server-core": { - "version": "0.7.5", - "resolved": "https://registry.npmjs.org/@web/dev-server-core/-/dev-server-core-0.7.5.tgz", - "integrity": "sha512-Da65zsiN6iZPMRuj4Oa6YPwvsmZmo5gtPWhW2lx3GTUf5CAEapjVpZVlUXnKPL7M7zRuk72jSsIl8lo+XpTCtw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/koa": "^2.11.6", - "@types/ws": "^7.4.0", - "@web/parse5-utils": "^2.1.0", - "chokidar": "^4.0.1", - "clone": "^2.1.2", - "es-module-lexer": "^1.0.0", - "get-stream": "^6.0.0", - "is-stream": "^2.0.0", - "isbinaryfile": "^5.0.0", - "koa": "^2.13.0", - "koa-etag": "^4.0.0", - "koa-send": "^5.0.1", - "koa-static": "^5.0.0", - "lru-cache": "^8.0.4", - "mime-types": "^2.1.27", - "parse5": "^6.0.1", - "picomatch": "^2.2.2", - "ws": "^7.5.10" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/webawesome-pro/node_modules/@web/dev-server-rollup": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@web/dev-server-rollup/-/dev-server-rollup-0.6.4.tgz", - "integrity": "sha512-sJZfTGCCrdku5xYnQQG51odGI092hKY9YFM0X3Z0tRY3iXKXcYRaLZrErw5KfCxr6g0JRuhe4BBhqXTA5Q2I3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rollup/plugin-node-resolve": "^15.0.1", - "@web/dev-server-core": "^0.7.2", - "nanocolors": "^0.2.1", - "parse5": "^6.0.1", - "rollup": "^4.4.0", - "whatwg-url": "^14.0.0" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/webawesome-pro/node_modules/@web/parse5-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@web/parse5-utils/-/parse5-utils-2.1.0.tgz", - "integrity": "sha512-GzfK5disEJ6wEjoPwx8AVNwUe9gYIiwc+x//QYxYDAFKUp4Xb1OJAGLc2l2gVrSQmtPGLKrTRcW90Hv4pEq1qA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/parse5": "^6.0.1", - "parse5": "^6.0.1" - }, - "engines": { - "node": ">=18.0.0" - } - }, - "packages/webawesome-pro/node_modules/chokidar": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", - "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "readdirp": "^4.0.1" - }, - "engines": { - "node": ">= 14.16.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "packages/webawesome-pro/node_modules/esbuild": { "version": "0.25.11", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", @@ -14600,16 +14510,6 @@ "@esbuild/win32-x64": "0.25.11" } }, - "packages/webawesome-pro/node_modules/lru-cache": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", - "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=16.14" - } - }, "packages/webawesome-pro/node_modules/nanoid": { "version": "5.1.5", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", @@ -14642,69 +14542,6 @@ "npm": ">=8.12.1" } }, - "packages/webawesome-pro/node_modules/readdirp": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", - "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.18.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "packages/webawesome-pro/node_modules/tr46": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", - "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, - "packages/webawesome-pro/node_modules/whatwg-url": { - "version": "14.2.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", - "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tr46": "^5.1.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "packages/webawesome-pro/node_modules/ws": { - "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "packages/webawesome/node_modules/@esbuild/aix-ppc64": { "version": "0.25.11", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", From 6e5a720f51b4dff943cb25655ac16e405830f7bd Mon Sep 17 00:00:00 2001 From: konnorrogers <konnor5456@gmail.com> Date: Tue, 16 Dec 2025 13:42:13 -0500 Subject: [PATCH 58/59] update lockfiles and prettierignore --- .prettierignore | 1 + package-lock.json | 165 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 165 insertions(+), 1 deletion(-) diff --git a/.prettierignore b/.prettierignore index 3da34c16a..93892e2b0 100644 --- a/.prettierignore +++ b/.prettierignore @@ -20,4 +20,5 @@ packages/**/*/src/react/index.ts node_modules packages/**/*/_site +packages/**/*/_bundle_ packages/webawesome/docs/assets/scripts/prism-downloaded.js diff --git a/package-lock.json b/package-lock.json index dac94783c..61dcacd43 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12139,6 +12139,16 @@ "fsevents": "~2.3.2" } }, + "node_modules/rollup-plugin-typescript-paths": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-typescript-paths/-/rollup-plugin-typescript-paths-1.5.0.tgz", + "integrity": "sha512-zly2aiGNjYJNq5YUi6eyGrQnCYUQ8b5czOtHZIGriwG9U3Ba2F9hlSklafXCdsNulK/IlNmE0Kzj0h+fVV32pA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "typescript": ">=3.4" + } + }, "node_modules/run-async": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", @@ -14052,9 +14062,11 @@ }, "devDependencies": { "@wc-toolkit/jsx-types": "^1.3.0", + "@web/dev-server-rollup": "^0.6.4", "eleventy-plugin-git-commit-date": "^0.1.3", "esbuild": "^0.25.11", - "npm-check-updates": "^19.1.2" + "npm-check-updates": "^19.1.2", + "rollup-plugin-typescript-paths": "^1.5.0" }, "engines": { "node": ">=14.17.0" @@ -14468,6 +14480,84 @@ "node": ">=18" } }, + "packages/webawesome-pro/node_modules/@web/dev-server-core": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@web/dev-server-core/-/dev-server-core-0.7.5.tgz", + "integrity": "sha512-Da65zsiN6iZPMRuj4Oa6YPwvsmZmo5gtPWhW2lx3GTUf5CAEapjVpZVlUXnKPL7M7zRuk72jSsIl8lo+XpTCtw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/koa": "^2.11.6", + "@types/ws": "^7.4.0", + "@web/parse5-utils": "^2.1.0", + "chokidar": "^4.0.1", + "clone": "^2.1.2", + "es-module-lexer": "^1.0.0", + "get-stream": "^6.0.0", + "is-stream": "^2.0.0", + "isbinaryfile": "^5.0.0", + "koa": "^2.13.0", + "koa-etag": "^4.0.0", + "koa-send": "^5.0.1", + "koa-static": "^5.0.0", + "lru-cache": "^8.0.4", + "mime-types": "^2.1.27", + "parse5": "^6.0.1", + "picomatch": "^2.2.2", + "ws": "^7.5.10" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/webawesome-pro/node_modules/@web/dev-server-rollup": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/@web/dev-server-rollup/-/dev-server-rollup-0.6.4.tgz", + "integrity": "sha512-sJZfTGCCrdku5xYnQQG51odGI092hKY9YFM0X3Z0tRY3iXKXcYRaLZrErw5KfCxr6g0JRuhe4BBhqXTA5Q2I3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-node-resolve": "^15.0.1", + "@web/dev-server-core": "^0.7.2", + "nanocolors": "^0.2.1", + "parse5": "^6.0.1", + "rollup": "^4.4.0", + "whatwg-url": "^14.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/webawesome-pro/node_modules/@web/parse5-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@web/parse5-utils/-/parse5-utils-2.1.0.tgz", + "integrity": "sha512-GzfK5disEJ6wEjoPwx8AVNwUe9gYIiwc+x//QYxYDAFKUp4Xb1OJAGLc2l2gVrSQmtPGLKrTRcW90Hv4pEq1qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/parse5": "^6.0.1", + "parse5": "^6.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "packages/webawesome-pro/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "packages/webawesome-pro/node_modules/esbuild": { "version": "0.25.11", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.11.tgz", @@ -14510,6 +14600,16 @@ "@esbuild/win32-x64": "0.25.11" } }, + "packages/webawesome-pro/node_modules/lru-cache": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-8.0.5.tgz", + "integrity": "sha512-MhWWlVnuab1RG5/zMRRcVGXZLCXrZTgfwMikgzCegsPnG62yDQo5JnqKkrK4jO5iKqDAZGItAqN5CtKBCBWRUA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16.14" + } + }, "packages/webawesome-pro/node_modules/nanoid": { "version": "5.1.5", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.5.tgz", @@ -14542,6 +14642,69 @@ "npm": ">=8.12.1" } }, + "packages/webawesome-pro/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, + "packages/webawesome-pro/node_modules/tr46": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz", + "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "packages/webawesome-pro/node_modules/whatwg-url": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz", + "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^5.1.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "packages/webawesome-pro/node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "packages/webawesome/node_modules/@esbuild/aix-ppc64": { "version": "0.25.11", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.11.tgz", From c748721f1274804460c53721384eae21009deb06 Mon Sep 17 00:00:00 2001 From: Brian Talbot <hi.talbs@gmail.com> Date: Wed, 17 Dec 2025 15:22:18 -0500 Subject: [PATCH 59/59] adding event tracking to site search (#1872) --- .../webawesome/docs/assets/scripts/search.js | 182 +++++++++++++++--- 1 file changed, 155 insertions(+), 27 deletions(-) diff --git a/packages/webawesome/docs/assets/scripts/search.js b/packages/webawesome/docs/assets/scripts/search.js index 77969844b..6ae6d49c1 100644 --- a/packages/webawesome/docs/assets/scripts/search.js +++ b/packages/webawesome/docs/assets/scripts/search.js @@ -1,12 +1,24 @@ // Search data const version = document.documentElement.getAttribute('data-version') || ''; -const res = await Promise.all([import('https://cdn.jsdelivr.net/npm/lunr/+esm'), fetch(`/search.json?v=${version}`)]); +const res = await Promise.all([ + import('https://cdn.jsdelivr.net/npm/lunr/+esm'), + fetch(`/search.json?v=${version}`), + import('/assets/scripts/track.js').catch(() => null), +]); const lunr = res[0].default; const searchData = await res[1].json(); const searchIndex = lunr.Index.load(searchData.searchIndex); const map = searchData.map; const searchDebounce = 200; +const queryTrackDelay = 1000; let searchTimeout; +let queryTrackTimeout; +let lastTrackedQuery = ''; +let resultSelected = false; + +// Optional event tracking - works standalone if track.js isn't available +const trackModule = res[2]; +const trackEvent = trackModule?.trackEvent || window.trackEvent || (() => {}); // We're using Turbo, so references to these elements aren't guaranteed to remain intact function getElements() { @@ -17,6 +29,24 @@ function getElements() { }; } +function trackQuerySubmit(query, resultSelectedValue) { + if (!query || query.length === 0) return; + + const { results } = getElements(); + if (!results) return; + + const matches = results.querySelectorAll('li').length; + const truncatedQuery = query.length > 500 ? query.substring(0, 500) : query; + + trackEvent('navigation:search_query_submit', { + query: truncatedQuery, + query_length: query.length, + result_count: matches, + has_results: matches > 0, + result_selected: resultSelectedValue, + }); +} + // Show the search dialog when slash (or CMD+K) is pressed and focus is not inside a form element document.addEventListener('keydown', event => { if ( @@ -42,40 +72,98 @@ document.addEventListener('click', event => { function show() { const { dialog, input, results } = getElements(); + if (!dialog || !input || !results) return; + const wasAlreadyOpen = dialog.open; + + // Remove existing listeners before adding to prevent duplicates + input.removeEventListener('input', handleInput); + results.removeEventListener('click', handleSelection); + dialog.removeEventListener('keydown', handleKeyDown); + dialog.removeEventListener('wa-hide', handleClose); + resultSelected = false; + lastTrackedQuery = ''; input.addEventListener('input', handleInput); results.addEventListener('click', handleSelection); dialog.addEventListener('keydown', handleKeyDown); dialog.addEventListener('wa-hide', handleClose); dialog.open = true; + if (!wasAlreadyOpen) { + trackEvent('navigation:search_dialog_open'); + } } -function hide() { +function cleanup() { const { dialog, input, results } = getElements(); - + if (!dialog || !input || !results) return; + clearTimeout(searchTimeout); + clearTimeout(queryTrackTimeout); input.removeEventListener('input', handleInput); results.removeEventListener('click', handleSelection); dialog.removeEventListener('keydown', handleKeyDown); dialog.removeEventListener('wa-hide', handleClose); - dialog.open = false; + + // Reset state to prevent leakage between dialog sessions + resultSelected = false; + lastTrackedQuery = ''; } -function handleClose() { - const { input } = getElements(); +async function handleClose() { + const { dialog, input } = getElements(); + if (!dialog || !input) return; + clearTimeout(queryTrackTimeout); + queryTrackTimeout = null; + dialog.removeEventListener('wa-hide', handleClose); + if (!resultSelected) { + const query = input.value.trim(); + if (query.length > 0 && query !== lastTrackedQuery) { + trackQuerySubmit(query, false); + lastTrackedQuery = query; + } + } input.value = ''; - updateResults(); + try { + await updateResults(); + } catch (error) { + // Silently handle errors - UI cleanup should continue + } + cleanup(); + trackEvent('navigation:search_dialog_close'); } function handleInput() { const { input } = getElements(); - + if (!input) return; clearTimeout(searchTimeout); - searchTimeout = setTimeout(() => updateResults(input.value), searchDebounce); + clearTimeout(queryTrackTimeout); + + const query = input.value.trim(); + + if (query.length === 0) { + lastTrackedQuery = ''; + } + + searchTimeout = setTimeout(async () => { + await updateResults(query); + if (query.length > 0 && query !== lastTrackedQuery) { + queryTrackTimeout = setTimeout(() => { + const { input: currentInput, results } = getElements(); + if (!currentInput || resultSelected) return; + + const currentQuery = currentInput.value.trim(); + if (currentQuery === query && currentQuery !== lastTrackedQuery) { + trackQuerySubmit(currentQuery, false); + lastTrackedQuery = currentQuery; + } + }, queryTrackDelay); + } + }, searchDebounce); } function handleKeyDown(event) { const { input, results } = getElements(); + if (!input || !results) return; // Handle keyboard selections if (['ArrowDown', 'ArrowUp', 'Home', 'End', 'Enter'].includes(event.key)) { @@ -104,7 +192,12 @@ function handleKeyDown(event) { nextEl = items[items.length - 1]; break; case 'Enter': - currentEl?.querySelector('a')?.click(); + if (currentEl) { + const link = currentEl.querySelector('a'); + if (link) { + selectResult(link, 'keyboard_enter'); + } + } break; } @@ -121,27 +214,62 @@ function handleKeyDown(event) { } } +function selectResult(link, selectionMethod) { + const { input, results } = getElements(); + if (!input || !link) return; + + // Clear pending query tracking timeout to prevent duplicate events + clearTimeout(queryTrackTimeout); + queryTrackTimeout = null; + resultSelected = true; // Set immediately so timeout callback (if executing) sees it + + const query = input.value.trim(); + if (!link.dataset.searchResultIndex) return; + const resultIndex = parseInt(link.dataset.searchResultIndex, 10); + if (isNaN(resultIndex) || resultIndex < 1) return; + + const resultUrl = link.dataset.searchResultUrl || link.getAttribute('href'); + if (!resultUrl) return; + lastTrackedQuery = query; + trackQuerySubmit(query, true); + trackEvent('navigation:search_result_click', { + query, + result_index: resultIndex, + result_url: resultUrl, + selection_method: selectionMethod, + }); + + const { dialog } = getElements(); + if (dialog) { + dialog.removeEventListener('wa-hide', handleClose); + cleanup(); + trackEvent('navigation:search_dialog_close'); + dialog.open = false; + } + + if (window.Turbo) { + Turbo.visit(resultUrl); + } else { + location.href = resultUrl; + } +} + function handleSelection(event) { const link = event.target.closest('a'); if (link) { event.preventDefault(); - hide(); - - if (window.Turbo) { - Turbo.visit(link.href); - } else { - location.href = link.href; - } + selectResult(link, 'mouse_click'); } } // Queries the search index and updates the results async function updateResults(query = '') { const { dialog, input, results } = getElements(); - + if (!dialog || !input || !results) return; try { - const hasQuery = query.length > 0; + const trimmedQuery = query.trim(); + const hasQuery = trimmedQuery.length > 0; let matches = []; if (hasQuery) { @@ -149,13 +277,13 @@ async function updateResults(query = '') { const seenRefs = new Set(); // Start with a standard search to get the best "exact match" result - searchIndex.search(`${query}`).forEach(match => { + searchIndex.search(`${trimmedQuery}`).forEach(match => { matches.push(match); seenRefs.add(match.ref); }); // Add wildcard matches if not already included - searchIndex.search(`${query}*`).forEach(match => { + searchIndex.search(`${trimmedQuery}*`).forEach(match => { if (!seenRefs.has(match.ref)) { matches.push(match); seenRefs.add(match.ref); @@ -163,11 +291,10 @@ async function updateResults(query = '') { }); // Add fuzzy search matches last - const fuzzyTokens = query + const fuzzyTokens = trimmedQuery .split(' ') .map(term => `${term}~1`) .join(' '); - searchIndex.search(fuzzyTokens).forEach(match => { if (!seenRefs.has(match.ref)) { matches.push(match); @@ -180,12 +307,12 @@ async function updateResults(query = '') { dialog.classList.toggle('has-results', hasQuery && hasResults); dialog.classList.toggle('no-results', hasQuery && !hasResults); - input.setAttribute('aria-activedescendant', ''); results.innerHTML = ''; - matches.forEach((match, index) => { const page = map[match.ref]; + if (!page || !page.url) return; + const li = document.createElement('li'); const a = document.createElement('a'); const displayTitle = page.title ?? ''; @@ -197,12 +324,10 @@ async function updateResults(query = '') { li.setAttribute('role', 'option'); li.setAttribute('id', `search-result-item-${match.ref}`); li.setAttribute('data-selected', index === 0 ? 'true' : 'false'); - if (page.url === '/') icon = 'home'; if (page.url.startsWith('/docs/utilities/native')) icon = 'code'; if (page.url.startsWith('/docs/components')) icon = 'puzzle-piece'; if (page.url.startsWith('/docs/theme') || page.url.startsWith('/docs/restyle')) icon = 'palette'; - a.href = page.url; a.innerHTML = ` <div class="site-search-result-icon" aria-hidden="true"> @@ -218,6 +343,9 @@ async function updateResults(query = '') { a.querySelector('.site-search-result-description').textContent = displayDescription; a.querySelector('.site-search-result-url').textContent = displayUrl; + // Use 1-based indexing for analytics + a.dataset.searchResultIndex = (index + 1).toString(); + a.dataset.searchResultUrl = page.url; li.appendChild(a); results.appendChild(li); });