diff --git a/dist-ts/index.d.ts b/dist-ts/index.d.ts new file mode 100644 index 0000000..901ef00 --- /dev/null +++ b/dist-ts/index.d.ts @@ -0,0 +1,30 @@ +import { h, AnyComponent } from 'preact'; +type PreactCustomElement = HTMLElement & { + _root: ShadowRoot | HTMLElement; + _vdomComponent: AnyComponent; + _vdom: ReturnType | null; + _props: Record; +}; +type Options = + | { + shadow: false; + } + | { + shadow: true; + mode?: 'open' | 'closed'; + adoptedStyleSheets?: CSSStyleSheet[]; + serializable?: boolean; + }; +/** + * Register a preact component as web-component. + */ +export default function register

( + Component: AnyComponent, + tagName?: string, + propNames?: (keyof P)[], + options?: Options +): typeof HTMLElement & { + new (): PreactCustomElement; +}; +export {}; +//# sourceMappingURL=index.d.ts.map diff --git a/dist-ts/index.d.ts.map b/dist-ts/index.d.ts.map new file mode 100644 index 0000000..a23ad42 --- /dev/null +++ b/dist-ts/index.d.ts.map @@ -0,0 +1 @@ +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAA2C,YAAY,EAAoC,MAAM,QAAQ,CAAC;AAEpH,KAAK,mBAAmB,GAAG,WAAW,GAAG;IACxC,KAAK,EAAE,UAAU,GAAG,WAAW,CAAC;IAChC,cAAc,EAAE,YAAY,CAAC;IAC7B,KAAK,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IACnC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC,CAAC;AAEF,KAAK,OAAO,GACT;IACA,MAAM,EAAE,KAAK,CAAC;CACb,GACD;IACA,MAAM,EAAE,IAAI,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACzB,kBAAkB,CAAC,EAAE,aAAa,EAAE,CAAC;IACrC,YAAY,CAAC,EAAE,OAAO,CAAC;CACtB,CAAC;AAmDL;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,EAAE,EAC9C,SAAS,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,EAC7B,OAAO,CAAC,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,EAAE,EACvB,OAAO,CAAC,EAAE,OAAO,GACf,OAAO,WAAW,GAAG;IACvB,QAAQ,mBAAmB,CAAC;CAC5B,CA2IA"} \ No newline at end of file diff --git a/dist-ts/index.js b/dist-ts/index.js new file mode 100644 index 0000000..98b59b9 --- /dev/null +++ b/dist-ts/index.js @@ -0,0 +1,262 @@ +import { + h, + cloneElement, + render, + hydrate, + Fragment, + createContext, +} from 'preact'; +/** + * Creates a shadow root with serializable support if available + */ +function createShadowRoot(element, options) { + const shadowOptions = { + mode: options.mode || 'open', + }; + // Add serializable option if requested and supported + if (options.serializable) { + shadowOptions.serializable = true; + } + try { + return element.attachShadow(shadowOptions); + } catch { + // Fallback for browsers that don't support serializable + return element.attachShadow({ mode: options.mode || 'open' }); + } +} +// WeakMaps for private instance data +const privateData = new WeakMap(); +const PRIMITIVE_TYPES = new Set(['string', 'boolean', 'number']); +/** + * Register a preact component as web-component. + */ +export default function register(Component, tagName, propNames, options) { + class PreactElement extends HTMLElement { + constructor() { + super(); + // Initialize private data + privateData.set(this, { + vdomComponent: Component, + props: null, + vdom: null, + }); + this._root = options?.shadow ? createShadowRoot(this, options) : this; + if (options?.shadow && options.adoptedStyleSheets) { + this._root.adoptedStyleSheets = options.adoptedStyleSheets; + } + } + connectedCallback() { + connectedCallback.call(this, options); + } + attributeChangedCallback(name, oldValue, newValue) { + const data = privateData.get(this); + if (!data?.vdom) return; + // Attributes use `null` as an empty value whereas `undefined` is more + // common in pure JS components, especially with default parameters. + const processedNewValue = newValue == null ? undefined : newValue; + const props = {}; + props[name] = processedNewValue; + props[toCamelCase(name)] = processedNewValue; + data.vdom = cloneElement(data.vdom, props); + render(data.vdom, this._root); + } + disconnectedCallback() { + const data = privateData.get(this); + if (data) { + data.vdom = null; + } + render(null, this._root); + } + // Getter and setter for vdom + get _vdom() { + return privateData.get(this)?.vdom ?? null; + } + set _vdom(value) { + const data = privateData.get(this); + if (data) { + data.vdom = value; + } + } + // Getter and setter for props + get _props() { + return privateData.get(this)?.props ?? {}; + } + set _props(value) { + const data = privateData.get(this); + if (data) { + data.props = value; + } + } + get _vdomComponent() { + return privateData.get(this)?.vdomComponent ?? Component; + } + } + /** + * @type {string[]} + */ + const resolvedPropNames = + propNames || + Component.observedAttributes || + Object.keys(Component.propTypes || {}); + PreactElement.observedAttributes = resolvedPropNames; + if (Component.formAssociated) { + PreactElement.formAssociated = true; + } + // Keep DOM properties and Preact props in sync + resolvedPropNames.forEach((name) => { + Object.defineProperty(PreactElement.prototype, name, { + get() { + const data = privateData.get(this); + const vdomProps = data?.vdom?.props; + return vdomProps?.[name] ?? data?.props?.[name]; + }, + set(v) { + const data = privateData.get(this); + if (data?.vdom) { + this.attributeChangedCallback(name, null, String(v)); + } else if (data) { + if (!data.props) data.props = {}; + data.props[name] = v; + } + // Reflect property changes to attributes if the value is a primitive + const type = typeof v; + if (v == null || PRIMITIVE_TYPES.has(type)) { + this.setAttribute(name, String(v)); + } + }, + enumerable: true, + configurable: true, + }); + }); + customElements.define( + tagName || + Component.tagName || + Component.displayName || + Component.name || + 'custom-element', + PreactElement + ); + return PreactElement; +} +// Create a modern context for passing values between components +const PreactContext = createContext(undefined); +function ContextProvider(props) { + const { context, children, ...rest } = props; + // Pass additional props to the child element by cloning it + const wrappedChild = children ? cloneElement(children, rest) : children; + return h(PreactContext.Provider, { value: context }, wrappedChild); +} +/** + * @this {PreactCustomElement} + */ +function connectedCallback(options) { + // Obtain a reference to the previous context by pinging the nearest + // higher up node that was rendered with Preact. If one Preact component + // higher up receives our ping, it will set the `detail` property of + // our custom event. This works because events are dispatched + // synchronously. + const event = new CustomEvent('_preact', { + detail: {}, + bubbles: true, + cancelable: true, + }); + this.dispatchEvent(event); + // Context property is added dynamically by event listeners + const context = event.detail?.context; + const data = privateData.get(this); + if (data) { + data.vdom = h( + ContextProvider, + { ...data.props, context }, + toVdom(this, data.vdomComponent, options) + ); + (this.hasAttribute('hydrate') ? hydrate : render)(data.vdom, this._root); + } +} +/** + * Camel-cases a string + */ +function toCamelCase(str) { + return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : '')); +} +/** + * Pass an event listener to each `` that "forwards" the current + * context value to the rendered child. The child will trigger a custom + * event, where will add the context value to. Because events work + * synchronously, the child can immediately pull of the value right + * after having fired the event. + */ +const slotControllers = new WeakMap(); +function Slot(props, context) { + const ref = (r) => { + const controller = slotControllers.get(this); + if (!r) { + // Cleanup: abort the controller to remove all listeners + if (controller) { + controller.abort(); + slotControllers.delete(this); + } + } else { + // Setup: create new AbortController for this slot + const newController = new AbortController(); + slotControllers.set(this, newController); + const listener = (event) => { + event.stopPropagation(); + // Context is added dynamically to event detail + event.detail.context = context; + }; + r.addEventListener('_preact', listener, { + signal: newController.signal, + }); + } + }; + const { useFragment, ...rest } = props; + if (useFragment) { + // Fragment doesn't accept ref or other props + return h(Fragment, {}); + } else { + // Slot element can accept ref and other attributes + return h('slot', { ...rest, ref }); + } +} +function toVdom(element, nodeName, options) { + if (element.nodeType === 3) return element.data; + if (element.nodeType !== 1) return null; + const htmlElement = element; + const children = []; + const props = {}; + const attributes = htmlElement.attributes; + const childNodes = htmlElement.childNodes; + // Process attributes + for (let i = attributes.length; i--; ) { + if (attributes[i].name !== 'slot') { + props[attributes[i].name] = attributes[i].value; + props[toCamelCase(attributes[i].name)] = attributes[i].value; + } + } + // Process child nodes + for (let i = childNodes.length; i--; ) { + const vnode = toVdom(childNodes[i], null, options); + // Move slots correctly + const name = childNodes[i].slot; + if (name) { + props[name] = h(Slot, { name }, vnode); + } else { + children[i] = vnode; + } + } + const shadow = !!(options && options.shadow); + // Only wrap the topmost node with a slot + const wrappedChildren = nodeName + ? h(Slot, { useFragment: !shadow }, children) + : children; + if (!shadow && nodeName) { + htmlElement.innerHTML = ''; + } + return h( + nodeName || htmlElement.nodeName.toLowerCase(), + props, + wrappedChildren + ); +} +//# sourceMappingURL=index.js.map diff --git a/dist-ts/index.js.map b/dist-ts/index.js.map new file mode 100644 index 0000000..d1f83b7 --- /dev/null +++ b/dist-ts/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index b38bde8..2e56683 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,25 +1,28 @@ { "name": "preact-custom-element", - "version": "4.4.0", + "version": "4.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "preact-custom-element", - "version": "4.4.0", + "version": "4.5.0", "license": "MIT", "devDependencies": { "@open-wc/testing": "^4.0.0", "@types/mocha": "^10.0.10", "@web/dev-server-esbuild": "^1.0.4", "@web/test-runner": "^0.20.2", + "@web/test-runner-puppeteer": "^0.18.0", "eslint": "^7.7.0", "eslint-config-developit": "^1.2.0", "microbundle": "^0.15.1", "nano-staged": "^0.8.0", + "oxlint": "^1.15.0", "preact": "^10.27.1", "prettier": "^2.1.1", - "simple-git-hooks": "^2.13.1" + "simple-git-hooks": "^2.13.1", + "typescript": "^5.9.2" }, "peerDependencies": { "preact": ">= 10.25.0 || >=11.0.0-0" @@ -2713,12 +2716,115 @@ "lit-html": "^2.0.0 || ^3.0.0" } }, + "node_modules/@oxlint/darwin-arm64": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@oxlint/darwin-arm64/-/darwin-arm64-1.15.0.tgz", + "integrity": "sha512-fwYg7WDKI6eAErREBGMXkIAOqBuBFN0LWbQJvVNXCGjywGxsisdwkHnNu4UG8IpHo4P71mUxf3l2xm+5Xiy+TA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxlint/darwin-x64": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@oxlint/darwin-x64/-/darwin-x64-1.15.0.tgz", + "integrity": "sha512-RtaAmB6NZZx4hvjCg6w35shzRY5fLclbMsToC92MTZ9lMDF9LotzcbyNHCZ1tvZb1tNPObpIsuX16BFeElF8nw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxlint/linux-arm64-gnu": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@oxlint/linux-arm64-gnu/-/linux-arm64-gnu-1.15.0.tgz", + "integrity": "sha512-8uV0lAbmqp93KTBlJWyCdQWuxTzLn+QrDRidUaCLJjn65uvv8KlRhZJoZoyLh17X6U/cgezYktWTMiMhxX56BA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint/linux-arm64-musl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@oxlint/linux-arm64-musl/-/linux-arm64-musl-1.15.0.tgz", + "integrity": "sha512-/+hTqh1J29+2GitKrWUHIYjQBM1szWSJ1U7OzQlgL+Uvf8jxg4sn1nV79LcPMXhC2t8lZy5EOXOgwIh92DsdhQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint/linux-x64-gnu": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@oxlint/linux-x64-gnu/-/linux-x64-gnu-1.15.0.tgz", + "integrity": "sha512-GzeY3AhUd49yV+/76Gw0pjpwUJwxCkwYAJTNe7fFTdWjEQ6M6g8ZzJg5FKtUvgA5sMgmfzHhvSXxvT57YhcXnA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint/linux-x64-musl": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@oxlint/linux-x64-musl/-/linux-x64-musl-1.15.0.tgz", + "integrity": "sha512-p/7+juizUOCpGYreFmdfmIOSSSE3+JfsgnXnOHuP8mqlZfiOeXyevyajuXpPNRM60+k0reGvlV7ezp1iFitF7w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint/win32-arm64": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@oxlint/win32-arm64/-/win32-arm64-1.15.0.tgz", + "integrity": "sha512-2LaDLOtCMq+lzIQ63Eir3UJV/hQNlw01xtsij2L8sSxt4gA+zWvubOQJQIOPGMDxEKFcWT1lo/6YEXX/sNnZDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxlint/win32-x64": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/@oxlint/win32-x64/-/win32-x64-1.15.0.tgz", + "integrity": "sha512-+jgRPpZrFIcrNxCVsDIy6HVCRpKVDN0DHD8VJodjrsDv6heqhq/qCTa2IXY3R4glWe1nWQ5JgdFKLn3Bl+aLNg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@puppeteer/browsers": { - "version": "2.10.8", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.8.tgz", - "integrity": "sha512-f02QYEnBDE0p8cteNoPYHHjbDuwyfbe4cCIVlNi8/MRicIxFW4w4CfgU0LNgWEID6s06P+hRJ1qjpBLMhPRCiQ==", + "version": "2.10.9", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.9.tgz", + "integrity": "sha512-kUGHwABarVhvMP+zhW5zvDA7LmGcd4TwrTEBwcTQic5EebUqaK5NjC0UXLJepIFVGsr2N/Z8NJQz2JYGo1ZwxA==", "dev": true, - "license": "Apache-2.0", "dependencies": { "debug": "^4.4.1", "extract-zip": "^2.0.1", @@ -2735,60 +2841,6 @@ "node": ">=18" } }, - "node_modules/@puppeteer/browsers/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@puppeteer/browsers/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=10" - } - }, - "node_modules/@puppeteer/browsers/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@puppeteer/browsers/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=12" - } - }, "node_modules/@rollup/plugin-alias": { "version": "3.1.9", "resolved": "https://registry.npmjs.org/@rollup/plugin-alias/-/plugin-alias-3.1.9.tgz", @@ -4058,6 +4110,20 @@ "node": ">=18.0.0" } }, + "node_modules/@web/test-runner-puppeteer": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/@web/test-runner-puppeteer/-/test-runner-puppeteer-0.18.0.tgz", + "integrity": "sha512-pc0gADGjqflSRIZQehwD9INKUY1DZ92eWJwuVwZXzKxr5soniT/Bknv2hT5ttpJ5P7ILuCyjJyq5IjKzPBFUXg==", + "dev": true, + "dependencies": { + "@web/test-runner-chrome": "^0.18.0", + "@web/test-runner-core": "^0.13.0", + "puppeteer": "^24.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/@web/test-runner/node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -4984,6 +5050,20 @@ "node": ">=8" } }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", @@ -5601,11 +5681,10 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1475386", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1475386.tgz", - "integrity": "sha512-RQ809ykTfJ+dgj9bftdeL2vRVxASAuGU+I9LEx9Ij5TXU5HrgAQVmzi72VA+mkzscE12uzlRv5/tWWv9R9J1SA==", - "dev": true, - "license": "BSD-3-Clause" + "version": "0.0.1495869", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1495869.tgz", + "integrity": "sha512-i+bkd9UYFis40RcnkW7XrOprCujXRAHg62IVh/Ah3G8MmNXpCGt1m0dTFhSdx/AVs8XEMbdOGRwdkR1Bcta8AA==", + "dev": true }, "node_modules/diff": { "version": "5.1.0", @@ -5790,6 +5869,15 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -9228,6 +9316,40 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oxlint": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.15.0.tgz", + "integrity": "sha512-GZngkdF2FabM0pp0/l5OOhIQg+9L6LmOrmS8V8Vg+Swv9/VLJd/oc/LtAkv4HO45BNWL3EVaXzswI0CmGokVzw==", + "dev": true, + "bin": { + "oxc_language_server": "bin/oxc_language_server", + "oxlint": "bin/oxlint" + }, + "engines": { + "node": ">=8.*" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxlint/darwin-arm64": "1.15.0", + "@oxlint/darwin-x64": "1.15.0", + "@oxlint/linux-arm64-gnu": "1.15.0", + "@oxlint/linux-arm64-musl": "1.15.0", + "@oxlint/linux-x64-gnu": "1.15.0", + "@oxlint/linux-x64-musl": "1.15.0", + "@oxlint/win32-arm64": "1.15.0", + "@oxlint/win32-x64": "1.15.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=0.2.0" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + } + } + }, "node_modules/p-event": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz", @@ -10195,18 +10317,39 @@ "node": ">=6" } }, + "node_modules/puppeteer": { + "version": "24.20.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.20.0.tgz", + "integrity": "sha512-iLnLV9oHKKAujmxiSxRWKfcT1q2COu0g1N9iU2TCp1MlmsyjgNAkcBOR3cAOqKb5UTiVPIGG4z5PO5yfpYZ6jA==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "@puppeteer/browsers": "2.10.9", + "chromium-bidi": "8.0.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1495869", + "puppeteer-core": "24.20.0", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/puppeteer-core": { - "version": "24.18.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.18.0.tgz", - "integrity": "sha512-As0BvfXxek2MbV0m7iqBmQKFnfSrzSvTM4zGipjd4cL+9f2Ccgut6RvHlc8+qBieKHqCMFy9BSI4QyveoYXTug==", + "version": "24.20.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.20.0.tgz", + "integrity": "sha512-n0y/f8EYyZt4yEJkjP3Vrqf9A4qa3uYpKYdsiedIY4bxIfTw1aAJSpSVPmWBPlr1LO4cNq2hGNIBWKPhvBF68w==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.10.8", + "@puppeteer/browsers": "2.10.9", "chromium-bidi": "8.0.0", "debug": "^4.4.1", - "devtools-protocol": "0.0.1475386", + "devtools-protocol": "0.0.1495869", "typed-query-selector": "^2.12.0", + "webdriver-bidi-protocol": "0.2.8", "ws": "^8.18.3" }, "engines": { @@ -10235,6 +10378,50 @@ } } }, + "node_modules/puppeteer/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/puppeteer/node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/puppeteer/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/qs": { "version": "6.11.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", @@ -10815,56 +11002,6 @@ } } }, - "node_modules/rollup-plugin-visualizer/node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/rollup-plugin-visualizer/node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/rollup-plugin-visualizer/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, - "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/rollup-plugin-visualizer/node_modules/yargs-parser": { - "version": "21.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", - "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", - "dev": true, - "engines": { - "node": ">=12" - } - }, "node_modules/rollup-pluginutils": { "version": "2.8.2", "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", @@ -11909,11 +12046,10 @@ "license": "MIT" }, "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", "dev": true, - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -12093,6 +12229,12 @@ "node": ">= 0.8" } }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.2.8.tgz", + "integrity": "sha512-KPvtVAIX8VHjLZH1KHT5GXoOaPeb0Ju+JlAcdshw6Z/gsmRtLoxt0Hw99PgJwZta7zUQaAUIHHWDRkzrPHsQTQ==", + "dev": true + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -12276,6 +12418,15 @@ } } }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", @@ -12291,6 +12442,33 @@ "node": ">= 6" } }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", diff --git a/package.json b/package.json index 4782d19..d2dfc3a 100644 --- a/package.json +++ b/package.json @@ -10,9 +10,15 @@ "scripts": { "prepare": "npx simple-git-hooks", "build": "microbundle -f cjs,es,umd --no-generateTypes", - "lint": "eslint src/*.{js,jsx}", - "test": "wtr src/*.test.{js,jsx}", - "prettier": "prettier **/*.{js,jsx} --write", + "build:ts": "tsc -p tsconfig.build.json", + "test:ts": "npm run build:ts && cp dist-ts/index.js src/index.compiled.js && npm run test:compiled && rm src/index.compiled.js", + "test:compiled": "wtr src/*.test.{js,jsx} --node-resolve --puppeteer", + "lint": "oxlint src/", + "lint:legacy": "eslint src/*.{js,jsx}", + "typecheck": "tsc --noEmit", + "test": "wtr src/*.test.{js,jsx} --puppeteer", + "test:all": "npm run typecheck && npm run test && npm run test:ts", + "prettier": "prettier **/*.{js,jsx,ts} --write", "prepublishOnly": "npm run build && npm run lint && npm run test" }, "eslintConfig": { @@ -69,13 +75,16 @@ "@types/mocha": "^10.0.10", "@web/dev-server-esbuild": "^1.0.4", "@web/test-runner": "^0.20.2", + "@web/test-runner-puppeteer": "^0.18.0", "eslint": "^7.7.0", "eslint-config-developit": "^1.2.0", "microbundle": "^0.15.1", "nano-staged": "^0.8.0", + "oxlint": "^1.15.0", "preact": "^10.27.1", "prettier": "^2.1.1", - "simple-git-hooks": "^2.13.1" + "simple-git-hooks": "^2.13.1", + "typescript": "^5.9.2" }, "simple-git-hooks": { "pre-commit": "npx nano-staged" diff --git a/src/index.d.ts b/src/index.d.ts index f92c6a3..5cdae11 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -15,6 +15,7 @@ type Options = shadow: true; mode?: 'open' | 'closed'; adoptedStyleSheets?: CSSStyleSheet[]; + serializable?: boolean; }; /** diff --git a/src/index.js b/src/index.js index 5a0aac3..23b641f 100644 --- a/src/index.js +++ b/src/index.js @@ -4,33 +4,108 @@ import { h, cloneElement, render, hydrate, Fragment } from 'preact'; * @typedef {import('./index.d.ts').PreactCustomElement} PreactCustomElement */ +/** + * Creates a shadow root with serializable support if available + * @param {HTMLElement} element - The element to attach the shadow to + * @param {object} options - Shadow root options + * @returns {ShadowRoot} The created shadow root + */ +function createShadowRoot(element, options) { + const shadowOptions = { mode: options.mode || 'open' }; + + // Add serializable option if requested and supported + if (options.serializable) { + shadowOptions.serializable = true; + } + + try { + return element.attachShadow(shadowOptions); + } catch { + // Fallback for browsers that don't support serializable + return element.attachShadow({ mode: options.mode || 'open' }); + } +} + +// WeakMaps for private instance data +const privateData = new WeakMap(); +const PRIMITIVE_TYPES = new Set(['string', 'boolean', 'number']); + /** * @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; - inst._root = - options && options.shadow - ? inst.attachShadow({ mode: options.mode || 'open' }) - : inst; - - if (options && options.adoptedStyleSheets) { - inst._root.adoptedStyleSheets = options.adoptedStyleSheets; + class PreactElement extends HTMLElement { + constructor() { + super(); + + // Initialize private data + privateData.set(this, { + vdomComponent: Component, + props: null, + vdom: null, + }); + + this._root = options?.shadow ? createShadowRoot(this, options) : this; + + if (options?.adoptedStyleSheets) { + this._root.adoptedStyleSheets = options.adoptedStyleSheets; + } + } + + connectedCallback() { + connectedCallback.call(this, options); + } + + attributeChangedCallback(name, oldValue, newValue) { + const data = privateData.get(this); + if (!data?.vdom) return; + + // Attributes use `null` as an empty value whereas `undefined` is more + // common in pure JS components, especially with default parameters. + newValue = newValue == null ? undefined : newValue; + const props = {}; + props[name] = newValue; + props[toCamelCase(name)] = newValue; + data.vdom = cloneElement(data.vdom, props); + render(data.vdom, this._root); + } + + disconnectedCallback() { + const data = privateData.get(this); + if (data) { + data.vdom = null; + } + render(null, this._root); + } + + // Getter and setter for vdom + get _vdom() { + return privateData.get(this)?.vdom; + } + + set _vdom(value) { + const data = privateData.get(this); + if (data) { + data.vdom = value; + } } - return inst; + // Getter and setter for props + get _props() { + return privateData.get(this)?.props; + } + + set _props(value) { + const data = privateData.get(this); + if (data) { + data.props = value; + } + } + + get _vdomComponent() { + return privateData.get(this)?.vdomComponent; + } } - 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[]} @@ -49,29 +124,26 @@ export default function register(Component, tagName, propNames, options) { propNames.forEach((name) => { Object.defineProperty(PreactElement.prototype, name, { get() { - return this._vdom - ? this._vdom.props[name] - : this._props[name]; + const data = privateData.get(this); + return data?.vdom?.props[name] ?? data?.props?.[name]; }, set(v) { - if (this._vdom) { + const data = privateData.get(this); + if (data?.vdom) { this.attributeChangedCallback(name, null, v); } else { - if (!this._props) this._props = {}; - this._props[name] = v; + if (!data.props) data.props = {}; + data.props[name] = v; } // Reflect property changes to attributes if the value is a primitive const type = typeof v; - if ( - v == null || - type === 'string' || - type === 'boolean' || - type === 'number' - ) { + if (v == null || PRIMITIVE_TYPES.has(type)) { this.setAttribute(name, v); } }, + enumerable: true, + configurable: true, }); }); @@ -105,14 +177,18 @@ function connectedCallback(options) { cancelable: true, }); this.dispatchEvent(event); - const context = event.detail.context; + // @ts-ignore - context property is added dynamically by event listeners + const context = event.detail && event.detail.context; - this._vdom = h( - ContextProvider, - { ...this._props, context }, - toVdom(this, this._vdomComponent, options) - ); - (this.hasAttribute('hydrate') ? hydrate : render)(this._vdom, this._root); + const data = privateData.get(this); + if (data) { + data.vdom = h( + ContextProvider, + { ...data.props, context }, + toVdom(this, data.vdomComponent, options) + ); + (this.hasAttribute('hydrate') ? hydrate : render)(data.vdom, this._root); + } } /** @@ -124,34 +200,6 @@ function toCamelCase(str) { return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : '')); } -/** - * 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; - props[toCamelCase(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 `` that "forwards" the current * context value to the rendered child. The child will trigger a custom @@ -159,21 +207,35 @@ function disconnectedCallback() { * synchronously, the child can immediately pull of the value right * after having fired the event. */ +const slotControllers = new WeakMap(); + function Slot(props, context) { const ref = (r) => { + const controller = slotControllers.get(this); + if (!r) { - this.ref.removeEventListener('_preact', this._listener); - } else { - this.ref = r; - if (!this._listener) { - this._listener = (event) => { - event.stopPropagation(); - event.detail.context = context; - }; - r.addEventListener('_preact', this._listener); + // Cleanup: abort the controller to remove all listeners + if (controller) { + controller.abort(); + slotControllers.delete(this); } + } else { + // Setup: create new AbortController for this slot + const newController = new AbortController(); + slotControllers.set(this, newController); + + const listener = (event) => { + event.stopPropagation(); + // @ts-ignore - we know context exists in this scope + event.detail.context = context; + }; + + r.addEventListener('_preact', listener, { + signal: newController.signal, + }); } }; + const { useFragment, ...rest } = props; return h(useFragment ? Fragment : 'slot', { ...rest, ref }); } diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..dbe8ebf --- /dev/null +++ b/src/index.ts @@ -0,0 +1,391 @@ +import { + h, + cloneElement, + render, + hydrate, + Fragment, + AnyComponent, + createContext, + ComponentChildren, +} from 'preact'; + +type PreactCustomElement = HTMLElement & { + _root: ShadowRoot | HTMLElement; + _vdomComponent: AnyComponent; + _vdom: ReturnType | null; + _props: Record; +}; + +type Options = + | { + shadow: false; + } + | { + shadow: true; + mode?: 'open' | 'closed'; + adoptedStyleSheets?: CSSStyleSheet[]; + serializable?: boolean; + }; + +interface PrivateData { + vdomComponent: AnyComponent; + props: Record | null; + vdom: ReturnType | null; +} + +interface SlotProps { + useFragment?: boolean; + name?: string; + [key: string]: unknown; +} + +/** + * Creates a shadow root with serializable support if available + */ +function createShadowRoot( + element: HTMLElement, + options: Extract +): ShadowRoot { + const shadowOptions: ShadowRootInit & { serializable?: boolean } = { + mode: options.mode || 'open', + }; + + // Add serializable option if requested and supported + if (options.serializable) { + shadowOptions.serializable = true; + } + + try { + return element.attachShadow(shadowOptions); + } catch { + // Fallback for browsers that don't support serializable + return element.attachShadow({ mode: options.mode || 'open' }); + } +} + +// WeakMaps for private instance data +const privateData = new WeakMap(); +const PRIMITIVE_TYPES = new Set(['string', 'boolean', 'number']); + +type ComponentWithMeta = AnyComponent & { + observedAttributes?: string[]; + propTypes?: Record; + formAssociated?: boolean; + tagName?: string; + displayName?: string; + name?: string; +}; + +/** + * Register a preact component as web-component. + */ +export default function register

( + Component: AnyComponent, + tagName?: string, + propNames?: (keyof P)[], + options?: Options +): typeof HTMLElement & { + new (): PreactCustomElement; +} { + class PreactElement extends HTMLElement implements PreactCustomElement { + _root: ShadowRoot | HTMLElement; + + constructor() { + super(); + + // Initialize private data + privateData.set(this, { + vdomComponent: Component as AnyComponent, + props: null, + vdom: null, + }); + + this._root = options?.shadow ? createShadowRoot(this, options) : this; + + if (options?.shadow && options.adoptedStyleSheets) { + (this._root as ShadowRoot).adoptedStyleSheets = + options.adoptedStyleSheets; + } + } + + connectedCallback(): void { + connectedCallback.call(this, options); + } + + attributeChangedCallback( + name: string, + oldValue: string | null, + newValue: string | null + ): void { + const data = privateData.get(this); + if (!data?.vdom) return; + + // Attributes use `null` as an empty value whereas `undefined` is more + // common in pure JS components, especially with default parameters. + const processedNewValue = newValue == null ? undefined : newValue; + const props: Record = {}; + props[name] = processedNewValue; + props[toCamelCase(name)] = processedNewValue; + data.vdom = cloneElement(data.vdom, props); + render(data.vdom, this._root); + } + + disconnectedCallback(): void { + const data = privateData.get(this); + if (data) { + data.vdom = null; + } + render(null, this._root); + } + + // Getter and setter for vdom + get _vdom(): ReturnType | null { + return privateData.get(this)?.vdom ?? null; + } + + set _vdom(value: ReturnType | null) { + const data = privateData.get(this); + if (data) { + data.vdom = value; + } + } + + // Getter and setter for props + get _props(): Record { + return privateData.get(this)?.props ?? {}; + } + + set _props(value: Record) { + const data = privateData.get(this); + if (data) { + data.props = value; + } + } + + get _vdomComponent(): AnyComponent { + return ( + privateData.get(this)?.vdomComponent ?? (Component as AnyComponent) + ); + } + + static observedAttributes: string[]; + static formAssociated?: boolean; + } + + /** + * @type {string[]} + */ + const resolvedPropNames: string[] = + (propNames as string[]) || + (Component as ComponentWithMeta).observedAttributes || + Object.keys((Component as ComponentWithMeta).propTypes || {}); + PreactElement.observedAttributes = resolvedPropNames; + + if ((Component as ComponentWithMeta).formAssociated) { + PreactElement.formAssociated = true; + } + + // Keep DOM properties and Preact props in sync + resolvedPropNames.forEach((name: string) => { + Object.defineProperty(PreactElement.prototype, name, { + get(this: PreactElement) { + const data = privateData.get(this); + const vdomProps = data?.vdom?.props as unknown as Record< + string, + unknown + >; + return vdomProps?.[name] ?? data?.props?.[name]; + }, + set(this: PreactElement, v: unknown) { + const data = privateData.get(this); + if (data?.vdom) { + this.attributeChangedCallback(name, null, String(v)); + } else if (data) { + if (!data.props) data.props = {}; + data.props[name] = v; + } + + // Reflect property changes to attributes if the value is a primitive + const type = typeof v; + if (v == null || PRIMITIVE_TYPES.has(type)) { + this.setAttribute(name, String(v)); + } + }, + enumerable: true, + configurable: true, + }); + }); + + customElements.define( + tagName || + (Component as ComponentWithMeta).tagName || + (Component as ComponentWithMeta).displayName || + (Component as ComponentWithMeta).name || + 'custom-element', + PreactElement + ); + + return PreactElement as typeof HTMLElement & { + new (): PreactCustomElement; + }; +} + +// Create a modern context for passing values between components +const PreactContext = createContext(undefined); + +interface ContextProviderProps { + context?: unknown; + children?: ComponentChildren; + [key: string]: unknown; // Allow additional props to be passed through +} + +function ContextProvider(props: ContextProviderProps): ReturnType { + const { context, children, ...rest } = props; + // Pass additional props to the child element by cloning it + const wrappedChild = children + ? cloneElement(children as ReturnType, rest) + : children; + return h( + PreactContext.Provider, + { value: context }, + wrappedChild + ) as ReturnType; +} + +/** + * @this {PreactCustomElement} + */ +function connectedCallback(this: PreactCustomElement, options?: Options): void { + // Obtain a reference to the previous context by pinging the nearest + // higher up node that was rendered with Preact. If one Preact component + // higher up receives our ping, it will set the `detail` property of + // our custom event. This works because events are dispatched + // synchronously. + const event = new CustomEvent<{ context?: unknown }>('_preact', { + detail: {}, + bubbles: true, + cancelable: true, + }); + this.dispatchEvent(event); + // Context property is added dynamically by event listeners + const context = event.detail?.context; + + const data = privateData.get(this); + if (data) { + data.vdom = h( + ContextProvider, + { ...data.props, context } as ContextProviderProps, + toVdom(this, data.vdomComponent, options) + ) as ReturnType; + (this.hasAttribute('hydrate') ? hydrate : render)(data.vdom, this._root); + } +} + +/** + * Camel-cases a string + */ +function toCamelCase(str: string): string { + return str.replace(/-(\w)/g, (_, c) => (c ? c.toUpperCase() : '')); +} + +/** + * Pass an event listener to each `` that "forwards" the current + * context value to the rendered child. The child will trigger a custom + * event, where will add the context value to. Because events work + * synchronously, the child can immediately pull of the value right + * after having fired the event. + */ +const slotControllers = new WeakMap(); + +interface SlotThis { + [key: string]: unknown; +} + +function Slot(this: SlotThis, props: SlotProps, context: unknown) { + const ref = (r: HTMLElement | null) => { + const controller = slotControllers.get(this); + + if (!r) { + // Cleanup: abort the controller to remove all listeners + if (controller) { + controller.abort(); + slotControllers.delete(this); + } + } else { + // Setup: create new AbortController for this slot + const newController = new AbortController(); + slotControllers.set(this, newController); + + const listener = (event: Event) => { + event.stopPropagation(); + // Context is added dynamically to event detail + (event as CustomEvent<{ context?: unknown }>).detail.context = context; + }; + + r.addEventListener('_preact', listener, { + signal: newController.signal, + }); + } + }; + + const { useFragment, ...rest } = props; + + if (useFragment) { + // Fragment doesn't accept ref or other props + return h(Fragment, {}); + } else { + // Slot element can accept ref and other attributes + return h('slot', { ...rest, ref }); + } +} + +function toVdom( + element: Node, + nodeName: AnyComponent | null, + options?: Options +): ReturnType | string | null { + if (element.nodeType === 3) return (element as Text).data; + if (element.nodeType !== 1) return null; + + const htmlElement = element as HTMLElement; + const children: (ReturnType | string | null)[] = []; + const props: Record = {}; + const attributes = htmlElement.attributes; + const childNodes = htmlElement.childNodes; + + // Process attributes + for (let i = attributes.length; i--; ) { + if (attributes[i].name !== 'slot') { + props[attributes[i].name] = attributes[i].value; + props[toCamelCase(attributes[i].name)] = attributes[i].value; + } + } + + // Process child nodes + for (let i = childNodes.length; i--; ) { + const vnode = toVdom(childNodes[i], null, options); + // Move slots correctly + const name = (childNodes[i] as HTMLElement).slot; + if (name) { + props[name] = h(Slot, { name }, vnode); + } else { + children[i] = vnode; + } + } + + const shadow = !!(options && options.shadow); + + // Only wrap the topmost node with a slot + const wrappedChildren = nodeName + ? h(Slot, { useFragment: !shadow }, children) + : children; + + if (!shadow && nodeName) { + htmlElement.innerHTML = ''; + } + return h( + nodeName || htmlElement.nodeName.toLowerCase(), + props, + wrappedChildren + ); +} diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..408d772 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM"], + "module": "ESNext", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "strict": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "./dist-ts", + "rootDir": "./src" + }, + "include": [ + "src/index.ts" + ], + "exclude": [ + "node_modules", + "dist", + "src/*.test.*" + ] +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..5e852dd --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["ES2020", "DOM"], + "module": "ESNext", + "moduleResolution": "node", + "allowSyntheticDefaultImports": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "jsxImportSource": "preact", + "strict": true, + "noEmit": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file