Compare commits

...

46 Commits

Author SHA1 Message Date
Lea Verou
10b067e805 Add JS snppet for setting wa-dark based on OS default 2025-01-25 14:58:57 -05:00
Lindsay M
815cc4220b Add breaks between radios in examples (#598) 2025-01-24 17:19:26 -05:00
Lea Verou
a7caf19b34 Tintless variables + OKLCH coords as comments (#596)
Co-authored-by: Lindsay M <126139086+lindsaym-fa@users.noreply.github.com>
2025-01-24 16:55:21 -05:00
Lindsay M
71c054d6e4 Use background-image for native select caret (#594) 2025-01-24 11:52:06 -05:00
Cory LaViska
e1bf5471bf Use native events when possible (#590)
* fix filename

* <wa-input> input + change (change not working)

* compose that horribly misleading change event

* use native blur/focus events

* update checkbox

* update color picker events

* update color picker events

* update radio group events

* update button events

* remove event options

* update select events

* update image comparer events

* update icon button events

* update slider events

* update rating events

* update radio events

* update switch event

* update textarea events

* update radio button events

* remove unused events

* cleanup

* fix react wrappers

* fix react events for real this time

* update changelog

* add note

* Update src/components/input/input.ts

Co-authored-by: Lea Verou <lea@verou.me>

* delete unused close event

* use same event type

* fix tests

* remove wa- from events in docs and examples

* fix comment

* Update docs/docs/resources/changelog.md

Co-authored-by: Lea Verou <lea@verou.me>

---------

Co-authored-by: Lea Verou <lea@verou.me>
2025-01-23 17:52:41 -05:00
Lea Verou
099edc5186 Implement wide page-level data 2025-01-22 13:25:29 -05:00
Lea Verou
e3560dcf98 Do not reflect default values
- Add `default` descriptor
- Do not reflect attributes when equal to their default value
- Patch getter return value to return default value when empty
- Use it in button `appearance`
2025-01-21 15:16:11 -05:00
Cory LaViska
f2bb2c84a0 fix nested tab groups (#576) 2025-01-21 12:54:38 -05:00
Cory LaViska
13b3342017 fix slot names (#577) 2025-01-21 12:52:41 -05:00
Cory LaViska
d1c1d689ce ensure fix works on backers website (#579) 2025-01-21 12:36:54 -05:00
Cory LaViska
44e5e37a2b 3.0.0-alpha.9 2025-01-17 10:40:54 -05:00
Cory LaViska
566aae927d fix typo 2025-01-17 10:39:18 -05:00
Lea Verou
7258c001a7 Demo for mixing and matching (#565) 2025-01-17 10:38:03 -05:00
Lea Verou
7a70940c6a Update changelog, remove .wa-callout from alpha 2025-01-16 18:27:47 -05:00
Lea Verou
45f4edc426 Fix #561 2025-01-16 16:50:28 -05:00
Lea Verou
da32015f27 Basic mixing and matching docs (#560) 2025-01-16 16:31:24 -05:00
Lea Verou
03d8238edb Add missing :state(blank) docs 2025-01-16 16:17:50 -05:00
Lindsay M
34f8744493 Fix missing mild palette doc and links (#557) 2025-01-16 16:09:52 -05:00
Lindsay M
fa3fe5f753 Theme files housekeeping, closes #519 (#552)
* Reorg Glossy theme

* Reorg Playful theme

* Remove redundant dark mode tokens
2025-01-16 16:09:28 -05:00
Lindsay M
fc6c7de1fd Fix contrast issues with Bright teal (#556) 2025-01-16 16:09:03 -05:00
Lea Verou
0037712549 Matter Ripple MVP (#558)
Co-authored-by: lindsaym-fa <dev@lindsaym.design>
2025-01-16 16:05:50 -05:00
Lea Verou
5301945bfa Filled inputs 2025-01-16 14:48:56 -05:00
Lea Verou
1298651dd8 Floating labels on select 2025-01-16 14:48:56 -05:00
Lea Verou
5f9695fde1 Floating labels on textarea 2025-01-16 14:48:56 -05:00
Lea Verou
2eb2597efe Floating labels for input 2025-01-16 14:48:56 -05:00
lindsaym-fa
431e82261b Initial floating label attempt 2025-01-16 14:48:56 -05:00
Lea Verou
df51149d0a Show contrast ratios in contrast pair tables 2025-01-16 13:18:26 -05:00
Lindsay M
fba0b11343 Add Matter theme (#547)
* Add Matter theme

* Add Matter to alpha build

* Add changelog

* Fix incorrect palette name

* Make loud fills darker in light mode
2025-01-16 12:08:51 -05:00
Lea Verou
3618e93490 Reintroduce --border-width and --border-radius, fixes #531 2025-01-16 10:36:03 -05:00
Lea Verou
cfa95307d1 Quick proof of concept contrast tests 2025-01-16 10:22:01 -05:00
Lea Verou
15344c2a2a Appease Turbo for the color scheme picker too, fixes #520 2025-01-16 10:15:52 -05:00
lindsaym-fa
3974aa5130 Decrease line height on swatches 2025-01-16 10:15:16 -05:00
lindsaym-fa
a6702ad6d2 Revise Color tokens doc and link to palettes 2025-01-16 10:15:16 -05:00
lindsaym-fa
ecf21adddc Fix punctuation 2025-01-16 10:15:16 -05:00
Lea Verou
52c24fc3b7 Add color palette to theme pages 2025-01-16 10:15:16 -05:00
Lea Verou
d464714d7b Rudimentary palette icons 2025-01-16 10:15:16 -05:00
Lea Verou
7d089bbe2f Palette Docs 2025-01-16 10:15:16 -05:00
Lea Verou
71914afc91 defaultPalette -> palette 2025-01-16 10:15:16 -05:00
Lindsay M
9d139e3fa0 Minor style update for checkboxes in wa-tree-item (#542)
* Use theme value line height for tree item checkbox

* Apply theme checkbox styles to selected tree items
2025-01-16 09:30:35 -05:00
Lindsay M
db3039e9fe Fix green-40 in classic color palette (#548) 2025-01-16 09:26:06 -05:00
Lindsay M
9494b9bb67 Improve default tooltip styles (#543)
* Use inverted colors for tooltips

* Remove redundant tooltip overrides from themes
2025-01-16 04:35:08 -05:00
Lea Verou
7e1f4f0351 Separate meaningful theme parts out (#526)
Co-authored-by: lindsaym-fa <dev@lindsaym.design>
2025-01-15 17:32:36 -05:00
Lindsay M
5ebe4f4d3e Finalize playful theme, closes #490 (#527)
* Finalize Playful theme

* Add changelog, add to alpha

* Add playful theme to alpha build

* Touchup and tweaks

* Tweak hover mix color in `wa-dark`

* Avoid transforming buttons in button groups

* Final touchup

---------

Co-authored-by: Lea Verou <lea@verou.me>
2025-01-15 17:13:48 -05:00
Lea Verou
dfb9d53a25 Checkbox improvements
- Added `part="control", fixes #529
- Removed wrapper div which is no longer needed
- Removed `form-control--has-hint` class which is no longer used anywhere
2025-01-15 15:03:58 -05:00
Lindsay M
c2c1a2ff5b Add missing Glossy and Premium themes to alpha build (#528) 2025-01-15 10:45:43 -05:00
Lindsay M
ac86c037a1 Finalize Glossy theme, closes #491 (#525)
* Initial glassy theme progress

* Add fallback to slider thumb box shadow

* Remove redundant `wa-dark` styles and refactor shadows

* Rename to 'Glossy' since it fits the vibe better
2025-01-14 18:44:31 -05:00
167 changed files with 5073 additions and 3765 deletions

1
docs/_data/hues.json Normal file
View File

@@ -0,0 +1 @@
["red", "yellow", "green", "teal", "blue", "indigo", "violet", "gray"]

1
docs/_data/palettes.js Normal file
View File

@@ -0,0 +1 @@
export { default as default } from '../../src/styles/color/palettes.js';

View File

@@ -16,7 +16,7 @@
{# Docs styles #}
<link rel="stylesheet" href="/assets/styles/docs.css" />
</head>
<body class="layout-{{ layout | stripExtension }}">
<body class="layout-{{ layout | stripExtension }}{{ ' page-wide' if wide }}">
<!-- use view="desktop" as default to reduce layout jank on desktop site. -->
<wa-page view="desktop" disable-navigation-toggle="">
<header slot="header" class="wa-split">

View File

@@ -0,0 +1,40 @@
<table class="colors wa-palette-{{ paletteId }}">
<thead>
<tr>
<th></th>
{% for tint_bg in tints -%}
{% for tint_fg in tints | reverse -%}
{% if (tint_fg - tint_bg) | abs == difference %}
<th>{{ tint_fg }} on {{ tint_bg }}</th>
{% endif %}
{%- endfor -%}
{%- endfor %}
</tr>
</thead>
{% for hue in hues -%}
<tr>
<th>{{ hue | capitalize }}</th>
{% for tint_bg in tints -%}
{% set color_bg = palettes[paletteId][hue][tint_bg] %}
{% for tint_fg in tints | reverse -%}
{% set color_fg = palettes[paletteId][hue][tint_fg] %}
{% if (tint_fg - tint_bg) | abs == difference %}
<td>
<div class="color swatch" style="background-color: var(--wa-color-{{ hue }}-{{ tint_bg }}); color: var(--wa-color-{{ hue }}-{{ tint_fg }})">
{% set contrast_wcag = '' %}
{% if color_fg and color_bg %}
{% set contrast_wcag = color_bg.contrast(color_fg, 'WCAG21') %}
{% endif %}
{% if contrast_wcag %}
{{ contrast_wcag | number({maximumSignificantDigits: 2}) }}
{% else %}
{{ tint_fg }} on {{ tint_bg }}
{% endif %}
</div>
</td>
{% endif %}
{%- endfor -%}
{%- endfor -%}
</tr>
{%- endfor %}
</table>

View File

@@ -0,0 +1,18 @@
<wa-tab-group>
<wa-tab panel="html">In HTML</wa-tab>
<wa-tab panel="css">In CSS</wa-tab>
<wa-tab-panel name="html">
Simply add the following code to the `<head>` of your page:
```html
<link rel="stylesheet" href="{% cdnUrl stylesheet %}" />
```
</wa-tab-panel>
<wa-tab-panel name="css">
Simply add the following code at the top of your CSS file:
```css
@import url('{% cdnUrl stylesheet %}');
```
</wa-tab-panel>
</wa-tab-group>

View File

@@ -25,6 +25,7 @@
'utilities': 'Style Utilities',
'layout': 'Layout',
'patterns': 'Patterns',
'palettes': 'Color Palettes',
'tokens': 'Design Tokens'
} %}
{% include 'sidebar-group.njk' %}

View File

@@ -0,0 +1,24 @@
{% set paletteId = page.fileSlug %}
{% set tints = [80, 60, 40, 20] %}
{% set width = 20 %}
{% set height = 13 %}
{% set gap_x = 3 %}
{% set gap_y = 3 %}
<svg viewBox="0 0 {{ (width + gap_x) * hues|length }} {{ (height + gap_y) * tints|length }}" fill="none" xmlns="http://www.w3.org/2000/svg" class="wa-palette-{{ paletteId }} palette-icon">
<style>
@import url('/dist/styles/color/{{ paletteId }}.css') layer(palette.{{ paletteId }});
.palette-icon {
height: 8ch;
}
</style>
{% for hue in hues -%}
{% set hueIndex = loop.index0 %}
{% for tint in tints -%}
<rect x="{{ hueIndex * (width + gap_x) }}" y="{{ loop.index0 * (height + gap_y) }}"
width="{{ width }}" height="{{ height }}"
fill="var(--wa-color-{{ hue }}-{{ tint }})" rx="4" />
{%- endfor %}
{% endfor %}
</svg>

119
docs/_layouts/palette.njk Normal file
View File

@@ -0,0 +1,119 @@
{% set hasSidebar = true %}
{% set hasOutline = true %}
{# {% set forceTheme = page.fileSlug %} #}
{% extends '../_layouts/block.njk' %}
{% set paletteId = page.fileSlug %}
{% block afterContent %}
<style>@import url('/dist/styles/color/{{ paletteId }}.css') layer(palette.{{ paletteId }});</style>
{% set tints = ["95", "90", "80", "70", "60", "50", "40", "30", "20", "10", "05"] %}
<table class="colors wa-palette-{{ paletteId }}">
<thead>
<tr>
<th></th>
{% for tint in tints -%}
<th>{{ tint }}</th>
{%- endfor %}
</tr>
</thead>
{% for hue in hues -%}
<tr>
<th>{{ hue | capitalize }}</th>
{% for tint in tints -%}
<td>
<div class="color swatch" style="background-color: var(--wa-color-{{ hue }}-{{ tint }})">
<wa-copy-button value="--wa-color-{{ hue }}-{{ tint }}" copy-label="--wa-color-{{ hue }}-{{ tint }}"></wa-copy-button>
</div>
</td>
{%- endfor -%}
</tr>
{%- endfor %}
</table>
<h2>Used By</h2>
<section class="index-grid">
{% for page in collections.theme %}
{%- if page.data.palette == paletteId -%}
{% include "page-card.njk" %}
{%- endif -%}
{% endfor %}
</section>
{% markdown %}
## Color Contrast
Web Awesome color scales are designed to guarantee certain contrast ratios,
both per [WCAG 2.1 success criteria](https://www.w3.org/TR/WCAG21/#contrast-minimum)
as well as the emergent APCA specification _(planned)_,
so you can ensure that text is both legible to all users, and legally conformant.
### Level 1
A difference of `40` ensures a minimum **3:1** contrast ratio, suitable for large text and icons (AA).
{% endmarkdown %}
{% set difference = 40 %}
{% include "contrast-table.njk" %}
{% markdown %}
This also goes for a difference of `45`:
{% endmarkdown %}
{% set difference = 45 %}
{% include "contrast-table.njk" %}
{% markdown %}
### Level 2
A difference of `50` ensures a minimum **4.5:1** contrast ratio, suitable for normal text (AA) and large text (AAA)
{% endmarkdown %}
{% set difference = 50 %}
{% include "contrast-table.njk" %}
{% markdown %}
This also goes for a difference of `55`:
{% endmarkdown %}
{% set difference = 55 %}
{% include "contrast-table.njk" %}
{% markdown %}
### Level 3
A difference of `60` ensures a minimum **7:1** contrast ratio, suitable for all text (AAA)
{% endmarkdown %}
{% set difference = 60 %}
{% include "contrast-table.njk" %}
{% markdown %}
This also goes for a difference of `65`:
{% endmarkdown %}
{% set difference = 65 %}
{% include "contrast-table.njk" %}
{% markdown %}
## How to use this palette
If you are using a Web Awesome theme that uses this palette, it will already be included.
To use a different palette than a theme default, or to use it in a custom theme, you can import this palette directly from the Web Awesome CDN.
{% set stylesheet = 'styles/color/' + page.fileSlug + '.css' %}
{% include 'import-stylesheet-code.md.njk' %}
{% endmarkdown %}
{% endblock %}

View File

@@ -5,7 +5,71 @@
{% extends '../_includes/base.njk' %}
{% block header %}
<iframe src='{{ page.url }}demo.html'></iframe>
<iframe src='{{ page.url }}demo.html' id="demo"></iframe>
<p id="mix_and_match" class="wa-gap-m">
<strong>
<wa-icon name="merge" slot="prefix"></wa-icon>
Remix
<wa-icon-button href="#remixing" name="circle-question" slot="suffix" variant="regular" label="How to use?"></wa-icon-button>
</strong>
<wa-select name="colors" label="Colors from…" size="small">
<wa-icon name="palette" slot="prefix" variant="regular"></wa-icon>
<wa-option value="">(Theme default)</wa-option>
<wa-divider></wa-divider>
{% for theme in collections.theme | sort %}
{% if theme.fileSlug !== page.fileSlug %}
<wa-option value="{{ theme.fileSlug }}">{{ theme.data.title }}</wa-option>
{% endif %}
{% endfor %}
</wa-select>
<wa-select name="palette" label="Palette" size="small">
<wa-icon name="swatchbook" slot="prefix" variant="regular"></wa-icon>
<wa-option value="">(Theme default)</wa-option>
<wa-divider></wa-divider>
{% for p in collections.palette | sort %}
{% if p.fileSlug !== palette %}
<wa-option value="{{ p.fileSlug }}">{{ p.data.title }}</wa-option>
{% endif %}
{% endfor %}
</wa-select>
<wa-select name="typography" label="Typography from…" size="small">
<wa-icon name="font-case" slot="prefix"></wa-icon>
<wa-option value="">(Theme default)</wa-option>
<wa-divider></wa-divider>
{% for theme in collections.theme | sort %}
{% if theme.fileSlug !== page.fileSlug %}
<wa-option value="{{ theme.fileSlug }}">{{ theme.data.title }}</wa-option>
{% endif %}
{% endfor %}
</wa-select>
</p>
<script>
document.querySelector('#mix_and_match').addEventListener('change', function(event) {
let selects = document.querySelectorAll('#mix_and_match wa-select');
let url = new URL(demo.src);
for (let select of selects) {
url.searchParams.set(select.name, select.value);
}
demo.src = url;
});
</script>
<h2>Default Color Palette</h2>
{% set paletteURL = '/docs/palettes/' + palette + '/' %}
{% set themePage = page %}
{% set page = paletteURL | getCollectionItemFromUrl %}
<div class="index-grid">
{% include 'page-card.njk' %}
</div>
{% set page = themePage %}
{% endblock %}
{% block afterContent %}
@@ -14,24 +78,25 @@
You can import this theme from the Web Awesome CDN.
<wa-tab-group>
<wa-tab panel="html">In HTML</wa-tab>
<wa-tab panel="css">In CSS</wa-tab>
<wa-tab-panel name="html">
{% set stylesheet = 'styles/themes/' + page.fileSlug + '.css' %}
{% include 'import-stylesheet-code.md.njk' %}
Simply add the following code to the `<head>` of your page:
```html
<link rel="stylesheet" href="{% cdnUrl 'styles/themes/' + page.fileSlug + '.css' %}" />
```
</wa-tab-panel>
<wa-tab-panel name="css">
### Remixing { #remixing }
Simply add the following code at the top of your CSS file:
```css
@import url('{% cdnUrl 'styles/themes/' + page.fileSlug + '.css' %}');
```
</wa-tab-panel>
</wa-tab-group>
If you want to combine the **colors** from this theme with another theme, you can import this CSS file *after* the other themes CSS file:
{% set stylesheet = 'styles/themes/' + page.fileSlug + '/color.css' %}
{% include 'import-stylesheet-code.md.njk' %}
To use the **typography** from this theme with another theme, you can import this CSS file *after* the other themes CSS file:
{% set stylesheet = 'styles/themes/' + page.fileSlug + '/typography.css' %}
{% include 'import-stylesheet-code.md.njk' %}
<wa-callout variant="warning">
<wa-icon slot="icon" name="triangle-exclamation" variant="regular"></wa-icon>
Please note that not all combinations will look good — once youre mixing and matching, youre on your own!
</wa-callout>
## Dark mode
@@ -73,6 +138,7 @@ You can apply the class to the `<html>` element on your page to activate the dar
Web Awesome's themes have both light and dark styles built in.
However, Web Awesome doesn't try to auto-detect the user's light/dark mode preference.
This should be done at the application level.
As a best practice, to provide a dark theme in your app, you should:
- Check for [`prefers-color-scheme`](https://stackoverflow.com/a/57795495/567486) and use its value by default
@@ -81,5 +147,18 @@ As a best practice, to provide a dark theme in your app, you should:
Web Awesome avoids using the `prefers-color-scheme` media query because not all apps support dark mode, and it would break things for the ones that don't.
Assuming the user's preference is in a variable called `colorScheme` (values: `auto`, `light`, `dark`),
you can use the following JS snippet to apply the `wa-dark` class to the `<html>` element accordingly:
```js
const systemDark = window.matchMedia('(prefers-color-scheme: dark)');
const applyDark = function (event = systemDark) {
const isDark = colorScheme === 'auto' ? event.matches : colorScheme === 'dark';
document.documentElement.classList.toggle('wa-dark', isDark);
};
systemDark.addEventListener('change', applyDark);
applyDark();
```
{% endmarkdown %}
{% endblock %}

View File

@@ -105,6 +105,23 @@ export function deepValue(obj, key) {
return key.reduce((subObj, property) => subObj?.[property], obj);
}
export function number(value, options) {
if (typeof value !== 'number' && isNaN(value)) {
return value;
}
let lang = options?.lang ?? 'en';
if (options?.lang) {
delete options.lang;
}
if (!options || Object.keys(options).length === 0) {
options = { maximumSignificantDigits: 3 };
}
return Number(value).toLocaleString(lang, options);
}
export function isNumeric(value) {
return typeof value === 'number' || (typeof value === 'string' && !isNaN(value));
}

View File

@@ -18,7 +18,7 @@ function updateResults(input) {
}
}
document.documentElement.addEventListener('wa-input', e => {
document.documentElement.addEventListener('input', e => {
if (e.target?.matches('#block-filter wa-input')) {
updateResults(e.target);
}

View File

@@ -47,22 +47,6 @@ const presetTheme = new ThemeAspect({
},
});
/**
* Without this, there's a flash of the incorrect preset theme.
*/
function updateSelectionBeforeTurboLoad(e) {
const newElement = e.detail.newBody || e.detail.newFrame || e.detail.newStream;
if (newElement) {
presetTheme.syncUI(newElement);
}
}
['turbo:before-render', 'turbo:before-stream-render', 'turbo:before-frame-render'].forEach(eventName => {
document.addEventListener(eventName, updateSelectionBeforeTurboLoad);
});
window.addEventListener('turbo:render', e => {
presetTheme.applyChange({ behavior: 'instant' });
});
window.presetTheme = presetTheme;

View File

@@ -28,12 +28,21 @@ export class ThemeAspect {
});
// Listen for selections
document.addEventListener('wa-change', event => {
document.addEventListener('change', event => {
const picker = event.target.closest(this.picker);
if (picker) {
this.set(picker.value);
}
});
['turbo:before-render', 'turbo:before-stream-render', 'turbo:before-frame-render'].forEach(eventName => {
document.addEventListener(eventName, e => {
const newElement = e.detail.newBody || e.detail.newFrame || e.detail.newStream;
if (newElement) {
this.syncUI(newElement);
}
});
});
}
get() {

View File

@@ -333,6 +333,7 @@ wa-page > main:has(> .index-grid) {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(22ch, 100%), 1fr));
gap: var(--wa-space-2xl);
margin-block-end: var(--wa-space-3xl);
a {
border-radius: var(--wa-border-radius-l);
@@ -383,15 +384,23 @@ wa-page > main:has(> .index-grid) {
/* Swatches */
.swatch {
position: relative;
display: flex;
align-items: center;
justify-content: center;
background-color: transparent;
border-color: var(--wa-color-neutral-border-normal);
border-style: var(--wa-border-style);
border-width: var(--wa-border-width-s);
border-radius: var(--wa-border-radius-m);
box-sizing: border-box;
line-height: 2.5;
height: 2.5em;
padding-inline: var(--wa-space-xs);
min-height: 2.5em;
padding: var(--wa-space-xs);
font-weight: var(--wa-font-weight-semibold);
line-height: var(--wa-line-height-condensed);
&.color {
border-color: transparent;
}
wa-copy-button {
position: absolute;
@@ -421,6 +430,34 @@ wa-page > main:has(> .index-grid) {
}
}
table.colors {
thead {
th {
text-align: center;
padding-block: 0;
}
}
tbody {
tr {
border: none;
&:hover {
background: transparent;
}
}
th {
width: 0;
vertical-align: middle;
text-align: right;
}
td {
padding-inline: var(--wa-space-3xs);
padding-block: var(--wa-space-s);
}
}
}
/* Layout Examples */
.layout-example-boundary {
border: var(--wa-border-width-s) dashed var(--wa-color-neutral-border-normal);
@@ -484,7 +521,7 @@ wa-page > main:has(> .index-grid) {
}
}
.layout-theme {
.page-wide {
wa-page > main {
max-width: 140ch;
@@ -492,11 +529,32 @@ wa-page > main:has(> .index-grid) {
max-width: 80ch;
}
}
}
.layout-theme {
iframe {
width: 100%;
min-height: 16lh;
height: 65vh;
max-height: 21lh;
}
#mix_and_match {
strong {
display: flex;
align-items: center;
gap: var(--wa-space-2xs);
margin-top: 1.2em;
}
wa-select::part(label) {
margin-block-end: 0;
}
wa-select[value='']::part(display-input),
wa-option[value=''] {
font-style: italic;
color: var(--wa-color-text-quiet);
}
}
}

View File

@@ -77,9 +77,9 @@ This example demonstrates all of the baked-in animations and easings. Animations
easingName.appendChild(option);
});
animationName.addEventListener('wa-change', () => (animation.name = animationName.value));
easingName.addEventListener('wa-change', () => (animation.easing = easingName.value));
playbackRate.addEventListener('wa-input', () => (animation.playbackRate = playbackRate.value));
animationName.addEventListener('change', () => (animation.name = animationName.value));
easingName.addEventListener('change', () => (animation.easing = easingName.value));
playbackRate.addEventListener('input', () => (animation.playbackRate = playbackRate.value));
</script>
<style>

View File

@@ -249,7 +249,7 @@ This example is best demonstrated using a mouse. Try clicking and dragging the s
const carousel = container.querySelector('wa-carousel');
const toggle = container.querySelector('wa-switch');
toggle.addEventListener('wa-change', () => {
toggle.addEventListener('change', () => {
carousel.toggleAttribute('mouse-dragging', toggle.checked);
});
</script>
@@ -450,7 +450,7 @@ Use the `--aspect-ratio` custom property to customize the size of the carousel's
const carousel = document.querySelector('wa-carousel.aspect-ratio');
const aspectRatio = document.querySelector('wa-select[name="aspect"]');
aspectRatio.addEventListener('wa-change', () => {
aspectRatio.addEventlistener('change', () => {
carousel.style.setProperty('--aspect-ratio', aspectRatio.value);
});
})();

View File

@@ -73,4 +73,4 @@ if (name_search.value) {
filterByName(name_search.value);
}
name_search_group.addEventListener('wa-input', e => filterByName(name_search.value));
name_search_group.addEventListener('input', e => filterByName(name_search.value));

View File

@@ -82,7 +82,7 @@ Use the `setCustomValidity()` method to set a custom validation message. This wi
});
// Update validity on change
checkbox.addEventListener('wa-change', () => {
checkbox.addEventlistener('change', () => {
checkbox.setCustomValidity(checkbox.checked ? '' : errorMessage);
});

View File

@@ -16,7 +16,7 @@ icon: format-bytes
const formatter = container.querySelector('wa-format-bytes');
const input = container.querySelector('wa-input');
input.addEventListener('wa-input', () => (formatter.value = input.value || 0));
input.addEventListener('input', () => (formatter.value = input.value || 0));
</script>
```

View File

@@ -19,7 +19,7 @@ Localization is handled by the browser's [`Intl.NumberFormat` API](https://devel
const formatter = container.querySelector('wa-format-number');
const input = container.querySelector('wa-input');
input.addEventListener('wa-input', () => (formatter.value = input.value || 0));
input.addEventListener('input', () => (formatter.value = input.value || 0));
</script>
```

View File

@@ -54,11 +54,11 @@ Popup is a low-level utility built specifically for positioning elements. Do not
const active = container.querySelector('wa-switch[name="active"]');
const arrow = container.querySelector('wa-switch[name="arrow"]');
select.addEventListener('wa-change', () => (popup.placement = select.value));
distance.addEventListener('wa-input', () => (popup.distance = distance.value));
skidding.addEventListener('wa-input', () => (popup.skidding = skidding.value));
active.addEventListener('wa-change', () => (popup.active = active.checked));
arrow.addEventListener('wa-change', () => (popup.arrow = arrow.checked));
select.addEventListener('change', () => (popup.placement = select.value));
distance.addEventListener('input', () => (popup.distance = distance.value));
skidding.addEventListener('input', () => (popup.skidding = skidding.value));
active.addEventListener('change', () => (popup.active = active.checked));
arrow.addEventListener('change', () => (popup.arrow = arrow.checked));
</script>
<style>
@@ -145,7 +145,7 @@ Popups are inactive and hidden until the `active` attribute is applied. Removing
const popup = container.querySelector('wa-popup');
const active = container.querySelector('wa-switch');
active.addEventListener('wa-change', () => (popup.active = active.checked));
active.addEventListener('change', () => (popup.active = active.checked));
</script>
```
@@ -233,7 +233,7 @@ Since placement is preferred when using `flip`, you can observe the popup's curr
const popup = container.querySelector('wa-popup');
const select = container.querySelector('wa-select');
select.addEventListener('wa-change', () => (popup.placement = select.value));
select.addEventListener('change', () => (popup.placement = select.value));
</script>
```
@@ -277,7 +277,7 @@ Use the `distance` attribute to change the distance between the popup and its an
const popup = container.querySelector('wa-popup');
const distance = container.querySelector('wa-slider');
distance.addEventListener('wa-input', () => (popup.distance = distance.value));
distance.addEventListener('input', () => (popup.distance = distance.value));
</script>
```
@@ -321,7 +321,7 @@ The `skidding` attribute is similar to `distance`, but instead allows you to off
const popup = container.querySelector('wa-popup');
const skidding = container.querySelector('wa-slider');
skidding.addEventListener('wa-input', () => (popup.skidding = skidding.value));
skidding.addEventListener('input', () => (popup.skidding = skidding.value));
</script>
```
@@ -409,9 +409,9 @@ By default, the arrow will be aligned as close to the center of the _anchor_ as
const arrowPlacement = container.querySelector('[name="arrow-placement"]');
const arrow = container.querySelector('[name="arrow"]');
placement.addEventListener('wa-change', () => (popup.placement = placement.value));
arrowPlacement.addEventListener('wa-change', () => (popup.arrowPlacement = arrowPlacement.value));
arrow.addEventListener('wa-change', () => (popup.arrow = arrow.checked));
placement.addEventListener('change', () => (popup.placement = placement.value));
arrowPlacement.addEventListener('change', () => (popup.arrowPlacement = arrowPlacement.value));
arrow.addEventListener('change', () => (popup.arrow = arrow.checked));
</script>
</div>
```
@@ -464,7 +464,7 @@ Use the `sync` attribute to make the popup the same width or height as the ancho
const fixed = container.querySelector('wa-switch');
const sync = container.querySelector('wa-select');
sync.addEventListener('wa-change', () => (popup.sync = sync.value));
sync.addEventListener('change', () => (popup.sync = sync.value));
</script>
```
@@ -523,7 +523,7 @@ Toggle the switch and scroll the container to see the difference.
const popup = container.querySelector('wa-popup');
const fixed = container.querySelector('wa-switch');
fixed.addEventListener('wa-change', () => (popup.strategy = fixed.checked ? 'fixed' : 'absolute'));
fixed.addEventListener('change', () => (popup.strategy = fixed.checked ? 'fixed' : 'absolute'));
</script>
```
@@ -575,7 +575,7 @@ Scroll the container to see how the popup flips to prevent clipping.
const popup = container.querySelector('wa-popup');
const flip = container.querySelector('wa-switch');
flip.addEventListener('wa-change', () => (popup.flip = flip.checked));
flip.addEventListener('change', () => (popup.flip = flip.checked));
</script>
```
@@ -670,7 +670,7 @@ Toggle the switch to see the difference.
const popup = container.querySelector('wa-popup');
const shift = container.querySelector('wa-switch');
shift.addEventListener('wa-change', () => (popup.shift = shift.checked));
shift.addEventListener('change', () => (popup.shift = shift.checked));
</script>
```
@@ -731,7 +731,7 @@ Scroll the container to see the popup resize as its available space changes.
const popup = container.querySelector('wa-popup');
const autoSize = container.querySelector('wa-switch');
autoSize.addEventListener('wa-change', () => (popup.autoSize = autoSize.checked ? 'both' : ''));
autoSize.addEventListener('change', () => (popup.autoSize = autoSize.checked ? 'both' : ''));
</script>
```
@@ -782,9 +782,9 @@ When a gap exists between the anchor and the popup element, this option will add
const hoverBridge = container.querySelector('wa-switch');
const distance = container.querySelector('wa-slider[label="Distance"]');
const skidding = container.querySelector('wa-slider[label="Skidding"]');
distance.addEventListener('wa-input', () => (popup.distance = distance.value));
skidding.addEventListener('wa-input', () => (popup.skidding = skidding.value));
hoverBridge.addEventListener('wa-change', () => (popup.hoverBridge = hoverBridge.checked));
distance.addEventListener('input', () => (popup.distance = distance.value));
skidding.addEventListener('input', () => (popup.skidding = skidding.value));
hoverBridge.addEventListener('change', () => (popup.hoverBridge = hoverBridge.checked));
</script>
```
@@ -837,7 +837,7 @@ This example anchors a popup to the mouse cursor using a virtual element. As suc
};
// Only activate the popup when the switch is checked
enabled.addEventListener('wa-change', () => {
enabled.addEventListener('change', () => {
popup.active = enabled.checked;
});

View File

@@ -24,7 +24,7 @@ QR codes are useful for providing small pieces of information to users who can q
customElements.whenDefined('wa-qr-code').then(() => {
input.value = qrCode.value;
input.addEventListener('wa-input', () => (qrCode.value = input.value));
input.addEventListener('input', () => (qrCode.value = input.value));
});
</script>

View File

@@ -7,8 +7,8 @@ icon: radio-group
```html {.example}
<wa-radio-group label="Select an option" name="a" value="1">
<wa-radio value="1">Option 1</wa-radio>
<wa-radio value="2">Option 2</wa-radio>
<wa-radio value="1">Option 1</wa-radio><br>
<wa-radio value="2">Option 2</wa-radio><br>
<wa-radio value="3">Option 3</wa-radio>
</wa-radio-group>
```
@@ -21,8 +21,8 @@ Add descriptive hint to a radio group with the `hint` attribute. For hints that
```html {.example}
<wa-radio-group label="Select an option" hint="Choose the most appropriate option." name="a" value="1">
<wa-radio value="1">Option 1</wa-radio>
<wa-radio value="2">Option 2</wa-radio>
<wa-radio value="1">Option 1</wa-radio><br>
<wa-radio value="2">Option 2</wa-radio><br>
<wa-radio value="3">Option 3</wa-radio>
</wa-radio-group>
```
@@ -45,8 +45,8 @@ Radios and radio buttons can be disabled by adding the `disabled` attribute to t
```html {.example}
<wa-radio-group label="Select an option" name="a" value="1">
<wa-radio value="1">Option 1</wa-radio>
<wa-radio value="2" disabled>Option 2</wa-radio>
<wa-radio value="1">Option 1</wa-radio><br>
<wa-radio value="2" disabled>Option 2</wa-radio><br>
<wa-radio value="3">Option 3</wa-radio>
</wa-radio-group>
```
@@ -57,15 +57,15 @@ The size of [Radios](/docs/components/radio) and [Radio Buttons](/docs/component
```html preview
<wa-radio-group label="Select an option" size="medium" value="medium" class="radio-group-size">
<wa-radio value="small">Small</wa-radio>
<wa-radio value="medium">Medium</wa-radio>
<wa-radio value="small">Small</wa-radio><br>
<wa-radio value="medium">Medium</wa-radio><br>
<wa-radio value="large">Large</wa-radio>
</wa-radio-group>
<script>
const radioGroup = document.querySelector('.radio-group-size');
radioGroup.addEventListener('wa-change', () => {
radioGroup.addEventlistener('change', () => {
radioGroup.size = radioGroup.value;
});
</script>
@@ -82,8 +82,8 @@ Setting the `required` attribute to make selecting an option mandatory. If a val
```html {.example}
<form class="validation">
<wa-radio-group label="Select an option" name="a" required>
<wa-radio value="1">Option 1</wa-radio>
<wa-radio value="2">Option 2</wa-radio>
<wa-radio value="1">Option 1</wa-radio><br>
<wa-radio value="2">Option 2</wa-radio><br>
<wa-radio value="3">Option 3</wa-radio>
</wa-radio-group>
<br />
@@ -108,8 +108,8 @@ Use the `setCustomValidity()` method to set a custom validation message. This wi
```html {.example}
<form class="custom-validity">
<wa-radio-group label="Select an option" name="a" value="1">
<wa-radio value="1">Not me</wa-radio>
<wa-radio value="2">Me neither</wa-radio>
<wa-radio value="1">Not me</wa-radio><br>
<wa-radio value="2">Me neither</wa-radio><br>
<wa-radio value="3">Choose me</wa-radio>
</wa-radio-group>
<br />
@@ -127,7 +127,7 @@ Use the `setCustomValidity()` method to set a custom validation message. This wi
});
// Update validity when a selection is made
form.addEventListener('wa-change', () => {
form.addEventlistener('change', () => {
const isValid = radioGroup.value === '3';
radioGroup.setCustomValidity(isValid ? '' : errorMessage);
});

View File

@@ -420,5 +420,7 @@ This can be hard to conceptualize, so heres a fairly large example showing how l
//
// TODO - remove once we switch to the Popover API
//
document.querySelectorAll('wa-code-demo [slot="preview"] wa-select').forEach(select => select.hoist = true);
customElements.whenDefined('wa-select').then(() => {
document.querySelectorAll('wa-code-demo [slot="preview"] wa-select').forEach(select => select.hoist = true);
});
</script>

View File

@@ -208,7 +208,7 @@ Try resizing the example below with each option and notice how the panels respon
const splitPanel = container.querySelector('wa-split-panel');
const select = container.querySelector('wa-select');
select.addEventListener('wa-change', () => (splitPanel.primary = select.value));
select.addEventlistener('change', () => (splitPanel.primary = select.value));
</script>
```

View File

@@ -88,7 +88,7 @@ The `selection` attribute lets you change the selection behavior of the tree.
const selectionMode = document.querySelector('#selection-mode');
const tree = document.querySelector('.tree-selectable');
selectionMode.addEventListener('wa-change', () => {
selectionMode.addEventlistener('change', () => {
tree.querySelectorAll('wa-tree-item').forEach(item => (item.selected = false));
tree.selection = selectionMode.value;
});

View File

@@ -506,7 +506,7 @@ hasOutline: false
<wa-option data-alpha="remove" value="playful">Playful</wa-option>
<wa-option data-alpha="remove" value="brutalist">Brutalist</wa-option>
<wa-option data-alpha="remove" value="tailspin">Tailspin</wa-option>
<wa-option data-alpha="remove" value="glassy">Glassy</wa-option>
<wa-option data-alpha="remove" value="glossy">Glossy</wa-option>
<wa-option data-alpha="remove" value="active">Active</wa-option>
<wa-option value="classic">Classic</wa-option>
</wa-select>
@@ -803,7 +803,7 @@ hasOutline: false
const queue = [];
let inputTimeout;
variantInput.addEventListener('wa-change', () => {
variantInput.addEventlistener('change', () => {
iconList.dataset.variant = variantInput.value;
});
@@ -823,7 +823,7 @@ hasOutline: false
});
// Filter as the user types
input.addEventListener('wa-input', () => {
input.addEventListener('input', () => {
clearTimeout(inputTimeout);
inputTimeout = setTimeout(() => {
[...iconList.children].map(item => {
@@ -886,7 +886,7 @@ hasOutline: false
case 'active':
colorPalette = 'rudimentary';
break;
case 'glassy':
case 'glossy':
colorPalette = 'elegant';
break;
case 'premium':
@@ -1049,7 +1049,7 @@ hasOutline: false
case 'brutalist':
case 'classic':
case 'awesome':
case 'glassy':
case 'glossy':
case 'active':
assetFolder = themeSelect.value;
break;
@@ -1084,10 +1084,10 @@ hasOutline: false
el.classList.add(`wa-theme-${theme}-${colorMode}`);
}
colorModeSelect.addEventListener('wa-change', setColorMode);
colorModeSelect.addEventlistener('change', setColorMode);
// Theme Switcher
themeSelect.addEventListener('wa-change', event => {
themeSelect.addEventlistener('change', event => {
const theme = event.target.value
const newStylesheet = Object.assign(document.createElement("link"), {
// This media: "print" allows us to lazy load the stylesheet then hot swap it on load.
@@ -1132,14 +1132,14 @@ hasOutline: false
});
// Color Palette
colorSelect.addEventListener('wa-change', event => {
colorSelect.addEventlistener('change', event => {
const colorPalette = event.target.value;
colorStylesheet.href = `/dist/styles/themes/color/${colorPalette}.css`;
});
// Brand Color
brandColor.addEventListener('wa-change', event => {
brandColor.addEventlistener('change', event => {
const documentStyles = document.documentElement.style
documentStyles.setProperty('--wa-color-primary-95', `var(--wa-color-${event.target.value}-95)`);
documentStyles.setProperty('--wa-color-primary-90', `var(--wa-color-${event.target.value}-90)`);
@@ -1223,7 +1223,7 @@ hasOutline: false
})
// Pre-generated logos
logoSelector.addEventListener('wa-change', event => {
logoSelector.addEventlistener('change', event => {
const value = event.currentTarget.value
const projectLogo = previewContainer.querySelector("#project-logo");
@@ -1259,7 +1259,7 @@ hasOutline: false
case 'tailspin':
presetLogoIcons = ['wind', 'feather', 'lemon', 'wind-turbine'];
break;
case 'glassy':
case 'glossy':
presetLogoIcons = ['raindrops', 'citrus-slice', 'lighthouse', 'kiwi-bird'];
break;
case 'active':
@@ -1279,21 +1279,21 @@ hasOutline: false
})
}
themeSelect.addEventListener('wa-change', setLogoIcons);
themeSelect.addEventlistener('change', setLogoIcons);
// Project Name
container.querySelector('[name="project-name"]').addEventListener('wa-input', event => {
container.querySelector('[name="project-name"]').addEventListener('input', event => {
previewContainer.querySelector("#project-name").innerText = event.target.value || event.target.getAttribute("placeholder")
})
// Heading font weight
resetHeadingFontWeightValue()
fontWeightHeading.addEventListener('wa-input', event => {
fontWeightHeading.addEventListener('input', event => {
document.documentElement.style.setProperty('--wa-font-weight-heading', event.target.value);
});
// Heading text
fontFamilyHeading.addEventListener('wa-change', event => {
fontFamilyHeading.addEventlistener('change', event => {
let fontFamily;
switch (event.target.value) {
case 'assistant':
@@ -1351,7 +1351,7 @@ hasOutline: false
})
// Body text
fontFamilyBody.addEventListener('wa-change', event => {
fontFamilyBody.addEventlistener('change', event => {
let fontFamily;
switch (event.target.value) {
case 'assistant':
@@ -1404,7 +1404,7 @@ hasOutline: false
// Body font weight
resetBodyFontWeightValue()
fontWeightBody.addEventListener('wa-input', event => {
fontWeightBody.addEventListener('input', event => {
document.documentElement.style.setProperty('--wa-font-weight-body', event.target.value);
});
@@ -1580,7 +1580,7 @@ hasOutline: false
}
// Swaps icons to the preferred set for the selected theme
themeSelect.addEventListener('wa-change', event => {
themeSelect.addEventlistener('change', event => {
setPreferredIcons();
showIconStyleOptions();
syncLogoIcon();
@@ -1599,32 +1599,32 @@ hasOutline: false
});
// Changes available Icon Styles and swaps icons based on the selected Icon Family
iconFamily.addEventListener('wa-change', event => {
iconFamily.addEventlistener('change', event => {
useFaIcons();
showIconStyleOptions();
});
// Swaps icons based on the selected Icon Style
iconStyle.addEventListener('wa-change', useFaIcons);
iconStyle.addEventlistener('change', useFaIcons);
// Corners
container.querySelector('[name="corners"]').addEventListener('wa-input', event => {
container.querySelector('[name="corners"]').addEventListener('input', event => {
document.documentElement.style.setProperty('--wa-border-radius-scale', `${event.target.value}`);
});
// Border width
container.querySelector('[name="border-width"]').addEventListener('wa-input', event => {
container.querySelector('[name="border-width"]').addEventListener('input', event => {
document.documentElement.style.setProperty('--wa-border-width-scale', `${event.target.value / 16}`);
});
// Border style
borderStyle.addEventListener('wa-input', event => {
borderStyle.addEventListener('input', event => {
document.documentElement.style.setProperty('--wa-border-style', event.target.value);
});
// Spacing style
spacing.addEventListener('wa-input', event => {
spacing.addEventListener('input', event => {
document.documentElement.style.setProperty('--wa-space-scale', `${event.target.value}`);
});

View File

@@ -146,7 +146,7 @@ To create a custom validation error, pass a non-empty string to the `setCustomVa
alert('All fields are valid!');
});
input.addEventListener('wa-input', () => {
input.addEventListener('input', () => {
if (input.value === 'webawesome') {
input.setCustomValidity('');
} else {

View File

@@ -4,6 +4,7 @@ description: Callouts are used to display important messages inline.
component: callout
icon: callout
snippets: '.wa-callout'
noAlpha: true
---
```html {.example}

View File

@@ -33,3 +33,39 @@ Use the [appearance utilities](/docs/utilities/appearance/) to change the select
</select>
</label>
```
### Grouping options
In [modern browsers](https://caniuse.com/mdn-html_elements_select_hr_in_select), you can use the `<hr>` element as a divider:
```html {.example}
<select>
<small>Section 1</small>
<option value="option-1">Option 1</option>
<option value="option-2">Option 2</option>
<option value="option-3">Option 3</option>
<hr />
<small>Section 2</small>
<option value="option-4">Option 4</option>
<option value="option-5">Option 5</option>
<option value="option-6">Option 6</option>
</select>
```
To provide labels, you can use the `<optgroup>` element (with or without dividers):
```html {.example}
<select>
<optgroup label="Section 1">
<option value="option-1">Option 1</option>
<option value="option-2">Option 2</option>
<option value="option-3">Option 3</option>
</optgroup>
<optgroup label="Section 2">
<option value="option-4">Option 4</option>
<option value="option-5">Option 5</option>
<hr />
<option value="option-6">Option 6</option>
</optgroup>
</select>
```

View File

@@ -0,0 +1,5 @@
---
title: Anodized
isPro: true
tags: pro
---

View File

@@ -0,0 +1,3 @@
---
title: Bright
---

View File

@@ -0,0 +1,4 @@
---
title: Classic
description: The original Shoelace color palette.
---

View File

@@ -0,0 +1,4 @@
---
title: Default
description: This is the palette used in the default theme.
---

View File

@@ -0,0 +1,5 @@
---
title: Elegant
isPro: true
tags: pro
---

View File

@@ -0,0 +1,10 @@
---
title: Color Palettes
description: Palettes define [literal colors](/docs/tokens/colors) that are used in the design system.
layout: overview
override:tags: []
forTag: palette
categories:
other: Free
pro: Pro
---

View File

@@ -0,0 +1,5 @@
---
title: Mild
isPro: true
tags: pro
---

View File

@@ -0,0 +1,5 @@
---
title: Natural
isPro: true
tags: pro
---

View File

@@ -0,0 +1,8 @@
{
"layout": "palette.njk",
"tags": ["palettes", "palette"],
"eleventyComputed": {
"snippet": ".wa-palette-{{ page.fileSlug }}",
"icon": "palette"
}
}

View File

@@ -0,0 +1,5 @@
---
title: Rudimentary
isPro: true
tags: pro
---

View File

@@ -0,0 +1,5 @@
---
title: Vogue
isPro: true
tags: pro
---

View File

@@ -1,5 +1,6 @@
{
"layout": "block.njk",
"tags": ["patterns"],
"wide": true,
"noAlpha": true
}

View File

@@ -12,12 +12,29 @@ Components with the <wa-badge variant="warning" pill>Experimental</wa-badge> bad
During the alpha period, things might break! We take breaking changes very seriously, but sometimes they're necessary to make the final product that much better. We appreciate your patience!
:::
## Next
- 🚨 BREAKING: updated all components to use native events instead of `wa-` prefixed events. This will allow components to work more like native elements in your code, frameworks, third-party plugins, etc. To update your code, simply remove the prefix from your event listeners for the following events.
- `wa-input` => `input`
- `wa-change` => `change`
- `wa-blur` => `blur` (this event will no longer bubble, use `focusout` for a bubbling version)
- `wa-focus` => `focus` (this event will no longer bubble)
- Added `.wa-callout` utility class
- Fixed a bug in `<wa-tab-group>` that prevented nested tab groups from working properly
- Fixed slot names for `show-password-icon` and `hide-password-icon` in `<wa-input>` to more intuitively represent their functions
## 3.0.0-alpha.9
- Added `.wa-callout` utility class
- Added new themes:
- Glossy
- Matter
- Premium
- Playful
- Added docs on themes and palettes
- Separated colors and typography out from themes so they can be used independently
- Added test suite to ensure all color palettes provide the color contrast they are supposed to
- Added `.wa-invert` utility class to invert the current color scheme
- Added `:state(blank)` to `<wa-input>`, `<wa-textarea>`, and `<wa-select>` to style form inputs differently when empty.
## 3.0.0-alpha.8

View File

@@ -3,5 +3,5 @@ title: Active
description: Energetic and tactile, always in motion.
isPro: true
tags: pro
defaultPalette: rudimentary
palette: rudimentary
---

View File

@@ -2,5 +2,5 @@
title: Awesome
description: Punchy and vibrant, the rockstar of themes.
order: 0.2
defaultPalette: bright
palette: bright
---

View File

@@ -3,5 +3,5 @@ title: Brutalist
description: Sharp, square, and unapologetically bold.
isPro: true
tags: pro
defaultPalette: default
palette: default
---

View File

@@ -2,5 +2,5 @@
title: Classic
description: Timeless elegance that never goes out of style.
order: 0.1
defaultPalette: classic
palette: classic
---

View File

@@ -2,5 +2,5 @@
title: Default
description: Your trusty companion, like a perfectly broken-in pair of jeans.
order: 0
defaultPalette: default
palette: default
---

View File

@@ -10,12 +10,6 @@ override:tags: []
eleventyComputed:
forceTheme: "{{ theme.fileSlug }}"
---
<script>
document.getElementById('theme-stylesheet').href = '/dist/styles/themes/{{ theme.fileSlug }}.css';
</script>
<script type=module>
document.getElementById('theme-stylesheet').href = '/dist/styles/themes/{{ theme.fileSlug }}.css';
</script>
<link rel="stylesheet" href="/dist/styles/themes/{{ theme.fileSlug }}.css" />
<link rel="stylesheet" href="/docs/themes/showcase.css" />
@@ -24,6 +18,7 @@ eleventyComputed:
<header>
{% include 'breadcrumbs.njk' %}
<h1 class="title">{{ theme.data.title }}</h1>
<p id="mix_and_match" hidden class="wa-size-s"></p>
<p>{% include 'status.njk' %}</p>
<p id="theme-showcase-description">{{ theme.data.description | inlineMarkdown | safe }}</p>
</header>
@@ -38,3 +33,47 @@ eleventyComputed:
{{ content | safe }}
</div>
</wa-image-comparer>
<script>
function updateTheme() {
let params = new URLSearchParams(window.location.search);
let script = document.currentScript;
const stylesheetURLs = {
colors: id => `/dist/styles/themes/${ id }/color.css`,
palette: id => `/dist/styles/color/${ id }.css`,
typography: id => `/dist/styles/themes/${ id }/typography.css`
};
const icons = {
colors: 'palette',
palette: 'swatchbook',
typography: 'font-case'
}
for (let link of document.querySelectorAll('link.mix-and-match')) {
link.remove();
}
let msgs = [];
for (let name in stylesheetURLs) {
if (params.get(name)) {
let url = stylesheetURLs[name](params.get(name));
script.insertAdjacentHTML('afterend', `<link rel="stylesheet" href="${ url }" class="mix-and-match" />`);
let docsURL = (name === 'palette' ? '/docs/palettes/' : '/docs/themes/') + params.get(name) + '/';
let title = params.get(name).replace(/^[a-z]/g, c => c.toUpperCase());
let icon = icons[name];
msgs.push(`<wa-icon name="${icon}" variant="regular"></wa-icon> <a href="${ docsURL }">${ title }</a>`);
}
}
for (let p of mix_and_match) {
p.hidden = msgs.length === 0;
if (msgs.length) {
let icon =
p.innerHTML = `<strong><wa-icon name="merge"></wa-icon> Remixed</strong> ` + msgs.map(msg => `<wa-badge appearance=outlined>
${ msg }</wa-badge>`).join(' ');
}
}
}
updateTheme();
</script>

View File

@@ -1,8 +0,0 @@
---
title: Glassy
description: Smooth, sleek, and reflective.
isPro: true
tags: pro
noAlpha: true
defaultPalette: elegant
---

7
docs/docs/themes/glossy.md vendored Normal file
View File

@@ -0,0 +1,7 @@
---
title: Glossy
description: Bustling with plenty of luster and shine.
isPro: true
tags: pro
palette: elegant
---

85
docs/docs/themes/matter.md vendored Normal file
View File

@@ -0,0 +1,85 @@
---
title: Matter
description: Digital design inspired by the real world.
isPro: true
tags: pro
palette: mild
---
Set the page theme to "{{ title }}" from the top right to preview the following examples.
## Floating Labels
This theme implements "floating labels" for `wa-input`, `wa-textarea`, `wa-select`,
which makes labels look like placeholders when the input is empty, does not have an actual placeholder, and is not focused.
```html {.example}
<wa-input label="What is your name?"></wa-input>
<br>
<wa-input label="What is your name?" appearance="filled"></wa-input>
```
## Ripple Effect
This theme implements a ripple effect for buttons, including native buttons.
Click on the following buttons to observe it:
```html {.example}
<wa-button variant="brand">Button</wa-button>
<button class="wa-brand">Button</button>
```
### Customization
You can use several properties to customize the ripple effect.
These properties can be set on any ancestor, including the root element:
| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `--wa-ripple-start-radius` | `<length>` | `0.1em` | The starting radius of the ripple effect. |
| `--wa-ripple-start-opacity` | `<number>` | `0.1` | The starting opacity of the ripple effect. |
| `--wa-ripple-duration` | `<time>` | `calc(2 * var(--wa-transition-slow))` | The duration of the ripple effect transition. |
Any of these can be used to disable the ripple effect:
```css
--wa-ripple-start-radius: 0em;
```
```css
--wa-ripple-start-opacity: 0;
```
```css
--wa-ripple-duration: 0s;
```
These properties would only work on the button itself:
| Property | Type | Default | Description |
| --- | --- | --- | --- |
| `--wa-ripple-center-x` | `<percentage>` | `50%` | The x-coordinate of the ripple center point as a percentage of the button width. |
| `--wa-ripple-center-y` | `<percentage>` | `50%` | The y-coordinate of the ripple center point as a percentage of the button height. |
### Ripple Center Point
By default the ripple effect starts from the center of the button.
If you want it to start from the position the button was clicked, you can use this JS snippet:
```js
document.addEventListener("mousedown", evt => {
let target = evt.target;
if (!target.matches?.('wa-button, button, .wa-button')) {
return;
}
let rect = target.getBoundingClientRect();
let x = (evt.clientX - rect.left) / rect.width;
let y = (evt.clientY - rect.top) / rect.height;
target.style.setProperty("--mouse-local-x", x);
target.style.setProperty("--mouse-local-y", y);
});
```

View File

@@ -3,5 +3,5 @@ title: Mellow
description: Soft and soothing, like a lazy Sunday morning.
isPro: true
tags: pro
defaultPalette: natural
palette: natural
---

View File

@@ -1,8 +1,7 @@
---
title: Playful
description: Fun, colorful, and full of personality.
description: Cheerful and engaging, like a playground on screen.
isPro: true
tags: pro
noAlpha: true
defaultPalette: rudimentary
palette: rudimentary
---

View File

@@ -3,5 +3,5 @@ title: Premium
description: The ultimate in sophistication and style.
isPro: true
tags: pro
defaultPalette: anodized
palette: anodized
---

View File

@@ -9,7 +9,17 @@ body,
overflow: hidden;
}
#mix_and_match {
font-weight: var(--wa-font-weight-semibold);
color: var(--wa-color-text-quiet);
wa-icon {
vertical-align: middle;
}
}
.theme-showcase {
isolation: isolate;
background-color: var(--wa-color-surface-lowered);
border-radius: var(--wa-border-radius-l);
padding: var(--wa-space-xl);

View File

@@ -3,5 +3,5 @@ title: Tailspin
description: Like a bird in flight, guiding you from there to here.
isPro: true
tags: pro
defaultPalette: vogue
palette: vogue
---

View File

@@ -1,4 +1,5 @@
{
"layout": "theme.njk",
"wide": true,
"tags": ["themes", "theme"]
}

View File

@@ -49,29 +49,31 @@ Web Awesome's color system is made up of CSS custom properties to help with cons
Color is organized by three main categories:
- [Literal colors](/#literal-colors) that give familiar names to your starting color palette
- [Literal colors](/#literal-colors) that give familiar names to your starting [color palette](/docs/palettes/)
- [Foundational colors](/#foundational-colors) that lay the groundwork for your theme
- [Semantic colors](/#semantic-colors) that draw attention and convey meaning
## Literal Colors
Literal colors are the lowest level color properties in your theme. Each color is identified by a name, like red or gray, and a number that roughly corresponds to the color's perceived lightness. On this scale, 100 is equal to pure white and 0 is equal to pure black.
Literal colors are defined by your theme's [color palette](/docs/palettes/) and are the lowest level color tokens in your theme. Each token is identified by a name, like red or gray, and a number based on the color's lightness. On this scale, 100 is equal to pure white and 0 is equal to pure black.
Lightness values on this scale have a strong correlation to [relative luminance](https://www.w3.org/WAI/GL/wiki/Relative_luminance), which is used to calculate color contrast. To meet [WCAG 2.1 success criteria for minimum or enhanced contrast](https://www.w3.org/TR/WCAG21/#contrast-minimum), even across hues, calculate the difference between the lightness values of any two colors:
You can use these numbers to ensure accessible color contrast per [WCAG 2.1 success criteria](https://www.w3.org/TR/WCAG21/#contrast-minimum):
- A difference of 40 ensures a minimum 3:1 contrast ratio, suitable for large text and icons (AA)
- A difference of 50 ensures a minimum 4.5:1 contrast ratio, suitable for normal text (AA) and large text (AAA)
- A difference of 60 ensures a minimum 7:1 contrast ratio, suitable for all text (AAA)
Web Awesome defines seven literal colors each with 11 lightness values using the format `--wa-color-{hue}-{tint}`.
Each Web Awesome palette defines seven literal colors each with 11 lightness values using the format `--wa-color-{hue}-{tint}`.
{% for hue in ["red", "yellow", "green", "teal", "blue", "indigo", "violet", "gray"] -%}
<div class="color-name">{{ hue | capitalize }}</div>
<ul class="color-group">
{% for tint in ["95", "90", "80", "70", "60", "50", "40", "30", "20", "10", "05"] -%}
<li class="color-preview">
<div class="swatch" style="background-color: var(--wa-color-{{ hue }}-{{ tint }})"></div>
<div class="color swatch" style="background-color: var(--wa-color-{{ hue }}-{{ tint }})">
<wa-copy-button value="--wa-color-{{ hue }}-{{ tint }}" copy-label="--wa-color-{{ hue }}-{{ tint }}"></wa-copy-button>
</div>
<small>{{ tint }}</small>
</li>
{%- endfor %}
@@ -187,23 +189,3 @@ Finally, each color is named according to how much attention it draws. Here, we
{%- endfor %}
{%- endfor %}
</table>
<script type="module">
const computedStyle = getComputedStyle(document.body)
document.querySelectorAll(".swatch").forEach((swatch) => {
let varName = swatch.getAttribute("value")
if (!varName) {
const bgColor = swatch.style.backgroundColor
varName = bgColor.replace(/^var\((--.*)\)$/, "$1")
}
const copyButton = Object.assign(document.createElement("wa-copy-button"), {
value: varName,
copyLabel: varName,
errorLabel: "Whoops, your browser doesn't support this!",
})
swatch.appendChild(copyButton)
})
</script>

View File

@@ -24,22 +24,9 @@ Some properties are boolean, so they only have true/false values. To activate a
## Events
You can listen for standard events such as `click`, `mouseover`, etc. as you normally would. However, it's important to note that many events emitted within a component's shadow root will be [retargeted](https://dom.spec.whatwg.org/#retarget) to the host element. This may result in, for example, multiple `click` handlers executing even if the user clicks just once. Furthermore, `event.target` will point to the host element, making things even more confusing.
You can listen for standard events such as `click`, `mouseover`, etc. as you normally would. In addition, some components have their own custom events. For example, you might listen to `wa-after-show` to determine when a dialog has been shown.
As a result, you should almost always listen for Web Awesome events instead. For example, instead of listening to `click` to determine when an `<wa-checkbox>` gets toggled, listen to `wa-change`.
```html
<wa-checkbox>Check me</wa-checkbox>
<script>
const checkbox = document.querySelector('wa-checkbox');
checkbox.addEventListener('wa-change', event => {
console.log(event.target.checked ? 'checked' : 'not checked');
});
</script>
```
All Web Awesome events are prefixed with `wa-` to prevent collisions with standard events and other libraries. Refer to a component's documentation for a complete list of its events.
Custom Web Awesome events are prefixed with `wa-` to prevent collisions with standard events and other libraries. Refer to a component's documentation for a complete list of its events.
## Methods

16
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@shoelace-style/webawesome",
"version": "3.0.0-alpha.8",
"version": "3.0.0-alpha.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@shoelace-style/webawesome",
"version": "3.0.0-alpha.8",
"version": "3.0.0-alpha.9",
"license": "MIT",
"dependencies": {
"@ctrl/tinycolor": "^4.1.0",
@@ -34,6 +34,7 @@
"chalk": "^5.3.0",
"change-case": "^4.1.2",
"chokidar": "^3.5.3",
"colorjs.io": "^0.6.0-alpha.1",
"command-line-args": "^5.2.1",
"comment-parser": "^1.4.1",
"cspell": "^6.18.1",
@@ -5078,6 +5079,17 @@
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"dev": true
},
"node_modules/colorjs.io": {
"version": "0.6.0-alpha.1",
"resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.6.0-alpha.1.tgz",
"integrity": "sha512-c/h/8uAmPydQcriRdX8UTAFHj6SpSHFHBA8LvMikvYWAVApPTwg/pyOXNsGmaCBd6L/EeDlRHSNhTtnIFp/qsg==",
"dev": true,
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/color"
}
},
"node_modules/command-line-args": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz",

View File

@@ -1,7 +1,7 @@
{
"name": "@shoelace-style/webawesome",
"description": "A forward-thinking library of web components.",
"version": "3.0.0-alpha.8",
"version": "3.0.0-alpha.9",
"homepage": "https://webawesome.com/",
"author": "Web Awesome",
"license": "MIT",
@@ -54,6 +54,7 @@
"create": "plop --plopfile scripts/plop/plopfile.js",
"test": "web-test-runner --group default",
"test:component": "web-test-runner -- --watch --group",
"test:contrast": "cd src/styles/color && node contrast.test.js",
"test:watch": "web-test-runner --watch --group default",
"prettier": "prettier --check --log-level=warn .",
"prettier:fix": "prettier --write --log-level=warn .",
@@ -91,6 +92,7 @@
"chalk": "^5.3.0",
"change-case": "^4.1.2",
"chokidar": "^3.5.3",
"colorjs.io": "^0.6.0-alpha.1",
"command-line-args": "^5.2.1",
"comment-parser": "^1.4.1",
"cspell": "^6.18.1",

View File

@@ -126,7 +126,11 @@ async function generateStyles() {
file.includes('themes/default') ||
file.includes('themes/awesome') ||
file.includes('themes/active') ||
file.includes('themes/glossy') ||
file.includes('themes/matter') ||
file.includes('themes/mellow') ||
file.includes('themes/playful') ||
file.includes('themes/premium') ||
file.includes('themes/tailspin') ||
file.includes('themes/brutalist')
) {

View File

@@ -24,14 +24,16 @@ for await (const component of components) {
const componentDir = path.join(reactDir, tagWithoutPrefix);
const componentFile = path.join(componentDir, 'index.ts');
const importPath = component.path.replace(/\.js$/, '.js');
const eventImports = (component.events || [])
// We only want to wrap wa- prefixed events, because the others are native
const eventsToWrap = component.events?.filter(event => event.name.startsWith('wa-')) || [];
const eventImports = eventsToWrap
.map(event => `import type { ${event.eventName} } from '../../events/events.js';`)
.join('\n');
const eventExports = (component.events || [])
const eventExports = eventsToWrap
.map(event => `export type { ${event.eventName} } from '../../events/events.js';`)
.join('\n');
const eventNameImport = (component.events || []).length > 0 ? `import { type EventName } from '@lit/react';` : ``;
const events = (component.events || [])
const eventNameImport = eventsToWrap.length > 0 ? `import { type EventName } from '@lit/react';` : ``;
const events = eventsToWrap
.map(event => `${event.reactName}: '${event.name}' as EventName<${event.eventName}>`)
.join(',\n');

View File

@@ -318,13 +318,13 @@ describe('<wa-button>', () => {
});
describe('when using methods', () => {
it('should emit wa-focus and wa-blur when the button is focused and blurred', async () => {
it('should emit focus and blur when the button is focused and blurred', async () => {
const el = await fixture<WaButton>(html` <wa-button>Button</wa-button> `);
const focusHandler = sinon.spy();
const blurHandler = sinon.spy();
el.addEventListener('wa-focus', focusHandler);
el.addEventListener('wa-blur', blurHandler);
el.addEventListener('focus', focusHandler);
el.addEventListener('blur', blurHandler);
el.focus();
await waitUntil(() => focusHandler.calledOnce);

View File

@@ -2,12 +2,10 @@ import { customElement, property, query, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { html, literal } from 'lit/static-html.js';
import { WaBlurEvent } from '../../events/blur.js';
import { WaFocusEvent } from '../../events/focus.js';
import { WaInvalidEvent } from '../../events/invalid.js';
import { MirrorValidator } from '../../internal/validators/mirror-validator.js';
import { watch } from '../../internal/watch.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js';
import nativeStyles from '../../styles/native/button.css';
import appearanceStyles from '../../styles/utilities/appearance.css';
import sizeStyles from '../../styles/utilities/size.css';
@@ -26,8 +24,8 @@ import styles from './button.css';
* @dependency wa-icon
* @dependency wa-spinner
*
* @event wa-blur - Emitted when the button loses focus.
* @event wa-focus - Emitted when the button gains focus.
* @event blur - Emitted when the button loses focus.
* @event focus - Emitted when the button gains focus.
* @event wa-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.
*
* @slot - The button's label.
@@ -72,7 +70,8 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
@property({ reflect: true }) variant: 'neutral' | 'brand' | 'success' | 'warning' | 'danger' = 'neutral';
/** The button's visual appearance. */
@property({ reflect: true }) appearance: 'accent' | 'filled' | 'outlined' | 'plain' = 'accent';
@property({ reflect: true, default: 'accent' })
appearance: 'accent' | 'filled' | 'outlined' | 'plain' = 'accent';
/** The button's size. */
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
@@ -141,14 +140,6 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
/** Used to override the form owner's `target` attribute. */
@property({ attribute: 'formtarget' }) formTarget: '_self' | '_blank' | '_parent' | '_top' | string;
private handleBlur() {
this.dispatchEvent(new WaBlurEvent());
}
private handleFocus() {
this.dispatchEvent(new WaFocusEvent());
}
private handleClick() {
const form = this.getForm();
@@ -273,8 +264,6 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
role=${ifDefined(isLink ? undefined : 'button')}
aria-disabled=${this.disabled ? 'true' : 'false'}
tabindex=${this.disabled ? '-1' : '0'}
@blur=${this.handleBlur}
@focus=${this.handleFocus}
@invalid=${this.isButton() ? this.handleInvalid : null}
@click=${this.handleClick}
>

View File

@@ -1,29 +1,35 @@
:host {
--spacing: var(--wa-space-xl);
--border-width: var(--wa-panel-border-width);
--border-radius: var(--wa-panel-border-radius);
--inner-border-radius: calc(var(--border-radius) - var(--border-width));
display: flex;
flex-direction: column;
background-color: var(--wa-color-surface-default);
border-color: var(--wa-color-surface-border);
border-radius: var(--wa-panel-border-radius);
border-radius: var(--border-radius);
border-style: var(--wa-panel-border-style);
border-width: var(--wa-panel-border-width);
box-shadow: var(--wa-shadow-s);
border-width: var(--border-width);
color: var(--wa-color-text-normal);
}
.image,
:host(:not([with-image])) .header {
border-start-start-radius: var(--inner-border-radius);
border-start-end-radius: var(--inner-border-radius);
}
.image {
display: flex;
border-top-left-radius: inherit;
border-top-right-radius: inherit;
margin: calc(-1 * var(--border-width));
overflow: hidden;
&::slotted(img) {
display: block;
width: 100%;
border-bottom-left-radius: 0 !important;
border-bottom-right-radius: 0 !important;
border-start-start-radius: inherit !important;
border-start-end-radius: inherit !important;
}
}
@@ -33,11 +39,6 @@
padding: calc(var(--spacing) / 2) var(--spacing);
}
:host(:not([with-image])) .header {
border-top-left-radius: inherit;
border-top-right-radius: inherit;
}
.body {
display: block;
padding: var(--spacing);
@@ -46,8 +47,8 @@
.footer {
display: block;
border-top: inherit;
border-bottom-left-radius: inherit;
border-bottom-right-radius: inherit;
border-end-start-radius: var(--inner-border-radius);
border-end-end-radius: var(--inner-border-radius);
padding: var(--spacing);
}

View File

@@ -19,6 +19,8 @@ import styles from './card.css';
* @csspart body - The container that wraps the card's main content.
* @csspart footer - The container that wraps the card's footer.
*
* @cssproperty --border-radius - The radius for the card's corners. Expects a single value. Defaults to `var(--wa-panel-border-radius)`.
* @cssproperty --border-width - The width of the card's borders. Expects a single value. Defaults to `var(--wa-panel-border-width)`.
* @cssproperty --spacing - The amount of space around and between sections of the card. Expects a single value.
*/
@customElement('wa-card')

View File

@@ -60,13 +60,13 @@ describe('<wa-checkbox>', () => {
expect(el.checkValidity()).to.be.true;
});
it('should emit wa-change and wa-input when clicked', async () => {
it('should emit change and input when clicked', async () => {
const el = await fixture<WaCheckbox>(html` <wa-checkbox></wa-checkbox> `);
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
el.click();
await aTimeout(0);
await el.updateComplete;
@@ -76,13 +76,13 @@ describe('<wa-checkbox>', () => {
expect(el.checked).to.be.true;
});
it('should emit wa-change and wa-input when toggled with spacebar', async () => {
it('should emit change and input when toggled with spacebar', async () => {
const el = await fixture<WaCheckbox>(html` <wa-checkbox></wa-checkbox> `);
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
el.focus();
await el.updateComplete;
await sendKeys({ press: ' ' });
@@ -92,11 +92,11 @@ describe('<wa-checkbox>', () => {
expect(el.checked).to.be.true;
});
it('should not emit wa-change or wa-input when checked programmatically', async () => {
it('should not emit change or input when checked programmatically', async () => {
const el = await fixture<WaCheckbox>(html` <wa-checkbox></wa-checkbox> `);
el.addEventListener('wa-change', () => expect.fail('wa-change should not be emitted'));
el.addEventListener('wa-input', () => expect.fail('wa-input should not be emitted'));
el.addEventListener('change', () => expect.fail('change should not be emitted'));
el.addEventListener('input', () => expect.fail('input should not be emitted'));
el.checked = true;
await el.updateComplete;
await aTimeout(0);

View File

@@ -4,14 +4,10 @@ import { customElement, property, query } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { live } from 'lit/directives/live.js';
import { WaBlurEvent } from '../../events/blur.js';
import { WaChangeEvent } from '../../events/change.js';
import { WaFocusEvent } from '../../events/focus.js';
import { WaInputEvent } from '../../events/input.js';
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-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js';
import nativeStyles from '../../styles/native/checkbox.css';
import formControlStyles from '../../styles/shadow/form-control.css';
import sizeStyles from '../../styles/utilities/size.css';
@@ -29,10 +25,10 @@ import styles from './checkbox.css';
* @slot - The checkbox's label.
* @slot hint - Text that describes how to use the checkbox. Alternatively, you can use the `hint` attribute.
*
* @event wa-blur - Emitted when the checkbox loses focus.
* @event wa-change - Emitted when the checked state changes.
* @event wa-focus - Emitted when the checkbox gains focus.
* @event wa-input - Emitted when the checkbox receives input.
* @event blur - Emitted when the checkbox loses focus.
* @event change - Emitted when the checked state changes.
* @event focus - Emitted when the checkbox gains focus.
* @event input - Emitted when the checkbox receives input.
* @event wa-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.
*
* @csspart base - The component's label .
@@ -137,19 +133,7 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
this.hasInteracted = true;
this.checked = !this.checked;
this.indeterminate = false;
this.dispatchEvent(new WaChangeEvent());
}
private handleBlur() {
this.dispatchEvent(new WaBlurEvent());
}
private handleInput() {
this.dispatchEvent(new WaInputEvent());
}
private handleFocus() {
this.dispatchEvent(new WaFocusEvent());
this.dispatchEvent(new Event('change'));
}
@watch('defaultChecked')
@@ -233,47 +217,37 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
// https://bugs.chromium.org/p/chromium/issues/detail?id=1413733
//
return html`
<div
class=${classMap({
'form-control--has-hint': hasHint,
'form-control': true,
})}
<label part="base">
<span part="control">
<input
class="input"
type="checkbox"
title=${this.title /* An empty title prevents browser validation tooltips from appearing on hover */}
name=${this.name}
value=${ifDefined(this._value)}
.indeterminate=${live(this.indeterminate)}
.checked=${live(this.checked)}
.disabled=${this.disabled}
.required=${this.required}
aria-checked=${this.checked ? 'true' : 'false'}
aria-describedby="hint"
@click=${this.handleClick}
/>
<wa-icon part="${iconState}-icon icon" library="system" name=${iconName}></wa-icon>
</span>
<slot part="label"></slot>
</label>
<slot
name="hint"
aria-hidden=${hasHint ? 'false' : 'true'}
class="${classMap({ 'has-slotted': hasHint })}"
id="hint"
part="hint"
>${this.hint}</slot
>
<label part="base">
<span class="control">
<input
class="input"
type="checkbox"
title=${this.title /* An empty title prevents browser validation tooltips from appearing on hover */}
name=${this.name}
value=${ifDefined(this._value)}
.indeterminate=${live(this.indeterminate)}
.checked=${live(this.checked)}
.disabled=${this.disabled}
.required=${this.required}
aria-checked=${this.checked ? 'true' : 'false'}
aria-describedby="hint"
@click=${this.handleClick}
@input=${this.handleInput}
@blur=${this.handleBlur}
@focus=${this.handleFocus}
/>
<wa-icon part="${iconState}-icon icon" library="system" name=${iconName}></wa-icon>
</span>
<slot part="label"></slot>
</label>
<slot
name="hint"
aria-hidden=${hasHint ? 'false' : 'true'}
class="${classMap({ 'has-slotted': hasHint })}"
id="hint"
part="hint"
>${this.hint}</slot
>
</div>
`;
}
}

View File

@@ -14,31 +14,31 @@ describe('<wa-color-picker>', () => {
for (const fixture of fixtures) {
describe(`with "${fixture.type}" rendering`, () => {
describe('when the value changes', () => {
it('should not emit wa-change or wa-input when the value is changed programmatically', async () => {
it('should not emit change or input when the value is changed programmatically', async () => {
const el = await fixture<WaColorPicker>(html` <wa-color-picker></wa-color-picker> `);
const color = 'rgb(255, 204, 0)';
el.addEventListener('wa-change', () => expect.fail('wa-change should not be emitted'));
el.addEventListener('wa-input', () => expect.fail('wa-change should not be emitted'));
el.addEventListener('change', () => expect.fail('change should not be emitted'));
el.addEventListener('input', () => expect.fail('change should not be emitted'));
el.value = color;
await el.updateComplete;
});
it('should emit wa-change and wa-input when the color grid selector is moved', async () => {
it('should emit change and input when the color grid selector is moved', async () => {
const el = await fixture<WaColorPicker>(html` <wa-color-picker></wa-color-picker> `);
const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~="trigger"]')!;
const grid = el.shadowRoot!.querySelector<HTMLElement>('[part~="grid"]')!;
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
await clickOnElement(trigger); // open the dropdown
await aTimeout(200); // wait for the dropdown to open
await el.updateComplete;
// Simulate a drag event. "wa-change" should not fire until we stop dragging.
// Simulate a drag event. "change" should not fire until we stop dragging.
await dragElement(grid, 2, 0, {
afterMouseDown: () => {
expect(changeHandler).to.have.not.been.called;
@@ -53,20 +53,20 @@ describe('<wa-color-picker>', () => {
expect(inputHandler).to.have.been.calledTwice;
});
it('should emit wa-change and wa-input when the hue slider is moved', async () => {
it('should emit change and input when the hue slider is moved', async () => {
const el = await fixture<WaColorPicker>(html` <wa-color-picker></wa-color-picker> `);
const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~="trigger"]')!;
const slider = el.shadowRoot!.querySelector<HTMLElement>('[part~="hue-slider"]')!;
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
await clickOnElement(trigger); // open the dropdown
await aTimeout(200); // wait for the dropdown to open
// Simulate a drag event. "wa-change" should not fire until we stop dragging.
// Simulate a drag event. "change" should not fire until we stop dragging.
await dragElement(slider, 20, 0, {
afterMouseDown: () => {
expect(changeHandler).to.have.not.been.called;
@@ -85,20 +85,20 @@ describe('<wa-color-picker>', () => {
expect(inputHandler).to.have.been.calledOnce;
});
it('should emit wa-change and wa-input when the opacity slider is moved', async () => {
it('should emit change and input when the opacity slider is moved', async () => {
const el = await fixture<WaColorPicker>(html` <wa-color-picker opacity></wa-color-picker> `);
const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~="trigger"]')!;
const slider = el.shadowRoot!.querySelector<HTMLElement>('[part~="opacity-slider"]')!;
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
await clickOnElement(trigger); // open the dropdown
await aTimeout(200); // wait for the dropdown to open
// Simulate a drag event. "wa-change" should not fire until we stop dragging.
// Simulate a drag event. "change" should not fire until we stop dragging.
await dragElement(slider, 2, 0, {
afterMouseDown: () => {
expect(changeHandler).to.have.not.been.called;
@@ -115,15 +115,15 @@ describe('<wa-color-picker>', () => {
expect(inputHandler).to.have.been.calledTwice;
});
it('should emit wa-change and wa-input when toggling the format', async () => {
it('should emit change and input when toggling the format', async () => {
const el = await fixture<WaColorPicker>(html` <wa-color-picker value="#fff"></wa-color-picker> `);
const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~="trigger"]')!;
const formatButton = el.shadowRoot!.querySelector<HTMLElement>('[part~="format-button"]')!;
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
await clickOnElement(trigger); // open the dropdown
await aTimeout(200); // wait for the dropdown to open
@@ -160,7 +160,7 @@ describe('<wa-color-picker>', () => {
expect(getComputedStyle(swatches[2]).backgroundColor).to.equal('rgb(0, 0, 255)');
});
it('should emit wa-change and wa-input when clicking on a swatch', async () => {
it('should emit change and input when clicking on a swatch', async () => {
const el = await fixture<WaColorPicker>(html`
<wa-color-picker swatches="red; green; blue;"></wa-color-picker>
`);
@@ -169,8 +169,8 @@ describe('<wa-color-picker>', () => {
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
await clickOnElement(trigger); // open the dropdown
await aTimeout(200); // wait for the dropdown to open
@@ -181,15 +181,15 @@ describe('<wa-color-picker>', () => {
expect(inputHandler).to.have.been.calledOnce;
});
it('should emit wa-change and wa-input when selecting a color with the keyboard', async () => {
it('should emit change and input when selecting a color with the keyboard', async () => {
const el = await fixture<WaColorPicker>(html` <wa-color-picker></wa-color-picker> `);
const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~="trigger"]')!;
const gridHandle = el.shadowRoot!.querySelector<HTMLElement>('[part~="grid-handle"]')!;
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
await clickOnElement(trigger); // open the dropdown
await aTimeout(200); // wait for the dropdown to open
@@ -201,15 +201,15 @@ describe('<wa-color-picker>', () => {
expect(inputHandler).to.have.been.calledOnce;
});
it('should emit wa-change and wa-input when selecting a color with the keyboard', async () => {
it('should emit change and input when selecting a color with the keyboard', async () => {
const el = await fixture<WaColorPicker>(html` <wa-color-picker></wa-color-picker> `);
const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~="trigger"]')!;
const handle = el.shadowRoot!.querySelector<HTMLElement>('[part~="grid-handle"]')!;
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
await clickOnElement(trigger); // open the dropdown
await aTimeout(200); // wait for the dropdown to open
@@ -221,15 +221,15 @@ describe('<wa-color-picker>', () => {
expect(inputHandler).to.have.been.calledOnce;
});
it('should emit wa-change and wa-input when selecting hue with the keyboard', async () => {
it('should emit change and input when selecting hue with the keyboard', async () => {
const el = await fixture<WaColorPicker>(html` <wa-color-picker></wa-color-picker> `);
const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~="trigger"]')!;
const handle = el.shadowRoot!.querySelector<HTMLElement>('[part~="hue-slider"] > span')!;
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
await clickOnElement(trigger); // open the dropdown
await aTimeout(200); // wait for the dropdown to open
@@ -241,15 +241,15 @@ describe('<wa-color-picker>', () => {
expect(inputHandler).to.have.been.calledOnce;
});
it('should emit wa-change and wa-input when selecting opacity with the keyboard', async () => {
it('should emit change and input when selecting opacity with the keyboard', async () => {
const el = await fixture<WaColorPicker>(html` <wa-color-picker opacity></wa-color-picker> `);
const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~="trigger"]')!;
const handle = el.shadowRoot!.querySelector<HTMLElement>('[part~="opacity-slider"] > span')!;
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
await clickOnElement(trigger); // open the dropdown
await aTimeout(200); // wait for the dropdown to open
@@ -261,15 +261,15 @@ describe('<wa-color-picker>', () => {
expect(inputHandler).to.have.been.calledOnce;
});
it('should emit wa-change and wa-input when entering a value in the color input and pressing enter', async () => {
it('should emit change and input when entering a value in the color input and pressing enter', async () => {
const el = await fixture<WaColorPicker>(html` <wa-color-picker opacity></wa-color-picker> `);
const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~="trigger"]')!;
const input = el.shadowRoot!.querySelector<HTMLElement>('[part~="input"]')!;
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
await clickOnElement(trigger); // open the dropdown
await aTimeout(200); // wait for the dropdown to open
@@ -283,15 +283,15 @@ describe('<wa-color-picker>', () => {
expect(inputHandler).to.have.been.calledOnce;
});
it('should emit wa-change and wa-input when entering a value in the color input and blurring the field', async () => {
it('should emit change and input when entering a value in the color input and blurring the field', async () => {
const el = await fixture<WaColorPicker>(html` <wa-color-picker opacity></wa-color-picker> `);
const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~="trigger"]')!;
const input = el.shadowRoot!.querySelector<HTMLElement>('[part~="input"]')!;
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
await clickOnElement(trigger); // open the dropdown
await aTimeout(200); // wait for the dropdown to open
@@ -311,8 +311,8 @@ describe('<wa-color-picker>', () => {
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
el.swatches = ['#fff'];
await el.updateComplete;
@@ -354,14 +354,14 @@ describe('<wa-color-picker>', () => {
it.skip('should display a color with opacity when an initial value with opacity is provided', async () => {
const el = await fixture<WaColorPicker>(html` <wa-color-picker opacity value="#ff000050"></wa-color-picker> `);
const trigger = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~="trigger"]')!;
const previewButton = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~="preview"]');
const previewButton = el.shadowRoot!.querySelector<HTMLButtonElement>('[part~="preview"]')!;
const previewColor = getComputedStyle(previewButton).getPropertyValue('--preview-color');
expect(trigger.style.color).to.equal('rgba(255, 0, 0, 0.314)');
expect(previewColor).to.equal('#ff000050');
});
it.skip('should emit wa-focus when rendered as a dropdown and focused', async () => {
it.skip('should emit focus when rendered as a dropdown and focused', async () => {
const el = await fixture<WaColorPicker>(html`
<div>
<wa-color-picker></wa-color-picker>
@@ -374,8 +374,8 @@ describe('<wa-color-picker>', () => {
const focusHandler = sinon.spy();
const blurHandler = sinon.spy();
colorPicker.addEventListener('wa-focus', focusHandler);
colorPicker.addEventListener('wa-blur', blurHandler);
colorPicker.addEventListener('focus', focusHandler);
colorPicker.addEventListener('blur', blurHandler);
await clickOnElement(trigger);
await colorPicker.updateComplete;
@@ -391,8 +391,8 @@ describe('<wa-color-picker>', () => {
const focusHandler = sinon.spy();
const blurHandler = sinon.spy();
colorPicker.addEventListener('wa-focus', focusHandler);
colorPicker.addEventListener('wa-blur', blurHandler);
colorPicker.addEventListener('focus', focusHandler);
colorPicker.addEventListener('blur', blurHandler);
// Focus
colorPicker.focus();

View File

@@ -5,17 +5,13 @@ import { customElement, eventOptions, property, query, state } from 'lit/decorat
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { styleMap } from 'lit/directives/style-map.js';
import { WaBlurEvent } from '../../events/blur.js';
import { WaChangeEvent } from '../../events/change.js';
import { WaFocusEvent } from '../../events/focus.js';
import { WaInputEvent } from '../../events/input.js';
import { WaInvalidEvent } from '../../events/invalid.js';
import { drag } from '../../internal/drag.js';
import { clamp } from '../../internal/math.js';
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-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js';
import formControlStyles from '../../styles/shadow/form-control.css';
import sizeStyles from '../../styles/utilities/size.css';
import visuallyHidden from '../../styles/utilities/visually-hidden.css';
@@ -54,10 +50,10 @@ declare const EyeDropper: EyeDropperConstructor;
* @slot label - The color picker's form label. Alternatively, you can use the `label` attribute.
* @slot hint - The color picker's form hint. Alternatively, you can use the `hint` attribute.
*
* @event wa-blur - Emitted when the color picker loses focus.
* @event wa-change - Emitted when the color picker's value changes.
* @event wa-focus - Emitted when the color picker receives focus.
* @event wa-input - Emitted when the color picker receives input.
* @event blur - Emitted when the color picker loses focus.
* @event change - Emitted when the color picker's value changes.
* @event focus - Emitted when the color picker receives focus.
* @event input - Emitted when the color picker receives input.
* @event wa-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.
*
* @csspart base - The component's base wrapper.
@@ -266,12 +262,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
private handleFocusIn = () => {
this.hasFocus = true;
this.dispatchEvent(new WaFocusEvent());
};
private handleFocusOut = () => {
this.hasFocus = false;
this.dispatchEvent(new WaBlurEvent());
};
private handleFormatToggle() {
@@ -279,8 +273,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
const nextIndex = (formats.indexOf(this.format) + 1) % formats.length;
this.format = formats[nextIndex] as 'hex' | 'rgb' | 'hsl' | 'hsv';
this.setColor(this.value || '');
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new Event('change'));
this.dispatchEvent(new InputEvent('input'));
}
private handleAlphaDrag(event: PointerEvent) {
@@ -300,13 +294,13 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
if (this.value !== currentValue) {
currentValue = this.value;
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new InputEvent('input'));
}
},
onStop: () => {
if (this.value !== initialValue) {
initialValue = this.value;
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new Event('change'));
}
},
initialEvent: event,
@@ -330,13 +324,13 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
if (this.value !== currentValue) {
currentValue = this.value;
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new InputEvent('input'));
}
},
onStop: () => {
if (this.value !== initialValue) {
initialValue = this.value;
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new Event('change'));
}
},
initialEvent: event,
@@ -363,14 +357,14 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
if (this.value !== currentValue) {
currentValue = this.value;
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new InputEvent('input'));
}
},
onStop: () => {
this.isDraggingGridHandle = false;
if (this.value !== initialValue) {
initialValue = this.value;
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new Event('change'));
}
},
initialEvent: event,
@@ -406,8 +400,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
}
if (this.value !== oldValue) {
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change'));
}
}
@@ -440,8 +434,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
}
if (this.value !== oldValue) {
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change'));
}
}
@@ -474,12 +468,12 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
}
if (this.value !== oldValue) {
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change'));
}
}
private handleInputChange(event: WaChangeEvent) {
private handleInputChange(event: Event) {
const target = event.target as HTMLInputElement;
const oldValue = this.value;
@@ -494,15 +488,15 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
}
if (this.value !== oldValue) {
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change'));
}
}
private handleInputInput(event: WaInputEvent) {
private handleInputInput(event: InputEvent) {
this.updateValidity();
// Prevent the `<wa-input>` element's `wa-input` event from bubbling up
// Prevent the `<wa-input>` element's `input` event from bubbling up
event.stopPropagation();
}
@@ -515,8 +509,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
this.input.value = this.value;
if (this.value !== oldValue) {
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change'));
}
setTimeout(() => this.input.select());
@@ -691,8 +685,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
this.setColor(colorSelectionResult.sRGBHex);
if (this.value !== oldValue) {
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change'));
}
})
.catch(() => {
@@ -707,8 +701,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
this.setColor(color);
if (this.value !== oldValue) {
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change'));
}
}
}
@@ -1008,10 +1002,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
?disabled=${this.disabled}
aria-label=${this.localize.term('currentValue')}
@keydown=${this.handleInputKeyDown}
@wa-change=${this.handleInputChange}
@wa-input=${this.handleInputInput}
@wa-blur=${this.stopNestedEventPropagation}
@wa-focus=${this.stopNestedEventPropagation}
@change=${this.handleInputChange}
@input=${this.handleInputInput}
@blur=${this.stopNestedEventPropagation}
@focus=${this.stopNestedEventPropagation}
></wa-input>
<wa-button-group>
@@ -1029,8 +1023,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
caret:format-button__caret
"
@click=${this.handleFormatToggle}
@wa-blur=${this.stopNestedEventPropagation}
@wa-focus=${this.stopNestedEventPropagation}
@blur=${this.stopNestedEventPropagation}
@focus=${this.stopNestedEventPropagation}
>
${this.setLetterCase(this.format)}
</wa-button>
@@ -1049,8 +1043,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
caret:eye-dropper-button__caret
"
@click=${this.handleEyeDropper}
@wa-blur=${this.stopNestedEventPropagation}
@wa-focus=${this.stopNestedEventPropagation}
@blur=${this.stopNestedEventPropagation}
@focus=${this.stopNestedEventPropagation}
>
<wa-icon
library="system"

View File

@@ -146,13 +146,13 @@ describe('<wa-icon-button>', () => {
});
describe('when using methods', () => {
it('should emit wa-focus and wa-blur when the button is focused and blurred', async () => {
it('should emit focus and blur when the button is focused and blurred', async () => {
const el = await fixture<WaIconButton>(html` <wa-icon-button></wa-icon-button> `);
const focusHandler = sinon.spy();
const blurHandler = sinon.spy();
el.addEventListener('wa-focus', focusHandler);
el.addEventListener('wa-blur', blurHandler);
el.addEventListener('focus', focusHandler);
el.addEventListener('blur', blurHandler);
el.focus();
await waitUntil(() => focusHandler.calledOnce);

View File

@@ -2,9 +2,7 @@ import { customElement, property, query } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { html, literal } from 'lit/static-html.js';
import { WaBlurEvent } from '../../events/blur.js';
import { WaFocusEvent } from '../../events/focus.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js';
import '../icon/icon.js';
import styles from './icon-button.css';
@@ -16,8 +14,8 @@ import styles from './icon-button.css';
*
* @dependency wa-icon
*
* @event wa-blur - Emitted when the icon button loses focus.
* @event wa-focus - Emitted when the icon button gains focus.
* @event blur - Emitted when the icon button loses focus.
* @event focus - Emitted when the icon button gains focus.
*
* @cssproperty --background-color-hover - The color of the button's background on hover.
*
@@ -71,14 +69,6 @@ export default class WaIconButton extends WebAwesomeFormAssociatedElement {
/** Disables the button. */
@property({ type: Boolean }) disabled = false;
private handleBlur() {
this.dispatchEvent(new WaBlurEvent());
}
private handleFocus() {
this.dispatchEvent(new WaFocusEvent());
}
private handleClick(event: MouseEvent) {
if (this.disabled) {
event.preventDefault();
@@ -122,8 +112,6 @@ export default class WaIconButton extends WebAwesomeFormAssociatedElement {
aria-disabled=${this.disabled ? 'true' : 'false'}
aria-label="${this.label}"
tabindex=${this.disabled ? '-1' : '0'}
@blur=${this.handleBlur}
@focus=${this.handleFocus}
@click=${this.handleClick}
>
<wa-icon

View File

@@ -17,16 +17,13 @@
.before,
.after {
display: block;
pointer-events: none;
}
.before::slotted(img),
.after::slotted(img),
.before::slotted(svg),
.after::slotted(svg) {
display: block;
max-width: 100% !important;
height: auto;
&::slotted(img),
&::slotted(svg) {
display: block;
max-width: 100% !important;
height: auto;
}
}
.after {

View File

@@ -37,7 +37,7 @@ describe('<wa-image-comparer>', () => {
`);
const handler = sinon.spy();
el.addEventListener('wa-change', handler, { once: true });
el.addEventListener('change', handler, { once: true });
el.position = 40;
await el.updateComplete;

View File

@@ -1,7 +1,6 @@
import { html } from 'lit';
import { customElement, property, query } from 'lit/decorators.js';
import { styleMap } from 'lit/directives/style-map.js';
import { WaChangeEvent } from '../../events/change.js';
import { drag } from '../../internal/drag.js';
import { clamp } from '../../internal/math.js';
import { watch } from '../../internal/watch.js';
@@ -22,7 +21,7 @@ import styles from './image-comparer.css';
* @slot after - The after image, an `<img>` or `<svg>` element.
* @slot handle - The icon used inside the handle.
*
* @event wa-change - Emitted when the position changes.
* @event change - Emitted when the position changes.
*
* @csspart base - The component's base wrapper.
* @csspart before - The container that wraps the before image.
@@ -92,7 +91,7 @@ export default class WaImageComparer extends WebAwesomeElement {
@watch('position', { waitUntilFirstUpdate: true })
handlePositionChange() {
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new Event('change'));
}
render() {

View File

@@ -60,7 +60,7 @@ describe('<wa-input>', () => {
const label = el.shadowRoot!.querySelector('[part~="form-control-label"]')!;
const focusHandler = sinon.spy();
el.addEventListener('wa-focus', focusHandler);
el.addEventListener('focus', focusHandler);
(label as HTMLLabelElement).click();
await waitUntil(() => focusHandler.calledOnce);
@@ -312,13 +312,13 @@ describe('<wa-input>', () => {
});
describe('when the value changes', () => {
it('should emit wa-change and wa-input when the user types in the input', async () => {
it('should emit change and input when the user types in the input', async () => {
const el = await fixture<WaInput>(html` <wa-input></wa-input> `);
const inputHandler = sinon.spy();
const changeHandler = sinon.spy();
el.addEventListener('wa-input', inputHandler);
el.addEventListener('wa-change', changeHandler);
el.addEventListener('input', inputHandler);
el.addEventListener('change', changeHandler);
el.focus();
await sendKeys({ type: 'abc' });
el.blur();
@@ -328,21 +328,21 @@ describe('<wa-input>', () => {
expect(inputHandler).to.have.been.calledThrice;
});
it('should not emit wa-change or wa-input when the value is set programmatically', async () => {
it('should not emit change or input when the value is set programmatically', async () => {
const el = await fixture<WaInput>(html` <wa-input></wa-input> `);
el.addEventListener('wa-change', () => expect.fail('wa-change should not be emitted'));
el.addEventListener('wa-input', () => expect.fail('wa-input should not be emitted'));
el.addEventListener('change', () => expect.fail('change should not be emitted'));
el.addEventListener('input', () => expect.fail('input should not be emitted'));
el.value = 'abc';
await el.updateComplete;
});
it('should not emit wa-change or wa-input when calling setRangeText()', async () => {
it('should not emit change or input when calling setRangeText()', async () => {
const el = await fixture<WaInput>(html` <wa-input value="hi there"></wa-input> `);
el.addEventListener('wa-change', () => expect.fail('wa-change should not be emitted'));
el.addEventListener('wa-input', () => expect.fail('wa-input should not be emitted'));
el.addEventListener('change', () => expect.fail('change should not be emitted'));
el.addEventListener('input', () => expect.fail('input should not be emitted'));
el.focus();
el.setSelectionRange(0, 2);
el.setRangeText('hello');
@@ -399,21 +399,21 @@ describe('<wa-input>', () => {
expect(el.value).to.equal('0');
});
it('should not emit wa-input or wa-change when stepUp() is called programmatically', async () => {
it('should not emit input or change when stepUp() is called programmatically', async () => {
const el = await fixture<WaInput>(html` <wa-input type="number" step="2" value="2"></wa-input> `);
el.addEventListener('wa-change', () => expect.fail('wa-change should not be emitted'));
el.addEventListener('wa-input', () => expect.fail('wa-input should not be emitted'));
el.addEventListener('change', () => expect.fail('change should not be emitted'));
el.addEventListener('input', () => expect.fail('input should not be emitted'));
el.stepUp();
await el.updateComplete;
});
it('should not emit wa-input and wa-change when stepDown() is called programmatically', async () => {
it('should not emit input and change when stepDown() is called programmatically', async () => {
const el = await fixture<WaInput>(html` <wa-input type="number" step="2" value="2"></wa-input> `);
el.addEventListener('wa-change', () => expect.fail('wa-change should not be emitted'));
el.addEventListener('wa-input', () => expect.fail('wa-input should not be emitted'));
el.addEventListener('change', () => expect.fail('change should not be emitted'));
el.addEventListener('input', () => expect.fail('input should not be emitted'));
el.stepDown();
await el.updateComplete;

View File

@@ -1,17 +1,13 @@
import { html, isServer } from 'lit';
import { html, isServer, type PropertyValues } from 'lit';
import { customElement, property, query, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { live } from 'lit/directives/live.js';
import { WaBlurEvent } from '../../events/blur.js';
import { WaChangeEvent } from '../../events/change.js';
import { WaClearEvent } from '../../events/clear.js';
import { WaFocusEvent } from '../../events/focus.js';
import { WaInputEvent } from '../../events/input.js';
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-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js';
import nativeStyles from '../../styles/native/input.css';
import formControlStyles from '../../styles/shadow/form-control.css';
import appearanceStyles from '../../styles/utilities/appearance.css';
@@ -37,11 +33,11 @@ import styles from './input.css';
* @slot hide-password-icon - An icon to use in lieu of the default hide password icon.
* @slot hint - Text that describes how to use the input. Alternatively, you can use the `hint` attribute.
*
* @event wa-blur - Emitted when the control loses focus.
* @event wa-change - Emitted when an alteration to the control's value is committed by the user.
* @event blur - Emitted when the control loses focus.
* @event change - Emitted when an alteration to the control's value is committed by the user.
* @event focus - Emitted when the control gains focus.
* @event input - Emitted when the control receives input.
* @event wa-clear - Emitted when the clear button is activated.
* @event wa-focus - Emitted when the control gains focus.
* @event wa-input - Emitted when the control receives input.
* @event wa-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.
*
* @csspart label - The label
@@ -57,6 +53,8 @@ import styles from './input.css';
* @cssproperty --border-color - The color of the input's borders.
* @cssproperty --border-width - The width of the input's borders. Expects a single value.
* @cssproperty --box-shadow - The shadow effects around the edges of the input.
*
* @cssstate blank - The input is empty.
*/
@customElement('wa-input')
export default class WaInput extends WebAwesomeFormAssociatedElement {
@@ -68,7 +66,7 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
return [...super.validators, MirrorValidator()];
}
assumeInteractionOn = ['wa-blur', 'wa-input'];
assumeInteractionOn = ['blur', 'input'];
private readonly hasSlotController = new HasSlotController(this, 'hint', 'label');
private readonly localize = new LocalizeController(this);
@@ -225,13 +223,9 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
*/
@property({ attribute: 'with-hint', type: Boolean }) withHint = false;
private handleBlur() {
this.dispatchEvent(new WaBlurEvent());
}
private handleChange() {
private handleChange(event: Event) {
this.dispatchComposedEvent(event);
this.value = this.input.value;
this.dispatchEvent(new WaChangeEvent());
}
private handleClearClick(event: MouseEvent) {
@@ -240,20 +234,15 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
if (this.value !== '') {
this.value = '';
this.dispatchEvent(new WaClearEvent());
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change'));
}
this.input.focus();
}
private handleFocus() {
this.dispatchEvent(new WaFocusEvent());
}
private handleInput() {
this.value = this.input.value;
this.dispatchEvent(new WaInputEvent());
}
private handleKeyDown(event: KeyboardEvent) {
@@ -308,6 +297,14 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
this.passwordVisible = !this.passwordVisible;
}
updated(changedProperties: PropertyValues<this>) {
super.updated(changedProperties);
if (changedProperties.has('value')) {
this.toggleCustomState('blank', !this.value);
}
}
@watch('step', { waitUntilFirstUpdate: true })
handleStepChange() {
// If step changes, the value may become invalid so we need to recheck after the update. We set the new step
@@ -435,8 +432,6 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
@change=${this.handleChange}
@input=${this.handleInput}
@keydown=${this.handleKeyDown}
@focus=${this.handleFocus}
@blur=${this.handleBlur}
/>
${isClearIconVisible
@@ -465,15 +460,15 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
@click=${this.handlePasswordToggle}
tabindex="-1"
>
${this.passwordVisible
${!this.passwordVisible
? html`
<slot name="show-password-icon">
<wa-icon name="eye-slash" library="system" variant="regular"></wa-icon>
<wa-icon name="eye" library="system" variant="regular"></wa-icon>
</slot>
`
: html`
<slot name="hide-password-icon">
<wa-icon name="eye" library="system" variant="regular"></wa-icon>
<wa-icon name="eye-slash" library="system" variant="regular"></wa-icon>
</slot>
`}
</button>

View File

@@ -2,11 +2,9 @@ import { customElement, property, query } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { html } from 'lit/static-html.js';
import { WaBlurEvent } from '../../events/blur.js';
import { WaFocusEvent } from '../../events/focus.js';
import { HasSlotController } from '../../internal/slot.js';
import { watch } from '../../internal/watch.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js';
import nativeStyles from '../../styles/native/button.css';
import appearanceStyles from '../../styles/utilities/appearance.css';
import sizeStyles from '../../styles/utilities/size.css';
@@ -24,8 +22,8 @@ import styles from './radio-button.css';
* @slot prefix - A presentational prefix icon or similar element.
* @slot suffix - A presentational suffix icon or similar element.
*
* @event wa-blur - Emitted when the button loses focus.
* @event wa-focus - Emitted when the button gains focus.
* @event blur - Emitted when the button loses focus.
* @event focus - Emitted when the button gains focus.
*
* @cssproperty --background-color - The button's background color.
* @cssproperty --background-color-active - The button's background color when active.
@@ -107,10 +105,6 @@ export default class WaRadioButton extends WebAwesomeFormAssociatedElement {
this.setAttribute('role', 'presentation');
}
private handleBlur() {
this.dispatchEvent(new WaBlurEvent());
}
private handleClick(e: MouseEvent) {
if (this.disabled) {
e.preventDefault();
@@ -121,10 +115,6 @@ export default class WaRadioButton extends WebAwesomeFormAssociatedElement {
this.checked = true;
}
private handleFocus() {
this.dispatchEvent(new WaFocusEvent());
}
@watch('disabled', { waitUntilFirstUpdate: true })
handleDisabledChange() {
this.setAttribute('aria-disabled', this.disabled ? 'true' : 'false');
@@ -164,8 +154,6 @@ export default class WaRadioButton extends WebAwesomeFormAssociatedElement {
aria-disabled=${this.disabled}
type="button"
value=${ifDefined(this.value)}
@blur=${this.handleBlur}
@focus=${this.handleFocus}
@click=${this.handleClick}
>
<slot name="prefix" part="prefix" class="prefix"></slot>

View File

@@ -2,7 +2,6 @@ import { aTimeout, expect, oneEvent } from '@open-wc/testing';
import { sendKeys } from '@web/test-runner-commands';
import { html } from 'lit';
import sinon from 'sinon';
import type { WaChangeEvent } from '../../events/change.js';
import { clickOnElement } from '../../internal/test.js';
import { fixtures } from '../../internal/test/fixture.js';
import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js';
@@ -326,7 +325,7 @@ describe('<wa-radio-group>', () => {
const validFocusHandler = sinon.spy();
Array.from(el.querySelectorAll<WaRadio>('wa-radio')).forEach(radio =>
radio.addEventListener('wa-focus', validFocusHandler),
radio.addEventListener('focus', validFocusHandler),
);
expect(validFocusHandler).to.not.have.been.called;
@@ -350,8 +349,8 @@ describe('<wa-radio-group>', () => {
const disabledRadio = el.querySelector('#radio-0')!;
const validRadio = el.querySelector('#radio-1')!;
disabledRadio.addEventListener('wa-focus', invalidFocusHandler);
validRadio.addEventListener('wa-focus', validFocusHandler);
disabledRadio.addEventListener('focus', invalidFocusHandler);
validRadio.addEventListener('focus', validFocusHandler);
expect(invalidFocusHandler).to.not.have.been.called;
expect(validFocusHandler).to.not.have.been.called;
@@ -378,8 +377,8 @@ describe('<wa-radio-group>', () => {
const disabledRadio = el.querySelector('#radio-0')!;
const validRadio = el.querySelector('#radio-2')!;
disabledRadio.addEventListener('wa-focus', invalidFocusHandler);
validRadio.addEventListener('wa-focus', validFocusHandler);
disabledRadio.addEventListener('focus', invalidFocusHandler);
validRadio.addEventListener('focus', validFocusHandler);
expect(invalidFocusHandler).to.not.have.been.called;
expect(validFocusHandler).to.not.have.been.called;
@@ -394,7 +393,7 @@ describe('<wa-radio-group>', () => {
});
describe('when the value changes', () => {
it('should emit wa-change when toggled with the arrow keys', async () => {
it('should emit change when toggled with the arrow keys', async () => {
const radioGroup = await fixture<WaRadioGroup>(html`
<wa-radio-group>
<wa-radio id="radio-1" value="1"></wa-radio>
@@ -405,8 +404,8 @@ describe('<wa-radio-group>', () => {
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
radioGroup.addEventListener('wa-change', changeHandler);
radioGroup.addEventListener('wa-input', inputHandler);
radioGroup.addEventListener('change', changeHandler);
radioGroup.addEventListener('input', inputHandler);
firstRadio.focus();
await sendKeys({ press: 'ArrowRight' });
await radioGroup.updateComplete;
@@ -416,7 +415,7 @@ describe('<wa-radio-group>', () => {
expect(radioGroup.value).to.equal('2');
});
it('should emit wa-change and wa-input when clicked', async () => {
it('should emit change and input when clicked', async () => {
const radioGroup = await fixture<WaRadioGroup>(html`
<wa-radio-group>
<wa-radio id="radio-1" value="1"></wa-radio>
@@ -425,12 +424,12 @@ describe('<wa-radio-group>', () => {
`);
const radio = radioGroup.querySelector<WaRadio>('#radio-1')!;
setTimeout(() => radio.click());
const event = (await oneEvent(radioGroup, 'wa-change')) as WaChangeEvent;
const event = await oneEvent(radioGroup, 'change');
expect(event.target).to.equal(radioGroup);
expect(radioGroup.value).to.equal('1');
});
it('should emit wa-change and wa-input when toggled with spacebar', async () => {
it('should emit change and input when toggled with spacebar', async () => {
const radioGroup = await fixture<WaRadioGroup>(html`
<wa-radio-group>
<wa-radio id="radio-1" value="1"></wa-radio>
@@ -440,12 +439,12 @@ describe('<wa-radio-group>', () => {
const radio = radioGroup.querySelector<WaRadio>('#radio-1')!;
radio.focus();
setTimeout(() => sendKeys({ press: ' ' }));
const event = (await oneEvent(radioGroup, 'wa-change')) as WaChangeEvent;
const event = await oneEvent(radioGroup, 'change');
expect(event.target).to.equal(radioGroup);
expect(radioGroup.value).to.equal('1');
});
it('should not emit wa-change or wa-input when the value is changed programmatically', async () => {
it('should not emit change or input when the value is changed programmatically', async () => {
const radioGroup = await fixture<WaRadioGroup>(html`
<wa-radio-group value="1">
<wa-radio id="radio-1" value="1"></wa-radio>
@@ -453,8 +452,8 @@ describe('<wa-radio-group>', () => {
</wa-radio-group>
`);
radioGroup.addEventListener('wa-change', () => expect.fail('wa-change should not be emitted'));
radioGroup.addEventListener('wa-input', () => expect.fail('wa-input should not be emitted'));
radioGroup.addEventListener('change', () => expect.fail('change should not be emitted'));
radioGroup.addEventListener('input', () => expect.fail('input should not be emitted'));
radioGroup.value = '2';
await radioGroup.updateComplete;
});

View File

@@ -1,13 +1,11 @@
import { html, isServer } from 'lit';
import { customElement, property, query, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { WaChangeEvent } from '../../events/change.js';
import { WaInputEvent } from '../../events/input.js';
import { uniqueId } from '../../internal/math.js';
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-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js';
import formControlStyles from '../../styles/shadow/form-control.css';
import buttonGroupStyles from '../../styles/utilities/button-group.css';
import sizeStyles from '../../styles/utilities/size.css';
@@ -29,8 +27,8 @@ import styles from './radio-group.css';
* attribute.
* @slot hint - Text that describes how to use the radio group. Alternatively, you can use the `hint` attribute.
*
* @event wa-change - Emitted when the radio group's selected value changes.
* @event wa-input - Emitted when the radio group receives user input.
* @event change - Emitted when the radio group's selected value changes.
* @event input - Emitted when the radio group receives user input.
* @event wa-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.
*
* @csspart form-control - The form control that wraps the label, input, and hint.
@@ -159,8 +157,8 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
}
if (this.value !== oldValue) {
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change'));
}
};
@@ -290,8 +288,8 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
}
if (this.value !== oldValue) {
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change'));
}
event.preventDefault();

View File

@@ -1,10 +1,8 @@
import { html, isServer } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { WaBlurEvent } from '../../events/blur.js';
import { WaFocusEvent } from '../../events/focus.js';
import { watch } from '../../internal/watch.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js';
import nativeStyles from '../../styles/native/radio.css';
import sizeStyles from '../../styles/utilities/size.css';
import '../icon/icon.js';
@@ -20,8 +18,8 @@ import styles from './radio.css';
*
* @slot - The radio's label.
*
* @event wa-blur - Emitted when the control loses focus.
* @event wa-focus - Emitted when the control gains focus.
* @event blur - Emitted when the control loses focus.
* @event focus - Emitted when the control gains focus.
*
* @csspart base - The component's base wrapper.
* @csspart control - The circular container that wraps the radio's checked state.
@@ -69,8 +67,6 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement {
super();
if (!isServer) {
this.addEventListener('click', this.handleClick);
this.addEventListener('blur', this.handleBlur);
this.addEventListener('focus', this.handleFocus);
}
}
@@ -79,14 +75,6 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement {
this.setInitialAttributes();
}
private handleBlur = () => {
this.dispatchEvent(new WaBlurEvent());
};
private handleFocus = () => {
this.dispatchEvent(new WaFocusEvent());
};
private setInitialAttributes() {
this.setAttribute('role', 'radio');
this.tabIndex = 0;

View File

@@ -54,12 +54,12 @@ describe('<wa-rating>', () => {
expect(base.getAttribute('aria-valuenow')).to.equal('3');
});
it('should emit wa-change when clicked', async () => {
it('should emit change when clicked', async () => {
const el = await fixture<WaRating>(html` <wa-rating></wa-rating> `);
const lastSymbol = el.shadowRoot!.querySelector<HTMLSpanElement>('.symbol:last-child')!;
const changeHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('change', changeHandler);
await clickOnElement(lastSymbol);
await el.updateComplete;
@@ -68,11 +68,11 @@ describe('<wa-rating>', () => {
expect(el.value).to.equal(5);
});
it('should emit wa-change when the value is changed with the keyboard', async () => {
it('should emit change when the value is changed with the keyboard', async () => {
const el = await fixture<WaRating>(html` <wa-rating></wa-rating> `);
const changeHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('change', changeHandler);
el.focus();
await el.updateComplete;
await sendKeys({ press: 'ArrowRight' });
@@ -82,12 +82,12 @@ describe('<wa-rating>', () => {
expect(el.value).to.equal(1);
});
it('should not emit wa-change when disabled', async () => {
it('should not emit change when disabled', async () => {
const el = await fixture<WaRating>(html` <wa-rating value="5" disabled></wa-rating> `);
const lastSymbol = el.shadowRoot!.querySelector<HTMLSpanElement>('.symbol:last-child')!;
const changeHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('change', changeHandler);
await clickOnElement(lastSymbol);
await el.updateComplete;
@@ -96,9 +96,9 @@ describe('<wa-rating>', () => {
expect(el.value).to.equal(5);
});
it('should not emit wa-change when the value is changed programmatically', async () => {
it('should not emit change when the value is changed programmatically', async () => {
const el = await fixture<WaRating>(html` <wa-rating label="Test" value="1"></wa-rating> `);
el.addEventListener('wa-change', () => expect.fail('wa-change incorrectly emitted'));
el.addEventListener('change', () => expect.fail('change incorrectly emitted'));
el.value = 5;
await el.updateComplete;
});

View File

@@ -3,7 +3,6 @@ import { customElement, eventOptions, property, query, state } from 'lit/decorat
import { classMap } from 'lit/directives/class-map.js';
import { styleMap } from 'lit/directives/style-map.js';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import { WaChangeEvent } from '../../events/change.js';
import { WaHoverEvent } from '../../events/hover.js';
import { clamp } from '../../internal/math.js';
import { watch } from '../../internal/watch.js';
@@ -20,7 +19,7 @@ import styles from './rating.css';
*
* @dependency wa-icon
*
* @event wa-change - Emitted when the rating's value changes.
* @event change - Emitted when the rating's value changes.
* @event {{ phase: 'start' | 'move' | 'end', value: number }} wa-hover - Emitted when the user hovers over a value. The
* `phase` property indicates when hovering starts, moves to a new value, or ends. The `value` property tells what the
* rating's value would be if the user were to commit to the hovered value.
@@ -96,7 +95,7 @@ export default class WaRating extends WebAwesomeElement {
}
this.setValue(this.getValueFromMousePosition(event));
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new Event('change'));
}
private setValue(newValue: number) {
@@ -140,7 +139,7 @@ export default class WaRating extends WebAwesomeElement {
}
if (this.value !== oldValue) {
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new Event('change'));
}
}
@@ -173,7 +172,7 @@ export default class WaRating extends WebAwesomeElement {
private handleTouchEnd(event: TouchEvent) {
this.isHovering = false;
this.setValue(this.hoverValue);
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new Event('change'));
// Prevent click on mobile devices
event.preventDefault();

View File

@@ -107,7 +107,7 @@ describe('<wa-select>', () => {
const label = el.shadowRoot!.querySelector('[part~="form-control-label"]')!;
const submitHandler = sinon.spy();
el.addEventListener('wa-focus', submitHandler);
el.addEventListener('focus', submitHandler);
(label as HTMLLabelElement).click();
await waitUntil(() => submitHandler.calledOnce);
@@ -115,7 +115,7 @@ describe('<wa-select>', () => {
});
describe('when the value changes', () => {
it('should emit wa-change when the value is changed with the mouse', async () => {
it('should emit change when the value is changed with the mouse', async () => {
const el = await fixture<WaSelect>(html`
<wa-select value="option-1">
<wa-option value="option-1">Option 1</wa-option>
@@ -132,8 +132,8 @@ describe('<wa-select>', () => {
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
await el.show();
await clickOnElement(secondOption);
@@ -144,7 +144,7 @@ describe('<wa-select>', () => {
expect(el.value).to.equal('option-2');
});
it('should emit wa-change and wa-input when the value is changed with the keyboard', async () => {
it('should emit change and input when the value is changed with the keyboard', async () => {
const el = await fixture<WaSelect>(html`
<wa-select value="option-1">
<wa-option value="option-1">Option 1</wa-option>
@@ -155,8 +155,8 @@ describe('<wa-select>', () => {
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
el.focus();
await el.updateComplete;
@@ -175,7 +175,7 @@ describe('<wa-select>', () => {
expect(el.value).to.equal('option-3');
});
it('should not emit wa-change or wa-input when the value is changed programmatically', async () => {
it('should not emit change or input when the value is changed programmatically', async () => {
const el = await fixture<WaSelect>(html`
<wa-select value="option-1">
<wa-option value="option-1">Option 1</wa-option>
@@ -184,14 +184,14 @@ describe('<wa-select>', () => {
</wa-select>
`);
el.addEventListener('wa-change', () => expect.fail('wa-change should not be emitted'));
el.addEventListener('wa-input', () => expect.fail('wa-input should not be emitted'));
el.addEventListener('change', () => expect.fail('change should not be emitted'));
el.addEventListener('input', () => expect.fail('input should not be emitted'));
el.value = 'option-2';
await el.updateComplete;
});
it('should emit wa-change and wa-input with the correct validation message when the value changes', async () => {
it('should emit change and input with the correct validation message when the value changes', async () => {
const el = await fixture<WaSelect>(html`
<wa-select required>
<wa-option value="option-1">Option 1</wa-option>
@@ -206,8 +206,8 @@ describe('<wa-select>', () => {
}
});
el.addEventListener('wa-change', handler);
el.addEventListener('wa-input', handler);
el.addEventListener('change', handler);
el.addEventListener('input', handler);
await clickOnElement(el);
await aTimeout(500);
@@ -532,7 +532,7 @@ describe('<wa-select>', () => {
expect(displayInput.value).to.equal('updated');
});
it('should emit wa-focus and wa-blur when receiving and losing focus', async () => {
it('should emit focus and blur when receiving and losing focus', async () => {
const el = await fixture<WaSelect>(html`
<wa-select value="option-1">
<wa-option value="option-1">Option 1</wa-option>
@@ -543,8 +543,8 @@ describe('<wa-select>', () => {
const focusHandler = sinon.spy();
const blurHandler = sinon.spy();
el.addEventListener('wa-focus', focusHandler);
el.addEventListener('wa-blur', blurHandler);
el.addEventListener('focus', focusHandler);
el.addEventListener('blur', blurHandler);
el.focus();
await el.updateComplete;
@@ -574,7 +574,7 @@ describe('<wa-select>', () => {
expect(clearHandler).to.have.been.calledOnce;
});
it('should emit wa-change and wa-input when a tag is removed', async () => {
it('should emit change and input when a tag is removed', async () => {
const el = await fixture<WaSelect>(html`
<wa-select value="option-1 option-2 option-3" multiple>
<wa-option value="option-1">Option 1</wa-option>
@@ -587,8 +587,8 @@ describe('<wa-select>', () => {
const tag = el.shadowRoot!.querySelector('[part~="tag"]')!;
const removeButton = tag.shadowRoot!.querySelector('[part~="remove-button"]')!;
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
// The offsets are a funky hack for Firefox.
await clickOnElement(removeButton, 'center', 1, 1);

View File

@@ -1,16 +1,12 @@
import type { TemplateResult } from 'lit';
import type { PropertyValues, TemplateResult } from 'lit';
import { html, isServer } from 'lit';
import { customElement, property, query, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { unsafeHTML } from 'lit/directives/unsafe-html.js';
import { WaAfterHideEvent } from '../../events/after-hide.js';
import { WaAfterShowEvent } from '../../events/after-show.js';
import { WaBlurEvent } from '../../events/blur.js';
import { WaChangeEvent } from '../../events/change.js';
import { WaClearEvent } from '../../events/clear.js';
import { WaFocusEvent } from '../../events/focus.js';
import { WaHideEvent } from '../../events/hide.js';
import { WaInputEvent } from '../../events/input.js';
import type { WaRemoveEvent } from '../../events/remove.js';
import { WaShowEvent } from '../../events/show.js';
import { animateWithClass } from '../../internal/animate.js';
@@ -19,7 +15,7 @@ import { scrollIntoView } from '../../internal/scroll.js';
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-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js';
import nativeStyles from '../../styles/native/select.css';
import formControlStyles from '../../styles/shadow/form-control.css';
import appearanceStyles from '../../styles/utilities/appearance.css';
@@ -50,11 +46,11 @@ import styles from './select.css';
* @slot expand-icon - The icon to show when the control is expanded and collapsed. Rotates on open and close.
* @slot hint - Text that describes how to use the input. Alternatively, you can use the `hint` attribute.
*
* @event wa-change - Emitted when the control's value changes.
* @event change - Emitted when the control's value changes.
* @event input - Emitted when the control receives input.
* @event focus - Emitted when the control gains focus.
* @event blur - Emitted when the control loses focus.
* @event wa-clear - Emitted when the control's value is cleared.
* @event wa-input - Emitted when the control receives input.
* @event wa-focus - Emitted when the control gains focus.
* @event wa-blur - Emitted when the control loses focus.
* @event wa-show - Emitted when the select's menu opens.
* @event wa-after-show - Emitted after the select's menu opens and all animations are complete.
* @event wa-hide - Emitted when the select's menu closes.
@@ -83,6 +79,8 @@ import styles from './select.css';
* @cssproperty --border-color - The border color of the select's combobox.
* @cssproperty --border-width - The width of the select's borders, including the listbox.
* @cssproperty --box-shadow - The shadow effects around the edges of the select's combobox.
*
* @cssstate blank - The select is empty.
*/
@customElement('wa-select')
export default class WaSelect extends WebAwesomeFormAssociatedElement {
@@ -99,7 +97,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
return [...super.validators, ...validators];
}
assumeInteractionOn = ['wa-blur', 'wa-input'];
assumeInteractionOn = ['blur', 'input'];
private readonly hasSlotController = new HasSlotController(this, 'hint', 'label');
private readonly localize = new LocalizeController(this);
@@ -310,11 +308,6 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
private handleFocus() {
this.displayInput.setSelectionRange(0, 0);
this.dispatchEvent(new WaFocusEvent());
}
private handleBlur() {
this.dispatchEvent(new WaBlurEvent());
}
private handleDocumentFocusIn = (event: KeyboardEvent) => {
@@ -366,8 +359,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
// Emit after updating
this.updateComplete.then(() => {
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change'));
});
if (!this.multiple) {
@@ -496,8 +489,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
// Emit after update
this.updateComplete.then(() => {
this.dispatchEvent(new WaClearEvent());
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change'));
});
}
}
@@ -527,8 +520,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
if (this.value !== oldValue) {
// Emit after updating
this.updateComplete.then(() => {
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change'));
});
}
@@ -565,8 +558,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
// Emit after updating
this.updateComplete.then(() => {
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change'));
});
}
}
@@ -674,6 +667,14 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
});
}
updated(changedProperties: PropertyValues<this>) {
super.updated(changedProperties);
if (changedProperties.has('value')) {
this.toggleCustomState('blank', !this.value);
}
}
@watch('disabled', { waitUntilFirstUpdate: true })
handleDisabledChange() {
// Close the listbox when the control is disabled
@@ -799,7 +800,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
>
<label
id="label"
part="form-control-label"
part="form-control-label label"
class="label"
aria-hidden=${hasLabel ? 'false' : 'true'}
@click=${this.handleLabelClick}
@@ -859,7 +860,6 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
role="combobox"
tabindex="0"
@focus=${this.handleFocus}
@blur=${this.handleBlur}
/>
<!-- Tags need to wait for first hydration before populating otherwise it will create a hydration mismatch. -->

View File

@@ -50,13 +50,13 @@ describe('<wa-slider>', () => {
});
describe('when the value changes', () => {
it('should emit wa-change and wa-input and decrease the value when pressing right arrow', async () => {
it('should emit change and input and decrease the value when pressing right arrow', async () => {
const el = await fixture<WaSlider>(html` <wa-slider value="50"></wa-slider> `);
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
el.focus();
await sendKeys({ press: 'ArrowRight' });
await el.updateComplete;
@@ -66,30 +66,30 @@ describe('<wa-slider>', () => {
expect(inputHandler).to.have.been.calledOnce;
});
it('should not emit wa-change or wa-input when changing the value programmatically', async () => {
it('should not emit change or input when changing the value programmatically', async () => {
const el = await fixture<WaSlider>(html` <wa-slider value="0"></wa-slider> `);
el.addEventListener('wa-change', () => expect.fail('wa-change should not be emitted'));
el.addEventListener('wa-input', () => expect.fail('wa-input should not be emitted'));
el.addEventListener('change', () => expect.fail('change should not be emitted'));
el.addEventListener('input', () => expect.fail('input should not be emitted'));
el.value = 50;
await el.updateComplete;
});
it('should not emit wa-change or wa-input when stepUp() is called programmatically', async () => {
it('should not emit change or input when stepUp() is called programmatically', async () => {
const el = await fixture<WaSlider>(html` <wa-slider step="2" value="2"></wa-slider> `);
el.addEventListener('wa-change', () => expect.fail('wa-change should not be emitted'));
el.addEventListener('wa-input', () => expect.fail('wa-input should not be emitted'));
el.addEventListener('change', () => expect.fail('change should not be emitted'));
el.addEventListener('input', () => expect.fail('input should not be emitted'));
el.stepUp();
await el.updateComplete;
});
it('should not emit wa-change or wa-input when stepDown() is called programmatically', async () => {
it('should not emit change or input when stepDown() is called programmatically', async () => {
const el = await fixture<WaSlider>(html` <wa-slider step="2" value="2"></wa-slider> `);
el.addEventListener('wa-change', () => expect.fail('wa-change should not be emitted'));
el.addEventListener('wa-input', () => expect.fail('wa-input should not be emitted'));
el.addEventListener('change', () => expect.fail('change should not be emitted'));
el.addEventListener('input', () => expect.fail('input should not be emitted'));
el.stepDown();
await el.updateComplete;
});

View File

@@ -3,14 +3,10 @@ import { customElement, eventOptions, property, query, state } from 'lit/decorat
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { live } from 'lit/directives/live.js';
import { WaBlurEvent } from '../../events/blur.js';
import { WaChangeEvent } from '../../events/change.js';
import { WaFocusEvent } from '../../events/focus.js';
import { WaInputEvent } from '../../events/input.js';
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-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js';
import sliderStyles from '../../styles/native/slider.css';
import formControlStyles from '../../styles/shadow/form-control.css';
import { LocalizeController } from '../../utilities/localize.js';
@@ -25,10 +21,10 @@ import styles from './slider.css';
* @slot label - The slider label. Alternatively, you can use the `label` attribute.
* @slot hint - Text that describes how to use the input. Alternatively, you can use the `hint` attribute.
*
* @event wa-blur - Emitted when the control loses focus.
* @event wa-change - Emitted when an alteration to the control's value is committed by the user.
* @event wa-focus - Emitted when the control gains focus.
* @event wa-input - Emitted when the control receives input.
* @event blur - Emitted when the control loses focus.
* @event change - Emitted when an alteration to the control's value is committed by the user.
* @event focus - Emitted when the control gains focus.
* @event input - Emitted when the control receives input.
* @event wa-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.
*
* @csspart form-control - The form control that wraps the label, input, and hint.
@@ -160,24 +156,21 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
this.resizeObserver?.unobserve(this.input);
}
private handleChange() {
this.dispatchEvent(new WaChangeEvent());
private handleChange(event: Event) {
this.dispatchComposedEvent(event);
}
private handleInput() {
this.value = parseFloat(this.input.value);
this.dispatchEvent(new WaInputEvent());
this.syncRange();
}
private handleBlur() {
this.hasTooltip = false;
this.dispatchEvent(new WaBlurEvent());
}
private handleFocus() {
this.hasTooltip = true;
this.dispatchEvent(new WaFocusEvent());
}
@eventOptions({ passive: true })

View File

@@ -49,13 +49,13 @@ describe('<wa-switch>', () => {
expect(el.checkValidity()).to.be.true;
});
it('should emit wa-change and wa-input when clicked', async () => {
it('should emit change and input when clicked', async () => {
const el = await fixture<WaSwitch>(html` <wa-switch></wa-switch> `);
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
el.click();
await el.updateComplete;
@@ -64,13 +64,13 @@ describe('<wa-switch>', () => {
expect(el.checked).to.be.true;
});
it('should emit wa-change when toggled with spacebar', async () => {
it('should emit change when toggled with spacebar', async () => {
const el = await fixture<WaSwitch>(html` <wa-switch></wa-switch> `);
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
el.focus();
await sendKeys({ press: ' ' });
@@ -79,13 +79,13 @@ describe('<wa-switch>', () => {
expect(el.checked).to.be.true;
});
it('should emit wa-change and wa-input when toggled with the right arrow', async () => {
it('should emit change and input when toggled with the right arrow', async () => {
const el = await fixture<WaSwitch>(html` <wa-switch></wa-switch> `);
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
el.focus();
await sendKeys({ press: 'ArrowRight' });
await el.updateComplete;
@@ -95,13 +95,13 @@ describe('<wa-switch>', () => {
expect(el.checked).to.be.true;
});
it('should emit wa-change and wa-input when toggled with the left arrow', async () => {
it('should emit change and input when toggled with the left arrow', async () => {
const el = await fixture<WaSwitch>(html` <wa-switch checked></wa-switch> `);
const changeHandler = sinon.spy();
const inputHandler = sinon.spy();
el.addEventListener('wa-change', changeHandler);
el.addEventListener('wa-input', inputHandler);
el.addEventListener('change', changeHandler);
el.addEventListener('input', inputHandler);
el.focus();
await sendKeys({ press: 'ArrowLeft' });
await el.updateComplete;
@@ -111,10 +111,10 @@ describe('<wa-switch>', () => {
expect(el.checked).to.be.false;
});
it('should not emit wa-change or wa-input when checked is set by JavaScript', async () => {
it('should not emit change or input when checked is set by JavaScript', async () => {
const el = await fixture<WaSwitch>(html` <wa-switch></wa-switch> `);
el.addEventListener('wa-change', () => expect.fail('wa-change incorrectly emitted'));
el.addEventListener('wa-input', () => expect.fail('wa-change incorrectly emitted'));
el.addEventListener('change', () => expect.fail('change incorrectly emitted'));
el.addEventListener('input', () => expect.fail('input incorrectly emitted'));
el.checked = true;
await el.updateComplete;
el.checked = false;

View File

@@ -4,14 +4,10 @@ import { customElement, property, query } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { live } from 'lit/directives/live.js';
import { WaBlurEvent } from '../../events/blur.js';
import { WaChangeEvent } from '../../events/change.js';
import { WaFocusEvent } from '../../events/focus.js';
import { WaInputEvent } from '../../events/input.js';
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-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js';
import formControlStyles from '../../styles/shadow/form-control.css';
import sizeStyles from '../../styles/utilities/size.css';
import styles from './switch.css';
@@ -25,10 +21,10 @@ import styles from './switch.css';
* @slot - The switch's label.
* @slot hint - Text that describes how to use the switch. Alternatively, you can use the `hint` attribute.
*
* @event wa-blur - Emitted when the control loses focus.
* @event wa-change - Emitted when the control's checked state changes.
* @event wa-input - Emitted when the control receives input.
* @event wa-focus - Emitted when the control gains focus.
* @event blur - Emitted when the control loses focus.
* @event change - Emitted when the control's checked state changes.
* @event input - Emitted when the control receives input.
* @event focus - Emitted when the control gains focus.
* @event wa-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.
*
* @csspart base - The component's base wrapper.
@@ -117,37 +113,25 @@ export default class WaSwitch extends WebAwesomeFormAssociatedElement {
this.handleValueOrCheckedChange();
}
private handleBlur() {
this.dispatchEvent(new WaBlurEvent());
}
private handleInput() {
this.dispatchEvent(new WaInputEvent());
}
private handleClick() {
this.hasInteracted = true;
this.checked = !this.checked;
this.dispatchEvent(new WaChangeEvent());
}
private handleFocus() {
this.dispatchEvent(new WaFocusEvent());
this.dispatchEvent(new Event('change'));
}
private handleKeyDown(event: KeyboardEvent) {
if (event.key === 'ArrowLeft') {
event.preventDefault();
this.checked = false;
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new Event('change'));
this.dispatchEvent(new InputEvent('input'));
}
if (event.key === 'ArrowRight') {
event.preventDefault();
this.checked = true;
this.dispatchEvent(new WaChangeEvent());
this.dispatchEvent(new WaInputEvent());
this.dispatchEvent(new Event('change'));
this.dispatchEvent(new InputEvent('input'));
}
}
@@ -250,9 +234,6 @@ export default class WaSwitch extends WebAwesomeFormAssociatedElement {
aria-checked=${this.checked ? 'true' : 'false'}
aria-describedby="hint"
@click=${this.handleClick}
@input=${this.handleInput}
@blur=${this.handleBlur}
@focus=${this.handleFocus}
@keydown=${this.handleKeyDown}
/>

View File

@@ -90,17 +90,22 @@ export default class WaTabGroup extends WebAwesomeElement {
setTimeout(() => this.setAriaLabels());
}
// Only process mutations for elements that are children of this tab group
const relevantMutations = mutations.filter(m => {
const target = m.target as HTMLElement;
return target.closest('wa-tab-group') === this;
});
// Sync tabs when disabled states change
if (mutations.some(m => m.attributeName === 'disabled')) {
if (relevantMutations.some(m => m.attributeName === 'disabled')) {
this.syncTabsAndPanels();
// sync tabs when active state on tab changes
} else if (mutations.some(m => m.attributeName === 'active')) {
const tabs = mutations
} else if (relevantMutations.some(m => m.attributeName === 'active')) {
const tabs = relevantMutations
.filter(m => m.attributeName === 'active' && (m.target as HTMLElement).tagName.toLowerCase() === 'wa-tab')
.map(m => m.target as WaTab);
const newActiveTab = tabs.find(tab => tab.active);
if (newActiveTab) {
if (newActiveTab && newActiveTab.closest('wa-tab-group') === this) {
this.setActiveTab(newActiveTab);
}
}
@@ -164,7 +169,7 @@ export default class WaTabGroup extends WebAwesomeElement {
const tab = target.closest('wa-tab');
const tabGroup = tab?.closest('wa-tab-group');
// Ensure the target tab is in this tab group
// Ensure the target tab is in this specific tab group instance
if (tabGroup !== this) {
return;
}
@@ -179,7 +184,7 @@ export default class WaTabGroup extends WebAwesomeElement {
const tab = target.closest('wa-tab');
const tabGroup = tab?.closest('wa-tab-group');
// Ensure the target tab is in this tab group
// Ensure the target tab is in this specific tab group instance
if (tabGroup !== this) {
return;
}
@@ -297,6 +302,11 @@ export default class WaTabGroup extends WebAwesomeElement {
...options,
};
// Ensure the tab belongs to this tab group before activating
if (tab.closest('wa-tab-group') !== this) {
return;
}
if (tab !== this.activeTab && !tab.disabled) {
const previousTab = this.activeTab;
this.active = tab.panel;

View File

@@ -64,7 +64,7 @@ describe('<wa-textarea>', () => {
const label = el.shadowRoot!.querySelector('[part~="label"]')!;
const submitHandler = sinon.spy();
el.addEventListener('wa-focus', submitHandler);
el.addEventListener('focus', submitHandler);
(label as HTMLLabelElement).click();
await waitUntil(() => submitHandler.calledOnce);
@@ -72,13 +72,13 @@ describe('<wa-textarea>', () => {
});
describe('when the value changes', () => {
it('should emit wa-change and wa-input when the user types in the textarea', async () => {
it('should emit change and input when the user types in the textarea', async () => {
const el = await fixture<WaTextarea>(html` <wa-textarea></wa-textarea> `);
const inputHandler = sinon.spy();
const changeHandler = sinon.spy();
el.addEventListener('wa-input', inputHandler);
el.addEventListener('wa-change', changeHandler);
el.addEventListener('input', inputHandler);
el.addEventListener('change', changeHandler);
el.focus();
await sendKeys({ type: 'abc' });
el.blur();
@@ -88,21 +88,21 @@ describe('<wa-textarea>', () => {
expect(inputHandler).to.have.been.calledThrice;
});
it('should not emit wa-change or wa-input when the value is set programmatically', async () => {
it('should not emit change or input when the value is set programmatically', async () => {
const el = await fixture<WaTextarea>(html` <wa-textarea></wa-textarea> `);
el.addEventListener('wa-change', () => expect.fail('wa-change should not be emitted'));
el.addEventListener('wa-input', () => expect.fail('wa-input should not be emitted'));
el.addEventListener('change', () => expect.fail('change should not be emitted'));
el.addEventListener('input', () => expect.fail('input should not be emitted'));
el.value = 'abc';
await el.updateComplete;
});
it('should not emit wa-change or wa-input when calling setRangeText()', async () => {
it('should not emit change or input when calling setRangeText()', async () => {
const el = await fixture<WaTextarea>(html` <wa-textarea value="hi there"></wa-textarea> `);
el.addEventListener('wa-change', () => expect.fail('wa-change should not be emitted'));
el.addEventListener('wa-input', () => expect.fail('wa-input should not be emitted'));
el.addEventListener('change', () => expect.fail('change should not be emitted'));
el.addEventListener('input', () => expect.fail('input should not be emitted'));
el.focus();
el.setSelectionRange(0, 2);
el.setRangeText('hello');

View File

@@ -4,14 +4,10 @@ import { customElement, property, query, state } from 'lit/decorators.js';
import { classMap } from 'lit/directives/class-map.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { live } from 'lit/directives/live.js';
import { WaBlurEvent } from '../../events/blur.js';
import { WaChangeEvent } from '../../events/change.js';
import { WaFocusEvent } from '../../events/focus.js';
import { WaInputEvent } from '../../events/input.js';
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-formassociated-element.js';
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js';
import nativeStyles from '../../styles/native/input.css';
import formControlStyles from '../../styles/shadow/form-control.css';
import appearanceStyles from '../../styles/utilities/appearance.css';
@@ -27,10 +23,10 @@ import styles from './textarea.css';
* @slot label - The textarea's label. Alternatively, you can use the `label` attribute.
* @slot hint - Text that describes how to use the input. Alternatively, you can use the `hint` attribute.
*
* @event wa-blur - Emitted when the control loses focus.
* @event wa-change - Emitted when an alteration to the control's value is committed by the user.
* @event wa-focus - Emitted when the control gains focus.
* @event wa-input - Emitted when the control receives input.
* @event blur - Emitted when the control loses focus.
* @event change - Emitted when an alteration to the control's value is committed by the user.
* @event focus - Emitted when the control gains focus.
* @event input - Emitted when the control receives input.
* @event wa-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.
*
* @csspart label - The label
@@ -43,6 +39,8 @@ import styles from './textarea.css';
* @cssproperty --border-color - The color of the textarea's borders.
* @cssproperty --border-width - The width of the textarea's borders.
* @cssproperty --box-shadow - The shadow effects around the edges of the textarea.
*
* @cssstate blank - The textarea is empty.
*/
@customElement('wa-textarea')
export default class WaTextarea extends WebAwesomeFormAssociatedElement {
@@ -52,7 +50,7 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement {
return [...super.validators, MirrorValidator()];
}
assumeInteractionOn = ['wa-blur', 'wa-input'];
assumeInteractionOn = ['blur', 'input'];
private readonly hasSlotController = new HasSlotController(this, 'hint', 'label');
private resizeObserver: ResizeObserver;
@@ -202,26 +200,20 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement {
}
private handleBlur() {
this.dispatchEvent(new WaBlurEvent());
this.checkValidity();
}
private handleChange() {
private handleChange(event: Event) {
this.valueHasChanged = true;
this.value = this.input.value;
this.setTextareaDimensions();
this.dispatchEvent(new WaChangeEvent());
this.dispatchComposedEvent(event);
this.checkValidity();
}
private handleFocus() {
this.dispatchEvent(new WaFocusEvent());
}
private handleInput() {
this.valueHasChanged = true;
this.value = this.input.value;
this.dispatchEvent(new WaInputEvent());
}
private setTextareaDimensions() {
@@ -275,7 +267,12 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement {
if (changedProperties.has('resize')) {
this.setTextareaDimensions();
}
super.updated(changedProperties);
if (changedProperties.has('value')) {
this.toggleCustomState('blank', !this.value);
}
}
/** Sets focus on the textarea. */
@@ -375,7 +372,6 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement {
aria-describedby="hint"
@change=${this.handleChange}
@input=${this.handleInput}
@focus=${this.handleFocus}
@blur=${this.handleBlur}
></textarea>

View File

@@ -30,14 +30,16 @@ slot:not([name])::slotted(wa-icon) {
}
.checkbox {
line-height: var(--wa-form-control-value-line-height);
pointer-events: none;
}
.expand-button,
.checkbox,
.label {
font: inherit;
font-family: inherit;
font-size: var(--wa-font-size-m);
font-weight: inherit;
}
.checkbox::part(base) {

View File

@@ -1,11 +0,0 @@
export class WaBlurEvent extends Event {
constructor() {
super('wa-blur', { bubbles: true, cancelable: false, composed: true });
}
}
declare global {
interface GlobalEventHandlersEventMap {
'wa-blur': WaBlurEvent;
}
}

View File

@@ -1,11 +0,0 @@
export class WaChangeEvent extends Event {
constructor() {
super('wa-change', { bubbles: true, cancelable: false, composed: true });
}
}
declare global {
interface GlobalEventHandlersEventMap {
'wa-change': WaChangeEvent;
}
}

View File

@@ -1,11 +0,0 @@
export class WaCloseEvent extends Event {
constructor() {
super('wa-close', { bubbles: true, cancelable: false, composed: true });
}
}
declare global {
interface GlobalEventHandlersEventMap {
'wa-close': WaCloseEvent;
}
}

View File

@@ -2,20 +2,15 @@ export type { WaAfterCollapseEvent } from './after-collapse.js';
export type { WaAfterExpandEvent } from './after-expand.js';
export type { WaAfterHideEvent } from './after-hide.js';
export type { WaAfterShowEvent } from './after-show.js';
export type { WaBlurEvent } from './blur.js';
export type { WaCancelEvent } from './cancel.js';
export type { WaChangeEvent } from './change.js';
export type { WaClearEvent } from './clear.js';
export type { WaCloseEvent } from './close.js';
export type { WaCollapseEvent } from './collapse.js';
export type { WaCopyEvent } from './copy.js';
export type { WaErrorEvent } from './error.js';
export type { WaExpandEvent } from './expand.js';
export type { WaFinishEvent } from './finish.js';
export type { WaFocusEvent } from './focus.js';
export type { WaHideEvent } from './hide.js';
export type { WaHoverEvent } from './hover.js';
export type { WaInputEvent } from './input.js';
export type { WaInvalidEvent } from './invalid.js';
export type { WaLazyChangeEvent } from './lazy-change.js';
export type { WaLazyLoadEvent } from './lazy-load.js';

View File

@@ -1,11 +0,0 @@
export class WaFocusEvent extends Event {
constructor() {
super('wa-focus', { bubbles: true, cancelable: false, composed: true });
}
}
declare global {
interface GlobalEventHandlersEventMap {
'wa-focus': WaFocusEvent;
}
}

Some files were not shown because too many files have changed in this diff Show More