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

experiment: add Lit based version of vaadin-tabsheet #7945

Merged
merged 2 commits into from
Oct 31, 2024
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
6 changes: 5 additions & 1 deletion packages/tabsheet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@
"type": "module",
"files": [
"src",
"!src/vaadin-lit-tabsheet-scroller.js",
"!src/vaadin-lit-tabsheet.d.ts",
"!src/vaadin-lit-tabsheet.js",
"theme",
"vaadin-*.d.ts",
"vaadin-*.js",
Expand All @@ -42,7 +45,8 @@
"@vaadin/tabs": "24.6.0-alpha4",
"@vaadin/vaadin-lumo-styles": "24.6.0-alpha4",
"@vaadin/vaadin-material-styles": "24.6.0-alpha4",
"@vaadin/vaadin-themable-mixin": "24.6.0-alpha4"
"@vaadin/vaadin-themable-mixin": "24.6.0-alpha4",
"lit": "^3.0.0"
},
"devDependencies": {
"@vaadin/chai-plugins": "24.6.0-alpha4",
Expand Down
21 changes: 21 additions & 0 deletions packages/tabsheet/src/vaadin-lit-tabsheet-scroller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* @license
* Copyright (c) 2022 - 2024 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
import { Scroller } from '@vaadin/scroller/src/vaadin-lit-scroller.js';

/**
* An element used internally by `<vaadin-tabsheet>`. Not intended to be used separately.
*
* @extends Scroller
* @private
*/
class TabsheetScroller extends Scroller {
static get is() {
return 'vaadin-tabsheet-scroller';
}
}

defineCustomElement(TabsheetScroller);
1 change: 1 addition & 0 deletions packages/tabsheet/src/vaadin-lit-tabsheet.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './vaadin-tabsheet.js';
79 changes: 79 additions & 0 deletions packages/tabsheet/src/vaadin-lit-tabsheet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* @license
* Copyright (c) 2022 - 2024 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import '@vaadin/tabs/src/vaadin-lit-tabs.js';
import './vaadin-lit-tabsheet-scroller.js';
import { css, html, LitElement } from 'lit';
import { defineCustomElement } from '@vaadin/component-base/src/define.js';
import { ElementMixin } from '@vaadin/component-base/src/element-mixin.js';
import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js';
import { ThemableMixin } from '@vaadin/vaadin-themable-mixin/vaadin-themable-mixin.js';
import { TabSheetMixin } from './vaadin-tabsheet-mixin.js';

/**
* LitElement based version of `<vaadin-tabsheet>` web component.
*
* ## Disclaimer
*
* This component is an experiment and not yet a part of Vaadin platform.
* There is no ETA regarding specific Vaadin version where it'll land.
* Feel free to try this code in your apps as per Apache 2.0 license.
*/
class TabSheet extends TabSheetMixin(ThemableMixin(ElementMixin(PolylitMixin(LitElement)))) {
static get is() {
return 'vaadin-tabsheet';
}

static get styles() {
return css`
:host {
display: flex;
flex-direction: column;
}

:host([hidden]) {
display: none !important;
}

[part='tabs-container'] {
position: relative;
display: flex;
align-items: center;
}

::slotted([slot='tabs']) {
flex: 1;
align-self: stretch;
min-width: 8em;
}

[part='content'] {
position: relative;
flex: 1;
box-sizing: border-box;
}
`;
}

/** @protected */
render() {
return html`
<div part="tabs-container">
<slot name="prefix"></slot>
<slot name="tabs"></slot>
<slot name="suffix"></slot>
</div>

<vaadin-tabsheet-scroller part="content">
<div part="loader"></div>
<slot id="panel-slot"></slot>
</vaadin-tabsheet-scroller>
`;
}
}

defineCustomElement(TabSheet);

export { TabSheet };
26 changes: 20 additions & 6 deletions packages/tabsheet/src/vaadin-tabsheet-mixin.js
tomivirkki marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -123,12 +123,7 @@ export const TabSheetMixin = (superClass) =>

/** @override */
static get delegateProps() {
return ['selected'];
}

/** @override */
static get delegateAttrs() {
return ['theme'];
return ['selected', '_theme'];
}

/** @protected */
Expand Down Expand Up @@ -171,6 +166,25 @@ export const TabSheetMixin = (superClass) =>
});
}

/**
* Override method from `DelegateStateMixin` to set delegate `theme`
* using attribute instead of property (needed for the Lit version).
* @protected
* @override
*/
_delegateProperty(name, value) {
if (!this.stateTarget) {
return;
}

if (name === '_theme') {
this._delegateAttribute('theme', value);
return;
}

super._delegateProperty(name, value);
}

/**
* An observer which applies the necessary roles and ARIA attributes
* to associate the tab elements with the panels.
Expand Down
3 changes: 3 additions & 0 deletions packages/tabsheet/test/tabsheet-lit.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import '@vaadin/tabs/src/vaadin-lit-tabs.js';
import '../src/vaadin-lit-tabsheet.js';
import './tabsheet.common.js';
3 changes: 3 additions & 0 deletions packages/tabsheet/test/tabsheet-polymer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import '@vaadin/tabs/src/vaadin-tabs.js';
import '../src/vaadin-tabsheet.js';
import './tabsheet.common.js';
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { expect } from '@vaadin/chai-plugins';
import { fixtureSync, nextFrame } from '@vaadin/testing-helpers';
import sinon from 'sinon';
import '../vaadin-tabsheet.js';

describe('tabsheet', () => {
let tabsheet, tabs;
Expand Down Expand Up @@ -54,9 +53,9 @@ describe('tabsheet', () => {
expect(tabsheet.items.length).to.be.equal(3);
});

it('should update items value when the tabs items change', () => {
it('should update items value when the tabs items change', async () => {
tabs.removeChild(tabsheet.items[2]);
tabs._observer.flush();
await nextFrame();
expect(tabsheet.items.length).to.be.equal(2);
});

Expand All @@ -81,8 +80,9 @@ describe('tabsheet', () => {
expect(tabsheet.items[0].selected).to.be.true;
});

it('should update selected to new index when other tab is selected', () => {
it('should update selected to new index when other tab is selected', async () => {
tabs.items[1].click();
await nextFrame();
expect(tabsheet.items[1].selected).to.be.true;
expect(tabsheet.selected).to.equal(1);
});
Expand All @@ -92,42 +92,48 @@ describe('tabsheet', () => {
await nextFrame();
tabs.selected = 1;
tabs.items[1].click();
await nextFrame();
expect(tabsheet.selected).not.to.equal(1);
});

it('should close currently selected tab when another one is selected', () => {
it('should close currently selected tab when another one is selected', async () => {
tabs.items[1].click();
await nextFrame();
expect(tabsheet.items[1].selected).to.be.true;
expect(tabsheet.items[0].selected).to.be.false;
});

it('should not change selected state if tab has been removed', () => {
it('should not change selected state if tab has been removed', async () => {
const tab = tabsheet.items[1];
tabs.removeChild(tab);
tabs._observer.flush();
tab.selected = true;
await nextFrame();
expect(tabsheet.selected).to.equal(0);
});

it('should dispatch selected-changed event when selected changes', () => {
it('should dispatch selected-changed event when selected changes', async () => {
const spy = sinon.spy();
tabsheet.addEventListener('selected-changed', spy);
tabs.items[1].click();
await nextFrame();
expect(spy.calledOnce).to.be.true;
});
});

describe('syncing properties', () => {
it('should propagate selected value to tabs', () => {
it('should propagate selected value to tabs', async () => {
expect(tabs.selected).to.equal(tabsheet.selected);
tabsheet.selected = 1;
await nextFrame();
expect(tabs.selected).to.equal(tabsheet.selected);
});

it('should not propagate value to a detached tabs', async () => {
tabs.remove();
await nextFrame();
tabsheet.selected = 1;
await nextFrame();
expect(tabs.selected).not.to.equal(tabsheet.selected);
});
});
Expand All @@ -139,14 +145,16 @@ describe('tabsheet', () => {
expect(getPanels()[2].hidden).to.be.true;
});

it('should show another panel on tab change', () => {
it('should show another panel on tab change', async () => {
tabsheet.selected = 1;
await nextFrame();
expect(getPanels()[0].hidden).to.be.true;
expect(getPanels()[1].hidden).to.be.false;
});

it('should not show a panel if no matching panel found', () => {
it('should not show a panel if no matching panel found', async () => {
tabsheet.selected = 3;
await nextFrame();
expect(getPanels()[0].hidden).to.be.true;
expect(getPanels()[1].hidden).to.be.true;
expect(getPanels()[2].hidden).to.be.true;
Expand Down Expand Up @@ -227,8 +235,9 @@ describe('tabsheet', () => {
expect(tabsheet.hasAttribute('loading')).to.be.false;
});

it('should be in loading state after opening a tab with no panel', () => {
it('should be in loading state after opening a tab with no panel', async () => {
newTab.click();
await nextFrame();
expect(tabsheet.hasAttribute('loading')).to.be.true;
expect(tabsheet.getAttribute('loading')).to.equal('');
});
Expand Down Expand Up @@ -308,19 +317,23 @@ describe('tabsheet', () => {
});

describe('theme propagation', () => {
it('should set the theme attribute to the slotted tabs', () => {
it('should set the theme attribute to the slotted tabs', async () => {
tabsheet.setAttribute('theme', 'foo');
await nextFrame();
expect(tabs.getAttribute('theme')).to.equal('foo');
});

it('should remove the theme attribute to the slotted tabs', () => {
it('should remove the theme attribute to the slotted tabs', async () => {
tabsheet.setAttribute('theme', 'foo');
await nextFrame();
tabsheet.removeAttribute('theme');
await nextFrame();
expect(tabs.hasAttribute('theme')).to.be.false;
});

it('should set the theme attribute to newly added tabs', async () => {
tabsheet.setAttribute('theme', 'foo');
await nextFrame();
tabs.remove();

const newTabs = fixtureSync(`<vaadin-tabs slot="tabs"></vaadin-tabs>`);
Expand Down