allow actions on <svelte:window>

pull/1864/head
Rich Harris 7 years ago
parent a7fe66960f
commit 8cc12b73e1

@ -4,6 +4,7 @@ import EventHandler from './EventHandler';
import flattenReference from '../../utils/flattenReference';
import fuzzymatch from '../../utils/fuzzymatch';
import list from '../../utils/list';
import Action from './Action';
const validBindings = [
'innerWidth',
@ -17,15 +18,13 @@ const validBindings = [
export default class Window extends Node {
type: 'Window';
handlers: EventHandler[];
bindings: Binding[];
handlers: EventHandler[] = [];
bindings: Binding[] = [];
actions: Action[] = [];
constructor(component, parent, scope, info) {
super(component, parent, scope, info);
this.handlers = [];
this.bindings = [];
info.attributes.forEach(node => {
if (node.type === 'EventHandler') {
this.handlers.push(new EventHandler(component, this, scope, node));
@ -35,6 +34,7 @@ export default class Window extends Node {
if (node.expression.type !== 'Identifier') {
const { parts } = flattenReference(node.expression);
// TODO is this constraint necessary?
component.error(node.expression, {
code: `invalid-binding`,
message: `Bindings on <svelte:window> must be to top-level properties, e.g. '${parts[parts.length - 1]}' rather than '${parts.join('.')}'`
@ -42,11 +42,11 @@ export default class Window extends Node {
}
if (!~validBindings.indexOf(node.name)) {
const match = node.name === 'width'
? 'innerWidth'
: node.name === 'height'
? 'innerHeight'
: fuzzymatch(node.name, validBindings);
const match = (
node.name === 'width' ? 'innerWidth' :
node.name === 'height' ? 'innerHeight' :
fuzzymatch(node.name, validBindings)
);
const message = `'${node.name}' is not a valid binding on <svelte:window>`;
@ -66,6 +66,10 @@ export default class Window extends Node {
this.bindings.push(new Binding(component, this, scope, node));
}
else if (node.type === 'Action') {
this.actions.push(new Action(component, this, scope, node));
}
else {
// TODO there shouldn't be anything else here...
}

@ -17,6 +17,8 @@ import { dimensions } from '../../../../utils/patterns';
import Binding from './Binding';
import InlineComponentWrapper from '../InlineComponent';
import addToSet from '../../../../utils/addToSet';
import addEventHandlers from '../shared/addEventHandlers';
import addActions from '../shared/addActions';
const events = [
{
@ -629,43 +631,7 @@ export default class ElementWrapper extends Wrapper {
}
addEventHandlers(block: Block) {
const { renderer } = this;
const { component } = renderer;
this.node.handlers.forEach(handler => {
const modifiers = [];
if (handler.modifiers.has('preventDefault')) modifiers.push('event.preventDefault();');
if (handler.modifiers.has('stopPropagation')) modifiers.push('event.stopPropagation();');
const opts = ['passive', 'once', 'capture'].filter(mod => handler.modifiers.has(mod));
if (opts.length) {
const optString = (opts.length === 1 && opts[0] === 'capture')
? 'true'
: `{ ${opts.map(opt => `${opt}: true`).join(', ')} }`;
block.builders.hydrate.addLine(
`@addListener(${this.var}, "${handler.name}", ${handler.snippet}, ${optString});`
);
block.builders.destroy.addLine(
`@removeListener(${this.var}, "${handler.name}", ${handler.snippet}, ${optString});`
);
} else {
block.builders.hydrate.addLine(
`@addListener(${this.var}, "${handler.name}", ${handler.snippet});`
);
block.builders.destroy.addLine(
`@removeListener(${this.var}, "${handler.name}", ${handler.snippet});`
);
}
if (handler.expression) {
handler.expression.declarations.forEach(declaration => {
block.builders.init.addBlock(declaration);
});
}
});
addEventHandlers(block, this.var, this.node.handlers);
}
addRef(block: Block) {
@ -795,44 +761,7 @@ export default class ElementWrapper extends Wrapper {
}
addActions(block: Block) {
this.node.actions.forEach(action => {
const { expression } = action;
let snippet, dependencies;
if (expression) {
snippet = expression.snippet;
dependencies = expression.dependencies;
expression.declarations.forEach(declaration => {
block.builders.init.addBlock(declaration);
});
}
const name = block.getUniqueName(
`${action.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_action`
);
block.addVariable(name);
const fn = `ctx.${action.name}`;
block.builders.mount.addLine(
`${name} = ${fn}.call(null, ${this.var}${snippet ? `, ${snippet}` : ''}) || {};`
);
if (dependencies && dependencies.size > 0) {
let conditional = `typeof ${name}.update === 'function' && `;
const deps = [...dependencies].map(dependency => `changed.${dependency}`).join(' || ');
conditional += dependencies.size > 1 ? `(${deps})` : deps;
block.builders.update.addConditional(
conditional,
`${name}.update.call(null, ${snippet});`
);
}
block.builders.destroy.addLine(
`if (${name} && typeof ${name}.destroy === 'function') ${name}.destroy();`
);
});
addActions(block, this.var, this.node.actions);
}
addClasses(block: Block) {

@ -3,6 +3,9 @@ import Block from '../Block';
import Node from '../../nodes/shared/Node';
import Wrapper from './shared/Wrapper';
import deindent from '../../../utils/deindent';
import addEventHandlers from './shared/addEventHandlers';
import Window from '../../nodes/Window';
import addActions from './shared/addActions';
const associatedEvents = {
innerWidth: 'resize',
@ -28,6 +31,8 @@ const readonly = new Set([
]);
export default class WindowWrapper extends Wrapper {
node: Window;
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: Node) {
super(renderer, block, parent, node);
}
@ -39,17 +44,8 @@ export default class WindowWrapper extends Wrapper {
const events = {};
const bindings: Record<string, string> = {};
this.node.handlers.forEach(handler => {
const { snippet } = handler.expression;
block.builders.init.addLine(
`window.addEventListener("${handler.name}", ${snippet});`
);
block.builders.destroy.addLine(
`window.removeEventListener("${handler.name}", ${snippet});`
);
});
addActions(block, 'window', this.node.actions);
addEventHandlers(block, 'window', this.node.handlers);
this.node.bindings.forEach(binding => {
// in dev mode, throw if read-only values are written to

@ -0,0 +1,48 @@
import Renderer from '../../Renderer';
import Block from '../../Block';
import Action from '../../../nodes/Action';
export default function addActions(
block: Block,
target: string,
actions: Action[]
) {
actions.forEach(action => {
const { expression } = action;
let snippet, dependencies;
if (expression) {
snippet = expression.snippet;
dependencies = expression.dependencies;
expression.declarations.forEach(declaration => {
block.builders.init.addBlock(declaration);
});
}
const name = block.getUniqueName(
`${action.name.replace(/[^a-zA-Z0-9_$]/g, '_')}_action`
);
block.addVariable(name);
const fn = `ctx.${action.name}`;
block.builders.mount.addLine(
`${name} = ${fn}.call(null, ${target}${snippet ? `, ${snippet}` : ''}) || {};`
);
if (dependencies && dependencies.size > 0) {
let conditional = `typeof ${name}.update === 'function' && `;
const deps = [...dependencies].map(dependency => `changed.${dependency}`).join(' || ');
conditional += dependencies.size > 1 ? `(${deps})` : deps;
block.builders.update.addConditional(
conditional,
`${name}.update.call(null, ${snippet});`
);
}
block.builders.destroy.addLine(
`if (${name} && typeof ${name}.destroy === 'function') ${name}.destroy();`
);
});
}

@ -0,0 +1,43 @@
import Block from '../../Block';
import EventHandler from '../../../nodes/EventHandler';
export default function addEventHandlers(
block: Block,
target: string,
handlers: EventHandler[]
) {
handlers.forEach(handler => {
const modifiers = [];
if (handler.modifiers.has('preventDefault')) modifiers.push('event.preventDefault();');
if (handler.modifiers.has('stopPropagation')) modifiers.push('event.stopPropagation();');
const opts = ['passive', 'once', 'capture'].filter(mod => handler.modifiers.has(mod));
if (opts.length) {
const optString = (opts.length === 1 && opts[0] === 'capture')
? 'true'
: `{ ${opts.map(opt => `${opt}: true`).join(', ')} }`;
block.builders.hydrate.addLine(
`@addListener(${target}, "${handler.name}", ${handler.snippet}, ${optString});`
);
block.builders.destroy.addLine(
`@removeListener(${target}, "${handler.name}", ${handler.snippet}, ${optString});`
);
} else {
block.builders.hydrate.addLine(
`@addListener(${target}, "${handler.name}", ${handler.snippet});`
);
block.builders.destroy.addLine(
`@removeListener(${target}, "${handler.name}", ${handler.snippet});`
);
}
if (handler.expression) {
handler.expression.declarations.forEach(declaration => {
block.builders.init.addBlock(declaration);
});
}
});
}

@ -14,6 +14,6 @@
}
</script>
<svelte:window on:esc="{() => escaped = true}" />
<svelte:window use:esc="{() => escaped = true}" />
<p>escaped: {escaped}</p>
Loading…
Cancel
Save