mi-element 0.9.3 → 0.9.5

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/dist/html.js CHANGED
@@ -33,9 +33,10 @@ const unsafeHtml = str => new UnsafeHtml(str), escMap = {
33
33
  '>': '>',
34
34
  '"': '"',
35
35
  "'": '''
36
- }, esc = string => string.replace(/[&<>"']/g, tag => escMap[tag]), escHtml = string => string instanceof UnsafeHtml ? string : unsafeHtml(esc('' + string)), escValue = any => {
36
+ }, escRe = /[&<>"']/g, esc = string => string.replace(escRe, tag => escMap[tag]), escHtml = string => string instanceof UnsafeHtml ? string : unsafeHtml(esc('' + string)), escValue = any => {
37
37
  if (any instanceof UnsafeHtml) return any;
38
- if ([ 'object', 'function' ].includes(typeof any)) {
38
+ const t = typeof any;
39
+ if ("object" === t || "function" === t) {
39
40
  const key = globalRenderCache.set(any);
40
41
  return unsafeHtml(key);
41
42
  }
@@ -45,33 +46,44 @@ const unsafeHtml = str => new UnsafeHtml(str), escMap = {
45
46
  }, ...values.map(val => Array.isArray(val) ? val.map(escValue).join('') : escValue(val))));
46
47
 
47
48
  function render(node, template, handlers = {}) {
48
- const refs = {}, div = document.createElement('div');
49
- div.innerHTML = template.toString();
50
- for (let child of Array.from(div.children)) renderAttrs(child, handlers, refs),
51
- node.appendChild(child);
49
+ const refs = {};
50
+ node.innerHTML = template.toString();
51
+ for (let i = 0, l = node.children.length; i < l; i++) renderAttrs(node.children[i], handlers, refs);
52
52
  return refs;
53
53
  }
54
54
 
55
+ const REF = 'ref', REF_Q = '[ref]';
56
+
55
57
  function renderAttrs(node, handlers = {}, refs = {}) {
56
- if (node.nodeType === Node.ELEMENT_NODE) for (let attr of node.attributes) {
57
- const startsWith = attr.name[0], name = attr.name.slice(1);
58
- let rm = 0;
59
- if ('?' === startsWith) toJson(attr.value) ? node.setAttribute(name, '') : node.removeAttribute(name),
60
- rm = 1; else if ('...' === attr.name) {
61
- const obj = globalRenderCache.get(attr.value);
62
- if (obj && 'object' == typeof obj) for (const [k, v] of Object.entries(obj)) node[k] = v;
63
- rm = 1;
64
- } else if ('.' === startsWith) node[name] = globalRenderCache.get(attr.value) ?? attr.value,
65
- rm = 1; else if ('@' === startsWith) {
66
- const handlerName = attr.value, fn = globalRenderCache.get(handlerName);
67
- fn ? node.addEventListener(name, e => fn(e)) : 'function' == typeof handlers[handlerName] && node.addEventListener(name, e => handlers[handlerName](e)),
68
- rm = 1;
69
- } else 'ref' === attr.name && (refs[attr.value] = node, rm = 1);
70
- rm && requestAnimationFrame(() => {
71
- node.removeAttribute(attr.name);
72
- });
58
+ if (node.nodeType === Node.ELEMENT_NODE) {
59
+ const rmAttrs = [], attrs = node.attributes;
60
+ for (let i = 0, l = attrs.length; i < l; i++) {
61
+ const attr = attrs[i], attrName = attr.name, code = attrName.charCodeAt(0), name = attrName.slice(1);
62
+ let rm = 0;
63
+ if (63 === code) toJson(attr.value) ? node.setAttribute(name, '') : node.removeAttribute(name),
64
+ rm = 1; else if ('...' === attr.name) {
65
+ const obj = globalRenderCache.get(attr.value);
66
+ if (obj && "object" == typeof obj) for (const [k, v] of Object.entries(obj)) node[k] = v;
67
+ rm = 1;
68
+ } else if (46 === code) node[name] = globalRenderCache.get(attr.value) ?? attr.value,
69
+ rm = 1; else if (64 === code) {
70
+ const handlerName = attr.value, fn = globalRenderCache.get(handlerName);
71
+ fn ? node.addEventListener(name, fn) : "function" == typeof handlers[handlerName] && node.addEventListener(name, handlers[handlerName]),
72
+ rm = 1;
73
+ } else attr.name === REF && (refs[attr.value] = node, rm = 1);
74
+ rm && rmAttrs.push(attr.name);
75
+ }
76
+ for (let i = 0, l = rmAttrs.length; i < l; i++) node.removeAttribute(rmAttrs[i]);
77
+ }
78
+ if (customElements.get(node.localName)) {
79
+ const q = node.querySelectorAll(REF_Q);
80
+ for (let el of q) {
81
+ const refName = el.getAttribute(REF);
82
+ refName && !refs[refName] && (refs[refName] = el);
83
+ }
84
+ return refs;
73
85
  }
74
- if (!node.children?.length || customElements.get(node.localName)) return refs;
86
+ if (!node.children?.length) return refs;
75
87
  for (let child of Array.from(node.children)) renderAttrs(child, handlers, refs);
76
88
  return refs;
77
89
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mi-element",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "description": "Build lightweight reactive micro web-components",
5
5
  "keywords": [],
6
6
  "homepage": "https://github.com/commenthol/mi-element/tree/main/packages/mi-element#readme",
package/src/html.js CHANGED
@@ -63,7 +63,9 @@ const escMap = {
63
63
  "'": '&#39;'
64
64
  }
65
65
 
66
- const esc = (string) => string.replace(/[&<>"']/g, (tag) => escMap[tag])
66
+ const escRe = /[&<>"']/g
67
+
68
+ const esc = (string) => string.replace(escRe, (tag) => escMap[tag])
67
69
 
68
70
  /**
69
71
  * escape HTML and prevent double escaping of '&'
@@ -77,6 +79,9 @@ export const escHtml = (string) =>
77
79
  // @ts-expect-error
78
80
  string instanceof UnsafeHtml ? string : unsafeHtml(esc('' + string))
79
81
 
82
+ const OBJECT = 'object'
83
+ const FUNCTION = 'function'
84
+
80
85
  /**
81
86
  * escape any value for HTML context; objects and functions are stored in the render cache
82
87
  * @param {any} any
@@ -87,7 +92,8 @@ const escValue = (any) => {
87
92
  // @ts-expect-error
88
93
  return any
89
94
  }
90
- if (['object', 'function'].includes(typeof any)) {
95
+ const t = typeof any
96
+ if (t === OBJECT || t === FUNCTION) {
91
97
  const key = globalRenderCache.set(any)
92
98
  return unsafeHtml(key)
93
99
  }
@@ -117,25 +123,26 @@ export const html = (strings, ...values) =>
117
123
  /**
118
124
  * render HTML template into given node with support for special attributes
119
125
  *
120
- * @param {Element} node to append rendered content
126
+ * @param {Element} node to render content
121
127
  * @param {string|UnsafeHtml} template HTML template string
122
128
  * @param {Record<string, Function>|HTMLElement} [handlers={}] event handlers or HTMLElement for method lookup
123
129
  * @returns {Record<string, Element>} references collected
124
130
  */
125
131
  export function render(node, template, handlers = {}) {
126
132
  const refs = {}
127
- const div = document.createElement('div')
128
- div.innerHTML = template.toString()
129
- // don't understand why `for (let child of div.children)` does not work here
130
- for (let child of Array.from(div.children)) {
133
+ node.innerHTML = template.toString()
134
+ for (let i = 0, l = node.children.length; i < l; i++) {
135
+ const child = node.children[i]
131
136
  // @ts-expect-error
132
137
  renderAttrs(child, handlers, refs)
133
- node.appendChild(child)
134
138
  }
135
139
  // @ts-expect-error
136
140
  return refs
137
141
  }
138
142
 
143
+ const REF = 'ref'
144
+ const REF_Q = '[ref]'
145
+
139
146
  /**
140
147
  * Post-processing of rendered nodes to handle special attributes:
141
148
  *
@@ -156,11 +163,15 @@ export function render(node, template, handlers = {}) {
156
163
  */
157
164
  export function renderAttrs(node, handlers = {}, refs = {}) {
158
165
  if (node.nodeType === Node.ELEMENT_NODE) {
159
- for (let attr of node.attributes) {
160
- const startsWith = attr.name[0]
161
- const name = attr.name.slice(1)
166
+ const rmAttrs = []
167
+ const attrs = node.attributes
168
+ for (let i = 0, l = attrs.length; i < l; i++) {
169
+ const attr = attrs[i]
170
+ const attrName = attr.name
171
+ const code = attrName.charCodeAt(0)
172
+ const name = attrName.slice(1)
162
173
  let rm = 0
163
- if (startsWith === '?') {
174
+ if (code === 63 /* '?' */) {
164
175
  // boolean attributes
165
176
  if (toJson(attr.value)) {
166
177
  node.setAttribute(name, '')
@@ -171,41 +182,52 @@ export function renderAttrs(node, handlers = {}, refs = {}) {
171
182
  } else if (attr.name === '...') {
172
183
  // spread attribute
173
184
  const obj = globalRenderCache.get(attr.value)
174
- if (obj && typeof obj === 'object') {
185
+ if (obj && typeof obj === OBJECT) {
175
186
  for (const [k, v] of Object.entries(obj)) {
176
187
  node[k] = v
177
188
  }
178
189
  }
179
190
  rm = 1
180
- } else if (startsWith === '.') {
191
+ } else if (code === 46 /* '.' */) {
181
192
  // property binding
182
193
  node[name] = globalRenderCache.get(attr.value) ?? attr.value
183
194
  rm = 1
184
- } else if (startsWith === '@') {
195
+ } else if (code === 64 /* '@' */) {
185
196
  // event listener
186
197
  const handlerName = attr.value
187
198
  const fn = globalRenderCache.get(handlerName)
188
199
  if (fn) {
189
- node.addEventListener(name, (e) => fn(e))
190
- } else if (typeof handlers[handlerName] === 'function') {
191
- node.addEventListener(name, (e) => handlers[handlerName](e))
200
+ node.addEventListener(name, fn)
201
+ } else if (typeof handlers[handlerName] === FUNCTION) {
202
+ node.addEventListener(name, handlers[handlerName])
192
203
  }
193
204
  rm = 1
194
- } else if (attr.name === 'ref') {
205
+ } else if (attr.name === REF) {
195
206
  // element reference - remove as well to prevent collection by other processors
196
- const refName = attr.value
197
- refs[refName] = node
207
+ refs[attr.value] = node
198
208
  rm = 1
199
209
  }
200
210
  if (rm) {
201
- requestAnimationFrame(() => {
202
- node.removeAttribute(attr.name)
203
- })
211
+ rmAttrs.push(attr.name)
204
212
  }
205
213
  }
214
+ for (let i = 0, l = rmAttrs.length; i < l; i++) {
215
+ node.removeAttribute(rmAttrs[i])
216
+ }
217
+ }
218
+ // early abort if custom element but resolve slotted refs
219
+ if (customElements.get(node.localName)) {
220
+ const q = node.querySelectorAll(REF_Q)
221
+ for (let el of q) {
222
+ const refName = el.getAttribute(REF)
223
+ if (refName && !refs[refName]) {
224
+ refs[refName] = el
225
+ }
226
+ }
227
+ return refs
206
228
  }
207
- // early abort if no children or custom element
208
- if (!node.children?.length || customElements.get(node.localName)) {
229
+ // early abort if no children
230
+ if (!node.children?.length) {
209
231
  return refs
210
232
  }
211
233
  for (let child of Array.from(node.children)) {
package/types/html.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * render HTML template into given node with support for special attributes
3
3
  *
4
- * @param {Element} node to append rendered content
4
+ * @param {Element} node to render content
5
5
  * @param {string|UnsafeHtml} template HTML template string
6
6
  * @param {Record<string, Function>|HTMLElement} [handlers={}] event handlers or HTMLElement for method lookup
7
7
  * @returns {Record<string, Element>} references collected