Skip to content

Commit 04065a6

Browse files
committed
feat(render): boolean attrs via Constant and applyBooleanAttr 🚗
- Add Render.applyBooleanAttr and toBoolean; remove attr when false - Add Constant.booleanAttrKeys (checked, disabled, readonly, selected, inert, hidden) - Extend disabled to fieldset, optgroup, option; add readonly, selected, inert, hidden - Refactor applyAttrs to use booleanAttrKeys - One-line tagAliases in Constant
1 parent ec81aed commit 04065a6

File tree

2 files changed

+98
-19
lines changed

2 files changed

+98
-19
lines changed

src/Constant.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,15 @@ export default class Constant {
2121
return key !== 'type' && key !== 'children'
2222
})
2323
)
24+
/** Boolean attribute names; presence means true. */
25+
static readonly booleanAttrKeys: Set<string> = new Set<string>([
26+
'checked',
27+
'disabled',
28+
'readonly',
29+
'selected',
30+
'inert',
31+
'hidden'
32+
])
2433
/** Container tag names for el.* (non-void). */
2534
static readonly containerTags: readonly string[] = [
2635
'div',
@@ -74,8 +83,5 @@ export default class Constant {
7483
'wbr'
7584
])
7685
/** All tag names for el.* aliases (container + void). */
77-
static readonly tagAliases: readonly string[] = [
78-
...Constant.containerTags,
79-
...Constant.voidTags
80-
]
86+
static readonly tagAliases: readonly string[] = [...Constant.containerTags, ...Constant.voidTags]
8187
}

src/Render.ts

Lines changed: 88 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@ export default class Render {
1313

1414
/**
1515
* Apply attrs map to element.
16-
* @description Sets attributes, className, value, checked, disabled.
16+
* @description Sets attributes, className, value, boolean and form attrs.
1717
* @param element - Target element
1818
* @param attrs - Attribute key-value map
1919
*/
2020
static applyAttrs(element: Element, attrs: Types.Attrs): void {
2121
for (const [key, value] of Object.entries(attrs)) {
22+
if (Constant.booleanAttrKeys.has(key)) {
23+
if (Render.applyBooleanAttr(element, key, value)) {
24+
continue
25+
}
26+
}
2227
if (value === undefined || value === null) {
2328
continue
2429
}
@@ -40,20 +45,6 @@ export default class Render {
4045
element.value = attrValueStr
4146
continue
4247
}
43-
if (key === 'checked' && element instanceof HTMLInputElement) {
44-
element.checked = value === true || value === 'true'
45-
continue
46-
}
47-
if (
48-
key === 'disabled' &&
49-
(element instanceof HTMLInputElement ||
50-
element instanceof HTMLButtonElement ||
51-
element instanceof HTMLSelectElement ||
52-
element instanceof HTMLTextAreaElement)
53-
) {
54-
element.disabled = value === true || value === 'true'
55-
continue
56-
}
5748
try {
5849
element.setAttribute(key, attrValueStr)
5950
} catch {
@@ -285,4 +276,86 @@ export default class Render {
285276
}
286277
return String(layoutValue)
287278
}
279+
280+
/**
281+
* Apply boolean attribute to element.
282+
* @description Sets property and removes attribute when false.
283+
* @param element - Target DOM element
284+
* @param attrKey - Attribute name
285+
* @param attrValue - Attribute value
286+
* @returns True if attribute was applied
287+
*/
288+
private static applyBooleanAttr(
289+
element: Element,
290+
attrKey: string,
291+
attrValue: unknown
292+
): boolean {
293+
const isAttrTruthy = attrValue !== undefined && attrValue !== null &&
294+
Render.toBoolean(attrValue)
295+
if (attrKey === 'checked' && element instanceof HTMLInputElement) {
296+
element.checked = isAttrTruthy
297+
if (!isAttrTruthy) {
298+
element.removeAttribute('checked')
299+
}
300+
return true
301+
}
302+
if (attrKey === 'disabled') {
303+
if (
304+
element instanceof HTMLInputElement ||
305+
element instanceof HTMLButtonElement ||
306+
element instanceof HTMLSelectElement ||
307+
element instanceof HTMLTextAreaElement ||
308+
element instanceof HTMLFieldSetElement ||
309+
element instanceof HTMLOptGroupElement ||
310+
element instanceof HTMLOptionElement
311+
) {
312+
element.disabled = isAttrTruthy
313+
if (!isAttrTruthy) {
314+
element.removeAttribute('disabled')
315+
}
316+
return true
317+
}
318+
}
319+
if (attrKey === 'readonly') {
320+
if (element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement) {
321+
element.readOnly = isAttrTruthy
322+
if (!isAttrTruthy) {
323+
element.removeAttribute('readonly')
324+
}
325+
return true
326+
}
327+
}
328+
if (attrKey === 'selected' && element instanceof HTMLOptionElement) {
329+
element.selected = isAttrTruthy
330+
if (!isAttrTruthy) {
331+
element.removeAttribute('selected')
332+
}
333+
return true
334+
}
335+
if (attrKey === 'inert' && element instanceof HTMLElement) {
336+
element.inert = isAttrTruthy
337+
if (!isAttrTruthy) {
338+
element.removeAttribute('inert')
339+
}
340+
return true
341+
}
342+
if (attrKey === 'hidden' && element instanceof HTMLElement) {
343+
element.hidden = isAttrTruthy
344+
if (!isAttrTruthy) {
345+
element.removeAttribute('hidden')
346+
}
347+
return true
348+
}
349+
return false
350+
}
351+
352+
/**
353+
* Normalize attribute value to boolean.
354+
* @description True for true or 'true' string; otherwise false.
355+
* @param attrValue - Raw attribute value
356+
* @returns True when value is true or 'true'
357+
*/
358+
private static toBoolean(attrValue: unknown): boolean {
359+
return attrValue === true || attrValue === 'true'
360+
}
288361
}

0 commit comments

Comments
 (0)