diff --git a/package.json b/package.json index efcc3fc..094d8d6 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "scripts": { "prepare": "npx simple-git-hooks", "build": "mkdir -p dist && terser src/index.js -o dist/preact-custom-element.js --config-file terser-config.json", - "test": "npm run test:types & npm run test:browser", + "test": "npm run test:types && npm run test:browser", "test:browser": "wtr test/browser/*.test.{js,jsx}", "test:types": "tsc -p test/types/", "lint": "eslint src/*.js", diff --git a/src/index.d.ts b/src/index.d.ts index b399309..e7b7d62 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -52,4 +52,6 @@ export default function register
(
tagName?: string,
propNames?: (keyof P)[],
options?: Options
-): HTMLElement;
+): typeof HTMLElement & {
+ new (): HTMLElement;
+};
diff --git a/src/index.js b/src/index.js
index e742b24..efc007d 100644
--- a/src/index.js
+++ b/src/index.js
@@ -8,44 +8,58 @@ import { h, cloneElement, render, hydrate } from 'preact';
* @type {import('./index.d.ts').default}
*/
export default function register(Component, tagName, propNames, options) {
- function PreactElement() {
- const inst = /** @type {PreactCustomElement} */ (
- Reflect.construct(HTMLElement, [], PreactElement)
- );
- inst._vdomComponent = Component;
-
- if (options && options.shadow) {
- inst._root = inst.attachShadow({
- mode: options.mode || 'open',
- serializable: options.serializable ?? false,
- });
-
- if (options.adoptedStyleSheets) {
- inst._root.adoptedStyleSheets = options.adoptedStyleSheets;
+ class PreactElement extends HTMLElement {
+ constructor() {
+ super();
+
+ this._vdomComponent = Component;
+ if (options && options.shadow) {
+ this._root = this.attachShadow({
+ mode: options.mode || 'open',
+ serializable: options.serializable ?? false,
+ });
+
+ if (options.adoptedStyleSheets) {
+ this._root.adoptedStyleSheets = options.adoptedStyleSheets;
+ }
+ } else {
+ this._root = this;
}
- } else {
- inst._root = inst;
}
- return inst;
+ connectedCallback() {
+ connectedCallback.call(this, options);
+ }
+
+ /**
+ * Changed whenever an attribute of the HTML element changed
+ *
+ * @param {string} name The attribute name
+ * @param {unknown} oldValue The old value or undefined
+ * @param {unknown} newValue The new value
+ */
+ attributeChangedCallback(name, oldValue, newValue) {
+ if (!this._vdom) return;
+ // Attributes use `null` as an empty value whereas `undefined` is more
+ // common in pure JS components, especially with default parameters.
+ // When calling `node.removeAttribute()` we'll receive `null` as the new
+ // value. See issue #50.
+ newValue = newValue == null ? undefined : newValue;
+ const props = {};
+ props[name] = newValue;
+ this._vdom = cloneElement(this._vdom, props);
+ render(this._vdom, this._root);
+ }
+
+ disconnectedCallback() {
+ render((this._vdom = null), this._root);
+ }
}
- PreactElement.prototype = Object.create(HTMLElement.prototype);
- PreactElement.prototype.constructor = PreactElement;
- PreactElement.prototype.connectedCallback = function () {
- connectedCallback.call(this, options);
- };
- PreactElement.prototype.attributeChangedCallback = attributeChangedCallback;
- PreactElement.prototype.disconnectedCallback = disconnectedCallback;
- /**
- * @type {string[]}
- */
propNames = propNames || Component.observedAttributes || [];
PreactElement.observedAttributes = propNames;
- if (Component.formAssociated) {
- PreactElement.formAssociated = true;
- }
+ PreactElement.formAssociated = Component.formAssociated || false;
// Keep DOM properties and Preact props in sync
propNames.forEach((name) => {
@@ -115,33 +129,6 @@ function connectedCallback(options) {
(this.hasAttribute('hydrate') ? hydrate : render)(this._vdom, this._root);
}
-/**
- * Changed whenver an attribute of the HTML element changed
- * @this {PreactCustomElement}
- * @param {string} name The attribute name
- * @param {unknown} oldValue The old value or undefined
- * @param {unknown} newValue The new value
- */
-function attributeChangedCallback(name, oldValue, newValue) {
- if (!this._vdom) return;
- // Attributes use `null` as an empty value whereas `undefined` is more
- // common in pure JS components, especially with default parameters.
- // When calling `node.removeAttribute()` we'll receive `null` as the new
- // value. See issue #50.
- newValue = newValue == null ? undefined : newValue;
- const props = {};
- props[name] = newValue;
- this._vdom = cloneElement(this._vdom, props);
- render(this._vdom, this._root);
-}
-
-/**
- * @this {PreactCustomElement}
- */
-function disconnectedCallback() {
- render((this._vdom = null), this._root);
-}
-
/**
* Pass an event listener to each `