@progress/kendo-charts 2.4.0-dev.202405201104 → 2.4.0-dev.202405290547

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 (52) hide show
  1. package/dist/cdn/js/kendo-charts.js +1 -1
  2. package/dist/cdn/main.js +1 -1
  3. package/dist/es/chart/legend/legend-item.js +1 -1
  4. package/dist/es/common/event-map.js +17 -0
  5. package/dist/es/common/event-utils.js +61 -0
  6. package/dist/es/common/get-supported-features.js +55 -0
  7. package/dist/es/common/noop.js +1 -0
  8. package/dist/es/common/now.js +3 -0
  9. package/dist/es/common/observable.js +1 -3
  10. package/dist/es/{map/scroller → common}/user-events.js +43 -131
  11. package/dist/es/common.js +7 -0
  12. package/dist/es/map/layers/layer.js +2 -5
  13. package/dist/es/map/layers/marker.js +3 -3
  14. package/dist/es/map/map.js +6 -6
  15. package/dist/es/map/navigator.js +3 -3
  16. package/dist/es/map/scroller/draggable.js +6 -11
  17. package/dist/es/map/scroller/fx.js +2 -2
  18. package/dist/es/map/scroller/scroller.js +7 -12
  19. package/dist/es/map/utils.js +9 -339
  20. package/dist/es/map/zoom.js +3 -3
  21. package/dist/es/sankey/legend.js +5 -0
  22. package/dist/es/sankey/link.js +139 -1
  23. package/dist/es/sankey/node.js +56 -5
  24. package/dist/es/sankey/sankey.js +319 -4
  25. package/dist/es/services/dom-events-builder.js +2 -4
  26. package/dist/es2015/chart/legend/legend-item.js +1 -1
  27. package/dist/es2015/common/event-map.js +17 -0
  28. package/dist/es2015/common/event-utils.js +61 -0
  29. package/dist/es2015/common/get-supported-features.js +55 -0
  30. package/dist/es2015/common/noop.js +1 -0
  31. package/dist/es2015/common/now.js +3 -0
  32. package/dist/es2015/common/observable.js +1 -3
  33. package/dist/es2015/{map/scroller → common}/user-events.js +39 -131
  34. package/dist/es2015/common.js +7 -0
  35. package/dist/es2015/map/layers/layer.js +2 -5
  36. package/dist/es2015/map/layers/marker.js +3 -3
  37. package/dist/es2015/map/map.js +6 -6
  38. package/dist/es2015/map/navigator.js +3 -3
  39. package/dist/es2015/map/scroller/draggable.js +6 -11
  40. package/dist/es2015/map/scroller/fx.js +2 -2
  41. package/dist/es2015/map/scroller/scroller.js +7 -12
  42. package/dist/es2015/map/utils.js +8 -339
  43. package/dist/es2015/map/zoom.js +3 -3
  44. package/dist/es2015/sankey/legend.js +5 -0
  45. package/dist/es2015/sankey/link.js +119 -1
  46. package/dist/es2015/sankey/node.js +55 -5
  47. package/dist/es2015/sankey/sankey.js +307 -7
  48. package/dist/es2015/services/dom-events-builder.js +2 -4
  49. package/dist/npm/main.js +3194 -2954
  50. package/dist/npm/sankey.d.ts +65 -2
  51. package/dist/systemjs/kendo-charts.js +1 -1
  52. package/package.json +1 -1
@@ -1,15 +1,13 @@
1
- import {
2
- isFunction,
3
- isArray,
4
- hasOwnProperty
5
- } from '../common';
1
+ import { getSupportedFeatures } from '../common';
6
2
 
7
3
  /* eslint-disable arrow-body-style, no-useless-escape */
8
4
 
9
- const defineProperty = Object.defineProperty;
10
-
11
5
  export const extend = Object.assign;
12
6
 
7
+ export const proxy = (method, context) => {
8
+ return method.bind(context);
9
+ };
10
+
13
11
  export let convertToHtml = (html) => {
14
12
  const div = document.createElement("div");
15
13
  div.innerHTML = html;
@@ -65,331 +63,9 @@ export let toPixels = (value) => {
65
63
  return result;
66
64
  };
67
65
 
68
- const detectOS = (ua) => {
69
- let os = false, minorVersion, match = [],
70
- // notAndroidPhone = !/mobile safari/i.test(ua),
71
- agentRxs = {
72
- wp: /(Windows Phone(?: OS)?)\s(\d+)\.(\d+(\.\d+)?)/,
73
- fire: /(Silk)\/(\d+)\.(\d+(\.\d+)?)/,
74
- android: /(Android|Android.*(?:Opera|Firefox).*?\/)\s*(\d+)\.?(\d+(\.\d+)?)?/,
75
- iphone: /(iPhone|iPod).*OS\s+(\d+)[\._]([\d\._]+)/,
76
- ipad: /(iPad).*OS\s+(\d+)[\._]([\d_]+)/,
77
- meego: /(MeeGo).+NokiaBrowser\/(\d+)\.([\d\._]+)/,
78
- webos: /(webOS)\/(\d+)\.(\d+(\.\d+)?)/,
79
- blackberry: /(BlackBerry|BB10).*?Version\/(\d+)\.(\d+(\.\d+)?)/,
80
- playbook: /(PlayBook).*?Tablet\s*OS\s*(\d+)\.(\d+(\.\d+)?)/,
81
- windows: /(MSIE)\s+(\d+)\.(\d+(\.\d+)?)/,
82
- tizen: /(tizen).*?Version\/(\d+)\.(\d+(\.\d+)?)/i,
83
- sailfish: /(sailfish).*rv:(\d+)\.(\d+(\.\d+)?).*firefox/i,
84
- ffos: /(Mobile).*rv:(\d+)\.(\d+(\.\d+)?).*Firefox/
85
- },
86
- osRxs = {
87
- ios: /^i(phone|pad|pod)$/i,
88
- android: /^android|fire$/i,
89
- blackberry: /^blackberry|playbook/i,
90
- windows: /windows/,
91
- wp: /wp/,
92
- flat: /sailfish|ffos|tizen/i,
93
- meego: /meego/
94
- },
95
- formFactorRxs = {
96
- tablet: /playbook|ipad|fire/i
97
- },
98
- browserRxs = {
99
- omini: /Opera\sMini/i,
100
- omobile: /Opera\sMobi/i,
101
- firefox: /Firefox|Fennec/i,
102
- mobilesafari: /version\/.*safari/i,
103
- ie: /MSIE|Windows\sPhone/i,
104
- chrome: /chrome|crios/i,
105
- webkit: /webkit/i
106
- };
107
-
108
- for (let agent in agentRxs) {
109
- if (hasOwnProperty(agentRxs, agent)) {
110
- match = ua.match(agentRxs[agent]);
111
- if (match) {
112
- if (agent === "windows" && "plugins" in navigator) { return false; } // Break if not Metro/Mobile Windows
113
-
114
- os = {};
115
- os.device = agent;
116
- os.tablet = testRegex(agent, formFactorRxs, false);
117
- os.browser = testRegex(ua, browserRxs, "default");
118
- os.name = testRegex(agent, osRxs);
119
- os[os.name] = true;
120
- os.majorVersion = match[2];
121
- os.minorVersion = (match[3] || "0").replace("_", ".");
122
- minorVersion = os.minorVersion.replace(".", "").substr(0, 2);
123
- os.flatVersion = os.majorVersion + minorVersion + (new Array(3 - (minorVersion.length < 3 ? minorVersion.length : 2)).join("0"));
124
-
125
-
126
- break;
127
- }
128
- }
129
- }
130
-
131
- return os;
132
- };
133
-
134
- function testRegex(agent, regexes, dflt) {
135
- for (let regex in regexes) {
136
- if (hasOwnProperty(regexes, regex) && regexes[regex].test(agent)) {
137
- return regex;
138
- }
139
- }
140
- return dflt !== undefined ? dflt : agent;
141
- }
142
-
143
66
  export let hasNativeScrolling = (userAgent) => {
144
- const os = detectOS(userAgent);
145
- return os.ios || os.android;
146
- };
147
-
148
- const detectBrowser = (userAgent) => {
149
- let browser = false,
150
- match = [],
151
- browserRxs = {
152
- edge: /(edge)[ \/]([\w.]+)/i,
153
- webkit: /(chrome|crios)[ \/]([\w.]+)/i,
154
- safari: /(webkit)[ \/]([\w.]+)/i,
155
- opera: /(opera)(?:.*version|)[ \/]([\w.]+)/i,
156
- msie: /(msie\s|trident.*? rv:)([\w.]+)/i,
157
- mozilla: /(mozilla)(?:.*? rv:([\w.]+)|)/i
158
- };
159
-
160
- for (let agent in browserRxs) {
161
- if (hasOwnProperty(browserRxs, agent)) {
162
- match = userAgent.match(browserRxs[agent]);
163
-
164
- if (match) {
165
- browser = {};
166
- browser[agent] = true;
167
- browser[match[1].toLowerCase().split(" ")[0].split("/")[0]] = true;
168
- browser.version = parseInt(document.documentMode || match[2], 10);
169
-
170
- break;
171
- }
172
- }
173
- }
174
-
175
- return browser;
176
- };
177
-
178
- export let getEventMap = () => {
179
- let eventMap = {
180
- down: "touchstart mousedown",
181
- move: "mousemove touchmove",
182
- up: "mouseup touchend touchcancel",
183
- cancel: "mouseleave touchcancel"
184
- };
185
-
186
- const support = getSupportedFeatures();
187
-
188
- if (support.touch && (support.mobileOS.ios || support.mobileOS.android)) {
189
- eventMap = {
190
- down: "touchstart",
191
- move: "touchmove",
192
- up: "touchend touchcancel",
193
- cancel: "touchcancel"
194
- };
195
- } else if (support.pointers) {
196
- eventMap = {
197
- down: "pointerdown",
198
- move: "pointermove",
199
- up: "pointerup",
200
- cancel: "pointercancel pointerleave"
201
- };
202
- } else if (support.msPointers) {
203
- eventMap = {
204
- down: "MSPointerDown",
205
- move: "MSPointerMove",
206
- up: "MSPointerUp",
207
- cancel: "MSPointerCancel MSPointerLeave"
208
- };
209
- }
210
-
211
- return eventMap;
212
- };
213
-
214
- export const getSupportedFeatures = () => {
215
- const os = detectOS(navigator.userAgent);
216
- const browser = detectBrowser(navigator.userAgent);
217
-
218
- let chrome = browser.chrome,
219
- mobileChrome = browser.crios,
220
- mozilla = browser.mozilla,
221
- safari = browser.safari;
222
-
223
- const support = {};
224
-
225
- support.mobileOS = os;
226
- support.touch = "ontouchstart" in window;
227
- support.pointers = !chrome && !mobileChrome && !mozilla && !safari && window.PointerEvent;
228
- support.msPointers = !chrome && window.MSPointerEvent;
229
- support.mouseAndTouchPresent = support.touch && !(support.mobileOS.ios || support.mobileOS.android);
230
- support.eventCapture = document.documentElement.addEventListener;
231
-
232
- let table = document.createElement("table");
233
-
234
- let transitions = support.transitions = false,
235
- transforms = support.transforms = false;
236
-
237
- const STRING = "string";
238
-
239
- ["Moz", "webkit", "O", "ms"].forEach(function(prefix) {
240
- let hasTransitions = typeof table.style[prefix + "Transition"] === STRING;
241
-
242
- if (hasTransitions || typeof table.style[prefix + "Transform"] === STRING) {
243
- let lowPrefix = prefix.toLowerCase();
244
-
245
- transforms = {
246
- css: (lowPrefix !== "ms") ? "-" + lowPrefix + "-" : "",
247
- prefix: prefix,
248
- event: (lowPrefix === "o" || lowPrefix === "webkit") ? lowPrefix : ""
249
- };
250
-
251
- if (hasTransitions) {
252
- transitions = transforms;
253
- transitions.event = transitions.event ? transitions.event + "TransitionEnd" : "transitionend";
254
- }
255
-
256
- return false;
257
- }
258
- });
259
-
260
- table = null;
261
-
262
- support.transforms = transforms;
263
- support.transitions = transitions;
264
-
265
- support.delayedClick = function() {
266
- // only the mobile devices with touch events do this.
267
- if (support.touch) {
268
- // All iOS devices so far (by the time I am writing this, iOS 9.0.2 is the latest),
269
- // delay their click events.
270
- if (support.mobileOS.ios) {
271
- return true;
272
- }
273
-
274
- if (support.mobileOS.android) {
275
-
276
- if (!support.browser.chrome) { // older webkits and webviews delay the click
277
- return true;
278
- }
279
-
280
- // from here on, we deal with Chrome on Android.
281
- if (support.browser.version < 32) {
282
- return false;
283
- }
284
-
285
- // Chrome 32+ does conditional fast clicks if the view port is not user scalable.
286
- const meta = document.querySelector("meta[name=viewport]");
287
- const contentAttr = meta ? meta.getAttribute("content") : "";
288
- return !contentAttr.match(/user-scalable=no/i);
289
- }
290
- }
291
-
292
- return false;
293
- };
294
-
295
- return support;
296
- };
297
-
298
- export const ownsElement = (parent, element) => {
299
- if (!element) {
300
- return false;
301
- }
302
-
303
- let node = element.parentNode;
304
-
305
- while (node !== null) {
306
- if (node === parent) {
307
- return true;
308
- }
309
-
310
- node = node.parentNode;
311
- }
312
-
313
- return false;
314
- };
315
-
316
- export const contains = (parent, element) => {
317
- return parent === element || ownsElement(parent, element);
318
- };
319
-
320
- export const proxy = (method, context) => {
321
- return method.bind(context);
322
- };
323
-
324
- function isString(value) {
325
- return typeof(value) === "string";
326
- }
327
-
328
- export const on = (element, events, filter, handler, useCapture) => {
329
- addEventListeners(element, events, filter, handler, useCapture);
330
- };
331
-
332
- export const addEventListeners = (element, events, filter, handler, useCapture) => {
333
- const eventNames = isArray(events) ? events : (events || "").split(" ");
334
-
335
- eventNames.forEach(function(eventName) {
336
- addEventListener(element, eventName, filter, handler, useCapture);
337
- });
338
- };
339
-
340
- export const addEventListener = (element, event, filter, handler, useCapture) => {
341
- let eventHandler = handler;
342
- let eventFilter;
343
-
344
- if (filter && isFunction(filter) && !handler) {
345
- eventHandler = filter;
346
- } else if (filter && isString(filter) && isFunction(eventHandler)) {
347
- eventFilter = filter;
348
- }
349
-
350
- element.addEventListener(event, function(e) {
351
- const closestMatchingTarget = e.target ? e.target.closest(eventFilter) : null;
352
-
353
- if (!eventFilter ||
354
- (eventFilter && e.target && closestMatchingTarget)) {
355
- const currentTarget = eventFilter ? closestMatchingTarget : e.currentTarget;
356
-
357
- // reassign the property as it is a getters only
358
- defineProperty(e, "currentTarget", { value: currentTarget });
359
- // keep a reference to the top-level target
360
- defineProperty(e, "delegateTarget", { value: element });
361
-
362
- eventHandler(e);
363
- }
364
- }, Boolean(useCapture));
365
- };
366
-
367
- export const off = (element, events, filter, handler, useCapture) => {
368
- removeEventListeners(element, events, filter, handler, useCapture);
369
- };
370
-
371
- export const removeEventListeners = (element, events, handler, useCapture) => {
372
- const eventNames = isArray(events) ? events : (events || "").split(" ");
373
-
374
- eventNames.forEach(function(eventName) {
375
- removeEventListener(element, eventName, handler, useCapture);
376
- });
377
- };
378
-
379
- export const removeEventListener = (element, event, handler, useCapture) => {
380
- element.removeEventListener(event, handler, Boolean(useCapture));
381
- };
382
-
383
- export const applyEventMap = (events) => {
384
- const eventMap = getEventMap(navigator.userAgent);
385
- function queryEventMap(e) {
386
- return eventMap[e] || e;
387
- }
388
-
389
- const eventRegEx = /([^ ]+)/g;
390
- const appliedEvents = events.replace(eventRegEx, queryEventMap);
391
-
392
- return appliedEvents;
67
+ const { mobileOS } = getSupportedFeatures(userAgent);
68
+ return mobileOS.ios || mobileOS.android;
393
69
  };
394
70
 
395
71
  export const setDefaultEvents = (type, events) => {
@@ -406,8 +82,7 @@ export const setDefaultEvents = (type, events) => {
406
82
  }
407
83
  };
408
84
 
409
- export const wheelDeltaY = (jQueryEvent) => {
410
- const e = jQueryEvent.originalEvent || jQueryEvent;
85
+ export const wheelDeltaY = (e) => {
411
86
  const deltaY = e.wheelDeltaY;
412
87
  let delta;
413
88
 
@@ -422,12 +97,6 @@ export const wheelDeltaY = (jQueryEvent) => {
422
97
  return delta;
423
98
  };
424
99
 
425
- export const now = () => {
426
- return Number(new Date());
427
- };
428
-
429
- export const noop = () => {};
430
-
431
100
  export const renderPos = (pos) => {
432
101
  let result = [];
433
102
 
@@ -5,12 +5,12 @@ import {
5
5
  keys,
6
6
  hasClasses,
7
7
  setDefaultOptions,
8
- renderIcon
8
+ renderIcon,
9
+ on,
10
+ off,
9
11
  } from '../common';
10
12
 
11
13
  import {
12
- on,
13
- off,
14
14
  setDefaultEvents,
15
15
  convertToHtml
16
16
  } from './utils';
@@ -52,6 +52,11 @@ setDefaultOptions(Legend, {
52
52
  },
53
53
  position: BOTTOM,
54
54
  align: CENTER,
55
+ accessibility: {
56
+ role: 'presentation',
57
+ ariaLabel: null,
58
+ ariaRoleDescription: null
59
+ },
55
60
  border: {
56
61
  width: 0
57
62
  }
@@ -2,6 +2,66 @@ import { drawing } from '@progress/kendo-drawing';
2
2
  import { SankeyElement } from './element';
3
3
  import { deepExtend } from '../common';
4
4
  import { defined } from '../drawing-utils';
5
+ import { ARIA_ACTIVE_DESCENDANT } from '../common/constants';
6
+
7
+ const distanceToLine = (line, point) => {
8
+ const [x1, y1] = line[0];
9
+ const [x2, y2] = line[1];
10
+ const [x3, y3] = point;
11
+
12
+ return Math.abs((x2 - x1) * (y3 - y1) - (x3 - x1) * (y2 - y1)) / Math.sqrt(Math.pow( (x2 - x1), 2 ) + Math.pow( (y2 - y1), 2 ));
13
+ };
14
+
15
+ const bezierPoint = (p1, p2, p3, p4, t) => {
16
+ const t1 = 1 - t;
17
+ const t1t1 = t1 * t1;
18
+ const tt = t * t;
19
+ return (p1 * t1t1 * t1) + (3 * p2 * t * t1t1) + (3 * p3 * tt * t1) + (p4 * tt * t);
20
+ };
21
+
22
+ const angelBetweenTwoLines = (line1, line2) => {
23
+ const [x1, y1] = line1[0];
24
+ const [x2, y2] = line1[1];
25
+ const [x3, y3] = line2[0];
26
+ const [x4, y4] = line2[1];
27
+
28
+ const a1 = Math.atan2(y2 - y1, x2 - x1);
29
+ const a2 = Math.atan2(y4 - y3, x4 - x3);
30
+
31
+ return Math.abs(a1 - a2);
32
+ };
33
+
34
+ const calculateControlPointsOffsetX = (link) => {
35
+ const { x0, x1, y0, y1 } = link;
36
+ let xC = (x0 + x1) / 2;
37
+
38
+ const width = link.width;
39
+ const halfWidth = width / 2;
40
+
41
+ // upper curve, t = 0.5
42
+ const upperCurveMiddleLine = [[(x0 + xC) / 2, y0 - halfWidth], [(x1 + xC) / 2, y1 - halfWidth]];
43
+
44
+ // for lower curve, bezier-point at t = 0.5
45
+ // for the case t = 0.5, the bezier-point is the middle point of the curve. => ((y0 + halfWidth) + (y1 + halfWidth)) / 2
46
+ const lowerCurveMiddlePoint = [xC, bezierPoint(y0 + halfWidth, y0 + halfWidth, y1 + halfWidth, y1 + halfWidth, 0.5)];
47
+
48
+ // The actual width of the link at its middle point as can be seen on the screen.
49
+ const actualWidth = distanceToLine(upperCurveMiddleLine, lowerCurveMiddlePoint);
50
+
51
+ const upperNarrowness = (width - actualWidth) / 2;
52
+
53
+ // The line `upperCurveMiddleLine` shows the upper border of the link.
54
+ // Assumption 1: Translated to the left to the desired link width and the translate value will be the `offset`.
55
+ // Assumption 2: The translate value is a hypotenuse of a triangle.
56
+ const alpha = angelBetweenTwoLines(upperCurveMiddleLine, [[x0, y0 - halfWidth], [xC, y0 - halfWidth]]);
57
+ const a = upperNarrowness;
58
+ const b = Math.sin(alpha) * a;
59
+ const offset = Math.sqrt(a * a + b * b);
60
+ // Another option is to assume the triangle is isosceles
61
+ // => offset = Math.sqrt(2) * upperNarrowness;
62
+
63
+ return y0 - y1 > 0 ? (-1) * offset : offset;
64
+ };
5
65
 
6
66
  export class Link extends SankeyElement {
7
67
  getElement() {
@@ -13,17 +73,75 @@ export class Link extends SankeyElement {
13
73
  .moveTo(x0, y0).curveTo([xC, y0], [xC, y1], [x1, y1]);
14
74
  }
15
75
 
76
+ getLabelText(options) {
77
+ let labelTemplate = options.labels.ariaTemplate;
78
+
79
+ if (labelTemplate) {
80
+ return labelTemplate({ link: options.link });
81
+ }
82
+ }
83
+
16
84
  visualOptions() {
17
85
  const options = this.options;
18
86
  const link = this.options.link;
87
+ const ariaLabel = this.getLabelText(options);
88
+
19
89
  return {
20
90
  stroke: {
21
91
  width: options.link.width,
22
92
  color: link.color || options.color,
23
93
  opacity: defined(link.opacity) ? link.opacity : options.opacity
24
- }
94
+ },
95
+ role: 'graphics-symbol',
96
+ ariaRoleDescription: 'Link',
97
+ ariaLabel: ariaLabel
25
98
  };
26
99
  }
100
+
101
+ createFocusHighlight() {
102
+ if (!this.options.navigatable) {
103
+ return;
104
+ }
105
+ const link = this.options.link;
106
+ const { x0, x1, y0, y1 } = link;
107
+ const xC = (x0 + x1) / 2;
108
+ const halfWidth = link.width / 2;
109
+
110
+ const offset = calculateControlPointsOffsetX(link);
111
+
112
+ this._highlight = new drawing.Path({ stroke: this.options.focusHighlight, visible: false })
113
+ .moveTo(x0, y0 + halfWidth)
114
+ .lineTo(x0, y0 - halfWidth)
115
+ .curveTo([xC + offset, y0 - halfWidth], [xC + offset, y1 - halfWidth], [x1, y1 - halfWidth])
116
+ .lineTo(x1, y1 + halfWidth)
117
+ .curveTo([xC - offset, y1 + halfWidth], [xC - offset, y0 + halfWidth], [x0, y0 + halfWidth]);
118
+ }
119
+
120
+ focus(options) {
121
+ if (this._highlight) {
122
+ const { highlight = true } = options || {};
123
+ if (highlight) {
124
+ this._highlight.options.set('visible', true);
125
+ }
126
+ const id = `${this.options.link.sourceId}->${this.options.link.targetId}`;
127
+ this.visual.options.set('id', id);
128
+
129
+ if (this.options.root()) {
130
+ this.options.root().setAttribute(ARIA_ACTIVE_DESCENDANT, id);
131
+ }
132
+ }
133
+ }
134
+
135
+ blur() {
136
+ if (this._highlight) {
137
+ this._highlight.options.set('visible', false);
138
+ this.visual.options.set('id', '');
139
+
140
+ if (this.options.root()) {
141
+ this.options.root().removeAttribute(ARIA_ACTIVE_DESCENDANT);
142
+ }
143
+ }
144
+ }
27
145
  }
28
146
 
29
147
  export const resolveLinkOptions = (link, options, sourceNode, targetNode) => {
@@ -1,18 +1,29 @@
1
1
  import { geometry, drawing } from '@progress/kendo-drawing';
2
2
  import { SankeyElement } from './element';
3
3
  import { deepExtend } from '../common';
4
+ import { ARIA_ACTIVE_DESCENDANT } from '../common/constants';
4
5
 
5
6
  export class Node extends SankeyElement {
6
7
  getElement() {
7
- const options = this.options;
8
- const node = options.node;
9
- const rect = new geometry.Rect([node.x0, node.y0], [node.x1 - node.x0, node.y1 - node.y0]);
8
+ return drawing.Path.fromRect(this.getRect(), this.visualOptions());
9
+ }
10
+
11
+ getRect() {
12
+ const node = this.options.node;
13
+ return new geometry.Rect([node.x0, node.y0], [node.x1 - node.x0, node.y1 - node.y0]);
14
+ }
10
15
 
11
- return drawing.Path.fromRect(rect, this.visualOptions());
16
+ getLabelText(options) {
17
+ let labelTemplate = options.labels.ariaTemplate;
18
+
19
+ if (labelTemplate) {
20
+ return labelTemplate({ node: options.node });
21
+ }
12
22
  }
13
23
 
14
24
  visualOptions() {
15
25
  const options = deepExtend({}, this.options, this.options.node);
26
+ const ariaLabel = this.getLabelText(options);
16
27
 
17
28
  return {
18
29
  fill: {
@@ -23,9 +34,48 @@ export class Node extends SankeyElement {
23
34
  className: 'k-sankey-node',
24
35
  role: 'graphics-symbol',
25
36
  ariaRoleDescription: 'Node',
26
- ariaLabel: options.node.label.text
37
+ ariaLabel: ariaLabel
27
38
  };
28
39
  }
40
+
41
+ createFocusHighlight() {
42
+ if (!this.options.navigatable) {
43
+ return;
44
+ }
45
+
46
+ this._highlight = new drawing.Path.fromRect(this.getRect(), {
47
+ stroke: this.options.focusHighlight,
48
+ visible: false
49
+ });
50
+
51
+ return this._highlight;
52
+ }
53
+
54
+ focus(options) {
55
+ if (this._highlight) {
56
+ const { highlight = true } = options || {};
57
+ if (highlight) {
58
+ this._highlight.options.set('visible', true);
59
+ }
60
+ const id = this.options.node.id;
61
+ this.visual.options.set('id', id);
62
+
63
+ if (this.options.root()) {
64
+ this.options.root().setAttribute(ARIA_ACTIVE_DESCENDANT, id);
65
+ }
66
+ }
67
+ }
68
+
69
+ blur() {
70
+ if (this._highlight) {
71
+ this._highlight.options.set('visible', false);
72
+ this.visual.options.set('id', '');
73
+
74
+ if (this.options.root()) {
75
+ this.options.root().removeAttribute(ARIA_ACTIVE_DESCENDANT);
76
+ }
77
+ }
78
+ }
29
79
  }
30
80
 
31
81
  const nodeColor = (node, nodeColors, index) => node.color || nodeColors[index % nodeColors.length];