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 CHANGED
@@ -2,10 +2,9 @@
2
2
 
3
3
  A custom element to define custom elements.
4
4
 
5
- * Components as content, not code — paste into any page, CMS, or markdown
6
- * No class, no build, no framework — one `<script>` tag
7
- * Typed props, scoped styles, shadow DOM, slots, lifecycle
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. Set `.processor` to a `(root, state) => state` functioncalled once per instance after template content is cloned into `root`.
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
- `root` is the render target — the element itself (light DOM) or its `shadowRoot` (shadow DOM), with template content already cloned in. The original `<template>` element is available as `root.template` for processors that need it.
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
- import sprae from 'sprae'
164
+ let DE = customElements.get('define-element')
159
165
 
160
- // sprae matches the signature directly returns a reactive store
161
- customElements.get('define-element').processor = sprae
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
- let DE = customElements.get('define-element')
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 (v-text, v-bind, {{ }})
196
+ // petite-vue
189
197
  import { createApp, reactive } from 'petite-vue'
190
- DE.processor = (root, state) => (createApp(state).mount(root), reactive(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 (x-text, x-bind, @click)
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 ~265-line polyfill is evidence that the W3C proposal is implementable and useful. Ship it natively.
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
- // collect parts
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 (per-definition > global)
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.0.0",
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": ["define-element.js"],
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"