From 2a0fcf84b98b28692cd22ce6316a622a4cdbeabe Mon Sep 17 00:00:00 2001 From: Florin Raducan Date: Fri, 9 Aug 2024 10:19:45 +0300 Subject: [PATCH 1/6] Use the order of observedAttributes and fix double trigger of attributes changed listeners by cleanDataset --- scripts/component-base.js | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/scripts/component-base.js b/scripts/component-base.js index bd7805e1..b9e5c62e 100644 --- a/scripts/component-base.js +++ b/scripts/component-base.js @@ -15,6 +15,8 @@ import { } from './libs.js'; export default class ComponentBase extends HTMLElement { + // All supported data attributes must be added to observedAttributes + // The order of observedAttributes is the order in which the values from config are added. static observedAttributes = []; static loaderConfig = { @@ -158,6 +160,8 @@ export default class ComponentBase extends HTMLElement { // Build-in method called after the element is added to the DOM. async connectedCallback() { + // Common identifier for raqn web components + this.setAttribute('raqnWebComponent', ''); this.setAttribute('isloading', ''); try { this.initialized = this.getAttribute('initialized'); @@ -233,15 +237,6 @@ export default class ComponentBase extends HTMLElement { this.attributesValues = deepMerge({}, this.attributesValues, values); } - get sortedAttributes() { - const knownAttr = this.Handler.observedAttributes; - // Sometimes the order in which the attributes are set matters. - // Control the order by using the order of the observedAttributes. - return Object.entries(this.attributesValues).sort( - (a, b) => knownAttr.indexOf(`data-${a}`) - knownAttr.indexOf(`data-${b}`), - ); - } - addDefaultsToNestedConfig() { Object.keys(this.nestedComponentsConfig).forEach((key) => { const defaults = { @@ -286,10 +281,8 @@ export default class ComponentBase extends HTMLElement { const { name } = getBreakPoints().active; const current = deepMerge({}, this.attributesValues.all, this.attributesValues[name]); this.className = ''; - this.cleanDataset(); Object.keys(current).forEach((key) => { const action = `apply${key.charAt(0).toUpperCase() + key.slice(1)}`; - if (typeof this[action] === 'function') { return this[action]?.(current[key]); } @@ -302,9 +295,19 @@ export default class ComponentBase extends HTMLElement { // received as {col:{ direction:2 }, columns: 2} const values = flat(entries); // transformed into values as {col-direction: 2, columns: 2} - Object.keys(values).forEach((key) => { - // camelCaseAttr converst col-direction into colDirection - this.dataset[camelCaseAttr(key)] = values[key]; + + // Add only supported data attributes from observedAttributes; + // Sometimes the order in which the attributes are set matters. + // Control the order by using the order of the observedAttributes. + this.Handler.observedAttributes.forEach((dataAttr) => { + const [, key] = dataAttr.split('data-'); + const camelCaseAttribute = camelCaseAttr(key); + + if (typeof values[key] !== 'undefined') { + this.dataset[camelCaseAttribute] = values[key]; + } else { + delete this.dataset[camelCaseAttribute]; + } }); } @@ -348,12 +351,6 @@ export default class ComponentBase extends HTMLElement { }); } - cleanDataset() { - Object.keys(this.dataset).forEach((key) => { - delete this.dataset[key]; - }); - } - /** * Attributes are assigned before the `connectedCallback` is triggered. * In some cases a check for `this.initialized` inside `onAttribute${capitalizedAttr}Changed` might be required From 4d2bf9b31ed5e6498f51d25d01a7974cb1f7f031 Mon Sep 17 00:00:00 2001 From: Florin Raducan Date: Fri, 9 Aug 2024 10:21:58 +0300 Subject: [PATCH 2/6] Change the container approach to fix visible scrollbars on windows --- blocks/navigation/navigation.css | 2 +- .../sidekick-tools-palette.css | 17 +-- .../sidekick-tools-palette.js | 135 ++++++++++-------- scripts/libs.js | 5 +- styles/styles.css | 46 ++---- 5 files changed, 99 insertions(+), 106 deletions(-) diff --git a/blocks/navigation/navigation.css b/blocks/navigation/navigation.css index 89ad6984..39d26d2d 100644 --- a/blocks/navigation/navigation.css +++ b/blocks/navigation/navigation.css @@ -26,7 +26,7 @@ raqn-navigation .level-1 a:hover { } raqn-navigation > nav > ul { - overflow-y: auto; + overflow-y: hidden; max-height: calc(100vh - var(--header-height)); } diff --git a/blocks/sidekick-tools-palette/sidekick-tools-palette.css b/blocks/sidekick-tools-palette/sidekick-tools-palette.css index ec2cd5f2..64957a06 100644 --- a/blocks/sidekick-tools-palette/sidekick-tools-palette.css +++ b/blocks/sidekick-tools-palette/sidekick-tools-palette.css @@ -11,8 +11,9 @@ raqn-sidekick-tools-palette ul { list-style: none; padding: 0; margin: 0; - overflow: scroll; - transition: transform .2s ease-in-out; + overflow-y: auto; + overflow-x: hidden; + transition: transform 0.2s ease-in-out; } raqn-sidekick-tools-palette h3, @@ -28,7 +29,7 @@ raqn-sidekick-tools-palette li { } raqn-sidekick-tools-palette h3.hidden { - display: none; + display: none; } raqn-sidekick-tools-palette .menu-wrapper { @@ -49,7 +50,7 @@ raqn-sidekick-tools-palette h4::after { padding: 3px; position: absolute; right: 14px; - transition: transform .2s ease-in-out; + transition: transform 0.2s ease-in-out; transform: rotate(-45deg); } @@ -72,7 +73,7 @@ raqn-sidekick-tools-palette h3::before { display: inline-block; padding: 3px; margin: auto 10px; - transition: transform .2s ease-in-out; + transition: transform 0.2s ease-in-out; transform: rotate(-45deg); } @@ -81,8 +82,8 @@ raqn-sidekick-tools-palette .submenu ul.hidden { } raqn-sidekick-tools-palette ul:first-child li:hover, -raqn-sidekick-tools-palette h3:hover, -raqn-sidekick-tools-palette h4:hover, +raqn-sidekick-tools-palette h3:hover, +raqn-sidekick-tools-palette h4:hover, raqn-sidekick-tools-palette .submenu ul li:hover, raqn-sidekick-tools-palette .submenu .selectable:hover { cursor: pointer; @@ -115,5 +116,5 @@ raqn-sidekick-tools-palette .submenu p { } raqn-sidekick-tools-palette .submenu p.details { - font-size: .8rem; + font-size: 0.8rem; } diff --git a/blocks/sidekick-tools-palette/sidekick-tools-palette.js b/blocks/sidekick-tools-palette/sidekick-tools-palette.js index b64c249e..783cb47a 100644 --- a/blocks/sidekick-tools-palette/sidekick-tools-palette.js +++ b/blocks/sidekick-tools-palette/sidekick-tools-palette.js @@ -182,8 +182,7 @@ function decorateImages(element) { function getTable(block) { const name = getBlockName(block); const rows = [...block.children]; - const maxCols = rows.reduce((cols, row) => ( - row.children.length > cols ? row.children.length : cols), 0); + const maxCols = rows.reduce((cols, row) => (row.children.length > cols ? row.children.length : cols), 0); const table = document.createElement('table'); table.setAttribute('border', 1); const headerRow = document.createElement('tr'); @@ -224,13 +223,12 @@ function getHtml(container) { } export default class SidekickToolsPalette extends ComponentBase { - createEntry(name) { const subHeader = document.createElement('h3'); subHeader.classList.add('hidden'); subHeader.innerText = name; this.menuWrapper.before(subHeader); - + const menuItem = document.createElement('li'); menuItem.innerText = name; this.menuRoot.append(menuItem); @@ -258,54 +256,55 @@ export default class SidekickToolsPalette extends ComponentBase { const subMenu = this.createEntry(title); const blocksResponse = await fetch(url); - if(!blocksResponse.ok) return; + if (!blocksResponse.ok) return; const blocks = await blocksResponse.json(); - await Promise.all(blocks.data.map(async (block) => { - const blockResponse = await fetch(`${block.path}.plain.html`); - if (!blockResponse.ok) return; + await Promise.all( + blocks.data.map(async (block) => { + const blockResponse = await fetch(`${block.path}.plain.html`); + if (!blockResponse.ok) return; - const html = await blockResponse.text(); - const parser = new DOMParser(); - const doc = parser.parseFromString(html, 'text/html'); - const containers = getContainers(doc); - if(!containers.length) return; - const item = document.createElement('li'); - item.innerHTML = `

${block.name}

`; - const itemHeader = item.querySelector('h4'); - const wrapper = item.querySelector('ul'); - itemHeader.addEventListener('click', () => { - if(itemHeader.classList.contains('active')) { - itemHeader.classList.remove('active'); - wrapper.classList.add('hidden'); - } else { - subMenu.querySelectorAll('h4').forEach((h) => h.classList.remove('active')); - subMenu.querySelectorAll('ul').forEach((ul) => ul.classList.add('hidden')); - itemHeader.classList.add('active'); - wrapper.classList.remove('hidden'); - } - }); - containers.forEach((container) => { - const copyOption = document.createElement('li'); - copyOption.innerHTML = `${getContainerName(container)}`; - wrapper.append(copyOption); - copyOption.addEventListener('click', () => { - const blob = new Blob([`
${getHtml(container)}
`], { type: 'text/html' }); - const data = [new ClipboardItem({ [blob.type]: blob })]; - navigator.clipboard.write(data); - copyOption.classList.add('copied'); - setTimeout(() => copyOption.classList.remove('copied'), 1000); + const html = await blockResponse.text(); + const parser = new DOMParser(); + const doc = parser.parseFromString(html, 'text/html'); + const containers = getContainers(doc); + if (!containers.length) return; + const item = document.createElement('li'); + item.innerHTML = `

${block.name}

`; + const itemHeader = item.querySelector('h4'); + const wrapper = item.querySelector('ul'); + itemHeader.addEventListener('click', () => { + if (itemHeader.classList.contains('active')) { + itemHeader.classList.remove('active'); + wrapper.classList.add('hidden'); + } else { + subMenu.querySelectorAll('h4').forEach((h) => h.classList.remove('active')); + subMenu.querySelectorAll('ul').forEach((ul) => ul.classList.add('hidden')); + itemHeader.classList.add('active'); + wrapper.classList.remove('hidden'); + } }); - }); - subMenu.append(item); - })); - + containers.forEach((container) => { + const copyOption = document.createElement('li'); + copyOption.innerHTML = `${getContainerName(container)}`; + wrapper.append(copyOption); + copyOption.addEventListener('click', () => { + const blob = new Blob([`
${getHtml(container)}
`], { type: 'text/html' }); + const data = [new ClipboardItem({ [blob.type]: blob })]; + navigator.clipboard.write(data); + copyOption.classList.add('copied'); + setTimeout(() => copyOption.classList.remove('copied'), 1000); + }); + }); + subMenu.append(item); + }), + ); } async initPlaceholders({ title, url }) { const subMenu = this.createEntry(title); const placeholdersResponse = await fetch(url); - if(!placeholdersResponse.ok) return; + if (!placeholdersResponse.ok) return; const placeholders = await placeholdersResponse.json(); placeholders.data.forEach((placeholder) => { @@ -326,12 +325,16 @@ export default class SidekickToolsPalette extends ComponentBase { async initPalette() { const { searchParams } = new URL(window.location.href); this.innerHTML = 'loading ...'; - if(!searchParams.has('ref') || !searchParams.has('repo') || !searchParams.has('owner')) { + if (!searchParams.has('ref') || !searchParams.has('repo') || !searchParams.has('owner')) { this.innerHTML = 'loading ... failed.'; return; } - const configResponse = await fetch(`https://admin.hlx.page/sidekick/${searchParams.get('owner')}/${searchParams.get('repo')}/${searchParams.get('ref')}/config.json`); - if(!configResponse.ok) { + const configResponse = await fetch( + `https://admin.hlx.page/sidekick/${searchParams.get('owner')}/${searchParams.get('repo')}/${searchParams.get( + 'ref', + )}/config.json`, + ); + if (!configResponse.ok) { this.innerHTML = 'loading ... failed.'; return; } @@ -346,28 +349,34 @@ export default class SidekickToolsPalette extends ComponentBase { this.menuWrapper = this.querySelector('.menu-wrapper'); this.menuRoot = this.querySelector('ul'); - const thisPlugin = config.plugins.find((plugin) => - plugin.url === window.location.pathname || plugin.url === `${window.location.origin}${window.location.pathname}`); - if(!thisPlugin) { + const thisPlugin = config.plugins.find( + (plugin) => + plugin.url === window.location.pathname || + plugin.url === `${window.location.origin}${window.location.pathname}`, + ); + if (!thisPlugin) { this.innerHTML = 'loading ... failed.'; return; } - config.plugins.filter((plugin) => plugin.containerId === thisPlugin.id).forEach((plugin) => { - switch (plugin.id) { - case `${thisPlugin.id}-blocks`: - this.initBlocks(plugin); - break; - case `${thisPlugin.id}-placeholders`: - this.initPlaceholders(plugin); - break; - default: - console.warn('failed to load plugin', plugin); - break; - } - }); + config.plugins + .filter((plugin) => plugin.containerId === thisPlugin.id) + .forEach((plugin) => { + switch (plugin.id) { + case `${thisPlugin.id}-blocks`: + this.initBlocks(plugin); + break; + case `${thisPlugin.id}-placeholders`: + this.initPlaceholders(plugin); + break; + default: + // eslint-disable-next-line no-console + console.warn('failed to load plugin', plugin); + break; + } + }); } connected() { this.initPalette(); } -} \ No newline at end of file +} diff --git a/scripts/libs.js b/scripts/libs.js index ae549f34..ffd6f601 100644 --- a/scripts/libs.js +++ b/scripts/libs.js @@ -207,7 +207,7 @@ export function stringToArray(val, options) { }); } -// retrive data from excel json format +// retrieve data from excel json format export function readValue(data, extend = {}) { const k = Object.keys; const keys = k(data[0]).filter((item) => item !== 'key'); @@ -320,7 +320,7 @@ export const externalConfig = { }, simplifiedConfig() { - window.raqnParsedConfigs = window.raqnParsedConfigs || {}; + window.raqnParsedConfigs ??= {}; if (window.raqnComponentsConfig) { Object.keys(window.raqnComponentsConfig).forEach((key) => { if (!window.raqnComponentsConfig[key]) return; @@ -358,6 +358,7 @@ export function loadModule(urlWithoutExtension, loadCSS = true) { return { css, js }; } catch (error) { + // eslint-disable-next-line no-console console.log('could not load module', urlWithoutExtension, error); } return { css: Promise.resolve(), js: Promise.resolve() }; diff --git a/styles/styles.css b/styles/styles.css index b54c13c9..947a7091 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -1,8 +1,10 @@ -@media screen and (max-width: 768px) { - body { - --raqn-max-width-default: var(--max-width, 80vw); - --max-width: 100vw; - } +:root { + --max-width: 80% --padding-container: 20px; + --header-height: 110px; +} + +* { + box-sizing: border-box; } img { @@ -12,10 +14,6 @@ img { html, body { - --raqn-max-width-default: 80vw; - --max-width: var(--raqn-max-width-default, 80vw); - --header-height: 110px; - width: 100%; height: 100%; margin: 0; @@ -110,34 +108,18 @@ head:has(meta[name='footer'][content='false' i]) + body > footer { display: none; } -main > div { - max-width: var(--max-width, 100%); - margin: 0 auto; -} - -main > div > * { - max-width: var(--max-width, 100%); - min-width: var(--max-width, 100%); - margin-inline: auto; - box-sizing: border-box; - background-color: var(--background, #fff); + /* Use :where() to give lower specificity in order to not overwrite any display option set on the web component tag */ +:where([raqnWebComponent]) { + display: block; } -main > .raqn-grid > * { - max-width: auto; - min-width: auto; +main > div > *:not(.full-width) { + margin-inline: max(calc((100% - var(--max-width)) / 2 - var(--padding-container)), var(--padding-container)); + padding-inline: var(--padding-container); } .full-width { - --outer-gap: calc((var(--raqn-max-width-default) - 100vw) / 2); - --inner-gap: calc((100vw - var(--max-width)) / 2); - - display: grid; - min-width: 100vw; - max-width: none; - margin-inline-start: var(--outer-gap); - padding-inline: var(--inner-gap); - box-sizing: border-box; + padding-inline: max(calc((100% - var(--max-width)) / 2), var(--padding-container)); } main > div > div { From 59cbd458007f36f4b493e1541a6c749bec2eef67 Mon Sep 17 00:00:00 2001 From: Florin Raducan Date: Fri, 9 Aug 2024 10:27:30 +0300 Subject: [PATCH 3/6] Fix variable declaration --- styles/styles.css | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/styles/styles.css b/styles/styles.css index 947a7091..a62ae03e 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -1,8 +1,14 @@ :root { - --max-width: 80% --padding-container: 20px; + --max-width: 80%; + --padding-container: 20px; --header-height: 110px; } +@media screen and (max-width: 768px) { + body { + --max-width: 100% + } + * { box-sizing: border-box; } From 40f2005fa2fbc1b769e4072dfff6f90a947aa1ae Mon Sep 17 00:00:00 2001 From: Florin Raducan Date: Fri, 9 Aug 2024 10:28:49 +0300 Subject: [PATCH 4/6] Fix variable declaration --- styles/styles.css | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/styles/styles.css b/styles/styles.css index a62ae03e..56d1da2e 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -5,9 +5,10 @@ } @media screen and (max-width: 768px) { - body { - --max-width: 100% + :root { + --max-width: 100%; } +} * { box-sizing: border-box; @@ -114,7 +115,7 @@ head:has(meta[name='footer'][content='false' i]) + body > footer { display: none; } - /* 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 display option set on the web component tag */ :where([raqnWebComponent]) { display: block; } From d3d1bd56ef360d650fd258d8bb8cdbd21767f612 Mon Sep 17 00:00:00 2001 From: Florin Raducan Date: Fri, 9 Aug 2024 16:52:11 +0300 Subject: [PATCH 5/6] Fix popup and remove popup-config support and fix component creation without component.ini() --- blocks/popup-trigger/popup-trigger.js | 36 +++++++++++++-------------- blocks/popup/popup.js | 33 +++--------------------- scripts/component-base.js | 30 ++++++++++++++++++---- 3 files changed, 45 insertions(+), 54 deletions(-) diff --git a/blocks/popup-trigger/popup-trigger.js b/blocks/popup-trigger/popup-trigger.js index 5b61ae8d..3fec38ad 100644 --- a/blocks/popup-trigger/popup-trigger.js +++ b/blocks/popup-trigger/popup-trigger.js @@ -53,7 +53,7 @@ export default class PopupTrigger extends ComponentBase { if (anchorUrl.hash === closePopupIdentifier) { this.isClosePopupTrigger = true; } else { - this.dataset.url = anchorUrl.href; + this.dataset.url = anchorUrl.pathname; } if (anchor.hasAttribute('aria-label')) { @@ -84,8 +84,16 @@ export default class PopupTrigger extends ComponentBase { onAttributeUrlChanged({ oldValue, newValue }) { 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; + } - const sourceUrl = new URL(newValue); this.popupSourceUrl = sourceUrl.pathname; if (this.popup) { @@ -123,24 +131,14 @@ export default class PopupTrigger extends ComponentBase { } async createPopup() { - const { - instances: [popup], - } = await component.init({ - componentName: 'popup', - attributesValues: { - url: { all: this.popupSourceUrl }, - active: { all: true }, - }, - componentConfig: { - contentFromTargets: false, - addToTargetMethod: 'append', - }, - props: { - popupTrigger: this, - }, - targets: [null], - }); + await component.loadAndDefine('popup'); + const popup = document.createElement('raqn-popup'); + + popup.dataset.url = this.popupSourceUrl; + popup.dataset.active = true; + popup.dataset.type = 'flyout'; + popup.popupTrigger = this; return popup; } diff --git a/blocks/popup/popup.js b/blocks/popup/popup.js index 3c220f0f..319a91d2 100644 --- a/blocks/popup/popup.js +++ b/blocks/popup/popup.js @@ -56,7 +56,6 @@ export default class Popup extends ComponentBase { setDefaults() { super.setDefaults(); this.popupTrigger = null; - this.getConfigFromFragment = true; } setBinds() { @@ -88,7 +87,7 @@ export default class Popup extends ComponentBase { template() { return ` -