mirror of
https://github.com/shoelace-style/webawesome.git
synced 2026-01-19 07:29:14 +00:00
Compare commits
2 Commits
remixing
...
theme-pale
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be3b595e99 | ||
|
|
1ad43b3b7d |
@@ -1 +0,0 @@
|
||||
["red", "yellow", "green", "teal", "blue", "indigo", "violet", "gray"]
|
||||
@@ -1 +0,0 @@
|
||||
export { default as default } from '../../src/styles/color/palettes.js';
|
||||
@@ -16,7 +16,7 @@
|
||||
{# Docs styles #}
|
||||
<link rel="stylesheet" href="/assets/styles/docs.css" />
|
||||
</head>
|
||||
<body class="layout-{{ layout | stripExtension }}{{ ' page-wide' if wide }}">
|
||||
<body class="layout-{{ layout | stripExtension }}">
|
||||
<!-- 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">
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
<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>
|
||||
@@ -1,24 +0,0 @@
|
||||
{%- if not stylesheets %}{% set stylesheets = [stylesheet] %}{% endif -%}
|
||||
|
||||
<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
|
||||
{% for stylesheet in stylesheets -%}
|
||||
<link rel="stylesheet" href="{% cdnUrl stylesheet %}" />{% if not loop.last %}
|
||||
{% endif %}{% endfor %}
|
||||
```
|
||||
</wa-tab-panel>
|
||||
<wa-tab-panel name="css">
|
||||
|
||||
Simply add the following code at the top of your CSS file:
|
||||
```css
|
||||
{% for stylesheet in stylesheets -%}
|
||||
@import url('{% cdnUrl stylesheet %}');{% if not loop.last %}
|
||||
{% endif %}{% endfor %}
|
||||
```
|
||||
</wa-tab-panel>
|
||||
</wa-tab-group>
|
||||
41
docs/_includes/palette.njk
Normal file
41
docs/_includes/palette.njk
Normal file
@@ -0,0 +1,41 @@
|
||||
<div class="color-palette">
|
||||
<template shadowrootmode="open">
|
||||
<link href="/assets/styles/docs.css" rel="stylesheet" />
|
||||
<link id="theme" href="/dist/styles/themes/{{ themeId }}.css" rel="stylesheet" />
|
||||
<link id="palette" href="/dist/styles/color/{{ palette }}.css" rel="stylesheet" />
|
||||
<ul class="color-group">
|
||||
<li class="color-preview">
|
||||
<div class="swatch" style="background-color: var(--wa-color-brand-fill-loud)"></div>
|
||||
</li>
|
||||
<li class="color-preview">
|
||||
<div class="swatch" style="background-color: var(--wa-color-neutral-fill-loud)"></div>
|
||||
</li>
|
||||
<li class="color-preview">
|
||||
<div class="swatch" style="background-color: var(--wa-color-success-fill-loud)"></div>
|
||||
</li>
|
||||
<li class="color-preview">
|
||||
<div class="swatch" style="background-color: var(--wa-color-warning-fill-loud)"></div>
|
||||
</li>
|
||||
<li class="color-preview">
|
||||
<div class="swatch" style="background-color: var(--wa-color-danger-fill-loud)"></div>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="color-group">
|
||||
<li class="color-preview">
|
||||
<div class="swatch" style="background-color: var(--wa-color-brand-fill-normal)"></div>
|
||||
</li>
|
||||
<li class="color-preview">
|
||||
<div class="swatch" style="background-color: var(--wa-color-neutral-fill-normal)"></div>
|
||||
</li>
|
||||
<li class="color-preview">
|
||||
<div class="swatch" style="background-color: var(--wa-color-success-fill-normal)"></div>
|
||||
</li>
|
||||
<li class="color-preview">
|
||||
<div class="swatch" style="background-color: var(--wa-color-warning-fill-normal)"></div>
|
||||
</li>
|
||||
<li class="color-preview">
|
||||
<div class="swatch" style="background-color: var(--wa-color-danger-fill-normal)"></div>
|
||||
</li>
|
||||
</ul>
|
||||
</template>
|
||||
</div>
|
||||
@@ -25,7 +25,6 @@
|
||||
'utilities': 'Style Utilities',
|
||||
'layout': 'Layout',
|
||||
'patterns': 'Patterns',
|
||||
'palettes': 'Color Palettes',
|
||||
'tokens': 'Design Tokens'
|
||||
} %}
|
||||
{% include 'sidebar-group.njk' %}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
{% 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>
|
||||
@@ -1,119 +0,0 @@
|
||||
{% 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 %}
|
||||
@@ -3,37 +3,52 @@
|
||||
{# {% set forceTheme = page.fileSlug %} #}
|
||||
|
||||
{% extends '../_includes/base.njk' %}
|
||||
|
||||
{% set themeId = page.fileSlug %}
|
||||
{% if themeId == 'remixed' -%}
|
||||
{% set themeId = 'default' %}
|
||||
{% endif -%}
|
||||
{% set palette = defaultPalette %}
|
||||
{% set palettes = ['default', 'classic', 'rudimentary', 'bright', 'anodized', 'elegant', 'vogue'] %}
|
||||
|
||||
{% block header %}
|
||||
<iframe src='/docs/themes/{{ themeId }}/demo.html' id="demo"></iframe>
|
||||
|
||||
{% if palette -%}
|
||||
<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 %}
|
||||
{% endif %}
|
||||
<iframe id="theme-demo" src='{{ page.url }}demo.html'></iframe>
|
||||
|
||||
<p>
|
||||
<wa-select label="Color palette:" value="{{ defaultPalette }}" id="palette-picker">
|
||||
{% for p in palettes -%}
|
||||
<wa-option value="{{ p }}">{{ p | capitalize }}</wa-option>
|
||||
{%- endfor %}
|
||||
</wa-select>
|
||||
<script type="module" src="/docs/themes/palette-picker.js"></script>
|
||||
</p>
|
||||
{% include 'palette.njk' %}
|
||||
|
||||
<br>
|
||||
{% endblock %}
|
||||
|
||||
{% block afterContent %}
|
||||
{% markdown %}
|
||||
{%- if page.fileSlug != 'remixed' %}
|
||||
## How to use this theme
|
||||
|
||||
You can import this theme from the Web Awesome CDN.
|
||||
|
||||
{% set stylesheet = 'styles/themes/' + themeId + '.css' %}
|
||||
{% include 'import-stylesheet-code.md.njk' %}
|
||||
{% endif %}
|
||||
<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 'styles/themes/' + page.fileSlug + '.css' %}" />
|
||||
```
|
||||
</wa-tab-panel>
|
||||
<wa-tab-panel name="css">
|
||||
|
||||
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>
|
||||
|
||||
## Dark mode
|
||||
|
||||
|
||||
@@ -105,23 +105,6 @@ 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));
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ function updateResults(input) {
|
||||
}
|
||||
}
|
||||
|
||||
document.documentElement.addEventListener('input', e => {
|
||||
document.documentElement.addEventListener('wa-input', e => {
|
||||
if (e.target?.matches('#block-filter wa-input')) {
|
||||
updateResults(e.target);
|
||||
}
|
||||
|
||||
@@ -47,6 +47,22 @@ 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;
|
||||
|
||||
@@ -28,21 +28,12 @@ export class ThemeAspect {
|
||||
});
|
||||
|
||||
// Listen for selections
|
||||
document.addEventListener('change', event => {
|
||||
document.addEventListener('wa-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() {
|
||||
|
||||
@@ -333,7 +333,6 @@ 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);
|
||||
@@ -384,23 +383,16 @@ 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-color: var(--wa-color-neutral-border-normal); */
|
||||
border-color: transparent;
|
||||
border-style: var(--wa-border-style);
|
||||
border-width: var(--wa-border-width-s);
|
||||
border-radius: var(--wa-border-radius-m);
|
||||
box-sizing: border-box;
|
||||
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;
|
||||
}
|
||||
line-height: 2.5;
|
||||
height: 2.5em;
|
||||
padding-inline: var(--wa-space-xs);
|
||||
|
||||
wa-copy-button {
|
||||
position: absolute;
|
||||
@@ -430,32 +422,33 @@ wa-page > main:has(> .index-grid) {
|
||||
}
|
||||
}
|
||||
|
||||
table.colors {
|
||||
thead {
|
||||
th {
|
||||
text-align: center;
|
||||
padding-block: 0;
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
tr {
|
||||
border: none;
|
||||
&:hover {
|
||||
background: transparent;
|
||||
}
|
||||
}
|
||||
.color-name {
|
||||
font-weight: var(--wa-font-weight-semibold);
|
||||
margin-block-end: var(--wa-space-2xs);
|
||||
}
|
||||
|
||||
th {
|
||||
width: 0;
|
||||
vertical-align: middle;
|
||||
text-align: right;
|
||||
}
|
||||
.color-group {
|
||||
align-items: start;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.25em;
|
||||
|
||||
td {
|
||||
padding-inline: var(--wa-space-3xs);
|
||||
padding-block: var(--wa-space-s);
|
||||
}
|
||||
&:where(ul) {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
+ * {
|
||||
margin-block-start: var(--wa-space-xl);
|
||||
}
|
||||
|
||||
+ & {
|
||||
margin-block-start: var(--wa-space-2xs);
|
||||
}
|
||||
}
|
||||
.color-preview {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
/* Layout Examples */
|
||||
@@ -521,7 +514,7 @@ table.colors {
|
||||
}
|
||||
}
|
||||
|
||||
.page-wide {
|
||||
.layout-theme {
|
||||
wa-page > main {
|
||||
max-width: 140ch;
|
||||
|
||||
@@ -529,9 +522,7 @@ table.colors {
|
||||
max-width: 80ch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.layout-theme {
|
||||
iframe {
|
||||
width: 100%;
|
||||
min-height: 16lh;
|
||||
|
||||
@@ -77,9 +77,9 @@ This example demonstrates all of the baked-in animations and easings. Animations
|
||||
easingName.appendChild(option);
|
||||
});
|
||||
|
||||
animationName.addEventListener('change', () => (animation.name = animationName.value));
|
||||
easingName.addEventListener('change', () => (animation.easing = easingName.value));
|
||||
playbackRate.addEventListener('input', () => (animation.playbackRate = playbackRate.value));
|
||||
animationName.addEventListener('wa-change', () => (animation.name = animationName.value));
|
||||
easingName.addEventListener('wa-change', () => (animation.easing = easingName.value));
|
||||
playbackRate.addEventListener('wa-input', () => (animation.playbackRate = playbackRate.value));
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -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('change', () => {
|
||||
toggle.addEventListener('wa-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('change', () => {
|
||||
aspectRatio.addEventListener('wa-change', () => {
|
||||
carousel.style.setProperty('--aspect-ratio', aspectRatio.value);
|
||||
});
|
||||
})();
|
||||
|
||||
@@ -73,4 +73,4 @@ if (name_search.value) {
|
||||
filterByName(name_search.value);
|
||||
}
|
||||
|
||||
name_search_group.addEventListener('input', e => filterByName(name_search.value));
|
||||
name_search_group.addEventListener('wa-input', e => filterByName(name_search.value));
|
||||
|
||||
@@ -82,7 +82,7 @@ Use the `setCustomValidity()` method to set a custom validation message. This wi
|
||||
});
|
||||
|
||||
// Update validity on change
|
||||
checkbox.addEventlistener('change', () => {
|
||||
checkbox.addEventListener('wa-change', () => {
|
||||
checkbox.setCustomValidity(checkbox.checked ? '' : errorMessage);
|
||||
});
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ icon: format-bytes
|
||||
const formatter = container.querySelector('wa-format-bytes');
|
||||
const input = container.querySelector('wa-input');
|
||||
|
||||
input.addEventListener('input', () => (formatter.value = input.value || 0));
|
||||
input.addEventListener('wa-input', () => (formatter.value = input.value || 0));
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
@@ -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('input', () => (formatter.value = input.value || 0));
|
||||
input.addEventListener('wa-input', () => (formatter.value = input.value || 0));
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
@@ -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('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));
|
||||
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));
|
||||
</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('change', () => (popup.active = active.checked));
|
||||
active.addEventListener('wa-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('change', () => (popup.placement = select.value));
|
||||
select.addEventListener('wa-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('input', () => (popup.distance = distance.value));
|
||||
distance.addEventListener('wa-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('input', () => (popup.skidding = skidding.value));
|
||||
skidding.addEventListener('wa-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('change', () => (popup.placement = placement.value));
|
||||
arrowPlacement.addEventListener('change', () => (popup.arrowPlacement = arrowPlacement.value));
|
||||
arrow.addEventListener('change', () => (popup.arrow = arrow.checked));
|
||||
placement.addEventListener('wa-change', () => (popup.placement = placement.value));
|
||||
arrowPlacement.addEventListener('wa-change', () => (popup.arrowPlacement = arrowPlacement.value));
|
||||
arrow.addEventListener('wa-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('change', () => (popup.sync = sync.value));
|
||||
sync.addEventListener('wa-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('change', () => (popup.strategy = fixed.checked ? 'fixed' : 'absolute'));
|
||||
fixed.addEventListener('wa-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('change', () => (popup.flip = flip.checked));
|
||||
flip.addEventListener('wa-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('change', () => (popup.shift = shift.checked));
|
||||
shift.addEventListener('wa-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('change', () => (popup.autoSize = autoSize.checked ? 'both' : ''));
|
||||
autoSize.addEventListener('wa-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('input', () => (popup.distance = distance.value));
|
||||
skidding.addEventListener('input', () => (popup.skidding = skidding.value));
|
||||
hoverBridge.addEventListener('change', () => (popup.hoverBridge = hoverBridge.checked));
|
||||
distance.addEventListener('wa-input', () => (popup.distance = distance.value));
|
||||
skidding.addEventListener('wa-input', () => (popup.skidding = skidding.value));
|
||||
hoverBridge.addEventListener('wa-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('change', () => {
|
||||
enabled.addEventListener('wa-change', () => {
|
||||
popup.active = enabled.checked;
|
||||
});
|
||||
|
||||
|
||||
@@ -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('input', () => (qrCode.value = input.value));
|
||||
input.addEventListener('wa-input', () => (qrCode.value = input.value));
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -65,7 +65,7 @@ The size of [Radios](/docs/components/radio) and [Radio Buttons](/docs/component
|
||||
<script>
|
||||
const radioGroup = document.querySelector('.radio-group-size');
|
||||
|
||||
radioGroup.addEventlistener('change', () => {
|
||||
radioGroup.addEventListener('wa-change', () => {
|
||||
radioGroup.size = radioGroup.value;
|
||||
});
|
||||
</script>
|
||||
@@ -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('change', () => {
|
||||
form.addEventListener('wa-change', () => {
|
||||
const isValid = radioGroup.value === '3';
|
||||
radioGroup.setCustomValidity(isValid ? '' : errorMessage);
|
||||
});
|
||||
|
||||
@@ -420,7 +420,5 @@ This can be hard to conceptualize, so heres a fairly large example showing how l
|
||||
//
|
||||
// TODO - remove once we switch to the Popover API
|
||||
//
|
||||
customElements.whenDefined('wa-select').then(() => {
|
||||
document.querySelectorAll('wa-code-demo [slot="preview"] wa-select').forEach(select => select.hoist = true);
|
||||
});
|
||||
document.querySelectorAll('wa-code-demo [slot="preview"] wa-select').forEach(select => select.hoist = true);
|
||||
</script>
|
||||
|
||||
@@ -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('change', () => (splitPanel.primary = select.value));
|
||||
select.addEventListener('wa-change', () => (splitPanel.primary = select.value));
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
@@ -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('change', () => {
|
||||
selectionMode.addEventListener('wa-change', () => {
|
||||
tree.querySelectorAll('wa-tree-item').forEach(item => (item.selected = false));
|
||||
tree.selection = selectionMode.value;
|
||||
});
|
||||
|
||||
@@ -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="glossy">Glossy</wa-option>
|
||||
<wa-option data-alpha="remove" value="glassy">Glassy</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('change', () => {
|
||||
variantInput.addEventListener('wa-change', () => {
|
||||
iconList.dataset.variant = variantInput.value;
|
||||
});
|
||||
|
||||
@@ -823,7 +823,7 @@ hasOutline: false
|
||||
});
|
||||
|
||||
// Filter as the user types
|
||||
input.addEventListener('input', () => {
|
||||
input.addEventListener('wa-input', () => {
|
||||
clearTimeout(inputTimeout);
|
||||
inputTimeout = setTimeout(() => {
|
||||
[...iconList.children].map(item => {
|
||||
@@ -886,7 +886,7 @@ hasOutline: false
|
||||
case 'active':
|
||||
colorPalette = 'rudimentary';
|
||||
break;
|
||||
case 'glossy':
|
||||
case 'glassy':
|
||||
colorPalette = 'elegant';
|
||||
break;
|
||||
case 'premium':
|
||||
@@ -1049,7 +1049,7 @@ hasOutline: false
|
||||
case 'brutalist':
|
||||
case 'classic':
|
||||
case 'awesome':
|
||||
case 'glossy':
|
||||
case 'glassy':
|
||||
case 'active':
|
||||
assetFolder = themeSelect.value;
|
||||
break;
|
||||
@@ -1084,10 +1084,10 @@ hasOutline: false
|
||||
el.classList.add(`wa-theme-${theme}-${colorMode}`);
|
||||
}
|
||||
|
||||
colorModeSelect.addEventlistener('change', setColorMode);
|
||||
colorModeSelect.addEventListener('wa-change', setColorMode);
|
||||
|
||||
// Theme Switcher
|
||||
themeSelect.addEventlistener('change', event => {
|
||||
themeSelect.addEventListener('wa-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('change', event => {
|
||||
colorSelect.addEventListener('wa-change', event => {
|
||||
const colorPalette = event.target.value;
|
||||
|
||||
colorStylesheet.href = `/dist/styles/themes/color/${colorPalette}.css`;
|
||||
});
|
||||
|
||||
// Brand Color
|
||||
brandColor.addEventlistener('change', event => {
|
||||
brandColor.addEventListener('wa-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('change', event => {
|
||||
logoSelector.addEventListener('wa-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 'glossy':
|
||||
case 'glassy':
|
||||
presetLogoIcons = ['raindrops', 'citrus-slice', 'lighthouse', 'kiwi-bird'];
|
||||
break;
|
||||
case 'active':
|
||||
@@ -1279,21 +1279,21 @@ hasOutline: false
|
||||
})
|
||||
}
|
||||
|
||||
themeSelect.addEventlistener('change', setLogoIcons);
|
||||
themeSelect.addEventListener('wa-change', setLogoIcons);
|
||||
|
||||
// Project Name
|
||||
container.querySelector('[name="project-name"]').addEventListener('input', event => {
|
||||
container.querySelector('[name="project-name"]').addEventListener('wa-input', event => {
|
||||
previewContainer.querySelector("#project-name").innerText = event.target.value || event.target.getAttribute("placeholder")
|
||||
})
|
||||
|
||||
// Heading font weight
|
||||
resetHeadingFontWeightValue()
|
||||
fontWeightHeading.addEventListener('input', event => {
|
||||
fontWeightHeading.addEventListener('wa-input', event => {
|
||||
document.documentElement.style.setProperty('--wa-font-weight-heading', event.target.value);
|
||||
});
|
||||
|
||||
// Heading text
|
||||
fontFamilyHeading.addEventlistener('change', event => {
|
||||
fontFamilyHeading.addEventListener('wa-change', event => {
|
||||
let fontFamily;
|
||||
switch (event.target.value) {
|
||||
case 'assistant':
|
||||
@@ -1351,7 +1351,7 @@ hasOutline: false
|
||||
})
|
||||
|
||||
// Body text
|
||||
fontFamilyBody.addEventlistener('change', event => {
|
||||
fontFamilyBody.addEventListener('wa-change', event => {
|
||||
let fontFamily;
|
||||
switch (event.target.value) {
|
||||
case 'assistant':
|
||||
@@ -1404,7 +1404,7 @@ hasOutline: false
|
||||
|
||||
// Body font weight
|
||||
resetBodyFontWeightValue()
|
||||
fontWeightBody.addEventListener('input', event => {
|
||||
fontWeightBody.addEventListener('wa-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('change', event => {
|
||||
themeSelect.addEventListener('wa-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('change', event => {
|
||||
iconFamily.addEventListener('wa-change', event => {
|
||||
useFaIcons();
|
||||
showIconStyleOptions();
|
||||
});
|
||||
|
||||
// Swaps icons based on the selected Icon Style
|
||||
iconStyle.addEventlistener('change', useFaIcons);
|
||||
iconStyle.addEventListener('wa-change', useFaIcons);
|
||||
|
||||
|
||||
// Corners
|
||||
container.querySelector('[name="corners"]').addEventListener('input', event => {
|
||||
container.querySelector('[name="corners"]').addEventListener('wa-input', event => {
|
||||
document.documentElement.style.setProperty('--wa-border-radius-scale', `${event.target.value}`);
|
||||
});
|
||||
|
||||
// Border width
|
||||
container.querySelector('[name="border-width"]').addEventListener('input', event => {
|
||||
container.querySelector('[name="border-width"]').addEventListener('wa-input', event => {
|
||||
document.documentElement.style.setProperty('--wa-border-width-scale', `${event.target.value / 16}`);
|
||||
});
|
||||
|
||||
// Border style
|
||||
borderStyle.addEventListener('input', event => {
|
||||
borderStyle.addEventListener('wa-input', event => {
|
||||
document.documentElement.style.setProperty('--wa-border-style', event.target.value);
|
||||
});
|
||||
|
||||
// Spacing style
|
||||
spacing.addEventListener('input', event => {
|
||||
spacing.addEventListener('wa-input', event => {
|
||||
document.documentElement.style.setProperty('--wa-space-scale', `${event.target.value}`);
|
||||
});
|
||||
|
||||
|
||||
@@ -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('input', () => {
|
||||
input.addEventListener('wa-input', () => {
|
||||
if (input.value === 'webawesome') {
|
||||
input.setCustomValidity('');
|
||||
} else {
|
||||
|
||||
@@ -4,7 +4,6 @@ description: Callouts are used to display important messages inline.
|
||||
component: callout
|
||||
icon: callout
|
||||
snippets: '.wa-callout'
|
||||
noAlpha: true
|
||||
---
|
||||
|
||||
```html {.example}
|
||||
|
||||
@@ -33,39 +33,3 @@ 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>
|
||||
```
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
title: Anodized
|
||||
isPro: true
|
||||
tags: pro
|
||||
---
|
||||
@@ -1,3 +0,0 @@
|
||||
---
|
||||
title: Bright
|
||||
---
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: Classic
|
||||
description: The original Shoelace color palette.
|
||||
---
|
||||
@@ -1,4 +0,0 @@
|
||||
---
|
||||
title: Default
|
||||
description: This is the palette used in the default theme.
|
||||
---
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
title: Elegant
|
||||
isPro: true
|
||||
tags: pro
|
||||
---
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
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
|
||||
---
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
title: Mild
|
||||
isPro: true
|
||||
tags: pro
|
||||
---
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
title: Natural
|
||||
isPro: true
|
||||
tags: pro
|
||||
---
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"layout": "palette.njk",
|
||||
"tags": ["palettes", "palette"],
|
||||
"eleventyComputed": {
|
||||
"snippet": ".wa-palette-{{ page.fileSlug }}",
|
||||
"icon": "palette"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
title: Rudimentary
|
||||
isPro: true
|
||||
tags: pro
|
||||
---
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
title: Vogue
|
||||
isPro: true
|
||||
tags: pro
|
||||
---
|
||||
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"layout": "block.njk",
|
||||
"tags": ["patterns"],
|
||||
"wide": true,
|
||||
"noAlpha": true
|
||||
}
|
||||
|
||||
@@ -12,29 +12,12 @@ 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
|
||||
|
||||
|
||||
2
docs/docs/themes/active.md
vendored
2
docs/docs/themes/active.md
vendored
@@ -3,5 +3,5 @@ title: Active
|
||||
description: Energetic and tactile, always in motion.
|
||||
isPro: true
|
||||
tags: pro
|
||||
palette: rudimentary
|
||||
defaultPalette: rudimentary
|
||||
---
|
||||
|
||||
2
docs/docs/themes/awesome.md
vendored
2
docs/docs/themes/awesome.md
vendored
@@ -2,5 +2,5 @@
|
||||
title: Awesome
|
||||
description: Punchy and vibrant, the rockstar of themes.
|
||||
order: 0.2
|
||||
palette: bright
|
||||
defaultPalette: bright
|
||||
---
|
||||
|
||||
2
docs/docs/themes/brutalist.md
vendored
2
docs/docs/themes/brutalist.md
vendored
@@ -3,5 +3,5 @@ title: Brutalist
|
||||
description: Sharp, square, and unapologetically bold.
|
||||
isPro: true
|
||||
tags: pro
|
||||
palette: default
|
||||
defaultPalette: default
|
||||
---
|
||||
|
||||
2
docs/docs/themes/classic.md
vendored
2
docs/docs/themes/classic.md
vendored
@@ -2,5 +2,5 @@
|
||||
title: Classic
|
||||
description: Timeless elegance that never goes out of style.
|
||||
order: 0.1
|
||||
palette: classic
|
||||
defaultPalette: classic
|
||||
---
|
||||
|
||||
2
docs/docs/themes/default.md
vendored
2
docs/docs/themes/default.md
vendored
@@ -2,5 +2,5 @@
|
||||
title: Default
|
||||
description: Your trusty companion, like a perfectly broken-in pair of jeans.
|
||||
order: 0
|
||||
palette: default
|
||||
defaultPalette: default
|
||||
---
|
||||
|
||||
60
docs/docs/themes/demo.njk
vendored
60
docs/docs/themes/demo.njk
vendored
@@ -10,19 +10,20 @@ 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 id="theme" rel="stylesheet" href="/dist/styles/themes/{{ theme.fileSlug }}.css" />
|
||||
<link rel="stylesheet" href="/docs/themes/showcase.css" />
|
||||
|
||||
{% set content %}
|
||||
<header>
|
||||
{% include 'breadcrumbs.njk' %}
|
||||
<h1 class="title">{{ theme.data.title }}</h1>
|
||||
<p id="mix_and_match" hidden class="wa-size-s"></p>
|
||||
<wa-button href="/docs/themes/remixed/?base={{ theme.fileSlug }}" class="remix-link" size="small" variant="brand" target="_parent">
|
||||
<wa-icon name="merge"></wa-icon>
|
||||
Remix
|
||||
</wa-button>
|
||||
<p>{% include 'status.njk' %}</p>
|
||||
<p id="theme-showcase-description">{{ theme.data.description | inlineMarkdown | safe }}</p>
|
||||
</header>
|
||||
@@ -37,50 +38,3 @@ eleventyComputed:
|
||||
{{ content | safe }}
|
||||
</div>
|
||||
</wa-image-comparer>
|
||||
|
||||
<script>
|
||||
function updateTheme() {
|
||||
let params = new URLSearchParams(window.location.search);
|
||||
let script = document.currentScript;
|
||||
const stylesheetURLs = {
|
||||
color: id => `/dist/styles/themes/${ id }/color.css`,
|
||||
palette: id => `/dist/styles/color/${ id }.css`,
|
||||
typography: id => `/dist/styles/themes/${ id }/typography.css`
|
||||
};
|
||||
const icons = {
|
||||
color: '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>`);
|
||||
}
|
||||
}
|
||||
|
||||
let isRemixed = msgs.length > 0;
|
||||
document.documentElement.classList.toggle("remixed", isRemixed);
|
||||
|
||||
for (let p of mix_and_match) {
|
||||
p.hidden = !isRemixed;
|
||||
if (isRemixed) {
|
||||
let icon =
|
||||
p.innerHTML = `<strong><wa-icon name="merge"></wa-icon> Remixed</strong><br> ` + msgs.map(msg => `<wa-badge appearance=outlined>
|
||||
${ msg }</wa-badge>`).join(' ');
|
||||
}
|
||||
}
|
||||
}
|
||||
updateTheme();
|
||||
</script>
|
||||
|
||||
8
docs/docs/themes/glassy.md
vendored
Normal file
8
docs/docs/themes/glassy.md
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
---
|
||||
title: Glassy
|
||||
description: Smooth, sleek, and reflective.
|
||||
isPro: true
|
||||
tags: pro
|
||||
noAlpha: true
|
||||
defaultPalette: elegant
|
||||
---
|
||||
7
docs/docs/themes/glossy.md
vendored
7
docs/docs/themes/glossy.md
vendored
@@ -1,7 +0,0 @@
|
||||
---
|
||||
title: Glossy
|
||||
description: Bustling with plenty of luster and shine.
|
||||
isPro: true
|
||||
tags: pro
|
||||
palette: elegant
|
||||
---
|
||||
85
docs/docs/themes/matter.md
vendored
85
docs/docs/themes/matter.md
vendored
@@ -1,85 +0,0 @@
|
||||
---
|
||||
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);
|
||||
});
|
||||
```
|
||||
2
docs/docs/themes/mellow.md
vendored
2
docs/docs/themes/mellow.md
vendored
@@ -3,5 +3,5 @@ title: Mellow
|
||||
description: Soft and soothing, like a lazy Sunday morning.
|
||||
isPro: true
|
||||
tags: pro
|
||||
palette: natural
|
||||
defaultPalette: natural
|
||||
---
|
||||
|
||||
18
docs/docs/themes/palette-picker.js
vendored
Normal file
18
docs/docs/themes/palette-picker.js
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
let palettePicker = document.getElementById('palette-picker');
|
||||
let paletteDisplay = palettePicker.parentNode.nextElementSibling;
|
||||
let themeDemo = document.getElementById('theme-demo');
|
||||
|
||||
palettePicker.addEventListener('wa-change', function () {
|
||||
let palette = palettePicker.value;
|
||||
let paletteStylesheet = paletteDisplay.shadowRoot.getElementById('palette');
|
||||
paletteStylesheet.href = paletteStylesheet.href.replace(/[a-z-]+.css/, palette + '.css');
|
||||
let demoPaletteStylesheet = themeDemo.contentDocument.getElementById('palette');
|
||||
let paletteUrl = `/dist/styles/color/${palette}.css`;
|
||||
|
||||
if (demoPaletteStylesheet) {
|
||||
demoPaletteStylesheet.href = paletteUrl;
|
||||
} else {
|
||||
let themeStylesheet = themeDemo.contentDocument.getElementById('theme');
|
||||
themeStylesheet.insertAdjacentHTML('afterend', `<link id="palette" rel="stylesheet" href="${paletteUrl}">`);
|
||||
}
|
||||
});
|
||||
5
docs/docs/themes/playful.md
vendored
5
docs/docs/themes/playful.md
vendored
@@ -1,7 +1,8 @@
|
||||
---
|
||||
title: Playful
|
||||
description: Cheerful and engaging, like a playground on screen.
|
||||
description: Fun, colorful, and full of personality.
|
||||
isPro: true
|
||||
tags: pro
|
||||
palette: rudimentary
|
||||
noAlpha: true
|
||||
defaultPalette: rudimentary
|
||||
---
|
||||
|
||||
2
docs/docs/themes/premium.md
vendored
2
docs/docs/themes/premium.md
vendored
@@ -3,5 +3,5 @@ title: Premium
|
||||
description: The ultimate in sophistication and style.
|
||||
isPro: true
|
||||
tags: pro
|
||||
palette: anodized
|
||||
defaultPalette: anodized
|
||||
---
|
||||
|
||||
138
docs/docs/themes/remixed/index.js
vendored
138
docs/docs/themes/remixed/index.js
vendored
@@ -1,138 +0,0 @@
|
||||
let params = { base: 'default', palette: '', color: '', typography: '' };
|
||||
|
||||
// Find usage code snippet and prepare it
|
||||
let snippets = document.querySelectorAll('#remixed-usage ~ wa-tab-group pre > code');
|
||||
let copyButtons = document.querySelectorAll('#remixed-usage ~ wa-tab-group pre > wa-copy-button');
|
||||
let codeExamples = [];
|
||||
|
||||
for (let snippet of snippets) {
|
||||
let tokens = [...snippet.children];
|
||||
let base = tokens.shift();
|
||||
let [palette, color, typography] = tokens;
|
||||
|
||||
codeExamples.push({ snippet, base, palette, color, typography });
|
||||
|
||||
// Remove non-base tokens
|
||||
for (let token of tokens) {
|
||||
let whitespace = token.previousSibling;
|
||||
|
||||
if (whitespace.nodeType === Node.TEXT_NODE) {
|
||||
// Move whitespace to beginning of node
|
||||
token.prepend(token.previousSibling);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read URL params and apply them. This facilitates permalinks.
|
||||
if (location.search) {
|
||||
let urlParams = new URLSearchParams(location.search);
|
||||
|
||||
for (let aspect in params) {
|
||||
if (urlParams.has(aspect)) {
|
||||
params[aspect] = urlParams.get(aspect);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const selects = Object.fromEntries(
|
||||
[...document.querySelectorAll('#mix_and_match wa-select')].map(select => [select.getAttribute('name'), select]),
|
||||
);
|
||||
|
||||
document.querySelector('#mix_and_match').addEventListener(
|
||||
'change',
|
||||
function (event) {
|
||||
for (let name in selects) {
|
||||
params[name] = selects[name].value;
|
||||
}
|
||||
|
||||
render();
|
||||
},
|
||||
{ capture: true },
|
||||
);
|
||||
|
||||
function hasOverride(name) {
|
||||
if (!params[name]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (name === 'palette') {
|
||||
return params[name] !== defaultPalettes[params.base];
|
||||
}
|
||||
|
||||
if (name !== 'base') {
|
||||
return params[name] !== params.base;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function render() {
|
||||
let demoUrl = new URL(`/docs/themes/${params.base}/demo.html`, location);
|
||||
let pageParams = new URLSearchParams(params);
|
||||
|
||||
for (let aspect in params) {
|
||||
if (aspect !== 'base' && params[aspect]) {
|
||||
demoUrl.searchParams.set(aspect, params[aspect]);
|
||||
}
|
||||
|
||||
if (!params[aspect] || (aspect === 'base' && params[aspect] === 'default') || !hasOverride(aspect)) {
|
||||
pageParams.delete(aspect);
|
||||
}
|
||||
|
||||
// Output code snippet
|
||||
if (aspect !== 'base') {
|
||||
for (let codeExample of codeExamples) {
|
||||
let token = codeExample[aspect];
|
||||
let value = params[aspect];
|
||||
|
||||
if (hasOverride(aspect)) {
|
||||
// Update code example
|
||||
let valueToken = [...token.querySelectorAll('.code-attr-value, .code-url')].pop();
|
||||
valueToken.textContent = replaceStyleSheetURL(valueToken.textContent, aspect, value);
|
||||
|
||||
// Add code example to <pre>
|
||||
codeExample.snippet.append(token);
|
||||
} else {
|
||||
token.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update selects
|
||||
if (selects[aspect].value === undefined) {
|
||||
selects[aspect].setAttribute('value', params[aspect]);
|
||||
} else {
|
||||
selects[aspect].value = params[aspect];
|
||||
}
|
||||
}
|
||||
|
||||
// Update code snippet copy buttons
|
||||
for (let copyButton of copyButtons) {
|
||||
copyButton.value = copyButton.nextElementSibling.textContent;
|
||||
}
|
||||
|
||||
// Update demo URL
|
||||
demo.src = demoUrl;
|
||||
|
||||
// Update page URL. If there’s already a search, replace it.
|
||||
// We don’t want to clog the user’s history while they iterate
|
||||
let historyAction = location.search ? 'replaceState' : 'pushState';
|
||||
history[historyAction](null, '', `?${pageParams}`);
|
||||
}
|
||||
|
||||
const regexes = {
|
||||
base: /\/themes\/([a-z-]+)\.css/,
|
||||
palette: /\/color\/([a-z-]+)\.css/,
|
||||
color: /\/themes\/([a-z-]+)\/color\.css/,
|
||||
typography: /\/themes\/([a-z-]+)\/typography\.css/,
|
||||
};
|
||||
|
||||
function replaceStyleSheetURL(url, name, value) {
|
||||
let regex = regexes[name];
|
||||
return url.replace(regex, (match, oldValue) => {
|
||||
return match.replace(oldValue, value);
|
||||
});
|
||||
}
|
||||
|
||||
globalThis.params = params;
|
||||
render();
|
||||
84
docs/docs/themes/remixed/index.njk
vendored
84
docs/docs/themes/remixed/index.njk
vendored
@@ -1,84 +0,0 @@
|
||||
---
|
||||
title: Remixed
|
||||
description: TODO
|
||||
isPro: true
|
||||
tags: pro
|
||||
---
|
||||
<link rel="stylesheet" href="{{ page.url }}/style.css">
|
||||
{% block header %}
|
||||
<script>
|
||||
globalThis.defaultPalettes = {
|
||||
{% for theme in collections.theme | sort -%}
|
||||
"{{ theme.fileSlug }}": "{{ theme.data.palette }}",
|
||||
{%- endfor %}
|
||||
};
|
||||
</script>
|
||||
<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="base" label="Base:" size="small" value="default">
|
||||
<wa-icon name="brush" slot="prefix" variant="regular"></wa-icon>
|
||||
{% for theme in collections.theme | sort %}
|
||||
{% if theme.fileSlug !== page.fileSlug %}
|
||||
<wa-option value="{{ theme.fileSlug }}" data-palette="{{ theme.data.palette }}">{{ 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 %}
|
||||
<wa-option value="{{ p.fileSlug }}">{{ p.data.title }}</wa-option>
|
||||
{% endfor %}
|
||||
</wa-select>
|
||||
|
||||
<wa-select name="color" label="Colors from…" size="small" value="">
|
||||
<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 %}
|
||||
<wa-option value="{{ theme.fileSlug }}">{{ theme.data.title }}</wa-option>
|
||||
{% endfor %}
|
||||
</wa-select>
|
||||
|
||||
<wa-select name="typography" label="Typography from…" size="small" value="">
|
||||
<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 %}
|
||||
<wa-option value="{{ theme.fileSlug }}">{{ theme.data.title }}</wa-option>
|
||||
{% endfor %}
|
||||
</wa-select>
|
||||
</p>
|
||||
<script src="{{ page.url }}index.js" type="module"></script>
|
||||
|
||||
<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 you’re mixing and matching, you’re on your own!
|
||||
</wa-callout>
|
||||
{% endblock %}
|
||||
|
||||
{% block afterContent %}
|
||||
{% markdown %}
|
||||
|
||||
## How to use your remixed theme { #remixed-usage }
|
||||
|
||||
You can import your remixed theme by importing the individual theme files from the Web Awesome CDN.
|
||||
{# Note: If you change the order here, you need to update index.js too #}
|
||||
{% set stylesheets = [
|
||||
'styles/themes/default.css',
|
||||
'styles/color/default.css',
|
||||
'styles/themes/default/color.css',
|
||||
'styles/themes/default/typography.css'
|
||||
] %}
|
||||
{% set stylesheet = 'styles/themes/default/color.css' %}
|
||||
{% include 'import-stylesheet-code.md.njk' %}
|
||||
|
||||
|
||||
{% endmarkdown %}
|
||||
{% endblock %}
|
||||
18
docs/docs/themes/remixed/style.css
vendored
18
docs/docs/themes/remixed/style.css
vendored
@@ -1,18 +0,0 @@
|
||||
#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);
|
||||
}
|
||||
}
|
||||
22
docs/docs/themes/showcase.css
vendored
22
docs/docs/themes/showcase.css
vendored
@@ -9,29 +9,7 @@ body,
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
html.remixed {
|
||||
.remix-link {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
html:not(.remixed) {
|
||||
#mix_and_match {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#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);
|
||||
|
||||
2
docs/docs/themes/tailspin.md
vendored
2
docs/docs/themes/tailspin.md
vendored
@@ -3,5 +3,5 @@ title: Tailspin
|
||||
description: Like a bird in flight, guiding you from there to here.
|
||||
isPro: true
|
||||
tags: pro
|
||||
palette: vogue
|
||||
defaultPalette: vogue
|
||||
---
|
||||
|
||||
1
docs/docs/themes/themes.json
vendored
1
docs/docs/themes/themes.json
vendored
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"layout": "theme.njk",
|
||||
"wide": true,
|
||||
"tags": ["themes", "theme"]
|
||||
}
|
||||
|
||||
@@ -6,30 +6,6 @@ description: Ensure consistent use of color and readable contrast with Web Aweso
|
||||
<style>
|
||||
td { vertical-align: middle; }
|
||||
|
||||
.color-name {
|
||||
font-weight: var(--wa-font-weight-semibold);
|
||||
margin-block-end: var(--wa-space-2xs);
|
||||
}
|
||||
ul.color-group {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.color-group {
|
||||
align-items: start;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
gap: 0.25em;
|
||||
}
|
||||
.color-group + * {
|
||||
margin-block-start: var(--wa-space-xl);
|
||||
}
|
||||
.color-preview {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
.swatch {
|
||||
border-color: transparent;
|
||||
}
|
||||
.color-mix-example {
|
||||
background-image:
|
||||
linear-gradient(to right,
|
||||
@@ -49,31 +25,29 @@ 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](/docs/palettes/)
|
||||
- [Literal colors](/#literal-colors) that give familiar names to your starting color palette
|
||||
- [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 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.
|
||||
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.
|
||||
|
||||
You can use these numbers to ensure accessible color contrast per [WCAG 2.1 success criteria](https://www.w3.org/TR/WCAG21/#contrast-minimum):
|
||||
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:
|
||||
|
||||
- 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)
|
||||
|
||||
Each Web Awesome palette defines seven literal colors each with 11 lightness values using the format `--wa-color-{hue}-{tint}`.
|
||||
Web Awesome 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="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>
|
||||
<div class="swatch" style="background-color: var(--wa-color-{{ hue }}-{{ tint }})"></div>
|
||||
<small>{{ tint }}</small>
|
||||
</li>
|
||||
{%- endfor %}
|
||||
@@ -189,3 +163,23 @@ 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>
|
||||
|
||||
@@ -24,9 +24,22 @@ 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. 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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
## Methods
|
||||
|
||||
|
||||
16
package-lock.json
generated
16
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@shoelace-style/webawesome",
|
||||
"version": "3.0.0-alpha.9",
|
||||
"version": "3.0.0-alpha.8",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@shoelace-style/webawesome",
|
||||
"version": "3.0.0-alpha.9",
|
||||
"version": "3.0.0-alpha.8",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "^4.1.0",
|
||||
@@ -34,7 +34,6 @@
|
||||
"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",
|
||||
@@ -5079,17 +5078,6 @@
|
||||
"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",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@shoelace-style/webawesome",
|
||||
"description": "A forward-thinking library of web components.",
|
||||
"version": "3.0.0-alpha.9",
|
||||
"version": "3.0.0-alpha.8",
|
||||
"homepage": "https://webawesome.com/",
|
||||
"author": "Web Awesome",
|
||||
"license": "MIT",
|
||||
@@ -54,7 +54,6 @@
|
||||
"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 .",
|
||||
@@ -92,7 +91,6 @@
|
||||
"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",
|
||||
|
||||
@@ -126,11 +126,7 @@ 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')
|
||||
) {
|
||||
|
||||
@@ -24,16 +24,14 @@ 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');
|
||||
// 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
|
||||
const eventImports = (component.events || [])
|
||||
.map(event => `import type { ${event.eventName} } from '../../events/events.js';`)
|
||||
.join('\n');
|
||||
const eventExports = eventsToWrap
|
||||
const eventExports = (component.events || [])
|
||||
.map(event => `export type { ${event.eventName} } from '../../events/events.js';`)
|
||||
.join('\n');
|
||||
const eventNameImport = eventsToWrap.length > 0 ? `import { type EventName } from '@lit/react';` : ``;
|
||||
const events = eventsToWrap
|
||||
const eventNameImport = (component.events || []).length > 0 ? `import { type EventName } from '@lit/react';` : ``;
|
||||
const events = (component.events || [])
|
||||
.map(event => `${event.reactName}: '${event.name}' as EventName<${event.eventName}>`)
|
||||
.join(',\n');
|
||||
|
||||
|
||||
@@ -318,13 +318,13 @@ describe('<wa-button>', () => {
|
||||
});
|
||||
|
||||
describe('when using methods', () => {
|
||||
it('should emit focus and blur when the button is focused and blurred', async () => {
|
||||
it('should emit wa-focus and wa-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('focus', focusHandler);
|
||||
el.addEventListener('blur', blurHandler);
|
||||
el.addEventListener('wa-focus', focusHandler);
|
||||
el.addEventListener('wa-blur', blurHandler);
|
||||
|
||||
el.focus();
|
||||
await waitUntil(() => focusHandler.calledOnce);
|
||||
|
||||
@@ -2,10 +2,12 @@ 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-form-associated-element.js';
|
||||
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-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 +26,8 @@ import styles from './button.css';
|
||||
* @dependency wa-icon
|
||||
* @dependency wa-spinner
|
||||
*
|
||||
* @event blur - Emitted when the button loses focus.
|
||||
* @event focus - Emitted when the button gains focus.
|
||||
* @event wa-blur - Emitted when the button loses focus.
|
||||
* @event wa-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.
|
||||
@@ -70,8 +72,7 @@ export default class WaButton extends WebAwesomeFormAssociatedElement {
|
||||
@property({ reflect: true }) variant: 'neutral' | 'brand' | 'success' | 'warning' | 'danger' = 'neutral';
|
||||
|
||||
/** The button's visual appearance. */
|
||||
@property({ reflect: true, default: 'accent' })
|
||||
appearance: 'accent' | 'filled' | 'outlined' | 'plain' = 'accent';
|
||||
@property({ reflect: true }) appearance: 'accent' | 'filled' | 'outlined' | 'plain' = 'accent';
|
||||
|
||||
/** The button's size. */
|
||||
@property({ reflect: true }) size: 'small' | 'medium' | 'large' = 'medium';
|
||||
@@ -140,6 +141,14 @@ 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();
|
||||
|
||||
@@ -264,6 +273,8 @@ 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}
|
||||
>
|
||||
|
||||
@@ -1,35 +1,29 @@
|
||||
: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(--border-radius);
|
||||
border-radius: var(--wa-panel-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-start-start-radius: inherit !important;
|
||||
border-start-end-radius: inherit !important;
|
||||
border-bottom-left-radius: 0 !important;
|
||||
border-bottom-right-radius: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +33,11 @@
|
||||
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);
|
||||
@@ -47,8 +46,8 @@
|
||||
.footer {
|
||||
display: block;
|
||||
border-top: inherit;
|
||||
border-end-start-radius: var(--inner-border-radius);
|
||||
border-end-end-radius: var(--inner-border-radius);
|
||||
border-bottom-left-radius: inherit;
|
||||
border-bottom-right-radius: inherit;
|
||||
padding: var(--spacing);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,8 +19,6 @@ 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')
|
||||
|
||||
@@ -60,13 +60,13 @@ describe('<wa-checkbox>', () => {
|
||||
expect(el.checkValidity()).to.be.true;
|
||||
});
|
||||
|
||||
it('should emit change and input when clicked', async () => {
|
||||
it('should emit wa-change and wa-input when clicked', async () => {
|
||||
const el = await fixture<WaCheckbox>(html` <wa-checkbox></wa-checkbox> `);
|
||||
const changeHandler = sinon.spy();
|
||||
const inputHandler = sinon.spy();
|
||||
|
||||
el.addEventListener('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-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 change and input when toggled with spacebar', async () => {
|
||||
it('should emit wa-change and wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-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 change or input when checked programmatically', async () => {
|
||||
it('should not emit wa-change or wa-input when checked programmatically', async () => {
|
||||
const el = await fixture<WaCheckbox>(html` <wa-checkbox></wa-checkbox> `);
|
||||
|
||||
el.addEventListener('change', () => expect.fail('change should not be emitted'));
|
||||
el.addEventListener('input', () => expect.fail('input should not be emitted'));
|
||||
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.checked = true;
|
||||
await el.updateComplete;
|
||||
await aTimeout(0);
|
||||
|
||||
@@ -4,10 +4,14 @@ 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-form-associated-element.js';
|
||||
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
|
||||
import nativeStyles from '../../styles/native/checkbox.css';
|
||||
import formControlStyles from '../../styles/shadow/form-control.css';
|
||||
import sizeStyles from '../../styles/utilities/size.css';
|
||||
@@ -25,10 +29,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 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-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 wa-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.
|
||||
*
|
||||
* @csspart base - The component's label .
|
||||
@@ -133,7 +137,19 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
|
||||
this.hasInteracted = true;
|
||||
this.checked = !this.checked;
|
||||
this.indeterminate = false;
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
}
|
||||
|
||||
private handleBlur() {
|
||||
this.dispatchEvent(new WaBlurEvent());
|
||||
}
|
||||
|
||||
private handleInput() {
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
|
||||
private handleFocus() {
|
||||
this.dispatchEvent(new WaFocusEvent());
|
||||
}
|
||||
|
||||
@watch('defaultChecked')
|
||||
@@ -217,37 +233,47 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=1413733
|
||||
//
|
||||
return html`
|
||||
<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
|
||||
<div
|
||||
class=${classMap({
|
||||
'form-control--has-hint': hasHint,
|
||||
'form-control': true,
|
||||
})}
|
||||
>
|
||||
<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>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 change or input when the value is changed programmatically', async () => {
|
||||
it('should not emit wa-change or wa-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('change', () => expect.fail('change should not be emitted'));
|
||||
el.addEventListener('input', () => expect.fail('change should not be emitted'));
|
||||
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.value = color;
|
||||
await el.updateComplete;
|
||||
});
|
||||
|
||||
it('should emit change and input when the color grid selector is moved', async () => {
|
||||
it('should emit wa-change and wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-input', inputHandler);
|
||||
|
||||
await clickOnElement(trigger); // open the dropdown
|
||||
await aTimeout(200); // wait for the dropdown to open
|
||||
await el.updateComplete;
|
||||
|
||||
// Simulate a drag event. "change" should not fire until we stop dragging.
|
||||
// Simulate a drag event. "wa-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 change and input when the hue slider is moved', async () => {
|
||||
it('should emit wa-change and wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-input', inputHandler);
|
||||
|
||||
await clickOnElement(trigger); // open the dropdown
|
||||
await aTimeout(200); // wait for the dropdown to open
|
||||
|
||||
// Simulate a drag event. "change" should not fire until we stop dragging.
|
||||
// Simulate a drag event. "wa-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 change and input when the opacity slider is moved', async () => {
|
||||
it('should emit wa-change and wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-input', inputHandler);
|
||||
|
||||
await clickOnElement(trigger); // open the dropdown
|
||||
await aTimeout(200); // wait for the dropdown to open
|
||||
|
||||
// Simulate a drag event. "change" should not fire until we stop dragging.
|
||||
// Simulate a drag event. "wa-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 change and input when toggling the format', async () => {
|
||||
it('should emit wa-change and wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-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 change and input when clicking on a swatch', async () => {
|
||||
it('should emit wa-change and wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-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 change and input when selecting a color with the keyboard', async () => {
|
||||
it('should emit wa-change and wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-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 change and input when selecting a color with the keyboard', async () => {
|
||||
it('should emit wa-change and wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-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 change and input when selecting hue with the keyboard', async () => {
|
||||
it('should emit wa-change and wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-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 change and input when selecting opacity with the keyboard', async () => {
|
||||
it('should emit wa-change and wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-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 change and input when entering a value in the color input and pressing enter', async () => {
|
||||
it('should emit wa-change and wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-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 change and input when entering a value in the color input and blurring the field', async () => {
|
||||
it('should emit wa-change and wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-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 focus when rendered as a dropdown and focused', async () => {
|
||||
it.skip('should emit wa-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('focus', focusHandler);
|
||||
colorPicker.addEventListener('blur', blurHandler);
|
||||
colorPicker.addEventListener('wa-focus', focusHandler);
|
||||
colorPicker.addEventListener('wa-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('focus', focusHandler);
|
||||
colorPicker.addEventListener('blur', blurHandler);
|
||||
colorPicker.addEventListener('wa-focus', focusHandler);
|
||||
colorPicker.addEventListener('wa-blur', blurHandler);
|
||||
|
||||
// Focus
|
||||
colorPicker.focus();
|
||||
|
||||
@@ -5,13 +5,17 @@ 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-form-associated-element.js';
|
||||
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-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';
|
||||
@@ -50,10 +54,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 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-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 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.
|
||||
@@ -262,10 +266,12 @@ 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() {
|
||||
@@ -273,8 +279,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 Event('change'));
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
|
||||
private handleAlphaDrag(event: PointerEvent) {
|
||||
@@ -294,13 +300,13 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
if (this.value !== currentValue) {
|
||||
currentValue = this.value;
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
},
|
||||
onStop: () => {
|
||||
if (this.value !== initialValue) {
|
||||
initialValue = this.value;
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
}
|
||||
},
|
||||
initialEvent: event,
|
||||
@@ -324,13 +330,13 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
if (this.value !== currentValue) {
|
||||
currentValue = this.value;
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
},
|
||||
onStop: () => {
|
||||
if (this.value !== initialValue) {
|
||||
initialValue = this.value;
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
}
|
||||
},
|
||||
initialEvent: event,
|
||||
@@ -357,14 +363,14 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
if (this.value !== currentValue) {
|
||||
currentValue = this.value;
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
},
|
||||
onStop: () => {
|
||||
this.isDraggingGridHandle = false;
|
||||
if (this.value !== initialValue) {
|
||||
initialValue = this.value;
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
}
|
||||
},
|
||||
initialEvent: event,
|
||||
@@ -400,8 +406,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -434,8 +440,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,12 +474,12 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
}
|
||||
|
||||
private handleInputChange(event: Event) {
|
||||
private handleInputChange(event: WaChangeEvent) {
|
||||
const target = event.target as HTMLInputElement;
|
||||
const oldValue = this.value;
|
||||
|
||||
@@ -488,15 +494,15 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
}
|
||||
|
||||
private handleInputInput(event: InputEvent) {
|
||||
private handleInputInput(event: WaInputEvent) {
|
||||
this.updateValidity();
|
||||
|
||||
// Prevent the `<wa-input>` element's `input` event from bubbling up
|
||||
// Prevent the `<wa-input>` element's `wa-input` event from bubbling up
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
@@ -509,8 +515,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
this.input.value = this.value;
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
|
||||
setTimeout(() => this.input.select());
|
||||
@@ -685,8 +691,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
this.setColor(colorSelectionResult.sRGBHex);
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
@@ -701,8 +707,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
this.setColor(color);
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1002,10 +1008,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
?disabled=${this.disabled}
|
||||
aria-label=${this.localize.term('currentValue')}
|
||||
@keydown=${this.handleInputKeyDown}
|
||||
@change=${this.handleInputChange}
|
||||
@input=${this.handleInputInput}
|
||||
@blur=${this.stopNestedEventPropagation}
|
||||
@focus=${this.stopNestedEventPropagation}
|
||||
@wa-change=${this.handleInputChange}
|
||||
@wa-input=${this.handleInputInput}
|
||||
@wa-blur=${this.stopNestedEventPropagation}
|
||||
@wa-focus=${this.stopNestedEventPropagation}
|
||||
></wa-input>
|
||||
|
||||
<wa-button-group>
|
||||
@@ -1023,8 +1029,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
caret:format-button__caret
|
||||
"
|
||||
@click=${this.handleFormatToggle}
|
||||
@blur=${this.stopNestedEventPropagation}
|
||||
@focus=${this.stopNestedEventPropagation}
|
||||
@wa-blur=${this.stopNestedEventPropagation}
|
||||
@wa-focus=${this.stopNestedEventPropagation}
|
||||
>
|
||||
${this.setLetterCase(this.format)}
|
||||
</wa-button>
|
||||
@@ -1043,8 +1049,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
|
||||
caret:eye-dropper-button__caret
|
||||
"
|
||||
@click=${this.handleEyeDropper}
|
||||
@blur=${this.stopNestedEventPropagation}
|
||||
@focus=${this.stopNestedEventPropagation}
|
||||
@wa-blur=${this.stopNestedEventPropagation}
|
||||
@wa-focus=${this.stopNestedEventPropagation}
|
||||
>
|
||||
<wa-icon
|
||||
library="system"
|
||||
|
||||
@@ -146,13 +146,13 @@ describe('<wa-icon-button>', () => {
|
||||
});
|
||||
|
||||
describe('when using methods', () => {
|
||||
it('should emit focus and blur when the button is focused and blurred', async () => {
|
||||
it('should emit wa-focus and wa-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('focus', focusHandler);
|
||||
el.addEventListener('blur', blurHandler);
|
||||
el.addEventListener('wa-focus', focusHandler);
|
||||
el.addEventListener('wa-blur', blurHandler);
|
||||
|
||||
el.focus();
|
||||
await waitUntil(() => focusHandler.calledOnce);
|
||||
|
||||
@@ -2,7 +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, literal } from 'lit/static-html.js';
|
||||
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-form-associated-element.js';
|
||||
import { WaBlurEvent } from '../../events/blur.js';
|
||||
import { WaFocusEvent } from '../../events/focus.js';
|
||||
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
|
||||
import '../icon/icon.js';
|
||||
import styles from './icon-button.css';
|
||||
|
||||
@@ -14,8 +16,8 @@ import styles from './icon-button.css';
|
||||
*
|
||||
* @dependency wa-icon
|
||||
*
|
||||
* @event blur - Emitted when the icon button loses focus.
|
||||
* @event focus - Emitted when the icon button gains focus.
|
||||
* @event wa-blur - Emitted when the icon button loses focus.
|
||||
* @event wa-focus - Emitted when the icon button gains focus.
|
||||
*
|
||||
* @cssproperty --background-color-hover - The color of the button's background on hover.
|
||||
*
|
||||
@@ -69,6 +71,14 @@ 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();
|
||||
@@ -112,6 +122,8 @@ 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
|
||||
|
||||
@@ -17,13 +17,16 @@
|
||||
.before,
|
||||
.after {
|
||||
display: block;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&::slotted(img),
|
||||
&::slotted(svg) {
|
||||
display: block;
|
||||
max-width: 100% !important;
|
||||
height: auto;
|
||||
}
|
||||
.before::slotted(img),
|
||||
.after::slotted(img),
|
||||
.before::slotted(svg),
|
||||
.after::slotted(svg) {
|
||||
display: block;
|
||||
max-width: 100% !important;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.after {
|
||||
|
||||
@@ -37,7 +37,7 @@ describe('<wa-image-comparer>', () => {
|
||||
`);
|
||||
const handler = sinon.spy();
|
||||
|
||||
el.addEventListener('change', handler, { once: true });
|
||||
el.addEventListener('wa-change', handler, { once: true });
|
||||
|
||||
el.position = 40;
|
||||
await el.updateComplete;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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';
|
||||
@@ -21,7 +22,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 change - Emitted when the position changes.
|
||||
* @event wa-change - Emitted when the position changes.
|
||||
*
|
||||
* @csspart base - The component's base wrapper.
|
||||
* @csspart before - The container that wraps the before image.
|
||||
@@ -91,7 +92,7 @@ export default class WaImageComparer extends WebAwesomeElement {
|
||||
|
||||
@watch('position', { waitUntilFirstUpdate: true })
|
||||
handlePositionChange() {
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -60,7 +60,7 @@ describe('<wa-input>', () => {
|
||||
const label = el.shadowRoot!.querySelector('[part~="form-control-label"]')!;
|
||||
const focusHandler = sinon.spy();
|
||||
|
||||
el.addEventListener('focus', focusHandler);
|
||||
el.addEventListener('wa-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 change and input when the user types in the input', async () => {
|
||||
it('should emit wa-change and wa-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('input', inputHandler);
|
||||
el.addEventListener('change', changeHandler);
|
||||
el.addEventListener('wa-input', inputHandler);
|
||||
el.addEventListener('wa-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 change or input when the value is set programmatically', async () => {
|
||||
it('should not emit wa-change or wa-input when the value is set programmatically', async () => {
|
||||
const el = await fixture<WaInput>(html` <wa-input></wa-input> `);
|
||||
|
||||
el.addEventListener('change', () => expect.fail('change should not be emitted'));
|
||||
el.addEventListener('input', () => expect.fail('input should not be emitted'));
|
||||
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.value = 'abc';
|
||||
|
||||
await el.updateComplete;
|
||||
});
|
||||
|
||||
it('should not emit change or input when calling setRangeText()', async () => {
|
||||
it('should not emit wa-change or wa-input when calling setRangeText()', async () => {
|
||||
const el = await fixture<WaInput>(html` <wa-input value="hi there"></wa-input> `);
|
||||
|
||||
el.addEventListener('change', () => expect.fail('change should not be emitted'));
|
||||
el.addEventListener('input', () => expect.fail('input should not be emitted'));
|
||||
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.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 input or change when stepUp() is called programmatically', async () => {
|
||||
it('should not emit wa-input or wa-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('change', () => expect.fail('change should not be emitted'));
|
||||
el.addEventListener('input', () => expect.fail('input should not be emitted'));
|
||||
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.stepUp();
|
||||
|
||||
await el.updateComplete;
|
||||
});
|
||||
|
||||
it('should not emit input and change when stepDown() is called programmatically', async () => {
|
||||
it('should not emit wa-input and wa-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('change', () => expect.fail('change should not be emitted'));
|
||||
el.addEventListener('input', () => expect.fail('input should not be emitted'));
|
||||
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.stepDown();
|
||||
|
||||
await el.updateComplete;
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { html, isServer, type PropertyValues } 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 { 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-form-associated-element.js';
|
||||
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
|
||||
import nativeStyles from '../../styles/native/input.css';
|
||||
import formControlStyles from '../../styles/shadow/form-control.css';
|
||||
import appearanceStyles from '../../styles/utilities/appearance.css';
|
||||
@@ -33,11 +37,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 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-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-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
|
||||
@@ -53,8 +57,6 @@ 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 {
|
||||
@@ -66,7 +68,7 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
|
||||
return [...super.validators, MirrorValidator()];
|
||||
}
|
||||
|
||||
assumeInteractionOn = ['blur', 'input'];
|
||||
assumeInteractionOn = ['wa-blur', 'wa-input'];
|
||||
private readonly hasSlotController = new HasSlotController(this, 'hint', 'label');
|
||||
private readonly localize = new LocalizeController(this);
|
||||
|
||||
@@ -223,9 +225,13 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
|
||||
*/
|
||||
@property({ attribute: 'with-hint', type: Boolean }) withHint = false;
|
||||
|
||||
private handleChange(event: Event) {
|
||||
this.dispatchComposedEvent(event);
|
||||
private handleBlur() {
|
||||
this.dispatchEvent(new WaBlurEvent());
|
||||
}
|
||||
|
||||
private handleChange() {
|
||||
this.value = this.input.value;
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
}
|
||||
|
||||
private handleClearClick(event: MouseEvent) {
|
||||
@@ -234,15 +240,20 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
|
||||
if (this.value !== '') {
|
||||
this.value = '';
|
||||
this.dispatchEvent(new WaClearEvent());
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
}
|
||||
|
||||
this.input.focus();
|
||||
}
|
||||
|
||||
private handleFocus() {
|
||||
this.dispatchEvent(new WaFocusEvent());
|
||||
}
|
||||
|
||||
private handleInput() {
|
||||
this.value = this.input.value;
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
|
||||
private handleKeyDown(event: KeyboardEvent) {
|
||||
@@ -297,14 +308,6 @@ 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
|
||||
@@ -432,6 +435,8 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
|
||||
@change=${this.handleChange}
|
||||
@input=${this.handleInput}
|
||||
@keydown=${this.handleKeyDown}
|
||||
@focus=${this.handleFocus}
|
||||
@blur=${this.handleBlur}
|
||||
/>
|
||||
|
||||
${isClearIconVisible
|
||||
@@ -460,15 +465,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" library="system" variant="regular"></wa-icon>
|
||||
<wa-icon name="eye-slash" library="system" variant="regular"></wa-icon>
|
||||
</slot>
|
||||
`
|
||||
: html`
|
||||
<slot name="hide-password-icon">
|
||||
<wa-icon name="eye-slash" library="system" variant="regular"></wa-icon>
|
||||
<wa-icon name="eye" library="system" variant="regular"></wa-icon>
|
||||
</slot>
|
||||
`}
|
||||
</button>
|
||||
|
||||
@@ -2,9 +2,11 @@ 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-form-associated-element.js';
|
||||
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
|
||||
import nativeStyles from '../../styles/native/button.css';
|
||||
import appearanceStyles from '../../styles/utilities/appearance.css';
|
||||
import sizeStyles from '../../styles/utilities/size.css';
|
||||
@@ -22,8 +24,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 blur - Emitted when the button loses focus.
|
||||
* @event focus - Emitted when the button gains focus.
|
||||
* @event wa-blur - Emitted when the button loses focus.
|
||||
* @event wa-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.
|
||||
@@ -105,6 +107,10 @@ 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();
|
||||
@@ -115,6 +121,10 @@ 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');
|
||||
@@ -154,6 +164,8 @@ 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>
|
||||
|
||||
@@ -2,6 +2,7 @@ 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';
|
||||
@@ -325,7 +326,7 @@ describe('<wa-radio-group>', () => {
|
||||
const validFocusHandler = sinon.spy();
|
||||
|
||||
Array.from(el.querySelectorAll<WaRadio>('wa-radio')).forEach(radio =>
|
||||
radio.addEventListener('focus', validFocusHandler),
|
||||
radio.addEventListener('wa-focus', validFocusHandler),
|
||||
);
|
||||
|
||||
expect(validFocusHandler).to.not.have.been.called;
|
||||
@@ -349,8 +350,8 @@ describe('<wa-radio-group>', () => {
|
||||
const disabledRadio = el.querySelector('#radio-0')!;
|
||||
const validRadio = el.querySelector('#radio-1')!;
|
||||
|
||||
disabledRadio.addEventListener('focus', invalidFocusHandler);
|
||||
validRadio.addEventListener('focus', validFocusHandler);
|
||||
disabledRadio.addEventListener('wa-focus', invalidFocusHandler);
|
||||
validRadio.addEventListener('wa-focus', validFocusHandler);
|
||||
|
||||
expect(invalidFocusHandler).to.not.have.been.called;
|
||||
expect(validFocusHandler).to.not.have.been.called;
|
||||
@@ -377,8 +378,8 @@ describe('<wa-radio-group>', () => {
|
||||
const disabledRadio = el.querySelector('#radio-0')!;
|
||||
const validRadio = el.querySelector('#radio-2')!;
|
||||
|
||||
disabledRadio.addEventListener('focus', invalidFocusHandler);
|
||||
validRadio.addEventListener('focus', validFocusHandler);
|
||||
disabledRadio.addEventListener('wa-focus', invalidFocusHandler);
|
||||
validRadio.addEventListener('wa-focus', validFocusHandler);
|
||||
|
||||
expect(invalidFocusHandler).to.not.have.been.called;
|
||||
expect(validFocusHandler).to.not.have.been.called;
|
||||
@@ -393,7 +394,7 @@ describe('<wa-radio-group>', () => {
|
||||
});
|
||||
|
||||
describe('when the value changes', () => {
|
||||
it('should emit change when toggled with the arrow keys', async () => {
|
||||
it('should emit wa-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>
|
||||
@@ -404,8 +405,8 @@ describe('<wa-radio-group>', () => {
|
||||
const changeHandler = sinon.spy();
|
||||
const inputHandler = sinon.spy();
|
||||
|
||||
radioGroup.addEventListener('change', changeHandler);
|
||||
radioGroup.addEventListener('input', inputHandler);
|
||||
radioGroup.addEventListener('wa-change', changeHandler);
|
||||
radioGroup.addEventListener('wa-input', inputHandler);
|
||||
firstRadio.focus();
|
||||
await sendKeys({ press: 'ArrowRight' });
|
||||
await radioGroup.updateComplete;
|
||||
@@ -415,7 +416,7 @@ describe('<wa-radio-group>', () => {
|
||||
expect(radioGroup.value).to.equal('2');
|
||||
});
|
||||
|
||||
it('should emit change and input when clicked', async () => {
|
||||
it('should emit wa-change and wa-input when clicked', async () => {
|
||||
const radioGroup = await fixture<WaRadioGroup>(html`
|
||||
<wa-radio-group>
|
||||
<wa-radio id="radio-1" value="1"></wa-radio>
|
||||
@@ -424,12 +425,12 @@ describe('<wa-radio-group>', () => {
|
||||
`);
|
||||
const radio = radioGroup.querySelector<WaRadio>('#radio-1')!;
|
||||
setTimeout(() => radio.click());
|
||||
const event = await oneEvent(radioGroup, 'change');
|
||||
const event = (await oneEvent(radioGroup, 'wa-change')) as WaChangeEvent;
|
||||
expect(event.target).to.equal(radioGroup);
|
||||
expect(radioGroup.value).to.equal('1');
|
||||
});
|
||||
|
||||
it('should emit change and input when toggled with spacebar', async () => {
|
||||
it('should emit wa-change and wa-input when toggled with spacebar', async () => {
|
||||
const radioGroup = await fixture<WaRadioGroup>(html`
|
||||
<wa-radio-group>
|
||||
<wa-radio id="radio-1" value="1"></wa-radio>
|
||||
@@ -439,12 +440,12 @@ describe('<wa-radio-group>', () => {
|
||||
const radio = radioGroup.querySelector<WaRadio>('#radio-1')!;
|
||||
radio.focus();
|
||||
setTimeout(() => sendKeys({ press: ' ' }));
|
||||
const event = await oneEvent(radioGroup, 'change');
|
||||
const event = (await oneEvent(radioGroup, 'wa-change')) as WaChangeEvent;
|
||||
expect(event.target).to.equal(radioGroup);
|
||||
expect(radioGroup.value).to.equal('1');
|
||||
});
|
||||
|
||||
it('should not emit change or input when the value is changed programmatically', async () => {
|
||||
it('should not emit wa-change or wa-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>
|
||||
@@ -452,8 +453,8 @@ describe('<wa-radio-group>', () => {
|
||||
</wa-radio-group>
|
||||
`);
|
||||
|
||||
radioGroup.addEventListener('change', () => expect.fail('change should not be emitted'));
|
||||
radioGroup.addEventListener('input', () => expect.fail('input should not be emitted'));
|
||||
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.value = '2';
|
||||
await radioGroup.updateComplete;
|
||||
});
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
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-form-associated-element.js';
|
||||
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-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';
|
||||
@@ -27,8 +29,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 change - Emitted when the radio group's selected value changes.
|
||||
* @event input - Emitted when the radio group receives user input.
|
||||
* @event wa-change - Emitted when the radio group's selected value changes.
|
||||
* @event wa-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.
|
||||
@@ -157,8 +159,8 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
};
|
||||
|
||||
@@ -288,8 +290,8 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
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-form-associated-element.js';
|
||||
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
|
||||
import nativeStyles from '../../styles/native/radio.css';
|
||||
import sizeStyles from '../../styles/utilities/size.css';
|
||||
import '../icon/icon.js';
|
||||
@@ -18,8 +20,8 @@ import styles from './radio.css';
|
||||
*
|
||||
* @slot - The radio's label.
|
||||
*
|
||||
* @event blur - Emitted when the control loses focus.
|
||||
* @event focus - Emitted when the control gains focus.
|
||||
* @event wa-blur - Emitted when the control loses focus.
|
||||
* @event wa-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.
|
||||
@@ -67,6 +69,8 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement {
|
||||
super();
|
||||
if (!isServer) {
|
||||
this.addEventListener('click', this.handleClick);
|
||||
this.addEventListener('blur', this.handleBlur);
|
||||
this.addEventListener('focus', this.handleFocus);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +79,14 @@ 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;
|
||||
|
||||
@@ -54,12 +54,12 @@ describe('<wa-rating>', () => {
|
||||
expect(base.getAttribute('aria-valuenow')).to.equal('3');
|
||||
});
|
||||
|
||||
it('should emit change when clicked', async () => {
|
||||
it('should emit wa-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('change', changeHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
|
||||
await clickOnElement(lastSymbol);
|
||||
await el.updateComplete;
|
||||
@@ -68,11 +68,11 @@ describe('<wa-rating>', () => {
|
||||
expect(el.value).to.equal(5);
|
||||
});
|
||||
|
||||
it('should emit change when the value is changed with the keyboard', async () => {
|
||||
it('should emit wa-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('change', changeHandler);
|
||||
el.addEventListener('wa-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 change when disabled', async () => {
|
||||
it('should not emit wa-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('change', changeHandler);
|
||||
el.addEventListener('wa-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 change when the value is changed programmatically', async () => {
|
||||
it('should not emit wa-change when the value is changed programmatically', async () => {
|
||||
const el = await fixture<WaRating>(html` <wa-rating label="Test" value="1"></wa-rating> `);
|
||||
el.addEventListener('change', () => expect.fail('change incorrectly emitted'));
|
||||
el.addEventListener('wa-change', () => expect.fail('wa-change incorrectly emitted'));
|
||||
el.value = 5;
|
||||
await el.updateComplete;
|
||||
});
|
||||
|
||||
@@ -3,6 +3,7 @@ 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';
|
||||
@@ -19,7 +20,7 @@ import styles from './rating.css';
|
||||
*
|
||||
* @dependency wa-icon
|
||||
*
|
||||
* @event change - Emitted when the rating's value changes.
|
||||
* @event wa-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.
|
||||
@@ -95,7 +96,7 @@ export default class WaRating extends WebAwesomeElement {
|
||||
}
|
||||
|
||||
this.setValue(this.getValueFromMousePosition(event));
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
}
|
||||
|
||||
private setValue(newValue: number) {
|
||||
@@ -139,7 +140,7 @@ export default class WaRating extends WebAwesomeElement {
|
||||
}
|
||||
|
||||
if (this.value !== oldValue) {
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,7 +173,7 @@ export default class WaRating extends WebAwesomeElement {
|
||||
private handleTouchEnd(event: TouchEvent) {
|
||||
this.isHovering = false;
|
||||
this.setValue(this.hoverValue);
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
|
||||
// Prevent click on mobile devices
|
||||
event.preventDefault();
|
||||
|
||||
@@ -107,7 +107,7 @@ describe('<wa-select>', () => {
|
||||
const label = el.shadowRoot!.querySelector('[part~="form-control-label"]')!;
|
||||
const submitHandler = sinon.spy();
|
||||
|
||||
el.addEventListener('focus', submitHandler);
|
||||
el.addEventListener('wa-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 change when the value is changed with the mouse', async () => {
|
||||
it('should emit wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-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 change and input when the value is changed with the keyboard', async () => {
|
||||
it('should emit wa-change and wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-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 change or input when the value is changed programmatically', async () => {
|
||||
it('should not emit wa-change or wa-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('change', () => expect.fail('change should not be emitted'));
|
||||
el.addEventListener('input', () => expect.fail('input should not be emitted'));
|
||||
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.value = 'option-2';
|
||||
|
||||
await el.updateComplete;
|
||||
});
|
||||
|
||||
it('should emit change and input with the correct validation message when the value changes', async () => {
|
||||
it('should emit wa-change and wa-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('change', handler);
|
||||
el.addEventListener('input', handler);
|
||||
el.addEventListener('wa-change', handler);
|
||||
el.addEventListener('wa-input', handler);
|
||||
|
||||
await clickOnElement(el);
|
||||
await aTimeout(500);
|
||||
@@ -532,7 +532,7 @@ describe('<wa-select>', () => {
|
||||
expect(displayInput.value).to.equal('updated');
|
||||
});
|
||||
|
||||
it('should emit focus and blur when receiving and losing focus', async () => {
|
||||
it('should emit wa-focus and wa-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('focus', focusHandler);
|
||||
el.addEventListener('blur', blurHandler);
|
||||
el.addEventListener('wa-focus', focusHandler);
|
||||
el.addEventListener('wa-blur', blurHandler);
|
||||
|
||||
el.focus();
|
||||
await el.updateComplete;
|
||||
@@ -574,7 +574,7 @@ describe('<wa-select>', () => {
|
||||
expect(clearHandler).to.have.been.calledOnce;
|
||||
});
|
||||
|
||||
it('should emit change and input when a tag is removed', async () => {
|
||||
it('should emit wa-change and wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-input', inputHandler);
|
||||
|
||||
// The offsets are a funky hack for Firefox.
|
||||
await clickOnElement(removeButton, 'center', 1, 1);
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import type { PropertyValues, TemplateResult } from 'lit';
|
||||
import type { 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';
|
||||
@@ -15,7 +19,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-form-associated-element.js';
|
||||
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
|
||||
import nativeStyles from '../../styles/native/select.css';
|
||||
import formControlStyles from '../../styles/shadow/form-control.css';
|
||||
import appearanceStyles from '../../styles/utilities/appearance.css';
|
||||
@@ -46,11 +50,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 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-change - Emitted when the control's value changes.
|
||||
* @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.
|
||||
@@ -79,8 +83,6 @@ 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 {
|
||||
@@ -97,7 +99,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
return [...super.validators, ...validators];
|
||||
}
|
||||
|
||||
assumeInteractionOn = ['blur', 'input'];
|
||||
assumeInteractionOn = ['wa-blur', 'wa-input'];
|
||||
|
||||
private readonly hasSlotController = new HasSlotController(this, 'hint', 'label');
|
||||
private readonly localize = new LocalizeController(this);
|
||||
@@ -308,6 +310,11 @@ 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) => {
|
||||
@@ -359,8 +366,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
// Emit after updating
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
});
|
||||
|
||||
if (!this.multiple) {
|
||||
@@ -489,8 +496,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
// Emit after update
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new WaClearEvent());
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -520,8 +527,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
if (this.value !== oldValue) {
|
||||
// Emit after updating
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -558,8 +565,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
|
||||
// Emit after updating
|
||||
this.updateComplete.then(() => {
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -667,14 +674,6 @@ 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
|
||||
@@ -800,7 +799,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
|
||||
>
|
||||
<label
|
||||
id="label"
|
||||
part="form-control-label label"
|
||||
part="form-control-label"
|
||||
class="label"
|
||||
aria-hidden=${hasLabel ? 'false' : 'true'}
|
||||
@click=${this.handleLabelClick}
|
||||
@@ -860,6 +859,7 @@ 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. -->
|
||||
|
||||
@@ -50,13 +50,13 @@ describe('<wa-slider>', () => {
|
||||
});
|
||||
|
||||
describe('when the value changes', () => {
|
||||
it('should emit change and input and decrease the value when pressing right arrow', async () => {
|
||||
it('should emit wa-change and wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-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 change or input when changing the value programmatically', async () => {
|
||||
it('should not emit wa-change or wa-input when changing the value programmatically', async () => {
|
||||
const el = await fixture<WaSlider>(html` <wa-slider value="0"></wa-slider> `);
|
||||
|
||||
el.addEventListener('change', () => expect.fail('change should not be emitted'));
|
||||
el.addEventListener('input', () => expect.fail('input should not be emitted'));
|
||||
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.value = 50;
|
||||
|
||||
await el.updateComplete;
|
||||
});
|
||||
|
||||
it('should not emit change or input when stepUp() is called programmatically', async () => {
|
||||
it('should not emit wa-change or wa-input when stepUp() is called programmatically', async () => {
|
||||
const el = await fixture<WaSlider>(html` <wa-slider step="2" value="2"></wa-slider> `);
|
||||
|
||||
el.addEventListener('change', () => expect.fail('change should not be emitted'));
|
||||
el.addEventListener('input', () => expect.fail('input should not be emitted'));
|
||||
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.stepUp();
|
||||
await el.updateComplete;
|
||||
});
|
||||
|
||||
it('should not emit change or input when stepDown() is called programmatically', async () => {
|
||||
it('should not emit wa-change or wa-input when stepDown() is called programmatically', async () => {
|
||||
const el = await fixture<WaSlider>(html` <wa-slider step="2" value="2"></wa-slider> `);
|
||||
|
||||
el.addEventListener('change', () => expect.fail('change should not be emitted'));
|
||||
el.addEventListener('input', () => expect.fail('input should not be emitted'));
|
||||
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.stepDown();
|
||||
await el.updateComplete;
|
||||
});
|
||||
|
||||
@@ -3,10 +3,14 @@ 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-form-associated-element.js';
|
||||
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
|
||||
import sliderStyles from '../../styles/native/slider.css';
|
||||
import formControlStyles from '../../styles/shadow/form-control.css';
|
||||
import { LocalizeController } from '../../utilities/localize.js';
|
||||
@@ -21,10 +25,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 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-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 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.
|
||||
@@ -156,21 +160,24 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
|
||||
this.resizeObserver?.unobserve(this.input);
|
||||
}
|
||||
|
||||
private handleChange(event: Event) {
|
||||
this.dispatchComposedEvent(event);
|
||||
private handleChange() {
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
}
|
||||
|
||||
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 })
|
||||
|
||||
@@ -49,13 +49,13 @@ describe('<wa-switch>', () => {
|
||||
expect(el.checkValidity()).to.be.true;
|
||||
});
|
||||
|
||||
it('should emit change and input when clicked', async () => {
|
||||
it('should emit wa-change and wa-input when clicked', async () => {
|
||||
const el = await fixture<WaSwitch>(html` <wa-switch></wa-switch> `);
|
||||
const changeHandler = sinon.spy();
|
||||
const inputHandler = sinon.spy();
|
||||
|
||||
el.addEventListener('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-input', inputHandler);
|
||||
el.click();
|
||||
await el.updateComplete;
|
||||
|
||||
@@ -64,13 +64,13 @@ describe('<wa-switch>', () => {
|
||||
expect(el.checked).to.be.true;
|
||||
});
|
||||
|
||||
it('should emit change when toggled with spacebar', async () => {
|
||||
it('should emit wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-input', inputHandler);
|
||||
el.focus();
|
||||
await sendKeys({ press: ' ' });
|
||||
|
||||
@@ -79,13 +79,13 @@ describe('<wa-switch>', () => {
|
||||
expect(el.checked).to.be.true;
|
||||
});
|
||||
|
||||
it('should emit change and input when toggled with the right arrow', async () => {
|
||||
it('should emit wa-change and wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-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 change and input when toggled with the left arrow', async () => {
|
||||
it('should emit wa-change and wa-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('change', changeHandler);
|
||||
el.addEventListener('input', inputHandler);
|
||||
el.addEventListener('wa-change', changeHandler);
|
||||
el.addEventListener('wa-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 change or input when checked is set by JavaScript', async () => {
|
||||
it('should not emit wa-change or wa-input when checked is set by JavaScript', async () => {
|
||||
const el = await fixture<WaSwitch>(html` <wa-switch></wa-switch> `);
|
||||
el.addEventListener('change', () => expect.fail('change incorrectly emitted'));
|
||||
el.addEventListener('input', () => expect.fail('input incorrectly emitted'));
|
||||
el.addEventListener('wa-change', () => expect.fail('wa-change incorrectly emitted'));
|
||||
el.addEventListener('wa-input', () => expect.fail('wa-change incorrectly emitted'));
|
||||
el.checked = true;
|
||||
await el.updateComplete;
|
||||
el.checked = false;
|
||||
|
||||
@@ -4,10 +4,14 @@ 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-form-associated-element.js';
|
||||
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
|
||||
import formControlStyles from '../../styles/shadow/form-control.css';
|
||||
import sizeStyles from '../../styles/utilities/size.css';
|
||||
import styles from './switch.css';
|
||||
@@ -21,10 +25,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 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-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 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.
|
||||
@@ -113,25 +117,37 @@ 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 Event('change'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
}
|
||||
|
||||
private handleFocus() {
|
||||
this.dispatchEvent(new WaFocusEvent());
|
||||
}
|
||||
|
||||
private handleKeyDown(event: KeyboardEvent) {
|
||||
if (event.key === 'ArrowLeft') {
|
||||
event.preventDefault();
|
||||
this.checked = false;
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
|
||||
if (event.key === 'ArrowRight') {
|
||||
event.preventDefault();
|
||||
this.checked = true;
|
||||
this.dispatchEvent(new Event('change'));
|
||||
this.dispatchEvent(new InputEvent('input'));
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -234,6 +250,9 @@ 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}
|
||||
/>
|
||||
|
||||
|
||||
@@ -90,22 +90,17 @@ 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 (relevantMutations.some(m => m.attributeName === 'disabled')) {
|
||||
if (mutations.some(m => m.attributeName === 'disabled')) {
|
||||
this.syncTabsAndPanels();
|
||||
} else if (relevantMutations.some(m => m.attributeName === 'active')) {
|
||||
const tabs = relevantMutations
|
||||
// sync tabs when active state on tab changes
|
||||
} else if (mutations.some(m => m.attributeName === 'active')) {
|
||||
const tabs = mutations
|
||||
.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 && newActiveTab.closest('wa-tab-group') === this) {
|
||||
if (newActiveTab) {
|
||||
this.setActiveTab(newActiveTab);
|
||||
}
|
||||
}
|
||||
@@ -169,7 +164,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 specific tab group instance
|
||||
// Ensure the target tab is in this tab group
|
||||
if (tabGroup !== this) {
|
||||
return;
|
||||
}
|
||||
@@ -184,7 +179,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 specific tab group instance
|
||||
// Ensure the target tab is in this tab group
|
||||
if (tabGroup !== this) {
|
||||
return;
|
||||
}
|
||||
@@ -302,11 +297,6 @@ 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;
|
||||
|
||||
@@ -64,7 +64,7 @@ describe('<wa-textarea>', () => {
|
||||
const label = el.shadowRoot!.querySelector('[part~="label"]')!;
|
||||
const submitHandler = sinon.spy();
|
||||
|
||||
el.addEventListener('focus', submitHandler);
|
||||
el.addEventListener('wa-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 change and input when the user types in the textarea', async () => {
|
||||
it('should emit wa-change and wa-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('input', inputHandler);
|
||||
el.addEventListener('change', changeHandler);
|
||||
el.addEventListener('wa-input', inputHandler);
|
||||
el.addEventListener('wa-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 change or input when the value is set programmatically', async () => {
|
||||
it('should not emit wa-change or wa-input when the value is set programmatically', async () => {
|
||||
const el = await fixture<WaTextarea>(html` <wa-textarea></wa-textarea> `);
|
||||
|
||||
el.addEventListener('change', () => expect.fail('change should not be emitted'));
|
||||
el.addEventListener('input', () => expect.fail('input should not be emitted'));
|
||||
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.value = 'abc';
|
||||
|
||||
await el.updateComplete;
|
||||
});
|
||||
|
||||
it('should not emit change or input when calling setRangeText()', async () => {
|
||||
it('should not emit wa-change or wa-input when calling setRangeText()', async () => {
|
||||
const el = await fixture<WaTextarea>(html` <wa-textarea value="hi there"></wa-textarea> `);
|
||||
|
||||
el.addEventListener('change', () => expect.fail('change should not be emitted'));
|
||||
el.addEventListener('input', () => expect.fail('input should not be emitted'));
|
||||
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.focus();
|
||||
el.setSelectionRange(0, 2);
|
||||
el.setRangeText('hello');
|
||||
|
||||
@@ -4,10 +4,14 @@ 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-form-associated-element.js';
|
||||
import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-formassociated-element.js';
|
||||
import nativeStyles from '../../styles/native/input.css';
|
||||
import formControlStyles from '../../styles/shadow/form-control.css';
|
||||
import appearanceStyles from '../../styles/utilities/appearance.css';
|
||||
@@ -23,10 +27,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 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-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 wa-invalid - Emitted when the form control has been checked for validity and its constraints aren't satisfied.
|
||||
*
|
||||
* @csspart label - The label
|
||||
@@ -39,8 +43,6 @@ 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 {
|
||||
@@ -50,7 +52,7 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement {
|
||||
return [...super.validators, MirrorValidator()];
|
||||
}
|
||||
|
||||
assumeInteractionOn = ['blur', 'input'];
|
||||
assumeInteractionOn = ['wa-blur', 'wa-input'];
|
||||
private readonly hasSlotController = new HasSlotController(this, 'hint', 'label');
|
||||
private resizeObserver: ResizeObserver;
|
||||
|
||||
@@ -200,20 +202,26 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement {
|
||||
}
|
||||
|
||||
private handleBlur() {
|
||||
this.dispatchEvent(new WaBlurEvent());
|
||||
this.checkValidity();
|
||||
}
|
||||
|
||||
private handleChange(event: Event) {
|
||||
private handleChange() {
|
||||
this.valueHasChanged = true;
|
||||
this.value = this.input.value;
|
||||
this.setTextareaDimensions();
|
||||
this.dispatchComposedEvent(event);
|
||||
this.dispatchEvent(new WaChangeEvent());
|
||||
this.checkValidity();
|
||||
}
|
||||
|
||||
private handleFocus() {
|
||||
this.dispatchEvent(new WaFocusEvent());
|
||||
}
|
||||
|
||||
private handleInput() {
|
||||
this.valueHasChanged = true;
|
||||
this.value = this.input.value;
|
||||
this.dispatchEvent(new WaInputEvent());
|
||||
}
|
||||
|
||||
private setTextareaDimensions() {
|
||||
@@ -267,12 +275,7 @@ 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. */
|
||||
@@ -372,6 +375,7 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement {
|
||||
aria-describedby="hint"
|
||||
@change=${this.handleChange}
|
||||
@input=${this.handleInput}
|
||||
@focus=${this.handleFocus}
|
||||
@blur=${this.handleBlur}
|
||||
></textarea>
|
||||
|
||||
|
||||
@@ -30,16 +30,14 @@ slot:not([name])::slotted(wa-icon) {
|
||||
}
|
||||
|
||||
.checkbox {
|
||||
line-height: var(--wa-form-control-value-line-height);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.expand-button,
|
||||
.checkbox,
|
||||
.label {
|
||||
font-family: inherit;
|
||||
font: inherit;
|
||||
font-size: var(--wa-font-size-m);
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
.checkbox::part(base) {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user