@umbraci/jsmind 0.10.11 → 0.10.13

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.
Files changed (39) hide show
  1. package/LICENSE +24 -24
  2. package/README.md +116 -116
  3. package/dist/jsmind.draggable-node.js +1 -1
  4. package/dist/jsmind.draggable-node.js.map +1 -1
  5. package/dist/jsmind.history.js +1 -1
  6. package/dist/jsmind.history.js.map +1 -1
  7. package/dist/jsmind.js +1 -1
  8. package/dist/jsmind.js.map +1 -1
  9. package/dist/jsmind.multiline-text.js +1 -1
  10. package/dist/jsmind.multiline-text.js.map +1 -1
  11. package/dist/jsmind.screenshot.js +1 -1
  12. package/dist/jsmind.screenshot.js.map +1 -1
  13. package/es/jsmind.draggable-node.js +1 -1
  14. package/es/jsmind.draggable-node.js.map +1 -1
  15. package/es/jsmind.history.js +1 -1
  16. package/es/jsmind.history.js.map +1 -1
  17. package/es/jsmind.js +1 -1
  18. package/es/jsmind.js.map +1 -1
  19. package/es/jsmind.multiline-text.js +1 -1
  20. package/es/jsmind.multiline-text.js.map +1 -1
  21. package/es/jsmind.screenshot.js +1 -1
  22. package/es/jsmind.screenshot.js.map +1 -1
  23. package/lib/jsmind.draggable-node.js +1 -1
  24. package/lib/jsmind.draggable-node.js.map +1 -1
  25. package/lib/jsmind.history.js +1 -1
  26. package/lib/jsmind.history.js.map +1 -1
  27. package/lib/jsmind.js +1 -1
  28. package/lib/jsmind.js.map +1 -1
  29. package/lib/jsmind.multiline-text.js +1 -1
  30. package/lib/jsmind.multiline-text.js.map +1 -1
  31. package/lib/jsmind.screenshot.js +1 -1
  32. package/lib/jsmind.screenshot.js.map +1 -1
  33. package/package.json +111 -111
  34. package/style/jsmind.css +408 -408
  35. package/types/generated/jsmind.d.ts +194 -0
  36. package/types/generated/jsmind.mind.d.ts +8 -0
  37. package/types/generated/jsmind.option.d.ts +6 -0
  38. package/types/generated/jsmind.view_provider.d.ts +27 -0
  39. package/types/tsconfig.declaration.json +19 -19
@@ -78,6 +78,10 @@ declare class jsMind {
78
78
  version: string;
79
79
  initialized: boolean;
80
80
  mind: Mind;
81
+ /** @type {'single'|'multi'|null} */
82
+ _selection_mode: "single" | "multi" | null;
83
+ /** @type {import('./jsmind.node.js').Node|null} */
84
+ _last_selected_node: import("./jsmind.node.js").Node | null;
81
85
  /** @type {Array<(type: number, data: EventData) => void>} */
82
86
  event_handles: Array<(type: number, data: EventData) => void>;
83
87
  /** Initialize sub-systems and plugins. */
@@ -332,8 +336,24 @@ declare class jsMind {
332
336
  * @returns {import('./jsmind.node.js').Node|null} Node instance or null
333
337
  */
334
338
  get_selected_node(): import("./jsmind.node.js").Node | null;
339
+ /**
340
+ * Get all currently selected nodes.
341
+ * @returns {import('./jsmind.node.js').Node[]}
342
+ */
343
+ get_selected_nodes(): import("./jsmind.node.js").Node[];
335
344
  /** clear selection */
336
345
  select_clear(): void;
346
+ /**
347
+ * Toggle multi-selection for a node (and optionally descendants).
348
+ * @param {string | import('./jsmind.node.js').Node} node
349
+ */
350
+ toggle_subtree_selection(node: string | import("./jsmind.node.js").Node): void;
351
+ /**
352
+ * Determine whether a node is currently selected.
353
+ * @param {string | import('./jsmind.node.js').Node} node
354
+ * @returns {boolean}
355
+ */
356
+ is_node_selected(node: string | import("./jsmind.node.js").Node): boolean;
337
357
  /** @param {string | import('./jsmind.node.js').Node} node */
338
358
  is_node_visible(node: string | import("./jsmind.node.js").Node): boolean;
339
359
  /**
@@ -341,6 +361,180 @@ declare class jsMind {
341
361
  * @param {string | import('./jsmind.node.js').Node} node
342
362
  */
343
363
  scroll_node_to_center(node: string | import("./jsmind.node.js").Node): void;
364
+ /**
365
+ * Add nodes into the current selection set without clearing existing ones.
366
+ * @param {import('./jsmind.node.js').Node[]} nodes
367
+ * @returns {import('./jsmind.node.js').Node[]}
368
+ * @private
369
+ */
370
+ /**
371
+ * @param {import('./jsmind.node.js').Node[]} nodes
372
+ * @param {{focusNode?: import('./jsmind.node.js').Node}=} options
373
+ * @private
374
+ */
375
+ private _append_selection;
376
+ /**
377
+ * Remove nodes from the current selection set.
378
+ * @param {import('./jsmind.node.js').Node[]} nodes
379
+ * @returns {import('./jsmind.node.js').Node[]}
380
+ * @private
381
+ */
382
+ private _remove_selection;
383
+ /**
384
+ * Deselect a node and all its descendants from the current selection.
385
+ * @param {string | import('./jsmind.node.js').Node} node
386
+ * @private
387
+ */
388
+ private _deselect_subtree;
389
+ /**
390
+ * Clear all current selections and return the nodes that were cleared.
391
+ * @returns {import('./jsmind.node.js').Node[]}
392
+ * @private
393
+ */
394
+ private _clear_selection_state;
395
+ /**
396
+ * Collect a node and optionally its descendants respecting filters.
397
+ * @param {import('./jsmind.node.js').Node} node
398
+ * @param {{includeChildren?:boolean, respectFilter?:boolean, skipRootFilter?:boolean}=} config
399
+ * @returns {import('./jsmind.node.js').Node[]}
400
+ * @private
401
+ */
402
+ private _collect_subtree_nodes;
403
+ /**
404
+ * Ensure ancestors of provided nodes are also selected (up to first selected ancestor).
405
+ * @param {import('./jsmind.node.js').Node[]} nodes
406
+ * @param {import('./jsmind.node.js').Node=} focusNode
407
+ * @returns {import('./jsmind.node.js').Node[]}
408
+ * @private
409
+ */
410
+ /**
411
+ * @param {import('./jsmind.node.js').Node[]} nodes
412
+ * @param {import('./jsmind.node.js').Node=} focusNode
413
+ * @param {{requireAncestorChainSelected?: boolean}=} options
414
+ * @returns {import('./jsmind.node.js').Node[]}
415
+ * @private
416
+ */
417
+ private _ensure_ancestor_selection;
418
+ /**
419
+ * Get the configured selection filter callback, if any.
420
+ * @returns {((node: import('./jsmind.node.js').Node)=>boolean)|null}
421
+ * @private
422
+ */
423
+ private _get_selection_filter;
424
+ /**
425
+ * Determine the multi-select mode based on event modifiers.
426
+ * Returns: null (single select), 'ctrl' (add/remove), 'shift' (range select)
427
+ * @param {MouseEvent} e
428
+ * @returns {null|'ctrl'|'shift'}
429
+ * @private
430
+ */
431
+ private _get_multi_select_mode;
432
+ /**
433
+ * Toggle selection of a single node (add if not selected, remove if selected).
434
+ * Used for Ctrl+Click behavior.
435
+ * @param {string | import('./jsmind.node.js').Node} node_id
436
+ * @private
437
+ */
438
+ private _toggle_node_selection;
439
+ /**
440
+ * Range select nodes for Shift+Click behavior.
441
+ * Logic:
442
+ * - If no nodes are currently selected: select all nodes under the clicked node
443
+ * - If nodes are already selected: select nodes in the range from first selected to clicked node
444
+ * @param {string | import('./jsmind.node.js').Node} node_id
445
+ * @private
446
+ */
447
+ private _range_select_nodes;
448
+ /**
449
+ * Find all nodes between two nodes (for range selection).
450
+ * This includes both nodes and all nodes in between them in tree order.
451
+ * @param {import('./jsmind.node.js').Node} node1
452
+ * @param {import('./jsmind.node.js').Node} node2
453
+ * @returns {import('./jsmind.node.js').Node[]}
454
+ * @private
455
+ */
456
+ private _find_nodes_between;
457
+ /**
458
+ * Check whether ancestor is an ancestor of node.
459
+ * @param {import('./jsmind.node.js').Node} ancestor
460
+ * @param {import('./jsmind.node.js').Node} node
461
+ * @returns {boolean}
462
+ * @private
463
+ */
464
+ private _is_ancestor_of;
465
+ /**
466
+ * Return nodes along the ancestor->descendant chain, inclusive. If 'ancestor' is not actually
467
+ * an ancestor of 'descendant', returns empty array.
468
+ * @param {import('./jsmind.node.js').Node} ancestor
469
+ * @param {import('./jsmind.node.js').Node} descendant
470
+ * @returns {import('./jsmind.node.js').Node[]}
471
+ * @private
472
+ */
473
+ private _get_path_nodes;
474
+ /**
475
+ * Find nearest selected ancestor of the given node from current selection set.
476
+ * @param {import('./jsmind.node.js').Node} node
477
+ * @returns {import('./jsmind.node.js').Node|null}
478
+ * @private
479
+ */
480
+ private _find_nearest_selected_ancestor;
481
+ /**
482
+ * Find lowest common ancestor of two nodes.
483
+ * @param {import('./jsmind.node.js').Node} a
484
+ * @param {import('./jsmind.node.js').Node} b
485
+ * @returns {import('./jsmind.node.js').Node|null}
486
+ * @private
487
+ */
488
+ private _find_lca;
489
+ /**
490
+ * Given a lowest common ancestor 'lca' and a descendant 'node',
491
+ * return the direct child of lca that lies on the path to node.
492
+ * Returns null if node is not a descendant of lca or node === lca.
493
+ * @param {import('./jsmind.node.js').Node} lca
494
+ * @param {import('./jsmind.node.js').Node} node
495
+ * @returns {import('./jsmind.node.js').Node|null}
496
+ * @private
497
+ */
498
+ private _child_on_path;
499
+ /**
500
+ * From a list of nodes, remove those that are ancestors of any other node in the same list.
501
+ * Keeps only the deepest nodes so that we don't auto-select parents implicitly.
502
+ * @param {import('./jsmind.node.js').Node[]} nodes
503
+ * @returns {import('./jsmind.node.js').Node[]}
504
+ * @private
505
+ */
506
+ private _remove_ancestor_nodes;
507
+ /**
508
+ * From a list of nodes, remove those that are descendants of any other node in the same list.
509
+ * Keeps only top-most nodes so that expanding subtrees covers full branches across siblings.
510
+ * @param {import('./jsmind.node.js').Node[]} nodes
511
+ * @returns {import('./jsmind.node.js').Node[]}
512
+ * @private
513
+ */
514
+ private _remove_descendant_nodes;
515
+ /**
516
+ * Expand a set of base nodes with all their descendants (and themselves).
517
+ * @param {import('./jsmind.node.js').Node[]} nodes
518
+ * @param {{respectFilter?: boolean}=} opts
519
+ * @returns {Set<import('./jsmind.node.js').Node>}
520
+ * @private
521
+ */
522
+ private _expand_with_descendants;
523
+ /**
524
+ * Promote parents into selection ONLY when all their direct children are selected
525
+ * AND at least one ancestor of that parent is already selected (in previous selection set).
526
+ * This avoids auto-selecting parents when only siblings are selected.
527
+ * @param {Set<import('./jsmind.node.js').Node>} set
528
+ * @returns {Set<import('./jsmind.node.js').Node>}
529
+ * @private
530
+ */
531
+ private _promote_parents_when_children_selected;
532
+ /**
533
+ * Determine selection mode based on current selection size.
534
+ * @returns {'single'|'multi'|null}
535
+ * @private
536
+ */
537
+ private _derive_selection_mode;
344
538
  /**
345
539
  * Find the previous sibling node of the given node.
346
540
  *
@@ -9,6 +9,8 @@ export class Mind {
9
9
  root: Node | null;
10
10
  /** @type {Node | null} */
11
11
  selected: Node | null;
12
+ /** @type {Set<Node>} */
13
+ selected_nodes: Set<Node>;
12
14
  /** @type {Record<string, Node>} */
13
15
  nodes: Record<string, Node>;
14
16
  /**
@@ -106,6 +108,12 @@ export class Mind {
106
108
  * @returns {boolean}
107
109
  */
108
110
  remove_node(node: Node): boolean;
111
+ /**
112
+ * Remove a node and its subtree from the cached selection set.
113
+ * @param {Node} node
114
+ * @private
115
+ */
116
+ private _purge_selection;
109
117
  /**
110
118
  * Put node into the map if id is not taken.
111
119
  * @param {Node} node
@@ -60,6 +60,12 @@ export type JsMindRuntimeOptions = {
60
60
  mapping?: Record<string, number | number[]>;
61
61
  id_generator?: () => string;
62
62
  };
63
+ selection?: {
64
+ enable_multi_select?: boolean;
65
+ include_descendants?: boolean;
66
+ shift_simple_mode?: boolean;
67
+ filter?: (node: import("./jsmind.node.js").Node) => boolean;
68
+ };
63
69
  fieldNames?: {
64
70
  id?: string;
65
71
  topic?: string;
@@ -71,6 +71,8 @@ export class ViewProvider {
71
71
  h: number;
72
72
  };
73
73
  selected_node: import("./jsmind.node.js").Node;
74
+ /** @type {Map<string, import('./jsmind.node.js').Node>} */
75
+ multi_selected_nodes: Map<string, import("./jsmind.node.js").Node>;
74
76
  editing_node: import("./jsmind.node.js").Node;
75
77
  graph: {
76
78
  view: ViewProvider;
@@ -253,6 +255,19 @@ export class ViewProvider {
253
255
  select_node(node: import("./jsmind.node.js").Node | null): void;
254
256
  /** Clear node selection. */
255
257
  select_clear(): void;
258
+ /**
259
+ * Append nodes to the current selection without clearing existing ones.
260
+ * @param {import('./jsmind.node.js').Node[]} nodes
261
+ * @param {import('./jsmind.node.js').Node=} focus_node
262
+ */
263
+ append_selected_nodes(nodes: import("./jsmind.node.js").Node[], focus_node?: import("./jsmind.node.js").Node | undefined): void;
264
+ /**
265
+ * Remove the provided nodes from selection state.
266
+ * @param {import('./jsmind.node.js').Node[]} nodes
267
+ */
268
+ remove_selected_nodes(nodes: import("./jsmind.node.js").Node[]): void;
269
+ /** Clear all selections at once. */
270
+ clear_all_selected_nodes(): void;
256
271
  /**
257
272
  * Get currently editing node.
258
273
  * @returns {import('./jsmind.node.js').Node|null} Currently editing node
@@ -329,6 +344,18 @@ export class ViewProvider {
329
344
  restore_selected_node_custom_style(node: import("./jsmind.node.js").Node): void;
330
345
  /** @param {import('./jsmind.node.js').Node} node */
331
346
  clear_selected_node_custom_style(node: import("./jsmind.node.js").Node): void;
347
+ /**
348
+ * Mark the DOM/state for a selected node.
349
+ * @param {import('./jsmind.node.js').Node} node
350
+ * @private
351
+ */
352
+ private _mark_node_selected;
353
+ /**
354
+ * Remove DOM/state selection for a node.
355
+ * @param {import('./jsmind.node.js').Node} node
356
+ * @private
357
+ */
358
+ private _unmark_node_selected;
332
359
  clear_lines(): void;
333
360
  show_lines(): void;
334
361
  /**
@@ -1,19 +1,19 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "ESNext",
5
- "moduleResolution": "node",
6
- "strict": false,
7
- "esModuleInterop": true,
8
- "skipLibCheck": true,
9
- "declaration": true,
10
- "emitDeclarationOnly": true,
11
- "stripInternal": true,
12
- "allowJs": true,
13
- "checkJs": false,
14
- "rootDirs": ["../src"],
15
- "outDir": "./generated",
16
- "baseUrl": "."
17
- },
18
- "include" : ["../src/**/*.ts", "../src/**/*.js"]
19
- }
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "moduleResolution": "node",
6
+ "strict": false,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "declaration": true,
10
+ "emitDeclarationOnly": true,
11
+ "stripInternal": true,
12
+ "allowJs": true,
13
+ "checkJs": false,
14
+ "rootDirs": ["../src"],
15
+ "outDir": "./generated",
16
+ "baseUrl": "."
17
+ },
18
+ "include" : ["../src/**/*.ts", "../src/**/*.js"]
19
+ }