diff --git a/src/compiler/compile/render_dom/wrappers/Window.ts b/src/compiler/compile/render_dom/wrappers/Window.ts index 274d56e188..f455f2589b 100644 --- a/src/compiler/compile/render_dom/wrappers/Window.ts +++ b/src/compiler/compile/render_dom/wrappers/Window.ts @@ -1,116 +1,112 @@ -import Renderer from '../Renderer'; -import Block from '../Block'; -import Wrapper from './shared/Wrapper'; +import Wrapper from './shared/Wrapper.js'; import { b, x } from 'code-red'; -import add_event_handlers from './shared/add_event_handlers'; -import Window from '../../nodes/Window'; -import add_actions from './shared/add_actions'; -import { Identifier } from 'estree'; -import { TemplateNode } from '../../../interfaces'; -import EventHandler from './Element/EventHandler'; - +import add_event_handlers from './shared/add_event_handlers.js'; +import add_actions from './shared/add_actions.js'; +import EventHandler from './Element/EventHandler.js'; const associated_events = { - innerWidth: 'resize', - innerHeight: 'resize', - outerWidth: 'resize', - outerHeight: 'resize', - devicePixelRatio: 'resize', - - scrollX: 'scroll', - scrollY: 'scroll' + innerWidth: 'resize', + innerHeight: 'resize', + outerWidth: 'resize', + outerHeight: 'resize', + devicePixelRatio: 'resize', + scrollX: 'scroll', + scrollY: 'scroll' }; - const properties = { - scrollX: 'pageXOffset', - scrollY: 'pageYOffset' + scrollX: 'pageXOffset', + scrollY: 'pageYOffset' }; - const readonly = new Set([ - 'innerWidth', - 'innerHeight', - 'outerWidth', - 'outerHeight', - 'devicePixelRatio', - 'online' + 'innerWidth', + 'innerHeight', + 'outerWidth', + 'outerHeight', + 'devicePixelRatio', + 'online' ]); +/** @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> = {}; - const bindings: Record = {}; - - 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 = - 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` + /** @type {import('../../nodes/Window.js').default} */ + node; + + /** @type {import('./Element/EventHandler.js').default[]} */ + handlers; + + /** + * @param {import('../Renderer.js').default} renderer + * @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>} */ + const events = {}; + + /** @type {Record} */ + 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}) { @_scrollTo(${scrollX || '@_window.pageXOffset'}, ${scrollY || '@_window.pageYOffset'}); } ${scrollX && `${scrollX} = @_window.pageXOffset;`} ${scrollY && `${scrollY} = @_window.pageYOffset;`} `); - - block.event_listeners.push(x` + block.event_listeners.push(x ` @listen(@_window, "${event}", () => { ${scrolling} = true; @_clearTimeout(${scrolling_timeout}); @@ -118,41 +114,35 @@ export default class WindowWrapper extends Wrapper { ${fn}(); }) `); - } else { - props.forEach((prop) => { - renderer.meta_bindings.push(b`this._state.${prop.name} = @_window.${prop.value};`); - }); - - block.event_listeners.push(x` + } + else { + props.forEach((prop) => { + renderer.meta_bindings.push(b `this._state.${prop.name} = @_window.${prop.value};`); + }); + block.event_listeners.push(x ` @listen(@_window, "${event}", ${fn}) `); - } - - component.partly_hoisted.push(b` + } + component.partly_hoisted.push(b ` 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}); `); - - component.has_reactive_assignments = true; - }); - - // special case... might need to abstract this out if we add more special cases - if (bindings.scrollX || bindings.scrollY) { - const condition = renderer.dirty([bindings.scrollX, bindings.scrollY].filter(Boolean)); - - const scrollX = bindings.scrollX - ? renderer.reference(bindings.scrollX) - : x`@_window.pageXOffset`; - const scrollY = bindings.scrollY - ? renderer.reference(bindings.scrollY) - : x`@_window.pageYOffset`; - - block.chunks.update.push(b` + component.has_reactive_assignments = true; + }); + // special case... might need to abstract this out if we add more special cases + if (bindings.scrollX || bindings.scrollY) { + const condition = renderer.dirty([bindings.scrollX, bindings.scrollY].filter(Boolean)); + const scrollX = bindings.scrollX + ? renderer.reference(bindings.scrollX) + : x `@_window.pageXOffset`; + const scrollY = bindings.scrollY + ? renderer.reference(bindings.scrollY) + : x `@_window.pageYOffset`; + block.chunks.update.push(b ` if (${condition} && !${scrolling}) { ${scrolling} = true; @_clearTimeout(${scrolling_timeout}); @@ -160,32 +150,27 @@ export default class WindowWrapper extends Wrapper { ${scrolling_timeout} = @_setTimeout(${clear_scrolling}, 100); } `); - } - - // another special case. (I'm starting to think these are all special cases.) - if (bindings.online) { - const id = block.get_unique_name('onlinestatuschanged'); - const name = bindings.online; - - renderer.add_to_context(id.name); - const reference = renderer.reference(id.name); - - component.partly_hoisted.push(b` + } + // another special case. (I'm starting to think these are all special cases.) + if (bindings.online) { + const id = block.get_unique_name('onlinestatuschanged'); + const name = bindings.online; + renderer.add_to_context(id.name); + const reference = renderer.reference(id.name); + component.partly_hoisted.push(b ` 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}); `); + 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; - } - } -}