You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
svelte/src/compile/render-dom/wrappers/Element/StyleAttribute.ts

176 lines
3.8 KiB

import Attribute from '../../../nodes/Attribute';
import Block from '../../Block';
import AttributeWrapper from './Attribute';
import Node from '../../../nodes/shared/Node';
import ElementWrapper from '.';
import { stringify } from '../../../utils/stringify';
import add_to_set from '../../../utils/add_to_set';
export interface StyleProp {
key: string;
value: Node[];
}
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 = new Set();
value =
((prop.value.length === 1 || prop.value[0].type === 'Text') ? '' : `"" + `) +
prop.value
.map((chunk: Node) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
const snippet = chunk.render();
add_to_set(prop_dependencies, chunk.dependencies);
return chunk.get_precedence() <= 13 ? `(${snippet})` : snippet;
}
})
.join(' + ');
if (prop_dependencies.size) {
const dependencies = Array.from(prop_dependencies);
const condition = (
(block.has_outros ? `!#current || ` : '') +
dependencies.map(dependency => `changed.${dependency}`).join(' || ')
);
block.builders.update.add_conditional(
condition,
`@set_style(${this.parent.var}, "${prop.key}", ${value});`
);
}
} else {
value = stringify(prop.value[0].data);
}
block.builders.hydrate.add_line(
`@set_style(${this.parent.var}, "${prop.key}", ${value});`
);
});
}
}
function optimize_style(value: Node[]) {
const props: { key: string, value: Node[] }[] = [];
let chunks = value.slice();
while (chunks.length) {
const chunk = chunks[0];
if (chunk.type !== 'Text') return null;
const key_match = /^\s*([\w-]+):\s*/.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
};
} else {
chunks.shift();
}
const result = get_style_value(chunks);
if (!result) return null;
props.push({ key, value: result.value });
chunks = result.chunks;
}
return props;
}
function get_style_value(chunks: Node[]) {
const value: Node[] = [];
let in_url = false;
let quote_mark = null;
let escaped = false;
while (chunks.length) {
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) {
break;
}
c += 1;
}
if (c > 0) {
value.push({
type: 'Text',
start: chunk.start,
end: chunk.start + c,
data: chunk.data.slice(0, c)
});
}
while (/[;\s]/.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
});
break;
}
}
else {
value.push(chunk);
}
}
return {
chunks,
value
};
}
function is_dynamic(value: Node[]) {
return value.length > 1 || value[0].type !== 'Text';
}