Compare commits

...

7 Commits

Author SHA1 Message Date
Cory LaViska
1791f27dda simplify internals and fix track width bug; fixes #1317 2025-11-18 15:18:26 -05:00
Jesse Jurman
e2f2a8a0d3 Fix: Allow wa-hide to be cancelled in WaDropdown (#1774)
* Fix: Allow wa-hide to be cancelled in WaDropdown

* add tests around open attribute / property

* update changelog
2025-11-18 14:07:53 -05:00
Christian Oliff
86293cc3e1 Remove width property from select component (#1736)
Deleted the duplicate 'width: 100%' style from the select component (already defined on line 56). Also fixed a typo in a comment in the docs CSS. You should setup Stylelint to prevent these issues :-)
2025-11-18 10:03:29 -05:00
Fred
8fb521d9ef Adding a reflect property option to the name property (#1716) 2025-11-17 22:15:18 +00:00
Kelsey Jackson
2491ca45ac Fix appearance="filled-outlined" in <wa-card> (#1694)
* upated filled-outlined attribute for cards

* updated selector

* updated changelog

* updated heading

* updated css

* remove final `~=` selector (thanks @Copilot)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Lindsay M <126139086+lindsaym-fa@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-17 15:12:23 -05:00
Cory LaViska
411d385d65 improve icon perf; closes #1729 (#1783) 2025-11-17 14:49:45 -05:00
Dimitri
634828d89a fix(wa-icon): add waitUntilFirstUpdate: true option to the setIcon() watch decorator (#1738) 2025-11-17 14:49:32 -05:00
11 changed files with 166 additions and 53 deletions

View File

@@ -38,7 +38,7 @@ wa-page > [slot='banner'] {
}
&.banner-wa-launch {
/* custom brand colors carrried over from theme-site for the banner */
/* custom brand colors carried over from theme-site for the banner */
--wa-color-brand-95: #fef0ec;
--wa-color-brand-90: #fce0d8;
--wa-color-brand-80: #f8bcac;

View File

@@ -13,8 +13,12 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh
## Next
- 🚨 BREAKING: Changed `appearance="filled outlined"` to `appearance="filled-outlined"` in `<wa-card>` [issue:1671]
- Fixed a bug in `<wa-slider>` that caused some touch devices to end up with the incorrect value [issue:1703]
- Fixed a bug in `<wa-card>` that prevented some slots from being detected correctly [discuss:1450]
- Fixed a bug in `<wa-dropdown>` that caused the browser to hang when cancelling the `wa-hide` event [issue:1483]
- Fixed a bug in `<wa-spinner>` that caused `--track-width` to not work correctly [issue:1317]
- Improved performance of `<wa-icon>` so initial rendering occurs faster, especially with multiple icons on the page [issue:1729]
## 3.0.0
@@ -22,6 +26,7 @@ Components with the <wa-badge variant="warning">Experimental</wa-badge> badge sh
- `<wa-badge>`
- `<wa-button>`
- `<wa-callout>`
- `<wa-card>`
- `<wa-details>`
- `<wa-input>`
- `<wa-select>`

View File

@@ -16,27 +16,28 @@
}
/* Appearance modifiers */
:host([appearance~='plain']) {
:host([appearance='plain']) {
background-color: transparent;
border-color: transparent;
box-shadow: none;
}
:host([appearance~='outlined']) {
:host([appearance='outlined']) {
background-color: var(--wa-color-surface-default);
border-color: var(--wa-color-surface-border);
}
:host([appearance~='filled']) {
:host([appearance='filled']) {
background-color: var(--wa-color-neutral-fill-quiet);
border-color: transparent;
}
:host([appearance~='filled'][appearance~='outlined']) {
border-color: var(--wa-color-neutral-border-quiet);
:host([appearance='filled-outlined']) {
background-color: var(--wa-color-neutral-fill-quiet);
border-color: var(--wa-color-surface-border);
}
:host([appearance~='accent']) {
:host([appearance='accent']) {
color: var(--wa-color-neutral-on-loud);
background-color: var(--wa-color-neutral-fill-loud);
border-color: transparent;

View File

@@ -42,7 +42,7 @@ export default class WaCard extends WebAwesomeElement {
/** The card's visual appearance. */
@property({ reflect: true })
appearance: 'accent' | 'filled' | 'outlined' | 'plain' = 'outlined';
appearance: 'accent' | 'filled' | 'outlined' | 'filled-outlined' | 'plain' = 'outlined';
/** Renders the card with a header. Only needed for SSR, otherwise is automatically added. */
@property({ attribute: 'with-header', type: Boolean, reflect: true }) withHeader = false;

View File

@@ -1,4 +1,6 @@
import { expect, fixture, html } from '@open-wc/testing';
import { aTimeout, expect, fixture, html, waitUntil } from '@open-wc/testing';
import sinon from 'sinon';
import type WaDropdown from './dropdown.js';
describe('<wa-dropdown>', () => {
it('should render a component', async () => {
@@ -6,4 +8,112 @@ describe('<wa-dropdown>', () => {
expect(el).to.exist;
});
it('should respect the open attribute when included', async () => {
const el = await fixture<WaDropdown>(html`
<wa-dropdown open>
<wa-button slot="trigger">Dropdown</wa-button>
<wa-dropdown-item>One</wa-dropdown-item>
</wa-dropdown>
`);
await el.updateComplete;
await aTimeout(200);
expect(el.open).to.be.true;
});
it('should fire a single show/after-show and hide/after-hide in normal open/close flow', async () => {
const el = await fixture<WaDropdown>(html`
<wa-dropdown>
<wa-button slot="trigger">Dropdown</wa-button>
<wa-dropdown-item>One</wa-dropdown-item>
<wa-dropdown-item>Two</wa-dropdown-item>
</wa-dropdown>
`);
// setup spies to track how often we see different show/hide events
const showSpy = sinon.spy();
const afterShowSpy = sinon.spy();
const hideSpy = sinon.spy();
const afterHideSpy = sinon.spy();
el.addEventListener('wa-show', showSpy);
el.addEventListener('wa-after-show', afterShowSpy);
el.addEventListener('wa-hide', hideSpy);
el.addEventListener('wa-after-hide', afterHideSpy);
// open the dropdown by triggering a click on the trigger
const trigger = el.querySelector<HTMLElement>('[slot="trigger"]')!;
trigger.click();
await waitUntil(() => showSpy.calledOnce);
await waitUntil(() => afterShowSpy.calledOnce);
expect(showSpy.callCount).to.equal(1);
expect(afterShowSpy.callCount).to.equal(1);
expect(el.open).to.be.true;
// close the dropdown by clicking the trigger again
trigger.click();
await waitUntil(() => hideSpy.calledOnce);
await waitUntil(() => afterHideSpy.calledOnce);
expect(hideSpy.callCount).to.equal(1);
expect(afterHideSpy.callCount).to.equal(1);
expect(el.open).to.be.false;
});
it('should fire a single show/after-show and hide/after-hide when wa-hide event is cancelled', async () => {
const el = await fixture<WaDropdown>(html`
<wa-dropdown>
<wa-button slot="trigger">Dropdown</wa-button>
<wa-dropdown-item>One</wa-dropdown-item>
<wa-dropdown-item>Two</wa-dropdown-item>
</wa-dropdown>
`);
// setup spies to track how often we see different show/hide events
const showSpy = sinon.spy();
const afterShowSpy = sinon.spy();
const hideSpy = sinon.spy();
const afterHideSpy = sinon.spy();
el.addEventListener('wa-show', showSpy);
el.addEventListener('wa-after-show', afterShowSpy);
// Intercept wa-hide and prevent it
el.addEventListener('wa-hide', event => {
event.preventDefault();
hideSpy(event);
});
el.addEventListener('wa-after-hide', afterHideSpy);
// open the dropdown by triggering a click on the trigger
const trigger = el.querySelector<HTMLElement>('[slot="trigger"]')!;
trigger.click();
await waitUntil(() => showSpy.calledOnce);
await waitUntil(() => afterShowSpy.calledOnce);
expect(showSpy.callCount).to.equal(1);
expect(afterShowSpy.callCount).to.equal(1);
expect(el.open).to.be.true;
// click on the trigger (which should do nothing to the open state)
trigger.click();
await waitUntil(() => hideSpy.calledOnce);
expect(hideSpy.callCount).to.equal(1);
// after-hide should not have been called if hide is cancelled
expect(afterHideSpy.callCount).to.equal(0);
expect(el.open).to.be.true;
});
});

View File

@@ -109,6 +109,18 @@ export default class WaDropdown extends WebAwesomeElement {
async updated(changedProperties: PropertyValues) {
if (changedProperties.has('open')) {
const previousOpen = changedProperties.get('open');
// check if the previous value is the same
// (if they are, do not trigger menu showing / hiding)
if (previousOpen === this.open) {
return;
}
// check if we are changing from undefined to false
// (if we are, we can skip menu hiding)
if (previousOpen === undefined && this.open === false) {
return;
}
this.customStates.set('open', this.open);
if (this.open) {
@@ -227,6 +239,12 @@ export default class WaDropdown extends WebAwesomeElement {
return;
}
// if this dropdown is already open, do nothing
// (this can happen when wa-hide was cancelled)
if (this.popup.active) {
return;
}
openDropdowns.forEach(dropdown => (dropdown.open = false));
this.popup.active = true; // Use wa-popup's active property instead of showPopover

View File

@@ -187,7 +187,7 @@ export default class WaIcon extends WebAwesomeElement {
}
}
@watch(['family', 'name', 'library', 'variant', 'src', 'autoWidth', 'swapOpacity'])
@watch(['family', 'name', 'library', 'variant', 'src', 'autoWidth', 'swapOpacity'], { waitUntilFirstUpdate: true })
async setIcon() {
const { url, fromLibrary } = this.getIconSource();
const library = fromLibrary ? getIconLibrary(this.library) : undefined;

View File

@@ -74,7 +74,6 @@
padding: 0 var(--wa-form-control-padding-inline);
position: relative;
vertical-align: middle;
width: 100%;
transition:
background-color var(--wa-transition-normal),
border var(--wa-transition-normal),

View File

@@ -119,7 +119,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
@state() optionValues: Set<string | null> | undefined;
/** The name of the select, submitted as a name/value pair with form data. */
@property() name = '';
@property({ reflect: true }) name = '';
private _defaultValue: null | string | string[] = null;

View File

@@ -2,58 +2,43 @@
--track-width: 2px;
--track-color: var(--wa-color-neutral-fill-normal);
--indicator-color: var(--wa-color-brand-fill-loud);
--speed: 2s;
--speed: 1s;
/* Resizing a spinner element using anything but font-size will break the animation because the animation uses em units.
Therefore, if a spinner is used in a flex container without `flex: none` applied, the spinner can grow/shrink and
break the animation. The use of `flex: none` on the host element prevents this by always having the spinner sized
according to its actual dimensions.
*/
flex: none;
display: inline-flex;
width: 1em;
height: 1em;
}
svg {
[part='base'] {
position: relative;
width: 100%;
height: 100%;
aspect-ratio: 1;
animation: spin var(--speed) linear infinite;
}
.track {
stroke: var(--track-color);
position: absolute;
inset: 0;
border: var(--track-width) solid var(--track-color);
border-radius: 50%;
}
.indicator {
stroke: var(--indicator-color);
stroke-dasharray: 75, 100;
stroke-dashoffset: -5;
animation: dash 1.5s ease-in-out infinite;
stroke-linecap: round;
position: absolute;
inset: 0;
border: var(--track-width) solid transparent;
border-top-color: var(--indicator-color);
border-right-color: var(--indicator-color);
border-radius: 50%;
animation: spin var(--speed) linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes dash {
0% {
stroke-dasharray: 1, 150;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -35;
}
100% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -124;
}
}

View File

@@ -11,6 +11,8 @@ import styles from './spinner.css';
* @since 2.0
*
* @csspart base - The component's base wrapper.
* @csspart track - The spinner's track.
* @csspart indicator - The spinner's indicator.
*
* @cssproperty --track-width - The width of the track.
* @cssproperty --track-color - The color of the track.
@@ -25,17 +27,10 @@ export default class WaSpinner extends WebAwesomeElement {
render() {
return html`
<svg
part="base"
role="progressbar"
aria-label=${this.localize.term('loading')}
fill="none"
viewBox="0 0 50 50"
xmlns="http://www.w3.org/2000/svg"
>
<circle class="track" cx="25" cy="25" r="20" fill="none" stroke-width="5" />
<circle class="indicator" cx="25" cy="25" r="20" fill="none" stroke-width="5" />
</svg>
<div part="base" role="progressbar" aria-label=${this.localize.term('loading')}>
<div class="track" part="track"></div>
<div class="indicator" part="indicator"></div>
</div>
`;
}
}