@@ -111,18 +111,17 @@ export default function dom(
111111 ` ) ;
112112 }
113113
114- if ( generator . stylesheet . hasStyles && options . css !== false ) {
115- const { css, cssMap } = generator . stylesheet . render ( options . filename ) ;
116-
117- const textContent = stringify ( options . dev ?
118- `${ css } \n/*# sourceMappingURL=${ cssMap . toUrl ( ) } */` :
119- css , { onlyEscapeAtSymbol : true } ) ;
114+ const { css, cssMap } = generator . stylesheet . render ( options . filename , ! generator . customElement ) ;
115+ const styles = generator . stylesheet . hasStyles && stringify ( options . dev ?
116+ `${ css } \n/*# sourceMappingURL=${ cssMap . toUrl ( ) } */` :
117+ css , { onlyEscapeAtSymbol : true } ) ;
120118
119+ if ( styles && generator . options . css !== false && ! generator . customElement ) {
121120 builder . addBlock ( deindent `
122121 function @add_css () {
123122 var style = @createElement( 'style' );
124123 style.id = '${ generator . stylesheet . id } -style';
125- style.textContent = ${ textContent } ;
124+ style.textContent = ${ styles } ;
126125 @appendNode( style, document.head );
127126 }
128127 ` ) ;
@@ -143,95 +142,156 @@ export default function dom(
143142 ? `@proto `
144143 : deindent `
145144 {
146- ${ [ 'destroy' , 'get' , 'fire' , 'observe' , 'on' , 'set' , '_set' , 'teardown ' ]
145+ ${ [ 'destroy' , 'get' , 'fire' , 'observe' , 'on' , 'set' , 'teardown' , ' _set', '_mount' , '_unmount ']
147146 . map ( n => `${ n } : @${ n === 'teardown' ? 'destroy' : n } ` )
148147 . join ( ',\n' ) }
149148 }` ;
150149
151- // TODO deprecate component.teardown()
152- builder . addBlock ( deindent `
153- function ${ name } ( options ) {
154- ${ options . dev &&
155- `if ( !options || (!options.target && !options._root) ) throw new Error( "'target' is a required option" );` }
156- this.options = options;
157- ${ generator . usesRefs && `this.refs = {};` }
158- this._state = ${ templateProperties . data
159- ? `@assign( @template.data(), options.data )`
160- : `options.data || {}` } ;
161- ${ generator . metaBindings }
162- ${ computations . length && `this._recompute( {}, this._state, {}, true );` }
163- ${ options . dev &&
164- Array . from ( generator . expectedProperties ) . map (
165- prop =>
166- `if ( !( '${ prop } ' in this._state ) ) console.warn( "Component was created without expected data property '${ prop } '" );`
167- ) }
168- ${ generator . bindingGroups . length &&
169- `this._bindingGroups = [ ${ Array ( generator . bindingGroups . length )
170- . fill ( '[]' )
171- . join ( ', ' ) } ];`}
172-
173- this._observers = {
174- pre: Object.create( null ),
175- post: Object.create( null )
176- };
150+ const constructorBody = deindent `
151+ ${ options . dev && ! generator . customElement &&
152+ `if ( !options || (!options.target && !options._root) ) throw new Error( "'target' is a required option" );` }
153+ this.options = options;
154+ ${ generator . usesRefs && `this.refs = {};` }
155+ this._state = ${ templateProperties . data
156+ ? `@assign( @template.data(), options.data )`
157+ : `options.data || {}` } ;
158+ ${ generator . metaBindings }
159+ ${ computations . length && `this._recompute( {}, this._state, {}, true );` }
160+ ${ options . dev &&
161+ Array . from ( generator . expectedProperties ) . map (
162+ prop =>
163+ `if ( !( '${ prop } ' in this._state ) ) console.warn( "Component was created without expected data property '${ prop } '" );`
164+ ) }
165+ ${ generator . bindingGroups . length &&
166+ `this._bindingGroups = [ ${ Array ( generator . bindingGroups . length )
167+ . fill ( '[]' )
168+ . join ( ', ' ) } ];`}
169+
170+ this._observers = {
171+ pre: Object.create( null ),
172+ post: Object.create( null )
173+ };
174+
175+ this._handlers = Object.create( null );
176+ ${ templateProperties . ondestroy && `this._handlers.destroy = [@template.ondestroy]` }
177+
178+ this._root = options._root || this;
179+ this._yield = options._yield;
180+ this._bind = options._bind;
181+ ${ generator . slots . size && `this._slotted = options.slots || {};` }
182+
183+ ${ generator . customElement ?
184+ deindent `
185+ this.attachShadow({ mode: 'open' });
186+ ${ css && `this.shadowRoot.innerHTML = \`<style>${ options . dev ? `${ css } \n/*# sourceMappingURL=${ cssMap . toUrl ( ) } */` : css } </style>\`;` }
187+ ` :
188+ ( generator . stylesheet . hasStyles && options . css !== false &&
189+ `if ( !document.getElementById( '${ generator . stylesheet . id } -style' ) ) @add_css();` )
190+ }
177191
178- this._handlers = Object.create( null );
179- ${ templateProperties . ondestroy && `this._handlers.destroy = [@template.ondestroy]` }
192+ ${ templateProperties . oncreate && `var oncreate = @template.oncreate.bind( this );` }
180193
181- this._root = options._root || this;
182- this._yield = options._yield;
183- this._bind = options._bind;
184- ${ generator . slots . size && `this._slotted = options.slots || {};` }
194+ ${ ( templateProperties . oncreate || generator . hasComponents || generator . hasComplexBindings || generator . hasIntroTransitions ) && deindent `
195+ if ( !options._root ) {
196+ this._oncreate = [${ templateProperties . oncreate && `oncreate` } ];
197+ ${ ( generator . hasComponents || generator . hasComplexBindings ) && `this._beforecreate = [];` }
198+ ${ ( generator . hasComponents || generator . hasIntroTransitions ) && `this._aftercreate = [];` }
199+ } ${ templateProperties . oncreate && deindent `
200+ else {
201+ this._root._oncreate.push(oncreate);
202+ }
203+ ` }
204+ ` }
185205
186- ${ generator . stylesheet . hasStyles &&
187- options . css !== false &&
188- `if ( !document.getElementById( '${ generator . stylesheet . id } -style' ) ) @add_css();` }
206+ ${ generator . slots . size && `this.slots = {};` }
189207
190- ${ templateProperties . oncreate && `var oncreate = @template.oncreate.bind ( this );` }
208+ this._fragment = @create_main_fragment ( this._state, this );
191209
192- ${ ( templateProperties . oncreate || generator . hasComponents || generator . hasComplexBindings || generator . hasIntroTransitions ) && deindent `
193- if ( !options._root ) {
194- this._oncreate = [${ templateProperties . oncreate && `oncreate` } ];
195- ${ ( generator . hasComponents || generator . hasComplexBindings ) && `this._beforecreate = [];` }
196- ${ ( generator . hasComponents || generator . hasIntroTransitions ) && `this._aftercreate = [];` }
197- } ${ templateProperties . oncreate && deindent `
198- else {
199- this._root._oncreate.push(oncreate);
200- }
210+ if ( options.target ) {
211+ ${ generator . hydratable
212+ ? deindent `
213+ var nodes = @children( options.target );
214+ options.hydrate ? this._fragment.claim( nodes ) : this._fragment.create();
215+ nodes.forEach( @detachNode );
216+ ` :
217+ deindent `
218+ ${ options . dev && `if ( options.hydrate ) throw new Error( 'options.hydrate only works if the component was compiled with the \`hydratable: true\` option' );` }
219+ this._fragment.create();
201220 ` }
221+ ${ generator . customElement ?
222+ `this._mount( options.target, options.anchor || null );` :
223+ `this._fragment.${ block . hasIntroMethod ? 'intro' : 'mount' } ( options.target, options.anchor || null );` }
224+
225+ ${ ( generator . hasComponents || generator . hasComplexBindings || templateProperties . oncreate || generator . hasIntroTransitions ) && deindent `
226+ ${ generator . hasComponents && `this._lock = true;` }
227+ ${ ( generator . hasComponents || generator . hasComplexBindings ) && `@callAll(this._beforecreate);` }
228+ ${ ( generator . hasComponents || templateProperties . oncreate ) && `@callAll(this._oncreate);` }
229+ ${ ( generator . hasComponents || generator . hasIntroTransitions ) && `@callAll(this._aftercreate);` }
230+ ${ generator . hasComponents && `this._lock = false;` }
202231 ` }
232+ }
233+ ` ;
234+
235+ if ( generator . customElement ) {
236+ const props = generator . props || Array . from ( generator . expectedProperties ) ;
237+
238+ builder . addBlock ( deindent `
239+ class ${ name } extends HTMLElement {
240+ constructor(options = {}) {
241+ super();
242+ ${ constructorBody }
243+ }
244+
245+ static get observedAttributes() {
246+ return ${ JSON . stringify ( props ) } ;
247+ }
203248
204- ${ generator . slots . size && `this.slots = {};` }
205-
206- this._fragment = @create_main_fragment( this._state, this );
207-
208- if ( options.target ) {
209- ${ generator . hydratable
210- ? deindent `
211- var nodes = @children( options.target );
212- options.hydrate ? this._fragment.claim( nodes ) : this._fragment.create();
213- nodes.forEach( @detachNode );
214- ` :
215- deindent `
216- ${ options . dev && `if ( options.hydrate ) throw new Error( 'options.hydrate only works if the component was compiled with the \`hydratable: true\` option' );` }
217- this._fragment.create();
218- ` }
219- this._fragment.${ block . hasIntroMethod ? 'intro' : 'mount' } ( options.target, options.anchor || null );
249+ ${ props . map ( prop => deindent `
250+ get ${ prop } () {
251+ return this.get('${ prop } ');
252+ }
253+
254+ set ${ prop } (value) {
255+ this.set({ ${ prop } : value });
256+ }
257+ ` ) . join ( '\n\n' ) }
258+
259+ ${ generator . slots . size && deindent `
260+ connectedCallback() {
261+ Object.keys(this._slotted).forEach(key => {
262+ this.appendChild(this._slotted[key]);
263+ });
264+ }` }
265+
266+ attributeChangedCallback ( attr, oldValue, newValue ) {
267+ this.set({ [attr]: newValue });
268+ }
220269 }
221270
222- ${ ( generator . hasComponents || generator . hasComplexBindings || templateProperties . oncreate || generator . hasIntroTransitions ) && deindent `
223- if ( !options._root ) {
224- ${ generator . hasComponents && `this._lock = true;` }
225- ${ ( generator . hasComponents || generator . hasComplexBindings ) && `@callAll(this._beforecreate);` }
226- ${ ( generator . hasComponents || templateProperties . oncreate ) && `@callAll(this._oncreate);` }
227- ${ ( generator . hasComponents || generator . hasIntroTransitions ) && `@callAll(this._aftercreate);` }
228- ${ generator . hasComponents && `this._lock = false;` }
271+ customElements.define('${ generator . tag } ', ${ name } );
272+ @assign( ${ prototypeBase } , ${ proto } , {
273+ _mount(target, anchor) {
274+ this._fragment.${ block . hasIntroMethod ? 'intro' : 'mount' } (this.shadowRoot, null);
275+ target.insertBefore(this, anchor);
276+ },
277+
278+ _unmount() {
279+ this.parentNode.removeChild(this);
229280 }
230- ` }
231- }
281+ });
282+ ` ) ;
283+ } else {
284+ builder . addBlock ( deindent `
285+ function ${ name } ( options ) {
286+ ${ constructorBody }
287+ }
232288
233- @assign( ${ prototypeBase } , ${ proto } );
289+ @assign( ${ prototypeBase } , ${ proto } );
290+ ` ) ;
291+ }
234292
293+ // TODO deprecate component.teardown()
294+ builder . addBlock ( deindent `
235295 ${ options . dev && deindent `
236296 ${ name } .prototype._checkReadOnly = function _checkReadOnly ( newState ) {
237297 ${ Array . from ( generator . readonly ) . map (
0 commit comments