Skip to content

Commit

Permalink
feat: add Lit renderer directive for popover (#7612)
Browse files Browse the repository at this point in the history
  • Loading branch information
web-padawan authored Aug 2, 2024
1 parent 4e1d346 commit 0c4b0b2
Show file tree
Hide file tree
Showing 7 changed files with 201 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/popover/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/popover/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/popover/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
"module": "vaadin-popover.js",
"type": "module",
"files": [
"lit.d.ts",
"lit.js",
"src",
"theme",
"vaadin-*.d.ts",
Expand All @@ -37,6 +39,7 @@
"@open-wc/dedupe-mixin": "^1.3.0",
"@vaadin/a11y-base": "24.5.0-alpha6",
"@vaadin/component-base": "24.5.0-alpha6",
"@vaadin/lit-renderer": "24.5.0-alpha6",
"@vaadin/overlay": "24.5.0-alpha6",
"@vaadin/vaadin-lumo-styles": "24.5.0-alpha6",
"@vaadin/vaadin-material-styles": "24.5.0-alpha6",
Expand Down
58 changes: 58 additions & 0 deletions packages/popover/src/lit/renderer-directives.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* @license
* Copyright (c) 2024 Vaadin Ltd.
* This program is available under Apache License Version 2.0, available at https://vaadin.com/license/
*/
import type { DirectiveResult } from 'lit/directive.js';
import { LitRendererDirective, type LitRendererResult } from '@vaadin/lit-renderer';
import type { Popover } from '../vaadin-popover.js';

export type PopoverLitRenderer = (popover: Popover) => LitRendererResult;

export class PopoverRendererDirective extends LitRendererDirective<Popover, PopoverLitRenderer> {
/**
* Adds the renderer callback to the popover.
*/
addRenderer(): void;

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

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

/**
* A Lit directive for populating the content of the `<vaadin-popover-overlay>` element.
*
* The directive accepts a renderer callback returning a Lit template and assigns it to the popover
* 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-popover
* ${popoverRenderer((popover) => html`...`)}
* ></vaadin-popover>`
* ```
*
* @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 popoverRenderer(
renderer: PopoverLitRenderer,
dependencies?: unknown,
): DirectiveResult<typeof PopoverRendererDirective>;
60 changes: 60 additions & 0 deletions packages/popover/src/lit/renderer-directives.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* @license
* Copyright (c) 2024 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 PopoverRendererDirective extends LitRendererDirective {
/**
* Adds the renderer callback to the popover.
*/
addRenderer() {
this.element.renderer = (root, popover) => {
this.renderRenderer(root, popover);
};
}

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

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

/**
* A Lit directive for populating the content of the `<vaadin-popover-overlay>` element.
*
* The directive accepts a renderer callback returning a Lit template and assigns it to the popover
* 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-popover
* ${popoverRenderer((popover) => html`...`)}
* ></vaadin-popover>`
* ```
*
* @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 popoverRenderer = directive(PopoverRendererDirective);
69 changes: 69 additions & 0 deletions packages/popover/test/lit-renderer-directives.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { expect } from '@esm-bundle/chai';
import { fixtureSync, nextFrame, nextUpdate } from '@vaadin/testing-helpers';
import sinon from 'sinon';
import './not-animated-styles.js';
import '../vaadin-popover.js';
import { html, nothing, render } from 'lit';
import { popoverRenderer } from '../lit.js';

async function renderOpenedPopover(container, { content }) {
render(
html`
<vaadin-popover opened ${content ? popoverRenderer(() => html`${content}`, content) : nothing}></vaadin-popover>
`,
container,
);
const popover = container.querySelector('vaadin-popover');
await nextUpdate(popover);
return popover;
}

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

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

describe('popoverRenderer', () => {
describe('basic', () => {
beforeEach(async () => {
popover = await renderOpenedPopover(container, { content: 'Content' });
overlay = popover._overlayElement;
});

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

it('should unset `renderer` property when the directive is detached', async () => {
await renderOpenedPopover(container, {});
expect(popover.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 renderOpenedPopover(container, { content: 'New Content' });
expect(overlay.textContent).to.equal('New Content');
});
});

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

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

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

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

assertType<(renderer: PopoverLitRenderer, dependencies?: unknown) => DirectiveResult<typeof PopoverRendererDirective>>(
popoverRenderer,
);

0 comments on commit 0c4b0b2

Please sign in to comment.