Convert src/compiler/compile/render_dom/wrappers/Window.ts to JavaScript

pull/8569/head
Simon Holthausen 3 years ago
parent 711dfa073b
commit 28962d1378

@ -1,116 +1,112 @@
import Renderer from '../Renderer'; import Wrapper from './shared/Wrapper.js';
import Block from '../Block';
import Wrapper from './shared/Wrapper';
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import add_event_handlers from './shared/add_event_handlers'; import add_event_handlers from './shared/add_event_handlers.js';
import Window from '../../nodes/Window'; import add_actions from './shared/add_actions.js';
import add_actions from './shared/add_actions'; import EventHandler from './Element/EventHandler.js';
import { Identifier } from 'estree';
import { TemplateNode } from '../../../interfaces';
import EventHandler from './Element/EventHandler';
const associated_events = { const associated_events = {
innerWidth: 'resize', innerWidth: 'resize',
innerHeight: 'resize', innerHeight: 'resize',
outerWidth: 'resize', outerWidth: 'resize',
outerHeight: 'resize', outerHeight: 'resize',
devicePixelRatio: 'resize', devicePixelRatio: 'resize',
scrollX: 'scroll',
scrollX: 'scroll', scrollY: 'scroll'
scrollY: 'scroll'
}; };
const properties = { const properties = {
scrollX: 'pageXOffset', scrollX: 'pageXOffset',
scrollY: 'pageYOffset' scrollY: 'pageYOffset'
}; };
const readonly = new Set([ const readonly = new Set([
'innerWidth', 'innerWidth',
'innerHeight', 'innerHeight',
'outerWidth', 'outerWidth',
'outerHeight', 'outerHeight',
'devicePixelRatio', 'devicePixelRatio',
'online' 'online'
]); ]);
/** @extends Wrapper */
export default class WindowWrapper extends Wrapper { export default class WindowWrapper extends Wrapper {
node: Window;
handlers: EventHandler[];
constructor(renderer: Renderer, block: Block, parent: Wrapper, node: TemplateNode) {
super(renderer, block, parent, node);
this.handlers = this.node.handlers.map((handler) => new EventHandler(handler, this));
}
render(block: Block, _parent_node: Identifier, _parent_nodes: Identifier) {
const { renderer } = this;
const { component } = renderer;
const events: Record<string, Array<{ name: string; value: string }>> = {};
const bindings: Record<string, string> = {};
add_actions(block, '@_window', this.node.actions);
add_event_handlers(block, '@_window', this.handlers);
this.node.bindings.forEach((binding) => {
// TODO: what if it's a MemberExpression?
const binding_name = (binding.expression.node as Identifier).name;
// in dev mode, throw if read-only values are written to
if (readonly.has(binding.name)) {
renderer.readonly.add(binding_name);
}
bindings[binding.name] = binding_name;
// bind:online is a special case, we need to listen for two separate events
if (binding.name === 'online') return;
const associated_event = associated_events[binding.name];
const property = properties[binding.name] || binding.name;
if (!events[associated_event]) events[associated_event] = [];
events[associated_event].push({
name: binding_name,
value: property
});
});
const scrolling = block.get_unique_name('scrolling');
const clear_scrolling = block.get_unique_name('clear_scrolling');
const scrolling_timeout = block.get_unique_name('scrolling_timeout');
Object.keys(events).forEach((event) => {
const id = block.get_unique_name(`onwindow${event}`);
const props = events[event];
renderer.add_to_context(id.name);
const fn = renderer.reference(id.name);
if (event === 'scroll') {
// TODO other bidirectional bindings...
block.add_variable(scrolling, x`false`);
block.add_variable(clear_scrolling, x`() => { ${scrolling} = false }`);
block.add_variable(scrolling_timeout);
const condition = /** @type {import('../../nodes/Window.js').default} */
bindings.scrollX && bindings.scrollY node;
? x`"${bindings.scrollX}" in this._state || "${bindings.scrollY}" in this._state`
: x`"${bindings.scrollX || bindings.scrollY}" in this._state`; /** @type {import('./Element/EventHandler.js').default[]} */
handlers;
const scrollX = bindings.scrollX && x`this._state.${bindings.scrollX}`;
const scrollY = bindings.scrollY && x`this._state.${bindings.scrollY}`; /**
* @param {import('../Renderer.js').default} renderer
renderer.meta_bindings.push(b` * @param {import('../Block.js').default} block
* @param {import('./shared/Wrapper.js').default} parent
* @param {import('../../../interfaces.js').TemplateNode} node
*/
constructor(renderer, block, parent, node) {
super(renderer, block, parent, node);
this.handlers = this.node.handlers.map((handler) => new EventHandler(handler, this));
}
/**
* @param {import('../Block.js').default} block
* @param {import('estree').Identifier} _parent_node
* @param {import('estree').Identifier} _parent_nodes
*/
render(block, _parent_node, _parent_nodes) {
const { renderer } = this;
const { component } = renderer;
/** @type {Record<string, Array<{ name: string; value: string }>>} */
const events = {};
/** @type {Record<string, string>} */
const bindings = {};
add_actions(block, '@_window', this.node.actions);
add_event_handlers(block, '@_window', this.handlers);
this.node.bindings.forEach((binding) => {
// TODO: what if it's a MemberExpression?
const binding_name = ( /** @type {import('estree').Identifier} */(binding.expression.node)).name;
// in dev mode, throw if read-only values are written to
if (readonly.has(binding.name)) {
renderer.readonly.add(binding_name);
}
bindings[binding.name] = binding_name;
// bind:online is a special case, we need to listen for two separate events
if (binding.name === 'online')
return;
const associated_event = associated_events[binding.name];
const property = properties[binding.name] || binding.name;
if (!events[associated_event])
events[associated_event] = [];
events[associated_event].push({
name: binding_name,
value: property
});
});
const scrolling = block.get_unique_name('scrolling');
const clear_scrolling = block.get_unique_name('clear_scrolling');
const scrolling_timeout = block.get_unique_name('scrolling_timeout');
Object.keys(events).forEach((event) => {
const id = block.get_unique_name(`onwindow${event}`);
const props = events[event];
renderer.add_to_context(id.name);
const fn = renderer.reference(id.name);
if (event === 'scroll') {
// TODO other bidirectional bindings...
block.add_variable(scrolling, x `false`);
block.add_variable(clear_scrolling, x `() => { ${scrolling} = false }`);
block.add_variable(scrolling_timeout);
const condition = bindings.scrollX && bindings.scrollY
? x `"${bindings.scrollX}" in this._state || "${bindings.scrollY}" in this._state`
: x `"${bindings.scrollX || bindings.scrollY}" in this._state`;
const scrollX = bindings.scrollX && x `this._state.${bindings.scrollX}`;
const scrollY = bindings.scrollY && x `this._state.${bindings.scrollY}`;
renderer.meta_bindings.push(b `
if (${condition}) { if (${condition}) {
@_scrollTo(${scrollX || '@_window.pageXOffset'}, ${scrollY || '@_window.pageYOffset'}); @_scrollTo(${scrollX || '@_window.pageXOffset'}, ${scrollY || '@_window.pageYOffset'});
} }
${scrollX && `${scrollX} = @_window.pageXOffset;`} ${scrollX && `${scrollX} = @_window.pageXOffset;`}
${scrollY && `${scrollY} = @_window.pageYOffset;`} ${scrollY && `${scrollY} = @_window.pageYOffset;`}
`); `);
block.event_listeners.push(x `
block.event_listeners.push(x`
@listen(@_window, "${event}", () => { @listen(@_window, "${event}", () => {
${scrolling} = true; ${scrolling} = true;
@_clearTimeout(${scrolling_timeout}); @_clearTimeout(${scrolling_timeout});
@ -118,41 +114,35 @@ export default class WindowWrapper extends Wrapper {
${fn}(); ${fn}();
}) })
`); `);
} else { }
props.forEach((prop) => { else {
renderer.meta_bindings.push(b`this._state.${prop.name} = @_window.${prop.value};`); props.forEach((prop) => {
}); renderer.meta_bindings.push(b `this._state.${prop.name} = @_window.${prop.value};`);
});
block.event_listeners.push(x` block.event_listeners.push(x `
@listen(@_window, "${event}", ${fn}) @listen(@_window, "${event}", ${fn})
`); `);
} }
component.partly_hoisted.push(b `
component.partly_hoisted.push(b`
function ${id}() { function ${id}() {
${props.map((prop) => renderer.invalidate(prop.name, x`${prop.name} = @_window.${prop.value}`))} ${props.map((prop) => renderer.invalidate(prop.name, x `${prop.name} = @_window.${prop.value}`))}
} }
`); `);
block.chunks.init.push(b `
block.chunks.init.push(b`
@add_render_callback(${fn}); @add_render_callback(${fn});
`); `);
component.has_reactive_assignments = true;
component.has_reactive_assignments = true; });
}); // special case... might need to abstract this out if we add more special cases
if (bindings.scrollX || bindings.scrollY) {
// special case... might need to abstract this out if we add more special cases const condition = renderer.dirty([bindings.scrollX, bindings.scrollY].filter(Boolean));
if (bindings.scrollX || bindings.scrollY) { const scrollX = bindings.scrollX
const condition = renderer.dirty([bindings.scrollX, bindings.scrollY].filter(Boolean)); ? renderer.reference(bindings.scrollX)
: x `@_window.pageXOffset`;
const scrollX = bindings.scrollX const scrollY = bindings.scrollY
? renderer.reference(bindings.scrollX) ? renderer.reference(bindings.scrollY)
: x`@_window.pageXOffset`; : x `@_window.pageYOffset`;
const scrollY = bindings.scrollY block.chunks.update.push(b `
? renderer.reference(bindings.scrollY)
: x`@_window.pageYOffset`;
block.chunks.update.push(b`
if (${condition} && !${scrolling}) { if (${condition} && !${scrolling}) {
${scrolling} = true; ${scrolling} = true;
@_clearTimeout(${scrolling_timeout}); @_clearTimeout(${scrolling_timeout});
@ -160,32 +150,27 @@ export default class WindowWrapper extends Wrapper {
${scrolling_timeout} = @_setTimeout(${clear_scrolling}, 100); ${scrolling_timeout} = @_setTimeout(${clear_scrolling}, 100);
} }
`); `);
} }
// another special case. (I'm starting to think these are all special cases.)
// another special case. (I'm starting to think these are all special cases.) if (bindings.online) {
if (bindings.online) { const id = block.get_unique_name('onlinestatuschanged');
const id = block.get_unique_name('onlinestatuschanged'); const name = bindings.online;
const name = bindings.online; renderer.add_to_context(id.name);
const reference = renderer.reference(id.name);
renderer.add_to_context(id.name); component.partly_hoisted.push(b `
const reference = renderer.reference(id.name);
component.partly_hoisted.push(b`
function ${id}() { function ${id}() {
${renderer.invalidate(name, x`${name} = @_navigator.onLine`)} ${renderer.invalidate(name, x `${name} = @_navigator.onLine`)}
} }
`); `);
block.chunks.init.push(b `
block.chunks.init.push(b`
@add_render_callback(${reference}); @add_render_callback(${reference});
`); `);
block.event_listeners.push(x `@listen(@_window, "online", ${reference})`, x `@listen(@_window, "offline", ${reference})`);
component.has_reactive_assignments = true;
}
}
}
block.event_listeners.push(
x`@listen(@_window, "online", ${reference})`,
x`@listen(@_window, "offline", ${reference})`
);
component.has_reactive_assignments = true;
}
}
}

Loading…
Cancel
Save