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/package-lock.json b/package-lock.json index f82819c0d..dd58b9bd2 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", @@ -14052,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" @@ -14525,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", @@ -14966,6 +14983,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/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/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 @@

Celebrate our official launch with a 20% discount on a Web Awesome Pro plan…for life! But hurry, this lifetime discount is only available for a limited time.

- Maybe Later - + Maybe Later + Get Pro + Save 20% @@ -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 26b50c2b1..eb7d837ec 100644 --- a/packages/webawesome/docs/_includes/base.njk +++ b/packages/webawesome/docs/_includes/base.njk @@ -17,7 +17,6 @@ {% if hasSidebar %}{% endif %} - {% block head %} @@ -142,6 +141,9 @@ {% include "_dialog-wa-launch.njk" ignore missing %} {% endif %} + {#- Cookie Consent Dialog -#} + {% include "cookie-consent.njk" ignore missing %} + {# Footer #} {% block pageFooter %}{% endblock %} diff --git a/packages/webawesome/docs/_includes/sidebar.njk b/packages/webawesome/docs/_includes/sidebar.njk index ee902cd5d..1f85fb8c9 100644 --- a/packages/webawesome/docs/_includes/sidebar.njk +++ b/packages/webawesome/docs/_includes/sidebar.njk @@ -78,10 +78,18 @@ -
  • +
  • Checkbox
  • Color Picker
  • -
  • +
  • + +
  • Comparison
  • -
  • -
  • +
  • +
  • Details
  • Dialog
  • Divider
  • @@ -101,7 +109,7 @@
  • Dropdown Item
  • -
  • +
  • Format Bytes
  • Format Date
  • Format Number
  • @@ -146,7 +154,7 @@
  • Tag
  • Textarea
  • -
  • +
  • Tooltip
  • Tree @@ -154,7 +162,7 @@
  • Tree Item
  • -
  • +
  • Zoomable Frame
  • {# PLOP_NEW_COMPONENT_PLACEHOLDER #} 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 @@ Since {{ component.since }} {{ component.status }} + {% if isProComponent %} + Pro + {% endif %}

    {{ component.summary | inlineMarkdown | safe }} @@ -20,6 +23,37 @@ {# Component API #} {% block afterContent %} + {# Importing #} +

    Importing

    +

    + Autoloading components via projects is the recommended way to import components. If you prefer to do it manually, use one of the following code snippets. +

    + + {% set componentName = component.tagName | stripPrefix %} + {% set componentPath = ["components/", componentName, "/", componentName, ".js"] | join("") %} + + CDN + npm + React + +

    + Let your project code do the work! Sign up for free to use a project with your very own CDN — it's the fastest and easiest way to use Web Awesome. +

    +
    + +

    + To manually import this component from NPM, use the following code. +

    +
    import '@awesome.me/webawesome/dist/{{ componentPath }}';
    +
    + +

    + To manually import this component from React, use the following code. +

    +
    import {{ component.name }} from '@awesome.me/webawesome/dist/react/{{ componentName }}';
    +
    +
    + {# Slots #} {% if component.slots.length %}

    Slots

    @@ -270,38 +304,6 @@ {% endif %} - {# Importing #} -

    Importing

    -

    - Autoloading components via projects is the recommended way to import components. If you prefer to do it manually, use one of the following code snippets. -

    - - - {% set componentName = component.tagName | stripPrefix %} - {% set componentPath = ["components/", componentName, "/", componentName, ".js"] | join("") %} - - CDN - npm - React - -

    - Let your project code do the work! Sign up for free to use a project with your very own CDN — it's the fastest and easiest way to use Web Awesome. -

    -
    - -

    - To manually import this component from NPM, use the following code. -

    -
    import '@awesome.me/webawesome/dist/{{ componentPath }}';
    -
    - -

    - To manually import this component from React, use the following code. -

    -
    import {{ component.name }} from '@awesome.me/webawesome/dist/react/{{ componentName }}';
    -
    -
    -
    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, 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. ``` -### Theme Buttons - -Theme buttons are supported through the button group's `variant` attribute. - -```html {.example} - - Left - Center - Right - - -

    - - - Left - Center - Right - - -

    - - - Left - Center - Right - - -

    - - - Left - Center - Right - - -

    - - - Left - Center - Right - -``` - -You can still use the buttons’ own `variant` attribute to override the inherited variant. - -```html {.example} - - Left - Center - Right - -``` - ### Pill Buttons Pill buttons are supported through the button's `pill` attribute. 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. 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 ` - - + + ${option.label} `; @@ -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 ` */ - &: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 */ + &: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..930b85ee7 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. @@ -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 `
    ` 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/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..2bbf69242 100644 --- a/packages/webawesome/src/components/option/option.ts +++ b/packages/webawesome/src/components/option/option.ts @@ -5,7 +5,8 @@ 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 type WaSelect from '../select/select.js'; +import styles from './option.styles.js'; /** * @summary Options define the selectable items within a select component. @@ -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 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 to update + customElements.whenDefined('wa-combobox').then(() => { + // We cast to because it shares the same API as combobox + const controller = this.closest('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): void { if (changedProperties.has('defaultSelected')) { - if (!this.closest('wa-select')?.hasInteracted) { + // We cast to because it shares the same API as combobox + if (!this.closest('wa-combobox, wa-select')?.hasInteracted) { const oldVal = this.selected; this.selected = this.defaultSelected; this.requestUpdate('selected', oldVal); 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 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 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(); 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..0f7d6f991 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. @@ -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/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..46d1b8cf6 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'; @@ -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. @@ -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 = new Map(); private typeToSelectString = ''; private typeToSelectTimeout: number; @@ -256,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 `` 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; @@ -285,6 +279,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} @@ -520,6 +516,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 +600,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 +700,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 +710,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 +907,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement { } formResetCallback() { + this.selectionOrder.clear(); this.value = this.defaultValue; super.formResetCallback(); this.handleValueChange(); 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..dab5b6eb7 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'; /** * @@ -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 `` 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; @@ -437,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)); } 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..99e3b8de0 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. @@ -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 `` 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/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..3e7870f7d 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. @@ -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 `` 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/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/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 `` 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; 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);