From dbf014832da46cb617d027276158ffc7098fa833 Mon Sep 17 00:00:00 2001 From: Sergey Vinogradov Date: Wed, 4 May 2022 14:38:54 +0300 Subject: [PATCH] feat: add Lit renderer directive for select (#3775) --- packages/select/lit.d.ts | 1 + packages/select/lit.js | 1 + packages/select/package.json | 3 + .../select/src/lit/renderer-directives.d.ts | 59 +++++++++++++++++ .../select/src/lit/renderer-directives.js | 60 +++++++++++++++++ .../test/lit-renderer-directives.test.js | 66 +++++++++++++++++++ packages/select/test/typings/lit.types.ts | 8 +++ 7 files changed, 198 insertions(+) create mode 100644 packages/select/lit.d.ts create mode 100644 packages/select/lit.js create mode 100644 packages/select/src/lit/renderer-directives.d.ts create mode 100644 packages/select/src/lit/renderer-directives.js create mode 100644 packages/select/test/lit-renderer-directives.test.js create mode 100644 packages/select/test/typings/lit.types.ts diff --git a/packages/select/lit.d.ts b/packages/select/lit.d.ts new file mode 100644 index 00000000000..06ba82bf8a2 --- /dev/null +++ b/packages/select/lit.d.ts @@ -0,0 +1 @@ +export * from './src/lit/renderer-directives.js'; diff --git a/packages/select/lit.js b/packages/select/lit.js new file mode 100644 index 00000000000..06ba82bf8a2 --- /dev/null +++ b/packages/select/lit.js @@ -0,0 +1 @@ +export * from './src/lit/renderer-directives.js'; diff --git a/packages/select/package.json b/packages/select/package.json index e93a0203717..55b87798758 100644 --- a/packages/select/package.json +++ b/packages/select/package.json @@ -22,6 +22,8 @@ "files": [ "src", "theme", + "lit.js", + "lit.d.ts", "vaadin-*.d.ts", "vaadin-*.js" ], @@ -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", diff --git a/packages/select/src/lit/renderer-directives.d.ts b/packages/select/src/lit/renderer-directives.d.ts new file mode 100644 index 00000000000..c8f65ef4d89 --- /dev/null +++ b/packages/select/src/lit/renderer-directives.d.ts @@ -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 { + /** + * 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 `` 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 + * ` html`...`)} + * >` + * ``` + * + * @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; diff --git a/packages/select/src/lit/renderer-directives.js b/packages/select/src/lit/renderer-directives.js new file mode 100644 index 00000000000..dd9211ddfc3 --- /dev/null +++ b/packages/select/src/lit/renderer-directives.js @@ -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 `` 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 + * ` html`...`)} + * >` + * ``` + * + * @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); diff --git a/packages/select/test/lit-renderer-directives.test.js b/packages/select/test/lit-renderer-directives.test.js new file mode 100644 index 00000000000..127750cf4f3 --- /dev/null +++ b/packages/select/test/lit-renderer-directives.test.js @@ -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` html`${content}`, content) : null}>`, + container, + ); + await nextFrame(); + return container.querySelector('vaadin-select'); +} + +describe('lit renderer directives', () => { + let container, select, overlay; + + beforeEach(() => { + container = fixtureSync('
'); + }); + + 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``, 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); + }); + }); + }); +}); diff --git a/packages/select/test/typings/lit.types.ts b/packages/select/test/typings/lit.types.ts new file mode 100644 index 00000000000..426f5bbee74 --- /dev/null +++ b/packages/select/test/typings/lit.types.ts @@ -0,0 +1,8 @@ +import { DirectiveResult } from 'lit/directive.js'; +import { SelectLitRenderer, selectRenderer, SelectRendererDirective } from '../../lit.js'; + +const assertType = (actual: TExpected) => actual; + +assertType<(renderer: SelectLitRenderer, dependencies?: unknown) => DirectiveResult>( + selectRenderer, +);