Giter VIP home page Giter VIP logo

preact-custom-element's Introduction

preact-custom-element

Generate/register a custom element from a preact component. As of 3.0.0, this library implements the Custom Elements v1 spec. Previous versions (< 3.0.0) implemented the v0 proposal, which was only implemented in Chrome and is abandoned.

Usage

Import register and call it with your component, a tag name*, and a list of attribute names you want to observe:

import register from 'preact-custom-element';

const Greeting = ({ name = 'World' }) => (
  <p>Hello, {name}!</p>
);

register(Greeting, 'x-greeting', ['name']);

* Note: as per the Custom Elements specification, the tag name must contain a hyphen.

Use the new tag name in HTML, attribute keys and values will be passed in as props:

<x-greeting name="Billy Jo"></x-greeting>

Output:

<p>Hello, Billy Jo!</p>

Prop Names and Automatic Prop Names

The Custom Elements V1 specification requires explictly stating the names of any attributes you want to observe. From your Preact component perspective, props could be an object with any keys at runtime, so it's not always clear which props should be accepted as attributes.

If you omit the third parameter to register(), the list of attributes to observe can be specified using a static observedAttributes property on your Component. This also works for the Custom Element's name, which can be specified using a tagName static property:

import register from 'preact-custom-element';

// <x-greeting name="Bo"></x-greeting>
class Greeting extends Component {
  // Register as <x-greeting>:
  static tagName = 'x-greeting';

  // Track these attributes:
  static observedAttributes = ['name'];

  render({ name }) {
    return <p>Hello, {name}!</p>;
  }
}
register(Greeting);

If no observedAttributes are specified, they will be inferred from the keys of propTypes if present on the Component:

// Other option: use PropTypes:
function FullName(props) {
  return <span>{props.first} {props.last}</span>
}
FullName.propTypes = {
  first: Object,   // you can use PropTypes, or this
  last: Object     // trick to define untyped props.
};
register(FullName, 'full-name');

Related

preact-shadow-dom

preact-custom-element's People

Contributors

ansballard avatar bhollis avatar bspaulding avatar developit avatar ffriedl89 avatar filipbech avatar forsakenharmony avatar janbiasi avatar johnrees avatar jovidecroock avatar marvinhagemeister avatar rschristian avatar stevenle avatar tyom avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

preact-custom-element's Issues

Hyphenated attributes and camelCased properties

When a component is connected, preact-custom-element will loop through all attributes and set them as props on the component - both with the original casing and with a camelCased name. Automatic camelCasing is also done for observed attributes.

On the other hand, when determining which attributes to observe, the wrapper does not perform any corresponding transformation - it simply uses the given propNames directly (passed in explicitly, or extracted from the component definition). This is a problem because component props are usually in camelCase, while HTML attributes must be in lowercase (usually hyphenated).

If propNames are extracted from the component's propTypes, there is virtually no chance that they will be hyphenated, and the wrapper will only listen for (and try to reflect) changes to a camelCased attribute, not a hyphenated one. However, the spec requires attributes to be in lowercase, and attempting to set a camelCase attribute in a framework like Vue, manually through browser dev tools, or in the HTML source, will lead to a lowercase attribute being set instead. This effectively means that any prop name with a capital letter cannot be observed (as an attribute) by preact-custom-element. Initial values will still be set, since preact-custom-element loops through all attributes and converts them to camelCase props, but that only happens once. The DOM property will also work, but supporting the attribute - for simple data - would be nice.

propNames could be hyphenated if they are explicitly passed, or defined as observedAttributes on the component. But in this case the wrapper will set up a hyphenated DOM property instead of a camelCased one.


Should preact-custom-element camelCase all propNames before defining properties, and hyphenate them before setting observedAttributes?

  • This is the option I would prefer as a consumer, but I would like some feedback on the idea before putting together a PR

Should consumers pass in camelCased names for complex types (which will use properties) and hyphenated names for simple types (which will use attributes)? Maybe both forms for simple types?

  • This would avoid registering complex types as observedAttributes, which won't work anyways
  • This could probably be extracted automatically from propTypes, but would require some additional logic compared to just getting the names.

Should properties and attributes be separated in the preact-custom-element API?

  • Removes unnecessary observedAttributes (for properties) or defineProperty calls (for attributes)
  • Attributes and properties have some important differences. Maybe trying to abstract them into one thing isn't a good idea anyways?
  • Would probably be a breaking change, although perhaps compatibility could be maintained by using propNames in both places if the consumer does not specify

Add support for {mode: 'closed'}

The current implementation of register(..., {shadow: true}), always attaches the shadow DOM in {mode: 'open'}. This is a request to add support for {mode: 'closed'} as well.

Relevant docs:
https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_shadow_DOM#basic_usage

Screen Shot 2022-09-09 at 3 14 20 AM

I'm happy to send a PR to support the above. The way I see it there are two options:

  1. In addition to the option{shadow: true}, add support for {shadow: 'open'} and {shadow: 'closed'}.

  2. Add a new option called mode, e.g.: {shadow: true, mode: 'closed'}.

Let me know which option is preferred and I'm happy to send over the PR, including updating the @types/ package.

Parse attributes from custom element

I want to pass attribs in my custom element like this:

<my-custom-element attribute-one="[1,2,3,4]" ></my-custom-element>

and receive that into my preact component as an Array, and not a String. For now, im parsing all my props before rendering:

for(let key in this.props){
	try {
		this.props[key] = JSON.parse(this.props[key])
	} catch (e) {}
}

Do you guys think that is a nice improvement for the project? If you agree, i can make a PR with that, just let me know!

New release

Are there any plans to cut a new release of this package anytime soon? There are several post 3.0 improvements already merged to the main branch. If there is anything I can do to in order to help facilitate a new release, please tell.

Function as attribute

Is there an easy way to pass in a function as an attribute?

Example :

 render({ ondataloaded }) {
      return (
          <button onClick={ondataloaded}>Trigger Data Loaded</button>
      );

registerCustomElement(Clock, "clock", ["ondataloaded"]);

Slots fail to pass as props when element is created after register() is called

If I follow the example in https://preactjs.com/guide/v10/web-components/#passing-slots-as-props and bundle the jsx file into an iife (e.g., via ./node_modules/.bin/esbuild test-src.jsx --bundle --jsx-import-source=preact --jsx=automatic --minify --outfile=test.js), then the example works as documented if my script runs after the element such as:

<text-section>
  <span slot="heading">Nice heading</span>
  <span slot="content">Great content</span>
</text-section>
<script src="test.js"></script>

However, the heading and content props are undefined if my script runs before the element such as:

<script src="test.js"></script>
<text-section>
  <span slot="heading">Nice heading</span>
  <span slot="content">Great content</span>
</text-section>

It resets complex property to undefined when provided as second or later prop

Hi. Firstly, thank you all for your amazing work on the preactjs project!

When I set a complex property (callback) AFTER at least one other property
Then the complex property become undefined.

All complex properties become undefined, unless the complex property is the first property set.

I wrote failing test to explain better. (See attached diff file)

TLDR;

// this works as expected
<dummy-button zz={() => console.log('zz')}  aa="123" ></dummy-button>

// and this doesn't
<dummy-button aa="123" zz={() => console.log('zz')} ></dummy-button>
// zz become eventually undefined

Also one other thing I've noticed when I tried to debug this is that connectedCallback is called multiple times and the complex properties are reset right after the connectedCallback is called.
I don't know yet why connectedCallback is called multiple times, but I if it's called I would expect disconnectedCallback to be called before connectedCallback is called second time. But it's never called.

When I forcefully prevent the connecting new vdom if we already connected one, then it works as expected. But I would rather understand what causes the element to connect to dom second time, and if we should set there the same properties we already set (same as we do in attributeChangedCallback when we cloning the previous vdom).

version: 4.2.1

Git diff file:
preact-custom-element-bug.txt

diff --git a/src/index.js b/src/index.js
index 42318d7..41374e8 100644
--- a/src/index.js
+++ b/src/index.js
@@ -63,6 +63,7 @@ function ContextProvider(props) {
 }
 
 function connectedCallback() {
+	// if (this._vdom) return; // prevent from resetting vdom if we already have one, and connected callback was called second time
 	// 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
diff --git a/src/index.test.jsx b/src/index.test.jsx
index 00da646..fba77d3 100644
--- a/src/index.test.jsx
+++ b/src/index.test.jsx
@@ -130,6 +130,33 @@ describe('web components', () => {
 			});
 			assert.equal(other, 1);
 		});
+
+		it('sets complex property after simple property', () => {
+			const el = document.createElement('x-dummy-button');
+
+			// set simple property first
+			el.text = 'click me';
+
+			let clicks = 0;
+			const onClick = () => clicks++;
+			el.onClick = onClick;
+
+			assert.equal(el.text, 'click me');
+			assert.equal(el.onClick, onClick);
+
+			root.appendChild(el);
+			assert.equal(
+				root.innerHTML,
+				'<x-dummy-button text="click me"><button>click me</button></x-dummy-button>'
+			);
+
+			act(() => {
+				el.querySelector('button').click();
+			});
+
+			assert.equal(el.onClick, onClick);
+			assert.equal(clicks, 1);
+		});
 	});
 
 	function Foo({ text, children }) {

Can we make this work for SSR?

Here is a sandbox trying to combine Preact inside a custom element with Nextjs. But it errors

Warning: Did not expect server HTML to contain a <div> in <custom-element>

https://codesandbox.io/s/github/secretlifeof/nextjs-dynamic-import-issue/tree/custom-element-ssr/

Is there a possibility to make this work for SSR? It tried adding "window !== undefined" but that always caused some other errors. To be honest I am too much of a newbie to really understand how this script works. If anybody can give me some hints I would love to try to contribute though.

[Improvment] Add documentation or template for vite

Hello,

I have problems to get this working with vite.
It would be cool to have more documentation for this or have a seperate vite template to start with.

The standard preact template split each files in seperate modules and expects an <div id="app"/> to inject the preact app there.
But we want to build a web component wich can be included by a js module and its custom tag.

But we still want to be able to use the vite dev server (best with hot reloading).

Maybe someone can give a example project config for now, please.

Thanks for any help :)

Feature Request: expose class decorator without define call

Hi,

I need to expose a preact component as an unregistered custom element (so that the end user will call customElements.define with the tag name of its choosing).

WDYT about also exporting the class decoration without define call ?

We could keep the current default export for backward compatibility, yet (also) export named functions registerCustomElement and createCustomElement (or similar) ?

I can work out the PR if you think that's a worthy addition to this library !

Thanks.

I can't use hook

My Code
image

Console Error
image


But work fine without hook

Code:
image

browser:
image

Action required: Greenkeeper could not be activated ๐Ÿšจ

๐Ÿšจ You need to enable Continuous Integration on Greenkeeper branches of this repository. ๐Ÿšจ

To enable Greenkeeper, you need to make sure that a commit status is reported on all branches. This is required by Greenkeeper because it uses your CI build statuses to figure out when to notify you about breaking changes.

Since we didnโ€™t receive a CI status on the greenkeeper/initial branch, itโ€™s possible that you donโ€™t have CI set up yet.
We recommend using:

If you have already set up a CI for this repository, you might need to check how itโ€™s configured. Make sure it is set to run on all new branches. If you donโ€™t want it to run on absolutely every branch, you can whitelist branches starting with greenkeeper/.

Once you have installed and configured CI on this repository correctly, youโ€™ll need to re-trigger Greenkeeperโ€™s initial pull request. To do this, please click the 'fix repo' button on account.greenkeeper.io.

Injecting global styles to `shadow: true` Custom Elements

Hello! Me and my org were recently very impressed with your hard work to support slots and property reflection.

A common developer issue when you start having Custom Elements with Shadow DOM enabled, is to somehow inject styles into the element. Sometimes you don't have the luxury of being able to inject the needed stylings directly into the Custom Element's Shadow DOM.

After my org forked the @vue/web-component-wrapper to add support for IE 11, we added functionality for injecting global styles using a mutation observer in our fork, named @telia/vue-web-component-wrapper-ie11. Link to relevant code from the fork can be found somewhere around here.

This approach seems to have surprisingly good performance (partly because browsers seem to cache style-elements that have identical contents), and since we'll definitely need this in our experiment to turn React components into Web Components, we figured this functionality would fit well within your wrapper.

Please let us know what you think!

Preact Component's constructor is not called when custom element is created via document.createElement

This is documented here in this comment from another PR: #64 (comment). document.createElement should run the component's constructor method.

class CounterPreact extends Component {
  constructor() {
    super()
    console.log('here')
  }
}

register(CounterPreact, 'x-counter-preact')

// ...

document.createElement('x-counter-preact') // no output
class CounterPlain extends HTMLElement {
  constructor() {
    super()
    console.log('here')
  }
}

customElements.define('x-counter-plain', CounterPlain)

// ...

document.createElement('x-counter-plain') // outputs here

The constructor only seems to fire when the custom element as appended to the DOM.

Children of custom element gets rendered twice

This may be related to #18 but when custom element renders its children property like this:

const Greeting = ({ children, name = "World" }) => (
  <h1 id="hello">
    <p>Hello {name}!</p>
    {children}
  </h1>
);

They are rendered twice in two different styles (shadow and document?) like this:

screenshot_1031

See: https://codesandbox.io/s/preact-custom-element-rendering-content-issue-g7ko9

I also noticed in some test cases where the custom element, not just children, gets rendered twice when some elements are added. When traced in such cases, children property included the root element (direct children of shadowRoot) instead of the children from HTML.

Looking at preact-custom-element source code, I'm having difficulty finding where children parameter comes from. In my 'bare-metal' custom element implementation, I capture children via a MutationObserver like this:

    connectedCallback() {
        const shadowRoot = this.shadowRoot;
        var observer = new MutationObserver(function (mutations) {
            mutations.forEach((mutation) => {
                for (const child of mutation.addedNodes) {
                    shadowRoot.querySelector('.info').appendChild(child)
                }
            })
        })
        observer.observe(this, { childList: true })
    }

Hydration with shadow root doesn't work

If you register a component as a custom element with { shadow: true } then the code in the connectedCallback will try to run the hydrate function with the shadow root as the target node. This ends up rendering the entire component inside the shadow root while preserving, but not rendering, existing children (perhaps they're being treated like slot contents?) outside the shadow root which defeats the purpose of hydration.

I found this out while testing SSR with shadow root; I know you can't declaratively add a shadow root. But I was wondering if moving existing children of the custom element into the shadow root then attempting to hydrate would work? Is there risk of content reflow or style flashing? Is this simply just a limitation of custom elements that cannot be circumvented and therefore not in scope here?

Here's an example:

import { h, Fragment } from 'preact';
import { useState } from 'preact/hooks'
import register from 'preact-custom-element';

export const Counter = ({increment}) => {
	let [ count, setCount ] = useState(0)

	return (
		<>
			<p>Count: {count}</p>

			<button onClick={() => setCount(count + Number(increment))}>Increment by: {increment}</button>
			<button onClick={() => setCount(count - Number(increment))}>Decrement by: {increment}</button>
			<slot />
		</>
	)
}

register(Counter, 'my-counter', ['increment'], { shadow: true })
<my-counter increment="5" hydrate>
	<p>Count: 0</p>
	<button>Increment by: 5</button>
	<button>Decrement by: 5</button>
</my-counter>

These are the results when the script loads:

<my-counter increment="5" hydrate>
	#shadow-root
		<p>Count: 0</p>
		<button>Increment by: 5</button>
		<button>Decrement by: 5</button>
	<p>Count: 0</p>
	<button>Increment by: 5</button>
	<button>Decrement by: 5</button>
</my-counter>

This would be the desired results:

<my-counter increment="5" hydrate>
	#shadow-root
		<p>Count: 0</p>
		<button>Increment by: 5</button>
		<button>Decrement by: 5</button>
</my-counter>

Feature Request: Use the "render" function of preact/compat

Hi,

I see the library is using the preact render function to render the custom elements.

import { h, cloneElement, render, hydrate } from 'preact';

But as mentionned in the preact doc here https://preactjs.com/guide/v10/upgrade-guide#render-always-diffs-existing-children , the render function of preact has a different behavior than the React render method.

On my side, I'd like to use the import { render } from "preact/compat"; render function instead of the default one.

Do you think it would be nice if we add a choice about which render function to use ? It would be great !

Thank you ! ๐Ÿ˜Š

Allowing any attribute to passed to the Custom Element?

Hi! Thanks for all the hard work!

My org is unable to pass Props from the React component to the register() function for reasons (mainly regarding Functional Components and TypeScript, and an ambition to not have to modify the underlying component because we have quite many).

I was wondering, would it be possible to let us call register(), and let the default behavior of undefined to the attributes parameter, mean that all attributes set on the Custom Element will be passed down?

My team thought this may be an issue for property assignment somehow, but I'd like to bring this up for discussion since we're at a cross-roads at the moment due to this on what to do (we've even gone as far as made TypeScript AST parsing for our typescript-related props type declarations)

"Children" not replaced on mount when shadow DOM is disabled

We have a design system with components written in React. I was thinking of rendering those with preact compat and then wrapping those in preact-custom-element to be able to use the components in a Vue/Angular or vanilla js app. I get a few issues tho.

  1. Children of the custom element in html is not replaced when the Preact component mounts. I tried the "solution" to this by @donpark in this issue thread but I can't just switch out {children} for <slot /> if I want to keep compatibility with React and also it didn't work when I tried it.
    Using a shadow DOM here, passing shadow: true to the register function of preact-custom-element works but since I'm using Emotion as a CSS-in-JS I need the global styling support. There are ways of controlling where Emotion inserts styles and use shadow DOM here but that would probably mess with other teams implementation as of now. I would like it to be able to use this lib without enabling shadow DOM.

2. I can't get property bindings to work at all as demonstrated in this PR. Attributes work fine, but properties doesn't end up as props on the Preact component (whether shadow DOM is used or not).

All the issues is demonstrated in this sandbox.

Batch attributeChangedCallback / Safeguard required props

The setup:

  • A small preact-application wrapped in preact-custom-element, accepting three props which all are required by the preact-application.
  • The resulting custom element is dynamically created and updated inside a React-application

From my observations it does not seem like the wrapped preact-application is guaranteed to receive all props, with their corresponding values, in one pass. Even though they are provided to the custom element in "one go". I assume this has to do with how web components and the attributeChangedCallback work https://github.com/preactjs/preact-custom-element/blob/master/src/index.js#L28. Missing values for required props can of course be mitigated by having safeguards in place in the preact-application. But I want to discuss approches to include this functionality in this library instead

Thoughts/Questions:

Exposing public properties?

Hi, great library. I have a question on how to expose properties of a component to the DOM. It looks like you are iterating through the attributes to expose as the CustomElement prototype:

https://github.com/bspaulding/preact-custom-element/blob/master/src/index.js#L6

but am a little confused on what these would actually be. I mistakenly assumed I'd be able to define properties on the Preact component and automatically export them:

class Example extends Component {
  report() {
    console.log('SOUND OFF!')
  }
  render() {
    ...
}

However this does not work:

var example = $('x-example')[0];
example.report();

What would be the best way to do this?

Help wanted: How to build & export a custom element

Hey and thank you very much for this!

I am trying to build a npm package that exports a custom-element, but I am struggling how to make this work. Trying different bundlers (Webpack, Rollup, Microbundle, Snowpack) without success. Preact-cli widget is not up to date.

Here is what I have so far:

import { h } from 'preact'
import { setPragma, styled } from 'goober'
import htm from 'htm'
import register from 'preact-custom-element'
const html = htm.bind(h)

setPragma(h)

const RedText = styled('span')`
  color: red;
`

const App = () =>
  html`
    <${RedText}><div>There you are</div><//>
  `

register(
  html`
    <${App} />
  `,
  'hi-hi-app',
  []
)

I use microbundle@next ("microbundle --alias react=preact/compat,react-dom=preact/compat") to build the scripts

When trying to import this package in Nextjs I get this error:

InvalidCharacterError: Failed to execute 'createElement' on 'Document': The tag name provided ('[object Object],>') is not a valid name.

import{hydrate as t,render as e,cloneElement as n,h as o}from"preact";import{setPragma as r,styled as i}from"goober";import u from"htm";function c(t,e){return e||(e=t.slice(0)),t.raw=e,t}function a(){this._vdom=function t(e,n){if(3===e.nodeType)return e.data;if(1!==e.nodeType)return null;var r=[],i={},u=0,c=e.attributes,a=e.childNodes;for(u=c.length;u--;)i[c[u].name]=c[u].value;for(u=a.length;u--;)r[u]=t(a[u]);return o(n||e.nodeName.toLowerCase(),i,r)}(this,this._vdomComponent),(this.hasAttribute("hydrate")?t:e)(this._vdom,this)}function d(t,o,r){if(this._vdom){var i={};i[t]=r,this._vdom=n(this._vdom,i),e(this._vdom,this)}}function s(){e(this._vdom=null,this)}function l(){var t=c(["\n

TEST
>\n "]);return l=function(){return t},t}function m(){var t=c(["\n <",">
THERE Here
<//>\n "]);return m=function(){return t},t}function p(){var t=c(["\n display: flex;\n flex: 1;\n color: red;\n width: 300px;\n height: 300px;\n background: yellow;\n"]);return p=function(){return t},t}var f=u.bind(o);r(o);var h=i("span")(p());console.log(()=>f(m(),h)),function(t,e,n){function o(){var e=Reflect.construct(HTMLElement,[],o);return e._vdomComponent=t,e}(o.prototype=Object.create(HTMLElement.prototype)).constructor=o,o.prototype.connectedCallback=a,o.prototype.attributeChangedCallback=d,o.prototype.detachedCallback=s,o.observedAttributes=[]||t.observedAttributes||Object.keys(t.propTypes||{}),customElements.define("pimello-cms",o)}(f(l()));

If anybody can point me towards a boilerplate that would be great :)

In Firefox, The Custom Element gets cloned when calling member function of props, like toUpperCase()

Sandbox: https://codesandbox.io/s/preact-custom-element-forked-mro87

How to reproduce

Try change the attribute value to something else.

You don't even need to render the string, just call size.toLowerCase() in an expression inside the functional component.

The issue

  • The Custom Element gets cloned
  • The size becomes null for a second and crashes the demo.

Workaround

Do a check if the size prop is null, and do an early return. The temporary null value is what caused the element to be cloned.

With null checking sandbox: https://codesandbox.io/s/preact-custom-element-forked-40vfr?file=/src/Button.tsx

Just try to change the size attribute as usual.

Document behavior for multi-word attributes/props?

Hi there! Love the library.

Currently the readme only has examples with single-word props like name. It would be nice to see documented how to use multi-word attrs / props like myName or my-name. Otherwise people would have to guess or read source code.

Thanks

publish to npm?

looks like the latest version, 3.0.0, was published in 2018.

Pass the created HTML node to the preact component

Hi!

It would be very helpful if the custom element that is created in the register function also gets passed to the preact component (along with the attributes), so that it's possible to reference the actual DOM element inside of the preact component.

This would provide a clean way to e.g. fire events on the custom element itself, when scoped events are necessary. For example, if a web component uses events to communicate, and multiple instances of the same web component are loaded on the same page. If the events aren't scoped in that case (but e.g. all emitted on the document), it's impossible to figure out which player fired the event.

Issues re-rendering CE root with preact

I do not using shadowDOM

class Clock extends Component<{},{time: number}> {
    private timer: any;
    constructor() {
        super();
        this.state.time = Date.now();
    }

    componentDidMount() {
        this.timer = setInterval(() => this.setState({ time: Date.now() }), 1000);
    }
    componentWillUnmount() {
        clearInterval(this.timer);
    }

    render(props: any, state: {time: number}) {
        let time = new Date(state.time).toLocaleTimeString();
        return time;
    }
}

register(Clock, 'x-c');

function renderApp() {
    render((<x-c/>), document.body, document.body.firstElementChild);
}

renderApp();
console.log(document.body.innerHTML);
renderApp();
console.log(document.body.innerHTML);

Output is:

<x-c>3:27:10 PM</x-c>
<x-c></x-c>  // bad: should render time as well

This works in this way because when preact is come to patch <x-c> is removing children of it.
<x-c> is not a component is regular node for preact, so on second time render preact is removing children of it because vnode do not contains any children, custom-element do not get any livecycle notification so it is not rerender it content again.

In other words custom element should control rendering of it by self, not from outside parent.
But this will require some changes in preact eg:

https://github.com/developit/preact/blob/master/src/vdom/diff.js#L128

should be replaced by:

var isCE = !!window.customElements.get(vnode.nodeName);

if (!isCE) {
    // Optimization: fast-path for elements containing a single TextNode:
    if (!hydrating && vchildren && vchildren.length===1 && typeof vchildren[0]==='string' &&
        fc!=null && fc.splitText!==undefined && fc.nextSibling==null) {
        if (fc.nodeValue!=vchildren[0]) {
            fc.nodeValue = vchildren[0];
        }
    }
    // otherwise, if there are existing or new children, diff them:
    else if (vchildren && vchildren.length || fc!=null) {
        innerDiffNode(out, vchildren, context, mountAll, hydrating ||
            props.dangerouslySetInnerHTML!=null);
    }
} 

Or more generic like https://github.com/Matt-Esch/virtual-dom/blob/master/docs/thunk.md

Webpack.bin: https://www.webpackbin.com/bins/-Ki44_xg5yie0o-jHaXv

Add `"jsnext": "src/index.js",` to `package.json`

I'm currently working on a little preact boilerplate for standalone components, and thought this might be a nice addition to it. But I'm bundling with rollup, and it would be nice to be able to pull in the es6 module version of this library without an alias or workaround. Shouldn't be any code changes since the source is already es6. just needs the jsnext field added to package.json.

package does not work

Hello,
this is my index.js:

import register from "preact-custom-element";
import './style';
import App from './components/app';
register(App, "uiwiwidget");

template.html:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="utf-8">
		<title><% preact.title %></title>
		<meta name="viewport" content="width=device-width,initial-scale=1">
		<meta name="mobile-web-app-capable" content="yes">
		<meta name="apple-mobile-web-app-capable" content="yes">
		<link rel="apple-touch-icon" href="/assets/icons/apple-touch-icon.png">
		<% preact.headEnd %>
	</head>
	<body>
		<% preact.bodyEnd %>
		<uiwiwidget></uiwiwidget>
	</body>
</html>

After yarn run dev or run build nothing happens. Page is empty. What should I do?

Interop problems with Svelte

I'm right now trying to integrate a Custom Element that has been created via preact-custom-element into a Svelte application.
Whenever the CE is rendered by svelte an error comes up that emerges at the "place" where Svelte hands over properties/attributes to the Custom Element:

image
("Uncaught TypeError: Cannot read properties of undefined (reading 'props')")

The place this error is thrown is this line of code:
image

(https://github.com/preactjs/preact-custom-element/blob/master/src/index.js#L27)

My understanding is that preact-custom-element assumes that a property value will always be set before it is ever read.
Therefore the if statement in line 30 that checks for an initialized vdom (https://github.com/preactjs/preact-custom-element/blob/master/src/index.js#L30).

Now the problem gets clearer when we look at the code where svelte assigns values to the custom element:
image
(https://github.com/sveltejs/svelte/blob/master/src/runtime/internal/dom.ts#L315)

The function that is responsible always reads (get) the value from the custom element (via the typeof statement) before it sets the value, while preact-custom-element assumes that the value is set before it is read.

Please let me know if my analysis makes sense and if you'd consider this something that should be fixed here or rather in the svelte implementation!

Observed attributes break context providers

When a custom element has an observed attribute, a context provider will not take a value.

const ThemeContext = createContext('light');

function Theme({ children, theme = 'dark' }) {
  // We can read the value being passed in:
  console.log(theme); // 'dark'

  // But the provider below will not set its value to 'dark'.
  //
  // Any children using the context will get 'light'.
  //
  // Even if we passed in a value directly:
  // <ThemeContext.Provider value={'blue'}>
  // the children will still get 'light'.
  return (
    <ThemeContext.Provider value={theme}>
      <div>{children}</div>
    </ThemeContext.Provider>
  );
}

// Removing the "theme" attribute below resolves the issue. The context will then take a value.
registerElement(Theme, 'x-theme', ['theme'], { shadow: true });

Full working example:
https://stackblitz.com/edit/vitejs-vite-fwbe4e?file=src/app.jsx (may need to run yarn dev in the console)

Web component interop improvements

Hey,
I've tinkered around quite a bit with using Preact and the preact-custom-element package to write Preact and "compile" to custom elements. This would be insanely helpful to provide a non preact version of the components for users that can't or don't want to use Preact for building their applications.

One major hurdle that I am facing is that parent - child communication is needed in composition usecases.
E.g.

<x-tab-group>
  <x-tab>First</x-tab>
  <x-tab>Second</x-tab>
</x-tab-group>

The user should not have to worry about hooking up the tabs with the tabgroup correctly, but the tabgroup should take care of this.

In a purely preact world I would do this by using cloneElement and extending the props of the children to pass down a onActive prop that gets called whenever a tab is clicked. The tab-group can then react to any tab being clicked, when both of those elements are registered as custom elements.

function TabGroup({ children }) {
	const extendedChildren = toChildArray(children).map(child => {
		if (child && child.type) {
			return cloneElement(child, {
				onActive: ...
			});
		}
		return child;
	});

	return <div class="children">{extendedChildren}</div>;
}

The props like the onActive prop are not passed down to the children. Therefore parent-child usecases cannot be realised right now if both components are custom elements.

I've created a rough test case in a fork to outline the current state.
https://github.com/ffriedl89/preact-custom-element/blob/wc-interop-props-fns/src/index.test.js#L106

The way to compile to custom-elements is already so promising that I really wanna explore this further. I am new to the preact world and probably not of much help for coming up with a solution, but if I can help in any way just let me know.

Any help on this is highly appreciated! ย ๐Ÿš€

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.