fonteva-design-guide
Version:
## Dev, Build and Test
258 lines (237 loc) • 9.07 kB
JavaScript
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
qaData;
// content attributes for Aura
cref;
ctext;
// Passthrough attributes
passthroughStyle;
passthroughClass;
passthroughContenteditable;
passthroughAccesskey;
// CSV list
eventlist = 'click';
// ELEMENT
id;
dir;
hidden;
tabIndex;
title;
inputmode;
draggable;
dropzone;
lang;
translate;
spellcheck;
autocapitalize;
itemid;
itemprop;
itemref;
itemscope;
itemtype;
// IMG
imgSrc;
imgAlign;
imgAlt;
imgName;
imgTitle;
imgBorder;
imgWidth;
imgHeight;
imgHspace;
imgVspace;
// Icons
iconPosition = 'left'; // left, right
icon; // only use utility icons found here: https://www.lightningdesignsystem.com/icons/#utility
iconClass;
iconLabel;
iconSize = 'xx-small'; // xx-small, x-small, small, medium, large, x-large
iconRight; // only use utility icons found here: https://www.lightningdesignsystem.com/icons/#utility
iconRightClass;
iconRightLabel;
iconRightSize = 'xx-small'; // xx-small, x-small, small, medium, large, x-large
iconDataset;
leftIcon = true;
rightIcon = false;
iconVariant;
// data-* attributes
dtasetid;
dtasethash;
dtasetitemid;
dtasetcontactid;
dtasetcomponent;
dtasetpackageid;
dtasetpage;
dtasetroute;
dtaseturl;
dtasettarget;
dtasetmenu;
dtasetgroup;
dtasetparentsol;
dtasetname;
dtasetclose;
dtasetline;
dtasetindex;
dtasettitle;
dtasettype;
dtasetfield;
// ARIA
ariaActivedescendant;
ariaAutocomplete;
ariaChecked = false;
ariaControls;
ariaDescribedby;
ariaDisabled = false;
ariaExpanded = false;
ariaHidden = false;
ariaInvalid = false;
ariaLabel;
ariaLabelledby;
ariaLive;
ariaOwns;
ariaPressed = false;
ariaRequired = false;
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);
}
}
}