@atlassian/aui 9.5.0 → 9.5.2

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@atlassian/aui",
3
3
  "description": "Atlassian User Interface library",
4
- "version": "9.5.0",
4
+ "version": "9.5.2",
5
5
  "author": "Atlassian Pty Ltd.",
6
6
  "homepage": "https://aui.atlassian.com",
7
7
  "license": "Apache-2.0",
@@ -37,8 +37,9 @@
37
37
  "@popperjs/core": "2.4.4",
38
38
  "backbone": "1.4.1",
39
39
  "css.escape": "1.5.1",
40
+ "dompurify": "2.4.0",
40
41
  "fancy-file-input": "2.0.4",
41
- "jquery-ui": "1.13.1",
42
+ "jquery-ui": "1.13.2",
42
43
  "skatejs": "0.13.17",
43
44
  "skatejs-template-html": "0.0.0",
44
45
  "trim-extra-html-whitespace": "1.3.0",
@@ -5,9 +5,6 @@ Version: 3.4.4 Timestamp: Thu Oct 24 13:23:11 PDT 2013
5
5
  margin: 0;
6
6
  position: relative;
7
7
  display: inline-block;
8
- /* inline-block for ie7 */
9
- zoom: 1;
10
- *display: inline;
11
8
  vertical-align: middle;
12
9
  }
13
10
 
@@ -35,8 +35,27 @@ function setDropdownTriggerActiveState(trigger, isActive) {
35
35
  * @param {Event} [e] - the event that triggered the dropdown being shown
36
36
  */
37
37
  function handleFocus(dropdown, e = {}) {
38
- const mouseEvent = e && e.type && (e.type.indexOf('mouse') > -1 || e.type.indexOf('hover') > -1);
39
- dropdown.isSubmenu && !mouseEvent ? dropdown.focusItem(0) : dropdown.focus();
38
+ if (e && e.type) {
39
+ const isMouseEvent = e.type.indexOf('mouse') > -1 || e.type.indexOf('hover') > -1;
40
+ const isKeyDownEvent = e.type.indexOf('key') > -1;
41
+ if (dropdown.isSubmenu) {
42
+ !isMouseEvent ? dropdown.focusItem(0) : dropdown.focus();
43
+ } else if (isKeyDownEvent){
44
+ const isUpArrow = e.keyCode === keyCode.UP;
45
+ // set focus to last item in the menu to navigate bottom -> up
46
+ if (isUpArrow) {
47
+ const visibleItems = getVisibleDropdownItems(dropdown);
48
+ if (visibleItems && visibleItems.length) {
49
+ dropdown.focusItem(visibleItems.length - 1);
50
+ }
51
+ } else {
52
+ // if enter/space/downArrow than focus goes to the first item
53
+ dropdown.focusItem(0);
54
+ }
55
+ } else {
56
+ dropdown.focus();
57
+ }
58
+ }
40
59
  }
41
60
 
42
61
  // LOADING STATES
@@ -0,0 +1,37 @@
1
+ import skate from 'skatejs';
2
+ import {onChildrenChange} from '../internal/detect-children-change';
3
+ import jQuery from 'jquery';
4
+
5
+ export function createFormsComponentBody(type) {
6
+
7
+ let unsub;
8
+
9
+ return {
10
+ type: skate.type.CLASSNAME,
11
+ attached: function(el) {
12
+
13
+ onChildrenChange(el,(unsubscribe) => {
14
+
15
+ // Store function that stops subscription
16
+ unsub = unsubscribe;
17
+
18
+ const innerCheckboxList = jQuery(`input[type=${type}]`, el);
19
+
20
+ innerCheckboxList.each(function (i, radio) {
21
+ jQuery('<span class="aui-form-glyph"></span>').insertAfter(radio);
22
+ });
23
+
24
+ const isInsertedAfterChange = innerCheckboxList.length > 0;
25
+ if (isInsertedAfterChange) {
26
+ unsub();
27
+ }
28
+ });
29
+ },
30
+ detached: function(el) {
31
+ jQuery('.aui-form-glyph', el).remove();
32
+ if (unsub) {
33
+ unsub();
34
+ }
35
+ }
36
+ }
37
+ }
@@ -1,18 +1,12 @@
1
1
  import skate from 'skatejs';
2
- import jQuery from 'jquery';
2
+ import {createFormsComponentBody} from './create-forms-component-body';
3
+
3
4
 
4
5
  /**
5
6
  * Allows us to add a new DOM element for rendering ADG styled checkbox glyphs,
6
7
  * so we can get our desired aesthetic without having to rely on a specific markup pattern.
7
8
  */
8
- skate('checkbox', {
9
- type: skate.type.CLASSNAME,
10
- attached: function(el) {
11
- jQuery('input[type=checkbox]', el).each(function(i, checkbox) {
12
- jQuery('<span class="aui-form-glyph"></span>').insertAfter(checkbox);
13
- });
14
- },
15
- detached: function(el) {
16
- jQuery('.aui-form-glyph', el).remove();
17
- }
18
- });
9
+ skate(
10
+ 'checkbox',
11
+ createFormsComponentBody('checkbox')
12
+ );
@@ -1,18 +1,12 @@
1
1
  import skate from 'skatejs';
2
- import jQuery from 'jquery';
2
+ import {createFormsComponentBody} from './create-forms-component-body';
3
+
3
4
 
4
5
  /**
5
6
  * Allows us to add a new DOM element for rendering ADG styled radio glyphs,
6
7
  * so we can get our desired aesthetic without having to rely on a specific markup pattern.
7
8
  */
8
- skate('radio', {
9
- type: skate.type.CLASSNAME,
10
- attached: function(el) {
11
- jQuery('input[type=radio]', el).each(function(i, radio) {
12
- jQuery('<span class="aui-form-glyph"></span>').insertAfter(radio);
13
- });
14
- },
15
- detached: function(el) {
16
- jQuery('.aui-form-glyph', el).remove();
17
- }
18
- });
9
+ skate(
10
+ 'radio',
11
+ createFormsComponentBody('radio')
12
+ );
@@ -0,0 +1,35 @@
1
+ function isChildrenChanged(mutationList) {
2
+ for (const mutation of mutationList) {
3
+ if (mutation.type === 'childList') {
4
+ return true;
5
+ }
6
+ }
7
+ return false;
8
+ }
9
+
10
+ /***
11
+ * Executes callback every time element children change. Called once initially.
12
+ *
13
+ * ***Make sure to use unsubscribe callback to free resources occupied for detection***
14
+ *
15
+ * @param element Element whose children should be monitored
16
+ * @param callback Function to be called when children change happened. Gets unsubscribe function as argument.
17
+ */
18
+ export function onChildrenChange(element, callback) {
19
+
20
+ let isCompleteOnInit = false;
21
+
22
+ callback(() => { isCompleteOnInit = true });
23
+
24
+ if (isCompleteOnInit) {
25
+ return;
26
+ }
27
+
28
+ const observer = new MutationObserver((mutationList) => {
29
+ if (isChildrenChanged(mutationList)) {
30
+ callback(() => observer.disconnect());
31
+ }
32
+ });
33
+
34
+ observer.observe(element, { childList: true });
35
+ }
@@ -1,7 +1,7 @@
1
1
  import Backbone from 'backbone';
2
2
 
3
3
  export default Backbone.Model.extend({
4
- idAttribute: 'label',
4
+ idAttribute: 'value',
5
5
  getLabel: function () {
6
6
  return this.get('label') || this.get('value');
7
7
  }
@@ -265,8 +265,14 @@ Sidebar.prototype.reflow = function reflow(
265
265
  Sidebar.prototype.toggle = function () {
266
266
  if (this.isCollapsed()) {
267
267
  this.expand();
268
+ this.$el
269
+ .find('.aui-sidebar-toggle')
270
+ .attr('aria-label', I18n.getText('aui.sidebar.collapse.tooltip'));
268
271
  } else {
269
272
  this.collapse();
273
+ this.$el
274
+ .find('.aui-sidebar-toggle')
275
+ .attr('aria-label', I18n.getText('aui.sidebar.expand.tooltip'));
270
276
  }
271
277
  return this;
272
278
  };
@@ -618,6 +624,7 @@ function initializeHandlers(sidebar) {
618
624
 
619
625
  sidebar.$el.tooltip({
620
626
  ...tooltipOptions,
627
+ aria: false,
621
628
  live: sidebar.toggleSelector,
622
629
  title: function () {
623
630
  return sidebar.isCollapsed() ?
@@ -1,5 +1,6 @@
1
1
  import $ from './jquery';
2
2
  import { createPopper } from '@popperjs/core';
3
+ import DOMPurify from 'dompurify';
3
4
 
4
5
  const AUI_TOOLTIP_CLASS_NAME = 'aui-tooltip';
5
6
  const AUI_TOOLTIP_ID = 'aui-tooltip';
@@ -32,7 +33,10 @@ const defaultOptions = {
32
33
  html: false,
33
34
  live: false,
34
35
  enabled: true,
35
- suppress: () => false
36
+ suppress: () => false,
37
+ aria: true,
38
+ sanitize: true,
39
+ maxWidth: 200
36
40
  }
37
41
 
38
42
  let $sharedTip;
@@ -70,7 +74,9 @@ class Tooltip {
70
74
 
71
75
  $triggerElement.attr('title', function (_, originalTitle) {
72
76
  tooltip.originalTitle = originalTitle;
73
- $triggerElement.attr('aria-describedby', AUI_TOOLTIP_ID);
77
+ if (tooltip.options.aria) {
78
+ $triggerElement.attr('aria-describedby', AUI_TOOLTIP_ID);
79
+ }
74
80
  return null;
75
81
  });
76
82
  }
@@ -97,11 +103,21 @@ class Tooltip {
97
103
  $(document.body).append($sharedTip);
98
104
  }
99
105
 
106
+ const tooltipContentElement = $sharedTip.find('.aui-tooltip-content');
107
+
100
108
  if (options.html) {
101
- $sharedTip.find('.aui-tooltip-content').html(title);
109
+ if (options.sanitize) {
110
+ title = DOMPurify.sanitize(title);
111
+ }
112
+ tooltipContentElement.html(title);
102
113
  } else {
103
- $sharedTip.find('.aui-tooltip-content').text(title);
114
+ tooltipContentElement.text(title);
104
115
  }
116
+
117
+ if (options.maxWidth) {
118
+ tooltipContentElement.css("max-width", options.maxWidth + "px");
119
+ }
120
+
105
121
  return $sharedTip;
106
122
  }
107
123
 
@@ -115,7 +131,7 @@ class Tooltip {
115
131
  () => this.originalTitle || '';
116
132
 
117
133
  let actualTitle = title.call(this.triggerElement);
118
- return (actualTitle.trim().length === 0) ? undefined : actualTitle;
134
+ return (!actualTitle || !actualTitle.trim().length) ? undefined : actualTitle;
119
135
  }
120
136
 
121
137
  show() {
@@ -127,9 +127,13 @@ function whenIType (keys) {
127
127
  var title = elem.attr('title') || '';
128
128
  var keyCombos = boundKeyCombos.slice();
129
129
  var existingCombos = elem.data('boundKeyCombos') || [];
130
- var shortcutInstructions = elem.data('kbShortcutAppended') || '';
131
- var isFirst = !shortcutInstructions;
132
- var originalTitle = isFirst ? title : title.substring(0, title.length - shortcutInstructions.length);
130
+ var kbShortcutAppended = elem.data('kbShortcutAppended') || ''
131
+ var kbShortcutAppendedScreenReader = elem.data('kbShortcutAppendedScreenReader') || ''
132
+ var ariaLabel = elem.attr('aria-label');
133
+ var isFirstShortcut = !kbShortcutAppended;
134
+ var isFirstShortcutScreenReader = !kbShortcutAppendedScreenReader;
135
+ var originalTitle = isFirstShortcut ? title : title.substring(0, title.length - kbShortcutAppended.length);
136
+ var originalAriaLabel = isFirstShortcutScreenReader ? title : ariaLabel.substring(0, ariaLabel.length - kbShortcutAppendedScreenReader.length);
133
137
 
134
138
  while (keyCombos.length) {
135
139
  var keyCombo = keyCombos.shift();
@@ -137,19 +141,26 @@ function whenIType (keys) {
137
141
  return isEqual(keyCombo, existingCombo);
138
142
  });
139
143
  if (!comboAlreadyExists) {
140
- shortcutInstructions = appendKeyComboInstructions(keyCombo.slice(), shortcutInstructions, isFirst);
141
- isFirst = false;
144
+ kbShortcutAppended = appendKeyComboInstructions(keyCombo.slice(), kbShortcutAppended, isFirstShortcut);
145
+ kbShortcutAppendedScreenReader = appendKeyComboInstructions(keyCombo.slice(), kbShortcutAppendedScreenReader, isFirstShortcutScreenReader, { workaroundJaws: true });
146
+ isFirstShortcut = false;
147
+ isFirstShortcutScreenReader = false;
142
148
  }
143
149
  }
144
150
 
145
151
  if (isMac) {
146
- shortcutInstructions = shortcutInstructions
152
+ kbShortcutAppended = kbShortcutAppended
153
+ .replace(/Meta/ig, '\u2318') //Apple cmd key
154
+ .replace(/Shift/ig, '\u21E7'); //Apple Shift symbol
155
+ kbShortcutAppendedScreenReader = kbShortcutAppendedScreenReader
147
156
  .replace(/Meta/ig, '\u2318') //Apple cmd key
148
157
  .replace(/Shift/ig, '\u21E7'); //Apple Shift symbol
149
158
  }
150
159
 
151
- elem.attr('title', originalTitle + shortcutInstructions);
152
- elem.data('kbShortcutAppended', shortcutInstructions);
160
+ elem.attr('title', originalTitle + kbShortcutAppended);
161
+ elem.attr('aria-label', originalAriaLabel + kbShortcutAppendedScreenReader)
162
+ elem.data('kbShortcutAppended', kbShortcutAppended);
163
+ elem.data('kbShortcutAppendedScreenReader', kbShortcutAppendedScreenReader);
153
164
  elem.data('boundKeyCombos', existingCombos.concat(boundKeyCombos));
154
165
  }
155
166
 
@@ -163,23 +174,32 @@ function whenIType (keys) {
163
174
 
164
175
  var title = elem.attr('title');
165
176
  elem.attr('title', title.replace(shortcuts, ''));
177
+ elem.attr('aria-label', title.replace(shortcuts, ''));
166
178
  elem.removeData('kbShortcutAppended');
179
+ elem.removeData('kbShortcutAppendedScreenReader');
167
180
  elem.removeData('boundKeyCombos');
168
181
  }
169
182
 
170
- //
171
- function appendKeyComboInstructions(keyCombo, title, isFirst) {
183
+ function appendKeyComboInstructions(keyCombo, title, isFirst, options) {
184
+ var openParenthesis = '(';
185
+ var closeParenthesis = ')';
186
+
187
+ if (options && options.workaroundJaws) {
188
+ openParenthesis = '';
189
+ closeParenthesis = '';
190
+ }
191
+
172
192
  if (isFirst) {
173
- title += ' (' + I18n.getText('aui.keyboard.shortcut.type.x', keyCombo.shift());
193
+ title += ' ' + openParenthesis + I18n.getText('aui.keyboard.shortcut.type.x', keyCombo.shift());
174
194
  } else {
175
195
  title = title.replace(/\)$/, '');
176
- title += I18n.getText('aui.keyboard.shortcut.or.x', keyCombo.shift());
196
+ title += ' ' + I18n.getText('aui.keyboard.shortcut.or.x', keyCombo.shift());
177
197
  }
178
198
 
179
199
  keyCombo.forEach(function (key) {
180
200
  title += ' ' + I18n.getText('aui.keyboard.shortcut.then.x', key);
181
201
  });
182
- title += ')';
202
+ title += closeParenthesis;
183
203
 
184
204
  return title;
185
205
  }
@@ -497,25 +517,14 @@ whenIType.fromJSON = function (json, switchCtrlToMetaOnMac) {
497
517
  var shortcuts = [];
498
518
 
499
519
  if (json) {
500
- var operation = null;
501
- var param = null;
502
- if (typeof json === 'object') {
503
- operation = json.op;
504
- param = json.param;
505
- }
506
520
  $.each(json, function (i,item) {
507
- operation = (item.op) ? item.op : operation;
508
- param = (item.param) ? item.param : param;
521
+ const operation = item.op;
522
+ const param = item.param;
509
523
  let params;
510
524
 
511
525
  if (operation === 'execute' || operation === 'evaluate') {
512
526
  // need to turn function string into function object
513
- // params = [new Function(param)];
514
-
515
- // params = [(param) => JSON.parse(JSON.stringify(param))];
516
- params = [function (param){
517
- return param;
518
- }];
527
+ params = [new Function(param)];
519
528
 
520
529
  } else if (/^\[[^\]\[]*,[^\]\[]*\]$/.test(param)) {
521
530
  // pass in an array to send multiple params