Open DrSensor opened 1 year ago
I can't justify the amount of layout shift caused by this approach in bigger html, it just too much! Better to wait for declarative syntax being part of HTML5 engine. Otherwise, just use template engine or site generator.
Still cause layout shift but I think this is cool alternative (inspired from strawberry framework)
<awesome-button>singleton burton</awesome-button>
<render-scope>
<link let=animate href=random-animation.js css.var="random duration">
<template shadowrootmode=closed>
<cool-button instance=false>singleton Brrr</cool-button>
<awesome-button instance> instanced burton</awesome-button>
<cool-button>instanced Brrr</cool-button>
</template>
<template instance define=cool-button use=animate>
<button style="
padding: 0.5rem 1rem;
border: 2px solid black;
border-radius: 0px;
box-shadow: 4px 4px 0px gray;
animation: var(--animate\.random);
animation-duration: var(--animate\.duration);
"
disabled ~ !disabled
@click=animate.shuffle
><slot /></button>
</template>
<template global define=awesome-button>
<cool-button>
<slot /> is awesome with <span ~ #text=animate.random /> animation
</cool-button>
</template>
</render-scope>
By default, each custom-element defined inside render-scope will use the same instance of all linked modules. To make each custom-element unique, instantiate
attribute need to be specified so it will instantiate all linked modules every time custom-element is created. You can control which module to use (or instantiate) by using use
attribute, by default it allow (and may instantiate) all modules in \<render-scope>.
I'm going to reopen this.
I think there's a way to prevent layout shift by making <body hidden ~ !hidden>
until <render-scope>
is defined/loaded.
Declarative <custom-element>
via <template define=custom-element>
has fatal footgun when the template has <slot>
. There is no way to automatically hide custom-element because the children will be rendered first before script is ready which cause glitch. At worst it show incorrect content (only show children inside of custom-element) when the JavaScript is disabled. The workaround is to use global hidden
attribute on every <custom-element>
.
<render-scope>
<link let=c href=counter.js>
<template shadowrootmode=closed>
<div ~ !hidden>loading...</div>
<my-counter hidden ~ !hidden count=0></my-counter>
</template>
<template instance define=my-counter>
<button ~ @click=c.increment>$count</button>
<input value=$count ~ @change=set:c.count .value=c.count>
</template>
</render-scope>
Another alternative would be to use manual slot assignment on named slot.
Note that the initial mode of declarative shadow dom is always be
slotAssignment: "named"
<render-scope> <link let=c href=counter.js>
<div #instance slot=counter style=display:contents> <button ~ @click=c.increment>$count <input value=$count ~ @change=set:c.count .value=c.count>
⬇️ **Downside**:
* awkward custom attributes which:
* may conflict with built-in `<slot>` and [global](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes) attributes 🤔
* may have incorrect attribute value when JavaScript disabled 😱
* glitch in text interpolation and worst the text only show the identifier rather than the value 😢
* no `<style>` encapsulation 😢
* can't render in multiple slots 😱 (browser engine can't render same Node in multiple place)
**Solution**:
* style encapsulation:
* handle `<style type=scoped>` (not work if JavaScript disabled 😢)
* use `<template shadowrootmode=open>` in the light DOM (i.e `<div>`) and declare `<style>` inside that shadow DOM
* custom attributes:
* disallow custom attributes 🤪
* use special prefix/suffix for name/binding of that custom attribute
```html
<render-scope>
<link let=c href=counter.js>
<template shadowrootmode=closed>
<slot name=counter $count=0 ~ .$suffix=c.count>Parent is</slot>
</template>
<div #instance slot=counter style=display:contents>
<slot></slot> <span ~ #text=$suffix></span>
<button ~ @click=c.increment #text=$count>loading...</button>
<input value="loading..." ~ @change=set:c.count .value="$count~>c.count">
</div>
</render-scope>
Note: make sure to provide a default value to handle missing custom attributes
All children inside <slot>
will be moved/copied to all slot.assignedElements()
🤔
I guess layout shift bound to be inevitable due to engine limitation 😅
Let's keep the <custom-element>
approach
<render-scope>
<link let=c href=counter.js>
<template shadowrootmode=closed>
<my-counter count=0 hidden ~ !hidden .suffix=c.count>Parent is</my-counter>
</template>
<template -let=prop -instance -define=my-counter>
<slot></slot> <prop.suffix/>
<button ~ @click=c.increment><prop.count/></button>
<input value=prop.count ~ @change=set:c.count .value=c.count>
</template>
</render-scope>
Has same signature as
<render-scope>
but for defining a custom-element.It only fetch then apply linked modules when the custom-element is in view.
TODO
extends
from existingHTML<*>Element
slotchange
Related
Declarative Syntax for Custom Elements (strawman proposal)
Maybe partially rewriting the current implementation as module 🤔