import Fragment from './Fragment';
import Text from './Text';
import normalize from './normalize';
// weak map of the parents of NodeLists
const parents = new WeakMap();
/** Return a new {@link NodeList}.
* @extends Array
*/
class NodeList extends Array {
/**
* @param {Container} parent
* @param {Array<Node>} nodes
*/
constructor (parent, ...nodes) {
super();
parents.set(this, parent);
if (nodes.length) {
this.push(...nodes);
}
}
/**
* Return the innerHTML of the current {@link Container} as a String.
* @returns {string}
* @example
* container.innerHTML // returns a string of innerHTML
*/
get innerHTML () {
return this.map(
node => node.type === 'text'
? getInnerHtmlEncodedString(node.data)
: 'outerHTML' in node
? node.outerHTML
: String(node)
).join('');
}
/**
* Define the nodes of the current {@link NodeList} from a String.
* @param {string} innerHTML - Source being processed.
* @returns {void}
* @example
* nodeList.innerHTML = 'Hello <strong>world</strong>';
* nodeList.length; // 2
*/
set innerHTML (innerHTML) {
const parent = this.parent;
const Result = Object(parent.result).constructor;
if (Result) {
const nodes = new Result(innerHTML).root.nodes;
this.splice(0, this.length, ...nodes);
}
}
/**
* Return the parent of the current {@link NodeList}.
* @returns {Container}
*/
get parent () {
return parents.get(this);
}
/**
* Return the text content of the current {@link NodeList} as a String.
* @returns {string}
*/
get textContent () {
return this.map(
node => Object(node).textContent || ''
).join('');
}
/**
* Define the content of the current {@link NodeList} as a new {@link Text} {@link Node}.
* @returns {string}
*/
set textContent (textContent) {
this.splice(0, this.length, new Text({ data: textContent }));
}
/**
* Return a clone of the current {@link NodeList}.
* @param {Node} parent - New parent containing the cloned {@link NodeList}.
* @returns {NodeList}
*/
clone (parent) {
return new NodeList(parent, ...this.map(node => node.clone({}, true)));
}
/**
* Remove and return the last {@link Node} in the {@link NodeList}.
* @returns {Node}
*/
pop () {
const [ remove ] = this.splice(this.length - 1, 1);
return remove;
}
/**
* Add {@link Node}s to the end of the {@link NodeList} and return the new length of the {@link NodeList}.
* @returns {number}
*/
push (...nodes) {
const parent = this.parent;
const inserts = nodes.filter(node => node !== parent);
this.splice(this.length, 0, ...inserts);
return this.length;
}
/**
* Remove and return the first {@link Node} in the {@link NodeList}.
* @returns {Node}
*/
shift () {
const [ remove ] = this.splice(0, 1);
return remove;
}
/**
* Add and remove {@link Node}s to and from the {@link NodeList}.
*/
splice (start, ...args) {
const { length, parent } = this;
const startIndex = start > length ? length : start < 0 ? Math.max(length + start, 0) : Number(start) || 0;
const deleteCount = 0 in args ? Number(args[0]) || 0 : length;
const inserts = getNodeListArray(args.slice(1).filter(node => node !== parent));
for (let insert of inserts) {
insert.remove();
insert.parent = parent;
}
/** @type {Array<Node>} */
const removes = Array.prototype.splice.call(this, startIndex, deleteCount, ...inserts);
for (let remove of removes) {
delete remove.parent;
}
return removes;
}
/**
* Add {@link Node}s to the beginning of the {@link NodeList} and return the new length of the {@link NodeList}.
*/
unshift (...nodes) {
const parent = this.parent;
const inserts = nodes.filter(node => node !== parent);
this.splice(0, 0, ...inserts);
return this.length;
}
/**
* Return the current {@link NodeList} as a String.
* @example
* nodeList.toString() // returns ''
*/
toString () {
return this.join('');
}
/**
* Return the current {@link NodeList} as an Array.
* @returns {Array<Node|string>}
* @example
* nodeList.toJSON() // returns []
*/
toJSON () {
return Array.from(this).map(node => node.toJSON());
}
/**
* Return a new {@link NodeList} from an object.
* @param {Node|Array<Node>} nodes - Array or object of nodes.
* @example <caption>Return a NodeList from an array of text.</caption>
* NodeList.from([ 'test' ]) // returns NodeList [ Text { data: 'test' } ]
*/
static from (nodes) {
return new NodeList(new Fragment(), ...getNodeListArray(nodes));
}
}
export default NodeList;
/**
* Return an NodeList-compatible array from an array.
* @param {Array<Node>} nodes
* @private
*/
function getNodeListArray (nodes) {
// coerce nodes into an array
return Object(nodes).length
? Array.from(nodes).filter(
node => node != null
).map(normalize)
: [];
}
/**
* Return an innerHTML-encoded string.
* @private
*/
function getInnerHtmlEncodedString (string) {
return string.replace(
/&|<|>/g,
match => match === '&'
? '&'
: match === '<'
? '<'
: '>'
);
}