Convert src/compiler/compile/render_dom/wrappers/Element/StyleAttribute.ts to JavaScript

pull/8569/head
Simon Holthausen 3 years ago
parent 50ced4db8d
commit 794b8a7bba

@ -1,3 +1,3 @@
import { BaseAttributeWrapper } from './Attribute'; import { BaseAttributeWrapper } from './Attribute.js';
export default class SpreadAttributeWrapper extends BaseAttributeWrapper {} export default class SpreadAttributeWrapper extends BaseAttributeWrapper {}

@ -1,195 +1,187 @@
import { b, x } from 'code-red'; import { b, x } from 'code-red';
import Attribute from '../../../nodes/Attribute'; import AttributeWrapper from './Attribute.js';
import Block from '../../Block'; import { string_literal } from '../../../utils/stringify.js';
import AttributeWrapper from './Attribute'; import add_to_set from '../../../utils/add_to_set.js';
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<Text | Expression>;
important: boolean;
}
/** @extends AttributeWrapper */
export default class StyleAttributeWrapper 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<string> = 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}) { if (${condition}) {
@set_style(${this.parent.var}, "${prop.key}", ${value}, ${prop.important ? 1 : null}); @set_style(${this.parent.var}, "${prop.key}", ${value}, ${prop.important ? 1 : null});
}`; }`;
block.chunks.update.push(update);
block.chunks.update.push(update); }
} }
} else { else {
value = string_literal((prop.value[0] as Text).data); 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});`);
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*/; const regex_style_prop_key = /^\s*([\w-]+):\s*/;
function optimize_style(value: Array<Text | Expression>) { /** @param {Array<Text | import('../../../nodes/shared/Expression.js').default>} value */
const props: StyleProp[] = []; function optimize_style(value) {
let chunks = value.slice();
/** @type {StyleProp[]} */
while (chunks.length) { const props = [];
const chunk = chunks[0]; let chunks = value.slice();
while (chunks.length) {
if (chunk.type !== 'Text') return null; const chunk = chunks[0];
if (chunk.type !== 'Text')
const key_match = regex_style_prop_key.exec(chunk.data); return null;
if (!key_match) return null; const key_match = regex_style_prop_key.exec(chunk.data);
if (!key_match)
const key = key_match[1]; return null;
const key = key_match[1];
const offset = key_match.index + key_match[0].length; const offset = key_match.index + key_match[0].length;
const remaining_data = chunk.data.slice(offset); const remaining_data = chunk.data.slice(offset);
if (remaining_data) {
if (remaining_data) { chunks[0] = /** @type {import('../../../nodes/Text.js').default} */ ({
chunks[0] = { start: chunk.start + offset,
start: chunk.start + offset, end: chunk.end,
end: chunk.end, type: 'Text',
type: 'Text', data: remaining_data
data: remaining_data });
} as Text; }
} else { else {
chunks.shift(); chunks.shift();
} }
const result = get_style_value(chunks);
const result = get_style_value(chunks); props.push({ key, value: result.value, important: result.important });
chunks = result.chunks;
props.push({ key, value: result.value, important: result.important }); }
chunks = result.chunks; return props;
}
return props;
} }
const regex_important_flag = /\s*!important\s*$/; const regex_important_flag = /\s*!important\s*$/;
const regex_semicolon_or_whitespace = /[;\s]/; const regex_semicolon_or_whitespace = /[;\s]/;
function get_style_value(chunks: Array<Text | Expression>) { /** @param {Array<Text | import('../../../nodes/shared/Expression.js').default>} chunks */
const value: Array<Text | Expression> = []; function get_style_value(chunks) {
let in_url = false; /** @type {Array<Text | import('../../../nodes/shared/Expression.js').default>} */
let quote_mark = null; const value = [];
let escaped = false; let in_url = false;
let closed = false; let quote_mark = null;
let escaped = false;
while (chunks.length && !closed) { let closed = false;
const chunk = chunks.shift(); while (chunks.length && !closed) {
const chunk = chunks.shift();
if (chunk.type === 'Text') { if (chunk.type === 'Text') {
let c = 0; let c = 0;
while (c < chunk.data.length) { while (c < chunk.data.length) {
const char = chunk.data[c]; const char = chunk.data[c];
if (escaped) {
if (escaped) { escaped = false;
escaped = false; }
} else if (char === '\\') { else if (char === '\\') {
escaped = true; escaped = true;
} else if (char === quote_mark) { }
quote_mark = null; else if (char === quote_mark) {
} else if (char === '"' || char === "'") { quote_mark = null;
quote_mark = char; }
} else if (char === ')' && in_url) { else if (char === '"' || char === "'") {
in_url = false; quote_mark = char;
} else if (char === 'u' && chunk.data.slice(c, c + 4) === 'url(') { }
in_url = true; else if (char === ')' && in_url) {
} else if (char === ';' && !in_url && !quote_mark) { in_url = false;
closed = true; }
break; else if (char === 'u' && chunk.data.slice(c, c + 4) === 'url(') {
} in_url = true;
}
c += 1; else if (char === ';' && !in_url && !quote_mark) {
} closed = true;
break;
if (c > 0) { }
value.push({ c += 1;
type: 'Text', }
start: chunk.start, if (c > 0) {
end: chunk.start + c, value.push(/** @type {import('../../../nodes/Text.js').default} */ ({
data: chunk.data.slice(0, c) type: 'Text',
} as 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); }
while (regex_semicolon_or_whitespace.test(chunk.data[c]))
if (remaining_data) { c += 1;
chunks.unshift({ const remaining_data = chunk.data.slice(c);
start: chunk.start + c, if (remaining_data) {
end: chunk.end, chunks.unshift(/** @type {import('../../../nodes/Text.js').default} */ ({
type: 'Text', start: chunk.start + c,
data: remaining_data end: chunk.end,
} as Text); 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; /** @param {Array<Text | import('../../../nodes/shared/Expression.js').default>} value */
} function is_dynamic(value) {
} else { return value.length > 1 || value[0].type !== 'Text';
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
};
}
function is_dynamic(value: Array<Text | Expression>) { /** @typedef {Object} StyleProp
return value.length > 1 || value[0].type !== 'Text'; * @property {string} key
} * @property {Array<Text|Expression>} value
* @property {boolean} important
*/
Loading…
Cancel
Save