import Renderer from '../../Renderer'; import Element from '../../../nodes/Element'; import Wrapper from '../shared/Wrapper'; import Block from '../../Block'; import Node from '../../../nodes/shared/Node'; import { quotePropIfNecessary, quoteNameIfNecessary } from '../../../../utils/quoteIfNecessary'; import isVoidElementName from '../../../../utils/isVoidElementName'; import FragmentWrapper from '../Fragment'; import { stringify, escapeHTML, escape } from '../../../../utils/stringify'; import TextWrapper from '../Text'; import fixAttributeCasing from '../../../../utils/fixAttributeCasing'; import deindent from '../../../../utils/deindent'; import { namespaces } from '../../../../utils/namespaces'; import AttributeWrapper from './Attribute'; import StyleAttributeWrapper from './StyleAttribute'; 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 = [ { eventNames: ['input'], filter: (node: Element, name: string) => node.name === 'textarea' || node.name === 'input' && !/radio|checkbox|range/.test(node.getStaticAttributeValue('type')) }, { eventNames: ['change'], filter: (node: Element, name: string) => node.name === 'select' || node.name === 'input' && /radio|checkbox/.test(node.getStaticAttributeValue('type')) }, { eventNames: ['change', 'input'], filter: (node: Element, name: string) => node.name === 'input' && node.getStaticAttributeValue('type') === 'range' }, { eventNames: ['resize'], filter: (node: Element, name: string) => dimensions.test(name) }, // media events { eventNames: ['timeupdate'], filter: (node: Element, name: string) => node.isMediaNode() && (name === 'currentTime' || name === 'played') }, { eventNames: ['durationchange'], filter: (node: Element, name: string) => node.isMediaNode() && name === 'duration' }, { eventNames: ['play', 'pause'], filter: (node: Element, name: string) => node.isMediaNode() && name === 'paused' }, { eventNames: ['progress'], filter: (node: Element, name: string) => node.isMediaNode() && name === 'buffered' }, { eventNames: ['loadedmetadata'], filter: (node: Element, name: string) => node.isMediaNode() && (name === 'buffered' || name === 'seekable') }, { eventNames: ['volumechange'], filter: (node: Element, name: string) => node.isMediaNode() && name === 'volume' } ]; export default class ElementWrapper extends Wrapper { node: Element; fragment: FragmentWrapper; attributes: AttributeWrapper[]; bindings: Binding[]; classDependencies: string[]; slotOwner?: InlineComponentWrapper; selectBindingDependencies?: Set; var: string; constructor( renderer: Renderer, block: Block, parent: Wrapper, node: Element, stripWhitespace: boolean, nextSibling: Wrapper ) { super(renderer, block, parent, node); this.var = node.name.replace(/[^a-zA-Z0-9_$]/g, '_') this.classDependencies = []; this.attributes = this.node.attributes.map(attribute => { if (attribute.name === 'slot') { // TODO make separate subclass for this? let owner = this.parent; while (owner) { if (owner.node.type === 'InlineComponent') { break; } if (owner.node.type === 'Element' && /-/.test(owner.node.name)) { break; } owner = owner.parent; } if (owner && owner.node.type === 'InlineComponent') { this.slotOwner = owner; owner._slots.add(attribute.getStaticValue()); } } if (attribute.name === 'style') { return new StyleAttributeWrapper(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.