From 794b8a7bba4e9effdf77df0a6a2b60fd1ca0a849 Mon Sep 17 00:00:00 2001 From: Simon Holthausen Date: Fri, 12 May 2023 14:34:47 +0200 Subject: [PATCH] Convert src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts to JavaScript --- ...{SpreadAttribute.ts => SpreadAttribute.js} | 2 +- .../wrappers/Element/StyleAttribute.ts | 344 +++++++++--------- 2 files changed, 169 insertions(+), 177 deletions(-) rename src/compiler/compile/render_dom/wrappers/Element/{SpreadAttribute.ts => SpreadAttribute.js} (58%) diff --git a/src/compiler/compile/render_dom/wrappers/Element/SpreadAttribute.ts b/src/compiler/compile/render_dom/wrappers/Element/SpreadAttribute.js similarity index 58% rename from src/compiler/compile/render_dom/wrappers/Element/SpreadAttribute.ts rename to src/compiler/compile/render_dom/wrappers/Element/SpreadAttribute.js index 2be346c15a..e2be16a247 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/SpreadAttribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/SpreadAttribute.js @@ -1,3 +1,3 @@ -import { BaseAttributeWrapper } from './Attribute'; +import { BaseAttributeWrapper } from './Attribute.js'; export default class SpreadAttributeWrapper extends BaseAttributeWrapper {} diff --git a/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts b/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts index 200538f138..876862f52b 100644 --- a/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts +++ b/src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts @@ -1,195 +1,187 @@ import { b, x } from 'code-red'; -import Attribute from '../../../nodes/Attribute'; -import Block from '../../Block'; -import AttributeWrapper from './Attribute'; -import ElementWrapper from '../Element'; -import { string_literal } from '../../../utils/stringify'; -import add_to_set from '../../../utils/add_to_set'; -import Expression from '../../../nodes/shared/Expression'; -import Text from '../../../nodes/Text'; - -export interface StyleProp { - key: string; - value: Array; - important: boolean; -} +import AttributeWrapper from './Attribute.js'; +import { string_literal } from '../../../utils/stringify.js'; +import add_to_set from '../../../utils/add_to_set.js'; +/** @extends AttributeWrapper */ export default class StyleAttributeWrapper extends AttributeWrapper { - node: Attribute; - parent: ElementWrapper; - - render(block: Block) { - const style_props = optimize_style(this.node.chunks); - if (!style_props) return super.render(block); - - style_props.forEach((prop: StyleProp) => { - let value; - - if (is_dynamic(prop.value)) { - const prop_dependencies: Set = new Set(); - - value = prop.value - .map((chunk) => { - if (chunk.type === 'Text') { - return string_literal(chunk.data); - } else { - add_to_set(prop_dependencies, chunk.dynamic_dependencies()); - return chunk.manipulate(block); - } - }) - .reduce((lhs, rhs) => x`${lhs} + ${rhs}`); - - // TODO is this necessary? style.setProperty always treats value as string, no? - // if (prop.value.length === 1 || prop.value[0].type !== 'Text') { - // value = x`"" + ${value}`; - // } - - if (prop_dependencies.size) { - let condition = block.renderer.dirty(Array.from(prop_dependencies)); - - if (block.has_outros) { - condition = x`!#current || ${condition}`; - } - const update = b` + /** @type {import('../../../nodes/Attribute.js').default} */ + node; + + /** @type {import('../Element.js').default} */ + parent; + + /** @param {import('../../Block.js').default} block */ + render(block) { + const style_props = optimize_style(this.node.chunks); + if (!style_props) + return super.render(block); + style_props.forEach((prop) => { + let value; + if (is_dynamic(prop.value)) { + const prop_dependencies = new Set(); + value = prop.value + .map((chunk) => { + if (chunk.type === 'Text') { + return string_literal(chunk.data); + } + else { + add_to_set(prop_dependencies, chunk.dynamic_dependencies()); + return chunk.manipulate(block); + } + }) + .reduce((lhs, rhs) => x `${lhs} + ${rhs}`); + // TODO is this necessary? style.setProperty always treats value as string, no? + // if (prop.value.length === 1 || prop.value[0].type !== 'Text') { + // value = x`"" + ${value}`; + // } + if (prop_dependencies.size) { + let condition = block.renderer.dirty(Array.from(prop_dependencies)); + if (block.has_outros) { + condition = x `!#current || ${condition}`; + } + const update = b ` if (${condition}) { @set_style(${this.parent.var}, "${prop.key}", ${value}, ${prop.important ? 1 : null}); }`; - - block.chunks.update.push(update); - } - } else { - value = string_literal((prop.value[0] as Text).data); - } - - block.chunks.hydrate.push( - b`@set_style(${this.parent.var}, "${prop.key}", ${value}, ${prop.important ? 1 : null});` - ); - }); - } + block.chunks.update.push(update); + } + } + else { + value = string_literal(( /** @type {import('../../../nodes/Text.js').default} */(prop.value[0])).data); + } + block.chunks.hydrate.push(b `@set_style(${this.parent.var}, "${prop.key}", ${value}, ${prop.important ? 1 : null});`); + }); + } } - const regex_style_prop_key = /^\s*([\w-]+):\s*/; -function optimize_style(value: Array) { - const props: StyleProp[] = []; - let chunks = value.slice(); - - while (chunks.length) { - const chunk = chunks[0]; - - if (chunk.type !== 'Text') return null; - - const key_match = regex_style_prop_key.exec(chunk.data); - if (!key_match) return null; - - const key = key_match[1]; - - const offset = key_match.index + key_match[0].length; - const remaining_data = chunk.data.slice(offset); - - if (remaining_data) { - chunks[0] = { - start: chunk.start + offset, - end: chunk.end, - type: 'Text', - data: remaining_data - } as Text; - } else { - chunks.shift(); - } - - const result = get_style_value(chunks); - - props.push({ key, value: result.value, important: result.important }); - chunks = result.chunks; - } - - return props; +/** @param {Array} value */ +function optimize_style(value) { + + /** @type {StyleProp[]} */ + const props = []; + let chunks = value.slice(); + while (chunks.length) { + const chunk = chunks[0]; + if (chunk.type !== 'Text') + return null; + const key_match = regex_style_prop_key.exec(chunk.data); + if (!key_match) + return null; + const key = key_match[1]; + const offset = key_match.index + key_match[0].length; + const remaining_data = chunk.data.slice(offset); + if (remaining_data) { + chunks[0] = /** @type {import('../../../nodes/Text.js').default} */ ({ + start: chunk.start + offset, + end: chunk.end, + type: 'Text', + data: remaining_data + }); + } + else { + chunks.shift(); + } + const result = get_style_value(chunks); + props.push({ key, value: result.value, important: result.important }); + chunks = result.chunks; + } + return props; } - const regex_important_flag = /\s*!important\s*$/; const regex_semicolon_or_whitespace = /[;\s]/; -function get_style_value(chunks: Array) { - const value: Array = []; - - let in_url = false; - let quote_mark = null; - let escaped = false; - let closed = false; - - while (chunks.length && !closed) { - const chunk = chunks.shift(); - - if (chunk.type === 'Text') { - let c = 0; - while (c < chunk.data.length) { - const char = chunk.data[c]; - - if (escaped) { - escaped = false; - } else if (char === '\\') { - escaped = true; - } else if (char === quote_mark) { - quote_mark = null; - } else if (char === '"' || char === "'") { - quote_mark = char; - } else if (char === ')' && in_url) { - in_url = false; - } else if (char === 'u' && chunk.data.slice(c, c + 4) === 'url(') { - in_url = true; - } else if (char === ';' && !in_url && !quote_mark) { - closed = true; - break; - } - - c += 1; - } - - if (c > 0) { - value.push({ - type: 'Text', - start: chunk.start, - end: chunk.start + c, - data: chunk.data.slice(0, c) - } as Text); - } - - while (regex_semicolon_or_whitespace.test(chunk.data[c])) c += 1; - const remaining_data = chunk.data.slice(c); - - if (remaining_data) { - chunks.unshift({ - start: chunk.start + c, - end: chunk.end, - type: 'Text', - data: remaining_data - } as Text); +/** @param {Array} chunks */ +function get_style_value(chunks) { + + /** @type {Array} */ + const value = []; + let in_url = false; + let quote_mark = null; + let escaped = false; + let closed = false; + while (chunks.length && !closed) { + const chunk = chunks.shift(); + if (chunk.type === 'Text') { + let c = 0; + while (c < chunk.data.length) { + const char = chunk.data[c]; + if (escaped) { + escaped = false; + } + else if (char === '\\') { + escaped = true; + } + else if (char === quote_mark) { + quote_mark = null; + } + else if (char === '"' || char === "'") { + quote_mark = char; + } + else if (char === ')' && in_url) { + in_url = false; + } + else if (char === 'u' && chunk.data.slice(c, c + 4) === 'url(') { + in_url = true; + } + else if (char === ';' && !in_url && !quote_mark) { + closed = true; + break; + } + c += 1; + } + if (c > 0) { + value.push(/** @type {import('../../../nodes/Text.js').default} */ ({ + type: 'Text', + start: chunk.start, + end: chunk.start + c, + data: chunk.data.slice(0, c) + })); + } + while (regex_semicolon_or_whitespace.test(chunk.data[c])) + c += 1; + const remaining_data = chunk.data.slice(c); + if (remaining_data) { + chunks.unshift(/** @type {import('../../../nodes/Text.js').default} */ ({ + start: chunk.start + c, + end: chunk.end, + type: 'Text', + data: remaining_data + })); + break; + } + } + else { + value.push(chunk); + } + } + let important = false; + const last_chunk = value[value.length - 1]; + if (last_chunk && last_chunk.type === 'Text' && regex_important_flag.test(last_chunk.data)) { + important = true; + last_chunk.data = last_chunk.data.replace(regex_important_flag, ''); + if (!last_chunk.data) + value.pop(); + } + return { + chunks, + value, + important + }; +} - break; - } - } else { - value.push(chunk); - } - } +/** @param {Array} value */ +function is_dynamic(value) { + return value.length > 1 || value[0].type !== 'Text'; +} - let important = false; - const last_chunk = value[value.length - 1]; - if (last_chunk && last_chunk.type === 'Text' && regex_important_flag.test(last_chunk.data)) { - important = true; - last_chunk.data = last_chunk.data.replace(regex_important_flag, ''); - if (!last_chunk.data) value.pop(); - } - return { - chunks, - value, - important - }; -} -function is_dynamic(value: Array) { - return value.length > 1 || value[0].type !== 'Text'; -} +/** @typedef {Object} StyleProp + * @property {string} key + * @property {Array} value + * @property {boolean} important + */ \ No newline at end of file