From 79fb535e852f4ec7163807885f9bf954d90561c0 Mon Sep 17 00:00:00 2001 From: Daniel Leroux Date: Thu, 3 Aug 2023 13:03:39 +0200 Subject: [PATCH 1/4] feat(core/tabs): add additional tab events --- packages/angular/src/components.ts | 16 ++- packages/core/component-doc.json | 29 +++++- packages/core/src/components.d.ts | 15 +++ .../core/src/components/tab-item/tab-item.tsx | 20 +++- packages/core/src/components/tabs/tabs.tsx | 38 +++++-- .../core/src/components/tabs/test/tabs.ct.ts | 98 +++++++++++++++++++ packages/vue/src/components.ts | 6 +- 7 files changed, 209 insertions(+), 13 deletions(-) create mode 100644 packages/core/src/components/tabs/test/tabs.ct.ts diff --git a/packages/angular/src/components.ts b/packages/angular/src/components.ts index 90563aeb90d..d690a44b154 100644 --- a/packages/angular/src/components.ts +++ b/packages/angular/src/components.ts @@ -1861,11 +1861,17 @@ export class IxTabItem { constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { c.detach(); this.el = r.nativeElement; + proxyOutputs(this, this.el, ['tabClick']); } } -export declare interface IxTabItem extends Components.IxTabItem {} +import type { TabClickDetail as IIxTabItemTabClickDetail } from '@siemens/ix'; + +export declare interface IxTabItem extends Components.IxTabItem { + + tabClick: EventEmitter>; +} @ProxyCmp({ @@ -1883,11 +1889,17 @@ export class IxTabs { constructor(c: ChangeDetectorRef, r: ElementRef, protected z: NgZone) { c.detach(); this.el = r.nativeElement; + proxyOutputs(this, this.el, ['selectedChange']); } } -export declare interface IxTabs extends Components.IxTabs {} +export declare interface IxTabs extends Components.IxTabs { + /** + * `selected` property changed + */ + selectedChange: EventEmitter>; +} @ProxyCmp({ diff --git a/packages/core/component-doc.json b/packages/core/component-doc.json index 72cdeb27fc3..89d8bc8f47e 100644 --- a/packages/core/component-doc.json +++ b/packages/core/component-doc.json @@ -9253,7 +9253,17 @@ } ], "methods": [], - "events": [], + "events": [ + { + "event": "tabClick", + "detail": "{ nativeEvent: MouseEvent; }", + "bubbles": true, + "cancelable": true, + "composed": true, + "docs": "", + "docsTags": [] + } + ], "styles": [], "slots": [], "parts": [], @@ -9387,7 +9397,17 @@ } ], "methods": [], - "events": [], + "events": [ + { + "event": "selectedChange", + "detail": "number", + "bubbles": true, + "cancelable": true, + "composed": true, + "docs": "`selected` property changed", + "docsTags": [] + } + ], "styles": [], "slots": [], "parts": [], @@ -9397,6 +9417,11 @@ "target": "window", "capture": false, "passive": true + }, + { + "event": "tabClick", + "capture": false, + "passive": false } ] }, diff --git a/packages/core/src/components.d.ts b/packages/core/src/components.d.ts index 18923a65f18..d9fbf73b853 100644 --- a/packages/core/src/components.d.ts +++ b/packages/core/src/components.d.ts @@ -29,6 +29,7 @@ import { KeyValueLabelPosition } from "./components/key-value/key-value"; import { IxModalSize } from "./components/modal/modal"; import { PushCardVariant } from "./components/push-card/push-card"; import { SplitButtonVariant } from "./components/split-button/split-button"; +import { TabClickDetail } from "./components/tab-item/tab-item"; import { TimePickerCorners } from "./components/time-picker/time-picker"; import { ToastConfig, ToastType } from "./components/toast/toast-utils"; import { TypedEvent } from "./components/utils/typed-event"; @@ -59,6 +60,7 @@ export { KeyValueLabelPosition } from "./components/key-value/key-value"; export { IxModalSize } from "./components/modal/modal"; export { PushCardVariant } from "./components/push-card/push-card"; export { SplitButtonVariant } from "./components/split-button/split-button"; +export { TabClickDetail } from "./components/tab-item/tab-item"; export { TimePickerCorners } from "./components/time-picker/time-picker"; export { ToastConfig, ToastType } from "./components/toast/toast-utils"; export { TypedEvent } from "./components/utils/typed-event"; @@ -2090,6 +2092,14 @@ export interface IxSplitButtonItemCustomEvent extends CustomEvent { detail: T; target: HTMLIxSplitButtonItemElement; } +export interface IxTabItemCustomEvent extends CustomEvent { + detail: T; + target: HTMLIxTabItemElement; +} +export interface IxTabsCustomEvent extends CustomEvent { + detail: T; + target: HTMLIxTabsElement; +} export interface IxTimePickerCustomEvent extends CustomEvent { detail: T; target: HTMLIxTimePickerElement; @@ -4536,6 +4546,7 @@ declare namespace LocalJSX { * Set layout width style */ "layout"?: 'auto' | 'stretched'; + "onTabClick"?: (event: IxTabItemCustomEvent) => void; /** * Set selected placement */ @@ -4558,6 +4569,10 @@ declare namespace LocalJSX { * Set layout width style */ "layout"?: 'auto' | 'stretched'; + /** + * `selected` property changed + */ + "onSelectedChange"?: (event: IxTabsCustomEvent) => void; /** * Set placement style */ diff --git a/packages/core/src/components/tab-item/tab-item.tsx b/packages/core/src/components/tab-item/tab-item.tsx index 401d1ba2c0a..1ea5f52e574 100644 --- a/packages/core/src/components/tab-item/tab-item.tsx +++ b/packages/core/src/components/tab-item/tab-item.tsx @@ -7,7 +7,11 @@ * LICENSE file in the root directory of this source tree. */ -import { Component, h, Host, Prop } from '@stencil/core'; +import { Component, Event, EventEmitter, h, Host, Prop } from '@stencil/core'; + +export type TabClickDetail = { + nativeEvent: MouseEvent; +}; @Component({ tag: 'ix-tab-item', @@ -55,6 +59,11 @@ export class TabItem { */ @Prop() placement: 'bottom' | 'top' = 'bottom'; + /** + * + */ + @Event() tabClick: EventEmitter; + private tabItemClasses(props: { selected: boolean; disabled: boolean; @@ -89,6 +98,15 @@ export class TabItem { circle: this.rounded, })} tabIndex={0} + onClick={(event: MouseEvent) => { + const clientEvent = this.tabClick.emit({ + nativeEvent: event, + }); + + if (clientEvent.defaultPrevented) { + event.stopPropagation(); + } + }} >
; + @State() totalItems = 0; @State() currentScrollAmount = 0; @State() scrollAmount = 100; @@ -164,7 +171,14 @@ export class Tabs { } private clickTab(index: number) { - if (this.dragStop()) return; + if (this.dragStop()) { + return; + } + + const { defaultPrevented } = this.selectedChange.emit(index); + if (defaultPrevented) { + return; + } this.setSelected(index); this.moveTabToView(index); @@ -236,17 +250,29 @@ export class Tabs { componentDidLoad() { const tabs = this.getTabs(); - tabs.forEach((element, index) => { - const isDisabled = element.getAttribute('disabled') !== null; - if (!isDisabled) - element.addEventListener('click', () => this.clickTab(index)); - + tabs.forEach((element) => { element.addEventListener('mousedown', (event) => this.dragStart(element, event) ); }); } + @Listen('tabClick') + test(event: CustomEvent) { + if (event.defaultPrevented) { + return; + } + + const target = event.target; + const tabs = this.getTabs(); + + tabs.forEach((tab, index) => { + if (!tab.disabled && tab === target) { + this.clickTab(index); + } + }); + } + render() { return ( diff --git a/packages/core/src/components/tabs/test/tabs.ct.ts b/packages/core/src/components/tabs/test/tabs.ct.ts new file mode 100644 index 00000000000..1f68eb65d6f --- /dev/null +++ b/packages/core/src/components/tabs/test/tabs.ct.ts @@ -0,0 +1,98 @@ +/* + * SPDX-FileCopyrightText: 2023 Siemens AG + * + * SPDX-License-Identifier: MIT + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +/* + * SPDX-FileCopyrightText: 2023 Siemens AG + * + * SPDX-License-Identifier: MIT + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import { expect } from '@playwright/test'; +import { test } from '@utils/test'; + +test('renders', async ({ mount, page }) => { + await mount(` + + Item 1 + Item 2 + Item 3 + + `); + const tabs = page.locator('ix-tabs'); + const tab = page.locator('ix-tab-item').nth(0); + + await expect(tabs).toHaveClass(/hydrated/); + await expect(tab).toHaveClass(/selected/); +}); + +test('should change tab', async ({ mount, page }) => { + await mount(` + + Item 1 + Item 2 + Item 3 + + `); + const tabs = page.locator('ix-tabs'); + const tab = page.locator('ix-tab-item').nth(2); + + await tab.click(); + + await expect(tabs).toHaveClass(/hydrated/); + await expect(tab).toHaveClass(/selected/); +}); + +test('should not change tab by tab click event', async ({ mount, page }) => { + await mount(` + + Item 1 + Item 2 + Item 3 + + `); + const tabs = page.locator('ix-tabs'); + const firstTab = page.locator('ix-tab-item').nth(0); + const lastTab = page.locator('ix-tab-item').nth(2); + + lastTab.evaluate((tabElement) => { + tabElement.addEventListener('tabClick', (event) => event.preventDefault()); + }); + + await lastTab.click(); + + await expect(tabs).toHaveClass(/hydrated/); + await expect(firstTab).toHaveClass(/selected/); + await expect(lastTab).not.toHaveClass(/selected/); +}); + +test('should not change tab by tabs event', async ({ mount, page }) => { + await mount(` + + Item 1 + Item 2 + Item 3 + + `); + const tabs = page.locator('ix-tabs'); + const firstTab = page.locator('ix-tab-item').nth(0); + const lastTab = page.locator('ix-tab-item').nth(2); + + tabs.evaluate((tabElement) => { + tabElement.addEventListener('selectedChange', (event) => + event.preventDefault() + ); + }); + + await lastTab.click(); + + await expect(tabs).toHaveClass(/hydrated/); + await expect(firstTab).toHaveClass(/selected/); + await expect(lastTab).not.toHaveClass(/selected/); +}); diff --git a/packages/vue/src/components.ts b/packages/vue/src/components.ts index 58098a2ea3a..72fc8355dd3 100644 --- a/packages/vue/src/components.ts +++ b/packages/vue/src/components.ts @@ -645,7 +645,8 @@ export const IxTabItem = /*@__PURE__*/ defineContainer('ix-tab-it 'rounded', 'counter', 'layout', - 'placement' + 'placement', + 'tabClick' ]); @@ -654,7 +655,8 @@ export const IxTabs = /*@__PURE__*/ defineContainer('ix-tabs', undef 'rounded', 'selected', 'layout', - 'placement' + 'placement', + 'selectedChange' ]); From f1772ce00093aff63aeaf73cd9e9073d05a4c1d2 Mon Sep 17 00:00:00 2001 From: Daniel Leroux Date: Thu, 3 Aug 2023 13:04:32 +0200 Subject: [PATCH 2/4] docs: add jsdocs --- packages/core/src/components/tab-item/tab-item.tsx | 2 ++ packages/core/src/components/tabs/tabs.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/core/src/components/tab-item/tab-item.tsx b/packages/core/src/components/tab-item/tab-item.tsx index 1ea5f52e574..726f2dfed74 100644 --- a/packages/core/src/components/tab-item/tab-item.tsx +++ b/packages/core/src/components/tab-item/tab-item.tsx @@ -60,7 +60,9 @@ export class TabItem { @Prop() placement: 'bottom' | 'top' = 'bottom'; /** + * On tab click * + * @since 2.0.0 */ @Event() tabClick: EventEmitter; diff --git a/packages/core/src/components/tabs/tabs.tsx b/packages/core/src/components/tabs/tabs.tsx index bcd830828d6..fb265871b23 100644 --- a/packages/core/src/components/tabs/tabs.tsx +++ b/packages/core/src/components/tabs/tabs.tsx @@ -56,6 +56,8 @@ export class Tabs { /** * `selected` property changed + * + * @since 2.0.0 */ @Event() selectedChange: EventEmitter; From 34c8b6ab45e9d08f1e6bbadb38ead503cadcf310 Mon Sep 17 00:00:00 2001 From: Daniel Leroux Date: Thu, 3 Aug 2023 14:48:12 +0200 Subject: [PATCH 3/4] fix: component-doc update --- packages/angular/src/components.ts | 6 ++++-- packages/core/component-doc.json | 16 +++++++++++++--- packages/core/src/components.d.ts | 5 +++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/packages/angular/src/components.ts b/packages/angular/src/components.ts index d690a44b154..3164b4e36c2 100644 --- a/packages/angular/src/components.ts +++ b/packages/angular/src/components.ts @@ -1869,7 +1869,9 @@ export class IxTabItem { import type { TabClickDetail as IIxTabItemTabClickDetail } from '@siemens/ix'; export declare interface IxTabItem extends Components.IxTabItem { - + /** + * On tab click @since 2.0.0 + */ tabClick: EventEmitter>; } @@ -1896,7 +1898,7 @@ export class IxTabs { export declare interface IxTabs extends Components.IxTabs { /** - * `selected` property changed + * `selected` property changed @since 2.0.0 */ selectedChange: EventEmitter>; } diff --git a/packages/core/component-doc.json b/packages/core/component-doc.json index 89d8bc8f47e..184ae44c60c 100644 --- a/packages/core/component-doc.json +++ b/packages/core/component-doc.json @@ -9260,8 +9260,13 @@ "bubbles": true, "cancelable": true, "composed": true, - "docs": "", - "docsTags": [] + "docs": "On tab click", + "docsTags": [ + { + "name": "since", + "text": "2.0.0" + } + ] } ], "styles": [], @@ -9405,7 +9410,12 @@ "cancelable": true, "composed": true, "docs": "`selected` property changed", - "docsTags": [] + "docsTags": [ + { + "name": "since", + "text": "2.0.0" + } + ] } ], "styles": [], diff --git a/packages/core/src/components.d.ts b/packages/core/src/components.d.ts index d9fbf73b853..67aa38c861c 100644 --- a/packages/core/src/components.d.ts +++ b/packages/core/src/components.d.ts @@ -4546,6 +4546,10 @@ declare namespace LocalJSX { * Set layout width style */ "layout"?: 'auto' | 'stretched'; + /** + * On tab click + * @since 2.0.0 + */ "onTabClick"?: (event: IxTabItemCustomEvent) => void; /** * Set selected placement @@ -4571,6 +4575,7 @@ declare namespace LocalJSX { "layout"?: 'auto' | 'stretched'; /** * `selected` property changed + * @since 2.0.0 */ "onSelectedChange"?: (event: IxTabsCustomEvent) => void; /** From 3da235a1279fd2a84d4ce5e9c0958e9c5e4c3f3f Mon Sep 17 00:00:00 2001 From: Daniel Leroux Date: Fri, 4 Aug 2023 14:45:20 +0200 Subject: [PATCH 4/4] refactor: rename method --- packages/core/src/components/tabs/tabs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/components/tabs/tabs.tsx b/packages/core/src/components/tabs/tabs.tsx index fb265871b23..30af28f7990 100644 --- a/packages/core/src/components/tabs/tabs.tsx +++ b/packages/core/src/components/tabs/tabs.tsx @@ -260,7 +260,7 @@ export class Tabs { } @Listen('tabClick') - test(event: CustomEvent) { + onTabClick(event: CustomEvent) { if (event.defaultPrevented) { return; }