Compare commits

...

26 Commits

Author SHA1 Message Date
Cory LaViska
15c77082a4 remove outdated section 2025-03-28 10:49:56 -04:00
Cory LaViska
5161936cb0 add option as a dep of select 2025-03-28 10:48:11 -04:00
Cory LaViska
f6e754c1b9 Merge branch 'next' into bug-fixes 2025-03-28 10:43:05 -04:00
Cory LaViska
8ad1a58914 revert example 2025-03-28 10:38:29 -04:00
Cory LaViska
5b1ef2717c fix radio labels (ALPHA-211) 2025-03-28 09:59:18 -04:00
Cory LaViska
30302fe632 update display labels when changed; fixes #702 2025-03-28 09:47:31 -04:00
Cory LaViska
22c0fd2301 fix whitespace 2025-03-28 09:41:34 -04:00
Cory LaViska
d9b3df2bea remove wa-cloak after components load 2025-03-28 09:24:46 -04:00
Cory LaViska
2c047c2843 add FA kit code for codepen 🤞🏻 2025-03-28 09:24:29 -04:00
Cory LaViska
9d3c0142b8 update test 2025-03-27 15:12:21 -04:00
Cory LaViska
acf02a7a03 stop running SSR tests locally 2025-03-27 15:07:38 -04:00
Cory LaViska
50034527e7 add comment 2025-03-27 14:13:31 -04:00
Cory LaViska
96e8ce8bf6 fix native checkbox indeterminate icon; closes #386 2025-03-27 14:08:24 -04:00
Cory LaViska
b8dd847b4b fix progress animation in Safari; closes #356 2025-03-27 13:20:55 -04:00
Cory LaViska
95fd134104 fix dropdown alignment in button group; closes #374 2025-03-27 12:52:02 -04:00
Cory LaViska
c6ccb3280d Merge branch 'next' into bug-fixes 2025-03-27 12:16:22 -04:00
Cory LaViska
aeb1c57be0 revert 2025-03-27 11:51:36 -04:00
Cory LaViska
486cf3d408 revert 2025-03-26 16:14:25 -04:00
Cory LaViska
4f81a432df Revert "improve details styles; fixes #685"
This reverts commit 8151872d22.
2025-03-26 16:09:39 -04:00
Cory LaViska
bef6ee8503 update examples 2025-03-26 15:52:49 -04:00
Cory LaViska
8151872d22 improve details styles; fixes #685 2025-03-26 15:22:29 -04:00
Cory LaViska
d78b984bd0 fix focus ring in Safari; fixes #745 2025-03-26 15:01:03 -04:00
Cory LaViska
2615fc8ab9 fixes #840 2025-03-26 14:38:33 -04:00
Cory LaViska
0d8d8d4055 remove redundant styles 2025-03-26 14:38:24 -04:00
Cory LaViska
be3cfc3420 fix radio button pill styles; fixes #759 2025-03-26 13:46:30 -04:00
Cory LaViska
f1149d22a0 add default icon spacing in tab; fixes #779 2025-03-26 13:05:57 -04:00
18 changed files with 140 additions and 77 deletions

View File

@@ -17,7 +17,7 @@ document.addEventListener('click', event => {
const code = codeExample.querySelector('code');
const cdnUrl = document.documentElement.dataset.cdnUrl;
const html =
`<script type="module" src="${cdnUrl}webawesome.loader.js"></script>\n` +
`<script data-fa-kit-code="b10bfbde90" type="module" src="${cdnUrl}webawesome.loader.js"></script>\n` +
`<link rel="stylesheet" href="${cdnUrl}styles/themes/default.css">\n` +
`<link rel="stylesheet" href="${cdnUrl}styles/webawesome.css">\n` +
`<link rel="stylesheet" href="${cdnUrl}styles/utilities.css">\n\n` +

View File

@@ -16,7 +16,12 @@ During the alpha period, things might break! We take breaking changes very serio
- Fixed the search dialog's styles so it doesn't jump around as you search
- Removed close watcher logic to backdrop hide animation bugs in `<wa-dialog>` and `<wa-drawer>`; this logic is already handled and we'll revisit `CloseWatcher` when browser support is better and behaviors are consistent
- Fixed a bug that caused dropdowns inside button groups to show with a vertical misalignment
- Revert `<wa-dialog>` structure and CSS to fix clipped content in dialogs (WA-A #123) and light dismiss in iOS Safari (WA-A #201)
- Fixed a bug in `<wa-progress>` that prevented Safari from animation progress changes
- Fixed the missing indeterminate icon in native checkbox styles
- Fixed a bug where changing a `<wa-option>` label wouldn't update the display label in `<wa-select>`
- Fixed a bug in `<wa-radio>` where elements would stack instead of display inline
### Enhancements
@@ -24,6 +29,8 @@ During the alpha period, things might break! We take breaking changes very serio
- Added an `orange` scale to all color palettes
- Added the `.wa-cloak` utility to prevent FOUCE
- Added the `allDefined()` utility for awaiting component registration
- Added default spacing to icons slotted into `<wa-tab>`
- Fixed `wa-pill` class for text fields
### Bugfixes
@@ -33,6 +40,9 @@ During the alpha period, things might break! We take breaking changes very serio
- Fixed `pill` style for `<wa-input>` elements
- Fixed a bug in `<wa-color-picker>` that prevented light dismiss from working when clicking immediately above the color picker dropdown
- Fixed a bug in `<wa-select multiple>` that sometimes resulted in empty `<div>` elements being output
- Fixed a bug in `<wa-radio-button>` that prevented the pill effect from applying properly
- Fixed a bug in `<wa-radio-button>` that prevented active buttons from receiving the correct styles
- Fixed a bug in `<wa-button>` that prevented the focus ring from showing in Safari
## 3.0.0-alpha.11

View File

@@ -112,49 +112,6 @@ For example, `<button>` and `<wa-button>` both have a `type` attribute, but the
**Don't make assumptions about a component's API!** To prevent unexpected behaviors, please take the time to review the documentation and make sure you understand what each attribute, property, method, and event is intended to do.
:::
## Waiting for Components to Load
Web components are registered with JavaScript, so depending on how and when you load Web Awesome, you may notice a [Flash of Undefined Custom Elements (FOUCE)](https://www.abeautifulsite.net/posts/flash-of-undefined-custom-elements/) when the page loads. There are a couple ways to prevent this, both of which are described in the linked article.
One option is to use the [`:defined`](https://developer.mozilla.org/en-US/docs/Web/CSS/:defined) CSS pseudo-class to "hide" custom elements that haven't been registered yet. You can scope it to specific tags or you can hide all undefined custom elements as shown below.
```css
:not(:defined) {
visibility: hidden;
}
```
As soon as a custom element is registered, it will immediately appear with all of its styles, effectively eliminating FOUCE. Note the use of `visibility: hidden` instead of `display: none` to reduce shifting as elements are registered. The drawback to this approach is that custom elements can potentially appear one by one instead of all at the same time.
Another option is to use [`customElements.whenDefined()`](https://developer.mozilla.org/en-US/docs/Web/API/CustomElementRegistry/whenDefined), which returns a promise that resolves when the specified element gets registered. You'll probably want to use it with [`Promise.allSettled()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/allSettled) in case an element fails to load for some reason.
A clever way to use this method is to hide the `<body>` with `opacity: 0` and add a class that fades it in as soon as all your custom elements are defined.
```html
<style>
body {
opacity: 0;
}
body.ready {
opacity: 1;
transition: 0.25s opacity;
}
</style>
<script type="module">
await Promise.allSettled([
customElements.whenDefined('wa-button'),
customElements.whenDefined('wa-card'),
customElements.whenDefined('wa-rating')
]);
// Button, card, and rating are registered now! Add
// the `ready` class so the UI fades in.
document.body.classList.add('ready');
</script>
```
## Component Rendering and Updating
Web Awesome components are built with [Lit](https://lit.dev/), a tiny library that makes authoring custom elements easier, more maintainable, and a lot of fun! As a Web Awesome user, here is some helpful information about rendering and updating you should probably be aware of.

View File

@@ -52,8 +52,8 @@
"start:alpha": "node scripts/build.js --alpha --develop",
"publish-alpha-cdn": "./publish-alpha-cdn.sh",
"create": "plop --plopfile scripts/plop/plopfile.js",
"test": "web-test-runner --group default",
"test:component": "web-test-runner -- --watch --group",
"test": "CSR_ONLY=\"true\" web-test-runner --group default",
"test:component": "CSR_ONLY=\"true\" 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 .",

View File

@@ -106,6 +106,11 @@ export default class WaOption extends WebAwesomeElement {
}
private handleDefaultSlotChange() {
// Tell the controller to update the label
if (customElements.get('wa-select')) {
this.closest('wa-select')?.selectionChanged();
}
this.updateDefaultLabel();
if (this.isInitialized) {
@@ -123,7 +128,7 @@ export default class WaOption extends WebAwesomeElement {
private handleHover = (event: Event) => {
// We need this because Safari doesn't honor :hover styles while dragging
// Testcase: https://codepen.io/leaverou/pen/VYZOOjy
// Test case: https://codepen.io/leaverou/pen/VYZOOjy
if (event.type === 'mouseenter') {
this.toggleCustomState('hover', true);
} else if (event.type === 'mouseleave') {

View File

@@ -16,7 +16,7 @@
}
.indicator {
width: calc(var(--value, 0) * 1%);
width: var(--percentage);
display: flex;
align-items: center;
justify-content: center;
@@ -26,9 +26,7 @@
overflow: hidden;
line-height: 1;
font-weight: var(--wa-font-weight-semibold);
transition: var(--wa-transition-slow, 200ms) var(--wa-transition-easing, ease);
transition: 1s;
/* transition-property: width, background; */
transition: all var(--wa-transition-slow, 200ms) var(--wa-transition-easing, ease);
user-select: none;
-webkit-user-select: none;
}

View File

@@ -38,8 +38,9 @@ describe('<wa-progress-bar>', () => {
expect(base.getAttribute('aria-valuenow')).to.equal('25');
});
it('uses the value parameter to set the custom property on the indicator', () => {
expect(indicator.style.getPropertyValue('--value')).to.equal('25');
it('uses the value parameter to set the custom property on the indicator', async () => {
await new Promise(requestAnimationFrame);
expect(el.style.getPropertyValue('--percentage')).to.equal('25%');
});
});

View File

@@ -1,6 +1,8 @@
import type { PropertyValues } from 'lit';
import { html } from 'lit';
import { customElement, property } from 'lit/decorators.js';
import { ifDefined } from 'lit/directives/if-defined.js';
import { clamp } from '../../internal/math.js';
import WebAwesomeElement from '../../internal/webawesome-element.js';
import { LocalizeController } from '../../utilities/localize.js';
import styles from './progress-bar.css';
@@ -33,6 +35,16 @@ export default class WaProgressBar extends WebAwesomeElement {
/** A custom label for assistive devices. */
@property() label = '';
updated(changedProperties: PropertyValues<this>) {
if (changedProperties.has('value')) {
// Wait a cycle before setting it so Safari animates it.
// https://github.com/shoelace-style/webawesome/issues/356
requestAnimationFrame(() => {
this.style.setProperty('--percentage', `${clamp(this.value, 0, 100)}%`);
});
}
}
render() {
return html`
<div
@@ -45,7 +57,7 @@ export default class WaProgressBar extends WebAwesomeElement {
aria-valuemax="100"
aria-valuenow=${this.indeterminate ? '0' : this.value}
>
<div part="indicator" class="indicator" style="--value: ${this.value}">
<div part="indicator" class="indicator">
${!this.indeterminate ? html` <slot part="label" class="label"></slot> ` : ''}
</div>
</div>

View File

@@ -32,7 +32,6 @@
/*
* Checked buttons
*/
:host([checked]) {
--indicator-color: var(--wa-form-control-activated-color);
--indicator-width: var(--wa-border-width-s);
@@ -43,3 +42,41 @@
--border-color: var(--indicator-color);
}
}
/*
* Active buttons
*/
button:active {
--text-color-active: var(--wa-form-control-activated-color);
--border-color-active: var(--wa-form-control-activated-color);
}
/* Horizontal radio pill buttons */
:host([data-wa-radio-horizontal][data-wa-radio-first]:not([data-wa-radio-last])) .wa-pill {
border-start-end-radius: 0;
border-end-end-radius: 0;
}
:host([data-wa-radio-horizontal][data-wa-radio-inner]) .wa-pill {
border-radius: 0;
}
:host([data-wa-radio-horizontal][data-wa-radio-last]:not([data-wa-radio-first])) .wa-pill {
border-start-start-radius: 0;
border-end-start-radius: 0;
}
/* Vertical radio pill buttons */
:host([data-wa-radio-vertical][data-wa-radio-first]:not([data-wa-radio-last])) .wa-pill {
border-end-start-radius: 0;
border-end-end-radius: 0;
}
:host([data-wa-radio-vertical][data-wa-radio-inner]) .wa-pill {
border-radius: 0;
}
:host([data-wa-radio-vertical][data-wa-radio-last]:not([data-wa-radio-first])) .wa-pill {
border-start-start-radius: 0;
border-start-end-radius: 0;
}

View File

@@ -9,7 +9,6 @@ import nativeStyles from '../../styles/native/button.css';
import appearanceStyles from '../../styles/utilities/appearance.css';
import sizeStyles from '../../styles/utilities/size.css';
import variantStyles from '../../styles/utilities/variants.css';
import buttonStyles from '../button/button.css';
import styles from './radio-button.css';
/**
@@ -49,7 +48,7 @@ import styles from './radio-button.css';
*/
@customElement('wa-radio-button')
export default class WaRadioButton extends WebAwesomeFormAssociatedElement {
static shadowStyle = [variantStyles, appearanceStyles, sizeStyles, nativeStyles, buttonStyles, styles];
static shadowStyle = [variantStyles, appearanceStyles, sizeStyles, nativeStyles, styles];
static rectProxy = 'input';
private readonly hasSlotController = new HasSlotController(this, '[default]', 'prefix', 'suffix');

View File

@@ -38,8 +38,6 @@ import styles from './radio-group.css';
* @csspart form-control-input - The input's wrapper.
* @csspart radios - The wrapper than surrounds radio items, styled as a flex container by default.
* @csspart hint - The hint's wrapper.
* @csspart button-group - The button group that wraps radio buttons.
* @csspart button-group__base - The button group's `base` part.
*/
@customElement('wa-radio-group')
export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
@@ -65,7 +63,6 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
@query('slot:not([name])') defaultSlot: HTMLSlotElement;
@state() private hasButtonGroup = false;
@state() private hasRadioButtons = false;
/**
@@ -150,7 +147,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
clickedRadio.checked = true;
const radios = this.getAllRadios();
const hasButtonGroup = radios.some(radio => radio.tagName.toLowerCase() === 'wa-radio-button');
const hasRadioButtons = radios.some(radio => radio.tagName.toLowerCase() === 'wa-radio-button');
for (const radio of radios) {
if (clickedRadio === radio) {
continue;
@@ -158,7 +155,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
radio.checked = false;
if (!hasButtonGroup) {
if (!hasRadioButtons) {
radio.setAttribute('tabindex', '-1');
}
}
@@ -183,6 +180,15 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
// Detect the presence of radio buttons
this.hasRadioButtons = radios.some(radio => radio.localName === 'wa-radio-button');
// Add data attributes to support styling
radios.forEach((radio, index) => {
radio.toggleAttribute('data-wa-radio-horizontal', this.orientation !== 'vertical');
radio.toggleAttribute('data-wa-radio-vertical', this.orientation === 'vertical');
radio.toggleAttribute('data-wa-radio-first', index === 0);
radio.toggleAttribute('data-wa-radio-inner', index !== 0 && index !== radios.length - 1);
radio.toggleAttribute('data-wa-radio-last', index === radios.length - 1);
});
await Promise.all(
// Sync the checked state and size
radios.map(async radio => {
@@ -196,10 +202,8 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
}),
);
this.hasButtonGroup = radios.some(radio => radio.tagName.toLowerCase() === 'wa-radio-button');
if (radios.length > 0 && !radios.some(radio => radio.checked)) {
if (this.hasButtonGroup) {
if (this.hasRadioButtons) {
const buttonRadio = radios[0].shadowRoot?.querySelector('button');
if (buttonRadio) {
@@ -210,7 +214,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
}
}
if (this.hasButtonGroup) {
if (this.hasRadioButtons) {
const buttonGroup = this.shadowRoot?.querySelector('wa-button-group');
if (buttonGroup) {
@@ -276,12 +280,12 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
index = 0;
}
const hasButtonGroup = radios.some(radio => radio.tagName.toLowerCase() === 'wa-radio-button');
const hasRadioButtons = radios.some(radio => radio.tagName.toLowerCase() === 'wa-radio-button');
this.getAllRadios().forEach(radio => {
radio.checked = false;
if (!hasButtonGroup) {
if (!hasRadioButtons) {
radio.setAttribute('tabindex', '-1');
}
});
@@ -289,7 +293,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
this.value = radios[index].value;
radios[index].checked = true;
if (!hasButtonGroup) {
if (!hasRadioButtons) {
radios[index].setAttribute('tabindex', '0');
radios[index].focus();
} else {
@@ -351,7 +355,7 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
<slot
part="form-control-input"
class=${classMap({
'wa-button-group': this.hasButtonGroup,
'wa-button-group': this.hasRadioButtons,
'wa-button-group-vertical': this.orientation === 'vertical',
})}
@slotchange=${this.syncRadioElements}

View File

@@ -23,6 +23,10 @@
opacity: 0;
}
[part~='label'] {
display: inline;
}
[part~='hint'] {
grid-column: 2;
margin-block-start: var(--wa-space-3xs);

View File

@@ -22,6 +22,7 @@ import appearanceStyles from '../../styles/utilities/appearance.css';
import sizeStyles from '../../styles/utilities/size.css';
import { LocalizeController } from '../../utilities/localize.js';
import '../icon/icon.js';
import '../option/option.js';
import type WaOption from '../option/option.js';
import '../popup/popup.js';
import type WaPopup from '../popup/popup.js';
@@ -37,6 +38,7 @@ import styles from './select.css';
* @dependency wa-icon
* @dependency wa-popup
* @dependency wa-tag
* @dependency wa-option
*
* @slot - The listbox options. Must be `<wa-option>` elements. You can use `<wa-divider>` to group items visually.
* @slot label - The input's label. Alternatively, you can use the `label` attribute.
@@ -670,9 +672,9 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
this.selectionChanged();
}
// This method must be called whenever the selection changes. It will update the selected options cache, the current
// value, and the display value
private selectionChanged() {
// @internal This method must be called whenever the selection changes. It will update the selected options cache, the
// current value, and the display value. The option component uses it internally to update labels as they change.
public selectionChanged() {
const options = this.getAllOptions();
// Update selected options cache
@@ -711,6 +713,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
this.updateValidity();
});
}
protected get tags() {
return this.selectedOptions.map((option, index) => {
if (index < this.maxOptionsVisible || this.maxOptionsVisible <= 0) {

View File

@@ -15,6 +15,14 @@
-webkit-user-select: none;
cursor: pointer;
transition: color var(--wa-transition-fast) var(--wa-transition-easing);
::slotted(wa-icon:first-child) {
margin-inline-end: var(--wa-space-xs);
}
::slotted(wa-icon:last-child) {
margin-inline-start: var(--wa-space-xs);
}
}
:host(:hover:not([disabled])) .tab {

View File

@@ -81,10 +81,6 @@ input:is([type='button'], [type='reset'], [type='submit']),
* States
*/
&::-moz-focus-inner {
border: 0;
}
&:focus {
outline: none;
}
@@ -103,6 +99,11 @@ input:is([type='button'], [type='reset'], [type='submit']),
pointer-events: none;
}
}
/* Keep it last so Safari doesn't stop parsing this block */
&::-moz-focus-inner {
border: 0;
}
}
/**

View File

@@ -38,6 +38,16 @@ input[type='checkbox']:where(:not(:host *)) {
height: 100%;
width: 100%;
}
&:indeterminate::after {
background-color: currentColor;
content: '';
mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" height="16" width="14" viewBox="0 0 448 512"><path d="M431 256c0 17.7-14.3 32-32 32H49c-17.7 0-32-14.3-32-32s14.3-32 32-32h350c17.7 0 32 14.3 32 32z"/></svg>')
center no-repeat;
position: absolute;
height: 100%;
width: 100%;
}
}
input[type='checkbox']:where(:not(:host *)),

View File

@@ -20,6 +20,11 @@
}
/* Horizontal */
.wa-button-group[aria-orientation='horizontal'] {
/* TODO - see https://github.com/shoelace-style/webawesome/issues/374 */
align-items: end;
}
.wa-button-group:not([aria-orientation='vertical']):not(.wa-button-group-vertical) {
> :not(:first-child),
&::slotted(:not(:first-child)) {

View File

@@ -3,3 +3,12 @@ import { startLoader } from './webawesome.js';
export * from './webawesome.js';
startLoader();
// Remove `wa-cloak` when the autoloader finishes OR after two seconds. This prevents the entire screen from flashing
// when unregistered components get added later on.
Promise.race([
new Promise(resolve => document.addEventListener('wa-discovery-complete', resolve)),
new Promise(resolve => setTimeout(resolve, 2000)),
]).then(() => {
document.querySelectorAll('.wa-cloak').forEach(el => el.classList.remove('wa-cloak'));
});