jq79 0.4.8 → 0.4.10
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/jq79.cjs +11 -11
- package/dist/jq79.cjs.map +1 -1
- package/dist/jq79.global.js +11 -11
- package/dist/jq79.global.js.map +1 -1
- package/dist/jq79.js +12 -12
- package/dist/jq79.js.map +1 -1
- package/package.json +1 -1
- package/src/dom.ts +8 -2
- package/src/jq79.ts +24 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jq79",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.10",
|
|
4
4
|
"description": "Mini reactive component library: single-file components, Svelte-style setup scripts, fine-grained proxy reactivity. Single-file build, zero dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"reactive",
|
package/src/dom.ts
CHANGED
|
@@ -173,10 +173,16 @@ function sanitizeNode(node: HTMLElement, depth: number, allowUrl?: AllowUrl): HT
|
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
export function sanitizeHTML(html: string, options?: SanitizeOptions): string {
|
|
176
|
-
|
|
176
|
+
// parsear con <template> en vez de con DOMParser: el contenido de un template
|
|
177
|
+
// es un documento inerte igual (ni scripts ni imágenes se ejecutan al asignar
|
|
178
|
+
// innerHTML), pero el parseo de fragmento conserva el espacio en blanco inicial
|
|
179
|
+
// que el modo "before body" de un documento completo descartaría - así una
|
|
180
|
+
// primera línea indentada (un diff, un <pre>) llega con su sangría intacta
|
|
181
|
+
const template = document.createElement('template');
|
|
182
|
+
template.innerHTML = html;
|
|
177
183
|
const container = document.createElement('div');
|
|
178
184
|
|
|
179
|
-
appendSanitizedChildren(
|
|
185
|
+
appendSanitizedChildren(template.content, container, 0, options?.allowUrl);
|
|
180
186
|
|
|
181
187
|
return container.innerHTML;
|
|
182
188
|
}
|
package/src/jq79.ts
CHANGED
|
@@ -97,6 +97,12 @@ const interpolate = (template: string, scope: Record<string, any>): string =>
|
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
const CONTROL_ATTRS = new Set([":attrs", ":class", ":value", ":checked", ":selected", ":if", ":elseif", ":else", ":each", ":key", ":with", ":text", ":html", ":html.allowed"])
|
|
100
|
+
|
|
101
|
+
// a control attribute is one the static-attr loop and nested-component prop
|
|
102
|
+
// collection must skip. The set holds the fixed names; `:class.<name>` (the
|
|
103
|
+
// single-flag shorthand) is open-ended, so it's matched by prefix - it can't be
|
|
104
|
+
// enumerated into the set
|
|
105
|
+
const isControlAttr = (attr: string): boolean => CONTROL_ATTRS.has(attr) || attr.startsWith(":class.")
|
|
100
106
|
// `item in items`, `item, i in items`, `(value, key) in props` - the second
|
|
101
107
|
// binding is the array index or the object key, parens optional (Vue-style).
|
|
102
108
|
// The list expression can span lines, so it matches [\s\S] rather than `.`
|
|
@@ -238,7 +244,7 @@ const renderNestedComponent = (key: string, node: TemplateNode, scope: Record<st
|
|
|
238
244
|
Object.entries(node.attrs).forEach(([attr, value]) => {
|
|
239
245
|
// the parent's scope stamp is stamped on every template element, this tag
|
|
240
246
|
// included - it's not a prop, and the child renders under its own scope
|
|
241
|
-
if (attr === SCOPE_ATTR ||
|
|
247
|
+
if (attr === SCOPE_ATTR || isControlAttr(attr)) return
|
|
242
248
|
if (attr.startsWith("@")) {
|
|
243
249
|
events.push([attr, value])
|
|
244
250
|
} else if (attr === ":model" || attr.startsWith(":model.")) {
|
|
@@ -491,7 +497,7 @@ const renderNode = (node: TemplateNode, outerScope: Record<string, any>, fx: Eff
|
|
|
491
497
|
if (!(el instanceof HTMLUnknownElement || node.tag.includes("-"))) {
|
|
492
498
|
console.warn(`jq79: ${key} on <${node.tag}> does nothing - :model binds component tags only (for now)`)
|
|
493
499
|
}
|
|
494
|
-
} else if (!
|
|
500
|
+
} else if (!isControlAttr(key)) el.setAttribute(key, value)
|
|
495
501
|
})
|
|
496
502
|
|
|
497
503
|
const bindExpr = node.attrs[":attrs"]
|
|
@@ -509,17 +515,27 @@ const renderNode = (node: TemplateNode, outerScope: Record<string, any>, fx: Eff
|
|
|
509
515
|
})
|
|
510
516
|
}
|
|
511
517
|
|
|
512
|
-
// :class="expr" adds classes on top of the static `class` attribute
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
//
|
|
518
|
+
// :class="expr" adds classes on top of the static `class` attribute, and
|
|
519
|
+
// :class.<name>="expr" is the single-flag shorthand for `{ <name>: expr }`
|
|
520
|
+
// (the name routed through classNames, so an empty `:class.` can't reach
|
|
521
|
+
// classList.add, which throws on ""). Both feed one effect and one set of
|
|
522
|
+
// added classes: only classes this binding added are ever removed, so the
|
|
523
|
+
// static list survives every re-run, even when the expression names one of
|
|
524
|
+
// its classes and then drops it (class="btn" :class="{ btn: cond }" keeps
|
|
525
|
+
// btn on false)
|
|
516
526
|
const classExpr = node.attrs[":class"]
|
|
517
|
-
|
|
527
|
+
const classToggles = Object.entries(node.attrs)
|
|
528
|
+
.filter(([key]) => key.startsWith(":class."))
|
|
529
|
+
.map(([key, expr]): [string, string] => [key.slice(":class.".length), expr])
|
|
530
|
+
if (classExpr !== undefined || classToggles.length) {
|
|
518
531
|
const staticClasses = new Set(classNames(node.attrs.class ?? ""))
|
|
519
532
|
let bound: string[] = []
|
|
520
533
|
|
|
521
534
|
fx.effect(() => {
|
|
522
|
-
const next = classNames(evalExpr(classExpr, scope))
|
|
535
|
+
const next = classExpr !== undefined ? classNames(evalExpr(classExpr, scope)) : []
|
|
536
|
+
classToggles.forEach(([name, expr]) => {
|
|
537
|
+
if (evalExpr(expr, scope)) next.push(...classNames(name))
|
|
538
|
+
})
|
|
523
539
|
bound.forEach(name => {
|
|
524
540
|
if (!next.includes(name) && !staticClasses.has(name)) el.classList.remove(name)
|
|
525
541
|
})
|