react-kityminder
Version:
Mind map for react, based on kityminder.
456 lines (394 loc) • 11.9 kB
JavaScript
import 'kity';
import 'kityminder-core';
import { useEffect, useState, createElement, useRef, useMemo, useCallback, forwardRef } from 'react';
function useValue(minder, value) {
useEffect(() => {
if (minder) {
// const animationOptionName = 'layoutAnimationDuration'
// const animationDuration = minder.getOption(animationOptionName)
// if (animationDuration) {
// minder.setOption(animationOptionName, 0)
// }
minder.importJson(value); // if (animationDuration) {
// minder.setOption(animationOptionName, animationDuration)
// }
}
}, [minder, value]);
}
const eventHandlerPrefixRE = /^on(\w+)$/;
const getEventName = propName => {
const name = eventHandlerPrefixRE.exec(propName);
return name && name[1].toLowerCase();
};
const forEachHandler = (minder, props, cb) => {
if (minder) {
Object.keys(props).forEach(propName => {
const eventName = getEventName(propName);
if (eventName && handlerPropKeys.indexOf(eventName) >= 0) {
cb(eventName, propName);
}
});
}
};
const eventNames = ('click,dblclick,mousedown,mousemove,mouseup,mousewheel,keydown,keyup,keypress,touchstart,touchend,touchmove,' + 'execcommand,' + 'selectionchange,contentchange,interactchange,' + 'editnoterequest,shownoterequest,hidenoterequest,' + 'nodechange,editnode').split(',');
let handlerPropKeys = [];
eventNames.map(eventName => {
handlerPropKeys = handlerPropKeys.concat([eventName, `before${eventName}`, `pre${eventName}`, `after${eventName}`]);
}); // 数据
function useEvents(minder, props) {
// 事件监听
useEffect(() => {
const handlers = {};
forEachHandler(minder, props, (eventName, propName) => {
handlers[eventName] = props[propName];
minder.on(eventName, props[propName]);
});
return () => {
forEachHandler(minder, props, eventName => {
minder.off(eventName, handlers[eventName]);
});
};
}, [minder, ...handlerPropKeys.map(handlerProp => props[handlerProp])]);
}
function useChangeHandler(minder, onChange) {
useEffect(() => {
const changeHandler = e => onChange(e.minder.exportJson());
if (minder && onChange) {
minder.on('contentchange', changeHandler);
}
return () => {
if (minder && onChange) {
minder.off('contentchange', changeHandler);
}
};
}, [minder, onChange]);
}
function getKeyCode(evt) {
return evt.keyCode || evt.witch;
}
function Editor(props) {
const [value, setValue] = useState(props.initialValue);
const onKeydown = evt => {
evt.stopPropagation();
const {
KeyMap
} = window.kityminder;
const keyCode = getKeyCode(evt);
if (keyCode === KeyMap.enter) {
props.onSubmit();
}
if (keyCode === KeyMap.esc) {
props.onCancel();
}
};
const onChange = ({
target: {
value
}
}) => {
setValue(value);
props.onChange(value);
};
return /*#__PURE__*/createElement("input", {
value: value,
onChange: onChange,
onKeyDown: onKeydown,
onBlur: props.onSubmit
});
}
function isInputValue(e) {
const keyCode = getKeyCode(e); // a-zA-Z
if (keyCode >= 65 && keyCode <= 90) return true; // 0-9 以及其上面的符号
if (keyCode >= 48 && keyCode <= 57) return true; // 小键盘区域 (除回车外)
if (keyCode !== 108 && keyCode >= 96 && keyCode <= 111) return true;
return false;
}
function isIntendToInput(e) {
const keyCode = getKeyCode(e);
if (e.ctrlKey || e.metaKey || e.altKey) return false;
if (isInputValue(e)) return true; // 输入法
if (keyCode === 229 || keyCode === 0) return true;
return false;
}
function EditorWrapper(minder, props) {
const valueRef = useRef();
const editingNodeRef = useRef();
const editorRef = useRef();
const [initialValue, setInitialValue] = useState();
const [editingNode, setEditingNode] = useState();
const {
Editor,
onEdit,
onEditEnd
} = props;
const setEditorValue = v => {
valueRef.current = v;
};
const setEditorEditingNode = v => {
editingNodeRef.current = v;
setEditingNode(v);
};
const exitEdit = () => {
setEditorEditingNode();
minder.focus();
};
const onSubmit = (...args) => {
const {
node
} = editingNodeRef.current || {};
if (node && (!onEditEnd || onEditEnd && onEditEnd(...args) !== false)) {
node.setText(valueRef.current);
minder.select(node, true);
minder.fire('nodechange', {
node
});
minder.fire('contentchange');
minder.getRoot().renderTree();
minder.layout(300);
}
exitEdit();
};
useEffect(() => {
const edit = e => {
if (onEdit && onEdit(e) !== false || !onEdit) {
const node = minder.getSelectedNode();
if (node) {
const box = node.getRenderer('OutlineRenderer').getRenderShape().getRenderBox('view');
const {
text = ''
} = node.data;
const editingNode = {
node,
box
};
if (box.x > 0 || box.y > 0) {
let value = text;
if (props.appendKey) {
value += isInputValue(e.originEvent) ? e.originEvent.key : '';
}
setEditorEditingNode(editingNode);
setInitialValue(value);
setEditorValue(value);
}
}
}
};
const editNodeName = 'editnode';
const editNodeHandler = edit;
const dblclickName = 'dblclick';
const dblclickHandler = edit;
const keydownName = 'keydown';
const keydownHandler = e => {
if (isIntendToInput(e.originEvent) && minder.getSelectedNode()) {
edit(e);
}
};
if (minder) {
minder.on(editNodeName, editNodeHandler);
minder.on(dblclickName, dblclickHandler);
minder.on(keydownName, keydownHandler);
}
return () => {
if (minder) {
minder.off(editNodeName, editNodeHandler);
minder.off(dblclickName, dblclickHandler);
minder.off(keydownName, keydownHandler);
}
};
}, [minder, onEdit]);
useEffect(() => {
const escHandler = evt => {
const keyCode = getKeyCode(evt);
if (keyCode === window.kityminder.KeyMap.esc) {
exitEdit();
}
};
document.addEventListener('keydown', escHandler);
return () => {
document.removeEventListener('keydown', escHandler);
};
}, [minder]);
useEffect(() => {
if (minder && editingNode && editorRef.current) {
let inputEl;
for (const type of ['input', 'textarea', 'select']) {
inputEl = editorRef.current.querySelector(type);
if (inputEl) {
inputEl.focus();
break;
}
}
}
}, [minder, Editor, initialValue, editingNode]);
return useMemo(() => props => editingNode ? /*#__PURE__*/createElement("div", {
style: {
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0
},
onClick: onSubmit
}, /*#__PURE__*/createElement("div", {
ref: editorRef,
style: {
position: 'absolute',
top: `${editingNode.box.y + editingNode.box.height / 2}px`,
left: `${editingNode.box.x}px`,
transform: 'translateY(-50%)'
},
onClick: e => e.stopPropagation()
}, /*#__PURE__*/createElement(Editor, Object.assign({}, props, {
minder: minder,
initialValue: initialValue,
onSubmit: onSubmit,
onChange: setEditorValue,
onCancel: exitEdit
})))) : null, [minder, Editor, initialValue, editingNode]);
}
function Note(props) {
return /*#__PURE__*/createElement("div", {
style: {
background: '#fff',
height: '100%'
}
}, props.node.data.note);
}
function NoteWrapper(minder, props) {
const [showingNode, setShowingNode] = useState();
const {
Note
} = props;
const showNote = useCallback(({
node
}) => {
if (node) {
const box = node.getRenderer('OutlineRenderer').getRenderShape().getRenderBox('view');
const editingNode = {
node,
box
};
if (box.x > 0 || box.y > 0) {
setShowingNode(editingNode);
}
}
}, []);
const hideNote = useCallback(() => {
setShowingNode();
}, []);
useEffect(() => {
const showNodeName = 'shownoterequest';
const mousewheel = 'mousewheel';
if (minder) {
minder.on(showNodeName, showNote);
minder.on(mousewheel, hideNote);
document.addEventListener(mousewheel, hideNote);
}
return () => {
if (minder) {
minder.off(showNodeName, showNote);
minder.off(mousewheel, hideNote);
document.removeEventListener(mousewheel, hideNote);
}
};
}, [minder]);
return useMemo(() => props => showingNode ? /*#__PURE__*/createElement("div", {
style: {
position: 'absolute',
top: `${showingNode.box.y + showingNode.box.height / 2}px`,
left: `${showingNode.box.x}px`,
width: `${showingNode.box.width}px`,
height: `${showingNode.box.height}px`,
transform: 'translateY(-50%)'
},
onMouseOut: hideNote
}, /*#__PURE__*/createElement(Note, Object.assign({}, props, {
node: showingNode.node
}))) : null, [minder, Note, showingNode]);
}
const domPropNames = ['id', 'className', 'style', 'title', 'tabIndex'];
var Kityminder = /*#__PURE__*/forwardRef(function Kityminder(props, ref) {
const minderRef = useRef();
let minder = minderRef.current;
const domProps = {};
Object.keys(props).forEach(propKey => {
if (domPropNames.indexOf(propKey) >= 0) {
domProps[propKey] = props[propKey];
}
});
if (!minder) {
minder = minderRef.current = new window.kityminder.Minder(props);
if (props.onMinderChange) {
setTimeout(() => {
// fix warning: Cannot update a component while rendering a different component.
props.onMinderChange(minder);
}, 0);
}
if (ref) {
ref.current = minder;
}
}
useValue(minder, props.value);
useEvents(minder, props);
useChangeHandler(minder, props.onChange);
const EditorComponent = EditorWrapper(minder, Object.assign({
Editor
}, props));
const NoteComponent = NoteWrapper(minder, Object.assign({
Note
}, props));
return /*#__PURE__*/createElement("div", Object.assign({}, domProps, {
style: {
position: 'relative',
width: '100%',
height: '400px',
...domProps.style
}
}), useMemo(() => /*#__PURE__*/createElement("div", {
ref: div => {
if (div) {
minder.renderTo(div);
const receiver = div.querySelector('.km-receiver');
if (receiver) {
Object.assign(receiver.style, {
position: 'absolute',
left: '-99999px',
top: '-99999px'
});
}
}
},
style: {
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0
}
}), [minder]), /*#__PURE__*/createElement(EditorComponent, props), /*#__PURE__*/createElement(NoteComponent, props));
});
function extendsKityminder(kityminder) {
// fix: detached node getMinder() returns undefined
kityminder.Minder.prototype.attachNode = function (node) {
const _this = this;
const rc = _this.getRenderContainer();
node.traverse(function (current) {
current.attached = true;
current.minder = _this;
rc.addShape(current.getRenderContainer());
});
rc.addShape(node.getRenderContainer());
_this.fire('nodeattach', {
node
});
};
kityminder.Node.prototype.getMinder = function () {
return this.getRoot().minder || this.minder;
};
}
const kity = window.kity;
const kityminder = window.kityminder;
extendsKityminder(kityminder);
export default Kityminder;
export { Kityminder, kity, kityminder };
//# sourceMappingURL=react-kityminder.modern.js.map