@lonli-lokli/react-mosaic-component
Version:
A React Tiling Window Manager
450 lines (448 loc) • 16.9 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// libs/react-mosaic-component/src/lib/util/mosaicUpdates.ts
var mosaicUpdates_exports = {};
__export(mosaicUpdates_exports, {
buildSpecFromUpdate: () => buildSpecFromUpdate,
convertToDropInfo: () => convertToDropInfo,
createAddChildUpdate: () => createAddChildUpdate,
createDragToUpdates: () => createDragToUpdates,
createExpandUpdate: () => createExpandUpdate,
createHideUpdate: () => createHideUpdate,
createRemoveUpdate: () => createRemoveUpdate,
updateTree: () => updateTree
});
module.exports = __toCommonJS(mosaicUpdates_exports);
var import_immutability_helper = __toESM(require("immutability-helper"), 1);
var import_lodash_es = require("lodash-es");
var import_internalTypes = require("../internalTypes.cjs");
var import_mosaicUtilities = require("./mosaicUtilities.cjs");
var import_assertNever = require("./assertNever.cjs");
function buildSpecFromUpdate(mosaicUpdate) {
if (mosaicUpdate.path.length > 0) {
return (0, import_lodash_es.set)({}, mosaicUpdate.path, mosaicUpdate.spec);
} else {
return mosaicUpdate.spec;
}
}
function buildSpecFromPath(tree, path, spec) {
return path.reduceRight((acc, pathSegment, index) => {
const parentPath = path.slice(0, index);
const parentNode = (0, import_mosaicUtilities.getNodeAtPath)(tree, parentPath);
let property = "children";
if (parentNode && typeof parentNode === "object" && "type" in parentNode && parentNode.type === "tabs") {
property = "tabs";
}
return {
[property]: {
[pathSegment]: acc
}
};
}, spec);
}
function updateTree(tree, updates) {
if (updates.length === 0) {
return tree;
}
let currentTree = tree;
for (const { path, spec } of updates) {
if (path.length === 0) {
currentTree = (0, import_immutability_helper.default)(currentTree, spec);
} else {
const nestedSpec = buildSpecFromPath(currentTree, path, spec);
currentTree = (0, import_immutability_helper.default)(currentTree, nestedSpec);
}
}
return currentTree;
}
function createRemoveUpdate(root, path) {
if (path.length === 0) {
throw new Error("Cannot remove root node");
}
const parentInfo = (0, import_mosaicUtilities.getParentAndChildIndex)(root, path);
if (!parentInfo) {
throw new Error("Cannot find parent for removal");
}
const { parent, childIndex } = parentInfo;
if ((0, import_mosaicUtilities.isSplitNode)(parent)) {
if (parent.children.length === 2) {
const siblingIndex = childIndex === 0 ? 1 : 0;
const sibling = parent.children[siblingIndex];
return {
path: (0, import_lodash_es.dropRight)(path),
spec: {
$set: sibling
}
};
} else {
const newChildren = parent.children.filter(
(_, index) => index !== childIndex
);
const oldPercentages = parent.splitPercentages || Array(parent.children.length).fill(100 / parent.children.length);
const removedPercentage = oldPercentages[childIndex];
const redistributeAmount = removedPercentage / newChildren.length;
const newPercentages = oldPercentages.filter((_, index) => index !== childIndex).map((percentage) => percentage + redistributeAmount);
return {
path: (0, import_lodash_es.dropRight)(path),
spec: {
children: { $set: newChildren },
splitPercentages: { $set: newPercentages }
}
};
}
} else if ((0, import_mosaicUtilities.isTabsNode)(parent)) {
const newTabs = parent.tabs.filter((_, index) => index !== childIndex);
if (newTabs.length === 0) {
throw new Error("Cannot remove last tab from tabs node");
}
let newActiveTabIndex = parent.activeTabIndex;
if (childIndex === parent.activeTabIndex) {
newActiveTabIndex = childIndex > 0 ? childIndex - 1 : 0;
} else if (childIndex < parent.activeTabIndex) {
newActiveTabIndex = parent.activeTabIndex - 1;
}
return {
path: (0, import_lodash_es.dropRight)(path),
spec: {
tabs: { $set: newTabs },
activeTabIndex: { $set: newActiveTabIndex }
}
};
}
throw new Error("Invalid parent node type for removal");
}
function createDragToUpdates(root, sourcePath, destinationPath, dropInfo) {
if ((0, import_lodash_es.isEqual)(sourcePath, destinationPath)) {
return [];
}
const destinationNode = (0, import_mosaicUtilities.getAndAssertNodeAtPathExists)(root, destinationPath);
const sourceNode = (0, import_mosaicUtilities.getAndAssertNodeAtPathExists)(root, sourcePath);
switch (dropInfo.type) {
case "tab-container":
case "tab-reorder": {
if (!(0, import_mosaicUtilities.isTabsNode)(destinationNode)) {
throw new Error(
`Expected tab container at destination path ${destinationPath.join(", ")}, but found: ${JSON.stringify(destinationNode)}`
);
}
const updates = [];
updates.push(createRemoveUpdate(root, sourcePath));
let adjustedDestinationPath = adjustPathAfterRemoval(
sourcePath,
destinationPath
);
const rootAfterRemoval = updateTree(root, updates);
let destinationAfterRemoval = (0, import_mosaicUtilities.getNodeAtPath)(
rootAfterRemoval,
adjustedDestinationPath
);
if (!destinationAfterRemoval || !(0, import_mosaicUtilities.isTabsNode)(destinationAfterRemoval)) {
let findTabsNodeInTree2 = function(node, currentPath = []) {
if (node && typeof node === "object") {
if ((0, import_mosaicUtilities.isTabsNode)(node) && (0, import_mosaicUtilities.isTabsNode)(destinationNode) && node.tabs && JSON.stringify(node.tabs) === JSON.stringify(destinationNode.tabs)) {
return { node, path: currentPath };
}
if ((0, import_mosaicUtilities.isSplitNode)(node)) {
for (let i = 0; i < node.children.length; i++) {
const result2 = findTabsNodeInTree2(node.children[i], [
...currentPath,
i
]);
if (result2) {
return result2;
}
}
}
}
return null;
};
var findTabsNodeInTree = findTabsNodeInTree2;
const result = findTabsNodeInTree2(rootAfterRemoval);
if (result) {
adjustedDestinationPath = result.path;
destinationAfterRemoval = result.node;
}
}
if (!destinationAfterRemoval || !(0, import_mosaicUtilities.isTabsNode)(destinationAfterRemoval)) {
throw new Error(
`Could not find tabs container after removal. Original path: ${destinationPath.join(", ")}, Adjusted path: ${adjustedDestinationPath.join(", ")}`
);
}
if (dropInfo.type === "tab-reorder") {
const newTabs = [...destinationAfterRemoval.tabs];
newTabs.splice(dropInfo.insertIndex, 0, sourceNode);
updates.push({
path: adjustedDestinationPath,
spec: {
tabs: { $set: newTabs },
activeTabIndex: { $set: dropInfo.insertIndex }
// Make the inserted tab active
}
});
} else {
updates.push({
path: adjustedDestinationPath,
spec: {
tabs: { $push: [sourceNode] },
activeTabIndex: { $set: destinationAfterRemoval.tabs.length }
}
});
}
return updates;
}
case "split": {
const updates = [];
updates.push(createRemoveUpdate(root, sourcePath));
let adjustedDestinationPath = adjustPathAfterRemoval(
sourcePath,
destinationPath
);
const rootAfterRemoval = updateTree(root, updates);
let destinationAfterRemoval = (0, import_mosaicUtilities.getNodeAtPath)(
rootAfterRemoval,
adjustedDestinationPath
);
if (!destinationAfterRemoval) {
for (let i = adjustedDestinationPath.length - 1; i >= 0; i--) {
const shorterPath = adjustedDestinationPath.slice(0, i);
const nodeAtShorterPath = (0, import_mosaicUtilities.getNodeAtPath)(
rootAfterRemoval,
shorterPath
);
if (nodeAtShorterPath) {
adjustedDestinationPath = shorterPath;
destinationAfterRemoval = nodeAtShorterPath;
break;
}
}
if (!destinationAfterRemoval) {
adjustedDestinationPath = [];
destinationAfterRemoval = rootAfterRemoval;
}
}
if (!destinationAfterRemoval) {
console.error(
"Could not find valid destination after removing source from:",
sourcePath,
"original destination was:",
destinationPath
);
throw new Error(
`Could not find valid destination after removing source from [${sourcePath.join(", ")}]`
);
}
let direction = "column";
if (dropInfo.position === import_internalTypes.MosaicDropTargetPosition.LEFT || dropInfo.position === import_internalTypes.MosaicDropTargetPosition.RIGHT) {
direction = "row";
}
if ((0, import_mosaicUtilities.isSplitNode)(destinationAfterRemoval) && destinationAfterRemoval.direction === direction) {
const insertIndex = dropInfo.position === import_internalTypes.MosaicDropTargetPosition.LEFT || dropInfo.position === import_internalTypes.MosaicDropTargetPosition.TOP ? 0 : destinationAfterRemoval.children.length;
updates.push(
createAddChildUpdate(
adjustedDestinationPath,
sourceNode,
insertIndex
)
);
} else {
let first;
let second;
if (dropInfo.position === import_internalTypes.MosaicDropTargetPosition.LEFT || dropInfo.position === import_internalTypes.MosaicDropTargetPosition.TOP) {
first = sourceNode;
second = destinationAfterRemoval;
} else {
first = destinationAfterRemoval;
second = sourceNode;
}
updates.push({
path: adjustedDestinationPath,
spec: {
$set: {
type: "split",
direction,
children: [first, second],
splitPercentages: [50, 50]
}
}
});
}
return updates;
}
default:
(0, import_assertNever.assertNever)(dropInfo);
}
}
function adjustPathAfterRemoval(removedPath, targetPath) {
if (removedPath.length === 0 || targetPath.length === 0) {
return targetPath;
}
if ((0, import_lodash_es.isEqual)(removedPath, targetPath)) {
return [];
}
const removedParentPath = removedPath.slice(0, -1);
const targetParentPath = targetPath.slice(0, -1);
if (!(0, import_lodash_es.isEqual)(removedParentPath, targetParentPath)) {
return targetPath;
}
const removedIndex = removedPath[removedPath.length - 1];
const targetIndex = targetPath[targetPath.length - 1];
if (typeof removedIndex === "number" && typeof targetIndex === "number") {
if (targetIndex > removedIndex) {
const adjustedPath = [...targetPath];
adjustedPath[adjustedPath.length - 1] = targetIndex - 1;
return adjustedPath;
}
}
return targetPath;
}
function createHideUpdate(root, path) {
if (path.length === 0) {
throw new Error("Cannot hide root node");
}
const parentInfo = (0, import_mosaicUtilities.getParentAndChildIndex)(root, path);
if (!parentInfo) {
throw new Error("Cannot hide node: parent not found");
}
const { parent, childIndex } = parentInfo;
if ((0, import_mosaicUtilities.isSplitNode)(parent)) {
const currentPercentages = parent.splitPercentages || Array(parent.children.length).fill(100 / parent.children.length);
const hidePercentage = currentPercentages[childIndex];
const otherIndices = currentPercentages.map((_, i) => i).filter((i) => i !== childIndex);
if (otherIndices.length === 0) {
throw new Error("Cannot hide the only child of a split node");
}
const redistributeAmount = hidePercentage / otherIndices.length;
const newPercentages = currentPercentages.map((percentage, index) => {
if (index === childIndex) {
return 0;
} else {
return percentage + redistributeAmount;
}
});
return {
path: (0, import_lodash_es.dropRight)(path),
spec: {
splitPercentages: {
$set: newPercentages
}
}
};
} else if ((0, import_mosaicUtilities.isTabsNode)(parent)) {
if (parent.tabs.length <= 1) {
throw new Error("Cannot hide the only tab in a tab container");
}
let newActiveTabIndex = parent.activeTabIndex;
if (childIndex === parent.activeTabIndex) {
newActiveTabIndex = childIndex > 0 ? childIndex - 1 : childIndex + 1;
}
return {
path: (0, import_lodash_es.dropRight)(path),
spec: {
activeTabIndex: { $set: newActiveTabIndex }
}
};
}
throw new Error("Cannot hide node: parent is not a split or tabs node");
}
function createExpandUpdate(path, percentage) {
let spec = {};
for (let i = path.length - 1; i >= 0; i--) {
const childIndex = typeof path[i] === "number" ? path[i] : Number(path[i]);
spec = {
splitPercentages: {
$apply: (currentPercentages) => {
if (!currentPercentages) {
throw new Error(
"Cannot expand: parent node has no splitPercentages defined"
);
}
const newPercentages = [...currentPercentages];
const targetChildPercentage = percentage;
const otherChildrenCount = newPercentages.length - 1;
if (otherChildrenCount === 0) {
return [100];
}
const remainingPercentage = 100 - targetChildPercentage;
const otherChildPercentage = remainingPercentage / otherChildrenCount;
for (let j = 0; j < newPercentages.length; j++) {
if (j === childIndex) {
newPercentages[j] = targetChildPercentage;
} else {
newPercentages[j] = otherChildPercentage;
}
}
return newPercentages;
}
},
children: {
[childIndex]: spec
}
};
}
return {
spec,
path: []
};
}
function createAddChildUpdate(path, newChild, insertIndex) {
return {
path,
spec: {
children: {
$apply: (currentChildren) => {
const newChildren = [...currentChildren];
const index = insertIndex !== void 0 ? insertIndex : newChildren.length;
newChildren.splice(index, 0, newChild);
return newChildren;
}
},
splitPercentages: {
$apply: (currentPercentages) => {
const currentLength = currentPercentages ? currentPercentages.length : 0;
const newLength = currentLength + 1;
const equalPercentage = 100 / newLength;
if (!currentPercentages) {
return Array(newLength).fill(equalPercentage);
}
const newPercentages = Array(newLength).fill(equalPercentage);
return newPercentages;
}
}
}
};
}
function convertToDropInfo(position, tabReorderIndex) {
if (tabReorderIndex !== void 0) {
return { type: "tab-reorder", insertIndex: tabReorderIndex };
} else if (position === void 0) {
return { type: "tab-container" };
} else {
return { type: "split", position };
}
}