diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.js b/src/compiler/compile/render_dom/wrappers/Element/index.js index ac987219a0..e6cb10e501 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.js +++ b/src/compiler/compile/render_dom/wrappers/Element/index.js @@ -9,7 +9,15 @@ import { namespaces } from '../../../../utils/namespaces.js'; import AttributeWrapper from './Attribute.js'; import StyleAttributeWrapper from './StyleAttribute.js'; import SpreadAttributeWrapper from './SpreadAttribute.js'; -import { regex_dimensions, regex_starts_with_newline, regex_backslashes, regex_border_box_size, regex_content_box_size, regex_device_pixel_content_box_size, regex_content_rect } from '../../../../utils/patterns.js'; +import { + regex_dimensions, + regex_starts_with_newline, + regex_backslashes, + regex_border_box_size, + regex_content_box_size, + regex_device_pixel_content_box_size, + regex_content_rect +} from '../../../../utils/patterns.js'; import Binding from './Binding.js'; import add_to_set from '../../../utils/add_to_set.js'; import { add_event_handler } from '../shared/add_event_handlers.js'; @@ -22,118 +30,126 @@ import Action from '../../../nodes/Action.js'; import MustacheTagWrapper from '../MustacheTag.js'; import RawMustacheTagWrapper from '../RawMustacheTag.js'; import is_dynamic from '../shared/is_dynamic.js'; -import { is_name_contenteditable, has_contenteditable_attr } from '../../../utils/contenteditable.js'; +import { + is_name_contenteditable, + has_contenteditable_attr +} from '../../../utils/contenteditable.js'; import create_debugging_comment from '../shared/create_debugging_comment.js'; import { push_array } from '../../../../utils/push_array.js'; import CommentWrapper from '../Comment.js'; const regex_contains_radio_or_checkbox_or_file = /radio|checkbox|file/; const regex_contains_radio_or_checkbox_or_range_or_file = /radio|checkbox|range|file/; const events = [ - { - event_names: ['input'], - filter: (node, _name) => node.name === 'textarea' || - (node.name === 'input' && - !regex_contains_radio_or_checkbox_or_range_or_file.test( - - /** @type {string} */ (node.get_static_attribute_value('type')))) - }, - { - event_names: ['input'], - filter: (node, name) => is_name_contenteditable(name) && has_contenteditable_attr(node) - }, - { - event_names: ['change'], - filter: (node, _name) => node.name === 'select' || - (node.name === 'input' && - regex_contains_radio_or_checkbox_or_file.test( - - /** @type {string} */ (node.get_static_attribute_value('type')))) - }, - { - event_names: ['change', 'input'], - filter: (node, _name) => node.name === 'input' && node.get_static_attribute_value('type') === 'range' - }, - // resize events - { - event_names: ['elementresize'], - filter: (_node, name) => regex_dimensions.test(name) - }, - { - event_names: ['elementresizecontentbox'], - filter: (_node, name) => regex_content_rect.test(name) ?? regex_content_box_size.test(name) - }, - { - event_names: ['elementresizeborderbox'], - filter: (_node, name) => regex_border_box_size.test(name) - }, - { - event_names: ['elementresizedevicepixelcontentbox'], - filter: (_node, name) => regex_device_pixel_content_box_size.test(name) - }, - // media events - { - event_names: ['timeupdate'], - filter: (node, name) => node.is_media_node() && (name === 'currentTime' || name === 'played' || name === 'ended') - }, - { - event_names: ['durationchange'], - filter: (node, name) => node.is_media_node() && name === 'duration' - }, - { - event_names: ['play', 'pause'], - filter: (node, name) => node.is_media_node() && name === 'paused' - }, - { - event_names: ['progress'], - filter: (node, name) => node.is_media_node() && name === 'buffered' - }, - { - event_names: ['loadedmetadata'], - filter: (node, name) => node.is_media_node() && (name === 'buffered' || name === 'seekable') - }, - { - event_names: ['volumechange'], - filter: (node, name) => node.is_media_node() && (name === 'volume' || name === 'muted') - }, - { - event_names: ['ratechange'], - filter: (node, name) => node.is_media_node() && name === 'playbackRate' - }, - { - event_names: ['seeking', 'seeked'], - filter: (node, name) => node.is_media_node() && name === 'seeking' - }, - { - event_names: ['ended'], - filter: (node, name) => node.is_media_node() && name === 'ended' - }, - { - event_names: ['resize'], - filter: (node, name) => node.is_media_node() && (name === 'videoHeight' || name === 'videoWidth') - }, - { - // from https://html.spec.whatwg.org/multipage/media.html#ready-states - // and https://html.spec.whatwg.org/multipage/media.html#loading-the-media-resource - event_names: [ - 'loadedmetadata', - 'loadeddata', - 'canplay', - 'canplaythrough', - 'playing', - 'waiting', - 'emptied' - ], - filter: (node, name) => node.is_media_node() && name === 'readyState' - }, - // details event - { - event_names: ['toggle'], - filter: (node, _name) => node.name === 'details' - }, - { - event_names: ['load'], - filter: (_, name) => name === 'naturalHeight' || name === 'naturalWidth' - } + { + event_names: ['input'], + filter: (node, _name) => + node.name === 'textarea' || + (node.name === 'input' && + !regex_contains_radio_or_checkbox_or_range_or_file.test( + /** @type {string} */ (node.get_static_attribute_value('type')) + )) + }, + { + event_names: ['input'], + filter: (node, name) => is_name_contenteditable(name) && has_contenteditable_attr(node) + }, + { + event_names: ['change'], + filter: (node, _name) => + node.name === 'select' || + (node.name === 'input' && + regex_contains_radio_or_checkbox_or_file.test( + /** @type {string} */ (node.get_static_attribute_value('type')) + )) + }, + { + event_names: ['change', 'input'], + filter: (node, _name) => + node.name === 'input' && node.get_static_attribute_value('type') === 'range' + }, + // resize events + { + event_names: ['elementresize'], + filter: (_node, name) => regex_dimensions.test(name) + }, + { + event_names: ['elementresizecontentbox'], + filter: (_node, name) => regex_content_rect.test(name) ?? regex_content_box_size.test(name) + }, + { + event_names: ['elementresizeborderbox'], + filter: (_node, name) => regex_border_box_size.test(name) + }, + { + event_names: ['elementresizedevicepixelcontentbox'], + filter: (_node, name) => regex_device_pixel_content_box_size.test(name) + }, + // media events + { + event_names: ['timeupdate'], + filter: (node, name) => + node.is_media_node() && (name === 'currentTime' || name === 'played' || name === 'ended') + }, + { + event_names: ['durationchange'], + filter: (node, name) => node.is_media_node() && name === 'duration' + }, + { + event_names: ['play', 'pause'], + filter: (node, name) => node.is_media_node() && name === 'paused' + }, + { + event_names: ['progress'], + filter: (node, name) => node.is_media_node() && name === 'buffered' + }, + { + event_names: ['loadedmetadata'], + filter: (node, name) => node.is_media_node() && (name === 'buffered' || name === 'seekable') + }, + { + event_names: ['volumechange'], + filter: (node, name) => node.is_media_node() && (name === 'volume' || name === 'muted') + }, + { + event_names: ['ratechange'], + filter: (node, name) => node.is_media_node() && name === 'playbackRate' + }, + { + event_names: ['seeking', 'seeked'], + filter: (node, name) => node.is_media_node() && name === 'seeking' + }, + { + event_names: ['ended'], + filter: (node, name) => node.is_media_node() && name === 'ended' + }, + { + event_names: ['resize'], + filter: (node, name) => + node.is_media_node() && (name === 'videoHeight' || name === 'videoWidth') + }, + { + // from https://html.spec.whatwg.org/multipage/media.html#ready-states + // and https://html.spec.whatwg.org/multipage/media.html#loading-the-media-resource + event_names: [ + 'loadedmetadata', + 'loadeddata', + 'canplay', + 'canplaythrough', + 'playing', + 'waiting', + 'emptied' + ], + filter: (node, name) => node.is_media_node() && name === 'readyState' + }, + // details event + { + event_names: ['toggle'], + filter: (node, _name) => node.name === 'details' + }, + { + event_names: ['load'], + filter: (_, name) => name === 'naturalHeight' || name === 'naturalWidth' + } ]; const CHILD_DYNAMIC_ELEMENT_BLOCK = 'child_dynamic_element'; const regex_invalid_variable_identifier_characters = /[^a-zA-Z0-9_$]/g; @@ -141,220 +157,248 @@ const regex_minus_signs = /-/g; /** @extends Wrapper */ export default class ElementWrapper extends Wrapper { + /** @type {import('../../../nodes/Element.js').default} */ + node; - /** @type {import('../../../nodes/Element.js').default} */ - node; - - /** @type {import('../Fragment.js').default} */ - fragment; + /** @type {import('../Fragment.js').default} */ + fragment; - /** @type {Array} */ - attributes; + /** @type {Array} */ + attributes; - /** @type {import('./Binding.js').default[]} */ - bindings; + /** @type {import('./Binding.js').default[]} */ + bindings; - /** @type {import('./EventHandler.js').default[]} */ - event_handlers; + /** @type {import('./EventHandler.js').default[]} */ + event_handlers; - /** @type {string[]} */ - class_dependencies; + /** @type {string[]} */ + class_dependencies; - /** @type {Set} */ - dynamic_style_dependencies; + /** @type {Set} */ + dynamic_style_dependencies; - /** @type {boolean} */ - has_dynamic_attribute; + /** @type {boolean} */ + has_dynamic_attribute; - /** @type {Set} */ - select_binding_dependencies; + /** @type {Set} */ + select_binding_dependencies; - /** @type {boolean} */ - has_dynamic_value; + /** @type {boolean} */ + has_dynamic_value; - /** @type {any} */ - dynamic_value_condition; + /** @type {any} */ + dynamic_value_condition; - /** @type {any} */ - var; + /** @type {any} */ + var; - /** @type {boolean} */ - void; + /** @type {boolean} */ + void; - /** @type {import('../../Block.js').default} */ - child_dynamic_element_block = null; + /** @type {import('../../Block.js').default} */ + child_dynamic_element_block = null; - /** @type {ElementWrapper} */ - child_dynamic_element = null; - /** */ - element_data_name = null; + /** @type {ElementWrapper} */ + child_dynamic_element = null; + /** */ + element_data_name = null; - /** - * @param {import('../../Renderer.js').default} renderer - * @param {import('../../Block.js').default} block - * @param {import('../shared/Wrapper.js').default} parent - * @param {import('../../../nodes/Element.js').default} node - * @param {boolean} strip_whitespace - * @param {import('../shared/Wrapper.js').default} next_sibling - */ - constructor(renderer, block, parent, node, strip_whitespace, next_sibling) { - super(renderer, block, parent, node); - this.var = { - type: 'Identifier', - name: node.name.replace(regex_invalid_variable_identifier_characters, '_') - }; - this.void = is_void(node.name); - this.class_dependencies = []; - 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); - // the original svelte:element is never used for rendering, because - // it gets assigned a child_dynamic_element which is used in all rendering logic. - // so doing all of this on the original svelte:element will just cause double - // code, because it will be done again on the child_dynamic_element. - return; - } - this.dynamic_style_dependencies = new Set(); - 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); - }); - this.has_dynamic_attribute = !!this.attributes.find((attr) => attr.node.get_dependencies().length > 0); - // ordinarily, there'll only be one... but we need to handle - // the rare case where an element can have multiple bindings, - // e.g.