diff --git a/index.js b/index.js index 4dfea78..fab21f1 100644 --- a/index.js +++ b/index.js @@ -1,3 +1,4 @@ export { registerDevtools } from './src/devtools.js'; export { LitAtom } from './integrations/lit.js'; -export { atom, selector } from './src/core.js'; \ No newline at end of file +export { AtomLitElement } from './integrations/lit-auto.js'; +export { atom, selector } from './src/core.js'; diff --git a/integrations/lit-auto.js b/integrations/lit-auto.js new file mode 100644 index 0000000..c3d1c22 --- /dev/null +++ b/integrations/lit-auto.js @@ -0,0 +1,67 @@ +import {LitElement} from 'lit-element'; + +const reactor = Symbol(); + +export class Reactor { + constructor(callback) { + this.callback = callback; + this.atoms = new Set(); + } + + dispose() { + for (const key of this.atoms) { + const found = atoms.get(key); + if (found) { + found.removeEventListener(key, this.callback); + } + } + } + + track(fn) { + const originalGet = atoms.get; + + atoms.get = (key) => { + this.atoms.add(key); + return originalGet.call(atoms, key); + }; + + fn(); + + atoms.get = originalGet; + + for (const [key, atom] of atoms.entries()) { + if (this.atoms.has(key)) { + atom.addEventListener(key, this.callback); + } + } + } +} + +export class AtomLitElement extends LitElement { + connectedCallback() { + super.connectedCallback(); + + this[reactor] = new Reactor(() => { + this.requestUpdate(); + }); + } + + disconnectedCallback() { + super.disconnectedCallback(); + + if (this[reactor]) { + this[reactor].dispose(); + this[reactor] = undefined; + } + } + + update(change) { + const instance = this[reactor]; + + if (instance) { + instance.track(super.update.bind(this, change)); + } else { + super.update(change); + } + } +} diff --git a/test/lit/lit.test.js b/test/lit/lit.test.js index e9427c0..7da5478 100644 --- a/test/lit/lit.test.js +++ b/test/lit/lit.test.js @@ -1,7 +1,8 @@ import { expect, fixture } from '@open-wc/testing'; import { LitElement } from 'lit-element'; -import { LitAtom } from '../../src/lit.js'; +import { LitAtom } from '../../integrations/lit.js'; import { atom, selector } from '../../src/core.js'; +import { AtomLitElement } from '../../integrations/lit-auto.js'; const [count, setCount] = atom({ key: 'count', @@ -13,7 +14,36 @@ class BasicAtom extends LitAtom(LitElement) { } customElements.define('basic-atom', BasicAtom); +class AutomaticAtom extends AtomLitElement { + render() { + return html`${count.getState()}`; + } +} +customElements.define('automatic-atom', AutomaticAtom); + describe('lit', () => { + describe('auto', () => { + let el; + + beforeEach(() => { + el = await fixture(''); + }); + + afterEach(() => { + el.remove(); + }); + + it('initializes correctly', () => { + expect(el.shadowRoot.textContent).to.equal('1'); + }); + + it('updates correctly with `setCount`', async () => { + setCount(old => old + 1); + await el.updateComplete; + expect(el.shadowRoot.textContent).to.equal('2'); + }); + }); + describe('atoms', () => { it('initializes correctly', async () => { const el = await fixture('');