From b29acde4a70fe60ff0372d119832ed1acffbbb17 Mon Sep 17 00:00:00 2001 From: alesun Date: Mon, 12 Feb 2024 19:05:12 +0100 Subject: [PATCH] feat(esl-utils): extend `attr` decorator with inherit option to take over the value of declared attribute --- src/modules/esl-utils/decorators/attr.ts | 17 ++++++++++++----- src/modules/esl-utils/dom/attr.ts | 7 +++++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/src/modules/esl-utils/decorators/attr.ts b/src/modules/esl-utils/decorators/attr.ts index 683fb5a1a..c3c4e9dcd 100644 --- a/src/modules/esl-utils/decorators/attr.ts +++ b/src/modules/esl-utils/decorators/attr.ts @@ -1,6 +1,6 @@ import {identity, resolveProperty} from '../misc/functions'; import {parseString, toKebabCase} from '../misc/format'; -import {getAttr, setAttr} from '../dom/attr'; +import {getAttr, getClosestAttr, setAttr} from '../dom/attr'; import type {PropertyProvider} from '../misc/functions'; import type {ESLAttributeDecorator} from '../dom/attr'; @@ -15,6 +15,13 @@ type AttrDescriptor = { name?: string; /** Create getter only */ readonly?: boolean; + /** Find value to inherit across closest elements in DOM tree based on declared attribute name (in case of string format) + * or same attribute name of current element (boolean value). + * Example, attribute 'ignore' with configuration: + * inherit: 'alt-ignore' - searches ignore or data-ignore attr (in case of dataAttr: true) on this element or alt-ignore attribute on closest parent + * inherit: true - searches ignore or data-ignore attr (in case of dataAttr: true) on this element or on closest parent + */ + inherit?: boolean | string; /** Use data-* attribute */ dataAttr?: boolean; /** Default property value. Used if no attribute is present on the element. Empty string by default. Supports provider function. */ @@ -36,14 +43,14 @@ const buildAttrName = export const attr = (config: AttrDescriptor = {}): ESLAttributeDecorator => { return (target: ESLDomElementTarget, propName: string): any => { const attrName = buildAttrName(config.name || propName, !!config.dataAttr); + const closestAttrName = typeof config.inherit === 'string' ? config.inherit : attrName; function get(): T | null { - const val = getAttr(this, attrName); - if (val === null && 'defaultValue' in config) { - return resolveProperty(config.defaultValue, this) as T; - } + const val = config.inherit ? getClosestAttr(this, closestAttrName) || getAttr(this, attrName) : getAttr(this, attrName); + if (val === null && 'defaultValue' in config) return resolveProperty(config.defaultValue, this) as T; return (config.parser || parseString as AttrParser)(val); } + function set(value: T): void { setAttr(this, attrName, (config.serializer as AttrSerializer || identity)(value)); } diff --git a/src/modules/esl-utils/dom/attr.ts b/src/modules/esl-utils/dom/attr.ts index ab9765fdd..93c85c1d8 100644 --- a/src/modules/esl-utils/dom/attr.ts +++ b/src/modules/esl-utils/dom/attr.ts @@ -35,3 +35,10 @@ export function setAttr($el: ESLAttributeTarget, name: string, value: undefined $el.setAttribute(name, value === true ? '' : value); } } + +/** Gets attribute value from the closest element with group behavior settings */ +export function getClosestAttr($el: ESLAttributeTarget, attrName: string): string | null { + if (!($el = resolveDomTarget($el))) return null; + const $closest = $el.closest(`[${attrName}]`); + return $closest ? $closest.getAttribute(attrName) : null; +}