orgchart 6.0.0 → 7.0.0

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 (35) hide show
  1. package/.github/workflows/publish.yml +11 -8
  2. package/README.md +47 -35
  3. package/README.zh-cn.md +73 -30
  4. package/README.zh-tw.md +73 -30
  5. package/demo/edit-chart.html +0 -4
  6. package/demo/edit-hybrid-chart.html +3 -4
  7. package/demo/index.html +5 -1
  8. package/demo/ondemand-loading-data.html +137 -80
  9. package/demo/options-compact-drag.html +82 -0
  10. package/demo/options-export-navigation.html +76 -0
  11. package/demo/options-horizontal-template.html +80 -0
  12. package/demo/options-hybrid-navigation.html +71 -0
  13. package/dist/css/orgchart.css +12 -1
  14. package/dist/css/orgchart.min.css +1 -1
  15. package/dist/js/orgchart.js +896 -523
  16. package/dist/js/orgchart.min.js +1 -1
  17. package/dist/js/orgchart.min.js.map +1 -1
  18. package/gulpfile.js +5 -1
  19. package/package.json +2 -1
  20. package/src/css/orgchart.css +12 -1
  21. package/src/js/orgchart.js +896 -523
  22. package/test/compatibility/README.md +123 -0
  23. package/test/compatibility/browser.tests.js +1279 -0
  24. package/test/compatibility/options.tests.js +628 -0
  25. package/test/cypress/e2e/demo-interactions.cy.js +3 -3
  26. package/test/cypress/e2e/demo-rendering.cy.js +62 -1
  27. package/test/cypress/e2e/drag-drop-hybrid-chart.cy.js +2 -1
  28. package/test/cypress/e2e/local-datasource.cy.js +16 -16
  29. package/test/cypress/e2e/ondemand-loading-data.cy.js +34 -10
  30. package/test/cypress/e2e/toggle-sibs-resp.cy.js +4 -4
  31. package/test/cypress/e2e/vertical-vevel.cy.js +5 -5
  32. package/test/integration/init-tests.js +30 -43
  33. package/test/integration/tests.js +21 -13
  34. package/test/unit/lifecycle-builders-export.tests.js +179 -118
  35. package/test/unit/tests.js +33 -34
@@ -18,6 +18,7 @@
18
18
  }
19
19
  }(function (window, document, undefined) {
20
20
  const elementDataStore = new WeakMap();
21
+ const nodeLayoutStore = new WeakMap();
21
22
  const getWindow = () => (typeof globalThis !== 'undefined' && globalThis.window ? globalThis.window : window);
22
23
  const getDocument = () => {
23
24
  const currentWindow = getWindow();
@@ -41,10 +42,128 @@
41
42
  return normalizeInput(input, context || getDocument());
42
43
  }
43
44
 
45
+ function getChildNodesContainer(nodeElement) {
46
+ const nodeData = getState(nodeElement, 'sourceData');
47
+ const containers = Array.from(nodeElement.parentElement ? nodeElement.parentElement.children : []).filter(function (element) {
48
+ return element.classList.contains('nodes');
49
+ });
50
+ const ownedContainer = containers.find(function (container) {
51
+ return nodeData && getState(container, 'parentData') === nodeData;
52
+ });
53
+ if (ownedContainer) {
54
+ return ownedContainer;
55
+ }
56
+ if (containers.some(function (container) { return !!getState(container, 'parentData'); })) {
57
+ return null;
58
+ }
59
+ const nextElement = nodeElement.nextElementSibling;
60
+ if (nextElement && nextElement.classList.contains('nodes')) {
61
+ return nextElement;
62
+ }
63
+ if (nodeElement.parentElement && nodeElement.parentElement.classList.contains('hierarchy')) {
64
+ return Array.from(nodeElement.parentElement.children).find(function (element) {
65
+ return element.classList.contains('nodes');
66
+ }) || null;
67
+ }
68
+ return null;
69
+ }
70
+
44
71
  function mergeObjects() {
45
72
  return Object.assign.apply(Object, [{}].concat(Array.from(arguments)));
46
73
  }
47
74
 
75
+ function mergeChartOptions(defaults) {
76
+ const options = mergeObjects({}, defaults);
77
+ options.icons = mergeObjects({}, defaults.icons);
78
+ Array.from(arguments).slice(1).forEach(function (overrides) {
79
+ if (!overrides) {
80
+ return;
81
+ }
82
+ Object.assign(options, overrides, { icons: mergeObjects({}, options.icons, overrides.icons) });
83
+ if (Object.prototype.hasOwnProperty.call(overrides, 'drag')
84
+ && !Object.prototype.hasOwnProperty.call(overrides, 'draggable')) {
85
+ options.draggable = overrides.drag;
86
+ }
87
+ });
88
+ options.visibleLevel = options.visibleLevel === true ? defaults.visibleLevel
89
+ : options.visibleLevel === false ? 1 : Number(options.visibleLevel);
90
+ if (!Number.isInteger(options.visibleLevel) || options.visibleLevel < 0) {
91
+ throw new RangeError('visibleLevel must be a nonnegative integer or boolean');
92
+ }
93
+ options.zoomoutLimit = Number(options.zoomoutLimit);
94
+ options.zoominLimit = Number(options.zoominLimit);
95
+ if (!Number.isFinite(options.zoomoutLimit) || !Number.isFinite(options.zoominLimit)
96
+ || options.zoomoutLimit <= 0 || options.zoominLimit < options.zoomoutLimit) {
97
+ throw new RangeError('zoom limits must be positive and zoomoutLimit must not exceed zoominLimit');
98
+ }
99
+ if (!['t2b', 'b2t', 'l2r', 'r2l'].includes(options.direction)) {
100
+ throw new RangeError('direction must be t2b, b2t, l2r, or r2l');
101
+ }
102
+ return options;
103
+ }
104
+
105
+ function prepareNodeData(data, resetLayout) {
106
+ if (Array.isArray(data)) {
107
+ data.forEach(function (item) {
108
+ prepareNodeData(item, resetLayout);
109
+ });
110
+ return;
111
+ }
112
+ if (!isPlainObject(data)) {
113
+ return;
114
+ }
115
+ const layoutKeys = ['compact', 'associatedCompact', 'vertical', 'level', 'parentId'];
116
+ let layout = nodeLayoutStore.get(data);
117
+ if (!layout) {
118
+ layout = {};
119
+ layoutKeys.forEach(function (key) {
120
+ if (Object.prototype.hasOwnProperty.call(data, key)) {
121
+ layout[key] = data[key];
122
+ }
123
+ });
124
+ nodeLayoutStore.set(data, layout);
125
+ }
126
+ if (resetLayout) {
127
+ delete layout.level;
128
+ delete layout.parentId;
129
+ }
130
+ layoutKeys.forEach(function (key) {
131
+ if (Object.prototype.hasOwnProperty.call(layout, key)) {
132
+ data[key] = layout[key];
133
+ } else {
134
+ delete data[key];
135
+ }
136
+ });
137
+ (data.children || []).forEach(function (child) {
138
+ prepareNodeData(child, resetLayout);
139
+ });
140
+ }
141
+
142
+ function usesCompactLayout(data, options, level) {
143
+ return !!data.compact && !data.hybrid && !data.vertical
144
+ && !(options.verticalLevel && level + 1 >= options.verticalLevel);
145
+ }
146
+
147
+ function findDataLocation(datasource, target) {
148
+ let location = null;
149
+ const visit = function (data, parent, list, index, outerList, outerIndex) {
150
+ if (location) {
151
+ return;
152
+ }
153
+ if (Array.isArray(data)) {
154
+ data.forEach(function (item, itemIndex) {
155
+ visit(item, parent, data, itemIndex, list, index);
156
+ });
157
+ } else if (data === target) {
158
+ location = { parent: parent, list: list, index: index, outerList: outerList, outerIndex: outerIndex };
159
+ } else if (data && data.children) {
160
+ visit(data.children, data, null, null, null, null);
161
+ }
162
+ };
163
+ visit(datasource, null, null, null, null, null);
164
+ return location;
165
+ }
166
+
48
167
  function forEachValue(collection, callback) {
49
168
  if (Array.isArray(collection) || isNodeList(collection)) {
50
169
  for (let index = 0; index < collection.length; index += 1) {
@@ -116,8 +235,16 @@
116
235
  if (!element || element.nodeType !== 1) {
117
236
  return false;
118
237
  }
119
- const style = getWindow().getComputedStyle(element);
120
- return style.display !== 'none' && style.visibility !== 'hidden';
238
+ let currentElement = element;
239
+ while (currentElement && currentElement.nodeType === 1) {
240
+ const style = getWindow().getComputedStyle(currentElement);
241
+ if (currentElement.classList.contains('hidden') || style.display === 'none'
242
+ || style.visibility === 'hidden' || style.visibility === 'collapse' || style.opacity === '0') {
243
+ return false;
244
+ }
245
+ currentElement = currentElement.parentElement;
246
+ }
247
+ return true;
121
248
  }
122
249
 
123
250
  function matchesSelector(element, selector) {
@@ -344,7 +471,7 @@
344
471
 
345
472
  function getChartTransform(chartElement) {
346
473
  let currentTransform;
347
- let matrixValues;
474
+ let matrixValues = [1, 0, 0, 1, 0, 0];
348
475
  let currentScale = 1;
349
476
  let currentTranslateX = 0;
350
477
  let currentTranslateY = 0;
@@ -353,7 +480,8 @@
353
480
  return {
354
481
  scale: currentScale,
355
482
  translateX: currentTranslateX,
356
- translateY: currentTranslateY
483
+ translateY: currentTranslateY,
484
+ matrix: matrixValues
357
485
  };
358
486
  }
359
487
 
@@ -363,14 +491,14 @@
363
491
  matrixValues = currentTransform.slice(9, -1).split(',').map(function (value) {
364
492
  return getWindow().parseFloat(value.trim());
365
493
  });
366
- currentScale = Math.abs(matrixValues[0]) || 1;
494
+ currentScale = Math.hypot(matrixValues[0], matrixValues[1], matrixValues[2]) || 1;
367
495
  currentTranslateX = matrixValues[12] || 0;
368
496
  currentTranslateY = matrixValues[13] || 0;
369
497
  } else if (currentTransform.indexOf('matrix(') === 0) {
370
498
  matrixValues = currentTransform.slice(7, -1).split(',').map(function (value) {
371
499
  return getWindow().parseFloat(value.trim());
372
500
  });
373
- currentScale = Math.abs(matrixValues[0]) || 1;
501
+ currentScale = Math.hypot(matrixValues[0], matrixValues[1]) || 1;
374
502
  currentTranslateX = matrixValues[4] || 0;
375
503
  currentTranslateY = matrixValues[5] || 0;
376
504
  } else if (currentTransform.indexOf('scale3d(') === 0) {
@@ -378,21 +506,82 @@
378
506
  return getWindow().parseFloat(value.trim());
379
507
  });
380
508
  currentScale = Math.abs(matrixValues[0]) || 1;
509
+ matrixValues = [matrixValues[0], 0, 0, 0, 0, matrixValues[1], 0, 0, 0, 0, matrixValues[2], 0, 0, 0, 0, 1];
381
510
  } else if (currentTransform.indexOf('scale(') === 0) {
382
511
  matrixValues = currentTransform.slice(6, -1).split(',').map(function (value) {
383
512
  return getWindow().parseFloat(value.trim());
384
513
  });
385
514
  currentScale = Math.abs(matrixValues[0]) || 1;
515
+ matrixValues = [matrixValues[0], 0, 0, matrixValues[1] === undefined ? matrixValues[0] : matrixValues[1], 0, 0];
386
516
  }
387
517
  }
388
518
 
389
519
  return {
390
520
  scale: currentScale,
391
521
  translateX: currentTranslateX,
392
- translateY: currentTranslateY
522
+ translateY: currentTranslateY,
523
+ matrix: matrixValues
393
524
  };
394
525
  }
395
526
 
527
+ function applyChartTransform(chartElement, chartTransform, scale, translateX, translateY) {
528
+ const matrixValues = chartTransform.matrix.slice();
529
+ const scaleRatio = scale / chartTransform.scale;
530
+ const is3d = matrixValues.length === 16;
531
+ const linearIndexes = is3d ? [0, 1, 2, 4, 5, 6, 8, 9, 10] : [0, 1, 2, 3];
532
+
533
+ linearIndexes.forEach(function (index) {
534
+ matrixValues[index] *= scaleRatio;
535
+ });
536
+ matrixValues[is3d ? 12 : 4] = translateX;
537
+ matrixValues[is3d ? 13 : 5] = translateY;
538
+ chartElement.style.transformOrigin = '0 0';
539
+ chartElement.style.transform = (is3d ? 'matrix3d(' : 'matrix(') + matrixValues.join(', ') + ')';
540
+ }
541
+
542
+ function rebuildFromData(orgChart, expandedData, updatedDatasource) {
543
+ const chartElement = orgChart.chart;
544
+ const datasource = updatedDatasource || getState(chartElement, 'datasource') || orgChart.options.data;
545
+ const expansionStates = new Map();
546
+ const transform = chartElement.style.transform;
547
+ const transformOrigin = chartElement.style.transformOrigin;
548
+ Array.from(chartElement.querySelectorAll('.node')).forEach(function (nodeElement) {
549
+ expansionStates.set(getState(nodeElement, 'sourceData'), {
550
+ visible: isVisibleElement(nodeElement),
551
+ expanded: nodeElement.classList.contains('compact')
552
+ ? nodeElement.classList.contains('looseMode')
553
+ : orgChart.getNodeState(nodeElement, 'children').visible
554
+ });
555
+ });
556
+ prepareNodeData(datasource, true);
557
+ orgChart.init({ data: datasource });
558
+ orgChart.chart.style.transform = transform;
559
+ orgChart.chart.style.transformOrigin = transformOrigin;
560
+ Array.from(orgChart.chart.querySelectorAll('.node')).forEach(function (nodeElement) {
561
+ const nodeData = getState(nodeElement, 'sourceData');
562
+ const state = expansionStates.get(nodeData);
563
+ const expanded = nodeData === expandedData || (state && state.expanded);
564
+ if (state && state.visible) {
565
+ nodeElement.classList.remove('slide-up');
566
+ }
567
+ if (nodeElement.classList.contains('compact')) {
568
+ if (expanded && orgChart.getChildren(nodeElement).length) {
569
+ orgChart.backToLooseHandler({ compactNode: nodeElement });
570
+ }
571
+ } else {
572
+ const childContainer = getChildNodesContainer(nodeElement);
573
+ if (childContainer && (state || nodeData === expandedData)) {
574
+ childContainer.classList.toggle('hidden', !expanded);
575
+ nodeElement.closest('.hierarchy').classList.toggle('isChildrenCollapsed', !expanded);
576
+ if (expanded) {
577
+ orgChart.getChildren(nodeElement).forEach(function (child) { child.classList.remove('slide-up'); });
578
+ }
579
+ }
580
+ }
581
+ });
582
+ orgChart.updateMinimap(true);
583
+ }
584
+
396
585
  const OrgChart = function (elem, opts) {
397
586
  if (!(this instanceof OrgChart)) {
398
587
  return new OrgChart(elem, opts);
@@ -405,6 +594,7 @@
405
594
  this.opts = opts || {};
406
595
  }
407
596
  this.chart = null;
597
+ this.exportButtonElement = null;
408
598
  this.defaultOptions = {
409
599
  'icons': {
410
600
  'theme': 'oci',
@@ -454,7 +644,9 @@
454
644
  OrgChart.prototype = {
455
645
  //
456
646
  init: function (opts) {
457
- this.options = mergeObjects({}, this.defaultOptions, this.opts, opts);
647
+ const options = mergeChartOptions(this.defaultOptions, this.opts, opts);
648
+ this.opts = options;
649
+ this.options = options;
458
650
  // build the org-chart
459
651
  const chartContainerElement = this.chartContainer;
460
652
  let chartElement;
@@ -464,6 +656,13 @@
464
656
 
465
657
  this.detachMinimap();
466
658
  if (this.chart) {
659
+ const initObserver = getState(this.chart, 'initMutationObserver');
660
+ if (initObserver) {
661
+ initObserver.disconnect();
662
+ }
663
+ this.panEndHandler({ chart: this.chart });
664
+ this.unbindPan();
665
+ this.unbindZoom();
467
666
  this.chart.remove();
468
667
  }
469
668
  const data = this.options.data;
@@ -479,12 +678,21 @@
479
678
  Array.from(chartElement.querySelectorAll('.node.focused')).forEach(function (focusedNodeEl) {
480
679
  focusedNodeEl.classList.remove('focused');
481
680
  });
681
+ const activeElement = getDocument().activeElement;
682
+ if (activeElement && chartElement.contains(activeElement) && activeElement.closest('.node')) {
683
+ activeElement.blur();
684
+ }
482
685
  }
483
686
  });
484
687
  if (typeof getWindow().MutationObserver !== 'undefined') {
485
688
  this.triggerInitEvent();
486
689
  }
487
690
  const isListElementData = isElement(data) && data.tagName === 'UL';
691
+ const datasource = isListElementData ? this.buildJsonDS(data.firstElementChild) : data;
692
+ prepareNodeData(datasource);
693
+ setState(chartElement, 'datasource', datasource);
694
+ this.options.data = datasource;
695
+ this.opts.data = datasource;
488
696
  rootNodesContainerElement = getDocument().createElement('ul');
489
697
  rootNodesContainerElement.className = 'nodes';
490
698
  chartElement.appendChild(rootNodesContainerElement);
@@ -498,23 +706,22 @@
498
706
  rootElement = rootHierarchyElement;
499
707
  }
500
708
 
501
- if (isListElementData) { // ul datasource
502
- this.buildHierarchy(rootElement, this.buildJsonDS(data.firstElementChild));
503
- } else { // local json datasource
504
- if (data.relationship) {
505
- this.buildHierarchy(rootElement, data);
506
- } else {
507
- this.buildHierarchy(rootElement, Array.isArray(data) ? data : this.attachRel(data, '00'));
508
- }
509
- }
709
+ this.buildHierarchy(rootElement, datasource);
510
710
 
511
711
  if (chartContainerElement) {
512
712
  chartContainerElement.appendChild(chartElement);
513
713
  }
514
714
 
515
715
  // append the export button
516
- if (this.options.exportButton && !getDocument().querySelector('.oc-export-btn')) {
517
- this.attachExportButton();
716
+ if (this.options.exportButton) {
717
+ if (!this.exportButtonElement || !this.exportButtonElement.parentElement) {
718
+ this.attachExportButton();
719
+ } else {
720
+ this.exportButtonElement.textContent = this.options.exportButtonName;
721
+ }
722
+ } else if (this.exportButtonElement) {
723
+ this.exportButtonElement.remove();
724
+ this.exportButtonElement = null;
518
725
  }
519
726
 
520
727
  if (this.options.pan) {
@@ -556,26 +763,30 @@
556
763
  //
557
764
  triggerInitEvent: function () {
558
765
  const orgChart = this;
766
+ const chartElement = this.chart;
767
+ const options = this.options;
559
768
  const MutationObserverCtor = getWindow().MutationObserver;
560
769
  const mo = new MutationObserverCtor(function (mutations) {
561
770
  mo.disconnect();
771
+ setState(chartElement, 'initMutationObserver', null);
562
772
  initTime:
563
773
  for (let i = 0; i < mutations.length; i++) {
564
774
  for (let j = 0; j < mutations[i].addedNodes.length; j++) {
565
- if (mutations[i].addedNodes[j].classList && mutations[i].addedNodes[j].classList.contains('orgchart')) {
775
+ if (mutations[i].addedNodes[j] === chartElement) {
566
776
  orgChart.handleCompactNodes();
567
- if (!orgChart.chart) {
777
+ if (!chartElement.isConnected) {
568
778
  break initTime;
569
779
  }
570
- if (orgChart.options.initCompleted && typeof orgChart.options.initCompleted === 'function') {
571
- orgChart.options.initCompleted(orgChart.chart);
780
+ if (options.initCompleted && typeof options.initCompleted === 'function') {
781
+ options.initCompleted(chartElement);
572
782
  }
573
- mutations[i].addedNodes[j].dispatchEvent(createTriggeredEvent('init.orgchart'));
783
+ chartElement.dispatchEvent(createTriggeredEvent('init.orgchart'));
574
784
  break initTime;
575
785
  }
576
786
  }
577
787
  }
578
788
  });
789
+ setState(chartElement, 'initMutationObserver', mo);
579
790
  mo.observe(this.chartContainer, { childList: true });
580
791
  },
581
792
  triggerShowEvent: function (targetEl, rel) {
@@ -601,72 +812,53 @@
601
812
  exportButtonElement = getDocument().createElement('button');
602
813
  exportButtonElement.className = 'oc-export-btn';
603
814
  exportButtonElement.textContent = this.options.exportButtonName;
815
+ setState(exportButtonElement, 'orgChart', this);
604
816
  exportButtonElement.addEventListener('click', function (event) {
605
817
  event.preventDefault();
606
818
  orgChart.export();
607
819
  });
820
+ this.exportButtonElement = exportButtonElement;
608
821
  chartContainerElement.insertAdjacentElement('afterend', exportButtonElement);
609
822
  },
610
823
  setOptions: function (opts, val) {
611
- if (typeof opts === 'string') {
612
- if (opts === 'pan') {
613
- if (val) {
614
- this.bindPan();
615
- } else {
616
- this.unbindPan();
617
- }
618
- }
619
- if (opts === 'zoom') {
620
- if (val) {
621
- this.bindZoom();
622
- } else {
623
- this.unbindZoom();
624
- }
824
+ const overrides = typeof opts === 'string' ? { [opts]: val } : opts;
825
+ if (!overrides || typeof overrides !== 'object') {
826
+ return this;
827
+ }
828
+ const options = mergeChartOptions(this.defaultOptions, this.opts, this.options, overrides);
829
+ const needsRebuild = Object.keys(overrides).some(function (key) {
830
+ return !['pan', 'zoom', 'minimap', 'zoominLimit', 'zoomoutLimit', 'exportScale', 'exportFilename', 'exportFileextension'].includes(key);
831
+ });
832
+ if (needsRebuild) {
833
+ return this.init(options);
834
+ }
835
+ this.opts = options;
836
+ this.options = options;
837
+ if (this.chart) {
838
+ setState(this.chart, 'options', options);
839
+ }
840
+ if (Object.prototype.hasOwnProperty.call(overrides, 'pan')) {
841
+ if (options.pan) {
842
+ this.bindPan();
843
+ } else {
844
+ this.panEndHandler({ chart: this.chart });
845
+ this.unbindPan();
625
846
  }
626
- if (opts === 'minimap') {
627
- this.options = mergeObjects({}, this.options || {}, { minimap: val });
628
- if (this.chart) {
629
- setState(this.chart, 'options', this.options);
630
- }
631
- if (val) {
632
- this.attachMinimap();
633
- } else {
634
- this.detachMinimap();
635
- }
847
+ }
848
+ if (Object.prototype.hasOwnProperty.call(overrides, 'zoom')) {
849
+ if (options.zoom) {
850
+ this.bindZoom();
851
+ } else {
852
+ this.unbindZoom();
636
853
  }
637
854
  }
638
- if (typeof opts === 'object') {
639
- if (opts.data) {
640
- this.init(opts);
855
+ if (Object.prototype.hasOwnProperty.call(overrides, 'minimap')) {
856
+ if (options.minimap) {
857
+ this.attachMinimap();
641
858
  } else {
642
- if (typeof opts.pan !== 'undefined') {
643
- if (opts.pan) {
644
- this.bindPan();
645
- } else {
646
- this.unbindPan();
647
- }
648
- }
649
- if (typeof opts.zoom !== 'undefined') {
650
- if (opts.zoom) {
651
- this.bindZoom();
652
- } else {
653
- this.unbindZoom();
654
- }
655
- }
656
- if (typeof opts.minimap !== 'undefined') {
657
- this.options = mergeObjects({}, this.options || {}, { minimap: opts.minimap });
658
- if (this.chart) {
659
- setState(this.chart, 'options', this.options);
660
- }
661
- if (opts.minimap) {
662
- this.attachMinimap();
663
- } else {
664
- this.detachMinimap();
665
- }
666
- }
859
+ this.detachMinimap();
667
860
  }
668
861
  }
669
-
670
862
  return this;
671
863
  },
672
864
  attachMinimap: function () {
@@ -679,7 +871,9 @@
679
871
  let minimapViewportElement;
680
872
  let minimapWheelBoundHandler;
681
873
  let minimapPointerDownBoundHandler;
874
+ let minimapViewportChangeBoundHandler;
682
875
  let minimapMutationObserver;
876
+ let minimapResizeObserver;
683
877
 
684
878
  if (!chartContainerElement || !chartElement) {
685
879
  return;
@@ -712,21 +906,53 @@
712
906
  minimapPointerDownBoundHandler = function (event) {
713
907
  orgChart.minimapStartHandler(event);
714
908
  };
909
+ minimapViewportChangeBoundHandler = function () {
910
+ orgChart.updateMinimap();
911
+ };
715
912
 
716
- minimapElement.addEventListener('wheel', minimapWheelBoundHandler);
913
+ minimapElement.addEventListener('wheel', minimapWheelBoundHandler, { passive: false });
717
914
  minimapViewportElement.addEventListener('mousedown', minimapPointerDownBoundHandler);
915
+ chartContainerElement.addEventListener('scroll', minimapViewportChangeBoundHandler);
916
+ getWindow().addEventListener('resize', minimapViewportChangeBoundHandler);
718
917
 
719
918
  if (typeof getWindow().MutationObserver !== 'undefined') {
720
- minimapMutationObserver = new (getWindow().MutationObserver)(function () {
721
- orgChart.updateMinimap(true);
919
+ minimapMutationObserver = new (getWindow().MutationObserver)(function (mutations) {
920
+ const controlsSelector = '.edge, .toggleBtn, .backToCompactSymbol, .backToLooseSymbol';
921
+ const needsRefresh = mutations.some(function (mutation) {
922
+ if (mutation.type === 'childList') {
923
+ return Array.from(mutation.addedNodes).concat(Array.from(mutation.removedNodes)).some(function (node) {
924
+ return node.nodeType !== 1 || !node.matches(controlsSelector);
925
+ });
926
+ }
927
+ if (mutation.target.matches(controlsSelector)) {
928
+ return false;
929
+ }
930
+ const layoutClasses = function (value) {
931
+ return (value || '').split(/\s+/).filter(function (className) {
932
+ return className && className !== 'controls-active' && className !== 'focused';
933
+ }).sort().join(' ');
934
+ };
935
+ return layoutClasses(mutation.oldValue) !== layoutClasses(mutation.target.className);
936
+ });
937
+ if (needsRefresh) {
938
+ orgChart.updateMinimap(true);
939
+ }
722
940
  });
723
941
  minimapMutationObserver.observe(chartElement, {
724
942
  childList: true,
725
943
  subtree: true,
726
944
  attributes: true,
945
+ attributeOldValue: true,
727
946
  attributeFilter: ['class']
728
947
  });
729
948
  }
949
+ if (typeof getWindow().ResizeObserver !== 'undefined') {
950
+ minimapResizeObserver = new (getWindow().ResizeObserver)(function () {
951
+ orgChart.updateMinimap(true);
952
+ });
953
+ minimapResizeObserver.observe(chartContainerElement);
954
+ minimapResizeObserver.observe(chartElement);
955
+ }
730
956
 
731
957
  setState(chartElement, 'minimapElement', minimapElement);
732
958
  setState(chartElement, 'minimapStageElement', minimapStageElement);
@@ -734,7 +960,9 @@
734
960
  setState(chartElement, 'minimapViewportElement', minimapViewportElement);
735
961
  setState(chartElement, 'minimapWheelBoundHandler', minimapWheelBoundHandler);
736
962
  setState(chartElement, 'minimapPointerDownBoundHandler', minimapPointerDownBoundHandler);
963
+ setState(chartElement, 'minimapViewportChangeBoundHandler', minimapViewportChangeBoundHandler);
737
964
  setState(chartElement, 'minimapMutationObserver', minimapMutationObserver || null);
965
+ setState(chartElement, 'minimapResizeObserver', minimapResizeObserver || null);
738
966
 
739
967
  this.updateMinimap(true);
740
968
  },
@@ -748,6 +976,8 @@
748
976
  let minimapMoveBoundHandler;
749
977
  let minimapEndBoundHandler;
750
978
  let minimapMutationObserver;
979
+ let minimapViewportChangeBoundHandler;
980
+ let minimapResizeObserver;
751
981
 
752
982
  if (chartElement) {
753
983
  minimapElement = getState(chartElement, 'minimapElement');
@@ -757,6 +987,8 @@
757
987
  minimapMoveBoundHandler = getState(chartElement, 'minimapMoveBoundHandler');
758
988
  minimapEndBoundHandler = getState(chartElement, 'minimapEndBoundHandler');
759
989
  minimapMutationObserver = getState(chartElement, 'minimapMutationObserver');
990
+ minimapViewportChangeBoundHandler = getState(chartElement, 'minimapViewportChangeBoundHandler');
991
+ minimapResizeObserver = getState(chartElement, 'minimapResizeObserver');
760
992
 
761
993
  if (minimapElement && minimapWheelBoundHandler) {
762
994
  minimapElement.removeEventListener('wheel', minimapWheelBoundHandler);
@@ -773,6 +1005,13 @@
773
1005
  if (minimapMutationObserver) {
774
1006
  minimapMutationObserver.disconnect();
775
1007
  }
1008
+ if (minimapViewportChangeBoundHandler) {
1009
+ chartContainerElement.removeEventListener('scroll', minimapViewportChangeBoundHandler);
1010
+ getWindow().removeEventListener('resize', minimapViewportChangeBoundHandler);
1011
+ }
1012
+ if (minimapResizeObserver) {
1013
+ minimapResizeObserver.disconnect();
1014
+ }
776
1015
  if (minimapElement && minimapElement.parentElement) {
777
1016
  minimapElement.remove();
778
1017
  }
@@ -787,6 +1026,8 @@
787
1026
  'minimapMoveBoundHandler',
788
1027
  'minimapEndBoundHandler',
789
1028
  'minimapMutationObserver',
1029
+ 'minimapViewportChangeBoundHandler',
1030
+ 'minimapResizeObserver',
790
1031
  'minimapDragState',
791
1032
  'minimapScale',
792
1033
  'minimapOffsetX',
@@ -813,6 +1054,9 @@
813
1054
  if (typeof event.preventDefault === 'function') {
814
1055
  event.preventDefault();
815
1056
  }
1057
+ if (typeof event.stopPropagation === 'function') {
1058
+ event.stopPropagation();
1059
+ }
816
1060
 
817
1061
  this.setChartScale(this.chart, 1 + (event.deltaY > 0 ? -0.2 : 0.2), {
818
1062
  x: chartContainerElement.getBoundingClientRect().left + (chartContainerElement.clientWidth / 2),
@@ -897,8 +1141,7 @@
897
1141
  let localViewportHeight;
898
1142
  let localLeft;
899
1143
  let localTop;
900
- let baseLeft;
901
- let baseTop;
1144
+ let transformedRect;
902
1145
 
903
1146
  if (!chartElement || !chartContainerElement || !minimapStageElement || !minimapScale || !chartRect || !containerRect) {
904
1147
  return;
@@ -908,13 +1151,11 @@
908
1151
  localViewportHeight = viewportHeight / minimapScale;
909
1152
  localLeft = clampValue((stageLeft - minimapOffsetX) / minimapScale, 0, Math.max(minimapContentWidth - localViewportWidth, 0));
910
1153
  localTop = clampValue((stageTop - minimapOffsetY) / minimapScale, 0, Math.max(minimapContentHeight - localViewportHeight, 0));
911
- baseLeft = chartRect.left - chartTransform.translateX;
912
- baseTop = chartRect.top - chartTransform.translateY;
913
-
914
- chartElement.style.transformOrigin = '0 0';
915
- chartElement.style.transform = 'matrix(' + chartTransform.scale + ', 0, 0, ' + chartTransform.scale + ', '
916
- + (containerRect.left - baseLeft - (localLeft * chartTransform.scale)) + ', '
917
- + (containerRect.top - baseTop - (localTop * chartTransform.scale)) + ')';
1154
+ applyChartTransform(chartElement, chartTransform, chartTransform.scale, chartTransform.translateX, chartTransform.translateY);
1155
+ transformedRect = chartElement.getBoundingClientRect();
1156
+ applyChartTransform(chartElement, chartTransform, chartTransform.scale,
1157
+ chartTransform.translateX + containerRect.left - (localLeft * chartTransform.scale) - transformedRect.left,
1158
+ chartTransform.translateY + containerRect.top - (localTop * chartTransform.scale) - transformedRect.top);
918
1159
  this.updateMinimap();
919
1160
  },
920
1161
  updateMinimap: function (forceRefresh) {
@@ -953,6 +1194,8 @@
953
1194
  minimapScale = Math.min(stageWidth / Math.max(contentWidth, 1), stageHeight / Math.max(contentHeight, 1));
954
1195
  minimapOffsetX = (stageWidth - (contentWidth * minimapScale)) / 2;
955
1196
  minimapOffsetY = (stageHeight - (contentHeight * minimapScale)) / 2;
1197
+ const minimapElement = getState(chartElement, 'minimapElement');
1198
+ minimapElement.style.transform = 'translate(' + chartContainerElement.scrollLeft + 'px, ' + chartContainerElement.scrollTop + 'px)';
956
1199
 
957
1200
  minimapContentElement.style.width = contentWidth + 'px';
958
1201
  minimapContentElement.style.height = contentHeight + 'px';
@@ -1005,11 +1248,11 @@
1005
1248
  if (lastTf !== 'none') {
1006
1249
  const temp = lastTf.split(',');
1007
1250
  if (lastTf.indexOf('3d') === -1) {
1008
- lastX = parseInt(temp[4]);
1009
- lastY = parseInt(temp[5]);
1251
+ lastX = parseFloat(temp[4]) || 0;
1252
+ lastY = parseFloat(temp[5]) || 0;
1010
1253
  } else {
1011
- lastX = parseInt(temp[12]);
1012
- lastY = parseInt(temp[13]);
1254
+ lastX = parseFloat(temp[12]) || 0;
1255
+ lastY = parseFloat(temp[13]) || 0;
1013
1256
  }
1014
1257
  }
1015
1258
  let startX = 0;
@@ -1065,8 +1308,8 @@
1065
1308
  matrix[13] = ' ' + newY;
1066
1309
  }
1067
1310
  chartElement.style.transform = matrix.join(',');
1068
- orgChart.updateMinimap();
1069
1311
  }
1312
+ orgChart.updateMinimap();
1070
1313
  };
1071
1314
 
1072
1315
  setState(chartElement, 'panMoveHandler', panMoveHandler);
@@ -1203,6 +1446,7 @@
1203
1446
  dist = orgChart.getPinchDist(nativeEvent);
1204
1447
  pinchCenter = orgChart.getPinchCenter(nativeEvent);
1205
1448
  setState(chartElement, 'pinchDistStart', dist);
1449
+ setState(chartElement, 'pinchDistEnd', dist);
1206
1450
  setState(chartElement, 'pinchCenterStart', pinchCenter);
1207
1451
  setState(chartElement, 'pinchCenterEnd', pinchCenter);
1208
1452
  }
@@ -1214,7 +1458,7 @@
1214
1458
  let dist;
1215
1459
  let pinchCenter;
1216
1460
 
1217
- if(chartElement && getState(chartElement, 'pinching')) {
1461
+ if(chartElement && getState(chartElement, 'pinching') && nativeEvent.touches && nativeEvent.touches.length === 2) {
1218
1462
  dist = orgChart.getPinchDist(nativeEvent);
1219
1463
  pinchCenter = orgChart.getPinchCenter(nativeEvent);
1220
1464
  setState(chartElement, 'pinchDistEnd', dist);
@@ -1340,16 +1584,9 @@
1340
1584
  const chartContainerElement = this.chartContainer;
1341
1585
  let opts;
1342
1586
  let chartRect;
1343
- let currentScale = 1;
1344
- let currentTranslateX = 0;
1345
- let currentTranslateY = 0;
1587
+ let chartTransform;
1588
+ let transformedRect;
1346
1589
  let targetScale = 1;
1347
- let anchorX;
1348
- let anchorY;
1349
- let localAnchorX;
1350
- let localAnchorY;
1351
- let baseLeft;
1352
- let baseTop;
1353
1590
  let containerRect;
1354
1591
  let targetTranslateX;
1355
1592
  let targetTranslateY;
@@ -1359,17 +1596,18 @@
1359
1596
  }
1360
1597
 
1361
1598
  opts = getState(chartElement, 'options');
1362
- currentScale = getChartTransform(chartElement).scale;
1363
- currentTranslateX = getChartTransform(chartElement).translateX;
1364
- currentTranslateY = getChartTransform(chartElement).translateY;
1599
+ chartTransform = getChartTransform(chartElement);
1365
1600
 
1366
- targetScale = currentScale * newScale;
1367
- if (!(targetScale > opts.zoomoutLimit && targetScale < opts.zoominLimit)) {
1601
+ if (!Number.isFinite(newScale) || newScale <= 0) {
1602
+ return;
1603
+ }
1604
+ targetScale = clampValue(chartTransform.scale * newScale, opts.zoomoutLimit, opts.zoominLimit);
1605
+ if (targetScale === chartTransform.scale) {
1368
1606
  return;
1369
1607
  }
1370
1608
 
1371
- targetTranslateX = currentTranslateX;
1372
- targetTranslateY = currentTranslateY;
1609
+ targetTranslateX = chartTransform.translateX;
1610
+ targetTranslateY = chartTransform.translateY;
1373
1611
  chartRect = typeof chartElement.getBoundingClientRect === 'function'
1374
1612
  ? chartElement.getBoundingClientRect()
1375
1613
  : null;
@@ -1380,19 +1618,15 @@
1380
1618
  y: containerRect.top + (containerRect.height / 2)
1381
1619
  };
1382
1620
  }
1383
- if (zoomAnchor && chartRect && typeof zoomAnchor.x === 'number' && typeof zoomAnchor.y === 'number' && currentScale !== 0) {
1384
- anchorX = zoomAnchor.x;
1385
- anchorY = zoomAnchor.y;
1386
- localAnchorX = (anchorX - chartRect.left) / currentScale;
1387
- localAnchorY = (anchorY - chartRect.top) / currentScale;
1388
- baseLeft = chartRect.left - currentTranslateX;
1389
- baseTop = chartRect.top - currentTranslateY;
1390
- targetTranslateX = anchorX - baseLeft - (localAnchorX * targetScale);
1391
- targetTranslateY = anchorY - baseTop - (localAnchorY * targetScale);
1621
+ applyChartTransform(chartElement, chartTransform, targetScale, targetTranslateX, targetTranslateY);
1622
+ if (zoomAnchor && chartRect && typeof zoomAnchor.x === 'number' && typeof zoomAnchor.y === 'number' && chartTransform.scale !== 0) {
1623
+ const scaleRatio = targetScale / chartTransform.scale;
1624
+ transformedRect = chartElement.getBoundingClientRect();
1625
+ targetTranslateX += zoomAnchor.x + ((chartRect.left - zoomAnchor.x) * scaleRatio) - transformedRect.left;
1626
+ targetTranslateY += zoomAnchor.y + ((chartRect.top - zoomAnchor.y) * scaleRatio) - transformedRect.top;
1392
1627
  }
1393
1628
 
1394
- chartElement.style.transformOrigin = '0 0';
1395
- chartElement.style.transform = 'matrix(' + targetScale + ', 0, 0, ' + targetScale + ', ' + targetTranslateX + ', ' + targetTranslateY + ')';
1629
+ applyChartTransform(chartElement, chartTransform, targetScale, targetTranslateX, targetTranslateY);
1396
1630
  this.updateMinimap();
1397
1631
  },
1398
1632
  //
@@ -1411,12 +1645,7 @@
1411
1645
  }) || null;
1412
1646
 
1413
1647
  jsonNodeData = {
1414
- 'name': ((liElement.childNodes && liElement.childNodes[0]) ? liElement.childNodes[0].textContent : '').trim(),
1415
- 'relationship': (liElement.parentElement && liElement.parentElement.parentElement && liElement.parentElement.parentElement.tagName === 'LI' ? '1' : '0')
1416
- + (liElement.parentElement ? Array.from(liElement.parentElement.children).filter(function (siblingEl) {
1417
- return siblingEl !== liElement && siblingEl.tagName === 'LI';
1418
- }).length ? 1 : 0 : 0)
1419
- + (childListElement ? 1 : 0)
1648
+ 'name': ((liElement.childNodes && liElement.childNodes[0]) ? liElement.childNodes[0].textContent : '').trim()
1420
1649
  };
1421
1650
  forEachValue(getDatasetSnapshot(liElement), function(key, value) {
1422
1651
  jsonNodeData[key] = value;
@@ -1435,33 +1664,10 @@
1435
1664
 
1436
1665
  return jsonNodeData;
1437
1666
  },
1438
- // process datasource and add necessary information
1439
- attachRel: function (data, flags) {
1440
- const orgChart = this;
1441
- data.relationship = flags + (data.children && data.children.length > 0 ? 1 : 0);
1442
- if (this.options?.compact?.constructor === Function && this.options.compact(data)) {
1443
- data.compact = true;
1444
- }
1445
- if (data.children) {
1446
- data.children.forEach(function(item) {
1447
- if (data.hybrid || data.vertical) { // identify all the descendant nodes except the root node of hybrid structure
1448
- item.vertical = true;
1449
- } else if (data.compact && item.children) { // identify all the compact ancestor nodes
1450
- item.compact = true;
1451
- } else if (data.compact && !item.children) { // identify all the compact descendant nodes
1452
- item.associatedCompact = true;
1453
- }
1454
- orgChart.attachRel(item, '1' + (data.children.length > 1 ? 1 : 0));
1455
- });
1456
- }
1457
- return data;
1458
- },
1459
- //
1460
1667
  loopChart: function (chartEl, includeNodeData) {
1461
1668
  const orgChart = this;
1462
1669
  const chartElement = getElement(chartEl);
1463
1670
  let nodeElement;
1464
- let childNodesContainerElement;
1465
1671
  let hierarchyNodeData;
1466
1672
 
1467
1673
  includeNodeData = (includeNodeData !== null && includeNodeData !== undefined) ? includeNodeData : false;
@@ -1470,11 +1676,13 @@
1470
1676
  return {};
1471
1677
  }
1472
1678
 
1473
- nodeElement = chartElement.classList && chartElement.classList.contains('hierarchy')
1474
- ? Array.from(chartElement.children || []).find(function (childEl) {
1679
+ nodeElement = chartElement.classList && chartElement.classList.contains('node')
1680
+ ? chartElement
1681
+ : chartElement.classList && chartElement.classList.contains('hierarchy')
1682
+ ? Array.from(chartElement.children || []).find(function (childEl) {
1475
1683
  return childEl.classList && childEl.classList.contains('node');
1476
1684
  })
1477
- : chartElement.querySelector('.node');
1685
+ : chartElement.querySelector('.node');
1478
1686
  if (!nodeElement) {
1479
1687
  return {};
1480
1688
  }
@@ -1485,18 +1693,10 @@
1485
1693
  hierarchyNodeData[key] = value;
1486
1694
  });
1487
1695
  }
1488
- childNodesContainerElement = Array.from(nodeElement.parentElement ? nodeElement.parentElement.children : []).find(function (childEl) {
1489
- return childEl !== nodeElement && childEl.classList && childEl.classList.contains('nodes');
1490
- }) || null;
1491
- if (childNodesContainerElement) {
1492
- Array.from(childNodesContainerElement.children).forEach(function (childEl) {
1493
- if (!childEl.classList || !childEl.classList.contains('hierarchy')) {
1494
- return;
1495
- }
1496
- if (!hierarchyNodeData.children) {
1497
- hierarchyNodeData.children = [];
1498
- }
1499
- hierarchyNodeData.children.push(orgChart.loopChart(childEl, includeNodeData));
1696
+ const childNodeElements = this.getChildren(nodeElement);
1697
+ if (childNodeElements.length) {
1698
+ hierarchyNodeData.children = childNodeElements.map(function (childNodeElement) {
1699
+ return orgChart.loopChart(childNodeElement, includeNodeData);
1500
1700
  });
1501
1701
  }
1502
1702
 
@@ -1520,95 +1720,61 @@
1520
1720
  }
1521
1721
  }
1522
1722
  }
1723
+ const datasource = getState(chartElement, 'datasource');
1724
+ if (Array.isArray(datasource)) {
1725
+ const exportFamily = function (groups) {
1726
+ return groups.map(function (group) {
1727
+ return group.map(function (data) {
1728
+ const node = nodeElements.find(function (element) { return getState(element, 'sourceData') === data; });
1729
+ const result = includeNodeData ? mergeObjects({}, getState(node, 'nodeData')) : {};
1730
+ result.id = node.id;
1731
+ if (data.children && data.children.length) {
1732
+ result.children = Array.isArray(data.children[0]) ? exportFamily(data.children)
1733
+ : data.children.map(function (childData) {
1734
+ const childNode = nodeElements.find(function (element) { return getState(element, 'sourceData') === childData; });
1735
+ return this.loopChart(childNode, includeNodeData);
1736
+ }, this);
1737
+ }
1738
+ return result;
1739
+ }, this);
1740
+ }, this);
1741
+ }.bind(this);
1742
+ return exportFamily(datasource);
1743
+ }
1523
1744
  return this.loopChart(chartElement, includeNodeData);
1524
1745
  },
1525
1746
  // detect the exist/display state of related node
1526
1747
  getNodeState: function (nodeEl, relation) {
1527
1748
  const nodeElement = getElement(nodeEl);
1528
- let relatedElement;
1529
- let isVerticalNode;
1530
- let siblingElements;
1531
1749
 
1532
1750
  if (!nodeElement || !nodeElement.classList || !nodeElement.classList.contains('node')) {
1533
1751
  return { 'exist': false, 'visible': false };
1534
1752
  }
1535
1753
 
1536
1754
  relation = relation || 'self';
1537
- isVerticalNode = !!nodeElement.closest('vertical');
1538
-
1539
1755
  if (relation === 'parent') {
1540
- if (isVerticalNode) {
1541
- relatedElement = nodeElement.closest('ul');
1542
- relatedElement = relatedElement ? relatedElement.parentElement && relatedElement.parentElement.closest('ul') : null;
1543
- if (!relatedElement) {
1544
- relatedElement = nodeElement.closest('.nodes');
1545
- if (!relatedElement) {
1546
- relatedElement = nodeElement.closest('.vertical');
1547
- relatedElement = relatedElement && relatedElement.parentElement ? relatedElement.parentElement.firstElementChild : null;
1548
- }
1549
- }
1550
- } else {
1551
- relatedElement = nodeElement.closest('.nodes');
1552
- relatedElement = relatedElement ? relatedElement.previousElementSibling : null;
1553
- }
1554
-
1555
- if (relatedElement) {
1556
- if (relatedElement.classList.contains('hidden')
1557
- || (relatedElement.closest('.nodes') && relatedElement.closest('.nodes').classList.contains('hidden'))
1558
- || (relatedElement.closest('.vertical') && relatedElement.closest('.vertical').classList.contains('hidden'))) {
1559
- return { 'exist': true, 'visible': false };
1560
- }
1561
- return { 'exist': true, 'visible': true };
1562
- }
1756
+ const parentElement = this.getParent(nodeElement);
1757
+ return { exist: !!parentElement, visible: !!parentElement && this.getNodeState(parentElement).visible };
1563
1758
  } else if (relation === 'children') {
1564
- relatedElement = isVerticalNode
1565
- ? Array.from(nodeElement.parentElement ? nodeElement.parentElement.children : []).find(function (childEl) {
1566
- return childEl.tagName === 'UL';
1567
- }) || null
1568
- : Array.from(nodeElement.parentElement ? nodeElement.parentElement.children : []).find(function (childEl) {
1569
- return childEl !== nodeElement && childEl.classList && childEl.classList.contains('nodes');
1570
- }) || null;
1571
-
1572
- if (relatedElement) {
1573
- if (!relatedElement.classList.contains('hidden')) {
1574
- return { 'exist': true, 'visible': true };
1575
- }
1576
- return { 'exist': true, 'visible': false };
1577
- }
1759
+ const childElements = this.getChildren(nodeElement);
1760
+ const childContainer = getChildNodesContainer(nodeElement);
1761
+ return {
1762
+ exist: childElements.length > 0 || !!childContainer,
1763
+ visible: childElements.length > 0
1764
+ ? childElements.some(function (childElement) { return this.getNodeState(childElement).visible; }, this)
1765
+ : !!childContainer && !childContainer.closest('.hidden') && this.getNodeState(nodeElement).visible
1766
+ };
1578
1767
  } else if (relation === 'siblings') {
1579
- if (isVerticalNode) {
1580
- relatedElement = nodeElement.closest('ul');
1581
- siblingElements = relatedElement ? Array.from(relatedElement.children || []).filter(function (childEl) {
1582
- return childEl.tagName === 'LI';
1583
- }) : [];
1584
- } else {
1585
- siblingElements = nodeElement.parentElement && nodeElement.parentElement.parentElement
1586
- ? Array.from(nodeElement.parentElement.parentElement.children).filter(function (childEl) {
1587
- return childEl !== nodeElement.parentElement;
1588
- })
1589
- : [];
1590
- relatedElement = siblingElements[0] || null;
1591
- }
1592
-
1593
- if (relatedElement && (!isVerticalNode || siblingElements.length > 1)) {
1594
- if (!siblingElements.some(function (siblingEl) { return siblingEl.classList.contains('hidden'); })
1595
- && !siblingElements.some(function (siblingEl) { return siblingEl.parentElement && siblingEl.parentElement.classList.contains('hidden'); })
1596
- && (!isVerticalNode || !relatedElement.closest('.vertical') || !relatedElement.closest('.vertical').classList.contains('hidden'))) {
1597
- return { 'exist': true, 'visible': true };
1598
- }
1599
- return { 'exist': true, 'visible': false };
1600
- }
1601
- } else {
1602
- if (!((nodeElement.closest('.nodes') && nodeElement.closest('.nodes').classList.contains('hidden')) ||
1603
- (nodeElement.closest('.hierarchy') && nodeElement.closest('.hierarchy').classList.contains('hidden')) ||
1604
- (nodeElement.closest('.vertical') && ((nodeElement.closest('.nodes') && nodeElement.closest('.nodes').classList.contains('hidden')) || nodeElement.closest('.vertical').classList.contains('hidden')))
1605
- )) {
1606
- return { 'exist': true, 'visible': true };
1607
- }
1608
- return { 'exist': true, 'visible': false };
1768
+ const siblingElements = this.getSiblings(nodeElement);
1769
+ return {
1770
+ exist: siblingElements.length > 0,
1771
+ visible: siblingElements.length > 0
1772
+ && siblingElements.every(function (siblingElement) { return this.getNodeState(siblingElement).visible; }, this)
1773
+ };
1609
1774
  }
1610
-
1611
- return { 'exist': false, 'visible': false };
1775
+ const collapsedCompactAncestor = nodeElement.parentElement
1776
+ ? nodeElement.parentElement.closest('.node.compact:not(.looseMode)') : null;
1777
+ return { exist: true, visible: !nodeElement.closest('.hidden') && !collapsedCompactAncestor };
1612
1778
  },
1613
1779
  getParent: function (nodeEl) {
1614
1780
  return this.getRelatedNodes(nodeEl, 'parent');
@@ -1631,12 +1797,25 @@
1631
1797
  }
1632
1798
 
1633
1799
  if (relation === 'parent') {
1800
+ if (nodeElement.parentElement && nodeElement.parentElement.classList.contains('node')) {
1801
+ return nodeElement.parentElement;
1802
+ }
1634
1803
  nodesContainerEl = nodeElement.closest('.nodes');
1804
+ const parentData = nodesContainerEl ? getState(nodesContainerEl, 'parentData') : null;
1805
+ if (parentData) {
1806
+ return Array.from(nodeElement.closest('.orgchart').querySelectorAll('.node')).find(function (element) {
1807
+ return getState(element, 'sourceData') === parentData;
1808
+ }) || null;
1809
+ }
1635
1810
  return nodesContainerEl ? nodesContainerEl.previousElementSibling : null;
1636
1811
  } else if (relation === 'children') {
1637
- childNodesContainerElement = Array.from(nodeElement.parentElement ? nodeElement.parentElement.children : []).find(function (childEl) {
1638
- return childEl !== nodeElement && childEl.classList && childEl.classList.contains('nodes');
1812
+ const nestedNodeElements = Array.from(nodeElement.children).filter(function (childElement) {
1813
+ return childElement.classList.contains('node');
1639
1814
  });
1815
+ if (nestedNodeElements.length) {
1816
+ return nestedNodeElements;
1817
+ }
1818
+ childNodesContainerElement = getChildNodesContainer(nodeElement);
1640
1819
 
1641
1820
  if (!childNodesContainerElement) {
1642
1821
  return [];
@@ -1644,20 +1823,27 @@
1644
1823
 
1645
1824
  return Array.from(childNodesContainerElement.children).filter(function (childEl) {
1646
1825
  return childEl.classList && childEl.classList.contains('hierarchy');
1647
- }).map(function (childHierarchyEl) {
1648
- return childHierarchyEl.querySelector('.node');
1649
- }).filter(Boolean);
1826
+ }).flatMap(function (childHierarchyEl) {
1827
+ return Array.from(childHierarchyEl.children).filter(function (element) { return element.classList.contains('node'); });
1828
+ });
1650
1829
  } else if (relation === 'siblings') {
1830
+ if (nodeElement.parentElement && nodeElement.parentElement.classList.contains('node')) {
1831
+ return Array.from(nodeElement.parentElement.children).filter(function (siblingElement) {
1832
+ return siblingElement !== nodeElement && siblingElement.classList.contains('node');
1833
+ });
1834
+ }
1651
1835
  hierarchyEl = nodeElement.closest('.hierarchy');
1652
1836
  if (!hierarchyEl || !hierarchyEl.parentElement) {
1653
1837
  return [];
1654
1838
  }
1655
1839
 
1656
1840
  return Array.from(hierarchyEl.parentElement.children).filter(function (siblingEl) {
1657
- return siblingEl !== hierarchyEl;
1658
- }).map(function (siblingHierarchyEl) {
1659
- return siblingHierarchyEl.querySelector('.node');
1660
- }).filter(Boolean);
1841
+ return siblingEl.classList.contains('hierarchy');
1842
+ }).flatMap(function (siblingHierarchyEl) {
1843
+ return Array.from(siblingHierarchyEl.children).filter(function (element) {
1844
+ return element !== nodeElement && element.classList.contains('node');
1845
+ });
1846
+ });
1661
1847
  }
1662
1848
 
1663
1849
  return [];
@@ -1820,9 +2006,7 @@
1820
2006
  }
1821
2007
 
1822
2008
  hierarchyElement = nodeElement.closest('.hierarchy');
1823
- lowerLevelElement = Array.from(nodeElement.parentElement ? nodeElement.parentElement.children : []).find(function (childEl) {
1824
- return childEl !== nodeElement && childEl.classList && childEl.classList.contains('nodes');
1825
- }) || null;
2009
+ lowerLevelElement = getChildNodesContainer(nodeElement);
1826
2010
 
1827
2011
  if (!lowerLevelElement) {
1828
2012
  return;
@@ -1898,9 +2082,7 @@
1898
2082
  }
1899
2083
 
1900
2084
  hierarchyElement = nodeElement.closest('.hierarchy');
1901
- levelsElement = Array.from(nodeElement.parentElement ? nodeElement.parentElement.children : []).find(function (childEl) {
1902
- return childEl !== nodeElement && childEl.classList && childEl.classList.contains('nodes');
1903
- }) || null;
2085
+ levelsElement = getChildNodesContainer(nodeElement);
1904
2086
 
1905
2087
  if (!levelsElement) {
1906
2088
  return;
@@ -1915,11 +2097,7 @@
1915
2097
  ? Array.from(levelsElement.querySelectorAll('.node')).filter(function (visibleNodeEl, index) {
1916
2098
  return this.isVisibleNode(index, visibleNodeEl);
1917
2099
  }, this)
1918
- : Array.from(levelsElement.children).filter(function (childEl) {
1919
- return childEl.classList && childEl.classList.contains('hierarchy');
1920
- }).map(function (childHierarchyEl) {
1921
- return childHierarchyEl.querySelector('.node');
1922
- }).filter(function (visibleNodeEl, index) {
2100
+ : this.getChildren(nodeElement).filter(function (visibleNodeEl, index) {
1923
2101
  return this.isVisibleNode(index, visibleNodeEl);
1924
2102
  }, this);
1925
2103
  if (!isVerticalDesc) {
@@ -2266,18 +2444,22 @@
2266
2444
  edgeElement.classList.add('hidden');
2267
2445
  nodeElement = edgeElement.parentElement;
2268
2446
  if (nodeElement) {
2447
+ setState(nodeElement, 'loadingEdge', edgeElement);
2269
2448
  nodeElement.insertAdjacentHTML('beforeend', `<i class="${this.options.icons.theme} ${this.options.icons.spinner} spinner"></i>`);
2270
2449
  spinnerElement = Array.from(nodeElement.children || []).find(function (childEl) {
2271
2450
  return childEl.classList && childEl.classList.contains('spinner');
2272
2451
  }) || null;
2273
2452
  Array.from(nodeElement.children || []).forEach(function (childEl) {
2274
2453
  if (childEl !== spinnerElement) {
2454
+ setState(childEl, 'loadingOriginalOpacity', childEl.style.opacity);
2275
2455
  childEl.style.opacity = 0.2;
2276
2456
  }
2277
2457
  });
2278
2458
  }
2279
2459
  setState(chartElement, 'inAjax', true);
2280
- Array.from(getDocument().querySelectorAll('.oc-export-btn')).forEach(function (exportButtonEl) {
2460
+ Array.from(getDocument().querySelectorAll('.oc-export-btn')).filter(function (exportButtonEl) {
2461
+ return !getState(exportButtonEl, 'orgChart') || getState(exportButtonEl, 'orgChart') === this;
2462
+ }, this).forEach(function (exportButtonEl) {
2281
2463
  exportButtonEl.disabled = true;
2282
2464
  });
2283
2465
  return true;
@@ -2299,32 +2481,132 @@
2299
2481
  spinnerEl.remove();
2300
2482
  });
2301
2483
  Array.from(nodeElement.children || []).forEach(function (childEl) {
2302
- childEl.removeAttribute('style');
2484
+ childEl.style.opacity = getState(childEl, 'loadingOriginalOpacity') || '';
2485
+ if (!childEl.style.cssText) {
2486
+ childEl.removeAttribute('style');
2487
+ }
2303
2488
  });
2489
+ setState(nodeElement, 'loadingEdge', null);
2490
+ if (this.isInAction(nodeElement)) {
2491
+ this.nodeEnterLeaveHandler({ type: 'mouseenter', node: nodeElement });
2492
+ } else {
2493
+ this.removeNodeControls(nodeElement);
2494
+ }
2304
2495
  }
2305
2496
  setState(chartElement, 'inAjax', false);
2306
- Array.from(getDocument().querySelectorAll('.oc-export-btn')).forEach(function (exportButtonEl) {
2497
+ Array.from(getDocument().querySelectorAll('.oc-export-btn')).filter(function (exportButtonEl) {
2498
+ return !getState(exportButtonEl, 'orgChart') || getState(exportButtonEl, 'orgChart') === this;
2499
+ }, this).forEach(function (exportButtonEl) {
2307
2500
  exportButtonEl.disabled = false;
2308
2501
  });
2309
2502
  },
2310
2503
  // whether the cursor is hovering over the node
2311
2504
  isInAction: function (nodeEl) {
2312
2505
  const nodeElement = getElement(nodeEl);
2313
- const edgeClassName = Array.from(nodeElement ? nodeElement.children : []).filter(function (childEl) {
2314
- return childEl.classList && childEl.classList.contains('edge');
2315
- }).map(function (edgeEl) {
2316
- return edgeEl.className;
2317
- }).join(' ');
2318
-
2319
- // TODO: 展开/折叠的按钮不止4个箭头,还有toggleBtn
2320
- return [
2321
- this.options.icons.expandToUp,
2322
- this.options.icons.collapseToDown,
2323
- this.options.icons.collapseToLeft,
2324
- this.options.icons.expandToRight
2325
- ].some(function (icon) {
2326
- return edgeClassName.indexOf(icon) > -1;
2506
+ return !!nodeElement && nodeElement.classList.contains('controls-active');
2507
+ },
2508
+ renderNodeControls: function (nodeEl) {
2509
+ const nodeElement = getElement(nodeEl);
2510
+ if (!nodeElement || !this.isInAction(nodeElement) || getState(nodeElement, 'loadingEdge')
2511
+ || getState(nodeElement, 'updatingControls')) {
2512
+ return;
2513
+ }
2514
+ const data = getState(nodeElement, 'sourceData') || {};
2515
+ const opts = this.options;
2516
+ const icons = opts.icons;
2517
+ const hasChildren = this.getNodeState(nodeElement, 'children').exist;
2518
+ const isVertical = data.vertical || (opts.verticalLevel && data.level >= opts.verticalLevel)
2519
+ || !!nodeElement.closest('.nodes.vertical');
2520
+ const controls = [];
2521
+ if (isVertical) {
2522
+ if (hasChildren) {
2523
+ controls.push(`<i class="toggleBtn ${icons.theme}"></i>`);
2524
+ }
2525
+ } else if (data.hybrid) {
2526
+ if (hasChildren) {
2527
+ controls.push(`<i class="edge verticalEdge bottomEdge ${icons.theme}"></i>`);
2528
+ }
2529
+ } else if (usesCompactLayout(data, opts, data.level)) {
2530
+ if (hasChildren) {
2531
+ const isLoose = nodeElement.classList.contains('looseMode');
2532
+ controls.push(`<i class="${icons.theme} ${icons.backToCompact} backToCompactSymbol${isLoose ? '' : ' hidden'}"></i>`);
2533
+ controls.push(`<i class="${icons.theme} ${icons.backToLoose} backToLooseSymbol${isLoose ? ' hidden' : ''}"></i>`);
2534
+ }
2535
+ } else if (!data.associatedCompact) {
2536
+ if (this.getNodeState(nodeElement, 'parent').exist) {
2537
+ controls.push(`<i class="edge verticalEdge topEdge ${icons.theme}"></i>`);
2538
+ }
2539
+ if (this.getNodeState(nodeElement, 'siblings').exist) {
2540
+ controls.push(`<i class="edge horizontalEdge rightEdge ${icons.theme}"></i>`);
2541
+ controls.push(`<i class="edge horizontalEdge leftEdge ${icons.theme}"></i>`);
2542
+ }
2543
+ if (hasChildren) {
2544
+ controls.push(`<i class="edge verticalEdge bottomEdge ${icons.theme}"></i>`);
2545
+ }
2546
+ }
2547
+ const controlClasses = ['topEdge', 'bottomEdge', 'leftEdge', 'rightEdge', 'toggleBtn', 'backToCompactSymbol', 'backToLooseSymbol'];
2548
+ const newControls = createNodesFromHtml(controls.join(''));
2549
+ const desiredClasses = newControls.map(function (control) {
2550
+ return controlClasses.find(function (className) { return control.classList.contains(className); });
2327
2551
  });
2552
+ setState(nodeElement, 'updatingControls', true);
2553
+ try {
2554
+ Array.from(nodeElement.children).filter(function (child) {
2555
+ return controlClasses.some(function (className) { return child.classList.contains(className); })
2556
+ && !desiredClasses.some(function (className) { return child.classList.contains(className); });
2557
+ }).forEach(function (control) { control.remove(); });
2558
+ newControls.forEach(function (control, index) {
2559
+ const existingControl = nodeElement.querySelector(':scope > .' + desiredClasses[index]);
2560
+ const label = desiredClasses[index] === 'topEdge' ? 'Toggle parent nodes'
2561
+ : desiredClasses[index] === 'leftEdge' || desiredClasses[index] === 'rightEdge' ? 'Toggle sibling nodes'
2562
+ : desiredClasses[index] === 'backToCompactSymbol' ? 'Use compact layout'
2563
+ : desiredClasses[index] === 'backToLooseSymbol' ? 'Expand compact layout' : 'Toggle child nodes';
2564
+ if (existingControl) {
2565
+ existingControl.className = control.className;
2566
+ } else {
2567
+ control.tabIndex = 0;
2568
+ control.setAttribute('role', 'button');
2569
+ control.setAttribute('aria-label', label);
2570
+ control.title = label;
2571
+ nodeElement.appendChild(control);
2572
+ }
2573
+ });
2574
+ } finally {
2575
+ setState(nodeElement, 'updatingControls', false);
2576
+ }
2577
+ },
2578
+ removeNodeControls: function (nodeEl) {
2579
+ const nodeElement = getElement(nodeEl);
2580
+ if (!nodeElement || getState(nodeElement, 'updatingControls')) {
2581
+ return;
2582
+ }
2583
+ setState(nodeElement, 'updatingControls', true);
2584
+ try {
2585
+ Array.from(nodeElement.children).filter(function (child) {
2586
+ return child.matches('.edge, .toggleBtn, .backToCompactSymbol, .backToLooseSymbol')
2587
+ && child !== getState(nodeElement, 'loadingEdge');
2588
+ }).forEach(function (control) { control.remove(); });
2589
+ } finally {
2590
+ setState(nodeElement, 'updatingControls', false);
2591
+ }
2592
+ },
2593
+ refreshNodeControls: function (nodeEl) {
2594
+ const nodeElement = getElement(nodeEl);
2595
+ if (nodeElement) {
2596
+ const titleElement = nodeElement.querySelector(':scope > .title');
2597
+ const symbol = titleElement ? titleElement.querySelector('.parentNodeSymbol') : null;
2598
+ const hasChildren = this.getNodeState(nodeElement, 'children').exist;
2599
+ if (titleElement && hasChildren && !symbol) {
2600
+ titleElement.insertAdjacentHTML('afterbegin', `<i class="${this.options.icons.theme} ${this.options.icons.parentNode} parentNodeSymbol"></i>`);
2601
+ } else if (!hasChildren && symbol) {
2602
+ symbol.remove();
2603
+ }
2604
+ }
2605
+ if (this.chart) {
2606
+ Array.from(this.chart.querySelectorAll('.node.controls-active')).forEach(function (activeNode) {
2607
+ this.nodeEnterLeaveHandler({ type: 'mouseenter', node: activeNode });
2608
+ }, this);
2609
+ }
2328
2610
  },
2329
2611
  //
2330
2612
  switchVerticalArrow: function (arrowEl) {
@@ -2402,61 +2684,42 @@
2402
2684
  const nativeEvent = event.originalEvent || event;
2403
2685
  const eventType = nativeEvent.type;
2404
2686
  const nodeElement = event.node || nativeEvent.currentTarget;
2405
- const childElements = Array.from(nodeElement ? nodeElement.children : []);
2406
- const isVerticalNode = !!(nodeElement && nodeElement.closest('.nodes.vertical'));
2407
- const toggleBtnElement = childElements.find(function (childEl) {
2408
- return childEl.classList && childEl.classList.contains('toggleBtn');
2409
- }) || null;
2410
- const topEdgeElement = childElements.find(function (childEl) {
2411
- return childEl.classList && childEl.classList.contains('topEdge');
2412
- }) || null;
2413
- const rightEdgeElement = childElements.find(function (childEl) {
2414
- return childEl.classList && childEl.classList.contains('rightEdge');
2415
- }) || null;
2416
- const bottomEdgeElement = childElements.find(function (childEl) {
2417
- return childEl.classList && childEl.classList.contains('bottomEdge');
2418
- }) || null;
2419
- const leftEdgeElement = childElements.find(function (childEl) {
2420
- return childEl.classList && childEl.classList.contains('leftEdge');
2421
- }) || null;
2422
- let isExpanded = false;
2423
- if (isVerticalNode) {
2424
- if (eventType === 'mouseenter') {
2425
- if (toggleBtnElement) {
2426
- isExpanded = this.getNodeState(nodeElement, 'children').visible;
2427
- toggleBtnElement.classList.toggle(this.options.icons.collapsed, !isExpanded);
2428
- toggleBtnElement.classList.toggle(this.options.icons.expanded, isExpanded);
2429
- }
2430
- } else if (toggleBtnElement) {
2431
- toggleBtnElement.classList.remove(this.options.icons.collapsed, this.options.icons.expanded);
2432
- }
2433
- } else {
2434
- if (eventType === 'mouseenter') {
2435
- if (topEdgeElement) {
2436
- isExpanded = this.getNodeState(nodeElement, 'parent').visible;
2437
- topEdgeElement.classList.toggle(this.options.icons.expandToUp, !isExpanded);
2438
- topEdgeElement.classList.toggle(this.options.icons.collapseToDown, isExpanded);
2439
- }
2440
- if (bottomEdgeElement) {
2441
- isExpanded = this.getNodeState(nodeElement, 'children').visible;
2442
- bottomEdgeElement.classList.toggle(this.options.icons.collapseToDown, !isExpanded);
2443
- bottomEdgeElement.classList.toggle(this.options.icons.expandToUp, isExpanded);
2444
- }
2445
- if (leftEdgeElement || rightEdgeElement) {
2446
- this.switchHorizontalArrow(nodeElement);
2447
- }
2448
- } else {
2449
- childElements.forEach(function (childEl) {
2450
- if (childEl.classList && childEl.classList.contains('edge')) {
2451
- childEl.classList.remove(
2452
- this.options.icons.expandToUp,
2453
- this.options.icons.collapseToDown,
2454
- this.options.icons.collapseToLeft,
2455
- this.options.icons.expandToRight
2456
- );
2457
- }
2458
- }, this);
2687
+ if (!nodeElement || getState(nodeElement, 'updatingControls')) {
2688
+ return;
2689
+ }
2690
+ if (eventType === 'mouseleave' || eventType === 'focusout') {
2691
+ const activeElement = getDocument().activeElement;
2692
+ if ((eventType === 'mouseleave' && activeElement && activeElement.closest('.node') === nodeElement)
2693
+ || (eventType === 'focusout' && ((nativeEvent.relatedTarget && nodeElement.contains(nativeEvent.relatedTarget))
2694
+ || nodeElement.matches(':hover')))) {
2695
+ return;
2459
2696
  }
2697
+ nodeElement.classList.remove('controls-active');
2698
+ this.removeNodeControls(nodeElement);
2699
+ return;
2700
+ }
2701
+ nodeElement.classList.add('controls-active');
2702
+ this.renderNodeControls(nodeElement);
2703
+ const toggle = nodeElement.querySelector(':scope > .toggleBtn');
2704
+ const topEdge = nodeElement.querySelector(':scope > .topEdge');
2705
+ const bottomEdge = nodeElement.querySelector(':scope > .bottomEdge');
2706
+ if (toggle) {
2707
+ const expanded = this.getNodeState(nodeElement, 'children').visible;
2708
+ toggle.classList.toggle(this.options.icons.collapsed, !expanded);
2709
+ toggle.classList.toggle(this.options.icons.expanded, expanded);
2710
+ }
2711
+ if (topEdge) {
2712
+ const expanded = this.getNodeState(nodeElement, 'parent').visible;
2713
+ topEdge.classList.toggle(this.options.icons.expandToUp, !expanded);
2714
+ topEdge.classList.toggle(this.options.icons.collapseToDown, expanded);
2715
+ }
2716
+ if (bottomEdge) {
2717
+ const expanded = this.getNodeState(nodeElement, 'children').visible;
2718
+ bottomEdge.classList.toggle(this.options.icons.collapseToDown, !expanded);
2719
+ bottomEdge.classList.toggle(this.options.icons.expandToUp, expanded);
2720
+ }
2721
+ if (nodeElement.querySelector(':scope > .horizontalEdge')) {
2722
+ this.switchHorizontalArrow(nodeElement);
2460
2723
  }
2461
2724
  },
2462
2725
  //
@@ -2468,11 +2731,18 @@
2468
2731
  if (!chartElement || !nodeElement || !nodeElement.classList) {
2469
2732
  return;
2470
2733
  }
2734
+ if (nativeEvent.target && nativeEvent.target.closest && nativeEvent.target.closest('.node') !== nodeElement) {
2735
+ return;
2736
+ }
2471
2737
 
2472
2738
  Array.from(chartElement.querySelectorAll('.focused')).forEach(function (focusedEl) {
2473
2739
  focusedEl.classList.remove('focused');
2474
2740
  });
2475
2741
  nodeElement.classList.add('focused');
2742
+ if (!this.isInAction(nodeElement)) {
2743
+ nodeElement.focus({ preventScroll: true });
2744
+ this.nodeEnterLeaveHandler({ type: 'mouseenter', node: nodeElement });
2745
+ }
2476
2746
  },
2477
2747
  addAncestors: function (data, parentId) {
2478
2748
  const rootHierarchyElement = this.chart ? this.chart.querySelector(':scope > .nodes > .hierarchy') : null;
@@ -2502,6 +2772,7 @@
2502
2772
  if (targetSiblingsContainerElement && targetSiblingsContainerElement !== ancestorWrapperElement.parentElement) {
2503
2773
  targetSiblingsContainerElement.appendChild(ancestorWrapperElement);
2504
2774
  }
2775
+ this.refreshNodeControls(targetParentNodeElement);
2505
2776
  },
2506
2777
  addDescendants:function (data, parentEl) {
2507
2778
  const orgChart = this;
@@ -2511,6 +2782,10 @@
2511
2782
  if (!parentElement || !parentElement.parentElement) {
2512
2783
  return;
2513
2784
  }
2785
+ if (getState(parentElement, 'sourceData')) {
2786
+ this.addChildren(parentElement, data);
2787
+ return;
2788
+ }
2514
2789
 
2515
2790
  descendantsElement = getDocument().createElement('ul');
2516
2791
  descendantsElement.className = 'nodes';
@@ -2526,8 +2801,9 @@
2526
2801
  //
2527
2802
  HideFirstParentEnd: function (event) {
2528
2803
  const nativeEvent = event.originalEvent || event;
2529
- const topEdgeElement = getElement(event.topEdge || nativeEvent.topEdge);
2530
- const nodeElement = topEdgeElement ? topEdgeElement.parentElement : null;
2804
+ const previousEdge = getElement(event.topEdge || nativeEvent.topEdge);
2805
+ const nodeElement = getElement(event.node || nativeEvent.node) || (previousEdge ? previousEdge.parentElement : null);
2806
+ const topEdgeElement = nodeElement ? nodeElement.querySelector(':scope > .topEdge') : null;
2531
2807
 
2532
2808
  if (this.isInAction(nodeElement)) {
2533
2809
  this.switchVerticalArrow(topEdgeElement);
@@ -2553,7 +2829,7 @@
2553
2829
  this.hideParent(nodeElement);
2554
2830
  if (parentEl) {
2555
2831
  parentEl.addEventListener('transitionend', function (transitionEvent) {
2556
- transitionEvent.topEdge = topEdgeElement;
2832
+ transitionEvent.node = nodeElement;
2557
2833
  orgChart.HideFirstParentEnd(transitionEvent);
2558
2834
  }, { once: true });
2559
2835
  }
@@ -2572,16 +2848,8 @@
2572
2848
  let childNodesContainerElement;
2573
2849
  let childNodeElements;
2574
2850
  if (childrenState.exist) {
2575
- childNodesContainerElement = Array.from(nodeElement.parentElement ? nodeElement.parentElement.children : []).find(function (childEl) {
2576
- return childEl !== nodeElement && childEl.classList && childEl.classList.contains('nodes');
2577
- }) || null;
2578
- childNodeElements = childNodesContainerElement
2579
- ? Array.from(childNodesContainerElement.children).filter(function (childEl) {
2580
- return childEl.classList && childEl.classList.contains('hierarchy');
2581
- }).map(function (childHierarchyEl) {
2582
- return childHierarchyEl.querySelector('.node');
2583
- }).filter(Boolean)
2584
- : [];
2851
+ childNodesContainerElement = getChildNodesContainer(nodeElement);
2852
+ childNodeElements = childNodesContainerElement ? this.getChildren(nodeElement) : [];
2585
2853
  if (childNodeElements.some(function (childNodeEl) { return childNodeEl.classList.contains('sliding'); })) { return; }
2586
2854
  // hide the descendant nodes of the specified node
2587
2855
  if (childrenState.visible) {
@@ -2658,7 +2926,13 @@
2658
2926
  }
2659
2927
 
2660
2928
  compactNodeEl.classList.remove('looseMode');
2661
- nestedLooseModeElements = Array.from(compactNodeEl.querySelectorAll('.looseMode'));
2929
+ const familyContainer = getChildNodesContainer(compactNodeEl);
2930
+ if (familyContainer && familyContainer.classList.contains('compact-family')) {
2931
+ familyContainer.classList.add('hidden');
2932
+ }
2933
+ nestedLooseModeElements = Array.from(compactNodeEl.querySelectorAll('.looseMode')).concat(
2934
+ familyContainer ? Array.from(familyContainer.querySelectorAll('.looseMode')) : []
2935
+ );
2662
2936
  nestedLooseModeElements.forEach(function (nestedLooseModeEl) {
2663
2937
  const nestedBackToCompactSymbolElement = Array.from(nestedLooseModeEl.children || []).find(function (childEl) {
2664
2938
  return childEl.classList && childEl.classList.contains('backToCompactSymbol');
@@ -2668,6 +2942,10 @@
2668
2942
  }) || null;
2669
2943
 
2670
2944
  nestedLooseModeEl.classList.remove('looseMode');
2945
+ const nestedContainer = getChildNodesContainer(nestedLooseModeEl);
2946
+ if (nestedContainer && nestedContainer.classList.contains('compact-family')) {
2947
+ nestedContainer.classList.add('hidden');
2948
+ }
2671
2949
  if (nestedBackToCompactSymbolElement) {
2672
2950
  nestedBackToCompactSymbolElement.classList.add('hidden');
2673
2951
  }
@@ -2701,6 +2979,13 @@
2701
2979
  }
2702
2980
 
2703
2981
  compactNodeEl.classList.add('looseMode');
2982
+ const familyContainer = getChildNodesContainer(compactNodeEl);
2983
+ if (familyContainer && familyContainer.classList.contains('compact-family')) {
2984
+ familyContainer.classList.remove('hidden');
2985
+ }
2986
+ this.getChildren(compactNodeEl).forEach(function (childElement) {
2987
+ childElement.classList.remove('slide-up');
2988
+ });
2704
2989
  backToLooseSymbolElement = Array.from(compactNodeEl.children || []).find(function (childEl) {
2705
2990
  return childEl.classList && childEl.classList.contains('backToLooseSymbol');
2706
2991
  }) || null;
@@ -2804,8 +3089,6 @@
2804
3089
  const origEvent = nativeEvent;
2805
3090
  const isFirefox = /firefox/.test(getWindow().navigator.userAgent.toLowerCase());
2806
3091
  let sourceRect;
2807
- let transformValue;
2808
- let transformValues;
2809
3092
  let isVerticalChartDirection;
2810
3093
  let scaleFactor;
2811
3094
  let ghostNodeEl, nodeCoverEl;
@@ -2831,10 +3114,8 @@
2831
3114
  }
2832
3115
 
2833
3116
  sourceRect = sourceNodeEl.getBoundingClientRect();
2834
- transformValue = chartElement.style.transform || getWindow().getComputedStyle(chartElement).transform || 'none';
2835
- transformValues = transformValue.split(',');
2836
3117
  isVerticalChartDirection = opts.direction === 't2b' || opts.direction === 'b2t';
2837
- scaleFactor = Math.abs(getWindow().parseFloat(isVerticalChartDirection ? transformValues[0].slice(transformValues[0].indexOf('(') + 1) : transformValues[1]));
3118
+ scaleFactor = getChartTransform(chartElement).scale;
2838
3119
  ghostNodeEl.setAttribute('width', isVerticalChartDirection ? sourceRect.width : sourceRect.height);
2839
3120
  ghostNodeEl.setAttribute('height', isVerticalChartDirection ? sourceRect.height : sourceRect.width);
2840
3121
  nodeCoverEl.setAttribute('x',5 * scaleFactor);
@@ -2878,12 +3159,10 @@
2878
3159
  return 0;
2879
3160
  }
2880
3161
 
2881
- currentElement = nodeElement.parentElement;
3162
+ currentElement = nodeElement;
2882
3163
  while (currentElement) {
2883
- if (currentElement.classList && currentElement.classList.contains('hierarchy')) {
2884
- levelCount += 1;
2885
- }
2886
- currentElement = currentElement.parentElement;
3164
+ levelCount += 1;
3165
+ currentElement = this.getParent(currentElement);
2887
3166
  }
2888
3167
 
2889
3168
  return levelCount;
@@ -2955,15 +3234,18 @@
2955
3234
  ? draggedNodeEl.closest('[draggable]')
2956
3235
  : null;
2957
3236
  draggingNode = !!(draggableHostEl && draggableHostEl.classList && draggableHostEl.classList.contains('node'));
2958
- draggedParentNodeEl = draggedNodeEl && draggedNodeEl.closest('.nodes')
2959
- ? draggedNodeEl.closest('.nodes').previousElementSibling
2960
- : null;
2961
- draggedSubtreeNodeEls = draggedNodeEl && draggedNodeEl.closest('.hierarchy')
2962
- ? Array.from(draggedNodeEl.closest('.hierarchy').querySelectorAll('.node'))
2963
- : [];
3237
+ draggedParentNodeEl = this.getParent(draggedNodeEl);
3238
+ draggedSubtreeNodeEls = [];
3239
+ const pendingNodes = draggingNode ? [draggedNodeEl.closest('.node')] : [];
3240
+ while (pendingNodes.length) {
3241
+ const currentNode = pendingNodes.pop();
3242
+ draggedSubtreeNodeEls.push(currentNode);
3243
+ Array.prototype.push.apply(pendingNodes, this.getChildren(currentNode));
3244
+ }
2964
3245
  excludedNodeEls = new Set(draggedSubtreeNodeEls);
2965
3246
  setState(chartElement, 'dragged', draggedNodeEl);
2966
3247
  Array.from(chartElement.querySelectorAll('.node')).forEach(function (node) {
3248
+ node.classList.remove('allowedDrop');
2967
3249
  if (!draggingNode || !excludedNodeEls.has(node)) {
2968
3250
  if (opts.dropCriteria) {
2969
3251
  if (opts.dropCriteria(draggedNodeEl, draggedParentNodeEl, node)) {
@@ -3052,7 +3334,7 @@
3052
3334
  return;
3053
3335
  }
3054
3336
 
3055
- draggedParentNodeEl = draggedEl.closest('.nodes') ? draggedEl.closest('.nodes').previousElementSibling : null;
3337
+ draggedParentNodeEl = this.getParent(draggedEl);
3056
3338
  const dropEvent = createEventLike('nodedrop.orgchart');
3057
3339
  if (chartElement) {
3058
3340
  const triggeredDropEvent = createTriggeredEvent(dropEvent, {
@@ -3068,23 +3350,66 @@
3068
3350
  if (dropEvent.isDefaultPrevented()) {
3069
3351
  return;
3070
3352
  }
3353
+ const sourceData = getState(draggedEl, 'sourceData');
3354
+ const parentData = draggedParentNodeEl ? getState(draggedParentNodeEl, 'sourceData') : null;
3355
+ const targetData = getState(dropZoneEl, 'sourceData');
3356
+ const datasource = getState(chartElement, 'datasource') || chartOptions.data;
3357
+ const sourceLocation = sourceData ? findDataLocation(datasource, sourceData) : null;
3358
+ if (sourceData && targetData && findDataLocation(sourceData, targetData)) {
3359
+ return;
3360
+ }
3361
+ const needsRebuild = Array.isArray(datasource) || this.options.compact || chartOptions.verticalLevel
3362
+ || chartElement.querySelector('.node.compact, .nodes.vertical');
3363
+ if (needsRebuild && sourceLocation && sourceLocation.list && targetData) {
3364
+ sourceLocation.list.splice(sourceLocation.index, 1);
3365
+ if (!sourceLocation.list.length && sourceLocation.outerList) {
3366
+ sourceLocation.outerList.splice(sourceLocation.outerIndex, 1);
3367
+ }
3368
+ if (sourceLocation.parent && sourceLocation.parent.children && !sourceLocation.parent.children.length) {
3369
+ delete sourceLocation.parent.children;
3370
+ }
3371
+ const isGrouped = Array.isArray(datasource) || (targetData.children && Array.isArray(targetData.children[0]));
3372
+ if (!targetData.children) {
3373
+ targetData.children = [];
3374
+ }
3375
+ targetData.children.push(isGrouped ? [sourceData] : sourceData);
3376
+ rebuildFromData(this, targetData);
3377
+ return;
3378
+ }
3071
3379
  // special process for hybrid chart
3072
- const datasource = chartOptions.data;
3073
3380
  const dataDigger = new JSONDigger(datasource, chartOptions.nodeId, 'children');
3074
3381
  const hybridNodeData = dataDigger.findOneNode({ 'hybrid': true });
3075
3382
  if (chartOptions.verticalLevel > 1 || hybridNodeData) {
3076
- const draggedNodeData = dataDigger.findNodeById(getState(draggedEl, 'nodeData').id);
3383
+ const draggedNodeId = getState(draggedEl, 'nodeData')[chartOptions.nodeId];
3384
+ const dropNodeId = getState(dropZoneEl, 'nodeData')[chartOptions.nodeId];
3385
+ const draggedNodeData = dataDigger.findNodeById(draggedNodeId);
3386
+ const dropNodeData = dataDigger.findNodeById(dropNodeId);
3387
+ if (!draggedNodeData || !dropNodeData) {
3388
+ return;
3389
+ }
3077
3390
  const draggedNodeCopy = Object.assign({}, draggedNodeData);
3078
- dataDigger.removeNode(draggedNodeData.id);
3079
- const dropNodeData = dataDigger.findNodeById(getState(dropZoneEl, 'nodeData').id);
3391
+ dataDigger.removeNode(draggedNodeId);
3080
3392
  if (dropNodeData.children) {
3081
3393
  dropNodeData.children.push(draggedNodeCopy);
3082
3394
  } else {
3083
3395
  dropNodeData.children = [draggedNodeCopy];
3084
3396
  }
3085
- orgChart.attachRel(datasource, '00');
3086
3397
  orgChart.init({ 'data': datasource });
3087
3398
  } else {
3399
+ if (sourceData && parentData && targetData && parentData.children
3400
+ && parentData.children.indexOf(sourceData) !== -1) {
3401
+ parentData.children.splice(parentData.children.indexOf(sourceData), 1);
3402
+ if (!parentData.children.length) {
3403
+ delete parentData.children;
3404
+ }
3405
+ if (!targetData.children) {
3406
+ targetData.children = [];
3407
+ }
3408
+ targetData.children.push(sourceData);
3409
+ sourceData.parentId = targetData[chartOptions.nodeId];
3410
+ getState(draggedEl, 'nodeData').parentId = sourceData.parentId;
3411
+ draggedEl.setAttribute('data-parent', sourceData.parentId);
3412
+ }
3088
3413
  // The folowing code snippets are used to process horizontal chart
3089
3414
  draggedHierarchyEl = draggedEl.closest('.hierarchy');
3090
3415
  dropChildrenContainerEl = Array.from(dropZoneEl.parentElement ? dropZoneEl.parentElement.children : []).find(function (childEl) {
@@ -3092,33 +3417,11 @@
3092
3417
  }) || null;
3093
3418
  // firstly, deal with the hierarchy of drop zone
3094
3419
  if (!dropChildrenContainerEl) { // if the drop zone is a leaf node
3095
- dropZoneEl.insertAdjacentHTML('beforeend', `<i class="edge verticalEdge bottomEdge ${this.options.icons.theme}"></i>`);
3096
3420
  dropZoneEl.insertAdjacentHTML('afterend', '<ul class="nodes"></ul>');
3097
3421
  dropChildrenContainerEl = dropZoneEl.nextElementSibling;
3098
- Array.from(draggedEl.querySelectorAll('.horizontalEdge')).forEach(function (edgeEl) {
3099
- edgeEl.remove();
3100
- });
3101
3422
  dropChildrenContainerEl.appendChild(draggedHierarchyEl);
3102
- const titleEl = Array.from(dropZoneEl.children || []).find(function (childEl) {
3103
- return childEl.classList && childEl.classList.contains('title');
3104
- }) || null;
3105
- if (titleEl) {
3106
- titleEl.insertAdjacentHTML('afterbegin', `<i class="${this.options.icons.theme} ${chartOptions.icons.parentNode} parentNodeSymbol"></i>`);
3107
- }
3108
3423
  } else {
3109
- const horizontalEdges = `<i class="edge horizontalEdge rightEdge ${this.options.icons.theme}"></i><i class="edge horizontalEdge leftEdge ${this.options.icons.theme}"></i>`;
3110
- if (!draggedEl.querySelector('.horizontalEdge')) {
3111
- draggedEl.insertAdjacentHTML('beforeend', horizontalEdges);
3112
- }
3113
3424
  dropChildrenContainerEl.appendChild(draggedHierarchyEl);
3114
- const dropSiblingNodeEls = Array.from(dropChildrenContainerEl.children || []).filter(function (childEl) {
3115
- return childEl !== draggedHierarchyEl && childEl.classList && childEl.classList.contains('hierarchy');
3116
- }).map(function (siblingHierarchyEl) {
3117
- return siblingHierarchyEl.querySelector('.node');
3118
- }).filter(Boolean);
3119
- if (dropSiblingNodeEls.length === 1) {
3120
- dropSiblingNodeEls[0].insertAdjacentHTML('beforeend', horizontalEdges);
3121
- }
3122
3425
  }
3123
3426
  // secondly, deal with the hierarchy of dragged node
3124
3427
  draggedParentChildrenContainerEl = Array.from(draggedParentNodeEl && draggedParentNodeEl.parentElement ? draggedParentNodeEl.parentElement.children : []).find(function (childEl) {
@@ -3129,21 +3432,20 @@
3129
3432
  return childEl.classList && childEl.classList.contains('hierarchy');
3130
3433
  })
3131
3434
  : [];
3132
- if (remainingHierarchyEls.length === 1) { // if there is only one sibling node left
3133
- const remainingNodeEl = remainingHierarchyEls[0].querySelector('.node');
3134
- if (remainingNodeEl) {
3135
- Array.from(remainingNodeEl.querySelectorAll('.horizontalEdge')).forEach(function (edgeEl) {
3136
- edgeEl.remove();
3137
- });
3138
- }
3139
- } else if (remainingHierarchyEls.length === 0) {
3140
- Array.from(draggedParentNodeEl.querySelectorAll('.bottomEdge, .parentNodeSymbol')).forEach(function (edgeEl) {
3141
- edgeEl.remove();
3142
- });
3435
+ if (remainingHierarchyEls.length === 0) {
3143
3436
  if (draggedParentChildrenContainerEl) {
3144
3437
  draggedParentChildrenContainerEl.remove();
3145
3438
  }
3146
3439
  }
3440
+ dropChildrenContainerEl.classList.remove('hidden');
3441
+ dropZoneEl.closest('.hierarchy').classList.remove('isChildrenCollapsed');
3442
+ this.getChildren(dropZoneEl).forEach(function (childNode) {
3443
+ childNode.classList.remove('slide-up', 'sliding');
3444
+ childNode.closest('.hierarchy').classList.remove('isCollapsedDescendant');
3445
+ });
3446
+ this.refreshNodeControls(draggedParentNodeEl);
3447
+ this.refreshNodeControls(dropZoneEl);
3448
+ this.updateMinimap(true);
3147
3449
  }
3148
3450
  },
3149
3451
  //
@@ -3334,19 +3636,33 @@
3334
3636
  return;
3335
3637
  }
3336
3638
 
3337
- nodeElement.addEventListener('dragstart', this.dragstartHandler.bind(this));
3338
- nodeElement.addEventListener('dragover', this.dragoverHandler.bind(this));
3339
- nodeElement.addEventListener('dragend', this.dragendHandler.bind(this));
3340
- nodeElement.addEventListener('drop', this.dropHandler.bind(this));
3341
- nodeElement.addEventListener('touchstart', this.touchstartHandler.bind(this));
3342
- nodeElement.addEventListener('touchmove', this.touchmoveHandler.bind(this));
3343
- nodeElement.addEventListener('touchend', this.touchendHandler.bind(this));
3639
+ const orgChart = this;
3640
+ const handlers = {
3641
+ dragstart: this.dragstartHandler,
3642
+ dragover: this.dragoverHandler,
3643
+ dragend: this.dragendHandler,
3644
+ drop: this.dropHandler,
3645
+ touchstart: this.touchstartHandler,
3646
+ touchmove: this.touchmoveHandler,
3647
+ touchend: this.touchendHandler
3648
+ };
3649
+ Object.keys(handlers).forEach(function (eventType) {
3650
+ nodeElement.addEventListener(eventType, function (event) {
3651
+ if (!event.target.closest || event.target.closest('.node') === nodeElement) {
3652
+ handlers[eventType].call(orgChart, event);
3653
+ }
3654
+ }, { passive: false });
3655
+ });
3344
3656
  },
3345
3657
  // create node
3346
3658
  createNode: function (data) {
3347
3659
  const opts = this.options;
3348
3660
  const level = data.level;
3349
- const flags = data.relationship || '';
3661
+ if ((opts.compact === true && data.children && data.children.length)
3662
+ || (typeof opts.compact === 'function' && opts.compact(data))) {
3663
+ data.compact = true;
3664
+ }
3665
+ const isCompactLayout = usesCompactLayout(data, opts, level);
3350
3666
  const nodeClassName = [
3351
3667
  'node',
3352
3668
  data.className || '',
@@ -3356,7 +3672,7 @@
3356
3672
  const titleMarkupParts = [];
3357
3673
  const contentMarkupParts = [];
3358
3674
  const nodeMarkupParts = [];
3359
- const shouldAddParentSymbol = Number(flags.substr(2, 1));
3675
+ const shouldAddParentSymbol = !!(data.children && data.children.length);
3360
3676
 
3361
3677
  if (data.children && data[opts.nodeId]) {
3362
3678
  forEachValue(data.children, function (index, child) {
@@ -3379,32 +3695,6 @@
3379
3695
  nodeMarkupParts.push(contentMarkupParts.join(''));
3380
3696
  }
3381
3697
 
3382
- if ((opts.verticalLevel && level >= opts.verticalLevel) || data.vertical) {
3383
- if (shouldAddParentSymbol) {
3384
- nodeMarkupParts.push(`<i class="toggleBtn ${opts.icons.theme}"></i>`);
3385
- }
3386
- } else if (data.hybrid) {
3387
- if (shouldAddParentSymbol) {
3388
- nodeMarkupParts.push(`<i class="edge verticalEdge bottomEdge ${opts.icons.theme}"></i>`);
3389
- }
3390
- } else if (data.compact) {
3391
- if (shouldAddParentSymbol) {
3392
- nodeMarkupParts.push(`<i class="${opts.icons.theme} ${opts.icons.backToCompact} backToCompactSymbol hidden"></i>`);
3393
- nodeMarkupParts.push(`<i class="${opts.icons.theme} ${opts.icons.backToLoose} backToLooseSymbol"></i>`);
3394
- }
3395
- } else if (!data.associatedCompact) {
3396
- if (Number(flags.substr(0, 1))) {
3397
- nodeMarkupParts.push(`<i class="edge verticalEdge topEdge ${opts.icons.theme}"></i>`);
3398
- }
3399
- if (Number(flags.substr(1, 1))) {
3400
- nodeMarkupParts.push(`<i class="edge horizontalEdge rightEdge ${opts.icons.theme}"></i>`);
3401
- nodeMarkupParts.push(`<i class="edge horizontalEdge leftEdge ${opts.icons.theme}"></i>`);
3402
- }
3403
- if (shouldAddParentSymbol) {
3404
- nodeMarkupParts.push(`<i class="edge verticalEdge bottomEdge ${opts.icons.theme}"></i>`);
3405
- }
3406
- }
3407
-
3408
3698
  // construct the content of node
3409
3699
  const nodeElement = getDocument().createElement('div');
3410
3700
  const routeNodeClick = function (selector, handler) {
@@ -3431,19 +3721,38 @@
3431
3721
  nodeElement.setAttribute('data-parent', data.parentId);
3432
3722
  }
3433
3723
  nodeElement.className = nodeClassName;
3724
+ nodeElement.tabIndex = 0;
3434
3725
  nodeElement.insertAdjacentHTML('beforeend', nodeMarkupParts.join(''));
3435
3726
  //
3436
3727
  const storedNodeData = mergeObjects({}, data);
3437
3728
  delete storedNodeData.children;
3438
3729
  setState(nodeElement, 'nodeData', storedNodeData);
3730
+ setState(nodeElement, 'sourceData', data);
3439
3731
  nodeElement.__ocNodeData = storedNodeData;
3440
- if (data.compact && nodeElement && nodeElement.style) {
3441
- nodeElement.style.gridTemplateColumns = `repeat(${Math.floor(Math.sqrt(data.children.length + 1))}, auto)`;
3732
+ if (isCompactLayout && nodeElement && nodeElement.style) {
3733
+ nodeElement.style.gridTemplateColumns = `repeat(${Math.floor(Math.sqrt((data.children || []).length + 1))}, auto)`;
3442
3734
  }
3443
3735
 
3444
3736
  nodeElement.addEventListener('mouseenter', this.nodeEnterLeaveHandler.bind(this));
3445
3737
  nodeElement.addEventListener('mouseleave', this.nodeEnterLeaveHandler.bind(this));
3738
+ nodeElement.addEventListener('focusin', function (event) {
3739
+ if (event.target.closest('.node') === nodeElement) {
3740
+ this.nodeEnterLeaveHandler(event);
3741
+ }
3742
+ }.bind(this));
3743
+ nodeElement.addEventListener('focusout', function (event) {
3744
+ if (event.target.closest('.node') === nodeElement) {
3745
+ this.nodeEnterLeaveHandler(event);
3746
+ }
3747
+ }.bind(this));
3446
3748
  nodeElement.addEventListener('click', this.nodeClickHandler.bind(this));
3749
+ nodeElement.addEventListener('keydown', function (event) {
3750
+ const control = event.target.closest('.edge, .toggleBtn, .backToCompactSymbol, .backToLooseSymbol');
3751
+ if (control && control.parentElement === nodeElement && (event.key === 'Enter' || event.key === ' ')) {
3752
+ event.preventDefault();
3753
+ control.click();
3754
+ }
3755
+ });
3447
3756
  routeNodeClick('.topEdge', this.topEdgeClickHandler);
3448
3757
  routeNodeClick('.bottomEdge', this.bottomEdgeClickHandler);
3449
3758
  routeNodeClick('.leftEdge, .rightEdge', this.hEdgeClickHandler);
@@ -3471,16 +3780,27 @@
3471
3780
  const hierarchyElement = getElement(hierarchyEl);
3472
3781
  const nodeElement = getElement(nodeEl);
3473
3782
  const isHidden = level + 1 > opts.visibleLevel || (data.collapsed !== undefined && data.collapsed);
3783
+ const isCompactLayout = usesCompactLayout(data, opts, level);
3474
3784
  let nodesLayerElement;
3475
3785
 
3476
3786
  if (!hierarchyElement) {
3477
3787
  return;
3478
3788
  }
3479
3789
 
3480
- if ((opts.verticalLevel && (level + 1) >= opts.verticalLevel) || data.hybrid) {
3790
+ data.children.flat().forEach(function (child) {
3791
+ if (data.hybrid || data.vertical) {
3792
+ child.vertical = true;
3793
+ } else if (data.compact && child.children) {
3794
+ child.compact = true;
3795
+ } else if (data.compact) {
3796
+ child.associatedCompact = true;
3797
+ }
3798
+ });
3799
+
3800
+ if ((opts.verticalLevel && (level + 1) >= opts.verticalLevel) || data.hybrid || data.vertical) {
3481
3801
  nodesLayerElement = getDocument().createElement('ul');
3482
3802
  nodesLayerElement.className = 'nodes';
3483
- if (isHidden && (opts.verticalLevel && (level + 1) >= opts.verticalLevel)) {
3803
+ if (isHidden) {
3484
3804
  nodesLayerElement.classList.add('hidden');
3485
3805
  }
3486
3806
  if (((opts.verticalLevel && level + 1 === opts.verticalLevel) || data.hybrid)
@@ -3488,10 +3808,16 @@
3488
3808
  nodesLayerElement.classList.add('vertical');
3489
3809
  }
3490
3810
  hierarchyElement.appendChild(nodesLayerElement);
3491
- } else if (data.compact) {
3811
+ } else if (isCompactLayout) {
3492
3812
  if (nodeElement && nodeElement.classList) {
3493
3813
  nodeElement.classList.add('compact');
3494
3814
  }
3815
+ if (Array.isArray(data.children[0])) {
3816
+ nodesLayerElement = getDocument().createElement('ul');
3817
+ nodesLayerElement.className = 'nodes compact-family hidden';
3818
+ nodesLayerElement.style.setProperty('--compact-columns', Math.ceil(Math.sqrt(data.children.length)));
3819
+ hierarchyElement.appendChild(nodesLayerElement);
3820
+ }
3495
3821
  } else {
3496
3822
  nodesLayerElement = getDocument().createElement('ul');
3497
3823
  nodesLayerElement.className = isHidden ? 'nodes hidden' : 'nodes';
@@ -3500,16 +3826,20 @@
3500
3826
  }
3501
3827
  hierarchyElement.appendChild(nodesLayerElement);
3502
3828
  }
3829
+ if (nodesLayerElement) {
3830
+ const parentNode = nodeElement || hierarchyElement.querySelector(':scope > .node');
3831
+ setState(nodesLayerElement, 'parentData', parentNode ? getState(parentNode, 'sourceData') || data : data);
3832
+ }
3503
3833
  // recurse through children nodes
3504
3834
  if (Array.isArray(data.children[0])) {
3505
3835
  forEachValue(data.children, function() {
3506
- this.level = level + 1;
3836
+ forEachValue(this, function () { this.level = level + 1; });
3507
3837
  });
3508
3838
  this.buildHierarchy(nodesLayerElement, data.children); // 构造子一层的夫妻组合(每个组合可能有多妻多夫情况)
3509
3839
  } else {
3510
3840
  forEachValue(data.children, function () {
3511
3841
  this.level = level + 1;
3512
- if (data.compact) {
3842
+ if (isCompactLayout) {
3513
3843
  orgChart.buildHierarchy(nodeElement, this);
3514
3844
  } else {
3515
3845
  const nodeCellElement = getDocument().createElement('li');
@@ -3533,8 +3863,8 @@
3533
3863
  return;
3534
3864
  }
3535
3865
 
3536
- if (data.level || data[0]?.level) {
3537
- level = data.level;
3866
+ if (data.level || data[0]?.level || data[0]?.[0]?.level) {
3867
+ level = data.level || data[0]?.level || data[0]?.[0]?.level;
3538
3868
  } else {
3539
3869
  let currentAncestorElement = hierarchyElement.parentElement;
3540
3870
 
@@ -3545,6 +3875,7 @@
3545
3875
  currentAncestorElement = currentAncestorElement.parentElement;
3546
3876
  }
3547
3877
  if (Array.isArray(data) && Array.isArray(data[0])) {
3878
+ level = Math.max(level, 1);
3548
3879
  forEachValue(data, function () {
3549
3880
  forEachValue(this, function () {
3550
3881
  this.level = level;
@@ -3566,7 +3897,7 @@
3566
3897
  // if there are only two persons in a marriage, two single nodes will appear in a hierarchy
3567
3898
  if (familyNodes.length === 2 && i === 1) {
3568
3899
  spouseNodeElement = Array.from(hierarchyElement.querySelectorAll('.node')).find(function (candidateNodeEl) {
3569
- return candidateNodeEl.id === familyNodes[0].id;
3900
+ return getState(candidateNodeEl, 'sourceData') === familyNodes[0];
3570
3901
  }) || null;
3571
3902
  if (spouseNodeElement && spouseNodeElement.parentElement) {
3572
3903
  spouseNodeElement.after(nodeElement);
@@ -3600,7 +3931,7 @@
3600
3931
  });
3601
3932
  });
3602
3933
  } else {
3603
- if (Object.keys(data).length > 2) { // TODO: 应该用更好的方式来判断是否是供父一级节点创建的信息
3934
+ if (Object.keys(data).some(function (key) { return key !== 'children' && key !== 'level'; })) {
3604
3935
  nodeElement = this.createNode(data);
3605
3936
  hierarchyElement.appendChild(nodeElement);
3606
3937
  }
@@ -3639,35 +3970,26 @@
3639
3970
  // exposed method
3640
3971
  addChildren: function (nodeEl, data) {
3641
3972
  const nodeElement = getElement(nodeEl);
3642
- let titleElement;
3643
- let bottomEdgeElement;
3644
3973
 
3645
3974
  if (!nodeElement) {
3646
3975
  return;
3647
3976
  }
3648
-
3649
- this.buildChildNode(nodeElement.closest('.hierarchy'), data);
3650
- if (!nodeElement.querySelector('.parentNodeSymbol')) {
3651
- titleElement = Array.from(nodeElement.children || []).find(function (childEl) {
3652
- return childEl.classList && childEl.classList.contains('title');
3653
- }) || null;
3654
- if (titleElement) {
3655
- titleElement.insertAdjacentHTML('afterbegin', `<i class="${this.options.icons.theme} ${this.options.icons.parentNode} parentNodeSymbol"></i>`);
3977
+ const nodeData = getState(nodeElement, 'sourceData');
3978
+ if (nodeData) {
3979
+ const datasource = getState(this.chart, 'datasource');
3980
+ const isGrouped = Array.isArray(datasource) || (nodeData.children && Array.isArray(nodeData.children[0]));
3981
+ if (!nodeData.children) {
3982
+ nodeData.children = [];
3656
3983
  }
3657
- }
3658
- if (nodeElement.closest('.nodes.vertical')) {
3659
- if (!nodeElement.querySelector(':scope > .toggleBtn')) {
3660
- nodeElement.insertAdjacentHTML('beforeend', `<i class="toggleBtn ${this.options.icons.theme}"></i>`);
3984
+ Array.prototype.push.apply(nodeData.children, isGrouped ? data.map(function (item) { return Array.isArray(item) ? item : [item]; }) : data);
3985
+ if (isGrouped || this.chart.querySelector('.node.compact') || this.options.compact) {
3986
+ rebuildFromData(this, nodeData);
3987
+ return;
3661
3988
  }
3662
- } else if (!nodeElement.querySelector(':scope > .bottomEdge')) {
3663
- nodeElement.insertAdjacentHTML('beforeend', `<i class="edge verticalEdge bottomEdge ${this.options.icons.theme}"></i>`);
3664
- }
3665
- if (this.isInAction(nodeElement)) {
3666
- bottomEdgeElement = Array.from(nodeElement.children || []).find(function (childEl) {
3667
- return childEl.classList && childEl.classList.contains('bottomEdge');
3668
- }) || null;
3669
- this.switchVerticalArrow(bottomEdgeElement);
3670
3989
  }
3990
+
3991
+ this.buildChildNode(nodeElement.closest('.hierarchy'), data);
3992
+ this.refreshNodeControls(nodeElement);
3671
3993
  },
3672
3994
  // build the parent node of specific node
3673
3995
  buildParentNode: function (currentRootEl, data) {
@@ -3683,7 +4005,6 @@
3683
4005
  return;
3684
4006
  }
3685
4007
 
3686
- data.relationship = data.relationship || '001';
3687
4008
  currentRootListElement = currentRootElement.closest('ul');
3688
4009
  currentRootHierarchyElement = currentRootElement.closest('.hierarchy');
3689
4010
  parentHierarchyElement = currentRootListElement ? currentRootListElement.closest('.hierarchy') : null;
@@ -3714,28 +4035,51 @@
3714
4035
  // exposed method
3715
4036
  addParent: function (currentRootEl, data) {
3716
4037
  const currentRootElement = getElement(currentRootEl);
3717
- let titleElement;
3718
- let topEdgeElement;
3719
4038
 
3720
4039
  if (!currentRootElement) {
3721
4040
  return;
3722
4041
  }
4042
+ const currentData = getState(currentRootElement, 'sourceData');
4043
+ const datasource = getState(this.chart, 'datasource');
4044
+ if (currentData && (Array.isArray(datasource) || this.chart.querySelector('.node.compact') || this.options.compact)) {
4045
+ const parentElement = this.getParent(currentRootElement);
4046
+ const parentData = parentElement ? getState(parentElement, 'sourceData') : null;
4047
+ const newParentData = mergeObjects({}, data, { children: Array.isArray(datasource) ? [[currentData]] : [currentData] });
4048
+ const location = findDataLocation(datasource, currentData);
4049
+ if (Array.isArray(datasource) && location && location.list) {
4050
+ location.list.splice(location.index, 1, newParentData);
4051
+ rebuildFromData(this, newParentData);
4052
+ return;
4053
+ }
4054
+ if (parentData && parentData.children) {
4055
+ const index = parentData.children.indexOf(currentData);
4056
+ if (index === -1) {
4057
+ return;
4058
+ }
4059
+ parentData.children.splice(index, 1, newParentData);
4060
+ rebuildFromData(this, newParentData);
4061
+ } else {
4062
+ rebuildFromData(this, newParentData, newParentData);
4063
+ }
4064
+ return;
4065
+ }
3723
4066
 
4067
+ const currentLocation = currentData ? findDataLocation(datasource, currentData) : null;
3724
4068
  this.buildParentNode(currentRootElement, data);
3725
- if (!currentRootElement.querySelector(':scope > .topEdge')) {
3726
- titleElement = Array.from(currentRootElement.children || []).find(function (childEl) {
3727
- return childEl.classList && childEl.classList.contains('title');
3728
- }) || null;
3729
- if (titleElement) {
3730
- titleElement.insertAdjacentHTML('afterend', `<i class="edge verticalEdge topEdge ${this.options.icons.theme}"></i>`);
4069
+ if (currentData) {
4070
+ data.children = [currentData];
4071
+ if (currentLocation && currentLocation.list) {
4072
+ currentLocation.list.splice(currentLocation.index, 1, data);
4073
+ } else {
4074
+ setState(this.chart, 'datasource', data);
4075
+ this.options.data = data;
4076
+ this.opts.data = data;
3731
4077
  }
4078
+ currentData.parentId = data[this.options.nodeId];
4079
+ getState(currentRootElement, 'nodeData').parentId = currentData.parentId;
4080
+ currentRootElement.setAttribute('data-parent', currentData.parentId);
3732
4081
  }
3733
- if (this.isInAction(currentRootElement)) {
3734
- topEdgeElement = Array.from(currentRootElement.children || []).find(function (childEl) {
3735
- return childEl.classList && childEl.classList.contains('topEdge');
3736
- }) || null;
3737
- this.switchVerticalArrow(topEdgeElement);
3738
- }
4082
+ this.refreshNodeControls(this.getParent(currentRootElement));
3739
4083
  },
3740
4084
  // build the sibling nodes of specific node
3741
4085
  buildSiblingNode: function (nodeChartEl, data) {
@@ -3836,49 +4180,88 @@
3836
4180
  addSiblings: function (nodeEl, data) {
3837
4181
  const nodeElement = getElement(nodeEl);
3838
4182
  let nodesContainerElement;
3839
- let topEdgeElement;
3840
4183
 
3841
4184
  if (!nodeElement) {
3842
4185
  return;
3843
4186
  }
4187
+ const nodeData = getState(nodeElement, 'sourceData');
4188
+ const datasource = getState(this.chart, 'datasource');
4189
+ if (nodeData && (Array.isArray(datasource) || this.chart.querySelector('.node.compact') || this.options.compact)) {
4190
+ const parentElement = this.getParent(nodeElement);
4191
+ const parentData = parentElement ? getState(parentElement, 'sourceData') : null;
4192
+ const newSiblings = Array.isArray(data) ? data : data.children;
4193
+ if (parentData && parentData.children) {
4194
+ const siblings = Array.isArray(datasource) ? (newSiblings || []).map(function (item) { return Array.isArray(item) ? item : [item]; }) : newSiblings || [];
4195
+ Array.prototype.push.apply(parentData.children, siblings);
4196
+ rebuildFromData(this, parentData);
4197
+ } else if (Array.isArray(datasource) && Array.isArray(data)) {
4198
+ Array.prototype.push.apply(datasource, data.map(function (item) { return Array.isArray(item) ? item : [item]; }));
4199
+ rebuildFromData(this);
4200
+ } else if (!Array.isArray(data)) {
4201
+ const children = (newSiblings || []).slice();
4202
+ const insertPosition = Math.floor((children.length + 1) / 2 - 1) + 1;
4203
+ children.splice(Math.max(insertPosition, 0), 0, nodeData);
4204
+ const newParentData = mergeObjects({}, data, { children: children });
4205
+ rebuildFromData(this, newParentData, newParentData);
4206
+ }
4207
+ return;
4208
+ }
3844
4209
 
4210
+ const oldParent = this.getParent(nodeElement);
4211
+ const oldParentData = oldParent ? getState(oldParent, 'sourceData') : null;
3845
4212
  this.buildSiblingNode(nodeElement.closest('.hierarchy'), data);
4213
+ if (nodeData && oldParentData && oldParentData.children && Array.isArray(data)) {
4214
+ Array.prototype.push.apply(oldParentData.children, data);
4215
+ } else if (nodeData && !oldParentData && !Array.isArray(data) && data.children) {
4216
+ const insertPosition = Math.floor((data.children.length + 1) / 2 - 1) + 1;
4217
+ data.children.splice(Math.max(insertPosition, 0), 0, nodeData);
4218
+ setState(this.chart, 'datasource', data);
4219
+ this.options.data = data;
4220
+ this.opts.data = data;
4221
+ }
3846
4222
  nodesContainerElement = nodeElement.closest('.nodes');
3847
4223
  if (nodesContainerElement) {
3848
4224
  setState(nodesContainerElement, 'siblingsLoaded', true);
3849
4225
  }
3850
- if (!Array.from(nodeElement.children || []).some(function (childEl) {
3851
- return childEl.classList && childEl.classList.contains('leftEdge');
3852
- })) {
3853
- topEdgeElement = Array.from(nodeElement.children || []).find(function (childEl) {
3854
- return childEl.classList && childEl.classList.contains('topEdge');
3855
- }) || null;
3856
- if (topEdgeElement) {
3857
- topEdgeElement.insertAdjacentHTML('afterend', `<i class="edge horizontalEdge rightEdge ${this.options.icons.theme}"></i><i class="edge horizontalEdge leftEdge ${this.options.icons.theme}"></i>`);
3858
- }
3859
- }
3860
- if (this.isInAction(nodeElement)) {
3861
- this.switchHorizontalArrow(nodeElement);
3862
- topEdgeElement = Array.from(nodeElement.children || []).find(function (childEl) {
3863
- return childEl.classList && childEl.classList.contains('topEdge');
3864
- }) || null;
3865
- if (topEdgeElement && topEdgeElement.classList) {
3866
- topEdgeElement.classList.remove(this.options.icons.expandToUp);
3867
- topEdgeElement.classList.add(this.options.icons.collapseToDown);
3868
- }
3869
- }
4226
+ this.refreshNodeControls(this.getParent(nodeElement));
3870
4227
  },
3871
4228
  // remove node and its descendent nodes
3872
4229
  removeNodes: function (nodeEl) {
3873
4230
  const nodeElement = getElement(nodeEl);
3874
4231
  let hierarchyElement;
3875
4232
  let wrapperElement;
3876
- let remainingNodeElement;
3877
- let parentNodeElement;
3878
4233
 
3879
4234
  if (!nodeElement) {
3880
4235
  return;
3881
4236
  }
4237
+ const parentElement = this.getParent(nodeElement);
4238
+ const parentData = parentElement ? getState(parentElement, 'sourceData') : null;
4239
+ const nodeData = getState(nodeElement, 'sourceData');
4240
+ const datasource = getState(this.chart, 'datasource');
4241
+ if (Array.isArray(datasource) && nodeData) {
4242
+ const location = findDataLocation(datasource, nodeData);
4243
+ if (location && location.list) {
4244
+ location.list.splice(location.index, 1);
4245
+ if (!location.list.length && location.outerList) {
4246
+ location.outerList.splice(location.outerIndex, 1);
4247
+ }
4248
+ if (location.parent && location.parent.children && !location.parent.children.length) {
4249
+ delete location.parent.children;
4250
+ }
4251
+ rebuildFromData(this);
4252
+ return;
4253
+ }
4254
+ }
4255
+ if (parentData && parentData.children && parentData.children.indexOf(nodeData) !== -1) {
4256
+ parentData.children.splice(parentData.children.indexOf(nodeData), 1);
4257
+ if (!parentData.children.length) {
4258
+ delete parentData.children;
4259
+ }
4260
+ if (this.chart.querySelector('.node.compact') || this.options.compact) {
4261
+ rebuildFromData(this);
4262
+ return;
4263
+ }
4264
+ }
3882
4265
 
3883
4266
  hierarchyElement = nodeElement.closest('.hierarchy');
3884
4267
  wrapperElement = hierarchyElement ? hierarchyElement.parentElement : null;
@@ -3890,23 +4273,10 @@
3890
4273
  if (wrapperElement.parentElement && wrapperElement.parentElement.classList && wrapperElement.parentElement.classList.contains('hierarchy')) {
3891
4274
  if (this.getNodeState(nodeElement, 'siblings').exist) {
3892
4275
  hierarchyElement.remove();
3893
- if (wrapperElement.children.length === 1) {
3894
- remainingNodeElement = wrapperElement.querySelector('.node');
3895
- if (remainingNodeElement) {
3896
- Array.from(remainingNodeElement.querySelectorAll('.horizontalEdge')).forEach(function (edgeEl) {
3897
- edgeEl.remove();
3898
- });
3899
- }
3900
- }
3901
4276
  } else {
3902
- parentNodeElement = wrapperElement.parentElement.querySelector(':scope > .node');
3903
- if (parentNodeElement) {
3904
- Array.from(parentNodeElement.querySelectorAll('.bottomEdge, .parentNodeSymbol')).forEach(function (edgeEl) {
3905
- edgeEl.remove();
3906
- });
3907
- }
3908
4277
  wrapperElement.remove();
3909
4278
  }
4279
+ this.refreshNodeControls(parentElement);
3910
4280
  } else { // if $node is root node
3911
4281
  const chartElement = wrapperElement.closest('.orgchart');
3912
4282
  if (chartElement) {
@@ -3998,7 +4368,6 @@
3998
4368
  const isEdge = navigatorObject.appName === 'Microsoft Internet Explorer' || (navigatorObject.appName === 'Netscape' && navigatorObject.appVersion.indexOf('Edge') > -1);
3999
4369
  const chartContainerElement = this.chartContainer;
4000
4370
  let downloadButtonClassName;
4001
- let downloadSelector;
4002
4371
  let downloadAnchorElement;
4003
4372
 
4004
4373
  if (!chartContainerElement) {
@@ -4009,8 +4378,12 @@
4009
4378
  getWindow().navigator.msSaveBlob(canvas.msToBlob(), exportFilename + '.png');
4010
4379
  } else {
4011
4380
  downloadButtonClassName = 'download-btn' + (this.options.chartClass !== '' ? ' ' + this.options.chartClass : '');
4012
- downloadSelector = '.download-btn' + (this.options.chartClass !== '' ? '.' + this.options.chartClass : '');
4013
- downloadAnchorElement = chartContainerElement.querySelector(downloadSelector);
4381
+ const chartClassNames = this.options.chartClass.split(/\s+/).filter(Boolean);
4382
+ downloadAnchorElement = Array.from(chartContainerElement.querySelectorAll('a.download-btn')).find(function (anchorElement) {
4383
+ return chartClassNames.every(function (className) {
4384
+ return anchorElement.classList.contains(className);
4385
+ });
4386
+ });
4014
4387
 
4015
4388
  if (!downloadAnchorElement) {
4016
4389
  downloadAnchorElement = getDocument().createElement('a');