import Renderer from '../../Renderer'; import Element from '../../../nodes/Element'; import Wrapper from '../shared/Wrapper'; import Block from '../../Block'; import { is_void } from '../../../../utils/names'; import FragmentWrapper from '../Fragment'; import { escape_html, string_literal } from '../../../utils/stringify'; import TextWrapper from '../Text'; import fix_attribute_casing from './fix_attribute_casing'; import { b, x, p } from 'code-red'; import { namespaces } from '../../../../utils/namespaces'; import AttributeWrapper from './Attribute'; import StyleAttributeWrapper from './StyleAttribute'; import SpreadAttributeWrapper from './SpreadAttribute'; import { dimensions, start_newline } from '../../../../utils/patterns'; import Binding from './Binding'; import add_to_set from '../../../utils/add_to_set'; import { add_event_handler } from '../shared/add_event_handlers'; import { add_action } from '../shared/add_actions'; import bind_this from '../shared/bind_this'; import { is_head } from '../shared/is_head'; import { Identifier, ExpressionStatement, CallExpression } from 'estree'; import EventHandler from './EventHandler'; import { extract_names } from 'periscopic'; import Action from '../../../nodes/Action'; import MustacheTagWrapper from '../MustacheTag'; import RawMustacheTagWrapper from '../RawMustacheTag'; import is_dynamic from '../shared/is_dynamic'; import create_debugging_comment from '../shared/create_debugging_comment'; import { push_array } from '../../../../utils/push_array'; interface BindingGroup { events: string[]; bindings: Binding[]; } const events = [ { event_names: ['input'], filter: (node: Element, _name: string) => node.name === 'textarea' || node.name === 'input' && !/radio|checkbox|range|file/.test(node.get_static_attribute_value('type') as string) }, { event_names: ['input'], filter: (node: Element, name: string) => (name === 'textContent' || name === 'innerHTML') && node.attributes.some(attribute => attribute.name === 'contenteditable') }, { event_names: ['change'], filter: (node: Element, _name: string) => node.name === 'select' || node.name === 'input' && /radio|checkbox|file/.test(node.get_static_attribute_value('type') as string) }, { event_names: ['change', 'input'], filter: (node: Element, _name: string) => node.name === 'input' && node.get_static_attribute_value('type') === 'range' }, { event_names: ['elementresize'], filter: (_node: Element, name: string) => dimensions.test(name) }, // media events { event_names: ['timeupdate'], filter: (node: Element, name: string) => node.is_media_node() && (name === 'currentTime' || name === 'played' || name === 'ended') }, { event_names: ['durationchange'], filter: (node: Element, name: string) => node.is_media_node() && name === 'duration' }, { event_names: ['play', 'pause'], filter: (node: Element, name: string) => node.is_media_node() && name === 'paused' }, { event_names: ['progress'], filter: (node: Element, name: string) => node.is_media_node() && name === 'buffered' }, { event_names: ['loadedmetadata'], filter: (node: Element, name: string) => node.is_media_node() && (name === 'buffered' || name === 'seekable') }, { event_names: ['volumechange'], filter: (node: Element, name: string) => node.is_media_node() && (name === 'volume' || name === 'muted') }, { event_names: ['ratechange'], filter: (node: Element, name: string) => node.is_media_node() && name === 'playbackRate' }, { event_names: ['seeking', 'seeked'], filter: (node: Element, name: string) => node.is_media_node() && (name === 'seeking') }, { event_names: ['ended'], filter: (node: Element, name: string) => node.is_media_node() && name === 'ended' }, { event_names: ['resize'], filter: (node: Element, name: string) => node.is_media_node() && (name === 'videoHeight' || name === 'videoWidth') }, // details event { event_names: ['toggle'], filter: (node: Element, _name: string) => node.name === 'details' } ]; const CHILD_DYNAMIC_ELEMENT_BLOCK = 'child_dynamic_element'; export default class ElementWrapper extends Wrapper { node: Element; fragment: FragmentWrapper; attributes: Array; bindings: Binding[]; event_handlers: EventHandler[]; class_dependencies: string[]; select_binding_dependencies?: Set; var: any; void: boolean; child_dynamic_element_block?: Block = null; child_dynamic_element?: ElementWrapper = null; constructor( renderer: Renderer, block: Block, parent: Wrapper, node: Element, strip_whitespace: boolean, next_sibling: Wrapper ) { super(renderer, block, parent, node); if (node.is_dynamic_element && block.type !== CHILD_DYNAMIC_ELEMENT_BLOCK) { this.child_dynamic_element_block = block.child({ comment: create_debugging_comment(node, renderer.component), name: renderer.component.get_unique_name('create_dynamic_element'), type: CHILD_DYNAMIC_ELEMENT_BLOCK }); renderer.blocks.push(this.child_dynamic_element_block); this.child_dynamic_element = new ElementWrapper( renderer, this.child_dynamic_element_block, parent, node, strip_whitespace, next_sibling ); } this.var = { type: 'Identifier', name: node.name.replace(/[^a-zA-Z0-9_$]/g, '_') }; this.void = is_void(node.name); this.class_dependencies = []; if (this.node.children.length) { this.node.lets.forEach(l => { extract_names(l.value || l.name).forEach(name => { renderer.add_to_context(name, true); }); }); } this.attributes = this.node.attributes.map(attribute => { if (attribute.name === 'style') { return new StyleAttributeWrapper(this, block, attribute); } if (attribute.type === 'Spread') { return new SpreadAttributeWrapper(this, block, attribute); } return new AttributeWrapper(this, block, attribute); }); // ordinarily, there'll only be one... but we need to handle // the rare case where an element can have multiple bindings, // e.g.