UNPKG

@onenexus/polymorph

Version:

Style Synergy modules/BEM DOM elements using JavaScript

376 lines (297 loc) 12.6 kB
// `process` and `require` are exploited to help reduce bundle size if (typeof process === 'undefined') window.process = { env: {} }; let sQuery; export default function polymorph(element, styles, config = {}, globals) { var Synergy = window.Synergy || {}; sQuery = window.sQuery if (!process.env.SYNERGY && !sQuery) { sQuery = { getComponents: require('@onenexus/squery/src/api/getComponents').default, getSubComponents: require('@onenexus/squery/src/api/getSubComponents').default, hasModifier: require('@onenexus/squery/src/api/hasModifier').default, parent: require('@onenexus/squery/src/api/parent').default } } const modifierGlue = config.modifierGlue || Synergy.modifierGlue || '-'; const componentGlue = config.componentGlue || Synergy.componentGlue || '_'; const CONFIG = { componentGlue, modifierGlue }; let STYLESHEET = styles; if (typeof styles === 'function') { STYLESHEET = styles(element, config, globals); } if (styles.constructor === Array) { STYLESHEET = styles.map(style => { return typeof style === 'function' ? style(element, config, globals) : style }); } if (STYLESHEET.constructor === Array) { if (STYLESHEET.every(value => value && value.constructor === Object)) { STYLESHEET.forEach(value => handleStyleSheet(element, value, CONFIG)); } } else { handleStyleSheet(element, STYLESHEET, CONFIG); } element.repaint(); } /** * */ function handleStyleSheet(element, stylesheet, config, context = []) { if (!element.polymorph) { const WRAPPER_ELEMENT = [].slice.call(element.parentNode.classList).some(className => { return (className.indexOf('group') === 0 || className.indexOf('wrapper') === 0); }) && element.parentNode; element.polymorph = { rules: [], COMPONENTS: sQuery.getComponents.bind({...config})(element), SUB_COMPONENTS: sQuery.getSubComponents.bind({...config})(element) } const { COMPONENTS, SUB_COMPONENTS } = element.polymorph; element.repaint = function(disableDependentElements) { let allDependentElements = []; if (WRAPPER_ELEMENT && WRAPPER_ELEMENT.repaint) { WRAPPER_ELEMENT.repaint(true) } [element, ...COMPONENTS, ...SUB_COMPONENTS].forEach(el => { if (el.polymorph) { el.polymorph.rules.forEach(rule => { if (rule.context.every(ruleContext => { if (typeof ruleContext === 'function') { return ruleContext(); } if (ruleContext.value === 'hover') { return ruleContext.source.polymorph.isHovered; } if (ruleContext.value === 'focus') { return ruleContext.source.polymorph.isFocused; } return sQuery.hasModifier.bind({...config})(ruleContext.source, ruleContext.value) })) { // `doStyles()` applies the styles and returns dependent elements const dependentElements = doStyles(el, rule.styles) || []; dependentElements.length && allDependentElements.push(...dependentElements); } }); if (allDependentElements.includes(el)) { allDependentElements.filter(item => item !== el) } } }); if (!disableDependentElements && allDependentElements.length) { allDependentElements = allDependentElements.filter((item, pos) => { return allDependentElements.indexOf(item) == pos; }); allDependentElements.forEach(el => el.repaint && el.repaint(true)); } }; } element.polymorph.rules = element.polymorph.rules.concat({ context: context, styles: stylesheet }); if (typeof stylesheet === 'function') { stylesheet = stylesheet(element); } if (!stylesheet) return; Object.entries(stylesheet).forEach(([key, value]) => { let COMPONENTS = sQuery.getComponents.bind({...config})(element, key); let SUB_COMPONENTS = sQuery.getSubComponents.bind({...config})(element, key); if (value.styles) { if (value.condition) { context = context.concat(value.condition); } if (value.element) { element = value.element; } return handleStyleSheet(element, value.styles, config, context); } if (value instanceof Array) { if (typeof value[0] === 'undefined') { return; } if (value.every(val => val && typeof val === 'object' && val.constructor === Object)) { return value.forEach(val => handleStyleSheet(element, val, config, context)); } //Handle case where desired element for styles to be applied is manually controlled if (value[0] instanceof HTMLElement) { return handleStyleSheet(value[0], value[1], config, context); } //Handle case where desired element for styles to be applied is manually controlled if (value[0] instanceof NodeList) { value[0].forEach(el => { return handleStyleSheet(el, value[1], config, context); }); } //Handle case condition to apply styles is to be manually controlled if (typeof value[0] === 'function') { return handleStyleSheet(element, value[1], config, context.concat(value[0])); } return; } // Smart handle `components` if (COMPONENTS.length) { if (value.disableCascade) { COMPONENTS = COMPONENTS.filter(component => { return COMPONENTS.every(_component => component.contains(_component)); }); } return COMPONENTS.forEach(component => { return handleStyleSheet(component, value, config, context); }); } // Smart handle `sub-components` if (SUB_COMPONENTS.length) { if (value.disableCascade) { SUB_COMPONENTS = SUB_COMPONENTS.filter(subComponent => { const componentName = [...element.classList].reduce(($, currentValue) => { if (currentValue.indexOf(config.componentGlue) > 1) { const glueLength = config.componentGlue.length; const nameStart = currentValue.lastIndexOf(config.componentGlue) + glueLength; currentValue = currentValue.substring(nameStart, currentValue.length); return currentValue.substring(0, currentValue.indexOf(config.modifierGlue)); } }, []); const parentSubComponent = sQuery.parent.bind({...config})(subComponent, componentName); return element === parentSubComponent; }); } return SUB_COMPONENTS.forEach(component => { return handleStyleSheet(component, value, config, context); }); } // Handle `sub-components` if (key.indexOf('sub-component(') > -1) { const subComponent = key.replace('sub-component(', '').replace(/\)/g, ''); const subComponents = sQuery.getSubComponents.bind({...config})(element, subComponent); return subComponents.forEach(component => { return handleStyleSheet(component, value, config, context); }); } // Handle `components` if (key.indexOf('component(') > -1) { const component = key.replace('component(', '').replace(/\)/g, ''); const components = sQuery.getComponents.bind({...config})(element, component); return components.forEach(component => { return handleStyleSheet(component, value, config, context); }); } // Handle `modifiers` if (key.indexOf('modifier(') > -1) { const modifier = key.replace('modifier(', '').replace(/\)/g, ''); return handleStyleSheet(element, value, config, context.concat({ source: element, value: modifier })); } // Handle `hover` interaction if (key === ':hover') { handleStyleSheet(element, value, config, context.concat({ source: element, value: 'hover' })); element.addEventListener('mouseenter', event => { element.polymorph.isHovered = true; element.repaint(); }); element.addEventListener('mouseleave', event => { element.polymorph.isHovered = false; element.repaint(); }); return; } // Handle `focus` interaction if (key === ':focus') { handleStyleSheet(element, value, config, context.concat({ source: element, value: 'focus' })); element.addEventListener('focus', event => { element.polymorph.isFocused = true; element.repaint(); }); element.addEventListener('blur', event => { element.polymorph.isFocused = false; element.repaint(); }); return; } // Handle Group/Wrapper elements if (key === 'group' || key === 'wrapper') { const wrapper = element.parentNode; return wrapper.classList.forEach(className => { if (className.indexOf('group') === 0 || className.indexOf('wrapper') === 0) { handleStyleSheet(wrapper, value, config, context); } }); } }); element.polymorph.rules.sort((a, b) => { if (!a.context.length && !b.context.length) { return 0; } if (a.context.length && !b.context.length) { return 1; } if (!a.context.length && b.context.length) { return -1; } if (a.context.some(c => c.value === 'hover') && b.context.some(c => c.value === 'hover')) { return 0; } if (b.context.some(c => c.value === 'hover')) { return -1; } return 0; }); } /** * */ function doStyles(el, styles) { if (typeof styles === 'function') { styles = styles(el); } if (!styles) return; Object.entries(styles).forEach(([key, value]) => { if (typeof value === 'function') { try { return el.style[key] = value(el.style[key]); } catch(error) { return error; } } // https://stackoverflow.com/questions/55867116 if (!isNaN(key)) { return; } return el.style[key] = value; }); const dependentElements = Object.values(styles).reduce((accumulator, currentValue) => { if (currentValue instanceof Array && currentValue[0]) { if (currentValue[0] instanceof NodeList || currentValue[0] instanceof Array) { return accumulator.concat(...currentValue[0]); } return accumulator.concat(currentValue[0]); } return accumulator; }, []); return dependentElements; } /** * Wrapper for sQuery `hasModifier()` */ polymorph.modifier = (element, modifier, modifierGlue, componentGlue) => { var Synergy = window.Synergy || {}; modifierGlue = modifierGlue || Synergy.modifierGlue || '-'; componentGlue = componentGlue || Synergy.componentGlue || '_'; return sQuery.hasModifier.bind({ modifierGlue, componentGlue })(element, modifier); } /** * Attach to Window */ if (typeof window !== 'undefined') { window.polymorph = polymorph; }