vue-frag
Version:
Vue 2 fragment directive to return multiple root elements
266 lines (265 loc) • 9.63 kB
JavaScript
(function(global, factory) {
typeof exports === "object" && typeof module !== "undefined" ? module.exports = factory() : typeof define === "function" && define.amd ? define(factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self,
global.Frag = factory());
})(this, (function() {
"use strict";
var $placeholder = Symbol();
var $fakeParent = Symbol();
var $nextSiblingPatched = Symbol();
var $childNodesPatched = Symbol();
var isFrag = function isFrag(node) {
return "frag" in node;
};
var parentNodeDescriptor = {
get: function get() {
return this[$fakeParent] || this.parentElement;
},
configurable: true
};
var patchParentNode = function patchParentNode(node, fakeParent) {
if ($fakeParent in node) {
return;
}
node[$fakeParent] = fakeParent;
Object.defineProperty(node, "parentNode", parentNodeDescriptor);
};
var nextSiblingDescriptor = {
get: function get() {
var childNodes = this.parentNode.childNodes;
var index = childNodes.indexOf(this);
if (index > -1) {
return childNodes[index + 1] || null;
}
return null;
}
};
var patchNextSibling = function patchNextSibling(node) {
if ($nextSiblingPatched in node) {
return;
}
node[$nextSiblingPatched] = true;
Object.defineProperty(node, "nextSibling", nextSiblingDescriptor);
};
var getTopFragment = function getTopFragment(node, fromParent) {
while (node.parentNode !== fromParent) {
var _node = node, parentNode = _node.parentNode;
if (parentNode) {
node = parentNode;
}
}
return node;
};
var getChildNodes;
var getChildNodesWithFragments = function getChildNodesWithFragments(node) {
if (!getChildNodes) {
var _childNodesDescriptor = Object.getOwnPropertyDescriptor(Node.prototype, "childNodes");
getChildNodes = _childNodesDescriptor.get;
}
var realChildNodes = getChildNodes.apply(node);
var childNodes = Array.from(realChildNodes).map((function(childNode) {
return getTopFragment(childNode, node);
}));
return childNodes.filter((function(childNode, index) {
return childNode !== childNodes[index - 1];
}));
};
var childNodesDescriptor = {
get: function get() {
return this.frag || getChildNodesWithFragments(this);
}
};
var firstChildDescriptor = {
get: function get() {
return this.childNodes[0] || null;
}
};
function hasChildNodes() {
return this.childNodes.length > 0;
}
var patchChildNodes = function patchChildNodes(node) {
if ($childNodesPatched in node) {
return;
}
node[$childNodesPatched] = true;
Object.defineProperties(node, {
childNodes: childNodesDescriptor,
firstChild: firstChildDescriptor
});
node.hasChildNodes = hasChildNodes;
};
function before() {
var _this$frag$;
(_this$frag$ = this.frag[0]).before.apply(_this$frag$, arguments);
}
function remove() {
var frag = this.frag;
var removed = frag.splice(0, frag.length);
removed.forEach((function(node) {
node.remove();
}));
}
var getFragmentLeafNodes = function getFragmentLeafNodes(children) {
var _Array$prototype;
return (_Array$prototype = Array.prototype).concat.apply(_Array$prototype, children.map((function(childNode) {
return isFrag(childNode) ? getFragmentLeafNodes(childNode.frag) : childNode;
})));
};
var addPlaceholder = function addPlaceholder(node, insertBeforeNode) {
var placeholder = node[$placeholder];
insertBeforeNode.before(placeholder);
patchParentNode(placeholder, node);
node.frag.unshift(placeholder);
};
function removeChild(node) {
if (isFrag(this)) {
var hasChildInFragment = this.frag.indexOf(node);
if (hasChildInFragment > -1) {
var _this$frag$splice = this.frag.splice(hasChildInFragment, 1), removedNode = _this$frag$splice[0];
if (this.frag.length === 0) {
addPlaceholder(this, removedNode);
}
node.remove();
}
} else {
var children = getChildNodesWithFragments(this);
var hasChild = children.indexOf(node);
if (hasChild > -1) {
node.remove();
}
}
return node;
}
function insertBefore(insertNode, insertBeforeNode) {
var _this = this;
var insertNodes = insertNode.frag || [ insertNode ];
if (isFrag(this)) {
if (insertNode[$fakeParent] === this && insertNode.parentElement) {
return insertNode;
}
var _frag = this.frag;
if (insertBeforeNode) {
var index = _frag.indexOf(insertBeforeNode);
if (index > -1) {
_frag.splice.apply(_frag, [ index, 0 ].concat(insertNodes));
insertBeforeNode.before.apply(insertBeforeNode, insertNodes);
}
} else {
var _lastNode = _frag[_frag.length - 1];
_frag.push.apply(_frag, insertNodes);
_lastNode.after.apply(_lastNode, insertNodes);
}
removePlaceholder(this);
} else if (insertBeforeNode) {
if (this.childNodes.includes(insertBeforeNode)) {
insertBeforeNode.before.apply(insertBeforeNode, insertNodes);
}
} else {
this.append.apply(this, insertNodes);
}
insertNodes.forEach((function(node) {
patchParentNode(node, _this);
}));
var lastNode = insertNodes[insertNodes.length - 1];
patchNextSibling(lastNode);
return insertNode;
}
function appendChild(node) {
if (node[$fakeParent] === this && node.parentElement) {
return node;
}
var frag = this.frag;
var lastChild = frag[frag.length - 1];
lastChild.after(node);
patchParentNode(node, this);
removePlaceholder(this);
frag.push(node);
return node;
}
var removePlaceholder = function removePlaceholder(node) {
var placeholder = node[$placeholder];
if (node.frag[0] === placeholder) {
node.frag.shift();
placeholder.remove();
}
};
var innerHTMLDescriptor = {
set: function set(htmlString) {
var _this2 = this;
if (this.frag[0] !== this[$placeholder]) {
this.frag.slice().forEach((function(child) {
return _this2.removeChild(child);
}));
}
if (htmlString) {
var domify = document.createElement("div");
domify.innerHTML = htmlString;
Array.from(domify.childNodes).forEach((function(node) {
_this2.appendChild(node);
}));
}
},
get: function get() {
return "";
}
};
var frag = {
inserted: function inserted(element) {
var parentNode = element.parentNode, nextSibling = element.nextSibling, previousSibling = element.previousSibling;
var childNodes = Array.from(element.childNodes);
var placeholder = document.createComment("");
if (childNodes.length === 0) {
childNodes.push(placeholder);
}
element.frag = childNodes;
element[$placeholder] = placeholder;
var fragment = document.createDocumentFragment();
fragment.append.apply(fragment, getFragmentLeafNodes(childNodes));
element.replaceWith(fragment);
childNodes.forEach((function(node) {
patchParentNode(node, element);
patchNextSibling(node);
}));
patchChildNodes(element);
Object.assign(element, {
remove: remove,
appendChild: appendChild,
insertBefore: insertBefore,
removeChild: removeChild,
before: before
});
Object.defineProperty(element, "innerHTML", innerHTMLDescriptor);
if (parentNode) {
Object.assign(parentNode, {
removeChild: removeChild,
insertBefore: insertBefore
});
patchParentNode(element, parentNode);
patchChildNodes(parentNode);
}
if (nextSibling) {
patchNextSibling(element);
}
if (previousSibling) {
patchNextSibling(previousSibling);
}
},
unbind: function unbind(element) {
element.remove();
}
};
var Fragment = {
name: "Fragment",
directives: {
frag: frag
},
render: function render(h) {
return h("div", {
directives: [ {
name: "frag"
} ]
}, this.$slots["default"]);
}
};
frag.Fragment = Fragment;
return frag;
}));