Skip to content

Commit

Permalink
experiment: add Lit based version of vaadin-tabsheet (#7945)
Browse files Browse the repository at this point in the history
  • Loading branch information
web-padawan authored Oct 31, 2024
1 parent f93f164 commit 0767f50
Show file tree
Hide file tree
Showing 8 changed files with 158 additions and 20 deletions.
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
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

0 comments on commit 0767f50

Please sign in to comment.