mi-element 0.9.2 → 0.9.4
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 +44 -27
- package/dist/index.js +1 -1
- package/dist/styling.js +10 -4
- package/package.json +1 -1
- package/src/html.js +43 -18
- package/src/index.js +9 -2
- package/src/styling.js +33 -3
- package/types/html.d.ts +3 -2
- package/types/index.d.ts +2 -2
- package/types/styling.d.ts +2 -0
package/dist/html.js
CHANGED
|
@@ -2,21 +2,26 @@ import { toJson } from './utils.js';
|
|
|
2
2
|
|
|
3
3
|
const globalRenderCache = new class {
|
|
4
4
|
cnt=0;
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
cache=new Map;
|
|
6
|
+
last=0;
|
|
7
|
+
get size() {
|
|
8
|
+
return this.cache.size;
|
|
9
|
+
}
|
|
7
10
|
_inc() {
|
|
8
11
|
return this.cnt = 268435455 & ++this.cnt, this.cnt;
|
|
9
12
|
}
|
|
10
13
|
clear() {
|
|
11
|
-
this.cnt = 0, this.
|
|
14
|
+
this.cnt = 0, this.cache.clear();
|
|
12
15
|
}
|
|
13
16
|
set(value) {
|
|
14
|
-
const
|
|
15
|
-
|
|
17
|
+
const now = Date.now();
|
|
18
|
+
this.last < now && this.cache.clear(), this.last = now + 5e3;
|
|
19
|
+
const key = '__rc:' + this._inc().toString(36);
|
|
20
|
+
return this.cache.set(key, value), key;
|
|
16
21
|
}
|
|
17
22
|
get(key) {
|
|
18
|
-
const
|
|
19
|
-
return this.
|
|
23
|
+
const value = this.cache.get(key);
|
|
24
|
+
return this.cache.delete(key), value;
|
|
20
25
|
}
|
|
21
26
|
};
|
|
22
27
|
|
|
@@ -28,9 +33,9 @@ const unsafeHtml = str => new UnsafeHtml(str), escMap = {
|
|
|
28
33
|
'>': '>',
|
|
29
34
|
'"': '"',
|
|
30
35
|
"'": '''
|
|
31
|
-
}, esc = string => string.replace(/[&<>"']/g, tag => escMap[tag]), escHtml = string => string instanceof UnsafeHtml ? string : unsafeHtml(esc('' + string)), escValue = any => {
|
|
36
|
+
}, esc = string => string.replace(/[&<>"']/g, tag => escMap[tag]), escHtml = string => string instanceof UnsafeHtml ? string : unsafeHtml(esc('' + string)), OBJECT = 'object', FUNCTION = 'function', escValue = any => {
|
|
32
37
|
if (any instanceof UnsafeHtml) return any;
|
|
33
|
-
if ([
|
|
38
|
+
if ([ OBJECT, FUNCTION ].includes(typeof any)) {
|
|
34
39
|
const key = globalRenderCache.set(any);
|
|
35
40
|
return unsafeHtml(key);
|
|
36
41
|
}
|
|
@@ -47,26 +52,38 @@ function render(node, template, handlers = {}) {
|
|
|
47
52
|
return refs;
|
|
48
53
|
}
|
|
49
54
|
|
|
55
|
+
const REF = 'ref', REF_Q = '[ref]';
|
|
56
|
+
|
|
50
57
|
function renderAttrs(node, handlers = {}, refs = {}) {
|
|
51
|
-
if (node.nodeType === Node.ELEMENT_NODE)
|
|
52
|
-
const
|
|
53
|
-
let
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
58
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
59
|
+
const rmFns = [];
|
|
60
|
+
for (let attr of node.attributes) {
|
|
61
|
+
const startsWith = attr.name[0], name = attr.name.slice(1);
|
|
62
|
+
let rm = 0;
|
|
63
|
+
if ('?' === startsWith) 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 && typeof obj === OBJECT) for (const [k, v] of Object.entries(obj)) node[k] = v;
|
|
67
|
+
rm = 1;
|
|
68
|
+
} else if ('.' === startsWith) node[name] = globalRenderCache.get(attr.value) ?? attr.value,
|
|
69
|
+
rm = 1; else if ('@' === startsWith) {
|
|
70
|
+
const handlerName = attr.value, fn = globalRenderCache.get(handlerName);
|
|
71
|
+
fn ? node.addEventListener(name, e => fn(e)) : typeof handlers[handlerName] === FUNCTION && node.addEventListener(name, e => handlers[handlerName](e)),
|
|
72
|
+
rm = 1;
|
|
73
|
+
} else attr.name === REF && (refs[attr.value] = node, rm = 1);
|
|
74
|
+
rm && rmFns.push([ node, attr.name ]);
|
|
75
|
+
}
|
|
76
|
+
rmFns.forEach(([node, name]) => node.removeAttribute(name));
|
|
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;
|
|
68
85
|
}
|
|
69
|
-
if (!node.children?.length
|
|
86
|
+
if (!node.children?.length) return refs;
|
|
70
87
|
for (let child of Array.from(node.children)) renderAttrs(child, handlers, refs);
|
|
71
88
|
return refs;
|
|
72
89
|
}
|
package/dist/index.js
CHANGED
|
@@ -8,6 +8,6 @@ export { refsBySelector } from './refs.js';
|
|
|
8
8
|
|
|
9
9
|
export { Store } from './store.js';
|
|
10
10
|
|
|
11
|
-
export { addGlobalStyles, classNames, css, styleMap } from './styling.js';
|
|
11
|
+
export { addGlobalStyles, classNames, css, escCss, styleMap, unsafeCss } from './styling.js';
|
|
12
12
|
|
|
13
13
|
export { default as Signal } from 'mi-signal';
|
package/dist/styling.js
CHANGED
|
@@ -3,7 +3,7 @@ import { camelToKebabCase } from './case.js';
|
|
|
3
3
|
const classNames = (...args) => {
|
|
4
4
|
const classList = [];
|
|
5
5
|
return args.forEach(arg => {
|
|
6
|
-
arg && ('string' == typeof arg ? classList.push(arg) :
|
|
6
|
+
arg && ('string' == typeof arg ? classList.push(arg) : 'object' == typeof arg && Object.entries(arg).forEach(([key, value]) => {
|
|
7
7
|
value && classList.push(key);
|
|
8
8
|
}));
|
|
9
9
|
}), classList.join(' ');
|
|
@@ -26,8 +26,14 @@ function addGlobalStyles(renderRoot) {
|
|
|
26
26
|
})), globalSheets));
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
class UnsafeCss extends String {}
|
|
30
|
+
|
|
31
|
+
const unsafeCss = str => new UnsafeCss(str), escMap = {
|
|
32
|
+
'&': '\\26 ',
|
|
33
|
+
'<': '\\3c ',
|
|
34
|
+
'>': '\\3e '
|
|
35
|
+
}, escCss = string => string instanceof UnsafeCss ? string : unsafeCss((string => string.replace(/[&<>]/g, tag => escMap[tag]))('' + string)), css = (strings, ...values) => String.raw({
|
|
30
36
|
raw: strings
|
|
31
|
-
}, ...values);
|
|
37
|
+
}, ...values.map(escCss));
|
|
32
38
|
|
|
33
|
-
export { addGlobalStyles, classNames, css, styleMap };
|
|
39
|
+
export { addGlobalStyles, classNames, css, escCss, styleMap, unsafeCss };
|
package/package.json
CHANGED
package/src/html.js
CHANGED
|
@@ -5,8 +5,12 @@ import { toJson } from './utils.js'
|
|
|
5
5
|
*/
|
|
6
6
|
class RenderCache {
|
|
7
7
|
cnt = 0
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
cache = new Map()
|
|
9
|
+
last = 0
|
|
10
|
+
|
|
11
|
+
get size() {
|
|
12
|
+
return this.cache.size
|
|
13
|
+
}
|
|
10
14
|
|
|
11
15
|
_inc() {
|
|
12
16
|
this.cnt = ++this.cnt & 0xfffffff
|
|
@@ -15,21 +19,24 @@ class RenderCache {
|
|
|
15
19
|
|
|
16
20
|
clear() {
|
|
17
21
|
this.cnt = 0
|
|
18
|
-
this.
|
|
22
|
+
this.cache.clear()
|
|
19
23
|
}
|
|
20
24
|
|
|
21
25
|
set(value) {
|
|
26
|
+
const now = Date.now()
|
|
27
|
+
if (this.last < now) {
|
|
28
|
+
this.cache.clear()
|
|
29
|
+
}
|
|
30
|
+
this.last = now + 5e3
|
|
22
31
|
const key = '__rc:' + this._inc().toString(36)
|
|
23
|
-
|
|
24
|
-
this.map.set(key, ref)
|
|
25
|
-
this.cache.set(ref, value)
|
|
32
|
+
this.cache.set(key, value)
|
|
26
33
|
return key
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
get(key) {
|
|
30
|
-
const
|
|
31
|
-
this.
|
|
32
|
-
return
|
|
37
|
+
const value = this.cache.get(key)
|
|
38
|
+
this.cache.delete(key)
|
|
39
|
+
return value
|
|
33
40
|
}
|
|
34
41
|
}
|
|
35
42
|
|
|
@@ -70,6 +77,9 @@ export const escHtml = (string) =>
|
|
|
70
77
|
// @ts-expect-error
|
|
71
78
|
string instanceof UnsafeHtml ? string : unsafeHtml(esc('' + string))
|
|
72
79
|
|
|
80
|
+
const OBJECT = 'object'
|
|
81
|
+
const FUNCTION = 'function'
|
|
82
|
+
|
|
73
83
|
/**
|
|
74
84
|
* escape any value for HTML context; objects and functions are stored in the render cache
|
|
75
85
|
* @param {any} any
|
|
@@ -80,7 +90,7 @@ const escValue = (any) => {
|
|
|
80
90
|
// @ts-expect-error
|
|
81
91
|
return any
|
|
82
92
|
}
|
|
83
|
-
if ([
|
|
93
|
+
if ([OBJECT, FUNCTION].includes(typeof any)) {
|
|
84
94
|
const key = globalRenderCache.set(any)
|
|
85
95
|
return unsafeHtml(key)
|
|
86
96
|
}
|
|
@@ -129,6 +139,9 @@ export function render(node, template, handlers = {}) {
|
|
|
129
139
|
return refs
|
|
130
140
|
}
|
|
131
141
|
|
|
142
|
+
const REF = 'ref'
|
|
143
|
+
const REF_Q = '[ref]'
|
|
144
|
+
|
|
132
145
|
/**
|
|
133
146
|
* Post-processing of rendered nodes to handle special attributes:
|
|
134
147
|
*
|
|
@@ -149,6 +162,7 @@ export function render(node, template, handlers = {}) {
|
|
|
149
162
|
*/
|
|
150
163
|
export function renderAttrs(node, handlers = {}, refs = {}) {
|
|
151
164
|
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
165
|
+
const rmFns = []
|
|
152
166
|
for (let attr of node.attributes) {
|
|
153
167
|
const startsWith = attr.name[0]
|
|
154
168
|
const name = attr.name.slice(1)
|
|
@@ -164,7 +178,7 @@ export function renderAttrs(node, handlers = {}, refs = {}) {
|
|
|
164
178
|
} else if (attr.name === '...') {
|
|
165
179
|
// spread attribute
|
|
166
180
|
const obj = globalRenderCache.get(attr.value)
|
|
167
|
-
if (obj && typeof obj ===
|
|
181
|
+
if (obj && typeof obj === OBJECT) {
|
|
168
182
|
for (const [k, v] of Object.entries(obj)) {
|
|
169
183
|
node[k] = v
|
|
170
184
|
}
|
|
@@ -180,25 +194,36 @@ export function renderAttrs(node, handlers = {}, refs = {}) {
|
|
|
180
194
|
const fn = globalRenderCache.get(handlerName)
|
|
181
195
|
if (fn) {
|
|
182
196
|
node.addEventListener(name, (e) => fn(e))
|
|
183
|
-
} else if (typeof handlers[handlerName] ===
|
|
197
|
+
} else if (typeof handlers[handlerName] === FUNCTION) {
|
|
184
198
|
node.addEventListener(name, (e) => handlers[handlerName](e))
|
|
185
199
|
}
|
|
186
200
|
rm = 1
|
|
187
|
-
} else if (attr.name ===
|
|
201
|
+
} else if (attr.name === REF) {
|
|
188
202
|
// element reference - remove as well to prevent collection by other processors
|
|
189
203
|
const refName = attr.value
|
|
190
204
|
refs[refName] = node
|
|
191
205
|
rm = 1
|
|
192
206
|
}
|
|
193
207
|
if (rm) {
|
|
194
|
-
|
|
195
|
-
node.removeAttribute(attr.name)
|
|
196
|
-
})
|
|
208
|
+
rmFns.push([node, attr.name])
|
|
197
209
|
}
|
|
198
210
|
}
|
|
211
|
+
// @ts-expect-error
|
|
212
|
+
rmFns.forEach(([node, name]) => node.removeAttribute(name))
|
|
213
|
+
}
|
|
214
|
+
// early abort if custom element but resolve slotted refs
|
|
215
|
+
if (customElements.get(node.localName)) {
|
|
216
|
+
const q = node.querySelectorAll(REF_Q)
|
|
217
|
+
for (let el of q) {
|
|
218
|
+
const refName = el.getAttribute(REF)
|
|
219
|
+
if (refName && !refs[refName]) {
|
|
220
|
+
refs[refName] = el
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return refs
|
|
199
224
|
}
|
|
200
|
-
// early abort if no children
|
|
201
|
-
if (!node.children?.length
|
|
225
|
+
// early abort if no children
|
|
226
|
+
if (!node.children?.length) {
|
|
202
227
|
return refs
|
|
203
228
|
}
|
|
204
229
|
for (let child of Array.from(node.children)) {
|
package/src/index.js
CHANGED
|
@@ -10,11 +10,18 @@ export {
|
|
|
10
10
|
* @typedef {import('./element.js').HostController} HostController
|
|
11
11
|
*/
|
|
12
12
|
export { MiElement, convertType, define } from './element.js'
|
|
13
|
-
export {
|
|
13
|
+
export { html, unsafeHtml, escHtml, render, renderAttrs } from './html.js'
|
|
14
14
|
export { refsBySelector } from './refs.js'
|
|
15
15
|
/**
|
|
16
16
|
* @typedef {import('./store.js').Action} Action
|
|
17
17
|
*/
|
|
18
18
|
export { Store } from './store.js'
|
|
19
|
-
export {
|
|
19
|
+
export {
|
|
20
|
+
classNames,
|
|
21
|
+
styleMap,
|
|
22
|
+
addGlobalStyles,
|
|
23
|
+
css,
|
|
24
|
+
unsafeCss,
|
|
25
|
+
escCss
|
|
26
|
+
} from './styling.js'
|
|
20
27
|
export { default as Signal } from 'mi-signal'
|
package/src/styling.js
CHANGED
|
@@ -11,8 +11,6 @@ export const classNames = (...args) => {
|
|
|
11
11
|
if (!arg) return
|
|
12
12
|
if (typeof arg === 'string') {
|
|
13
13
|
classList.push(arg)
|
|
14
|
-
} else if (Array.isArray(arg)) {
|
|
15
|
-
classList.push(classNames(...arg))
|
|
16
14
|
} else if (typeof arg === 'object') {
|
|
17
15
|
Object.entries(arg).forEach(([key, value]) => {
|
|
18
16
|
if (value) {
|
|
@@ -77,9 +75,41 @@ export function addGlobalStyles(renderRoot) {
|
|
|
77
75
|
renderRoot.adoptedStyleSheets.push(...getGlobalStyleSheets())
|
|
78
76
|
}
|
|
79
77
|
|
|
78
|
+
/**
|
|
79
|
+
* A helper class to avoid double escaping of HTML strings
|
|
80
|
+
*/
|
|
81
|
+
class UnsafeCss extends String {}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* tag a string as css for not to be escaped
|
|
85
|
+
* @param {string} str
|
|
86
|
+
* @returns {string}
|
|
87
|
+
*/
|
|
88
|
+
// @ts-expect-error
|
|
89
|
+
export const unsafeCss = (str) => new UnsafeCss(str)
|
|
90
|
+
|
|
91
|
+
const escMap = {
|
|
92
|
+
'&': '\\26 ',
|
|
93
|
+
'<': '\\3c ',
|
|
94
|
+
'>': '\\3e '
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const esc = (string) => string.replace(/[&<>]/g, (tag) => escMap[tag])
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @see https://mathiasbynens.be/notes/css-escapes
|
|
101
|
+
* Escape a value interpolated into a css tagged template literal.
|
|
102
|
+
* Prevents injection of closing style tags or unexpected CSS constructs.
|
|
103
|
+
* @param {*} string
|
|
104
|
+
* @returns {string}
|
|
105
|
+
*/
|
|
106
|
+
export const escCss = (string) =>
|
|
107
|
+
// @ts-expect-error
|
|
108
|
+
string instanceof UnsafeCss ? string : unsafeCss(esc('' + string))
|
|
109
|
+
|
|
80
110
|
/**
|
|
81
111
|
* Helper literal to show css styles in JS e.g. with
|
|
82
112
|
* https://marketplace.visualstudio.com/items?itemName=Tobermory.es6-string-html
|
|
83
113
|
*/
|
|
84
114
|
export const css = (strings, ...values) =>
|
|
85
|
-
String.raw({ raw: strings }, ...values)
|
|
115
|
+
String.raw({ raw: strings }, ...values.map(escCss))
|
package/types/html.d.ts
CHANGED
|
@@ -40,8 +40,9 @@ declare class UnsafeHtml extends String {
|
|
|
40
40
|
*/
|
|
41
41
|
declare class RenderCache {
|
|
42
42
|
cnt: number;
|
|
43
|
-
|
|
44
|
-
|
|
43
|
+
cache: Map<any, any>;
|
|
44
|
+
last: number;
|
|
45
|
+
get size(): number;
|
|
45
46
|
_inc(): number;
|
|
46
47
|
clear(): void;
|
|
47
48
|
set(value: any): string;
|
package/types/index.d.ts
CHANGED
|
@@ -6,5 +6,5 @@ export type HostController = import("./element.js").HostController;
|
|
|
6
6
|
export type Action = import("./store.js").Action;
|
|
7
7
|
export { ContextConsumer, ContextProvider, ContextRequestEvent } from "./context.js";
|
|
8
8
|
export { MiElement, convertType, define } from "./element.js";
|
|
9
|
-
export {
|
|
10
|
-
export { classNames, styleMap, addGlobalStyles, css } from "./styling.js";
|
|
9
|
+
export { html, unsafeHtml, escHtml, render, renderAttrs } from "./html.js";
|
|
10
|
+
export { classNames, styleMap, addGlobalStyles, css, unsafeCss, escCss } from "./styling.js";
|
package/types/styling.d.ts
CHANGED