@atlassian/aui 9.5.2 → 9.6.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.
@@ -16,11 +16,12 @@ import SuggestionsView from './internal/select/suggestions-view';
16
16
  import template from './internal/select/template';
17
17
  import uniqueId from './unique-id';
18
18
  import { INPUT_SUFFIX } from './internal/constants';
19
- import {ifGone} from './internal/elements';
19
+ import { ifGone } from './internal/elements';
20
20
 
21
- var DESELECTED = -1;
22
- var NO_HIGHLIGHT = -1;
23
- var DEFAULT_SS_PDS_SIZE = 20;
21
+ const DESELECTED = -1;
22
+ const NO_HIGHLIGHT = -1;
23
+ const DEFAULT_SS_PDS_SIZE = 20;
24
+ const ASSISTIVE_STATUS_DELAY_MS = 50;
24
25
 
25
26
  function decodeHtmlEntities(input) {
26
27
  var doc = new DOMParser().parseFromString(input, 'text/html');
@@ -42,7 +43,7 @@ function hasResults(element) {
42
43
  }
43
44
 
44
45
  function waitForAssistive (callback) {
45
- setTimeout(callback, 50);
46
+ setTimeout(callback, ASSISTIVE_STATUS_DELAY_MS);
46
47
  }
47
48
 
48
49
  function setBusyState (element) {
@@ -64,9 +65,16 @@ function matchPrefix (model, query) {
64
65
  return value.indexOf(query.toLowerCase()) === 0;
65
66
  }
66
67
 
67
- function hideDropdown (element) {
68
- element._suggestionsView.hide();
68
+ function resetDropdown (element) {
69
69
  element._input.setAttribute('aria-expanded', 'false');
70
+ element._button.setAttribute('aria-expanded', 'false');
71
+ element._input.removeAttribute('aria-activedescendant');
72
+ updateAssistiveStatus(element, '');
73
+ }
74
+
75
+ function resetAndCloseDropdown (element) {
76
+ element._suggestionsView.hide();
77
+ resetDropdown(element);
70
78
  }
71
79
 
72
80
  function setInitialVisualState (element) {
@@ -74,7 +82,7 @@ function setInitialVisualState (element) {
74
82
 
75
83
  element._suggestionModel.highlight(initialHighlightedItem);
76
84
 
77
- hideDropdown(element);
85
+ resetAndCloseDropdown(element);
78
86
  }
79
87
 
80
88
  function setElementImage (element, imageSource) {
@@ -129,7 +137,7 @@ function clearValue (element) {
129
137
  function selectHighlightedSuggestion (element) {
130
138
  setValueAndDisplayFromModel(element, element._suggestionModel.highlighted());
131
139
  setInputImageToHighlightedSuggestion(element);
132
- setInitialVisualState(element);
140
+ resetAndCloseDropdown(element);
133
141
  }
134
142
 
135
143
  function convertOptionToModel (option) {
@@ -238,25 +246,26 @@ function initialiseProgressiveDataSet (element) {
238
246
  data.results.push(createNewValueModel(element));
239
247
  }
240
248
 
241
- if (!state(element).get('should-include-selected')) {
242
- var indexOfValueInResults = getIndexInResults(element.value, data.results);
243
-
244
- if (indexOfValueInResults >= 0) {
245
- data.results.splice(indexOfValueInResults, 1);
246
- }
247
- }
249
+ let indexOfValueInResults = getIndexInResults(element.value, data.results);
250
+ indexOfValueInResults = indexOfValueInResults === -1 ? 0 : indexOfValueInResults;
248
251
 
249
252
  const resultsChanged = clearAndSet(element, data);
250
- const optionToHighlight = element._suggestionModel.highlighted() || data.results[0];
253
+ const optionToHighlight = data.results[indexOfValueInResults];
251
254
 
252
255
  if (element._autoHighlight) {
253
256
  element._suggestionModel.setHighlighted(optionToHighlight);
254
257
  waitForAssistive(function () {
255
- element._input.setAttribute('aria-activedescendant', getActiveId(element));
258
+ const activeId = getActiveId(element);
259
+ if (activeId !== undefined && activeId !== null) {
260
+ element._input.setAttribute('aria-activedescendant', activeId);
261
+ } else {
262
+ element._input.removeAttribute('aria-activedescendant');
263
+ }
256
264
  });
257
265
  }
258
266
 
259
267
  element._input.setAttribute('aria-expanded', 'true');
268
+ element._button.setAttribute('aria-expanded', 'true');
260
269
 
261
270
  // If the response is async (append operation), has elements to append and has a highlighted element, we need to update the status.
262
271
  if (!element._isSync && resultsChanged && element._suggestionsView.getActive() && state(element).get('should-flag-new-suggestions')) {
@@ -295,19 +304,25 @@ export function bindSelectMousedown (element) {
295
304
  element._suggestionModel.highlight($(e.target).index());
296
305
  selectHighlightedSuggestion(element);
297
306
 
298
- element._suggestionsView.hide();
299
-
300
307
  if ($(element).closest('.aui-layer').length > 0) {
301
308
  preventClosingContainerLayer = true;
302
309
  }
303
-
304
- element._input.removeAttribute('aria-activedescendant');
305
310
  } else {
306
311
  return false;
307
312
  }
308
313
  });
309
314
  }
310
315
 
316
+ function updateAssistiveStatus (element, status) {
317
+ clearTimeout(element.assistiveStatusTimerId);
318
+ element.assistiveStatusTimerId = setTimeout(() => {
319
+ const assistiveStatusText = element._assistiveStatus.innerText;
320
+ if (status !== assistiveStatusText) {
321
+ element._assistiveStatus.innerText = status;
322
+ }
323
+ }, ASSISTIVE_STATUS_DELAY_MS);
324
+ }
325
+
311
326
  function initialiseValue (element) {
312
327
  var option = element._datalist.querySelector('aui-option[selected]');
313
328
 
@@ -323,9 +338,7 @@ function isQueryInProgress (element) {
323
338
  function focusInHandler (element) {
324
339
  //if there is a selected value the single select should do an empty
325
340
  //search and return everything
326
- var searchValue = element.value ? '' : element._input.value;
327
- var isInputEmpty = element._input.value === '';
328
- state(element).set('should-include-selected', isInputEmpty);
341
+ const searchValue = element.value ? '' : element._input.value;
329
342
  suggest(element, true, searchValue);
330
343
  }
331
344
 
@@ -370,14 +383,7 @@ function focusOutHandler (element) {
370
383
  cancelInProgressQueries(element);
371
384
  handleInvalidInputOnFocusOut(element);
372
385
  handleHighlightOnFocusOut(element);
373
- hideDropdown(element);
374
- }
375
-
376
- function handleTabOut (element) {
377
- var isSuggestionViewVisible = element._suggestionsView.isVisible();
378
- if (isSuggestionViewVisible) {
379
- selectHighlightedSuggestion(element);
380
- }
386
+ resetAndCloseDropdown(element);
381
387
  }
382
388
 
383
389
  const SelectEl = skate('aui-select', {
@@ -389,29 +395,38 @@ const SelectEl = skate('aui-select', {
389
395
  element._dropdown = element.querySelector('.aui-popover');
390
396
  element._datalist = element.querySelector('datalist');
391
397
  element._button = element.querySelector('button');
398
+ element._assistiveStatus = element.querySelector('.aui-select-status.assistive');
392
399
  element._suggestionsView = new SuggestionsView(element._dropdown, element._input);
393
400
  element._suggestionModel = new SuggestionsModel();
394
401
 
395
402
  element._suggestionModel.onChange = function (oldSuggestions) {
396
- var suggestionsToAdd = [];
403
+ const suggestionsToAdd = [];
397
404
 
398
405
  element._suggestionModel._suggestions.forEach(function (newSuggestion) {
399
- var inArray = oldSuggestions.some(function (oldSuggestion) {
400
- return newSuggestion.id === oldSuggestion.id;
401
- });
402
-
406
+ const inArray = oldSuggestions.some((oldSuggestion) => newSuggestion.id === oldSuggestion.id);
403
407
  if (!inArray) {
404
408
  suggestionsToAdd.push(newSuggestion);
405
409
  }
406
410
  });
411
+ const results = convertOptionsToModels(element);
412
+ const indexOfValueInResults = getIndexInResults(element.value, results);
413
+ const numberOfItems = oldSuggestions.length + suggestionsToAdd.length;
414
+ const status = numberOfItems ? '' : `${I18n.getText('aui.select.no.suggestions')}`;
407
415
 
408
- element._suggestionsView.render(suggestionsToAdd, oldSuggestions.length, element._listId);
416
+ element._suggestionsView.render(suggestionsToAdd, oldSuggestions.length, element._listId, indexOfValueInResults);
417
+ updateAssistiveStatus(element, status);
409
418
  };
410
419
 
411
420
  element._suggestionModel.onHighlightChange = function () {
412
- var active = element._suggestionModel.highlightedIndex();
421
+ const active = element._suggestionModel.highlightedIndex();
413
422
  element._suggestionsView.setActive(active);
414
- element._input.setAttribute('aria-activedescendant', getActiveId(element));
423
+
424
+ const activeId = getActiveId(element);
425
+ if (activeId !== undefined && activeId !== null) {
426
+ element._input.setAttribute('aria-activedescendant', activeId);
427
+ } else {
428
+ element._input.removeAttribute('aria-activedescendant');
429
+ }
415
430
  };
416
431
  },
417
432
 
@@ -420,7 +435,6 @@ const SelectEl = skate('aui-select', {
420
435
  initialiseProgressiveDataSet(element);
421
436
  associateDropdownAndTrigger(element);
422
437
  element._input.setAttribute('aria-controls', element._listId);
423
- element.setAttribute('tabindex', '-1');
424
438
  bindHighlightMouseover(element);
425
439
  bindSelectMousedown(element);
426
440
  initialiseValue(element);
@@ -431,7 +445,7 @@ const SelectEl = skate('aui-select', {
431
445
  detached: function (element) {
432
446
  $(document).off('aui-close-layers-on-outer-click');
433
447
  ifGone(element).then(() => {
434
- hideDropdown(element);
448
+ resetAndCloseDropdown(element);
435
449
  element._suggestionsView.destroy();
436
450
  });
437
451
  },
@@ -445,6 +459,9 @@ const SelectEl = skate('aui-select', {
445
459
 
446
460
  name(element, data) {
447
461
  element.querySelector('select').setAttribute('name', data.newValue);
462
+ element.querySelector('select').setAttribute('aria-label', `${data.newValue} list`);
463
+ element.querySelector('button').setAttribute('aria-label', `${data.newValue} list`);
464
+ element.querySelector('ul[role="listbox"]').setAttribute('aria-label', `${data.newValue} list`);
448
465
  },
449
466
 
450
467
  placeholder(element, data) {
@@ -487,10 +504,9 @@ const SelectEl = skate('aui-select', {
487
504
  if (state(element).get('button-clicked-prevent-dropdown-hide')) {
488
505
  state(element).set('button-clicked-prevent-dropdown-hide', false);
489
506
  } else {
490
- hideDropdown(element);
507
+ resetAndCloseDropdown(element);
491
508
  }
492
509
  } else {
493
- state(element).set('should-include-selected', true);
494
510
  suggest(element, true);
495
511
  }
496
512
  },
@@ -501,7 +517,11 @@ const SelectEl = skate('aui-select', {
501
517
 
502
518
  if (e.keyCode === keyCode.ESCAPE) {
503
519
  cancelInProgressQueries(element);
504
- hideDropdown(element);
520
+ // There is no need to hide layer manually here. It will be handled by layering system.
521
+ // Otherwise, it can fire ESC event to the next layer, so
522
+ // it will close the next ESCapable layer.
523
+ // The only what we need is clean the state of the component.
524
+ resetDropdown(element);
505
525
  return;
506
526
  }
507
527
 
@@ -513,7 +533,7 @@ const SelectEl = skate('aui-select', {
513
533
  selectHighlightedSuggestion(element);
514
534
  e.preventDefault();
515
535
  } else if (e.keyCode === keyCode.TAB) {
516
- handleTabOut(element);
536
+ selectHighlightedSuggestion(element);
517
537
  handled = true;
518
538
  } else if (e.keyCode === keyCode.UP) {
519
539
  element._suggestionModel.highlightPrevious();
@@ -115,7 +115,7 @@ class Tooltip {
115
115
  }
116
116
 
117
117
  if (options.maxWidth) {
118
- tooltipContentElement.css("max-width", options.maxWidth + "px");
118
+ tooltipContentElement.css('max-width', options.maxWidth + 'px');
119
119
  }
120
120
 
121
121
  return $sharedTip;