From d001114dab88b7ab3d99dc50caa4f4d3294eed2c Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 12 May 2023 14:38:47 +0200 Subject: [PATCH] Convert src/compiler/compile/render_dom/wrappers/Element/index.ts to JavaScript --- .../wrappers/Element/StyleAttribute.js | 18 +- .../render_dom/wrappers/Element/index.ts | 2410 ++++++++--------- 2 files changed, 1134 insertions(+), 1294 deletions(-) diff --git a/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.js b/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.js index 5881e5d62c..4b7888113f 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.js +++ b/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.js @@ -8,7 +8,7 @@ export default class StyleAttributeWrapper extends AttributeWrapper { /** @type {import('../../../nodes/Attribute.js').default} */ node; - /** @type {import('../Element.js').default} */ + /** @type {import('./index.js').default} */ parent; /** @param {import('../../Block.js').default} block */ @@ -57,9 +57,9 @@ export default class StyleAttributeWrapper extends AttributeWrapper { } const regex_style_prop_key = /^\s*([\w-]+):\s*/; -/** @param {Array} value */ +/** @param {Array} value */ function optimize_style(value) { - /** @type {StyleProp[]} */ + /** @type {Array<{ key: string; value: Array; important: boolean; }>} */ const props = []; let chunks = value.slice(); while (chunks.length) { @@ -89,9 +89,9 @@ function optimize_style(value) { const regex_important_flag = /\s*!important\s*$/; const regex_semicolon_or_whitespace = /[;\s]/; -/** @param {Array} chunks */ +/** @param {Array} chunks */ function get_style_value(chunks) { - /** @type {Array} */ + /** @type {Array} */ const value = []; let in_url = false; let quote_mark = null; @@ -162,13 +162,7 @@ function get_style_value(chunks) { }; } -/** @param {Array} value */ +/** @param {Array} value */ function is_dynamic(value) { return value.length > 1 || value[0].type !== 'Text'; } - -/** @typedef {Object} StyleProp - * @property {string} key - * @property {Array} value - * @property {boolean} important - */ diff --git a/src/compiler/compile/render_dom/wrappers/Element/index.ts b/src/compiler/compile/render_dom/wrappers/Element/index.ts index 2bda41ec6c..ac987219a0 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/index.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/index.ts @@ -1,406 +1,360 @@ -import Renderer from '../../Renderer'; -import Element from '../../../nodes/Element'; -import Wrapper from '../shared/Wrapper'; -import Block from '../../Block'; -import { is_void } from '../../../../../shared/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 Wrapper from '../shared/Wrapper.js'; +import { is_void } from '../../../../../shared/utils/names.js'; +import FragmentWrapper from '../Fragment.js'; +import { escape_html, string_literal } from '../../../utils/stringify.js'; +import TextWrapper from '../Text.js'; +import fix_attribute_casing from './fix_attribute_casing.js'; 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 { - 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'; -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, Node } from 'estree'; -import EventHandler from './EventHandler'; +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 Binding from './Binding.js'; +import add_to_set from '../../../utils/add_to_set.js'; +import { add_event_handler } from '../shared/add_event_handlers.js'; +import { add_action } from '../shared/add_actions.js'; +import bind_this from '../shared/bind_this.js'; +import { is_head } from '../shared/is_head.js'; +import EventHandler from './EventHandler.js'; 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 { is_name_contenteditable, has_contenteditable_attr } from '../../../utils/contenteditable'; -import create_debugging_comment from '../shared/create_debugging_comment'; -import { push_array } from '../../../../utils/push_array'; -import CommentWrapper from '../Comment'; - -interface BindingGroup { - events: string[]; - bindings: Binding[]; -} - +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 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: Element, _name: string) => - node.name === 'textarea' || - (node.name === 'input' && - !regex_contains_radio_or_checkbox_or_range_or_file.test( - node.get_static_attribute_value('type') as string - )) - }, - { - event_names: ['input'], - filter: (node: Element, name: string) => - is_name_contenteditable(name) && has_contenteditable_attr(node) - }, - { - event_names: ['change'], - filter: (node: Element, _name: string) => - node.name === 'select' || - (node.name === 'input' && - regex_contains_radio_or_checkbox_or_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' - }, - // resize events - { - event_names: ['elementresize'], - filter: (_node: Element, name: string) => regex_dimensions.test(name) - }, - { - event_names: ['elementresizecontentbox'], - filter: (_node: Element, name: string) => - regex_content_rect.test(name) ?? regex_content_box_size.test(name) - }, - - { - event_names: ['elementresizeborderbox'], - filter: (_node: Element, name: string) => regex_border_box_size.test(name) - }, - - { - event_names: ['elementresizedevicepixelcontentbox'], - filter: (_node: Element, name: string) => regex_device_pixel_content_box_size.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') - }, - { - // 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: Element, name: string) => node.is_media_node() && name === 'readyState' - }, - // details event - { - event_names: ['toggle'], - filter: (node: Element, _name: string) => node.name === 'details' - }, - { - event_names: ['load'], - filter: (_: Element, name: string) => 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; const regex_minus_signs = /-/g; +/** @extends Wrapper */ export default class ElementWrapper extends Wrapper { - node: Element; - fragment: FragmentWrapper; - attributes: Array; - bindings: Binding[]; - event_handlers: EventHandler[]; - class_dependencies: string[]; - dynamic_style_dependencies: Set; - has_dynamic_attribute: boolean; - - select_binding_dependencies?: Set; - has_dynamic_value: boolean; - dynamic_value_condition: any; - - var: any; - void: boolean; - - child_dynamic_element_block?: Block = null; - child_dynamic_element?: ElementWrapper = null; - - element_data_name = null; - - constructor( - renderer: Renderer, - block: Block, - parent: Wrapper, - node: Element, - strip_whitespace: boolean, - next_sibling: Wrapper - ) { - 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.