Skip to content

Commit

Permalink
[lit-labs/preact-signals] add abstract class support to SignalWatcher (
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewJakubowicz committed Jan 11, 2024
1 parent 5c8b142 commit b637103
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changeset/proud-rocks-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@lit-labs/preact-signals': patch
---

The `SignalWatcher` mixin can now accept abstract classes.
11 changes: 7 additions & 4 deletions packages/labs/preact-signals/src/lib/signal-watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
import type {ReactiveElement} from 'lit';
import {effect} from '@preact/signals-core';

// eslint-disable-next-line @typescript-eslint/no-explicit-any
type ReactiveElementConstructor = new (...args: any[]) => ReactiveElement;
type ReactiveElementConstructor = abstract new (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
...args: any[]
) => ReactiveElement;

/**
* Adds the ability for a LitElement or other ReactiveElement class to
Expand All @@ -18,7 +20,7 @@ type ReactiveElementConstructor = new (...args: any[]) => ReactiveElement;
export function SignalWatcher<T extends ReactiveElementConstructor>(
Base: T
): T {
return class SignalWatcher extends Base {
abstract class SignalWatcher extends Base {
private __dispose?: () => void;

override performUpdate() {
Expand Down Expand Up @@ -67,5 +69,6 @@ export function SignalWatcher<T extends ReactiveElementConstructor>(
super.disconnectedCallback();
this.__dispose?.();
}
};
}
return SignalWatcher;
}
42 changes: 42 additions & 0 deletions packages/labs/preact-signals/src/test/signal-watcher_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,46 @@ suite('SignalWatcher', () => {
assert.equal(el.shadowRoot?.querySelector('p')?.textContent, 'count: 3');
assert.equal(readCount, 2);
});

test('type-only test where mixin on an abstract class preserves abstract type', () => {
if (true as boolean) {
// This is a type-only test. Do not run it.
return;
}
abstract class BaseEl extends LitElement {
abstract foo(): void;
}
// @ts-expect-error foo() needs to be implemented.
class TestEl extends SignalWatcher(BaseEl) {}
console.log(TestEl); // usage to satisfy eslint.

const TestElFromAbstractSignalWatcher = SignalWatcher(BaseEl);
// @ts-expect-error cannot instantiate an abstract class.
new TestElFromAbstractSignalWatcher();

// This is fine, passed in class is not abstract.
const TestElFromConcreteClass = SignalWatcher(LitElement);
new TestElFromConcreteClass();
});

test('class returned from signal-watcher should be directly instantiatable if non-abstract', async () => {
const count = signal(0);
class TestEl extends LitElement {
override render() {
return html`<p>count: ${count.value}</p>`;
}
}
const TestElWithSignalWatcher = SignalWatcher(TestEl);
customElements.define(generateElementName(), TestElWithSignalWatcher);
const el = new TestElWithSignalWatcher();
container.append(el);

await el.updateComplete;
assert.equal(el.shadowRoot?.querySelector('p')?.textContent, 'count: 0');

count.value = 1;

await el.updateComplete;
assert.equal(el.shadowRoot?.querySelector('p')?.textContent, 'count: 1');
});
});

0 comments on commit b637103

Please sign in to comment.