orgchart 5.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 (86) hide show
  1. package/.github/workflows/publish.yml +35 -0
  2. package/README.md +214 -123
  3. package/README.zh-cn.md +232 -100
  4. package/README.zh-tw.md +232 -100
  5. package/bower.json +6 -9
  6. package/demo/10000-nodes.html +139 -0
  7. package/demo/ajax-datasource.html +46 -45
  8. package/demo/bottom2top.html +12 -9
  9. package/demo/center-on-selected-node.html +21 -17
  10. package/demo/color-coded.html +12 -9
  11. package/demo/css/style.css +28 -0
  12. package/demo/custom-icons.html +12 -9
  13. package/demo/custom-template.html +12 -9
  14. package/demo/data-prop-compact.html +11 -8
  15. package/demo/data-prop-hybrid.html +79 -50
  16. package/demo/default-collapsed.html +12 -9
  17. package/demo/drag-drop-hybrid-chart.html +17 -14
  18. package/demo/drag-drop.html +21 -16
  19. package/demo/edit-chart.html +144 -94
  20. package/demo/edit-hybrid-chart.html +177 -135
  21. package/demo/expand-to-given-depth.html +30 -22
  22. package/demo/export-chart-pdf.html +12 -9
  23. package/demo/export-chart-with-pictures.html +19 -12
  24. package/demo/export-chart.html +12 -9
  25. package/demo/family-tree.html +14 -11
  26. package/demo/familytree-custom-properties.html +108 -0
  27. package/demo/filter-node.html +104 -57
  28. package/demo/get-hierarchy.html +17 -12
  29. package/demo/get-related-nodes.html +27 -17
  30. package/demo/index.html +10 -3
  31. package/demo/integrate-map.html +32 -27
  32. package/demo/left2right.html +12 -9
  33. package/demo/level-offset.html +16 -15
  34. package/demo/link-node.html +12 -9
  35. package/demo/local-datasource.html +11 -8
  36. package/demo/minimap.html +224 -0
  37. package/demo/nodes-of-different-widths.html +11 -8
  38. package/demo/ondemand-loading-data.html +166 -112
  39. package/demo/option-createNode.html +28 -17
  40. package/demo/options-compact-drag.html +82 -0
  41. package/demo/options-export-navigation.html +76 -0
  42. package/demo/options-horizontal-template.html +80 -0
  43. package/demo/options-hybrid-navigation.html +71 -0
  44. package/demo/pan-zoom.html +20 -11
  45. package/demo/reload-data.html +17 -14
  46. package/demo/report-path.html +59 -29
  47. package/demo/responsive-design.html +14 -11
  48. package/demo/right2left.html +12 -9
  49. package/demo/toggle-sibs-resp.html +12 -9
  50. package/demo/ul-datasource.html +11 -8
  51. package/demo/vertical-level.html +12 -9
  52. package/dist/css/{jquery.orgchart.css → orgchart.css} +154 -80
  53. package/dist/css/orgchart.min.css +1 -0
  54. package/dist/js/orgchart.js +4474 -0
  55. package/dist/js/orgchart.min.js +2 -0
  56. package/dist/js/orgchart.min.js.map +1 -0
  57. package/gulpfile.js +42 -27
  58. package/package.json +33 -32
  59. package/src/css/{jquery.orgchart.css → orgchart.css} +154 -80
  60. package/src/js/orgchart.js +4474 -0
  61. package/test/compatibility/README.md +123 -0
  62. package/test/compatibility/browser.tests.js +1279 -0
  63. package/test/compatibility/options.tests.js +628 -0
  64. package/test/cypress/e2e/demo-interactions.cy.js +91 -0
  65. package/test/cypress/e2e/demo-rendering.cy.js +161 -0
  66. package/test/cypress/e2e/drag-drop-hybrid-chart.cy.js +23 -0
  67. package/test/cypress/e2e/edit-chart.cy.js +20 -0
  68. package/test/cypress/e2e/level-offset.cy.js +21 -0
  69. package/test/cypress/e2e/local-datasource.cy.js +16 -16
  70. package/test/cypress/e2e/minimap.cy.js +66 -0
  71. package/test/cypress/e2e/ondemand-loading-data.cy.js +34 -10
  72. package/test/cypress/e2e/pan-zoom.cy.js +165 -0
  73. package/test/cypress/e2e/report-path.cy.js +20 -0
  74. package/test/cypress/e2e/toggle-sibs-resp.cy.js +4 -4
  75. package/test/cypress/e2e/vertical-vevel.cy.js +7 -5
  76. package/test/e2e/local-datasource/test.js +1 -1
  77. package/test/integration/init-tests.js +192 -99
  78. package/test/integration/tests.js +1043 -57
  79. package/test/unit/lifecycle-builders-export.tests.js +1487 -0
  80. package/test/unit/minimap.tests.js +210 -0
  81. package/test/unit/tests.js +2372 -253
  82. package/dist/css/jquery.orgchart.min.css +0 -1
  83. package/dist/js/jquery.orgchart.js +0 -1741
  84. package/dist/js/jquery.orgchart.min.js +0 -5
  85. package/dist/js/jquery.orgchart.min.js.map +0 -1
  86. package/src/js/jquery.orgchart.js +0 -1741
@@ -0,0 +1,4474 @@
1
+ /*
2
+ * JavaScript OrgChart Component
3
+ * https://github.com/dabeng/OrgChart
4
+ *
5
+ * Copyright 2016, dabeng
6
+ * https://github.com/dabeng
7
+ *
8
+ * Licensed under the MIT license:
9
+ * http://www.opensource.org/licenses/MIT
10
+ */
11
+ 'use strict';
12
+
13
+ (function (factory) {
14
+ if (typeof module === 'object' && typeof module.exports === 'object') {
15
+ module.exports = factory(window, document);
16
+ } else {
17
+ window.OrgChart = factory(window, document);
18
+ }
19
+ }(function (window, document, undefined) {
20
+ const elementDataStore = new WeakMap();
21
+ const nodeLayoutStore = new WeakMap();
22
+ const getWindow = () => (typeof globalThis !== 'undefined' && globalThis.window ? globalThis.window : window);
23
+ const getDocument = () => {
24
+ const currentWindow = getWindow();
25
+ return currentWindow && currentWindow.document
26
+ ? currentWindow.document
27
+ : (typeof globalThis !== 'undefined' && globalThis.document ? globalThis.document : document);
28
+ };
29
+
30
+ const isElement = (value) => value && (value.nodeType === 1 || value.nodeType === 9 || value === getWindow());
31
+ const isNode = (value) => value && typeof value.nodeType === 'number';
32
+ const isNodeList = (value) => value && typeof value.length === 'number' && typeof value !== 'string' && !isNode(value);
33
+ const isHtmlString = (value) => typeof value === 'string' && value.trim().startsWith('<') && value.trim().endsWith('>');
34
+ const isPlainObject = (value) => Object.prototype.toString.call(value) === '[object Object]';
35
+ const unique = (elements) => Array.from(new Set(elements.filter(Boolean)));
36
+
37
+ function getElement(input, context) {
38
+ return normalizeInput(input, context || getDocument())[0] || null;
39
+ }
40
+
41
+ function getElements(input, context) {
42
+ return normalizeInput(input, context || getDocument());
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
+
71
+ function mergeObjects() {
72
+ return Object.assign.apply(Object, [{}].concat(Array.from(arguments)));
73
+ }
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
+
167
+ function forEachValue(collection, callback) {
168
+ if (Array.isArray(collection) || isNodeList(collection)) {
169
+ for (let index = 0; index < collection.length; index += 1) {
170
+ if (callback.call(collection[index], index, collection[index]) === false) {
171
+ break;
172
+ }
173
+ }
174
+ return collection;
175
+ }
176
+ Object.keys(collection || {}).some((key) => callback.call(collection[key], key, collection[key]) === false);
177
+ return collection;
178
+ }
179
+
180
+ function createEventLike(type) {
181
+ return {
182
+ type,
183
+ defaultPrevented: false,
184
+ preventDefault: function () {
185
+ this.defaultPrevented = true;
186
+ },
187
+ isDefaultPrevented: function () {
188
+ return this.defaultPrevented;
189
+ }
190
+ };
191
+ }
192
+
193
+ function normalizeSelector(selector) {
194
+ if (!selector) {
195
+ return selector;
196
+ }
197
+ return selector.replace(/:not\("([^"]+)"\)/g, ':not($1)');
198
+ }
199
+
200
+ function parseSelector(selector) {
201
+ const normalizedSelector = normalizeSelector(selector || '*');
202
+ const meta = {
203
+ selector: normalizedSelector,
204
+ first: false,
205
+ last: false,
206
+ gt: null,
207
+ hidden: false,
208
+ visible: false
209
+ };
210
+ meta.selector = meta.selector.replace(/:first\b/g, function () {
211
+ meta.first = true;
212
+ return '';
213
+ });
214
+ meta.selector = meta.selector.replace(/:last\b/g, function () {
215
+ meta.last = true;
216
+ return '';
217
+ });
218
+ meta.selector = meta.selector.replace(/:gt\((\d+)\)/g, function (_, index) {
219
+ meta.gt = Number(index);
220
+ return '';
221
+ });
222
+ meta.selector = meta.selector.replace(/:hidden\b/g, function () {
223
+ meta.hidden = true;
224
+ return '';
225
+ });
226
+ meta.selector = meta.selector.replace(/:visible\b/g, function () {
227
+ meta.visible = true;
228
+ return '';
229
+ });
230
+ meta.selector = meta.selector.trim() || '*';
231
+ return meta;
232
+ }
233
+
234
+ function isVisibleElement(element) {
235
+ if (!element || element.nodeType !== 1) {
236
+ return false;
237
+ }
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;
248
+ }
249
+
250
+ function matchesSelector(element, selector) {
251
+ if (!selector || selector === '*') {
252
+ return true;
253
+ }
254
+ if (!element || element.nodeType !== 1) {
255
+ return false;
256
+ }
257
+ try {
258
+ return element.matches(selector);
259
+ } catch (error) {
260
+ return false;
261
+ }
262
+ }
263
+
264
+ function filterBySelector(elements, selector) {
265
+ const meta = parseSelector(selector);
266
+ let filtered = elements.filter((element) => matchesSelector(element, meta.selector));
267
+ if (meta.hidden) {
268
+ filtered = filtered.filter((element) => !isVisibleElement(element));
269
+ }
270
+ if (meta.visible) {
271
+ filtered = filtered.filter((element) => isVisibleElement(element));
272
+ }
273
+ if (meta.gt !== null) {
274
+ filtered = filtered.filter((_, index) => index > meta.gt);
275
+ }
276
+ if (meta.first) {
277
+ filtered = filtered.slice(0, 1);
278
+ }
279
+ if (meta.last) {
280
+ filtered = filtered.slice(-1);
281
+ }
282
+ return filtered;
283
+ }
284
+
285
+ function createNodesFromHtml(html) {
286
+ const template = getDocument().createElement('template');
287
+ template.innerHTML = html.trim();
288
+ return Array.from(template.content.childNodes);
289
+ }
290
+
291
+ function toDataKey(key) {
292
+ return String(key).replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
293
+ }
294
+
295
+ function getStoredState(element) {
296
+ if (!elementDataStore.has(element)) {
297
+ elementDataStore.set(element, {});
298
+ }
299
+ return elementDataStore.get(element);
300
+ }
301
+
302
+ function setState(element, key, value) {
303
+ const store = getStoredState(element);
304
+ store[toDataKey(key)] = value;
305
+ }
306
+
307
+ function getState(element, key) {
308
+ const store = getStoredState(element);
309
+ const normalizedKey = toDataKey(key);
310
+ if (Object.prototype.hasOwnProperty.call(store, normalizedKey)) {
311
+ return store[normalizedKey];
312
+ }
313
+ return undefined;
314
+ }
315
+
316
+ function getDatasetSnapshot(element) {
317
+ return element.dataset ? Object.assign({}, element.dataset) : {};
318
+ }
319
+
320
+ function normalizeInput(input, context) {
321
+ if (input === null || input === undefined) {
322
+ return [];
323
+ }
324
+ if (Array.isArray(input)) {
325
+ return unique(input.flatMap((item) => normalizeInput(item, context)));
326
+ }
327
+ if (isNode(input)) {
328
+ return [input];
329
+ }
330
+ if (isNodeList(input)) {
331
+ return Array.from(input).flatMap((item) => normalizeInput(item, context));
332
+ }
333
+ if (typeof input === 'string') {
334
+ if (isHtmlString(input)) {
335
+ return createNodesFromHtml(input);
336
+ }
337
+ const currentDocument = getDocument();
338
+ const parents = normalizeInput(context || currentDocument, currentDocument);
339
+ const selected = parents.length
340
+ ? parents.flatMap((parent) => {
341
+ if (parent.nodeType !== 1 && parent.nodeType !== 9) {
342
+ return [];
343
+ }
344
+ return filterBySelector(Array.from(parent.querySelectorAll(parseSelector(input).selector)), input);
345
+ })
346
+ : filterBySelector(Array.from(currentDocument.querySelectorAll(parseSelector(input).selector)), input);
347
+ return unique(selected);
348
+ }
349
+ return [];
350
+ }
351
+
352
+ function toNodeArray(content) {
353
+ if (content instanceof DocumentFragment) {
354
+ return Array.from(content.childNodes);
355
+ }
356
+ if (Array.isArray(content)) {
357
+ return content.flatMap((item) => toNodeArray(item));
358
+ }
359
+ if (isNode(content)) {
360
+ return [content];
361
+ }
362
+ if (typeof content === 'string') {
363
+ return isHtmlString(content) ? createNodesFromHtml(content) : [getDocument().createTextNode(content)];
364
+ }
365
+ return [];
366
+ }
367
+
368
+ function cloneNodesForTarget(nodes, needsClone) {
369
+ return nodes.map((node) => (needsClone ? node.cloneNode(true) : node));
370
+ }
371
+
372
+ function createTriggeredEvent(eventLike, extraData) {
373
+ const type = typeof eventLike === 'string' ? eventLike : eventLike.type;
374
+ const event = new (getWindow().CustomEvent)(type, {
375
+ bubbles: true,
376
+ cancelable: true,
377
+ detail: extraData
378
+ });
379
+ event.originalEvent = event;
380
+ event.data = extraData;
381
+ event.delegateTarget = null;
382
+ return event;
383
+ }
384
+
385
+ function clampValue(value, min, max) {
386
+ return Math.min(Math.max(value, min), max);
387
+ }
388
+
389
+ function isTransparentColor(colorValue) {
390
+ const normalizedColor = (colorValue || '').replace(/\s+/g, '').toLowerCase();
391
+
392
+ return !normalizedColor
393
+ || normalizedColor === 'transparent'
394
+ || normalizedColor === 'rgba(0,0,0,0)'
395
+ || normalizedColor === 'hsla(0,0%,0%,0)';
396
+ }
397
+
398
+ function toReadableMinimapColor(colorValue) {
399
+ const rgbaMatch = /^rgba?\(([^)]+)\)$/i.exec((colorValue || '').trim());
400
+ const darkenFactor = 0.68;
401
+ let channelValues;
402
+
403
+ if (!rgbaMatch) {
404
+ return colorValue;
405
+ }
406
+
407
+ channelValues = rgbaMatch[1].split(',').slice(0, 3).map(function (value) {
408
+ return clampValue(Math.round(parseFloat(value.trim()) * darkenFactor), 0, 255);
409
+ });
410
+
411
+ if (channelValues.some(function (value) { return Number.isNaN(value); })) {
412
+ return colorValue;
413
+ }
414
+
415
+ return 'rgb(' + channelValues.join(', ') + ')';
416
+ }
417
+
418
+ function getMinimapNodeColor(nodeElement) {
419
+ const fallbackColor = 'rgba(217, 83, 79, 0.8)';
420
+ const titleElement = nodeElement ? nodeElement.querySelector('.title') : null;
421
+ const primaryColor = titleElement ? getWindow().getComputedStyle(titleElement).backgroundColor : '';
422
+ const secondaryColorCandidates = [
423
+ nodeElement,
424
+ titleElement,
425
+ nodeElement ? nodeElement.querySelector('.content') : null
426
+ ].flatMap(function (element) {
427
+ const style = element ? getWindow().getComputedStyle(element) : null;
428
+
429
+ if (!style) {
430
+ return [];
431
+ }
432
+
433
+ return [
434
+ style.backgroundColor,
435
+ style.borderTopColor,
436
+ style.borderRightColor,
437
+ style.borderBottomColor,
438
+ style.borderLeftColor
439
+ ];
440
+ });
441
+
442
+ return toReadableMinimapColor((!isTransparentColor(primaryColor) && primaryColor) || secondaryColorCandidates.find(function (color) {
443
+ return !isTransparentColor(color);
444
+ }) || fallbackColor);
445
+ }
446
+
447
+ const MINIMAP_NODE_HEIGHT = 18;
448
+
449
+ function renderMinimapNodes(chartElement, minimapContentElement, chartRect, chartScale) {
450
+ const fragment = getDocument().createDocumentFragment();
451
+
452
+ Array.from(chartElement.querySelectorAll('.node')).filter(function (nodeElement) {
453
+ return isVisibleElement(nodeElement);
454
+ }).forEach(function (nodeElement) {
455
+ const nodeRect = nodeElement.getBoundingClientRect();
456
+ const minimapNodeElement = getDocument().createElement('div');
457
+ const nodeTop = (nodeRect.top - chartRect.top) / chartScale;
458
+
459
+ minimapNodeElement.className = 'orgchart-minimap-node';
460
+ minimapNodeElement.style.left = Math.max((nodeRect.left - chartRect.left) / chartScale, 0) + 'px';
461
+ minimapNodeElement.style.top = Math.max(nodeTop + ((nodeRect.height / chartScale - MINIMAP_NODE_HEIGHT) / 2), 0) + 'px';
462
+ minimapNodeElement.style.width = Math.max(nodeRect.width / chartScale, 6) + 'px';
463
+ minimapNodeElement.style.height = MINIMAP_NODE_HEIGHT + 'px';
464
+ minimapNodeElement.style.backgroundColor = getMinimapNodeColor(nodeElement);
465
+ fragment.appendChild(minimapNodeElement);
466
+ });
467
+
468
+ minimapContentElement.innerHTML = '';
469
+ minimapContentElement.appendChild(fragment);
470
+ }
471
+
472
+ function getChartTransform(chartElement) {
473
+ let currentTransform;
474
+ let matrixValues = [1, 0, 0, 1, 0, 0];
475
+ let currentScale = 1;
476
+ let currentTranslateX = 0;
477
+ let currentTranslateY = 0;
478
+
479
+ if (!chartElement) {
480
+ return {
481
+ scale: currentScale,
482
+ translateX: currentTranslateX,
483
+ translateY: currentTranslateY,
484
+ matrix: matrixValues
485
+ };
486
+ }
487
+
488
+ currentTransform = getWindow().getComputedStyle(chartElement).transform || chartElement.style.transform || 'none';
489
+ if (currentTransform && currentTransform !== 'none') {
490
+ if (currentTransform.indexOf('matrix3d(') === 0) {
491
+ matrixValues = currentTransform.slice(9, -1).split(',').map(function (value) {
492
+ return getWindow().parseFloat(value.trim());
493
+ });
494
+ currentScale = Math.hypot(matrixValues[0], matrixValues[1], matrixValues[2]) || 1;
495
+ currentTranslateX = matrixValues[12] || 0;
496
+ currentTranslateY = matrixValues[13] || 0;
497
+ } else if (currentTransform.indexOf('matrix(') === 0) {
498
+ matrixValues = currentTransform.slice(7, -1).split(',').map(function (value) {
499
+ return getWindow().parseFloat(value.trim());
500
+ });
501
+ currentScale = Math.hypot(matrixValues[0], matrixValues[1]) || 1;
502
+ currentTranslateX = matrixValues[4] || 0;
503
+ currentTranslateY = matrixValues[5] || 0;
504
+ } else if (currentTransform.indexOf('scale3d(') === 0) {
505
+ matrixValues = currentTransform.slice(8, -1).split(',').map(function (value) {
506
+ return getWindow().parseFloat(value.trim());
507
+ });
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];
510
+ } else if (currentTransform.indexOf('scale(') === 0) {
511
+ matrixValues = currentTransform.slice(6, -1).split(',').map(function (value) {
512
+ return getWindow().parseFloat(value.trim());
513
+ });
514
+ currentScale = Math.abs(matrixValues[0]) || 1;
515
+ matrixValues = [matrixValues[0], 0, 0, matrixValues[1] === undefined ? matrixValues[0] : matrixValues[1], 0, 0];
516
+ }
517
+ }
518
+
519
+ return {
520
+ scale: currentScale,
521
+ translateX: currentTranslateX,
522
+ translateY: currentTranslateY,
523
+ matrix: matrixValues
524
+ };
525
+ }
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
+
585
+ const OrgChart = function (elem, opts) {
586
+ if (!(this instanceof OrgChart)) {
587
+ return new OrgChart(elem, opts);
588
+ }
589
+ if (isPlainObject(elem) && !opts) {
590
+ this.opts = elem;
591
+ this.chartContainer = getElement(elem.chartContainer || elem.container);
592
+ } else {
593
+ this.chartContainer = getElement(elem);
594
+ this.opts = opts || {};
595
+ }
596
+ this.chart = null;
597
+ this.exportButtonElement = null;
598
+ this.defaultOptions = {
599
+ 'icons': {
600
+ 'theme': 'oci',
601
+ 'parentNode': 'oci-menu',
602
+ 'expandToUp': 'oci-chevron-up',
603
+ 'collapseToDown': 'oci-chevron-down',
604
+ 'collapseToLeft': 'oci-chevron-left',
605
+ 'expandToRight': 'oci-chevron-right',
606
+ 'backToCompact': 'oci-corner-top-left',
607
+ 'backToLoose': 'oci-corner-bottom-right',
608
+ 'collapsed': 'oci-plus-square',
609
+ 'expanded': 'oci-minus-square',
610
+ 'spinner': 'oci-spinner'
611
+ },
612
+ 'nodeTitle': 'name',
613
+ 'nodeId': 'id',
614
+ 'toggleSiblingsResp': false,
615
+ 'visibleLevel': 999,
616
+ 'chartClass': '',
617
+ 'exportButton': false,
618
+ 'exportButtonName': 'Export',
619
+ 'exportFilename': 'OrgChart',
620
+ 'exportFileextension': 'png',
621
+ 'exportScale': 2,
622
+ 'draggable': false,
623
+ 'direction': 't2b',
624
+ 'pan': false,
625
+ 'minimap': false,
626
+ 'zoom': false,
627
+ 'zoominLimit': 7,
628
+ 'zoomoutLimit': 0.5
629
+ };
630
+ if (this.chartContainer) {
631
+ this.init();
632
+ }
633
+ };
634
+
635
+ if (typeof module === 'object' && typeof module.exports === 'object') {
636
+ Object.defineProperty(OrgChart, '__testing__', {
637
+ value: {
638
+ getState: getState,
639
+ setState: setState
640
+ }
641
+ });
642
+ }
643
+ //
644
+ OrgChart.prototype = {
645
+ //
646
+ init: function (opts) {
647
+ const options = mergeChartOptions(this.defaultOptions, this.opts, opts);
648
+ this.opts = options;
649
+ this.options = options;
650
+ // build the org-chart
651
+ const chartContainerElement = this.chartContainer;
652
+ let chartElement;
653
+ let rootElement;
654
+ let rootNodesContainerElement;
655
+ let rootHierarchyElement;
656
+
657
+ this.detachMinimap();
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();
666
+ this.chart.remove();
667
+ }
668
+ const data = this.options.data;
669
+ chartElement = getDocument().createElement('div');
670
+ chartElement.className = 'orgchart' + (this.options.chartClass !== '' ? ' ' + this.options.chartClass : '') + (this.options.direction !== 't2b' ? ' ' + this.options.direction : '');
671
+ setState(chartElement, 'options', this.options);
672
+ this.chart = chartElement;
673
+ chartElement.addEventListener('click', function (event) {
674
+ const nativeEvent = event.originalEvent || event;
675
+ const clickTargetElement = getElement(nativeEvent.target);
676
+
677
+ if (!clickTargetElement || !(typeof clickTargetElement.closest === 'function' && clickTargetElement.closest('.node'))) {
678
+ Array.from(chartElement.querySelectorAll('.node.focused')).forEach(function (focusedNodeEl) {
679
+ focusedNodeEl.classList.remove('focused');
680
+ });
681
+ const activeElement = getDocument().activeElement;
682
+ if (activeElement && chartElement.contains(activeElement) && activeElement.closest('.node')) {
683
+ activeElement.blur();
684
+ }
685
+ }
686
+ });
687
+ if (typeof getWindow().MutationObserver !== 'undefined') {
688
+ this.triggerInitEvent();
689
+ }
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;
696
+ rootNodesContainerElement = getDocument().createElement('ul');
697
+ rootNodesContainerElement.className = 'nodes';
698
+ chartElement.appendChild(rootNodesContainerElement);
699
+
700
+ if (!isListElementData && Array.isArray(data)) {
701
+ rootElement = rootNodesContainerElement;
702
+ } else {
703
+ rootHierarchyElement = getDocument().createElement('li');
704
+ rootHierarchyElement.className = 'hierarchy';
705
+ rootNodesContainerElement.appendChild(rootHierarchyElement);
706
+ rootElement = rootHierarchyElement;
707
+ }
708
+
709
+ this.buildHierarchy(rootElement, datasource);
710
+
711
+ if (chartContainerElement) {
712
+ chartContainerElement.appendChild(chartElement);
713
+ }
714
+
715
+ // append the export button
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;
725
+ }
726
+
727
+ if (this.options.pan) {
728
+ this.bindPan();
729
+ }
730
+
731
+ if (this.options.zoom) {
732
+ this.bindZoom();
733
+ }
734
+
735
+ if (this.options.minimap) {
736
+ this.attachMinimap();
737
+ }
738
+
739
+ return this;
740
+ },
741
+ handleCompactNodes: function () {
742
+ const chartElement = this.chart;
743
+
744
+ if (!chartElement) {
745
+ return;
746
+ }
747
+ // caculate the compact nodes' level which is used to add different styles
748
+ Array.from(chartElement.querySelectorAll('.node.compact')).forEach(function (compactNodeEl) {
749
+ let compactAncestorCount = 0;
750
+ let currentAncestorElement = compactNodeEl.parentElement;
751
+
752
+ while (currentAncestorElement) {
753
+ if (currentAncestorElement.classList && currentAncestorElement.classList.contains('compact')) {
754
+ compactAncestorCount += 1;
755
+ }
756
+ currentAncestorElement = currentAncestorElement.parentElement;
757
+ }
758
+
759
+ compactNodeEl.classList.add(compactAncestorCount % 2 === 0 ? 'even' : 'odd');
760
+ }); // the following code snippets are kept only as a reminder for a future native-DOM approach
761
+ // that would add direction arrows for the top compact node once the styles are corrected.
762
+ },
763
+ //
764
+ triggerInitEvent: function () {
765
+ const orgChart = this;
766
+ const chartElement = this.chart;
767
+ const options = this.options;
768
+ const MutationObserverCtor = getWindow().MutationObserver;
769
+ const mo = new MutationObserverCtor(function (mutations) {
770
+ mo.disconnect();
771
+ setState(chartElement, 'initMutationObserver', null);
772
+ initTime:
773
+ for (let i = 0; i < mutations.length; i++) {
774
+ for (let j = 0; j < mutations[i].addedNodes.length; j++) {
775
+ if (mutations[i].addedNodes[j] === chartElement) {
776
+ orgChart.handleCompactNodes();
777
+ if (!chartElement.isConnected) {
778
+ break initTime;
779
+ }
780
+ if (options.initCompleted && typeof options.initCompleted === 'function') {
781
+ options.initCompleted(chartElement);
782
+ }
783
+ chartElement.dispatchEvent(createTriggeredEvent('init.orgchart'));
784
+ break initTime;
785
+ }
786
+ }
787
+ }
788
+ });
789
+ setState(chartElement, 'initMutationObserver', mo);
790
+ mo.observe(this.chartContainer, { childList: true });
791
+ },
792
+ triggerShowEvent: function (targetEl, rel) {
793
+ if (targetEl && typeof targetEl.dispatchEvent === 'function') {
794
+ targetEl.dispatchEvent(createTriggeredEvent('show-' + rel + '.orgchart'));
795
+ }
796
+ },
797
+ triggerHideEvent: function (targetEl, rel) {
798
+ if (targetEl && typeof targetEl.dispatchEvent === 'function') {
799
+ targetEl.dispatchEvent(createTriggeredEvent('hide-' + rel + '.orgchart'));
800
+ }
801
+ },
802
+ // add export button for orgchart
803
+ attachExportButton: function () {
804
+ const orgChart = this;
805
+ const chartContainerElement = this.chartContainer;
806
+ let exportButtonElement;
807
+
808
+ if (!chartContainerElement || !chartContainerElement.parentElement) {
809
+ return;
810
+ }
811
+
812
+ exportButtonElement = getDocument().createElement('button');
813
+ exportButtonElement.className = 'oc-export-btn';
814
+ exportButtonElement.textContent = this.options.exportButtonName;
815
+ setState(exportButtonElement, 'orgChart', this);
816
+ exportButtonElement.addEventListener('click', function (event) {
817
+ event.preventDefault();
818
+ orgChart.export();
819
+ });
820
+ this.exportButtonElement = exportButtonElement;
821
+ chartContainerElement.insertAdjacentElement('afterend', exportButtonElement);
822
+ },
823
+ setOptions: function (opts, val) {
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();
846
+ }
847
+ }
848
+ if (Object.prototype.hasOwnProperty.call(overrides, 'zoom')) {
849
+ if (options.zoom) {
850
+ this.bindZoom();
851
+ } else {
852
+ this.unbindZoom();
853
+ }
854
+ }
855
+ if (Object.prototype.hasOwnProperty.call(overrides, 'minimap')) {
856
+ if (options.minimap) {
857
+ this.attachMinimap();
858
+ } else {
859
+ this.detachMinimap();
860
+ }
861
+ }
862
+ return this;
863
+ },
864
+ attachMinimap: function () {
865
+ const orgChart = this;
866
+ const chartContainerElement = this.chartContainer;
867
+ const chartElement = this.chart;
868
+ let minimapElement;
869
+ let minimapStageElement;
870
+ let minimapContentElement;
871
+ let minimapViewportElement;
872
+ let minimapWheelBoundHandler;
873
+ let minimapPointerDownBoundHandler;
874
+ let minimapViewportChangeBoundHandler;
875
+ let minimapMutationObserver;
876
+ let minimapResizeObserver;
877
+
878
+ if (!chartContainerElement || !chartElement) {
879
+ return;
880
+ }
881
+
882
+ this.detachMinimap();
883
+ if (!getWindow().getComputedStyle(chartContainerElement).position || getWindow().getComputedStyle(chartContainerElement).position === 'static') {
884
+ setState(chartContainerElement, 'minimapOriginalPosition', chartContainerElement.style.position || '');
885
+ setState(chartContainerElement, 'minimapManagedPosition', true);
886
+ chartContainerElement.style.position = 'relative';
887
+ }
888
+
889
+ minimapElement = getDocument().createElement('div');
890
+ minimapElement.className = 'orgchart-minimap';
891
+ minimapStageElement = getDocument().createElement('div');
892
+ minimapStageElement.className = 'orgchart-minimap-stage';
893
+ minimapContentElement = getDocument().createElement('div');
894
+ minimapContentElement.className = 'orgchart-minimap-content';
895
+ minimapViewportElement = getDocument().createElement('div');
896
+ minimapViewportElement.className = 'orgchart-minimap-viewport';
897
+
898
+ minimapStageElement.appendChild(minimapContentElement);
899
+ minimapStageElement.appendChild(minimapViewportElement);
900
+ minimapElement.appendChild(minimapStageElement);
901
+ chartContainerElement.appendChild(minimapElement);
902
+
903
+ minimapWheelBoundHandler = function (event) {
904
+ orgChart.minimapWheelHandler(event);
905
+ };
906
+ minimapPointerDownBoundHandler = function (event) {
907
+ orgChart.minimapStartHandler(event);
908
+ };
909
+ minimapViewportChangeBoundHandler = function () {
910
+ orgChart.updateMinimap();
911
+ };
912
+
913
+ minimapElement.addEventListener('wheel', minimapWheelBoundHandler, { passive: false });
914
+ minimapViewportElement.addEventListener('mousedown', minimapPointerDownBoundHandler);
915
+ chartContainerElement.addEventListener('scroll', minimapViewportChangeBoundHandler);
916
+ getWindow().addEventListener('resize', minimapViewportChangeBoundHandler);
917
+
918
+ if (typeof getWindow().MutationObserver !== 'undefined') {
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
+ }
940
+ });
941
+ minimapMutationObserver.observe(chartElement, {
942
+ childList: true,
943
+ subtree: true,
944
+ attributes: true,
945
+ attributeOldValue: true,
946
+ attributeFilter: ['class']
947
+ });
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
+ }
956
+
957
+ setState(chartElement, 'minimapElement', minimapElement);
958
+ setState(chartElement, 'minimapStageElement', minimapStageElement);
959
+ setState(chartElement, 'minimapContentElement', minimapContentElement);
960
+ setState(chartElement, 'minimapViewportElement', minimapViewportElement);
961
+ setState(chartElement, 'minimapWheelBoundHandler', minimapWheelBoundHandler);
962
+ setState(chartElement, 'minimapPointerDownBoundHandler', minimapPointerDownBoundHandler);
963
+ setState(chartElement, 'minimapViewportChangeBoundHandler', minimapViewportChangeBoundHandler);
964
+ setState(chartElement, 'minimapMutationObserver', minimapMutationObserver || null);
965
+ setState(chartElement, 'minimapResizeObserver', minimapResizeObserver || null);
966
+
967
+ this.updateMinimap(true);
968
+ },
969
+ detachMinimap: function () {
970
+ const chartContainerElement = this.chartContainer;
971
+ const chartElement = this.chart;
972
+ let minimapElement;
973
+ let minimapViewportElement;
974
+ let minimapWheelBoundHandler;
975
+ let minimapPointerDownBoundHandler;
976
+ let minimapMoveBoundHandler;
977
+ let minimapEndBoundHandler;
978
+ let minimapMutationObserver;
979
+ let minimapViewportChangeBoundHandler;
980
+ let minimapResizeObserver;
981
+
982
+ if (chartElement) {
983
+ minimapElement = getState(chartElement, 'minimapElement');
984
+ minimapViewportElement = getState(chartElement, 'minimapViewportElement');
985
+ minimapWheelBoundHandler = getState(chartElement, 'minimapWheelBoundHandler');
986
+ minimapPointerDownBoundHandler = getState(chartElement, 'minimapPointerDownBoundHandler');
987
+ minimapMoveBoundHandler = getState(chartElement, 'minimapMoveBoundHandler');
988
+ minimapEndBoundHandler = getState(chartElement, 'minimapEndBoundHandler');
989
+ minimapMutationObserver = getState(chartElement, 'minimapMutationObserver');
990
+ minimapViewportChangeBoundHandler = getState(chartElement, 'minimapViewportChangeBoundHandler');
991
+ minimapResizeObserver = getState(chartElement, 'minimapResizeObserver');
992
+
993
+ if (minimapElement && minimapWheelBoundHandler) {
994
+ minimapElement.removeEventListener('wheel', minimapWheelBoundHandler);
995
+ }
996
+ if (minimapViewportElement && minimapPointerDownBoundHandler) {
997
+ minimapViewportElement.removeEventListener('mousedown', minimapPointerDownBoundHandler);
998
+ }
999
+ if (minimapMoveBoundHandler) {
1000
+ getDocument().removeEventListener('mousemove', minimapMoveBoundHandler);
1001
+ }
1002
+ if (minimapEndBoundHandler) {
1003
+ getDocument().removeEventListener('mouseup', minimapEndBoundHandler);
1004
+ }
1005
+ if (minimapMutationObserver) {
1006
+ minimapMutationObserver.disconnect();
1007
+ }
1008
+ if (minimapViewportChangeBoundHandler) {
1009
+ chartContainerElement.removeEventListener('scroll', minimapViewportChangeBoundHandler);
1010
+ getWindow().removeEventListener('resize', minimapViewportChangeBoundHandler);
1011
+ }
1012
+ if (minimapResizeObserver) {
1013
+ minimapResizeObserver.disconnect();
1014
+ }
1015
+ if (minimapElement && minimapElement.parentElement) {
1016
+ minimapElement.remove();
1017
+ }
1018
+
1019
+ [
1020
+ 'minimapElement',
1021
+ 'minimapStageElement',
1022
+ 'minimapContentElement',
1023
+ 'minimapViewportElement',
1024
+ 'minimapWheelBoundHandler',
1025
+ 'minimapPointerDownBoundHandler',
1026
+ 'minimapMoveBoundHandler',
1027
+ 'minimapEndBoundHandler',
1028
+ 'minimapMutationObserver',
1029
+ 'minimapViewportChangeBoundHandler',
1030
+ 'minimapResizeObserver',
1031
+ 'minimapDragState',
1032
+ 'minimapScale',
1033
+ 'minimapOffsetX',
1034
+ 'minimapOffsetY',
1035
+ 'minimapContentWidth',
1036
+ 'minimapContentHeight'
1037
+ ].forEach(function (key) {
1038
+ setState(chartElement, key, null);
1039
+ });
1040
+ }
1041
+
1042
+ if (chartContainerElement && getState(chartContainerElement, 'minimapManagedPosition')) {
1043
+ chartContainerElement.style.position = getState(chartContainerElement, 'minimapOriginalPosition') || '';
1044
+ setState(chartContainerElement, 'minimapManagedPosition', null);
1045
+ setState(chartContainerElement, 'minimapOriginalPosition', null);
1046
+ }
1047
+ },
1048
+ minimapWheelHandler: function (event) {
1049
+ const chartContainerElement = this.chartContainer;
1050
+
1051
+ if (!this.chart || !chartContainerElement) {
1052
+ return;
1053
+ }
1054
+ if (typeof event.preventDefault === 'function') {
1055
+ event.preventDefault();
1056
+ }
1057
+ if (typeof event.stopPropagation === 'function') {
1058
+ event.stopPropagation();
1059
+ }
1060
+
1061
+ this.setChartScale(this.chart, 1 + (event.deltaY > 0 ? -0.2 : 0.2), {
1062
+ x: chartContainerElement.getBoundingClientRect().left + (chartContainerElement.clientWidth / 2),
1063
+ y: chartContainerElement.getBoundingClientRect().top + (chartContainerElement.clientHeight / 2)
1064
+ });
1065
+ },
1066
+ minimapStartHandler: function (event) {
1067
+ const chartElement = this.chart;
1068
+ const minimapViewportElement = chartElement ? getState(chartElement, 'minimapViewportElement') : null;
1069
+ const minimapMoveBoundHandler = this.minimapMoveHandler.bind(this);
1070
+ const minimapEndBoundHandler = this.minimapEndHandler.bind(this);
1071
+ const viewportRect = minimapViewportElement ? minimapViewportElement.getBoundingClientRect() : null;
1072
+
1073
+ if (!chartElement || !minimapViewportElement || !viewportRect) {
1074
+ return;
1075
+ }
1076
+ if (typeof event.preventDefault === 'function') {
1077
+ event.preventDefault();
1078
+ }
1079
+
1080
+ setState(chartElement, 'minimapDragState', {
1081
+ startClientX: event.clientX,
1082
+ startClientY: event.clientY,
1083
+ startLeft: viewportRect.left,
1084
+ startTop: viewportRect.top,
1085
+ viewportWidth: viewportRect.width,
1086
+ viewportHeight: viewportRect.height
1087
+ });
1088
+ setState(chartElement, 'minimapMoveBoundHandler', minimapMoveBoundHandler);
1089
+ setState(chartElement, 'minimapEndBoundHandler', minimapEndBoundHandler);
1090
+ getDocument().addEventListener('mousemove', minimapMoveBoundHandler);
1091
+ getDocument().addEventListener('mouseup', minimapEndBoundHandler);
1092
+ },
1093
+ minimapMoveHandler: function (event) {
1094
+ const chartElement = this.chart;
1095
+ const dragState = chartElement ? getState(chartElement, 'minimapDragState') : null;
1096
+ const minimapStageElement = chartElement ? getState(chartElement, 'minimapStageElement') : null;
1097
+ let stageRect;
1098
+
1099
+ if (!chartElement || !dragState || !minimapStageElement) {
1100
+ return;
1101
+ }
1102
+
1103
+ stageRect = minimapStageElement.getBoundingClientRect();
1104
+ this.moveChartFromMinimap(
1105
+ dragState.startLeft + (event.clientX - dragState.startClientX) - stageRect.left,
1106
+ dragState.startTop + (event.clientY - dragState.startClientY) - stageRect.top,
1107
+ dragState.viewportWidth,
1108
+ dragState.viewportHeight
1109
+ );
1110
+ },
1111
+ minimapEndHandler: function () {
1112
+ const chartElement = this.chart;
1113
+ const minimapMoveBoundHandler = chartElement ? getState(chartElement, 'minimapMoveBoundHandler') : null;
1114
+ const minimapEndBoundHandler = chartElement ? getState(chartElement, 'minimapEndBoundHandler') : null;
1115
+
1116
+ if (minimapMoveBoundHandler) {
1117
+ getDocument().removeEventListener('mousemove', minimapMoveBoundHandler);
1118
+ setState(chartElement, 'minimapMoveBoundHandler', null);
1119
+ }
1120
+ if (minimapEndBoundHandler) {
1121
+ getDocument().removeEventListener('mouseup', minimapEndBoundHandler);
1122
+ setState(chartElement, 'minimapEndBoundHandler', null);
1123
+ }
1124
+ if (chartElement) {
1125
+ setState(chartElement, 'minimapDragState', null);
1126
+ }
1127
+ },
1128
+ moveChartFromMinimap: function (stageLeft, stageTop, viewportWidth, viewportHeight) {
1129
+ const chartElement = this.chart;
1130
+ const chartContainerElement = this.chartContainer;
1131
+ const minimapStageElement = chartElement ? getState(chartElement, 'minimapStageElement') : null;
1132
+ const minimapScale = chartElement ? getState(chartElement, 'minimapScale') : null;
1133
+ const minimapOffsetX = chartElement ? getState(chartElement, 'minimapOffsetX') : 0;
1134
+ const minimapOffsetY = chartElement ? getState(chartElement, 'minimapOffsetY') : 0;
1135
+ const minimapContentWidth = chartElement ? getState(chartElement, 'minimapContentWidth') : 0;
1136
+ const minimapContentHeight = chartElement ? getState(chartElement, 'minimapContentHeight') : 0;
1137
+ const chartTransform = getChartTransform(chartElement);
1138
+ const chartRect = chartElement ? chartElement.getBoundingClientRect() : null;
1139
+ const containerRect = chartContainerElement ? chartContainerElement.getBoundingClientRect() : null;
1140
+ let localViewportWidth;
1141
+ let localViewportHeight;
1142
+ let localLeft;
1143
+ let localTop;
1144
+ let transformedRect;
1145
+
1146
+ if (!chartElement || !chartContainerElement || !minimapStageElement || !minimapScale || !chartRect || !containerRect) {
1147
+ return;
1148
+ }
1149
+
1150
+ localViewportWidth = viewportWidth / minimapScale;
1151
+ localViewportHeight = viewportHeight / minimapScale;
1152
+ localLeft = clampValue((stageLeft - minimapOffsetX) / minimapScale, 0, Math.max(minimapContentWidth - localViewportWidth, 0));
1153
+ localTop = clampValue((stageTop - minimapOffsetY) / minimapScale, 0, Math.max(minimapContentHeight - localViewportHeight, 0));
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);
1159
+ this.updateMinimap();
1160
+ },
1161
+ updateMinimap: function (forceRefresh) {
1162
+ const chartElement = this.chart;
1163
+ const chartContainerElement = this.chartContainer;
1164
+ const minimapStageElement = chartElement ? getState(chartElement, 'minimapStageElement') : null;
1165
+ const minimapContentElement = chartElement ? getState(chartElement, 'minimapContentElement') : null;
1166
+ const minimapViewportElement = chartElement ? getState(chartElement, 'minimapViewportElement') : null;
1167
+ const chartTransform = getChartTransform(chartElement);
1168
+ const chartRect = chartElement ? chartElement.getBoundingClientRect() : null;
1169
+ const containerRect = chartContainerElement ? chartContainerElement.getBoundingClientRect() : null;
1170
+ let contentWidth;
1171
+ let contentHeight;
1172
+ let stageWidth;
1173
+ let stageHeight;
1174
+ let minimapScale;
1175
+ let minimapOffsetX;
1176
+ let minimapOffsetY;
1177
+ let viewportLeft;
1178
+ let viewportTop;
1179
+ let viewportWidth;
1180
+ let viewportHeight;
1181
+
1182
+ if (!this.options || !this.options.minimap || !chartElement || !chartContainerElement || !minimapStageElement || !minimapContentElement || !minimapViewportElement || !chartRect || !containerRect) {
1183
+ return;
1184
+ }
1185
+
1186
+ contentWidth = chartTransform.scale !== 0 && chartRect.width
1187
+ ? chartRect.width / chartTransform.scale
1188
+ : (chartElement.scrollWidth || chartElement.offsetWidth || 1);
1189
+ contentHeight = chartTransform.scale !== 0 && chartRect.height
1190
+ ? chartRect.height / chartTransform.scale
1191
+ : (chartElement.scrollHeight || chartElement.offsetHeight || 1);
1192
+ stageWidth = minimapStageElement.clientWidth || 180;
1193
+ stageHeight = minimapStageElement.clientHeight || 120;
1194
+ minimapScale = Math.min(stageWidth / Math.max(contentWidth, 1), stageHeight / Math.max(contentHeight, 1));
1195
+ minimapOffsetX = (stageWidth - (contentWidth * minimapScale)) / 2;
1196
+ minimapOffsetY = (stageHeight - (contentHeight * minimapScale)) / 2;
1197
+ const minimapElement = getState(chartElement, 'minimapElement');
1198
+ minimapElement.style.transform = 'translate(' + chartContainerElement.scrollLeft + 'px, ' + chartContainerElement.scrollTop + 'px)';
1199
+
1200
+ minimapContentElement.style.width = contentWidth + 'px';
1201
+ minimapContentElement.style.height = contentHeight + 'px';
1202
+ minimapContentElement.style.transformOrigin = '0 0';
1203
+ minimapContentElement.style.transform = 'translate(' + minimapOffsetX + 'px, ' + minimapOffsetY + 'px) scale(' + minimapScale + ')';
1204
+
1205
+ if (forceRefresh || !minimapContentElement.firstElementChild) {
1206
+ renderMinimapNodes(chartElement, minimapContentElement, chartRect, chartTransform.scale || 1);
1207
+ }
1208
+
1209
+ viewportLeft = clampValue((containerRect.left - chartRect.left) / chartTransform.scale, 0, Math.max(contentWidth, 0));
1210
+ viewportTop = clampValue((containerRect.top - chartRect.top) / chartTransform.scale, 0, Math.max(contentHeight, 0));
1211
+ viewportWidth = Math.min(contentWidth, containerRect.width / chartTransform.scale);
1212
+ viewportHeight = Math.min(contentHeight, containerRect.height / chartTransform.scale);
1213
+
1214
+ minimapViewportElement.style.width = Math.max(viewportWidth * minimapScale, 12) + 'px';
1215
+ minimapViewportElement.style.height = Math.max(viewportHeight * minimapScale, 12) + 'px';
1216
+ minimapViewportElement.style.transform = 'translate(' + (minimapOffsetX + (viewportLeft * minimapScale)) + 'px, ' + (minimapOffsetY + (viewportTop * minimapScale)) + 'px)';
1217
+
1218
+ setState(chartElement, 'minimapScale', minimapScale);
1219
+ setState(chartElement, 'minimapOffsetX', minimapOffsetX);
1220
+ setState(chartElement, 'minimapOffsetY', minimapOffsetY);
1221
+ setState(chartElement, 'minimapContentWidth', contentWidth);
1222
+ setState(chartElement, 'minimapContentHeight', contentHeight);
1223
+ },
1224
+ //
1225
+ panStartHandler: function (e) {
1226
+ const orgChart = this;
1227
+ const nativeEvent = e.originalEvent || e;
1228
+ const chartElement = getElement(e.chart || nativeEvent.currentTarget);
1229
+ const targetElement = getElement(nativeEvent.target);
1230
+ let lastTf;
1231
+ let existingPanMoveHandler;
1232
+ let panMoveHandler;
1233
+
1234
+ if (!chartElement) {
1235
+ return;
1236
+ }
1237
+
1238
+ if ((targetElement && typeof targetElement.closest === 'function' && targetElement.closest('.node')) || (nativeEvent.touches && nativeEvent.touches.length > 1)) {
1239
+ setState(chartElement, 'panning', false);
1240
+ return;
1241
+ } else {
1242
+ chartElement.style.cursor = 'move';
1243
+ setState(chartElement, 'panning', true);
1244
+ }
1245
+ let lastX = 0;
1246
+ let lastY = 0;
1247
+ lastTf = chartElement.style.transform || getWindow().getComputedStyle(chartElement).transform || 'none';
1248
+ if (lastTf !== 'none') {
1249
+ const temp = lastTf.split(',');
1250
+ if (lastTf.indexOf('3d') === -1) {
1251
+ lastX = parseFloat(temp[4]) || 0;
1252
+ lastY = parseFloat(temp[5]) || 0;
1253
+ } else {
1254
+ lastX = parseFloat(temp[12]) || 0;
1255
+ lastY = parseFloat(temp[13]) || 0;
1256
+ }
1257
+ }
1258
+ let startX = 0;
1259
+ let startY = 0;
1260
+ if (!nativeEvent.targetTouches) { // pand on desktop
1261
+ startX = (typeof nativeEvent.pageX === 'number' ? nativeEvent.pageX : nativeEvent.clientX) - lastX;
1262
+ startY = (typeof nativeEvent.pageY === 'number' ? nativeEvent.pageY : nativeEvent.clientY) - lastY;
1263
+ } else if (nativeEvent.targetTouches.length === 1) { // pan on mobile device
1264
+ startX = nativeEvent.targetTouches[0].pageX - lastX;
1265
+ startY = nativeEvent.targetTouches[0].pageY - lastY;
1266
+ } else if (nativeEvent.targetTouches.length > 1) {
1267
+ return;
1268
+ }
1269
+
1270
+ existingPanMoveHandler = getState(chartElement, 'panMoveHandler');
1271
+ if (existingPanMoveHandler) {
1272
+ chartElement.removeEventListener('mousemove', existingPanMoveHandler);
1273
+ chartElement.removeEventListener('touchmove', existingPanMoveHandler);
1274
+ }
1275
+
1276
+ panMoveHandler = function (e) {
1277
+ let currentTransform;
1278
+ let matrix;
1279
+
1280
+ if (!getState(chartElement, 'panning')) {
1281
+ return;
1282
+ }
1283
+ let newX = 0;
1284
+ let newY = 0;
1285
+ if (!e.targetTouches) { // pand on desktop
1286
+ newX = (typeof e.pageX === 'number' ? e.pageX : e.clientX) - startX;
1287
+ newY = (typeof e.pageY === 'number' ? e.pageY : e.clientY) - startY;
1288
+ } else if (e.targetTouches.length === 1) { // pan on mobile device
1289
+ newX = e.targetTouches[0].pageX - startX;
1290
+ newY = e.targetTouches[0].pageY - startY;
1291
+ } else if (e.targetTouches.length > 1) {
1292
+ return;
1293
+ }
1294
+ currentTransform = chartElement.style.transform || getWindow().getComputedStyle(chartElement).transform || 'none';
1295
+ if (currentTransform === 'none') {
1296
+ if (currentTransform.indexOf('3d') === -1) {
1297
+ chartElement.style.transform = 'matrix(1, 0, 0, 1, ' + newX + ', ' + newY + ')';
1298
+ } else {
1299
+ chartElement.style.transform = 'matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, ' + newX + ', ' + newY + ', 0, 1)';
1300
+ }
1301
+ } else {
1302
+ matrix = currentTransform.split(',');
1303
+ if (currentTransform.indexOf('3d') === -1) {
1304
+ matrix[4] = ' ' + newX;
1305
+ matrix[5] = ' ' + newY + ')';
1306
+ } else {
1307
+ matrix[12] = ' ' + newX;
1308
+ matrix[13] = ' ' + newY;
1309
+ }
1310
+ chartElement.style.transform = matrix.join(',');
1311
+ }
1312
+ orgChart.updateMinimap();
1313
+ };
1314
+
1315
+ setState(chartElement, 'panMoveHandler', panMoveHandler);
1316
+ chartElement.addEventListener('mousemove', panMoveHandler);
1317
+ chartElement.addEventListener('touchmove', panMoveHandler);
1318
+ },
1319
+ //
1320
+ panEndHandler: function (e) {
1321
+ const nativeEvent = e.originalEvent || e;
1322
+ const chartElement = getElement(e.chart || nativeEvent.chart || nativeEvent.currentTarget);
1323
+ let panMoveHandler;
1324
+
1325
+ if (!chartElement) {
1326
+ return;
1327
+ }
1328
+
1329
+ if (getState(chartElement, 'panning')) {
1330
+ panMoveHandler = getState(chartElement, 'panMoveHandler');
1331
+ setState(chartElement, 'panning', false);
1332
+ chartElement.style.cursor = 'default';
1333
+ if (panMoveHandler) {
1334
+ chartElement.removeEventListener('mousemove', panMoveHandler);
1335
+ chartElement.removeEventListener('touchmove', panMoveHandler);
1336
+ setState(chartElement, 'panMoveHandler', null);
1337
+ }
1338
+ }
1339
+ },
1340
+ //
1341
+ bindPan: function () {
1342
+ const orgChart = this;
1343
+ const chartContainerElement = this.chartContainer;
1344
+ const chartElement = this.chart;
1345
+ let panStartBoundHandler;
1346
+ let panEndBoundHandler;
1347
+
1348
+ if (!chartContainerElement || !chartElement) {
1349
+ return;
1350
+ }
1351
+
1352
+ this.unbindPan();
1353
+ chartContainerElement.style.overflow = 'hidden';
1354
+ panStartBoundHandler = function (event) {
1355
+ orgChart.panStartHandler({
1356
+ chart: chartElement,
1357
+ originalEvent: event
1358
+ });
1359
+ };
1360
+ panEndBoundHandler = function () {
1361
+ orgChart.panEndHandler({
1362
+ chart: chartElement
1363
+ });
1364
+ };
1365
+
1366
+ setState(chartElement, 'panStartBoundHandler', panStartBoundHandler);
1367
+ setState(chartElement, 'panEndBoundHandler', panEndBoundHandler);
1368
+ chartElement.addEventListener('mousedown', panStartBoundHandler);
1369
+ chartElement.addEventListener('touchstart', panStartBoundHandler);
1370
+ getDocument().addEventListener('mouseup', panEndBoundHandler);
1371
+ getDocument().addEventListener('touchend', panEndBoundHandler);
1372
+ },
1373
+ //
1374
+ unbindPan: function () {
1375
+ const chartContainerElement = this.chartContainer;
1376
+ const chartElement = this.chart;
1377
+ let panStartBoundHandler;
1378
+ let panEndBoundHandler;
1379
+
1380
+ if (!chartContainerElement || !chartElement) {
1381
+ return;
1382
+ }
1383
+
1384
+ chartContainerElement.style.overflow = 'auto';
1385
+ panStartBoundHandler = getState(chartElement, 'panStartBoundHandler');
1386
+ panEndBoundHandler = getState(chartElement, 'panEndBoundHandler');
1387
+ if (panStartBoundHandler) {
1388
+ chartElement.removeEventListener('mousedown', panStartBoundHandler);
1389
+ chartElement.removeEventListener('touchstart', panStartBoundHandler);
1390
+ setState(chartElement, 'panStartBoundHandler', null);
1391
+ }
1392
+ if (panEndBoundHandler) {
1393
+ getDocument().removeEventListener('mouseup', panEndBoundHandler);
1394
+ getDocument().removeEventListener('touchend', panEndBoundHandler);
1395
+ setState(chartElement, 'panEndBoundHandler', null);
1396
+ }
1397
+ },
1398
+ //
1399
+ zoomWheelHandler: function (e) {
1400
+ const nativeEvent = e.originalEvent || e;
1401
+ const orgChart = e.orgChart;
1402
+ const chartContainerElement = orgChart ? orgChart.chartContainer : null;
1403
+ const chartContainerRect = chartContainerElement && typeof chartContainerElement.getBoundingClientRect === 'function'
1404
+ ? chartContainerElement.getBoundingClientRect()
1405
+ : null;
1406
+ const hasPointerPosition = nativeEvent && typeof nativeEvent.clientX === 'number' && typeof nativeEvent.clientY === 'number';
1407
+ let zoomAnchor = null;
1408
+
1409
+ if (!orgChart || !orgChart.chart || !chartContainerElement) {
1410
+ return;
1411
+ }
1412
+
1413
+ if (chartContainerRect && hasPointerPosition) {
1414
+ if (nativeEvent.clientX < chartContainerRect.left || nativeEvent.clientX > chartContainerRect.right
1415
+ || nativeEvent.clientY < chartContainerRect.top || nativeEvent.clientY > chartContainerRect.bottom) {
1416
+ return;
1417
+ }
1418
+
1419
+ zoomAnchor = {
1420
+ x: nativeEvent.clientX,
1421
+ y: nativeEvent.clientY
1422
+ };
1423
+ }
1424
+
1425
+ if (nativeEvent && typeof nativeEvent.preventDefault === 'function') {
1426
+ nativeEvent.preventDefault();
1427
+ }
1428
+ const newScale = 1 + (nativeEvent.deltaY > 0 ? -0.2 : 0.2);
1429
+ orgChart.setChartScale(orgChart.chart, newScale, zoomAnchor);
1430
+ },
1431
+ //
1432
+ zoomStartHandler: function (e) {
1433
+ let orgChart;
1434
+ let chartElement;
1435
+ let dist;
1436
+ let pinchCenter;
1437
+ const nativeEvent = e.originalEvent || e;
1438
+
1439
+ if(nativeEvent.touches && nativeEvent.touches.length === 2) {
1440
+ orgChart = e.orgChart;
1441
+ chartElement = orgChart.chart;
1442
+ if (!chartElement) {
1443
+ return;
1444
+ }
1445
+ setState(chartElement, 'pinching', true);
1446
+ dist = orgChart.getPinchDist(nativeEvent);
1447
+ pinchCenter = orgChart.getPinchCenter(nativeEvent);
1448
+ setState(chartElement, 'pinchDistStart', dist);
1449
+ setState(chartElement, 'pinchDistEnd', dist);
1450
+ setState(chartElement, 'pinchCenterStart', pinchCenter);
1451
+ setState(chartElement, 'pinchCenterEnd', pinchCenter);
1452
+ }
1453
+ },
1454
+ zoomingHandler: function (e) {
1455
+ const nativeEvent = e.originalEvent || e;
1456
+ const orgChart = e.orgChart;
1457
+ const chartElement = orgChart.chart;
1458
+ let dist;
1459
+ let pinchCenter;
1460
+
1461
+ if(chartElement && getState(chartElement, 'pinching') && nativeEvent.touches && nativeEvent.touches.length === 2) {
1462
+ dist = orgChart.getPinchDist(nativeEvent);
1463
+ pinchCenter = orgChart.getPinchCenter(nativeEvent);
1464
+ setState(chartElement, 'pinchDistEnd', dist);
1465
+ setState(chartElement, 'pinchCenterEnd', pinchCenter);
1466
+ }
1467
+ },
1468
+ zoomEndHandler: function (e) {
1469
+ const orgChart = e.orgChart;
1470
+ const chartElement = orgChart.chart;
1471
+ let diff;
1472
+ let pinchCenter;
1473
+
1474
+ if(chartElement && getState(chartElement, 'pinching')) {
1475
+ setState(chartElement, 'pinching', false);
1476
+ diff = getState(chartElement, 'pinchDistEnd') - getState(chartElement, 'pinchDistStart');
1477
+ pinchCenter = getState(chartElement, 'pinchCenterEnd') || getState(chartElement, 'pinchCenterStart') || null;
1478
+ if (diff > 0) {
1479
+ orgChart.setChartScale(orgChart.chart, 1.2, pinchCenter);
1480
+ } else if (diff < 0) {
1481
+ orgChart.setChartScale(orgChart.chart, 0.8, pinchCenter);
1482
+ }
1483
+ }
1484
+ },
1485
+ //
1486
+ bindZoom: function () {
1487
+ const orgChart = this;
1488
+ const chartContainerElement = this.chartContainer;
1489
+ const chartElement = this.chart;
1490
+ let zoomWheelBoundHandler;
1491
+ let zoomStartBoundHandler;
1492
+ let zoomingBoundHandler;
1493
+ let zoomEndBoundHandler;
1494
+
1495
+ if (!chartContainerElement || !chartElement) {
1496
+ return;
1497
+ }
1498
+
1499
+ this.unbindZoom();
1500
+ zoomWheelBoundHandler = function (event) {
1501
+ orgChart.zoomWheelHandler({
1502
+ orgChart: orgChart,
1503
+ originalEvent: event
1504
+ });
1505
+ };
1506
+ zoomStartBoundHandler = function (event) {
1507
+ orgChart.zoomStartHandler({
1508
+ orgChart: orgChart,
1509
+ originalEvent: event
1510
+ });
1511
+ };
1512
+ zoomingBoundHandler = function (event) {
1513
+ orgChart.zoomingHandler({
1514
+ orgChart: orgChart,
1515
+ originalEvent: event
1516
+ });
1517
+ };
1518
+ zoomEndBoundHandler = function () {
1519
+ orgChart.zoomEndHandler({
1520
+ orgChart: orgChart
1521
+ });
1522
+ };
1523
+
1524
+ setState(chartElement, 'zoomWheelBoundHandler', zoomWheelBoundHandler);
1525
+ setState(chartElement, 'zoomStartBoundHandler', zoomStartBoundHandler);
1526
+ setState(chartElement, 'zoomingBoundHandler', zoomingBoundHandler);
1527
+ setState(chartElement, 'zoomEndBoundHandler', zoomEndBoundHandler);
1528
+ chartContainerElement.addEventListener('wheel', zoomWheelBoundHandler);
1529
+ chartContainerElement.addEventListener('touchstart', zoomStartBoundHandler);
1530
+ getDocument().addEventListener('touchmove', zoomingBoundHandler);
1531
+ getDocument().addEventListener('touchend', zoomEndBoundHandler);
1532
+ },
1533
+ unbindZoom: function () {
1534
+ const chartContainerElement = this.chartContainer;
1535
+ const chartElement = this.chart;
1536
+ let zoomWheelBoundHandler;
1537
+ let zoomStartBoundHandler;
1538
+ let zoomingBoundHandler;
1539
+ let zoomEndBoundHandler;
1540
+
1541
+ if (!chartContainerElement || !chartElement) {
1542
+ return;
1543
+ }
1544
+
1545
+ zoomWheelBoundHandler = getState(chartElement, 'zoomWheelBoundHandler');
1546
+ zoomStartBoundHandler = getState(chartElement, 'zoomStartBoundHandler');
1547
+ zoomingBoundHandler = getState(chartElement, 'zoomingBoundHandler');
1548
+ zoomEndBoundHandler = getState(chartElement, 'zoomEndBoundHandler');
1549
+ if (zoomWheelBoundHandler) {
1550
+ chartContainerElement.removeEventListener('wheel', zoomWheelBoundHandler);
1551
+ setState(chartElement, 'zoomWheelBoundHandler', null);
1552
+ }
1553
+ if (zoomStartBoundHandler) {
1554
+ chartContainerElement.removeEventListener('touchstart', zoomStartBoundHandler);
1555
+ setState(chartElement, 'zoomStartBoundHandler', null);
1556
+ }
1557
+ if (zoomingBoundHandler) {
1558
+ getDocument().removeEventListener('touchmove', zoomingBoundHandler);
1559
+ setState(chartElement, 'zoomingBoundHandler', null);
1560
+ }
1561
+ if (zoomEndBoundHandler) {
1562
+ getDocument().removeEventListener('touchend', zoomEndBoundHandler);
1563
+ setState(chartElement, 'zoomEndBoundHandler', null);
1564
+ }
1565
+ },
1566
+ //
1567
+ getPinchDist: function (e) {
1568
+ const nativeEvent = e.originalEvent || e;
1569
+
1570
+ return Math.sqrt((nativeEvent.touches[0].clientX - nativeEvent.touches[1].clientX) * (nativeEvent.touches[0].clientX - nativeEvent.touches[1].clientX) +
1571
+ (nativeEvent.touches[0].clientY - nativeEvent.touches[1].clientY) * (nativeEvent.touches[0].clientY - nativeEvent.touches[1].clientY));
1572
+ },
1573
+ getPinchCenter: function (e) {
1574
+ const nativeEvent = e.originalEvent || e;
1575
+
1576
+ return {
1577
+ x: (nativeEvent.touches[0].clientX + nativeEvent.touches[1].clientX) / 2,
1578
+ y: (nativeEvent.touches[0].clientY + nativeEvent.touches[1].clientY) / 2
1579
+ };
1580
+ },
1581
+ //
1582
+ setChartScale: function (chartEl, newScale, zoomAnchor) {
1583
+ const chartElement = getElement(chartEl);
1584
+ const chartContainerElement = this.chartContainer;
1585
+ let opts;
1586
+ let chartRect;
1587
+ let chartTransform;
1588
+ let transformedRect;
1589
+ let targetScale = 1;
1590
+ let containerRect;
1591
+ let targetTranslateX;
1592
+ let targetTranslateY;
1593
+
1594
+ if (!chartElement) {
1595
+ return;
1596
+ }
1597
+
1598
+ opts = getState(chartElement, 'options');
1599
+ chartTransform = getChartTransform(chartElement);
1600
+
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) {
1606
+ return;
1607
+ }
1608
+
1609
+ targetTranslateX = chartTransform.translateX;
1610
+ targetTranslateY = chartTransform.translateY;
1611
+ chartRect = typeof chartElement.getBoundingClientRect === 'function'
1612
+ ? chartElement.getBoundingClientRect()
1613
+ : null;
1614
+ if (!zoomAnchor && chartContainerElement && typeof chartContainerElement.getBoundingClientRect === 'function') {
1615
+ containerRect = chartContainerElement.getBoundingClientRect();
1616
+ zoomAnchor = {
1617
+ x: containerRect.left + (containerRect.width / 2),
1618
+ y: containerRect.top + (containerRect.height / 2)
1619
+ };
1620
+ }
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;
1627
+ }
1628
+
1629
+ applyChartTransform(chartElement, chartTransform, targetScale, targetTranslateX, targetTranslateY);
1630
+ this.updateMinimap();
1631
+ },
1632
+ //
1633
+ buildJsonDS: function (liEl) {
1634
+ const orgChart = this;
1635
+ const liElement = getElement(liEl);
1636
+ let childListElement;
1637
+ let jsonNodeData;
1638
+
1639
+ if (!liElement) {
1640
+ return {};
1641
+ }
1642
+
1643
+ childListElement = Array.from(liElement.children || []).find(function (childEl) {
1644
+ return childEl.tagName === 'UL';
1645
+ }) || null;
1646
+
1647
+ jsonNodeData = {
1648
+ 'name': ((liElement.childNodes && liElement.childNodes[0]) ? liElement.childNodes[0].textContent : '').trim()
1649
+ };
1650
+ forEachValue(getDatasetSnapshot(liElement), function(key, value) {
1651
+ jsonNodeData[key] = value;
1652
+ });
1653
+ if (childListElement) {
1654
+ Array.from(childListElement.children).forEach(function (childEl) {
1655
+ if (childEl.tagName !== 'LI') {
1656
+ return;
1657
+ }
1658
+ if (!jsonNodeData.children) {
1659
+ jsonNodeData.children = [];
1660
+ }
1661
+ jsonNodeData.children.push(orgChart.buildJsonDS(childEl));
1662
+ });
1663
+ }
1664
+
1665
+ return jsonNodeData;
1666
+ },
1667
+ loopChart: function (chartEl, includeNodeData) {
1668
+ const orgChart = this;
1669
+ const chartElement = getElement(chartEl);
1670
+ let nodeElement;
1671
+ let hierarchyNodeData;
1672
+
1673
+ includeNodeData = (includeNodeData !== null && includeNodeData !== undefined) ? includeNodeData : false;
1674
+
1675
+ if (!chartElement) {
1676
+ return {};
1677
+ }
1678
+
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) {
1683
+ return childEl.classList && childEl.classList.contains('node');
1684
+ })
1685
+ : chartElement.querySelector('.node');
1686
+ if (!nodeElement) {
1687
+ return {};
1688
+ }
1689
+
1690
+ hierarchyNodeData = { 'id': nodeElement.id };
1691
+ if (includeNodeData) {
1692
+ forEachValue(getState(nodeElement, 'nodeData'), function (key, value) {
1693
+ hierarchyNodeData[key] = value;
1694
+ });
1695
+ }
1696
+ const childNodeElements = this.getChildren(nodeElement);
1697
+ if (childNodeElements.length) {
1698
+ hierarchyNodeData.children = childNodeElements.map(function (childNodeElement) {
1699
+ return orgChart.loopChart(childNodeElement, includeNodeData);
1700
+ });
1701
+ }
1702
+
1703
+ return hierarchyNodeData;
1704
+ },
1705
+ //
1706
+ getHierarchy: function (includeNodeData) {
1707
+ const chartElement = this.chart;
1708
+ let nodeElements;
1709
+
1710
+ includeNodeData = (includeNodeData !== null && includeNodeData !== undefined) ? includeNodeData : false;
1711
+ if (!chartElement) {
1712
+ return 'Error: orgchart does not exist'
1713
+ } else {
1714
+ nodeElements = chartElement ? Array.from(chartElement.querySelectorAll('.node')) : [];
1715
+ if (!nodeElements.length) {
1716
+ return 'Error: nodes do not exist'
1717
+ } else {
1718
+ if (nodeElements.some(function (nodeEl) { return !nodeEl.id; })) {
1719
+ return 'Error: All nodes of orghcart to be exported must have data-id attribute!';
1720
+ }
1721
+ }
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
+ }
1744
+ return this.loopChart(chartElement, includeNodeData);
1745
+ },
1746
+ // detect the exist/display state of related node
1747
+ getNodeState: function (nodeEl, relation) {
1748
+ const nodeElement = getElement(nodeEl);
1749
+
1750
+ if (!nodeElement || !nodeElement.classList || !nodeElement.classList.contains('node')) {
1751
+ return { 'exist': false, 'visible': false };
1752
+ }
1753
+
1754
+ relation = relation || 'self';
1755
+ if (relation === 'parent') {
1756
+ const parentElement = this.getParent(nodeElement);
1757
+ return { exist: !!parentElement, visible: !!parentElement && this.getNodeState(parentElement).visible };
1758
+ } else if (relation === 'children') {
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
+ };
1767
+ } else if (relation === 'siblings') {
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
+ };
1774
+ }
1775
+ const collapsedCompactAncestor = nodeElement.parentElement
1776
+ ? nodeElement.parentElement.closest('.node.compact:not(.looseMode)') : null;
1777
+ return { exist: true, visible: !nodeElement.closest('.hidden') && !collapsedCompactAncestor };
1778
+ },
1779
+ getParent: function (nodeEl) {
1780
+ return this.getRelatedNodes(nodeEl, 'parent');
1781
+ },
1782
+ getChildren: function (nodeEl) {
1783
+ return this.getRelatedNodes(nodeEl, 'children');
1784
+ },
1785
+ getSiblings: function (nodeEl) {
1786
+ return this.getRelatedNodes(nodeEl, 'siblings');
1787
+ },
1788
+ // find the related nodes
1789
+ getRelatedNodes: function (nodeEl, relation) {
1790
+ const nodeElement = getElement(nodeEl);
1791
+ let nodesContainerEl;
1792
+ let childNodesContainerElement;
1793
+ let hierarchyEl;
1794
+
1795
+ if (!nodeElement || !nodeElement.classList || !nodeElement.classList.contains('node')) {
1796
+ return relation === 'parent' ? null : [];
1797
+ }
1798
+
1799
+ if (relation === 'parent') {
1800
+ if (nodeElement.parentElement && nodeElement.parentElement.classList.contains('node')) {
1801
+ return nodeElement.parentElement;
1802
+ }
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
+ }
1810
+ return nodesContainerEl ? nodesContainerEl.previousElementSibling : null;
1811
+ } else if (relation === 'children') {
1812
+ const nestedNodeElements = Array.from(nodeElement.children).filter(function (childElement) {
1813
+ return childElement.classList.contains('node');
1814
+ });
1815
+ if (nestedNodeElements.length) {
1816
+ return nestedNodeElements;
1817
+ }
1818
+ childNodesContainerElement = getChildNodesContainer(nodeElement);
1819
+
1820
+ if (!childNodesContainerElement) {
1821
+ return [];
1822
+ }
1823
+
1824
+ return Array.from(childNodesContainerElement.children).filter(function (childEl) {
1825
+ return childEl.classList && childEl.classList.contains('hierarchy');
1826
+ }).flatMap(function (childHierarchyEl) {
1827
+ return Array.from(childHierarchyEl.children).filter(function (element) { return element.classList.contains('node'); });
1828
+ });
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
+ }
1835
+ hierarchyEl = nodeElement.closest('.hierarchy');
1836
+ if (!hierarchyEl || !hierarchyEl.parentElement) {
1837
+ return [];
1838
+ }
1839
+
1840
+ return Array.from(hierarchyEl.parentElement.children).filter(function (siblingEl) {
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
+ });
1847
+ }
1848
+
1849
+ return [];
1850
+ },
1851
+ hideParentEnd: function (event) {
1852
+ const nativeEvent = event.originalEvent || event;
1853
+ const animatedNodeElement = getElement(event.animatedNode || nativeEvent.animatedNode || nativeEvent.target);
1854
+ const parentElement = getElement(event.parent || nativeEvent.parent);
1855
+
1856
+ if (animatedNodeElement && animatedNodeElement.classList) {
1857
+ animatedNodeElement.classList.remove('sliding');
1858
+ }
1859
+ if (parentElement && parentElement.classList) {
1860
+ parentElement.classList.add('hidden');
1861
+ }
1862
+ },
1863
+ // recursively hide the ancestor node and sibling nodes of the specified node
1864
+ hideParent: function (nodeEl) {
1865
+ const nodeElement = getElement(nodeEl);
1866
+ let parentEl;
1867
+ let chartElement;
1868
+ let hierarchyElement;
1869
+
1870
+ if (!nodeElement) {
1871
+ return;
1872
+ }
1873
+
1874
+ parentEl = this.getParent(nodeElement);
1875
+ chartElement = nodeElement.closest('.orgchart');
1876
+ hierarchyElement = nodeElement.closest('.hierarchy');
1877
+ if (parentEl && parentEl.querySelector && parentEl.querySelector('.spinner')) {
1878
+ if (chartElement) {
1879
+ setState(chartElement, 'inAjax', false);
1880
+ }
1881
+ }
1882
+
1883
+ if (this.getNodeState(nodeElement, 'siblings').visible) {
1884
+ this.hideSiblings(nodeElement);
1885
+ }
1886
+
1887
+ if (hierarchyElement) {
1888
+ hierarchyElement.classList.add('isAncestorsCollapsed');
1889
+ }
1890
+ if (this.getNodeState(parentEl).visible) {
1891
+ parentEl.classList.add('sliding', 'slide-down');
1892
+ parentEl.addEventListener('transitionend', function (event) {
1893
+ event.animatedNode = parentEl;
1894
+ event.parent = parentEl;
1895
+ OrgChart.prototype.hideParentEnd(event);
1896
+ }, { once: true });
1897
+ }
1898
+
1899
+ if (this.getNodeState(parentEl, 'parent').visible) {
1900
+ this.hideParent(parentEl);
1901
+ }
1902
+ },
1903
+ showParentEnd: function (event) {
1904
+ const nativeEvent = event.originalEvent || event;
1905
+ const animatedNodeElement = getElement(event.animatedNode || nativeEvent.animatedNode || nativeEvent.target);
1906
+ const nodeElement = getElement(event.node || nativeEvent.node);
1907
+ let topEdgeElement;
1908
+
1909
+ if (animatedNodeElement && animatedNodeElement.classList) {
1910
+ animatedNodeElement.classList.remove('sliding');
1911
+ }
1912
+ if (this.isInAction(nodeElement)) {
1913
+ topEdgeElement = nodeElement ? Array.from(nodeElement.children || []).find(function (childEl) {
1914
+ return childEl.classList && childEl.classList.contains('topEdge');
1915
+ }) || null : null;
1916
+ this.switchVerticalArrow(topEdgeElement);
1917
+ }
1918
+ },
1919
+ // show the parent node of the specified node
1920
+ showParent: function (nodeEl) {
1921
+ const orgChart = this;
1922
+ const nodeElement = getElement(nodeEl);
1923
+ let parentEl;
1924
+ let hierarchyElement;
1925
+
1926
+ if (!nodeElement) {
1927
+ return;
1928
+ }
1929
+
1930
+ parentEl = this.getParent(nodeElement);
1931
+ hierarchyElement = nodeElement.closest('.hierarchy');
1932
+ if (parentEl && parentEl.classList) {
1933
+ parentEl.classList.remove('hidden');
1934
+ }
1935
+ if (hierarchyElement) {
1936
+ hierarchyElement.classList.remove('isAncestorsCollapsed');
1937
+ }
1938
+ this.repaint(parentEl);
1939
+ if (parentEl && parentEl.classList) {
1940
+ parentEl.classList.add('sliding');
1941
+ parentEl.classList.remove('slide-down');
1942
+ parentEl.addEventListener('transitionend', function (event) {
1943
+ event.animatedNode = parentEl;
1944
+ event.node = nodeElement;
1945
+ orgChart.showParentEnd(event);
1946
+ }, { once: true });
1947
+ }
1948
+ },
1949
+ stopAjax: function (nodeLevelEl) {
1950
+ const nodeLevelElement = getElement(nodeLevelEl);
1951
+ let chartElement;
1952
+
1953
+ if (!nodeLevelElement || !nodeLevelElement.querySelector('.spinner')) {
1954
+ return;
1955
+ }
1956
+
1957
+ chartElement = nodeLevelElement.closest('.orgchart');
1958
+ if (chartElement) {
1959
+ setState(chartElement, 'inAjax', false);
1960
+ }
1961
+ },
1962
+ isVisibleNode: function (index, elem) {
1963
+ return this.getNodeState(elem).visible;
1964
+ },
1965
+ isCompactDescendant: function (index, elem) {
1966
+ return !!(elem && elem.parentElement && elem.parentElement.matches('.node.compact'));
1967
+ },
1968
+ // do some necessary cleanup tasks when hide animation is finished
1969
+ hideChildrenEnd: function (event) {
1970
+ const nativeEvent = event.originalEvent || event;
1971
+ const nodeElement = getElement(event.node || nativeEvent.node);
1972
+ const animatedNodeElements = event.animatedNodes || nativeEvent.animatedNodes;
1973
+ let bottomEdgeElement;
1974
+
1975
+ getElements(animatedNodeElements).forEach(function (animatedNodeEl) {
1976
+ let lowerNodesElement;
1977
+
1978
+ if (animatedNodeEl.classList) {
1979
+ animatedNodeEl.classList.remove('sliding');
1980
+ }
1981
+ lowerNodesElement = animatedNodeEl && typeof animatedNodeEl.closest === 'function'
1982
+ ? animatedNodeEl.closest('.nodes')
1983
+ : null;
1984
+ if (lowerNodesElement && lowerNodesElement.classList) {
1985
+ lowerNodesElement.classList.add('hidden');
1986
+ }
1987
+ });
1988
+ if (this.isInAction(nodeElement)) {
1989
+ bottomEdgeElement = nodeElement ? Array.from(nodeElement.children || []).find(function (childEl) {
1990
+ return childEl.classList && childEl.classList.contains('bottomEdge');
1991
+ }) || null : null;
1992
+ this.switchVerticalArrow(bottomEdgeElement);
1993
+ }
1994
+ },
1995
+ // recursively hide the descendant nodes of the specified node
1996
+ hideChildren: function (nodeEl) {
1997
+ const orgChart = this;
1998
+ const nodeElement = getElement(nodeEl);
1999
+ let hierarchyElement;
2000
+ let lowerLevelElement;
2001
+ let animatedNodeElements;
2002
+ let isVerticalDesc;
2003
+
2004
+ if (!nodeElement) {
2005
+ return;
2006
+ }
2007
+
2008
+ hierarchyElement = nodeElement.closest('.hierarchy');
2009
+ lowerLevelElement = getChildNodesContainer(nodeElement);
2010
+
2011
+ if (!lowerLevelElement) {
2012
+ return;
2013
+ }
2014
+
2015
+ if (hierarchyElement) {
2016
+ hierarchyElement.classList.add('isChildrenCollapsed');
2017
+ }
2018
+ this.stopAjax(lowerLevelElement);
2019
+ animatedNodeElements = Array.from(lowerLevelElement.querySelectorAll('.node')).filter(function (visibleNodeEl, index) {
2020
+ return this.isVisibleNode(index, visibleNodeEl) && !this.isCompactDescendant(index, visibleNodeEl);
2021
+ }, this);
2022
+ isVerticalDesc = lowerLevelElement.classList.contains('vertical');
2023
+ if (!isVerticalDesc) {
2024
+ animatedNodeElements.forEach(function (animatedNodeEl) {
2025
+ const animatedHierarchyElement = animatedNodeEl.closest('.hierarchy');
2026
+
2027
+ if (animatedHierarchyElement) {
2028
+ animatedHierarchyElement.classList.add('isCollapsedDescendant');
2029
+ }
2030
+ });
2031
+ }
2032
+ if (lowerLevelElement.classList.contains('vertical') || lowerLevelElement.querySelector('.vertical')) {
2033
+ animatedNodeElements.forEach(function (animatedNodeEl) {
2034
+ Array.from(animatedNodeEl.querySelectorAll('.' + this.options.icons.expanded)).forEach(function (expandedIconEl) {
2035
+ expandedIconEl.classList.remove(this.options.icons.expanded);
2036
+ expandedIconEl.classList.add(this.options.icons.collapsed);
2037
+ }, this);
2038
+ }, this);
2039
+ }
2040
+ this.repaint(animatedNodeElements[0]);
2041
+ animatedNodeElements.forEach(function (animatedNodeEl) {
2042
+ animatedNodeEl.classList.add('sliding', 'slide-up');
2043
+ });
2044
+ if (animatedNodeElements[0]) {
2045
+ animatedNodeElements[0].addEventListener('transitionend', function (event) {
2046
+ event.node = nodeElement;
2047
+ event.animatedNodes = animatedNodeElements;
2048
+ orgChart.hideChildrenEnd(event);
2049
+ }, { once: true });
2050
+ }
2051
+ },
2052
+ //
2053
+ showChildrenEnd: function (event) {
2054
+ const nativeEvent = event.originalEvent || event;
2055
+ const nodeElement = getElement(event.node || nativeEvent.node);
2056
+ const animatedNodeElements = event.animatedNodes || nativeEvent.animatedNodes;
2057
+ let bottomEdgeElement;
2058
+
2059
+ getElements(animatedNodeElements).forEach(function (animatedNodeEl) {
2060
+ if (animatedNodeEl.classList) {
2061
+ animatedNodeEl.classList.remove('sliding');
2062
+ }
2063
+ });
2064
+ if (this.isInAction(nodeElement)) {
2065
+ bottomEdgeElement = nodeElement ? Array.from(nodeElement.children || []).find(function (childEl) {
2066
+ return childEl.classList && childEl.classList.contains('bottomEdge');
2067
+ }) || null : null;
2068
+ this.switchVerticalArrow(bottomEdgeElement);
2069
+ }
2070
+ },
2071
+ // show the children nodes of the specified node
2072
+ showChildren: function (nodeEl) {
2073
+ const orgChart = this;
2074
+ const nodeElement = getElement(nodeEl);
2075
+ let hierarchyElement;
2076
+ let levelsElement;
2077
+ let isVerticalDesc;
2078
+ let animatedNodeElements;
2079
+
2080
+ if (!nodeElement) {
2081
+ return;
2082
+ }
2083
+
2084
+ hierarchyElement = nodeElement.closest('.hierarchy');
2085
+ levelsElement = getChildNodesContainer(nodeElement);
2086
+
2087
+ if (!levelsElement) {
2088
+ return;
2089
+ }
2090
+
2091
+ if (hierarchyElement) {
2092
+ hierarchyElement.classList.remove('isChildrenCollapsed');
2093
+ }
2094
+ levelsElement.classList.remove('hidden');
2095
+ isVerticalDesc = levelsElement.classList.contains('vertical');
2096
+ animatedNodeElements = isVerticalDesc
2097
+ ? Array.from(levelsElement.querySelectorAll('.node')).filter(function (visibleNodeEl, index) {
2098
+ return this.isVisibleNode(index, visibleNodeEl);
2099
+ }, this)
2100
+ : this.getChildren(nodeElement).filter(function (visibleNodeEl, index) {
2101
+ return this.isVisibleNode(index, visibleNodeEl);
2102
+ }, this);
2103
+ if (!isVerticalDesc) {
2104
+ animatedNodeElements.forEach(function (animatedNodeEl) {
2105
+ const animatedHierarchyElement = animatedNodeEl.closest('.hierarchy');
2106
+
2107
+ if (animatedHierarchyElement && !animatedNodeEl.matches(':only-child')) {
2108
+ animatedHierarchyElement.classList.add('isChildrenCollapsed');
2109
+ }
2110
+ if (animatedHierarchyElement) {
2111
+ animatedHierarchyElement.classList.remove('isCollapsedDescendant');
2112
+ }
2113
+ });
2114
+ }
2115
+ this.repaint(animatedNodeElements[0]);
2116
+ animatedNodeElements.forEach(function (animatedNodeEl) {
2117
+ animatedNodeEl.classList.add('sliding');
2118
+ animatedNodeEl.classList.remove('slide-up');
2119
+ });
2120
+ if (animatedNodeElements[0]) {
2121
+ animatedNodeElements[0].addEventListener('transitionend', function (event) {
2122
+ event.node = nodeElement;
2123
+ event.animatedNodes = animatedNodeElements;
2124
+ orgChart.showChildrenEnd(event);
2125
+ }, { once: true });
2126
+ }
2127
+ },
2128
+ //
2129
+ hideSiblingsEnd: function (event) {
2130
+ const orgChart = this;
2131
+ const nativeEvent = event.originalEvent || event;
2132
+ const nodeElement = getElement(event.node || nativeEvent.node);
2133
+ const nodeContainerElement = getElement(event.nodeContainer || nativeEvent.nodeContainer);
2134
+ let direction = typeof event.direction !== 'undefined' ? event.direction : nativeEvent.direction;
2135
+ const animatedNodeElements = event.animatedNodes || nativeEvent.animatedNodes;
2136
+
2137
+ if (typeof direction === 'undefined') {
2138
+ direction = nativeEvent.direction;
2139
+ }
2140
+ let siblingElements = [];
2141
+ let currentSiblingElement;
2142
+
2143
+ if (nodeContainerElement) {
2144
+ if (direction === 'left') {
2145
+ currentSiblingElement = nodeContainerElement.previousElementSibling;
2146
+ while (currentSiblingElement) {
2147
+ if (!currentSiblingElement.classList.contains('hidden')) {
2148
+ siblingElements.push(currentSiblingElement);
2149
+ }
2150
+ currentSiblingElement = currentSiblingElement.previousElementSibling;
2151
+ }
2152
+ } else if (direction === 'right') {
2153
+ currentSiblingElement = nodeContainerElement.nextElementSibling;
2154
+ while (currentSiblingElement) {
2155
+ if (!currentSiblingElement.classList.contains('hidden')) {
2156
+ siblingElements.push(currentSiblingElement);
2157
+ }
2158
+ currentSiblingElement = currentSiblingElement.nextElementSibling;
2159
+ }
2160
+ } else if (nodeContainerElement.parentElement) {
2161
+ siblingElements = Array.from(nodeContainerElement.parentElement.children).filter(function (childEl) {
2162
+ return childEl !== nodeContainerElement;
2163
+ });
2164
+ }
2165
+ }
2166
+
2167
+ getElements(animatedNodeElements).forEach(function (animatedNodeEl) {
2168
+ if (animatedNodeEl.classList) {
2169
+ animatedNodeEl.classList.remove('sliding');
2170
+ }
2171
+ });
2172
+ siblingElements.forEach(function (siblingElement) {
2173
+ Array.from(siblingElement.querySelectorAll('.node')).filter(function (siblingNodeEl) {
2174
+ return orgChart.isVisibleNode(null, siblingNodeEl);
2175
+ }).slice(1)
2176
+ .forEach(function (siblingNodeEl) {
2177
+ siblingNodeEl.classList.remove('slide-left', 'slide-right');
2178
+ if (!orgChart.options.compact) {
2179
+ siblingNodeEl.classList.add('slide-up');
2180
+ }
2181
+ });
2182
+ Array.from(siblingElement.querySelectorAll('.nodes, .vertical')).forEach(function (descendantEl) {
2183
+ descendantEl.classList.add('hidden');
2184
+ });
2185
+ siblingElement.classList.add('hidden');
2186
+ });
2187
+
2188
+ if (this.isInAction(nodeElement)) {
2189
+ this.switchHorizontalArrow(nodeElement);
2190
+ }
2191
+ },
2192
+ // hide the sibling nodes of the specified node
2193
+ hideSiblings: function (nodeEl, direction) {
2194
+ const orgChart = this;
2195
+ const nodeElement = getElement(nodeEl);
2196
+ let chartElement;
2197
+ let nodeContainerElement;
2198
+ let siblingContainerElements = [];
2199
+ let animatedNodeElements = [];
2200
+
2201
+ if (!nodeElement) {
2202
+ return;
2203
+ }
2204
+
2205
+ chartElement = nodeElement.closest('.orgchart');
2206
+ nodeContainerElement = nodeElement.closest('.hierarchy');
2207
+ if (!nodeContainerElement) {
2208
+ return;
2209
+ }
2210
+
2211
+ nodeContainerElement.classList.add('isSiblingsCollapsed');
2212
+ siblingContainerElements = nodeContainerElement.parentElement
2213
+ ? Array.from(nodeContainerElement.parentElement.children).filter(function (childEl) {
2214
+ return childEl !== nodeContainerElement;
2215
+ })
2216
+ : [];
2217
+
2218
+ if (siblingContainerElements.some(function (siblingContainerEl) {
2219
+ return siblingContainerEl.querySelector('.spinner');
2220
+ })) {
2221
+ if (chartElement) {
2222
+ setState(chartElement, 'inAjax', false);
2223
+ }
2224
+ }
2225
+
2226
+ if (direction) {
2227
+ if (direction === 'left') {
2228
+ let currentPreviousSiblingElement = nodeContainerElement.previousElementSibling;
2229
+
2230
+ nodeContainerElement.classList.add('left-sibs');
2231
+ siblingContainerElements.forEach(function (siblingContainerEl) {
2232
+ if (siblingContainerEl.classList.contains('isSiblingsCollapsed')) {
2233
+ siblingContainerEl.classList.remove('isSiblingsCollapsed', 'left-sibs');
2234
+ }
2235
+ });
2236
+
2237
+ while (currentPreviousSiblingElement) {
2238
+ currentPreviousSiblingElement.classList.add('isCollapsedSibling', 'isChildrenCollapsed');
2239
+ animatedNodeElements = animatedNodeElements.concat(
2240
+ Array.from(currentPreviousSiblingElement.querySelectorAll('.node')).filter(function (visibleNodeEl, index) {
2241
+ return this.isVisibleNode(index, visibleNodeEl);
2242
+ }, this)
2243
+ );
2244
+ currentPreviousSiblingElement = currentPreviousSiblingElement.previousElementSibling;
2245
+ }
2246
+ animatedNodeElements.forEach(function (animatedNodeEl) {
2247
+ animatedNodeEl.classList.add('sliding', 'slide-right');
2248
+ });
2249
+ } else {
2250
+ let currentNextSiblingElement = nodeContainerElement.nextElementSibling;
2251
+
2252
+ nodeContainerElement.classList.add('right-sibs');
2253
+ siblingContainerElements.forEach(function (siblingContainerEl) {
2254
+ if (siblingContainerEl.classList.contains('isSiblingsCollapsed')) {
2255
+ siblingContainerEl.classList.remove('isSiblingsCollapsed', 'right-sibs');
2256
+ }
2257
+ });
2258
+
2259
+ while (currentNextSiblingElement) {
2260
+ currentNextSiblingElement.classList.add('isCollapsedSibling', 'isChildrenCollapsed');
2261
+ animatedNodeElements = animatedNodeElements.concat(
2262
+ Array.from(currentNextSiblingElement.querySelectorAll('.node')).filter(function (visibleNodeEl, index) {
2263
+ return this.isVisibleNode(index, visibleNodeEl);
2264
+ }, this)
2265
+ );
2266
+ currentNextSiblingElement = currentNextSiblingElement.nextElementSibling;
2267
+ }
2268
+ animatedNodeElements.forEach(function (animatedNodeEl) {
2269
+ animatedNodeEl.classList.add('sliding', 'slide-left');
2270
+ });
2271
+ }
2272
+ } else {
2273
+ siblingContainerElements.forEach(function (siblingContainerEl) {
2274
+ const siblingAnimatedNodeElements = Array.from(siblingContainerEl.querySelectorAll('.node')).filter(function (visibleNodeEl, index) {
2275
+ return this.isVisibleNode(index, visibleNodeEl);
2276
+ }, this);
2277
+
2278
+ siblingContainerEl.classList.add('isCollapsedSibling', 'isChildrenCollapsed');
2279
+ animatedNodeElements = animatedNodeElements.concat(siblingAnimatedNodeElements);
2280
+ siblingAnimatedNodeElements.forEach(function (animatedNodeEl) {
2281
+ animatedNodeEl.classList.add('sliding');
2282
+ if (siblingContainerEl.compareDocumentPosition(nodeContainerElement) & Node.DOCUMENT_POSITION_FOLLOWING) {
2283
+ animatedNodeEl.classList.add('slide-right');
2284
+ } else {
2285
+ animatedNodeEl.classList.add('slide-left');
2286
+ }
2287
+ });
2288
+ }, this);
2289
+ }
2290
+
2291
+ if (animatedNodeElements[0]) {
2292
+ animatedNodeElements[0].addEventListener('transitionend', function (event) {
2293
+ event.node = nodeElement;
2294
+ event.nodeContainer = nodeContainerElement;
2295
+ event.direction = direction;
2296
+ event.animatedNodes = animatedNodeElements;
2297
+ orgChart.hideSiblingsEnd(event);
2298
+ }, { once: true });
2299
+ }
2300
+ },
2301
+ //
2302
+ showSiblingsEnd: function (event) {
2303
+ const nativeEvent = event.originalEvent || event;
2304
+ const nodeElement = getElement(event.node || nativeEvent.node);
2305
+ const visibleNodeElements = event.visibleNodes || nativeEvent.visibleNodes;
2306
+ let topEdgeElement;
2307
+
2308
+ getElements(visibleNodeElements).forEach(function (visibleNodeEl) {
2309
+ if (visibleNodeEl.classList) {
2310
+ visibleNodeEl.classList.remove('sliding');
2311
+ }
2312
+ });
2313
+ if (this.isInAction(nodeElement)) {
2314
+ this.switchHorizontalArrow(nodeElement);
2315
+ topEdgeElement = nodeElement ? Array.from(nodeElement.children || []).find(function (childEl) {
2316
+ return childEl.classList && childEl.classList.contains('topEdge');
2317
+ }) || null : null;
2318
+ if (topEdgeElement && topEdgeElement.classList) {
2319
+ topEdgeElement.classList.remove(this.options.icons.expandToUp);
2320
+ topEdgeElement.classList.add(this.options.icons.collapseToDown);
2321
+ }
2322
+ }
2323
+ },
2324
+ //
2325
+ showRelatedParentEnd: function(event) {
2326
+ const nativeEvent = event.originalEvent || event;
2327
+ const animatedNodeElement = getElement(event.animatedNode || nativeEvent.animatedNode || nativeEvent.target);
2328
+
2329
+ if (animatedNodeElement && animatedNodeElement.classList) {
2330
+ animatedNodeElement.classList.remove('sliding');
2331
+ }
2332
+ },
2333
+ // show the sibling nodes of the specified node
2334
+ showSiblings: function (nodeEl, direction) {
2335
+ const orgChart = this;
2336
+ const nodeElement = getElement(nodeEl);
2337
+ let nodeContainerElement;
2338
+ let siblingElements = [];
2339
+ let parentNodesContainerElement;
2340
+ let upperLevelElement;
2341
+ let visibleNodeElements;
2342
+
2343
+ if (!nodeElement) {
2344
+ return;
2345
+ }
2346
+
2347
+ // firstly, show the sibling nodes
2348
+ nodeContainerElement = nodeElement.closest('.hierarchy');
2349
+ if (!nodeContainerElement) {
2350
+ return;
2351
+ }
2352
+
2353
+ if (direction) {
2354
+ if (direction === 'left') {
2355
+ let currentPreviousSiblingElement = nodeContainerElement.previousElementSibling;
2356
+ while (currentPreviousSiblingElement) {
2357
+ currentPreviousSiblingElement.classList.remove('hidden');
2358
+ siblingElements.push(currentPreviousSiblingElement);
2359
+ currentPreviousSiblingElement = currentPreviousSiblingElement.previousElementSibling;
2360
+ }
2361
+ } else {
2362
+ let currentNextSiblingElement = nodeContainerElement.nextElementSibling;
2363
+ while (currentNextSiblingElement) {
2364
+ currentNextSiblingElement.classList.remove('hidden');
2365
+ siblingElements.push(currentNextSiblingElement);
2366
+ currentNextSiblingElement = currentNextSiblingElement.nextElementSibling;
2367
+ }
2368
+ }
2369
+ } else {
2370
+ siblingElements = nodeContainerElement.parentElement
2371
+ ? Array.from(nodeContainerElement.parentElement.children).filter(function (childEl) {
2372
+ return childEl !== nodeContainerElement;
2373
+ })
2374
+ : [];
2375
+ siblingElements.forEach(function (siblingEl) {
2376
+ siblingEl.classList.remove('hidden');
2377
+ });
2378
+ }
2379
+ // secondly, show the lines
2380
+ parentNodesContainerElement = nodeElement.closest('.nodes');
2381
+ upperLevelElement = parentNodesContainerElement ? parentNodesContainerElement.previousElementSibling : null;
2382
+ if (direction) {
2383
+ nodeContainerElement.classList.remove(direction + '-sibs');
2384
+ if (!Array.from(nodeContainerElement.classList).some(function (className) { return className.indexOf('-sibs') > -1; })) {
2385
+ nodeContainerElement.classList.remove('isSiblingsCollapsed');
2386
+ }
2387
+ siblingElements.forEach(function (siblingEl) {
2388
+ siblingEl.classList.remove('isCollapsedSibling', direction + '-sibs');
2389
+ });
2390
+ } else {
2391
+ nodeContainerElement.classList.remove('isSiblingsCollapsed');
2392
+ siblingElements.forEach(function (siblingEl) {
2393
+ siblingEl.classList.remove('isCollapsedSibling');
2394
+ });
2395
+ }
2396
+ // thirdly, show parent node if it is collapsed
2397
+ if (!this.getNodeState(nodeElement, 'parent').visible) {
2398
+ nodeContainerElement.classList.remove('isAncestorsCollapsed');
2399
+ if (upperLevelElement) {
2400
+ upperLevelElement.classList.remove('hidden');
2401
+ this.repaint(upperLevelElement);
2402
+ upperLevelElement.classList.add('sliding');
2403
+ upperLevelElement.classList.remove('slide-down');
2404
+ upperLevelElement.addEventListener('transitionend', function (event) {
2405
+ event.animatedNode = upperLevelElement;
2406
+ orgChart.showRelatedParentEnd(event);
2407
+ }, { once: true });
2408
+ }
2409
+ }
2410
+ // lastly, show the sibling nodes with animation
2411
+ visibleNodeElements = siblingElements.flatMap(function (siblingEl) {
2412
+ return Array.from(siblingEl.querySelectorAll('.node'));
2413
+ }, this).filter(function (visibleNodeEl, index) {
2414
+ return this.isVisibleNode(index, visibleNodeEl);
2415
+ }, this);
2416
+ this.repaint(visibleNodeElements[0]);
2417
+ visibleNodeElements.forEach(function (visibleNodeEl) {
2418
+ visibleNodeEl.classList.add('sliding');
2419
+ visibleNodeEl.classList.remove('slide-left', 'slide-right');
2420
+ });
2421
+ if (visibleNodeElements[0]) {
2422
+ visibleNodeElements[0].addEventListener('transitionend', function (event) {
2423
+ event.node = nodeElement;
2424
+ event.visibleNodes = visibleNodeElements;
2425
+ orgChart.showSiblingsEnd(event);
2426
+ }, { once: true });
2427
+ }
2428
+ },
2429
+ // start up loading status for requesting new nodes
2430
+ startLoading: function (edgeEl) {
2431
+ const edgeElement = getElement(edgeEl);
2432
+ const chartElement = this.chart;
2433
+ let nodeElement;
2434
+ let spinnerElement;
2435
+
2436
+ if (!edgeElement || !chartElement) {
2437
+ return false;
2438
+ }
2439
+
2440
+ if (typeof getState(chartElement, 'inAjax') !== 'undefined' && getState(chartElement, 'inAjax') === true) {
2441
+ return false;
2442
+ }
2443
+
2444
+ edgeElement.classList.add('hidden');
2445
+ nodeElement = edgeElement.parentElement;
2446
+ if (nodeElement) {
2447
+ setState(nodeElement, 'loadingEdge', edgeElement);
2448
+ nodeElement.insertAdjacentHTML('beforeend', `<i class="${this.options.icons.theme} ${this.options.icons.spinner} spinner"></i>`);
2449
+ spinnerElement = Array.from(nodeElement.children || []).find(function (childEl) {
2450
+ return childEl.classList && childEl.classList.contains('spinner');
2451
+ }) || null;
2452
+ Array.from(nodeElement.children || []).forEach(function (childEl) {
2453
+ if (childEl !== spinnerElement) {
2454
+ setState(childEl, 'loadingOriginalOpacity', childEl.style.opacity);
2455
+ childEl.style.opacity = 0.2;
2456
+ }
2457
+ });
2458
+ }
2459
+ setState(chartElement, 'inAjax', true);
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) {
2463
+ exportButtonEl.disabled = true;
2464
+ });
2465
+ return true;
2466
+ },
2467
+ // terminate loading status for requesting new nodes
2468
+ endLoading: function (edgeEl) {
2469
+ const edgeElement = getElement(edgeEl);
2470
+ let nodeElement;
2471
+ const chartElement = this.chart;
2472
+
2473
+ if (!edgeElement || !chartElement) {
2474
+ return;
2475
+ }
2476
+
2477
+ nodeElement = edgeElement.parentElement;
2478
+ edgeElement.classList.remove('hidden');
2479
+ if (nodeElement) {
2480
+ Array.from(nodeElement.querySelectorAll('.spinner')).forEach(function (spinnerEl) {
2481
+ spinnerEl.remove();
2482
+ });
2483
+ Array.from(nodeElement.children || []).forEach(function (childEl) {
2484
+ childEl.style.opacity = getState(childEl, 'loadingOriginalOpacity') || '';
2485
+ if (!childEl.style.cssText) {
2486
+ childEl.removeAttribute('style');
2487
+ }
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
+ }
2495
+ }
2496
+ setState(chartElement, 'inAjax', false);
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) {
2500
+ exportButtonEl.disabled = false;
2501
+ });
2502
+ },
2503
+ // whether the cursor is hovering over the node
2504
+ isInAction: function (nodeEl) {
2505
+ const nodeElement = getElement(nodeEl);
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); });
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
+ }
2610
+ },
2611
+ //
2612
+ switchVerticalArrow: function (arrowEl) {
2613
+ const arrowElement = getElement(arrowEl);
2614
+
2615
+ if (!arrowElement || !arrowElement.classList) {
2616
+ return;
2617
+ }
2618
+
2619
+ arrowElement.classList.toggle(this.options.icons.expandToUp);
2620
+ arrowElement.classList.toggle(this.options.icons.collapseToDown);
2621
+ },
2622
+ //
2623
+ switchHorizontalArrow: function (nodeEl) {
2624
+ const nodeElement = getElement(nodeEl);
2625
+ const opts = this.options;
2626
+ let hierarchyElement;
2627
+ let siblingsContainerElement;
2628
+ let leftEdgeElement;
2629
+ let rightEdgeElement;
2630
+ let previousSiblingElement;
2631
+ let nextSiblingElement;
2632
+ let siblingElements;
2633
+ let sibsVisible;
2634
+
2635
+ if (!nodeElement) {
2636
+ return;
2637
+ }
2638
+
2639
+ hierarchyElement = nodeElement.closest('.hierarchy');
2640
+ siblingsContainerElement = nodeElement.closest('.nodes');
2641
+ leftEdgeElement = Array.from(nodeElement.children || []).find(function (childEl) {
2642
+ return childEl.classList && childEl.classList.contains('leftEdge');
2643
+ }) || null;
2644
+ rightEdgeElement = Array.from(nodeElement.children || []).find(function (childEl) {
2645
+ return childEl.classList && childEl.classList.contains('rightEdge');
2646
+ }) || null;
2647
+
2648
+ if (opts.toggleSiblingsResp && (typeof opts.ajaxURL === 'undefined' || getState(siblingsContainerElement, 'siblingsLoaded'))) {
2649
+ previousSiblingElement = hierarchyElement ? hierarchyElement.previousElementSibling : null;
2650
+ if (previousSiblingElement && leftEdgeElement) {
2651
+ leftEdgeElement.classList.toggle(opts.icons.collapseToLeft, previousSiblingElement.classList.contains('hidden'));
2652
+ leftEdgeElement.classList.toggle(opts.icons.expandToRight, !previousSiblingElement.classList.contains('hidden'));
2653
+ }
2654
+ nextSiblingElement = hierarchyElement ? hierarchyElement.nextElementSibling : null;
2655
+ if (nextSiblingElement && rightEdgeElement) {
2656
+ rightEdgeElement.classList.toggle(opts.icons.expandToRight, nextSiblingElement.classList.contains('hidden'));
2657
+ rightEdgeElement.classList.toggle(opts.icons.collapseToLeft, !nextSiblingElement.classList.contains('hidden'));
2658
+ }
2659
+ } else {
2660
+ siblingElements = hierarchyElement && hierarchyElement.parentElement
2661
+ ? Array.from(hierarchyElement.parentElement.children).filter(function (childEl) {
2662
+ return childEl !== hierarchyElement;
2663
+ })
2664
+ : [];
2665
+ sibsVisible = siblingElements.length ? !siblingElements[0].classList.contains('hidden') : false;
2666
+ if (leftEdgeElement) {
2667
+ leftEdgeElement.classList.toggle(opts.icons.expandToRight, sibsVisible);
2668
+ leftEdgeElement.classList.toggle(opts.icons.collapseToLeft, !sibsVisible);
2669
+ }
2670
+ if (rightEdgeElement) {
2671
+ rightEdgeElement.classList.toggle(opts.icons.collapseToLeft, sibsVisible);
2672
+ rightEdgeElement.classList.toggle(opts.icons.expandToRight, !sibsVisible);
2673
+ }
2674
+ }
2675
+ },
2676
+ //
2677
+ repaint: function (node) {
2678
+ if (node) {
2679
+ node.style.offsetWidth = node.offsetWidth;
2680
+ }
2681
+ },
2682
+ // determines how to show arrow buttons
2683
+ nodeEnterLeaveHandler: function (event) {
2684
+ const nativeEvent = event.originalEvent || event;
2685
+ const eventType = nativeEvent.type;
2686
+ const nodeElement = event.node || nativeEvent.currentTarget;
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;
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);
2723
+ }
2724
+ },
2725
+ //
2726
+ nodeClickHandler: function (event) {
2727
+ const chartElement = this.chart;
2728
+ const nativeEvent = event.originalEvent || event;
2729
+ const nodeElement = event.node || nativeEvent.currentTarget;
2730
+
2731
+ if (!chartElement || !nodeElement || !nodeElement.classList) {
2732
+ return;
2733
+ }
2734
+ if (nativeEvent.target && nativeEvent.target.closest && nativeEvent.target.closest('.node') !== nodeElement) {
2735
+ return;
2736
+ }
2737
+
2738
+ Array.from(chartElement.querySelectorAll('.focused')).forEach(function (focusedEl) {
2739
+ focusedEl.classList.remove('focused');
2740
+ });
2741
+ nodeElement.classList.add('focused');
2742
+ if (!this.isInAction(nodeElement)) {
2743
+ nodeElement.focus({ preventScroll: true });
2744
+ this.nodeEnterLeaveHandler({ type: 'mouseenter', node: nodeElement });
2745
+ }
2746
+ },
2747
+ addAncestors: function (data, parentId) {
2748
+ const rootHierarchyElement = this.chart ? this.chart.querySelector(':scope > .nodes > .hierarchy') : null;
2749
+ let originalRootChildren;
2750
+ let ancestorWrapperElement;
2751
+ let targetParentNodeElement;
2752
+ let targetSiblingsContainerElement;
2753
+
2754
+ if (!rootHierarchyElement) {
2755
+ return;
2756
+ }
2757
+
2758
+ this.buildHierarchy(rootHierarchyElement, data);
2759
+ originalRootChildren = Array.from(rootHierarchyElement.children).slice(0, 2);
2760
+ ancestorWrapperElement = getDocument().createElement('li');
2761
+ ancestorWrapperElement.className = 'hierarchy';
2762
+ rootHierarchyElement.insertBefore(ancestorWrapperElement, originalRootChildren[0] || null);
2763
+ originalRootChildren.forEach(function (childEl) {
2764
+ ancestorWrapperElement.appendChild(childEl);
2765
+ });
2766
+ targetParentNodeElement = getDocument().getElementById(parentId);
2767
+ targetSiblingsContainerElement = targetParentNodeElement
2768
+ ? Array.from(targetParentNodeElement.parentElement ? targetParentNodeElement.parentElement.children : []).find(function (childEl) {
2769
+ return childEl !== targetParentNodeElement && childEl.classList && childEl.classList.contains('nodes');
2770
+ }) || null
2771
+ : null;
2772
+ if (targetSiblingsContainerElement && targetSiblingsContainerElement !== ancestorWrapperElement.parentElement) {
2773
+ targetSiblingsContainerElement.appendChild(ancestorWrapperElement);
2774
+ }
2775
+ this.refreshNodeControls(targetParentNodeElement);
2776
+ },
2777
+ addDescendants:function (data, parentEl) {
2778
+ const orgChart = this;
2779
+ const parentElement = getElement(parentEl);
2780
+ let descendantsElement;
2781
+
2782
+ if (!parentElement || !parentElement.parentElement) {
2783
+ return;
2784
+ }
2785
+ if (getState(parentElement, 'sourceData')) {
2786
+ this.addChildren(parentElement, data);
2787
+ return;
2788
+ }
2789
+
2790
+ descendantsElement = getDocument().createElement('ul');
2791
+ descendantsElement.className = 'nodes';
2792
+ parentElement.after(descendantsElement);
2793
+ forEachValue(data, function () {
2794
+ const descendantHierarchyElement = getDocument().createElement('li');
2795
+
2796
+ descendantHierarchyElement.className = 'hierarchy';
2797
+ descendantsElement.appendChild(descendantHierarchyElement);
2798
+ orgChart.buildHierarchy(descendantHierarchyElement, this);
2799
+ });
2800
+ },
2801
+ //
2802
+ HideFirstParentEnd: function (event) {
2803
+ const nativeEvent = event.originalEvent || event;
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;
2807
+
2808
+ if (this.isInAction(nodeElement)) {
2809
+ this.switchVerticalArrow(topEdgeElement);
2810
+ this.switchHorizontalArrow(nodeElement);
2811
+ }
2812
+ },
2813
+ // actions on clinking top edge of a node
2814
+ topEdgeClickHandler: function (event) {
2815
+ const orgChart = this;
2816
+ const nativeEvent = event.originalEvent || event;
2817
+ const nodeElement = event.node || nativeEvent.currentTarget;
2818
+ const topEdgeElement = getElement(event.edge || nativeEvent.target);
2819
+ const parentEl = this.getParent(nodeElement);
2820
+ const parentState = this.getNodeState(nodeElement, 'parent');
2821
+ let parentSliding;
2822
+ let parentHiding;
2823
+ if (parentState.exist) {
2824
+ parentSliding = !!(parentEl && parentEl.classList && parentEl.classList.contains('sliding'));
2825
+ parentHiding = !!(parentEl && parentEl.classList && parentEl.classList.contains('slide-down'));
2826
+ if (parentSliding && !parentHiding) { return; }
2827
+ // hide the ancestor nodes and sibling nodes of the specified node
2828
+ if (parentState.visible && !parentHiding) {
2829
+ this.hideParent(nodeElement);
2830
+ if (parentEl) {
2831
+ parentEl.addEventListener('transitionend', function (transitionEvent) {
2832
+ transitionEvent.node = nodeElement;
2833
+ orgChart.HideFirstParentEnd(transitionEvent);
2834
+ }, { once: true });
2835
+ }
2836
+ this.triggerHideEvent(nodeElement, 'parent');
2837
+ } else { // show the ancestors and siblings
2838
+ this.showParent(nodeElement);
2839
+ this.triggerShowEvent(nodeElement, 'parent');
2840
+ }
2841
+ }
2842
+ },
2843
+ // actions on clinking bottom edge of a node
2844
+ bottomEdgeClickHandler: function (event) {
2845
+ const nativeEvent = event.originalEvent || event;
2846
+ const nodeElement = event.node || nativeEvent.currentTarget;
2847
+ const childrenState = this.getNodeState(nodeElement, 'children');
2848
+ let childNodesContainerElement;
2849
+ let childNodeElements;
2850
+ if (childrenState.exist) {
2851
+ childNodesContainerElement = getChildNodesContainer(nodeElement);
2852
+ childNodeElements = childNodesContainerElement ? this.getChildren(nodeElement) : [];
2853
+ if (childNodeElements.some(function (childNodeEl) { return childNodeEl.classList.contains('sliding'); })) { return; }
2854
+ // hide the descendant nodes of the specified node
2855
+ if (childrenState.visible) {
2856
+ this.hideChildren(nodeElement);
2857
+ this.triggerHideEvent(nodeElement, 'children');
2858
+ } else { // show the descendants
2859
+ this.showChildren(nodeElement);
2860
+ this.triggerShowEvent(nodeElement, 'children');
2861
+ }
2862
+ }
2863
+ },
2864
+ // actions on clicking horizontal edges
2865
+ hEdgeClickHandler: function (event) {
2866
+ const nativeEvent = event.originalEvent || event;
2867
+ const nodeElement = event.node || nativeEvent.currentTarget;
2868
+ const edgeElement = getElement(event.edge || nativeEvent.target);
2869
+ const opts = this.options;
2870
+ const siblingsState = this.getNodeState(nodeElement, 'siblings');
2871
+ let hierarchyElement;
2872
+ let siblingElements;
2873
+ let previousSiblingElement;
2874
+ let nextSiblingElement;
2875
+ if (siblingsState.exist) {
2876
+ hierarchyElement = nodeElement.closest('.hierarchy');
2877
+ siblingElements = hierarchyElement && hierarchyElement.parentElement
2878
+ ? Array.from(hierarchyElement.parentElement.children).filter(function (childEl) {
2879
+ return childEl !== hierarchyElement;
2880
+ })
2881
+ : [];
2882
+ if (siblingElements.some(function (siblingEl) {
2883
+ return siblingEl.querySelector('.sliding');
2884
+ })) { return; }
2885
+ if (opts.toggleSiblingsResp) {
2886
+ previousSiblingElement = hierarchyElement ? hierarchyElement.previousElementSibling : null;
2887
+ nextSiblingElement = hierarchyElement ? hierarchyElement.nextElementSibling : null;
2888
+ if (edgeElement && edgeElement.classList && edgeElement.classList.contains('leftEdge')) {
2889
+ if (previousSiblingElement && previousSiblingElement.classList.contains('hidden')) {
2890
+ this.showSiblings(nodeElement, 'left');
2891
+ this.triggerShowEvent(nodeElement,'siblings');
2892
+ } else {
2893
+ this.hideSiblings(nodeElement, 'left');
2894
+ this.triggerHideEvent(nodeElement, 'siblings');
2895
+ }
2896
+ } else {
2897
+ if (nextSiblingElement && nextSiblingElement.classList.contains('hidden')) {
2898
+ this.showSiblings(nodeElement, 'right');
2899
+ this.triggerShowEvent(nodeElement,'siblings');
2900
+ } else {
2901
+ this.hideSiblings(nodeElement, 'right');
2902
+ this.triggerHideEvent(nodeElement, 'siblings');
2903
+ }
2904
+ }
2905
+ } else {
2906
+ if (siblingsState.visible) {
2907
+ this.hideSiblings(nodeElement);
2908
+ this.triggerHideEvent(nodeElement, 'siblings');
2909
+ } else {
2910
+ this.showSiblings(nodeElement);
2911
+ this.triggerShowEvent(nodeElement, 'siblings');
2912
+ }
2913
+ }
2914
+ }
2915
+ },
2916
+ // show the compact node's children in the compact mode
2917
+ backToCompactHandler: function (event) {
2918
+ const nativeEvent = event.originalEvent || event;
2919
+ const compactNodeEl = event.compactNode || nativeEvent.currentTarget;
2920
+ let nestedLooseModeElements;
2921
+ let backToCompactSymbolElement;
2922
+ let backToLooseSymbolElement;
2923
+
2924
+ if (!compactNodeEl) {
2925
+ return;
2926
+ }
2927
+
2928
+ compactNodeEl.classList.remove('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
+ );
2936
+ nestedLooseModeElements.forEach(function (nestedLooseModeEl) {
2937
+ const nestedBackToCompactSymbolElement = Array.from(nestedLooseModeEl.children || []).find(function (childEl) {
2938
+ return childEl.classList && childEl.classList.contains('backToCompactSymbol');
2939
+ }) || null;
2940
+ const nestedBackToLooseSymbolElement = Array.from(nestedLooseModeEl.children || []).find(function (childEl) {
2941
+ return childEl.classList && childEl.classList.contains('backToLooseSymbol');
2942
+ }) || null;
2943
+
2944
+ nestedLooseModeEl.classList.remove('looseMode');
2945
+ const nestedContainer = getChildNodesContainer(nestedLooseModeEl);
2946
+ if (nestedContainer && nestedContainer.classList.contains('compact-family')) {
2947
+ nestedContainer.classList.add('hidden');
2948
+ }
2949
+ if (nestedBackToCompactSymbolElement) {
2950
+ nestedBackToCompactSymbolElement.classList.add('hidden');
2951
+ }
2952
+ if (nestedBackToLooseSymbolElement) {
2953
+ nestedBackToLooseSymbolElement.classList.remove('hidden');
2954
+ }
2955
+ });
2956
+
2957
+ backToCompactSymbolElement = Array.from(compactNodeEl.children || []).find(function (childEl) {
2958
+ return childEl.classList && childEl.classList.contains('backToCompactSymbol');
2959
+ }) || null;
2960
+ backToLooseSymbolElement = Array.from(compactNodeEl.children || []).find(function (childEl) {
2961
+ return childEl.classList && childEl.classList.contains('backToLooseSymbol');
2962
+ }) || null;
2963
+ if (backToCompactSymbolElement) {
2964
+ backToCompactSymbolElement.classList.add('hidden');
2965
+ }
2966
+ if (backToLooseSymbolElement) {
2967
+ backToLooseSymbolElement.classList.remove('hidden');
2968
+ }
2969
+ },
2970
+ // show the compact node's children in the loose mode
2971
+ backToLooseHandler: function (event) {
2972
+ const nativeEvent = event.originalEvent || event;
2973
+ const compactNodeEl = event.compactNode || nativeEvent.currentTarget;
2974
+ let backToLooseSymbolElement;
2975
+ let backToCompactSymbolElement;
2976
+
2977
+ if (!compactNodeEl) {
2978
+ return;
2979
+ }
2980
+
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
+ });
2989
+ backToLooseSymbolElement = Array.from(compactNodeEl.children || []).find(function (childEl) {
2990
+ return childEl.classList && childEl.classList.contains('backToLooseSymbol');
2991
+ }) || null;
2992
+ backToCompactSymbolElement = Array.from(compactNodeEl.children || []).find(function (childEl) {
2993
+ return childEl.classList && childEl.classList.contains('backToCompactSymbol');
2994
+ }) || null;
2995
+ if (backToLooseSymbolElement) {
2996
+ backToLooseSymbolElement.classList.add('hidden');
2997
+ }
2998
+ if (backToCompactSymbolElement) {
2999
+ backToCompactSymbolElement.classList.remove('hidden');
3000
+ }
3001
+ },
3002
+ //
3003
+ expandVNodesEnd: function (event) {
3004
+ const nativeEvent = event.originalEvent || event;
3005
+ const verticalNodeElements = event.vNodes || nativeEvent.vNodes;
3006
+
3007
+ getElements(verticalNodeElements).forEach(function (verticalNodeEl) {
3008
+ if (verticalNodeEl.classList) {
3009
+ verticalNodeEl.classList.remove('sliding');
3010
+ }
3011
+ });
3012
+ },
3013
+ //
3014
+ collapseVNodesEnd: function (event) {
3015
+ const nativeEvent = event.originalEvent || event;
3016
+ const verticalNodeElements = event.vNodes || nativeEvent.vNodes;
3017
+
3018
+ getElements(verticalNodeElements).forEach(function (verticalNodeEl) {
3019
+ const verticalNodesWrapperElement = verticalNodeEl && typeof verticalNodeEl.closest === 'function'
3020
+ ? verticalNodeEl.closest('ul')
3021
+ : null;
3022
+
3023
+ if (verticalNodeEl.classList) {
3024
+ verticalNodeEl.classList.remove('sliding');
3025
+ }
3026
+ if (verticalNodesWrapperElement && verticalNodesWrapperElement.classList) {
3027
+ verticalNodesWrapperElement.classList.add('hidden');
3028
+ }
3029
+ });
3030
+ },
3031
+ // event handler for toggle buttons in Hybrid(horizontal + vertical) OrgChart
3032
+ toggleVNodes: function (event) {
3033
+ const orgChart = this;
3034
+ const nativeEvent = event.originalEvent || event;
3035
+ const toggleBtnEl = event.toggleButton || nativeEvent.target;
3036
+ const descendantWrapperElement = toggleBtnEl && toggleBtnEl.parentElement ? toggleBtnEl.parentElement.nextElementSibling : null;
3037
+ let descendantNodeElements;
3038
+ let childNodeElements;
3039
+
3040
+ if (!toggleBtnEl || !descendantWrapperElement) {
3041
+ return;
3042
+ }
3043
+
3044
+ descendantNodeElements = Array.from(descendantWrapperElement.querySelectorAll('.node'));
3045
+ childNodeElements = Array.from(descendantWrapperElement.children || []).map(function (childEl) {
3046
+ return childEl.querySelector('.node');
3047
+ }).filter(Boolean);
3048
+ if (childNodeElements.some(function (childNodeEl) { return childNodeEl.classList.contains('sliding'); })) { return; }
3049
+ toggleBtnEl.classList.toggle(this.options.icons.collapsed);
3050
+ toggleBtnEl.classList.toggle(this.options.icons.expanded);
3051
+ if (descendantNodeElements[0] && descendantNodeElements[0].classList.contains('slide-up')) {
3052
+ descendantWrapperElement.classList.remove('hidden');
3053
+ this.repaint(childNodeElements[0]);
3054
+ childNodeElements.forEach(function (childNodeEl) {
3055
+ childNodeEl.classList.add('sliding');
3056
+ childNodeEl.classList.remove('slide-up');
3057
+ });
3058
+ if (childNodeElements[0]) {
3059
+ childNodeElements[0].addEventListener('transitionend', function (transitionEvent) {
3060
+ transitionEvent.vNodes = childNodeElements;
3061
+ orgChart.expandVNodesEnd(transitionEvent);
3062
+ }, { once: true });
3063
+ }
3064
+ } else {
3065
+ descendantNodeElements.forEach(function (descendantNodeEl) {
3066
+ descendantNodeEl.classList.add('sliding', 'slide-up');
3067
+ });
3068
+ if (descendantNodeElements[0]) {
3069
+ descendantNodeElements[0].addEventListener('transitionend', function (transitionEvent) {
3070
+ transitionEvent.vNodes = descendantNodeElements;
3071
+ orgChart.collapseVNodesEnd(transitionEvent);
3072
+ }, { once: true });
3073
+ }
3074
+ descendantNodeElements.forEach(function (descendantNodeEl) {
3075
+ Array.from(descendantNodeEl.querySelectorAll('.toggleBtn')).forEach(function (nestedToggleBtnEl) {
3076
+ nestedToggleBtnEl.classList.remove(this.options.icons.collapsed, this.options.icons.expanded);
3077
+ }, this);
3078
+ }, this);
3079
+ }
3080
+ },
3081
+ //
3082
+ createGhostNode: function (event) {
3083
+ const nativeEvent = event.originalEvent || event;
3084
+ const sourceNodeEl = event.sourceNode || nativeEvent.target;
3085
+ const chartElement = sourceNodeEl && typeof sourceNodeEl.closest === 'function'
3086
+ ? sourceNodeEl.closest('.orgchart')
3087
+ : null;
3088
+ const opts = this.options;
3089
+ const origEvent = nativeEvent;
3090
+ const isFirefox = /firefox/.test(getWindow().navigator.userAgent.toLowerCase());
3091
+ let sourceRect;
3092
+ let isVerticalChartDirection;
3093
+ let scaleFactor;
3094
+ let ghostNodeEl, nodeCoverEl;
3095
+
3096
+ if (!sourceNodeEl || !chartElement) {
3097
+ return;
3098
+ }
3099
+
3100
+ if (!getDocument().querySelector('.ghost-node')) {
3101
+ ghostNodeEl = getDocument().createElementNS("http://www.w3.org/2000/svg", "svg");
3102
+ if (!ghostNodeEl.classList) return;
3103
+ ghostNodeEl.classList.add('ghost-node');
3104
+ nodeCoverEl = getDocument().createElementNS('http://www.w3.org/2000/svg','rect');
3105
+ ghostNodeEl.appendChild(nodeCoverEl);
3106
+ chartElement.appendChild(ghostNodeEl);
3107
+ } else {
3108
+ ghostNodeEl = chartElement.querySelector('.ghost-node');
3109
+ nodeCoverEl = ghostNodeEl ? ghostNodeEl.firstElementChild : null;
3110
+ }
3111
+
3112
+ if (!ghostNodeEl || !nodeCoverEl) {
3113
+ return;
3114
+ }
3115
+
3116
+ sourceRect = sourceNodeEl.getBoundingClientRect();
3117
+ isVerticalChartDirection = opts.direction === 't2b' || opts.direction === 'b2t';
3118
+ scaleFactor = getChartTransform(chartElement).scale;
3119
+ ghostNodeEl.setAttribute('width', isVerticalChartDirection ? sourceRect.width : sourceRect.height);
3120
+ ghostNodeEl.setAttribute('height', isVerticalChartDirection ? sourceRect.height : sourceRect.width);
3121
+ nodeCoverEl.setAttribute('x',5 * scaleFactor);
3122
+ nodeCoverEl.setAttribute('y',5 * scaleFactor);
3123
+ nodeCoverEl.setAttribute('width', 120 * scaleFactor);
3124
+ nodeCoverEl.setAttribute('height', 40 * scaleFactor);
3125
+ nodeCoverEl.setAttribute('rx', 4 * scaleFactor);
3126
+ nodeCoverEl.setAttribute('ry', 4 * scaleFactor);
3127
+ nodeCoverEl.setAttribute('stroke-width', 1 * scaleFactor);
3128
+ let xOffset = origEvent.offsetX * scaleFactor;
3129
+ let yOffset = origEvent.offsetY * scaleFactor;
3130
+ if (opts.direction === 'l2r') {
3131
+ xOffset = origEvent.offsetY * scaleFactor;
3132
+ yOffset = origEvent.offsetX * scaleFactor;
3133
+ } else if (opts.direction === 'r2l') {
3134
+ xOffset = sourceRect.width - origEvent.offsetY * scaleFactor;
3135
+ yOffset = origEvent.offsetX * scaleFactor;
3136
+ } else if (opts.direction === 'b2t') {
3137
+ xOffset = sourceRect.width - origEvent.offsetX * scaleFactor;
3138
+ yOffset = sourceRect.height - origEvent.offsetY * scaleFactor;
3139
+ }
3140
+ if (isFirefox) { // hack for old version of Firefox(< 48.0)
3141
+ nodeCoverEl.setAttribute('fill', 'rgb(255, 255, 255)');
3142
+ nodeCoverEl.setAttribute('stroke', 'rgb(191, 0, 0)');
3143
+ const ghostNodeImageEl = getDocument().createElement('img');
3144
+ ghostNodeImageEl.src = 'data:image/svg+xml;utf8,' + (new XMLSerializer()).serializeToString(ghostNodeEl);
3145
+ origEvent.dataTransfer.setDragImage(ghostNodeImageEl, xOffset, yOffset);
3146
+ } else {
3147
+ // IE/Edge do not support this, so only use it if we can
3148
+ if (origEvent.dataTransfer.setDragImage)
3149
+ origEvent.dataTransfer.setDragImage(ghostNodeEl, xOffset, yOffset);
3150
+ }
3151
+ },
3152
+ // get the level amount of a hierachy
3153
+ getUpperLevel: function (nodeEl) {
3154
+ const nodeElement = getElement(nodeEl);
3155
+ let currentElement;
3156
+ let levelCount = 0;
3157
+
3158
+ if (!nodeElement || !nodeElement.matches('.node')) {
3159
+ return 0;
3160
+ }
3161
+
3162
+ currentElement = nodeElement;
3163
+ while (currentElement) {
3164
+ levelCount += 1;
3165
+ currentElement = this.getParent(currentElement);
3166
+ }
3167
+
3168
+ return levelCount;
3169
+ },
3170
+ // get the level amount of a hierachy
3171
+ getLowerLevel: function (nodeEl) {
3172
+ const nodeElement = getElement(nodeEl);
3173
+ let levelCount = 1;
3174
+ let currentLevelNodes;
3175
+
3176
+ if (!nodeElement || !nodeElement.matches('.node')) {
3177
+ return 0;
3178
+ }
3179
+
3180
+ currentLevelNodes = this.getChildren(nodeElement);
3181
+ while (currentLevelNodes.length) {
3182
+ const nextLevelNodes = [];
3183
+ levelCount += 1;
3184
+ currentLevelNodes.forEach(function (childNodeEl) {
3185
+ Array.prototype.push.apply(nextLevelNodes, this.getChildren(childNodeEl));
3186
+ }, this);
3187
+ currentLevelNodes = nextLevelNodes;
3188
+ }
3189
+
3190
+ return levelCount;
3191
+ },
3192
+ // get nodes in level order traversal
3193
+ getLevelOrderNodes: function (rootEl) {
3194
+ const rootElement = getElement(rootEl);
3195
+ const pendingNodeElements = [];
3196
+ const nodesByLevel = [];
3197
+ if (!rootElement) {
3198
+ return [];
3199
+ }
3200
+
3201
+ pendingNodeElements.push(rootElement);
3202
+ while(pendingNodeElements.length) {
3203
+ const levelNodeElements = [];
3204
+ const levelNodeCount = pendingNodeElements.length;
3205
+ for(let i = 0; i < levelNodeCount; i++) {
3206
+ const currentNodeElement = pendingNodeElements.shift();
3207
+ const childNodeElements = this.getChildren(currentNodeElement);
3208
+ if(childNodeElements.length) {
3209
+ Array.prototype.push.apply(pendingNodeElements, childNodeElements);
3210
+ }
3211
+ levelNodeElements.push(currentNodeElement);
3212
+ }
3213
+ nodesByLevel.push(levelNodeElements);
3214
+ }
3215
+ return nodesByLevel;
3216
+ },
3217
+ //
3218
+ filterAllowedDropNodes: function (draggedEl) {
3219
+ const draggedNodeEl = getElement(draggedEl);
3220
+ const opts = this.options;
3221
+ const chartElement = this.chart;
3222
+ let draggableHostEl;
3223
+ let draggedParentNodeEl;
3224
+ let draggedSubtreeNodeEls;
3225
+ let excludedNodeEls;
3226
+ let draggingNode;
3227
+
3228
+ if (!chartElement) {
3229
+ return;
3230
+ }
3231
+
3232
+ // what is being dragged? a node, or something within a node?
3233
+ draggableHostEl = draggedNodeEl && typeof draggedNodeEl.closest === 'function'
3234
+ ? draggedNodeEl.closest('[draggable]')
3235
+ : null;
3236
+ draggingNode = !!(draggableHostEl && draggableHostEl.classList && draggableHostEl.classList.contains('node'));
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
+ }
3245
+ excludedNodeEls = new Set(draggedSubtreeNodeEls);
3246
+ setState(chartElement, 'dragged', draggedNodeEl);
3247
+ Array.from(chartElement.querySelectorAll('.node')).forEach(function (node) {
3248
+ node.classList.remove('allowedDrop');
3249
+ if (!draggingNode || !excludedNodeEls.has(node)) {
3250
+ if (opts.dropCriteria) {
3251
+ if (opts.dropCriteria(draggedNodeEl, draggedParentNodeEl, node)) {
3252
+ node.classList.add('allowedDrop');
3253
+ }
3254
+ } else {
3255
+ node.classList.add('allowedDrop');
3256
+ }
3257
+ }
3258
+ });
3259
+ },
3260
+ //
3261
+ dragstartHandler: function (event) {
3262
+ const nativeEvent = event.originalEvent || event;
3263
+ const draggedNodeEl = event.draggedNode || nativeEvent.target;
3264
+ const chartElement = this.chart;
3265
+ const transformValue = chartElement
3266
+ ? (chartElement.style.transform || getWindow().getComputedStyle(chartElement).transform || 'none')
3267
+ : 'none';
3268
+
3269
+ nativeEvent.dataTransfer.setData('text/html', 'hack for firefox');
3270
+ // if users enable zoom or direction options
3271
+ if (transformValue !== 'none') {
3272
+ this.createGhostNode({
3273
+ sourceNode: draggedNodeEl,
3274
+ originalEvent: nativeEvent
3275
+ });
3276
+ }
3277
+ this.filterAllowedDropNodes(draggedNodeEl);
3278
+ },
3279
+ //
3280
+ dragoverHandler: function (event) {
3281
+ const nativeEvent = event.originalEvent || event;
3282
+ const dropZoneEl = event.dropZone || nativeEvent.currentTarget;
3283
+
3284
+ if (!dropZoneEl || !dropZoneEl.classList || !dropZoneEl.classList.contains('allowedDrop')) {
3285
+ nativeEvent.dataTransfer.dropEffect = 'none';
3286
+ } else {
3287
+ // default action for drag-and-drop of div is not to drop, so preventing default action for nodes which have allowedDrop class
3288
+ //to fix drag and drop on IE and Edge
3289
+ if (typeof nativeEvent.preventDefault === 'function') {
3290
+ nativeEvent.preventDefault();
3291
+ }
3292
+ }
3293
+ },
3294
+ //
3295
+ dragendHandler: function (event) {
3296
+ const chartElement = this.chart;
3297
+
3298
+ if (!chartElement) {
3299
+ return;
3300
+ }
3301
+
3302
+ Array.from(chartElement.querySelectorAll('.allowedDrop')).forEach(function (node) {
3303
+ node.classList.remove('allowedDrop');
3304
+ });
3305
+ },
3306
+ // when user drops the node, it will be removed from original parent node and be added to new parent node
3307
+ dropHandler: async function (event) {
3308
+ const nativeEvent = event.originalEvent || event;
3309
+ const orgChart = this;
3310
+ const chartElement = this.chart;
3311
+ const dropZoneEl = event.dropZone || nativeEvent.currentTarget;
3312
+ const draggedEl = chartElement ? getElement(getState(chartElement, 'dragged')) : null;
3313
+ const chartOptions = chartElement ? getState(chartElement, 'options') : null;
3314
+ let draggedParentNodeEl;
3315
+ let dropChildrenContainerEl;
3316
+ let draggedHierarchyEl;
3317
+ let draggedParentChildrenContainerEl;
3318
+ let remainingHierarchyEls;
3319
+
3320
+ // Pass on drops which are not nodes (since they are not our doing)
3321
+ if (!draggedEl || !draggedEl.classList || !draggedEl.classList.contains('node')) {
3322
+ if (chartElement) {
3323
+ chartElement.dispatchEvent(createTriggeredEvent('otherdropped.orgchart', {
3324
+ 'draggedItem': draggedEl,
3325
+ 'dropZone': dropZoneEl
3326
+ }));
3327
+ }
3328
+ return;
3329
+ }
3330
+
3331
+ if (!dropZoneEl || !dropZoneEl.classList || !dropZoneEl.classList.contains('allowedDrop')) {
3332
+ // We are trying to drop a node into a node which isn't allowed
3333
+ // IE/Edge have a habit of allowing this, so we need our own double-check
3334
+ return;
3335
+ }
3336
+
3337
+ draggedParentNodeEl = this.getParent(draggedEl);
3338
+ const dropEvent = createEventLike('nodedrop.orgchart');
3339
+ if (chartElement) {
3340
+ const triggeredDropEvent = createTriggeredEvent(dropEvent, {
3341
+ 'draggedNode': draggedEl,
3342
+ 'dragZone': draggedParentNodeEl,
3343
+ 'dropZone': dropZoneEl
3344
+ });
3345
+
3346
+ triggeredDropEvent.delegateTarget = chartElement;
3347
+ chartElement.dispatchEvent(triggeredDropEvent);
3348
+ dropEvent.defaultPrevented = triggeredDropEvent.defaultPrevented;
3349
+ }
3350
+ if (dropEvent.isDefaultPrevented()) {
3351
+ return;
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
+ }
3379
+ // special process for hybrid chart
3380
+ const dataDigger = new JSONDigger(datasource, chartOptions.nodeId, 'children');
3381
+ const hybridNodeData = dataDigger.findOneNode({ 'hybrid': true });
3382
+ if (chartOptions.verticalLevel > 1 || hybridNodeData) {
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
+ }
3390
+ const draggedNodeCopy = Object.assign({}, draggedNodeData);
3391
+ dataDigger.removeNode(draggedNodeId);
3392
+ if (dropNodeData.children) {
3393
+ dropNodeData.children.push(draggedNodeCopy);
3394
+ } else {
3395
+ dropNodeData.children = [draggedNodeCopy];
3396
+ }
3397
+ orgChart.init({ 'data': datasource });
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
+ }
3413
+ // The folowing code snippets are used to process horizontal chart
3414
+ draggedHierarchyEl = draggedEl.closest('.hierarchy');
3415
+ dropChildrenContainerEl = Array.from(dropZoneEl.parentElement ? dropZoneEl.parentElement.children : []).find(function (childEl) {
3416
+ return childEl !== dropZoneEl && childEl.classList && childEl.classList.contains('nodes');
3417
+ }) || null;
3418
+ // firstly, deal with the hierarchy of drop zone
3419
+ if (!dropChildrenContainerEl) { // if the drop zone is a leaf node
3420
+ dropZoneEl.insertAdjacentHTML('afterend', '<ul class="nodes"></ul>');
3421
+ dropChildrenContainerEl = dropZoneEl.nextElementSibling;
3422
+ dropChildrenContainerEl.appendChild(draggedHierarchyEl);
3423
+ } else {
3424
+ dropChildrenContainerEl.appendChild(draggedHierarchyEl);
3425
+ }
3426
+ // secondly, deal with the hierarchy of dragged node
3427
+ draggedParentChildrenContainerEl = Array.from(draggedParentNodeEl && draggedParentNodeEl.parentElement ? draggedParentNodeEl.parentElement.children : []).find(function (childEl) {
3428
+ return childEl !== draggedParentNodeEl && childEl.classList && childEl.classList.contains('nodes');
3429
+ }) || null;
3430
+ remainingHierarchyEls = draggedParentChildrenContainerEl
3431
+ ? Array.from(draggedParentChildrenContainerEl.children || []).filter(function (childEl) {
3432
+ return childEl.classList && childEl.classList.contains('hierarchy');
3433
+ })
3434
+ : [];
3435
+ if (remainingHierarchyEls.length === 0) {
3436
+ if (draggedParentChildrenContainerEl) {
3437
+ draggedParentChildrenContainerEl.remove();
3438
+ }
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);
3449
+ }
3450
+ },
3451
+ //
3452
+ touchstartHandler: function (event) {
3453
+ const nativeEvent = event.originalEvent || event;
3454
+
3455
+ if (this.touchHandled)
3456
+ return;
3457
+
3458
+ if (nativeEvent.touches && nativeEvent.touches.length > 1)
3459
+ return;
3460
+
3461
+ this.touchHandled = true;
3462
+ this.touchMoved = false; // this is so we can work out later if this was a 'press' or a 'drag' touch
3463
+ if (typeof nativeEvent.preventDefault === 'function') {
3464
+ nativeEvent.preventDefault();
3465
+ }
3466
+ },
3467
+ //
3468
+ touchmoveHandler: function (event) {
3469
+ const nativeEvent = event.originalEvent || event;
3470
+ const draggedNodeEl = event.draggedNode || nativeEvent.currentTarget || nativeEvent.target;
3471
+ const chartElement = this.chart;
3472
+ let cachedDraggedNodeEl;
3473
+ let touchedElement;
3474
+ let candidateDropNodeEl;
3475
+
3476
+ if (!this.touchHandled)
3477
+ return;
3478
+
3479
+ if (nativeEvent.touches && nativeEvent.touches.length > 1)
3480
+ return;
3481
+
3482
+ if (typeof nativeEvent.preventDefault === 'function') {
3483
+ nativeEvent.preventDefault();
3484
+ }
3485
+
3486
+ if (!this.touchMoved) {
3487
+ // we do not bother with createGhostNode (dragstart does) since the touch event does not have a dataTransfer property
3488
+ this.filterAllowedDropNodes(draggedNodeEl); // will also cache the dragged node on the chart element for us
3489
+ // create an image which can be used to illustrate the drag (our own createGhostNode)
3490
+ cachedDraggedNodeEl = chartElement ? getElement(getState(chartElement, 'dragged')) : null;
3491
+ if (!cachedDraggedNodeEl) {
3492
+ return;
3493
+ }
3494
+ this.touchDragImage = this.createDragImage(nativeEvent, cachedDraggedNodeEl);
3495
+ }
3496
+ this.touchMoved = true;
3497
+
3498
+ // move our dragimage so it follows our finger
3499
+ this.moveDragImage(nativeEvent, this.touchDragImage);
3500
+
3501
+ touchedElement = getDocument().elementFromPoint(nativeEvent.touches[0].clientX, nativeEvent.touches[0].clientY);
3502
+ candidateDropNodeEl = touchedElement && typeof touchedElement.closest === 'function'
3503
+ ? touchedElement.closest('div.node')
3504
+ : null;
3505
+ if (candidateDropNodeEl && candidateDropNodeEl.classList && candidateDropNodeEl.classList.contains('allowedDrop')) {
3506
+ this.touchTargetNode = candidateDropNodeEl;
3507
+ } else {
3508
+ this.touchTargetNode = null;
3509
+ }
3510
+ },
3511
+ //
3512
+ touchendHandler: function (event) {
3513
+ const nativeEvent = event.originalEvent || event;
3514
+ let firstTouch;
3515
+ let targetEl;
3516
+ let fakeMouseClickEvent;
3517
+
3518
+ if (!this.touchHandled) {
3519
+ return;
3520
+ }
3521
+
3522
+ this.destroyDragImage();
3523
+ if (this.touchMoved) {
3524
+ // we've had movement, so this was a 'drag' touch
3525
+ if (this.touchTargetNode) {
3526
+ const syntheticDropEvent = { dropZone: this.touchTargetNode };
3527
+ this.dropHandler(syntheticDropEvent);
3528
+ this.touchTargetNode = null;
3529
+ }
3530
+ this.dragendHandler(event);
3531
+ } else {
3532
+ // we did not move, so this was a 'press' touch (fake a click)
3533
+ firstTouch = nativeEvent.changedTouches[0];
3534
+ targetEl = nativeEvent.target;
3535
+ fakeMouseClickEvent = new (getWindow().MouseEvent)('click', {
3536
+ bubbles: true,
3537
+ cancelable: true,
3538
+ view: getWindow(),
3539
+ screenX: firstTouch.screenX,
3540
+ screenY: firstTouch.screenY,
3541
+ clientX: firstTouch.clientX,
3542
+ clientY: firstTouch.clientY,
3543
+ ctrlKey: nativeEvent.ctrlKey,
3544
+ altKey: nativeEvent.altKey,
3545
+ shiftKey: nativeEvent.shiftKey,
3546
+ metaKey: nativeEvent.metaKey,
3547
+ button: 0
3548
+ });
3549
+ targetEl.dispatchEvent(fakeMouseClickEvent);
3550
+ }
3551
+
3552
+ this.touchHandled = false;
3553
+ },
3554
+ //
3555
+ createDragImage: function (event, sourceEl) {
3556
+ const dragImageEl = sourceEl.cloneNode(true);
3557
+ this.copyStyle(sourceEl, dragImageEl);
3558
+ dragImageEl.style.top = dragImageEl.style.left = '-9999px';
3559
+ const sourceRect = sourceEl.getBoundingClientRect();
3560
+ const sourcePoint = this.getTouchPoint(event);
3561
+ this.touchDragImageOffset = { x: sourcePoint.x - sourceRect.left, y: sourcePoint.y - sourceRect.top };
3562
+ dragImageEl.style.opacity = '0.5';
3563
+ getDocument().body.appendChild(dragImageEl);
3564
+ return dragImageEl;
3565
+ },
3566
+ //
3567
+ destroyDragImage: function () {
3568
+ if (this.touchDragImage && this.touchDragImage.parentElement)
3569
+ this.touchDragImage.parentElement.removeChild(this.touchDragImage);
3570
+ this.touchDragImageOffset = null;
3571
+ this.touchDragImage = null;
3572
+ },
3573
+ //
3574
+ copyStyle: function (sourceEl, targetEl) {
3575
+ // remove potentially troublesome attributes
3576
+ const badAttributes = ['id', 'class', 'style', 'draggable'];
3577
+ badAttributes.forEach(function (attributeName) {
3578
+ targetEl.removeAttribute(attributeName);
3579
+ });
3580
+ // copy canvas content
3581
+ if (sourceEl instanceof HTMLCanvasElement) {
3582
+ const sourceCanvas = sourceEl;
3583
+ const targetCanvas = targetEl;
3584
+ targetCanvas.width = sourceCanvas.width;
3585
+ targetCanvas.height = sourceCanvas.height;
3586
+ targetCanvas.getContext('2d').drawImage(sourceCanvas, 0, 0);
3587
+ }
3588
+ // copy style (without transitions)
3589
+ const computedStyle = getComputedStyle(sourceEl);
3590
+ for (let i = 0; i < computedStyle.length; i++) {
3591
+ const key = computedStyle[i];
3592
+ if (key.indexOf('transition') < 0) {
3593
+ targetEl.style[key] = computedStyle[key];
3594
+ }
3595
+ }
3596
+ targetEl.style.pointerEvents = 'none';
3597
+ // and repeat for all children
3598
+ for (let i = 0; i < sourceEl.children.length; i++) {
3599
+ this.copyStyle(sourceEl.children[i], targetEl.children[i]);
3600
+ }
3601
+ },
3602
+ //
3603
+ getTouchPoint: function (event) {
3604
+ let nativeEvent = event ? (event.originalEvent || event) : null;
3605
+
3606
+ if (nativeEvent && nativeEvent.touches) {
3607
+ nativeEvent = nativeEvent.touches[0];
3608
+ }
3609
+ return {
3610
+ x: nativeEvent.clientX,
3611
+ y: nativeEvent.clientY
3612
+ };
3613
+ },
3614
+ //
3615
+ moveDragImage: function (event, dragImageEl) {
3616
+ if (!event || !dragImageEl)
3617
+ return;
3618
+ const orgChartMaster = this;
3619
+ requestAnimationFrame(function () {
3620
+ const touchPoint = orgChartMaster.getTouchPoint(event);
3621
+ const dragImageStyle = dragImageEl.style;
3622
+ dragImageStyle.position = 'absolute';
3623
+ dragImageStyle.pointerEvents = 'none';
3624
+ dragImageStyle.zIndex = '999999';
3625
+ if (orgChartMaster.touchDragImageOffset) {
3626
+ dragImageStyle.left = Math.round(touchPoint.x - orgChartMaster.touchDragImageOffset.x) + 'px';
3627
+ dragImageStyle.top = Math.round(touchPoint.y - orgChartMaster.touchDragImageOffset.y) + 'px';
3628
+ }
3629
+ });
3630
+ },
3631
+ //
3632
+ bindDragDrop: function (nodeEl) {
3633
+ const nodeElement = getElement(nodeEl);
3634
+
3635
+ if (!nodeElement) {
3636
+ return;
3637
+ }
3638
+
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
+ });
3656
+ },
3657
+ // create node
3658
+ createNode: function (data) {
3659
+ const opts = this.options;
3660
+ const level = data.level;
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);
3666
+ const nodeClassName = [
3667
+ 'node',
3668
+ data.className || '',
3669
+ data?.outsider ? 'outsider' : '',
3670
+ level > opts.visibleLevel ? 'slide-up' : ''
3671
+ ].filter(Boolean).join(' ');
3672
+ const titleMarkupParts = [];
3673
+ const contentMarkupParts = [];
3674
+ const nodeMarkupParts = [];
3675
+ const shouldAddParentSymbol = !!(data.children && data.children.length);
3676
+
3677
+ if (data.children && data[opts.nodeId]) {
3678
+ forEachValue(data.children, function (index, child) {
3679
+ child.parentId = data[opts.nodeId]
3680
+ });
3681
+ }
3682
+
3683
+ if (shouldAddParentSymbol) {
3684
+ titleMarkupParts.push(`<i class="${opts.icons.theme} ${opts.icons.parentNode} parentNodeSymbol"></i>`);
3685
+ }
3686
+ titleMarkupParts.push(data[opts.nodeTitle]);
3687
+
3688
+ if (opts.nodeTemplate) {
3689
+ nodeMarkupParts.push(opts.nodeTemplate(data));
3690
+ } else {
3691
+ nodeMarkupParts.push(`<div class="title">${titleMarkupParts.join('')}</div>`);
3692
+ if (typeof opts.nodeContent !== 'undefined') {
3693
+ contentMarkupParts.push(`<div class="content">${data[opts.nodeContent] || ''}</div>`);
3694
+ }
3695
+ nodeMarkupParts.push(contentMarkupParts.join(''));
3696
+ }
3697
+
3698
+ // construct the content of node
3699
+ const nodeElement = getDocument().createElement('div');
3700
+ const routeNodeClick = function (selector, handler) {
3701
+ nodeElement.addEventListener('click', function (event) {
3702
+ const matchedElement = event.target && typeof event.target.closest === 'function'
3703
+ ? event.target.closest(selector)
3704
+ : null;
3705
+
3706
+ if (!matchedElement || matchedElement.parentElement !== nodeElement) {
3707
+ return;
3708
+ }
3709
+
3710
+ handler.call(this, event);
3711
+ }.bind(this));
3712
+ }.bind(this);
3713
+
3714
+ if (opts.draggable) {
3715
+ nodeElement.setAttribute('draggable', 'true');
3716
+ }
3717
+ if (data[opts.nodeId]) {
3718
+ nodeElement.id = data[opts.nodeId];
3719
+ }
3720
+ if (data.parentId) {
3721
+ nodeElement.setAttribute('data-parent', data.parentId);
3722
+ }
3723
+ nodeElement.className = nodeClassName;
3724
+ nodeElement.tabIndex = 0;
3725
+ nodeElement.insertAdjacentHTML('beforeend', nodeMarkupParts.join(''));
3726
+ //
3727
+ const storedNodeData = mergeObjects({}, data);
3728
+ delete storedNodeData.children;
3729
+ setState(nodeElement, 'nodeData', storedNodeData);
3730
+ setState(nodeElement, 'sourceData', data);
3731
+ nodeElement.__ocNodeData = storedNodeData;
3732
+ if (isCompactLayout && nodeElement && nodeElement.style) {
3733
+ nodeElement.style.gridTemplateColumns = `repeat(${Math.floor(Math.sqrt((data.children || []).length + 1))}, auto)`;
3734
+ }
3735
+
3736
+ nodeElement.addEventListener('mouseenter', this.nodeEnterLeaveHandler.bind(this));
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));
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
+ });
3756
+ routeNodeClick('.topEdge', this.topEdgeClickHandler);
3757
+ routeNodeClick('.bottomEdge', this.bottomEdgeClickHandler);
3758
+ routeNodeClick('.leftEdge, .rightEdge', this.hEdgeClickHandler);
3759
+ routeNodeClick('.toggleBtn', this.toggleVNodes);
3760
+ routeNodeClick('.backToCompactSymbol', this.backToCompactHandler);
3761
+ routeNodeClick('.backToLooseSymbol', this.backToLooseHandler);
3762
+
3763
+ if (opts.draggable) {
3764
+ this.bindDragDrop(nodeElement);
3765
+ this.touchHandled = false;
3766
+ this.touchMoved = false;
3767
+ this.touchTargetNode = null;
3768
+ }
3769
+ // allow user to append dom modification after finishing node create of orgchart
3770
+ if (opts.createNode) {
3771
+ opts.createNode(nodeElement, data);
3772
+ }
3773
+
3774
+ return nodeElement;
3775
+ },
3776
+ // Construct the inferior nodes within a hierarchy
3777
+ buildInferiorNodes: function (hierarchyEl, nodeEl, data, level) {
3778
+ const orgChart = this;
3779
+ const opts = this.options;
3780
+ const hierarchyElement = getElement(hierarchyEl);
3781
+ const nodeElement = getElement(nodeEl);
3782
+ const isHidden = level + 1 > opts.visibleLevel || (data.collapsed !== undefined && data.collapsed);
3783
+ const isCompactLayout = usesCompactLayout(data, opts, level);
3784
+ let nodesLayerElement;
3785
+
3786
+ if (!hierarchyElement) {
3787
+ return;
3788
+ }
3789
+
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) {
3801
+ nodesLayerElement = getDocument().createElement('ul');
3802
+ nodesLayerElement.className = 'nodes';
3803
+ if (isHidden) {
3804
+ nodesLayerElement.classList.add('hidden');
3805
+ }
3806
+ if (((opts.verticalLevel && level + 1 === opts.verticalLevel) || data.hybrid)
3807
+ && !hierarchyElement.closest('.vertical')) {
3808
+ nodesLayerElement.classList.add('vertical');
3809
+ }
3810
+ hierarchyElement.appendChild(nodesLayerElement);
3811
+ } else if (isCompactLayout) {
3812
+ if (nodeElement && nodeElement.classList) {
3813
+ nodeElement.classList.add('compact');
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
+ }
3821
+ } else {
3822
+ nodesLayerElement = getDocument().createElement('ul');
3823
+ nodesLayerElement.className = isHidden ? 'nodes hidden' : 'nodes';
3824
+ if (isHidden) {
3825
+ hierarchyElement.classList.add('isChildrenCollapsed');
3826
+ }
3827
+ hierarchyElement.appendChild(nodesLayerElement);
3828
+ }
3829
+ if (nodesLayerElement) {
3830
+ const parentNode = nodeElement || hierarchyElement.querySelector(':scope > .node');
3831
+ setState(nodesLayerElement, 'parentData', parentNode ? getState(parentNode, 'sourceData') || data : data);
3832
+ }
3833
+ // recurse through children nodes
3834
+ if (Array.isArray(data.children[0])) {
3835
+ forEachValue(data.children, function() {
3836
+ forEachValue(this, function () { this.level = level + 1; });
3837
+ });
3838
+ this.buildHierarchy(nodesLayerElement, data.children); // 构造子一层的夫妻组合(每个组合可能有多妻多夫情况)
3839
+ } else {
3840
+ forEachValue(data.children, function () {
3841
+ this.level = level + 1;
3842
+ if (isCompactLayout) {
3843
+ orgChart.buildHierarchy(nodeElement, this);
3844
+ } else {
3845
+ const nodeCellElement = getDocument().createElement('li');
3846
+
3847
+ nodeCellElement.className = 'hierarchy';
3848
+ nodesLayerElement.appendChild(nodeCellElement);
3849
+ orgChart.buildHierarchy(nodeCellElement, this);
3850
+ }
3851
+ });
3852
+ }
3853
+ },
3854
+ // recursively build the tree
3855
+ buildHierarchy: function (hierarchyEl, data) {
3856
+ const orgChart = this;
3857
+ const hierarchyElement = getElement(hierarchyEl);
3858
+ let level = 0;
3859
+ let nodeCollection;
3860
+ let nodeElement;
3861
+
3862
+ if (!hierarchyElement) {
3863
+ return;
3864
+ }
3865
+
3866
+ if (data.level || data[0]?.level || data[0]?.[0]?.level) {
3867
+ level = data.level || data[0]?.level || data[0]?.[0]?.level;
3868
+ } else {
3869
+ let currentAncestorElement = hierarchyElement.parentElement;
3870
+
3871
+ while (currentAncestorElement && !currentAncestorElement.matches('.orgchart')) {
3872
+ if (currentAncestorElement.classList && currentAncestorElement.classList.contains('nodes')) {
3873
+ level += 1;
3874
+ }
3875
+ currentAncestorElement = currentAncestorElement.parentElement;
3876
+ }
3877
+ if (Array.isArray(data) && Array.isArray(data[0])) {
3878
+ level = Math.max(level, 1);
3879
+ forEachValue(data, function () {
3880
+ forEachValue(this, function () {
3881
+ this.level = level;
3882
+ });
3883
+ });
3884
+ } else {
3885
+ data.level = level;
3886
+ }
3887
+ }
3888
+ // Construct the single node in OrgChart or the multiple nodes in family tree
3889
+ if (Array.isArray(data) && Array.isArray(data[0])) { // 处理family tree的情况
3890
+ forEachValue(data, function () { // 构造一个家庭的hierarchy
3891
+ const familyNodes = this;
3892
+ forEachValue(this, function (i) { // 构造一个夫/妻节点
3893
+ let spouseNodeElement;
3894
+ let wrapperElement;
3895
+
3896
+ nodeElement = orgChart.createNode(this);
3897
+ // if there are only two persons in a marriage, two single nodes will appear in a hierarchy
3898
+ if (familyNodes.length === 2 && i === 1) {
3899
+ spouseNodeElement = Array.from(hierarchyElement.querySelectorAll('.node')).find(function (candidateNodeEl) {
3900
+ return getState(candidateNodeEl, 'sourceData') === familyNodes[0];
3901
+ }) || null;
3902
+ if (spouseNodeElement && spouseNodeElement.parentElement) {
3903
+ spouseNodeElement.after(nodeElement);
3904
+ }
3905
+ if (this.children && this.children.length && this.children[0].length) {
3906
+ orgChart.buildInferiorNodes(spouseNodeElement ? spouseNodeElement.parentElement : null, nodeElement, this, level);
3907
+ }
3908
+ } else {
3909
+ // if there are more than two persons in a marriage, every node will be included in a single hierarchy
3910
+ wrapperElement = getDocument().createElement('li');
3911
+ wrapperElement.className = `hierarchy${familyNodes.length > 1 ? ' spouse' : ''}${familyNodes.length === 2 ? ' couple' : ''}${!!this.outsider === false && familyNodes.length > 2 ? ' insider' : ''}`;
3912
+
3913
+ //在family tree中,一个多妻/多夫组合里,本姓人只有一个,外姓人可能有多个,我们通过水平的连线来表示他们是一家子
3914
+ if (i === 0) {
3915
+ wrapperElement.style.setProperty('--ft-width', '50%');
3916
+ wrapperElement.style.setProperty('--ft-left-offset', '50%');
3917
+ } else if (i > 0 && i < familyNodes.length - 1) {
3918
+ wrapperElement.style.setProperty('--ft-width', '100%');
3919
+ wrapperElement.style.setProperty('--ft-left-offset', '0px');
3920
+ } else {
3921
+ wrapperElement.style.setProperty('--ft-width', '50%');
3922
+ wrapperElement.style.setProperty('--ft-left-offset', '0px');
3923
+ }
3924
+
3925
+ wrapperElement.appendChild(nodeElement);
3926
+ hierarchyElement.appendChild(wrapperElement);
3927
+ if (this.children && this.children.length && this.children[0].length) {
3928
+ orgChart.buildInferiorNodes(wrapperElement, nodeElement, this, level);
3929
+ }
3930
+ }
3931
+ });
3932
+ });
3933
+ } else {
3934
+ if (Object.keys(data).some(function (key) { return key !== 'children' && key !== 'level'; })) {
3935
+ nodeElement = this.createNode(data);
3936
+ hierarchyElement.appendChild(nodeElement);
3937
+ }
3938
+ if (data.children && data.children.length) {
3939
+ this.buildInferiorNodes(hierarchyElement, nodeElement || null, data, level);
3940
+ }
3941
+ }
3942
+ },
3943
+ // build the child nodes of specific node
3944
+ buildChildNode: function (appendToEl, data) {
3945
+ const parentHierarchyEl = appendToEl;
3946
+ const existingChildNodesElement = parentHierarchyEl
3947
+ ? Array.from(parentHierarchyEl.children || []).find(function (childEl) {
3948
+ return childEl.classList && childEl.classList.contains('nodes');
3949
+ }) || null
3950
+ : null;
3951
+ const orgChart = this;
3952
+
3953
+ if (!parentHierarchyEl) {
3954
+ return;
3955
+ }
3956
+
3957
+ if (!existingChildNodesElement) {
3958
+ this.buildHierarchy(parentHierarchyEl, { 'children': data });
3959
+ return;
3960
+ }
3961
+
3962
+ forEachValue(data, function () {
3963
+ const childHierarchyElement = getDocument().createElement('li');
3964
+
3965
+ childHierarchyElement.className = 'hierarchy';
3966
+ existingChildNodesElement.appendChild(childHierarchyElement);
3967
+ orgChart.buildHierarchy(childHierarchyElement, this);
3968
+ });
3969
+ },
3970
+ // exposed method
3971
+ addChildren: function (nodeEl, data) {
3972
+ const nodeElement = getElement(nodeEl);
3973
+
3974
+ if (!nodeElement) {
3975
+ return;
3976
+ }
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 = [];
3983
+ }
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;
3988
+ }
3989
+ }
3990
+
3991
+ this.buildChildNode(nodeElement.closest('.hierarchy'), data);
3992
+ this.refreshNodeControls(nodeElement);
3993
+ },
3994
+ // build the parent node of specific node
3995
+ buildParentNode: function (currentRootEl, data) {
3996
+ const currentRootElement = getElement(currentRootEl);
3997
+ let currentRootListElement;
3998
+ let currentRootHierarchyElement;
3999
+ let parentHierarchyElement;
4000
+ let newRootWrapperElement;
4001
+ let newRootHierarchyElement;
4002
+ let newChildNodesElement;
4003
+
4004
+ if (!currentRootElement) {
4005
+ return;
4006
+ }
4007
+
4008
+ currentRootListElement = currentRootElement.closest('ul');
4009
+ currentRootHierarchyElement = currentRootElement.closest('.hierarchy');
4010
+ parentHierarchyElement = currentRootListElement ? currentRootListElement.closest('.hierarchy') : null;
4011
+ newRootWrapperElement = getDocument().createElement('ul');
4012
+ newRootWrapperElement.className = 'nodes';
4013
+ newRootHierarchyElement = getDocument().createElement('li');
4014
+ newRootHierarchyElement.className = 'hierarchy';
4015
+ newRootHierarchyElement.appendChild(this.createNode(data));
4016
+
4017
+ if (parentHierarchyElement && currentRootHierarchyElement) {
4018
+ newChildNodesElement = getDocument().createElement('ul');
4019
+ newChildNodesElement.className = 'nodes';
4020
+ newRootHierarchyElement.appendChild(newChildNodesElement);
4021
+ currentRootListElement.replaceChild(newRootHierarchyElement, currentRootHierarchyElement);
4022
+ newChildNodesElement.appendChild(currentRootHierarchyElement);
4023
+ return;
4024
+ }
4025
+
4026
+ newRootWrapperElement.appendChild(newRootHierarchyElement);
4027
+ if (this.chart) {
4028
+ this.chart.prepend(newRootWrapperElement);
4029
+ }
4030
+ if (currentRootListElement) {
4031
+ currentRootListElement.classList.add('nodes');
4032
+ newRootHierarchyElement.appendChild(currentRootListElement);
4033
+ }
4034
+ },
4035
+ // exposed method
4036
+ addParent: function (currentRootEl, data) {
4037
+ const currentRootElement = getElement(currentRootEl);
4038
+
4039
+ if (!currentRootElement) {
4040
+ return;
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
+ }
4066
+
4067
+ const currentLocation = currentData ? findDataLocation(datasource, currentData) : null;
4068
+ this.buildParentNode(currentRootElement, data);
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;
4077
+ }
4078
+ currentData.parentId = data[this.options.nodeId];
4079
+ getState(currentRootElement, 'nodeData').parentId = currentData.parentId;
4080
+ currentRootElement.setAttribute('data-parent', currentData.parentId);
4081
+ }
4082
+ this.refreshNodeControls(this.getParent(currentRootElement));
4083
+ },
4084
+ // build the sibling nodes of specific node
4085
+ buildSiblingNode: function (nodeChartEl, data) {
4086
+ const nodeChartElement = getElement(nodeChartEl);
4087
+ let nodeChartParentElement;
4088
+ let newSiblingCount;
4089
+ let existingSiblingCount;
4090
+ let siblingCount;
4091
+ let insertPosition;
4092
+ let ancestorNodesElement;
4093
+ let parentHierarchyElement;
4094
+ let newSiblingContainerElement;
4095
+ let newSiblingHierarchyElements;
4096
+ let existingHierarchyElements;
4097
+ let newParentHierarchyElement;
4098
+ let newParentChildrenContainerElement;
4099
+ let insertAnchorElement;
4100
+ const removeIfEmpty = function (containerElement) {
4101
+ if (containerElement && !containerElement.children.length) {
4102
+ containerElement.remove();
4103
+ }
4104
+ };
4105
+ const getHierarchyChildren = function (containerElement) {
4106
+ return containerElement
4107
+ ? Array.from(containerElement.children).filter(function (childEl) {
4108
+ return childEl.classList && childEl.classList.contains('hierarchy');
4109
+ })
4110
+ : [];
4111
+ };
4112
+ const getLastNodesChild = function (containerElement) {
4113
+ let childElement;
4114
+
4115
+ if (!containerElement) {
4116
+ return null;
4117
+ }
4118
+
4119
+ childElement = containerElement.lastElementChild;
4120
+ while (childElement) {
4121
+ if (childElement.classList && childElement.classList.contains('nodes')) {
4122
+ return childElement;
4123
+ }
4124
+ childElement = childElement.previousElementSibling;
4125
+ }
4126
+
4127
+ return null;
4128
+ };
4129
+
4130
+ if (!nodeChartElement) {
4131
+ return;
4132
+ }
4133
+
4134
+ nodeChartParentElement = nodeChartElement.parentElement;
4135
+ newSiblingCount = Array.isArray(data) ? data.length : data.children.length;
4136
+ existingSiblingCount = nodeChartParentElement && nodeChartParentElement.classList && nodeChartParentElement.classList.contains('nodes')
4137
+ ? getHierarchyChildren(nodeChartParentElement).length
4138
+ : 1;
4139
+ siblingCount = existingSiblingCount + newSiblingCount;
4140
+ insertPosition = (siblingCount > 1) ? Math.floor(siblingCount / 2 - 1) : 0;
4141
+ // just build the sibling nodes for the specific node
4142
+ ancestorNodesElement = nodeChartElement.closest('.nodes');
4143
+ if (ancestorNodesElement && ancestorNodesElement.parentElement && ancestorNodesElement.parentElement.classList && ancestorNodesElement.parentElement.classList.contains('hierarchy')) {
4144
+ parentHierarchyElement = nodeChartParentElement ? nodeChartParentElement.closest('.hierarchy') : null;
4145
+ this.buildChildNode(parentHierarchyElement, data);
4146
+ newSiblingContainerElement = getLastNodesChild(parentHierarchyElement);
4147
+ newSiblingHierarchyElements = getHierarchyChildren(newSiblingContainerElement);
4148
+ if (existingSiblingCount > 1 && newSiblingContainerElement !== nodeChartParentElement) {
4149
+ existingHierarchyElements = getHierarchyChildren(nodeChartParentElement);
4150
+ existingHierarchyElements.forEach(function (existingSiblingEl) {
4151
+ if (newSiblingHierarchyElements[0] && newSiblingHierarchyElements[0].parentElement) {
4152
+ newSiblingHierarchyElements[0].parentElement.insertBefore(existingSiblingEl, newSiblingHierarchyElements[0]);
4153
+ }
4154
+ });
4155
+ removeIfEmpty(nodeChartParentElement);
4156
+ } else if (newSiblingContainerElement !== nodeChartParentElement) {
4157
+ insertAnchorElement = newSiblingHierarchyElements[insertPosition] || null;
4158
+ if (insertAnchorElement && insertAnchorElement.parentElement) {
4159
+ insertAnchorElement.parentElement.insertBefore(nodeChartElement, insertAnchorElement.nextSibling);
4160
+ }
4161
+ removeIfEmpty(nodeChartParentElement);
4162
+ }
4163
+ } else { // build the sibling nodes and parent node for the specific ndoe
4164
+ newParentHierarchyElement = getDocument().createElement('li');
4165
+ newParentHierarchyElement.className = 'hierarchy';
4166
+ if (nodeChartParentElement) {
4167
+ nodeChartParentElement.insertBefore(newParentHierarchyElement, nodeChartParentElement.firstChild);
4168
+ }
4169
+ this.buildHierarchy(newParentHierarchyElement, data);
4170
+ newParentChildrenContainerElement = getLastNodesChild(newParentHierarchyElement);
4171
+ insertAnchorElement = newParentChildrenContainerElement
4172
+ ? getHierarchyChildren(newParentChildrenContainerElement)[insertPosition] || null
4173
+ : null;
4174
+ if (insertAnchorElement && insertAnchorElement.parentElement) {
4175
+ insertAnchorElement.parentElement.insertBefore(nodeChartElement, insertAnchorElement.nextSibling);
4176
+ }
4177
+ }
4178
+ },
4179
+ //
4180
+ addSiblings: function (nodeEl, data) {
4181
+ const nodeElement = getElement(nodeEl);
4182
+ let nodesContainerElement;
4183
+
4184
+ if (!nodeElement) {
4185
+ return;
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
+ }
4209
+
4210
+ const oldParent = this.getParent(nodeElement);
4211
+ const oldParentData = oldParent ? getState(oldParent, 'sourceData') : null;
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
+ }
4222
+ nodesContainerElement = nodeElement.closest('.nodes');
4223
+ if (nodesContainerElement) {
4224
+ setState(nodesContainerElement, 'siblingsLoaded', true);
4225
+ }
4226
+ this.refreshNodeControls(this.getParent(nodeElement));
4227
+ },
4228
+ // remove node and its descendent nodes
4229
+ removeNodes: function (nodeEl) {
4230
+ const nodeElement = getElement(nodeEl);
4231
+ let hierarchyElement;
4232
+ let wrapperElement;
4233
+
4234
+ if (!nodeElement) {
4235
+ return;
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
+ }
4265
+
4266
+ hierarchyElement = nodeElement.closest('.hierarchy');
4267
+ wrapperElement = hierarchyElement ? hierarchyElement.parentElement : null;
4268
+
4269
+ if (!wrapperElement) {
4270
+ return;
4271
+ }
4272
+
4273
+ if (wrapperElement.parentElement && wrapperElement.parentElement.classList && wrapperElement.parentElement.classList.contains('hierarchy')) {
4274
+ if (this.getNodeState(nodeElement, 'siblings').exist) {
4275
+ hierarchyElement.remove();
4276
+ } else {
4277
+ wrapperElement.remove();
4278
+ }
4279
+ this.refreshNodeControls(parentElement);
4280
+ } else { // if $node is root node
4281
+ const chartElement = wrapperElement.closest('.orgchart');
4282
+ if (chartElement) {
4283
+ chartElement.remove();
4284
+ }
4285
+ }
4286
+ },
4287
+ //
4288
+ hideDropZones: function () {
4289
+ const chartElement = this.chart;
4290
+
4291
+ if (!chartElement) {
4292
+ return;
4293
+ }
4294
+
4295
+ // Remove all the 'this is a drop zone' indicators
4296
+ Array.from(chartElement.querySelectorAll('.allowedDrop')).forEach(function (node) {
4297
+ node.classList.remove('allowedDrop');
4298
+ });
4299
+ },
4300
+ //
4301
+ showDropZones: function (draggedEl) {
4302
+ // Highlight all the 'drop zones', and set dragged, so that the drop/enter can work out what happens later
4303
+ // TODO: This assumes all nodes are droppable: it doesn't run the custom isDroppable function - it should!
4304
+ const draggedNodeEl = getElement(draggedEl);
4305
+ const chartElement = this.chart;
4306
+
4307
+ if (!chartElement) {
4308
+ return;
4309
+ }
4310
+
4311
+ Array.from(chartElement.querySelectorAll('.node')).forEach(function (node) {
4312
+ node.classList.add('allowedDrop');
4313
+ });
4314
+ setState(chartElement, 'dragged', draggedNodeEl);
4315
+ },
4316
+ //
4317
+ processExternalDrop: function (dropTargetEl, draggedEl) {
4318
+ const dropZoneEl = getElement(dropTargetEl);
4319
+ const droppedOnNodeEl = dropZoneEl && typeof dropZoneEl.closest === 'function'
4320
+ ? dropZoneEl.closest('.node')
4321
+ : null;
4322
+ let draggedNodeEl;
4323
+ const chartElement = this.chart;
4324
+
4325
+ // Allow an external drop event to be handled by one of our nodes
4326
+ if (draggedEl && chartElement) {
4327
+ draggedNodeEl = getElement(draggedEl);
4328
+ setState(chartElement, 'dragged', draggedNodeEl);
4329
+ }
4330
+ if (!droppedOnNodeEl) {
4331
+ return;
4332
+ }
4333
+ // would like to just call 'dropZoneHandler', but I can't reach it from here
4334
+ // instead raise a drop event on the node element
4335
+ droppedOnNodeEl.dispatchEvent(createTriggeredEvent('drop'));
4336
+ },
4337
+ //
4338
+ exportPDF: function(canvas, exportFilename, exportScale){
4339
+ let doc = {};
4340
+ const scale = exportScale > 0 ? exportScale : 1;
4341
+ const docWidth = Math.floor(canvas.width / scale);
4342
+ const docHeight = Math.floor(canvas.height / scale);
4343
+ if (!getWindow().jsPDF) {
4344
+ getWindow().jsPDF = getWindow().jspdf.jsPDF;
4345
+ }
4346
+
4347
+ if (docWidth > docHeight) {
4348
+ doc = new jsPDF({
4349
+ orientation: 'landscape',
4350
+ unit: 'px',
4351
+ format: [docWidth, docHeight]
4352
+ });
4353
+ } else {
4354
+ doc = new jsPDF({
4355
+ orientation: 'portrait',
4356
+ unit: 'px',
4357
+ format: [docHeight, docWidth]
4358
+ });
4359
+ }
4360
+ doc.addImage(canvas.toDataURL(), 'png', 0, 0, docWidth, docHeight);
4361
+ doc.save(exportFilename + '.pdf');
4362
+ },
4363
+ //
4364
+ exportPNG: function(canvas, exportFilename){
4365
+ const isWebkit = 'WebkitAppearance' in getDocument().documentElement.style;
4366
+ const isFf = !!getWindow().sidebar;
4367
+ const navigatorObject = getWindow().navigator;
4368
+ const isEdge = navigatorObject.appName === 'Microsoft Internet Explorer' || (navigatorObject.appName === 'Netscape' && navigatorObject.appVersion.indexOf('Edge') > -1);
4369
+ const chartContainerElement = this.chartContainer;
4370
+ let downloadButtonClassName;
4371
+ let downloadAnchorElement;
4372
+
4373
+ if (!chartContainerElement) {
4374
+ return;
4375
+ }
4376
+
4377
+ if ((!isWebkit && !isFf) || isEdge) {
4378
+ getWindow().navigator.msSaveBlob(canvas.msToBlob(), exportFilename + '.png');
4379
+ } else {
4380
+ downloadButtonClassName = 'download-btn' + (this.options.chartClass !== '' ? ' ' + this.options.chartClass : '');
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
+ });
4387
+
4388
+ if (!downloadAnchorElement) {
4389
+ downloadAnchorElement = getDocument().createElement('a');
4390
+ downloadAnchorElement.className = downloadButtonClassName;
4391
+ chartContainerElement.appendChild(downloadAnchorElement);
4392
+ }
4393
+
4394
+ downloadAnchorElement.setAttribute('download', exportFilename + '.png');
4395
+ downloadAnchorElement.setAttribute('href', canvas.toDataURL());
4396
+ downloadAnchorElement.click();
4397
+ }
4398
+ },
4399
+ //
4400
+ export: function (exportFilename, exportFileextension) {
4401
+ const orgChart = this;
4402
+ const chartContainerElement = this.chartContainer;
4403
+ let maskElement;
4404
+ let sourceChartElement;
4405
+ let isHorizontalDirection;
4406
+ let exportScale;
4407
+
4408
+ exportFilename = (typeof exportFilename !== 'undefined') ? exportFilename : this.options.exportFilename;
4409
+ exportFileextension = (typeof exportFileextension !== 'undefined') ? exportFileextension : this.options.exportFileextension;
4410
+ if (chartContainerElement && chartContainerElement.querySelector('.spinner')) {
4411
+ return false;
4412
+ }
4413
+
4414
+ if (!chartContainerElement) {
4415
+ return false;
4416
+ }
4417
+
4418
+ maskElement = chartContainerElement.querySelector('.mask');
4419
+ if (!maskElement) {
4420
+ maskElement = getDocument().createElement('div');
4421
+ maskElement.className = 'mask';
4422
+ maskElement.insertAdjacentHTML('beforeend', `<i class="${this.options.icons.theme} ${this.options.icons.spinner} spinner"></i>`);
4423
+ chartContainerElement.appendChild(maskElement);
4424
+ } else {
4425
+ maskElement.classList.remove('hidden');
4426
+ }
4427
+ chartContainerElement.classList.add('canvasContainer');
4428
+ sourceChartElement = Array.from(chartContainerElement.querySelectorAll('.orgchart')).find(function (chartEl) {
4429
+ return !chartEl.classList.contains('hidden');
4430
+ }) || null;
4431
+ isHorizontalDirection = orgChart.options.direction === 'l2r' || orgChart.options.direction === 'r2l';
4432
+ exportScale = orgChart.options.exportScale > 0 ? orgChart.options.exportScale : 1;
4433
+ html2canvas(sourceChartElement, {
4434
+ 'width': isHorizontalDirection ? sourceChartElement.clientHeight : sourceChartElement.clientWidth,
4435
+ 'height': isHorizontalDirection ? sourceChartElement.clientWidth : sourceChartElement.clientHeight,
4436
+ 'scale': exportScale,
4437
+ 'useCORS': true,
4438
+ 'onclone': function (cloneDoc) {
4439
+ const clonedContainerElement = cloneDoc.querySelector('.canvasContainer');
4440
+ let clonedChartElement;
4441
+
4442
+ if (!clonedContainerElement) {
4443
+ return;
4444
+ }
4445
+
4446
+ clonedContainerElement.style.overflow = 'visible';
4447
+ clonedChartElement = Array.from(clonedContainerElement.querySelectorAll('.orgchart')).find(function (chartEl) {
4448
+ return !chartEl.classList.contains('hidden');
4449
+ }) || null;
4450
+ if (clonedChartElement) {
4451
+ clonedChartElement.style.transform = '';
4452
+ }
4453
+ }
4454
+ })
4455
+ .then(function (canvas) {
4456
+ if (maskElement) {
4457
+ maskElement.classList.add('hidden');
4458
+ }
4459
+
4460
+ if (exportFileextension.toLowerCase() === 'pdf') {
4461
+ orgChart.exportPDF(canvas, exportFilename, exportScale);
4462
+ } else {
4463
+ orgChart.exportPNG(canvas, exportFilename);
4464
+ }
4465
+
4466
+ chartContainerElement.classList.remove('canvasContainer');
4467
+ }, function () {
4468
+ chartContainerElement.classList.remove('canvasContainer');
4469
+ });
4470
+ }
4471
+ };
4472
+
4473
+ return OrgChart;
4474
+ }));