Last active
April 9, 2025 19:31
-
-
Save krhoyt/c0fbb7ad1fbb1bdf8e808115e6d62487 to your computer and use it in GitHub Desktop.
Template attempting to show the many facets of a baseline web standards-based component. Not intended to function as a 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
export default class HoytBox extends HTMLElement { | |
constructor() { | |
super(); | |
const template = document.createElement( 'template' ); | |
template.innerHTML = /* template */ ` | |
<style> | |
:host { | |
box-sizing: border-box; | |
display: inline-block; | |
position: relative; | |
} | |
:host( [center] ) div { | |
align-items: center; | |
} | |
:host( [direction=column] ) div { | |
flex-direction: column; | |
} | |
:host( [direction=column-reverse] ) div { | |
flex-direction: column-reverse; | |
} | |
:host( [direction=row-reverse] ) div { | |
flex-direction: row-reverse; | |
} | |
:host( [gap=xs] ) div { | |
gap: 2px; | |
} | |
:host( [gap=s] ) div { | |
gap: 4px; | |
} | |
:host( [gap=m] ) div { | |
gap: 8px; | |
} | |
:host( [gap=l] ) div { | |
gap: 16px; | |
} | |
:host( [gap=xl] ) div { | |
gap: 32px; | |
} | |
:host( [grow] ) { | |
flex-basis: 0; | |
flex-grow: 1; | |
} | |
:host( [hidden] ) { | |
display: none; | |
} | |
:host( [justify] ) div { | |
justify-content: center; | |
} | |
:host( :not( [label] ) ) hoyt-label { | |
display: none; | |
} | |
div { | |
box-sizing: border-box; | |
display: flex; | |
flex-direction: row; | |
height: 100%; | |
width: 100%; | |
} | |
</style> | |
<hoyt-label exportparts="content" part="label"></hoyt-label> | |
<div part="box"> | |
<slot></slot> | |
</div> | |
`; | |
// Root | |
this.attachShadow( {mode: 'open'} ); | |
this.shadowRoot.appendChild( template.content.cloneNode( true ) ); | |
// Elements | |
this.$label = this.shadowRoot.querySelector( 'hoyt-label' ); | |
} | |
// When attributes change | |
_render() { | |
this.$label.textContent = this.label === null ? '' : this.label; | |
} | |
// Promote properties | |
// Values may be set before module load | |
_upgrade( property ) { | |
if( this.hasOwnProperty( property ) ) { | |
const value = this[property]; | |
delete this[property]; | |
this[property] = value; | |
} | |
} | |
// Setup | |
connectedCallback() { | |
this._upgrade( 'center' ); | |
this._upgrade( 'direction' ); | |
this._upgrade( 'gap' ); | |
this._upgrade( 'grow' ); | |
this._upgrade( 'hidden' ); | |
this._upgrade( 'justify' ); | |
this._upgrade( 'label' ); | |
this._render(); | |
} | |
// Watched attributes | |
static get observedAttributes() { | |
return [ | |
'center', | |
'direction', | |
'gap', | |
'grow', | |
'hidden', | |
'justify', | |
'label' | |
]; | |
} | |
// Observed attribute has changed | |
// Update render | |
attributeChangedCallback( name, old, value ) { | |
this._render(); | |
} | |
// Attributes | |
// Reflected | |
// Boolean, Number, String, null | |
get center() { | |
return this.hasAttribute( 'center' ); | |
} | |
set center( value ) { | |
if( value !== null ) { | |
if( typeof value === 'boolean' ) { | |
value = value.toString(); | |
} | |
if( value === 'false' ) { | |
this.removeAttribute( 'center' ); | |
} else { | |
this.setAttribute( 'center', '' ); | |
} | |
} else { | |
this.removeAttribute( 'center' ); | |
} | |
} | |
get direction() { | |
if( this.hasAttribute( 'direction' ) ) { | |
return this.getAttribute( 'direction' ); | |
} | |
return null; | |
} | |
set direction( value ) { | |
if( value !== null ) { | |
this.setAttribute( 'direction', value ); | |
} else { | |
this.removeAttribute( 'direction' ); | |
} | |
} | |
get gap() { | |
if( this.hasAttribute( 'gap' ) ) { | |
return this.getAttribute( 'gap' ); | |
} | |
return null; | |
} | |
set gap( value ) { | |
if( value !== null ) { | |
this.setAttribute( 'gap', value ); | |
} else { | |
this.removeAttribute( 'gap' ); | |
} | |
} | |
get grow() { | |
return this.hasAttribute( 'grow' ); | |
} | |
set grow( value ) { | |
if( value !== null ) { | |
if( typeof value === 'boolean' ) { | |
value = value.toString(); | |
} | |
if( value === 'false' ) { | |
this.removeAttribute( 'grow' ); | |
} else { | |
this.setAttribute( 'grow', '' ); | |
} | |
} else { | |
this.removeAttribute( 'grow' ); | |
} | |
} | |
get hidden() { | |
return this.hasAttribute( 'hidden' ); | |
} | |
set hidden( value ) { | |
if( value !== null ) { | |
if( typeof value === 'boolean' ) { | |
value = value.toString(); | |
} | |
if( value === 'false' ) { | |
this.removeAttribute( 'hidden' ); | |
} else { | |
this.setAttribute( 'hidden', '' ); | |
} | |
} else { | |
this.removeAttribute( 'hidden' ); | |
} | |
} | |
get justify() { | |
return this.hasAttribute( 'justify' ); | |
} | |
set justify( value ) { | |
if( value !== null ) { | |
if( typeof value === 'boolean' ) { | |
value = value.toString(); | |
} | |
if( value === 'false' ) { | |
this.removeAttribute( 'justify' ); | |
} else { | |
this.setAttribute( 'justify', '' ); | |
} | |
} else { | |
this.removeAttribute( 'justify' ); | |
} | |
} | |
get label() { | |
if( this.hasAttribute( 'label' ) ) { | |
return this.getAttribute( 'label' ); | |
} | |
return null; | |
} | |
set label( value ) { | |
if( value !== null ) { | |
this.setAttribute( 'label', value ); | |
} else { | |
this.removeAttribute( 'label' ); | |
} | |
} | |
} | |
window.customElements.define( 'hoyt-box', HoytBox ); |
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
export default class HoytIcon extends HTMLElement { | |
constructor() { | |
super(); | |
const template = document.createElement( 'template' ); | |
template.innerHTML = /* template */ ` | |
<style> | |
:host { | |
box-sizing: border-box; | |
display: inline-block; | |
position: relative; | |
} | |
:host( [hidden] ) { | |
display: none; | |
} | |
i { | |
box-sizing: border-box; | |
color: var( --icon-color, #161616 ); | |
cursor: var( --icon-cursor, default ); | |
direction: ltr; | |
display: flex; | |
font-family: 'Material Symbols'; | |
font-size: var( --icon-size, 24px ); | |
font-style: normal; | |
font-weight: normal; | |
height: var( --icon-size, 24px ); | |
letter-spacing: normal; | |
line-height: var( --icon-size, 24px ); | |
margin: 0; | |
max-height: var( --icon-size, 24px ); | |
max-width: var( --icon-size, 24px ); | |
min-height: var( --icon-size, 24px ); | |
min-width: var( --icon-size, 24px ); | |
padding: 0; | |
text-align: center; | |
text-rendering: optimizeLegibility; | |
text-transform: none; | |
white-space: nowrap; | |
width: var( --icon-size, 24px ); | |
word-wrap: normal; | |
} | |
:host( [disabled] ) i { | |
color: #aaaaaa; | |
} | |
:host( [size=xs] ) i { | |
font-size: 16px; | |
height: 16px; | |
line-height: 16px; | |
max-height: 16px; | |
max-width: 16px; | |
min-height: 16px; | |
min-width: 16px; | |
width: 16px; | |
} | |
:host( [size=s] ) i { | |
font-size: 20px; | |
height: 20px; | |
line-height: 20px; | |
max-height: 20px; | |
max-width: 20px; | |
min-height: 20px; | |
min-width: 20px; | |
width: 20px; | |
} | |
:host( [size=l] ) i { | |
font-size: 32px; | |
height: 32px; | |
line-height: 32px; | |
max-height: 32px; | |
max-width: 32px; | |
min-height: 32px; | |
min-width: 32px; | |
width: 32px; | |
} | |
:host( [size=xl] ) i { | |
font-size: 48px; | |
height: 48px; | |
line-height: 48px; | |
max-height: 48px; | |
max-width: 48px; | |
min-height: 48px; | |
min-width: 48px; | |
width: 48px; | |
} | |
</style> | |
<i part="icon"></i> | |
`; | |
// Root | |
this.attachShadow( {mode: 'open'} ); | |
this.shadowRoot.appendChild( template.content.cloneNode( true ) ); | |
// Elements | |
this.$icon = this.shadowRoot.querySelector( 'i' ); | |
} | |
// When attributes change | |
_render() { | |
this.$icon.textContent = this.name === null ? '' : this.name; | |
if( this.name !== null ) { | |
const variation = []; | |
variation.push( '\'FILL\' ' + ( this.filled ? 1 : 0 ) ); | |
variation.push( '\'wght\' ' + ( this.weight === null ? 400 : this.weight ) ); | |
variation.push( '\'GRAD\' ' + ( this.filled ? 0 : 0 ) ); | |
variation.push( '\'opsz\' ' + ( this.optical === null ? 24 : this.optical ) ); | |
this.$icon.style.fontVariationSettings = variation.toString(); | |
} | |
} | |
// Promote properties | |
// Values may be set before module load | |
_upgrade( property ) { | |
if( this.hasOwnProperty( property ) ) { | |
const value = this[property]; | |
delete this[property]; | |
this[property] = value; | |
} | |
} | |
// Setup | |
connectedCallback() { | |
this._upgrade( 'disabled' ); | |
this._upgrade( 'filled' ); | |
this._upgrade( 'hidden' ); | |
this._upgrade( 'name' ); | |
this._upgrade( 'optical' ); | |
this._upgrade( 'size' ); | |
this._upgrade( 'weight' ); | |
this._render(); | |
} | |
// Watched attributes | |
static get observedAttributes() { | |
return [ | |
'disabled', | |
'filled', | |
'hidden', | |
'name', | |
'optical', | |
'size', | |
'weight' | |
]; | |
} | |
// Observed attribute has changed | |
// Update render | |
attributeChangedCallback( name, old, value ) { | |
this._render(); | |
} | |
// Attributes | |
// Reflected | |
// Boolean, Number, String, null | |
get disabled() { | |
return this.hasAttribute( 'disabled' ); | |
} | |
set disabled( value ) { | |
if( value !== null ) { | |
if( typeof value === 'boolean' ) { | |
value = value.toString(); | |
} | |
if( value === 'false' ) { | |
this.removeAttribute( 'disabled' ); | |
} else { | |
this.setAttribute( 'disabled', '' ); | |
} | |
} else { | |
this.removeAttribute( 'disabled' ); | |
} | |
} | |
get filled() { | |
return this.hasAttribute( 'filled' ); | |
} | |
set filled( value ) { | |
if( value !== null ) { | |
if( typeof value === 'boolean' ) { | |
value = value.toString(); | |
} | |
if( value === 'false' ) { | |
this.removeAttribute( 'filled' ); | |
} else { | |
this.setAttribute( 'filled', '' ); | |
} | |
} else { | |
this.removeAttribute( 'filled' ); | |
} | |
} | |
get hidden() { | |
return this.hasAttribute( 'hidden' ); | |
} | |
set hidden( value ) { | |
if( value !== null ) { | |
if( typeof value === 'boolean' ) { | |
value = value.toString(); | |
} | |
if( value === 'false' ) { | |
this.removeAttribute( 'hidden' ); | |
} else { | |
this.setAttribute( 'hidden', '' ); | |
} | |
} else { | |
this.removeAttribute( 'hidden' ); | |
} | |
} | |
get name() { | |
if( this.hasAttribute( 'name' ) ) { | |
return this.getAttribute( 'name' ); | |
} | |
return null; | |
} | |
set name( value ) { | |
if( value !== null ) { | |
this.setAttribute( 'name', value ); | |
} else { | |
this.removeAttribute( 'name' ); | |
} | |
} | |
get optical() { | |
if( this.hasAttribute( 'optical' ) ) { | |
return parseInt( this.getAttribute( 'optical' ) ); | |
} | |
return null; | |
} | |
set optical( value ) { | |
if( value !== null ) { | |
this.setAttribute( 'optical', value ); | |
} else { | |
this.removeAttribute( 'optical' ); | |
} | |
} | |
get size() { | |
if( this.hasAttribute( 'size' ) ) { | |
return this.getAttribute( 'size' ); | |
} | |
return null; | |
} | |
set size( value ) { | |
if( value !== null ) { | |
this.setAttribute( 'size', value ); | |
} else { | |
this.removeAttribute( 'size' ); | |
} | |
} | |
get weight() { | |
if( this.hasAttribute( 'weight' ) ) { | |
return parseInt( this.getAttribute( 'weight' ) ); | |
} | |
return null; | |
} | |
set weight( value ) { | |
if( value !== null ) { | |
this.setAttribute( 'weight', value ); | |
} else { | |
this.removeAttribute( 'weight' ); | |
} | |
} | |
} | |
window.customElements.define( 'hoyt-icon', HoytIcon ); |
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
export default class HoytLabel extends HTMLElement { | |
constructor() { | |
super(); | |
const template = document.createElement( 'template' ); | |
template.innerHTML = /* template */ ` | |
<style> | |
:host { | |
box-sizing: border-box; | |
display: inline-block; | |
position: relative; | |
} | |
:host( [hidden] ) { | |
display: none; | |
} | |
p { | |
box-sizing: border-box; | |
color: var( --label-color, #161616 ); | |
cursor: var( --label-cursor, default ); | |
font-family: 'IBM Plex Sans', sans-serif; | |
font-size: var( --label-font-size, 16px ); | |
font-style: normal; | |
font-weight: var( --label-font-weight, 400 ); | |
line-height: var( --label-line-height, 20px ); | |
margin: 0; | |
padding: 0; | |
text-align: var( --label-text-align, left ); | |
text-decoration: var( --label-text-decoration, none ); | |
text-transform: var( --label-text-transform, none ); | |
text-rendering: optimizeLegibility; | |
} | |
:host( [size=xs] ) p { | |
font-size: 12px; | |
line-height: 14px; | |
} | |
:host( [size=s] ) p { | |
font-size: 14px; | |
line-height: 18px; | |
} | |
:host( [size=l] ) p { | |
font-size: 18px; | |
line-height: 22px; | |
} | |
:host( [size=xl] ) p { | |
font-size: 24px; | |
line-height: 30px; | |
} | |
:host( [size=heading] ) p { | |
font-size: 32px; | |
line-height: 36px; | |
} | |
:host( [weight=bold] ) p { | |
font-weight: 600; | |
} | |
:host( [truncate] ) { | |
overflow: hidden; | |
} | |
:host( [truncate] ) p { | |
overflow: hidden; | |
text-overflow: ellipsis; | |
white-space: nowrap; | |
} | |
:host( :not( [text] ) ) span { | |
display: none; | |
} | |
::slotted( strong ) { | |
font-weight: 600; | |
} | |
</style> | |
<p part="content"> | |
<span></span> | |
<slot></slot> | |
</p> | |
`; | |
// Root | |
this.attachShadow( {mode: 'open'} ); | |
this.shadowRoot.appendChild( template.content.cloneNode( true ) ); | |
// Elements | |
this.$inline = this.shadowRoot.querySelector( 'span' ); | |
} | |
// When attributes change | |
_render() { | |
this.$inline.textContent = this.text === null ? '' : this.text; | |
} | |
// Promote properties | |
// Values may be set before module load | |
_upgrade( property ) { | |
if( this.hasOwnProperty( property ) ) { | |
const value = this[property]; | |
delete this[property]; | |
this[property] = value; | |
} | |
} | |
// Setup | |
connectedCallback() { | |
this._upgrade( 'hidden' ); | |
this._upgrade( 'size' ); | |
this._upgrade( 'text' ); | |
this._upgrade( 'truncate' ); | |
this._upgrade( 'weight' ); | |
this._render(); | |
} | |
// Watched attributes | |
static get observedAttributes() { | |
return [ | |
'hidden', | |
'size', | |
'text', | |
'truncate', | |
'weight' | |
]; | |
} | |
// Observed attribute has changed | |
// Update render | |
attributeChangedCallback( name, old, value ) { | |
this._render(); | |
} | |
// Attributes | |
// Reflected | |
// Boolean, Number, String, null | |
get hidden() { | |
return this.hasAttribute( 'hidden' ); | |
} | |
set hidden( value ) { | |
if( value !== null ) { | |
if( typeof value === 'boolean' ) { | |
value = value.toString(); | |
} | |
if( value === 'false' ) { | |
this.removeAttribute( 'hidden' ); | |
} else { | |
this.setAttribute( 'hidden', '' ); | |
} | |
} else { | |
this.removeAttribute( 'hidden' ); | |
} | |
} | |
get size() { | |
if( this.hasAttribute( 'size' ) ) { | |
return this.getAttribute( 'size' ); | |
} | |
return null; | |
} | |
set size( value ) { | |
if( value !== null ) { | |
this.setAttribute( 'size', value ); | |
} else { | |
this.removeAttribute( 'size' ); | |
} | |
} | |
get text() { | |
if( this.hasAttribute( 'text' ) ) { | |
return this.getAttribute( 'text' ); | |
} | |
return null; | |
} | |
set text( value ) { | |
if( value !== null ) { | |
this.setAttribute( 'text', value ); | |
} else { | |
this.removeAttribute( 'text' ); | |
} | |
} | |
get truncate() { | |
return this.hasAttribute( 'truncate' ); | |
} | |
set truncate( value ) { | |
if( value !== null ) { | |
if( typeof value === 'boolean' ) { | |
value = value.toString(); | |
} | |
if( value === 'false' ) { | |
this.removeAttribute( 'truncate' ); | |
} else { | |
this.setAttribute( 'truncate', '' ); | |
} | |
} else { | |
this.removeAttribute( 'truncate' ); | |
} | |
} | |
get weight() { | |
if( this.hasAttribute( 'weight' ) ) { | |
return this.getAttribute( 'weight' ); | |
} | |
return null; | |
} | |
set weight( value ) { | |
if( value !== null ) { | |
this.setAttribute( 'weight', value ); | |
} else { | |
this.removeAttribute( 'weight' ); | |
} | |
} | |
} | |
window.customElements.define( 'hoyt-label', HoytLabel ); |
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
export default class HoytTemplate extends HTMLElement { | |
constructor() { | |
super(); | |
const template = document.createElement( 'template' ); | |
template.innerHTML = /* template */ ` | |
<style> | |
:host { | |
box-sizing: border-box; | |
display: inline-block; | |
position: relative; | |
} | |
:host( [a-boolean] ) { | |
display: none; | |
} | |
hoyt-component::part( inner ) { | |
--component-stylet: var( --my-component-style, #123456 ); | |
} | |
</style> | |
<hoyt-component exportparts="inner: outer" part="also-outer"></hoyt-component> | |
`; | |
// Properties | |
this._anArray = []; | |
this._aDate = null; | |
this._anObject = null; | |
// Consider for mobile | |
this._touch = ( 'ontouchstart' in document.documentElement ) ? 'touchstart' : 'click'; | |
// Events | |
this.onComponentClick = this.onComponentClick.bind( this ); | |
// Root | |
this.attachShadow( {mode: 'open'} ); | |
this.shadowRoot.appendChild( template.content.cloneNode( true ) ); | |
// Elements | |
this.$component = this.shadowRoot.querySelector( 'element-name' ); | |
} | |
onComponentClick( evt ) { | |
this.dispatchEvent( new CustomEvent( 'krh-event', { | |
bubbles: true, | |
cancelable: false, | |
composed: true, | |
detail: { | |
'abc': 123 | |
} | |
} ) ); | |
} | |
// When attributes change | |
_render() { | |
this.$component.aBoolean = this.aInteger === null ? false : true; | |
} | |
// Promote properties | |
// Values may be set before module load | |
_upgrade( property ) { | |
if( this.hasOwnProperty( property ) ) { | |
const value = this[property]; | |
delete this[property]; | |
this[property] = value; | |
} | |
} | |
// Setup | |
connectedCallback() { | |
this.$component.addEventListener( 'click', this.onComponentClick ); | |
this._upgrade( 'anArray' ); | |
this._upgrade( 'aBoolean' ); | |
this._upgrade( 'aDate' ); | |
this._upgrade( 'aFloat' ); | |
this._upgrade( 'anInteger' ); | |
this._upgrade( 'anObject' ); | |
this._upgrade( 'aString' ); | |
this._render(); | |
} | |
// Set down | |
diconnectedCallback() { | |
this.$component.removeEventListener( 'click', this.onComponentClick ); | |
} | |
// Watched attributes | |
static get observedAttributes() { | |
return [ | |
'a-boolean', | |
'a-float', | |
'an-integer', | |
'a-string' | |
]; | |
} | |
// Observed attribute has changed | |
// Update render | |
attributeChangedCallback( name, old, value ) { | |
this._render(); | |
} | |
// Properties | |
// Not reflected | |
// Array, Date, Object, null | |
get anArray() { | |
return this._anArray.length === 0 ? null : this._anArray; | |
} | |
set anArray( value ) { | |
this._anArray = value === null ? [] : [... value]; | |
} | |
get aDate() { | |
return this._aDate; | |
} | |
set aDate( value ) { | |
this._aDate = value === null ? null : new Date( value.getTime() ); | |
} | |
get anObject() { | |
return this._anObject; | |
} | |
set anObject( value ) { | |
this._anObject = Object.assign( value, {} ); | |
} | |
// Attributes | |
// Reflected | |
// Boolean, Float, Integer, String, null | |
get aBoolean() { | |
return this.hasAttribute( 'a-boolean' ); | |
} | |
set aBoolean( value ) { | |
if( value !== null ) { | |
if( typeof value === 'boolean' ) { | |
value = value.toString(); | |
} | |
if( value === 'false' ) { | |
this.removeAttribute( 'a-boolean' ); | |
} else { | |
this.setAttribute( 'a-boolean', '' ); | |
} | |
} else { | |
this.removeAttribute( 'a-boolean' ); | |
} | |
} | |
get aFloat() { | |
if( this.hasAttribute( 'a-float' ) ) { | |
return parseFloat( this.getAttribute( 'a-float' ) ); | |
} | |
return null; | |
} | |
set aFloat( value ) { | |
if( value !== null ) { | |
this.setAttribute( 'a-float', value ); | |
} else { | |
this.removeAttribute( 'a-float' ); | |
} | |
} | |
get anInteger() { | |
if( this.hasAttribute( 'an-integer' ) ) { | |
return parseInt( this.getAttribute( 'an-integer' ) ); | |
} | |
return null; | |
} | |
set anInteger( value ) { | |
if( value !== null ) { | |
this.setAttribute( 'an-integer', value ); | |
} else { | |
this.removeAttribute( 'an-integer' ); | |
} | |
} | |
get aString() { | |
if( this.hasAttribute( 'a-string' ) ) { | |
return this.getAttribute( 'a-string' ); | |
} | |
return null; | |
} | |
set aString( value ) { | |
if( value !== null ) { | |
this.setAttribute( 'a-string', value ); | |
} else { | |
this.removeAttribute( 'a-string' ); | |
} | |
} | |
} | |
window.customElements.define( 'krh-template', HoytTemplate ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment