define-element 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -16
- package/define-element.js +19 -12
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -2,10 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
A custom element to define custom elements.
|
|
4
4
|
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Pluggable template engine — native components for [sprae](https://github.com/dy/sprae), [Alpine](https://alpinejs.dev), [petite-vue](https://github.com/vuejs/petite-vue), [template-parts](https://github.com/nicegist/template-parts) and others
|
|
5
|
+
* [Declarative Custom Elements](https://github.com/WICG/webcomponents/blob/gh-pages/proposals/Declarative-Custom-Elements-Strawman.md) reference implemetation
|
|
6
|
+
* Typed props, scoped styles, shadow DOM, slots, lifecycle — one `<script>` tag
|
|
7
|
+
* Native web components for [sprae](https://github.com/dy/sprae), [alpine](https://alpinejs.dev), [petite-vue](https://github.com/vuejs/petite-vue), [template-parts](https://github.com/nicegist/template-parts) and others
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
```html
|
|
@@ -150,15 +149,26 @@ Add `shadowrootmode` to the template for encapsulation. Slots work natively:
|
|
|
150
149
|
|
|
151
150
|
## Processor
|
|
152
151
|
|
|
153
|
-
Pluggable template engine. Without a processor, templates are static HTML
|
|
152
|
+
Pluggable template engine. Without a processor, templates are static HTML (cloned automatically). With a processor, the processor owns template mounting — it receives an empty `root` with `root.template` pointing to the original `<template>` element, and is responsible for cloning/rendering content.
|
|
154
153
|
|
|
155
|
-
|
|
154
|
+
```js
|
|
155
|
+
processor(root, state) => state
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
- `root` — element (light DOM) or shadowRoot (shadow DOM), empty
|
|
159
|
+
- `root.template` — original `<template>` element (shared across instances)
|
|
160
|
+
- `state` — `{ propName: value }` from prop defaults + instance attributes
|
|
161
|
+
- Returns reactive state object (stored as `el.state`)
|
|
156
162
|
|
|
157
163
|
```js
|
|
158
|
-
|
|
164
|
+
let DE = customElements.get('define-element')
|
|
159
165
|
|
|
160
|
-
// sprae
|
|
161
|
-
|
|
166
|
+
// sprae — clone template, then sprae processes content reactively
|
|
167
|
+
import sprae from 'sprae'
|
|
168
|
+
DE.processor = (root, state) => {
|
|
169
|
+
root.appendChild(root.template.content.cloneNode(true))
|
|
170
|
+
return sprae(root, state)
|
|
171
|
+
}
|
|
162
172
|
```
|
|
163
173
|
|
|
164
174
|
```html
|
|
@@ -176,22 +186,26 @@ customElements.get('define-element').processor = sprae
|
|
|
176
186
|
No `<script>` needed — [sprae](https://github.com/dy/sprae) updates the template automatically when state changes. Other processors:
|
|
177
187
|
|
|
178
188
|
```js
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
// @github/template-parts ({{x}} interpolation, W3C Template Instantiation proposal)
|
|
189
|
+
// @github/template-parts — renders directly from template, no pre-clone needed
|
|
182
190
|
import { TemplateInstance } from '@github/template-parts'
|
|
183
191
|
DE.processor = (root, state) => {
|
|
184
192
|
root.replaceChildren(new TemplateInstance(root.template, state))
|
|
185
193
|
return state
|
|
186
194
|
}
|
|
187
195
|
|
|
188
|
-
// petite-vue
|
|
196
|
+
// petite-vue
|
|
189
197
|
import { createApp, reactive } from 'petite-vue'
|
|
190
|
-
DE.processor = (root, state) =>
|
|
198
|
+
DE.processor = (root, state) => {
|
|
199
|
+
root.appendChild(root.template.content.cloneNode(true))
|
|
200
|
+
let r = reactive(state)
|
|
201
|
+
createApp(r).mount(root)
|
|
202
|
+
return r
|
|
203
|
+
}
|
|
191
204
|
|
|
192
|
-
// Alpine.js
|
|
205
|
+
// Alpine.js
|
|
193
206
|
import Alpine from 'alpinejs'
|
|
194
207
|
DE.processor = (root, state) => {
|
|
208
|
+
root.appendChild(root.template.content.cloneNode(true))
|
|
195
209
|
let r = Alpine.reactive(state)
|
|
196
210
|
Alpine.addScopeToNode(root, r)
|
|
197
211
|
Alpine.initTree(root)
|
|
@@ -217,7 +231,7 @@ The [W3C Declarative Custom Elements proposal](https://github.com/WICG/webcompon
|
|
|
217
231
|
|
|
218
232
|
The gap: no lightweight way to define a custom element as HTML — components as content, not as code. Paste a `<define-element>` block into any page, CMS, or markdown file and it works. No npm, no import maps, no build step. One `<script>` tag.
|
|
219
233
|
|
|
220
|
-
This ~
|
|
234
|
+
This ~270-line reference implementation is evidence that the W3C proposal is implementable and useful. Ship it natively.
|
|
221
235
|
|
|
222
236
|
|
|
223
237
|
## Alternatives
|
package/define-element.js
CHANGED
|
@@ -105,10 +105,6 @@ function define(el) {
|
|
|
105
105
|
root = this.shadowRoot || this.attachShadow({ mode: shadowMode })
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
if (tpl && !root.firstChild) {
|
|
109
|
-
root.appendChild(tpl.content.cloneNode(true))
|
|
110
|
-
}
|
|
111
|
-
|
|
112
108
|
// inject style
|
|
113
109
|
if (styleText) {
|
|
114
110
|
if (shadowMode) {
|
|
@@ -131,13 +127,7 @@ function define(el) {
|
|
|
131
127
|
}
|
|
132
128
|
}
|
|
133
129
|
|
|
134
|
-
//
|
|
135
|
-
this.part = {}
|
|
136
|
-
root.querySelectorAll('[part]').forEach(p =>
|
|
137
|
-
this.part[p.getAttribute('part')] = p
|
|
138
|
-
)
|
|
139
|
-
|
|
140
|
-
// expose original template on root for processors that need it
|
|
130
|
+
// expose original template for processors
|
|
141
131
|
if (tpl) root.template = tpl
|
|
142
132
|
|
|
143
133
|
// build initial state from prop defaults + current attributes
|
|
@@ -148,15 +138,25 @@ function define(el) {
|
|
|
148
138
|
state[p.name] = attrVal != null ? coerce(attrVal) : this._de_props[p.name]
|
|
149
139
|
}
|
|
150
140
|
|
|
151
|
-
// run processor
|
|
141
|
+
// run processor or clone template
|
|
152
142
|
let p = DefineElement.processor
|
|
153
143
|
if (p) {
|
|
144
|
+
// processor owns template mounting — no pre-clone
|
|
154
145
|
let result = p(root, state)
|
|
155
146
|
this.state = result || state
|
|
156
147
|
} else {
|
|
148
|
+
if (tpl && !root.firstChild) {
|
|
149
|
+
root.appendChild(tpl.content.cloneNode(true))
|
|
150
|
+
}
|
|
157
151
|
this.state = state
|
|
158
152
|
}
|
|
159
153
|
|
|
154
|
+
// collect parts (after processor, since it populates root)
|
|
155
|
+
this.part = {}
|
|
156
|
+
root.querySelectorAll('[part]').forEach(p =>
|
|
157
|
+
this.part[p.getAttribute('part')] = p
|
|
158
|
+
)
|
|
159
|
+
|
|
160
160
|
// run script (errors in injected scripts don't throw — browser reports via onerror)
|
|
161
161
|
if (scriptText) runScript(scriptText, this)
|
|
162
162
|
}
|
|
@@ -173,6 +173,7 @@ function define(el) {
|
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
attributeChangedCallback(name, oldVal, newVal) {
|
|
176
|
+
if (this._de_reflecting) return
|
|
176
177
|
let def = propMap[name]
|
|
177
178
|
if (!def) return
|
|
178
179
|
|
|
@@ -197,9 +198,13 @@ function define(el) {
|
|
|
197
198
|
let val = coerce(v)
|
|
198
199
|
this._de_props[p.name] = val
|
|
199
200
|
if (this._de_init && this.state) this.state[p.name] = val
|
|
201
|
+
// skip reflection for functions — can't round-trip through attributes
|
|
202
|
+
if (typeof val === 'function') return
|
|
200
203
|
let s = serialize(val, p.type)
|
|
204
|
+
this._de_reflecting = true
|
|
201
205
|
if (s == null) this.removeAttribute(p.name)
|
|
202
206
|
else this.setAttribute(p.name, s)
|
|
207
|
+
this._de_reflecting = false
|
|
203
208
|
},
|
|
204
209
|
enumerable: true,
|
|
205
210
|
configurable: true
|
|
@@ -262,3 +267,5 @@ class DefineElement extends HTMLElement {
|
|
|
262
267
|
DefineElement.processor = null
|
|
263
268
|
|
|
264
269
|
customElements.define('define-element', DefineElement)
|
|
270
|
+
|
|
271
|
+
export { DefineElement, define }
|
package/package.json
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "define-element",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "A custom element to define custom elements",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "define-element.js",
|
|
7
7
|
"unpkg": "define-element.js",
|
|
8
|
-
"files": [
|
|
8
|
+
"files": [
|
|
9
|
+
"define-element.js"
|
|
10
|
+
],
|
|
9
11
|
"sideEffects": true,
|
|
10
12
|
"scripts": {
|
|
11
13
|
"test": "node -r ./test/register.cjs test/test.js"
|