From 2dc3c2f6e0c5c5db76965f52840c433fe32770d8 Mon Sep 17 00:00:00 2001 From: ilhan007 Date: Thu, 26 Sep 2024 12:15:29 +0300 Subject: [PATCH 01/18] docs: describe non-bubbling events in api ref --- packages/fiori/src/ViewSettingsDialog.ts | 3 ++- packages/main/src/Menu.ts | 12 ++++++++---- packages/main/src/Popup.ts | 12 ++++++++---- packages/main/src/Select.ts | 4 +++- packages/tools/lib/cem/event.mjs | 2 ++ packages/tools/lib/cem/schema-internal.json | 4 ++++ packages/tools/lib/cem/types-internal.d.ts | 8 +++++++- packages/tools/lib/cem/utils.mjs | 4 ++-- .../api-reference-generation/component-file.mjs | 4 ++++ 9 files changed, 40 insertions(+), 13 deletions(-) diff --git a/packages/fiori/src/ViewSettingsDialog.ts b/packages/fiori/src/ViewSettingsDialog.ts index 61554759773d..4024ffbf8616 100644 --- a/packages/fiori/src/ViewSettingsDialog.ts +++ b/packages/fiori/src/ViewSettingsDialog.ts @@ -189,8 +189,9 @@ type VSDInternalSettings = { }) /** - * Fired before the component is opened. **This event does not bubble.** + * Fired before the component is opened. * @public + * @nonBubbling */ @event("before-open") /** diff --git a/packages/main/src/Menu.ts b/packages/main/src/Menu.ts index ebf71738a395..c28af0f5c333 100644 --- a/packages/main/src/Menu.ts +++ b/packages/main/src/Menu.ts @@ -135,11 +135,12 @@ type MenuBeforeCloseEventDetail = { escPressed: boolean }; }) /** - * Fired before the menu is opened. This event can be cancelled, which will prevent the menu from opening. **This event does not bubble.** + * Fired before the menu is opened. This event can be cancelled, which will prevent the menu from opening. * * **Note:** Since 1.14.0 the event is also fired before a sub-menu opens. * @public * @allowPreventDefault + * @nonBubbling * @since 1.10.0 * @param { HTMLElement } item The `ui5-menu-item` that triggers opening of the sub-menu or undefined when fired upon root menu opening. */ @@ -156,16 +157,18 @@ type MenuBeforeCloseEventDetail = { escPressed: boolean }; }) /** - * Fired after the menu is opened. **This event does not bubble.** + * Fired after the menu is opened. * @public + * @nonBubbling * @since 1.10.0 */ @event("open") /** - * Fired before the menu is closed. This event can be cancelled, which will prevent the menu from closing. **This event does not bubble.** + * Fired before the menu is closed. This event can be cancelled, which will prevent the menu from closing. * @public * @allowPreventDefault + * @nonBubbling * @param {boolean} escPressed Indicates that `ESC` key has triggered the event. * @since 1.10.0 */ @@ -181,8 +184,9 @@ type MenuBeforeCloseEventDetail = { escPressed: boolean }; }) /** - * Fired after the menu is closed. **This event does not bubble.** + * Fired after the menu is closed. * @public + * @nonBubbling * @since 1.10.0 */ @event("close") diff --git a/packages/main/src/Popup.ts b/packages/main/src/Popup.ts index 0bda1944df40..5e488a484f4f 100644 --- a/packages/main/src/Popup.ts +++ b/packages/main/src/Popup.ts @@ -82,22 +82,25 @@ type PopupBeforeCloseEventDetail = { ], }) /** - * Fired before the component is opened. This event can be cancelled, which will prevent the popup from opening. **This event does not bubble.** + * Fired before the component is opened. This event can be cancelled, which will prevent the popup from opening. * @public * @allowPreventDefault + * @nonBubbling */ @event("before-open") /** - * Fired after the component is opened. **This event does not bubble.** + * Fired after the component is opened. + * @nonBubbling * @public */ @event("open") /** - * Fired before the component is closed. This event can be cancelled, which will prevent the popup from closing. **This event does not bubble.** + * Fired before the component is closed. This event can be cancelled, which will prevent the popup from closing. * @public * @allowPreventDefault + * @nonBubbling * @param {boolean} escPressed Indicates that `ESC` key has triggered the event. */ @event("before-close", { @@ -112,8 +115,9 @@ type PopupBeforeCloseEventDetail = { }) /** - * Fired after the component is closed. **This event does not bubble.** + * Fired after the component is closed. * @public + * @nonBubbling */ @event("close") diff --git a/packages/main/src/Select.ts b/packages/main/src/Select.ts index fbd4a44d0dd1..3d6f6c255ad3 100644 --- a/packages/main/src/Select.ts +++ b/packages/main/src/Select.ts @@ -181,9 +181,11 @@ type SelectLiveChangeEventDetail = { * @public */ @event("open") + /** * Fired after the component's dropdown menu closes. * @public + * @nonBubbling */ @event("close") class Select extends UI5Element implements IFormInputElement { @@ -722,7 +724,7 @@ class Select extends UI5Element implements IFormInputElement { this._fireChangeEvent(this.options[this._selectedIndex]); this._lastSelectedOption = this.options[this._selectedIndex]; } - this.fireEvent("close"); + this.fireEvent("close", undefined, false, false); } get hasCustomLabel() { diff --git a/packages/tools/lib/cem/event.mjs b/packages/tools/lib/cem/event.mjs index b1f1fec59bd6..b20a9853b5a8 100644 --- a/packages/tools/lib/cem/event.mjs +++ b/packages/tools/lib/cem/event.mjs @@ -78,6 +78,7 @@ function processEvent(ts, event, classNode, moduleDoc) { const sinceTag = findTag(eventParsedComment, "since"); const commentParams = findAllTags(eventParsedComment, "param"); const allowPreventDefault = hasTag(eventParsedComment, "allowPreventDefault") || undefined; + const nonBubbling = hasTag(eventParsedComment, "nonBubbling") || undefined; const description = normalizeDescription(eventParsedComment?.description); const native = hasTag(eventParsedComment, "native"); const eventDetails = event?.expression?.arguments?.[1]?.properties?.find(prop => prop?.name?.text === "detail")?.initializer?.properties; @@ -88,6 +89,7 @@ function processEvent(ts, event, classNode, moduleDoc) { result.description = description; result._ui5allowPreventDefault = allowPreventDefault; + result._ui5nonBubbling = nonBubbling; if (native) { result.type = { text: "Event" }; diff --git a/packages/tools/lib/cem/schema-internal.json b/packages/tools/lib/cem/schema-internal.json index 4b0c8960e549..ac9cc35d68f0 100644 --- a/packages/tools/lib/cem/schema-internal.json +++ b/packages/tools/lib/cem/schema-internal.json @@ -818,6 +818,10 @@ "description": "Whether the parameter is optional. Undefined implies non-optional.", "type": "boolean" }, + "_ui5nonBubbling": { + "description": "Whether the event is bubbling or not.", + "type": "boolean" + }, "_ui5since": { "description": "Marks when the field was introduced", "type": "string" diff --git a/packages/tools/lib/cem/types-internal.d.ts b/packages/tools/lib/cem/types-internal.d.ts index d97d4959bd51..bf973d656c2c 100644 --- a/packages/tools/lib/cem/types-internal.d.ts +++ b/packages/tools/lib/cem/types-internal.d.ts @@ -319,9 +319,15 @@ export interface Event { _ui5parameters?: Parameter[] _ui5privacy?: Privacy /** - * Whether the parameter is optional. Undefined implies non-optional. + * Whether the event is preventable. Undefined meants the event is not preventable. */ _ui5allowPreventDefault?: boolean + + /** + * Whether the event is bubbling. Undefined means the event is bubbling. + */ + _ui5nonBubbling?: boolean + /** * Marks when the field was introduced */ diff --git a/packages/tools/lib/cem/utils.mjs b/packages/tools/lib/cem/utils.mjs index eb0ffb419b8d..5e104449bfe1 100644 --- a/packages/tools/lib/cem/utils.mjs +++ b/packages/tools/lib/cem/utils.mjs @@ -222,7 +222,7 @@ const commonTags = ["public", "protected", "private", "since", "deprecated"]; const allowedTags = { field: [...commonTags, "formEvents", "formProperty", "default"], slot: [...commonTags, "default"], - event: [...commonTags, "param", "allowPreventDefault", "native"], + event: [...commonTags, "param", "allowPreventDefault", "nonBubbling", "native"], eventParam: [...commonTags], method: [...commonTags, "param", "returns", "override"], class: [...commonTags, "constructor", "class", "abstract", "experimental", "implements", "extends", "slot", "csspart"], @@ -283,7 +283,7 @@ const findAllTags = (jsDoc, tagName) => { }; const validateJSDocTag = (tag) => { - const booleanTags = ["private", "protected", "public", "abstract", "allowPreventDefault", "native", "formProperty", "constructor", "override"]; + const booleanTags = ["private", "protected", "public", "abstract", "allowPreventDefault", "nonBubbling", "native", "formProperty", "constructor", "override"]; let tagName = tag.tag; if (booleanTags.includes(tag.tag)) { diff --git a/packages/website/build-scripts/api-reference-generation/component-file.mjs b/packages/website/build-scripts/api-reference-generation/component-file.mjs index e431aa4a6a9f..ddef8cb71a39 100644 --- a/packages/website/build-scripts/api-reference-generation/component-file.mjs +++ b/packages/website/build-scripts/api-reference-generation/component-file.mjs @@ -152,6 +152,10 @@ No events available for this component.` eventResult += `\n| Parameters | ${processDescription(paramsText)} |` } + if (event._ui5nonBubbling) { + eventResult += `\n| Bubbling | The ${event.name} event does not bubble |` + } + if (event._ui5since) { eventResult += `\n| Since | ${event._ui5since} |` } From 17dca464cd974983076fe14e828004e2f6e3421d Mon Sep 17 00:00:00 2001 From: ilhan007 Date: Fri, 27 Sep 2024 16:18:23 +0300 Subject: [PATCH 02/18] chore: enhance event decarotor --- docs/4-development/05-events.md | 44 ++++++++++++++++++------ packages/base/src/UI5Element.ts | 14 ++++++-- packages/base/src/UI5ElementMetadata.ts | 2 +- packages/base/src/decorators/event.ts | 4 ++- packages/fiori/src/ViewSettingsDialog.ts | 5 +-- packages/main/src/CheckBox.ts | 18 +++++++--- packages/main/src/Input.ts | 18 +++++++--- packages/main/src/Menu.ts | 8 ++--- packages/main/src/Popup.ts | 19 ++++++---- packages/main/src/Select.ts | 16 ++++++--- packages/main/src/Switch.ts | 11 +++++- packages/main/src/TextArea.ts | 5 +++ packages/tools/lib/cem/event.mjs | 4 +-- 13 files changed, 123 insertions(+), 45 deletions(-) diff --git a/docs/4-development/05-events.md b/docs/4-development/05-events.md index 77d25123dc2c..6533cc8c5583 100644 --- a/docs/4-development/05-events.md +++ b/docs/4-development/05-events.md @@ -4,7 +4,7 @@ In this article, we will discuss events in the context of UI5 Web Components. Components use `CustomEvent` to inform developers of important state changes in the components. For example, the `change` event is fired whenever the value of a `ui5-input` is changed. -## `@event` Decorator +## The `@event` Decorator To define your own custom event, you need to use the `@event` decorator. @@ -28,7 +28,7 @@ class MyDemoComponent extends UI5Element {} **Note:** This decorator is used only to describe the events of the component and is not meant to create emitters. -## How to use events +## Usage As mentioned earlier, the `@event` decorator doesn't create event emitters. To notify developers of component changes, we have to fire events ourselves. This can be done using the `fireEvent` method that comes from the `UI5Element` class. @@ -51,15 +51,10 @@ class MyDemoComponent extends UI5Element { } ``` -Events fired by the `fireEvent` method can be configurable, meaning you can decide whether the event should be cancelable or able to bubble. This can be done by setting the third and fourth parameters of the function to true, respectively. - -```ts -this.fireEvent("change", {}, cancelable, bubbles); -``` - **Note:** By default, the `fireEvent` method returns a boolean value that helps you understand whether the event was canceled (i.e., if the `preventDefault` method was called). -## Types +## Event Detail + The `@event` decorator is generic and accepts a TypeScript type that describes its detail. This type is crucial for preventing incorrect detail data when the event is fired using `fireEvent` (which is also generic) and for ensuring type safety when listening for the event, so you know what kind of detail data to expect. **Note:** It's required to export all types that describe specific event details for all public events. @@ -97,7 +92,36 @@ class MyDemoComponent extends UI5Element { export { MyDemoComponent }; ``` -## noConflict mode +## Event Configuration + +### Bubbles + +Whether the events should be cancelable or able to bubble is configurable. +by setting `cancelable` and `bubbles` in the `@event` decorator: + +```ts +import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; +import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; +import event from "@ui5/webcomponents-base/dist/decorators/event.js"; + +@customElement("my-demo-component") +@event("change", { + bubbles: false // true by default + cancelable: true // false by default +}) +class MyDemoComponent extends UI5Element {} +``` + +Or, this can be `lso` done by setting the third and fourth parameters of the function to true, respectively. + +```ts +this.fireEvent("change", {}, cancelable, bubbles); +``` + +**Note:** The parameters, given to the `fireEvent` have higher priority than the configuration in the `@event` decorator. + +### noConflict mode + By default, UI5 Web Components fire all custom events twice: once with their name (e.g., `change`) and once more with a `ui5-` prefix (e.g., `ui5-change`). For example, when the `ui5-switch` is toggled, it fires a `change` event and a `ui5-change` event. This `noConflict` setting allows us to prevent clashes between native and custom events. diff --git a/packages/base/src/UI5Element.ts b/packages/base/src/UI5Element.ts index c674fb830837..479b174471fd 100644 --- a/packages/base/src/UI5Element.ts +++ b/packages/base/src/UI5Element.ts @@ -944,8 +944,12 @@ abstract class UI5Element extends HTMLElement { * @param bubbles - true, if the event bubbles * @returns false, if the event was cancelled (preventDefault called), true otherwise */ - fireEvent(name: string, data?: T, cancelable = false, bubbles = true): boolean { - const eventResult = this._fireEvent(name, data, cancelable, bubbles); + fireEvent(name: string, data?: T, cancelable?: boolean, bubbles?: boolean): boolean { + const eventData = this.getEventData(name); + const _cancellable = cancelable === undefined ? eventData.cancelable : cancelable; + const _bubbles = bubbles === undefined ? eventData.bubbles : bubbles; + + const eventResult = this._fireEvent(name, data, _cancellable, _bubbles); const pascalCaseEventName = kebabToPascalCase(name); // pascal events are more convinient for native react usage @@ -988,6 +992,12 @@ abstract class UI5Element extends HTMLElement { return normalEventResult && noConflictEventResult; } + getEventData(name: string) { + const ctor = this.constructor as typeof UI5Element; + const eventMap = ctor.getMetadata().getEvents(); + return eventMap[name]; + } + /** * Returns the actual children, associated with a slot. * Useful when there are transitive slots in nested component scenarios and you don't want to get a list of the slots, but rather of their content. diff --git a/packages/base/src/UI5ElementMetadata.ts b/packages/base/src/UI5ElementMetadata.ts index b9ff81fee95b..8db2d9811724 100644 --- a/packages/base/src/UI5ElementMetadata.ts +++ b/packages/base/src/UI5ElementMetadata.ts @@ -28,7 +28,7 @@ type Property = { type PropertyValue = boolean | number | string | object | undefined | null; -type EventData = Record; +type EventData = Record, cancelable: boolean, bubbles: boolean }>; type Metadata = { tag?: string, diff --git a/packages/base/src/decorators/event.ts b/packages/base/src/decorators/event.ts index 62bf5e86f33c..3aa0b45a1512 100644 --- a/packages/base/src/decorators/event.ts +++ b/packages/base/src/decorators/event.ts @@ -5,7 +5,7 @@ * @param { EventData } data the event data * @returns { ClassDecorator } */ -const event = (name: string, data: { detail?: Record } = {}): ClassDecorator => { +const event = (name: string, data: { detail?: Record, bubbles?: boolean, cancelable?: boolean } = {}): ClassDecorator => { return (target: any) => { if (!Object.prototype.hasOwnProperty.call(target, "metadata")) { target.metadata = {}; @@ -18,6 +18,8 @@ const event = (name: string, data: { detail?: Record) { @@ -409,7 +405,7 @@ class Menu extends UI5Element { _afterPopoverClose() { this.open = false; - this.fireEvent("close", {}, false, true); + this.fireEvent("close"); } } diff --git a/packages/main/src/Popup.ts b/packages/main/src/Popup.ts index 5e488a484f4f..8cffe00f60a1 100644 --- a/packages/main/src/Popup.ts +++ b/packages/main/src/Popup.ts @@ -85,22 +85,24 @@ type PopupBeforeCloseEventDetail = { * Fired before the component is opened. This event can be cancelled, which will prevent the popup from opening. * @public * @allowPreventDefault - * @nonBubbling */ -@event("before-open") +@event("before-open", { + bubbles: false, + cancelable: true, +}) /** * Fired after the component is opened. - * @nonBubbling * @public */ -@event("open") +@event("open", { + bubbles: false, +}) /** * Fired before the component is closed. This event can be cancelled, which will prevent the popup from closing. * @public * @allowPreventDefault - * @nonBubbling * @param {boolean} escPressed Indicates that `ESC` key has triggered the event. */ @event("before-close", { @@ -112,14 +114,17 @@ type PopupBeforeCloseEventDetail = { type: Boolean, }, }, + cancelable: true, + bubbles: false, }) /** * Fired after the component is closed. * @public - * @nonBubbling */ -@event("close") +@event("close", { + bubbles: false, +}) /** * Fired whenever the popup content area is scrolled diff --git a/packages/main/src/Select.ts b/packages/main/src/Select.ts index 3d6f6c255ad3..63962706554a 100644 --- a/packages/main/src/Select.ts +++ b/packages/main/src/Select.ts @@ -160,6 +160,7 @@ type SelectLiveChangeEventDetail = { */ selectedOption: { type: HTMLElement }, }, + cancelable: true, }) /** * Fired when the user navigates through the options, but the selection is not finalized, @@ -185,9 +186,16 @@ type SelectLiveChangeEventDetail = { /** * Fired after the component's dropdown menu closes. * @public - * @nonBubbling */ -@event("close") +@event("close", { + bubbles: false, +}) + +/** + * Fired to make Angular two way data binding work properly. + * @private + */ +@event("selected-item-changed") class Select extends UI5Element implements IFormInputElement { static i18nBundle: I18nBundle; @@ -724,7 +732,7 @@ class Select extends UI5Element implements IFormInputElement { this._fireChangeEvent(this.options[this._selectedIndex]); this._lastSelectedOption = this.options[this._selectedIndex]; } - this.fireEvent("close", undefined, false, false); + this.fireEvent("close"); } get hasCustomLabel() { @@ -732,7 +740,7 @@ class Select extends UI5Element implements IFormInputElement { } _fireChangeEvent(selectedOption: IOption) { - const changePrevented = !this.fireEvent("change", { selectedOption }, true); + const changePrevented = !this.fireEvent("change", { selectedOption }); // Angular two way data binding this.fireEvent("selected-item-changed"); diff --git a/packages/main/src/Switch.ts b/packages/main/src/Switch.ts index cb4187c4e6e9..d115e49f4eab 100644 --- a/packages/main/src/Switch.ts +++ b/packages/main/src/Switch.ts @@ -66,7 +66,16 @@ import switchCss from "./generated/themes/Switch.css.js"; * @public * @allowPreventDefault */ -@event("change") +@event("change", { + cancelable: true, +}) +/** + * Fired to make Angular two way data binding work properly. + * @private + */ +@event("value-changed", { + cancelable: true, +}) class Switch extends UI5Element implements IFormInputElement { /** * Defines the component design. diff --git a/packages/main/src/TextArea.ts b/packages/main/src/TextArea.ts index c4c685b82554..f3147f09faee 100644 --- a/packages/main/src/TextArea.ts +++ b/packages/main/src/TextArea.ts @@ -89,6 +89,11 @@ type ExceededText = { * @public */ @event("change") +/** + * Fired to make Angular two way data binding work properly. + * @private + */ +@event("value-changed") /** * Fired when the value of the component changes at each keystroke or when diff --git a/packages/tools/lib/cem/event.mjs b/packages/tools/lib/cem/event.mjs index b20a9853b5a8..43c31bbbae33 100644 --- a/packages/tools/lib/cem/event.mjs +++ b/packages/tools/lib/cem/event.mjs @@ -82,8 +82,8 @@ function processEvent(ts, event, classNode, moduleDoc) { const description = normalizeDescription(eventParsedComment?.description); const native = hasTag(eventParsedComment, "native"); const eventDetails = event?.expression?.arguments?.[1]?.properties?.find(prop => prop?.name?.text === "detail")?.initializer?.properties; - - if (event?.expression?.arguments?.[1] && !event?.expression?.typeArguments) { + + if (eventDetails && !event?.expression?.typeArguments) { logDocumentationError(moduleDoc.path, `Event details for event '${name}' must be described using generics. Add type via generics to the decorator: @event("${name}", {details}).`) } From c72a7919f673140b1a986b85c9c3c84f1d5cc048 Mon Sep 17 00:00:00 2001 From: ilhan007 Date: Fri, 27 Sep 2024 16:27:47 +0300 Subject: [PATCH 03/18] chore: cleanup --- docs/4-development/05-events.md | 8 ++++++-- packages/tools/lib/cem/event.mjs | 2 -- packages/tools/lib/cem/schema-internal.json | 4 ---- packages/tools/lib/cem/types-internal.d.ts | 5 ----- .../api-reference-generation/component-file.mjs | 4 ---- 5 files changed, 6 insertions(+), 17 deletions(-) diff --git a/docs/4-development/05-events.md b/docs/4-development/05-events.md index 6533cc8c5583..986f8e2e78cc 100644 --- a/docs/4-development/05-events.md +++ b/docs/4-development/05-events.md @@ -94,10 +94,14 @@ export { MyDemoComponent }; ## Event Configuration -### Bubbles +### Bubbling and Preventing Whether the events should be cancelable or able to bubble is configurable. -by setting `cancelable` and `bubbles` in the `@event` decorator: +by setting `cancelable` and `bubbles` in the `@event` decorator. + +- `cancelable: true` means the event can be prevented by calling the native `preventDefault()` method in the event handler. By default, `cancelable` is `false`, e.g. events are not preventable/cancelable. + +- `bubbles: false` means the event will not bubble, while by default it's `true` as all events bubble. ```ts import UI5Element from "@ui5/webcomponents-base/dist/UI5Element.js"; diff --git a/packages/tools/lib/cem/event.mjs b/packages/tools/lib/cem/event.mjs index 43c31bbbae33..5376b1027216 100644 --- a/packages/tools/lib/cem/event.mjs +++ b/packages/tools/lib/cem/event.mjs @@ -78,7 +78,6 @@ function processEvent(ts, event, classNode, moduleDoc) { const sinceTag = findTag(eventParsedComment, "since"); const commentParams = findAllTags(eventParsedComment, "param"); const allowPreventDefault = hasTag(eventParsedComment, "allowPreventDefault") || undefined; - const nonBubbling = hasTag(eventParsedComment, "nonBubbling") || undefined; const description = normalizeDescription(eventParsedComment?.description); const native = hasTag(eventParsedComment, "native"); const eventDetails = event?.expression?.arguments?.[1]?.properties?.find(prop => prop?.name?.text === "detail")?.initializer?.properties; @@ -89,7 +88,6 @@ function processEvent(ts, event, classNode, moduleDoc) { result.description = description; result._ui5allowPreventDefault = allowPreventDefault; - result._ui5nonBubbling = nonBubbling; if (native) { result.type = { text: "Event" }; diff --git a/packages/tools/lib/cem/schema-internal.json b/packages/tools/lib/cem/schema-internal.json index ac9cc35d68f0..4b0c8960e549 100644 --- a/packages/tools/lib/cem/schema-internal.json +++ b/packages/tools/lib/cem/schema-internal.json @@ -818,10 +818,6 @@ "description": "Whether the parameter is optional. Undefined implies non-optional.", "type": "boolean" }, - "_ui5nonBubbling": { - "description": "Whether the event is bubbling or not.", - "type": "boolean" - }, "_ui5since": { "description": "Marks when the field was introduced", "type": "string" diff --git a/packages/tools/lib/cem/types-internal.d.ts b/packages/tools/lib/cem/types-internal.d.ts index bf973d656c2c..d2e21513e14f 100644 --- a/packages/tools/lib/cem/types-internal.d.ts +++ b/packages/tools/lib/cem/types-internal.d.ts @@ -323,11 +323,6 @@ export interface Event { */ _ui5allowPreventDefault?: boolean - /** - * Whether the event is bubbling. Undefined means the event is bubbling. - */ - _ui5nonBubbling?: boolean - /** * Marks when the field was introduced */ diff --git a/packages/website/build-scripts/api-reference-generation/component-file.mjs b/packages/website/build-scripts/api-reference-generation/component-file.mjs index ddef8cb71a39..e431aa4a6a9f 100644 --- a/packages/website/build-scripts/api-reference-generation/component-file.mjs +++ b/packages/website/build-scripts/api-reference-generation/component-file.mjs @@ -152,10 +152,6 @@ No events available for this component.` eventResult += `\n| Parameters | ${processDescription(paramsText)} |` } - if (event._ui5nonBubbling) { - eventResult += `\n| Bubbling | The ${event.name} event does not bubble |` - } - if (event._ui5since) { eventResult += `\n| Since | ${event._ui5since} |` } From d4f23e6e3a1574dd9603a7c21cce2b407019f7a2 Mon Sep 17 00:00:00 2001 From: ilhan007 Date: Fri, 27 Sep 2024 16:30:58 +0300 Subject: [PATCH 04/18] chore: remove nonBubbling tag --- packages/tools/lib/cem/utils.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/tools/lib/cem/utils.mjs b/packages/tools/lib/cem/utils.mjs index 5e104449bfe1..eb0ffb419b8d 100644 --- a/packages/tools/lib/cem/utils.mjs +++ b/packages/tools/lib/cem/utils.mjs @@ -222,7 +222,7 @@ const commonTags = ["public", "protected", "private", "since", "deprecated"]; const allowedTags = { field: [...commonTags, "formEvents", "formProperty", "default"], slot: [...commonTags, "default"], - event: [...commonTags, "param", "allowPreventDefault", "nonBubbling", "native"], + event: [...commonTags, "param", "allowPreventDefault", "native"], eventParam: [...commonTags], method: [...commonTags, "param", "returns", "override"], class: [...commonTags, "constructor", "class", "abstract", "experimental", "implements", "extends", "slot", "csspart"], @@ -283,7 +283,7 @@ const findAllTags = (jsDoc, tagName) => { }; const validateJSDocTag = (tag) => { - const booleanTags = ["private", "protected", "public", "abstract", "allowPreventDefault", "nonBubbling", "native", "formProperty", "constructor", "override"]; + const booleanTags = ["private", "protected", "public", "abstract", "allowPreventDefault", "native", "formProperty", "constructor", "override"]; let tagName = tag.tag; if (booleanTags.includes(tag.tag)) { From efef31761ee0f52bc6e80fb67a7179baa7511fef Mon Sep 17 00:00:00 2001 From: ilhan007 Date: Mon, 30 Sep 2024 15:42:30 +0300 Subject: [PATCH 05/18] chore: display info in website --- packages/tools/lib/cem/event.mjs | 22 +++++++++++++++++-- packages/tools/lib/cem/schema-internal.json | 8 +++++++ packages/tools/lib/cem/types-internal.d.ts | 14 ++++++++++-- .../component-file.mjs | 8 +++++++ 4 files changed, 48 insertions(+), 4 deletions(-) diff --git a/packages/tools/lib/cem/event.mjs b/packages/tools/lib/cem/event.mjs index 5376b1027216..3967a9686e5e 100644 --- a/packages/tools/lib/cem/event.mjs +++ b/packages/tools/lib/cem/event.mjs @@ -16,6 +16,7 @@ import { } from "./utils.mjs"; const jsDocRegExp = /\/\*\*(.|\n)+?\s+\*\//; +const ASTFalseKeywordCode = 94; const getParams = (ts, eventDetails, commentParams, classNode, moduleDoc) => { return commentParams?.map(commentParam => { @@ -80,14 +81,31 @@ function processEvent(ts, event, classNode, moduleDoc) { const allowPreventDefault = hasTag(eventParsedComment, "allowPreventDefault") || undefined; const description = normalizeDescription(eventParsedComment?.description); const native = hasTag(eventParsedComment, "native"); - const eventDetails = event?.expression?.arguments?.[1]?.properties?.find(prop => prop?.name?.text === "detail")?.initializer?.properties; - + const eventArgs = event?.expression?.arguments; + let eventBubbles; + let eventCancelable; + let eventDetails; + + eventArgs && eventArgs.forEach(arg => { + arg.properties?.forEach(prop => { + if (prop.name?.text === "bubbles") { + eventBubbles = prop.initializer.kind === ASTFalseKeywordCode ? false : true; + } else if (prop.name?.text === "cancelable") { + eventCancelable = prop.initializer.kind === ASTFalseKeywordCode ? false : true; + } else if (prop.name?.text === "detail") { + eventDetails = prop.initializer?.properties; + } + }); + }); + if (eventDetails && !event?.expression?.typeArguments) { logDocumentationError(moduleDoc.path, `Event details for event '${name}' must be described using generics. Add type via generics to the decorator: @event("${name}", {details}).`) } result.description = description; result._ui5allowPreventDefault = allowPreventDefault; + result._ui5Cancelable = eventCancelable !== undefined ? eventCancelable : undefined; + result._ui5Bubbles = eventBubbles !== undefined ? eventBubbles : undefined; if (native) { result.type = { text: "Event" }; diff --git a/packages/tools/lib/cem/schema-internal.json b/packages/tools/lib/cem/schema-internal.json index 4b0c8960e549..fab987786fc3 100644 --- a/packages/tools/lib/cem/schema-internal.json +++ b/packages/tools/lib/cem/schema-internal.json @@ -818,6 +818,14 @@ "description": "Whether the parameter is optional. Undefined implies non-optional.", "type": "boolean" }, + "_ui5Cancelable": { + "description": "Whether the event is cancelable or not.", + "type": "boolean" + }, + "_ui5Bubbles": { + "description": "Whether the event bubbles or not.", + "type": "boolean" + }, "_ui5since": { "description": "Marks when the field was introduced", "type": "string" diff --git a/packages/tools/lib/cem/types-internal.d.ts b/packages/tools/lib/cem/types-internal.d.ts index d2e21513e14f..8c5b242590bd 100644 --- a/packages/tools/lib/cem/types-internal.d.ts +++ b/packages/tools/lib/cem/types-internal.d.ts @@ -319,9 +319,19 @@ export interface Event { _ui5parameters?: Parameter[] _ui5privacy?: Privacy /** - * Whether the event is preventable. Undefined meants the event is not preventable. + * Whether the event is preventable. */ - _ui5allowPreventDefault?: boolean + _ui5allowPreventDefault?: boolean; + + /** + * Whether the event is cancelable. + */ + _ui5Cancelable?: boolean; + + /** + * Whether the event is bubbles. + */ + _ui5Bubbles?: boolean; /** * Marks when the field was introduced diff --git a/packages/website/build-scripts/api-reference-generation/component-file.mjs b/packages/website/build-scripts/api-reference-generation/component-file.mjs index e431aa4a6a9f..1d29d455b5de 100644 --- a/packages/website/build-scripts/api-reference-generation/component-file.mjs +++ b/packages/website/build-scripts/api-reference-generation/component-file.mjs @@ -156,6 +156,14 @@ No events available for this component.` eventResult += `\n| Since | ${event._ui5since} |` } + if (typeof event._ui5Bubbles === "boolean") { + eventResult += `\n| Bubbles | The event ${event._ui5Bubbles ? "Yes" : "No"}. |` + } + + if (typeof event._ui5Cancelable === "boolean") { + eventResult += `\n| Cancelable | The event ${event._ui5Cancelable ? "Yes - by calling preventDefault()." : "No."} |` + } + if (event.deprecated) { eventResult += `\n| Deprecated | ${processDescription(typeof event.deprecated === "boolean" ? "true" : event.deprecated)} |` } From 1d4d1b3204c85341b054282a729fcbac55904599 Mon Sep 17 00:00:00 2001 From: ilhan007 Date: Mon, 30 Sep 2024 15:45:59 +0300 Subject: [PATCH 06/18] chore: update --- .../build-scripts/api-reference-generation/component-file.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/website/build-scripts/api-reference-generation/component-file.mjs b/packages/website/build-scripts/api-reference-generation/component-file.mjs index 1d29d455b5de..92cb2ff58663 100644 --- a/packages/website/build-scripts/api-reference-generation/component-file.mjs +++ b/packages/website/build-scripts/api-reference-generation/component-file.mjs @@ -157,11 +157,11 @@ No events available for this component.` } if (typeof event._ui5Bubbles === "boolean") { - eventResult += `\n| Bubbles | The event ${event._ui5Bubbles ? "Yes" : "No"}. |` + eventResult += `\n| Bubbles | ${event._ui5Bubbles ? "Yes" : "No"}. |` } if (typeof event._ui5Cancelable === "boolean") { - eventResult += `\n| Cancelable | The event ${event._ui5Cancelable ? "Yes - by calling preventDefault()." : "No."} |` + eventResult += `\n| Cancelable | ${event._ui5Cancelable ? "Yes - by calling preventDefault()." : "No."} |` } if (event.deprecated) { From da6346ff40cb5c48f10f370d3c673980513e9a75 Mon Sep 17 00:00:00 2001 From: ilhan007 Date: Mon, 30 Sep 2024 16:15:15 +0300 Subject: [PATCH 07/18] chore: fix compat tests --- packages/compat/src/TableRow.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/compat/src/TableRow.ts b/packages/compat/src/TableRow.ts index aa31cc243529..34e1fe9ad806 100644 --- a/packages/compat/src/TableRow.ts +++ b/packages/compat/src/TableRow.ts @@ -79,7 +79,18 @@ type TableRowF7PressEventDetail = { * @private */ @event("row-click") +/** + * @private + */ @event("_focused") +/** + * @private + */ +@event("_forward-before") +/** + * @private + */ +@event("_forward-after") /** * Fired on selection change of an active row. * @since 2.0.0 From e48f3518e22427ad9abd03910b953102a88a3ed0 Mon Sep 17 00:00:00 2001 From: ilhan007 Date: Mon, 30 Sep 2024 16:52:00 +0300 Subject: [PATCH 08/18] chore: describe private event --- packages/base/src/UI5Element.ts | 15 +++++++++++---- packages/compat/src/TableRow.ts | 16 ++++++++++++++-- packages/fiori/src/MediaGalleryItem.ts | 5 +++++ 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/packages/base/src/UI5Element.ts b/packages/base/src/UI5Element.ts index 479b174471fd..599c8d73fb52 100644 --- a/packages/base/src/UI5Element.ts +++ b/packages/base/src/UI5Element.ts @@ -945,9 +945,8 @@ abstract class UI5Element extends HTMLElement { * @returns false, if the event was cancelled (preventDefault called), true otherwise */ fireEvent(name: string, data?: T, cancelable?: boolean, bubbles?: boolean): boolean { - const eventData = this.getEventData(name); - const _cancellable = cancelable === undefined ? eventData.cancelable : cancelable; - const _bubbles = bubbles === undefined ? eventData.bubbles : bubbles; + const _cancellable = cancelable !== undefined ? cancelable : this.eventCancelable(name); + const _bubbles = bubbles !== undefined ? bubbles: this.eventBubbles(name); const eventResult = this._fireEvent(name, data, _cancellable, _bubbles); const pascalCaseEventName = kebabToPascalCase(name); @@ -957,7 +956,7 @@ abstract class UI5Element extends HTMLElement { // Before: onlive-change // After: onLiveChange if (pascalCaseEventName !== name) { - return eventResult && this._fireEvent(pascalCaseEventName, data, cancelable, bubbles); + return eventResult && this._fireEvent(pascalCaseEventName, data, _cancellable, _bubbles); } return eventResult; @@ -997,6 +996,14 @@ abstract class UI5Element extends HTMLElement { const eventMap = ctor.getMetadata().getEvents(); return eventMap[name]; } + eventCancelable(name: string): boolean { + const eventData = this.getEventData(name); + return eventData ? eventData.cancelable : false; + } + eventBubbles(name: string): boolean { + const eventData = this.getEventData(name); + return eventData ? eventData.bubbles : true; + } /** * Returns the actual children, associated with a slot. diff --git a/packages/compat/src/TableRow.ts b/packages/compat/src/TableRow.ts index 34e1fe9ad806..6e5b46f4c0de 100644 --- a/packages/compat/src/TableRow.ts +++ b/packages/compat/src/TableRow.ts @@ -86,11 +86,23 @@ type TableRowF7PressEventDetail = { /** * @private */ -@event("_forward-before") +@event("_forward-before", { + detail: { + target: { + type: HTMLElement, + }, + }, +}) /** * @private */ -@event("_forward-after") +@event("_forward-after", { + detail: { + target: { + type: HTMLElement, + }, + }, +}) /** * Fired on selection change of an active row. * @since 2.0.0 diff --git a/packages/fiori/src/MediaGalleryItem.ts b/packages/fiori/src/MediaGalleryItem.ts index 093c983033f3..316c0adac471 100644 --- a/packages/fiori/src/MediaGalleryItem.ts +++ b/packages/fiori/src/MediaGalleryItem.ts @@ -6,6 +6,7 @@ import Icon from "@ui5/webcomponents/dist/Icon.js"; import "@ui5/webcomponents-icons/dist/background.js"; import customElement from "@ui5/webcomponents-base/dist/decorators/customElement.js"; import property from "@ui5/webcomponents-base/dist/decorators/property.js"; +import event from "@ui5/webcomponents-base/dist/decorators/event.js"; import slot from "@ui5/webcomponents-base/dist/decorators/slot.js"; import type MediaGalleryItemLayout from "./types/MediaGalleryItemLayout.js"; import type { IMediaGalleryItem } from "./MediaGallery.js"; @@ -46,6 +47,10 @@ import MediaGalleryItemTemplate from "./generated/templates/MediaGalleryItemTemp template: MediaGalleryItemTemplate, dependencies: [Icon], }) +/** + * @private + */ +@event("click") class MediaGalleryItem extends UI5Element implements IMediaGalleryItem { /** * Defines the selected state of the component. From 8c7d413415d69e4338a4eb356e81353144a9ecd4 Mon Sep 17 00:00:00 2001 From: ilhan007 Date: Mon, 30 Sep 2024 17:01:42 +0300 Subject: [PATCH 09/18] chore: lint --- packages/base/src/UI5Element.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/base/src/UI5Element.ts b/packages/base/src/UI5Element.ts index 599c8d73fb52..6cd0efd01245 100644 --- a/packages/base/src/UI5Element.ts +++ b/packages/base/src/UI5Element.ts @@ -946,7 +946,7 @@ abstract class UI5Element extends HTMLElement { */ fireEvent(name: string, data?: T, cancelable?: boolean, bubbles?: boolean): boolean { const _cancellable = cancelable !== undefined ? cancelable : this.eventCancelable(name); - const _bubbles = bubbles !== undefined ? bubbles: this.eventBubbles(name); + const _bubbles = bubbles !== undefined ? bubbles : this.eventBubbles(name); const eventResult = this._fireEvent(name, data, _cancellable, _bubbles); const pascalCaseEventName = kebabToPascalCase(name); From a148662310e5612cfb1ba012668e5e148ba451e4 Mon Sep 17 00:00:00 2001 From: ilhan007 Date: Tue, 1 Oct 2024 12:46:01 +0300 Subject: [PATCH 10/18] chore: replace allowPreventDefault --- packages/fiori/src/NotificationList.ts | 8 ++++---- packages/fiori/src/ShellBar.ts | 12 ++++++------ packages/fiori/src/ShellBarItem.ts | 4 ++-- packages/fiori/src/SideNavigation.ts | 4 ++-- packages/main/src/Breadcrumbs.ts | 6 +++--- packages/main/src/Calendar.ts | 4 ++-- packages/main/src/CheckBox.ts | 1 - packages/main/src/DatePicker.ts | 10 +++++----- packages/main/src/Link.ts | 4 ++-- packages/main/src/List.ts | 16 ++++++++-------- packages/main/src/ListItemGroup.ts | 6 +++--- packages/main/src/Menu.ts | 15 +++++++++------ packages/main/src/MultiComboBox.ts | 4 ++-- packages/main/src/Popup.ts | 2 -- packages/main/src/Select.ts | 1 - packages/main/src/StepInput.ts | 4 ++-- packages/main/src/Switch.ts | 5 ++--- packages/main/src/TabContainer.ts | 16 ++++++++-------- packages/main/src/ToolbarSelect.ts | 8 +++++--- packages/main/src/Tree.ts | 8 ++++---- packages/tools/lib/cem/event.mjs | 7 +++---- packages/tools/lib/cem/utils.mjs | 4 ++-- .../api-reference-generation/component-file.mjs | 8 ++------ 23 files changed, 76 insertions(+), 81 deletions(-) diff --git a/packages/fiori/src/NotificationList.ts b/packages/fiori/src/NotificationList.ts index ada7c4b0a633..b6cb67702149 100644 --- a/packages/fiori/src/NotificationList.ts +++ b/packages/fiori/src/NotificationList.ts @@ -77,7 +77,6 @@ type NotificationItemCloseEventDetail = NotificationItemEventDetail; /** * Fired when an item is clicked. - * @allowPreventDefault * @param {HTMLElement} item The clicked item. * @public */ @@ -88,11 +87,11 @@ type NotificationItemCloseEventDetail = NotificationItemEventDetail; */ item: { type: HTMLElement }, }, + cancelable: true, }) /** * Fired when the `Close` button of any item is clicked. - * * @param {HTMLElement} item the item about to be closed. * @public */ @@ -103,6 +102,7 @@ type NotificationItemCloseEventDetail = NotificationItemEventDetail; */ item: { type: HTMLElement }, }, + cancelable: true, }) /** @@ -154,7 +154,7 @@ class NotificationList extends UI5Element { _onItemClick(e: CustomEvent) { const item = e.detail.item as NotificationListItemBase; - if (!this.fireEvent("item-click", { item }, true)) { + if (!this.fireEvent("item-click", { item })) { e.preventDefault(); } } @@ -162,7 +162,7 @@ class NotificationList extends UI5Element { _onItemClose(e: CustomEvent) { const item = e.detail.item as NotificationListItemBase; - if (!this.fireEvent("item-close", { item }, true)) { + if (!this.fireEvent("item-close", { item })) { e.preventDefault(); } } diff --git a/packages/fiori/src/ShellBar.ts b/packages/fiori/src/ShellBar.ts index 658b5ece0056..9373af7a3a9a 100644 --- a/packages/fiori/src/ShellBar.ts +++ b/packages/fiori/src/ShellBar.ts @@ -162,7 +162,6 @@ const HANDLE_RESIZE_DEBOUNCE_RATE = 200; // ms /** * * Fired, when the notification icon is activated. - * @allowPreventDefault * @param {HTMLElement} targetRef dom ref of the activated element * @public */ @@ -173,6 +172,7 @@ const HANDLE_RESIZE_DEBOUNCE_RATE = 200; // ms */ targetRef: { type: HTMLElement }, }, + cancelable: true, }) /** @@ -193,7 +193,6 @@ const HANDLE_RESIZE_DEBOUNCE_RATE = 200; // ms * Fired, when the product switch icon is activated. * * **Note:** You can prevent closing of overflow popover by calling `event.preventDefault()`. - * @allowPreventDefault * @param {HTMLElement} targetRef dom ref of the activated element * @public */ @@ -204,6 +203,7 @@ const HANDLE_RESIZE_DEBOUNCE_RATE = 200; // ms */ targetRef: { type: HTMLElement }, }, + cancelable: true, }) /** @@ -242,7 +242,6 @@ const HANDLE_RESIZE_DEBOUNCE_RATE = 200; // ms * Fired, when the search button is activated. * * **Note:** You can prevent expanding/collapsing of the search field by calling `event.preventDefault()`. - * @allowPreventDefault * @param {HTMLElement} targetRef dom ref of the activated element * @param {Boolean} searchFieldVisible whether the search field is visible * @public @@ -253,6 +252,7 @@ const HANDLE_RESIZE_DEBOUNCE_RATE = 200; // ms targetRef: { type: HTMLElement }, searchFieldVisible: { type: Boolean }, }, + cancelable: true, }) class ShellBar extends UI5Element { @@ -734,7 +734,7 @@ class ShellBar extends UI5Element { const defaultPrevented = !this.fireEvent("search-button-click", { targetRef: searchButtonRef, searchFieldVisible: this.showSearchField, - }, true); + }); if (defaultPrevented) { return; @@ -796,7 +796,7 @@ class ShellBar extends UI5Element { this._defaultItemPressPrevented = !this.fireEvent("notifications-click", { targetRef: notificationIconRef.classList.contains("ui5-shellbar-hidden-button") ? target : notificationIconRef, - }, true); + }); } _handleProfilePress() { @@ -815,7 +815,7 @@ class ShellBar extends UI5Element { this._defaultItemPressPrevented = !this.fireEvent("product-switch-click", { targetRef: buttonRef.classList.contains("ui5-shellbar-hidden-button") ? target : buttonRef, - }, true); + }); } /** diff --git a/packages/fiori/src/ShellBarItem.ts b/packages/fiori/src/ShellBarItem.ts index c66db511c06e..17c225c27060 100644 --- a/packages/fiori/src/ShellBarItem.ts +++ b/packages/fiori/src/ShellBarItem.ts @@ -27,7 +27,6 @@ type ShellBarItemClickEventDetail = { @customElement("ui5-shellbar-item") /** * Fired, when the item is pressed. - * @allowPreventDefault * @param {HTMLElement} targetRef DOM ref of the clicked element * @public */ @@ -35,6 +34,7 @@ type ShellBarItemClickEventDetail = { detail: { targetRef: { type: HTMLElement }, }, + cancelable: true, }) class ShellBarItem extends UI5Element { @@ -72,7 +72,7 @@ class ShellBarItem extends UI5Element { fireClickEvent(e: MouseEvent) { return this.fireEvent("click", { targetRef: (e.target as HTMLElement), - }, true); + }); } } diff --git a/packages/fiori/src/SideNavigation.ts b/packages/fiori/src/SideNavigation.ts index a9b696ca836b..03650c3765a4 100644 --- a/packages/fiori/src/SideNavigation.ts +++ b/packages/fiori/src/SideNavigation.ts @@ -123,7 +123,6 @@ type NavigationMenuClickEventDetail = MenuItemClickEventDetail & { * Fired when the selection has changed via user interaction * * @param {SideNavigationSelectableItemBase} item the clicked item. - * @allowPreventDefault * @public */ @event("selection-change", { @@ -133,6 +132,7 @@ type NavigationMenuClickEventDetail = MenuItemClickEventDetail & { */ item: { type: HTMLElement }, }, + cancelable: true, }) class SideNavigation extends UI5Element { /** @@ -603,7 +603,7 @@ class SideNavigation extends UI5Element { return; } - if (!this.fireEvent("selection-change", { item }, true)) { + if (!this.fireEvent("selection-change", { item })) { return; } diff --git a/packages/main/src/Breadcrumbs.ts b/packages/main/src/Breadcrumbs.ts index caf3cb5a415b..b2f0538ea350 100644 --- a/packages/main/src/Breadcrumbs.ts +++ b/packages/main/src/Breadcrumbs.ts @@ -110,7 +110,6 @@ type FocusAdaptor = ITabbable & { * Fires when a `BreadcrumbsItem` is clicked. * * **Note:** You can prevent browser location change by calling `event.preventDefault()`. - * @allowPreventDefault * @param {HTMLElement} item The clicked item. * @param {Boolean} altKey Returns whether the "ALT" key was pressed when the event was triggered. * @param {Boolean} ctrlKey Returns whether the "CTRL" key was pressed when the event was triggered. @@ -141,6 +140,7 @@ type FocusAdaptor = ITabbable & { */ shiftKey: { type: Boolean }, }, + cancelable: true, }) class Breadcrumbs extends UI5Element { /** @@ -408,7 +408,7 @@ class Breadcrumbs extends UI5Element { ctrlKey, metaKey, shiftKey, - }, true)) { + })) { e.preventDefault(); } } @@ -437,7 +437,7 @@ class Breadcrumbs extends UI5Element { items = this._getItems(), item = items.find(x => `${x._id}-li` === listItem.id)!; - if (this.fireEvent("item-click", { item }, true)) { + if (this.fireEvent("item-click", { item })) { locationOpen(item.href, item.target || "_self", "noopener,noreferrer"); this.responsivePopover!.open = false; } diff --git a/packages/main/src/Calendar.ts b/packages/main/src/Calendar.ts index 2f2b107d5a44..2957e73c1d99 100644 --- a/packages/main/src/Calendar.ts +++ b/packages/main/src/Calendar.ts @@ -209,7 +209,6 @@ type SpecialCalendarDateT = { * * **Note:** If you call `preventDefault()` for this event, the component will not * create instances of `ui5-date` for the newly selected dates. In that case you should do this manually. - * @allowPreventDefault * @param {Array} selectedValues The selected dates * @param {Array} selectedDates The selected dates as UTC timestamps * @public @@ -227,6 +226,7 @@ type SpecialCalendarDateT = { timestamp: { type: Number }, }, + cancelable: true, }) @event("show-month-view") @@ -633,7 +633,7 @@ class Calendar extends CalendarPart { return this.getFormat().format(calendarDate.toUTCJSDate(), true); }); - const defaultPrevented = !this.fireEvent("selection-change", { timestamp: this.timestamp, selectedDates: [...selectedDates], selectedValues: datesValues }, true); + const defaultPrevented = !this.fireEvent("selection-change", { timestamp: this.timestamp, selectedDates: [...selectedDates], selectedValues: datesValues }); if (!defaultPrevented) { this._setSelectedDates(selectedDates); } diff --git a/packages/main/src/CheckBox.ts b/packages/main/src/CheckBox.ts index 1044f74a0328..a4b83714b3ed 100644 --- a/packages/main/src/CheckBox.ts +++ b/packages/main/src/CheckBox.ts @@ -90,7 +90,6 @@ let activeCb: CheckBox; /** * Fired when the component checked state changes. * @public - * @allowPreventDefault */ @event("change", { cancelable: true, diff --git a/packages/main/src/DatePicker.ts b/packages/main/src/DatePicker.ts index f3d3a31492ec..ce9dd4737e2e 100644 --- a/packages/main/src/DatePicker.ts +++ b/packages/main/src/DatePicker.ts @@ -177,7 +177,6 @@ type DatePickerInputEventDetail = { }) /** * Fired when the input operation has finished by pressing Enter or on focusout. - * @allowPreventDefault * @public * @param {string} value The submitted value. * @param {boolean} valid Indicator if the value is in correct format pattern and in valid range. @@ -197,10 +196,10 @@ type DatePickerInputEventDetail = { type: Boolean, }, }, + cancelable: true, }) /** * Fired when the value of the component is changed at each key stroke. - * @allowPreventDefault * @public * @param {string} value The submitted value. * @param {boolean} valid Indicator if the value is in correct format pattern and in valid range. @@ -220,12 +219,12 @@ type DatePickerInputEventDetail = { type: Boolean, }, }, + cancelable: true, }) /** * Fired before the value state of the component is updated internally. * The event is preventable, meaning that if it's default action is * prevented, the component will not update the value state. - * @allowPreventDefault * @public * @param {string} valueState The new `valueState` that will be set. * @param {boolean} valid Indicator if the value is in correct format pattern and in valid range. @@ -245,6 +244,7 @@ type DatePickerInputEventDetail = { type: Boolean, }, }, + cancelable: true, }) class DatePicker extends DateComponentBase implements IFormInputElement { /** @@ -543,7 +543,7 @@ class DatePicker extends DateComponentBase implements IFormInputElement { } events.forEach((e: string) => { - if (!this.fireEvent(e, { value, valid }, true)) { + if (!this.fireEvent(e, { value, valid })) { executeEvent = false; } }); @@ -565,7 +565,7 @@ class DatePicker extends DateComponentBase implements IFormInputElement { this.valueState = valid ? ValueState.None : ValueState.Negative; - const eventPrevented = !this.fireEvent("value-state-change", { valueState: this.valueState, valid }, true); + const eventPrevented = !this.fireEvent("value-state-change", { valueState: this.valueState, valid }); if (eventPrevented) { this.valueState = previousValueState; diff --git a/packages/main/src/Link.ts b/packages/main/src/Link.ts index bfaefc753723..0997da7678a3 100644 --- a/packages/main/src/Link.ts +++ b/packages/main/src/Link.ts @@ -85,7 +85,6 @@ type LinkAccessibilityAttributes = Pick} selectedItems An array of the selected items. * @param {Array} previouslySelectedItems An array of the previously selected items. * @public @@ -273,6 +272,7 @@ type ListItemClickEventDetail = { */ key: { type: String }, }, + cancelable: true, }) /** @@ -301,7 +301,6 @@ type ListItemClickEventDetail = { * @param {object} destination Contains information about the destination of the moved element. Has `element` and `placement` properties. * @public * @since 2.0.0 - * @allowPreventDefault */ @event("move-over", { @@ -319,6 +318,7 @@ type ListItemClickEventDetail = { */ destination: { type: Object }, }, + cancelable: true, }) /** @@ -328,7 +328,6 @@ type ListItemClickEventDetail = { * @param {object} source Contains information about the moved element under `element` property. * @param {object} destination Contains information about the destination of the moved element. Has `element` and `placement` properties. * @public - * @allowPreventDefault */ @event("move", { detail: { @@ -345,6 +344,7 @@ type ListItemClickEventDetail = { */ destination: { type: Object }, }, + cancelable: true, }) class List extends UI5Element { /** @@ -803,7 +803,7 @@ class List extends UI5Element { selectionComponentPressed: e.detail.selectionComponentPressed, targetItem: e.detail.item, key: e.detail.key, - }, true); + }); if (changePrevented) { this._revertSelection(previouslySelectedItems); } @@ -926,7 +926,7 @@ class List extends UI5Element { element, placement, }, - }, true); + }); }); if (acceptedPosition) { @@ -1104,7 +1104,7 @@ class List extends UI5Element { element: closestPosition.element, placement, }, - }, true); + }); if (beforeItemMovePrevented) { e.preventDefault(); @@ -1186,7 +1186,7 @@ class List extends UI5Element { onItemPress(e: CustomEvent) { const pressedItem = e.detail.item; - if (!this.fireEvent("item-click", { item: pressedItem }, true)) { + if (!this.fireEvent("item-click", { item: pressedItem })) { return; } diff --git a/packages/main/src/ListItemGroup.ts b/packages/main/src/ListItemGroup.ts index ab7e690999b4..662ff2f4df01 100644 --- a/packages/main/src/ListItemGroup.ts +++ b/packages/main/src/ListItemGroup.ts @@ -61,7 +61,6 @@ type ListItemGroupMoveEventDetail = { * @param {object} destination Contains information about the destination of the moved element. Has `element` and `placement` properties. * @public * @since 2.1.0 - * @allowPreventDefault */ @event("move-over", { @@ -75,6 +74,7 @@ type ListItemGroupMoveEventDetail = { */ destination: { type: Object }, }, + cancelable: true, }) /** @@ -85,7 +85,6 @@ type ListItemGroupMoveEventDetail = { * @param {object} destination Contains information about the destination of the moved element. Has `element` and `placement` properties. * @public * @since 2.1.0 - * @allowPreventDefault */ @event("move", { detail: { @@ -98,6 +97,7 @@ type ListItemGroupMoveEventDetail = { */ destination: { type: Object }, }, + cancelable: true, }) class ListItemGroup extends UI5Element { @@ -217,7 +217,7 @@ class ListItemGroup extends UI5Element { element: closestPosition.element, placement, }, - }, true); + }); if (beforeItemMovePrevented) { e.preventDefault(); diff --git a/packages/main/src/Menu.ts b/packages/main/src/Menu.ts index 9d729e949658..6cb588faa0ff 100644 --- a/packages/main/src/Menu.ts +++ b/packages/main/src/Menu.ts @@ -112,7 +112,6 @@ type MenuBeforeCloseEventDetail = { escPressed: boolean }; * Fired when an item is being clicked. * * **Note:** Since 1.17.0 the event is preventable, allowing the menu to remain open after an item is pressed. - * @allowPreventDefault * @param { HTMLElement } item The currently clicked menu item. * @param { string } text The text of the currently clicked menu item. * @public @@ -132,6 +131,8 @@ type MenuBeforeCloseEventDetail = { escPressed: boolean }; type: String, }, }, + bubbles: false, + cancelable: true, }) /** @@ -139,7 +140,6 @@ type MenuBeforeCloseEventDetail = { escPressed: boolean }; * * **Note:** Since 1.14.0 the event is also fired before a sub-menu opens. * @public - * @allowPreventDefault * @since 1.10.0 * @param { HTMLElement } item The `ui5-menu-item` that triggers opening of the sub-menu or undefined when fired upon root menu opening. */ @@ -153,6 +153,7 @@ type MenuBeforeCloseEventDetail = { escPressed: boolean }; type: HTMLElement, }, }, + cancelable: true, }) /** @@ -165,7 +166,6 @@ type MenuBeforeCloseEventDetail = { escPressed: boolean }; /** * Fired before the menu is closed. This event can be cancelled, which will prevent the menu from closing. * @public - * @allowPreventDefault * @param {boolean} escPressed Indicates that `ESC` key has triggered the event. * @since 1.10.0 */ @@ -178,6 +178,7 @@ type MenuBeforeCloseEventDetail = { escPressed: boolean }; type: Boolean, }, }, + cancelable: true, }) /** @@ -293,6 +294,8 @@ class Menu extends UI5Element { return; } + // Menu "before-open" event should be fired always with the same config for cancelable and bubbles. + // Currently we configure it as cancelable and bubbling, while here we fire it as not cancelable and not bubbling. this.fireEvent("before-open", { item, }, false, false); @@ -348,7 +351,7 @@ class Menu extends UI5Element { const prevented = !this.fireEvent("item-click", { "item": item, "text": item.text || "", - }, true, false); + }); if (!prevented && this._popover) { item.fireEvent("close-menu", {}); @@ -381,7 +384,7 @@ class Menu extends UI5Element { } _beforePopoverOpen(e: CustomEvent) { - const prevented = !this.fireEvent("before-open", {}, true, true); + const prevented = !this.fireEvent("before-open"); if (prevented) { this.open = false; @@ -395,7 +398,7 @@ class Menu extends UI5Element { } _beforePopoverClose(e: CustomEvent) { - const prevented = !this.fireEvent("before-close", { escPressed: e.detail.escPressed }, true, true); + const prevented = !this.fireEvent("before-close", { escPressed: e.detail.escPressed }); if (prevented) { this.open = true; diff --git a/packages/main/src/MultiComboBox.ts b/packages/main/src/MultiComboBox.ts index c9f1e9a839e0..fc280ae696df 100644 --- a/packages/main/src/MultiComboBox.ts +++ b/packages/main/src/MultiComboBox.ts @@ -237,7 +237,6 @@ type MultiComboboxItemWithSelection = { * Fired when selection is changed by user interaction. * @param {IMultiComboBoxItem[]} items an array of the selected items. * @public - * @allowPreventDefault */ @event("selection-change", { detail: { @@ -246,6 +245,7 @@ type MultiComboboxItemWithSelection = { */ items: { type: Array }, }, + cancelable: true, }) class MultiComboBox extends UI5Element implements IFormInputElement { @@ -1482,7 +1482,7 @@ class MultiComboBox extends UI5Element implements IFormInputElement { fireSelectionChange() { const changePrevented = !this.fireEvent("selection-change", { items: this._getSelectedItems(), - }, true); + }); // Angular 2 way data binding this.fireEvent("value-changed"); diff --git a/packages/main/src/Popup.ts b/packages/main/src/Popup.ts index 8cffe00f60a1..1987f3ad22ef 100644 --- a/packages/main/src/Popup.ts +++ b/packages/main/src/Popup.ts @@ -84,7 +84,6 @@ type PopupBeforeCloseEventDetail = { /** * Fired before the component is opened. This event can be cancelled, which will prevent the popup from opening. * @public - * @allowPreventDefault */ @event("before-open", { bubbles: false, @@ -102,7 +101,6 @@ type PopupBeforeCloseEventDetail = { /** * Fired before the component is closed. This event can be cancelled, which will prevent the popup from closing. * @public - * @allowPreventDefault * @param {boolean} escPressed Indicates that `ESC` key has triggered the event. */ @event("before-close", { diff --git a/packages/main/src/Select.ts b/packages/main/src/Select.ts index 63962706554a..51dd06a0f8a6 100644 --- a/packages/main/src/Select.ts +++ b/packages/main/src/Select.ts @@ -149,7 +149,6 @@ type SelectLiveChangeEventDetail = { }) /** * Fired when the selected option changes. - * @allowPreventDefault * @param {IOption} selectedOption the selected option. * @public */ diff --git a/packages/main/src/StepInput.ts b/packages/main/src/StepInput.ts index dbb2e0cdbfd0..1522332489d0 100644 --- a/packages/main/src/StepInput.ts +++ b/packages/main/src/StepInput.ts @@ -108,7 +108,6 @@ type StepInputValueStateChangeEventDetail = { * Fired before the value state of the component is updated internally. * The event is preventable, meaning that if it's default action is * prevented, the component will not update the value state. - * @allowPreventDefault * @since 1.23.0 * @public * @param {string} valueState The new `valueState` that will be set. @@ -129,6 +128,7 @@ type StepInputValueStateChangeEventDetail = { type: Boolean, }, }, + cancelable: true, }) class StepInput extends UI5Element implements IFormInputElement { /** @@ -426,7 +426,7 @@ class StepInput extends UI5Element implements IFormInputElement { const eventPrevented = !this.fireEvent("value-state-change", { valueState: this.valueState, valid: isValid, - }, true); + }); if (eventPrevented) { this.valueState = previousValueState; diff --git a/packages/main/src/Switch.ts b/packages/main/src/Switch.ts index d115e49f4eab..e886d98451b3 100644 --- a/packages/main/src/Switch.ts +++ b/packages/main/src/Switch.ts @@ -64,7 +64,6 @@ import switchCss from "./generated/themes/Switch.css.js"; /** * Fired when the component checked state changes. * @public - * @allowPreventDefault */ @event("change", { cancelable: true, @@ -231,9 +230,9 @@ class Switch extends UI5Element implements IFormInputElement { toggle() { if (!this.disabled) { this.checked = !this.checked; - const changePrevented = !this.fireEvent("change", null, true); + const changePrevented = !this.fireEvent("change"); // Angular two way data binding; - const valueChangePrevented = !this.fireEvent("value-changed", null, true); + const valueChangePrevented = !this.fireEvent("value-changed"); if (changePrevented || valueChangePrevented) { this.checked = !this.checked; diff --git a/packages/main/src/TabContainer.ts b/packages/main/src/TabContainer.ts index bf7148cc6395..4953b05b0e42 100644 --- a/packages/main/src/TabContainer.ts +++ b/packages/main/src/TabContainer.ts @@ -182,7 +182,6 @@ interface ITab extends UI5Element { * @param {Integer} tabIndex The selected `tab` index in the flattened array of all tabs and their subTabs, provided by the `allItems` getter. * @public * @since 2.0.0 - * @allowPreventDefault */ @event("tab-select", { detail: { @@ -195,6 +194,7 @@ interface ITab extends UI5Element { */ tabIndex: { type: Number }, }, + cancelable: true, }) /** * Fired when element is being moved over the tab container. @@ -204,7 +204,6 @@ interface ITab extends UI5Element { * @param {object} destination Contains information about the destination of the moved element. Has `element` and `placement` properties. * @public * @since 2.0.0 - * @allowPreventDefault */ @event("move-over", { detail: { @@ -217,6 +216,7 @@ interface ITab extends UI5Element { */ destination: { type: Object }, }, + cancelable: true, }) /** * Fired when element is moved to the tab container. @@ -225,7 +225,6 @@ interface ITab extends UI5Element { * @param {object} source Contains information about the moved element under `element` property. * @param {object} destination Contains information about the destination of the moved element. Has `element` and `placement` properties. * @public - * @allowPreventDefault */ @event("move", { detail: { @@ -238,6 +237,7 @@ interface ITab extends UI5Element { */ destination: { type: Object }, }, + cancelable: true, }) class TabContainer extends UI5Element { /** @@ -562,7 +562,7 @@ class TabContainer extends UI5Element { element: dropTarget, placement, }, - }, true); + }); if (dragOverPrevented) { e.preventDefault(); @@ -645,7 +645,7 @@ class TabContainer extends UI5Element { element: (element as TabInStrip).realTabReference, placement, }, - }, true); + }); }); if (acceptedPosition) { @@ -712,7 +712,7 @@ class TabContainer extends UI5Element { element: destinationElement, placement: destination.placement, }, - }, true); + }); if (placementAccepted) { e.preventDefault(); @@ -756,7 +756,7 @@ class TabContainer extends UI5Element { element: destinationElement, placement: destination.placement, }, - }, true); + }); this.dropIndicatorDOM!.targetReference = null; draggedElement.focus(); @@ -967,7 +967,7 @@ class TabContainer extends UI5Element { * @returns true if the tab selection is successful, false if it was prevented */ selectTab(selectedTab: Tab, selectedTabIndex: number) { - if (!this.fireEvent("tab-select", { tab: selectedTab, tabIndex: selectedTabIndex }, true)) { + if (!this.fireEvent("tab-select", { tab: selectedTab, tabIndex: selectedTabIndex })) { return false; } diff --git a/packages/main/src/ToolbarSelect.ts b/packages/main/src/ToolbarSelect.ts index a6e41953ce05..028b5a7ce8b9 100644 --- a/packages/main/src/ToolbarSelect.ts +++ b/packages/main/src/ToolbarSelect.ts @@ -42,7 +42,6 @@ type ToolbarSelectChangeEventDetail = SelectChangeEventDetail; /** * Fired when the selected option changes. - * @allowPreventDefault * @param {HTMLElement} selectedOption the selected option. * @public */ @@ -53,6 +52,7 @@ type ToolbarSelectChangeEventDetail = SelectChangeEventDetail; */ selectedOption: { type: HTMLElement }, }, + cancelable: true, }) /** @@ -60,12 +60,14 @@ type ToolbarSelectChangeEventDetail = SelectChangeEventDetail; * @public */ @event("open") + /** * Fired after the component's dropdown menu closes. * @public */ -@event("close") - +@event("close", { + bubbles: false, +}) class ToolbarSelect extends ToolbarItem { /** * Defines the width of the select. diff --git a/packages/main/src/Tree.ts b/packages/main/src/Tree.ts index 983da3af27ff..bae4b2c33478 100644 --- a/packages/main/src/Tree.ts +++ b/packages/main/src/Tree.ts @@ -122,7 +122,6 @@ type WalkCallback = (item: TreeItemBase, level: number, index: number) => void; * This may be handy for example if you want to dynamically load tree items upon the user expanding a node. * Even if you prevented the event's default behavior, you can always manually call `toggle()` on a tree item. * @param {HTMLElement} item the toggled item. - * @allowPreventDefault * @public */ @event("item-toggle", { @@ -132,6 +131,7 @@ type WalkCallback = (item: TreeItemBase, level: number, index: number) => void; */ item: { type: HTMLElement }, }, + cancelable: true, }) /** * Fired when the mouse cursor enters the tree item borders. @@ -163,7 +163,6 @@ type WalkCallback = (item: TreeItemBase, level: number, index: number) => void; }) /** * Fired when a tree item is activated. - * @allowPreventDefault * @param {HTMLElement} item The clicked item. * @public */ @@ -174,6 +173,7 @@ type WalkCallback = (item: TreeItemBase, level: number, index: number) => void; */ item: { type: HTMLElement }, }, + cancelable: true, }) /** @@ -447,7 +447,7 @@ class Tree extends UI5Element { _onListItemToggle(e: CustomEvent) { const treeItem = e.detail.item; - const defaultPrevented = !this.fireEvent("item-toggle", { item: treeItem }, true); + const defaultPrevented = !this.fireEvent("item-toggle", { item: treeItem }); if (!defaultPrevented) { treeItem.toggle(); } @@ -456,7 +456,7 @@ class Tree extends UI5Element { _onListItemClick(e: CustomEvent) { const treeItem = e.detail.item as TreeItemBase; - if (!this.fireEvent("item-click", { item: treeItem }, true)) { + if (!this.fireEvent("item-click", { item: treeItem })) { e.preventDefault(); } } diff --git a/packages/tools/lib/cem/event.mjs b/packages/tools/lib/cem/event.mjs index 3967a9686e5e..ee83381ebe3b 100644 --- a/packages/tools/lib/cem/event.mjs +++ b/packages/tools/lib/cem/event.mjs @@ -78,7 +78,6 @@ function processEvent(ts, event, classNode, moduleDoc) { const privacy = findTag(eventParsedComment, ["public", "private", "protected"])?.tag || "private"; const sinceTag = findTag(eventParsedComment, "since"); const commentParams = findAllTags(eventParsedComment, "param"); - const allowPreventDefault = hasTag(eventParsedComment, "allowPreventDefault") || undefined; const description = normalizeDescription(eventParsedComment?.description); const native = hasTag(eventParsedComment, "native"); const eventArgs = event?.expression?.arguments; @@ -103,9 +102,9 @@ function processEvent(ts, event, classNode, moduleDoc) { } result.description = description; - result._ui5allowPreventDefault = allowPreventDefault; - result._ui5Cancelable = eventCancelable !== undefined ? eventCancelable : undefined; - result._ui5Bubbles = eventBubbles !== undefined ? eventBubbles : undefined; + result._ui5Cancelable = eventCancelable !== undefined ? eventCancelable : false; + result._ui5allowPreventDefault = result._ui5Cancelable; + result._ui5Bubbles = eventBubbles !== undefined ? eventBubbles : true; if (native) { result.type = { text: "Event" }; diff --git a/packages/tools/lib/cem/utils.mjs b/packages/tools/lib/cem/utils.mjs index eb0ffb419b8d..d83a718f399c 100644 --- a/packages/tools/lib/cem/utils.mjs +++ b/packages/tools/lib/cem/utils.mjs @@ -222,7 +222,7 @@ const commonTags = ["public", "protected", "private", "since", "deprecated"]; const allowedTags = { field: [...commonTags, "formEvents", "formProperty", "default"], slot: [...commonTags, "default"], - event: [...commonTags, "param", "allowPreventDefault", "native"], + event: [...commonTags, "param", "native"], eventParam: [...commonTags], method: [...commonTags, "param", "returns", "override"], class: [...commonTags, "constructor", "class", "abstract", "experimental", "implements", "extends", "slot", "csspart"], @@ -283,7 +283,7 @@ const findAllTags = (jsDoc, tagName) => { }; const validateJSDocTag = (tag) => { - const booleanTags = ["private", "protected", "public", "abstract", "allowPreventDefault", "native", "formProperty", "constructor", "override"]; + const booleanTags = ["private", "protected", "public", "abstract", "native", "formProperty", "constructor", "override"]; let tagName = tag.tag; if (booleanTags.includes(tag.tag)) { diff --git a/packages/website/build-scripts/api-reference-generation/component-file.mjs b/packages/website/build-scripts/api-reference-generation/component-file.mjs index 92cb2ff58663..68fd9f86f2d2 100644 --- a/packages/website/build-scripts/api-reference-generation/component-file.mjs +++ b/packages/website/build-scripts/api-reference-generation/component-file.mjs @@ -156,13 +156,9 @@ No events available for this component.` eventResult += `\n| Since | ${event._ui5since} |` } - if (typeof event._ui5Bubbles === "boolean") { - eventResult += `\n| Bubbles | ${event._ui5Bubbles ? "Yes" : "No"}. |` - } + eventResult += `\n| Bubbles | ${event._ui5Bubbles ? "Yes" : "No"} |` - if (typeof event._ui5Cancelable === "boolean") { - eventResult += `\n| Cancelable | ${event._ui5Cancelable ? "Yes - by calling preventDefault()." : "No."} |` - } + eventResult += `\n| Cancelable | ${event._ui5Cancelable ? "Yes - via preventDefault()" : "No"} |` if (event.deprecated) { eventResult += `\n| Deprecated | ${processDescription(typeof event.deprecated === "boolean" ? "true" : event.deprecated)} |` From 1cc892022c059d59daa07e9c75d54473efe9b3f1 Mon Sep 17 00:00:00 2001 From: ilhan007 Date: Wed, 2 Oct 2024 17:22:40 +0300 Subject: [PATCH 11/18] chore: add fireDecoratorEvent --- packages/base/src/UI5Element.ts | 41 +++++++++++++------ packages/base/src/decorators/event.ts | 4 +- packages/compat/src/TableRow.ts | 34 ++++++++++------ packages/fiori/src/MediaGalleryItem.ts | 6 ++- packages/fiori/src/NotificationList.ts | 12 ++++-- packages/fiori/src/ShellBar.ts | 21 ++++++---- packages/fiori/src/ShellBarItem.ts | 3 +- packages/fiori/src/SideNavigation.ts | 3 +- packages/fiori/src/ViewSettingsDialog.ts | 22 ++++++---- packages/main/src/Breadcrumbs.ts | 7 ++-- packages/main/src/Calendar.ts | 27 ++++++++----- packages/main/src/CheckBox.ts | 6 ++- packages/main/src/DatePicker.ts | 7 +++- packages/main/src/Input.ts | 51 ++++++++++++++---------- packages/main/src/Link.ts | 3 +- packages/main/src/List.ts | 35 ++++++++++------ packages/main/src/ListItemGroup.ts | 7 ++-- packages/main/src/Menu.ts | 27 ++++++------- packages/main/src/MultiComboBox.ts | 35 ++++++++++------ packages/main/src/Popup.ts | 24 +++++------ packages/main/src/Select.ts | 26 +++++++----- packages/main/src/StepInput.ts | 9 +++-- packages/main/src/Switch.ts | 6 ++- packages/main/src/TabContainer.ts | 18 +++++---- packages/main/src/TextArea.ts | 32 ++++++++++----- packages/main/src/ToolbarSelect.ts | 9 +++-- packages/main/src/Tree.ts | 42 ++++++++++++++----- 27 files changed, 319 insertions(+), 198 deletions(-) diff --git a/packages/base/src/UI5Element.ts b/packages/base/src/UI5Element.ts index df3d42314a1f..ba8fb7bdebd4 100644 --- a/packages/base/src/UI5Element.ts +++ b/packages/base/src/UI5Element.ts @@ -947,11 +947,34 @@ abstract class UI5Element extends HTMLElement { * @param bubbles - true, if the event bubbles * @returns false, if the event was cancelled (preventDefault called), true otherwise */ - fireEvent(name: string, data?: T, cancelable?: boolean, bubbles?: boolean): boolean { - const _cancellable = cancelable !== undefined ? cancelable : this.eventCancelable(name); - const _bubbles = bubbles !== undefined ? bubbles : this.eventBubbles(name); + fireEvent(name: string, data?: T, cancelable = false, bubbles = true): boolean { + const eventResult = this._fireEvent(name, data, cancelable, bubbles); + const pascalCaseEventName = kebabToPascalCase(name); + + // pascal events are more convinient for native react usage + // live-change: + // Before: onlive-change + // After: onLiveChange + if (pascalCaseEventName !== name) { + return eventResult && this._fireEvent(pascalCaseEventName, data, cancelable, bubbles); + } + + return eventResult; + } - const eventResult = this._fireEvent(name, data, _cancellable, _bubbles); + /** + * Fires a custom event, configured via the "event" decorator. + * @public + * @param name - name of the event + * @param data - additional data for the event + * @returns false, if the event was cancelled (preventDefault called), true otherwise + */ + fireDecoratorEvent(name: string, data?: T): boolean { + const eventData = this.getEventData(name); + const cancellable = eventData ? eventData.cancelable : false; + const bubbles = eventData ? eventData.bubbles : false; + + const eventResult = this._fireEvent(name, data, cancellable, bubbles); const pascalCaseEventName = kebabToPascalCase(name); // pascal events are more convinient for native react usage @@ -959,7 +982,7 @@ abstract class UI5Element extends HTMLElement { // Before: onlive-change // After: onLiveChange if (pascalCaseEventName !== name) { - return eventResult && this._fireEvent(pascalCaseEventName, data, _cancellable, _bubbles); + return eventResult && this._fireEvent(pascalCaseEventName, data, cancellable, bubbles); } return eventResult; @@ -999,14 +1022,6 @@ abstract class UI5Element extends HTMLElement { const eventMap = ctor.getMetadata().getEvents(); return eventMap[name]; } - eventCancelable(name: string): boolean { - const eventData = this.getEventData(name); - return eventData ? eventData.cancelable : false; - } - eventBubbles(name: string): boolean { - const eventData = this.getEventData(name); - return eventData ? eventData.bubbles : true; - } /** * Returns the actual children, associated with a slot. diff --git a/packages/base/src/decorators/event.ts b/packages/base/src/decorators/event.ts index 3aa0b45a1512..726b9d999637 100644 --- a/packages/base/src/decorators/event.ts +++ b/packages/base/src/decorators/event.ts @@ -18,8 +18,8 @@ const event = (name: string, data: { detail?: Record("_forward-after", { target: activeElement }); + this.fireDecoratorEvent("_forward-after", { target: activeElement }); } if (isTabPrevious(e) && activeElement === this.root) { - this.fireEvent("_forward-before", { target: activeElement }); + this.fireDecoratorEvent("_forward-before", { target: activeElement }); } if (isSpace(e) && target.tagName.toLowerCase() === "tr") { @@ -240,11 +250,11 @@ class TableRow extends UI5Element implements ITableRow { if (isRowFocused && !checkboxPressed) { if ((isSpace(e) && itemSelectable) || (isEnter(e) && isSingleSelect)) { - this.fireEvent("selection-requested", { row: this }); + this.fireDecoratorEvent("selection-requested", { row: this }); } if (isEnter(e) && itemActive) { - this.fireEvent("row-click", { row: this }); + this.fireDecoratorEvent("row-click", { row: this }); if (!isSingleSelect) { this.activate(); } @@ -253,7 +263,7 @@ class TableRow extends UI5Element implements ITableRow { if (isF7(e)) { e.preventDefault(); - this.fireEvent("f7-pressed", { row: this }); + this.fireDecoratorEvent("f7-pressed", { row: this }); } } @@ -277,7 +287,7 @@ class TableRow extends UI5Element implements ITableRow { this.activate(); } - this.fireEvent("_focused"); + this.fireDecoratorEvent("_focused"); } _onrowclick(e: MouseEvent) { @@ -305,13 +315,13 @@ class TableRow extends UI5Element implements ITableRow { } if (this.type === TableRowType.Active && !checkboxPressed) { - this.fireEvent("row-click", { row: this }); + this.fireDecoratorEvent("row-click", { row: this }); } } } _handleSelection() { - this.fireEvent("selection-requested", { row: this }); + this.fireDecoratorEvent("selection-requested", { row: this }); } _activeElementHasAttribute(attr: string): boolean { diff --git a/packages/fiori/src/MediaGalleryItem.ts b/packages/fiori/src/MediaGalleryItem.ts index 316c0adac471..2a4c20d96d1a 100644 --- a/packages/fiori/src/MediaGalleryItem.ts +++ b/packages/fiori/src/MediaGalleryItem.ts @@ -50,7 +50,9 @@ import MediaGalleryItemTemplate from "./generated/templates/MediaGalleryItemTemp /** * @private */ -@event("click") +@event("click", { + bubbles: true, +}) class MediaGalleryItem extends UI5Element implements IMediaGalleryItem { /** * Defines the selected state of the component. @@ -257,7 +259,7 @@ class MediaGalleryItem extends UI5Element implements IMediaGalleryItem { } _fireItemClick() { - this.fireEvent("click", { item: this }); + this.fireDecoratorEvent("click", { item: this }); } } diff --git a/packages/fiori/src/NotificationList.ts b/packages/fiori/src/NotificationList.ts index 0ce6474fff8d..1667d98e7728 100644 --- a/packages/fiori/src/NotificationList.ts +++ b/packages/fiori/src/NotificationList.ts @@ -87,6 +87,7 @@ type NotificationItemCloseEventDetail = NotificationItemEventDetail; */ item: { type: HTMLElement }, }, + bubbles: true, cancelable: true, }) @@ -102,6 +103,7 @@ type NotificationItemCloseEventDetail = NotificationItemEventDetail; */ item: { type: HTMLElement }, }, + bubbles: true, cancelable: true, }) @@ -118,6 +120,8 @@ type NotificationItemCloseEventDetail = NotificationItemEventDetail; */ item: { type: HTMLElement }, }, + bubbles: true, + cancelable: true, }) class NotificationList extends UI5Element { @@ -155,7 +159,7 @@ class NotificationList extends UI5Element { _onItemClick(e: CustomEvent) { const item = e.detail.item as NotificationListItemBase; - if (!this.fireEvent("item-click", { item })) { + if (!this.fireDecoratorEvent("item-click", { item })) { e.preventDefault(); } } @@ -163,7 +167,7 @@ class NotificationList extends UI5Element { _onItemClose(e: CustomEvent) { const item = e.detail.item as NotificationListItemBase; - if (!this.fireEvent("item-close", { item })) { + if (!this.fireDecoratorEvent("item-close", { item })) { e.preventDefault(); } } @@ -171,13 +175,13 @@ class NotificationList extends UI5Element { _onItemToggle(e: CustomEvent) { const item = e.detail.item as NotificationListItemBase; - if (!this.fireEvent("item-toggle", { item }, true)) { + if (!this.fireDecoratorEvent("item-toggle", { item })) { e.preventDefault(); } } _onLoadMore() { - this.fireEvent("load-more"); + this.fireDecoratorEvent("load-more"); } } diff --git a/packages/fiori/src/ShellBar.ts b/packages/fiori/src/ShellBar.ts index 3bca5bafb8b6..9cccfdc9e7f4 100644 --- a/packages/fiori/src/ShellBar.ts +++ b/packages/fiori/src/ShellBar.ts @@ -173,6 +173,7 @@ const HANDLE_RESIZE_DEBOUNCE_RATE = 200; // ms targetRef: { type: HTMLElement }, }, cancelable: true, + bubbles: true, }) /** @@ -187,6 +188,7 @@ const HANDLE_RESIZE_DEBOUNCE_RATE = 200; // ms */ targetRef: { type: HTMLElement }, }, + bubbles: true, }) /** @@ -204,6 +206,7 @@ const HANDLE_RESIZE_DEBOUNCE_RATE = 200; // ms targetRef: { type: HTMLElement }, }, cancelable: true, + bubbles: true, }) /** @@ -219,6 +222,7 @@ const HANDLE_RESIZE_DEBOUNCE_RATE = 200; // ms */ targetRef: { type: HTMLElement }, }, + bubbles: true, }) /** @@ -236,6 +240,8 @@ const HANDLE_RESIZE_DEBOUNCE_RATE = 200; // ms */ item: { type: HTMLElement }, }, + bubbles: true, + cancelable: true, }) /** @@ -253,6 +259,7 @@ const HANDLE_RESIZE_DEBOUNCE_RATE = 200; // ms searchFieldVisible: { type: Boolean }, }, cancelable: true, + bubbles: true, }) class ShellBar extends UI5Element { @@ -528,16 +535,16 @@ class ShellBar extends UI5Element { } _menuItemPress(e: CustomEvent) { - const shouldContinue = this.fireEvent("menu-item-click", { + const shouldContinue = this.fireDecoratorEvent("menu-item-click", { item: e.detail.selectedItems[0], - }, true); + }); if (shouldContinue) { this.menuPopover!.open = false; } } _logoPress() { - this.fireEvent("logo-click", { + this.fireDecoratorEvent("logo-click", { targetRef: this.shadowRoot!.querySelector(".ui5-shellbar-logo")!, }); } @@ -732,7 +739,7 @@ class ShellBar extends UI5Element { _handleSearchIconPress() { const searchButtonRef = this.shadowRoot!.querySelector