UNPKG

fonteva-design-guide

Version:

## Dev, Build and Test

258 lines (237 loc) 9.07 kB
import { LightningElement, api, track } from 'lwc'; import { refireEvents, Selector } from 'c/utils'; import validateWcag from 'c/wcag'; export default class PfmElement extends Selector(LightningElement) { static passthroughPrefix = 'passthrough'; static passthroughPrefixRegex = new RegExp('^passthrough'); static dataSetPrefix = 'dtaset'; static datasetPrefixRegex = new RegExp('^dtaset'); // misc for QA @api qaData; // content attributes for Aura @api cref; @api ctext; // Passthrough attributes @api passthroughStyle; @api passthroughClass; @api passthroughContenteditable; @api passthroughAccesskey; // CSV list @api eventlist = 'click'; // ELEMENT @api id; @api dir; @api hidden; @api tabIndex; @api title; @api inputmode; @api draggable; @api dropzone; @api lang; @api translate; @api spellcheck; @api autocapitalize; @api itemid; @api itemprop; @api itemref; @api itemscope; @api itemtype; // IMG @api imgSrc; @api imgAlign; @api imgAlt; @api imgName; @api imgTitle; @api imgBorder; @api imgWidth; @api imgHeight; @api imgHspace; @api imgVspace; // Icons @api iconPosition = 'left'; // left, right @api icon; // only use utility icons found here: https://www.lightningdesignsystem.com/icons/#utility @api iconClass; @api iconLabel; @api iconSize = 'xx-small'; // xx-small, x-small, small, medium, large, x-large @api iconRight; // only use utility icons found here: https://www.lightningdesignsystem.com/icons/#utility @api iconRightClass; @api iconRightLabel; @api iconRightSize = 'xx-small'; // xx-small, x-small, small, medium, large, x-large @api iconDataset; @track leftIcon = true; @track rightIcon = false; @track iconVariant; // data-* attributes @api dtasetid; @api dtasethash; @api dtasetitemid; @api dtasetcontactid; @api dtasetcomponent; @api dtasetpackageid; @api dtasetpage; @api dtasetroute; @api dtaseturl; @api dtasettarget; @api dtasetmenu; @api dtasetgroup; @api dtasetparentsol; @api dtasetname; @api dtasetclose; @api dtasetline; @api dtasetindex; @api dtasettitle; @api dtasettype; @api dtasetfield; // ARIA @api ariaActivedescendant; @api ariaAutocomplete; @api ariaChecked = false; @api ariaControls; @api ariaDescribedby; @api ariaDisabled = false; @api ariaExpanded = false; @api ariaHidden = false; @api ariaInvalid = false; @api ariaLabel; @api ariaLabelledby; @api ariaLive; @api ariaOwns; @api ariaPressed = false; @api ariaRequired = false; @api ariaSelected = false; /** * This function performs some magic by taking any non-excluded @api/public attributes on a parent component, * and applying them to a given child component using normalized attribute names. * @param parentCmp * @param childCmp * @param excludedAttributes */ static applyAttributesToChild(selfCmp, parentCmp, superProto, childCmp, excludedAttributes = []) { Object.entries( Object.assign( Object.getOwnPropertyDescriptors(selfCmp.__proto__), Object.getOwnPropertyDescriptors(parentCmp.__proto__), Object.getOwnPropertyDescriptors(superProto.__proto__) ) ).forEach(descriptor => { const [attribute, method] = descriptor; const isPublic = !!(method && method.get && method.set); const attributeValue = parentCmp[attribute]; if (attribute.includes('img') || true) { // console.warn('ATTRIBUTE', attribute, attributeValue, method, isPublic); } if ( isPublic && !excludedAttributes.includes(attribute) && attributeValue !== null && attributeValue !== false && attributeValue !== undefined ) { switch (true) { case attribute === 'qaData': let qaData = JSON.parse(attributeValue); Object.keys(qaData).forEach(key => { this.setAttribute(childCmp, 'data-' + key, qaData[key]); }); break; case this.datasetPrefixRegex.test(attribute): this.setAttribute(selfCmp, attribute, attributeValue); this.setAttribute(childCmp, attribute, attributeValue); break; case /^img/.test(attribute): this.setAttribute(childCmp.querySelector('img'), attribute, attributeValue); break; case !/^(icon|img)/.test(attribute): this.setAttribute(childCmp, attribute, attributeValue); break; } } }); } static setAttribute(component, attribute, attributeValue) { if (component) { if (attribute === 'passthroughClass') { const classAttr = component.getAttribute('class'); const currentClassNames = classAttr ? classAttr.split(' ') : []; attributeValue = attributeValue.split(' ').concat(currentClassNames).join(' '); } component.setAttribute(this.normalizeAttributeName(attribute), attributeValue); } } static setContentFromRef(cmp, tagName) { if (cmp.cref && !cmp.ctext) { const contentTarget = cmp.template.querySelector(tagName); const contentSource = window.document.querySelector('.' + cmp.cref); if (contentSource === null) { return; } contentSource.style.display = 'none'; contentTarget.innerHTML = contentSource.innerHTML; contentSource.innerHTML = ''; } } static normalizeAttributeName(attribute) { switch (true) { case attribute.startsWith('aria'): return attribute.replace(/^aria([A-Z])/, 'aria-$1').toLowerCase(); case attribute.startsWith('img'): return attribute.replace(/^img([0-9A-Z])/i, '$1').toLowerCase(); case this.passthroughPrefixRegex.test(attribute): return attribute.replace(this.passthroughPrefix, '').toLowerCase(); case this.datasetPrefixRegex.test(attribute): return 'data-' + attribute.replace(this.dataSetPrefix, ''); default: return attribute; } } static styleVariants = { default: 'inverse', standard: '' }; static styleVariantMappings = { standard: ['outline', 'bare', 'link'], default: ['default', 'success', 'danger'] }; static getStyleVariants() { return Object.values(this.styleVariantMappings).reduce( (allVariants, variants) => allVariants.concat(variants), [] ); } static initIcon(cmp) { if (cmp.icon) { if (cmp.iconVariant === undefined) { cmp.iconVariant = this.styleVariants.default; Object.entries(this.styleVariantMappings).forEach(([mapping, variants]) => { if (variants.includes(cmp.variant)) { cmp.iconVariant = this.styleVariants[mapping]; } }); } if (cmp.iconDataset !== undefined) { const icon = cmp.template.querySelector('lightning-icon'); const iconDataSet = JSON.parse(cmp.iconDataset); Object.entries(iconDataSet).forEach((dataKey, dataVal) => { icon.dataset[dataKey] = dataVal; }); } } } initializeElement(component, superProto, elementTagName, excludedAttributes = [], isRendered = false) { if (!isRendered) { this.constructor.initIcon(component); this.constructor.setContentFromRef(component, elementTagName); } const element = component.template.querySelector(elementTagName); this.constructor.applyAttributesToChild(component, this, superProto, element, excludedAttributes); if (!isRendered) { refireEvents(component, element, this.eventlist, this); setTimeout(() => { const isValid = validateWcag(element, elementTagName); if (!isValid) { // console.log('NOT VALID:', element); } }, 500); } } }