Merge pull request #391 from sveltejs/gh-371

<:Window> tags
pull/407/head
Rich Harris 8 years ago committed by GitHub
commit 9988749b74

@ -14,6 +14,11 @@ class DomGenerator extends Generator {
this.renderers = [];
this.uses = new Set();
// initial values for e.g. window.innerWidth, if there's a <:Window> meta tag
this.builders = {
metaBindings: new CodeBuilder()
};
this.importedComponents = new Map();
}
@ -329,6 +334,10 @@ export default function dom ( parsed, source, options, names ) {
`this._state = ${templateProperties.data ? `Object.assign( ${generator.alias( 'template' )}.data(), options.data )` : `options.data || {}`};`
);
if ( !generator.builders.metaBindings.isEmpty() ) {
constructorBlock.addBlock( generator.builders.metaBindings );
}
if ( templateProperties.computed ) {
constructorBlock.addLine(
`${generator.alias( 'applyComputations' )}( this._state, this._state, {}, true );`

@ -2,10 +2,20 @@ import CodeBuilder from '../../../utils/CodeBuilder.js';
import deindent from '../../../utils/deindent.js';
import addElementAttributes from './attributes/addElementAttributes.js';
import Component from './Component.js';
import Window from './meta/Window.js';
const meta = {
':Window': Window
};
export default {
enter ( generator, node ) {
if ( node.name in meta ) {
return meta[ node.name ].enter( generator, node );
}
const isComponent = generator.components.has( node.name ) || node.name === ':Self';
if ( isComponent ) {
return Component.enter( generator, node );
}
@ -100,7 +110,13 @@ export default {
},
leave ( generator, node ) {
if ( node.name in meta ) {
if ( meta[ node.name ].leave ) meta[ node.name ].leave( generator, node );
return;
}
const isComponent = generator.components.has( node.name );
if ( isComponent ) {
return Component.leave( generator, node );
}

@ -0,0 +1,84 @@
import flattenReference from '../../../../utils/flattenReference.js';
import deindent from '../../../../utils/deindent.js';
const associatedEvents = {
innerWidth: 'resize',
innerHeight: 'resize',
outerWidth: 'resize',
outerHeight: 'resize',
scrollX: 'scroll',
scrollY: 'scroll'
};
export default {
enter ( generator, node ) {
const events = {};
node.attributes.forEach( attribute => {
if ( attribute.type === 'EventHandler' ) {
// TODO verify that it's a valid callee (i.e. built-in or declared method)
generator.addSourcemapLocations( attribute.expression );
const flattened = flattenReference( attribute.expression.callee );
if ( flattened.name !== 'event' && flattened.name !== 'this' ) {
// allow event.stopPropagation(), this.select() etc
generator.code.prependRight( attribute.expression.start, 'component.' );
}
const handlerName = generator.current.getUniqueName( `onwindow${attribute.name}` );
generator.current.builders.init.addBlock( deindent`
var ${handlerName} = function ( event ) {
[${attribute.expression.start}-${attribute.expression.end}];
};
window.addEventListener( '${attribute.name}', ${handlerName} );
` );
generator.current.builders.teardown.addBlock( deindent`
window.removeEventListener( '${attribute.name}', ${handlerName} );
` );
}
if ( attribute.type === 'Binding' ) {
const associatedEvent = associatedEvents[ attribute.name ];
if ( !associatedEvent ) {
throw new Error( `Cannot bind to ${attribute.name} on <:Window>` );
}
if ( attribute.value.type !== 'Identifier' ) {
const { parts, keypath } = flattenReference( attribute.value );
throw new Error( `Bindings on <:Window/> must be to top-level properties, e.g. '${parts.pop()}' rather than '${keypath}'` );
}
if ( !events[ associatedEvent ] ) events[ associatedEvent ] = [];
events[ associatedEvent ].push( `${attribute.value.name}: this.${attribute.name}` );
// add initial value
generator.builders.metaBindings.addLine(
`this._state.${attribute.value.name} = window.${attribute.name};`
);
}
});
Object.keys( events ).forEach( event => {
const handlerName = generator.current.getUniqueName( `onwindow${event}` );
const props = events[ event ].join( ',\n' );
generator.current.builders.init.addBlock( deindent`
var ${handlerName} = function ( event ) {
component.set({
${props}
});
};
window.addEventListener( '${event}', ${handlerName} );
` );
generator.current.builders.teardown.addBlock( deindent`
window.removeEventListener( '${event}', ${handlerName} );
` );
});
}
};

@ -1,8 +1,17 @@
import Component from './Component.js';
import isVoidElementName from '../../../utils/isVoidElementName.js';
import Window from './meta/Window.js';
const meta = {
':Window': Window
};
export default {
enter ( generator, node ) {
if ( node.name in meta ) {
return meta[ node.name ].enter( generator, node );
}
if ( generator.components.has( node.name ) || node.name === ':Self' ) {
Component.enter( generator, node );
return;
@ -39,6 +48,11 @@ export default {
},
leave ( generator, node ) {
if ( node.name in meta ) {
if ( meta[ node.name ].leave ) meta[ node.name ].leave( generator, node );
return;
}
if ( generator.components.has( node.name ) || node.name === ':Self' ) {
Component.leave( generator, node );
return;

@ -0,0 +1,9 @@
export default {
enter () {
// noop
},
leave () {
// noop
}
};

@ -32,6 +32,7 @@ export default function parse ( template, options = {} ) {
index: 0,
template,
stack: [],
metaTags: {},
current () {
return this.stack[ this.stack.length - 1 ];

@ -11,6 +11,10 @@ const invalidUnquotedAttributeCharacters = /[\s"'=<>\/`]/;
const SELF = ':Self';
const metaTags = {
':Window': true
};
const specials = new Map( [
[ 'script', {
read: readScript,
@ -80,6 +84,22 @@ export default function tag ( parser ) {
const name = readTagName( parser );
if ( name in metaTags ) {
if ( name in parser.metaTags ) {
if ( isClosingTag && parser.current().children.length ) {
parser.error( `<${name}> cannot have children`, parser.current().children[0].start );
}
parser.error( `A component can only have one <${name}> tag`, start );
}
parser.metaTags[ name ] = true;
if ( parser.stack.length > 1 ) {
parser.error( `<${name}> tags cannot be inside elements or blocks`, start );
}
}
parser.allowWhitespace();
if ( isClosingTag ) {
@ -192,6 +212,9 @@ function readTagName ( parser ) {
}
const name = parser.readUntil( /(\s|\/|>)/ );
if ( name in metaTags ) return name;
if ( !validTagName.test( name ) ) {
parser.error( `Expected valid tag name`, start );
}

@ -93,6 +93,8 @@ describe( 'generate', () => {
return env()
.then( window => {
global.window = window;
// Put the constructor on window for testing
window.SvelteComponent = SvelteComponent;

@ -0,0 +1,29 @@
export default {
html: `<div>1024x768</div>`,
skip: /^v4/.test( process.version ), // node 4 apparently does some dumb stuff
'skip-ssr': true, // there's some kind of weird bug with this test... it compiles with the wrong require.extensions hook for some bizarre reason
test ( assert, component, target, window ) {
const event = new window.Event( 'resize' );
// JSDOM executes window event listeners with `global` rather than
// `window` (bug?) so we need to do this
Object.defineProperties( global, {
innerWidth: {
value: 567,
configurable: true
},
innerHeight: {
value: 456,
configurable: true
}
});
window.dispatchEvent( event );
assert.htmlEqual( target.innerHTML, `
<div>567x456</div>
`);
}
};

@ -0,0 +1,3 @@
<:Window bind:innerWidth='width' bind:innerHeight='height'/>
<div>{{width}}x{{height}}</div>

@ -0,0 +1,29 @@
export default {
html: `<div>undefinedxundefined</div>`,
skip: /^v4/.test( process.version ), // node 4 apparently does some dumb stuff
'skip-ssr': true, // there's some kind of weird bug with this test... it compiles with the wrong require.extensions hook for some bizarre reason
test ( assert, component, target, window ) {
const event = new window.Event( 'resize' );
// JSDOM executes window event listeners with `global` rather than
// `window` (bug?) so we need to do this
Object.defineProperties( global, {
innerWidth: {
value: 567,
configurable: true
},
innerHeight: {
value: 456,
configurable: true
}
});
window.dispatchEvent( event );
assert.htmlEqual( target.innerHTML, `
<div>567x456</div>
`);
}
};

@ -0,0 +1,3 @@
<:Window on:resize='set({ width: this.innerWidth, height: this.innerHeight })'/>
<div>{{width}}x{{height}}</div>

@ -0,0 +1,8 @@
{
"message": "<:Window> cannot have children",
"loc": {
"line": 1,
"column": 9
},
"pos": 9
}

@ -0,0 +1,8 @@
{
"message": "A component can only have one <:Window> tag",
"loc": {
"line": 2,
"column": 0
},
"pos": 11
}

@ -0,0 +1,8 @@
{
"message": "<:Window> tags cannot be inside elements or blocks",
"loc": {
"line": 2,
"column": 1
},
"pos": 13
}

@ -0,0 +1,8 @@
{
"message": "<:Window> tags cannot be inside elements or blocks",
"loc": {
"line": 2,
"column": 1
},
"pos": 7
}
Loading…
Cancel
Save