11import { KEYS } from '../util' ;
22
3+ // Keep track of prepared ShadyDOM templates
4+ const SHADY_TEMPLATES = { } ;
5+
36/**
47 * @external HTMLElement
58 * @see <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement" target="_blank">MDN - HTMLElement</a>
@@ -99,7 +102,10 @@ export class HXElement extends HTMLElement {
99102 */
100103 $onAttributeChange ( attr , oldVal , newVal ) { } // eslint-disable-line no-unused-vars
101104
102- // Define an element using the customElements registry.
105+ /**
106+ * Register class with the customElements registry.
107+ * Note: the custom element is only registered if the "is" class property is defined.
108+ */
103109 static $define ( ) {
104110 if ( this . is ) {
105111 customElements . define ( this . is , this ) ;
@@ -109,21 +115,7 @@ export class HXElement extends HTMLElement {
109115 // Called when an instance is created
110116 constructor ( ) {
111117 super ( ) ;
112-
113- // Don't attach shadow DOM unless "template" class property is defined.
114- if ( this . constructor . template ) {
115- let _template = document . createElement ( 'template' ) ;
116- _template . innerHTML = this . constructor . template ;
117-
118- this . attachShadow ( { mode : 'open' } ) ;
119-
120- if ( window . ShadyCSS ) {
121- ShadyCSS . prepareTemplate ( _template , this . constructor . is ) ;
122- ShadyCSS . styleElement ( this ) ;
123- }
124-
125- this . shadowRoot . appendChild ( _template . content . cloneNode ( true ) ) ;
126- }
118+ this . _$setupShadowDOM ( ) ;
127119
128120 this . $onAttributeChange = this . $onAttributeChange . bind ( this ) ;
129121 this . $onConnect = this . $onConnect . bind ( this ) ;
@@ -151,29 +143,29 @@ export class HXElement extends HTMLElement {
151143 * @type {Array<String> }
152144 */
153145 static get observedAttributes ( ) {
154- return [ 'disabled' ] . concat ( this . $observedAttributes ) ;
146+ let common = [ 'disabled' ] ;
147+ let extra = this . $observedAttributes ;
148+ return [ ...common , ...extra ] ;
155149 }
156150
157151 // Called when an attribute UPDATES (not just when it changes).
158152 attributeChangedCallback ( attr , oldVal , newVal ) {
159- switch ( attr ) {
160- case 'disabled' :
161- if ( newVal !== null ) {
162- this . removeAttribute ( 'tabindex' ) ;
163- this . setAttribute ( 'aria-disabled' , true ) ;
164- this . blur ( ) ;
165- } else {
166- this . setAttribute ( 'tabindex' , this . _$tabIndex ) ;
167- this . removeAttribute ( 'aria-disabled' ) ;
168- }
169- break ;
153+ if ( attr === 'disabled' ) {
154+ if ( newVal !== null ) {
155+ this . removeAttribute ( 'tabindex' ) ;
156+ this . setAttribute ( 'aria-disabled' , true ) ;
157+ this . blur ( ) ;
158+ } else {
159+ this . setAttribute ( 'tabindex' , this . _$tabIndex ) ;
160+ this . removeAttribute ( 'aria-disabled' ) ;
161+ }
162+ }
170163
171- default :
172- if ( newVal !== oldVal ) {
173- this . $onAttributeChange ( attr , oldVal , newVal ) ;
174- }
175- break ;
176- } //switch
164+ // Always call $onAttributeChange, so that we can run additional
165+ // logic against common attributes in subclasses, too.
166+ if ( newVal !== oldVal ) {
167+ this . $onAttributeChange ( attr , oldVal , newVal ) ;
168+ }
177169 } //attributeChangedCallback
178170
179171 /**
@@ -320,4 +312,50 @@ export class HXElement extends HTMLElement {
320312 this . removeAttribute ( 'disabled' ) ;
321313 }
322314 }
315+
316+ /**
317+ * @private
318+ * @description
319+ * If the browser doesn't have native ShadowDOM, this method
320+ * will ensure that the ShadyDOM template is prepared no more
321+ * than once, and applies ShadyDOM styling to the element.
322+ *
323+ * @param {HTMLTemplate } template
324+ */
325+ _$setupShadyDOM ( template ) {
326+ let _elementName = this . constructor . is ;
327+
328+ if ( window . ShadyCSS ) {
329+ // check to see if the ShadyDOM template has already been prepared
330+ if ( ! SHADY_TEMPLATES [ _elementName ] ) {
331+ // modifies 'template' variable in-place
332+ ShadyCSS . prepareTemplate ( template , _elementName ) ;
333+ // memoize prepared template, so it isn't prepared more than once
334+ SHADY_TEMPLATES [ _elementName ] = template ;
335+ }
336+ // Apply ShadyDOM styling (rewrites Light DOM)
337+ ShadyCSS . styleElement ( this ) ;
338+ }
339+ } //_$setupShadyDOM
340+
341+ /**
342+ * @private
343+ * @description
344+ * If a ShadowDOM needs to be setup, this method handles:
345+ *
346+ * 1. creating the <template> element
347+ * 2. attaching a shadow root
348+ * 3. applying ShadyDOM styling (if needed)
349+ * 4. stamping the template
350+ */
351+ _$setupShadowDOM ( ) {
352+ // Don't do anything unless the "template" class property is defined.
353+ if ( this . constructor . template ) {
354+ let _template = document . createElement ( 'template' ) ;
355+ _template . innerHTML = this . constructor . template ;
356+ this . attachShadow ( { mode : 'open' } ) ;
357+ this . _$setupShadyDOM ( _template ) ;
358+ this . shadowRoot . appendChild ( _template . content . cloneNode ( true ) ) ;
359+ }
360+ } //_$setupShadowDOM()
323361}
0 commit comments