Closed lokimckay closed 2 years ago
Thanks for your observation, I had never analyzed the limitations of HTM, I will review this to know the cost vs. benefit.
Regarding the use of the html of the lightDOM within the shadowDOM, the ideal is to always use a slot, but depending on the case I attach some techniques that today would work maintaining the expected effect:
This technique allows you to manipulate the cloned node, be it associating events, properties or even associating new children. example https://webcomponents.dev/edit/PUYhxjl2JSsyFQ5dvrL4/src/index.jsx
import { c, html, useRef } from "atomico";
import { useSlot } from "@atomico/hooks/use-slot";
function myExample() {
const ref = useRef();
const childNodes = useSlot(ref);
return html`<host shadowDom>
<h1>Inside</h1>
<slot ref=${ref} style="display:none"></slot>
<ul>
${childNodes
.filter((el) => el instanceof Element)
.map((child) => html`<li><${child.cloneNode(true)} /></li>`)}
</ul>
</host>`;
}
export const MyExample = c(myExample);
If you use innerHTML on a node that does not define children, the Atomico render will skip the parsing of that Node, example:
html`<span innerHTML="<b>a<b>"></span>`
Would this meet your objectives?
Thanks for the info @UpperCod - those seem like reasonable ways to manipulate the child nodes.
However, my use case is a bit strange I'm afraid The component I have created is an accordion which is intended to be used within sitebuilder platforms e.g. Squarespace / Wordpress etc.
The sitebuilder user simply adds a "code block" (common feature among these platforms that allows you to insert HTML) to create a page that looks like this:
(code block)
<my-accordion></my-accordion>
(/code block)
(text block)
<h1>Accordion button 1</h1>
(/text block)
(other Wordpress/Squarespace text/image blocks etc.)
(text block)
<h1>Accordion button 2</h1>
(/text block)
(other Wordpress/Squarespace text/image blocks etc.)
(code block)
<my-accordion></my-accordion>
(/code block)
My accordion grabs everything between the two <h1/2/3/4/5/6>
headings and their matching content that should be displayed when the heading is clicked
I want the sitebuilder user to be able to use the full features of their platform, so I cannot ask them to pass the child nodes as a <slot>
in the code block
I wanted to avoid including an extra container <div>
or <span>
and then set the innerHTML
. In my case I just preferred to render the HTML without an extra container
I understand this use case might be outside the normal expected usage of Atomico.
No worries if you decide nothing needs to be done to support it๐
What you are looking for is interesting and it can be achieved with Atomico without problems, I attach the example:
https://webcomponents.dev/edit/YzDmaVb3hNgw2IUW9dvZ
The other alternative is the use of the IS
attribute on the div the container to define, this avoids the webcomponent wrapper. https://twitter.com/atomicojs/status/1388713726279311360
... personally I have worked a lot with Wordpress, I am attentive to what you need
Fantastic! thanks for that example, I didn't think of using the useHost
hook ๐
My only extra requirement that I did not mention earlier is that sometimes the web component will be nested by the sitebuilder platform e.g:
<div>
<div>
<div>
<div>
<my-fragment></my-fragment>
</div>
</div>
</div>
<h1>Title 1</h1>
<p>Bla bla...</p>
<h1>Title 2</h1>
<p>Bla bla...</p>
<h1>Title 3</h1>
<p>Bla bla...</p>
<h1>Title 4</h1>
<p>Bla bla...</p>
<div>
<div>
<div>
<my-fragment></my-fragment>
</div>
</div>
</div>
</div>
I was using a Range previously to combat this
Based on your example, I guess I would still need to create a range using the current host reference from useHost
and a reference to the second instance of <my-fragment>
from document.querySelectorAll(my-fragment)
?
Range is the solution, there is a hook called useParentPath
with which you will be able to know the parent nodes of the webcomponent, useful when looking for the sibling nodes through a querySelectorAll, example:
import { c, useHost, useMemo } from "atomico";
import { useParentPath } from "@atomico/hooks/use-parent";
function useRange() {
const host = useHost();
const path = useParentPath();
return useMemo(() => {
let brother;
path.some((el) => {
const current = [...el.querySelectorAll(host.current.localName)].find(
(el) => el !== host.current
);
if (el) {
brother = current;
return true;
}
});
if (brother) {
const range = new Range();
range.setStartBefore(host.current);
range.setEndBefore(brother);
}
});
}
I am curious how it captures the nodes within the range?
Cool thanks, I'll check out that hook too ๐
I am curious how it captures the nodes within the range?
Here's the process I was using:
<my-component>
using a range<my-component>
which renders the results as it pleasesI've just recently open-sourced my code, here are the relevant files ๐
Thanks for sharing, I find the use of Range and renderHTML interesting, I'm going to explore more solutions with it
Example repo
https://github.com/lokimckay-references/atomico-html2jsx
Motivation
As an atomico user, I want to grab regular HTML elements from the "light DOM" and render them in my web component (including HTML5 void elements)
According to the docs, the way to do that is like this:
..but atomico uses "htm" (htm) which is not intended to be an HTML parser, and does not support HTML5 void elements
As a result of this, the following snippet will not work in atomico:
Workaround
Use the xhtm module instead