import Renderer from '../../Renderer'; import Element from '../../../nodes/Element'; import Wrapper from '../shared/Wrapper'; import Block from '../../Block'; import { is_void, quote_prop_if_necessary, quote_name_if_necessary, sanitize } from '../../../../utils/names'; import FragmentWrapper from '../Fragment'; import { stringify, escape_html, escape } from '../../../utils/stringify'; import TextWrapper from '../Text'; import fix_attribute_casing from './fix_attribute_casing'; 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 add_to_set from '../../../utils/add_to_set'; import add_event_handlers from '../shared/add_event_handlers'; import add_actions from '../shared/add_actions'; import create_debugging_comment from '../shared/create_debugging_comment'; import { get_context_merger } from '../shared/get_context_merger'; import bind_this from '../shared/bind_this'; 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: ['resize'], 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') }, { 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' }, { event_names: ['ratechange'], filter: (node: Element, name: string) => node.is_media_node() && name === 'playbackRate' }, // details event { event_names: ['toggle'], filter: (node: Element, _name: string) => node.name === 'details' }, ]; export default class ElementWrapper extends Wrapper { node: Element; fragment: FragmentWrapper; attributes: AttributeWrapper[]; bindings: Binding[]; class_dependencies: string[]; slot_block: Block; select_binding_dependencies?: Set; var: string; constructor( renderer: Renderer, block: Block, parent: Wrapper, node: Element, strip_whitespace: boolean, next_sibling: Wrapper ) { super(renderer, block, parent, node); this.var = node.name.replace(/[^a-zA-Z0-9_$]/g, '_'); this.class_dependencies = []; 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') { const name = attribute.get_static_value() as string; if (!(owner as InlineComponentWrapper).slots.has(name)) { const child_block = block.child({ comment: create_debugging_comment(node, this.renderer.component), name: this.renderer.component.get_unique_name(`create_${sanitize(name)}_slot`) }); const lets = this.node.lets; const seen = new Set(lets.map(l => l.name)); (owner as InlineComponentWrapper).node.lets.forEach(l => { if (!seen.has(l.name)) lets.push(l); }); const fn = get_context_merger(lets); (owner as InlineComponentWrapper).slots.set(name, { block: child_block, scope: this.node.scope, fn }); this.renderer.blocks.push(child_block); } this.slot_block = (owner as InlineComponentWrapper).slots.get(name).block; block = this.slot_block; } } 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.