From f1e97d8642f91325ebc1e59a5643d495af6675a0 Mon Sep 17 00:00:00 2001 From: Felipe Simoes Date: Thu, 23 May 2024 15:26:41 +0200 Subject: [PATCH 01/13] editor --- blocks/button/button.css | 11 ++- blocks/button/button.editor.js | 127 +++++++++++++++++++++++++++++++++ blocks/card/card.editor.js | 91 +++++++++++++++++++++++ blocks/footer/footer.editor.js | 38 ++++++++++ blocks/hero/hero.css | 5 ++ blocks/hero/hero.editor.js | 80 +++++++++++++++++++++ scripts/component-base.js | 2 +- scripts/component-loader.js | 9 ++- scripts/editor.js | 94 ++++++++++++++++++++++++ scripts/init.js | 14 ++++ scripts/libs.js | 46 ++++++------ scripts/pubsub.js | 20 +++--- 12 files changed, 504 insertions(+), 33 deletions(-) create mode 100644 blocks/button/button.editor.js create mode 100644 blocks/card/card.editor.js create mode 100644 blocks/footer/footer.editor.js create mode 100644 blocks/hero/hero.editor.js create mode 100644 scripts/editor.js diff --git a/blocks/button/button.css b/blocks/button/button.css index c1e60516..bc5ebc04 100644 --- a/blocks/button/button.css +++ b/blocks/button/button.css @@ -4,6 +4,14 @@ raqn-button { align-content: center; align-items: center; justify-items: var(--scope-justify, start); + + --button-padding-block: var(--scope-button-padding-block, 10px); + --button-padding-inline: var(--scope-button-padding-inline, 20px); + --scope-border-radius: 0; + --scope-border-block-start: 1px solid transparent; + --scope-border-block-end: 1px solid transparent; + --scope-border-inline-start: 1px solid transparent; + --scope-border-inline-end: 1px solid transparent; } raqn-button > * { @@ -26,7 +34,8 @@ raqn-button > *:hover { raqn-button a { color: var(--scope-accent-color, currentcolor); - padding: 10px 20px; + padding-block: var(--button-padding-block, 10px); + padding-inline: var(--button-padding-inline, 20px); text-decoration: none; } diff --git a/blocks/button/button.editor.js b/blocks/button/button.editor.js new file mode 100644 index 00000000..ed580075 --- /dev/null +++ b/blocks/button/button.editor.js @@ -0,0 +1,127 @@ +export default function config() { + return { + variables: { + '--scope-accent-background': { + type: 'text', + label: 'Background', + helpText: 'The background color of the button.', + }, + '--scope-accent-color': { + type: 'text', + label: 'Color', + helpText: 'The text color of the button.', + }, + '--button-padding-block': { + type: 'text', + label: 'Padding Block', + helpText: 'The padding block of the button.', + }, + '--button-padding-inline': { + type: 'text', + label: 'Padding Inline', + helpText: 'The padding inline of the button.', + }, + '--scope-border-block-end': { + type: 'text', + label: 'Border Block End', + helpText: 'The border block end of the button.', + }, + '--scope-border-radius': { + type: 'text', + label: 'Border Radius', + helpText: 'The border radius of the button.', + }, + '--scope-border-block-start': { + type: 'text', + label: 'Border Block Start', + helpText: 'The border block start of the button.', + }, + '--scope-border-inline-end': { + type: 'text', + label: 'Border Inline End', + helpText: 'The border inline end of the button.', + }, + '--scope-border-inline-start': { + type: 'text', + label: 'Border Inline Start', + helpText: 'The border inline start of the button.', + }, + '--scope-box-shadow': { + type: 'text', + label: 'Box Shadow', + helpText: 'The box shadow of the button.', + }, + '--scope-accent-background-hover': { + type: 'text', + label: 'Background Hover', + helpText: 'The background color of the button when hovered.', + }, + '--scope-accent-color-hover': { + type: 'text', + label: 'Color Hover', + helpText: 'The text color of the button when hovered.', + }, + '--scope-justify': { + type: 'text', + label: 'Justify', + helpText: 'The justify of the button.', + }, + }, + selection: { + Blue: { + descritpion: { + label: 'Regular Blue Button', + preview: 'http://localhost:8888/@henkel/theme-interface/assets/previews/button/blue.png', + }, + variables: { + '--scope-accent-background': '#007bff', + '--scope-accent-color': '#fff', + '--scope-border-block-end': '0', + '--scope-border-block-start': '0', + '--scope-border-inline-end': '0', + '--scope-border-inline-start': '0', + '--scope-box-shadow': 'none', + '--scope-accent-background-hover': '#0056b3', + '--scope-accent-color-hover': '#fff', + '--scope-justify': 'start', + }, + }, + Red: { + descritpion: { + label: 'Regular Red Button', + preview: 'http://localhost:8888/@henkel/theme-interface/assets/previews/button/red.png', + }, + variables: { + '--scope-accent-background': 'red', + '--scope-accent-color': 'white', + '--scope-border-block-end': '1px', + '--scope-border-block-start': '1px', + '--scope-border-inline-end': '1px', + '--scope-border-inline-start': '1px', + '--scope-box-shadow': '1px 1px 1px 1px rgba(0, 0, 0, 0.1)', + '--scope-accent-background-hover': 'white', + '--scope-accent-color-hover': 'red', + '--scope-justify': 'start', + }, + }, + White: { + descritpion: { + label: 'Regular white Button', + preview: 'http://localhost:8888/@henkel/theme-interface/assets/previews/button/white.png', + }, + variables: { + '--scope-accent-background': 'white', + '--scope-accent-color': 'black', + '--scope-border-block-end': '10px', + '--scope-border-block-start': '10px', + '--scope-border-inline-end': '1px', + '--scope-border-inline-start': '1px', + '--scope-box-shadow': '1px 1px 1px 1px rgba(0, 0, 0, 0.1)', + '--scope-accent-background-hover': 'white', + '--scope-accent-color-hover': 'red', + '--scope-justify': 'start', + }, + }, + }, + }; +} diff --git a/blocks/card/card.editor.js b/blocks/card/card.editor.js new file mode 100644 index 00000000..0403c67d --- /dev/null +++ b/blocks/card/card.editor.js @@ -0,0 +1,91 @@ +export default function config() { + return { + variables: { + '--scope-background': { + type: 'text', + label: 'Background', + helpText: 'The background color of the card.', + }, + '--scope-color': { + type: 'text', + label: 'Color', + helpText: 'The text color of the card.', + }, + '--scope-gap': { + type: 'text', + label: 'Gap', + helpText: 'The gap between cards.', + }, + '--scope-padding': { + type: 'text', + label: 'Padding', + helpText: 'The padding of the card.', + }, + }, + attributes: { + class: { + type: 'text', + label: 'Class', + helpText: 'The class of the card.', + }, + 'data-columns': { + type: 'text', + label: 'Number of Columns', + helpText: 'The number of columns in the card grid.', + }, + 'data-ratio': { + type: 'text', + label: 'Aspect Ratio', + helpText: 'The aspect ratio of the card.', + }, + 'data-eager': { + type: 'text', + label: 'Eager Loading', + helpText: 'The number of images to load eagerly.', + }, + 'data-background': { + type: 'text', + label: 'Background', + helpText: 'The background color of the card.', + }, + }, + selection: { + variant1: { + variables: { + '--scope-color': 'white', + '--scope-gap': '40px', + '--scope-padding': '20px', + }, + attributes: { + 'data-columns': '2', + 'data-ratio': '4/3', + 'data-eager': '0', + }, + }, + variant2: { + variables: { + '--scope-color': 'white', + '--scope-gap': '40px', + '--scope-padding': '20px', + }, + attributes: { + 'data-columns': '3', + 'data-ratio': '4/3', + 'data-eager': '0', + }, + }, + variant3: { + variables: { + '--scope-color': 'white', + '--scope-gap': '40px', + '--scope-padding': '20px', + }, + attributes: { + 'data-columns': '4', + 'data-ratio': '4/3', + 'data-eager': '0', + }, + }, + }, + }; +} diff --git a/blocks/footer/footer.editor.js b/blocks/footer/footer.editor.js new file mode 100644 index 00000000..142db2a3 --- /dev/null +++ b/blocks/footer/footer.editor.js @@ -0,0 +1,38 @@ +export default function config() { + return { + variables: { + '--scope-background-color': { + type: 'text', + label: 'Background Color', + helpText: 'The background color of the footer.', + }, + '--scope-color': { + type: 'text', + label: 'Color', + helpText: 'The text color of the footer.', + }, + }, + selection: { + Default: { + descritpion: { + label: 'Default', + preview: 'http://localhost:8888/@henkel/theme-interface/assets/previews/footer/default.png', + }, + variables: { + '--scope-background-color': 'black', + '--scope-color': 'white', + }, + }, + Inverted: { + descritpion: { + label: 'Inverted colors', + preview: 'http://localhost:8888/@henkel/theme-interface/assets/previews/footer/inverted.png', + }, + variables: { + '--scope-background-color': 'white', + '--scope-color': 'black', + }, + }, + }, + }; +} diff --git a/blocks/hero/hero.css b/blocks/hero/hero.css index 822306c0..fefadc59 100644 --- a/blocks/hero/hero.css +++ b/blocks/hero/hero.css @@ -12,6 +12,11 @@ raqn-hero { align-items: center; grid-template-columns: var(--hero-grid-template-columns, 1fr); padding-block: var(--hero-padding-block); + + > * { + --scope-color: var(--hero-color); + --scope-headings-color: var(--hero-headings-color, var(--hero-color)); + } } @media screen and (min-width: 767px) { diff --git a/blocks/hero/hero.editor.js b/blocks/hero/hero.editor.js new file mode 100644 index 00000000..5d327a99 --- /dev/null +++ b/blocks/hero/hero.editor.js @@ -0,0 +1,80 @@ +export default function config() { + return { + variables: { + '--hero-color': { + type: 'text', + label: 'Color', + helpText: 'The text color of the hero.', + }, + '--hero-background': { + type: 'text', + label: 'Background', + helpText: 'The background color of the hero.', + }, + '--hero-padding-block': { + type: 'text', + label: 'Padding Block', + helpText: 'The padding block of the hero.', + }, + '--hero-grid-template-columns': { + type: 'text', + label: 'Grid Template Columns', + helpText: 'The grid template columns of the hero.', + }, + }, + attributes: { + 'data-order': { + type: 'text', + label: 'Order', + helpText: 'The order of the hero.', + }, + }, + selection: { + Default: { + descritpion: { + label: 'Image on the Left', + preview: 'http://localhost:8888/@henkel/theme-interface/assets/previews/hero/imageontheleft.png', + }, + variables: { + '--hero-color': 'white', + '--hero-background': 'black', + '--hero-padding-block': '20px', + '--hero-grid-template-columns': '0.4fr 0.6fr', + }, + attributes: { + 'data-order': '1', + }, + }, + Inverted: { + descritpion: { + label: 'Invert colors', + preview: 'http://localhost:8888/@henkel/theme-interface/assets/previews/hero/invertedcolor.png', + }, + variables: { + '--hero-color': 'black', + '--hero-background': 'white', + '--hero-padding-block': '20px', + '--hero-grid-template-columns': '0.4fr 0.6fr', + }, + attributes: { + 'data-order': '2', + }, + }, + 'Image on the right': { + descritpion: { + label: 'Image on the right', + preview: 'http://localhost:8888/@henkel/theme-interface/assets/previews/hero/imageontherigth.png', + }, + variables: { + '--hero-color': 'white', + '--hero-background': 'black', + '--hero-padding-block': '20px', + '--hero-grid-template-columns': '0.6fr 0.4fr', + }, + attributes: { + 'data-order': '0', + }, + }, + }, + }; +} diff --git a/scripts/component-base.js b/scripts/component-base.js index 79bc3e83..e35ee270 100644 --- a/scripts/component-base.js +++ b/scripts/component-base.js @@ -3,9 +3,9 @@ import component from './init.js'; import { getBreakPoints, listenBreakpointChange, camelCaseAttr, capitalizeCaseAttr, deepMerge } from './libs.js'; export default class ComponentBase extends HTMLElement { - constructor() { super(); + // window.raqnInstances = window.raqnInstances || {}; this.uuid = `gen${crypto.randomUUID().split('-')[0]}`; this.componentName = null; // set by component loader this.webComponentName = null; // set by component loader diff --git a/scripts/component-loader.js b/scripts/component-loader.js index fba668b1..f8a2d654 100644 --- a/scripts/component-loader.js +++ b/scripts/component-loader.js @@ -1,11 +1,14 @@ import { collectAttributes, loadModule, deepMerge, mergeUniqueArrays } from './libs.js'; +window.raqnInstances = window.raqnInstances || {}; + export default class ComponentLoader { constructor({ componentName, targets = [], loaderConfig, rawClasses, config, nestedComponentsConfig, active }) { window.raqnComponents ??= {}; if (!componentName) { throw new Error('`componentName` is required'); } + this.instances = window.raqnInstances; this.componentName = componentName; this.targets = targets.map((target) => ({ target })); this.loaderConfig = loaderConfig; @@ -74,7 +77,9 @@ export default class ComponentLoader { data.componentElem = elem; returnVal = elem; this.addContentFromTarget(data); - await this.connectComponent(data); + const { componentElem } = await this.connectComponent(data); + this.instances[componentElem.componentName] = this.instances[componentElem.componentName] || []; + this.instances[componentElem.componentName].push(await this.connectComponent(data)); } catch (error) { const err = new Error(error); err.elem = returnVal; @@ -220,7 +225,7 @@ export default class ComponentLoader { const addToTargetMethod = targetsAsContainers ? conf.targetsAsContainers.addToTargetMethod : conf.addToTargetMethod; data.target[addToTargetMethod](componentElem); - return initialized; + return { initialized, componentElem }; } async loadAndDefine() { diff --git a/scripts/editor.js b/scripts/editor.js new file mode 100644 index 00000000..e930bec2 --- /dev/null +++ b/scripts/editor.js @@ -0,0 +1,94 @@ +import { loadModule } from './libs.js'; +import { publish } from './pubsub.js'; + +let origin = null; +let target = null; + +window.raqnEditors = window.raqnEditors || {}; + +export function updateComponent(params) { + const { uuid, name, option } = params; + const dialog = window.raqnEditors[name]; + const component = window.raqnInstances[name].find((item) => item.componentElem.uuid === uuid); + const { componentElem } = component; + const { variables, attributes } = option; + if (variables) { + Object.keys(variables).forEach((variable) => { + componentElem.style.setProperty(variable, variables[variable]); + }); + } + if (attributes) { + Object.keys(attributes).forEach((attribute) => { + componentElem.setAttribute(attribute, attributes[attribute]); + }); + } + const bodyRect = window.document.body.getBoundingClientRect(); + // eslint-disable-next-line no-use-before-define + const instance = getComponentValues(dialog, component); + // eslint-disable-next-line no-use-before-define + initEditor(origin, target, false); + publish('editor:rendered', { instance, dialog, bodyRect }, { usePostMessage: true, targetOrigin: '*' }); +} + +export function getComponentValues(dialog, item) { + const domRect = item.componentElem.getBoundingClientRect(); + console.log('dialog.variables', dialog.variables, item.componentElem); + dialog.variables = Object.keys(dialog.variables).reduce((data, variable) => { + const value = getComputedStyle(item.componentElem).getPropertyValue(variable); + console.log('value', data); + data[variable] = { ...dialog.variables[variable], value }; + console.log('value', data); + return data; + }, {}); + const cleanData = Object.fromEntries(Object.entries(item.componentElem)); + delete cleanData.nestedComponents; + delete cleanData.nestedComponentsConfig; + return { ...cleanData, domRect }; +} + +export default function initEditor(o, t, listeners = true) { + origin = o; + target = t; + + Object.keys(window.raqnComponents).forEach(async (componentName) => { + try { + const editor = await loadModule(`/blocks/${componentName}/${componentName}.editor`, false); + const mod = await editor.js; + + if (mod && mod.default) { + const dialog = mod.default(); + window.raqnEditors[componentName] = dialog; + const instances = window.raqnInstances[componentName].map((item) => getComponentValues(dialog, item)); + + const bodyRect = window.document.body.getBoundingClientRect(); + + publish( + 'editor:loaded', + { componentName, dialog, instances, bodyRect }, + { + usePostMessage: true, + targetOrigin: '*', + }, + ); + } + } catch (error) { + // console.log(error); + } + }); + if (listeners) { + // init editor if message from parent + window.addEventListener('message', async (e) => { + if (e && e.data) { + const { message, params } = e.data; + switch (message) { + case 'editor:select': + updateComponent(params); + break; + + default: + break; + } + } + }); + } +} diff --git a/scripts/init.js b/scripts/init.js index 78aea1fd..bdc57b7f 100644 --- a/scripts/init.js +++ b/scripts/init.js @@ -188,4 +188,18 @@ const globalInit = { globalInit.init(); +// init editor if message from parent +window.addEventListener('message', async (e) => { + if (e && e.data) { + const { message, params } = e.data; + if (message === 'initEditor' && !Array.isArray(params)) { + const editor = await import('./editor.js'); + const { origin, target } = params; + setTimeout(() => { + editor.default(origin, target); + }, 2000); + } + } +}); + export default component; diff --git a/scripts/libs.js b/scripts/libs.js index 0ab08d5e..fe909911 100644 --- a/scripts/libs.js +++ b/scripts/libs.js @@ -256,26 +256,32 @@ export function collectAttributes(componentName, classes, knownAttributes = [], }; } -export function loadModule(urlWithoutExtension) { - const js = import(`${urlWithoutExtension}.js`); - const css = new Promise((resolve, reject) => { - const cssHref = `${urlWithoutExtension}.css`; - if (!document.querySelector(`head > link[href="${cssHref}"]`)) { - const link = document.createElement('link'); - link.rel = 'stylesheet'; - link.href = cssHref; - link.onload = resolve; - link.onerror = reject; - document.head.append(link); - } else { - resolve(); - } - }).catch((error) => - // eslint-disable-next-line no-console - console.error('could not load module style', urlWithoutExtension, error), - ); - - return { css, js }; +export function loadModule(urlWithoutExtension, loadCSS = true) { + try { + const js = import(`${urlWithoutExtension}.js`); + if (!loadCSS) return { js, css: Promise.resolve() }; + const css = new Promise((resolve, reject) => { + const cssHref = `${urlWithoutExtension}.css`; + if (!document.querySelector(`head > link[href="${cssHref}"]`)) { + const link = document.createElement('link'); + link.rel = 'stylesheet'; + link.href = cssHref; + link.onload = resolve; + link.onerror = reject; + document.head.append(link); + } else { + resolve(); + } + }).catch((error) => + // eslint-disable-next-line no-console + console.log('could not load module style', urlWithoutExtension, error), + ); + + return { css, js }; + } catch (error) { + console.log('could not load module', urlWithoutExtension, error); + } + return { css: Promise.resolve(), js: Promise.resolve() }; } export function mergeUniqueArrays(...arrays) { diff --git a/scripts/pubsub.js b/scripts/pubsub.js index 4f21b20f..47f51dc0 100644 --- a/scripts/pubsub.js +++ b/scripts/pubsub.js @@ -53,12 +53,11 @@ export const unsubscribeAll = (options = {}) => { return; } - Object.keys(actions) - .forEach((key) => { - if (exactFit ? key === message : key.includes(message)) { - delete actions[key]; - } - }); + Object.keys(actions).forEach((key) => { + if (exactFit ? key === message : key.includes(message)) { + delete actions[key]; + } + }); }; export const callStack = (message, params, options) => { @@ -94,14 +93,16 @@ export const postMessage = (message, params, options = {}) => { let data = { message }; try { - data = JSON.parse(JSON.stringify({ message, params })); + data = { message, params: JSON.parse(JSON.stringify(params)) }; } catch (error) { // some objects cannot be passed by post messages like when passing htmlElements. // for those that can be published but are not compatible with postMessages we don't send params // eslint-disable-next-line no-console console.warn(error); } - + // upward message + window.parent.postMessage(data, targetOrigin); + // downward message window.postMessage(data, targetOrigin); }; @@ -111,6 +112,7 @@ export const publish = (message, params, options = {}) => { callStack(message, params, options); return; } + console.log('postMessage', message); postMessage(message, params, options); }; @@ -125,4 +127,4 @@ if (!window.messageListenerAdded) { } } }); -} \ No newline at end of file +} From 62ceaf14e9cf17daf67461d18ccb411d109dbe9a Mon Sep 17 00:00:00 2001 From: Felipe Simoes Date: Tue, 28 May 2024 19:19:05 +0200 Subject: [PATCH 02/13] Feature editor --- scripts/editor.js | 120 +++++++++++++++++++++++++++++----------------- scripts/init.js | 21 +++++--- scripts/pubsub.js | 13 +---- 3 files changed, 92 insertions(+), 62 deletions(-) diff --git a/scripts/editor.js b/scripts/editor.js index e930bec2..bc624536 100644 --- a/scripts/editor.js +++ b/scripts/editor.js @@ -1,14 +1,38 @@ import { loadModule } from './libs.js'; import { publish } from './pubsub.js'; -let origin = null; -let target = null; +window.raqnEditor = window.raqnEditor || {}; -window.raqnEditors = window.raqnEditors || {}; +export const MessagesEvents = { + init: 'raqn:editor:start', + loaded: 'raqn:editor:loaded', + active: 'raqn:editor:active', + disabled: 'raqn:editor:disabled', + render: 'raqn:editor:render', + select: 'raqn:editor:select', +}; + +export function refresh(id) { + const bodyRect = window.document.body.getBoundingClientRect(); + Object.keys(window.raqnEditor).forEach((name) => { + window.raqnEditor[name].instances = window.raqnInstances[name].map((item) => + // eslint-disable-next-line no-use-before-define + getComponentValues(window.raqnEditor[name].dialog, item), + ); + }); + + console.log('renderd', window.raqnEditor, bodyRect); + + publish( + MessagesEvents.render, + { components: window.raqnEditor, bodyRect, uuid: id }, + { usePostMessage: true, targetOrigin: '*' }, + ); +} export function updateComponent(params) { const { uuid, name, option } = params; - const dialog = window.raqnEditors[name]; + // const dialog = window.raqnEditor[name]; const component = window.raqnInstances[name].find((item) => item.componentElem.uuid === uuid); const { componentElem } = component; const { variables, attributes } = option; @@ -22,58 +46,64 @@ export function updateComponent(params) { componentElem.setAttribute(attribute, attributes[attribute]); }); } - const bodyRect = window.document.body.getBoundingClientRect(); - // eslint-disable-next-line no-use-before-define - const instance = getComponentValues(dialog, component); - // eslint-disable-next-line no-use-before-define - initEditor(origin, target, false); - publish('editor:rendered', { instance, dialog, bodyRect }, { usePostMessage: true, targetOrigin: '*' }); + + refresh(uuid); } export function getComponentValues(dialog, item) { const domRect = item.componentElem.getBoundingClientRect(); - console.log('dialog.variables', dialog.variables, item.componentElem); - dialog.variables = Object.keys(dialog.variables).reduce((data, variable) => { + let { variables = {}, attributes = {} } = dialog; + const { selection = {} } = dialog; + variables = Object.keys(variables).reduce((data, variable) => { const value = getComputedStyle(item.componentElem).getPropertyValue(variable); - console.log('value', data); - data[variable] = { ...dialog.variables[variable], value }; - console.log('value', data); + + data[variable] = { ...variables[variable], value }; + + return data; + }, {}); + attributes = Object.keys(attributes).reduce((data, attribute) => { + const value = item.componentElem.getAttribute(attribute); + + data[attribute] = { ...attributes[attribute], value }; return data; }, {}); const cleanData = Object.fromEntries(Object.entries(item.componentElem)); delete cleanData.nestedComponents; delete cleanData.nestedComponentsConfig; - return { ...cleanData, domRect }; + return { ...cleanData, domRect, editor: { variables, attributes, selection } }; } -export default function initEditor(o, t, listeners = true) { - origin = o; - target = t; - - Object.keys(window.raqnComponents).forEach(async (componentName) => { - try { - const editor = await loadModule(`/blocks/${componentName}/${componentName}.editor`, false); - const mod = await editor.js; - - if (mod && mod.default) { - const dialog = mod.default(); - window.raqnEditors[componentName] = dialog; - const instances = window.raqnInstances[componentName].map((item) => getComponentValues(dialog, item)); - - const bodyRect = window.document.body.getBoundingClientRect(); - - publish( - 'editor:loaded', - { componentName, dialog, instances, bodyRect }, - { - usePostMessage: true, - targetOrigin: '*', - }, - ); - } - } catch (error) { - // console.log(error); - } +export default function initEditor(listeners = true) { + Promise.all( + Object.keys(window.raqnComponents).map( + (componentName) => + new Promise((resolve) => { + setTimeout(async () => { + try { + const component = await loadModule(`/blocks/${componentName}/${componentName}.editor`, false); + const mod = await component.js; + if (mod && mod.default) { + const dialog = await mod.default(); + // available dialog and component instances + window.raqnEditor[componentName] = { dialog, instances: [], name: componentName }; + window.raqnEditor[componentName].instances = window.raqnInstances[componentName].map((item) => + getComponentValues(dialog, item), + ); + } + resolve(); + } catch (error) { + resolve(); + } + }); + }), + ), + ).finally(() => { + const bodyRect = window.document.body.getBoundingClientRect(); + publish( + MessagesEvents.loaded, + { components: window.raqnEditor, bodyRect }, + { usePostMessage: true, targetOrigin: '*' }, + ); }); if (listeners) { // init editor if message from parent @@ -81,7 +111,7 @@ export default function initEditor(o, t, listeners = true) { if (e && e.data) { const { message, params } = e.data; switch (message) { - case 'editor:select': + case MessagesEvents.select: updateComponent(params); break; diff --git a/scripts/init.js b/scripts/init.js index bdc57b7f..c989d8ce 100644 --- a/scripts/init.js +++ b/scripts/init.js @@ -192,12 +192,21 @@ globalInit.init(); window.addEventListener('message', async (e) => { if (e && e.data) { const { message, params } = e.data; - if (message === 'initEditor' && !Array.isArray(params)) { - const editor = await import('./editor.js'); - const { origin, target } = params; - setTimeout(() => { - editor.default(origin, target); - }, 2000); + if (!Array.isArray(params)) { + switch (message) { + case 'raqn:editor:start': + (async function startEditor() { + const editor = await import('./editor.js'); + const { origin, target } = params; + setTimeout(() => { + editor.default(origin, target); + }, 2000); + })(); + break; + // other cases? + default: + break; + } } } }); diff --git a/scripts/pubsub.js b/scripts/pubsub.js index 47f51dc0..6dc0a32b 100644 --- a/scripts/pubsub.js +++ b/scripts/pubsub.js @@ -68,19 +68,9 @@ export const callStack = (message, params, options) => { if (actions[message]) { const messageCallStack = Array.from(actions[message]); // copy array // call all actions by last one registered - let prevent = false; - - // Some current usages of `publish` are not passing `params` as an object. - // For these cases the option to `stopImmediatePropagation` will not be available. - if (params && typeof params === 'object' && !Array.isArray(params)) { - params.stopImmediatePropagation = () => { - prevent = true; - }; - } - // run the call stack unless `stopImmediatePropagation()` was called in previous action (prevent further actions to run) const callStackMethod = callStackAscending ? 'shift' : 'pop'; - while (!prevent && messageCallStack.length > 0) { + while (messageCallStack.length > 0) { const action = messageCallStack[callStackMethod](); action(params); } @@ -100,6 +90,7 @@ export const postMessage = (message, params, options = {}) => { // eslint-disable-next-line no-console console.warn(error); } + console.log('postMessage', data); // upward message window.parent.postMessage(data, targetOrigin); // downward message From 1405e2f249f06880d25e761af626ccff4793217f Mon Sep 17 00:00:00 2001 From: Felipe Simoes Date: Mon, 3 Jun 2024 10:38:06 +0200 Subject: [PATCH 03/13] fonts indexer --- fonts/index.json | 11 +++++++++++ scripts/editor.js | 17 +++++++++-------- 2 files changed, 20 insertions(+), 8 deletions(-) create mode 100644 fonts/index.json diff --git a/fonts/index.json b/fonts/index.json new file mode 100644 index 00000000..ed28a60a --- /dev/null +++ b/fonts/index.json @@ -0,0 +1,11 @@ +{ + "roboto": { + "regular": "roboto-regular.woff", + "bold": "roboto-bold.woff" + }, + "segoe-ui": { + "regular": "segoe-ui-regular.woff", + "bold": "segoe-ui-bold.woff", + "italic": "segoe-ui-italic.woff" + } +} diff --git a/scripts/editor.js b/scripts/editor.js index bc624536..189a6a8c 100644 --- a/scripts/editor.js +++ b/scripts/editor.js @@ -15,14 +15,13 @@ export const MessagesEvents = { export function refresh(id) { const bodyRect = window.document.body.getBoundingClientRect(); Object.keys(window.raqnEditor).forEach((name) => { + console.log('name', name); window.raqnEditor[name].instances = window.raqnInstances[name].map((item) => // eslint-disable-next-line no-use-before-define getComponentValues(window.raqnEditor[name].dialog, item), ); }); - console.log('renderd', window.raqnEditor, bodyRect); - publish( MessagesEvents.render, { components: window.raqnEditor, bodyRect, uuid: id }, @@ -33,7 +32,7 @@ export function refresh(id) { export function updateComponent(params) { const { uuid, name, option } = params; // const dialog = window.raqnEditor[name]; - const component = window.raqnInstances[name].find((item) => item.componentElem.uuid === uuid); + const component = window.raqnInstances[name].find((element) => element.uuid === uuid); const { componentElem } = component; const { variables, attributes } = option; if (variables) { @@ -50,24 +49,26 @@ export function updateComponent(params) { refresh(uuid); } -export function getComponentValues(dialog, item) { - const domRect = item.componentElem.getBoundingClientRect(); +export function getComponentValues(dialog, element) { + const domRect = element.getBoundingClientRect(); let { variables = {}, attributes = {} } = dialog; const { selection = {} } = dialog; variables = Object.keys(variables).reduce((data, variable) => { - const value = getComputedStyle(item.componentElem).getPropertyValue(variable); + const value = getComputedStyle(element).getPropertyValue(variable); data[variable] = { ...variables[variable], value }; return data; }, {}); attributes = Object.keys(attributes).reduce((data, attribute) => { - const value = item.componentElem.getAttribute(attribute); + const value = element.getAttribute(attribute); data[attribute] = { ...attributes[attribute], value }; return data; }, {}); - const cleanData = Object.fromEntries(Object.entries(item.componentElem)); + const cleanData = Object.fromEntries(Object.entries(element)); + delete cleanData.initOptions; + delete cleanData.childComponents; delete cleanData.nestedComponents; delete cleanData.nestedComponentsConfig; return { ...cleanData, domRect, editor: { variables, attributes, selection } }; From a86b4abc507d24c7873cd14a00bb2646e2b272bf Mon Sep 17 00:00:00 2001 From: Felipe Simoes Date: Mon, 3 Jun 2024 22:27:53 +0200 Subject: [PATCH 04/13] updates --- blocks/theming/theming.js | 192 ++++++++++++++++++++++++++------------ fonts/index.json | 16 +++- scripts/component-base.js | 1 - scripts/editor.js | 7 ++ scripts/libs.js | 28 ++++++ 5 files changed, 176 insertions(+), 68 deletions(-) diff --git a/blocks/theming/theming.js b/blocks/theming/theming.js index 607c005b..020692c5 100644 --- a/blocks/theming/theming.js +++ b/blocks/theming/theming.js @@ -1,34 +1,35 @@ import ComponentBase from '../../scripts/component-base.js'; -import { globalConfig, getMeta } from '../../scripts/libs.js'; +import { globalConfig, getMeta, unflat } from '../../scripts/libs.js'; // minify alias -const metaTheming = getMeta('theming'); -const metaFragment = metaTheming && `${metaTheming}.json`; +// const metaTheming = getMeta('theming'); +// const metaFragment = metaTheming && `${metaTheming}.json`; const k = Object.keys; export default class Theming extends ComponentBase { - nestedComponentsConfig = {}; setDefaults() { super.setDefaults(); this.scapeDiv = document.createElement('div'); - // keep as it is - this.fragmentPath = metaFragment || 'theming.json'; - this.skip = ['tags']; - this.toTags = [ - 'font-size', - 'font-weight', - 'font-family', - 'line-height', - 'font-style', - 'font-margin-block', - ]; - this.transform = { 'font-margin-block': 'margin-block' }; + this.themeJson = { data: [] }; + + this.globalsVar = ['c-', 'global']; + this.toTags = []; + this.transform = {}; this.tags = ''; this.fontFace = ''; this.atomic = ''; } + isGlobal(key) { + return this.globalsVar.reduce((a, g) => { + if (key.indexOf(g) > -1) { + return true; + } + return a; + }, false); + } + fontFaceTemplate(fontFace) { if (fontFace.indexOf('-') > -1) { const [name, ...rest] = fontFace.split('-'); @@ -58,10 +59,7 @@ export default class Theming extends ComponentBase { return k(values).map((value) => { const val = values[value]; return `${tag} {${k(val) - .map( - (v) => - `${this.getKey(v)}: var(--scope-${this.getKey(v)}, ${val[v]});`, - ) + .map((v) => `${this.getKey(v)}: var(--${this.getKey(v)}, ${val[v]});`) .join('')}}`; }); } @@ -79,23 +77,44 @@ export default class Theming extends ComponentBase { const value = t[key][row]; let variable = ''; if (value) { - if (key === 'font-face') { - this.fontFace += this.fontFaceTemplate(value); - } else { - variable = `\n--raqn-${this.getKey(key)}-${row}: ${this.escapeHtml( - value, - ).trim()};`; - this.atomic += `body .${this.getKey(key)}-${row} {--scope-${this.getKey( - key, - )}: var(--raqn-${this.getKey(key)}-${row});}\n`; - } + variable = `\n--${this.getKey(key)}-${row}: ${this.escapeHtml(value).trim()};`; + this.atomic += `body .${this.getKey(key)}-${row} {--${this.getKey(key)}: var(--${this.getKey(key)}-${row});}\n`; } return variable; } - readValue() { - const { data } = this.themeJson; - const keys = data.map((item) => item.key); + getThemeClasses(themeKeys, keys, t, segment = 'theme') { + return themeKeys.reduce( + (acc, theme) => `${acc} + .${segment}-${theme} { + ${keys.reduce((a, key) => { + if (t[key][theme]) { + return `${a} \n --${key}: var(--${key}-${theme});`; + } + return a; + }, '')} + } + `, + '', + ); + } + + getVariables(themeKeys, keys, t) { + return keys.reduce( + (acc, key) => + `${acc} + ${k(t[key]) + .map((row) => this.renderVariables(key, row, t)) + .join('')}`, + '', + ); + } + + readValue(data, type) { + let keys = data.map((item) => item.key); + const themeKeys = k(data[0]).slice(1); + const globals = keys.filter((item) => this.isGlobal(item)); + keys = keys.filter((item) => !globals.includes(item)); const t = data.reduce( (ac, item, i) => keys.reduce((acc, key) => { @@ -111,33 +130,67 @@ export default class Theming extends ComponentBase { }, ac), {}, ); - // font tags - if (t.tags) { - this.tags = k(t.tags) - .map((index) => this.fontTags(t, index)) - .join('\n'); - } - // full scoped theme classes - this.themes = this.themesKeys + + this.themes = `${this.themes || ''} ${this.getThemeClasses(themeKeys, keys, t, type)}`; + this.variables = `${this.variables || ''} + body { ${this.getVariables(themeKeys, keys, t)} } + `; + + return { keys, themeKeys, t }; + + // console.log('theme classes', this.variables); + + // // font tags + // if (t.tags) { + // this.tags = + // (this.tags || '') + + // k(t.tags) + // .map((index) => this.fontTags(t, index)) + // .join('\n'); + // } + // // full scoped theme classes + // this.themes = + // (this.themes || '') + + // this.themesKeys + // .map( + // (theme) => `.theme-${theme} {${k(t) + // .filter((key) => ![...this.skip, ...this.toTags].includes(key)) + // .map((key) => (t[key][theme] ? `--${key}: var(--${key}-${theme});` : '')) + // .filter((v) => v !== '') + // .join('')} + // }`, + // ) + // .join(''); + + // this.variables = `${this.variables || ''} body{${k(t) + // .filter((key) => ![...this.skip].includes(key)) + // .map((key) => { + // const rows = k(t[key]); + // return rows.map((row) => this.renderVariables(key, row, t)).join(''); + // }) + // .join('')}}`; + } + + prepareTags(keys, themeKeys, t) { + // console.log('prepareTags keys', unFlattenPropertieskeys, themeKeys, t); + const tags = unflat(t); + + this.tags = Object.keys(tags) .map( - (theme) => `.theme-${theme} {${k(t) - .filter((key) => ![...this.skip, ...this.toTags].includes(key)) - .map((key) => - t[key][theme] ? `--scope-${key}: var(--raqn-${key}-${theme});` : '', - ) - .filter((v) => v !== '') - .join('')} - }`, + (tag) => + `${tag} { + ${keys + .filter((key) => key.indexOf(tag) > -1) + .reduce((acc, prop) => { + const val = t[prop].default; + + return `${acc} + ${prop.replace(`${tag}-`, '')}: var(--${prop},${val}); + `; + }, '')}} + `, ) .join(''); - - this.variables = `body{${k(t) - .filter((key) => ![...this.skip].includes(key)) - .map((key) => { - const rows = k(t[key]); - return rows.map((row) => this.renderVariables(key, row, t)).join(''); - }) - .join('')}}`; } styles() { @@ -148,14 +201,29 @@ export default class Theming extends ComponentBase { document.head.appendChild(style); }); const themeMeta = getMeta('theme'); - document.body.classList.add(themeMeta || 'theme-default'); + document.body.classList.add(themeMeta, 'font-default', 'color-default'); } - async processFragment(response) { + async processFragment(response, type = 'color') { if (response.ok) { - this.themeJson = await response.json(); - this.readValue(); - this.styles(); + const responseData = await response.json(); + const { keys, themeKeys, t } = this.readValue(responseData.data, type); + if (type === 'font') { + this.prepareTags(keys, themeKeys, t); + } } } + + colors(data) { + console.log('colors', data); + } + + async loadFragment() { + // load colors + await fetch('colors.json').then((response) => this.processFragment(response)); + // load fonts + await fetch('fonts.json').then((response) => this.processFragment(response, 'font')); + + this.styles(); + } } diff --git a/fonts/index.json b/fonts/index.json index ed28a60a..75701e1b 100644 --- a/fonts/index.json +++ b/fonts/index.json @@ -1,11 +1,17 @@ { "roboto": { - "regular": "roboto-regular.woff", - "bold": "roboto-bold.woff" + "stack": "'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif", + "options": { + "regular": "roboto-regular.woff2", + "bold": "roboto-bold.woff2" + } }, "segoe-ui": { - "regular": "segoe-ui-regular.woff", - "bold": "segoe-ui-bold.woff", - "italic": "segoe-ui-italic.woff" + "stack": "Helvetica Neue, Helvetica, Arial, sans-serif", + "options": { + "regular": "segoe-ui-regular.woff", + "bold": "segoe-ui-bold.woff", + "italic": "segoe-ui-italic.woff" + } } } diff --git a/scripts/component-base.js b/scripts/component-base.js index a7e0a57c..cba90444 100644 --- a/scripts/component-base.js +++ b/scripts/component-base.js @@ -238,7 +238,6 @@ export default class ComponentBase extends HTMLElement { const { target } = this.initOptions; const { contentFromTargets } = this.config; if (!contentFromTargets) return; - this.append(...target.childNodes); } diff --git a/scripts/editor.js b/scripts/editor.js index 189a6a8c..24bf5827 100644 --- a/scripts/editor.js +++ b/scripts/editor.js @@ -2,6 +2,7 @@ import { loadModule } from './libs.js'; import { publish } from './pubsub.js'; window.raqnEditor = window.raqnEditor || {}; +let watcher = false; export const MessagesEvents = { init: 'raqn:editor:start', @@ -105,6 +106,12 @@ export default function initEditor(listeners = true) { { components: window.raqnEditor, bodyRect }, { usePostMessage: true, targetOrigin: '*' }, ); + if (!watcher) { + window.addEventListener('resize', () => { + refresh(); + }); + watcher = true; + } }); if (listeners) { // init editor if message from parent diff --git a/scripts/libs.js b/scripts/libs.js index 7cdbe329..ac5f2a3e 100644 --- a/scripts/libs.js +++ b/scripts/libs.js @@ -452,3 +452,31 @@ export function getBaseUrl() { export function isHomePage(url) { return getBaseUrl() === (url || window.location.href); } + +export const flat = (obj = {}, alreadyFlat = '') => + Object.entries(obj).reduce((acc, [key, value]) => { + const newKey = `${alreadyFlat ? `${alreadyFlat}-` : ''}${key}`; + if (isObject(value)) { + Object.assign(acc, flat(value, newKey)); + } else { + acc[newKey] = value; + } + return acc; + }, {}); + +export const unflat = (obj = {}) => { + const un = {}; + Object.keys(obj).forEach((k) => { + const keys = k.split('-'); + keys.reduce((acc, key, index) => { + if (index === keys.length - 1) { + acc[key] = obj[k]; + } else { + acc[key] ??= {}; + return acc[key]; + } + return acc; + }, un); + }); + return un; +}; From 577a0110151543fe2f0fa835951a3464cce65e09 Mon Sep 17 00:00:00 2001 From: Felipe Simoes Date: Wed, 5 Jun 2024 14:54:13 +0200 Subject: [PATCH 05/13] update variables --- .stylelintrc.json | 5 +- blocks/accordion/accordion.css | 26 ++--- blocks/breadcrumbs/breadcrumbs.css | 6 +- blocks/button/button.css | 36 +++---- blocks/button/button.editor.js | 82 ++++++++-------- blocks/card/card.css | 23 +++-- blocks/card/card.editor.js | 26 ++--- blocks/column/column.css | 2 +- blocks/footer/footer.css | 12 +-- blocks/footer/footer.editor.js | 31 ++---- blocks/header/header.css | 12 +-- blocks/header/header.js | 2 + blocks/hero/hero.css | 13 +-- blocks/icon/icon.css | 14 +-- blocks/navigation/navigation.css | 52 +++++----- blocks/router/router.css | 2 +- blocks/theming/theming.editor.js | 37 +++++++ blocks/theming/theming.js | 133 ++++++++----------------- docs/raqn/components.md | 18 ++-- docs/raqn/theming.md | 150 +++++++++++++++-------------- styles/styles.css | 103 ++++++++------------ 21 files changed, 371 insertions(+), 414 deletions(-) create mode 100644 blocks/theming/theming.editor.js diff --git a/.stylelintrc.json b/.stylelintrc.json index b70de4de..b5ef7f49 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,6 +1,7 @@ { "extends": ["stylelint-config-standard"], "rules": { - "no-descending-specificity": null + "no-descending-specificity": null, + "custom-property-pattern": null } -} \ No newline at end of file +} diff --git a/blocks/accordion/accordion.css b/blocks/accordion/accordion.css index 9774cf74..0a681932 100644 --- a/blocks/accordion/accordion.css +++ b/blocks/accordion/accordion.css @@ -1,12 +1,12 @@ raqn-accordion { - --scope-icon-size: 1em; - --accordion-background: var(--scope-background, black); - --accordion-color: var(--scope-color, white); + --icon-size: 1em; + --accordion-background: var(--background, black); + --accordion-color: var(--title, white); background: var(--accordion-background); color: var(--accordion-color); - margin: var(--scope-margin, 0); - padding: var(--scope-padding, 0); + margin: var(--margin, 0); + padding: var(--padding, 0); display: grid; } @@ -21,9 +21,9 @@ raqn-accordion accordion-control.active raqn-icon { } .accordion-control { - border-block-start: var(--scope-border-block-start, none); - border-inline-start: var(--scope-border-inline-start, none); - border-inline-end: var(--scope-border-inline-end, none); + border-block-start: var(--border-block-start, none); + border-inline-start: var(--border-inline-start, none); + border-inline-end: var(--border-inline-end, none); cursor: pointer; display: flex; align-items: center; @@ -36,8 +36,8 @@ raqn-accordion accordion-control.active raqn-icon { } .accordion-control > * { - --scope-headings-color: var(--scope-color, black); - --scope-hover-color: var(--scope-accent-color, gray); + --headings-color: var(--title, black); + --hover-color: var(--accent, gray); width: 100%; display: flex; @@ -46,7 +46,7 @@ raqn-accordion accordion-control.active raqn-icon { } .accordion-control:hover { - --scope-color: var(--scope-headings-color); + color: var(--headings-color); } .accordion-content { @@ -54,8 +54,8 @@ raqn-accordion accordion-control.active raqn-icon { max-height: 0; overflow: hidden; opacity: 0; - border-block-end: var(--scope-border-block-end, none); - border-block-start: var(--scope-border-block-start, none); + border-block-end: var(--border-block-end, none); + border-block-start: var(--border-block-start, none); margin-block-end: -1px; transition: max-height 0.5s ease-in-out, diff --git a/blocks/breadcrumbs/breadcrumbs.css b/blocks/breadcrumbs/breadcrumbs.css index f53ea6f9..be04d905 100644 --- a/blocks/breadcrumbs/breadcrumbs.css +++ b/blocks/breadcrumbs/breadcrumbs.css @@ -4,8 +4,8 @@ raqn-breadcrumbs { gap: 10px; align-items: center; padding: 10px 0; - background: var(--scope-background, transparent); - color: var(--scope-color, #000); + background: var(--background, transparent); + color: var(--color, #000); } raqn-breadcrumbs ul { @@ -21,7 +21,7 @@ raqn-breadcrumbs ul li { } raqn-breadcrumbs ul li a { - color: var(--scope-color); + color: var(--text); font-weight: normal; } diff --git a/blocks/button/button.css b/blocks/button/button.css index bc5ebc04..8d775ddb 100644 --- a/blocks/button/button.css +++ b/blocks/button/button.css @@ -3,37 +3,37 @@ raqn-button { display: grid; align-content: center; align-items: center; - justify-items: var(--scope-justify, start); + justify-items: var(--justify, start); - --button-padding-block: var(--scope-button-padding-block, 10px); - --button-padding-inline: var(--scope-button-padding-inline, 20px); - --scope-border-radius: 0; - --scope-border-block-start: 1px solid transparent; - --scope-border-block-end: 1px solid transparent; - --scope-border-inline-start: 1px solid transparent; - --scope-border-inline-end: 1px solid transparent; + --button-padding-block: var(--button-padding-block, 10px); + --button-padding-inline: var(--button-padding-inline, 20px); + --border-radius: 0; + --border-block-start: 1px solid transparent; + --border-block-end: 1px solid transparent; + --border-inline-start: 1px solid transparent; + --border-inline-end: 1px solid transparent; } raqn-button > * { - background: var(--scope-accent-background, #000); - color: var(--scope-accent-color, #fff); + background: var(--accent, #000); + color: var(--accentText, #fff); text-transform: none; - border-radius: var(--scope-border-radius, 0); - border-block-start: var(--scope-border-block-start, 1px solid transparent); - border-block-end: var(--scope-border-block-end, 1px solid transparent); - border-inline-start: var(--scope-border-inline-start, 1px solid transparent); - border-inline-end: var(--scope-border-inline-end, 1px solid transparent); + border-radius: var(--border-radius, 0); + border-block-start: var(--border-block-start, 1px solid transparent); + border-block-end: var(--border-block-end, 1px solid transparent); + border-inline-start: var(--border-inline-start, 1px solid transparent); + border-inline-end: var(--border-inline-end, 1px solid transparent); overflow: hidden; } raqn-button > *:hover { - background: var(--scope-accent-background-hover, #fff); - color: var(--scope-accent-color-hover, #fff); + background: var(--hover, #fff); + color: var(--hoverText, #fff); border-color: currentcolor; } raqn-button a { - color: var(--scope-accent-color, currentcolor); + color: var(--accentText, currentcolor); padding-block: var(--button-padding-block, 10px); padding-inline: var(--button-padding-inline, 20px); text-decoration: none; diff --git a/blocks/button/button.editor.js b/blocks/button/button.editor.js index ed580075..1bb63567 100644 --- a/blocks/button/button.editor.js +++ b/blocks/button/button.editor.js @@ -1,12 +1,12 @@ export default function config() { return { variables: { - '--scope-accent-background': { + '--accent-background': { type: 'text', label: 'Background', helpText: 'The background color of the button.', }, - '--scope-accent-color': { + '--accent-color': { type: 'text', label: 'Color', helpText: 'The text color of the button.', @@ -21,47 +21,47 @@ export default function config() { label: 'Padding Inline', helpText: 'The padding inline of the button.', }, - '--scope-border-block-end': { + '--border-block-end': { type: 'text', label: 'Border Block End', helpText: 'The border block end of the button.', }, - '--scope-border-radius': { + '--border-radius': { type: 'text', label: 'Border Radius', helpText: 'The border radius of the button.', }, - '--scope-border-block-start': { + '--border-block-start': { type: 'text', label: 'Border Block Start', helpText: 'The border block start of the button.', }, - '--scope-border-inline-end': { + '--border-inline-end': { type: 'text', label: 'Border Inline End', helpText: 'The border inline end of the button.', }, - '--scope-border-inline-start': { + '--border-inline-start': { type: 'text', label: 'Border Inline Start', helpText: 'The border inline start of the button.', }, - '--scope-box-shadow': { + '--box-shadow': { type: 'text', label: 'Box Shadow', helpText: 'The box shadow of the button.', }, - '--scope-accent-background-hover': { + '--accent-background-hover': { type: 'text', label: 'Background Hover', helpText: 'The background color of the button when hovered.', }, - '--scope-accent-color-hover': { + '--accent-color-hover': { type: 'text', label: 'Color Hover', helpText: 'The text color of the button when hovered.', }, - '--scope-justify': { + '--justify': { type: 'text', label: 'Justify', helpText: 'The justify of the button.', @@ -74,16 +74,16 @@ export default function config() { preview: 'http://localhost:8888/@henkel/theme-interface/assets/previews/button/blue.png', }, variables: { - '--scope-accent-background': '#007bff', - '--scope-accent-color': '#fff', - '--scope-border-block-end': '0', - '--scope-border-block-start': '0', - '--scope-border-inline-end': '0', - '--scope-border-inline-start': '0', - '--scope-box-shadow': 'none', - '--scope-accent-background-hover': '#0056b3', - '--scope-accent-color-hover': '#fff', - '--scope-justify': 'start', + '--accent-background': '#007bff', + '--accent-color': '#fff', + '--border-block-end': '0', + '--border-block-start': '0', + '--border-inline-end': '0', + '--border-inline-start': '0', + '--box-shadow': 'none', + '--accent-background-hover': '#0056b3', + '--accent-color-hover': '#fff', + '--justify': 'start', }, }, Red: { @@ -92,16 +92,16 @@ export default function config() { preview: 'http://localhost:8888/@henkel/theme-interface/assets/previews/button/red.png', }, variables: { - '--scope-accent-background': 'red', - '--scope-accent-color': 'white', - '--scope-border-block-end': '1px', - '--scope-border-block-start': '1px', - '--scope-border-inline-end': '1px', - '--scope-border-inline-start': '1px', - '--scope-box-shadow': '1px 1px 1px 1px rgba(0, 0, 0, 0.1)', - '--scope-accent-background-hover': 'white', - '--scope-accent-color-hover': 'red', - '--scope-justify': 'start', + '--accent-background': 'red', + '--accent-color': 'white', + '--border-block-end': '1px', + '--border-block-start': '1px', + '--border-inline-end': '1px', + '--border-inline-start': '1px', + '--box-shadow': '1px 1px 1px 1px rgba(0, 0, 0, 0.1)', + '--accent-background-hover': 'white', + '--accent-color-hover': 'red', + '--justify': 'start', }, }, White: { @@ -110,16 +110,16 @@ export default function config() { preview: 'http://localhost:8888/@henkel/theme-interface/assets/previews/button/white.png', }, variables: { - '--scope-accent-background': 'white', - '--scope-accent-color': 'black', - '--scope-border-block-end': '10px', - '--scope-border-block-start': '10px', - '--scope-border-inline-end': '1px', - '--scope-border-inline-start': '1px', - '--scope-box-shadow': '1px 1px 1px 1px rgba(0, 0, 0, 0.1)', - '--scope-accent-background-hover': 'white', - '--scope-accent-color-hover': 'red', - '--scope-justify': 'start', + '--accent-background': 'white', + '--accent-color': 'black', + '--border-block-end': '10px', + '--border-block-start': '10px', + '--border-inline-end': '1px', + '--border-inline-start': '1px', + '--box-shadow': '1px 1px 1px 1px rgba(0, 0, 0, 0.1)', + '--accent-background-hover': 'white', + '--accent-color-hover': 'red', + '--justify': 'start', }, }, }, diff --git a/blocks/card/card.css b/blocks/card/card.css index 3d61bfeb..edf3a13d 100644 --- a/blocks/card/card.css +++ b/blocks/card/card.css @@ -1,23 +1,23 @@ raqn-card { - background: var(--scope-background, transparent); - color: var(--scope-color, #fff); + background: var(--background, transparent); + color: var(--text, #fff); display: grid; position: relative; grid-template-columns: var(--card-columns, 1fr); - gap: var(--scope-gap, 20px); - padding: var(--scope-padding, 20px 0); + gap: var(--gap, 20px); + padding: var(--padding, 20px 0); } raqn-card > div { display: flex; - gap: var(--scope-gap, 20px); + gap: var(--gap, 20px); position: relative; - background: var(--scope-inner-background, transparent); - padding: var(--scope-inner-padding, 20px); - border-block-start: var(--scope-border-block-start, none); - border-block-end: var(--scope-border-block-end, none); - border-inline-start: var(--scope-border-inline-start, none); - border-inline-end: var(--scope-border-inline-end, none); + background: var(--inner-background, transparent); + padding: var(--inner-padding, 20px); + border-block-start: var(--border-block-start, none); + border-block-end: var(--border-block-end, none); + border-inline-start: var(--border-inline-start, none); + border-inline-end: var(--border-inline-end, none); } raqn-card :where(a, button) { @@ -41,7 +41,6 @@ raqn-card div > div:first-child > p:has(> em:only-child > a:only-child) { margin: 0; } - raqn-card div > div { display: flex; flex-direction: column; diff --git a/blocks/card/card.editor.js b/blocks/card/card.editor.js index 0403c67d..98a58380 100644 --- a/blocks/card/card.editor.js +++ b/blocks/card/card.editor.js @@ -1,22 +1,22 @@ export default function config() { return { variables: { - '--scope-background': { + '--background': { type: 'text', label: 'Background', helpText: 'The background color of the card.', }, - '--scope-color': { + '--color': { type: 'text', label: 'Color', helpText: 'The text color of the card.', }, - '--scope-gap': { + '--gap': { type: 'text', label: 'Gap', helpText: 'The gap between cards.', }, - '--scope-padding': { + '--padding': { type: 'text', label: 'Padding', helpText: 'The padding of the card.', @@ -52,9 +52,9 @@ export default function config() { selection: { variant1: { variables: { - '--scope-color': 'white', - '--scope-gap': '40px', - '--scope-padding': '20px', + '--color': 'white', + '--gap': '40px', + '--padding': '20px', }, attributes: { 'data-columns': '2', @@ -64,9 +64,9 @@ export default function config() { }, variant2: { variables: { - '--scope-color': 'white', - '--scope-gap': '40px', - '--scope-padding': '20px', + '--color': 'white', + '--gap': '40px', + '--padding': '20px', }, attributes: { 'data-columns': '3', @@ -76,9 +76,9 @@ export default function config() { }, variant3: { variables: { - '--scope-color': 'white', - '--scope-gap': '40px', - '--scope-padding': '20px', + '--color': 'white', + '--gap': '40px', + '--padding': '20px', }, attributes: { 'data-columns': '4', diff --git a/blocks/column/column.css b/blocks/column/column.css index 002ec5ac..f24646d4 100644 --- a/blocks/column/column.css +++ b/blocks/column/column.css @@ -1,5 +1,5 @@ raqn-column { - margin: var(--scope-margin, 0); + margin: var(--margin, 0); width: 100%; display: grid; } diff --git a/blocks/footer/footer.css b/blocks/footer/footer.css index 7aa0fb17..9be87f1b 100644 --- a/blocks/footer/footer.css +++ b/blocks/footer/footer.css @@ -1,12 +1,12 @@ footer { - background: var(--scope-background-color); - width: var(--scope-max-width); + background: var(--background-color); + width: var(--max-width); margin: 0 auto; } raqn-footer { - background: var(--scope-background-color); - border-top: 1px solid var(--scope-color); + background: var(--background-color); + border-top: 1px solid var(--text); } raqn-footer ul { @@ -17,7 +17,7 @@ raqn-footer ul { } raqn-footer ul li a { - color: var(--scope-color); + color: var(--text); } @media screen and (min-width: 1024px) { @@ -28,7 +28,7 @@ raqn-footer ul li a { raqn-footer ul li a { padding: 10px 1.2em; - border-inline-end: 1px solid var(--scope-color); + border-inline-end: 1px solid var(--text); } raqn-footer ul { diff --git a/blocks/footer/footer.editor.js b/blocks/footer/footer.editor.js index 142db2a3..4010848f 100644 --- a/blocks/footer/footer.editor.js +++ b/blocks/footer/footer.editor.js @@ -1,37 +1,24 @@ export default function config() { return { variables: { - '--scope-background-color': { + '--background-color': { type: 'text', label: 'Background Color', + scope: 'page', helpText: 'The background color of the footer.', }, - '--scope-color': { + '--color': { type: 'text', label: 'Color', + scope: 'global', helpText: 'The text color of the footer.', }, }, - selection: { - Default: { - descritpion: { - label: 'Default', - preview: 'http://localhost:8888/@henkel/theme-interface/assets/previews/footer/default.png', - }, - variables: { - '--scope-background-color': 'black', - '--scope-color': 'white', - }, - }, - Inverted: { - descritpion: { - label: 'Inverted colors', - preview: 'http://localhost:8888/@henkel/theme-interface/assets/previews/footer/inverted.png', - }, - variables: { - '--scope-background-color': 'white', - '--scope-color': 'black', - }, + attributes: { + class: { + type: 'text', + label: 'Class', + helpText: 'The class of the footer.', }, }, }; diff --git a/blocks/header/header.css b/blocks/header/header.css index 0f9948f8..c8806448 100644 --- a/blocks/header/header.css +++ b/blocks/header/header.css @@ -1,14 +1,14 @@ raqn-header { - --scope-background: var(--scope-header-background, #fff); - --scope-color: var(--scope-header-color, #000); - --scope-top: var(--scope-header-top, 0); + --header-background: var(--background, #fff); + --color: var(--text, #000); + --top: var(--header-top, 0); position: fixed; - top: var(--scope-top); + top: var(--top); width: 100%; - min-height: var(--scope-header-height, 64px); + min-height: var(--header-height, 110px); display: grid; - background: var(--scope-header-background, #fff); + background: var(--header-background, #fff); align-items: center; z-index: 100; } diff --git a/blocks/header/header.js b/blocks/header/header.js index 0a9209e3..a6e8854e 100644 --- a/blocks/header/header.js +++ b/blocks/header/header.js @@ -2,6 +2,7 @@ import ComponentBase from '../../scripts/component-base.js'; import { eagerImage, getMeta } from '../../scripts/libs.js'; const metaHeader = getMeta('header'); +const headerClass = getMeta('headerClass') || 'color-primary'; const metaFragment = !!metaHeader && `${metaHeader}.plain.html`; export default class Header extends ComponentBase { static loaderConfig = { @@ -25,6 +26,7 @@ export default class Header extends ComponentBase { } connected() { + this.classList.add(...headerClass.split('.')); eagerImage(this, 1); } } diff --git a/blocks/hero/hero.css b/blocks/hero/hero.css index fefadc59..fe90bfc0 100644 --- a/blocks/hero/hero.css +++ b/blocks/hero/hero.css @@ -1,22 +1,17 @@ /* block specific CSS goes here */ raqn-hero { - --hero-background: var(--scope-background, black); - --hero-color: var(--scope-color, white); - --hero-grid-template-columns: var(--scope-hero-columns, 1fr); + --hero-background: var(--background, black); + --hero-color: var(--text, white); + --hero-grid-template-columns: var(--hero-columns, 1fr); --hero-hero-order: 0; - --hero-padding-block: var(--scope-hero-padding-block, 40px); + --hero-padding-block: var(--hero-padding-block, 40px); background: var(--hero-background); color: var(--hero-color); align-items: center; grid-template-columns: var(--hero-grid-template-columns, 1fr); padding-block: var(--hero-padding-block); - - > * { - --scope-color: var(--hero-color); - --scope-headings-color: var(--hero-headings-color, var(--hero-color)); - } } @media screen and (min-width: 767px) { diff --git a/blocks/icon/icon.css b/blocks/icon/icon.css index b34e808b..72ea9a03 100644 --- a/blocks/icon/icon.css +++ b/blocks/icon/icon.css @@ -1,11 +1,11 @@ raqn-icon { display: inline-flex; - font-size: 1em; - line-height: 1em; text-align: center; - min-width: var(--scope-icon-size, 1em); - min-height: var(--scope-icon-size, 1em); - justify-content: var(--scope-icon-align, start); + font-size: 1.2em; + line-height: 1.2em; + width: var(--icon-size, 1.2em); + height: var(--icon-size, 1.2em); + justify-content: var(--icon-align, start); text-transform: none; vertical-align: middle; -webkit-font-smoothing: antialiased; @@ -14,8 +14,8 @@ raqn-icon { raqn-icon svg { display: inline-block; - width: var(--scope-icon-size, 1em); - height: var(--scope-icon-size, 1em); + width: var(--icon-size, 1.2em); + height: var(--icon-size, 1.2em); fill: currentcolor; overflow: hidden; vertical-align: middle; diff --git a/blocks/navigation/navigation.css b/blocks/navigation/navigation.css index 8b3db883..fc467970 100644 --- a/blocks/navigation/navigation.css +++ b/blocks/navigation/navigation.css @@ -1,11 +1,11 @@ /* stylelint-disable CssSyntaxError */ raqn-navigation { - --raqn-navigation-background: var(--scope-background, #fff); - --raqn-navigation-color: var(--scope-color, #000); + --raqn-navigation-background: var(--background, #fff); + --raqn-navigation-color: var(--text, #000); --raqn-navigation-level-1: var(--raqn-font-size-4, 1.25rem); --raqn-navigation-level-2: var(--raqn-font-size-5, 1rem); - margin: var(--scope-margin); + margin: var(--margin); width: 100%; display: grid; justify-content: center; @@ -18,12 +18,16 @@ raqn-navigation > nav p { } raqn-navigation .level-1 a:not(:hover) { - color: var(--scope-accent-background, #000); + color: var(--accent, #000); +} + +raqn-navigation .level-1 a:hover { + color: var(--highlight, #000); } raqn-navigation > nav > ul { overflow-y: auto; - max-height: calc(100vh - var(--scope-header-height)); + max-height: calc(100vh - var(--header-height)); } raqn-navigation.active > nav ul, @@ -54,8 +58,8 @@ raqn-navigation > nav button { justify-self: end; align-items: center; justify-content: center; - background: var(--scope-background, #fff); - color: var(--scope-color, #000); + background: var(--accent, #fff); + color: var(--accentText, #000); border: none; border-radius: var(--border-radius); padding: var(--padding-vertical, 10px) var(--padding-horizontal, 10px); @@ -63,8 +67,8 @@ raqn-navigation > nav button { } raqn-navigation.active button { - background: var(--scope-background-hover, #000); - color: var(--scope-color-hover, #fff); + background: var(--hover, #000); + color: var(--hoverText, #fff); } raqn-navigation.active > nav > ul { @@ -72,18 +76,18 @@ raqn-navigation.active > nav > ul { display: block; list-style: none; max-width: 0; - background: var(--scope-background, #fff); + background: var(--background, #fff); min-width: 100%; inset-inline-start: 0; - inset-block-start: var(--scope-header-height, 64px); + inset-block-start: var(--header-height, 64px); height: 100%; - max-height: calc(100vh - var(--scope-header-height, 64px)); + max-height: calc(100vh - var(--header-height, 64px)); margin: 0 auto; padding: 0; } raqn-navigation.active > nav > ul li { - max-width: var(--scope-max-width, 100%); + max-width: var(--max-width, 100%); margin: 0 auto; } @@ -96,7 +100,7 @@ raqn-navigation .accordion-content-wrapper { } raqn-navigation:not([data-compact='true']) > nav a { - line-height: var(--scope-icon-size, 24px); + line-height: var(--icon-size, 24px); } raqn-navigation:not([data-compact='true']) > nav ul { @@ -105,8 +109,8 @@ raqn-navigation:not([data-compact='true']) > nav ul { } raqn-navigation:not([data-compact='true']) > nav > ul { - inset-inline-start: calc((100vw - var(--scope-max-width)) / 2); - inset-block-start: var(--scope-header-height, 64px); + inset-inline-start: calc((100vw - var(--max-width)) / 2); + inset-block-start: var(--header-height, 64px); } raqn-navigation:not([data-compact='true']) > nav > p { @@ -118,16 +122,16 @@ raqn-navigation:not([data-compact='true']) > nav [data-icon='chevron-right'] { } raqn-navigation:not([data-compact='true']) > nav .level-1 a { - padding: var(--scope-padding-vertical, 10px) var(--scope-padding-horizontal, 20px); + padding: var(--padding-vertical, 10px) var(--padding-horizontal, 20px); } raqn-navigation:not([data-compact='true']) > nav .level-2 > a { - color: var(--scope-link-color-hover); + color: var(--highlight, #000); font-size: 1.2em; } raqn-navigation:not([data-compact='true']) > nav .level-2 > a:hover { - color: var(--scope-color, #fff); + color: var(--highlight); } raqn-navigation:not([data-compact='true']) > nav .level-2, @@ -146,8 +150,8 @@ raqn-navigation:not([data-compact='true']) > nav .level-1 > ul { clip-path: inset(0% -100vw 100% -100vw); position: absolute; padding: 0; - inset-block-start: var(--scope-header-height, 64px); - inset-inline-start: calc((100vw - var(--scope-max-width)) / 2); + inset-block-start: var(--header-height, 64px); + inset-inline-start: calc((100vw - var(--max-width)) / 2); transition: clip-path 0.4s ease-in-out; overflow: visible; } @@ -161,13 +165,13 @@ raqn-navigation:not([data-compact='true']) > nav .level-1 > ul .level-2 { raqn-navigation:not([data-compact='true']) > nav .level-1 > ul::after { content: ' '; - margin-inline: calc(-1 * ((100vw - var(--scope-max-width)) / 2)); + margin-inline: calc(-1 * ((100vw - var(--max-width)) / 2)); position: absolute; height: 100%; width: 100vw; inset-inline-start: 0; - background: var(--scope-background, #fff); - border-block-start: 1px solid var(--scope-color, #000); + background: var(--background, #fff); + border-block-start: 1px solid var(--accent, #000); box-shadow: 0 0 30px #000; z-index: 1; } diff --git a/blocks/router/router.css b/blocks/router/router.css index 1aca89f2..89061fe7 100644 --- a/blocks/router/router.css +++ b/blocks/router/router.css @@ -1,3 +1,3 @@ raqn-router { - background-color: var(--scope-background, transparent); + background-color: var(--background, transparent); } diff --git a/blocks/theming/theming.editor.js b/blocks/theming/theming.editor.js new file mode 100644 index 00000000..82756403 --- /dev/null +++ b/blocks/theming/theming.editor.js @@ -0,0 +1,37 @@ +import Theming from './theming.js'; + +let listener = false; +let themeInstance = null; + +export default function config() { + // init editor if message from parent + if (!listener) { + listener = true; + window.addEventListener('message', (e) => { + if (e && e.data) { + const { message, params } = e.data; + if (message && message === 'updateTheme') { + console.log('updateTheme'); + [themeInstance] = window.raqnInstances[Theming.name.toLowerCase()]; + const { name, data } = params; + const keys = Object.keys(data); + const themeKeys = Object.keys(data[keys[0]]).slice(1); + const t = data; + themeInstance.variables = ''; + themeInstance.themes = ''; + themeInstance.tags = ''; + + themeInstance.getTheme(themeKeys, keys, t, name); + if (name === 'font') { + themeInstance.prepareTags(keys, themeKeys, t); + } + + themeInstance.styles(); + } + } + }); + } + return { + variables: {}, + }; +} diff --git a/blocks/theming/theming.js b/blocks/theming/theming.js index 020692c5..2b5909b2 100644 --- a/blocks/theming/theming.js +++ b/blocks/theming/theming.js @@ -1,8 +1,6 @@ import ComponentBase from '../../scripts/component-base.js'; -import { globalConfig, getMeta, unflat } from '../../scripts/libs.js'; -// minify alias -// const metaTheming = getMeta('theming'); -// const metaFragment = metaTheming && `${metaTheming}.json`; +import { getMeta, unflat } from '../../scripts/libs.js'; + const k = Object.keys; export default class Theming extends ComponentBase { @@ -30,42 +28,25 @@ export default class Theming extends ComponentBase { }, false); } - fontFaceTemplate(fontFace) { - if (fontFace.indexOf('-') > -1) { - const [name, ...rest] = fontFace.split('-'); - const params = rest.pop().split('.'); - const format = params.pop(); - const lastBit = params.pop(); - const fontWeight = globalConfig.fontWeights[lastBit] || 'regular'; - const fontStyle = lastBit === 'italic' ? lastBit : 'normal'; - // eslint-disable-next-line max-len - return `@font-face {font-family: ${name};font-weight: ${fontWeight};font-display: swap;font-style: ${fontStyle};src: url('/fonts/${fontFace}') format(${format});}`; - } - return ''; - } - - fontTags(t, index) { - const tag = t.tags[index]; - const values = this.toTags.reduce((acc, key) => { - if (t[key][index]) { - if (acc[tag]) { - acc[tag][key] = t[key][index]; - } else { - acc[tag] = { [key]: t[key][index] }; - } - } - return acc; - }, {}); - return k(values).map((value) => { - const val = values[value]; - return `${tag} {${k(val) - .map((v) => `${this.getKey(v)}: var(--${this.getKey(v)}, ${val[v]});`) - .join('')}}`; - }); - } - - getKey(key) { - return this.transform[key] ? this.transform[key] : key; + fontFaceTemplate(data) { + const names = Object.keys(data); + + this.fontFace = names + .map((key) => { + // files + const types = Object.keys(data[key].options); + return types + .map( + (type) => `@font-face { + font-family: '${key}'; + src: url('${window.location.origin}/fonts/${data[key].options[type]}'); + ${type === 'italic' ? 'font-style' : 'font-weight'}: ${type}; + } + `, + ) + .join(''); + }) + .join(''); } escapeHtml(unsafe) { @@ -77,8 +58,8 @@ export default class Theming extends ComponentBase { const value = t[key][row]; let variable = ''; if (value) { - variable = `\n--${this.getKey(key)}-${row}: ${this.escapeHtml(value).trim()};`; - this.atomic += `body .${this.getKey(key)}-${row} {--${this.getKey(key)}: var(--${this.getKey(key)}-${row});}\n`; + variable = `\n--${key}-${row}: ${this.escapeHtml(value).trim()};`; + this.atomic += `body .${key}-${row} {--${key}: var(--${key}-${row});}\n`; } return variable; } @@ -99,7 +80,7 @@ export default class Theming extends ComponentBase { ); } - getVariables(themeKeys, keys, t) { + getVariables(keys, t) { return keys.reduce( (acc, key) => `${acc} @@ -110,7 +91,7 @@ export default class Theming extends ComponentBase { ); } - readValue(data, type) { + readValue(data) { let keys = data.map((item) => item.key); const themeKeys = k(data[0]).slice(1); const globals = keys.filter((item) => this.isGlobal(item)); @@ -130,57 +111,26 @@ export default class Theming extends ComponentBase { }, ac), {}, ); + return { keys, themeKeys, t, globals }; + } + getTheme(themeKeys, keys, t, type = 'color') { this.themes = `${this.themes || ''} ${this.getThemeClasses(themeKeys, keys, t, type)}`; this.variables = `${this.variables || ''} - body { ${this.getVariables(themeKeys, keys, t)} } + body { ${this.getVariables(keys, t)} } `; return { keys, themeKeys, t }; - - // console.log('theme classes', this.variables); - - // // font tags - // if (t.tags) { - // this.tags = - // (this.tags || '') + - // k(t.tags) - // .map((index) => this.fontTags(t, index)) - // .join('\n'); - // } - // // full scoped theme classes - // this.themes = - // (this.themes || '') + - // this.themesKeys - // .map( - // (theme) => `.theme-${theme} {${k(t) - // .filter((key) => ![...this.skip, ...this.toTags].includes(key)) - // .map((key) => (t[key][theme] ? `--${key}: var(--${key}-${theme});` : '')) - // .filter((v) => v !== '') - // .join('')} - // }`, - // ) - // .join(''); - - // this.variables = `${this.variables || ''} body{${k(t) - // .filter((key) => ![...this.skip].includes(key)) - // .map((key) => { - // const rows = k(t[key]); - // return rows.map((row) => this.renderVariables(key, row, t)).join(''); - // }) - // .join('')}}`; } prepareTags(keys, themeKeys, t) { - // console.log('prepareTags keys', unFlattenPropertieskeys, themeKeys, t); const tags = unflat(t); - this.tags = Object.keys(tags) .map( (tag) => `${tag} { ${keys - .filter((key) => key.indexOf(tag) > -1) + .filter((key) => key.indexOf(tag) === 0) .reduce((acc, prop) => { const val = t[prop].default; @@ -194,7 +144,7 @@ export default class Theming extends ComponentBase { } styles() { - ['variables', 'tags', 'atomic', 'themes'].forEach((cssSegment) => { + ['variables', 'tags', 'atomic', 'themes', 'fontFace'].forEach((cssSegment) => { const style = document.createElement('style'); style.innerHTML = this[cssSegment]; style.classList.add(cssSegment); @@ -207,23 +157,22 @@ export default class Theming extends ComponentBase { async processFragment(response, type = 'color') { if (response.ok) { const responseData = await response.json(); - const { keys, themeKeys, t } = this.readValue(responseData.data, type); - if (type === 'font') { - this.prepareTags(keys, themeKeys, t); + if (type === 'fontface') { + this.fontFaceTemplate(responseData); + } else { + const { keys, themeKeys, t } = this.readValue(responseData.data, type); + this.getTheme(themeKeys, keys, t, type); + if (type === 'font') { + this.prepareTags(keys, themeKeys, t); + } } } } - colors(data) { - console.log('colors', data); - } - async loadFragment() { - // load colors - await fetch('colors.json').then((response) => this.processFragment(response)); - // load fonts + await fetch('colors.json').then((response) => this.processFragment(response, 'color')); await fetch('fonts.json').then((response) => this.processFragment(response, 'font')); - + await fetch('/fonts/index.json').then((response) => this.processFragment(response, 'fontface')); this.styles(); } } diff --git a/docs/raqn/components.md b/docs/raqn/components.md index c3991092..b269e90e 100644 --- a/docs/raqn/components.md +++ b/docs/raqn/components.md @@ -83,8 +83,8 @@ With the component loader, it will be rendered as:

Get started

- Learn the basics: how to best get started and create a page. And how to - transfer your brand theme to the new capabilities of RAQN web. + Learn the basics: how to best get started and create a page. And how to transfer your brand theme to the new + capabilities of RAQN web.

@@ -141,8 +141,8 @@ Now, let's add a little style at `hero.css`: ```css /* Block-specific CSS goes here */ raqn-hero { - --hero-background-color: var(--scope-background, black); - --hero-color: var(--scope-color, white); + --hero-background-color: var(--background, black); + --hero-color: var(--text, white); --hero-grid-template-columns: 0.6fr 0.4fr; --hero-hero-order: 0; @@ -186,10 +186,10 @@ To set a param only to a specific viewport, prefix it with the viewport key: 1. **xs**: 0 to 479, 1. **s**: 480 to 767, -2. **m**: 768 to 1023, -3. **l**: 1024 to 1279, -4. **xl**: 1280 to 1919, -5. **xxl**: 1920. +1. **m**: 768 to 1023, +1. **l**: 1024 to 1279, +1. **xl**: 1280 to 1919, +1. **xxl**: 1920. Let's set the order param to apply only on the S (0 to 767) viewport: @@ -202,4 +202,4 @@ Now, the param is only set on S viewports: Where: 1. Regular params will be set to all viewports. -2. Prefixed params will be applied only to the specific viewport, overriding the general one. \ No newline at end of file +2. Prefixed params will be applied only to the specific viewport, overriding the general one. diff --git a/docs/raqn/theming.md b/docs/raqn/theming.md index 43b5744f..abf3e2fa 100644 --- a/docs/raqn/theming.md +++ b/docs/raqn/theming.md @@ -9,11 +9,11 @@ To enhance future developments, we aim to introduce theme capabilities within th ## CSS variables for theme - Leveraging EDS capabilities for delivering a spreadsheet as JSON, we'll employ a `theme.xls` as a theme storage. The following example illustrates the structure: +Leveraging EDS capabilities for delivering a spreadsheet as JSON, we'll employ a `theme.xls` as a theme storage. The following example illustrates the structure: ![Theme concept](../assets/theme-concept-excel.png) -- The first row defines the name of the theme, which can be expressed as strings (e.g., primary, secondary) or numbers for simplicity. *(Note: The A1 cell is illustrative, and its value is ignored.)* +- The first row defines the name of the theme, which can be expressed as strings (e.g., primary, secondary) or numbers for simplicity. _(Note: The A1 cell is illustrative, and its value is ignored.)_ - The first column outlines the property/variable names. For effective theme application, we require: @@ -31,44 +31,44 @@ ${property}-${columnName}: ${value}; ```css /* Global CSS variables */ body { - --raqn-color-1: red; - --raqn-color-2: blue; - --raqn-color-default: black; - --raqn-background-1: #eee; - --raqn-background-2: #ddd; - --raqn-background-default: #fff; + --raqn-color-1: red; + --raqn-color-2: blue; + --raqn-color-default: black; + --raqn-background-1: #eee; + --raqn-background-2: #ddd; + --raqn-background-default: #fff; } /* Atomic classes with specificity of 2 */ body .color-1 { - --scope-color: var(--raqn-color-1); + --color: var(--raqn-color-1); } body .color-2 { - --scope-color: var(--raqn-color-2); + --color: var(--raqn-color-2); } body .color-default { - --scope-color: var(--raqn-color-default); + --color: var(--raqn-color-default); } body .background-1 { - --scope-background: var(--raqn-background-1); + --background: var(--raqn-background-1); } body .background-2 { - --scope-background: var(--raqn-background-2); + --background: var(--raqn-background-2); } body .background-default { - --scope-background: var(--raqn-background-default); + --background: var(--raqn-background-default); } /* Theme classes to apply all scopes */ .theme-1 { - --scope-color: var(--raqn-color-1); - --scope-background: var(--raqn-background-1); + --color: var(--raqn-color-1); + --background: var(--raqn-background-1); } .theme-2 { - --scope-color: var(--raqn-color-2); - --scope-background: var(--raqn-background-2); + --color: var(--raqn-color-2); + --background: var(--raqn-background-2); } .theme-default { - --scope-color: var(--raqn-color-default); - --scope-background: var(--raqn-background-default); + --color: var(--raqn-color-default); + --background: var(--raqn-background-default); } ``` @@ -86,35 +86,43 @@ ${tags} { CSS output: ```css -h1, .heading1 { - font-size: 40px; - font-weight: bold; - line-height: 1.4em; -} -h2, .heading2 { - font-size: 30px; - font-weight: 600; - line-height: 1em; - font-style: italic; -} -h3, .heading3 { - font-size: 25px; - font-weight: bold; -} -h4, .heading4 { - font-size: 20px; - font-weight: bold; -} -h5, .heading5 { - font-size: 18px; - font-weight: bold; -} -p,body,pre,input { - font-size: 12px; - font-weight: normal; - font-family: Roboto,Arial, sans-serif; - line-height: 1.2em; - font-style: normal; +h1, +.heading1 { + font-size: 40px; + font-weight: bold; + line-height: 1.4em; +} +h2, +.heading2 { + font-size: 30px; + font-weight: 600; + line-height: 1em; + font-style: italic; +} +h3, +.heading3 { + font-size: 25px; + font-weight: bold; +} +h4, +.heading4 { + font-size: 20px; + font-weight: bold; +} +h5, +.heading5 { + font-size: 18px; + font-weight: bold; +} +p, +body, +pre, +input { + font-size: 12px; + font-weight: normal; + font-family: Roboto, Arial, sans-serif; + line-height: 1.2em; + font-style: normal; } ``` @@ -143,28 +151,28 @@ The theme and fonts are applied by default to the site: You can set up additional variables for general purposes. Here are some examples: 1. **Colors Variables** - - `background`: Change general background - - `inner-background`: Change a child element background, e.g., card backgrounds - - `link-color`: Link colors - - `link-color-hover`: Link hover and active color - - `accent-color`: Buttons and CTAs color - - `accent-background`: Buttons and CTAs background - - `accent-color-hover`: Buttons and CTAs hover and active color - - `accent-background-hover`: Buttons and CTAs hover and active background - - `header-background`: Header background - - `header-color`: Header text color - - `headings-color`: Headings color (h1 to h3) - - `footer-background`: Footer background color + - `background`: Change general background + - `inner-background`: Change a child element background, e.g., card backgrounds + - `link-color`: Link colors + - `link-color-hover`: Link hover and active color + - `accent-color`: Buttons and CTAs color + - `accent-background`: Buttons and CTAs background + - `accent-color-hover`: Buttons and CTAs hover and active color + - `accent-background-hover`: Buttons and CTAs hover and active background + - `header-background`: Header background + - `header-color`: Header text color + - `headings-color`: Headings color (h1 to h3) + - `footer-background`: Footer background color 2. **Block Model** - - `max-width`: Full width / max container (preferably using vw unit) - - `padding`: Padding of an element - - `inner-padding`: Padding of a child element, e.g., cards - - `gap`: Grid gap between columns - - `margin`: Margin of an element - - `icon-size`: Icon size (square) + - `max-width`: Full width / max container (preferably using vw unit) + - `padding`: Padding of an element + - `inner-padding`: Padding of a child element, e.g., cards + - `gap`: Grid gap between columns + - `margin`: Margin of an element + - `icon-size`: Icon size (square) 3. **Alignment** - - `align`: Vertical alignment of elements - - `justify`: Horizontal alignment of elements + - `align`: Vertical alignment of elements + - `justify`: Horizontal alignment of elements ## Example of Theme spreadsheet @@ -211,7 +219,7 @@ And its corresponding documentation: A special block named **Style** allows the use of only theme and atomic classes, without loading additional features. Here's an example: Wherer: -1 - You don't need to add `class=` just the classname +1 - You don't need to add `class=` just the classname 2 - No other feature or block is loaded ![Style Example](../assets/style-example.png) @@ -220,4 +228,4 @@ Wherer: Although we have developed a font-face theme definition, the current EDGE delivery lacks the capability to maintain fonts in drive or serve them: -![Font Limitation](../assets/font-limitation.png) \ No newline at end of file +![Font Limitation](../assets/font-limitation.png) diff --git a/styles/styles.css b/styles/styles.css index c671a642..957796bc 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -1,6 +1,7 @@ @media screen and (max-width: 768px) { body { - --scope-max-width: 100vw; + --raqn-max-width-default: var(--max-width, 80vw); + --max-width: 100vw; } } @@ -11,15 +12,20 @@ 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; padding: 0; + color: var(--text, #000); } body { display: none; - background: var(--scope-background, #fff); + background: var(--background, #fff); padding: 0; margin: 0; width: 100%; @@ -59,33 +65,37 @@ legend, caption { font-size: 100%; vertical-align: baseline; - color: currentcolor; } -header { - --scope-background: var(--scope-header-background, #fff); - --scope-color: var(--scope-header-color, #000); +h1, +h2, +h3, +h4 { + color: var(--title, #000); +} - min-height: var(--scope-header-height, 64px); +header { + min-height: var(--header-height, 64px); display: grid; - background: var(--scope-header-background, #fff); + background: var(--header-background, #fff); } -head:has(meta[name="header"][content="false" i]) + body > header, -head:has(meta[name="footer"][content="false" i]) + body > footer { +head:has(meta[name='header'][content='false' i]) + body > header, +head:has(meta[name='footer'][content='false' i]) + body > footer { display: none; } main > div { - max-width: var(--scope-max-width, 100%); + max-width: var(--max-width, 100%); margin: 0 auto; } main > div > * { - max-width: var(--scope-max-width, 100%); - min-width: var(--scope-max-width, 100%); + max-width: var(--max-width, 100%); + min-width: var(--max-width, 100%); margin-inline: auto; box-sizing: border-box; + background-color: var(--background, #fff); } main > .raqn-grid > * { @@ -94,53 +104,53 @@ main > .raqn-grid > * { } .full-width { - --scope-outer-gap: calc((var(--raqn-max-width-default) - 100vw) / 2); - --scope-inner-gap: calc((100vw - var(--scope-max-width)) / 2); + --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(--scope-outer-gap); - padding-inline: var(--scope-inner-gap); + margin-inline-start: var(--outer-gap); + padding-inline: var(--inner-gap); box-sizing: border-box; } main > div > div { - background: var(--scope-background, #fff); - color: var(--scope-color, #000); - padding: var(--scope-padding, 0); + background: var(--background, #fff); + color: var(--text, #000); + padding: var(--padding, 0); } main > div > div > div { - max-width: var(--scope-max-width, 100%); - margin: var(--scope-margin, 0 auto); + max-width: var(--max-width, 100%); + margin: var(--margin, 0 auto); width: 100%; } .breadcrumbs { display: grid; - grid-template-columns: var(--scope-grid-template-columns, 1fr); - gap: var(--scope-gap, 20px); + grid-template-columns: var(--grid-template-columns, 1fr); + gap: var(--gap, 20px); align-items: center; justify-items: start; - min-height: var(--scope-font-size, 1.2em); + min-height: var(--font-size, 1.2em); } a { display: inline-flex; line-height: 1em; align-items: center; - color: var(--scope-link-color, inherit); + color: var(--highlight, inherit); text-decoration: none; - font-size: var(--scope-font-size, 1em); + font-size: var(--font-size, 1em); } a:hover { - color: var(--scope-link-color-hover, inherit); + color: var(--text, inherit); } .raqn-grid { - width: var(--scope-max-width, 100%); + width: var(--max-width, 100%); margin: 0 auto; display: grid; grid-template-columns: var(--grid-template-columns, 1fr); @@ -155,7 +165,6 @@ img { height: auto; } - [isloading], .hide-with-error, .hide { @@ -163,40 +172,6 @@ img { pointer-events: none; } -p, -h1, -h2, -h3, -h4, -h5, -h6 { - margin-block: var(--scope-margin-block, 1em); -} - -@media screen and (min-width: 1024px) { - p, - ul, - ol, - pre, - h1, - h2, - h3, - h4, - h5, - h6 { - max-width: 50vw; - } -} - -h1, -h2, -h3, -h4, -h5, -h6 { - color: var(--scope-headings-color, var(--scope-color, currentColor)); -} - #franklin-svg-sprite { display: none; } From bcf2db9f52fb17c9b38195a472da25b15688cced Mon Sep 17 00:00:00 2001 From: Felipe Simoes Date: Wed, 5 Jun 2024 14:58:39 +0200 Subject: [PATCH 06/13] update variables --- styles/styles.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/styles/styles.css b/styles/styles.css index 957796bc..afd323d0 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -21,6 +21,12 @@ body { margin: 0; padding: 0; color: var(--text, #000); + font-family: var(--p-font-family, roboto); + font-size: var(--p-font-size, 16px); + font-weight: var(--p-font-weight, normal); + font-style: var(--p-font-style, normal); + line-height: var(--p-line-height, 1.2em); + margin-block: var(--p-margin-block, 1.2em); } body { From 27e0f3dfb8a0562712ba89cd921ae299348b9f79 Mon Sep 17 00:00:00 2001 From: Felipe Simoes Date: Wed, 5 Jun 2024 14:59:19 +0200 Subject: [PATCH 07/13] update body --- styles/styles.css | 1 - 1 file changed, 1 deletion(-) diff --git a/styles/styles.css b/styles/styles.css index afd323d0..ef251a37 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -26,7 +26,6 @@ body { font-weight: var(--p-font-weight, normal); font-style: var(--p-font-style, normal); line-height: var(--p-line-height, 1.2em); - margin-block: var(--p-margin-block, 1.2em); } body { From f5089daa01ee32e1996ee15c94770ce9110d8fb5 Mon Sep 17 00:00:00 2001 From: Felipe Simoes Date: Wed, 5 Jun 2024 16:09:37 +0200 Subject: [PATCH 08/13] avoiding cors --- blocks/theming/theming.editor.js | 11 ++++++++++- blocks/theming/theming.js | 5 +++-- scripts/editor.js | 3 +++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/blocks/theming/theming.editor.js b/blocks/theming/theming.editor.js index 82756403..7c63050e 100644 --- a/blocks/theming/theming.editor.js +++ b/blocks/theming/theming.editor.js @@ -1,3 +1,5 @@ +import { MessagesEvents } from '../../scripts/editor.js'; +import { publish } from '../../scripts/pubsub.js'; import Theming from './theming.js'; let listener = false; @@ -6,12 +8,19 @@ let themeInstance = null; export default function config() { // init editor if message from parent if (!listener) { + [themeInstance] = window.raqnInstances[Theming.name.toLowerCase()]; + + publish( + MessagesEvents.theme, + { name: 'theme', data: themeInstance.themeJson }, + { usePostMessage: true, targetOrigin: '*' }, + ); + listener = true; window.addEventListener('message', (e) => { if (e && e.data) { const { message, params } = e.data; if (message && message === 'updateTheme') { - console.log('updateTheme'); [themeInstance] = window.raqnInstances[Theming.name.toLowerCase()]; const { name, data } = params; const keys = Object.keys(data); diff --git a/blocks/theming/theming.js b/blocks/theming/theming.js index 2b5909b2..cc971ff3 100644 --- a/blocks/theming/theming.js +++ b/blocks/theming/theming.js @@ -9,7 +9,7 @@ export default class Theming extends ComponentBase { setDefaults() { super.setDefaults(); this.scapeDiv = document.createElement('div'); - this.themeJson = { data: [] }; + this.themeJson = {}; this.globalsVar = ['c-', 'global']; this.toTags = []; @@ -99,7 +99,6 @@ export default class Theming extends ComponentBase { const t = data.reduce( (ac, item, i) => keys.reduce((acc, key) => { - delete item.key; if (!this.themesKeys) { this.themesKeys = k(item); } @@ -157,10 +156,12 @@ export default class Theming extends ComponentBase { async processFragment(response, type = 'color') { if (response.ok) { const responseData = await response.json(); + this.themeJson[type] = responseData; if (type === 'fontface') { this.fontFaceTemplate(responseData); } else { const { keys, themeKeys, t } = this.readValue(responseData.data, type); + this.getTheme(themeKeys, keys, t, type); if (type === 'font') { this.prepareTags(keys, themeKeys, t); diff --git a/scripts/editor.js b/scripts/editor.js index 24bf5827..d29489ed 100644 --- a/scripts/editor.js +++ b/scripts/editor.js @@ -11,6 +11,8 @@ export const MessagesEvents = { disabled: 'raqn:editor:disabled', render: 'raqn:editor:render', select: 'raqn:editor:select', + theme: 'raqn:editor:theme', + themeUpdate: 'raqn:editor:theme:update', }; export function refresh(id) { @@ -106,6 +108,7 @@ export default function initEditor(listeners = true) { { components: window.raqnEditor, bodyRect }, { usePostMessage: true, targetOrigin: '*' }, ); + if (!watcher) { window.addEventListener('resize', () => { refresh(); From f9ae585ad4823ada1c63616500ec52db7d36d8c6 Mon Sep 17 00:00:00 2001 From: Felipe Simoes Date: Thu, 6 Jun 2024 04:35:49 +0200 Subject: [PATCH 09/13] Editor preview --- blocks/card/card.editor.js | 32 +---------------------- blocks/hero/hero.css | 1 + blocks/theming/theming.editor.js | 2 +- scripts/component-loader.js | 2 ++ scripts/editor-preview.js | 44 ++++++++++++++++++++++++++++++++ scripts/editor.js | 39 ++++++++++++++++++---------- scripts/init.js | 24 ++++++++++++++--- scripts/pubsub.js | 3 --- styles/styles.css | 7 +++++ 9 files changed, 101 insertions(+), 53 deletions(-) create mode 100644 scripts/editor-preview.js diff --git a/blocks/card/card.editor.js b/blocks/card/card.editor.js index 98a58380..562d46f4 100644 --- a/blocks/card/card.editor.js +++ b/blocks/card/card.editor.js @@ -1,6 +1,6 @@ export default function config() { return { - variables: { + sets: { '--background': { type: 'text', label: 'Background', @@ -23,39 +23,19 @@ export default function config() { }, }, attributes: { - class: { - type: 'text', - label: 'Class', - helpText: 'The class of the card.', - }, 'data-columns': { type: 'text', label: 'Number of Columns', helpText: 'The number of columns in the card grid.', }, - 'data-ratio': { - type: 'text', - label: 'Aspect Ratio', - helpText: 'The aspect ratio of the card.', - }, 'data-eager': { type: 'text', label: 'Eager Loading', helpText: 'The number of images to load eagerly.', }, - 'data-background': { - type: 'text', - label: 'Background', - helpText: 'The background color of the card.', - }, }, selection: { variant1: { - variables: { - '--color': 'white', - '--gap': '40px', - '--padding': '20px', - }, attributes: { 'data-columns': '2', 'data-ratio': '4/3', @@ -63,11 +43,6 @@ export default function config() { }, }, variant2: { - variables: { - '--color': 'white', - '--gap': '40px', - '--padding': '20px', - }, attributes: { 'data-columns': '3', 'data-ratio': '4/3', @@ -75,11 +50,6 @@ export default function config() { }, }, variant3: { - variables: { - '--color': 'white', - '--gap': '40px', - '--padding': '20px', - }, attributes: { 'data-columns': '4', 'data-ratio': '4/3', diff --git a/blocks/hero/hero.css b/blocks/hero/hero.css index fe90bfc0..987e0d33 100644 --- a/blocks/hero/hero.css +++ b/blocks/hero/hero.css @@ -9,6 +9,7 @@ raqn-hero { background: var(--hero-background); color: var(--hero-color); + display: grid; align-items: center; grid-template-columns: var(--hero-grid-template-columns, 1fr); padding-block: var(--hero-padding-block); diff --git a/blocks/theming/theming.editor.js b/blocks/theming/theming.editor.js index 7c63050e..21ad5630 100644 --- a/blocks/theming/theming.editor.js +++ b/blocks/theming/theming.editor.js @@ -20,7 +20,7 @@ export default function config() { window.addEventListener('message', (e) => { if (e && e.data) { const { message, params } = e.data; - if (message && message === 'updateTheme') { + if (message && message === MessagesEvents.themeUpdate) { [themeInstance] = window.raqnInstances[Theming.name.toLowerCase()]; const { name, data } = params; const keys = Object.keys(data); diff --git a/scripts/component-loader.js b/scripts/component-loader.js index 940cf2a5..ba07402e 100644 --- a/scripts/component-loader.js +++ b/scripts/component-loader.js @@ -90,6 +90,7 @@ export default class ComponentLoader { let elem = null; try { elem = await this.createElementAndConfigure(data); + elem.webComponentName = this.webComponentName; this.instances[elem.componentName] = this.instances[elem.componentName] || []; this.instances[elem.componentName].push(elem); } catch (error) { @@ -177,6 +178,7 @@ export default class ComponentLoader { async createElementAndConfigure(data) { const componentElem = document.createElement(this.webComponentName); + this.componentElem = componentElem; try { await componentElem.init(data); } catch (error) { diff --git a/scripts/editor-preview.js b/scripts/editor-preview.js new file mode 100644 index 00000000..d50a7793 --- /dev/null +++ b/scripts/editor-preview.js @@ -0,0 +1,44 @@ +import ComponentLoader from './component-loader.js'; +import { publish } from './pubsub.js'; + +export default async function preview(component, classes, uuid) { + const { componentName } = component; + const header = document.querySelector('header'); + const footer = document.querySelector('footer'); + const main = document.querySelector('main'); + main.innerHTML = ''; + + main.classList.add(classes); + if (header) { + header.parentNode.removeChild(header); + } + if (footer) { + footer.parentNode.removeChild(footer); + } + const el = document.createElement('div'); + const { attributesValues } = component; + el.classList.add(...attributesValues.class.split(' ')); + el.innerHTML = component.innerHTML; + + const loader = new ComponentLoader({ componentName, targets: [el] }); + loader.init(); + main.innerHTML = `
${component.html}
`; + document.body.style.display = 'block'; + + window.addEventListener( + 'click', + (e) => { + e.preventDefault(); + e.stopImmediatePropagation(); + }, + true, + ); + + setTimeout(() => { + const webComponent = main.querySelector(component.webComponentName); + webComponent.style.display = 'inline-grid'; + webComponent.style.width = 'auto'; + const bodyRect = webComponent.getBoundingClientRect(); + publish('raqn:editor:preview:render', { bodyRect, uuid }, { usePostMessage: true, targetOrigin: '*' }); + }, 200); +} diff --git a/scripts/editor.js b/scripts/editor.js index d29489ed..93c2778d 100644 --- a/scripts/editor.js +++ b/scripts/editor.js @@ -18,7 +18,6 @@ export const MessagesEvents = { export function refresh(id) { const bodyRect = window.document.body.getBoundingClientRect(); Object.keys(window.raqnEditor).forEach((name) => { - console.log('name', name); window.raqnEditor[name].instances = window.raqnInstances[name].map((item) => // eslint-disable-next-line no-use-before-define getComponentValues(window.raqnEditor[name].dialog, item), @@ -32,20 +31,29 @@ export function refresh(id) { ); } -export function updateComponent(params) { - const { uuid, name, option } = params; - // const dialog = window.raqnEditor[name]; - const component = window.raqnInstances[name].find((element) => element.uuid === uuid); - const { componentElem } = component; - const { variables, attributes } = option; - if (variables) { - Object.keys(variables).forEach((variable) => { - componentElem.style.setProperty(variable, variables[variable]); - }); - } +export function updateComponent(component) { + const { componentName, uuid } = component; + const instance = window.raqnInstances[componentName].find((element) => element.uuid === uuid); + const { attributesValues } = instance; + const attributes = component.attributesValues; + console.log('attributes', attributes); + // if (variables) { + // Object.keys(variables).forEach((variable) => { + // componentElem.style.setProperty(variable, variables[variable]); + // }); + // } if (attributes) { Object.keys(attributes).forEach((attribute) => { - componentElem.setAttribute(attribute, attributes[attribute]); + const val = attributes[attribute]; + if (attribute === 'class' && attributesValues[attribute]) { + const classes = Array.from(instance.classList).filter((c) => !c.includes(val.split('-')[0] || 'color')); + classes.push(...val.split(' ')); + const set = new Set(classes); + + instance.setAttribute(attribute, Array.from(set).join(' ')); + } else { + instance.setAttribute(attribute, val); + } }); } @@ -53,6 +61,7 @@ export function updateComponent(params) { } export function getComponentValues(dialog, element) { + const html = element.outerHTML; const domRect = element.getBoundingClientRect(); let { variables = {}, attributes = {} } = dialog; const { selection = {} } = dialog; @@ -74,7 +83,7 @@ export function getComponentValues(dialog, element) { delete cleanData.childComponents; delete cleanData.nestedComponents; delete cleanData.nestedComponentsConfig; - return { ...cleanData, domRect, editor: { variables, attributes, selection } }; + return { ...cleanData, domRect, editor: { variables, attributes, selection }, html }; } export default function initEditor(listeners = true) { @@ -103,6 +112,7 @@ export default function initEditor(listeners = true) { ), ).finally(() => { const bodyRect = window.document.body.getBoundingClientRect(); + publish( MessagesEvents.loaded, { components: window.raqnEditor, bodyRect }, @@ -123,6 +133,7 @@ export default function initEditor(listeners = true) { const { message, params } = e.data; switch (message) { case MessagesEvents.select: + console.log('select', params); updateComponent(params); break; diff --git a/scripts/init.js b/scripts/init.js index 2c4e17ca..7daef6c2 100644 --- a/scripts/init.js +++ b/scripts/init.js @@ -161,13 +161,14 @@ const onLoadComponents = { initBlocks() { // Keep the page hidden until specific components are initialized to prevent CLS component.multiInit(this.lcpBlocks).then(() => { - document.body.style.setProperty('display', 'unset'); + window.postMessage({ message: 'raqn:components:loaded' }); + document.body.style.setProperty('display', 'block'); }); component.multiInit(this.lazyBlocks); }, }; -const globalInit = { +export const globalInit = { async init() { this.setLang(); this.initEagerImages(); @@ -195,17 +196,32 @@ window.addEventListener('message', async (e) => { if (e && e.data) { const { message, params } = e.data; if (!Array.isArray(params)) { + const query = new URLSearchParams(window.location.search); switch (message) { case 'raqn:editor:start': (async function startEditor() { const editor = await import('./editor.js'); - const { origin, target } = params; + const { origin, target, preview = false } = params; setTimeout(() => { - editor.default(origin, target); + editor.default(origin, target, preview); }, 2000); })(); break; // other cases? + case 'raqn:editor:preview': + // preview editor + if (query.has('preview')) { + (async function startEditor() { + const preview = query.get('preview'); + const win = await import('./editor-preview.js'); + const { uuid } = params; + + if (uuid === preview) { + win.default(params.component, params.classes, uuid); + } + })(); + } + break; default: break; } diff --git a/scripts/pubsub.js b/scripts/pubsub.js index 6dc0a32b..5dfd25c3 100644 --- a/scripts/pubsub.js +++ b/scripts/pubsub.js @@ -90,7 +90,6 @@ export const postMessage = (message, params, options = {}) => { // eslint-disable-next-line no-console console.warn(error); } - console.log('postMessage', data); // upward message window.parent.postMessage(data, targetOrigin); // downward message @@ -103,8 +102,6 @@ export const publish = (message, params, options = {}) => { callStack(message, params, options); return; } - console.log('postMessage', message); - postMessage(message, params, options); }; diff --git a/styles/styles.css b/styles/styles.css index ef251a37..2bad1fce 100644 --- a/styles/styles.css +++ b/styles/styles.css @@ -36,6 +36,13 @@ body { width: 100%; } +main { + background: var(--background, #fff); + padding: 0; + margin: 0; + width: 100%; +} + html, body, div, From 16a9c934c129437c30b689c449245c22dcc5b591 Mon Sep 17 00:00:00 2001 From: Felipe Simoes Date: Thu, 6 Jun 2024 05:40:36 +0200 Subject: [PATCH 10/13] feature-editor --- blocks/hero/hero.editor.js | 43 ++++---------------------------------- scripts/editor-preview.js | 9 +++++++- scripts/editor.js | 10 +++++---- 3 files changed, 18 insertions(+), 44 deletions(-) diff --git a/blocks/hero/hero.editor.js b/blocks/hero/hero.editor.js index 5d327a99..336ffb51 100644 --- a/blocks/hero/hero.editor.js +++ b/blocks/hero/hero.editor.js @@ -30,49 +30,14 @@ export default function config() { }, }, selection: { - Default: { - descritpion: { - label: 'Image on the Left', - preview: 'http://localhost:8888/@henkel/theme-interface/assets/previews/hero/imageontheleft.png', - }, - variables: { - '--hero-color': 'white', - '--hero-background': 'black', - '--hero-padding-block': '20px', - '--hero-grid-template-columns': '0.4fr 0.6fr', - }, - attributes: { - 'data-order': '1', - }, - }, - Inverted: { - descritpion: { - label: 'Invert colors', - preview: 'http://localhost:8888/@henkel/theme-interface/assets/previews/hero/invertedcolor.png', - }, - variables: { - '--hero-color': 'black', - '--hero-background': 'white', - '--hero-padding-block': '20px', - '--hero-grid-template-columns': '0.4fr 0.6fr', - }, + 'Image on the right': { attributes: { - 'data-order': '2', + 'data-order': '0', }, }, - 'Image on the right': { - descritpion: { - label: 'Image on the right', - preview: 'http://localhost:8888/@henkel/theme-interface/assets/previews/hero/imageontherigth.png', - }, - variables: { - '--hero-color': 'white', - '--hero-background': 'black', - '--hero-padding-block': '20px', - '--hero-grid-template-columns': '0.6fr 0.4fr', - }, + 'Image on the left': { attributes: { - 'data-order': '0', + 'data-order': '1', }, }, }, diff --git a/scripts/editor-preview.js b/scripts/editor-preview.js index d50a7793..54f5de3d 100644 --- a/scripts/editor-preview.js +++ b/scripts/editor-preview.js @@ -17,9 +17,16 @@ export default async function preview(component, classes, uuid) { } const el = document.createElement('div'); const { attributesValues } = component; - el.classList.add(...attributesValues.class.split(' ')); + if (typeof attributesValues.class === 'string') { + el.classList.add(...attributesValues.class.split(' ')); + } el.innerHTML = component.innerHTML; + if (component.attributesValues.attributes) { + Object.keys(component.attributesValues.attributes).forEach((attr) => { + el.setAttribute(attr, component.attributesValues.attributes[attr]); + }); + } const loader = new ComponentLoader({ componentName, targets: [el] }); loader.init(); main.innerHTML = `
${component.html}
`; diff --git a/scripts/editor.js b/scripts/editor.js index 93c2778d..7f3edb3c 100644 --- a/scripts/editor.js +++ b/scripts/editor.js @@ -16,14 +16,13 @@ export const MessagesEvents = { }; export function refresh(id) { - const bodyRect = window.document.body.getBoundingClientRect(); Object.keys(window.raqnEditor).forEach((name) => { window.raqnEditor[name].instances = window.raqnInstances[name].map((item) => // eslint-disable-next-line no-use-before-define getComponentValues(window.raqnEditor[name].dialog, item), ); }); - + const bodyRect = window.document.body.getBoundingClientRect(); publish( MessagesEvents.render, { components: window.raqnEditor, bodyRect, uuid: id }, @@ -36,7 +35,6 @@ export function updateComponent(component) { const instance = window.raqnInstances[componentName].find((element) => element.uuid === uuid); const { attributesValues } = instance; const attributes = component.attributesValues; - console.log('attributes', attributes); // if (variables) { // Object.keys(variables).forEach((variable) => { // componentElem.style.setProperty(variable, variables[variable]); @@ -49,8 +47,11 @@ export function updateComponent(component) { const classes = Array.from(instance.classList).filter((c) => !c.includes(val.split('-')[0] || 'color')); classes.push(...val.split(' ')); const set = new Set(classes); - instance.setAttribute(attribute, Array.from(set).join(' ')); + } else if (attribute === 'attributes' && attributes[attribute]) { + Object.keys(val).forEach((attr) => { + instance.setAttribute(attr, val[attr]); + }); } else { instance.setAttribute(attribute, val); } @@ -62,6 +63,7 @@ export function updateComponent(component) { export function getComponentValues(dialog, element) { const html = element.outerHTML; + window.document.body.style.height = 'auto'; const domRect = element.getBoundingClientRect(); let { variables = {}, attributes = {} } = dialog; const { selection = {} } = dialog; From 1cfd4dbd5ac5ad466e891917763f97996387f1f8 Mon Sep 17 00:00:00 2001 From: Felipe Simoes Date: Wed, 3 Jul 2024 19:10:53 +0200 Subject: [PATCH 11/13] Create breakpoint variables rendering --- blocks/theming/theming.editor.js | 1 + blocks/theming/theming.js | 6 ++++-- scripts/libs.js | 7 +++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/blocks/theming/theming.editor.js b/blocks/theming/theming.editor.js index 21ad5630..545c4c2a 100644 --- a/blocks/theming/theming.editor.js +++ b/blocks/theming/theming.editor.js @@ -21,6 +21,7 @@ export default function config() { if (e && e.data) { const { message, params } = e.data; if (message && message === MessagesEvents.themeUpdate) { + console.log('themeUpdate', params); [themeInstance] = window.raqnInstances[Theming.name.toLowerCase()]; const { name, data } = params; const keys = Object.keys(data); diff --git a/blocks/theming/theming.js b/blocks/theming/theming.js index cc971ff3..4400286c 100644 --- a/blocks/theming/theming.js +++ b/blocks/theming/theming.js @@ -123,7 +123,9 @@ export default class Theming extends ComponentBase { } prepareTags(keys, themeKeys, t) { + console.log('prepareTags', keys, themeKeys, t); const tags = unflat(t); + console.log('tags', tags); this.tags = Object.keys(tags) .map( (tag) => @@ -144,7 +146,7 @@ export default class Theming extends ComponentBase { styles() { ['variables', 'tags', 'atomic', 'themes', 'fontFace'].forEach((cssSegment) => { - const style = document.createElement('style'); + const style = document.querySelector(`style.${cssSegment}`) || document.createElement('style'); style.innerHTML = this[cssSegment]; style.classList.add(cssSegment); document.head.appendChild(style); @@ -161,7 +163,7 @@ export default class Theming extends ComponentBase { this.fontFaceTemplate(responseData); } else { const { keys, themeKeys, t } = this.readValue(responseData.data, type); - + console.log('get them keys', themeKeys, keys, t, type); this.getTheme(themeKeys, keys, t, type); if (type === 'font') { this.prepareTags(keys, themeKeys, t); diff --git a/scripts/libs.js b/scripts/libs.js index ac5f2a3e..35b3a3d0 100644 --- a/scripts/libs.js +++ b/scripts/libs.js @@ -19,10 +19,13 @@ export const camelCaseAttr = (val) => val.replace(/-([a-z])/g, (k) => k[1].toUpp export const capitalizeCaseAttr = (val) => camelCaseAttr(val.replace(/^[a-z]/g, (k) => k.toUpperCase())); export function matchMediaQuery(breakpointMin, breakpointMax) { + return window.matchMedia(this.getMediaQuery(breakpointMin, breakpointMax)); +} + +export function getMediaQuery(breakpointMin, breakpointMax) { const min = `(min-width: ${breakpointMin}px)`; const max = breakpointMax ? ` and (max-width: ${breakpointMax}px)` : ''; - - return window.matchMedia(`${min}${max}`); + return `${min}${max}`; } export function getBreakPoints() { From 952f00f0f76e9de967d93f550d9548c96b9589b8 Mon Sep 17 00:00:00 2001 From: Felipe Simoes Date: Wed, 3 Jul 2024 19:11:41 +0200 Subject: [PATCH 12/13] Create breakpoint variables rendering --- scripts/libs.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/libs.js b/scripts/libs.js index 35b3a3d0..d195b47c 100644 --- a/scripts/libs.js +++ b/scripts/libs.js @@ -18,16 +18,16 @@ export const globalConfig = { export const camelCaseAttr = (val) => val.replace(/-([a-z])/g, (k) => k[1].toUpperCase()); export const capitalizeCaseAttr = (val) => camelCaseAttr(val.replace(/^[a-z]/g, (k) => k.toUpperCase())); -export function matchMediaQuery(breakpointMin, breakpointMax) { - return window.matchMedia(this.getMediaQuery(breakpointMin, breakpointMax)); -} - export function getMediaQuery(breakpointMin, breakpointMax) { const min = `(min-width: ${breakpointMin}px)`; const max = breakpointMax ? ` and (max-width: ${breakpointMax}px)` : ''; return `${min}${max}`; } +export function matchMediaQuery(breakpointMin, breakpointMax) { + return window.matchMedia(getMediaQuery(breakpointMin, breakpointMax)); +} + export function getBreakPoints() { window.raqnBreakpoints ??= { ordered: [], From 674cbbbd4bb60fd3b5e0723c5505890a92c9fe5c Mon Sep 17 00:00:00 2001 From: Felipe Simoes Date: Mon, 8 Jul 2024 21:58:13 +0200 Subject: [PATCH 13/13] Udpates --- blocks/accordion/accordion.css | 2 +- blocks/button/button.css | 13 +- blocks/navigation/navigation.css | 12 +- blocks/theming/theming.editor.js | 15 +-- blocks/theming/theming.js | 199 +++++++++++++++---------------- scripts/libs.js | 57 ++++++--- 6 files changed, 151 insertions(+), 147 deletions(-) diff --git a/blocks/accordion/accordion.css b/blocks/accordion/accordion.css index 0a681932..84a24023 100644 --- a/blocks/accordion/accordion.css +++ b/blocks/accordion/accordion.css @@ -37,7 +37,7 @@ raqn-accordion accordion-control.active raqn-icon { .accordion-control > * { --headings-color: var(--title, black); - --hover-color: var(--accent, gray); + --hover-background-color: var(--accent-background, gray); width: 100%; display: flex; diff --git a/blocks/button/button.css b/blocks/button/button.css index 8d775ddb..8c3b8790 100644 --- a/blocks/button/button.css +++ b/blocks/button/button.css @@ -15,25 +15,26 @@ raqn-button { } raqn-button > * { - background: var(--accent, #000); - color: var(--accentText, #fff); + background: var(--accent-background, #000); + color: var(--accent-text, #fff); text-transform: none; border-radius: var(--border-radius, 0); border-block-start: var(--border-block-start, 1px solid transparent); border-block-end: var(--border-block-end, 1px solid transparent); border-inline-start: var(--border-inline-start, 1px solid transparent); border-inline-end: var(--border-inline-end, 1px solid transparent); + border-color: var(--accent-border, none); overflow: hidden; } raqn-button > *:hover { - background: var(--hover, #fff); - color: var(--hoverText, #fff); - border-color: currentcolor; + background: var(--hover-background, #fff); + color: var(--hover-text, #fff); + border-color: var(--hover-border, currentcolor); } raqn-button a { - color: var(--accentText, currentcolor); + color: var(--accent-text, currentcolor); padding-block: var(--button-padding-block, 10px); padding-inline: var(--button-padding-inline, 20px); text-decoration: none; diff --git a/blocks/navigation/navigation.css b/blocks/navigation/navigation.css index fc467970..291e1e41 100644 --- a/blocks/navigation/navigation.css +++ b/blocks/navigation/navigation.css @@ -18,7 +18,7 @@ raqn-navigation > nav p { } raqn-navigation .level-1 a:not(:hover) { - color: var(--accent, #000); + color: var(--accent-background, #000); } raqn-navigation .level-1 a:hover { @@ -58,8 +58,8 @@ raqn-navigation > nav button { justify-self: end; align-items: center; justify-content: center; - background: var(--accent, #fff); - color: var(--accentText, #000); + background: var(--accent-background, #fff); + color: var(--accent-text, #000); border: none; border-radius: var(--border-radius); padding: var(--padding-vertical, 10px) var(--padding-horizontal, 10px); @@ -67,8 +67,8 @@ raqn-navigation > nav button { } raqn-navigation.active button { - background: var(--hover, #000); - color: var(--hoverText, #fff); + background: var(--hover-background, #000); + color: var(--hover-text, #fff); } raqn-navigation.active > nav > ul { @@ -171,7 +171,7 @@ raqn-navigation:not([data-compact='true']) > nav .level-1 > ul::after { width: 100vw; inset-inline-start: 0; background: var(--background, #fff); - border-block-start: 1px solid var(--accent, #000); + border-block-start: 1px solid var(--accent-background, #000); box-shadow: 0 0 30px #000; z-index: 1; } diff --git a/blocks/theming/theming.editor.js b/blocks/theming/theming.editor.js index 545c4c2a..b0acac95 100644 --- a/blocks/theming/theming.editor.js +++ b/blocks/theming/theming.editor.js @@ -21,21 +21,10 @@ export default function config() { if (e && e.data) { const { message, params } = e.data; if (message && message === MessagesEvents.themeUpdate) { - console.log('themeUpdate', params); [themeInstance] = window.raqnInstances[Theming.name.toLowerCase()]; const { name, data } = params; - const keys = Object.keys(data); - const themeKeys = Object.keys(data[keys[0]]).slice(1); - const t = data; - themeInstance.variables = ''; - themeInstance.themes = ''; - themeInstance.tags = ''; - - themeInstance.getTheme(themeKeys, keys, t, name); - if (name === 'font') { - themeInstance.prepareTags(keys, themeKeys, t); - } - + const row = Object.keys(data).map((key) => data[key]); + themeInstance.defineVariations(themeInstance.readValue(row, name)); themeInstance.styles(); } } diff --git a/blocks/theming/theming.js b/blocks/theming/theming.js index 4400286c..00723a1e 100644 --- a/blocks/theming/theming.js +++ b/blocks/theming/theming.js @@ -1,11 +1,15 @@ import ComponentBase from '../../scripts/component-base.js'; -import { getMeta, unflat } from '../../scripts/libs.js'; +import { flat, getBreakPoints, getMediaQuery, getMeta, unflat } from '../../scripts/libs.js'; const k = Object.keys; export default class Theming extends ComponentBase { nestedComponentsConfig = {}; + elements = {}; + + variations = {}; + setDefaults() { super.setDefaults(); this.scapeDiv = document.createElement('div'); @@ -19,15 +23,6 @@ export default class Theming extends ComponentBase { this.atomic = ''; } - isGlobal(key) { - return this.globalsVar.reduce((a, g) => { - if (key.indexOf(g) > -1) { - return true; - } - return a; - }, false); - } - fontFaceTemplate(data) { const names = Object.keys(data); @@ -54,105 +49,52 @@ export default class Theming extends ComponentBase { return this.scapeDiv.innerHTML; } - renderVariables(key, row, t) { - const value = t[key][row]; - let variable = ''; - if (value) { - variable = `\n--${key}-${row}: ${this.escapeHtml(value).trim()};`; - this.atomic += `body .${key}-${row} {--${key}: var(--${key}-${row});}\n`; - } - return variable; - } - - getThemeClasses(themeKeys, keys, t, segment = 'theme') { - return themeKeys.reduce( - (acc, theme) => `${acc} - .${segment}-${theme} { - ${keys.reduce((a, key) => { - if (t[key][theme]) { - return `${a} \n --${key}: var(--${key}-${theme});`; - } - return a; - }, '')} - } - `, - '', - ); - } - - getVariables(keys, t) { - return keys.reduce( - (acc, key) => - `${acc} - ${k(t[key]) - .map((row) => this.renderVariables(key, row, t)) - .join('')}`, - '', - ); + reduceViewports(obj, callback) { + const breakpoints = Object.keys(obj); + return breakpoints + .map((bp) => { + const options = getBreakPoints(); + if (options.byName[bp]) { + const { min, max } = options.byName[bp]; + const query = getMediaQuery(min, max); + return ` +@media ${query} { + ${callback(obj[bp])} + } + `; + } + // regular + return callback(obj[bp]); + }) + .join('\n'); } readValue(data) { - let keys = data.map((item) => item.key); - const themeKeys = k(data[0]).slice(1); - const globals = keys.filter((item) => this.isGlobal(item)); - keys = keys.filter((item) => !globals.includes(item)); - const t = data.reduce( - (ac, item, i) => - keys.reduce((acc, key) => { - if (!this.themesKeys) { - this.themesKeys = k(item); - } - const ind = keys.indexOf(key); - if (i === ind) { - acc[key] = item; - } - return acc; - }, ac), - {}, - ); - return { keys, themeKeys, t, globals }; - } - - getTheme(themeKeys, keys, t, type = 'color') { - this.themes = `${this.themes || ''} ${this.getThemeClasses(themeKeys, keys, t, type)}`; - this.variables = `${this.variables || ''} - body { ${this.getVariables(keys, t)} } - `; - - return { keys, themeKeys, t }; - } - - prepareTags(keys, themeKeys, t) { - console.log('prepareTags', keys, themeKeys, t); - const tags = unflat(t); - console.log('tags', tags); - this.tags = Object.keys(tags) - .map( - (tag) => - `${tag} { - ${keys - .filter((key) => key.indexOf(tag) === 0) - .reduce((acc, prop) => { - const val = t[prop].default; - - return `${acc} - ${prop.replace(`${tag}-`, '')}: var(--${prop},${val}); - `; - }, '')}} - `, - ) - .join(''); + console.log('readValue', data); + const keys = k(data[0]).filter((item) => item !== 'key'); + return data.reduce((acc, row) => { + const mainKey = row.key; + keys.reduce((a, key) => { + if (!a[key]) { + a[key] = { [mainKey]: row[key] }; + } else { + a[key][mainKey] = row[key]; + } + return a; + }, acc); + return acc; + }, this.variations); } styles() { - ['variables', 'tags', 'atomic', 'themes', 'fontFace'].forEach((cssSegment) => { + ['variables', 'tags', 'fontFace'].forEach((cssSegment) => { const style = document.querySelector(`style.${cssSegment}`) || document.createElement('style'); style.innerHTML = this[cssSegment]; style.classList.add(cssSegment); document.head.appendChild(style); }); const themeMeta = getMeta('theme'); - document.body.classList.add(themeMeta, 'font-default', 'color-default'); + document.body.classList.add(themeMeta, 'color-default', 'font-default'); } async processFragment(response, type = 'color') { @@ -162,16 +104,67 @@ export default class Theming extends ComponentBase { if (type === 'fontface') { this.fontFaceTemplate(responseData); } else { - const { keys, themeKeys, t } = this.readValue(responseData.data, type); - console.log('get them keys', themeKeys, keys, t, type); - this.getTheme(themeKeys, keys, t, type); - if (type === 'font') { - this.prepareTags(keys, themeKeys, t); - } + console.log('responseData', this.readValue(responseData.data, type)); + this.defineVariations(this.readValue(responseData.data, type)); } } } + defineVariations() { + const names = k(this.variations); + const result = names.reduce((a, name) => { + const unflatted = unflat(this.variations[name]); + return ( + a + + this.reduceViewports(unflatted, (actionData) => { + const actions = k(actionData); + return actions.reduce((b, action) => { + const actionName = `render${action.charAt(0).toUpperCase()}${action.slice(1)}`; + if (this[actionName]) { + return b + this[actionName](actionData[action], name); + } + return b; + }, ''); + }) + ); + }, ''); + this.variables = result; + } + + renderColor(data, name) { + return this.variablesValues(data, name, '.color-'); + } + + variablesValues(data, name, prepend = '.') { + const f = flat(data); + return `${prepend || '.'}${name} { + ${k(f) + .map((key) => `\n--${key}: ${f[key]};`) + .join('')} + } + `; + } + + variablesScopes(data, name, prepend = '.') { + const f = flat(data); + return `${prepend}${name} { + ${k(f) + .map((key) => `\n${key}: var(--${name}-${key}, ${f[key]});`) + .join('')} + } + `; + } + + renderFont(data, name) { + const elements = k(data); + const flattened = flat(data); + this.tags = elements.reduce((a, key) => { + const props = flat(data[key]); + return a + this.variablesScopes(props, key, ''); + }, ''); + return this.variablesValues(flattened, name, '.font-'); + } + async loadFragment() { await fetch('colors.json').then((response) => this.processFragment(response, 'color')); await fetch('fonts.json').then((response) => this.processFragment(response, 'font')); diff --git a/scripts/libs.js b/scripts/libs.js index d195b47c..a6b36a47 100644 --- a/scripts/libs.js +++ b/scripts/libs.js @@ -456,30 +456,51 @@ export function isHomePage(url) { return getBaseUrl() === (url || window.location.href); } -export const flat = (obj = {}, alreadyFlat = '') => - Object.entries(obj).reduce((acc, [key, value]) => { - const newKey = `${alreadyFlat ? `${alreadyFlat}-` : ''}${key}`; +/** + * flattenProperties: convert objects from {a:{b:{c:{d:1}}}} to all subkeys as strings {'a.b.c.d':1} + * + * @param {Object} obj - Object to flatten + * @param {String} alreadyFlat - prefix or recursive keys. + * */ + +export function flat(obj = {}, alreadyFlat = '', sep = '-') { + const f = {}; + // check if its a object + Object.keys(obj).forEach((k) => { + // get the value + const value = obj[k].valueOf() || obj[k]; + // append key to already flatten Keys + const key = `${alreadyFlat ? `${alreadyFlat}${sep}` : ''}${k}`; + // if still a object fo recursive if (isObject(value)) { - Object.assign(acc, flat(value, newKey)); + Object.assign(f, flat(value, key)); } else { - acc[newKey] = value; + // there is a real value so add key to flat object + f[key] = value; } - return acc; - }, {}); + }); + return f; +} -export const unflat = (obj = {}) => { +/** + * unFlattenProperties: convert objects from subkeys as strings {'a.b.c.d':1} to tree {a:{b:{c:{d:1}}}} + * + * @param {Object} obj - Object to unflatten + * */ + +export function unflat(f, sep = '-') { const un = {}; - Object.keys(obj).forEach((k) => { - const keys = k.split('-'); - keys.reduce((acc, key, index) => { - if (index === keys.length - 1) { - acc[key] = obj[k]; - } else { - acc[key] ??= {}; - return acc[key]; + // for each key create objects + Object.keys(f).forEach((key) => { + const properties = key.split(sep); + const value = f[key]; + properties.reduce((unflating, prop, i) => { + if (!unflating[prop]) { + const step = i < properties.length - 1 ? { [prop]: {} } : { [prop]: value }; + Object.assign(unflating, step); } - return acc; + return unflating[prop]; }, un); }); return un; -}; +}