react-reparenting
Version:
The reparenting tools for React
1,260 lines (1,021 loc) • 43.3 kB
JavaScript
/**
* React-reparenting v0.6.1
* https://paol-imi.github.io/react-reparenting
* Copyright (c) 2020-present, Paol-imi
* https://github.com/Paol-imi/react-reparenting/blob/master/LICENSE
* @license MIT
*/
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var React = require('react');
var _classCallCheck = require('@babel/runtime/helpers/classCallCheck');
var _assertThisInitialized = require('@babel/runtime/helpers/assertThisInitialized');
var _inherits = require('@babel/runtime/helpers/inherits');
var _possibleConstructorReturn = require('@babel/runtime/helpers/possibleConstructorReturn');
var _getPrototypeOf = require('@babel/runtime/helpers/getPrototypeOf');
var _wrapNativeSuper = require('@babel/runtime/helpers/wrapNativeSuper');
var _defineProperty = require('@babel/runtime/helpers/defineProperty');
var _createClass = require('@babel/runtime/helpers/createClass');
var _typeof = require('@babel/runtime/helpers/typeof');
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
var _classCallCheck__default = /*#__PURE__*/_interopDefaultLegacy(_classCallCheck);
var _assertThisInitialized__default = /*#__PURE__*/_interopDefaultLegacy(_assertThisInitialized);
var _inherits__default = /*#__PURE__*/_interopDefaultLegacy(_inherits);
var _possibleConstructorReturn__default = /*#__PURE__*/_interopDefaultLegacy(_possibleConstructorReturn);
var _getPrototypeOf__default = /*#__PURE__*/_interopDefaultLegacy(_getPrototypeOf);
var _wrapNativeSuper__default = /*#__PURE__*/_interopDefaultLegacy(_wrapNativeSuper);
var _defineProperty__default = /*#__PURE__*/_interopDefaultLegacy(_defineProperty);
var _createClass__default = /*#__PURE__*/_interopDefaultLegacy(_createClass);
var _typeof__default = /*#__PURE__*/_interopDefaultLegacy(_typeof);
/**
* The host environment.
* Default configuration to work with ReactDOM renderer.
*/
var Env = {
appendChildToContainer: function appendChildToContainer(container, child) {
container.appendChild(child);
},
insertInContainerBefore: function insertInContainerBefore(container, child, before) {
container.insertBefore(child, before);
},
removeChildFromContainer: function removeChildFromContainer(container, child) {
container.removeChild(child);
},
isElement: function isElement(_, stateNode) {
return stateNode instanceof Element;
}
};
/**
* Configure the host environment.
*
* @param configuration - The configuration.
*/
function configure(configuration) {
return Object.assign(Env, configuration);
}
/**
* Return the child fiber at the given index or null if the parent has no children.
* If the index provided is greater than the number of children the last child is returned.
*
* @param parent - The parent fiber.
* @param index - The index of the child fiber to find.
* @returns - The child fiber found or null.
*/
function findChildFiberAt(parent, index) {
// The first child.
var child = parent.child; // If the parent has no children.
if (child === null) {
return null;
}
if (index === -1) {
// Find the last child.
while (child.sibling) {
child = child.sibling;
}
} else {
// Find the child at the given index.
while (child.sibling && index > 0) {
index -= 1;
child = child.sibling;
}
}
return child;
}
/**
* Return the child fiber with the given key or null if it is not found.
*
* @param parent - The parent fiber.
* @param key - The key of the child fiber to find.
* @returns - The child fiber found or null.
*/
function findChildFiber(parent, key) {
// The first child.
var child = parent.child; // If the parent has no children.
if (child === null) {
return null;
} // If the fiber to find is the first one.
if (child.key === key) {
return child;
} // Find the fiber in the siblings.
return findSiblingFiber(child, key);
}
/**
* Return the fiber before the one with the given key or null if it is not found.
* If the fiber with the given key is the first child of the parent, the parent is returned.
*
* @param parent - The parent fiber.
* @param key - The key of the child fiber.
* @returns - The fiber found or null.
*/
function findPreviousFiber(parent, key) {
var child = parent.child; // If the parent has no child.
if (child === null) {
return null;
} // If the fiber to find is the first one.
if (child.key === key) {
return parent;
}
var _child = child,
sibling = _child.sibling; // Find the previous sibling.
while (sibling) {
// If the fiber is found.
if (sibling.key === key) {
return child;
}
child = sibling;
sibling = child.sibling;
}
return null;
}
/**
* Return the child fiber with the given key or null if it is not found.
*
* @param parent - The parent fiber.
* @param key - The key of the child fiber to find.
* @returns - The fiber found or null.
*/
function findSiblingFiber(fiber, key) {
var sibling = fiber.sibling; // Find the child with the given key.
while (sibling && sibling.key !== key) {
sibling = sibling.sibling;
}
return sibling;
}
/**
* Add a child fiber in a parent fiber at the given index and return the actual
* index in which it is added.
* If the index is -1 the fiber is added at the bottom.
* If the index provided is greater than the number of children available the
* fiber is added at the bottom.
*
* @param parent - The parent fiber in which to add the child fiber.
* @param child - The child fiber to add.
* @param index - The index in which to add the fiber.
* @returns - The index in which the child fiber is added.
*/
function addChildFiberAt(parent, child, index) {
// Add the fiber at the bottom.
if (index === -1) return appendChildFiber(parent, child); // Add the fiber at the beginning.
if (index === 0) return prependChildFiber(parent, child); // Find the previous sibling.
// At this point we are sure that the index is greater than 0.
var previousSibling = findChildFiberAt(parent, index - 1); // If there are no children, the fiber is added as the only child.
if (previousSibling === null) {
return prependChildFiber(parent, child);
} // Add the fiber as sibling of the previous one.
return addSiblingFiber(previousSibling, child);
}
/**
* Add a child fiber in a parent fiber before the child fiber with the given
* key and return the index in which it is added.
* If the key is not found the fiber is added at the bottom.
*
* @param parent - The parent fiber in which to add the child fiber.
* @param child - The child fiber to add.
* @param key - The key of the previous child fiber.
* @returns - The index in which the child fiber is added.
*/
function addChildFiberBefore(parent, child, key) {
// Find the previous fiber.
var previousFiber = findPreviousFiber(parent, key); // If the previous child fiber is not found add the child fiber at the bottom.
if (previousFiber === null) {
return appendChildFiber(parent, child);
} // If the fiber with the given key is the first one.
if (previousFiber === parent) {
return prependChildFiber(parent, child);
} // Add the fiber as sibling of the previous one.
return addSiblingFiber(previousFiber, child);
}
/**
* Add a child fiber at the bottom and return the index in which it is added.
*
* @param parent - The parent fiber in which to add the child fiber.
* @param child - The child fiber to add.
* @returns - The index in which the fiber is added.
*/
function appendChildFiber(parent, child) {
var previousFiber = findChildFiberAt(parent, -1); // If the parent fiber has no children.
if (previousFiber === null) {
return prependChildFiber(parent, child);
}
return addSiblingFiber(previousFiber, child);
}
/**
* Add a sibling fiber after a fiber and return the index in which it is added.
*
* @param fiber - The fiber.
* @param sibling - The sibling fiber to add.
* @returns - The index in which the sibling fiber is added.
*/
function addSiblingFiber(fiber, sibling) {
var oldSibling = fiber.sibling;
var index = fiber.index + 1; // Update the sibling fiber fields.
fiber.sibling = sibling;
sibling["return"] = fiber["return"];
sibling.sibling = oldSibling;
return index;
}
/**
* Add a child fiber at the beginning child and retun 0.
*
* @param parent - The parent fiber.
* @param child - The child fiber.
* @returns - The index in which the fiber is added.
*/
function prependChildFiber(parent, child) {
var oldFirstChild = parent.child; // Update the child fiber fields.
parent.child = child;
child.sibling = oldFirstChild;
child["return"] = parent;
return 0;
}
var oldConvention = React__default['default'].version.startsWith('16');
var Int = {
// Prefix of the attribute that contains the fiber in a DOM node.
nodePrefix: oldConvention ? // React 16.x.x
'__reactInternalInstance$' : // React 17.x.x
'__reactFiber$',
// Attribute that contains the fiber in a class component.
componentAttribute: oldConvention ? // React 16.x.x
'_reactInternalFiber' : // React 17.x.x
'_reactInternals'
};
function _createSuper(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct(); return function _createSuperInternal() { var Super = _getPrototypeOf__default['default'](Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf__default['default'](this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn__default['default'](this, result); }; }
function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
var prefix = 'Invariant failed'; // Invariant error instance.
var Invariant = /*#__PURE__*/function (_Error) {
_inherits__default['default'](Invariant, _Error);
var _super = _createSuper(Invariant);
function Invariant() {
var _this;
_classCallCheck__default['default'](this, Invariant);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this = _super.call.apply(_super, [this].concat(args));
_defineProperty__default['default'](_assertThisInitialized__default['default'](_this), "name", 'Invariant');
return _this;
}
return Invariant;
}( /*#__PURE__*/_wrapNativeSuper__default['default'](Error));
/**
* Throw an error if the condition fails.
* The message is tripped in production.
*
* @param condition - The condition.
* @param message - The error message.
*/
function invariant(condition, message) {
if (condition) return;
{
// When not in production we allow the message to pass through.
throw new Invariant("".concat(prefix, ": ").concat(message || ''));
}
}
/**
* Return the first valid fiber or null.
*
* @param fiber - The fiber to start looking for.
* @param next - The callback to get the next fiber to iterate.
* @param stop - The callback to check if the fiber is found.
* @returns - The found fiber or null.
*/
function getFiberFromPath(fiber, next, stop) {
while (fiber) {
if (stop(fiber)) {
return fiber;
} // Search in the next instance.
fiber = next(fiber);
}
return null;
}
/**
* The fiber could be in the current tree or in the work-in-progress tree.
* Return the fiber in the current tree, it could be the given fiber or its alternate.
* For now, no special cases are handled (It doesn't make sense to manage
* portals as this package was created to avoid them).
*
* @param fiber - The fiber.
* @returns - The current fiber.
*/
function getCurrentFiber(fiber) {
// If there is no alternate we are shure that it is the current fiber.
if (fiber.alternate === null) {
return fiber;
} // Get the top fiber.
var topFiber = fiber;
while (topFiber["return"] !== null) {
topFiber = topFiber["return"];
} // The top fiber must be an HoostRoot.
invariant(topFiber.stateNode !== null && 'current' in topFiber.stateNode, 'Unable to find node on an unmounted component.');
var rootFiber = topFiber.stateNode;
var topCurrentFiber = rootFiber.current; // If true we are in the current tree.
return topCurrentFiber === topFiber ? fiber : fiber.alternate;
}
/**
* Returns the fiber of the given element (for now limited to DOM nodes).
*
* @param element - The element.
* @returns - The fiber.
*/
function getFiberFromElementInstance(element) {
var internalKey = Object.keys(element).find(function (key) {
return key.startsWith(Int.nodePrefix);
});
invariant(typeof internalKey === 'string', "Cannot find the ".concat(Int.nodePrefix, ". ") + "This is a problem with React-reparenting, please file an issue."); // The internal instance is not present in the types definition.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return element[internalKey];
}
/**
* Returns the fiber of the given class component instance.
*
* @param instance - The class component instance.
* @returns - The fiber.
*/
function getFiberFromClassInstance(instance) {
invariant(Int.componentAttribute in instance, 'Cannot find the _reactInternalFiber. ' + 'This is a problem with React-reparenting, please file an issue.'); // The internal fiber is not present in the types definition.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
return instance[Int.componentAttribute];
}
/**
* Update the indices of a fiber and its siblings.
* Return the last sibling index.
*
* @param fiber - The fiber.
* @param index - The index of the fiber.
* @returns - The last sibling index.
*/
function updateFibersIndex(fiber, index) {
while (fiber) {
fiber.index = index;
fiber = fiber.sibling;
index += 1;
}
return index - 1;
}
/**
* Update the debug fields.
* I have not yet inquired about how the _debug fields are chosen.
* For now only the owner and source are set based on the siblings/parent fields.
*
* @param child - The child fiber.
* @param parent - The parent fiber.
*/
function updateFiberDebugFields(child, parent) {
// The fiber from wich to copy the debug fields.
var fiberToCopy; // Try to find a fiber to copy.
if (parent.child === child) {
if (child.sibling === null) {
fiberToCopy = parent;
} else {
fiberToCopy = child.sibling;
}
} else {
fiberToCopy = parent.child || parent;
}
child._debugOwner = fiberToCopy._debugOwner;
child._debugSource = fiberToCopy._debugSource;
}
/**
* Prints a warning in the console.
*
* @param message - The warning message.
*/
function warning(message) {
var text = "Warning: ".concat(message); // Check console for IE9 support which provides console
// only with open devtools.
if (typeof console !== 'undefined') {
// eslint-disable-next-line no-console
console.error(text);
} // Throwing an error and catching it immediately
// to improve debugging.
// A consumer can use 'pause on caught exceptions'
// https://github.com/facebook/react/issues/4216
try {
throw Error(text);
} catch (x) {} // eslint-disable-line no-empty
}
/**
* Add a child in a parent and return the index in which it is added.
* The position of the child can be chosen by providing a key (string) or an index (number).
* If a key (string) is provided the child will be added after the one with that key.
* The child is added at the bottom if none of the children have that key.
* If an index (number) is provided the child will be added in that position.
* The child is added at the bottom if -1 is provided or the index is greater
* than the number of children.
* The method will also try to add the elements connected to the child (e.g. DOM elements),
* it is possible to disable this function using the skipUpdate parameter.
*
* @param parent - The parent in which to add the child.
* @param child - The child to add.
* @param position - The position in which to add the child.
* @param skipUpdate - Whether to add or not the elements.
* @returns - The index in which the child is added.
*/
function addChild(parent, child, position, skipUpdate) {
invariant(typeof position !== 'number' || position >= -1, "The index provided to add the child must be " + "greater than or equal to -1, found: ".concat(position, ".")); // The index in which the child is added.
var index; // Add the child.
if (typeof position === 'number') {
index = addChildFiberAt(parent, child, position);
} else {
index = addChildFiberBefore(parent, child, position);
}
{
if (typeof position === 'number') {
// If the child is added in a different position.
if (position !== -1 && index !== position) {
warning("The index provided is greater than the number of children, " + "the child is added at the bottom.");
}
} else {
// If no children have the provided key.
if (findPreviousFiber(parent, position) === null) {
warning("No child with the key: '".concat(position, "' has been found, ") + "the child is added at the bottom.");
}
}
} // Update the child fields.
updateFibersIndex(child, index);
{
updateFiberDebugFields(child, parent);
} // If there are the alternates.
if (child.alternate === null || parent.alternate === null) {
if (child.alternate !== null) {
// The React team has done such a good job with the reconciler that we can simply
// leave the alternate attached (although the parent does not yet exist)
// and the reconciler will update it during the next render.
// Removing it would take a lot of work (sync all the subtree and update the stateNodes references).
child.alternate["return"] = null;
child.alternate.sibling = null;
}
} else {
// Add the alternate child.
if (typeof position === 'number') {
addChildFiberAt(parent.alternate, child.alternate, position);
} else {
addChildFiberBefore(parent.alternate, child.alternate, position);
} // Update the alternate child fields.
updateFibersIndex(child.alternate, index);
{
updateFiberDebugFields(child.alternate, parent);
}
} // If we don't have to send the elements we can return here.
if (skipUpdate) {
return index;
} // Get the fibers that belong to the container elements.
var containerFiber = getFiberFromPath(parent, function (fiber) {
return fiber["return"];
}, function (fiber) {
return Env.isElement(fiber.elementType, fiber.stateNode);
}); // Get the fibers that belong to the child element.
var elementFiber = getFiberFromPath(child, function (fiber) {
return fiber.child;
}, function (fiber) {
return Env.isElement(fiber.elementType, fiber.stateNode);
}); // Container element not found.
if (containerFiber === null) {
{
warning('Cannot find the container element, neither the parent nor any ' + 'component before it seems to generate an element instance. ' + 'You should manually send the element and use the `skipUpdate` option.');
}
return index;
} // Child element not found.
if (elementFiber === null) {
{
warning('Cannot find the child element. ' + 'You should manually send the element and use the `skipUpdate` option.');
}
return index;
} // Get the elements instances.
var container = containerFiber.stateNode;
var element = elementFiber.stateNode; // Add the child element.
if (child.sibling === null) {
// Append the child to the container.
Env.appendChildToContainer(container, element);
} else {
// Get the fibers that belong to the previous element.
var beforeFiber = getFiberFromPath(child.sibling, function (fiber) {
return fiber.child;
}, function (fiber) {
return Env.isElement(fiber.elementType, fiber.stateNode);
});
if (beforeFiber !== null) {
var before = beforeFiber.stateNode; // Insert the child element in the container.
Env.insertInContainerBefore(container, element, before);
} // Previous element not found.
{
if (beforeFiber === null) {
warning('Cannot find the previous element. ' + 'You should manually send the element and use the `skipUpdate` option.');
}
}
}
return index;
}
/**
* Remove the child fiber at the given index and return it or null if it not exists.
*
* @param parent - The parent fiber.
* @param index - The index of the child fiber to remove.
* @returns - The removed fiber or null.
*/
function removeChildFiberAt(parent, index) {
// Remove the first child fiber.
if (index === 0) {
return removeFirstChildFiber(parent);
} // Find the previous fiber.
// At this point we are shure that index > 0.
var previousFiber = findChildFiberAt(parent, index - 1); // If the fiber is not found.
if (previousFiber === null) {
return null;
} // Remove the sibling.
return removeSiblingFiber(previousFiber);
}
/**
* Remove the child fiber with the given key and return it or null if it not exists.
*
* @param parent - The parent fiber.
* @param key - The key of the child fiber to remove.
* @returns - The removed fiber or null.
*/
function removeChildFiber(parent, key) {
// Find the previous fiber.
var previousFiber = findPreviousFiber(parent, key); // If the fiber is not found.
if (previousFiber === null) {
return null;
} // If The fiber with the given key is the first one.
if (previousFiber === parent) {
return removeFirstChildFiber(parent);
} // Add the fiber as sibling of the previous one.
return removeSiblingFiber(previousFiber);
}
/**
* Remove the first child fiber of the given parent and return it or null if it not exists.
*
* @param parent - The parent fiber.
* @returns - The removed child fiber or null.
*/
function removeFirstChildFiber(parent) {
var removed = parent.child; // If the parent has no children return null.
if (removed === null) {
return null;
} // Update fiber references.
parent.child = removed.sibling;
return removed;
}
/**
* Remove the next sibling from a fiber and return it or null if it not exist.
*
* @param fiber - The fiber.
* @returns - The removed sibling fiber or null.
*/
function removeSiblingFiber(fiber) {
var removed = fiber.sibling; // If the fiber has no sibling return null.
if (removed === null) {
return null;
} // Update fiber references.
fiber.sibling = removed.sibling;
return removed;
}
/**
* Remove a child from its parent and return it.
* The child to remove can be chosen by providing its key (string) or its index (number).
* The method will also try to remove the elements connected to the child (e.g. DOM elements),
* it is possible to disable this function using the skipUpdate parameter.
* If the child is not found null is returned.
*
* @param parent - The parent from which to remove the child.
* @param childSelector - The child selector.
* @param skipUpdate - Whether to add or not the elements.
* @returns - The removed child or null.
*/
function removeChild(parent, childSelector, skipUpdate) {
invariant(typeof childSelector !== 'number' || childSelector >= 0, "The index provided to remove the child must be " + "greater than or equal to 0, found: ".concat(childSelector, ".")); // The removed child.
var child = null; // Remove the child.
if (typeof childSelector === 'number') {
child = removeChildFiberAt(parent, childSelector);
} else {
child = removeChildFiber(parent, childSelector);
} // If the child is not found return null.
if (child === null) {
{
if (typeof childSelector === 'number') {
// Invalid index.
warning("Cannot find and remove the child at index: ".concat(childSelector, "."));
} else {
// Invalid key.
warning("No child with the key: '".concat(childSelector, "' has been found, ") + "the child cannot be removed.");
}
}
return null;
} // If there are siblings their indices need to be updated.
if (child.sibling !== null) {
updateFibersIndex(child.sibling, child.index);
} // If There is no alternate we can return here.
if (child.alternate !== null && parent.alternate !== null) {
// The alternate child.
var alternate = null; // Remove the alternate child.
if (typeof childSelector === 'number') {
alternate = removeChildFiberAt(parent.alternate, childSelector);
} else {
alternate = removeChildFiber(parent.alternate, childSelector);
} // We should find it because we are shure it exists.
invariant(alternate !== null, 'The alternate child has not been removed. ' + 'This is a bug in React-reparenting, please file an issue.'); // If there are siblings their indices need to be updated.
if (alternate.sibling !== null) {
updateFibersIndex(alternate.sibling, alternate.index);
}
} // If we don't have to send the elements we can return here.
if (skipUpdate) {
return child;
} // Get the fibers that belong to the container elements.
var containerFiber = getFiberFromPath(parent, function (fiber) {
return fiber["return"];
}, function (fiber) {
return Env.isElement(fiber.elementType, fiber.stateNode);
}); // Get the fibers that belong to the child element.
var elementFiber = getFiberFromPath(child, function (fiber) {
return fiber.child;
}, function (fiber) {
return Env.isElement(fiber.elementType, fiber.stateNode);
}); // Container element not found.
if (containerFiber === null) {
{
warning('Cannot find the container element, neither the parent nor any ' + 'component before it seems to generate an element instance. ' + 'You should manually send the element and use the `skipUpdate` option.');
}
return child;
} // Child element not found.
if (elementFiber === null) {
{
warning('Cannot find the child element. ' + 'You should manually send the element and use the `skipUpdate` option.');
}
return child;
} // Get the elements instances.
var container = containerFiber.stateNode;
var element = elementFiber.stateNode; // Remove the element instance.
Env.removeChildFromContainer(container, element);
return child;
}
/**
* The ParentFiber implement the logic to manage a fiber of a parent component.
* It provides simple methods for managing reparenting, such as add(), remove() and send().
*/
var ParentFiber = /*#__PURE__*/function () {
function ParentFiber() {
_classCallCheck__default['default'](this, ParentFiber);
_defineProperty__default['default'](this, "fiber", null);
_defineProperty__default['default'](this, "findFiber", void 0);
}
_createClass__default['default'](ParentFiber, [{
key: "setFiber",
value:
/**
* Parent fiber setter.
*
* @param fiber - The parent fiber to manage.
*/
function setFiber(fiber) {
this.fiber = fiber;
}
/**
* FindFiber method setter.
*
* @param findFiber - The method.
*/
}, {
key: "setFinder",
value: function setFinder(findFiber) {
this.findFiber = findFiber;
}
/**
* Parent fiber getter.
* This is necessary to always get the
* reference of the current fiber.
*
* @returns - The current parent fiber.
*/
}, {
key: "getCurrent",
value: function getCurrent() {
invariant(this.fiber !== null, 'Cannot call ParentFiber methods before it is initialized.'); // Find the current fiber.
var current = getCurrentFiber(this.fiber);
return typeof this.findFiber === 'function' ? this.findFiber(current) : current;
}
/**
* Add a child fiber in this instance and return the index in which it is added.
* The position can be chosen by providing a key (string) or by providing an index (number).
* If a key (string) is provided the child will be added after the one with that key.
* The child is added at the bottom if none of the children have that key.
* If an index (number) is provided the child will be added in that position.
* The child is added at the bottom if -1 is provided or the index is greater
* than the number of children.
* The method will also try to add the elements connected to the fibers (e.g. DOM elements),
* to disable this function you can use the skipUpdate parameter.
*
* @param child - The child fiber to add.
* @param position - The position in which to add the child fiber.
* @param skipUpdate - Whether to add or not the elements.
* @returns - The index in which the child fiber is added.
*/
}, {
key: "addChild",
value: function addChild$1(child, position, skipUpdate) {
return addChild(this.getCurrent(), child, position, skipUpdate);
}
/**
* Remove a child fiber from this instance and return it.
* The child to remove can be chosen by providing its key (string) or by
* providing its index (number).
* The method will also try to remove the elements connected to the fibers (e.g. DOM elements),
* to disable this function you can use the skipUpdate parameter.
* If the child is not found null is returned.
*
* @param childSelector - The child fiber selector.
* @param skipUpdate - Whether to remove or not the elements.
* @returns - The removed child fiber or null.
*/
}, {
key: "removeChild",
value: function removeChild$1(childSelector, skipUpdate) {
return removeChild(this.getCurrent(), childSelector, skipUpdate);
}
/**
* Remove a child fiber from this instance and add it to another ParentFiber instance.
* Return the index in which the child is added or -1 if the child is not found.
* The child to remove can be chosen by providing its key (string) or by providing its index (number).
* The position can be chosen by providing a key (string) or by providing an index (number).
* If a key (string) is provided the child will be added after the one with that key.
* The child is added at the bottom if none of the children have that key.
* If an index (number) is provided the child will be added in that position.
* The child is added at the bottom if -1 is provided or the index is greater than the number of children.
* The method will also try to send the elements connected to the fibers (e.g. DOM elements),
* to disable this function you can use the skipUpdate parameter.
*
* @param toParent - The ParentFiber instance in which to send the child fiber.
* @param childSelector - The child fiber selector.
* @param position - The position in which to add the child fiber.
* @param skipUpdate - Whether to send or not the elements.
* @returns - The position in which the child fiber is sent or -1.
*/
}, {
key: "sendChild",
value: function sendChild(toParent, childSelector, position, skipUpdate) {
// Remove the child fiber.
var child = this.removeChild(childSelector, skipUpdate); // Return -1 if the child fiber does not exist.
if (child === null) {
return -1;
} // Add the child fiber.
return toParent.addChild(child, position, skipUpdate);
}
/**
* Clear the parent fiber.
*/
}, {
key: "clear",
value: function clear() {
this.fiber = null;
}
}]);
return ParentFiber;
}();
/**
* Generate a ParentFiber instance given a class instance of a component.
* If the class component is not the parent, it is possible to provide
* a function to get the correct parent given the class component fiber.
*
* @param instance - The class instance.
* @param findFiber - Get a different parent fiber.
* @returns - The ParentFiber instance.
*/
function createParent(instance, findFiber) {
var parent = new ParentFiber();
var componentDidMount = instance.componentDidMount,
componentWillUnmount = instance.componentWillUnmount; // Wrap the componentDidMount method.
instance.componentDidMount = function cdm() {
var fiber = getFiberFromClassInstance(instance); // Set the fiber.
parent.setFiber(fiber);
parent.setFinder(findFiber); // Call the original method.
if (typeof componentDidMount === 'function') {
componentDidMount.call(this);
}
}; // Wrap the componentDidMount method.
instance.componentWillUnmount = function cwu() {
// Call the original method.
if (typeof componentWillUnmount === 'function') {
componentWillUnmount.call(this);
} // Clear the parent.
parent.clear();
};
return parent;
}
function _createSuper$1(Derived) { var hasNativeReflectConstruct = _isNativeReflectConstruct$1(); return function _createSuperInternal() { var Super = _getPrototypeOf__default['default'](Derived), result; if (hasNativeReflectConstruct) { var NewTarget = _getPrototypeOf__default['default'](this).constructor; result = Reflect.construct(Super, arguments, NewTarget); } else { result = Super.apply(this, arguments); } return _possibleConstructorReturn__default['default'](this, result); }; }
function _isNativeReflectConstruct$1() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; try { Date.prototype.toString.call(Reflect.construct(Date, [], function () {})); return true; } catch (e) { return false; } }
/**
* It is a simple wrapper that generate internally a
* ParentFiber and allow to access it through a React.Ref.
* The children in which to enable reparenting must belong to this component.
*/
var Parent = /*#__PURE__*/function (_Component) {
_inherits__default['default'](Parent, _Component);
var _super = _createSuper$1(Parent);
function Parent() {
var _this;
_classCallCheck__default['default'](this, Parent);
for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
args[_key] = arguments[_key];
}
_this = _super.call.apply(_super, [this].concat(args));
_defineProperty__default['default'](_assertThisInitialized__default['default'](_this), "parent", new ParentFiber());
return _this;
}
_createClass__default['default'](Parent, [{
key: "componentDidMount",
value:
/**
* The class instance contains the fiber data
* only after the component is mounted.
*/
function componentDidMount() {
var _this$props = this.props,
parentRef = _this$props.parentRef,
findFiber = _this$props.findFiber;
var fiber = getFiberFromClassInstance(this); // Ensure a ref is passed.
invariant(parentRef !== null && (typeof parentRef === 'function' || _typeof__default['default'](parentRef) === 'object'), 'You must provide a parentRef to the <Parent> component.'); // Set the fiber.
this.parent.setFiber(fiber);
this.parent.setFinder(findFiber); // Set the ref.
if (typeof parentRef === 'function') {
parentRef(this.parent);
}
if (_typeof__default['default'](parentRef) === 'object' && parentRef !== null) {
// The type of ref that is normally returned by useRef and createRef
// is not mutable, and the user may not know how to obtain a mutable one,
// causing annoying problems. Plus, it makes sense that this property is
// immutable, so I just use the refObject interface (and not
// the MutableRefObject interface) with the @ts-ignore.
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
parentRef.current = this.parent;
}
}
/** Update the findFiber method. */
}, {
key: "componentDidUpdate",
value: function componentDidUpdate() {
var findFiber = this.props.findFiber;
this.parent.setFinder(findFiber);
}
/**
* Clear on unmount.
*/
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
this.parent.clear();
}
/**
* Render only the children.
* In this way the component (and therefore its fiber)
* will be the direct parent of the children.
*/
}, {
key: "render",
value: function render() {
var children = this.props.children;
return children;
}
}]);
return Parent;
}(React.Component);
/* Parent props. */
/**
* Create a reparentable Space. Only <Reparentables>s belonging to the same
* Space can send children to each other.
*/
function createReparentableSpace() {
/** Reparentable map. */
var ReparentableMap = new Map();
/**
* Remove a child from a <Reparentable> component and add it to another <Reparentable> component.
* Return the index in which the child is added or -1 if the child is not found.
* The child to remove can be chosen by providing its key (string) or by providing its index (number).
* The position can be chosen by providing a key (string) or by providing an index (number).
* If a key (string) is provided the child will be added after the one with that key.
* The child is added at the bottom if none of the children have that key.
* If an index (number) is provided the child will be added in that position.
* The child is added at the bottom if -1 is provided or the index is greater than the number of children.
* The method will also try to send the elements connected to the fibers (e.g. DOM elements),
* to disable this function you can use the skipUpdate parameter.
*
* @param fromParentId - The id of the <Reparentable> from whuch to remove the child.
* @param toParentId - The id of the <Reparentable> in which to add the child.
* @param childSelector - The child selector.
* @param position - The position in which to add the child fiber.
* @param skipUpdate - Whether to send or not the elements.
* @returns - The position in which the child is sent or -1.
*/
function sendReparentableChild(fromParentId, toParentId, childSelector, position, skipUpdate) {
// Get the ParetFiber instances.
var fromParent = ReparentableMap.get(fromParentId);
var toParent = ReparentableMap.get(toParentId);
{
if (fromParent === undefined) {
warning("Cannot find a <Reparentable> with the id: '".concat(fromParentId, "'."));
}
if (toParent === undefined) {
warning("Cannot find a <Reparentable> with the id: '".concat(toParentId, "'."));
}
} // Parents ids not valid.
if (fromParent === undefined || toParent === undefined) {
return -1;
} // Send the child.
return fromParent.sendChild(toParent, childSelector, position, skipUpdate);
}
/**
* This component generate internally a ParentFiber instance
* and allow to access it through a global provided map.
* This component must be the parent of the children to reparent
* (it is possible to get around this by providing a findFiber method).
*/
function Reparentable(_ref) {
var id = _ref.id,
children = _ref.children,
findFiber = _ref.findFiber;
var parentRef = React.useRef(null);
React.useEffect(function () {
// Ensure the id is a string.
invariant(typeof id === 'string', 'You must provide an id to the <Reparentable> component.');
{
if (ReparentableMap.has(id)) {
warning("It seems that a new <Reparentable> has been mounted with the id: '".concat(id, "', ") + "while there is another <Reparentable> with that id.");
}
}
invariant(parentRef.current !== null); // Set the ParentFiber instance in the map.
ReparentableMap.set(id, parentRef.current);
return function () {
// Remove the ParentFiber instance from the map.
ReparentableMap["delete"](id);
};
}, []);
return /*#__PURE__*/React__default['default'].createElement(Parent, {
parentRef: parentRef,
findFiber: findFiber
}, children);
}
return {
Reparentable: Reparentable,
sendReparentableChild: sendReparentableChild,
ReparentableMap: ReparentableMap
};
}
/* Reparentable props. */
/**
* An hook to get a ParentFiber instance in a function component.
* The ref returned must reference the element that is the parent
* of the children to reparent (it is possible to get around this by
* providing a findFiber method).
*
* @param findFiber - Get a different parent fiber.
* @returns - The ParentFiber instance.
*/
function useParent(ref, findFiber) {
// The parent instance.
var parentRef = React.useRef(null); // Generate the instance.
if (parentRef.current === null) {
parentRef.current = new ParentFiber();
} // Get a reference.
var parent = parentRef.current;
parent.setFinder(findFiber); // When the component is mounted the fiber is set.
React.useEffect(function () {
invariant(ref.current !== null && ref.current !== undefined, 'You must set the ref returned by the useParent hook.'); // The element fiber.
parent.setFiber(getFiberFromElementInstance(ref.current)); // Clean up.
return function () {
parent.clear();
};
}, []);
return parent;
}
exports.Env = Env;
exports.Int = Int;
exports.Parent = Parent;
exports.ParentFiber = ParentFiber;
exports.addChild = addChild;
exports.addChildFiberAt = addChildFiberAt;
exports.addChildFiberBefore = addChildFiberBefore;
exports.addSiblingFiber = addSiblingFiber;
exports.appendChildFiber = appendChildFiber;
exports.configure = configure;
exports.createParent = createParent;
exports.createReparentableSpace = createReparentableSpace;
exports.findChildFiber = findChildFiber;
exports.findChildFiberAt = findChildFiberAt;
exports.findPreviousFiber = findPreviousFiber;
exports.findSiblingFiber = findSiblingFiber;
exports.getCurrentFiber = getCurrentFiber;
exports.getFiberFromClassInstance = getFiberFromClassInstance;
exports.getFiberFromElementInstance = getFiberFromElementInstance;
exports.getFiberFromPath = getFiberFromPath;
exports.prependChildFiber = prependChildFiber;
exports.removeChild = removeChild;
exports.removeChildFiber = removeChildFiber;
exports.removeChildFiberAt = removeChildFiberAt;
exports.removeFirstChildFiber = removeFirstChildFiber;
exports.removeSiblingFiber = removeSiblingFiber;
exports.updateFiberDebugFields = updateFiberDebugFields;
exports.updateFibersIndex = updateFibersIndex;
exports.useParent = useParent;