Skip to content

Commit

Permalink
[lit-html] Add keyed directive (#2337)
Browse files Browse the repository at this point in the history
  • Loading branch information
justinfagnani committed Dec 7, 2021
1 parent d319cf5 commit fcc2b3d
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 6 deletions.
6 changes: 6 additions & 0 deletions .changeset/brown-eels-help.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'lit': minor
'lit-html': minor
---

Add a `keyed(key, value)` directive that clears a part if the key changes.
16 changes: 10 additions & 6 deletions packages/lit-html/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,26 @@
"development": "./development/directives/join.js",
"default": "./directives/join.js"
},
"./directives/map.js": {
"development": "./development/directives/map.js",
"default": "./directives/map.js"
"./directives/keyed.js": {
"development": "./development/directives/keyed.js",
"default": "./directives/keyed.js"
},
"./directives/live.js": {
"development": "./development/directives/live.js",
"default": "./directives/live.js"
},
"./directives/ref.js": {
"development": "./development/directives/ref.js",
"default": "./directives/ref.js"
"./directives/map.js": {
"development": "./development/directives/map.js",
"default": "./directives/map.js"
},
"./directives/range.js": {
"development": "./development/directives/range.js",
"default": "./directives/range.js"
},
"./directives/ref.js": {
"development": "./development/directives/ref.js",
"default": "./directives/ref.js"
},
"./directives/repeat.js": {
"development": "./development/directives/repeat.js",
"default": "./directives/repeat.js"
Expand Down
1 change: 1 addition & 0 deletions packages/lit-html/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export const defaultConfig = (options = {}) =>
'directives/guard',
'directives/if-defined',
'directives/join',
'directives/keyed',
'directives/live',
'directives/map',
'directives/range',
Expand Down
39 changes: 39 additions & 0 deletions packages/lit-html/src/directives/keyed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {nothing} from '../lit-html.js';
import {
directive,
Directive,
ChildPart,
DirectiveParameters,
} from '../directive.js';
import {setCommittedValue} from '../directive-helpers.js';

class Keyed extends Directive {
key: unknown = nothing;

render(k: unknown, v: unknown) {
this.key = k;
return v;
}

override update(part: ChildPart, [k, v]: DirectiveParameters<this>) {
if (k !== this.key) {
// Clear the part before returning a value. The one-arg form of
// setCommittedValue sets the value to a sentinel which forces a
// commit the next render.
setCommittedValue(part);
this.key = k;
}
return v;
}
}

/**
* Associates a renderable value with a unique key. When the key changes, the
* previous DOM is removed and disposed before rendering the next value, even
* if the value - such as a template - is the same.
*
* This is useful for forcing re-renders of stateful components, or working
* with code that expects new data to generate new HTML elements, such as some
* animation techniques.
*/
export const keyed = directive(Keyed);
43 changes: 43 additions & 0 deletions packages/lit-html/src/test/directives/keyed_test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/

import {keyed} from '../../directives/keyed.js';
import {html, render} from '../../lit-html.js';
import {stripExpressionMarkers} from '../test-utils/strip-markers.js';
import {assert} from '@esm-bundle/chai';

suite('keyed directive', () => {
let container: HTMLDivElement;

setup(() => {
container = document.createElement('div');
});

test('re-renders when the key changes', () => {
const go = (k: any) =>
render(keyed(k, html`<div .foo=${k}></div>`), container);

// Initial render
go(1);
const div = container.firstElementChild;
assert.equal(stripExpressionMarkers(container.innerHTML), '<div></div>');
assert.equal((div as any).foo, 1);

// Rerendering with same key should reuse the DOM
go(1);
const div2 = container.firstElementChild;
assert.equal(stripExpressionMarkers(container.innerHTML), '<div></div>');
assert.equal((div2 as any).foo, 1);
assert.strictEqual(div, div2);

// Rerendering with a different key should not reuse the DOM
go(2);
const div3 = container.firstElementChild;
assert.equal(stripExpressionMarkers(container.innerHTML), '<div></div>');
assert.equal((div3 as any).foo, 2);
assert.notStrictEqual(div, div3);
});
});
3 changes: 3 additions & 0 deletions packages/lit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@
"./directives/join.js": {
"default": "./directives/join.js"
},
"./directives/keyed.js": {
"default": "./directives/keyed.js"
},
"./directives/live.js": {
"default": "./directives/live.js"
},
Expand Down
1 change: 1 addition & 0 deletions packages/lit/rollup.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default litProdConfig({
'directives/guard',
'directives/if-defined',
'directives/join',
'directives/keyed',
'directives/live',
'directives/map',
'directives/range',
Expand Down
7 changes: 7 additions & 0 deletions packages/lit/src/directives/keyed.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/

export * from 'lit-html/directives/keyed.js';

0 comments on commit fcc2b3d

Please sign in to comment.