@umbraci/jsmind 0.9.6 → 0.9.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @license BSD-3-Clause
3
+ * @copyright 2014-2025 hizzgdev@163.com
4
+ *
5
+ * Project Home:
6
+ * https://github.com/hizzgdev/jsmind/
7
+ */
8
+ import e from"@umbraci/jsmind";if(!e)throw new Error("jsMind is not defined");const n=e.$,t={text_width:200,line_height:"1.5"};function i(e={}){const n=Object.assign({},t,e);return function(e,t,i){return!(!i.topic||!i.topic.includes("\n"))&&(t.style.whiteSpace="pre-wrap",t.style.wordBreak="break-word",t.style.maxWidth=n.text_width+"px",t.textContent=i.topic,!0)}}function o(i,o){console.log("[Multiline Plugin] Initializing...",o);const d=Object.assign({},t,o),l=i.view;let r=null,a=null;function s(){if(!r||!a)return;const e=r._data.view.element;a.parentNode&&a.parentNode.removeChild(a),e.style.zIndex="auto",r=null,l.editing_node=null,a=null}l.opts.custom_node_render&&(l.render_node=l._custom_node_render.bind(l),console.log("[Multiline Plugin] Re-bound view.render_node")),i.mind&&i.mind.root&&(!function(e){const n=e.view,t=e.mind;if(!t||!t.root)return;const i=[];for(const e in t.nodes){const n=t.nodes[e];n._data&&n._data.view&&n._data.view.element&&i.push(n)}for(const e of i){const t=e._data.view.element;n.render_node(t,e)}for(const n of i)if(e.layout.is_visible(n)){const e=n._data.view.element;n._data.view.width=e.clientWidth,n._data.view.height=e.clientHeight}e.layout.layout(),e.view.show(!1)}(i),console.log("[Multiline Plugin] Re-rendered all nodes")),l.edit_node_begin=function(e){if(console.log("[Multiline Plugin] edit_node_begin called",e),!e.topic)return;r&&l.edit_node_end(),r=e,l.editing_node=e;const t=n.c("div");t.contentEditable="plaintext-only",t.className="jsmind-multiline-editor",t.textContent=e.topic,a=t;const i=e._data.view.element;Object.assign(t.style,{width:"auto",minHeight:i.clientHeight+"px",lineHeight:d.line_height,border:"none",outline:"none",whiteSpace:"pre-wrap",wordBreak:"break-word",boxSizing:"border-box",overflow:"hidden"});const o=()=>{t.style.height="auto",t.style.height=t.scrollHeight+"px"};n.on(t,"input",o),setTimeout(o,0),n.on(t,"keydown",e=>{"Enter"!==e.key||e.shiftKey?"Escape"===e.key?(e.preventDefault(),e.stopPropagation(),function(){if(!r||!a)return;const e=r;s(),l.render_node(e._data.view.element,e),l.e_panel.focus()}()):"Tab"===e.key&&(e.preventDefault(),e.stopPropagation(),l.edit_node_end()):(e.preventDefault(),e.stopPropagation(),l.edit_node_end())}),n.on(t,"blur",()=>{setTimeout(()=>{r&&l.edit_node_end()},100)}),i.innerHTML="",i.appendChild(t),i.style.zIndex=5,t.focus();const c=n.d.createRange();c.selectNodeContents(t);const u=n.w.getSelection();u.removeAllRanges(),u.addRange(c)},l.edit_node_end=function(){if(!r||!a)return;const n=r,t=(a.textContent||"").trim().replace(/\r\n/g,"\n").replace(/\r/g,"\n").replace(/\n{3,}/g,"\n\n");s(),e.util.text.is_empty(t)||n.topic===t?l.render_node(n._data.view.element,n):i.update_node(n.id,t),l.e_panel.focus()}}e.register_plugin(new e.plugin("multiline_text",o));var d={name:"multiline_text",init:o,createMultilineRender:i};export{i as createMultilineRender,d as default};
9
+ //# sourceMappingURL=jsmind.multiline-text.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsmind.multiline-text.esm.js","sources":["../src/plugins/jsmind.multiline-text.js"],"sourcesContent":["/**\n * @license BSD\n * @copyright 2014-2025 UmbraCi\n *\n * Project Home:\n * https://github.com/hizzgdev/jsmind/\n */\n\nimport jsMind from '@umbraci/jsmind';\n\nif (!jsMind) {\n throw new Error('jsMind is not defined');\n}\n\nconst $ = jsMind.$;\n\n/**\n * Multiline Text Plugin\n * Provides multiline text support for jsMind nodes\n */\n\n/**\n * Default plugin options\n * @typedef {Object} MultilineTextOptions\n * @property {number} text_width - Maximum width for multiline text nodes (default: 200)\n * @property {string} line_height - Line height for text (default: '1.5')\n */\nconst DEFAULT_OPTIONS = {\n text_width: 200,\n line_height: '1.5',\n};\n\n/**\n * Create a custom node render function for multiline text\n * @param {MultilineTextOptions} [options={}] - Plugin options\n * @param {number} [options.text_width=200] - Maximum width for multiline text nodes\n * @param {string} [options.line_height='1.5'] - Line height for text\n * @returns {function(jsMind, HTMLElement, Node): boolean} Custom render function\n * @example\n * const options = {\n * view: {\n * custom_node_render: createMultilineRender({\n * text_width: 250,\n * line_height: '1.6',\n * })\n * }\n * };\n */\nexport function createMultilineRender(options = {}) {\n const opts = Object.assign({}, DEFAULT_OPTIONS, options);\n\n return function (jm, element, node) {\n if (node.topic && node.topic.includes('\\n')) {\n // Multiline text - apply styles BEFORE setting content\n element.style.whiteSpace = 'pre-wrap';\n element.style.wordBreak = 'break-word';\n element.style.maxWidth = opts.text_width + 'px';\n element.textContent = node.topic;\n return true;\n }\n // Single line text - use default render\n return false;\n };\n}\n\n/**\n * Re-render all nodes to apply multiline styles and recalculate sizes\n * @param {import('../jsmind.js').default} jm - jsMind instance\n * @private\n */\nfunction _rerender_all_nodes(jm) {\n const view = jm.view;\n const mind = jm.mind;\n\n if (!mind || !mind.root) {\n return;\n }\n\n // Collect all nodes to update\n const nodesToUpdate = [];\n for (const nodeId in mind.nodes) {\n const node = mind.nodes[nodeId];\n if (node._data && node._data.view && node._data.view.element) {\n nodesToUpdate.push(node);\n }\n }\n\n // Batch render nodes (only update DOM, no layout trigger)\n for (const node of nodesToUpdate) {\n const element = node._data.view.element;\n view.render_node(element, node);\n }\n\n // Batch update node sizes (read all sizes at once to avoid layout thrashing)\n for (const node of nodesToUpdate) {\n if (jm.layout.is_visible(node)) {\n const element = node._data.view.element;\n node._data.view.width = element.clientWidth;\n node._data.view.height = element.clientHeight;\n }\n }\n\n // Finally recalculate layout and show (only trigger reflow/repaint once)\n jm.layout.layout();\n jm.view.show(false);\n}\n\n/**\n * Plugin initialization function\n * @param {import('../jsmind.js').default} jm - jsMind instance\n * @param {MultilineTextOptions} options - Plugin options\n * @private\n */\nfunction init(jm, options) {\n console.log('[Multiline Plugin] Initializing...', options);\n\n const opts = Object.assign({}, DEFAULT_OPTIONS, options);\n const view = jm.view;\n\n // Plugin state\n let editing_node = null;\n let multiline_editor = null;\n\n // IMPORTANT: Re-set view.render_node to use custom render\n // Because ViewProvider constructor already set it based on options.custom_node_render\n // We need to ensure it uses the custom render function\n if (view.opts.custom_node_render) {\n view.render_node = view._custom_node_render.bind(view);\n console.log('[Multiline Plugin] Re-bound view.render_node');\n }\n\n // Re-render all nodes to apply multiline styles\n if (jm.mind && jm.mind.root) {\n _rerender_all_nodes(jm);\n console.log('[Multiline Plugin] Re-rendered all nodes');\n }\n\n /**\n * Begin editing a node with multiline support\n * @param {import('../jsmind.node.js').Node} node\n */\n view.edit_node_begin = function (node) {\n console.log('[Multiline Plugin] edit_node_begin called', node);\n if (!node.topic) {\n return;\n }\n\n // End editing if another node is being edited\n if (editing_node) {\n view.edit_node_end();\n }\n\n editing_node = node;\n view.editing_node = node;\n\n // Create editor (div with contentEditable)\n const editor = $.c('div');\n editor.contentEditable = 'plaintext-only';\n editor.className = 'jsmind-multiline-editor';\n editor.textContent = node.topic;\n multiline_editor = editor;\n\n // Get element and set editor styles to match\n const element = node._data.view.element;\n\n // Set editor styles to match element width, auto-expand height\n Object.assign(editor.style, {\n width: 'auto',\n minHeight: element.clientHeight + 'px',\n lineHeight: opts.line_height,\n border: 'none',\n outline: 'none',\n whiteSpace: 'pre-wrap',\n wordBreak: 'break-word',\n boxSizing: 'border-box',\n overflow: 'hidden',\n });\n\n // Auto-expand height on input\n const autoExpand = () => {\n editor.style.height = 'auto';\n editor.style.height = editor.scrollHeight + 'px';\n };\n $.on(editor, 'input', autoExpand);\n // Initial expand\n setTimeout(autoExpand, 0);\n\n // Keyboard events\n $.on(editor, 'keydown', e => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n e.stopPropagation(); // Prevent jsMind shortcut from triggering\n view.edit_node_end();\n } else if (e.key === 'Escape') {\n e.preventDefault();\n e.stopPropagation();\n cancel_editing();\n } else if (e.key === 'Tab') {\n e.preventDefault();\n e.stopPropagation();\n view.edit_node_end();\n }\n });\n\n // Blur event - save on blur\n $.on(editor, 'blur', () => {\n setTimeout(() => {\n if (editing_node) {\n view.edit_node_end();\n }\n }, 100);\n });\n\n // Replace node content and focus\n element.innerHTML = '';\n element.appendChild(editor);\n element.style.zIndex = 5;\n editor.focus();\n\n // Select all text\n const range = $.d.createRange();\n range.selectNodeContents(editor);\n const selection = $.w.getSelection();\n selection.removeAllRanges();\n selection.addRange(range);\n };\n\n /**\n * End editing and save changes\n */\n view.edit_node_end = function () {\n if (!editing_node || !multiline_editor) {\n return;\n }\n\n const node = editing_node;\n const topic = (multiline_editor.textContent || '')\n .trim()\n .replace(/\\r\\n/g, '\\n')\n .replace(/\\r/g, '\\n')\n .replace(/\\n{3,}/g, '\\n\\n');\n\n // Clean up editor\n cleanup_editor();\n\n // Update node if content changed\n if (!jsMind.util.text.is_empty(topic) && node.topic !== topic) {\n jm.update_node(node.id, topic);\n } else {\n view.render_node(node._data.view.element, node);\n }\n\n // Focus panel\n view.e_panel.focus();\n };\n\n /**\n * Cancel editing without saving changes\n */\n function cancel_editing() {\n if (!editing_node || !multiline_editor) {\n return;\n }\n\n const node = editing_node;\n\n // Clean up editor\n cleanup_editor();\n\n // Re-render node\n view.render_node(node._data.view.element, node);\n\n // Focus panel\n view.e_panel.focus();\n }\n\n /**\n * Clean up editor and reset state\n */\n function cleanup_editor() {\n if (!editing_node || !multiline_editor) {\n return;\n }\n\n const element = editing_node._data.view.element;\n\n // Remove editor\n if (multiline_editor.parentNode) {\n multiline_editor.parentNode.removeChild(multiline_editor);\n }\n\n // Reset styles and state\n element.style.zIndex = 'auto';\n editing_node = null;\n view.editing_node = null;\n multiline_editor = null;\n }\n}\n\n// Register plugin\njsMind.register_plugin(new jsMind.plugin('multiline_text', init));\n\n// Export for ES6 modules\nexport default {\n name: 'multiline_text',\n init,\n createMultilineRender,\n};\n"],"names":["jsMind","Error","$","DEFAULT_OPTIONS","text_width","line_height","createMultilineRender","options","opts","Object","assign","jm","element","node","topic","includes","style","whiteSpace","wordBreak","maxWidth","textContent","init","console","log","view","editing_node","multiline_editor","cleanup_editor","_data","parentNode","removeChild","zIndex","custom_node_render","render_node","_custom_node_render","bind","mind","root","nodesToUpdate","nodeId","nodes","push","layout","is_visible","width","clientWidth","height","clientHeight","show","_rerender_all_nodes","edit_node_begin","edit_node_end","editor","c","contentEditable","className","minHeight","lineHeight","border","outline","boxSizing","overflow","autoExpand","scrollHeight","on","setTimeout","e","key","shiftKey","preventDefault","stopPropagation","e_panel","focus","cancel_editing","innerHTML","appendChild","range","d","createRange","selectNodeContents","selection","w","getSelection","removeAllRanges","addRange","trim","replace","util","text","is_empty","update_node","id","register_plugin","plugin","jsmind_multilineText","name"],"mappings":"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"}
@@ -5,5 +5,5 @@
5
5
  * Project Home:
6
6
  * https://github.com/hizzgdev/jsmind/
7
7
  */
8
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@umbraci/jsmind")):"function"==typeof define&&define.amd?define(["exports","@umbraci/jsmind"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).jsMindMultilineText={},e.jsMind)}(this,function(e,t){"use strict";function i(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var n=i(t);if(!n.default)throw new Error("jsMind is not defined");const o=n.default.$,s={text_width:200,editor_border_color:"#4CAF50",editor_border_width:"2px",save_shortcut:"Enter",cancel_shortcut:"Escape",newline_shortcut:"Shift+Enter",auto_resize:!0,min_height:20,line_height:1.2},r={tagName:"span",clearElement:!0,applyStyles:!0,customClasses:[],customAttributes:{},customStyles:{},supportHtml:!1,preserveWhitespace:!0};function l(e,t,i={}){if(!(e&&e instanceof HTMLElement))throw new Error("renderTextToElement: element must be a valid DOM element");if(null==t)return console.warn("renderTextToElement: text is null or undefined, skipping render"),e;const s={};n.default.util.json.merge(s,r),n.default.util.json.merge(s,i),s.clearElement&&(e.innerHTML="");const l=String(t);if(s.customAttributes&&"object"==typeof s.customAttributes)for(const[t,i]of Object.entries(s.customAttributes))try{e.setAttribute(t,String(i))}catch(e){console.warn(`renderTextToElement: Failed to set attribute ${t}:`,e)}if(s.customClasses&&Array.isArray(s.customClasses))for(const t of s.customClasses)"string"==typeof t&&t.trim()&&e.classList.add(t.trim());if(l.includes("\n")?(e.textContent=l,s.applyStyles&&(e.style.whiteSpace="pre-wrap",e.style.wordBreak="break-word")):(s.applyStyles&&(e.style.whiteSpace="",e.style.wordBreak=""),s.supportHtml?o.h(e,l):o.t(e,l)),s.customStyles&&"object"==typeof s.customStyles)for(const[t,i]of Object.entries(s.customStyles))try{if(l.includes("\n")&&s.applyStyles){if("whiteSpace"===t||"white-space"===t){console.warn("renderTextToElement: Ignoring whiteSpace override for multiline text");continue}if("wordBreak"===t||"word-break"===t){console.warn("renderTextToElement: Ignoring wordBreak override for multiline text");continue}}e.style[t]=String(i)}catch(e){console.warn(`renderTextToElement: Failed to set style ${t}:`,e)}return e}function d(e,t={}){const i={};n.default.util.json.merge(i,r),n.default.util.json.merge(i,t);return l(o.c(i.tagName||"span"),e,{...i,clearElement:!1})}class _{constructor(e,t){var i={};n.default.util.json.merge(i,s),n.default.util.json.merge(i,t),this.version="0.1.0",this.jm=e,this.options=i,this.original_methods={},this.editing_node=null,this.multiline_editor=null}init(){this.override_view_methods(),this.setup_event_listeners(),this.initialized=!0,console.log("Multiline text plugin initialized - ready for first render")}override_view_methods(){const e=this.jm.view;this.original_methods.edit_node_begin=e.edit_node_begin.bind(e),this.original_methods.edit_node_end=e.edit_node_end.bind(e),this.original_methods.render_node=e.render_node.bind(e),this.original_methods.show=e.show.bind(e),this.original_methods._custom_node_render=e._custom_node_render?e._custom_node_render.bind(e):null,this.original_methods._default_node_render=e._default_node_render?e._default_node_render.bind(e):null;try{e.edit_node_begin=this.edit_node_begin.bind(this),e.edit_node_end=this.edit_node_end.bind(this),e.render_node=this._render_multiline_node_wrapper.bind(this),e.show=this._show_wrapper.bind(this),console.log("Multiline text plugin: Successfully overrode view methods")}catch(e){console.error("Multiline text plugin: Failed to override methods",e),this.restore_original_methods()}}restore_original_methods(){const e=this.jm.view;["edit_node_begin","edit_node_end","render_node","show","_custom_node_render","_default_node_render"].forEach(t=>{this.original_methods[t]&&(e[t]=this.original_methods[t])})}setup_event_listeners(){this.jm.add_event_listener((e,t)=>{this.jm_event_handle(e,t)})}_render_multiline_node_wrapper(e,t){try{this._render_multiline_node(e,t)}catch(i){console.error("Multiline text plugin: Error in render_node",i),this.original_methods.render_node&&this.original_methods.render_node(e,t)}}_show_wrapper(e){try{this.original_methods.show(!1)}catch(t){console.error("Multiline text plugin: Error in show",t),this.original_methods.show&&this.original_methods.show(e)}}_render_multiline_node(e,t){if(!t.topic)return;const i=t.topic.includes("\n"),n=i?e.clientHeight:0;let o=!1;if(this.jm.view.opts.custom_node_render&&"function"==typeof this.jm.view.opts.custom_node_render)try{o=this.jm.view.opts.custom_node_render(this.jm,e,t)}catch(e){console.error("Multiline text plugin: Error in custom_node_render",e),o=!1}if(!o)if(i)l(e,t.topic,{clearElement:!0,applyStyles:!0,supportHtml:this.jm.view.opts.support_html||!1});else{if(this.original_methods.render_node)return void this.original_methods.render_node(e,t);l(e,t.topic,{clearElement:!0,applyStyles:!1,supportHtml:this.jm.view.opts.support_html||!1})}if(i&&n>0){n!==e.clientHeight&&setTimeout(()=>{this.recalculate_layout(t)},0)}}renderMultilineText(e,t,i={}){const o={supportHtml:this.jm.view.opts.support_html||!1},s={};return n.default.util.json.merge(s,o),n.default.util.json.merge(s,i),l(e,t,s)}createMultilineElement(e,t={}){const i={supportHtml:this.jm.view.opts.support_html||!1},o={};return n.default.util.json.merge(o,i),n.default.util.json.merge(o,t),d(e,o)}renderMultilineText(e,t,i={}){const o={supportHtml:this.jm.view.opts.support_html||!1},s={};return n.default.util.json.merge(s,o),n.default.util.json.merge(s,i),l(e,t,s)}createMultilineElement(e,t={}){const i={supportHtml:this.jm.view.opts.support_html||!1},o={};return n.default.util.json.merge(o,i),n.default.util.json.merge(o,t),d(e,o)}edit_node_begin(e){e.topic?(null!=this.editing_node&&this.edit_node_end(),this.editing_node=e,this.jm.view.editing_node=e,this.create_multiline_editor(e._data.view.element,e.topic)):console.warn("don't edit image nodes")}create_multiline_editor(e,t){this.multiline_editor=o.c("div"),this.multiline_editor.contentEditable="plaintext-only",this.multiline_editor.className="jsmind-multiline-editor",this.multiline_editor.textContent=t,this.style_multiline_editor(e),this.setup_editor_events(),e.innerHTML="",e.appendChild(this.multiline_editor),e.style.zIndex=5,this.multiline_editor.focus(),this.select_all_text()}style_multiline_editor(e){const t=getComputedStyle(e),i=this.multiline_editor;i.style.width=Math.max(e.clientWidth-parseInt(t.getPropertyValue("padding-left"))-parseInt(t.getPropertyValue("padding-right")),this.options.text_width)+"px",i.style.minHeight=this.options.min_height+"px",i.style.lineHeight=this.options.line_height,i.style.border=this.options.editor_border_width+" solid "+this.options.editor_border_color,i.style.borderRadius="4px",i.style.padding="4px",i.style.outline="none",i.style.resize="none",i.style.overflow="hidden",i.style.whiteSpace="pre-wrap",i.style.wordBreak="break-word"}setup_editor_events(){const e=this.multiline_editor;o.on(e,"keydown",e=>{this.handle_editor_keydown(e)}),o.on(e,"blur",()=>{setTimeout(()=>{this.editing_node&&this.edit_node_end()},100)}),o.on(e,"input",()=>{this.auto_resize_editor()})}handle_editor_keydown(e){const t=e.key,i=e.shiftKey;if("Enter"===t){if(i)return;e.preventDefault(),this.edit_node_end()}else"Escape"===t?(e.preventDefault(),this.cancel_editing()):"Tab"===t&&(e.preventDefault(),this.edit_node_end())}auto_resize_editor(){if(!this.options.auto_resize||!this.multiline_editor)return;const e=this.multiline_editor;e.style.height="auto";const t=e.scrollHeight,i=this.options.min_height;e.style.height=Math.max(t,i)+"px"}select_all_text(){if(!this.multiline_editor)return;const e=o.d.createRange();e.selectNodeContents(this.multiline_editor);const t=o.w.getSelection();t.removeAllRanges(),t.addRange(e)}_reset_editing_state(){this.editing_node=null,this.jm.view.editing_node=null,this.multiline_editor=null,this.jm.view.e_panel.focus()}edit_node_end(){if(null==this.editing_node||!this.multiline_editor)return;const e=this.editing_node,t=e._data.view.element,i=this.multiline_editor.textContent||"";this.cleanup_editor(t);const o=this.process_multiline_text(i);n.default.util.text.is_empty(o)||e.topic===o?this._render_multiline_node(t,e):this.jm.update_node(e.id,o),this.recalculate_layout(e),this._reset_editing_state()}cancel_editing(){if(null==this.editing_node||!this.multiline_editor)return;const e=this.editing_node,t=e._data.view.element;this.cleanup_editor(t),this._render_multiline_node(t,e),this._reset_editing_state()}cleanup_editor(e){this.multiline_editor&&this.multiline_editor.parentNode&&this.multiline_editor.parentNode.removeChild(this.multiline_editor),e.style.zIndex="auto"}process_multiline_text(e){return e?e=(e=(e=e.trim()).replace(/\r\n/g,"\n").replace(/\r/g,"\n")).replace(/\n{3,}/g,"\n\n"):""}rerender_multiline_nodes_only(){if(!this.jm.mind||!this.jm.mind.nodes)return;let e=0;const t=this.jm.mind.nodes;for(const i in t){const n=t[i];if(n.topic&&n.topic.includes("\n")){const t=n._data.view;t&&t.element&&(this._render_multiline_node(t.element,n),e++)}}console.log(`Multiline text plugin: Re-rendered ${e} multiline nodes`)}rerender_existing_nodes(){this.rerender_multiline_nodes_only()}recalculate_layout(e){this.jm.layout.cache_valid=!1;const t=this.jm.mind.nodes;for(let e in t){const i=t[e];i._data.layout&&(delete i._data.layout._offset_,delete i._data.layout._pout_)}this.jm.view.update_node(e),this.jm.layout.layout(),this.jm.view.show()}jm_event_handle(e,t){n.default.event_type.resize}}const u=new n.default.plugin("multiline_text",function(e,t){var i=new _(e,t);i.init(),e.multiline_text=i});n.default.register_plugin(u),e.MultilineText=_,e.createTextElement=d,e.default=_,e.multiline_text_plugin=u,e.renderTextToElement=l,Object.defineProperty(e,"__esModule",{value:!0})});
8
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("@umbraci/jsmind")):"function"==typeof define&&define.amd?define(["exports","@umbraci/jsmind"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).jsMindMultilineText={},e.jsMind)}(this,function(e,t){"use strict";function n(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var i=n(t);if(!i.default)throw new Error("jsMind is not defined");const o=i.default.$,d={text_width:200,line_height:"1.5"};function l(e={}){const t=Object.assign({},d,e);return function(e,n,i){return!(!i.topic||!i.topic.includes("\n"))&&(n.style.whiteSpace="pre-wrap",n.style.wordBreak="break-word",n.style.maxWidth=t.text_width+"px",n.textContent=i.topic,!0)}}function r(e,t){console.log("[Multiline Plugin] Initializing...",t);const n=Object.assign({},d,t),l=e.view;let r=null,a=null;function s(){if(!r||!a)return;const e=r._data.view.element;a.parentNode&&a.parentNode.removeChild(a),e.style.zIndex="auto",r=null,l.editing_node=null,a=null}l.opts.custom_node_render&&(l.render_node=l._custom_node_render.bind(l),console.log("[Multiline Plugin] Re-bound view.render_node")),e.mind&&e.mind.root&&(!function(e){const t=e.view,n=e.mind;if(!n||!n.root)return;const i=[];for(const e in n.nodes){const t=n.nodes[e];t._data&&t._data.view&&t._data.view.element&&i.push(t)}for(const e of i){const n=e._data.view.element;t.render_node(n,e)}for(const t of i)if(e.layout.is_visible(t)){const e=t._data.view.element;t._data.view.width=e.clientWidth,t._data.view.height=e.clientHeight}e.layout.layout(),e.view.show(!1)}(e),console.log("[Multiline Plugin] Re-rendered all nodes")),l.edit_node_begin=function(e){if(console.log("[Multiline Plugin] edit_node_begin called",e),!e.topic)return;r&&l.edit_node_end(),r=e,l.editing_node=e;const t=o.c("div");t.contentEditable="plaintext-only",t.className="jsmind-multiline-editor",t.textContent=e.topic,a=t;const i=e._data.view.element;Object.assign(t.style,{width:"auto",minHeight:i.clientHeight+"px",lineHeight:n.line_height,border:"none",outline:"none",whiteSpace:"pre-wrap",wordBreak:"break-word",boxSizing:"border-box",overflow:"hidden"});const d=()=>{t.style.height="auto",t.style.height=t.scrollHeight+"px"};o.on(t,"input",d),setTimeout(d,0),o.on(t,"keydown",e=>{"Enter"!==e.key||e.shiftKey?"Escape"===e.key?(e.preventDefault(),e.stopPropagation(),function(){if(!r||!a)return;const e=r;s(),l.render_node(e._data.view.element,e),l.e_panel.focus()}()):"Tab"===e.key&&(e.preventDefault(),e.stopPropagation(),l.edit_node_end()):(e.preventDefault(),e.stopPropagation(),l.edit_node_end())}),o.on(t,"blur",()=>{setTimeout(()=>{r&&l.edit_node_end()},100)}),i.innerHTML="",i.appendChild(t),i.style.zIndex=5,t.focus();const u=o.d.createRange();u.selectNodeContents(t);const c=o.w.getSelection();c.removeAllRanges(),c.addRange(u)},l.edit_node_end=function(){if(!r||!a)return;const t=r,n=(a.textContent||"").trim().replace(/\r\n/g,"\n").replace(/\r/g,"\n").replace(/\n{3,}/g,"\n\n");s(),i.default.util.text.is_empty(n)||t.topic===n?l.render_node(t._data.view.element,t):e.update_node(t.id,n),l.e_panel.focus()}}i.default.register_plugin(new i.default.plugin("multiline_text",r));var a={name:"multiline_text",init:r,createMultilineRender:l};e.createMultilineRender=l,e.default=a,Object.defineProperty(e,"__esModule",{value:!0})});
9
9
  //# sourceMappingURL=jsmind.multiline-text.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"jsmind.multiline-text.js","sources":["../src/plugins/jsmind.multiline-text.js"],"sourcesContent":["/**\n * @license BSD\n * @copyright 2014-2025 UmbraCi\n *\n * Project Home:\n * https://github.com/UmbraCi/jsmind/\n */\n\nimport jsMind from '@umbraci/jsmind';\n\nif (!jsMind) {\n throw new Error('jsMind is not defined');\n}\n\nconst $ = jsMind.$;\n\n/**\n * Default options for multiline text plugin.\n * @typedef {Object} MultilineTextOptions\n * @property {number} [text_width] - Maximum text width in pixels\n * @property {string} [editor_border_color] - Border color for active editor\n * @property {string} [editor_border_width] - Border width for active editor\n * @property {string} [save_shortcut] - Key to save (without Shift)\n * @property {string} [cancel_shortcut] - Key to cancel editing\n * @property {string} [newline_shortcut] - Key combination for new line\n * @property {boolean} [auto_resize] - Auto-resize nodes for multiline text\n * @property {number} [min_height] - Minimum node height\n * @property {number} [line_height] - Line height multiplier\n */\nconst DEFAULT_OPTIONS = {\n text_width: 200,\n editor_border_color: '#4CAF50',\n editor_border_width: '2px',\n save_shortcut: 'Enter',\n cancel_shortcut: 'Escape',\n newline_shortcut: 'Shift+Enter',\n auto_resize: true,\n min_height: 20,\n line_height: 1.2,\n};\n\n/**\n * Default options for text rendering functions.\n * @typedef {Object} TextRenderOptions\n * @property {string} [tagName='span'] - HTML tag name for created elements\n * @property {boolean} [clearElement=true] - Whether to clear existing element content\n * @property {boolean} [applyStyles=true] - Whether to apply multiline CSS styles\n * @property {string[]} [customClasses=[]] - Array of CSS class names to add\n * @property {Object} [customAttributes={}] - Object of HTML attributes to set\n * @property {Object} [customStyles={}] - Object of inline styles to apply\n * @property {boolean} [supportHtml=false] - Whether to support HTML content\n * @property {boolean} [preserveWhitespace=true] - Whether to preserve whitespace in multiline text\n */\nconst DEFAULT_RENDER_OPTIONS = {\n tagName: 'span',\n clearElement: true,\n applyStyles: true,\n customClasses: [],\n customAttributes: {},\n customStyles: {},\n supportHtml: false,\n preserveWhitespace: true,\n};\n\n/**\n * Static utility function to render text content into an existing DOM element.\n *\n * This function handles both single-line and multiline text rendering with proper CSS styling.\n * For multiline text (containing \\n), it applies 'white-space: pre-wrap' and 'word-break: break-word'.\n * For single-line text, it uses standard text or HTML rendering based on supportHtml option.\n *\n * @param {HTMLElement} element - Target DOM element to render text into\n * @param {string} text - Text content to render (supports \\n for line breaks)\n * @param {Partial<TextRenderOptions>} [options={}] - Rendering options\n * @param {string} [options.tagName='span'] - HTML tag name (only used when creating new elements)\n * @param {boolean} [options.clearElement=true] - Whether to clear existing element content\n * @param {boolean} [options.applyStyles=true] - Whether to apply multiline CSS styles\n * @param {string[]} [options.customClasses=[]] - Array of CSS class names to add\n * @param {Object} [options.customAttributes={}] - Object of HTML attributes to set\n * @param {Object} [options.customStyles={}] - Object of inline styles to apply\n * @param {boolean} [options.supportHtml=false] - Whether to support HTML content in single-line text\n * @param {boolean} [options.preserveWhitespace=true] - Whether to preserve whitespace in multiline text\n * @returns {HTMLElement} The element with rendered text content\n * @throws {Error} If element is not a valid DOM element\n *\n * @example\n * // Basic multiline text rendering\n * const element = document.createElement('div');\n * renderTextToElement(element, 'Line 1\\nLine 2\\nLine 3');\n *\n * @example\n * // Custom styling and attributes\n * renderTextToElement(element, 'Custom text', {\n * customClasses: ['highlight', 'bold'],\n * customAttributes: { 'data-id': '123' },\n * customStyles: { color: 'red', fontSize: '14px' }\n * });\n *\n * @example\n * // HTML support for single-line text\n * renderTextToElement(element, '<strong>Bold text</strong>', {\n * supportHtml: true\n * });\n */\nexport function renderTextToElement(element, text, options = {}) {\n // Validate parameters\n if (!element || !(element instanceof HTMLElement)) {\n throw new Error('renderTextToElement: element must be a valid DOM element');\n }\n\n if (text == null) {\n console.warn('renderTextToElement: text is null or undefined, skipping render');\n return element;\n }\n\n // Merge options with defaults\n const opts = {};\n jsMind.util.json.merge(opts, DEFAULT_RENDER_OPTIONS);\n jsMind.util.json.merge(opts, options);\n\n // Clear element content if requested\n if (opts.clearElement) {\n element.innerHTML = '';\n }\n\n // Convert text to string\n const textContent = String(text);\n\n // Apply custom attributes\n if (opts.customAttributes && typeof opts.customAttributes === 'object') {\n for (const [key, value] of Object.entries(opts.customAttributes)) {\n try {\n element.setAttribute(key, String(value));\n } catch (error) {\n console.warn(`renderTextToElement: Failed to set attribute ${key}:`, error);\n }\n }\n }\n\n // Apply custom classes\n if (opts.customClasses && Array.isArray(opts.customClasses)) {\n for (const className of opts.customClasses) {\n if (typeof className === 'string' && className.trim()) {\n element.classList.add(className.trim());\n }\n }\n }\n\n // Render text content\n if (textContent.includes('\\n')) {\n // Handle multiline text\n element.textContent = textContent;\n\n if (opts.applyStyles) {\n // Apply multiline styles (these take precedence over custom styles)\n element.style.whiteSpace = 'pre-wrap';\n element.style.wordBreak = 'break-word';\n }\n } else {\n // Handle single-line text\n if (opts.applyStyles) {\n // Reset multiline styles for single-line text\n element.style.whiteSpace = '';\n element.style.wordBreak = '';\n }\n\n // Render based on HTML support\n if (opts.supportHtml) {\n $.h(element, textContent);\n } else {\n $.t(element, textContent);\n }\n }\n\n // Apply custom styles (after multiline styles to allow overrides where appropriate)\n if (opts.customStyles && typeof opts.customStyles === 'object') {\n for (const [property, value] of Object.entries(opts.customStyles)) {\n try {\n // Don't override critical multiline styles if text is multiline\n if (textContent.includes('\\n') && opts.applyStyles) {\n if (property === 'whiteSpace' || property === 'white-space') {\n console.warn(\n 'renderTextToElement: Ignoring whiteSpace override for multiline text'\n );\n continue;\n }\n if (property === 'wordBreak' || property === 'word-break') {\n console.warn(\n 'renderTextToElement: Ignoring wordBreak override for multiline text'\n );\n continue;\n }\n }\n element.style[property] = String(value);\n } catch (error) {\n console.warn(`renderTextToElement: Failed to set style ${property}:`, error);\n }\n }\n }\n\n return element;\n}\n\n/**\n * Static utility function to create a new DOM element with rendered text content.\n *\n * This function creates a new DOM element and renders text content into it using the same\n * logic as renderTextToElement. It's useful when you need a new element rather than\n * modifying an existing one.\n *\n * @param {string} text - Text content to render (supports \\n for line breaks)\n * @param {Partial<TextRenderOptions>} [options={}] - Rendering options\n * @param {string} [options.tagName='span'] - HTML tag name for the created element\n * @param {boolean} [options.applyStyles=true] - Whether to apply multiline CSS styles\n * @param {string[]} [options.customClasses=[]] - Array of CSS class names to add\n * @param {Object} [options.customAttributes={}] - Object of HTML attributes to set\n * @param {Object} [options.customStyles={}] - Object of inline styles to apply\n * @param {boolean} [options.supportHtml=false] - Whether to support HTML content in single-line text\n * @returns {HTMLElement} New DOM element with rendered text content\n *\n * @example\n * // Create a div with multiline text\n * const textDiv = createTextElement('Line 1\\nLine 2', {\n * tagName: 'div',\n * customClasses: ['multiline-text']\n * });\n *\n * @example\n * // Create a span with custom styling\n * const styledSpan = createTextElement('Styled text', {\n * customStyles: {\n * backgroundColor: '#f0f0f0',\n * padding: '4px',\n * borderRadius: '4px'\n * }\n * });\n */\nexport function createTextElement(text, options = {}) {\n // Merge options with defaults\n const opts = {};\n jsMind.util.json.merge(opts, DEFAULT_RENDER_OPTIONS);\n jsMind.util.json.merge(opts, options);\n\n // Create new element\n const element = $.c(opts.tagName || 'span');\n\n // Use renderTextToElement to handle the rendering\n return renderTextToElement(element, text, { ...opts, clearElement: false });\n}\n\n/**\n * Multiline text plugin for jsMind.\n */\nexport class MultilineText {\n /**\n * Create multiline text plugin instance.\n * @param {import('../jsmind.js').default} jm - jsMind instance\n * @param {Partial<MultilineTextOptions>} options - Plugin options\n */\n constructor(jm, options) {\n var opts = {};\n jsMind.util.json.merge(opts, DEFAULT_OPTIONS);\n jsMind.util.json.merge(opts, options);\n\n this.version = '0.1.0';\n /** @type {import('../jsmind.js').default} */\n this.jm = jm;\n /** @type {MultilineTextOptions} */\n this.options = opts;\n\n // Store original methods for restoration if needed\n this.original_methods = {};\n\n // Current editing state\n this.editing_node = null;\n this.multiline_editor = null;\n }\n\n /** Initialize the multiline text plugin. */\n init() {\n this.override_view_methods();\n this.setup_event_listeners();\n this.initialized = true;\n console.log('Multiline text plugin initialized - ready for first render');\n }\n\n /**\n * Override ViewProvider methods to support multiline text.\n */\n override_view_methods() {\n const view = this.jm.view;\n\n // Store original methods for fallback\n this.original_methods.edit_node_begin = view.edit_node_begin.bind(view);\n this.original_methods.edit_node_end = view.edit_node_end.bind(view);\n this.original_methods.render_node = view.render_node.bind(view);\n this.original_methods.show = view.show.bind(view);\n\n // Store original custom render methods if they exist\n this.original_methods._custom_node_render = view._custom_node_render\n ? view._custom_node_render.bind(view)\n : null;\n this.original_methods._default_node_render = view._default_node_render\n ? view._default_node_render.bind(view)\n : null;\n\n // Override methods with error handling\n try {\n view.edit_node_begin = this.edit_node_begin.bind(this);\n view.edit_node_end = this.edit_node_end.bind(this);\n view.render_node = this._render_multiline_node_wrapper.bind(this);\n view.show = this._show_wrapper.bind(this);\n console.log('Multiline text plugin: Successfully overrode view methods');\n } catch (error) {\n console.error('Multiline text plugin: Failed to override methods', error);\n // Restore original methods if override fails\n this.restore_original_methods();\n }\n }\n\n /**\n * Restore original methods if override fails.\n */\n restore_original_methods() {\n const view = this.jm.view;\n const methods = [\n 'edit_node_begin',\n 'edit_node_end',\n 'render_node',\n 'show',\n '_custom_node_render',\n '_default_node_render',\n ];\n\n methods.forEach(method => {\n if (this.original_methods[method]) {\n view[method] = this.original_methods[method];\n }\n });\n }\n\n /**\n * Set up event listeners for the plugin.\n */\n setup_event_listeners() {\n this.jm.add_event_listener((type, data) => {\n this.jm_event_handle(type, data);\n });\n }\n\n /**\n * Wrapper for render_node method with error handling.\n * @param {HTMLElement} element - Node element\n * @param {import('../jsmind.node.js').Node} node - Node data\n */\n _render_multiline_node_wrapper(element, node) {\n try {\n this._render_multiline_node(element, node);\n } catch (error) {\n console.error('Multiline text plugin: Error in render_node', error);\n // Fall back to original method\n if (this.original_methods.render_node) {\n this.original_methods.render_node(element, node);\n }\n }\n }\n\n /**\n * Wrapper for show method to prevent unwanted view resets.\n * @param {boolean} keep_center - Whether to center on root node\n */\n _show_wrapper(keep_center) {\n try {\n // Always call show without centering to prevent view jumps\n this.original_methods.show(false);\n } catch (error) {\n console.error('Multiline text plugin: Error in show', error);\n // Fall back to original method\n if (this.original_methods.show) {\n this.original_methods.show(keep_center);\n }\n }\n }\n\n /**\n * Render multiline text in node display with custom_node_render support.\n * @param {HTMLElement} element - Node element\n * @param {import('../jsmind.node.js').Node} node - Node data\n */\n _render_multiline_node(element, node) {\n if (!node.topic) {\n return;\n }\n\n const hasMultilineText = node.topic.includes('\\n');\n const originalHeight = hasMultilineText ? element.clientHeight : 0;\n\n // Check if we have custom node render function\n const hasCustomRender =\n this.jm.view.opts.custom_node_render &&\n typeof this.jm.view.opts.custom_node_render === 'function';\n\n let customRendered = false;\n\n if (hasCustomRender) {\n try {\n customRendered = this.jm.view.opts.custom_node_render(this.jm, element, node);\n } catch (error) {\n console.error('Multiline text plugin: Error in custom_node_render', error);\n customRendered = false;\n }\n }\n\n // If custom render didn't handle it, use appropriate rendering strategy\n if (!customRendered) {\n if (hasMultilineText) {\n renderTextToElement(element, node.topic, {\n clearElement: true,\n applyStyles: true,\n supportHtml: this.jm.view.opts.support_html || false,\n });\n } else {\n if (this.original_methods.render_node) {\n this.original_methods.render_node(element, node);\n return;\n } else {\n renderTextToElement(element, node.topic, {\n clearElement: true,\n applyStyles: false,\n supportHtml: this.jm.view.opts.support_html || false,\n });\n }\n }\n }\n\n // Recalculate layout only if multiline text height changed\n if (hasMultilineText && originalHeight > 0) {\n const newHeight = element.clientHeight;\n if (originalHeight !== newHeight) {\n setTimeout(() => {\n this.recalculate_layout(node);\n }, 0);\n }\n }\n }\n\n /**\n * Plugin instance method to render text content into an existing DOM element.\n *\n * This method automatically uses jsMind configuration settings (like support_html)\n * and provides a convenient way to render multiline text within custom node renderers.\n *\n * @param {HTMLElement} element - Target DOM element to render text into\n * @param {string} text - Text content to render (supports \\n for line breaks)\n * @param {Partial<TextRenderOptions>} [options={}] - Additional rendering options\n * @returns {HTMLElement} The element with rendered text content\n *\n * @example\n * // In a custom node render function\n * customNodeRender(jm, element, node) {\n * const wrapper = document.createElement('div');\n * wrapper.style.backgroundColor = '#f0f0f0';\n * wrapper.style.padding = '4px';\n *\n * const textElement = document.createElement('span');\n * jm.multiline_text.renderMultilineText(textElement, node.topic);\n *\n * wrapper.appendChild(textElement);\n * element.appendChild(wrapper);\n * return true;\n * }\n */\n renderMultilineText(element, text, options = {}) {\n // Prepare options with jsMind configuration\n const defaultOptions = {\n supportHtml: this.jm.view.opts.support_html || false,\n };\n\n // Merge with provided options\n const mergedOptions = {};\n jsMind.util.json.merge(mergedOptions, defaultOptions);\n jsMind.util.json.merge(mergedOptions, options);\n\n // Use static function with merged options\n return renderTextToElement(element, text, mergedOptions);\n }\n\n /**\n * Plugin instance method to create a new DOM element with rendered text content.\n *\n * This method automatically uses jsMind configuration settings and creates a new\n * element with properly rendered multiline text. Useful for building complex\n * custom node structures.\n *\n * @param {string} text - Text content to render (supports \\n for line breaks)\n * @param {Partial<TextRenderOptions>} [options={}] - Additional rendering options\n * @returns {HTMLElement} New DOM element with rendered text content\n *\n * @example\n * // Create a text element for insertion into custom structure\n * customNodeRender(jm, element, node) {\n * const container = document.createElement('div');\n * container.className = 'custom-node';\n *\n * // Add priority indicator\n * if (node.data?.priority) {\n * const priority = document.createElement('span');\n * priority.className = 'priority-badge';\n * priority.textContent = node.data.priority;\n * container.appendChild(priority);\n * }\n *\n * // Add multiline text content\n * const textElement = jm.multiline_text.createMultilineElement(node.topic, {\n * tagName: 'div',\n * customClasses: ['node-text']\n * });\n * container.appendChild(textElement);\n *\n * element.appendChild(container);\n * return true;\n * }\n */\n createMultilineElement(text, options = {}) {\n // Prepare options with jsMind configuration\n const defaultOptions = {\n supportHtml: this.jm.view.opts.support_html || false,\n };\n\n // Merge with provided options\n const mergedOptions = {};\n jsMind.util.json.merge(mergedOptions, defaultOptions);\n jsMind.util.json.merge(mergedOptions, options);\n\n // Use static function with merged options\n return createTextElement(text, mergedOptions);\n }\n\n /**\n * Plugin instance method to render text content into an existing DOM element.\n *\n * This method automatically uses jsMind configuration settings (like support_html)\n * and provides a convenient way to render multiline text within custom node renderers.\n *\n * @param {HTMLElement} element - Target DOM element to render text into\n * @param {string} text - Text content to render (supports \\n for line breaks)\n * @param {Partial<TextRenderOptions>} [options={}] - Additional rendering options\n * @returns {HTMLElement} The element with rendered text content\n *\n * @example\n * // In a custom node render function\n * customNodeRender(jm, element, node) {\n * const wrapper = document.createElement('div');\n * wrapper.style.backgroundColor = '#f0f0f0';\n * wrapper.style.padding = '4px';\n *\n * const textElement = document.createElement('span');\n * jm.multiline_text.renderMultilineText(textElement, node.topic);\n *\n * wrapper.appendChild(textElement);\n * element.appendChild(wrapper);\n * return true;\n * }\n */\n renderMultilineText(element, text, options = {}) {\n // Prepare options with jsMind configuration\n const defaultOptions = {\n supportHtml: this.jm.view.opts.support_html || false,\n };\n\n // Merge with provided options\n const mergedOptions = {};\n jsMind.util.json.merge(mergedOptions, defaultOptions);\n jsMind.util.json.merge(mergedOptions, options);\n\n // Use static function with merged options\n return renderTextToElement(element, text, mergedOptions);\n }\n\n /**\n * Plugin instance method to create a new DOM element with rendered text content.\n *\n * This method automatically uses jsMind configuration settings and creates a new\n * element with properly rendered multiline text. Useful for building complex\n * custom node structures.\n *\n * @param {string} text - Text content to render (supports \\n for line breaks)\n * @param {Partial<TextRenderOptions>} [options={}] - Additional rendering options\n * @returns {HTMLElement} New DOM element with rendered text content\n *\n * @example\n * // Create a text element for insertion into custom structure\n * customNodeRender(jm, element, node) {\n * const container = document.createElement('div');\n * container.className = 'custom-node';\n *\n * // Add priority indicator\n * if (node.data?.priority) {\n * const priority = document.createElement('span');\n * priority.className = 'priority-badge';\n * priority.textContent = node.data.priority;\n * container.appendChild(priority);\n * }\n *\n * // Add multiline text content\n * const textElement = jm.multiline_text.createMultilineElement(node.topic, {\n * tagName: 'div',\n * customClasses: ['node-text']\n * });\n * container.appendChild(textElement);\n *\n * element.appendChild(container);\n * return true;\n * }\n */\n createMultilineElement(text, options = {}) {\n // Prepare options with jsMind configuration\n const defaultOptions = {\n supportHtml: this.jm.view.opts.support_html || false,\n };\n\n // Merge with provided options\n const mergedOptions = {};\n jsMind.util.json.merge(mergedOptions, defaultOptions);\n jsMind.util.json.merge(mergedOptions, options);\n\n // Use static function with merged options\n return createTextElement(text, mergedOptions);\n }\n\n /**\n * Begin editing a node with multiline support.\n * @param {import('../jsmind.node.js').Node} node - Node to edit\n */\n edit_node_begin(node) {\n if (!node.topic) {\n console.warn(\"don't edit image nodes\");\n return;\n }\n\n if (this.editing_node != null) {\n this.edit_node_end();\n }\n\n this.editing_node = node;\n this.jm.view.editing_node = node;\n\n this.create_multiline_editor(node._data.view.element, node.topic);\n }\n\n /**\n * Create contentEditable multiline editor.\n * @param {HTMLElement} element - Node element\n * @param {string} topic - Current text content\n */\n create_multiline_editor(element, topic) {\n // Create contentEditable div\n this.multiline_editor = $.c('div');\n this.multiline_editor.contentEditable = 'plaintext-only';\n this.multiline_editor.className = 'jsmind-multiline-editor';\n\n // Set initial content\n this.multiline_editor.textContent = topic;\n\n // Style the editor\n this.style_multiline_editor(element);\n\n // Add keyboard event handling\n this.setup_editor_events();\n\n // Replace element content with editor\n element.innerHTML = '';\n element.appendChild(this.multiline_editor);\n element.style.zIndex = 5;\n\n // Focus and select content\n this.multiline_editor.focus();\n this.select_all_text();\n }\n\n /**\n * Style the multiline editor.\n * @param {HTMLElement} element - Original node element\n */\n style_multiline_editor(element) {\n const ncs = getComputedStyle(element);\n const editor = this.multiline_editor;\n\n // Copy styles from original element\n editor.style.width =\n Math.max(\n element.clientWidth -\n parseInt(ncs.getPropertyValue('padding-left')) -\n parseInt(ncs.getPropertyValue('padding-right')),\n this.options.text_width\n ) + 'px';\n\n editor.style.minHeight = this.options.min_height + 'px';\n editor.style.lineHeight = this.options.line_height;\n editor.style.border =\n this.options.editor_border_width + ' solid ' + this.options.editor_border_color;\n editor.style.borderRadius = '4px';\n editor.style.padding = '4px';\n editor.style.outline = 'none';\n editor.style.resize = 'none';\n editor.style.overflow = 'hidden';\n editor.style.whiteSpace = 'pre-wrap';\n editor.style.wordBreak = 'break-word';\n }\n\n /**\n * Set up keyboard event handling for the editor.\n */\n setup_editor_events() {\n const editor = this.multiline_editor;\n\n $.on(editor, 'keydown', e => {\n this.handle_editor_keydown(e);\n });\n\n $.on(editor, 'blur', () => {\n // Delay to allow other events to process first\n setTimeout(() => {\n if (this.editing_node) {\n this.edit_node_end();\n }\n }, 100);\n });\n\n // Auto-resize editor as user types\n $.on(editor, 'input', () => {\n this.auto_resize_editor();\n });\n }\n\n /**\n * Handle keyboard events in the editor.\n * @param {KeyboardEvent} e - Keyboard event\n */\n handle_editor_keydown(e) {\n const key = e.key;\n const shiftKey = e.shiftKey;\n\n if (key === 'Enter') {\n if (shiftKey) {\n // Shift+Enter: Allow line break (default behavior)\n return;\n } else {\n // Enter: Save and exit\n e.preventDefault();\n this.edit_node_end();\n }\n } else if (key === 'Escape') {\n // Escape: Cancel editing\n e.preventDefault();\n this.cancel_editing();\n } else if (key === 'Tab') {\n // Tab: Save and exit (like Enter)\n e.preventDefault();\n this.edit_node_end();\n }\n }\n\n /**\n * Auto-resize editor based on content.\n */\n auto_resize_editor() {\n if (!this.options.auto_resize || !this.multiline_editor) {\n return;\n }\n\n const editor = this.multiline_editor;\n\n // Reset height to auto to get natural height\n editor.style.height = 'auto';\n\n // Set height to scroll height to fit content\n const scrollHeight = editor.scrollHeight;\n const minHeight = this.options.min_height;\n\n editor.style.height = Math.max(scrollHeight, minHeight) + 'px';\n }\n\n /**\n * Select all text in the editor.\n */\n select_all_text() {\n if (!this.multiline_editor) return;\n\n const range = $.d.createRange();\n range.selectNodeContents(this.multiline_editor);\n\n const selection = $.w.getSelection();\n selection.removeAllRanges();\n selection.addRange(range);\n }\n\n /**\n * Reset editing state and return focus to panel.\n */\n _reset_editing_state() {\n this.editing_node = null;\n this.jm.view.editing_node = null;\n this.multiline_editor = null;\n this.jm.view.e_panel.focus();\n }\n\n /**\n * End editing and save changes.\n */\n edit_node_end() {\n if (this.editing_node == null || !this.multiline_editor) {\n return;\n }\n\n const node = this.editing_node;\n const element = node._data.view.element;\n const topic = this.multiline_editor.textContent || '';\n\n this.cleanup_editor(element);\n\n const processed_topic = this.process_multiline_text(topic);\n\n if (jsMind.util.text.is_empty(processed_topic) || node.topic === processed_topic) {\n this._render_multiline_node(element, node);\n } else {\n this.jm.update_node(node.id, processed_topic);\n }\n\n this.recalculate_layout(node);\n this._reset_editing_state();\n }\n\n /**\n * Cancel editing without saving changes.\n */\n cancel_editing() {\n if (this.editing_node == null || !this.multiline_editor) {\n return;\n }\n\n const node = this.editing_node;\n const element = node._data.view.element;\n\n this.cleanup_editor(element);\n this._render_multiline_node(element, node);\n this._reset_editing_state();\n }\n\n /**\n * Clean up editor and restore element state.\n * @param {HTMLElement} element - Node element\n */\n cleanup_editor(element) {\n if (this.multiline_editor && this.multiline_editor.parentNode) {\n this.multiline_editor.parentNode.removeChild(this.multiline_editor);\n }\n element.style.zIndex = 'auto';\n }\n\n /**\n * Process and validate multiline text.\n * @param {string} text - Raw text from editor\n * @returns {string} Processed text\n */\n process_multiline_text(text) {\n if (!text) return '';\n\n // Trim whitespace but preserve internal line breaks\n text = text.trim();\n\n // Normalize line breaks to \\n\n text = text.replace(/\\r\\n/g, '\\n').replace(/\\r/g, '\\n');\n\n // Remove excessive consecutive line breaks (more than 2)\n text = text.replace(/\\n{3,}/g, '\\n\\n');\n\n return text;\n }\n\n /**\n * Re-render only nodes that contain multiline text for better performance.\n */\n rerender_multiline_nodes_only() {\n if (!this.jm.mind || !this.jm.mind.nodes) {\n return;\n }\n\n let rerendered_count = 0;\n const nodes = this.jm.mind.nodes;\n\n for (const node_id in nodes) {\n const node = nodes[node_id];\n if (node.topic && node.topic.includes('\\n')) {\n const view_data = node._data.view;\n if (view_data && view_data.element) {\n this._render_multiline_node(view_data.element, node);\n rerendered_count++;\n }\n }\n }\n\n console.log(`Multiline text plugin: Re-rendered ${rerendered_count} multiline nodes`);\n }\n\n /**\n * @deprecated Use rerender_multiline_nodes_only() for better performance\n */\n rerender_existing_nodes() {\n this.rerender_multiline_nodes_only();\n }\n\n /**\n * Recalculate layout after text changes.\n * @param {import('../jsmind.node.js').Node} node - Updated node\n */\n recalculate_layout(node) {\n // Clear layout cache to force recalculation\n this.jm.layout.cache_valid = false;\n\n // Clear any cached offset and point data for all nodes\n const nodes = this.jm.mind.nodes;\n for (let nodeid in nodes) {\n const n = nodes[nodeid];\n if (n._data.layout) {\n delete n._data.layout._offset_;\n delete n._data.layout._pout_;\n }\n }\n\n // Update node size first\n this.jm.view.update_node(node);\n\n // Trigger complete layout recalculation\n this.jm.layout.layout();\n\n // Redraw view with updated layout and lines\n this.jm.view.show();\n }\n\n /**\n * Handle jsMind events.\n * @param {number|string} type - Event type\n * @param {object} [data] - Event data\n */\n jm_event_handle(type, data) {\n if (type === jsMind.event_type.resize) {\n // Handle resize events if needed\n }\n void data; // Suppress unused parameter warning\n }\n}\n\n/**\n * Multiline text plugin registration.\n * @type {import('../jsmind.plugin.js').Plugin<Partial<MultilineTextOptions>>}\n */\nexport const multiline_text_plugin = new jsMind.plugin('multiline_text', function (jm, options) {\n var mt = new MultilineText(jm, options);\n mt.init();\n jm.multiline_text = mt;\n});\n\njsMind.register_plugin(multiline_text_plugin);\n\nexport default MultilineText;\n"],"names":["jsMind","Error","$","DEFAULT_OPTIONS","text_width","editor_border_color","editor_border_width","save_shortcut","cancel_shortcut","newline_shortcut","auto_resize","min_height","line_height","DEFAULT_RENDER_OPTIONS","tagName","clearElement","applyStyles","customClasses","customAttributes","customStyles","supportHtml","preserveWhitespace","renderTextToElement","element","text","options","HTMLElement","console","warn","opts","util","json","merge","innerHTML","textContent","String","key","value","Object","entries","setAttribute","error","Array","isArray","className","trim","classList","add","includes","style","whiteSpace","wordBreak","h","t","property","createTextElement","c","MultilineText","constructor","jm","this","version","original_methods","editing_node","multiline_editor","init","override_view_methods","setup_event_listeners","initialized","log","view","edit_node_begin","bind","edit_node_end","render_node","show","_custom_node_render","_default_node_render","_render_multiline_node_wrapper","_show_wrapper","restore_original_methods","forEach","method","add_event_listener","type","data","jm_event_handle","node","_render_multiline_node","keep_center","topic","hasMultilineText","originalHeight","clientHeight","customRendered","custom_node_render","support_html","setTimeout","recalculate_layout","renderMultilineText","defaultOptions","mergedOptions","createMultilineElement","create_multiline_editor","_data","contentEditable","style_multiline_editor","setup_editor_events","appendChild","zIndex","focus","select_all_text","ncs","getComputedStyle","editor","width","Math","max","clientWidth","parseInt","getPropertyValue","minHeight","lineHeight","border","borderRadius","padding","outline","resize","overflow","on","e","handle_editor_keydown","auto_resize_editor","shiftKey","preventDefault","cancel_editing","height","scrollHeight","range","d","createRange","selectNodeContents","selection","w","getSelection","removeAllRanges","addRange","_reset_editing_state","e_panel","cleanup_editor","processed_topic","process_multiline_text","is_empty","update_node","id","parentNode","removeChild","replace","rerender_multiline_nodes_only","mind","nodes","rerendered_count","node_id","view_data","rerender_existing_nodes","layout","cache_valid","nodeid","n","_offset_","_pout_","event_type","multiline_text_plugin","plugin","mt","multiline_text","register_plugin"],"mappings":"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"}
1
+ {"version":3,"file":"jsmind.multiline-text.js","sources":["../src/plugins/jsmind.multiline-text.js"],"sourcesContent":["/**\n * @license BSD\n * @copyright 2014-2025 UmbraCi\n *\n * Project Home:\n * https://github.com/hizzgdev/jsmind/\n */\n\nimport jsMind from '@umbraci/jsmind';\n\nif (!jsMind) {\n throw new Error('jsMind is not defined');\n}\n\nconst $ = jsMind.$;\n\n/**\n * Multiline Text Plugin\n * Provides multiline text support for jsMind nodes\n */\n\n/**\n * Default plugin options\n * @typedef {Object} MultilineTextOptions\n * @property {number} text_width - Maximum width for multiline text nodes (default: 200)\n * @property {string} line_height - Line height for text (default: '1.5')\n */\nconst DEFAULT_OPTIONS = {\n text_width: 200,\n line_height: '1.5',\n};\n\n/**\n * Create a custom node render function for multiline text\n * @param {MultilineTextOptions} [options={}] - Plugin options\n * @param {number} [options.text_width=200] - Maximum width for multiline text nodes\n * @param {string} [options.line_height='1.5'] - Line height for text\n * @returns {function(jsMind, HTMLElement, Node): boolean} Custom render function\n * @example\n * const options = {\n * view: {\n * custom_node_render: createMultilineRender({\n * text_width: 250,\n * line_height: '1.6',\n * })\n * }\n * };\n */\nexport function createMultilineRender(options = {}) {\n const opts = Object.assign({}, DEFAULT_OPTIONS, options);\n\n return function (jm, element, node) {\n if (node.topic && node.topic.includes('\\n')) {\n // Multiline text - apply styles BEFORE setting content\n element.style.whiteSpace = 'pre-wrap';\n element.style.wordBreak = 'break-word';\n element.style.maxWidth = opts.text_width + 'px';\n element.textContent = node.topic;\n return true;\n }\n // Single line text - use default render\n return false;\n };\n}\n\n/**\n * Re-render all nodes to apply multiline styles and recalculate sizes\n * @param {import('../jsmind.js').default} jm - jsMind instance\n * @private\n */\nfunction _rerender_all_nodes(jm) {\n const view = jm.view;\n const mind = jm.mind;\n\n if (!mind || !mind.root) {\n return;\n }\n\n // Collect all nodes to update\n const nodesToUpdate = [];\n for (const nodeId in mind.nodes) {\n const node = mind.nodes[nodeId];\n if (node._data && node._data.view && node._data.view.element) {\n nodesToUpdate.push(node);\n }\n }\n\n // Batch render nodes (only update DOM, no layout trigger)\n for (const node of nodesToUpdate) {\n const element = node._data.view.element;\n view.render_node(element, node);\n }\n\n // Batch update node sizes (read all sizes at once to avoid layout thrashing)\n for (const node of nodesToUpdate) {\n if (jm.layout.is_visible(node)) {\n const element = node._data.view.element;\n node._data.view.width = element.clientWidth;\n node._data.view.height = element.clientHeight;\n }\n }\n\n // Finally recalculate layout and show (only trigger reflow/repaint once)\n jm.layout.layout();\n jm.view.show(false);\n}\n\n/**\n * Plugin initialization function\n * @param {import('../jsmind.js').default} jm - jsMind instance\n * @param {MultilineTextOptions} options - Plugin options\n * @private\n */\nfunction init(jm, options) {\n console.log('[Multiline Plugin] Initializing...', options);\n\n const opts = Object.assign({}, DEFAULT_OPTIONS, options);\n const view = jm.view;\n\n // Plugin state\n let editing_node = null;\n let multiline_editor = null;\n\n // IMPORTANT: Re-set view.render_node to use custom render\n // Because ViewProvider constructor already set it based on options.custom_node_render\n // We need to ensure it uses the custom render function\n if (view.opts.custom_node_render) {\n view.render_node = view._custom_node_render.bind(view);\n console.log('[Multiline Plugin] Re-bound view.render_node');\n }\n\n // Re-render all nodes to apply multiline styles\n if (jm.mind && jm.mind.root) {\n _rerender_all_nodes(jm);\n console.log('[Multiline Plugin] Re-rendered all nodes');\n }\n\n /**\n * Begin editing a node with multiline support\n * @param {import('../jsmind.node.js').Node} node\n */\n view.edit_node_begin = function (node) {\n console.log('[Multiline Plugin] edit_node_begin called', node);\n if (!node.topic) {\n return;\n }\n\n // End editing if another node is being edited\n if (editing_node) {\n view.edit_node_end();\n }\n\n editing_node = node;\n view.editing_node = node;\n\n // Create editor (div with contentEditable)\n const editor = $.c('div');\n editor.contentEditable = 'plaintext-only';\n editor.className = 'jsmind-multiline-editor';\n editor.textContent = node.topic;\n multiline_editor = editor;\n\n // Get element and set editor styles to match\n const element = node._data.view.element;\n\n // Set editor styles to match element width, auto-expand height\n Object.assign(editor.style, {\n width: 'auto',\n minHeight: element.clientHeight + 'px',\n lineHeight: opts.line_height,\n border: 'none',\n outline: 'none',\n whiteSpace: 'pre-wrap',\n wordBreak: 'break-word',\n boxSizing: 'border-box',\n overflow: 'hidden',\n });\n\n // Auto-expand height on input\n const autoExpand = () => {\n editor.style.height = 'auto';\n editor.style.height = editor.scrollHeight + 'px';\n };\n $.on(editor, 'input', autoExpand);\n // Initial expand\n setTimeout(autoExpand, 0);\n\n // Keyboard events\n $.on(editor, 'keydown', e => {\n if (e.key === 'Enter' && !e.shiftKey) {\n e.preventDefault();\n e.stopPropagation(); // Prevent jsMind shortcut from triggering\n view.edit_node_end();\n } else if (e.key === 'Escape') {\n e.preventDefault();\n e.stopPropagation();\n cancel_editing();\n } else if (e.key === 'Tab') {\n e.preventDefault();\n e.stopPropagation();\n view.edit_node_end();\n }\n });\n\n // Blur event - save on blur\n $.on(editor, 'blur', () => {\n setTimeout(() => {\n if (editing_node) {\n view.edit_node_end();\n }\n }, 100);\n });\n\n // Replace node content and focus\n element.innerHTML = '';\n element.appendChild(editor);\n element.style.zIndex = 5;\n editor.focus();\n\n // Select all text\n const range = $.d.createRange();\n range.selectNodeContents(editor);\n const selection = $.w.getSelection();\n selection.removeAllRanges();\n selection.addRange(range);\n };\n\n /**\n * End editing and save changes\n */\n view.edit_node_end = function () {\n if (!editing_node || !multiline_editor) {\n return;\n }\n\n const node = editing_node;\n const topic = (multiline_editor.textContent || '')\n .trim()\n .replace(/\\r\\n/g, '\\n')\n .replace(/\\r/g, '\\n')\n .replace(/\\n{3,}/g, '\\n\\n');\n\n // Clean up editor\n cleanup_editor();\n\n // Update node if content changed\n if (!jsMind.util.text.is_empty(topic) && node.topic !== topic) {\n jm.update_node(node.id, topic);\n } else {\n view.render_node(node._data.view.element, node);\n }\n\n // Focus panel\n view.e_panel.focus();\n };\n\n /**\n * Cancel editing without saving changes\n */\n function cancel_editing() {\n if (!editing_node || !multiline_editor) {\n return;\n }\n\n const node = editing_node;\n\n // Clean up editor\n cleanup_editor();\n\n // Re-render node\n view.render_node(node._data.view.element, node);\n\n // Focus panel\n view.e_panel.focus();\n }\n\n /**\n * Clean up editor and reset state\n */\n function cleanup_editor() {\n if (!editing_node || !multiline_editor) {\n return;\n }\n\n const element = editing_node._data.view.element;\n\n // Remove editor\n if (multiline_editor.parentNode) {\n multiline_editor.parentNode.removeChild(multiline_editor);\n }\n\n // Reset styles and state\n element.style.zIndex = 'auto';\n editing_node = null;\n view.editing_node = null;\n multiline_editor = null;\n }\n}\n\n// Register plugin\njsMind.register_plugin(new jsMind.plugin('multiline_text', init));\n\n// Export for ES6 modules\nexport default {\n name: 'multiline_text',\n init,\n createMultilineRender,\n};\n"],"names":["jsMind","Error","$","DEFAULT_OPTIONS","text_width","line_height","createMultilineRender","options","opts","Object","assign","jm","element","node","topic","includes","style","whiteSpace","wordBreak","maxWidth","textContent","init","console","log","view","editing_node","multiline_editor","cleanup_editor","_data","parentNode","removeChild","zIndex","custom_node_render","render_node","_custom_node_render","bind","mind","root","nodesToUpdate","nodeId","nodes","push","layout","is_visible","width","clientWidth","height","clientHeight","show","_rerender_all_nodes","edit_node_begin","edit_node_end","editor","c","contentEditable","className","minHeight","lineHeight","border","outline","boxSizing","overflow","autoExpand","scrollHeight","on","setTimeout","e","key","shiftKey","preventDefault","stopPropagation","e_panel","focus","cancel_editing","innerHTML","appendChild","range","d","createRange","selectNodeContents","selection","w","getSelection","removeAllRanges","addRange","trim","replace","util","text","is_empty","update_node","id","register_plugin","plugin","jsmind_multilineText","name"],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @license BSD-3-Clause
3
+ * @copyright 2014-2025 hizzgdev@163.com
4
+ *
5
+ * Project Home:
6
+ * https://github.com/hizzgdev/jsmind/
7
+ */
8
+ import t from"@umbraci/jsmind";import e from"dom-to-image";if(!t)throw new Error("jsMind is not defined");if(!e)throw new Error("dom-to-image is required");const i=t.$,n={filename:null,watermark:{left:i.w.location,right:"https://github.com/UmbraCi/jsmind"},background:"transparent"};class r{constructor(e,i){var r={};t.util.json.merge(r,n),t.util.json.merge(r,i),this.version="0.2.0",this.jm=e,this.options=r,this.dpr=e.view.device_pixel_ratio}shoot(){let t=this.create_canvas(),e=t.getContext("2d");e.scale(this.dpr,this.dpr),Promise.resolve(e).then(()=>this.draw_background(e)).then(()=>this.draw_lines(e)).then(()=>this.draw_nodes(e)).then(()=>this.draw_watermark(t,e)).then(()=>this.download(t)).then(()=>this.clear(t))}create_canvas(){let t=i.c("canvas");const e=this.jm.view.size.w,n=this.jm.view.size.h;return t.width=e*this.dpr,t.height=n*this.dpr,t.style.width=e+"px",t.style.height=n+"px",t.style.visibility="hidden",this.jm.view.e_panel.appendChild(t),t}clear(t){t.parentNode.removeChild(t)}draw_background(t){return new Promise(function(e,i){const n=this.options.background;n&&"transparent"!==n&&(t.fillStyle=this.options.background,t.fillRect(0,0,this.jm.view.size.w,this.jm.view.size.h)),e(t)}.bind(this))}draw_lines(t){return new Promise(function(e,i){this.jm.view.graph.copy_to(t,function(){e(t)})}.bind(this))}draw_nodes(t){return e.toSvg(this.jm.view.e_nodes,{style:{zoom:1}}).then(this.load_image).then(function(e){return t.drawImage(e,0,0),t})}draw_watermark(t,e){return e.textBaseline="bottom",e.fillStyle="#000",e.font="11px Verdana,Arial,Helvetica,sans-serif",this.options.watermark.left&&(e.textAlign="left",e.fillText(this.options.watermark.left,5.5,t.height-2.5)),this.options.watermark.right&&(e.textAlign="right",e.fillText(this.options.watermark.right,t.width-5.5,t.height-2.5)),e}load_image(t){return new Promise(function(e,i){let n=new Image;n.onload=function(){e(n)},n.onerror=i,n.src=t})}download(t){var e=(this.options.filename||this.jm.mind.name)+".png";if(navigator.msSaveBlob&&t.msToBlob){var n=t.msToBlob();navigator.msSaveBlob(n,e)}else{var r=t.toDataURL(),o=i.c("a");if("download"in o){o.style.visibility="hidden",o.href=r,o.download=e,i.d.body.appendChild(o);var s=i.d.createEvent("MouseEvents");s.initEvent("click",!0,!0),o.dispatchEvent(s),i.d.body.removeChild(o)}else location.href=r}}}const o=new t.plugin("screenshot",function(t,e){var i=new r(t,e);t.screenshot=i,t.shoot=function(){i.shoot()}});t.register_plugin(o);export{r as JmScreenshot,r as default,o as screenshot_plugin};
9
+ //# sourceMappingURL=jsmind.screenshot.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsmind.screenshot.esm.js","sources":["../src/plugins/jsmind.screenshot.js"],"sourcesContent":["/**\n * @license BSD\n * @copyright 2014-2025 UmbraCi\n *\n * Project Home:\n * https://github.com/UmbraCi/jsmind/\n */\n\nimport jsMind from '@umbraci/jsmind';\nimport domtoimage from 'dom-to-image';\n\nif (!jsMind) {\n throw new Error('jsMind is not defined');\n}\n\nif (!domtoimage) {\n throw new Error('dom-to-image is required');\n}\n\nconst $ = jsMind.$;\n\n/**\n * Default options for screenshot plugin.\n * @typedef {Object} ScreenshotOptions\n * @property {string|null} [filename]\n * @property {{left?:string|Location,right?:string}} [watermark]\n * @property {string} [background]\n */\nconst DEFAULT_OPTIONS = {\n filename: null,\n watermark: {\n left: $.w.location,\n right: 'https://github.com/UmbraCi/jsmind',\n },\n background: 'transparent',\n};\n\n/**\n * Screenshot plugin for jsMind.\n */\nexport class JmScreenshot {\n /**\n * Create screenshot plugin instance.\n * @param {import('../jsmind.js').default} jm - jsMind instance\n * @param {Partial<ScreenshotOptions>} options - Plugin options\n */\n constructor(jm, options) {\n var opts = {};\n jsMind.util.json.merge(opts, DEFAULT_OPTIONS);\n jsMind.util.json.merge(opts, options);\n\n this.version = '0.2.0';\n /** @type {import('../jsmind.js').default} */\n this.jm = jm;\n /** @type {ScreenshotOptions} */\n this.options = opts;\n /** @type {number} */\n this.dpr = jm.view.device_pixel_ratio;\n }\n\n /** Take a screenshot of the mind map. */\n shoot() {\n let c = this.create_canvas();\n let ctx = c.getContext('2d');\n ctx.scale(this.dpr, this.dpr);\n Promise.resolve(ctx)\n .then(() => this.draw_background(ctx))\n .then(() => this.draw_lines(ctx))\n .then(() => this.draw_nodes(ctx))\n .then(() => this.draw_watermark(c, ctx))\n .then(() => this.download(c))\n .then(() => this.clear(c));\n }\n\n /**\n * Create canvas for screenshot.\n * @returns {HTMLCanvasElement} Canvas element\n */\n create_canvas() {\n let c = $.c('canvas');\n const w = this.jm.view.size.w;\n const h = this.jm.view.size.h;\n c.width = w * this.dpr;\n c.height = h * this.dpr;\n c.style.width = w + 'px';\n c.style.height = h + 'px';\n\n c.style.visibility = 'hidden';\n this.jm.view.e_panel.appendChild(c);\n return c;\n }\n\n /**\n * Clean up canvas element.\n * @param {HTMLCanvasElement} c - Canvas to remove\n */\n clear(c) {\n c.parentNode.removeChild(c);\n }\n\n /**\n * Draw background on canvas.\n * @param {CanvasRenderingContext2D} ctx - Canvas context\n * @returns {Promise<CanvasRenderingContext2D>} Promise resolving to context\n */\n draw_background(ctx) {\n return new Promise(\n function (resolve, _) {\n const bg = this.options.background;\n if (!!bg && bg !== 'transparent') {\n ctx.fillStyle = this.options.background;\n ctx.fillRect(0, 0, this.jm.view.size.w, this.jm.view.size.h);\n }\n resolve(ctx);\n }.bind(this)\n );\n }\n\n /**\n * Draw connection lines on canvas by copying from view graph.\n * @param {CanvasRenderingContext2D} ctx\n * @returns {Promise<CanvasRenderingContext2D>}\n */\n draw_lines(ctx) {\n return new Promise(\n function (resolve, _) {\n this.jm.view.graph.copy_to(ctx, function () {\n resolve(ctx);\n });\n }.bind(this)\n );\n }\n\n /**\n * Draw node DOM into canvas via SVG snapshot.\n * @param {CanvasRenderingContext2D} ctx\n * @returns {Promise<CanvasRenderingContext2D>}\n */\n draw_nodes(ctx) {\n return domtoimage\n .toSvg(this.jm.view.e_nodes, { style: { zoom: 1 } })\n .then(this.load_image)\n .then(function (img) {\n ctx.drawImage(img, 0, 0);\n return ctx;\n });\n }\n\n /**\n * Draw watermark text on canvas.\n * @param {HTMLCanvasElement} c\n * @param {CanvasRenderingContext2D} ctx\n * @returns {CanvasRenderingContext2D}\n */\n draw_watermark(c, ctx) {\n ctx.textBaseline = 'bottom';\n ctx.fillStyle = '#000';\n ctx.font = '11px Verdana,Arial,Helvetica,sans-serif';\n if (!!this.options.watermark.left) {\n ctx.textAlign = 'left';\n ctx.fillText(this.options.watermark.left, 5.5, c.height - 2.5);\n }\n if (!!this.options.watermark.right) {\n ctx.textAlign = 'right';\n ctx.fillText(this.options.watermark.right, c.width - 5.5, c.height - 2.5);\n }\n return ctx;\n }\n\n /**\n * Load image from URL and resolve img element.\n * @param {string} url\n * @returns {Promise<HTMLImageElement>}\n */\n load_image(url) {\n return new Promise(function (resolve, reject) {\n let img = new Image();\n img.onload = function () {\n resolve(img);\n };\n img.onerror = reject;\n img.src = url;\n });\n }\n\n /**\n * Trigger download of canvas content as PNG.\n * @param {HTMLCanvasElement} c\n */\n download(c) {\n var name = (this.options.filename || this.jm.mind.name) + '.png';\n\n if (navigator.msSaveBlob && !!c.msToBlob) {\n var blob = c.msToBlob();\n navigator.msSaveBlob(blob, name);\n } else {\n var blob_url = c.toDataURL();\n var anchor = $.c('a');\n if ('download' in anchor) {\n anchor.style.visibility = 'hidden';\n anchor.href = blob_url;\n anchor.download = name;\n $.d.body.appendChild(anchor);\n var evt = $.d.createEvent('MouseEvents');\n evt.initEvent('click', true, true);\n anchor.dispatchEvent(evt);\n $.d.body.removeChild(anchor);\n } else {\n location.href = blob_url;\n }\n }\n }\n}\n\n/**\n * Screenshot plugin registration.\n * @type {import('../jsmind.plugin.js').Plugin<Partial<ScreenshotOptions>>}\n */\nexport const screenshot_plugin = new jsMind.plugin('screenshot', function (jm, options) {\n var jmss = new JmScreenshot(jm, options);\n jm.screenshot = jmss;\n jm.shoot = function () {\n jmss.shoot();\n };\n});\n\njsMind.register_plugin(screenshot_plugin);\n\nexport default JmScreenshot;\n"],"names":["jsMind","Error","domtoimage","$","DEFAULT_OPTIONS","filename","watermark","left","w","location","right","background","JmScreenshot","constructor","jm","options","opts","util","json","merge","this","version","dpr","view","device_pixel_ratio","shoot","c","create_canvas","ctx","getContext","scale","Promise","resolve","then","draw_background","draw_lines","draw_nodes","draw_watermark","download","clear","size","h","width","height","style","visibility","e_panel","appendChild","parentNode","removeChild","_","bg","fillStyle","fillRect","bind","graph","copy_to","toSvg","e_nodes","zoom","load_image","img","drawImage","textBaseline","font","textAlign","fillText","url","reject","Image","onload","onerror","src","name","mind","navigator","msSaveBlob","msToBlob","blob","blob_url","toDataURL","anchor","href","d","body","evt","createEvent","initEvent","dispatchEvent","screenshot_plugin","plugin","jmss","screenshot","register_plugin"],"mappings":"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"}
package/package.json CHANGED
@@ -1,27 +1,27 @@
1
1
  {
2
2
  "name": "@umbraci/jsmind",
3
- "version": "0.9.6",
3
+ "version": "0.9.8",
4
4
  "description": "jsMind is a pure javascript library for mindmap, it base on html5 canvas. jsMind was released under BSD license, you can embed it in any project, if only you observe the license.",
5
5
  "main": "es6/jsmind.js",
6
6
  "types": "types/generated/index.d.ts",
7
7
  "exports": {
8
8
  ".": {
9
- "import": "./es6/jsmind.js",
9
+ "import": "./es6/jsmind.esm.js",
10
10
  "require": "./es6/jsmind.js",
11
11
  "types": "./types/generated/index.d.ts"
12
12
  },
13
13
  "./draggable-node": {
14
- "import": "./es6/jsmind.draggable-node.js",
14
+ "import": "./es6/jsmind.draggable-node.esm.js",
15
15
  "require": "./es6/jsmind.draggable-node.js",
16
16
  "types": "./types/generated/plugins/jsmind.draggable-node.d.ts"
17
17
  },
18
18
  "./screenshot": {
19
- "import": "./es6/jsmind.screenshot.js",
19
+ "import": "./es6/jsmind.screenshot.esm.js",
20
20
  "require": "./es6/jsmind.screenshot.js",
21
21
  "types": "./types/generated/plugins/jsmind.screenshot.d.ts"
22
22
  },
23
23
  "./multiline-text": {
24
- "import": "./es6/jsmind.multiline-text.js",
24
+ "import": "./es6/jsmind.multiline-text.esm.js",
25
25
  "require": "./es6/jsmind.multiline-text.js",
26
26
  "types": "./types/generated/plugins/jsmind.multiline-text.d.ts"
27
27
  },
@@ -87,6 +87,6 @@
87
87
  "testEnvironment": "jsdom"
88
88
  },
89
89
  "sideEffects": [
90
- "./es6/jsmind.multiline-text.js"
90
+ "./es6/*"
91
91
  ]
92
92
  }
@@ -202,16 +202,39 @@ export default class jsMind {
202
202
  add_node(parent_node: string | import("./jsmind.node.js").Node, node_id: string, topic: string, data?: Record<string, any> | undefined, direction?: ("left" | "center" | "right" | "-1" | "0" | "1" | number) | undefined): import("./jsmind.node.js").Node | null;
203
203
  /**
204
204
  * Add multiple nodes to the mind map with optimized performance.
205
+ * Supports standard jsMind formats: node_tree, node_array, and freemind with nested children structure.
205
206
  * @param {string | import('./jsmind.node.js').Node} parent_node - Parent node for all new nodes
206
- * @param {Array<{node_id: string, topic: string, data?: Record<string, any>, direction?: ('left'|'center'|'right'|'-1'|'0'|'1'|number)}>} nodes_data - Array of node data objects
207
- * @returns {Array<import('./jsmind.node.js').Node|null>} Array of created nodes
207
+ * @param {Array<{id: string, topic: string, data?: Record<string, any>, direction?: ('left'|'center'|'right'|'-1'|'0'|'1'|number), children?: Array}>} nodes_data - Array of node data objects with same format as add_node
208
+ * @returns {Array<import('./jsmind.node.js').Node|null>} Array of created nodes (flattened from all levels)
208
209
  */
209
210
  add_nodes(parent_node: string | import("./jsmind.node.js").Node, nodes_data: Array<{
210
- node_id: string;
211
+ id: string;
211
212
  topic: string;
212
213
  data?: Record<string, any>;
213
214
  direction?: ("left" | "center" | "right" | "-1" | "0" | "1" | number);
215
+ children?: any[];
214
216
  }>): Array<import("./jsmind.node.js").Node | null>;
217
+ /**
218
+ * Recursively add nodes using existing format processors.
219
+ * @private
220
+ * @param {import('./jsmind.node.js').Node} parent_node
221
+ * @param {object} node_data
222
+ * @returns {Array<import('./jsmind.node.js').Node|null>}
223
+ */
224
+ private _add_nodes_recursive;
225
+ /**
226
+ * Count expected nodes recursively.
227
+ * @private
228
+ * @param {Array} nodes_data
229
+ * @returns {number}
230
+ */
231
+ private _count_expected_nodes;
232
+ /**
233
+ * Clean up partially created nodes without triggering UI refresh for each node.
234
+ * @private
235
+ * @param {Array<import('./jsmind.node.js').Node>} created_nodes
236
+ */
237
+ private _cleanup_partial_nodes;
215
238
  /**
216
239
  * Insert a node before target node.
217
240
  * @param {string | import('./jsmind.node.js').Node} node_before
@@ -37,7 +37,6 @@ declare class SvgGraph {
37
37
  };
38
38
  custom_node_render?: Function;
39
39
  expander_style: "char" | "number";
40
- enable_multiline?: boolean;
41
40
  };
42
41
  e_svg: SVGElement;
43
42
  size: {
@@ -122,7 +121,6 @@ declare class CanvasGraph {
122
121
  };
123
122
  custom_node_render?: Function;
124
123
  expander_style: "char" | "number";
125
- enable_multiline?: boolean;
126
124
  };
127
125
  e_canvas: HTMLElement;
128
126
  canvas_ctx: CanvasRenderingContext2D;
@@ -10,8 +10,6 @@ export type JsMindRuntimeOptions = {
10
10
  theme?: (string | null);
11
11
  mode?: ("full" | "side");
12
12
  support_html?: boolean;
13
- enable_multiline?: boolean;
14
- textAutoWrapWidth?: number;
15
13
  log_level?: "debug" | "info" | "warn" | "error" | "disable";
16
14
  view?: {
17
15
  engine?: "canvas" | "svg";
@@ -16,8 +16,7 @@ export class ViewProvider {
16
16
  * node_overflow: 'hidden'|'wrap',
17
17
  * zoom: {min:number, max:number, step:number, mask_key:number},
18
18
  * custom_node_render?: Function,
19
- * expander_style: 'char'|'number',
20
- * enable_multiline?: boolean
19
+ * expander_style: 'char'|'number'
21
20
  * }} options - View configuration options
22
21
  */
23
22
  constructor(jm: import("./jsmind.js").default, options: {
@@ -40,7 +39,6 @@ export class ViewProvider {
40
39
  };
41
40
  custom_node_render?: Function;
42
41
  expander_style: "char" | "number";
43
- enable_multiline?: boolean;
44
42
  });
45
43
  opts: {
46
44
  engine: "canvas" | "svg";
@@ -62,7 +60,6 @@ export class ViewProvider {
62
60
  };
63
61
  custom_node_render?: Function;
64
62
  expander_style: "char" | "number";
65
- enable_multiline?: boolean;
66
63
  };
67
64
  jm: import("./jsmind.js").default;
68
65
  layout: import("./jsmind.layout_provider.js").LayoutProvider;
@@ -97,7 +94,6 @@ export class ViewProvider {
97
94
  };
98
95
  custom_node_render?: Function;
99
96
  expander_style: "char" | "number";
100
- enable_multiline?: boolean;
101
97
  };
102
98
  e_svg: SVGElement;
103
99
  size: {
@@ -148,7 +144,6 @@ export class ViewProvider {
148
144
  };
149
145
  custom_node_render?: Function;
150
146
  expander_style: "char" | "number";
151
- enable_multiline?: boolean;
152
147
  };
153
148
  e_canvas: HTMLElement;
154
149
  canvas_ctx: CanvasRenderingContext2D;
@@ -223,11 +218,6 @@ export class ViewProvider {
223
218
  * @param {import('./jsmind.node.js').Node} node - Target node
224
219
  */
225
220
  init_nodes_size(node: import("./jsmind.node.js").Node): void;
226
- /**
227
- * Ensure correct size calculation for multiline nodes
228
- * @param {HTMLElement} element - Node element
229
- */
230
- _ensure_multiline_node_size(element: HTMLElement): void;
231
221
  /** Initialize DOM elements for all nodes. */
232
222
  init_nodes(): void;
233
223
  /**
@@ -256,11 +246,6 @@ export class ViewProvider {
256
246
  * @param {import('./jsmind.node.js').Node} node - Node to update
257
247
  */
258
248
  update_node(node: import("./jsmind.node.js").Node): void;
259
- /**
260
- * Force recalculation of node size
261
- * @param {import('./jsmind.node.js').Node} node - Node to recalculate
262
- */
263
- _force_recalc_node_size(node: import("./jsmind.node.js").Node): void;
264
249
  /**
265
250
  * Select a node visually.
266
251
  * @param {import('./jsmind.node.js').Node|null} node - Node to select
@@ -285,16 +270,6 @@ export class ViewProvider {
285
270
  edit_node_begin(node: import("./jsmind.node.js").Node): void;
286
271
  /** End editing current node. */
287
272
  edit_node_end(): void;
288
- _editor_input_handler: () => void;
289
- /**
290
- * Setup editor resize listener for multiline editing
291
- * Reference TextEdit.js updateTextEditNode logic
292
- */
293
- _setup_editor_resize_listener(): void;
294
- /**
295
- * Dynamically update CSS styles for multiline nodes to sync max-width with textAutoWrapWidth
296
- */
297
- _update_multiline_css(): void;
298
273
  /** @returns {{x:number,y:number}} */
299
274
  get_view_offset(): {
300
275
  x: number;
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Create a custom node render function for multiline text
3
+ * @param {MultilineTextOptions} [options={}] - Plugin options
4
+ * @param {number} [options.text_width=200] - Maximum width for multiline text nodes
5
+ * @param {string} [options.line_height='1.5'] - Line height for text
6
+ * @returns {function(jsMind, HTMLElement, Node): boolean} Custom render function
7
+ * @example
8
+ * const options = {
9
+ * view: {
10
+ * custom_node_render: createMultilineRender({
11
+ * text_width: 250,
12
+ * line_height: '1.6',
13
+ * })
14
+ * }
15
+ * };
16
+ */
17
+ export function createMultilineRender(options?: MultilineTextOptions): (arg0: jsMind, arg1: HTMLElement, arg2: Node) => boolean;
18
+ declare namespace _default {
19
+ export let name: string;
20
+ export { init };
21
+ export { createMultilineRender };
22
+ }
23
+ export default _default;
24
+ /**
25
+ * Default plugin options
26
+ */
27
+ export type MultilineTextOptions = {
28
+ /**
29
+ * - Maximum width for multiline text nodes (default: 200)
30
+ */
31
+ text_width: number;
32
+ /**
33
+ * - Line height for text (default: '1.5')
34
+ */
35
+ line_height: string;
36
+ };
37
+ /**
38
+ * Plugin initialization function
39
+ * @param {import('../jsmind.js').default} jm - jsMind instance
40
+ * @param {MultilineTextOptions} options - Plugin options
41
+ * @private
42
+ */
43
+ declare function init(jm: import("../jsmind.js").default, options: MultilineTextOptions): void;
package/es6/jsmind-esm.js DELETED
@@ -1,16 +0,0 @@
1
- /**
2
- * @license BSD-3-Clause
3
- * @copyright 2014-2025 hizzgdev@163.com
4
- *
5
- * Project Home:
6
- * https://github.com/hizzgdev/jsmind/
7
- */
8
-
9
- // Import all the modules from the source
10
- import jsMind from '../src/jsmind.js';
11
-
12
- // Export as default
13
- export default jsMind;
14
-
15
- // Also export named exports for compatibility
16
- export const { mind, node, direction, event_type, $, plugin, register_plugin, util } = jsMind;