Files
postcss/lib/container.js
T

410 lines
12 KiB
JavaScript

var Node = require('./node');
var Declaration = require('./declaration');
// CSS node, that contain another nodes (like at-rules or rules with selectors)
class Container extends Node {
// Stringify container childs
stringifyContent(builder) {
if ( !this.rules && !this.decls ) return;
var i, last = this.list.length - 1;
if ( this.rules ) {
for ( i = 0; i < this.rules.length; i++ ) {
this.rules[i].stringify(builder, last == i);
}
} else if ( this.decls ) {
for ( i = 0; i < this.decls.length; i++ ) {
this.decls[i].stringify(builder, last != i || this.semicolon);
}
}
}
// Generate default spaces before }
defaultAfter() {
if ( this.list.length === 0 ) {
return '';
} else {
var last = this.list[0].before;
if ( typeof(last) != 'undefined' && last.indexOf("\n") == -1 ) {
return this.list[0].before;
} else {
return "\n";
}
}
}
// Stringify node with start (for example, selector) and brackets block
// with child inside
stringifyBlock(builder, start) {
var style = this.style();
if ( this.before ) builder(this.before);
builder(start, this, 'start');
this.stringifyContent(builder);
if ( style.after ) builder(style.after);
builder('}', this, 'end');
}
// Add child to end of list without any checks.
// Please, use `append()` method, `push()` is mostly for parser.
push(child) {
child.parent = this;
this.list.push(child);
return this;
}
// Execute `callback` on every child element. First arguments will be child
// node, second will be index.
//
// css.each( (rule, i) => {
// console.log(rule.type + ' at ' + i);
// });
//
// It is safe for add and remove elements to list while iterating:
//
// css.each( (rule) => {
// css.insertBefore( rule, addPrefix(rule) );
// # On next iteration will be next rule, regardless of that
// # list size was increased
// });
each(callback) {
if ( !this.lastEach ) this.lastEach = 0;
if ( !this.indexes ) this.indexes = { };
this.lastEach += 1;
var id = this.lastEach;
this.indexes[id] = 0;
var list = this.list;
if ( !list ) return;
var index, result;
while ( this.indexes[id] < list.length ) {
index = this.indexes[id];
result = callback(list[index], index);
if ( result === false ) break;
this.indexes[id] += 1;
}
delete this.indexes[id];
if ( result === false ) return false;
}
// Execute callback on every child in all rules inside.
//
// First argument will be child node, second will be index inside parent.
//
// css.eachInside( (node, i) => {
// console.log(node.type + ' at ' + i);
// });
//
// Also as `each` it is safe of insert/remove nodes inside iterating.
eachInside(callback) {
return this.each( (child, i) => {
var result = callback(child, i);
if ( result !== false && child.eachInside ) {
result = child.eachInside(callback);
}
if ( result === false ) return result;
});
}
// Execute callback on every declaration in all rules inside.
// It will goes inside at-rules recursivelly.
//
// First argument will be declaration node, second will be index inside
// parent rule.
//
// css.eachDecl( (decl, i) => {
// console.log(decl.prop + ' in ' + decl.parent.selector + ':' + i);
// });
//
// Also as `each` it is safe of insert/remove nodes inside iterating.
eachDecl(callback) {
// Different realization will be inside subclasses
}
// Execute callback on every block comment (only between rules
// and declarations, not inside selectors and values) in all rules inside.
//
// First argument will be comment node, second will be index inside
// parent rule.
//
// css.eachComment( (comment, i) => {
// console.log(comment.content + ' at ' + i);
// });
//
// Also as `each` it is safe of insert/remove nodes inside iterating.
eachComment(callback) {
return this.eachInside( (child, i) => {
if ( child.type == 'comment' ) {
var result = callback(child, i);
if ( result === false ) return result;
}
});
}
// Add child to container.
//
// css.append(rule);
//
// You can add declaration by hash:
//
// rule.append({ prop: 'color', value: 'black' });
append(child) {
var childs = this.normalize(child, this.list[this.list.length - 1]);
for ( child of childs ) {
this.list.push(child);
}
return this;
}
// Add child to beginning of container
//
// css.prepend(rule);
//
// You can add declaration by hash:
//
// rule.prepend({ prop: 'color', value: 'black' });
prepend(child) {
var childs = this.normalize(child, this.list[0], 'prepend').reverse();
for ( child of childs ) {
this.list.unshift(child);
}
for ( var id in this.indexes ) {
this.indexes[id] = this.indexes[id] + childs.length;
}
return this;
}
// Insert new `added` child before `exist`.
// You can set node object or node index (it will be faster) in `exist`.
//
// css.insertAfter(1, rule);
//
// You can add declaration by hash:
//
// rule.insertBefore(1, { prop: 'color', value: 'black' });
insertBefore(exist, add) {
exist = this.index(exist);
var type = exist === 0 ? 'prepend' : false;
var childs = this.normalize(add, this.list[exist], type).reverse();
for ( var child of childs ) {
this.list.splice(exist, 0, child);
}
for ( var id in this.indexes ) {
this.indexes[id] = this.indexes[id] + childs.length;
}
return this;
}
// Insert new `added` child after `exist`.
// You can set node object or node index (it will be faster) in `exist`.
//
// css.insertAfter(1, rule);
//
// You can add declaration by hash:
//
// rule.insertAfter(1, { prop: 'color', value: 'black' });
insertAfter(exist, add) {
exist = this.index(exist);
var childs = this.normalize(add, this.list[exist]).reverse();
for ( var child of childs ) {
this.list.splice(exist + 1, 0, child);
}
for ( var id in this.indexes ) {
this.indexes[id] = this.indexes[id] + childs.length;
}
return this;
}
// Remove `child` by index or node.
//
// css.remove(2);
remove(child) {
child = this.index(child);
this.list.splice(child, 1);
for ( var id in this.indexes ) {
var index = this.indexes[id];
if ( index >= child ) {
this.indexes[id] = index - 1;
}
}
return this;
}
// Return true if all childs return true in `condition`.
// Just shorcut for `list.every`.
every(condition) {
return this.list.every(condition);
}
// Return true if one or more childs return true in `condition`.
// Just shorcut for `list.some`.
some(condition) {
return this.list.some(condition);
}
// Return index of child
index(child) {
if ( typeof(child) == 'number' ) {
return child;
} else {
return this.list.indexOf(child);
}
}
// Shortcut to get first child
get first() {
if ( !this.list ) return undefined;
return this.list[0];
}
// Shortcut to get first child
get last() {
if ( !this.list ) return undefined;
return this.list[this.list.length - 1];
}
// Shortcut to get current list
get list() {
return this.rules || this.decls;
}
// Normalize child before insert. Copy before from `sample`.
normalize(child, sample) {
var childs;
if ( child.type == 'root' ) {
childs = child.rules;
} else if ( Array.isArray(child) ) {
childs = child.map( i => i.clone() );
} else {
if ( child.parent ) {
child = child.clone();
}
childs = [child];
}
for ( child of childs ) {
child.parent = this;
if ( typeof(child.before) == 'undefined' && sample ) {
child.before = sample.before;
}
}
return childs;
}
}
// Container with another rules, like this.media at-rule
Container.WithRules = class extends Container {
constructor(defaults) {
this.rules = [];
super(defaults);
}
// Execute `callback` on every declaration in all rules inside.
// It will goes inside at-rules recursivelly.
//
// See documentation in `Container#eachDecl`.
eachDecl(callback) {
return this.each( (child) => {
if ( child.eachDecl ) {
var result = child.eachDecl(callback);
if ( result === false ) return result;
}
});
}
// Execute `callback` on every rule in conatiner and inside child at-rules.
//
// First argument will be rule node, second will be index inside parent.
//
// css.eachRule( (rule, i) => {
// if ( parent.type == 'atrule' ) {
// console.log(rule.selector + ' in ' + rule.parent.name);
// } else {
// console.log(rule.selector + ' at ' + i);
// }
// });
eachRule(callback) {
return this.each( (child, i) => {
var result;
if ( child.type == 'rule' ) {
result = callback(child, i);
} else if ( child.eachRule ) {
result = child.eachRule(callback);
}
if ( result === false ) return result;
});
}
// Execute `callback` on every at-rule in conatiner and inside at-rules.
//
// First argument will be at-rule node, second will be index inside parent.
//
// css.eachAtRule( (atrule, parent, i) => {
// if ( parent.type == 'atrule' ) {
// console.log(atrule.name + ' in ' + atrule.parent.name);
// } else {
// console.log(atrule.name + ' at ' + i);
// }
// });
eachAtRule(callback) {
return this.eachInside( (child, i) => {
if ( child.type == 'atrule' ) {
var result = callback(child, i);
if ( result === false ) return result;
}
});
}
};
// Container with another rules, like this.media at-rule
Container.WithDecls = class extends Container {
constructor(defaults) {
this.decls = [];
super(defaults);
}
// Allow to define new declaration as hash
normalize(child, sample, type) {
if ( !child.type && !Array.isArray(child) ) {
child = new Declaration(child);
}
return super.normalize(child, sample, type);
}
// Execute callback on every declaration.
//
// See documentation in `Container#eachDecl`.
eachDecl(callback) {
return this.each( (child, i) => {
if ( child.type == 'decl' ) {
var result = callback(child, i);
if ( result === false ) return result;
}
});
}
};
module.exports = Container;