From 4c41f44f2b189ee82afb905c64036847e171b63b Mon Sep 17 00:00:00 2001 From: Florin Raducan Date: Wed, 5 Jun 2024 18:18:39 +0300 Subject: [PATCH] #486244 - Add icon active state --- blocks/icon/icon.js | 102 ++++++++++++++++++++++--------- blocks/navigation/navigation.css | 2 +- blocks/navigation/navigation.js | 33 ++++++---- 3 files changed, 95 insertions(+), 42 deletions(-) diff --git a/blocks/icon/icon.js b/blocks/icon/icon.js index 49ca9535..0c07e7f9 100644 --- a/blocks/icon/icon.js +++ b/blocks/icon/icon.js @@ -1,9 +1,21 @@ import ComponentBase from '../../scripts/component-base.js'; +import { stringToJsVal } from '../../scripts/libs.js'; export default class Icon extends ComponentBase { - static observedAttributes = ['data-icon']; + static observedAttributes = ['data-active', 'data-icon']; - nestedComponentsConfig = {}; + #initialIcon = null; + + #activeIcon = null; + + get cache() { + window.ICONS_CACHE ??= {}; + return window.ICONS_CACHE; + } + + get isActive() { + return stringToJsVal(this.dataset.active) === true; + } extendConfig() { return [ @@ -28,59 +40,92 @@ export default class Icon extends ComponentBase { } } - get iconUrl() { - return `assets/icons/${this.iconName}.svg`; + setDefaults() { + super.setDefaults(); + this.nestedComponentsConfig = {}; } - get cache() { - window.ICONS_CACHE = window.ICONS_CACHE || {}; - return window.ICONS_CACHE; + iconUrl(iconName) { + return `assets/icons/${iconName}.svg`; } async connected() { this.setAttribute('aria-hidden', 'true'); } - onAttributeIconChanged({ oldValue, newValue }) { + // Same icon component can be reused with any other icons just by changing the attribute + async onAttributeIconChanged({ oldValue, newValue }) { + if (oldValue === newValue) return; + + // ! The initial and active icon names are separated with a double underline + // ! The active icon is optional; + const [initial, active] = newValue.split('__'); + this.#initialIcon = initial; + this.#activeIcon = active || null; + + // Start loading both icons; + const loadInitialIcon = this.loadIcon(this.#initialIcon); + const loadActiveIcon = this.#activeIcon ? this.loadIcon(this.#activeIcon) : null; + + const isActiveWithIcon = this.isActive && this.#activeIcon; + // Wait only for the current icon + if (isActiveWithIcon) { + await loadActiveIcon; + } else { + await loadInitialIcon; + } + this.displayIcon(isActiveWithIcon ? this.#activeIcon : this.#initialIcon); + } + + // If there is an active icon toggle the icons + async onAttributeActiveChanged({ oldValue, newValue }) { + if (!this.initialized) return; if (oldValue === newValue) return; - this.loadIcon(newValue); + if (!this.#activeIcon) return; + this.displayIcon(this.isActive ? this.#activeIcon : this.#initialIcon); } - async loadIcon(icon) { - this.iconName = icon; - if (!this.cache[this.iconName]) { - this.cache[this.iconName] = { - loading: new Promise((resolve) => { - resolve(this.loadFragment(this.iconUrl)); - }), + displayIcon(iconName) { + this.innerHTML = this.template(iconName); + } + + // Load icon can be used externally to load additional icons in the cache + async loadIcon(iconName) { + // this.iconName = icon; + if (!this.cache[iconName]) { + this.cache[iconName] = { + loading: this.loadFragment(this.iconUrl(iconName), iconName), }; - } else { - await this.cache[this.iconName].loading; - this.innerHTML = this.template(); } - this.classList.add('loaded'); + await this.cache[iconName].loading; } - template() { - const { viewBox } = this.cache[this.iconName]; + template(iconName) { + if (!this.cache[iconName]) return ''; + const { viewBox } = this.cache[iconName]; const attributes = Object.keys({ viewBox }) .map((k) => { - if (this.cache[this.iconName][k]) { - return `${k}="${this.cache[this.iconName][k]}"`; + if (this.cache[iconName][k]) { + return `${k}="${this.cache[iconName][k]}"`; } return ''; }) .join(' '); - return ``; + return ``; } iconTemplate(iconName, svg, viewBox, width, height) { return `${svg.innerHTML}`; } - async processFragment(response) { + async loadFragment(path, iconName) { + if (typeof path !== 'string') return; + const response = await this.getFragment(path); + await this.processFragment(response, iconName); + } + + async processFragment(response, iconName) { if (response.ok) { - const { iconName } = this; this.svg = await response.text(); if (this.svg.match(/(