Skip to content

Commit 5aec54e

Browse files
authored
refactor: ES6 classes (#130)
* test: Correct tests & test script * refactor: Switch to es6 classes * fix: Support pre-ES2022
1 parent c2ab062 commit 5aec54e

File tree

4 files changed

+49
-60
lines changed

4 files changed

+49
-60
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"scripts": {
1515
"prepare": "npx simple-git-hooks",
1616
"build": "mkdir -p dist && terser src/index.js -o dist/preact-custom-element.js --config-file terser-config.json",
17-
"test": "npm run test:types & npm run test:browser",
17+
"test": "npm run test:types && npm run test:browser",
1818
"test:browser": "wtr test/browser/*.test.{js,jsx}",
1919
"test:types": "tsc -p test/types/",
2020
"lint": "eslint src/*.js",

src/index.d.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,6 @@ export default function register<P = {}, S = {}>(
5252
tagName?: string,
5353
propNames?: (keyof P)[],
5454
options?: Options
55-
): HTMLElement;
55+
): typeof HTMLElement & {
56+
new (): HTMLElement;
57+
};

src/index.js

Lines changed: 44 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -8,44 +8,58 @@ import { h, cloneElement, render, hydrate } from 'preact';
88
* @type {import('./index.d.ts').default}
99
*/
1010
export default function register(Component, tagName, propNames, options) {
11-
function PreactElement() {
12-
const inst = /** @type {PreactCustomElement} */ (
13-
Reflect.construct(HTMLElement, [], PreactElement)
14-
);
15-
inst._vdomComponent = Component;
16-
17-
if (options && options.shadow) {
18-
inst._root = inst.attachShadow({
19-
mode: options.mode || 'open',
20-
serializable: options.serializable ?? false,
21-
});
22-
23-
if (options.adoptedStyleSheets) {
24-
inst._root.adoptedStyleSheets = options.adoptedStyleSheets;
11+
class PreactElement extends HTMLElement {
12+
constructor() {
13+
super();
14+
15+
this._vdomComponent = Component;
16+
if (options && options.shadow) {
17+
this._root = this.attachShadow({
18+
mode: options.mode || 'open',
19+
serializable: options.serializable ?? false,
20+
});
21+
22+
if (options.adoptedStyleSheets) {
23+
this._root.adoptedStyleSheets = options.adoptedStyleSheets;
24+
}
25+
} else {
26+
this._root = this;
2527
}
26-
} else {
27-
inst._root = inst;
2828
}
2929

30-
return inst;
30+
connectedCallback() {
31+
connectedCallback.call(this, options);
32+
}
33+
34+
/**
35+
* Changed whenever an attribute of the HTML element changed
36+
*
37+
* @param {string} name The attribute name
38+
* @param {unknown} oldValue The old value or undefined
39+
* @param {unknown} newValue The new value
40+
*/
41+
attributeChangedCallback(name, oldValue, newValue) {
42+
if (!this._vdom) return;
43+
// Attributes use `null` as an empty value whereas `undefined` is more
44+
// common in pure JS components, especially with default parameters.
45+
// When calling `node.removeAttribute()` we'll receive `null` as the new
46+
// value. See issue #50.
47+
newValue = newValue == null ? undefined : newValue;
48+
const props = {};
49+
props[name] = newValue;
50+
this._vdom = cloneElement(this._vdom, props);
51+
render(this._vdom, this._root);
52+
}
53+
54+
disconnectedCallback() {
55+
render((this._vdom = null), this._root);
56+
}
3157
}
32-
PreactElement.prototype = Object.create(HTMLElement.prototype);
33-
PreactElement.prototype.constructor = PreactElement;
34-
PreactElement.prototype.connectedCallback = function () {
35-
connectedCallback.call(this, options);
36-
};
37-
PreactElement.prototype.attributeChangedCallback = attributeChangedCallback;
38-
PreactElement.prototype.disconnectedCallback = disconnectedCallback;
3958

40-
/**
41-
* @type {string[]}
42-
*/
4359
propNames = propNames || Component.observedAttributes || [];
4460
PreactElement.observedAttributes = propNames;
4561

46-
if (Component.formAssociated) {
47-
PreactElement.formAssociated = true;
48-
}
62+
PreactElement.formAssociated = Component.formAssociated || false;
4963

5064
// Keep DOM properties and Preact props in sync
5165
propNames.forEach((name) => {
@@ -115,33 +129,6 @@ function connectedCallback(options) {
115129
(this.hasAttribute('hydrate') ? hydrate : render)(this._vdom, this._root);
116130
}
117131

118-
/**
119-
* Changed whenver an attribute of the HTML element changed
120-
* @this {PreactCustomElement}
121-
* @param {string} name The attribute name
122-
* @param {unknown} oldValue The old value or undefined
123-
* @param {unknown} newValue The new value
124-
*/
125-
function attributeChangedCallback(name, oldValue, newValue) {
126-
if (!this._vdom) return;
127-
// Attributes use `null` as an empty value whereas `undefined` is more
128-
// common in pure JS components, especially with default parameters.
129-
// When calling `node.removeAttribute()` we'll receive `null` as the new
130-
// value. See issue #50.
131-
newValue = newValue == null ? undefined : newValue;
132-
const props = {};
133-
props[name] = newValue;
134-
this._vdom = cloneElement(this._vdom, props);
135-
render(this._vdom, this._root);
136-
}
137-
138-
/**
139-
* @this {PreactCustomElement}
140-
*/
141-
function disconnectedCallback() {
142-
render((this._vdom = null), this._root);
143-
}
144-
145132
/**
146133
* Pass an event listener to each `<slot>` that "forwards" the current
147134
* context value to the rendered child. The child will trigger a custom

test/types/index.test.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { h } from 'preact';
2-
import registerElement from '../../src/index';
2+
import registerElement from '../../src/index.js';
33

44
interface AppProps {
55
name: string;

0 commit comments

Comments
 (0)