@lonli-lokli/react-mosaic-component
Version:
A React Tiling Window Manager
426 lines (424 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/MosaicWindow.tsx
var MosaicWindow_exports = {};
__export(MosaicWindow_exports, {
InternalMosaicWindow: () => InternalMosaicWindow,
MosaicWindow: () => MosaicWindow
});
module.exports = __toCommonJS(MosaicWindow_exports);
var import_classnames = __toESM(require("classnames"), 1);
var import_lodash_es = require("lodash-es");
var import_react = __toESM(require("react"), 1);
var import_react_dnd = require("react-dnd");
var import_defaultToolbarControls = require("./buttons/defaultToolbarControls.cjs");
var import_Separator = require("./buttons/Separator.cjs");
var import_contextTypes = require("./contextTypes.cjs");
var import_internalTypes = require("./internalTypes.cjs");
var import_MosaicDropTarget = require("./MosaicDropTarget.cjs");
var import_types = require("./types.cjs");
var import_mosaicUpdates = require("./util/mosaicUpdates.cjs");
var import_mosaicUtilities = require("./util/mosaicUtilities.cjs");
var import_OptionalBlueprint = require("./util/OptionalBlueprint.cjs");
var InternalMosaicWindow = class extends import_react.default.Component {
static defaultProps = {
additionalControlButtonText: "More",
draggable: true,
renderPreview: ({ title }) => /* @__PURE__ */ import_react.default.createElement("div", { className: "mosaic-preview" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "mosaic-window-toolbar" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "mosaic-window-title" }, title)), /* @__PURE__ */ import_react.default.createElement("div", { className: "mosaic-window-body" }, /* @__PURE__ */ import_react.default.createElement("h4", null, title), /* @__PURE__ */ import_react.default.createElement(
import_OptionalBlueprint.OptionalBlueprint.Icon,
{
className: "default-preview-icon",
size: "large",
icon: "APPLICATION"
}
))),
renderToolbar: null
};
static contextType = import_contextTypes.MosaicContext;
state = {
additionalControlsOpen: false
};
rootElement = null;
render() {
const {
className,
isOver,
renderPreview,
additionalControls,
connectDropTarget,
connectDragPreview,
draggedMosaicId,
disableAdditionalControlsOverlay
} = this.props;
const root = this.context.mosaicActions.getRoot();
const parentNode = (0, import_mosaicUtilities.getParentNode)(root, this.props.path);
const isInTabContainer = (0, import_mosaicUtilities.isTabsNode)(parentNode);
return /* @__PURE__ */ import_react.default.createElement(import_contextTypes.MosaicWindowContext.Provider, { value: this.childContext }, connectDropTarget(
/* @__PURE__ */ import_react.default.createElement(
"div",
{
className: (0, import_classnames.default)(
"mosaic-window",
"mosaic-drop-target",
className,
{
"drop-target-hover": isOver && draggedMosaicId === this.context.mosaicId,
"additional-controls-open": this.state.additionalControlsOpen
}
),
ref: (element) => {
this.rootElement = element;
}
},
this.renderToolbar(),
/* @__PURE__ */ import_react.default.createElement("div", { className: "mosaic-window-body" }, this.props.children),
!disableAdditionalControlsOverlay && /* @__PURE__ */ import_react.default.createElement(
"div",
{
className: "mosaic-window-body-overlay",
onClick: () => {
this.setAdditionalControlsOpen(false);
}
}
),
/* @__PURE__ */ import_react.default.createElement("div", { className: "mosaic-window-additional-actions-bar" }, additionalControls),
connectDragPreview(renderPreview(this.props)),
!isInTabContainer && /* @__PURE__ */ import_react.default.createElement("div", { className: (0, import_classnames.default)("drop-target-container", {}) }, (0, import_lodash_es.values)(import_internalTypes.MosaicDropTargetPosition).map(
this.renderDropTarget
))
)
));
}
getToolbarControls() {
const { toolbarControls, createNode, path } = this.props;
if (toolbarControls) {
return toolbarControls;
}
const root = this.context.mosaicActions.getRoot();
const parentNode = (0, import_mosaicUtilities.getParentNode)(root, path);
if ((0, import_mosaicUtilities.isTabsNode)(parentNode)) {
return import_defaultToolbarControls.DEFAULT_PANEL_CONTROLS_IN_TABS;
} else if (createNode) {
return import_defaultToolbarControls.DEFAULT_PANEL_CONTROLS_WITH_CREATION;
} else {
return import_defaultToolbarControls.DEFAULT_PANEL_CONTROLS_WITHOUT_CREATION;
}
}
renderToolbar() {
const {
title,
draggable,
additionalControls,
additionalControlButtonText,
path,
renderToolbar
} = this.props;
const { additionalControlsOpen } = this.state;
const toolbarControls = this.getToolbarControls();
const root = this.context.mosaicActions.getRoot();
const parentNode = (0, import_mosaicUtilities.getParentNode)(root, path);
const isDragAllowed = draggable && path.length > 0 && !(0, import_mosaicUtilities.isTabsNode)(parentNode);
const connectIfDraggable = isDragAllowed ? this.props.connectDragSource : (el) => el;
if (renderToolbar) {
const connectedToolbar = connectIfDraggable(
renderToolbar(this.props, draggable)
);
return /* @__PURE__ */ import_react.default.createElement(
"div",
{
className: (0, import_classnames.default)("mosaic-window-toolbar", {
draggable: isDragAllowed
})
},
connectedToolbar
);
}
const titleDiv = connectIfDraggable(
/* @__PURE__ */ import_react.default.createElement("div", { title, className: "mosaic-window-title" }, title)
);
const hasAdditionalControls = !!additionalControls;
return /* @__PURE__ */ import_react.default.createElement(
"div",
{
className: (0, import_classnames.default)("mosaic-window-toolbar", {
draggable: isDragAllowed
})
},
titleDiv,
/* @__PURE__ */ import_react.default.createElement(
"div",
{
className: (0, import_classnames.default)(
"mosaic-window-controls",
import_OptionalBlueprint.OptionalBlueprint.getClasses("BUTTON_GROUP")
)
},
hasAdditionalControls && /* @__PURE__ */ import_react.default.createElement(
"button",
{
onClick: () => this.setAdditionalControlsOpen(!additionalControlsOpen),
className: (0, import_classnames.default)(
import_OptionalBlueprint.OptionalBlueprint.getClasses(
this.context.blueprintNamespace,
"BUTTON",
"MINIMAL"
),
import_OptionalBlueprint.OptionalBlueprint.getIconClass(
this.context.blueprintNamespace,
"MORE"
),
{
[import_OptionalBlueprint.OptionalBlueprint.getClasses(
this.context.blueprintNamespace,
"ACTIVE"
)]: additionalControlsOpen
}
)
},
/* @__PURE__ */ import_react.default.createElement("span", { className: "control-text" }, additionalControlButtonText)
),
hasAdditionalControls && /* @__PURE__ */ import_react.default.createElement(import_Separator.Separator, null),
toolbarControls
)
);
}
renderDropTarget = (position) => {
const { path } = this.props;
return /* @__PURE__ */ import_react.default.createElement(import_MosaicDropTarget.MosaicDropTarget, { position, path, key: position });
};
checkCreateNode() {
if (this.props.createNode == null) {
throw new Error("Operation invalid unless `createNode` is defined");
}
}
split = (...args) => {
this.checkCreateNode();
const { path } = this.props;
const { mosaicActions } = this.context;
const root = mosaicActions.getRoot();
const parentNode = (0, import_mosaicUtilities.getParentNode)(root, path);
if ((0, import_mosaicUtilities.isTabsNode)(parentNode)) {
if (process.env.NODE_ENV !== "production") {
console.warn(
"Mosaic: Cannot split a panel that is already inside a tab group."
);
}
return Promise.resolve();
}
const direction = this.rootElement.offsetWidth > this.rootElement.offsetHeight ? "row" : "column";
const currentNode = (0, import_mosaicUtilities.getNodeAtPath)(root, path);
if (!currentNode) {
throw new Error("Current node could not be found.");
}
return Promise.resolve(mosaicActions.createNode(...args)).then(
(second) => {
const newSplitNode = {
type: "split",
direction,
children: [currentNode, second],
splitPercentages: [50, 50]
};
mosaicActions.replaceWith(path, newSplitNode);
}
);
};
addTab = (...args) => {
this.checkCreateNode();
const { path, createNode } = this.props;
const { mosaicActions } = this.context;
const root = mosaicActions.getRoot();
const parentNode = (0, import_mosaicUtilities.getParentNode)(root, path);
if ((0, import_mosaicUtilities.isTabsNode)(parentNode)) {
if (process.env.NODE_ENV !== "production") {
console.warn(
`Mosaic: "Add Tab" from a panel toolbar is disabled when already in a tab group. Use the tab bar's "Add" button instead.`
);
}
return Promise.resolve();
}
const currentNode = (0, import_mosaicUtilities.getNodeAtPath)(root, path);
if (!currentNode) {
throw new Error("Current node could not be found.");
}
return Promise.resolve(createNode(...args)).then((newNode) => {
if (typeof newNode !== "string" && typeof newNode !== "number") {
console.error(
"createNode() for addTab should return a MosaicKey (string or number)."
);
return;
}
if (typeof currentNode === "object" && currentNode.type === "tabs") {
const newTabsNode = {
type: "tabs",
tabs: [...currentNode.tabs, newNode],
activeTabIndex: currentNode.tabs.length
// Make the new tab active
};
mosaicActions.replaceWith(path, newTabsNode);
} else {
if (typeof currentNode !== "string" && typeof currentNode !== "number") {
console.error("Cannot create a tab group from a non-panel node.");
return;
}
const newTabsNode = {
type: "tabs",
tabs: [currentNode, newNode],
activeTabIndex: 1
// Make the new tab active
};
mosaicActions.replaceWith(path, newTabsNode);
}
});
};
getRoot = () => {
const { mosaicActions } = this.context;
return mosaicActions.getRoot();
};
swap = (...args) => {
this.checkCreateNode();
const { mosaicActions } = this.context;
const { path, createNode } = this.props;
return Promise.resolve(createNode(...args)).then(
(node) => mosaicActions.replaceWith(path, node)
);
};
setAdditionalControlsOpen = (additionalControlsOpenOption) => {
const additionalControlsOpen = additionalControlsOpenOption === "toggle" ? !this.state.additionalControlsOpen : additionalControlsOpenOption;
this.setState({ additionalControlsOpen });
this.props.onAdditionalControlsToggle?.(additionalControlsOpen);
};
getPath = () => this.props.path;
connectDragSource = (connectedElements) => {
const { connectDragSource } = this.props;
return connectDragSource(connectedElements);
};
childContext = {
// @ts-expect-error we need it
blueprintNamespace: this.context.blueprintNamespace,
mosaicWindowActions: {
split: this.split,
addTab: this.addTab,
getRoot: this.getRoot,
replaceWithNew: this.swap,
setAdditionalControlsOpen: this.setAdditionalControlsOpen,
getPath: this.getPath,
connectDragSource: this.connectDragSource
}
};
};
function ConnectedInternalMosaicWindow(props) {
const { mosaicActions, mosaicId } = (0, import_react.useContext)(import_contextTypes.MosaicContext);
const [, connectDragSource, connectDragPreview] = (0, import_react_dnd.useDrag)({
type: import_types.MosaicDragType.WINDOW,
item: () => {
if (props.onDragStart) {
props.onDragStart();
}
const hideTimer = window.setTimeout(() => {
mosaicActions.hide(props.path, true);
}, 50);
return {
mosaicId,
hideTimer
};
},
end: (item, monitor) => {
window.clearTimeout(item.hideTimer);
const ownPath = props.path;
const dropResult = monitor.getDropResult() || {};
const { position, path: destinationPath } = dropResult;
const dropped = destinationPath != null;
const isSelfDrop = dropped && (0, import_lodash_es.isEqual)(destinationPath, ownPath);
const isTabContainerSelfDrop = dropped && (() => {
const root = mosaicActions.getRoot();
const ownParentPath = ownPath.slice(0, -1);
const ownParentNode = (0, import_mosaicUtilities.getNodeAtPath)(root, ownParentPath);
const destinationNode = (0, import_mosaicUtilities.getNodeAtPath)(root, destinationPath);
return (0, import_mosaicUtilities.isTabsNode)(ownParentNode) && (0, import_mosaicUtilities.isTabsNode)(destinationNode) && (0, import_lodash_es.isEqual)(ownParentPath, destinationPath);
})();
const isChildDrop = dropped && destinationPath.length > ownPath.length && (0, import_lodash_es.isEqual)(
ownPath,
(0, import_lodash_es.drop)(destinationPath, destinationPath.length - ownPath.length)
);
if (dropped && !isSelfDrop && !isChildDrop && !isTabContainerSelfDrop) {
const updates = (0, import_mosaicUpdates.createDragToUpdates)(
mosaicActions.getRoot(),
ownPath,
destinationPath,
dropResult.tabReorderIndex !== void 0 ? {
type: "tab-reorder",
insertIndex: dropResult.tabReorderIndex
} : position === void 0 ? { type: "tab-container" } : {
type: "split",
position
}
);
mosaicActions.updateTree(updates, {
shouldNormalize: true
});
if (props.onDragEnd) {
props.onDragEnd("drop");
}
} else {
mosaicActions.show(ownPath, true);
if (props.onDragEnd) {
props.onDragEnd("reset");
}
}
}
});
const [{ isOver, draggedMosaicId }, connectDropTarget] = (0, import_react_dnd.useDrop)({
accept: import_types.MosaicDragType.WINDOW,
collect: (monitor) => ({
isOver: monitor.isOver(),
draggedMosaicId: monitor.getItem()?.mosaicId
})
});
return /* @__PURE__ */ import_react.default.createElement(
InternalMosaicWindow,
{
...props,
connectDragPreview,
connectDragSource,
connectDropTarget,
isOver,
draggedMosaicId
}
);
}
var MosaicWindow = class extends import_react.default.PureComponent {
render() {
return /* @__PURE__ */ import_react.default.createElement(
ConnectedInternalMosaicWindow,
{
...this.props
}
);
}
};