diff --git a/blocks/accordion/accordion.js b/blocks/accordion/accordion.js
index cab0fe39..13117990 100644
--- a/blocks/accordion/accordion.js
+++ b/blocks/accordion/accordion.js
@@ -1,9 +1,6 @@
import ComponentBase from '../../scripts/component-base.js';
-import { componentList } from '../../scripts/component-list/component-list.js';
export default class Accordion extends ComponentBase {
- dependencies = componentList.accordion.module.dependencies;
-
init() {
super.init();
this.setAttribute('role', 'navigation');
diff --git a/blocks/button/button.js b/blocks/button/button.js
index 80888205..3496713c 100644
--- a/blocks/button/button.js
+++ b/blocks/button/button.js
@@ -1,40 +1,3 @@
import ComponentBase from '../../scripts/component-base.js';
-export default class Button extends ComponentBase {
- extendConfig() {
- return [
- ...super.extendConfig(),
- {
- selectors: {
- anchor: ':scope > a',
- ariaText: ':scope > a:has(> raqn-icon, > .icon) > strong',
- },
- },
- ];
- }
-
- init() {
- super.init();
- this.queryElements();
- this.wrapText();
- this.addAriaText();
- }
-
- wrapText() {
- const { anchor, ariaText } = this.elements;
- const wrap = document.createElement('span');
- if (ariaText) return;
- if (!anchor.childNodes) return;
- const label = [...anchor.childNodes].find(({ nodeName }) => nodeName === '#text');
- if (!label) return;
- wrap.textContent = label.textContent;
- label.replaceWith(wrap);
- }
-
- addAriaText() {
- const { anchor, ariaText } = this.elements;
- if (!ariaText) return;
- anchor.setAttribute('aria-label', ariaText.textContent);
- ariaText.remove();
- }
-}
+export default class Button extends ComponentBase {}
diff --git a/blocks/grid-item/grid-item.css b/blocks/grid-item/grid-item.css
index 36dffc43..0e6ec4f9 100644
--- a/blocks/grid-item/grid-item.css
+++ b/blocks/grid-item/grid-item.css
@@ -9,6 +9,14 @@ raqn-grid-item {
justify-self: var(--grid-item-justify);
align-self: var(--grid-item-align);
order: var(--grid-item-order);
+ margin-block-start: var(--grid-item-margin-block-start);
+ margin-block-end: var(--grid-item-margin-block-end);
+ margin-inline-start: var(--grid-item-margin-inline-start);
+ margin-inline-end: var(--grid-item-margin-inline-end);
+ padding-block-start: var(--grid-item-padding-block-start);
+ padding-block-end: var(--grid-item-padding-block-end);
+ padding-inline-start: var(--grid-item-padding-inline-start);
+ padding-inline-end: var(--grid-item-padding-inline-end);
}
/* Make grid item sticky */
diff --git a/blocks/grid/grid.css b/blocks/grid/grid.css
index 3948be10..4c95a4c1 100644
--- a/blocks/grid/grid.css
+++ b/blocks/grid/grid.css
@@ -12,6 +12,17 @@ raqn-grid {
--grid-background: var(--background, black);
--grid-color: var(--text, white);
+ /* Option to add margins/paddings for grid to grid-items
+ Values are set to initial prevent unwanted inheritance. */
+ --grid-item-margin-block-start: initial;
+ --grid-item-margin-block-end: initial;
+ --grid-item-margin-inline-start: initial;
+ --grid-item-margin-inline-end: initial;
+ --grid-item-padding-block-start: initial;
+ --grid-item-padding-block-end: initial;
+ --grid-item-padding-inline-start: initial;
+ --grid-item-padding-inline-end: initial;
+
display: grid;
/* defaults to 2 columns */
diff --git a/blocks/grid/grid.js b/blocks/grid/grid.js
index 13bbb735..732228e3 100644
--- a/blocks/grid/grid.js
+++ b/blocks/grid/grid.js
@@ -1,13 +1,10 @@
import ComponentBase from '../../scripts/component-base.js';
-import { componentList } from '../../scripts/component-list/component-list.js';
import { stringToJsVal } from '../../scripts/libs.js';
export default class Grid extends ComponentBase {
// only one attribute is observed rest is set as css variables directly
static observedAttributes = ['data-reverse'];
- dependencies = componentList.grid.module.dependencies;
-
async onAttributeReverseChanged({ oldValue, newValue }) {
await this.initialization;
diff --git a/blocks/header/header.js b/blocks/header/header.js
index 030dea66..762e69bc 100644
--- a/blocks/header/header.js
+++ b/blocks/header/header.js
@@ -1,12 +1,9 @@
import ComponentBase from '../../scripts/component-base.js';
import { eagerImage, getMeta, metaTags } from '../../scripts/libs.js';
-import { componentList } from '../../scripts/component-list/component-list.js';
const metaHeader = getMeta(metaTags.header.metaName);
export default class Header extends ComponentBase {
- dependencies = componentList.header.module.dependencies;
-
attributesValues = {
all: {
class: ['color-primary'],
@@ -15,15 +12,6 @@ export default class Header extends ComponentBase {
fragmentPath = `${metaHeader}.plain.html`;
- extendConfig() {
- return [
- ...super.extendConfig(),
- {
- addToTargetMethod: 'append',
- },
- ];
- }
-
async init() {
super.init();
eagerImage(this, 1);
diff --git a/blocks/icon/icon.js b/blocks/icon/icon.js
index 45ff1526..e1af4e74 100644
--- a/blocks/icon/icon.js
+++ b/blocks/icon/icon.js
@@ -48,17 +48,11 @@ export default class Icon extends ComponentBase {
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;
if (!newValue) return;
- const [initial, active] = newValue.split('__');
+ const { initial, active, loadActiveIcon, loadInitialIcon } = this.getIcons(newValue);
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) {
@@ -81,6 +75,19 @@ export default class Icon extends ComponentBase {
this.innerHTML = this.template(iconName);
}
+ getIcons(icon) {
+ // ! The initial and active icon names are separated with a double underline
+ // ! The active icon is optional;
+ const [initial, active] = icon.split('__');
+
+ return {
+ initial,
+ active,
+ loadInitialIcon: this.loadIcon(initial),
+ loadActiveIcon: active ? this.loadIcon(active) : null,
+ };
+ }
+
// Load icon can be used externally to load additional icons in the cache
async loadIcon(iconName) {
// this.iconName = icon;
diff --git a/blocks/navigation/navigation.js b/blocks/navigation/navigation.js
index b2075c8a..2a291dc5 100644
--- a/blocks/navigation/navigation.js
+++ b/blocks/navigation/navigation.js
@@ -9,7 +9,7 @@ export default class Navigation extends ComponentBase {
isActive = false;
- #navContentInit = false;
+ navContentInit = false;
navCompactedContentInit = false;
@@ -39,9 +39,9 @@ export default class Navigation extends ComponentBase {
async init() {
super.init();
- this.navContent = this.querySelector('ul');
+ this.elements.navContent = this.querySelector('ul');
this.innerHTML = '';
- this.navCompactedContent = this.navContent.cloneNode(true); // the clone need to be done before `this.navContent` is modified
+ this.elements.navCompactedContent = this.elements.navContent.cloneNode(true); // the clone need to be done before `this.navContent` is modified
this.nav = document.createElement('nav');
this.isCompact = this.dataset.compact === 'true';
this.append(this.nav);
@@ -56,24 +56,24 @@ export default class Navigation extends ComponentBase {
}
setupNav() {
- if (!this.#navContentInit) {
- this.#navContentInit = true;
- this.setupClasses(this.navContent);
+ if (!this.navContentInit) {
+ this.navContentInit = true;
+ this.setupClasses(this.elements.navContent);
}
- this.navButton?.remove();
- this.nav.append(this.navContent);
+ this.nav.append(this.elements.navContent);
}
async setupCompactedNav() {
+ const { navCompactedContent } = this.elements;
+
if (!this.navCompactedContentInit) {
loadAndDefine(componentList.accordion);
this.navCompactedContentInit = true;
- this.setupClasses(this.navCompactedContent, true);
- this.navCompactedContent.addEventListener('click', (e) => this.activate(e));
+ this.setupClasses(navCompactedContent, true);
}
-
this.prepend(this.createButton());
- this.nav.append(this.navCompactedContent);
+ this.nav.append(navCompactedContent);
+ this.addCompactedListeners();
}
onAttributeCompactChanged({ oldValue, newValue }) {
@@ -85,13 +85,7 @@ export default class Navigation extends ComponentBase {
if (this.isCompact) {
this.setupCompactedNav();
} else {
- if (this.navButton) {
- this.isActive = false;
- this.classList.remove('active');
- this.navButton.removeAttribute('aria-expanded');
- this.navIcon.dataset.active = this.isActive;
- this.closeAllLevels();
- }
+ this.cleanCompactedNav();
this.setupNav();
}
}
@@ -103,25 +97,16 @@ export default class Navigation extends ComponentBase {
}
createButton() {
- this.navButton = document.createElement('button');
- this.navButton.setAttribute('aria-label', 'Menu');
- this.navButton.setAttribute('aria-expanded', 'false');
- this.navButton.setAttribute('aria-controls', 'navigation');
- this.navButton.setAttribute('aria-haspopup', 'true');
- this.navButton.setAttribute('type', 'button');
- this.navButton.innerHTML = ``;
- this.navIcon = this.navButton.querySelector('raqn-icon');
-
- this.navButton.addEventListener('click', () => {
- this.isActive = !this.isActive;
- this.classList.toggle('active');
- this.navButton.setAttribute('aria-expanded', this.isActive);
- this.navIcon.dataset.active = this.isActive;
- blockBodyScroll(this.isActive);
- this.closeAllLevels();
- });
-
- return this.navButton;
+ this.elements.navButton = document.createElement('button');
+ const { navButton } = this.elements;
+ navButton.setAttribute('aria-label', 'Menu');
+ navButton.setAttribute('aria-expanded', 'false');
+ navButton.setAttribute('aria-controls', 'navigation');
+ navButton.setAttribute('aria-haspopup', 'true');
+ navButton.setAttribute('type', 'button');
+ navButton.innerHTML = ``;
+ this.elements.navIcon = navButton.querySelector('raqn-icon');
+ return navButton;
}
addIcon(elem) {
@@ -158,6 +143,34 @@ export default class Navigation extends ComponentBase {
});
}
+ addCompactedListeners() {
+ const { navCompactedContent, navButton } = this.elements;
+ navCompactedContent.addEventListener('click', (e) => this.activate(e));
+ navButton.addEventListener('click', (e) => this.toggleNav(e));
+ }
+
+ toggleNav() {
+ const { navIcon, navButton } = this.elements;
+ this.isActive = !this.isActive;
+ this.classList.toggle('active');
+ navButton.setAttribute('aria-expanded', this.isActive);
+ navIcon.dataset.active = this.isActive;
+ blockBodyScroll(this.isActive);
+ this.closeAllLevels();
+ }
+
+ cleanCompactedNav() {
+ if (!this.navCompactedContentInit) return;
+ const { navIcon, navButton } = this.elements;
+
+ this.isActive = false;
+ this.classList.remove('active');
+ navButton.removeAttribute('aria-expanded');
+ navIcon.dataset.active = this.isActive;
+ this.closeAllLevels();
+ navButton.remove();
+ }
+
activate(e) {
if (e.target.tagName.toLowerCase() === 'raqn-icon' || e.target.closest('raqn-icon')) {
e.preventDefault();
diff --git a/blocks/popup-trigger/popup-trigger.js b/blocks/popup-trigger/popup-trigger.js
index 6f4af659..650adb2e 100644
--- a/blocks/popup-trigger/popup-trigger.js
+++ b/blocks/popup-trigger/popup-trigger.js
@@ -7,10 +7,14 @@ export default class PopupTrigger extends ComponentBase {
isClosePopupTrigger = false;
- ariaLabel = null;
-
popupSourceUrl = null;
+ popupConfigId = null;
+
+ elements = {
+ popup: null,
+ };
+
get isActive() {
return this.dataset.active === 'true';
}
@@ -24,27 +28,40 @@ export default class PopupTrigger extends ComponentBase {
triggerIcon: 'raqn-icon',
},
closePopupIdentifier: '#popup-close',
+ triggerPopupIdentifier: '#popup-trigger',
},
];
}
init() {
- this.setAction();
- this.queryElements();
- this.addListeners();
+ this.setAction(this.dataset.action);
+ super.init();
}
- setAction() {
- const { closePopupIdentifier } = this.config;
- const anchorUrl = new URL(this.dataset.action, window.location.origin);
+ setAction(action) {
+ const sourceUrl = URL.parse(action, window.location.origin);
+ if (!sourceUrl) {
+ // eslint-disable-next-line no-console
+ console.warn(`The value provided is not a valid path: ${action}`);
+ return;
+ }
+
+ const { closePopupIdentifier, triggerPopupIdentifier } = this.config;
- if (anchorUrl.hash === closePopupIdentifier) {
+ if (sourceUrl.hash === closePopupIdentifier) {
this.isClosePopupTrigger = true;
- this.dataset.action = anchorUrl.hash;
+ return;
}
+
+ this.popupSourceUrl = sourceUrl.pathname;
+
+ const [, configId] = sourceUrl.hash.split(`${triggerPopupIdentifier}-`);
+
+ if (configId) this.popupConfigId = configId;
}
addListeners() {
+ super.addListeners();
this.elements.popupBtn.addEventListener('click', (e) => {
e.preventDefault();
this.dataset.active = !this.isActive;
@@ -52,21 +69,11 @@ export default class PopupTrigger extends ComponentBase {
}
onAttributeActionChanged({ oldValue, newValue }) {
- if (this.isClosePopupTrigger) {
- return;
- }
+ if (!this.initialized) return;
+ if (this.isClosePopupTrigger) return;
if (oldValue === newValue) return;
- let sourceUrl;
- try {
- sourceUrl = new URL(newValue, window.location.origin);
- } catch (error) {
- // eslint-disable-next-line no-console
- console.warn('The value provided is not a valid path', error);
- return;
- }
-
- this.popupSourceUrl = sourceUrl.pathname;
+ this.setAction(newValue);
if (this.popup) {
this.popup.dataset.url = this.popupSourceUrl;
@@ -92,10 +99,8 @@ export default class PopupTrigger extends ComponentBase {
if (this.isClosePopupTrigger) return;
if (!this.isActive) return;
- this.popup = await this.createPopup();
+ await this.createPopup();
this.addPopupToPage();
- // the icon is initialize async by page loader
- // this.triggerIcon = this.querySelector('raqn-icon');
// Reassign to just toggle after the popup is created;
this.loadPopup = this.togglePopup;
@@ -103,18 +108,20 @@ export default class PopupTrigger extends ComponentBase {
}
async createPopup() {
- loadAndDefine(componentList.popup);
+ await loadAndDefine(componentList.popup);
const popupEl = document.createElement('raqn-popup');
- popupEl.dataset.action = this.popupSourceUrl;
+ popupEl.dataset.url = this.popupSourceUrl;
popupEl.dataset.active = true;
- // Set the popupTrigger property of the popup component to this trigger instance
- popupEl.popupTrigger = this;
- return popupEl;
+ // link popup with popup-trigger
+ popupEl.elements.popupTrigger = this;
+ if (this.popupConfigId) popupEl.setAttribute('config-id', this.popupConfigId);
+
+ this.elements.popup = popupEl;
}
togglePopup() {
- this.popup.dataset.active = this.isActive;
+ this.elements.popup.dataset.active = this.isActive;
this.elements.popupBtn.setAttribute('aria-expanded', this.isActive);
if (this.elements.triggerIcon) {
this.elements.triggerIcon.dataset.active = this.isActive;
@@ -125,7 +132,7 @@ export default class PopupTrigger extends ComponentBase {
}
addPopupToPage() {
- if (!this.popup) return;
- document.body.append(this.popup);
+ if (!this.elements.popup) return;
+ document.body.append(this.elements.popup);
}
}
diff --git a/blocks/popup/popup.css b/blocks/popup/popup.css
index dbfe8eab..23942890 100644
--- a/blocks/popup/popup.css
+++ b/blocks/popup/popup.css
@@ -14,6 +14,7 @@ raqn-popup {
}
raqn-popup:has(.popup__base--flyout) {
+ --popup-grid-area-size: 6;
--popup-grid-area-start: calc(13 - var(--popup-grid-area-size));
--popup-close-btn-area-size: calc(var(--popup-close-btn-padding) * 2);
--popup-close-btn-icon-size: calc(var(--popup-close-btn-padding));
diff --git a/blocks/popup/popup.js b/blocks/popup/popup.js
index 2fb59ae4..6148f24a 100644
--- a/blocks/popup/popup.js
+++ b/blocks/popup/popup.js
@@ -18,11 +18,13 @@ export default class Popup extends ComponentBase {
configPopupAttributes = ['data-type', 'data-size', 'data-offset', 'data-height'];
- /**
- * Optional special property to set a reference to a popupTrigger element which controls this popup.
- * This will automatically control the states of the popupTrigger based on popup interaction.
- */
- popupTrigger = null;
+ elements = {
+ /**
+ * Optional special property to set a reference to a popupTrigger element which controls this popup.
+ * This will automatically control the states of the popupTrigger based on popup interaction.
+ */
+ popupTrigger: null,
+ };
get isActive() {
return this.dataset.active !== 'true';
@@ -32,6 +34,7 @@ export default class Popup extends ComponentBase {
return [
...super.extendConfig(),
{
+ addFragmentContentOnInit: false,
showCloseBtn: true,
selectors: {
popupBase: '.popup__base',
@@ -40,6 +43,7 @@ export default class Popup extends ComponentBase {
popupContent: '.popup__content',
popupOverlay: '.popup__overlay',
popupCloseBtn: '.popup__close-btn',
+ fragmentTarget: '.popup__container',
},
elements: {
sourceUrlAnchor: 'a',
@@ -54,15 +58,17 @@ export default class Popup extends ComponentBase {
}
setBinds() {
+ super.setBinds();
this.closeOnEsc = this.closeOnEsc.bind(this);
}
- onInit() {
+ init() {
this.showPopup(false);
this.createPopupHtml();
- this.setUrlFromTarget();
this.queryElements();
+ this.addListeners();
focusTrap(this.elements.popupContainer, { dynamicContent: true });
+ this.activeOnConnect();
}
createPopupHtml() {
@@ -96,13 +102,8 @@ export default class Popup extends ComponentBase {
`;
}
- addContentFromTarget() {
- const { target } = this.initOptions;
-
- this.elements.popupContent.append(...target.childNodes);
- }
-
addListeners() {
+ super.addListeners();
this.elements.popupCloseBtn.addEventListener('click', () => {
this.dataset.active = false;
});
@@ -111,23 +112,14 @@ export default class Popup extends ComponentBase {
});
}
- connected() {
- this.activeOnConnect();
- }
-
activeOnConnect() {
if (this.isActive) return;
- popupState.closeActivePopup();
- popupState.activePopup = this;
- blockBodyScroll(true);
- this.showPopup(true);
- this.toggleCloseOnEsc(true);
- focusFirstElementInContainer(this.elements.popupContainer);
+ this.openPopup();
}
async addFragmentContent() {
- this.elements.popupContent.innerHTML = await this.fragmentContent;
+ this.elements.popupContent.append(...this.fragmentContent);
}
setInnerBlocks() {
@@ -224,7 +216,6 @@ export default class Popup extends ComponentBase {
blockBodyScroll(true);
await this.addFragmentContent();
this.setInnerBlocks();
- await this.initChildComponents();
this.showPopup(true);
this.updatePopupTrigger(true);
this.toggleCloseOnEsc(true);
@@ -247,7 +238,7 @@ export default class Popup extends ComponentBase {
}
updatePopupTrigger(isActive) {
- if (this.popupTrigger) this.popupTrigger.dataset.active = isActive;
+ if (this.elements.popupTrigger) this.elements.popupTrigger.dataset.active = isActive;
}
showPopup(boolean) {
diff --git a/blocks/sidekick-tools-palette/sidekick-tools-palette.js b/blocks/sidekick-tools-palette/sidekick-tools-palette.js
index 783cb47a..b2fe5ebe 100644
--- a/blocks/sidekick-tools-palette/sidekick-tools-palette.js
+++ b/blocks/sidekick-tools-palette/sidekick-tools-palette.js
@@ -376,7 +376,8 @@ export default class SidekickToolsPalette extends ComponentBase {
});
}
- connected() {
+ init() {
+ super.init();
this.initPalette();
}
}
diff --git a/blocks/theming/theming.js b/blocks/theming/theming.js
index c1fe0a29..ddf22b71 100644
--- a/blocks/theming/theming.js
+++ b/blocks/theming/theming.js
@@ -11,7 +11,6 @@ import {
getBaseUrl,
runTasks,
} from '../../scripts/libs.js';
-import { externalConfig } from '../../scripts/libs/external-config.js';
const k = Object.keys;
@@ -189,10 +188,7 @@ ${k(f)
return {};
}
- const response =
- name === 'component'
- ? externalConfig.loadConfig(true) // use the loader to prevent duplicated calls
- : await fetch(`${name !== 'fontface' ? base : ''}${content}.json`);
+ const response = await fetch(`${name !== 'fontface' ? base : ''}${content}.json`);
return this.processFragment(response, name);
}),
);
diff --git a/head.html b/head.html
index aa013b0d..d5a6621b 100644
--- a/head.html
+++ b/head.html
@@ -1,35 +1,31 @@
+
+
-
-
+
diff --git a/scripts/component-base.js b/scripts/component-base.js
index 04adaf45..d5dc0961 100644
--- a/scripts/component-base.js
+++ b/scripts/component-base.js
@@ -7,11 +7,11 @@ import {
isObject,
deepMerge,
deepMergeByType,
- unFlat,
stringToArray,
mergeUniqueArrays,
stringToJsVal,
runTasks,
+ loadAndDefine,
} from './libs.js';
import { componentList } from './component-list/component-list.js';
import { externalConfig } from './libs/external-config.js';
@@ -23,8 +23,7 @@ export default class ComponentBase extends HTMLElement {
// The order of observedAttributes is the order in which the values from config are added.
static observedAttributes = [];
- // dependencies must to be added and checked locally for cases when components are created inside other components
- static dependencies = componentList[this.componentName]?.module?.dependencies || [];
+ static dependencies; // dynamically added to each constructor in `loadDependencies()`
initialization; // set as promise in constructor();
@@ -60,6 +59,9 @@ export default class ComponentBase extends HTMLElement {
// All settings which are not in `attributesValues` which might require extension in extended components should be in the config.
// Use the `extendConfig()` method to extend the config
config = {
+ addFragmentContentOnInit: true,
+ hideOnInitError: true,
+ listenBreakpoints: false,
selectors: {},
classes: {
showLoader: 'show-loader',
@@ -69,18 +71,20 @@ export default class ComponentBase extends HTMLElement {
dispatches: {
initialized: (uuid) => `initialized:${uuid}`,
},
- listenBreakpoints: false,
- hideOnInitError: true,
// All the component attributes which are not in the `observedAttributes`
knownAttributes: {
configId: 'config-id',
isLoading: 'isloading',
raqnwebcomponent: 'raqnwebcomponent',
},
+ };
+
+ mergeMethods = {
// Merge options for non object values in `attributesValues`
- attributesMergeMethods: {
+ forAttributesValues: {
'**.class': (a, b) => mergeUniqueArrays(a, b),
},
+ forConfig: null,
};
dataAttributesKeys = this.constructor.observedAttributes.flatMap((data) => {
@@ -95,9 +99,12 @@ export default class ComponentBase extends HTMLElement {
constructor() {
super();
+ this.constructor.instancesRef ??= [];
+ this.constructor.instancesRef.push(this);
this.setInitializationPromise();
this.setDefaults();
this.setBinds();
+ this.loadDependencies();
}
/**
@@ -119,6 +126,19 @@ export default class ComponentBase extends HTMLElement {
this.onBreakpointChange = this.onBreakpointChange.bind(this);
}
+ loadDependencies() {
+ if (this.constructor.dependencies) return;
+
+ this.constructor.dependencies = componentList[this.componentName]?.module?.dependencies || [];
+ this.constructor.dependencies.forEach((dependency) => {
+ if (!componentList[dependency]?.module?.path) return;
+ if (window.raqnComponents[this.webComponentName]) return;
+ setTimeout(() => {
+ loadAndDefine(componentList[dependency]);
+ }, 0);
+ });
+ }
+
// Build-in method called after the element is added to the DOM.
async connectedCallback() {
const { knownAttributes, hideOnInitError, dispatches } = this.config;
@@ -150,22 +170,37 @@ export default class ComponentBase extends HTMLElement {
/**
* Do not overwrite this method unless absolutely needed. */
async onConnected() {
- await this.initSettings();
- await this.loadFragment(this.fragmentPath);
- await this.init();
+ await runTasks.call(
+ this,
+ null,
+ this.initSettings,
+ async function loadFragment() {
+ await this.loadFragment(this.fragmentPath);
+ },
+ this.init,
+ );
}
/**
- * Use this method to add the component's functionality */
+ * Use this method to add the component's functionality
+ * If the functionality can generate long blocking tasks consider using runTasks() */
async init() {
+ this.queryElements();
await this.addListeners();
}
async initSettings() {
- this.extendConfigRunner({ field: 'config', method: 'extendConfig' });
- this.setInitialAttributesValues();
- await this.buildExternalConfig();
- this.runConfigsByViewport(); // set the values for current breakpoint
+ await runTasks.call(
+ this,
+ null,
+ function extendConfig() {
+ this.extendConfigRunner({ field: 'mergeMethods', method: 'extendMergeMethods' });
+ this.extendConfigRunner({ field: 'config', method: 'extendConfig' });
+ },
+ this.setInitialAttributesValues,
+ this.buildExternalConfig,
+ this.runConfigsByViewport, // set the values for current breakpoint
+ );
}
// Using the `method` which returns an array of objects it's easier to extend
@@ -173,7 +208,11 @@ export default class ComponentBase extends HTMLElement {
extendConfigRunner({ field, method }) {
const conf = this[method]?.();
if (conf.length <= 1) return;
- this[field] = deepMerge({}, ...conf);
+ this[field] = deepMergeByType(this.mergeMethods.forConfig, {}, ...conf);
+ }
+
+ extendMergeMethods() {
+ return [...(super.mergeMethods?.() || []), this.mergeMethods];
}
extendConfig() {
@@ -217,7 +256,7 @@ export default class ComponentBase extends HTMLElement {
});
this.attributesValues = deepMergeByType(
- this.config.attributesMergeMethods,
+ this.mergeMethods.forAttributesValues,
{},
this.attributesValues,
initialAttributesValues,
@@ -225,28 +264,30 @@ export default class ComponentBase extends HTMLElement {
}
async buildExternalConfig() {
- const unFlatConfig = unFlat(await externalConfig.getConfig(this.componentName, this.configId));
+ const extConfig = await externalConfig.getConfig(this.componentName, this.configId);
+ /**
+ * Any options which are not required to use `attributeChangedCallback`
+ * with different values per breakpoint should be added to this.config */
+ const configExternal = extConfig.config;
+ if (configExternal) {
+ delete extConfig.config;
+ deepMergeByType(this.mergeMethods.forConfig, {}, this.config, configExternal);
+ }
// turn classes to array
- Object.values(unFlatConfig).forEach((value) => {
+ Object.values(extConfig).forEach((value) => {
if (typeof value.class === 'string') {
value.class = stringToArray(value.class, { divider: ' ' });
}
});
- const toMerge = [this.attributesValues, unFlatConfig];
+ const toMerge = [this.attributesValues, extConfig];
if (this.overrideExternalConfig) toMerge.reverse();
this.attributesValues = deepMergeByType(this.config.attributesMergeMethods, {}, ...toMerge);
}
- onBreakpointChange(e) {
- if (e.matches) {
- this.runConfigsByViewport();
- }
- }
-
currentAttributesValues() {
const { name } = this.breakpoints.active;
const currentAttrValues = deepMergeByType(
@@ -319,13 +360,6 @@ export default class ComponentBase extends HTMLElement {
});
}
- // TODO handle this part
- applySetting(config) {
- // delete the setting to run only once on init
- delete this.attributesValues.all.setting;
- deepMerge(this.config, config);
- }
-
/**
* Attributes are assigned before the `connectedCallback` is triggered.
* In some cases a check for `this.initialized` inside `onAttribute${capitalizedAttr}Changed` might be required
@@ -358,12 +392,6 @@ export default class ComponentBase extends HTMLElement {
return attribute?.[activeBrName] ?? attribute?.all;
}
- addListeners() {
- if (Object.keys(this.attributesValues).length >= 1) {
- listenBreakpointChange(this.onBreakpointChange);
- }
- }
-
async loadFragment(path) {
if (typeof path !== 'string') return;
const response = await this.getFragment(path);
@@ -377,27 +405,46 @@ export default class ComponentBase extends HTMLElement {
async processFragment(response) {
if (response.ok) {
this.fragmentContent = await response.text();
- await this.addFragmentContent();
+ await runTasks.call(
+ this,
+ null,
+ this.fragmentVirtualDom,
+ this.fragmentVirtualDomManipulation,
+ this.renderFragment,
+ this.addFragmentContent,
+ );
}
}
- async addFragmentContent() {
- await runTasks.call(
- this,
- null,
- function fragmentVirtualDom() {
- const element = document.createElement('div');
- element.innerHTML = this.fragmentContent;
- return generateVirtualDom(element.childNodes);
- },
- // eslint-disable-next-line prefer-arrow-callback
- async function fragmentVirtualDomManipulation({ fragmentVirtualDom }) {
- await generalManipulation(fragmentVirtualDom);
- },
- function renderFragment({ fragmentVirtualDom }) {
- this.append(...renderVirtualDom(fragmentVirtualDom));
- },
- );
+ fragmentVirtualDom() {
+ const element = document.createElement('div');
+ element.innerHTML = this.fragmentContent;
+ return generateVirtualDom(element.childNodes);
+ }
+
+ async fragmentVirtualDomManipulation({ fragmentVirtualDom }) {
+ await generalManipulation(fragmentVirtualDom);
+ }
+
+ renderFragment({ fragmentVirtualDom }) {
+ this.fragmentContent = renderVirtualDom(fragmentVirtualDom);
+ return { stopTaskRun: !this.config.addFragmentContentOnInit };
+ }
+
+ addFragmentContent() {
+ this.append(...this.fragmentContent);
+ }
+
+ addListeners() {
+ if (Object.keys(this.attributesValues).length >= 1) {
+ listenBreakpointChange(this.onBreakpointChange);
+ }
+ }
+
+ onBreakpointChange(e) {
+ if (e.matches) {
+ this.runConfigsByViewport();
+ }
}
queryElements() {
diff --git a/scripts/component-list/component-list.js b/scripts/component-list/component-list.js
index 90ef9795..b29cb99a 100644
--- a/scripts/component-list/component-list.js
+++ b/scripts/component-list/component-list.js
@@ -184,11 +184,25 @@ export const componentList = {
},
button: {
tag: 'raqn-button',
- method: 'replace',
filterNode(node) {
if (node.tag === 'p' && node.hasOnlyChild('a')) return true;
return false;
},
+ transform(node) {
+ node.tag = this.tag;
+
+ const [textNode] = node.firstChild.queryAll((n) => n.tag === 'textNode', { queryLevel: 2 });
+ const [ariaLabel] = node.firstChild.queryAll(
+ (n) => n.tag === 'strong' && [n.nextSibling?.tag, n.previousSibling?.tag].includes('raqn-icon'),
+ { queryLevel: 1 },
+ );
+ if (!ariaLabel && textNode) {
+ textNode.tag = 'span';
+ } else if (ariaLabel && textNode) {
+ node.firstChild.attributes['aria-label'] = textNode.text;
+ ariaLabel.remove();
+ }
+ },
module: {
path: '/blocks/button/button',
priority: 0,
@@ -197,37 +211,36 @@ export const componentList = {
'popup-trigger': {
tag: 'raqn-popup-trigger',
method: 'replaceWith',
+ popupHash: '#popup-trigger',
+ closeHash: '#popup-close',
filterNode(node) {
if (node.tag === 'a') {
if (node.parentNode.tag === 'raqn-button') {
const { href } = node.attributes;
- const hash = href.substring(href.indexOf('#'));
- if (['#popup-trigger', '#popup-close'].includes(hash)) return true;
+ const [, hash] = href.split(/(?=#)/g);
+ if ([this.popupHash, this.closeHash].some((item) => hash?.startsWith(item))) {
+ return true;
+ }
}
}
return false;
},
transform(node) {
- const { href } = node.attributes;
- const hash = href.substring(href.indexOf('#'));
+ let { href } = node.attributes;
+ const [, hash] = href.split(/(?=#)/g);
+ href = hash.startsWith(this.closeHash) ? this.closeHash : href;
+ node.tag = 'button';
+ node.attributes['aria-expanded'] = 'false';
+ node.attributes['aria-haspopup'] = 'false';
+ delete node.attributes.href;
return [
{
- tag: 'raqn-popup-trigger',
+ tag: this.tag,
attributes: {
- 'data-action': hash,
+ 'data-action': href,
},
- children: [
- {
- tag: 'button',
- attributes: {
- 'aria-expanded': 'false',
- 'aria-haspopup': 'true',
- type: 'button',
- },
- children: [...node.children],
- },
- ],
+ children: [node],
},
{ processChildren: true },
];
diff --git a/scripts/libs.js b/scripts/libs.js
index d22c549e..91c91ad8 100644
--- a/scripts/libs.js
+++ b/scripts/libs.js
@@ -92,8 +92,8 @@ export const metaTags = {
fallbackContent: '/configs/layout',
// contentType: 'path without extension',
},
- themeConfigComponent: {
- metaName: 'theme-config-component',
+ componentsConfig: {
+ metaName: 'components-config',
fallbackContent: '/configs/components-config',
// contentType: 'path without extension',
},
@@ -366,13 +366,14 @@ export function deepMerge(origin, ...toMerge) {
export function deepMergeByType(keyPathMethods, origin, ...toMerge) {
if (!toMerge.length) return origin;
const merge = toMerge.shift();
+ const keyPathMethodsCopy = keyPathMethods || {};
const pathsArrays =
- keyPathMethods?.pathsArrays ||
- Object.entries(keyPathMethods).flatMap(([key, method]) => {
+ keyPathMethodsCopy?.pathsArrays ||
+ Object.entries(keyPathMethodsCopy).flatMap(([key, method]) => {
if (key === 'currentPath') return [];
return [[key.split('.').map((k) => k.split('|')), method]];
});
- const { currentPath = [] } = keyPathMethods;
+ const { currentPath = [] } = keyPathMethodsCopy;
if (isOnlyObject(origin) && isOnlyObject(merge)) {
Object.keys(merge).forEach((key) => {
@@ -462,6 +463,14 @@ export function loadModule(urlWithoutExtension, { loadCSS = true, loadJS = true
return modules;
}
+/**
+ * When creating elements that require properties to be set on them
+ * either await for this method when loading the component
+ * or use `await customElements.whenDefined('component-tag');`
+ * Otherwise properties set on the element before it was defined will be overwritten
+ * with the defaults from the class.
+ * This is not required for attributes.
+ */
export async function loadAndDefine(componentConfig) {
const { tag, module: { path, loadJS, loadCSS } = {} } = componentConfig;
if (window.raqnComponents[tag]) {
diff --git a/scripts/libs/external-config.js b/scripts/libs/external-config.js
index 960a4311..d01aa604 100644
--- a/scripts/libs/external-config.js
+++ b/scripts/libs/external-config.js
@@ -1,4 +1,4 @@
-import { getMeta, metaTags, readValue, deepMerge, getBaseUrl } from '../libs.js';
+import { getMeta, metaTags, readValue, deepMerge, getBaseUrl, unFlat } from '../libs.js';
window.raqnComponentsMasterConfig = window.raqnComponentsMasterConfig || null;
@@ -8,17 +8,19 @@ export const externalConfig = {
const configNameFallback = configName || 'default';
window.raqnComponentsMasterConfig ??= await this.loadConfig();
const componentConfig = window.raqnComponentsMasterConfig?.[componentName];
- const parsedConfig = componentConfig?.[configNameFallback];
+ if (componentConfig?.[configNameFallback]) {
+ componentConfig[configNameFallback] = unFlat(componentConfig?.[configNameFallback]);
+ // return copy of object to prevent mutation of raqnComponentsMasterConfig;
+ return deepMerge({}, componentConfig[configNameFallback]);
+ }
- // return copy of object to prevent mutation of raqnComponentsMasterConfig;
- if (parsedConfig) return deepMerge({}, parsedConfig);
return {};
},
async loadConfig(rawConfig) {
window.raqnComponentsConfig ??= (async () => {
const {
- themeConfigComponent: { metaName },
+ componentsConfig: { metaName },
themeConfig,
} = metaTags;
const metaConfigPath = getMeta(metaName);
@@ -40,6 +42,7 @@ export const externalConfig = {
} catch (error) {
// eslint-disable-next-line no-console
console.error(error);
+ return {};
}
return result;
})();
@@ -57,7 +60,7 @@ export const externalConfig = {
if (!window.raqnComponentsConfig[key]) return;
const { data } = window.raqnComponentsConfig[key];
if (data?.length) {
- window.raqnParsedConfigs[key] = window.raqnParsedConfigs[key] || {};
+ window.raqnParsedConfigs[key] ??= {};
window.raqnParsedConfigs[key] = readValue(data, window.raqnParsedConfigs[key]);
}
});
diff --git a/styles/styles.css b/styles/styles.css
index c5bc6c21..78dcfdd9 100644
--- a/styles/styles.css
+++ b/styles/styles.css
@@ -199,10 +199,29 @@ img {
}
/* Set default block style to all raqn web components
- Use :where() to give lower specificity in order to not overwrite any display option set on the web component tag
+ Use :where() to give lower specificity in order to not overwrite any styles set on the web component tag
*/
:where([raqnwebcomponent]) {
display: block;
+
+ /* Add defaults to initial to prevent inheritance */
+ --margin-block-start: initial;
+ --margin-block-end: initial;
+ --margin-inline-start: initial;
+ --margin-inline-end: initial;
+ --padding-block-start: initial;
+ --padding-block-end: initial;
+ --padding-inline-start: initial;
+ --padding-inline-end: initial;
+
+ margin-block-start: var(--margin-block-start);
+ margin-block-end: var(--margin-block-end);
+ margin-inline-start: var(--margin-inline-start);
+ margin-inline-end: var(--margin-inline-end);
+ padding-block-start: var(--padding-block-start);
+ padding-block-end: var(--padding-block-end);
+ padding-inline-start: var(--padding-inline-start);
+ padding-inline-end: var(--padding-inline-end);
}
/* Container: make all content act as a container where background of the container is limited to the content area */