slim-select 2.3.3 → 2.4.1

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.
@@ -59,8 +59,7 @@ class Settings {
59
59
  this.class = [];
60
60
  this.isMultiple = false;
61
61
  this.isOpen = false;
62
- this.isWindowFocused = true;
63
- this.triggerFocus = true;
62
+ this.isFullOpen = false;
64
63
  this.intervalMove = null;
65
64
  if (!settings) {
66
65
  settings = {};
@@ -347,6 +346,7 @@ class Render {
347
346
  this.main = this.mainDiv();
348
347
  this.content = this.contentDiv();
349
348
  this.updateClassStyles();
349
+ this.updateAriaAttributes();
350
350
  this.settings.contentLocation.appendChild(this.content.main);
351
351
  }
352
352
  enable() {
@@ -360,6 +360,7 @@ class Render {
360
360
  open() {
361
361
  this.main.arrow.path.setAttribute('d', this.classes.arrowOpen);
362
362
  this.main.main.classList.add(this.settings.openPosition === 'up' ? this.classes.openAbove : this.classes.openBelow);
363
+ this.main.main.setAttribute('aria-expanded', 'true');
363
364
  this.moveContent();
364
365
  const selectedOptions = this.store.getSelectedOptions();
365
366
  if (selectedOptions.length) {
@@ -373,6 +374,7 @@ class Render {
373
374
  close() {
374
375
  this.main.main.classList.remove(this.classes.openAbove);
375
376
  this.main.main.classList.remove(this.classes.openBelow);
377
+ this.main.main.setAttribute('aria-expanded', 'false');
376
378
  this.content.main.classList.remove(this.classes.openAbove);
377
379
  this.content.main.classList.remove(this.classes.openBelow);
378
380
  this.main.arrow.path.setAttribute('d', this.classes.arrowClose);
@@ -399,15 +401,18 @@ class Render {
399
401
  this.content.main.classList.add('ss-' + this.settings.contentPosition);
400
402
  }
401
403
  }
404
+ updateAriaAttributes() {
405
+ this.main.main.role = 'combobox';
406
+ this.main.main.setAttribute('aria-haspopup', 'listbox');
407
+ this.main.main.setAttribute('aria-controls', this.content.main.id);
408
+ this.main.main.setAttribute('aria-expanded', 'false');
409
+ this.content.main.setAttribute('role', 'listbox');
410
+ }
402
411
  mainDiv() {
403
412
  const main = document.createElement('div');
404
413
  main.dataset.id = this.settings.id;
414
+ main.id = this.settings.id;
405
415
  main.tabIndex = 0;
406
- main.onfocus = () => {
407
- if (this.settings.triggerFocus && this.settings.isWindowFocused) {
408
- this.callbacks.open();
409
- }
410
- };
411
416
  main.onkeydown = (e) => {
412
417
  switch (e.key) {
413
418
  case 'ArrowUp':
@@ -419,6 +424,8 @@ class Render {
419
424
  this.callbacks.close();
420
425
  return true;
421
426
  case 'Enter':
427
+ case ' ':
428
+ this.callbacks.open();
422
429
  const highlighted = this.content.list.querySelector('.' + this.classes.highlighted);
423
430
  if (highlighted) {
424
431
  highlighted.click();
@@ -495,12 +502,10 @@ class Render {
495
502
  },
496
503
  };
497
504
  }
498
- mainFocus(trigger) {
499
- if (!trigger) {
500
- this.settings.triggerFocus = false;
505
+ mainFocus(eventType) {
506
+ if (eventType !== 'click') {
507
+ this.main.main.focus({ preventScroll: true });
501
508
  }
502
- this.main.main.focus({ preventScroll: true });
503
- this.settings.triggerFocus = true;
504
509
  }
505
510
  placeholder() {
506
511
  const placeholderOption = this.store.filter((o) => o.placeholder, false);
@@ -676,6 +681,7 @@ class Render {
676
681
  contentDiv() {
677
682
  const main = document.createElement('div');
678
683
  main.dataset.id = this.settings.id;
684
+ main.id = this.settings.id;
679
685
  const search = this.searchDiv();
680
686
  main.appendChild(search.main);
681
687
  const list = this.listDiv();
@@ -733,7 +739,6 @@ class Render {
733
739
  switch (e.key) {
734
740
  case 'ArrowUp':
735
741
  case 'ArrowDown':
736
- this.callbacks.open();
737
742
  e.key === 'ArrowDown' ? this.highlight('down') : this.highlight('up');
738
743
  return false;
739
744
  case 'Tab':
@@ -743,6 +748,7 @@ class Render {
743
748
  this.callbacks.close();
744
749
  return false;
745
750
  case 'Enter':
751
+ case ' ':
746
752
  if (this.callbacks.addable && e.ctrlKey) {
747
753
  addable.click();
748
754
  }
@@ -755,12 +761,6 @@ class Render {
755
761
  return false;
756
762
  }
757
763
  };
758
- input.onfocus = () => {
759
- if (this.settings.isOpen) {
760
- return;
761
- }
762
- this.callbacks.open();
763
- };
764
764
  main.appendChild(input);
765
765
  if (this.callbacks.addable) {
766
766
  addable.classList.add(this.classes.addable);
@@ -833,12 +833,8 @@ class Render {
833
833
  }
834
834
  return searchReturn;
835
835
  }
836
- searchFocus(trigger) {
837
- if (!trigger) {
838
- this.settings.triggerFocus = false;
839
- }
836
+ searchFocus() {
840
837
  this.content.search.input.focus();
841
- this.settings.triggerFocus = true;
842
838
  }
843
839
  getOptions(notPlaceholder = false, notDisabled = false, notHidden = false) {
844
840
  let query = '.' + this.classes.option;
@@ -894,7 +890,6 @@ class Render {
894
890
  listDiv() {
895
891
  const options = document.createElement('div');
896
892
  options.classList.add(this.classes.list);
897
- options.setAttribute('role', 'listbox');
898
893
  return options;
899
894
  }
900
895
  renderError(error) {
@@ -1039,6 +1034,7 @@ class Render {
1039
1034
  }
1040
1035
  const optionEl = document.createElement('div');
1041
1036
  optionEl.dataset.id = option.id;
1037
+ optionEl.id = option.id;
1042
1038
  optionEl.classList.add(this.classes.option);
1043
1039
  optionEl.setAttribute('role', 'option');
1044
1040
  if (option.class) {
@@ -1072,9 +1068,12 @@ class Render {
1072
1068
  }
1073
1069
  if (option.selected) {
1074
1070
  optionEl.classList.add(this.classes.selected);
1071
+ optionEl.setAttribute('aria-selected', 'true');
1072
+ this.main.main.setAttribute('aria-activedescendant', optionEl.id);
1075
1073
  }
1076
1074
  else {
1077
1075
  optionEl.classList.remove(this.classes.selected);
1076
+ optionEl.setAttribute('aria-selected', 'false');
1078
1077
  }
1079
1078
  optionEl.addEventListener('click', (e) => {
1080
1079
  e.preventDefault();
@@ -1492,13 +1491,13 @@ class SlimSelect {
1492
1491
  afterClose: undefined,
1493
1492
  };
1494
1493
  this.windowResize = debounce(() => {
1495
- if (!this.settings.isOpen) {
1494
+ if (!this.settings.isOpen && !this.settings.isFullOpen) {
1496
1495
  return;
1497
1496
  }
1498
1497
  this.render.moveContent();
1499
1498
  });
1500
1499
  this.windowScroll = debounce(() => {
1501
- if (!this.settings.isOpen) {
1500
+ if (!this.settings.isOpen && !this.settings.isFullOpen) {
1502
1501
  return;
1503
1502
  }
1504
1503
  this.render.moveContent();
@@ -1508,19 +1507,13 @@ class SlimSelect {
1508
1507
  return;
1509
1508
  }
1510
1509
  if (e.target && !hasClassInTree(e.target, this.settings.id)) {
1511
- this.close();
1510
+ this.close(e.type);
1512
1511
  }
1513
1512
  };
1514
1513
  this.windowVisibilityChange = () => {
1515
1514
  if (document.hidden) {
1516
- this.settings.isWindowFocused = false;
1517
1515
  this.close();
1518
1516
  }
1519
- else {
1520
- setTimeout(() => {
1521
- this.settings.isWindowFocused = true;
1522
- }, 20);
1523
- }
1524
1517
  };
1525
1518
  this.selectEl = (typeof config.select === 'string' ? document.querySelector(config.select) : config.select);
1526
1519
  if (!this.selectEl) {
@@ -1593,6 +1586,14 @@ class SlimSelect {
1593
1586
  this.render = new Render(this.settings, this.store, callbacks);
1594
1587
  this.render.renderValues();
1595
1588
  this.render.renderOptions(this.store.getData());
1589
+ const selectAriaLabel = this.selectEl.getAttribute('aria-label');
1590
+ const selectAriaLabelledBy = this.selectEl.getAttribute('aria-labelledby');
1591
+ if (selectAriaLabel) {
1592
+ this.render.main.main.setAttribute('aria-label', selectAriaLabel);
1593
+ }
1594
+ else if (selectAriaLabelledBy) {
1595
+ this.render.main.main.setAttribute('aria-labelledby', selectAriaLabelledBy);
1596
+ }
1596
1597
  if (this.selectEl.parentNode) {
1597
1598
  this.selectEl.parentNode.insertBefore(this.render.main.main, this.selectEl.nextSibling);
1598
1599
  }
@@ -1682,13 +1683,16 @@ class SlimSelect {
1682
1683
  }
1683
1684
  this.render.open();
1684
1685
  if (this.settings.showSearch) {
1685
- this.render.searchFocus(false);
1686
+ this.render.searchFocus();
1686
1687
  }
1688
+ this.settings.isOpen = true;
1687
1689
  setTimeout(() => {
1688
1690
  if (this.events.afterOpen) {
1689
1691
  this.events.afterOpen();
1690
1692
  }
1691
- this.settings.isOpen = true;
1693
+ if (this.settings.isOpen) {
1694
+ this.settings.isFullOpen = true;
1695
+ }
1692
1696
  }, this.settings.timeoutDelay);
1693
1697
  if (this.settings.contentPosition === 'absolute') {
1694
1698
  if (this.settings.intervalMove) {
@@ -1697,7 +1701,7 @@ class SlimSelect {
1697
1701
  this.settings.intervalMove = setInterval(this.render.moveContent.bind(this.render), 500);
1698
1702
  }
1699
1703
  }
1700
- close() {
1704
+ close(eventType = null) {
1701
1705
  if (!this.settings.isOpen || this.settings.alwaysOpen) {
1702
1706
  return;
1703
1707
  }
@@ -1708,12 +1712,13 @@ class SlimSelect {
1708
1712
  if (this.render.content.search.input.value !== '') {
1709
1713
  this.search('');
1710
1714
  }
1711
- this.render.mainFocus(false);
1715
+ this.render.mainFocus(eventType);
1716
+ this.settings.isOpen = false;
1717
+ this.settings.isFullOpen = false;
1712
1718
  setTimeout(() => {
1713
1719
  if (this.events.afterClose) {
1714
1720
  this.events.afterClose();
1715
1721
  }
1716
- this.settings.isOpen = false;
1717
1722
  }, this.settings.timeoutDelay);
1718
1723
  if (this.settings.intervalMove) {
1719
1724
  clearInterval(this.settings.intervalMove);
@@ -60,8 +60,7 @@ var SlimSelectVue = (function (vue) {
60
60
  this.class = [];
61
61
  this.isMultiple = false;
62
62
  this.isOpen = false;
63
- this.isWindowFocused = true;
64
- this.triggerFocus = true;
63
+ this.isFullOpen = false;
65
64
  this.intervalMove = null;
66
65
  if (!settings) {
67
66
  settings = {};
@@ -348,6 +347,7 @@ var SlimSelectVue = (function (vue) {
348
347
  this.main = this.mainDiv();
349
348
  this.content = this.contentDiv();
350
349
  this.updateClassStyles();
350
+ this.updateAriaAttributes();
351
351
  this.settings.contentLocation.appendChild(this.content.main);
352
352
  }
353
353
  enable() {
@@ -361,6 +361,7 @@ var SlimSelectVue = (function (vue) {
361
361
  open() {
362
362
  this.main.arrow.path.setAttribute('d', this.classes.arrowOpen);
363
363
  this.main.main.classList.add(this.settings.openPosition === 'up' ? this.classes.openAbove : this.classes.openBelow);
364
+ this.main.main.setAttribute('aria-expanded', 'true');
364
365
  this.moveContent();
365
366
  const selectedOptions = this.store.getSelectedOptions();
366
367
  if (selectedOptions.length) {
@@ -374,6 +375,7 @@ var SlimSelectVue = (function (vue) {
374
375
  close() {
375
376
  this.main.main.classList.remove(this.classes.openAbove);
376
377
  this.main.main.classList.remove(this.classes.openBelow);
378
+ this.main.main.setAttribute('aria-expanded', 'false');
377
379
  this.content.main.classList.remove(this.classes.openAbove);
378
380
  this.content.main.classList.remove(this.classes.openBelow);
379
381
  this.main.arrow.path.setAttribute('d', this.classes.arrowClose);
@@ -400,15 +402,18 @@ var SlimSelectVue = (function (vue) {
400
402
  this.content.main.classList.add('ss-' + this.settings.contentPosition);
401
403
  }
402
404
  }
405
+ updateAriaAttributes() {
406
+ this.main.main.role = 'combobox';
407
+ this.main.main.setAttribute('aria-haspopup', 'listbox');
408
+ this.main.main.setAttribute('aria-controls', this.content.main.id);
409
+ this.main.main.setAttribute('aria-expanded', 'false');
410
+ this.content.main.setAttribute('role', 'listbox');
411
+ }
403
412
  mainDiv() {
404
413
  const main = document.createElement('div');
405
414
  main.dataset.id = this.settings.id;
415
+ main.id = this.settings.id;
406
416
  main.tabIndex = 0;
407
- main.onfocus = () => {
408
- if (this.settings.triggerFocus && this.settings.isWindowFocused) {
409
- this.callbacks.open();
410
- }
411
- };
412
417
  main.onkeydown = (e) => {
413
418
  switch (e.key) {
414
419
  case 'ArrowUp':
@@ -420,6 +425,8 @@ var SlimSelectVue = (function (vue) {
420
425
  this.callbacks.close();
421
426
  return true;
422
427
  case 'Enter':
428
+ case ' ':
429
+ this.callbacks.open();
423
430
  const highlighted = this.content.list.querySelector('.' + this.classes.highlighted);
424
431
  if (highlighted) {
425
432
  highlighted.click();
@@ -496,12 +503,10 @@ var SlimSelectVue = (function (vue) {
496
503
  },
497
504
  };
498
505
  }
499
- mainFocus(trigger) {
500
- if (!trigger) {
501
- this.settings.triggerFocus = false;
506
+ mainFocus(eventType) {
507
+ if (eventType !== 'click') {
508
+ this.main.main.focus({ preventScroll: true });
502
509
  }
503
- this.main.main.focus({ preventScroll: true });
504
- this.settings.triggerFocus = true;
505
510
  }
506
511
  placeholder() {
507
512
  const placeholderOption = this.store.filter((o) => o.placeholder, false);
@@ -677,6 +682,7 @@ var SlimSelectVue = (function (vue) {
677
682
  contentDiv() {
678
683
  const main = document.createElement('div');
679
684
  main.dataset.id = this.settings.id;
685
+ main.id = this.settings.id;
680
686
  const search = this.searchDiv();
681
687
  main.appendChild(search.main);
682
688
  const list = this.listDiv();
@@ -734,7 +740,6 @@ var SlimSelectVue = (function (vue) {
734
740
  switch (e.key) {
735
741
  case 'ArrowUp':
736
742
  case 'ArrowDown':
737
- this.callbacks.open();
738
743
  e.key === 'ArrowDown' ? this.highlight('down') : this.highlight('up');
739
744
  return false;
740
745
  case 'Tab':
@@ -744,6 +749,7 @@ var SlimSelectVue = (function (vue) {
744
749
  this.callbacks.close();
745
750
  return false;
746
751
  case 'Enter':
752
+ case ' ':
747
753
  if (this.callbacks.addable && e.ctrlKey) {
748
754
  addable.click();
749
755
  }
@@ -756,12 +762,6 @@ var SlimSelectVue = (function (vue) {
756
762
  return false;
757
763
  }
758
764
  };
759
- input.onfocus = () => {
760
- if (this.settings.isOpen) {
761
- return;
762
- }
763
- this.callbacks.open();
764
- };
765
765
  main.appendChild(input);
766
766
  if (this.callbacks.addable) {
767
767
  addable.classList.add(this.classes.addable);
@@ -834,12 +834,8 @@ var SlimSelectVue = (function (vue) {
834
834
  }
835
835
  return searchReturn;
836
836
  }
837
- searchFocus(trigger) {
838
- if (!trigger) {
839
- this.settings.triggerFocus = false;
840
- }
837
+ searchFocus() {
841
838
  this.content.search.input.focus();
842
- this.settings.triggerFocus = true;
843
839
  }
844
840
  getOptions(notPlaceholder = false, notDisabled = false, notHidden = false) {
845
841
  let query = '.' + this.classes.option;
@@ -895,7 +891,6 @@ var SlimSelectVue = (function (vue) {
895
891
  listDiv() {
896
892
  const options = document.createElement('div');
897
893
  options.classList.add(this.classes.list);
898
- options.setAttribute('role', 'listbox');
899
894
  return options;
900
895
  }
901
896
  renderError(error) {
@@ -1040,6 +1035,7 @@ var SlimSelectVue = (function (vue) {
1040
1035
  }
1041
1036
  const optionEl = document.createElement('div');
1042
1037
  optionEl.dataset.id = option.id;
1038
+ optionEl.id = option.id;
1043
1039
  optionEl.classList.add(this.classes.option);
1044
1040
  optionEl.setAttribute('role', 'option');
1045
1041
  if (option.class) {
@@ -1073,9 +1069,12 @@ var SlimSelectVue = (function (vue) {
1073
1069
  }
1074
1070
  if (option.selected) {
1075
1071
  optionEl.classList.add(this.classes.selected);
1072
+ optionEl.setAttribute('aria-selected', 'true');
1073
+ this.main.main.setAttribute('aria-activedescendant', optionEl.id);
1076
1074
  }
1077
1075
  else {
1078
1076
  optionEl.classList.remove(this.classes.selected);
1077
+ optionEl.setAttribute('aria-selected', 'false');
1079
1078
  }
1080
1079
  optionEl.addEventListener('click', (e) => {
1081
1080
  e.preventDefault();
@@ -1493,13 +1492,13 @@ var SlimSelectVue = (function (vue) {
1493
1492
  afterClose: undefined,
1494
1493
  };
1495
1494
  this.windowResize = debounce(() => {
1496
- if (!this.settings.isOpen) {
1495
+ if (!this.settings.isOpen && !this.settings.isFullOpen) {
1497
1496
  return;
1498
1497
  }
1499
1498
  this.render.moveContent();
1500
1499
  });
1501
1500
  this.windowScroll = debounce(() => {
1502
- if (!this.settings.isOpen) {
1501
+ if (!this.settings.isOpen && !this.settings.isFullOpen) {
1503
1502
  return;
1504
1503
  }
1505
1504
  this.render.moveContent();
@@ -1509,19 +1508,13 @@ var SlimSelectVue = (function (vue) {
1509
1508
  return;
1510
1509
  }
1511
1510
  if (e.target && !hasClassInTree(e.target, this.settings.id)) {
1512
- this.close();
1511
+ this.close(e.type);
1513
1512
  }
1514
1513
  };
1515
1514
  this.windowVisibilityChange = () => {
1516
1515
  if (document.hidden) {
1517
- this.settings.isWindowFocused = false;
1518
1516
  this.close();
1519
1517
  }
1520
- else {
1521
- setTimeout(() => {
1522
- this.settings.isWindowFocused = true;
1523
- }, 20);
1524
- }
1525
1518
  };
1526
1519
  this.selectEl = (typeof config.select === 'string' ? document.querySelector(config.select) : config.select);
1527
1520
  if (!this.selectEl) {
@@ -1594,6 +1587,14 @@ var SlimSelectVue = (function (vue) {
1594
1587
  this.render = new Render(this.settings, this.store, callbacks);
1595
1588
  this.render.renderValues();
1596
1589
  this.render.renderOptions(this.store.getData());
1590
+ const selectAriaLabel = this.selectEl.getAttribute('aria-label');
1591
+ const selectAriaLabelledBy = this.selectEl.getAttribute('aria-labelledby');
1592
+ if (selectAriaLabel) {
1593
+ this.render.main.main.setAttribute('aria-label', selectAriaLabel);
1594
+ }
1595
+ else if (selectAriaLabelledBy) {
1596
+ this.render.main.main.setAttribute('aria-labelledby', selectAriaLabelledBy);
1597
+ }
1597
1598
  if (this.selectEl.parentNode) {
1598
1599
  this.selectEl.parentNode.insertBefore(this.render.main.main, this.selectEl.nextSibling);
1599
1600
  }
@@ -1683,13 +1684,16 @@ var SlimSelectVue = (function (vue) {
1683
1684
  }
1684
1685
  this.render.open();
1685
1686
  if (this.settings.showSearch) {
1686
- this.render.searchFocus(false);
1687
+ this.render.searchFocus();
1687
1688
  }
1689
+ this.settings.isOpen = true;
1688
1690
  setTimeout(() => {
1689
1691
  if (this.events.afterOpen) {
1690
1692
  this.events.afterOpen();
1691
1693
  }
1692
- this.settings.isOpen = true;
1694
+ if (this.settings.isOpen) {
1695
+ this.settings.isFullOpen = true;
1696
+ }
1693
1697
  }, this.settings.timeoutDelay);
1694
1698
  if (this.settings.contentPosition === 'absolute') {
1695
1699
  if (this.settings.intervalMove) {
@@ -1698,7 +1702,7 @@ var SlimSelectVue = (function (vue) {
1698
1702
  this.settings.intervalMove = setInterval(this.render.moveContent.bind(this.render), 500);
1699
1703
  }
1700
1704
  }
1701
- close() {
1705
+ close(eventType = null) {
1702
1706
  if (!this.settings.isOpen || this.settings.alwaysOpen) {
1703
1707
  return;
1704
1708
  }
@@ -1709,12 +1713,13 @@ var SlimSelectVue = (function (vue) {
1709
1713
  if (this.render.content.search.input.value !== '') {
1710
1714
  this.search('');
1711
1715
  }
1712
- this.render.mainFocus(false);
1716
+ this.render.mainFocus(eventType);
1717
+ this.settings.isOpen = false;
1718
+ this.settings.isFullOpen = false;
1713
1719
  setTimeout(() => {
1714
1720
  if (this.events.afterClose) {
1715
1721
  this.events.afterClose();
1716
1722
  }
1717
- this.settings.isOpen = false;
1718
1723
  }, this.settings.timeoutDelay);
1719
1724
  if (this.settings.intervalMove) {
1720
1725
  clearInterval(this.settings.intervalMove);