Created
April 11, 2022 16:45
-
-
Save vdepizzol/69bb9f458dd113fdd7df10141574dcd5 to your computer and use it in GitHub Desktop.
class-map basic web component
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// #region [ function helpers ] | |
const ucFirst = (text) => { | |
if (!text) return text; | |
return `${text[0].toUpperCase()}${text.substring(1)}` | |
}; | |
const getAttributeUpdatedMethodName = (name) => `attributeChanged${ucFirst(name)}`; | |
// #endregion | |
class ClassMapWebComponent extends HTMLElement { | |
static get defaultClasses() { return []; } | |
static get classMap() { return {}; } | |
static get possibleAttributeValues() { return {}; } | |
constructor() { | |
super(); | |
this.classList.add(...this.constructor.defaultClasses); | |
} | |
// #region [ WC lifecycle ] | |
attributeChangedCallback(name, oldValue, newValue) { | |
if (!this.hasAttributeUpdatedMethod(name)) return; | |
const methodName = getAttributeUpdatedMethodName(name); | |
this[methodName](name, oldValue, newValue); | |
} | |
// #endregion | |
// #region [ style/attribute management ] | |
/** | |
* Force an update of the element style with the current attribute values | |
*/ | |
updateStyle() { | |
this.constructor.observedAttributes.forEach((attributeName) => { | |
this.updateAttribute(attributeName); | |
}) | |
} | |
/** | |
* Force an update of an attribute | |
*/ | |
updateAttribute(name) { | |
if (!this.hasAttributeUpdatedMethod(name)) return; | |
const methodName = getAttributeUpdatedMethodName(name); | |
const value = this.getAttribute(name); | |
this[methodName](name, undefined, value); | |
} | |
// #endregion | |
// #region [ Helpers ] | |
hasAttributeUpdatedMethod(name) { | |
const methodName = getAttributeUpdatedMethodName(name); | |
return methodName in this; | |
} | |
updateClassByAttributeClassMap(name, value) { | |
this.classList.remove( | |
...Object.values(this.constructor.classMap[name]) | |
); | |
if (this.isValueAccepted(name, value)) { | |
this.classList.add(this.constructor.classMap[name][value]); | |
return true; | |
} | |
return false; | |
} | |
isValueAccepted(attributeName, value) { | |
if (!value) return false; | |
value = value.trim(); | |
return !!this.constructor.possibleAttributeValues[attributeName]?.includes(value); | |
}; | |
// #endregion | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.Stack { | |
--Stack-gap: var(--scale-16); | |
display: flex; | |
flex-flow: column; | |
gap: var(--Stack-gap); | |
/* Gap */ | |
&.Stack--condensed { | |
--Stack-gap: var(--scale-16); | |
} | |
@media ($query-whenNotMobile) { | |
&.Stack--spacious { | |
--Stack-gap: var(--scale-24); | |
} | |
} | |
/* Direction */ | |
&.Stack--inline { | |
flex-flow: row; | |
} | |
&.Stack--block { | |
flex-flow: column; | |
} | |
/* Wrap */ | |
&.Stack--wrap { | |
flex-wrap: wrap; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// #region [Definitions] | |
//attributes | |
const attributeList = Object.freeze(['direction', 'gap', 'wrap']); | |
const possibleAttributeValues = Object.freeze({ | |
gap: ['normal', 'condensed', 'spacious'], | |
direction: ['inline', 'block'], | |
wrap: ['wrap', 'nowrap'], | |
}); | |
//classes and styles | |
const stackClassName = 'Stack'; | |
const defaultClasses = Object.freeze(['Stack', 'Stack--normal', 'Stack--block']); | |
const classMap = (() => { | |
const classMap = {}; | |
classMap.gap = possibleAttributeValues.gap | |
.reduce((map, value) => { | |
map[value] = `${stackClassName}--${value}` | |
return map; | |
}, {}); | |
classMap.direction = possibleAttributeValues.direction | |
.reduce((map, value) => { | |
map[value] = `${stackClassName}--${value}` | |
return map; | |
}, {}); | |
classMap.wrap = possibleAttributeValues.wrap | |
.reduce((map, value) => { | |
map[value] = `${stackClassName}--${value}` | |
return map; | |
}, {}); | |
return Object.freeze(classMap); | |
})(); | |
const customPropertyMap = Object.freeze({ | |
gap: `--${stackClassName}-gap` | |
}); | |
// #endregion | |
class UiStack extends ClassMapWebComponent { | |
// #region [Definitions] | |
static get observedAttributes() { | |
return attributeList; | |
} | |
static get possibleAttributeValues() { | |
return possibleAttributeValues; | |
} | |
static get defaultClasses() { | |
return defaultClasses; | |
} | |
static get classMap() { | |
return classMap; | |
} | |
static get customPropertyMap() { | |
return customPropertyMap; | |
} | |
// #endregion | |
constructor() { | |
super(); | |
} | |
// #region [ WC lifecycle ] | |
attributeChangedCallback(name, oldValue, newValue) { | |
super.attributeChangedCallback(name, oldValue, newValue); | |
} | |
// #endregion | |
// #region [ attribute updates ] | |
attributeChangedGap(name, _, newValue) { | |
const customProperty = UiStack.customPropertyMap[name]; | |
if (customProperty) { | |
this.style.removeProperty(customProperty); | |
} | |
const wasUpdated = this.updateClassByAttributeClassMap(name, newValue); | |
if (wasUpdated) return; | |
if (!newValue || !newValue.trim()) return; | |
this.style.setProperty(customProperty, newValue); | |
} | |
attributeChangedDirection(name, _, newValue) { | |
this.updateClassByAttributeClassMap(name, newValue); | |
} | |
attributeChangedWrap(name, _, newValue) { | |
if (typeof newValue === 'string' && (!newValue || !newValue.trim())) newValue = 'wrap'; | |
this.updateClassByAttributeClassMap(name, newValue); | |
} | |
// #endregion | |
} | |
// enabling custom element as a DOM interface | |
customElements.define('ui-stack', UiStack); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment