diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f0cd8b..b7764c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.16.0 + +### Feature +* Adds `lit` support. + +Added new ReactiveController named `StoreController` as part of lit v2.0. + ## 0.15.0 ### Feature diff --git a/e2e/lit/async-action.test.ts b/e2e/lit/async-action.test.ts new file mode 100644 index 0000000..75d771c --- /dev/null +++ b/e2e/lit/async-action.test.ts @@ -0,0 +1,58 @@ +import { join } from 'path' +import { suite } from 'uvu' +import assert from 'uvu/assert' + +import * as ENV from '../setup/playwright' +import { BrowserContext } from '../setup/playwright' + +const entry = join(__dirname, './async-action.ts') +const test = suite('Async action', { entry } as any) + +test.before(ENV.setup) +test.before.each(ENV.homepage) +test.after(ENV.reset) + +test('renders nothing inside

', async({ page }) => { + const h1 = (await page.$('h1'))! + const span = (await page.$('span'))! + + assert.equal(await h1.innerText(), '') + assert.equal(await span.innerText(), '1') +}) + +test('renders content inside

after action finishes', async({ page }) => { + const h1 = (await page.$('h1'))! + const span = (await page.$('span'))! + + assert.equal(await h1.innerText(), '') + assert.equal(await span.innerText(), '1') + + await page.click('#getMessage') + + assert.equal(await h1.innerText(), '') + assert.equal(await span.innerText(), '1') + + await page.waitForTimeout(100) + + assert.equal(await h1.innerText(), 'Hello world') + assert.equal(await span.innerText(), '2') +}) + +test('manages loading state correctly', async({ page }) => { + const span = (await page.$('span'))! + + assert.equal((await page.$('#loading')) === null, true) + assert.equal(await span.innerText(), '1') + + await page.click('#getMessageWithLoading') + + assert.equal((await page.$('#loading')) === null, false) + assert.equal(await span.innerText(), '2') + + await page.waitForTimeout(100) + + assert.equal((await page.$('#loading')) === null, true) + assert.equal(await span.innerText(), '4') +}) + +test.run() diff --git a/e2e/lit/async-action.ts b/e2e/lit/async-action.ts new file mode 100644 index 0000000..a668fc0 --- /dev/null +++ b/e2e/lit/async-action.ts @@ -0,0 +1,30 @@ +import { StoreController } from 'exome/lit' +import { LitElement, html } from 'lit' +import { customElement } from 'lit/decorators.js' + +import { asyncStore } from '../stores/async-store' + +@customElement('lit-app') +export class LitApp extends LitElement { + // Create the controller and store it + private readonly asyncStore = new StoreController(this, asyncStore) + + private renders = 0 + + // Use the controller in render() + render() { + const { message, loading, getMessage, getMessageWithLoading } = this.asyncStore.store + + this.renders += 1 + + return html` + ${loading && (html``)} +

${message}

+ + + ${this.renders} + ` + } +} + +document.body.innerHTML = '' diff --git a/e2e/lit/counter.test.ts b/e2e/lit/counter.test.ts new file mode 100644 index 0000000..b4b4dd9 --- /dev/null +++ b/e2e/lit/counter.test.ts @@ -0,0 +1,42 @@ +import { join } from 'path' +import { suite } from 'uvu' +import assert from 'uvu/assert' + +import * as ENV from '../setup/playwright' +import { BrowserContext } from '../setup/playwright' + +const entry = join(__dirname, './counter.ts') +const test = suite('Counter', { entry } as any) + +test.before(ENV.setup) +test.before.each(ENV.homepage) +test.after(ENV.reset) + +test('renders

with "0" inside', async({ page }) => { + const counterValue = await (await page.$('h1'))!.innerText() + + assert.equal(counterValue, '0') +}) + +test('increments count on click', async({ page }) => { + await page.click('h1') + + const counterValue = await (await page.$('h1'))!.innerText() + + assert.equal(counterValue, '1') +}) + +test('increments count on click multiple times', async({ page }) => { + await page.click('h1') + await page.click('h1') + await page.click('h1') + await page.click('h1') + + const counterValue = await (await page.$('h1'))!.innerText() + const renderCount = await (await page.$('span'))!.innerText() + + assert.equal(counterValue, '4') + assert.equal(renderCount, '5') +}) + +test.run() diff --git a/e2e/lit/counter.ts b/e2e/lit/counter.ts new file mode 100644 index 0000000..97cc302 --- /dev/null +++ b/e2e/lit/counter.ts @@ -0,0 +1,27 @@ +import { StoreController } from 'exome/lit' +import { LitElement, html } from 'lit' +import { customElement } from 'lit/decorators.js' + +import { counter } from '../stores/counter' + +@customElement('lit-app') +export class LitApp extends LitElement { + // Create the controller and store it + private readonly counter = new StoreController(this, counter) + + private renders = 0 + + // Use the controller in render() + render() { + const { count, increment } = this.counter.store + + this.renders += 1 + + return html` +

${count}

+ ${this.renders} + ` + } +} + +document.body.innerHTML = '' diff --git a/e2e/lit/recursive.test.ts b/e2e/lit/recursive.test.ts new file mode 100644 index 0000000..030b08b --- /dev/null +++ b/e2e/lit/recursive.test.ts @@ -0,0 +1,65 @@ +import { join } from 'path' +import { suite } from 'uvu' +import assert from 'uvu/assert' + +import * as ENV from '../setup/playwright' +import { BrowserContext } from '../setup/playwright' + +const entry = join(__dirname, './recursive.ts') +const test = suite('Recursive', { entry } as any) + +test.before(ENV.setup) +test.before.each(ENV.homepage) +test.after(ENV.reset) + +test('renders correct amount of elements', async({ page }) => { + const inputs = await page.$$('input') + + assert.equal(inputs.length, 9) +}) + +test('has correct values in input fields', async({ page }) => { + const inputs = await page.$$('input') + + assert.equal(await inputs[0].getAttribute('value'), 'root') + assert.equal(await inputs[1].getAttribute('value'), 'one') + assert.equal(await inputs[2].getAttribute('value'), 'ref') + assert.equal(await inputs[3].getAttribute('value'), 'first') + assert.equal(await inputs[4].getAttribute('value'), 'second') + assert.equal(await inputs[5].getAttribute('value'), 'two') + assert.equal(await inputs[6].getAttribute('value'), 'ref') + assert.equal(await inputs[7].getAttribute('value'), 'first') + assert.equal(await inputs[8].getAttribute('value'), 'second') +}) + +test('updates root value correctly', async({ page }) => { + const inputs = await page.$$('input') + + assert.equal(await inputs[0].getAttribute('value'), 'root') + + await inputs[0].fill('Foo') + + assert.equal(await inputs[0].getAttribute('value'), 'Foo') + assert.equal(await inputs[1].getAttribute('value'), 'one') + assert.equal(await inputs[2].getAttribute('value'), 'ref') + assert.equal(await inputs[3].getAttribute('value'), 'first') + assert.equal(await inputs[4].getAttribute('value'), 'second') + assert.equal(await inputs[5].getAttribute('value'), 'two') + assert.equal(await inputs[6].getAttribute('value'), 'ref') + assert.equal(await inputs[7].getAttribute('value'), 'first') + assert.equal(await inputs[8].getAttribute('value'), 'second') +}) + +test('updates reference value correctly', async({ page }) => { + const inputs = await page.$$('input') + + assert.equal(await inputs[2].getAttribute('value'), 'ref') + assert.equal(await inputs[6].getAttribute('value'), 'ref') + + await inputs[2].fill('Bar') + + assert.equal(await inputs[2].getAttribute('value'), 'Bar') + assert.equal(await inputs[6].getAttribute('value'), 'Bar') +}) + +test.run() diff --git a/e2e/lit/recursive.ts b/e2e/lit/recursive.ts new file mode 100644 index 0000000..4e3713a --- /dev/null +++ b/e2e/lit/recursive.ts @@ -0,0 +1,65 @@ +import { StoreController } from 'exome/lit' +import { LitElement, html } from 'lit' +import { customElement, property } from 'lit/decorators.js' + +import { RecursiveStore, recursiveStore } from '../stores/recursive' + +@customElement('lit-item') +export class LitItem extends LitElement { + @property() + public item!: RecursiveStore + + // Create the controller and store it + private recursiveStore!: StoreController + + connectedCallback() { + this.recursiveStore = new StoreController(this, this.item) + super.connectedCallback() + } + + // Use the controller in render() + render() { + const { name, items, rename } = this.recursiveStore.store + + return html` +
  • + { + rename(e.target.value) + }} + /> + + ${items && html` +
      + ${items.map((subItem) => html` + + `)} +
    + `} +
  • + ` + } + + // Allow external css + createRenderRoot() { + return this + } +} + +@customElement('lit-app') +export class LitApp extends LitElement { + render() { + return html` +
    + ` + } + + // Allow external css + createRenderRoot() { + return this + } +} + +document.body.innerHTML = '' diff --git a/e2e/lit/tsconfig.json b/e2e/lit/tsconfig.json new file mode 100644 index 0000000..8d9bb1f --- /dev/null +++ b/e2e/lit/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "lib": [ + "DOM", + "es2015" + ], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "experimentalDecorators": true, + "paths": { + "exome": [ + "../../src/index.ts" + ], + "exome/lit": [ + "../../src/lit.ts" + ] + }, + "allowJs": true + } +} diff --git a/package-lock.json b/package-lock.json index 30367b8..7e96412 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "exome", - "version": "0.15.0", + "version": "0.16.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "exome", - "version": "0.15.0", + "version": "0.16.0", "license": "MIT", "devDependencies": { "@types/node": "^14.14.41", @@ -23,19 +23,27 @@ "eslint-plugin-import": "^2.22.1", "eslint-plugin-node": "^11.1.0", "eslint-plugin-promise": "^4.3.1", + "lit": ">= 2.0.0-rc.3", "playwright": "^1.10.0", + "preact": ">= 10.1.0", "proxyquire": "^2.1.3", + "react": ">= 16.8.0", "react-dom": "^17.0.2", "sinon": "^10.0.0", "typescript": "^4.2.3", - "uvu": "^0.5.1" + "uvu": "^0.5.1", + "vue": ">= 3.0.0" }, "peerDependencies": { + "lit": ">= 2.0.0-rc.3", "preact": ">= 10.1.0", "react": ">= 16.8.0", "vue": ">= 3.0.0" }, "peerDependenciesMeta": { + "lit": { + "optional": true + }, "preact": { "optional": true }, @@ -57,10 +65,13 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", - "dev": true + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", + "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } }, "node_modules/@babel/highlight": { "version": "7.14.0", @@ -144,6 +155,31 @@ "node": ">=4" } }, + "node_modules/@babel/parser": { + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.3.tgz", + "integrity": "sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", + "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.14.9", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", @@ -179,6 +215,12 @@ "node": ">=8" } }, + "node_modules/@lit/reactive-element": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.0.0-rc.3.tgz", + "integrity": "sha512-Rs2px1keOQUNJUo5B+WExl5v244ZNCiN/iMVNO9evFdJjAdWCIupR/p14zRPkNHsciRBELLTcOZ379cI9O6PDg==", + "dev": true + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -320,6 +362,12 @@ "@sinonjs/fake-timers": "^7.1.0" } }, + "node_modules/@types/trusted-types": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-1.0.6.tgz", + "integrity": "sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==", + "dev": true + }, "node_modules/@types/yauzl": { "version": "2.9.1", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", @@ -487,6 +535,80 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@vue/compiler-core": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.6.tgz", + "integrity": "sha512-vbwnz7+OhtLO5p5i630fTuQCL+MlUpEMTKHuX+RfetQ+3pFCkItt2JUH+9yMaBG2Hkz6av+T9mwN/acvtIwpbw==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.15.0", + "@babel/types": "^7.15.0", + "@vue/shared": "3.2.6", + "estree-walker": "^2.0.2", + "source-map": "^0.6.1" + } + }, + "node_modules/@vue/compiler-core/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.6.tgz", + "integrity": "sha512-+a/3oBAzFIXhHt8L5IHJOTP4a5egzvpXYyi13jR7CUYOR1S+Zzv7vBWKYBnKyJLwnrxTZnTQVjeHCgJq743XKg==", + "dev": true, + "dependencies": { + "@vue/compiler-core": "3.2.6", + "@vue/shared": "3.2.6" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.6.tgz", + "integrity": "sha512-8vIDD2wpCnYisNNZjmcIj+Rixn0uhZNY3G1vzlgdVdLygeRSuFjkmnZk6WwvGzUWpKfnG0e/NUySM3mVi59hAA==", + "dev": true, + "dependencies": { + "@vue/shared": "3.2.6" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.6.tgz", + "integrity": "sha512-3mqtgpj/YSGFxtvTufSERRApo92B16JNNxz9p+5eG6PPuqTmuRJz214MqhKBEgLEAIQ6R6YCbd83ZDtjQnyw2g==", + "dev": true, + "dependencies": { + "@vue/reactivity": "3.2.6", + "@vue/shared": "3.2.6" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.6.tgz", + "integrity": "sha512-fq33urnP0BNCGm2O3KCzkJlKIHI80C94HJ4qDZbjsTtxyOn5IHqwKSqXVN3RQvO6epcQH+sWS+JNwcNDPzoasg==", + "dev": true, + "dependencies": { + "@vue/runtime-core": "3.2.6", + "@vue/shared": "3.2.6", + "csstype": "^2.6.8" + } + }, + "node_modules/@vue/runtime-dom/node_modules/csstype": { + "version": "2.6.17", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.17.tgz", + "integrity": "sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A==", + "dev": true + }, + "node_modules/@vue/shared": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.6.tgz", + "integrity": "sha512-uwX0Qs2e6kdF+WmxwuxJxOnKs/wEkMArtYpHSm7W+VY/23Tl8syMRyjnzEeXrNCAP0/8HZxEGkHJsjPEDNRuHw==", + "dev": true + }, "node_modules/acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -1533,6 +1655,12 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "node_modules/esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -2194,7 +2322,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "devOptional": true + "dev": true }, "node_modules/js-yaml": { "version": "3.14.1", @@ -2273,6 +2401,36 @@ "node": ">= 0.8.0" } }, + "node_modules/lit": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/lit/-/lit-2.0.0-rc.3.tgz", + "integrity": "sha512-UZDLWuspl7saA+WvS0e+TE3NdGGE05hOIwUPTWiibs34c5QupcEzpjB/aElt79V9bELQVNbUUwa0Ow7D1Wuszw==", + "dev": true, + "dependencies": { + "@lit/reactive-element": "^1.0.0-rc.2", + "lit-element": "^3.0.0-rc.2", + "lit-html": "^2.0.0-rc.4" + } + }, + "node_modules/lit-element": { + "version": "3.0.0-rc.3", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.0.0-rc.3.tgz", + "integrity": "sha512-NDe7yjW18gfYQb1GIEQr1T8sB1GUAb1HB62pdAEw+SK6lUW7OFPKQqCOlRhZ6qJXsw9KxMnyYIprLZT4FZdYdQ==", + "dev": true, + "dependencies": { + "@lit/reactive-element": "^1.0.0-rc.2", + "lit-html": "^2.0.0-rc.4" + } + }, + "node_modules/lit-html": { + "version": "2.0.0-rc.4", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.0.0-rc.4.tgz", + "integrity": "sha512-WSLGu3vxq7y8q/oOd9I3zxyBELNLLiDk6gAYoKK4PGctI5fbh6lhnO/jVBdy0PV/vTc+cLJCA/occzx3YoNPeg==", + "dev": true, + "dependencies": { + "@types/trusted-types": "^1.0.1" + } + }, "node_modules/load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -2337,7 +2495,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "devOptional": true, + "dev": true, "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, @@ -2513,7 +2671,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "devOptional": true, + "dev": true, "engines": { "node": ">=0.10.0" } @@ -2917,6 +3075,16 @@ "node": ">=10.13.0" } }, + "node_modules/preact": { + "version": "10.5.14", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.5.14.tgz", + "integrity": "sha512-KojoltCrshZ099ksUZ2OQKfbH66uquFoxHSbnwKbTJHeQNvx42EmC7wQVWNuDt6vC5s3nudRHFtKbpY4ijKlaQ==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -3006,8 +3174,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "devOptional": true, - "peer": true, + "dev": true, "dependencies": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -3571,6 +3738,15 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3753,6 +3929,17 @@ "spdx-expression-parse": "^3.0.0" } }, + "node_modules/vue": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.6.tgz", + "integrity": "sha512-Zlb3LMemQS3Xxa6xPsecu45bNjr1hxO8Bh5FUmE0Dr6Ot0znZBKiM47rK6O7FTcakxOnvVN+NTXWJF6u8ajpCQ==", + "dev": true, + "dependencies": { + "@vue/compiler-dom": "3.2.6", + "@vue/runtime-dom": "3.2.6", + "@vue/shared": "3.2.6" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3922,9 +4109,9 @@ } }, "@babel/helper-validator-identifier": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", - "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "version": "7.14.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.9.tgz", + "integrity": "sha512-pQYxPY0UP6IHISRitNe8bsijHex4TWZXi2HwKVsjPiltzlhse2znVcm9Ace510VT1kxIHjGJCZZQBX2gJDbo0g==", "dev": true }, "@babel/highlight": { @@ -3996,6 +4183,22 @@ } } }, + "@babel/parser": { + "version": "7.15.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.15.3.tgz", + "integrity": "sha512-O0L6v/HvqbdJawj0iBEfVQMc3/6WP+AeOsovsIgBFyJaG+W2w7eqvZB7puddATmWuARlm1SX7DwxJ/JJUnDpEA==", + "dev": true + }, + "@babel/types": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.15.0.tgz", + "integrity": "sha512-OBvfqnllOIdX4ojTHpwZbpvz4j3EWyjkZEdmjH0/cgsd6QOdSgU8rLSk6ard/pcW7rlmjdVSX/AWOaORR1uNOQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.9", + "to-fast-properties": "^2.0.0" + } + }, "@bcoe/v8-coverage": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", @@ -4025,6 +4228,12 @@ "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true }, + "@lit/reactive-element": { + "version": "1.0.0-rc.3", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-1.0.0-rc.3.tgz", + "integrity": "sha512-Rs2px1keOQUNJUo5B+WExl5v244ZNCiN/iMVNO9evFdJjAdWCIupR/p14zRPkNHsciRBELLTcOZ379cI9O6PDg==", + "dev": true + }, "@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -4157,6 +4366,12 @@ "@sinonjs/fake-timers": "^7.1.0" } }, + "@types/trusted-types": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-1.0.6.tgz", + "integrity": "sha512-230RC8sFeHoT6sSUlRO6a8cAnclO06eeiq1QDfiv2FGCLWFvvERWgwIQD4FWqD9A69BN7Lzee4OXwoMVnnsWDw==", + "dev": true + }, "@types/yauzl": { "version": "2.9.1", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz", @@ -4250,6 +4465,81 @@ "eslint-visitor-keys": "^2.0.0" } }, + "@vue/compiler-core": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.2.6.tgz", + "integrity": "sha512-vbwnz7+OhtLO5p5i630fTuQCL+MlUpEMTKHuX+RfetQ+3pFCkItt2JUH+9yMaBG2Hkz6av+T9mwN/acvtIwpbw==", + "dev": true, + "requires": { + "@babel/parser": "^7.15.0", + "@babel/types": "^7.15.0", + "@vue/shared": "3.2.6", + "estree-walker": "^2.0.2", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "@vue/compiler-dom": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.2.6.tgz", + "integrity": "sha512-+a/3oBAzFIXhHt8L5IHJOTP4a5egzvpXYyi13jR7CUYOR1S+Zzv7vBWKYBnKyJLwnrxTZnTQVjeHCgJq743XKg==", + "dev": true, + "requires": { + "@vue/compiler-core": "3.2.6", + "@vue/shared": "3.2.6" + } + }, + "@vue/reactivity": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.2.6.tgz", + "integrity": "sha512-8vIDD2wpCnYisNNZjmcIj+Rixn0uhZNY3G1vzlgdVdLygeRSuFjkmnZk6WwvGzUWpKfnG0e/NUySM3mVi59hAA==", + "dev": true, + "requires": { + "@vue/shared": "3.2.6" + } + }, + "@vue/runtime-core": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.2.6.tgz", + "integrity": "sha512-3mqtgpj/YSGFxtvTufSERRApo92B16JNNxz9p+5eG6PPuqTmuRJz214MqhKBEgLEAIQ6R6YCbd83ZDtjQnyw2g==", + "dev": true, + "requires": { + "@vue/reactivity": "3.2.6", + "@vue/shared": "3.2.6" + } + }, + "@vue/runtime-dom": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.2.6.tgz", + "integrity": "sha512-fq33urnP0BNCGm2O3KCzkJlKIHI80C94HJ4qDZbjsTtxyOn5IHqwKSqXVN3RQvO6epcQH+sWS+JNwcNDPzoasg==", + "dev": true, + "requires": { + "@vue/runtime-core": "3.2.6", + "@vue/shared": "3.2.6", + "csstype": "^2.6.8" + }, + "dependencies": { + "csstype": { + "version": "2.6.17", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-2.6.17.tgz", + "integrity": "sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A==", + "dev": true + } + } + }, + "@vue/shared": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.6.tgz", + "integrity": "sha512-uwX0Qs2e6kdF+WmxwuxJxOnKs/wEkMArtYpHSm7W+VY/23Tl8syMRyjnzEeXrNCAP0/8HZxEGkHJsjPEDNRuHw==", + "dev": true + }, "acorn": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", @@ -5030,6 +5320,12 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true + }, "esutils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", @@ -5514,7 +5810,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "devOptional": true + "dev": true }, "js-yaml": { "version": "3.14.1", @@ -5581,6 +5877,36 @@ "type-check": "~0.4.0" } }, + "lit": { + "version": "2.0.0-rc.3", + "resolved": "https://registry.npmjs.org/lit/-/lit-2.0.0-rc.3.tgz", + "integrity": "sha512-UZDLWuspl7saA+WvS0e+TE3NdGGE05hOIwUPTWiibs34c5QupcEzpjB/aElt79V9bELQVNbUUwa0Ow7D1Wuszw==", + "dev": true, + "requires": { + "@lit/reactive-element": "^1.0.0-rc.2", + "lit-element": "^3.0.0-rc.2", + "lit-html": "^2.0.0-rc.4" + } + }, + "lit-element": { + "version": "3.0.0-rc.3", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-3.0.0-rc.3.tgz", + "integrity": "sha512-NDe7yjW18gfYQb1GIEQr1T8sB1GUAb1HB62pdAEw+SK6lUW7OFPKQqCOlRhZ6qJXsw9KxMnyYIprLZT4FZdYdQ==", + "dev": true, + "requires": { + "@lit/reactive-element": "^1.0.0-rc.2", + "lit-html": "^2.0.0-rc.4" + } + }, + "lit-html": { + "version": "2.0.0-rc.4", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-2.0.0-rc.4.tgz", + "integrity": "sha512-WSLGu3vxq7y8q/oOd9I3zxyBELNLLiDk6gAYoKK4PGctI5fbh6lhnO/jVBdy0PV/vTc+cLJCA/occzx3YoNPeg==", + "dev": true, + "requires": { + "@types/trusted-types": "^1.0.1" + } + }, "load-json-file": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", @@ -5636,7 +5962,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "devOptional": true, + "dev": true, "requires": { "js-tokens": "^3.0.0 || ^4.0.0" } @@ -5782,7 +6108,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "devOptional": true + "dev": true }, "object-inspect": { "version": "1.10.3", @@ -6078,6 +6404,12 @@ "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", "dev": true }, + "preact": { + "version": "10.5.14", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.5.14.tgz", + "integrity": "sha512-KojoltCrshZ099ksUZ2OQKfbH66uquFoxHSbnwKbTJHeQNvx42EmC7wQVWNuDt6vC5s3nudRHFtKbpY4ijKlaQ==", + "dev": true + }, "prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -6144,8 +6476,7 @@ "version": "17.0.2", "resolved": "https://registry.npmjs.org/react/-/react-17.0.2.tgz", "integrity": "sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==", - "devOptional": true, - "peer": true, + "dev": true, "requires": { "loose-envify": "^1.1.0", "object-assign": "^4.1.1" @@ -6573,6 +6904,12 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", "dev": true }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6711,6 +7048,17 @@ "spdx-expression-parse": "^3.0.0" } }, + "vue": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.2.6.tgz", + "integrity": "sha512-Zlb3LMemQS3Xxa6xPsecu45bNjr1hxO8Bh5FUmE0Dr6Ot0znZBKiM47rK6O7FTcakxOnvVN+NTXWJF6u8ajpCQ==", + "dev": true, + "requires": { + "@vue/compiler-dom": "3.2.6", + "@vue/runtime-dom": "3.2.6", + "@vue/shared": "3.2.6" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index 65083a6..37dbef4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "exome", - "version": "0.15.0", + "version": "0.16.0", "description": "Proxy based store manager for deeply nested states", "main": "exome.js", "module": "exome.esm.js", @@ -29,7 +29,8 @@ "nested", "react", "preact", - "vue" + "vue", + "lit" ], "author": "Marcis ", "license": "MIT", @@ -57,14 +58,22 @@ "react-dom": "^17.0.2", "sinon": "^10.0.0", "typescript": "^4.2.3", - "uvu": "^0.5.1" + "uvu": "^0.5.1", + "lit": ">= 2.0.0-rc.3", + "preact": ">= 10.1.0", + "react": ">= 16.8.0", + "vue": ">= 3.0.0" }, "peerDependencies": { + "lit": ">= 2.0.0-rc.3", "preact": ">= 10.1.0", "react": ">= 16.8.0", "vue": ">= 3.0.0" }, "peerDependenciesMeta": { + "lit": { + "optional": true + }, "react": { "optional": true }, @@ -99,6 +108,10 @@ "./vue": { "require": "./vue.js", "import": "./vue.esm.js" + }, + "./lit": { + "require": "./lit.js", + "import": "./lit.esm.js" } } } diff --git a/scripts/build.ts b/scripts/build.ts index 9771a11..4b1591d 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -25,6 +25,7 @@ import { build } from 'esbuild' 'src/react.ts', 'src/preact.ts', 'src/vue.ts', + 'src/lit.ts', 'src/devtools.ts' ], outdir: 'dist', @@ -40,6 +41,7 @@ import { build } from 'esbuild' 'react', 'preact', 'vue', + 'lit', 'exome' ], logLevel: 'info' diff --git a/src/lit.ts b/src/lit.ts new file mode 100644 index 0000000..077d02d --- /dev/null +++ b/src/lit.ts @@ -0,0 +1,64 @@ +import { addMiddleware, Exome, getExomeId, Middleware, updateMap } from 'exome' +import { ReactiveController, ReactiveControllerHost } from 'lit' + +const litMiddleware: Middleware = (instance) => { + return () => { + const id = getExomeId(instance) + const renderers = updateMap[id] === undefined ? [] : updateMap[id] + + updateMap[id] = [] + + renderers.forEach((renderer) => renderer()) + } +} + +addMiddleware(litMiddleware) + +export class StoreController implements ReactiveController { + host: ReactiveControllerHost + + private readonly _id: string + private _queue!: any[] + private readonly _render: () => void + + constructor(host: ReactiveControllerHost, public store: T) { + (this.host = host).addController(this) + this._id = getExomeId(store) + + if (!this._id) { + throw new Error( + '"StoreController" encountered value that is not an instance of "Exome"' + ) + } + + if (updateMap[this._id] === undefined) { + updateMap[this._id] = [] + } + + this._render = () => { + this.host.requestUpdate() + } + } + + hostUpdated() { + this._queue = updateMap[this._id]! + this._queue.push(this._render) + } + + hostConnected() { + this._queue = updateMap[this._id]! + this._queue.push(this._render) + } + + hostDisconnected() { + if (this._queue === updateMap[this._id]!) { + const index = this._queue.indexOf(this._render) + + if (index === -1) { + return + } + + this._queue.splice(index, 1) + } + } +}