basic attribute spreading on elements

pull/1289/head
Rich-Harris 7 years ago
parent f6f8f66c90
commit 40cf29b2b7

@ -223,10 +223,9 @@ export default class Attribute {
);
}
} else {
const value =
this.value === true
const value = this.value === true
? 'true'
: this.value.length === 0 ? `''` : stringify(this.value[0].data);
: this.value.length === 0 ? `""` : stringify(this.value[0].data);
const statement = (
isLegacyInputType

@ -1,21 +1,14 @@
import deindent from '../../utils/deindent';
import { stringify } from '../../utils/stringify';
import stringifyProps from '../../utils/stringifyProps';
import CodeBuilder from '../../utils/CodeBuilder';
import getTailSnippet from '../../utils/getTailSnippet';
import getObject from '../../utils/getObject';
import getExpressionPrecedence from '../../utils/getExpressionPrecedence';
import isValidIdentifier from '../../utils/isValidIdentifier';
import reservedNames from '../../utils/reservedNames';
import quoteIfNecessary from '../../utils/quoteIfNecessary';
import mungeAttribute from './shared/mungeAttribute';
import Node from './shared/Node';
import Block from '../dom/Block';
import Attribute from './Attribute';
function quoteIfNecessary(name, legacy) {
if (!isValidIdentifier || (legacy && reservedNames.has(name))) return `"${name}"`;
return name;
}
export default class Component extends Node {
type: 'Component';
name: string;
@ -409,79 +402,6 @@ export default class Component extends Node {
}
}
function mungeAttribute(attribute: Node, block: Block): Attribute {
if (attribute.value === true) {
// attributes without values, e.g. <textarea readonly>
return {
name: attribute.name,
value: true,
dynamic: false
};
}
if (attribute.value.length === 0) {
return {
name: attribute.name,
value: `''`,
dynamic: false
};
}
if (attribute.value.length === 1) {
const value = attribute.value[0];
if (value.type === 'Text') {
// static attributes
return {
name: attribute.name,
value: isNaN(value.data) ? stringify(value.data) : value.data,
dynamic: false
};
}
// simple dynamic attributes
block.contextualise(value.expression); // TODO remove
const { dependencies, snippet } = value.metadata;
// TODO only update attributes that have changed
return {
name: attribute.name,
value: snippet,
dependencies,
dynamic: true
};
}
// otherwise we're dealing with a complex dynamic attribute
const allDependencies = new Set();
const value =
(attribute.value[0].type === 'Text' ? '' : `"" + `) +
attribute.value
.map((chunk: Node) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
block.contextualise(chunk.expression); // TODO remove
const { dependencies, snippet } = chunk.metadata;
dependencies.forEach((dependency: string) => {
allDependencies.add(dependency);
});
return getExpressionPrecedence(chunk.expression) <= 13 ? `(${snippet})` : snippet;
}
})
.join(' + ');
return {
name: attribute.name,
value,
dependencies: Array.from(allDependencies),
dynamic: true
};
}
function mungeBinding(binding: Node, block: Block): Binding {
const { name } = getObject(binding.value);
const { contexts } = block.contextualise(binding.value);

@ -5,6 +5,8 @@ import isVoidElementName from '../../utils/isVoidElementName';
import validCalleeObjects from '../../utils/validCalleeObjects';
import reservedNames from '../../utils/reservedNames';
import fixAttributeCasing from '../../utils/fixAttributeCasing';
import quoteIfNecessary from '../../utils/quoteIfNecessary';
import mungeAttribute from './shared/mungeAttribute';
import Node from './shared/Node';
import Block from '../dom/Block';
import Attribute from './Attribute';
@ -446,6 +448,59 @@ export default class Element extends Node {
});
}
addSpreadAttributes(block: Block) {
const levels = block.getUniqueName(`${this.var}_levels`);
const data = block.getUniqueName(`${this.var}_data`);
const initialProps = [];
const updates = [];
this.attributes
.filter(attr => attr.type === 'Attribute' || attr.type === 'Spread')
.forEach(attr => {
if (attr.type === 'Attribute') {
const { dynamic, value, dependencies } = mungeAttribute(attr, block);
const snippet = `{ ${quoteIfNecessary(attr.name, this.generator.legacy)}: ${value} }`;
initialProps.push(snippet);
const condition = dependencies && dependencies.map(d => `changed.${d}`).join(' || ');
updates.push(condition ? `${condition} && ${snippet}` : snippet);
}
else {
block.contextualise(attr.expression); // TODO gah
const { snippet, dependencies } = attr.metadata;
initialProps.push(snippet);
const condition = dependencies && dependencies.map(d => `changed.${d}`).join(' || ');
updates.push(condition ? `${condition} && ${snippet}` : snippet);
}
});
block.builders.init.addBlock(deindent`
var ${levels} = [
${initialProps.join(',\n')}
];
var ${data} = {};
for (var #i = 0; #i < ${levels}.length; #i += 1) {
${data} = @assign(${data}, ${levels}[#i]);
}
`);
block.builders.hydrate.addLine(
`@setAttributes(${this.var}, ${data});`
);
block.builders.update.addBlock(deindent`
@setAttributes(${this.var}, @getSpreadUpdate(${levels}, [
${updates.join(',\n')}
]));
`);
}
addEventHandlers(block: Block, allUsedContexts) {
const { generator } = this;
let eventHandlerUsesComponent = false;

@ -0,0 +1,78 @@
import { stringify } from '../../../utils/stringify';
import getExpressionPrecedence from '../../../utils/getExpressionPrecedence';
import Node from './Node';
import Attribute from '../Attribute';
import Block from '../../dom/Block';
export default function mungeAttribute(attribute: Node, block: Block): Attribute {
if (attribute.value === true) {
// attributes without values, e.g. <textarea readonly>
return {
name: attribute.name,
value: true,
dynamic: false
};
}
if (attribute.value.length === 0) {
return {
name: attribute.name,
value: `''`,
dynamic: false
};
}
if (attribute.value.length === 1) {
const value = attribute.value[0];
if (value.type === 'Text') {
// static attributes
return {
name: attribute.name,
value: isNaN(value.data) ? stringify(value.data) : value.data,
dynamic: false
};
}
// simple dynamic attributes
block.contextualise(value.expression); // TODO remove
const { dependencies, snippet } = value.metadata;
// TODO only update attributes that have changed
return {
name: attribute.name,
value: snippet,
dependencies,
dynamic: true
};
}
// otherwise we're dealing with a complex dynamic attribute
const allDependencies = new Set();
const value =
(attribute.value[0].type === 'Text' ? '' : `"" + `) +
attribute.value
.map((chunk: Node) => {
if (chunk.type === 'Text') {
return stringify(chunk.data);
} else {
block.contextualise(chunk.expression); // TODO remove
const { dependencies, snippet } = chunk.metadata;
dependencies.forEach((dependency: string) => {
allDependencies.add(dependency);
});
return getExpressionPrecedence(chunk.expression) <= 13 ? `(${snippet})` : snippet;
}
})
.join(' + ');
return {
name: attribute.name,
value,
dependencies: Array.from(allDependencies),
dynamic: true
};
}

@ -85,6 +85,13 @@ export function setAttribute(node, attribute, value) {
node.setAttribute(attribute, value);
}
export function setAttributes(node, attributes) {
for (var key in attributes) {
if (attributes[key] === undefined) removeAttribute(node, key);
else setAttribute(node, key, attributes[key]);
}
}
export function removeAttribute(node, attribute) {
node.removeAttribute(attribute);
}

@ -2,6 +2,7 @@ import { assign } from './utils.js';
import { noop } from './utils.js';
export * from './dom.js';
export * from './keyed-each.js';
export * from './spread.js';
export * from './transitions.js';
export * from './utils.js';

@ -0,0 +1,37 @@
export function getSpreadUpdate(levels, updates) {
var update = {};
var to_null_out = {};
var accounted_for = {};
var i = levels.length;
while (i--) {
var o = levels[i];
var n = updates[i];
if (n) {
for (var key in o) {
if (!(key in n)) to_null_out[key] = 1;
}
for (var key in n) {
if (!accounted_for[key]) {
update[key] = n[key];
accounted_for[key] = 1;
}
}
levels[i] = n;
} else {
for (var key in o) {
accounted_for[key] = 1;
}
}
}
for (var key in to_null_out) {
if (!(key in update)) update[key] = undefined;
}
return update;
}

@ -0,0 +1,7 @@
import isValidIdentifier from './isValidIdentifier';
import reservedNames from './reservedNames';
export default function quoteIfNecessary(name, legacy) {
if (!isValidIdentifier(name) || (legacy && reservedNames.has(name))) return `"${name}"`;
return name;
}

@ -10,12 +10,12 @@ export default {
assert.equal( div.dataset.named, 'value' );
component.set({ color: 'blue', props: { 'data-foo': 'baz', 'data-named': 'qux' } });
assert.equal( target.innerHTML, `<div data-named="value" data-foo="baz">blue</div>` );
assert.htmlEqual( target.innerHTML, `<div data-named="value" data-foo="baz">blue</div>` );
assert.equal( div.dataset.foo, 'baz' );
assert.equal( div.dataset.named, 'value' );
component.set({ color: 'blue', props: {} });
assert.equal( target.innerHTML, `<div data-named="value">blue</div>` );
assert.htmlEqual( target.innerHTML, `<div data-named="value">blue</div>` );
assert.equal( div.dataset.foo, undefined );
}
};

Loading…
Cancel
Save