diff --git a/custom-elements-manifest.js b/custom-elements-manifest.js index f60557583..03e3f5ab5 100644 --- a/custom-elements-manifest.js +++ b/custom-elements-manifest.js @@ -9,10 +9,6 @@ const packageData = JSON.parse(fs.readFileSync('./package.json', 'utf8')); const { name, description, version, author, homepage, license } = packageData; const outdir = 'dist'; -function noDash(string) { - return string.replace(/^\s?-/, '').trim(); -} - function replace(string, terms) { terms.forEach(({ from, to }) => { string = string?.replace(from, to); @@ -106,6 +102,7 @@ export default { if (classDoc?.events) { classDoc.events.forEach(event => { + if (!event.name) return; event.reactName = `on${pascalCase(event.name)}`; event.eventName = `${pascalCase(event.name)}Event`; }); @@ -174,12 +171,15 @@ export default { url: `https://shoelace.style/components/${tag.replace('wa-', '')}` }; } - }), - - customElementVuejsPlugin({ - outdir: './dist/types/vue', - fileName: 'index.d.ts', - componentTypePath: (_, tag) => `../../components/${tag.replace('wa-', '')}/${tag.replace('wa-', '')}.js` }) + + // + // TODO - figure out why this broke when events were updated + // + // customElementVuejsPlugin({ + // outdir: './dist/types/vue', + // fileName: 'index.d.ts', + // componentTypePath: (_, tag) => `../../components/${tag.replace('wa-', '')}/${tag.replace('wa-', '')}.js` + // }) ] }; diff --git a/docs/_layouts/component.njk b/docs/_layouts/component.njk index 045f4515e..2df14c5d0 100644 --- a/docs/_layouts/component.njk +++ b/docs/_layouts/component.njk @@ -184,10 +184,12 @@ {% for event in component.events %} - - {{ event.name }} - {{ event.description | inlineMarkdown | safe }} - + {% if event.name %} + + {{ event.name }} + {{ event.description | inlineMarkdown | safe }} + + {% endif %} {% endfor %} diff --git a/docs/docs/components/dialog.md b/docs/docs/components/dialog.md index 96b3e4ed2..132e725a4 100644 --- a/docs/docs/components/dialog.md +++ b/docs/docs/components/dialog.md @@ -392,13 +392,13 @@ const App = () => { By default, dialogs will close when the user clicks the close button, clicks the overlay, or presses the [[Escape]] key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur. -To keep the dialog open in such cases, you can cancel the `wa-request-close` event. When canceled, the dialog will remain open and pulse briefly to draw the user's attention to it. +To keep the dialog open in such cases, you can cancel the `wa-hide` event. When canceled, the dialog will remain open and pulse briefly to draw the user's attention to it. You can use `event.detail.source` to determine which element triggered the request to close. This example prevents the dialog from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it. ```html {.example} - This dialog will only close when you click the right button. + This dialog will only close when you click the button below. Only this button will close it @@ -412,7 +412,7 @@ You can use `event.detail.source` to determine which element triggered the reque openButton.addEventListener('click', () => dialog.open = true); // Prevent the dialog from closing unless the close button was clicked - dialog.addEventListener('wa-request-close', event => { + dialog.addEventListener('wa-hide', event => { if (event.detail.source !== closeButton) { event.preventDefault(); } diff --git a/docs/docs/components/drawer.md b/docs/docs/components/drawer.md index e36f3cb3a..bf339be14 100644 --- a/docs/docs/components/drawer.md +++ b/docs/docs/components/drawer.md @@ -137,7 +137,7 @@ const App = () => { ### Dismissing Drawers -You can add the special `data-dialog="dismiss"` attribute to a button inside the drawer to tell it to close without additional JavaScript. Alternatively, you can set the `open` property to `false` to close the drawer programmatically. +You can add the special `data-drawer="dismiss"` attribute to a button inside the drawer to tell it to close without additional JavaScript. Alternatively, you can set the `open` property to `false` to close the drawer programmatically. ```html {.example} @@ -521,13 +521,13 @@ const App = () => { By default, drawers will close when the user clicks the close button, clicks the overlay, or presses the [[Escape]] key. In most cases, the default behavior is the best behavior in terms of UX. However, there are situations where this may be undesirable, such as when data loss will occur. -To keep the drawer open in such cases, you can cancel the `wa-request-close` event. When canceled, the drawer will remain open and pulse briefly to draw the user's attention to it. +To keep the drawer open in such cases, you can cancel the `wa-hide` event. When canceled, the drawer will remain open and pulse briefly to draw the user's attention to it. You can use `event.detail.source` to determine what triggered the request to close. This example prevents the drawer from closing when the overlay is clicked, but allows the close button or [[Escape]] to dismiss it. ```html {.example} - This drawer will not close when you click on the overlay. + This drawer will only close when you click the button below. Close @@ -536,12 +536,13 @@ You can use `event.detail.source` to determine what triggered the request to clo ``` -All custom events are prefixed with `sl-` to prevent collisions with standard events and other libraries. Refer to a component's documentation for a complete list of its custom events. +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 diff --git a/scripts/shared.js b/scripts/shared.js index dcdba6e58..c1614b925 100644 --- a/scripts/shared.js +++ b/scripts/shared.js @@ -9,6 +9,14 @@ export function getAllComponents(metadata) { const path = module.path; if (component) { + // Calling `new Event()` adds a blank entry into the CEM, so we'll filter them out here + if (component.events) { + component.events = component.events.filter(event => { + return event.name ? true : false; + }); + } + + // component.events = component.events.filter(event => !!event.name); allComponents.push(Object.assign(component, { path })); } } diff --git a/src/components/animated-image/animated-image.ts b/src/components/animated-image/animated-image.ts index d1aab1df4..f6531e876 100644 --- a/src/components/animated-image/animated-image.ts +++ b/src/components/animated-image/animated-image.ts @@ -1,6 +1,8 @@ import '../icon/icon.js'; import { customElement, property, query, state } from 'lit/decorators.js'; import { html } from 'lit'; +import { WaErrorEvent } from '../../events/error.js'; +import { WaLoadEvent } from '../../events/load.js'; import { watch } from '../../internal/watch.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './animated-image.styles.js'; @@ -57,13 +59,13 @@ export default class WaAnimatedImage extends WebAwesomeElement { this.frozenFrame = canvas.toDataURL('image/gif'); if (!this.isLoaded) { - this.emit('wa-load'); + this.dispatchEvent(new WaLoadEvent()); this.isLoaded = true; } } private handleError() { - this.emit('wa-error'); + this.dispatchEvent(new WaErrorEvent()); } @watch('play', { waitUntilFirstUpdate: true }) diff --git a/src/components/animation/animation.ts b/src/components/animation/animation.ts index c6fea2e85..eb63fdb84 100644 --- a/src/components/animation/animation.ts +++ b/src/components/animation/animation.ts @@ -1,6 +1,9 @@ import { animations } from './animations.js'; import { customElement, property, queryAsync } from 'lit/decorators.js'; import { html } from 'lit'; +import { WaCancelEvent } from '../../events/cancel.js'; +import { WaFinishEvent } from '../../events/finish.js'; +import { WaStartEvent } from '../../events/start.js'; import { watch } from '../../internal/watch.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './animation.styles.js'; @@ -102,13 +105,13 @@ export default class WaAnimation extends WebAwesomeElement { private handleAnimationFinish = () => { this.play = false; this.hasStarted = false; - this.emit('wa-finish'); + this.dispatchEvent(new WaFinishEvent()); }; private handleAnimationCancel = () => { this.play = false; this.hasStarted = false; - this.emit('wa-cancel'); + this.dispatchEvent(new WaCancelEvent()); }; private handleSlotChange() { @@ -143,7 +146,7 @@ export default class WaAnimation extends WebAwesomeElement { if (this.play) { this.hasStarted = true; - this.emit('wa-start'); + this.dispatchEvent(new WaStartEvent()); } else { this.animation.pause(); } @@ -185,7 +188,7 @@ export default class WaAnimation extends WebAwesomeElement { if (this.animation) { if (this.play && !this.hasStarted) { this.hasStarted = true; - this.emit('wa-start'); + this.dispatchEvent(new WaStartEvent()); } if (this.play) { diff --git a/src/components/button/button.ts b/src/components/button/button.ts index 85ea6a3c2..53f77b8e4 100644 --- a/src/components/button/button.ts +++ b/src/components/button/button.ts @@ -6,6 +6,9 @@ import { html, literal } from 'lit/static-html.js'; import { ifDefined } from 'lit/directives/if-defined.js'; import { LocalizeController } from '../../utilities/localize.js'; import { MirrorValidator } from '../../internal/validators/mirror-validator.js'; +import { WaBlurEvent } from '../../events/blur.js'; +import { WaFocusEvent } from '../../events/focus.js'; +import { WaInvalidEvent } from '../../events/invalid.js'; import { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js'; import componentStyles from '../../styles/component.styles.js'; @@ -148,12 +151,12 @@ export default class WaButton extends WebAwesomeFormAssociatedElement { private handleBlur() { this.hasFocus = false; - this.emit('wa-blur'); + this.dispatchEvent(new WaBlurEvent()); } private handleFocus() { this.hasFocus = true; - this.emit('wa-focus'); + this.dispatchEvent(new WaFocusEvent()); } private handleClick() { @@ -193,7 +196,7 @@ export default class WaButton extends WebAwesomeFormAssociatedElement { } private handleInvalid() { - this.emit('wa-invalid'); + this.dispatchEvent(new WaInvalidEvent()); } private isButton() { diff --git a/src/components/carousel/carousel.ts b/src/components/carousel/carousel.ts index 2671b4c74..64f219464 100644 --- a/src/components/carousel/carousel.ts +++ b/src/components/carousel/carousel.ts @@ -11,6 +11,7 @@ import { map } from 'lit/directives/map.js'; import { prefersReducedMotion } from '../../internal/animate.js'; import { range } from 'lit/directives/range.js'; import { waitForEvent } from '../../internal/event.js'; +import { WaSlideChangeEvent } from '../../events/slide-change.js'; import { watch } from '../../internal/watch.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './carousel.styles.js'; @@ -387,12 +388,12 @@ export default class WaCarousel extends WebAwesomeElement { // Do not emit an event on first render if (this.hasUpdated) { - this.emit('wa-slide-change', { - detail: { + this.dispatchEvent( + new WaSlideChangeEvent({ index: this.activeSlide, slide: slides[this.activeSlide] - } - }); + }) + ); } } diff --git a/src/components/checkbox/checkbox.ts b/src/components/checkbox/checkbox.ts index a4422c218..b359ad517 100644 --- a/src/components/checkbox/checkbox.ts +++ b/src/components/checkbox/checkbox.ts @@ -6,6 +6,10 @@ import { html } from 'lit'; import { ifDefined } from 'lit/directives/if-defined.js'; import { live } from 'lit/directives/live.js'; import { RequiredValidator } from '../../internal/validators/required-validator.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 { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js'; import componentStyles from '../../styles/component.styles.js'; @@ -113,21 +117,21 @@ export default class WaCheckbox extends WebAwesomeFormAssociatedElement { private handleClick() { this.checked = !this.checked; this.indeterminate = false; - this.emit('wa-change'); + this.dispatchEvent(new WaChangeEvent()); } private handleBlur() { this.hasFocus = false; - this.emit('wa-blur'); + this.dispatchEvent(new WaBlurEvent()); } private handleInput() { - this.emit('wa-input'); + this.dispatchEvent(new WaInputEvent()); } private handleFocus() { this.hasFocus = true; - this.emit('wa-focus'); + this.dispatchEvent(new WaFocusEvent()); } @watch('defaultChecked') diff --git a/src/components/color-picker/color-picker.test.ts b/src/components/color-picker/color-picker.test.ts index 00930521b..c74b6a20d 100644 --- a/src/components/color-picker/color-picker.test.ts +++ b/src/components/color-picker/color-picker.test.ts @@ -32,7 +32,7 @@ describe('', async () => { await aTimeout(200); // wait for the dropdown to open await el.updateComplete; - // Simulate a drag event. "sl-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; diff --git a/src/components/color-picker/color-picker.ts b/src/components/color-picker/color-picker.ts index f54a08902..e3cdf6e57 100644 --- a/src/components/color-picker/color-picker.ts +++ b/src/components/color-picker/color-picker.ts @@ -14,13 +14,16 @@ import { LocalizeController } from '../../utilities/localize.js'; import { RequiredValidator } from '../../internal/validators/required-validator.js'; import { styleMap } from 'lit/directives/style-map.js'; import { TinyColor } from '@ctrl/tinycolor'; +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 { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './color-picker.styles.js'; import type { CSSResultGroup } from 'lit'; -import type { WaChangeEvent } from '../../events/wa-change.js'; -import type { WaInputEvent } from '../../events/wa-input.js'; import type WaDropdown from '../dropdown/dropdown.js'; import type WaInput from '../input/input.js'; @@ -218,12 +221,12 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement { private handleFocusIn = () => { this.hasFocus = true; - this.emit('wa-focus'); + this.dispatchEvent(new WaFocusEvent()); }; private handleFocusOut = () => { this.hasFocus = false; - this.emit('wa-blur'); + this.dispatchEvent(new WaBlurEvent()); }; private handleFormatToggle() { @@ -231,8 +234,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.emit('wa-change'); - this.emit('wa-input'); + this.dispatchEvent(new WaChangeEvent()); + this.dispatchEvent(new WaInputEvent()); } private handleAlphaDrag(event: PointerEvent) { @@ -252,13 +255,13 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement { if (this.value !== currentValue) { currentValue = this.value; - this.emit('wa-input'); + this.dispatchEvent(new WaInputEvent()); } }, onStop: () => { if (this.value !== initialValue) { initialValue = this.value; - this.emit('wa-change'); + this.dispatchEvent(new WaChangeEvent()); } }, initialEvent: event @@ -282,13 +285,13 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement { if (this.value !== currentValue) { currentValue = this.value; - this.emit('wa-input'); + this.dispatchEvent(new WaInputEvent()); } }, onStop: () => { if (this.value !== initialValue) { initialValue = this.value; - this.emit('wa-change'); + this.dispatchEvent(new WaChangeEvent()); } }, initialEvent: event @@ -315,14 +318,14 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement { if (this.value !== currentValue) { currentValue = this.value; - this.emit('wa-input'); + this.dispatchEvent(new WaInputEvent()); } }, onStop: () => { this.isDraggingGridHandle = false; if (this.value !== initialValue) { initialValue = this.value; - this.emit('wa-change'); + this.dispatchEvent(new WaChangeEvent()); } }, initialEvent: event @@ -358,8 +361,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement { } if (this.value !== oldValue) { - this.emit('wa-change'); - this.emit('wa-input'); + this.dispatchEvent(new WaChangeEvent()); + this.dispatchEvent(new WaInputEvent()); } } @@ -392,8 +395,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement { } if (this.value !== oldValue) { - this.emit('wa-change'); - this.emit('wa-input'); + this.dispatchEvent(new WaChangeEvent()); + this.dispatchEvent(new WaInputEvent()); } } @@ -426,8 +429,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement { } if (this.value !== oldValue) { - this.emit('wa-change'); - this.emit('wa-input'); + this.dispatchEvent(new WaChangeEvent()); + this.dispatchEvent(new WaInputEvent()); } } @@ -446,8 +449,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement { } if (this.value !== oldValue) { - this.emit('wa-change'); - this.emit('wa-input'); + this.dispatchEvent(new WaChangeEvent()); + this.dispatchEvent(new WaInputEvent()); } } @@ -467,8 +470,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement { this.input.value = this.value; if (this.value !== oldValue) { - this.emit('wa-change'); - this.emit('wa-input'); + this.dispatchEvent(new WaChangeEvent()); + this.dispatchEvent(new WaInputEvent()); } setTimeout(() => this.input.select()); @@ -643,8 +646,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement { this.setColor(colorSelectionResult.sRGBHex); if (this.value !== oldValue) { - this.emit('wa-change'); - this.emit('wa-input'); + this.dispatchEvent(new WaChangeEvent()); + this.dispatchEvent(new WaInputEvent()); } }) .catch(() => { @@ -659,8 +662,8 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement { this.setColor(color); if (this.value !== oldValue) { - this.emit('wa-change'); - this.emit('wa-input'); + this.dispatchEvent(new WaChangeEvent()); + this.dispatchEvent(new WaInputEvent()); } } } @@ -785,7 +788,7 @@ export default class WaColorPicker extends WebAwesomeFormAssociatedElement { if (!this.disabled) { // By standards we have to emit a `wa-invalid` event here synchronously. - this.emit('wa-invalid'); + this.dispatchEvent(new WaInvalidEvent()); } return false; diff --git a/src/components/copy-button/copy-button.ts b/src/components/copy-button/copy-button.ts index 0233e550a..dd1835c22 100644 --- a/src/components/copy-button/copy-button.ts +++ b/src/components/copy-button/copy-button.ts @@ -5,6 +5,8 @@ import { classMap } from 'lit/directives/class-map.js'; import { customElement, property, query, state } from 'lit/decorators.js'; import { html } from 'lit'; import { LocalizeController } from '../../utilities/localize.js'; +import { WaCopyEvent } from '../../events/copy.js'; +import { WaErrorEvent } from '../../events/error.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './copy-button.styles.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; @@ -132,27 +134,23 @@ export default class WaCopyButton extends WebAwesomeElement { } else { // No target this.showStatus('error'); - this.emit('wa-error'); + this.dispatchEvent(new WaErrorEvent()); } } // No value if (!valueToCopy) { this.showStatus('error'); - this.emit('wa-error'); + this.dispatchEvent(new WaErrorEvent()); } else { try { await navigator.clipboard.writeText(valueToCopy); this.showStatus('success'); - this.emit('wa-copy', { - detail: { - value: valueToCopy - } - }); + this.dispatchEvent(new WaCopyEvent({ value: valueToCopy })); } catch (error) { // Rejected by browser this.showStatus('error'); - this.emit('wa-error'); + this.dispatchEvent(new WaErrorEvent()); } } } diff --git a/src/components/details/details.test.ts b/src/components/details/details.test.ts index b09e5095b..35157cc85 100644 --- a/src/components/details/details.test.ts +++ b/src/components/details/details.test.ts @@ -1,8 +1,8 @@ // cspell:dictionaries lorem-ipsum import { expect, fixture, html, waitUntil } from '@open-wc/testing'; import sinon from 'sinon'; -import type { WaHideEvent } from '../../events/wa-hide.js'; -import type { WaShowEvent } from '../../events/wa-show.js'; +import type { WaHideEvent } from '../../events/hide.js'; +import type { WaShowEvent } from '../../events/show.js'; import type WaDetails from './details.js'; describe('', () => { diff --git a/src/components/details/details.ts b/src/components/details/details.ts index 7fb04c08d..467fe7d8f 100644 --- a/src/components/details/details.ts +++ b/src/components/details/details.ts @@ -1,9 +1,13 @@ import '../icon/icon.js'; +import { animate, parseDuration, stopAnimations } from '../../internal/animate.js'; import { classMap } from 'lit/directives/class-map.js'; import { customElement, property, query } from 'lit/decorators.js'; import { html } from 'lit'; -import { parseDuration, stopAnimations } from '../../internal/animate.js'; +import { WaAfterHideEvent } from '../../events/after-hide.js'; +import { WaAfterShowEvent } from '../../events/after-show.js'; +import { WaHideEvent } from '../../events/hide.js'; import { waitForEvent } from '../../internal/event.js'; +import { WaShowEvent } from '../../events/show.js'; import { watch } from '../../internal/watch.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './details.styles.js'; @@ -132,8 +136,9 @@ export default class WaDetails extends WebAwesomeElement { if (this.open) { this.details.open = true; // Show - const slShow = this.emit('wa-show', { cancelable: true }); - if (slShow.defaultPrevented) { + const waShow = new WaShowEvent(); + this.dispatchEvent(waShow); + if (waShow.defaultPrevented) { this.open = false; this.details.open = false; return; @@ -142,7 +147,8 @@ export default class WaDetails extends WebAwesomeElement { await stopAnimations(this.body); const duration = parseDuration(getComputedStyle(this.body).getPropertyValue('--show-duration')); // We can't animate to 'auto', so use the scroll height for now - await this.body.animate( + await animate( + this.body, [ { height: '0', opacity: '0' }, { height: `${this.body.scrollHeight}px`, opacity: '1' } @@ -151,14 +157,15 @@ export default class WaDetails extends WebAwesomeElement { duration, easing: 'linear' } - ).finished; + ); this.body.style.height = 'auto'; - this.emit('wa-after-show'); + this.dispatchEvent(new WaAfterShowEvent()); } else { // Hide - const slHide = this.emit('wa-hide', { cancelable: true }); - if (slHide.defaultPrevented) { + const waHide = new WaHideEvent(); + this.dispatchEvent(waHide); + if (waHide.defaultPrevented) { this.details.open = true; this.open = true; return; @@ -167,17 +174,18 @@ export default class WaDetails extends WebAwesomeElement { await stopAnimations(this.body); const duration = parseDuration(getComputedStyle(this.body).getPropertyValue('--hide-duration')); // We can't animate from 'auto', so use the scroll height for now - await this.body.animate( + await animate( + this.body, [ { height: `${this.body.scrollHeight}px`, opacity: '1' }, { height: '0', opacity: '0' } ], { duration, easing: 'linear' } - ).finished; + ); this.body.style.height = 'auto'; this.details.open = false; - this.emit('wa-after-hide'); + this.dispatchEvent(new WaAfterHideEvent()); } } diff --git a/src/components/dialog/dialog.test.ts b/src/components/dialog/dialog.test.ts index 33abb250b..b5c5f63e7 100644 --- a/src/components/dialog/dialog.test.ts +++ b/src/components/dialog/dialog.test.ts @@ -98,12 +98,12 @@ describe('', () => { expect(getComputedStyle(el).display).to.equal('none'); }); - it('should not close when wa-request-close is prevented', async () => { + it('should not close when wa-hide is prevented', async () => { const el = await fixture(html` Lorem ipsum dolor sit amet, consectetur adipiscing elit. `); - el.addEventListener('wa-request-close', event => { + el.addEventListener('wa-hide', event => { event.preventDefault(); }); await sendKeys({ press: 'Escape' }); diff --git a/src/components/dialog/dialog.ts b/src/components/dialog/dialog.ts index 5123976bc..7caf0dcf9 100644 --- a/src/components/dialog/dialog.ts +++ b/src/components/dialog/dialog.ts @@ -5,6 +5,10 @@ import { customElement, property, query } from 'lit/decorators.js'; import { html } from 'lit'; import { LocalizeController } from '../../utilities/localize.js'; import { lockBodyScrolling, unlockBodyScrolling } from '../../internal/scroll.js'; +import { WaAfterHideEvent } from '../../events/after-hide.js'; +import { WaAfterShowEvent } from '../../events/after-show.js'; +import { WaHideEvent } from '../../events/hide.js'; +import { WaShowEvent } from '../../events/show.js'; import { watch } from '../../internal/watch.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './dialog.styles.js'; @@ -26,13 +30,12 @@ import type { CSSResultGroup } from 'lit'; * * @event wa-show - Emitted when the dialog opens. * @event wa-after-show - Emitted after the dialog opens and all animations are complete. - * @event wa-hide - Emitted when the dialog closes. - * @event wa-after-hide - Emitted after the dialog closes and all animations are complete. - * @event {{ source: Element }} wa-request-close - Emitted when the user attempts to close the dialog. Calling - * `event.preventDefault()` will keep the dialog open. You can inspect `event.detail.source` to see which element - * caused the dialog to close. If the source is the dialog element itself, the user has pressed [[Escape]] or the - * dialog has been closed programmatically. Avoid using this unless closing the dialog will result in destructive + * @event {{ source: Element }} wa-hide - Emitted when the dialog is requested to close. Calling + * `event.preventDefault()` will prevent the dialog from closing. You can inspect `event.detail.source` to see which + * element caused the dialog to close. If the source is the dialog element itself, the user has pressed [[Escape]] or + * the dialog has been closed programmatically. Avoid using this unless closing the dialog will result in destructive * behavior such as data loss. + * @event wa-after-hide - Emitted after the dialog closes and all animations are complete. * * @csspart header - The dialog's header. This element wraps the title and header actions. * @csspart header-actions - Optional actions to add to the header. Works best with ``. @@ -95,33 +98,31 @@ export default class WaDialog extends WebAwesomeElement { } private async requestClose(source: Element) { - const waRequestClose = this.emit('wa-request-close', { - cancelable: true, - detail: { source } - }); + // Hide + const waHideEvent = new WaHideEvent({ source }); + this.dispatchEvent(waHideEvent); - if (waRequestClose.defaultPrevented) { + if (waHideEvent.defaultPrevented) { this.open = true; animateWithClass(this.dialog, 'pulse'); - } else { - // Hide - this.emit('wa-hide'); - this.removeOpenListeners(); - - await animateWithClass(this.dialog, 'hide'); - - this.open = false; - this.dialog.close(); - unlockBodyScrolling(this); - - // Restore focus to the original trigger - const trigger = this.originalTrigger; - if (typeof trigger?.focus === 'function') { - setTimeout(() => trigger.focus()); - } - - this.emit('wa-after-hide'); + return; } + + this.removeOpenListeners(); + + await animateWithClass(this.dialog, 'hide'); + + this.open = false; + this.dialog.close(); + unlockBodyScrolling(this); + + // Restore focus to the original trigger + const trigger = this.originalTrigger; + if (typeof trigger?.focus === 'function') { + setTimeout(() => trigger.focus()); + } + + this.dispatchEvent(new WaAfterHideEvent()); } private addOpenListeners() { @@ -193,7 +194,13 @@ export default class WaDialog extends WebAwesomeElement { /** Shows the dialog. */ private async show() { // Show - this.emit('wa-show'); + const waShowEvent = new WaShowEvent(); + this.dispatchEvent(waShowEvent); + if (waShowEvent.defaultPrevented) { + this.open = false; + return; + } + this.addOpenListeners(); this.originalTrigger = document.activeElement as HTMLElement; this.open = true; @@ -211,7 +218,7 @@ export default class WaDialog extends WebAwesomeElement { await animateWithClass(this.dialog, 'show'); - this.emit('wa-after-show'); + this.dispatchEvent(new WaAfterShowEvent()); } render() { diff --git a/src/components/drawer/drawer.test.ts b/src/components/drawer/drawer.test.ts index 263827aff..f782d85fd 100644 --- a/src/components/drawer/drawer.test.ts +++ b/src/components/drawer/drawer.test.ts @@ -98,12 +98,12 @@ describe('', () => { expect(getComputedStyle(el).display).to.equal('none'); }); - it('should not close when wa-request-close is prevented', async () => { + it('should not close when wa-hide is prevented', async () => { const el = await fixture(html` Lorem ipsum dolor sit amet, consectetur adipiscing elit. `); - el.addEventListener('wa-request-close', event => { + el.addEventListener('wa-hide', event => { event.preventDefault(); }); await sendKeys({ press: 'Escape' }); diff --git a/src/components/drawer/drawer.ts b/src/components/drawer/drawer.ts index ef0dbe4fe..f90fc7e34 100644 --- a/src/components/drawer/drawer.ts +++ b/src/components/drawer/drawer.ts @@ -5,6 +5,10 @@ import { customElement, property, query } from 'lit/decorators.js'; import { html } from 'lit'; import { LocalizeController } from '../../utilities/localize.js'; import { lockBodyScrolling, unlockBodyScrolling } from '../../internal/scroll.js'; +import { WaAfterHideEvent } from '../../events/after-hide.js'; +import { WaAfterShowEvent } from '../../events/after-show.js'; +import { WaHideEvent } from '../../events/hide.js'; +import { WaShowEvent } from '../../events/show.js'; import { watch } from '../../internal/watch.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './drawer.styles.js'; @@ -28,10 +32,10 @@ import type { CSSResultGroup } from 'lit'; * @event wa-after-show - Emitted after the drawer opens and all animations are complete. * @event wa-hide - Emitted when the drawer closes. * @event wa-after-hide - Emitted after the drawer closes and all animations are complete. - * @event {{ source: Element }} wa-request-close - Emitted when the user attempts to close the drawer. Calling - * `event.preventDefault()` will keep the drawer open. You can inspect `event.detail.source` to see which element - * caused the drawer to close. If the source is the drawer element itself, the user has pressed [[Escape]] or the - * drawer has been closed programmatically. Avoid using this unless closing the drawer will result in destructive + * @event {{ source: Element }} wa-hide - Emitted when the drawer is requesting to close. Calling + * `event.preventDefault()` will prevent the dialog from closing. You can inspect `event.detail.source` to see which + * element caused the dialog to close. If the source is the dialog element itself, the user has pressed [[Escape]] or + * the dialog has been closed programmatically. Avoid using this unless closing the dialog will result in destructive * behavior such as data loss. * * @csspart header - The drawer's header. This element wraps the title and header actions. @@ -103,33 +107,31 @@ export default class WaDrawer extends WebAwesomeElement { } private async requestClose(source: Element) { - const waRequestClose = this.emit('wa-request-close', { - cancelable: true, - detail: { source } - }); + // Hide + const waHideEvent = new WaHideEvent({ source }); + this.dispatchEvent(waHideEvent); - if (waRequestClose.defaultPrevented) { + if (waHideEvent.defaultPrevented) { this.open = true; animateWithClass(this.drawer, 'pulse'); - } else { - // Hide - this.emit('wa-hide'); - this.removeOpenListeners(); - - await animateWithClass(this.drawer, 'hide'); - - this.open = false; - this.drawer.close(); - unlockBodyScrolling(this); - - // Restore focus to the original trigger - const trigger = this.originalTrigger; - if (typeof trigger?.focus === 'function') { - setTimeout(() => trigger.focus()); - } - - this.emit('wa-after-hide'); + return; } + + this.removeOpenListeners(); + + await animateWithClass(this.drawer, 'hide'); + + this.open = false; + this.drawer.close(); + unlockBodyScrolling(this); + + // Restore focus to the original trigger + const trigger = this.originalTrigger; + if (typeof trigger?.focus === 'function') { + setTimeout(() => trigger.focus()); + } + + this.dispatchEvent(new WaAfterHideEvent()); } private addOpenListeners() { @@ -201,7 +203,14 @@ export default class WaDrawer extends WebAwesomeElement { /** Shows the drawer. */ private async show() { // Show - this.emit('wa-show'); + const waShowEvent = new WaShowEvent(); + this.dispatchEvent(waShowEvent); + if (waShowEvent.defaultPrevented) { + this.open = false; + return; + } + + // Show this.addOpenListeners(); this.originalTrigger = document.activeElement as HTMLElement; this.open = true; @@ -219,7 +228,7 @@ export default class WaDrawer extends WebAwesomeElement { await animateWithClass(this.drawer, 'show'); - this.emit('wa-after-show'); + this.dispatchEvent(new WaAfterShowEvent()); } render() { diff --git a/src/components/dropdown/dropdown.ts b/src/components/dropdown/dropdown.ts index 2c3c16527..ab010c3d5 100644 --- a/src/components/dropdown/dropdown.ts +++ b/src/components/dropdown/dropdown.ts @@ -4,13 +4,17 @@ import { classMap } from 'lit/directives/class-map.js'; import { customElement, property, query } from 'lit/decorators.js'; import { html } from 'lit'; import { ifDefined } from 'lit/directives/if-defined.js'; +import { WaAfterHideEvent } from '../../events/after-hide.js'; +import { WaAfterShowEvent } from '../../events/after-show.js'; +import { WaHideEvent } from '../../events/hide.js'; import { waitForEvent } from '../../internal/event.js'; +import { WaShowEvent } from '../../events/show.js'; import { watch } from '../../internal/watch.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './dropdown.styles.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; import type { CSSResultGroup } from 'lit'; -import type { WaSelectEvent } from '../../events/wa-select.js'; +import type { WaSelectEvent } from '../../events/select.js'; import type WaButton from '../button/button.js'; import type WaIconButton from '../icon-button/icon-button.js'; import type WaMenu from '../menu/menu.js'; @@ -361,24 +365,32 @@ export default class WaDropdown extends WebAwesomeElement { if (this.open) { // Show - this.emit('wa-show'); - this.addOpenListeners(); + const waShowEvent = new WaShowEvent(); + this.dispatchEvent(waShowEvent); + if (waShowEvent.defaultPrevented) { + this.open = false; + return; + } + this.addOpenListeners(); this.panel.hidden = false; this.popup.active = true; await animateWithClass(this.popup.popup, 'show-with-scale'); - - this.emit('wa-after-show'); + this.dispatchEvent(new WaAfterShowEvent()); } else { // Hide - this.emit('wa-hide'); - this.removeOpenListeners(); + const waHideEvent = new WaHideEvent(); + this.dispatchEvent(waHideEvent); + if (waHideEvent.defaultPrevented) { + this.open = true; + return; + } + this.removeOpenListeners(); await animateWithClass(this.popup.popup, 'hide-with-scale'); this.panel.hidden = true; this.popup.active = false; - - this.emit('wa-after-hide'); + this.dispatchEvent(new WaAfterHideEvent()); } } diff --git a/src/components/icon-button/icon-button.ts b/src/components/icon-button/icon-button.ts index 9d6fc010d..f9c368dd0 100644 --- a/src/components/icon-button/icon-button.ts +++ b/src/components/icon-button/icon-button.ts @@ -3,6 +3,8 @@ import { classMap } from 'lit/directives/class-map.js'; import { customElement, property, query, state } from 'lit/decorators.js'; import { html, literal } from 'lit/static-html.js'; import { ifDefined } from 'lit/directives/if-defined.js'; +import { WaBlurEvent } from '../../events/blur.js'; +import { WaFocusEvent } from '../../events/focus.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './icon-button.styles.js'; @@ -73,12 +75,12 @@ export default class WaIconButton extends WebAwesomeFormAssociatedElement { private handleBlur() { this.hasFocus = false; - this.emit('wa-blur'); + this.dispatchEvent(new WaBlurEvent()); } private handleFocus() { this.hasFocus = true; - this.emit('wa-focus'); + this.dispatchEvent(new WaFocusEvent()); } private handleClick(event: MouseEvent) { diff --git a/src/components/icon/icon.test.ts b/src/components/icon/icon.test.ts index 57d1e1c63..9d7f3c21d 100644 --- a/src/components/icon/icon.test.ts +++ b/src/components/icon/icon.test.ts @@ -1,7 +1,7 @@ import { aTimeout, elementUpdated, expect, fixture, html, oneEvent } from '@open-wc/testing'; import { registerIconLibrary } from '../../../dist/webawesome.js'; -import type { WaErrorEvent } from '../../events/wa-error.js'; -import type { WaLoadEvent } from '../../events/wa-load.js'; +import type { WaErrorEvent } from '../../events/error.js'; +import type { WaLoadEvent } from '../../events/load.js'; import type WaIcon from './icon.js'; const testLibraryIcons = { diff --git a/src/components/icon/icon.ts b/src/components/icon/icon.ts index b7d62539d..8faf5dc08 100644 --- a/src/components/icon/icon.ts +++ b/src/components/icon/icon.ts @@ -2,6 +2,8 @@ import { customElement, property, state } from 'lit/decorators.js'; import { getIconLibrary, type IconLibrary, unwatchIcon, watchIcon } from './library.js'; import { html } from 'lit'; import { isTemplateResult } from 'lit/directive-helpers.js'; +import { WaErrorEvent } from '../../events/error.js'; +import { WaLoadEvent } from '../../events/load.js'; import { watch } from '../../internal/watch.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './icon.styles.js'; @@ -206,12 +208,12 @@ export default class WaIcon extends WebAwesomeElement { case RETRYABLE_ERROR: case CACHEABLE_ERROR: this.svg = null; - this.emit('wa-error'); + this.dispatchEvent(new WaErrorEvent()); break; default: this.svg = svg.cloneNode(true) as SVGElement; library?.mutator?.(this.svg); - this.emit('wa-load'); + this.dispatchEvent(new WaLoadEvent()); } } diff --git a/src/components/image-comparer/image-comparer.ts b/src/components/image-comparer/image-comparer.ts index 941c9781c..9f3467ec3 100644 --- a/src/components/image-comparer/image-comparer.ts +++ b/src/components/image-comparer/image-comparer.ts @@ -5,6 +5,7 @@ import { customElement, property, query } from 'lit/decorators.js'; import { drag } from '../../internal/drag.js'; import { html } from 'lit'; import { styleMap } from 'lit/directives/style-map.js'; +import { WaChangeEvent } from '../../events/change.js'; import { watch } from '../../internal/watch.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './image-comparer.styles.js'; @@ -89,7 +90,7 @@ export default class WaImageComparer extends WebAwesomeElement { @watch('position', { waitUntilFirstUpdate: true }) handlePositionChange() { - this.emit('wa-change'); + this.dispatchEvent(new WaChangeEvent()); } render() { diff --git a/src/components/include/include.test.ts b/src/components/include/include.test.ts index acd165635..33611d6ac 100644 --- a/src/components/include/include.test.ts +++ b/src/components/include/include.test.ts @@ -48,7 +48,7 @@ describe('', () => { expect(loadHandler).to.have.been.calledOnce; }); - it('should emit wa-error when content cannot be loaded', async () => { + it('should emit wa-include-error when content cannot be loaded', async () => { sinon.stub(window, 'fetch').resolves({ ...stubbedFetchResponse, ok: false, @@ -58,7 +58,7 @@ describe('', () => { const el = await fixture(html` `); const loadHandler = sinon.spy(); - el.addEventListener('wa-error', loadHandler); + el.addEventListener('wa-include-error', loadHandler); await waitUntil(() => loadHandler.calledOnce); expect(loadHandler).to.have.been.calledOnce; diff --git a/src/components/include/include.ts b/src/components/include/include.ts index 36ab62140..fe789a3f6 100644 --- a/src/components/include/include.ts +++ b/src/components/include/include.ts @@ -1,6 +1,8 @@ import { customElement, property } from 'lit/decorators.js'; import { html } from 'lit'; import { requestInclude } from './request.js'; +import { WaIncludeErrorEvent } from '../../events/include-error.js'; +import { WaLoadEvent } from '../../events/load.js'; import { watch } from '../../internal/watch.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './include.styles.js'; @@ -55,7 +57,7 @@ export default class WaInclude extends WebAwesomeElement { } if (!file.ok) { - this.emit('wa-error', { detail: { status: file.status } }); + this.dispatchEvent(new WaIncludeErrorEvent({ status: file.status })); return; } @@ -65,9 +67,9 @@ export default class WaInclude extends WebAwesomeElement { [...this.querySelectorAll('script')].forEach(script => this.executeScript(script)); } - this.emit('wa-load'); + this.dispatchEvent(new WaLoadEvent()); } catch { - this.emit('wa-error', { detail: { status: -1 } }); + this.dispatchEvent(new WaIncludeErrorEvent({ status: -1 })); } } diff --git a/src/components/input/input.ts b/src/components/input/input.ts index 4eb842c8a..7f87f32f6 100644 --- a/src/components/input/input.ts +++ b/src/components/input/input.ts @@ -7,6 +7,11 @@ import { ifDefined } from 'lit/directives/if-defined.js'; import { live } from 'lit/directives/live.js'; import { LocalizeController } from '../../utilities/localize.js'; import { MirrorValidator } from '../../internal/validators/mirror-validator.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 { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js'; import componentStyles from '../../styles/component.styles.js'; @@ -204,12 +209,12 @@ export default class WaInput extends WebAwesomeFormAssociatedElement { private handleBlur() { this.hasFocus = false; - this.emit('wa-blur'); + this.dispatchEvent(new WaBlurEvent()); } private handleChange() { this.value = this.input.value; - this.emit('wa-change'); + this.dispatchEvent(new WaChangeEvent()); } private handleClearClick(event: MouseEvent) { @@ -217,9 +222,9 @@ export default class WaInput extends WebAwesomeFormAssociatedElement { if (this.value !== '') { this.value = ''; - this.emit('wa-clear'); - this.emit('wa-input'); - this.emit('wa-change'); + this.dispatchEvent(new WaClearEvent()); + this.dispatchEvent(new WaInputEvent()); + this.dispatchEvent(new WaChangeEvent()); } this.input.focus(); @@ -227,12 +232,12 @@ export default class WaInput extends WebAwesomeFormAssociatedElement { private handleFocus() { this.hasFocus = true; - this.emit('wa-focus'); + this.dispatchEvent(new WaFocusEvent()); } private handleInput() { this.value = this.input.value; - this.emit('wa-input'); + this.dispatchEvent(new WaInputEvent()); } private handleKeyDown(event: KeyboardEvent) { diff --git a/src/components/menu-item/menu-item.styles.ts b/src/components/menu-item/menu-item.styles.ts index 93a6a4a75..317b5715e 100644 --- a/src/components/menu-item/menu-item.styles.ts +++ b/src/components/menu-item/menu-item.styles.ts @@ -104,9 +104,10 @@ export default css` } :host(:focus-visible) .menu-item { - outline: none; - background-color: var(--wa-color-brand-spot); - color: var(--wa-color-brand-text-on-spot); + outline: var(--wa-focus-ring); + outline-offset: calc(-1 * var(--wa-focus-ring-width)); + background-color: var(--wa-color-neutral-fill-subtle); + color: var(--wa-color-neutral-text-on-fill); opacity: 1; } @@ -145,7 +146,7 @@ export default css` } } - ::slotted(sl-menu) { + ::slotted(wa-menu) { max-width: var(--auto-size-available-width) !important; max-height: var(--auto-size-available-height) !important; } diff --git a/src/components/menu-item/menu-item.test.ts b/src/components/menu-item/menu-item.test.ts index 14afbeebb..8ec838031 100644 --- a/src/components/menu-item/menu-item.test.ts +++ b/src/components/menu-item/menu-item.test.ts @@ -1,7 +1,7 @@ import { expect, fixture, html, waitUntil } from '@open-wc/testing'; import { sendKeys } from '@web/test-runner-commands'; import sinon from 'sinon'; -import type { WaSelectEvent } from '../../events/wa-select.js'; +import type { WaSelectEvent } from '../../events/select.js'; import type WaMenuItem from './menu-item.js'; describe('', () => { diff --git a/src/components/menu-item/menu-item.ts b/src/components/menu-item/menu-item.ts index f125cfa98..aac1c4b21 100644 --- a/src/components/menu-item/menu-item.ts +++ b/src/components/menu-item/menu-item.ts @@ -87,7 +87,8 @@ export default class WaMenuItem extends WebAwesomeElement { // When the label changes, emit a slotchange event so parent controls see it if (textLabel !== this.cachedTextLabel) { this.cachedTextLabel = textLabel; - this.emit('slotchange', { bubbles: true, composed: false, cancelable: false }); + /** @internal - prevent the CEM from recording this event */ + this.dispatchEvent(new Event('slotchange', { bubbles: true, composed: false, cancelable: false })); } } diff --git a/src/components/menu/menu.test.ts b/src/components/menu/menu.test.ts index 538088e3b..d89ec1291 100644 --- a/src/components/menu/menu.test.ts +++ b/src/components/menu/menu.test.ts @@ -3,7 +3,7 @@ import { expect, fixture } from '@open-wc/testing'; import { html } from 'lit'; import { sendKeys } from '@web/test-runner-commands'; import sinon from 'sinon'; -import type { WaSelectEvent } from '../../events/wa-select.js'; +import type { WaSelectEvent } from '../../events/select.js'; import type WaMenu from './menu.js'; describe('', () => { diff --git a/src/components/menu/menu.ts b/src/components/menu/menu.ts index 3e15329c4..922e1f9ca 100644 --- a/src/components/menu/menu.ts +++ b/src/components/menu/menu.ts @@ -1,6 +1,7 @@ import '../menu-item/menu-item.js'; import { customElement, query } from 'lit/decorators.js'; import { html } from 'lit'; +import { WaSelectEvent } from '../../events/select.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './menu.styles.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; @@ -48,7 +49,7 @@ export default class WaMenu extends WebAwesomeElement { item.checked = !item.checked; } - this.emit('wa-select', { detail: { item } }); + this.dispatchEvent(new WaSelectEvent({ item })); } private handleKeyDown(event: KeyboardEvent) { diff --git a/src/components/mutation-observer/mutation-observer.ts b/src/components/mutation-observer/mutation-observer.ts index b78afa0f5..485741acd 100644 --- a/src/components/mutation-observer/mutation-observer.ts +++ b/src/components/mutation-observer/mutation-observer.ts @@ -1,5 +1,6 @@ import { customElement, property } from 'lit/decorators.js'; import { html } from 'lit'; +import { WaMutationEvent } from '../../events/mutation.js'; import { watch } from '../../internal/watch.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './mutation-observer.styles.js'; @@ -59,9 +60,7 @@ export default class WaMutationObserver extends WebAwesomeElement { } private handleMutation = (mutationList: MutationRecord[]) => { - this.emit('wa-mutation', { - detail: { mutationList } - }); + this.dispatchEvent(new WaMutationEvent({ mutationList })); }; private startObserver() { diff --git a/src/components/option/option.ts b/src/components/option/option.ts index 367f0e71a..e59ec6f1f 100644 --- a/src/components/option/option.ts +++ b/src/components/option/option.ts @@ -69,7 +69,8 @@ export default class WaOption extends WebAwesomeElement { // When the label changes, emit a slotchange event so parent controls see it if (textLabel !== this.cachedTextLabel) { this.cachedTextLabel = textLabel; - this.emit('slotchange', { bubbles: true, composed: false, cancelable: false }); + /** @internal - prevent the CEM from recording this event */ + this.dispatchEvent(new Event('slotchange', { bubbles: true, composed: false, cancelable: false })); } } diff --git a/src/components/popup/popup.styles.ts b/src/components/popup/popup.styles.ts index 5d0cf07db..3eaaeb770 100644 --- a/src/components/popup/popup.styles.ts +++ b/src/components/popup/popup.styles.ts @@ -48,7 +48,7 @@ export default css` .popup-hover-bridge { position: fixed; - z-index: calc(var(--sl-z-index-dropdown) - 1); + z-index: calc(var(--wa-z-index-dropdown) - 1); top: 0; right: 0; bottom: 0; diff --git a/src/components/popup/popup.ts b/src/components/popup/popup.ts index 704028712..ba6adb52e 100644 --- a/src/components/popup/popup.ts +++ b/src/components/popup/popup.ts @@ -3,6 +3,7 @@ import { classMap } from 'lit/directives/class-map.js'; import { customElement, property, query } from 'lit/decorators.js'; import { html } from 'lit'; import { offsetParent } from 'composed-offset-position'; +import { WaRepositionEvent } from '../../events/reposition.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './popup.styles.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; @@ -470,7 +471,7 @@ export default class WaPopup extends WebAwesomeElement { // Wait until the new position is drawn before updating the hover bridge, otherwise it can get out of sync requestAnimationFrame(() => this.updateHoverBridge()); - this.emit('wa-reposition'); + this.dispatchEvent(new WaRepositionEvent()); } private updateHoverBridge = () => { diff --git a/src/components/radio-button/radio-button.ts b/src/components/radio-button/radio-button.ts index 88c06cc61..d2e0bd11a 100644 --- a/src/components/radio-button/radio-button.ts +++ b/src/components/radio-button/radio-button.ts @@ -3,6 +3,8 @@ import { customElement, property, query, state } from 'lit/decorators.js'; import { HasSlotController } from '../../internal/slot.js'; import { html } from 'lit/static-html.js'; import { ifDefined } from 'lit/directives/if-defined.js'; +import { WaBlurEvent } from '../../events/blur.js'; +import { WaFocusEvent } from '../../events/focus.js'; import { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js'; import componentStyles from '../../styles/component.styles.js'; @@ -76,7 +78,7 @@ export default class WaRadioButton extends WebAwesomeFormAssociatedElement { private handleBlur() { this.hasFocus = false; - this.emit('wa-blur'); + this.dispatchEvent(new WaBlurEvent()); } private handleClick(e: MouseEvent) { @@ -91,7 +93,7 @@ export default class WaRadioButton extends WebAwesomeFormAssociatedElement { private handleFocus() { this.hasFocus = true; - this.emit('wa-focus'); + this.dispatchEvent(new WaFocusEvent()); } @watch('disabled', { waitUntilFirstUpdate: true }) diff --git a/src/components/radio-group/radio-group.test.ts b/src/components/radio-group/radio-group.test.ts index ffe28ccd0..1db697f52 100644 --- a/src/components/radio-group/radio-group.test.ts +++ b/src/components/radio-group/radio-group.test.ts @@ -3,7 +3,7 @@ import { clickOnElement } from '../../internal/test.js'; import { runFormControlBaseTests } from '../../internal/test/form-control-base-tests.js'; import { sendKeys } from '@web/test-runner-commands'; import sinon from 'sinon'; -import type { WaChangeEvent } from '../../events/wa-change.js'; +import type { WaChangeEvent } from '../../events/change.js'; import type WaRadio from '../radio/radio.js'; import type WaRadioGroup from './radio-group.js'; diff --git a/src/components/radio-group/radio-group.ts b/src/components/radio-group/radio-group.ts index 6d6461341..9b5d0c7be 100644 --- a/src/components/radio-group/radio-group.ts +++ b/src/components/radio-group/radio-group.ts @@ -6,6 +6,8 @@ import { HasSlotController } from '../../internal/slot.js'; import { html } from 'lit'; import { RequiredValidator } from '../../internal/validators/required-validator.js'; import { uniqueId } from '../../internal/math.js'; +import { WaChangeEvent } from '../../events/change.js'; +import { WaInputEvent } from '../../events/input.js'; import { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js'; import componentStyles from '../../styles/component.styles.js'; @@ -123,8 +125,8 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement { } if (this.value !== oldValue) { - this.emit('wa-change'); - this.emit('wa-input'); + this.dispatchEvent(new WaChangeEvent()); + this.dispatchEvent(new WaInputEvent()); } }; @@ -259,8 +261,8 @@ export default class WaRadioGroup extends WebAwesomeFormAssociatedElement { } if (this.value !== oldValue) { - this.emit('wa-change'); - this.emit('wa-input'); + this.dispatchEvent(new WaChangeEvent()); + this.dispatchEvent(new WaInputEvent()); } event.preventDefault(); diff --git a/src/components/radio/radio.ts b/src/components/radio/radio.ts index d79113315..de3b0eabe 100644 --- a/src/components/radio/radio.ts +++ b/src/components/radio/radio.ts @@ -2,6 +2,8 @@ import '../icon/icon.js'; import { classMap } from 'lit/directives/class-map.js'; import { customElement, property, state } from 'lit/decorators.js'; import { html } from 'lit'; +import { WaBlurEvent } from '../../events/blur.js'; +import { WaFocusEvent } from '../../events/focus.js'; import { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js'; import componentStyles from '../../styles/component.styles.js'; @@ -76,12 +78,12 @@ export default class WaRadio extends WebAwesomeFormAssociatedElement { private handleBlur = () => { this.hasFocus = false; - this.emit('wa-blur'); + this.dispatchEvent(new WaBlurEvent()); }; private handleFocus = () => { this.hasFocus = true; - this.emit('wa-focus'); + this.dispatchEvent(new WaFocusEvent()); }; private setInitialAttributes() { diff --git a/src/components/range/range.ts b/src/components/range/range.ts index d9fd83615..03fc0a989 100644 --- a/src/components/range/range.ts +++ b/src/components/range/range.ts @@ -6,6 +6,10 @@ import { ifDefined } from 'lit/directives/if-defined.js'; import { live } from 'lit/directives/live.js'; import { LocalizeController } from '../../utilities/localize.js'; import { MirrorValidator } from '../../internal/validators/mirror-validator.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 { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js'; import componentStyles from '../../styles/component.styles.js'; @@ -128,25 +132,25 @@ export default class WaRange extends WebAwesomeFormAssociatedElement { } private handleChange() { - this.emit('wa-change'); + this.dispatchEvent(new WaChangeEvent()); } private handleInput() { this.value = parseFloat(this.input.value); - this.emit('wa-input'); + this.dispatchEvent(new WaInputEvent()); this.syncRange(); } private handleBlur() { this.hasFocus = false; this.hasTooltip = false; - this.emit('wa-blur'); + this.dispatchEvent(new WaBlurEvent()); } private handleFocus() { this.hasFocus = true; this.hasTooltip = true; - this.emit('wa-focus'); + this.dispatchEvent(new WaFocusEvent()); } @eventOptions({ passive: true }) diff --git a/src/components/rating/rating.ts b/src/components/rating/rating.ts index 9976a9a4d..1ad897c85 100644 --- a/src/components/rating/rating.ts +++ b/src/components/rating/rating.ts @@ -5,6 +5,8 @@ import { customElement, eventOptions, property, query, state } from 'lit/decorat import { html } from 'lit'; 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 { watch } from '../../internal/watch.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './rating.styles.js'; @@ -93,7 +95,7 @@ export default class WaRating extends WebAwesomeElement { } this.setValue(this.getValueFromMousePosition(event)); - this.emit('wa-change'); + this.dispatchEvent(new WaChangeEvent()); } private setValue(newValue: number) { @@ -137,7 +139,7 @@ export default class WaRating extends WebAwesomeElement { } if (this.value !== oldValue) { - this.emit('wa-change'); + this.dispatchEvent(new WaChangeEvent()); } } @@ -170,7 +172,7 @@ export default class WaRating extends WebAwesomeElement { private handleTouchEnd(event: TouchEvent) { this.isHovering = false; this.setValue(this.hoverValue); - this.emit('wa-change'); + this.dispatchEvent(new WaChangeEvent()); // Prevent click on mobile devices event.preventDefault(); @@ -183,22 +185,22 @@ export default class WaRating extends WebAwesomeElement { @watch('hoverValue') handleHoverValueChange() { - this.emit('wa-hover', { - detail: { + this.dispatchEvent( + new WaHoverEvent({ phase: 'move', value: this.hoverValue - } - }); + }) + ); } @watch('isHovering') handleIsHoveringChange() { - this.emit('wa-hover', { - detail: { + this.dispatchEvent( + new WaHoverEvent({ phase: this.isHovering ? 'start' : 'end', value: this.hoverValue - } - }); + }) + ); } /** Sets focus on the rating. */ diff --git a/src/components/resize-observer/resize-observer.ts b/src/components/resize-observer/resize-observer.ts index c432d97ff..219d45e9c 100644 --- a/src/components/resize-observer/resize-observer.ts +++ b/src/components/resize-observer/resize-observer.ts @@ -1,5 +1,6 @@ import { customElement, property } from 'lit/decorators.js'; import { html } from 'lit'; +import { WaResizeEvent } from '../../events/resize.js'; import { watch } from '../../internal/watch.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './resize-observer.styles.js'; @@ -29,7 +30,7 @@ export default class WaResizeObserver extends WebAwesomeElement { connectedCallback() { super.connectedCallback(); this.resizeObserver = new ResizeObserver((entries: ResizeObserverEntry[]) => { - this.emit('wa-resize', { detail: { entries } }); + this.dispatchEvent(new WaResizeEvent({ entries })); }); if (!this.disabled) { diff --git a/src/components/select/select.styles.ts b/src/components/select/select.styles.ts index e418fc5f3..629648299 100644 --- a/src/components/select/select.styles.ts +++ b/src/components/select/select.styles.ts @@ -85,7 +85,7 @@ export default css` } .select__display-input::placeholder { - color: var(--sl-input-placeholder-color); + color: var(--wa-form-controls-placeholder-color); } /* Visually hide the display input when multiple is enabled */ @@ -306,6 +306,7 @@ export default css` } .select__listbox ::slotted(small) { + display: block; font-size: var(--wa-font-size-s); font-weight: var(--wa-font-weight-medium); color: var(--wa-color-text-quiet); diff --git a/src/components/select/select.ts b/src/components/select/select.ts index f61459837..13ff19761 100644 --- a/src/components/select/select.ts +++ b/src/components/select/select.ts @@ -10,14 +10,23 @@ import { LocalizeController } from '../../utilities/localize.js'; import { RequiredValidator } from '../../internal/validators/required-validator.js'; import { scrollIntoView } from '../../internal/scroll.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 { waitForEvent } from '../../internal/event.js'; +import { WaShowEvent } from '../../events/show.js'; import { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js'; import componentStyles from '../../styles/component.styles.js'; import formControlStyles from '../../styles/form-control.styles.js'; import styles from './select.styles.js'; import type { CSSResultGroup, TemplateResult } from 'lit'; -import type { WaRemoveEvent } from '../../events/wa-remove.js'; +import type { WaRemoveEvent } from '../../events/remove.js'; import type WaOption from '../option/option.js'; import type WaPopup from '../popup/popup.js'; @@ -290,12 +299,12 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement { private handleFocus() { this.hasFocus = true; this.displayInput.setSelectionRange(0, 0); - this.emit('wa-focus'); + this.dispatchEvent(new WaFocusEvent()); } private handleBlur() { this.hasFocus = false; - this.emit('wa-blur'); + this.dispatchEvent(new WaBlurEvent()); } private handleDocumentFocusIn = (event: KeyboardEvent) => { @@ -346,8 +355,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement { // Emit after updating this.updateComplete.then(() => { - this.emit('wa-input'); - this.emit('wa-change'); + this.dispatchEvent(new WaInputEvent()); + this.dispatchEvent(new WaChangeEvent()); }); if (!this.multiple) { @@ -475,9 +484,9 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement { // Emit after update this.updateComplete.then(() => { - this.emit('wa-clear'); - this.emit('wa-input'); - this.emit('wa-change'); + this.dispatchEvent(new WaClearEvent()); + this.dispatchEvent(new WaInputEvent()); + this.dispatchEvent(new WaChangeEvent()); }); } } @@ -506,8 +515,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement { if (this.value !== oldValue) { // Emit after updating this.updateComplete.then(() => { - this.emit('wa-input'); - this.emit('wa-change'); + this.dispatchEvent(new WaInputEvent()); + this.dispatchEvent(new WaChangeEvent()); }); } @@ -543,8 +552,8 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement { // Emit after updating this.updateComplete.then(() => { - this.emit('wa-input'); - this.emit('wa-change'); + this.dispatchEvent(new WaInputEvent()); + this.dispatchEvent(new WaChangeEvent()); }); } } @@ -675,9 +684,14 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement { this.setCurrentOption(this.selectedOptions[0] || this.getFirstOption()); // Show - this.emit('wa-show'); - this.addOpenListeners(); + const waShowEvent = new WaShowEvent(); + this.dispatchEvent(waShowEvent); + if (waShowEvent.defaultPrevented) { + this.open = false; + return; + } + this.addOpenListeners(); this.listbox.hidden = false; this.popup.active = true; @@ -693,17 +707,22 @@ export default class WaSelect extends WebAwesomeFormAssociatedElement { scrollIntoView(this.currentOption, this.listbox, 'vertical', 'auto'); } - this.emit('wa-after-show'); + this.dispatchEvent(new WaAfterShowEvent()); } else { // Hide - this.emit('wa-hide'); - this.removeOpenListeners(); + const waHideEvent = new WaHideEvent(); + this.dispatchEvent(waHideEvent); + if (waHideEvent.defaultPrevented) { + this.open = false; + return; + } + this.removeOpenListeners(); await animateWithClass(this.popup.popup, 'hide'); this.listbox.hidden = true; this.popup.active = false; - this.emit('wa-after-hide'); + this.dispatchEvent(new WaAfterHideEvent()); } } diff --git a/src/components/split-panel/split-panel.ts b/src/components/split-panel/split-panel.ts index 8f28c4efe..896d29d2a 100644 --- a/src/components/split-panel/split-panel.ts +++ b/src/components/split-panel/split-panel.ts @@ -4,6 +4,7 @@ import { drag } from '../../internal/drag.js'; import { html } from 'lit'; import { ifDefined } from 'lit/directives/if-defined.js'; import { LocalizeController } from '../../utilities/localize.js'; +import { WaRepositionEvent } from '../../events/reposition.js'; import { watch } from '../../internal/watch.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './split-panel.styles.js'; @@ -208,7 +209,7 @@ export default class WaSplitPanel extends WebAwesomeElement { handlePositionChange() { this.cachedPositionInPixels = this.percentageToPixels(this.position); this.positionInPixels = this.percentageToPixels(this.position); - this.emit('wa-reposition'); + this.dispatchEvent(new WaRepositionEvent()); } @watch('positionInPixels') diff --git a/src/components/switch/switch.test.ts b/src/components/switch/switch.test.ts index 9a10d3a6e..3536b16c7 100644 --- a/src/components/switch/switch.test.ts +++ b/src/components/switch/switch.test.ts @@ -183,11 +183,11 @@ describe('', async () => { `); const button = form.querySelector('wa-button')!; - const slSwitch = form.querySelector('wa-switch')!; + const waSwitch = form.querySelector('wa-switch')!; const submitHandler = sinon.spy((event: SubmitEvent) => event.preventDefault()); // Submitting the form after setting custom validity should not trigger the handler - slSwitch.setCustomValidity('Invalid selection'); + waSwitch.setCustomValidity('Invalid selection'); form.addEventListener('submit', submitHandler); button.click(); await aTimeout(100); @@ -196,13 +196,13 @@ describe('', async () => { }); it('should be invalid when required and unchecked', async () => { - const slSwitch = await fixture(html` `); - expect(slSwitch.checkValidity()).to.be.false; + const waSwitch = await fixture(html` `); + expect(waSwitch.checkValidity()).to.be.false; }); it('should be valid when required and checked', async () => { - const slSwitch = await fixture(html` `); - expect(slSwitch.checkValidity()).to.be.true; + const waSwitch = await fixture(html` `); + expect(waSwitch.checkValidity()).to.be.true; }); it('should be present in form data when using the form attribute and located outside of a
', async () => { @@ -222,14 +222,14 @@ describe('', async () => { it('should receive validation attributes ("states") even when novalidate is used on the parent form', async () => { const el = await fixture(html` `); - const slSwitch = el.querySelector('wa-switch')!; + const waSwitch = el.querySelector('wa-switch')!; - expect(slSwitch.hasAttribute('data-wa-required')).to.be.true; - expect(slSwitch.hasAttribute('data-wa-optional')).to.be.false; - expect(slSwitch.hasAttribute('data-wa-invalid')).to.be.true; - expect(slSwitch.hasAttribute('data-wa-valid')).to.be.false; - expect(slSwitch.hasAttribute('data-wa-user-invalid')).to.be.false; - expect(slSwitch.hasAttribute('data-wa-user-valid')).to.be.false; + expect(waSwitch.hasAttribute('data-wa-required')).to.be.true; + expect(waSwitch.hasAttribute('data-wa-optional')).to.be.false; + expect(waSwitch.hasAttribute('data-wa-invalid')).to.be.true; + expect(waSwitch.hasAttribute('data-wa-valid')).to.be.false; + expect(waSwitch.hasAttribute('data-wa-user-invalid')).to.be.false; + expect(waSwitch.hasAttribute('data-wa-user-valid')).to.be.false; }); }); diff --git a/src/components/switch/switch.ts b/src/components/switch/switch.ts index 89683dab0..f0c88b114 100644 --- a/src/components/switch/switch.ts +++ b/src/components/switch/switch.ts @@ -5,6 +5,10 @@ import { html } from 'lit'; import { ifDefined } from 'lit/directives/if-defined.js'; import { live } from 'lit/directives/live.js'; import { MirrorValidator } from '../../internal/validators/mirror-validator.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 { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js'; import componentStyles from '../../styles/component.styles.js'; @@ -101,36 +105,36 @@ export default class WaSwitch extends WebAwesomeFormAssociatedElement { private handleBlur() { this.hasFocus = false; - this.emit('wa-blur'); + this.dispatchEvent(new WaBlurEvent()); } private handleInput() { - this.emit('wa-input'); + this.dispatchEvent(new WaInputEvent()); } private handleClick() { this.checked = !this.checked; - this.emit('wa-change'); + this.dispatchEvent(new WaChangeEvent()); } private handleFocus() { this.hasFocus = true; - this.emit('wa-focus'); + this.dispatchEvent(new WaFocusEvent()); } private handleKeyDown(event: KeyboardEvent) { if (event.key === 'ArrowLeft') { event.preventDefault(); this.checked = false; - this.emit('wa-change'); - this.emit('wa-input'); + this.dispatchEvent(new WaChangeEvent()); + this.dispatchEvent(new WaInputEvent()); } if (event.key === 'ArrowRight') { event.preventDefault(); this.checked = true; - this.emit('wa-change'); - this.emit('wa-input'); + this.dispatchEvent(new WaChangeEvent()); + this.dispatchEvent(new WaInputEvent()); } } diff --git a/src/components/tab-group/tab-group.test.ts b/src/components/tab-group/tab-group.test.ts index 35afcd8af..d3aea7b31 100644 --- a/src/components/tab-group/tab-group.test.ts +++ b/src/components/tab-group/tab-group.test.ts @@ -6,7 +6,7 @@ import { queryByTestId } from '../../internal/test/data-testid-helpers.js'; import { sendKeys } from '@web/test-runner-commands'; import { waitForScrollingToEnd } from '../../internal/test/wait-for-scrolling.js'; import type { HTMLTemplateResult } from 'lit'; -import type { WaTabShowEvent } from '../../events/wa-tab-show.js'; +import type { WaTabShowEvent } from '../../events/tab-show.js'; import type WaTab from '../tab/tab.js'; import type WaTabGroup from './tab-group.js'; import type WaTabPanel from '../tab-panel/tab-panel.js'; diff --git a/src/components/tab-group/tab-group.ts b/src/components/tab-group/tab-group.ts index f0dcd3d2f..7d0b43cbd 100644 --- a/src/components/tab-group/tab-group.ts +++ b/src/components/tab-group/tab-group.ts @@ -6,6 +6,8 @@ import { customElement, property, query, state } from 'lit/decorators.js'; import { html } from 'lit'; import { LocalizeController } from '../../utilities/localize.js'; import { scrollIntoView } from '../../internal/scroll.js'; +import { WaTabHideEvent } from '../../events/tab-hide.js'; +import { WaTabShowEvent } from '../../events/tab-show.js'; import { watch } from '../../internal/watch.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './tab-group.styles.js'; @@ -270,10 +272,10 @@ export default class WaTabGroup extends WebAwesomeElement { // Emit events if (options.emitEvents) { if (previousTab) { - this.emit('wa-tab-hide', { detail: { name: previousTab.panel } }); + this.dispatchEvent(new WaTabHideEvent({ name: previousTab.panel })); } - this.emit('wa-tab-show', { detail: { name: this.activeTab.panel } }); + this.dispatchEvent(new WaTabShowEvent({ name: this.activeTab.panel })); } } } diff --git a/src/components/tag/tag.ts b/src/components/tag/tag.ts index d58badc20..a171cae13 100644 --- a/src/components/tag/tag.ts +++ b/src/components/tag/tag.ts @@ -3,6 +3,7 @@ import { classMap } from 'lit/directives/class-map.js'; import { customElement, property } from 'lit/decorators.js'; import { html } from 'lit'; import { LocalizeController } from '../../utilities/localize.js'; +import { WaRemoveEvent } from '../../events/remove.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './tag.styles.js'; import WebAwesomeElement from '../../internal/webawesome-element.js'; @@ -51,7 +52,7 @@ export default class WaTag extends WebAwesomeElement { @property({ type: Boolean }) removable = false; private handleRemoveClick() { - this.emit('wa-remove'); + this.dispatchEvent(new WaRemoveEvent()); } render() { diff --git a/src/components/textarea/textarea.ts b/src/components/textarea/textarea.ts index 6ddb8908b..72b892bdc 100644 --- a/src/components/textarea/textarea.ts +++ b/src/components/textarea/textarea.ts @@ -5,6 +5,10 @@ import { html } from 'lit'; import { ifDefined } from 'lit/directives/if-defined.js'; import { live } from 'lit/directives/live.js'; import { MirrorValidator } from '../../internal/validators/mirror-validator.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 { watch } from '../../internal/watch.js'; import { WebAwesomeFormAssociatedElement } from '../../internal/webawesome-element.js'; import componentStyles from '../../styles/component.styles.js'; @@ -165,25 +169,25 @@ export default class WaTextarea extends WebAwesomeFormAssociatedElement { private handleBlur() { this.hasFocus = false; - this.emit('wa-blur'); + this.dispatchEvent(new WaBlurEvent()); this.checkValidity(); } private handleChange() { this.value = this.input.value; this.setTextareaHeight(); - this.emit('wa-change'); + this.dispatchEvent(new WaChangeEvent()); this.checkValidity(); } private handleFocus() { this.hasFocus = true; - this.emit('wa-focus'); + this.dispatchEvent(new WaFocusEvent()); } private handleInput() { this.value = this.input.value; - this.emit('wa-input'); + this.dispatchEvent(new WaInputEvent()); } private setTextareaHeight() { diff --git a/src/components/tooltip/tooltip.ts b/src/components/tooltip/tooltip.ts index 259fc076c..8b6c75777 100644 --- a/src/components/tooltip/tooltip.ts +++ b/src/components/tooltip/tooltip.ts @@ -2,7 +2,11 @@ import { animateWithClass, stopAnimations } from '../../internal/animate.js'; import { classMap } from 'lit/directives/class-map.js'; import { customElement, property, query } from 'lit/decorators.js'; import { html } from 'lit'; +import { WaAfterHideEvent } from '../../events/after-hide.js'; +import { WaAfterShowEvent } from '../../events/after-show.js'; +import { WaHideEvent } from '../../events/hide.js'; import { waitForEvent } from '../../internal/event.js'; +import { WaShowEvent } from '../../events/show.js'; import { watch } from '../../internal/watch.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './tooltip.styles.js'; @@ -182,7 +186,13 @@ export default class WaTooltip extends WebAwesomeElement { } // Show - this.emit('wa-show'); + const waShowEvent = new WaShowEvent(); + this.dispatchEvent(waShowEvent); + if (waShowEvent.defaultPrevented) { + this.open = false; + return; + } + if ('CloseWatcher' in window) { this.closeWatcher?.destroy(); this.closeWatcher = new CloseWatcher(); @@ -199,10 +209,16 @@ export default class WaTooltip extends WebAwesomeElement { await animateWithClass(this.popup.popup, 'show-with-scale'); this.popup.reposition(); - this.emit('wa-after-show'); + this.dispatchEvent(new WaAfterShowEvent()); } else { // Hide - this.emit('wa-hide'); + const waHideEvent = new WaHideEvent(); + this.dispatchEvent(waHideEvent); + if (waHideEvent.defaultPrevented) { + this.open = false; + return; + } + this.closeWatcher?.destroy(); document.removeEventListener('keydown', this.handleDocumentKeyDown); @@ -211,7 +227,7 @@ export default class WaTooltip extends WebAwesomeElement { this.popup.active = false; this.body.hidden = true; - this.emit('wa-after-hide'); + this.dispatchEvent(new WaAfterHideEvent()); } } diff --git a/src/components/tree-item/tree-item.ts b/src/components/tree-item/tree-item.ts index adc0e64a0..3182253e4 100644 --- a/src/components/tree-item/tree-item.ts +++ b/src/components/tree-item/tree-item.ts @@ -1,12 +1,18 @@ import '../checkbox/checkbox.js'; import '../icon/icon.js'; import '../spinner/spinner.js'; +import { animate, parseDuration, stopAnimations } from '../../internal/animate.js'; import { classMap } from 'lit/directives/class-map.js'; import { customElement, property, query, state } from 'lit/decorators.js'; import { html } from 'lit'; import { live } from 'lit/directives/live.js'; import { LocalizeController } from '../../utilities/localize.js'; -import { parseDuration, stopAnimations } from '../../internal/animate.js'; +import { WaAfterCollapseEvent } from '../../events/after-collapse.js'; +import { WaAfterExpandEvent } from '../../events/after-expand.js'; +import { WaCollapseEvent } from '../../events/collapse.js'; +import { WaExpandEvent } from '../../events/expand.js'; +import { WaLazyChangeEvent } from '../../events/lazy-change.js'; +import { WaLazyLoadEvent } from '../../events/lazy-load.js'; import { watch } from '../../internal/watch.js'; import { when } from 'lit/directives/when.js'; import componentStyles from '../../styles/component.styles.js'; @@ -114,21 +120,23 @@ export default class WaTreeItem extends WebAwesomeElement { } private async animateCollapse() { - this.emit('wa-collapse'); + this.dispatchEvent(new WaCollapseEvent()); await stopAnimations(this.childrenContainer); - // We can't animate from 'auto', so use the scroll height for now + const duration = parseDuration(getComputedStyle(this.childrenContainer).getPropertyValue('--hide-duration')); - await this.childrenContainer.animate( + await animate( + this.childrenContainer, [ + // We can't animate from 'auto', so use the scroll height for now { height: `${this.childrenContainer.scrollHeight}px`, opacity: '1', overflow: 'hidden' }, { height: '0', opacity: '0', overflow: 'hidden' } ], { duration, easing: 'cubic-bezier(0.4, 0.0, 0.2, 1)' } - ).finished; + ); this.childrenContainer.hidden = true; - this.emit('wa-after-collapse'); + this.dispatchEvent(new WaAfterCollapseEvent()); } // Checks whether the item is nested into an item @@ -149,13 +157,14 @@ export default class WaTreeItem extends WebAwesomeElement { } private async animateExpand() { - this.emit('wa-expand'); + this.dispatchEvent(new WaExpandEvent()); await stopAnimations(this.childrenContainer); this.childrenContainer.hidden = false; // We can't animate to 'auto', so use the scroll height for now const duration = parseDuration(getComputedStyle(this.childrenContainer).getPropertyValue('--show-duration')); - await this.childrenContainer.animate( + await animate( + this.childrenContainer, [ { height: '0', opacity: '0', overflow: 'hidden' }, { height: `${this.childrenContainer.scrollHeight}px`, opacity: '1', overflow: 'hidden' } @@ -164,10 +173,10 @@ export default class WaTreeItem extends WebAwesomeElement { duration, easing: 'cubic-bezier(0.4, 0.0, 0.2, 1)' } - ).finished; + ); this.childrenContainer.style.height = 'auto'; - this.emit('wa-after-expand'); + this.dispatchEvent(new WaAfterExpandEvent()); } @watch('loading', { waitUntilFirstUpdate: true }) @@ -203,8 +212,7 @@ export default class WaTreeItem extends WebAwesomeElement { if (this.expanded) { if (this.lazy) { this.loading = true; - - this.emit('wa-lazy-load'); + this.dispatchEvent(new WaLazyLoadEvent()); } else { this.animateExpand(); } @@ -215,7 +223,7 @@ export default class WaTreeItem extends WebAwesomeElement { @watch('lazy', { waitUntilFirstUpdate: true }) handleLazyChange() { - this.emit('wa-lazy-change'); + this.dispatchEvent(new WaLazyChangeEvent()); } /** Gets all the nested tree items in this node. */ diff --git a/src/components/tree/tree.test.ts b/src/components/tree/tree.test.ts index 8949057b5..03508b839 100644 --- a/src/components/tree/tree.test.ts +++ b/src/components/tree/tree.test.ts @@ -758,7 +758,7 @@ describe('', () => { Item 1 - + Item A diff --git a/src/components/tree/tree.ts b/src/components/tree/tree.ts index 5f01f95c9..e7b8bc4a7 100644 --- a/src/components/tree/tree.ts +++ b/src/components/tree/tree.ts @@ -1,6 +1,7 @@ import { clamp } from '../../internal/math.js'; import { customElement, property, query } from 'lit/decorators.js'; import { html } from 'lit'; +import { WaSelectionChangeEvent } from '../../events/selection-change.js'; import { watch } from '../../internal/watch.js'; import componentStyles from '../../styles/component.styles.js'; import styles from './tree.styles.js'; @@ -199,7 +200,7 @@ export default class WaTree extends WebAwesomeElement { ) { // Wait for the tree items' DOM to update before emitting Promise.all(nextSelection.map(el => el.updateComplete)).then(() => { - this.emit('wa-selection-change', { detail: { selection: nextSelection } }); + this.dispatchEvent(new WaSelectionChangeEvent({ selection: nextSelection })); }); } } diff --git a/src/events/after-collapse.ts b/src/events/after-collapse.ts new file mode 100644 index 000000000..cbb867b30 --- /dev/null +++ b/src/events/after-collapse.ts @@ -0,0 +1,11 @@ +export class WaAfterCollapseEvent extends Event { + constructor() { + super('wa-after-collapse', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-after-collapse': WaAfterCollapseEvent; + } +} diff --git a/src/events/after-expand.ts b/src/events/after-expand.ts new file mode 100644 index 000000000..47e7a2b64 --- /dev/null +++ b/src/events/after-expand.ts @@ -0,0 +1,11 @@ +export class WaAfterExpandEvent extends Event { + constructor() { + super('wa-after-expand', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-after-expand': WaAfterExpandEvent; + } +} diff --git a/src/events/after-hide.ts b/src/events/after-hide.ts new file mode 100644 index 000000000..b4133f129 --- /dev/null +++ b/src/events/after-hide.ts @@ -0,0 +1,11 @@ +export class WaAfterHideEvent extends Event { + constructor() { + super('wa-after-hide', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-after-hide': WaAfterHideEvent; + } +} diff --git a/src/events/after-show.ts b/src/events/after-show.ts new file mode 100644 index 000000000..d72183de0 --- /dev/null +++ b/src/events/after-show.ts @@ -0,0 +1,11 @@ +export class WaAfterShowEvent extends Event { + constructor() { + super('wa-after-show', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-after-show': WaAfterShowEvent; + } +} diff --git a/src/events/blur.ts b/src/events/blur.ts new file mode 100644 index 000000000..799099cf6 --- /dev/null +++ b/src/events/blur.ts @@ -0,0 +1,11 @@ +export class WaBlurEvent extends Event { + constructor() { + super('wa-blur', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-blur': WaBlurEvent; + } +} diff --git a/src/events/cancel.ts b/src/events/cancel.ts new file mode 100644 index 000000000..238aba7b0 --- /dev/null +++ b/src/events/cancel.ts @@ -0,0 +1,11 @@ +export class WaCancelEvent extends Event { + constructor() { + super('wa-cancel', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-cancel': WaCancelEvent; + } +} diff --git a/src/events/change.ts b/src/events/change.ts new file mode 100644 index 000000000..1abb38a89 --- /dev/null +++ b/src/events/change.ts @@ -0,0 +1,11 @@ +export class WaChangeEvent extends Event { + constructor() { + super('wa-change', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-change': WaChangeEvent; + } +} diff --git a/src/events/clear.ts b/src/events/clear.ts new file mode 100644 index 000000000..49c9b8acc --- /dev/null +++ b/src/events/clear.ts @@ -0,0 +1,11 @@ +export class WaClearEvent extends Event { + constructor() { + super('wa-clear', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-clear': WaClearEvent; + } +} diff --git a/src/events/close.ts b/src/events/close.ts new file mode 100644 index 000000000..81d06c17d --- /dev/null +++ b/src/events/close.ts @@ -0,0 +1,11 @@ +export class WaCloseEvent extends Event { + constructor() { + super('wa-close', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-close': WaCloseEvent; + } +} diff --git a/src/events/collapse.ts b/src/events/collapse.ts new file mode 100644 index 000000000..267cf6af4 --- /dev/null +++ b/src/events/collapse.ts @@ -0,0 +1,11 @@ +export class WaCollapseEvent extends Event { + constructor() { + super('wa-collapse', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-collapse': WaCollapseEvent; + } +} diff --git a/src/events/copy.ts b/src/events/copy.ts new file mode 100644 index 000000000..94736f855 --- /dev/null +++ b/src/events/copy.ts @@ -0,0 +1,18 @@ +export class WaCopyEvent extends Event { + readonly detail: WaCopyErrorEventDetail; + constructor(detail: WaCopyErrorEventDetail) { + super('wa-copy', { bubbles: true, cancelable: false, composed: true }); + this.detail = detail; + } +} + +interface WaCopyErrorEventDetail { + /** The value that occurred while copying. */ + value: string; +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-copy': WaCopyEvent; + } +} diff --git a/src/events/error.ts b/src/events/error.ts new file mode 100644 index 000000000..fa3b5341e --- /dev/null +++ b/src/events/error.ts @@ -0,0 +1,11 @@ +export class WaErrorEvent extends Event { + constructor() { + super('wa-error', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-error': WaErrorEvent; + } +} diff --git a/src/events/events.ts b/src/events/events.ts index 7785dafbd..e576977a9 100644 --- a/src/events/events.ts +++ b/src/events/events.ts @@ -1,34 +1,33 @@ -export type { WaAfterCollapseEvent } from './wa-after-collapse.js'; -export type { WaAfterExpandEvent } from './wa-after-expand.js'; -export type { WaAfterHideEvent } from './wa-after-hide.js'; -export type { WaAfterShowEvent } from './wa-after-show.js'; -export type { WaBlurEvent } from './wa-blur.js'; -export type { WaCancelEvent } from './wa-cancel.js'; -export type { WaChangeEvent } from './wa-change.js'; -export type { WaClearEvent } from './wa-clear.js'; -export type { WaCloseEvent } from './wa-close.js'; -export type { WaCollapseEvent } from './wa-collapse.js'; -export type { WaCopyEvent } from './wa-copy.js'; -export type { WaErrorEvent } from './wa-error.js'; -export type { WaExpandEvent } from './wa-expand.js'; -export type { WaFinishEvent } from './wa-finish.js'; -export type { WaFocusEvent } from './wa-focus.js'; -export type { WaHideEvent } from './wa-hide.js'; -export type { WaHoverEvent } from './wa-hover.js'; -export type { WaInputEvent } from './wa-input.js'; -export type { WaInvalidEvent } from './wa-invalid.js'; -export type { WaLazyChangeEvent } from './wa-lazy-change.js'; -export type { WaLazyLoadEvent } from './wa-lazy-load.js'; -export type { WaLoadEvent } from './wa-load.js'; -export type { WaMutationEvent } from './wa-mutation.js'; -export type { WaRemoveEvent } from './wa-remove.js'; -export type { WaRepositionEvent } from './wa-reposition.js'; -export type { WaRequestCloseEvent } from './wa-request-close.js'; -export type { WaResizeEvent } from './wa-resize.js'; -export type { WaSelectEvent } from './wa-select.js'; -export type { WaSelectionChangeEvent } from './wa-selection-change.js'; -export type { WaShowEvent } from './wa-show.js'; -export type { WaSlideChangeEvent } from './wa-slide-change.js'; -export type { WaStartEvent } from './wa-start.js'; -export type { WaTabHideEvent } from './wa-tab-hide.js'; -export type { WaTabShowEvent } from './wa-tab-show.js'; +export type { WaAfterCollapseEvent } from './after-collapse.js'; +export type { WaAfterExpandEvent } from './after-expand.js'; +export type { WaAfterHideEvent } from './after-hide.js'; +export type { WaAfterShowEvent } from './after-show.js'; +export type { WaBlurEvent } from './blur.js'; +export type { WaCancelEvent } from './cancel.js'; +export type { WaChangeEvent } from './change.js'; +export type { WaClearEvent } from './clear.js'; +export type { WaCloseEvent } from './close.js'; +export type { WaCollapseEvent } from './collapse.js'; +export type { WaCopyEvent } from './copy.js'; +export type { WaErrorEvent } from './error.js'; +export type { WaExpandEvent } from './expand.js'; +export type { WaFinishEvent } from './finish.js'; +export type { WaFocusEvent } from './focus.js'; +export type { WaHideEvent } from './hide.js'; +export type { WaHoverEvent } from './hover.js'; +export type { WaInputEvent } from './input.js'; +export type { WaInvalidEvent } from './invalid.js'; +export type { WaLazyChangeEvent } from './lazy-change.js'; +export type { WaLazyLoadEvent } from './lazy-load.js'; +export type { WaLoadEvent } from './load.js'; +export type { WaMutationEvent } from './mutation.js'; +export type { WaRemoveEvent } from './remove.js'; +export type { WaRepositionEvent } from './reposition.js'; +export type { WaResizeEvent } from './resize.js'; +export type { WaSelectEvent } from './select.js'; +export type { WaSelectionChangeEvent } from './selection-change.js'; +export type { WaShowEvent } from './show.js'; +export type { WaSlideChangeEvent } from './slide-change.js'; +export type { WaStartEvent } from './start.js'; +export type { WaTabHideEvent } from './tab-hide.js'; +export type { WaTabShowEvent } from './tab-show.js'; diff --git a/src/events/expand.ts b/src/events/expand.ts new file mode 100644 index 000000000..f544763bc --- /dev/null +++ b/src/events/expand.ts @@ -0,0 +1,11 @@ +export class WaExpandEvent extends Event { + constructor() { + super('wa-expand', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-expand': WaExpandEvent; + } +} diff --git a/src/events/finish.ts b/src/events/finish.ts new file mode 100644 index 000000000..6fd51a2e4 --- /dev/null +++ b/src/events/finish.ts @@ -0,0 +1,11 @@ +export class WaFinishEvent extends Event { + constructor() { + super('wa-finish', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-finish': WaFinishEvent; + } +} diff --git a/src/events/focus.ts b/src/events/focus.ts new file mode 100644 index 000000000..67cbf74c5 --- /dev/null +++ b/src/events/focus.ts @@ -0,0 +1,11 @@ +export class WaFocusEvent extends Event { + constructor() { + super('wa-focus', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-focus': WaFocusEvent; + } +} diff --git a/src/events/hide.ts b/src/events/hide.ts new file mode 100644 index 000000000..cdc4bffe5 --- /dev/null +++ b/src/events/hide.ts @@ -0,0 +1,18 @@ +export class WaHideEvent extends Event { + readonly detail: WaHideEventDetails | undefined; + + constructor(detail?: WaHideEventDetails) { + super('wa-hide', { bubbles: true, cancelable: true, composed: true }); + this.detail = detail; + } +} + +interface WaHideEventDetails { + source: Element; +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-hide': WaHideEvent; + } +} diff --git a/src/events/hover.ts b/src/events/hover.ts new file mode 100644 index 000000000..86577d302 --- /dev/null +++ b/src/events/hover.ts @@ -0,0 +1,19 @@ +interface WaHoverEventDetail { + phase: 'start' | 'move' | 'end'; + value: number; +} + +export class WaHoverEvent extends Event { + readonly detail: WaHoverEventDetail; + + constructor(detail: WaHoverEventDetail) { + super('wa-hover', { bubbles: true, cancelable: false, composed: true }); + this.detail = detail; + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-hover': WaHoverEvent; + } +} diff --git a/src/events/include-error.ts b/src/events/include-error.ts new file mode 100644 index 000000000..9296ba13f --- /dev/null +++ b/src/events/include-error.ts @@ -0,0 +1,18 @@ +export class WaIncludeErrorEvent extends Event { + readonly detail: WaIncludeErrorDetail; + + constructor(detail: WaIncludeErrorDetail) { + super('wa-include-error', { bubbles: true, cancelable: false, composed: true }); + this.detail = detail; + } +} + +interface WaIncludeErrorDetail { + status: number; +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-include-error': WaIncludeErrorEvent; + } +} diff --git a/src/events/input.ts b/src/events/input.ts new file mode 100644 index 000000000..5f72c413e --- /dev/null +++ b/src/events/input.ts @@ -0,0 +1,11 @@ +export class WaInputEvent extends Event { + constructor() { + super('wa-input', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-input': WaInputEvent; + } +} diff --git a/src/events/invalid.ts b/src/events/invalid.ts new file mode 100644 index 000000000..2eb17b9a7 --- /dev/null +++ b/src/events/invalid.ts @@ -0,0 +1,11 @@ +export class WaInvalidEvent extends Event { + constructor() { + super('wa-invalid', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-invalid': WaInvalidEvent; + } +} diff --git a/src/events/lazy-change.ts b/src/events/lazy-change.ts new file mode 100644 index 000000000..92a2907c7 --- /dev/null +++ b/src/events/lazy-change.ts @@ -0,0 +1,11 @@ +export class WaLazyChangeEvent extends Event { + constructor() { + super('wa-lazy-change', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-lazy-change': WaLazyChangeEvent; + } +} diff --git a/src/events/lazy-load.ts b/src/events/lazy-load.ts new file mode 100644 index 000000000..f259d38b9 --- /dev/null +++ b/src/events/lazy-load.ts @@ -0,0 +1,11 @@ +export class WaLazyLoadEvent extends Event { + constructor() { + super('wa-lazy-load', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-lazy-load': WaLazyLoadEvent; + } +} diff --git a/src/events/load.ts b/src/events/load.ts new file mode 100644 index 000000000..a061b45a3 --- /dev/null +++ b/src/events/load.ts @@ -0,0 +1,11 @@ +export class WaLoadEvent extends Event { + constructor() { + super('wa-load', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-load': WaLoadEvent; + } +} diff --git a/src/events/mutation.ts b/src/events/mutation.ts new file mode 100644 index 000000000..c6bc57b1e --- /dev/null +++ b/src/events/mutation.ts @@ -0,0 +1,18 @@ +export class WaMutationEvent extends Event { + readonly detail: WaMutationEventDetail; + + constructor(detail: WaMutationEventDetail) { + super('wa-mutation', { bubbles: true, cancelable: false, composed: true }); + this.detail = detail; + } +} + +interface WaMutationEventDetail { + mutationList: MutationRecord[]; +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-mutation': WaMutationEvent; + } +} diff --git a/src/events/remove.ts b/src/events/remove.ts new file mode 100644 index 000000000..8432185b8 --- /dev/null +++ b/src/events/remove.ts @@ -0,0 +1,11 @@ +export class WaRemoveEvent extends Event { + constructor() { + super('wa-remove', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-remove': WaRemoveEvent; + } +} diff --git a/src/events/reposition.ts b/src/events/reposition.ts new file mode 100644 index 000000000..0a49c79c3 --- /dev/null +++ b/src/events/reposition.ts @@ -0,0 +1,11 @@ +export class WaRepositionEvent extends Event { + constructor() { + super('wa-reposition', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-reposition': WaRepositionEvent; + } +} diff --git a/src/events/resize.ts b/src/events/resize.ts new file mode 100644 index 000000000..c2282bd88 --- /dev/null +++ b/src/events/resize.ts @@ -0,0 +1,18 @@ +export class WaResizeEvent extends Event { + readonly detail: WaResizeEventDetail; + + constructor(detail: WaResizeEventDetail) { + super('wa-resize', { bubbles: true, cancelable: false, composed: true }); + this.detail = detail; + } +} + +interface WaResizeEventDetail { + entries: ResizeObserverEntry[]; +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-resize': WaResizeEvent; + } +} diff --git a/src/events/select.ts b/src/events/select.ts new file mode 100644 index 000000000..667263445 --- /dev/null +++ b/src/events/select.ts @@ -0,0 +1,20 @@ +import type WaMenuItem from '../components/menu-item/menu-item.js'; + +export class WaSelectEvent extends Event { + readonly detail; + + constructor(detail: WaSelectEventDetail) { + super('wa-select', { bubbles: true, cancelable: false, composed: true }); + this.detail = detail; + } +} + +interface WaSelectEventDetail { + item: WaMenuItem; +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-select': WaSelectEvent; + } +} diff --git a/src/events/selection-change.ts b/src/events/selection-change.ts new file mode 100644 index 000000000..d622b6c5f --- /dev/null +++ b/src/events/selection-change.ts @@ -0,0 +1,20 @@ +import type WaTreeItem from '../components/tree-item/tree-item.js'; + +export class WaSelectionChangeEvent extends Event { + readonly detail: WaSelectionChangeEventDetail; + + constructor(detail: WaSelectionChangeEventDetail) { + super('wa-selection-change', { bubbles: true, cancelable: false, composed: true }); + this.detail = detail; + } +} + +interface WaSelectionChangeEventDetail { + selection: WaTreeItem[]; +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-selection-change': WaSelectionChangeEvent; + } +} diff --git a/src/events/show.ts b/src/events/show.ts new file mode 100644 index 000000000..695cd6f4a --- /dev/null +++ b/src/events/show.ts @@ -0,0 +1,11 @@ +export class WaShowEvent extends Event { + constructor() { + super('wa-show', { bubbles: true, cancelable: true, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-show': WaShowEvent; + } +} diff --git a/src/events/slide-change.ts b/src/events/slide-change.ts new file mode 100644 index 000000000..6960cffc5 --- /dev/null +++ b/src/events/slide-change.ts @@ -0,0 +1,21 @@ +import type WaCarouselItem from '../components/carousel-item/carousel-item.js'; + +export class WaSlideChangeEvent extends Event { + readonly detail: WaSlideChangeEventDetails; + + constructor(detail: WaSlideChangeEventDetails) { + super('wa-slide-change', { bubbles: true, cancelable: false, composed: true }); + this.detail = detail; + } +} + +interface WaSlideChangeEventDetails { + index: number; + slide: WaCarouselItem; +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-slide-change': WaSlideChangeEvent; + } +} diff --git a/src/events/start.ts b/src/events/start.ts new file mode 100644 index 000000000..fbfcc4fed --- /dev/null +++ b/src/events/start.ts @@ -0,0 +1,11 @@ +export class WaStartEvent extends Event { + constructor() { + super('wa-start', { bubbles: true, cancelable: false, composed: true }); + } +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-start': WaStartEvent; + } +} diff --git a/src/events/tab-hide.ts b/src/events/tab-hide.ts new file mode 100644 index 000000000..5aff7d29c --- /dev/null +++ b/src/events/tab-hide.ts @@ -0,0 +1,18 @@ +export class WaTabHideEvent extends Event { + readonly detail: WaTabHideEventDetail; + + constructor(detail: WaTabHideEventDetail) { + super('wa-tab-hide', { bubbles: true, cancelable: false, composed: true }); + this.detail = detail; + } +} + +interface WaTabHideEventDetail { + name: string; +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-tab-hide': WaTabHideEvent; + } +} diff --git a/src/events/tab-show.ts b/src/events/tab-show.ts new file mode 100644 index 000000000..65b23a412 --- /dev/null +++ b/src/events/tab-show.ts @@ -0,0 +1,18 @@ +export class WaTabShowEvent extends Event { + readonly detail: WaTabShowEventDetail; + + constructor(detail: WaTabShowEventDetail) { + super('wa-tab-show', { bubbles: true, cancelable: false, composed: true }); + this.detail = detail; + } +} + +interface WaTabShowEventDetail { + name: string; +} + +declare global { + interface GlobalEventHandlersEventMap { + 'wa-tab-show': WaTabShowEvent; + } +} diff --git a/src/events/wa-after-collapse.ts b/src/events/wa-after-collapse.ts deleted file mode 100644 index 869212558..000000000 --- a/src/events/wa-after-collapse.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaAfterCollapseEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-after-collapse': WaAfterCollapseEvent; - } -} diff --git a/src/events/wa-after-expand.ts b/src/events/wa-after-expand.ts deleted file mode 100644 index 9b92bcc73..000000000 --- a/src/events/wa-after-expand.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaAfterExpandEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-after-expand': WaAfterExpandEvent; - } -} diff --git a/src/events/wa-after-hide.ts b/src/events/wa-after-hide.ts deleted file mode 100644 index 882100847..000000000 --- a/src/events/wa-after-hide.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaAfterHideEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-after-hide': WaAfterHideEvent; - } -} diff --git a/src/events/wa-after-show.ts b/src/events/wa-after-show.ts deleted file mode 100644 index 4c6b2a4d0..000000000 --- a/src/events/wa-after-show.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaAfterShowEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-after-show': WaAfterShowEvent; - } -} diff --git a/src/events/wa-blur.ts b/src/events/wa-blur.ts deleted file mode 100644 index 4e5c7c9d5..000000000 --- a/src/events/wa-blur.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaBlurEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-blur': WaBlurEvent; - } -} diff --git a/src/events/wa-cancel.ts b/src/events/wa-cancel.ts deleted file mode 100644 index ef58cac16..000000000 --- a/src/events/wa-cancel.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaCancelEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-cancel': WaCancelEvent; - } -} diff --git a/src/events/wa-change.ts b/src/events/wa-change.ts deleted file mode 100644 index 32bfea05e..000000000 --- a/src/events/wa-change.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaChangeEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-change': WaChangeEvent; - } -} diff --git a/src/events/wa-clear.ts b/src/events/wa-clear.ts deleted file mode 100644 index b1b158a1d..000000000 --- a/src/events/wa-clear.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaClearEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-clear': WaClearEvent; - } -} diff --git a/src/events/wa-close.ts b/src/events/wa-close.ts deleted file mode 100644 index 396373a6b..000000000 --- a/src/events/wa-close.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaCloseEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-close': WaCloseEvent; - } -} diff --git a/src/events/wa-collapse.ts b/src/events/wa-collapse.ts deleted file mode 100644 index 323b37cc4..000000000 --- a/src/events/wa-collapse.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaCollapseEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-collapse': WaCollapseEvent; - } -} diff --git a/src/events/wa-copy.ts b/src/events/wa-copy.ts deleted file mode 100644 index f3e8e9aa4..000000000 --- a/src/events/wa-copy.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaCopyEvent = CustomEvent<{ value: string }>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-copy': WaCopyEvent; - } -} diff --git a/src/events/wa-error.ts b/src/events/wa-error.ts deleted file mode 100644 index e08d51c05..000000000 --- a/src/events/wa-error.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaErrorEvent = CustomEvent<{ status?: number }>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-error': WaErrorEvent; - } -} diff --git a/src/events/wa-expand.ts b/src/events/wa-expand.ts deleted file mode 100644 index 64f441fd9..000000000 --- a/src/events/wa-expand.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaExpandEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-expand': WaExpandEvent; - } -} diff --git a/src/events/wa-finish.ts b/src/events/wa-finish.ts deleted file mode 100644 index 1c9b3b41f..000000000 --- a/src/events/wa-finish.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaFinishEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-finish': WaFinishEvent; - } -} diff --git a/src/events/wa-focus.ts b/src/events/wa-focus.ts deleted file mode 100644 index d8fb97278..000000000 --- a/src/events/wa-focus.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaFocusEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-focus': WaFocusEvent; - } -} diff --git a/src/events/wa-hide.ts b/src/events/wa-hide.ts deleted file mode 100644 index 606cfa512..000000000 --- a/src/events/wa-hide.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaHideEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-hide': WaHideEvent; - } -} diff --git a/src/events/wa-hover.ts b/src/events/wa-hover.ts deleted file mode 100644 index 848420700..000000000 --- a/src/events/wa-hover.ts +++ /dev/null @@ -1,10 +0,0 @@ -export type WaHoverEvent = CustomEvent<{ - phase: 'start' | 'move' | 'end'; - value: number; -}>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-hover': WaHoverEvent; - } -} diff --git a/src/events/wa-input.ts b/src/events/wa-input.ts deleted file mode 100644 index de142c109..000000000 --- a/src/events/wa-input.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaInputEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-input': WaInputEvent; - } -} diff --git a/src/events/wa-invalid.ts b/src/events/wa-invalid.ts deleted file mode 100644 index a13989fc0..000000000 --- a/src/events/wa-invalid.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaInvalidEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-invalid': WaInvalidEvent; - } -} diff --git a/src/events/wa-lazy-change.ts b/src/events/wa-lazy-change.ts deleted file mode 100644 index a4f589b60..000000000 --- a/src/events/wa-lazy-change.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaLazyChangeEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-lazy-change': WaLazyChangeEvent; - } -} diff --git a/src/events/wa-lazy-load.ts b/src/events/wa-lazy-load.ts deleted file mode 100644 index dc4d1fdf4..000000000 --- a/src/events/wa-lazy-load.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaLazyLoadEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-lazy-load': WaLazyLoadEvent; - } -} diff --git a/src/events/wa-load.ts b/src/events/wa-load.ts deleted file mode 100644 index a666a89a7..000000000 --- a/src/events/wa-load.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaLoadEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-load': WaLoadEvent; - } -} diff --git a/src/events/wa-mutation.ts b/src/events/wa-mutation.ts deleted file mode 100644 index 7e490de32..000000000 --- a/src/events/wa-mutation.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaMutationEvent = CustomEvent<{ mutationList: MutationRecord[] }>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-mutation': WaMutationEvent; - } -} diff --git a/src/events/wa-remove.ts b/src/events/wa-remove.ts deleted file mode 100644 index 072d339b2..000000000 --- a/src/events/wa-remove.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaRemoveEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-remove': WaRemoveEvent; - } -} diff --git a/src/events/wa-reposition.ts b/src/events/wa-reposition.ts deleted file mode 100644 index 375aa85f3..000000000 --- a/src/events/wa-reposition.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaRepositionEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-reposition': WaRepositionEvent; - } -} diff --git a/src/events/wa-request-close.ts b/src/events/wa-request-close.ts deleted file mode 100644 index 47a61547b..000000000 --- a/src/events/wa-request-close.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaRequestCloseEvent = CustomEvent<{ source: Element }>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-request-close': WaRequestCloseEvent; - } -} diff --git a/src/events/wa-resize.ts b/src/events/wa-resize.ts deleted file mode 100644 index 6289febeb..000000000 --- a/src/events/wa-resize.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaResizeEvent = CustomEvent<{ entries: ResizeObserverEntry[] }>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-resize': WaResizeEvent; - } -} diff --git a/src/events/wa-select.ts b/src/events/wa-select.ts deleted file mode 100644 index 351088f23..000000000 --- a/src/events/wa-select.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type WaMenuItem from '../components/menu-item/menu-item.js'; - -export type WaSelectEvent = CustomEvent<{ item: WaMenuItem }>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-select': WaSelectEvent; - } -} diff --git a/src/events/wa-selection-change.ts b/src/events/wa-selection-change.ts deleted file mode 100644 index 67cf8d10a..000000000 --- a/src/events/wa-selection-change.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type WaTreeItem from '../components/tree-item/tree-item.js'; - -export type WaSelectionChangeEvent = CustomEvent<{ selection: WaTreeItem[] }>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-selection-change': WaSelectionChangeEvent; - } -} diff --git a/src/events/wa-show.ts b/src/events/wa-show.ts deleted file mode 100644 index cb52e8389..000000000 --- a/src/events/wa-show.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaShowEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-show': WaShowEvent; - } -} diff --git a/src/events/wa-slide-change.ts b/src/events/wa-slide-change.ts deleted file mode 100644 index fe2707641..000000000 --- a/src/events/wa-slide-change.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type WaCarouselItem from '../components/carousel-item/carousel-item.js'; - -export type WaSlideChangeEvent = CustomEvent<{ index: number; slide: WaCarouselItem }>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-slide-change': WaSlideChangeEvent; - } -} diff --git a/src/events/wa-start.ts b/src/events/wa-start.ts deleted file mode 100644 index 9ae08b316..000000000 --- a/src/events/wa-start.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaStartEvent = CustomEvent>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-start': WaStartEvent; - } -} diff --git a/src/events/wa-tab-hide.ts b/src/events/wa-tab-hide.ts deleted file mode 100644 index 4e61a1680..000000000 --- a/src/events/wa-tab-hide.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaTabHideEvent = CustomEvent<{ name: string }>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-tab-hide': WaTabHideEvent; - } -} diff --git a/src/events/wa-tab-show.ts b/src/events/wa-tab-show.ts deleted file mode 100644 index 5c0304437..000000000 --- a/src/events/wa-tab-show.ts +++ /dev/null @@ -1,7 +0,0 @@ -export type WaTabShowEvent = CustomEvent<{ name: string }>; - -declare global { - interface GlobalEventHandlersEventMap { - 'wa-tab-show': WaTabShowEvent; - } -} diff --git a/src/internal/animate.ts b/src/internal/animate.ts index 9192a067b..2fb1109b1 100644 --- a/src/internal/animate.ts +++ b/src/internal/animate.ts @@ -1,3 +1,10 @@ +/** Same as `el.animate()`, except returns a promise that doesn't throw an error when the animation is canceled. */ +export async function animate(el: Element, keyframes: Keyframe[], options?: KeyframeAnimationOptions) { + return el.animate(keyframes, options).finished.catch(() => { + /* suppress errors in Safari */ + }); +} + /** * Applies a class to the specified element to animate it. The class is removed after the animation finishes and then * the promise resolves. If a timeout is provided, the class will be removed and the animation will diff --git a/src/internal/webawesome-element.ts b/src/internal/webawesome-element.ts index 00f8b99bb..361f39896 100644 --- a/src/internal/webawesome-element.ts +++ b/src/internal/webawesome-element.ts @@ -1,98 +1,12 @@ import { CustomErrorValidator } from './validators/custom-error-validator.js'; import { LitElement, type PropertyValues } from 'lit'; import { property } from 'lit/decorators.js'; - -// Match event type name strings that are registered on GlobalEventHandlersEventMap... -type EventTypeRequiresDetail = T extends keyof GlobalEventHandlersEventMap - ? // ...where the event detail is an object... - GlobalEventHandlersEventMap[T] extends CustomEvent> - ? // ...that is non-empty... - GlobalEventHandlersEventMap[T] extends CustomEvent> - ? never - : // ...and has at least one non-optional property - Partial extends GlobalEventHandlersEventMap[T]['detail'] - ? never - : T - : never - : never; - -// The inverse of the above (match any type that doesn't match EventTypeRequiresDetail) -type EventTypeDoesNotRequireDetail = T extends keyof GlobalEventHandlersEventMap - ? GlobalEventHandlersEventMap[T] extends CustomEvent> - ? GlobalEventHandlersEventMap[T] extends CustomEvent> - ? T - : Partial extends GlobalEventHandlersEventMap[T]['detail'] - ? T - : never - : T - : T; - -// `keyof EventTypesWithRequiredDetail` lists all registered event types that require detail -type EventTypesWithRequiredDetail = { - [EventType in keyof GlobalEventHandlersEventMap as EventTypeRequiresDetail]: true; -}; - -// `keyof EventTypesWithoutRequiredDetail` lists all registered event types that do NOT require detail -type EventTypesWithoutRequiredDetail = { - [EventType in keyof GlobalEventHandlersEventMap as EventTypeDoesNotRequireDetail]: true; -}; - -// Helper to make a specific property of an object non-optional -type WithRequired = T & { [P in K]-?: T[P] }; - -// Given an event name string, get a valid type for the options to initialize the event that is more restrictive than -// just CustomEventInit when appropriate (validate the type of the event detail, and require it to be provided if the -// event requires it) -type SlEventInit = T extends keyof GlobalEventHandlersEventMap - ? GlobalEventHandlersEventMap[T] extends CustomEvent> - ? GlobalEventHandlersEventMap[T] extends CustomEvent> - ? CustomEventInit - : Partial extends GlobalEventHandlersEventMap[T]['detail'] - ? CustomEventInit - : WithRequired, 'detail'> - : CustomEventInit - : CustomEventInit; - -// Given an event name string, get the type of the event -type GetCustomEventType = T extends keyof GlobalEventHandlersEventMap - ? GlobalEventHandlersEventMap[T] extends CustomEvent - ? GlobalEventHandlersEventMap[T] - : CustomEvent - : CustomEvent; - -// `keyof ValidEventTypeMap` is equivalent to `keyof GlobalEventHandlersEventMap` but gives a nicer error message -type ValidEventTypeMap = EventTypesWithRequiredDetail | EventTypesWithoutRequiredDetail; +import { WaInvalidEvent } from '../events/invalid.js'; export default class WebAwesomeElement extends LitElement { // Make localization attributes reactive @property() dir: string; @property() lang: string; - - /** Emits a custom event with more convenient defaults. */ - emit( - name: EventTypeDoesNotRequireDetail, - options?: SlEventInit | undefined - ): GetCustomEventType; - emit( - name: EventTypeRequiresDetail, - options: SlEventInit - ): GetCustomEventType; - emit( - name: T, - options?: SlEventInit | undefined - ): GetCustomEventType { - const event = new CustomEvent(name, { - bubbles: true, - cancelable: false, - composed: true, - detail: {}, - ...options - }); - - this.dispatchEvent(event); - - return event as GetCustomEventType; - } } export interface Validator { @@ -238,7 +152,7 @@ export class WebAwesomeFormAssociatedElement // An "invalid" event counts as interacted, this is usually triggered by a button "submitting" this.hasInteracted = true; - this.emit('wa-invalid'); + this.dispatchEvent(new WaInvalidEvent()); }; protected willUpdate(changedProperties: PropertyValues) {