input / change always bubble now (#1057)

* make sure relayNativeEvent is synchronous

* prettier

* changelog entry

* fix event
This commit is contained in:
Konnor Rogers
2025-06-13 13:19:47 -04:00
committed by GitHub
parent b99d7771dc
commit 07327be95e
11 changed files with 125 additions and 54 deletions

View File

@@ -14,6 +14,7 @@ During the alpha period, things might break! We take breaking changes very serio
## Next
- 🚨 BREAKING: `input` and `change` events on form controls like `<wa-input>` now are always set to `bubble` and `compose`.
- 🚨 BREAKING: Greatly simplified how native styles work and removed redundant utilities
- Removed `.wa-button`, `.wa-callout` classes
- Removed `themes/native/*.css` files; use `native.css` to opt into native styles
@@ -377,4 +378,4 @@ Here's a list of some of the things that have changed since Shoelace v2. For que
Did we miss something? [Let us know!](https://github.com/shoelace-style/webawesome-alpha/discussions)
Are you coming from Shoelace? [The 2.x changelog can be found here.](https://shoelace.style/resources/changelog/)
Are you coming from Shoelace? [The 2.x changelog can be found here.](https://shoelace.style/resources/changelog/)

View File

@@ -132,7 +132,9 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement {
this.hasInteracted = true;
this.checked = !this.checked;
this.indeterminate = false;
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
@watch('defaultChecked')

View File

@@ -300,6 +300,7 @@ describe('<wa-color-picker>', () => {
await sendKeys({ type: 'fc0' }); // type in a color
input.blur(); // commit changes by blurring the field
await el.updateComplete;
await aTimeout(1);
expect(changeHandler).to.have.been.calledOnce;
expect(inputHandler).to.have.been.calledOnce;

View File

@@ -275,8 +275,11 @@ 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', { bubbles: true, composed: true }));
this.dispatchEvent(new InputEvent('input'));
this.updateComplete.then(() => {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
});
}
private handleAlphaDrag(event: PointerEvent) {
@@ -296,13 +299,18 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
if (this.value !== currentValue) {
currentValue = this.value;
this.dispatchEvent(new InputEvent('input'));
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
});
}
},
onStop: () => {
if (this.value !== initialValue) {
initialValue = this.value;
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
},
initialEvent: event,
@@ -326,13 +334,17 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
if (this.value !== currentValue) {
currentValue = this.value;
this.dispatchEvent(new InputEvent('input'));
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input'));
});
}
},
onStop: () => {
if (this.value !== initialValue) {
initialValue = this.value;
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
},
initialEvent: event,
@@ -359,14 +371,18 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
if (this.value !== currentValue) {
currentValue = this.value;
this.dispatchEvent(new InputEvent('input'));
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
});
}
},
onStop: () => {
this.isDraggingGridHandle = false;
if (this.value !== initialValue) {
initialValue = this.value;
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
},
initialEvent: event,
@@ -402,8 +418,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
}
if (this.value !== oldValue) {
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
}
@@ -436,8 +454,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
}
if (this.value !== oldValue) {
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
}
@@ -470,8 +490,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
}
if (this.value !== oldValue) {
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
}
@@ -490,8 +512,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
}
if (this.value !== oldValue) {
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
}
@@ -511,8 +535,10 @@ 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', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
setTimeout(() => this.input.select());
@@ -696,8 +722,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
this.setColor(colorSelectionResult.sRGBHex);
if (this.value !== oldValue) {
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
})
.catch(() => {
@@ -712,8 +740,10 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement {
this.setColor(color);
if (this.value !== oldValue) {
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
}
}

View File

@@ -223,8 +223,9 @@ export default class WaInput extends WebAwesomeFormAssociatedElement {
@property({ attribute: 'with-hint', type: Boolean }) withHint = false;
private handleChange(event: Event) {
this.relayNativeEvent(event, { bubbles: true, composed: true });
this.value = this.input.value;
this.relayNativeEvent(event, { bubbles: true, composed: true });
}
private handleClearClick(event: MouseEvent) {
@@ -232,9 +233,12 @@ 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', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new WaClearEvent());
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
this.input.focus();

View File

@@ -171,8 +171,10 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
}
if (this.value !== oldValue) {
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
};
@@ -274,8 +276,10 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement {
}
if (this.value !== oldValue) {
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
event.preventDefault();

View File

@@ -101,7 +101,9 @@ export default class WaRating extends WebAwesomeElement {
}
this.setValue(this.getValueFromMousePosition(event));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
private setValue(newValue: number) {
@@ -145,7 +147,9 @@ export default class WaRating extends WebAwesomeElement {
}
if (this.value !== oldValue) {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
}
@@ -178,7 +182,9 @@ export default class WaRating extends WebAwesomeElement {
private handleTouchEnd(event: TouchEvent) {
this.isHovering = false;
this.setValue(this.hoverValue);
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
// Prevent click on mobile devices
event.preventDefault();

View File

@@ -381,7 +381,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
// Emit after updating
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
@@ -511,7 +511,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
// Emit after update
this.updateComplete.then(() => {
this.dispatchEvent(new WaClearEvent());
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
@@ -542,7 +542,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
if (this.value !== oldValue) {
// Emit after updating
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
@@ -600,7 +600,7 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement {
// Emit after updating
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input'));
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}

View File

@@ -228,7 +228,9 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
},
stop: () => {
if (this.minValue !== this.valueWhenDraggingStarted) {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
this.hasInteracted = true;
}
this.hideRangeTooltips();
@@ -251,7 +253,9 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
},
stop: () => {
if (this.maxValue !== this.valueWhenDraggingStarted) {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
this.hasInteracted = true;
}
this.hideRangeTooltips();
@@ -321,7 +325,9 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
if (this.activeThumb) {
const currentValue = this.activeThumb === 'min' ? this.minValue : this.maxValue;
if (currentValue !== this.valueWhenDraggingStarted) {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
this.hasInteracted = true;
}
}
@@ -346,7 +352,10 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
},
stop: () => {
if (this.value !== this.valueWhenDraggingStarted) {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
this.hasInteracted = true;
}
this.hideTooltip();
@@ -602,8 +611,10 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
}
// Dispatch events
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
this.hasInteracted = true;
}
@@ -625,7 +636,9 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
// Dispatch input events when the value changes by dragging
if (this.value !== oldValue) {
this.dispatchEvent(new InputEvent('input'));
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
});
}
}
@@ -658,8 +671,10 @@ export default class WaSlider extends WebAwesomeFormAssociatedElement {
// Dispatch input events
if (oldValue !== (thumb === 'min' ? this.minValue : this.maxValue)) {
this.dispatchEvent(new InputEvent('input'));
this.updateFormValue();
this.updateComplete.then(() => {
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
});
}
}

View File

@@ -117,22 +117,29 @@ export default class WaSwitch extends WebAwesomeFormAssociatedElement {
private handleClick() {
this.hasInteracted = true;
this.checked = !this.checked;
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.updateComplete.then(() => {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
});
}
private handleKeyDown(event: KeyboardEvent) {
if (event.key === 'ArrowLeft') {
event.preventDefault();
this.checked = false;
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.dispatchEvent(new InputEvent('input'));
this.updateComplete.then(() => {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
});
}
if (event.key === 'ArrowRight') {
event.preventDefault();
this.checked = true;
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.dispatchEvent(new InputEvent('input'));
this.updateComplete.then(() => {
this.dispatchEvent(new Event('change', { bubbles: true, composed: true }));
this.dispatchEvent(new InputEvent('input', { bubbles: true, composed: true }));
});
}
}

View File

@@ -206,13 +206,14 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement {
this.valueHasChanged = true;
this.value = this.input.value;
this.setTextareaDimensions();
this.relayNativeEvent(event, { bubbles: true, composed: true });
this.checkValidity();
this.relayNativeEvent(event, { bubbles: true, composed: true });
}
private handleInput() {
private handleInput(event: InputEvent) {
this.valueHasChanged = true;
this.value = this.input.value;
this.relayNativeEvent(event, { bubbles: true, composed: true });
}
private setTextareaDimensions() {