Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core/tabs): add additional navigation events #669

Merged
merged 4 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions packages/angular/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1861,11 +1861,19 @@ 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 {
/**
* On tab click @since 2.0.0
*/
tabClick: EventEmitter<CustomEvent<IIxTabItemTabClickDetail>>;
}


@ProxyCmp({
Expand All @@ -1883,11 +1891,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 @since 2.0.0
*/
selectedChange: EventEmitter<CustomEvent<number>>;
}


@ProxyCmp({
Expand Down
39 changes: 37 additions & 2 deletions packages/core/component-doc.json
Original file line number Diff line number Diff line change
Expand Up @@ -9253,7 +9253,22 @@
}
],
"methods": [],
"events": [],
"events": [
{
"event": "tabClick",
"detail": "{ nativeEvent: MouseEvent; }",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": "On tab click",
"docsTags": [
{
"name": "since",
"text": "2.0.0"
}
]
}
],
"styles": [],
"slots": [],
"parts": [],
Expand Down Expand Up @@ -9387,7 +9402,22 @@
}
],
"methods": [],
"events": [],
"events": [
{
"event": "selectedChange",
"detail": "number",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": "`selected` property changed",
"docsTags": [
{
"name": "since",
"text": "2.0.0"
}
]
}
],
"styles": [],
"slots": [],
"parts": [],
Expand All @@ -9397,6 +9427,11 @@
"target": "window",
"capture": false,
"passive": true
},
{
"event": "tabClick",
"capture": false,
"passive": false
}
]
},
Expand Down
20 changes: 20 additions & 0 deletions packages/core/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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";
Expand Down Expand Up @@ -2090,6 +2092,14 @@ export interface IxSplitButtonItemCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIxSplitButtonItemElement;
}
export interface IxTabItemCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIxTabItemElement;
}
export interface IxTabsCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIxTabsElement;
}
export interface IxTimePickerCustomEvent<T> extends CustomEvent<T> {
detail: T;
target: HTMLIxTimePickerElement;
Expand Down Expand Up @@ -4536,6 +4546,11 @@ declare namespace LocalJSX {
* Set layout width style
*/
"layout"?: 'auto' | 'stretched';
/**
* On tab click
* @since 2.0.0
*/
"onTabClick"?: (event: IxTabItemCustomEvent<TabClickDetail>) => void;
/**
* Set selected placement
*/
Expand All @@ -4558,6 +4573,11 @@ declare namespace LocalJSX {
* Set layout width style
*/
"layout"?: 'auto' | 'stretched';
/**
* `selected` property changed
* @since 2.0.0
*/
"onSelectedChange"?: (event: IxTabsCustomEvent<number>) => void;
/**
* Set placement style
*/
Expand Down
22 changes: 21 additions & 1 deletion packages/core/src/components/tab-item/tab-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -55,6 +59,13 @@ export class TabItem {
*/
@Prop() placement: 'bottom' | 'top' = 'bottom';

/**
* On tab click
*
* @since 2.0.0
*/
@Event() tabClick: EventEmitter<TabClickDetail>;

private tabItemClasses(props: {
selected: boolean;
disabled: boolean;
Expand Down Expand Up @@ -89,6 +100,15 @@ export class TabItem {
circle: this.rounded,
})}
tabIndex={0}
onClick={(event: MouseEvent) => {
const clientEvent = this.tabClick.emit({
nativeEvent: event,
});

if (clientEvent.defaultPrevented) {
event.stopPropagation();
}
}}
>
<div
class={{
Expand Down
40 changes: 34 additions & 6 deletions packages/core/src/components/tabs/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import {
Component,
Element,
Event,
EventEmitter,
h,
Host,
Listen,
Expand Down Expand Up @@ -52,6 +54,13 @@ export class Tabs {
*/
@Prop() placement: 'bottom' | 'top' = 'bottom';

/**
* `selected` property changed
*
* @since 2.0.0
*/
@Event() selectedChange: EventEmitter<number>;

@State() totalItems = 0;
@State() currentScrollAmount = 0;
@State() scrollAmount = 100;
Expand Down Expand Up @@ -164,7 +173,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);
Expand Down Expand Up @@ -236,17 +252,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')
onTabClick(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 (
<Host>
Expand Down
98 changes: 98 additions & 0 deletions packages/core/src/components/tabs/test/tabs.ct.ts
Original file line number Diff line number Diff line change
@@ -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(`
<ix-tabs>
<ix-tab-item>Item 1</ix-tab-item>
<ix-tab-item>Item 2</ix-tab-item>
<ix-tab-item>Item 3</ix-tab-item>
</ix-tabs>
`);
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(`
<ix-tabs>
<ix-tab-item>Item 1</ix-tab-item>
<ix-tab-item>Item 2</ix-tab-item>
<ix-tab-item>Item 3</ix-tab-item>
</ix-tabs>
`);
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(`
<ix-tabs>
<ix-tab-item>Item 1</ix-tab-item>
<ix-tab-item>Item 2</ix-tab-item>
<ix-tab-item>Item 3</ix-tab-item>
</ix-tabs>
`);
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(`
<ix-tabs>
<ix-tab-item>Item 1</ix-tab-item>
<ix-tab-item>Item 2</ix-tab-item>
<ix-tab-item>Item 3</ix-tab-item>
</ix-tabs>
`);
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/);
});
6 changes: 4 additions & 2 deletions packages/vue/src/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,8 @@ export const IxTabItem = /*@__PURE__*/ defineContainer<JSX.IxTabItem>('ix-tab-it
'rounded',
'counter',
'layout',
'placement'
'placement',
'tabClick'
]);


Expand All @@ -654,7 +655,8 @@ export const IxTabs = /*@__PURE__*/ defineContainer<JSX.IxTabs>('ix-tabs', undef
'rounded',
'selected',
'layout',
'placement'
'placement',
'selectedChange'
]);


Expand Down