Skip to content

Commit

Permalink
feat: add Lit renderer directive for select (#3775)
Browse files Browse the repository at this point in the history
  • Loading branch information
vursen authored May 4, 2022
1 parent b6772d7 commit dbf0148
Show file tree
Hide file tree
Showing 7 changed files with 198 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/select/lit.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './src/lit/renderer-directives.js';
1 change: 1 addition & 0 deletions packages/select/lit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './src/lit/renderer-directives.js';
3 changes: 3 additions & 0 deletions packages/select/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
"files": [
"src",
"theme",
"lit.js",
"lit.d.ts",
"vaadin-*.d.ts",
"vaadin-*.js"
],
Expand All @@ -40,6 +42,7 @@
"@vaadin/input-container": "23.1.0-beta1",
"@vaadin/item": "23.1.0-beta1",
"@vaadin/list-box": "23.1.0-beta1",
"@vaadin/lit-renderer": "23.1.0-beta1",
"@vaadin/vaadin-list-mixin": "23.1.0-beta1",
"@vaadin/vaadin-lumo-styles": "23.1.0-beta1",
"@vaadin/vaadin-material-styles": "23.1.0-beta1",
Expand Down
59 changes: 59 additions & 0 deletions packages/select/src/lit/renderer-directives.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* @license
* Copyright (c) 2017 - 2022 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { TemplateResult } from 'lit';
import { DirectiveResult } from 'lit/directive.js';
import { LitRendererDirective } from '@vaadin/lit-renderer';
import { Select } from '../vaadin-select.js';

export type SelectLitRenderer = (select: Select) => TemplateResult;

export class SelectRendererDirective extends LitRendererDirective<Select, SelectLitRenderer> {
/**
* Adds the renderer callback to the select.
*/
addRenderer(): void;

/**
* Runs the renderer callback on the select.
*/
runRenderer(): void;

/**
* Removes the renderer callback from the select.
*/
removeRenderer(): void;
}

/**
* A Lit directive for populating the content of the `<vaadin-select-overlay>` element.
*
* The directive accepts a renderer callback returning a Lit template and assigns it to the select
* via the `renderer` property. The renderer is called once to populate the content when assigned
* and whenever a single dependency or an array of dependencies changes.
* It is not guaranteed that the renderer will be called immediately (synchronously) in both cases.
*
* Dependencies can be a single value or an array of values.
* Values are checked against previous values with strict equality (`===`),
* so the check won't detect nested property changes inside objects or arrays.
* When dependencies are provided as an array, each item is checked against the previous value
* at the same index with strict equality. Nested arrays are also checked only by strict
* equality.
*
* Example of usage:
* ```js
* `<vaadin-select
* ${selectRenderer((select) => html`...`)}
* ></vaadin-select>`
* ```
*
* @param renderer the renderer callback that returns a Lit template.
* @param dependencies a single dependency or an array of dependencies
* which trigger a re-render when changed.
*/
export declare function selectRenderer(
renderer: SelectLitRenderer,
dependencies?: unknown,
): DirectiveResult<typeof SelectRendererDirective>;
60 changes: 60 additions & 0 deletions packages/select/src/lit/renderer-directives.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* @license
* Copyright (c) 2017 - 2022 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import { directive } from 'lit/directive.js';
import { LitRendererDirective } from '@vaadin/lit-renderer';

export class SelectRendererDirective extends LitRendererDirective {
/**
* Adds the renderer callback to the select.
*/
addRenderer() {
this.element.renderer = (root, select) => {
this.renderRenderer(root, select);
};
}

/**
* Runs the renderer callback on the select.
*/
runRenderer() {
this.element.requestContentUpdate();
}

/**
* Removes the renderer callback from the select.
*/
removeRenderer() {
this.element.renderer = null;
}
}

/**
* A Lit directive for populating the content of the `<vaadin-select-overlay>` element.
*
* The directive accepts a renderer callback returning a Lit template and assigns it to the select
* via the `renderer` property. The renderer is called once to populate the content when assigned
* and whenever a single dependency or an array of dependencies changes.
* It is not guaranteed that the renderer will be called immediately (synchronously) in both cases.
*
* Dependencies can be a single value or an array of values.
* Values are checked against previous values with strict equality (`===`),
* so the check won't detect nested property changes inside objects or arrays.
* When dependencies are provided as an array, each item is checked against the previous value
* at the same index with strict equality. Nested arrays are also checked only by strict
* equality.
*
* Example of usage:
* ```js
* `<vaadin-select
* ${selectRenderer((select) => html`...`)}
* ></vaadin-select>`
* ```
*
* @param renderer the renderer callback that returns a Lit template.
* @param dependencies a single dependency or an array of dependencies
* which trigger a re-render when changed.
*/
export const selectRenderer = directive(SelectRendererDirective);
66 changes: 66 additions & 0 deletions packages/select/test/lit-renderer-directives.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { expect } from '@esm-bundle/chai';
import { fixtureSync, nextFrame } from '@vaadin/testing-helpers';
import sinon from 'sinon';
import './not-animated-styles.js';
import '../vaadin-select.js';
import { html, render } from 'lit';
import { selectRenderer } from '../lit.js';

async function renderOpenedSelect(container, { content }) {
render(
html`<vaadin-select opened ${content ? selectRenderer(() => html`${content}`, content) : null}></vaadin-select>`,
container,
);
await nextFrame();
return container.querySelector('vaadin-select');
}

describe('lit renderer directives', () => {
let container, select, overlay;

beforeEach(() => {
container = fixtureSync('<div></div>');
});

describe('selectRenderer', () => {
describe('basic', () => {
beforeEach(async () => {
select = await renderOpenedSelect(container, { content: 'Content' });
overlay = select.shadowRoot.querySelector('vaadin-select-overlay');
});

it('should set `renderer` property when the directive is attached', () => {
expect(select.renderer).to.exist;
});

it('should unset `renderer` property when the directive is detached', async () => {
await renderOpenedSelect(container, {});
expect(select.renderer).not.to.exist;
});

it('should render the content with the renderer', () => {
expect(overlay.textContent).to.equal('Content');
});

it('should re-render the content when a renderer dependency changes', async () => {
await renderOpenedSelect(container, { content: 'New Content' });
expect(overlay.textContent).to.equal('New Content');
});
});

describe('arguments', () => {
let rendererSpy;

beforeEach(async () => {
rendererSpy = sinon.spy();
render(html`<vaadin-select opened ${selectRenderer(rendererSpy)}></vaadin-select>`, container);
await nextFrame();
select = container.querySelector('vaadin-select');
});

it('should pass the select instance to the renderer', () => {
expect(rendererSpy.firstCall.args[0]).to.equal(select);
});
});
});
});
8 changes: 8 additions & 0 deletions packages/select/test/typings/lit.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { DirectiveResult } from 'lit/directive.js';
import { SelectLitRenderer, selectRenderer, SelectRendererDirective } from '../../lit.js';

const assertType = <TExpected>(actual: TExpected) => actual;

assertType<(renderer: SelectLitRenderer, dependencies?: unknown) => DirectiveResult<typeof SelectRendererDirective>>(
selectRenderer,
);

0 comments on commit dbf0148

Please sign in to comment.