jq79 0.4.1 → 0.4.2
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 +10 -10
- package/dist/jq79.cjs.map +1 -1
- package/dist/jq79.global.js +10 -10
- package/dist/jq79.global.js.map +1 -1
- package/dist/jq79.js +10 -10
- package/dist/jq79.js.map +1 -1
- package/package.json +1 -1
- package/src/jq79.ts +34 -1
package/package.json
CHANGED
package/src/jq79.ts
CHANGED
|
@@ -91,7 +91,7 @@ const interpolate = (template: string, scope: Record<string, any>): string =>
|
|
|
91
91
|
template.replace(/{{\s*([\s\S]+?)\s*}}/g, (_, expr) => evalExpr(expr, scope) ?? "")
|
|
92
92
|
|
|
93
93
|
|
|
94
|
-
const CONTROL_ATTRS = new Set([":attrs", ":if", ":elseif", ":else", ":each", ":key", ":with", ":text", ":html"])
|
|
94
|
+
const CONTROL_ATTRS = new Set([":attrs", ":class", ":if", ":elseif", ":else", ":each", ":key", ":with", ":text", ":html"])
|
|
95
95
|
// `item in items`, `item, i in items`, `(value, key) in props` - the second
|
|
96
96
|
// binding is the array index or the object key, parens optional (Vue-style).
|
|
97
97
|
// The list expression can span lines, so it matches [\s\S] rather than `.`
|
|
@@ -294,6 +294,20 @@ const createWithScope = (expr: string, scope: Record<string, any>): Record<strin
|
|
|
294
294
|
})
|
|
295
295
|
}
|
|
296
296
|
|
|
297
|
+
// what :class accepts, flattened to single class tokens: a string of
|
|
298
|
+
// space-separated names, an array (entries normalized recursively), or an
|
|
299
|
+
// object whose truthy-valued keys are the names (a key may itself hold
|
|
300
|
+
// several). Everything else - null, false, numbers - contributes nothing, so
|
|
301
|
+
// `cond && 'active'` reads naturally. The object form reads each value, so a
|
|
302
|
+
// store-backed flag is tracked per key
|
|
303
|
+
const classNames = (value: any): string[] => {
|
|
304
|
+
if (typeof value === "string") return value.split(/\s+/).filter(Boolean)
|
|
305
|
+
if (Array.isArray(value)) return value.flatMap(classNames)
|
|
306
|
+
if (value !== null && typeof value === "object")
|
|
307
|
+
return Object.entries(value).flatMap(([name, on]) => (on ? classNames(name) : []))
|
|
308
|
+
return []
|
|
309
|
+
}
|
|
310
|
+
|
|
297
311
|
// renders a single element node: static attrs, @event listeners, a reactive
|
|
298
312
|
// :attrs object, and its content - :text/:html override the element's own
|
|
299
313
|
// children with a reactive textContent/innerHTML, otherwise children render
|
|
@@ -353,6 +367,25 @@ const renderNode = (node: TemplateNode, outerScope: Record<string, any>, fx: Eff
|
|
|
353
367
|
})
|
|
354
368
|
}
|
|
355
369
|
|
|
370
|
+
// :class="expr" adds classes on top of the static `class` attribute. Only
|
|
371
|
+
// classes this binding added are ever removed: the static list survives
|
|
372
|
+
// every re-run, even when the expression names one of its classes and then
|
|
373
|
+
// drops it (class="btn" :class="{ btn: cond }" keeps btn on false)
|
|
374
|
+
const classExpr = node.attrs[":class"]
|
|
375
|
+
if (classExpr !== undefined) {
|
|
376
|
+
const staticClasses = new Set(classNames(node.attrs.class ?? ""))
|
|
377
|
+
let bound: string[] = []
|
|
378
|
+
|
|
379
|
+
fx.effect(() => {
|
|
380
|
+
const next = classNames(evalExpr(classExpr, scope))
|
|
381
|
+
bound.forEach(name => {
|
|
382
|
+
if (!next.includes(name) && !staticClasses.has(name)) el.classList.remove(name)
|
|
383
|
+
})
|
|
384
|
+
el.classList.add(...next)
|
|
385
|
+
bound = next
|
|
386
|
+
})
|
|
387
|
+
}
|
|
388
|
+
|
|
356
389
|
// :text="expr" sets textContent reactively, replacing any children.
|
|
357
390
|
// :html="expr" sets innerHTML reactively, sanitizing the value first so
|
|
358
391
|
// untrusted content can't inject scripts/attributes (see sanitizeHTML in
|