@nectary/components 5.1.2 → 5.1.3

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/bundle.js CHANGED
@@ -10494,12 +10494,80 @@ class SegmentedControlOption extends NectaryElement {
10494
10494
  };
10495
10495
  }
10496
10496
  defineCustomElement("sinch-segmented-control-option", SegmentedControlOption);
10497
+ function getActualActiveElement() {
10498
+ let activeElement = document.activeElement;
10499
+ while (activeElement?.shadowRoot?.activeElement != null) {
10500
+ activeElement = activeElement.shadowRoot.activeElement;
10501
+ }
10502
+ return activeElement;
10503
+ }
10504
+ function createKeyboardNavigation() {
10505
+ return {
10506
+ navigateToNextOption(enabledOptions, forward) {
10507
+ const optionsLength = enabledOptions.length;
10508
+ if (optionsLength === 0) {
10509
+ return;
10510
+ }
10511
+ const currentIndex = enabledOptions.findIndex((option) => option === getActualActiveElement());
10512
+ let nextIndex;
10513
+ if (currentIndex !== -1) {
10514
+ if (forward) {
10515
+ nextIndex = (currentIndex + 1) % optionsLength;
10516
+ } else {
10517
+ nextIndex = currentIndex === 0 ? optionsLength - 1 : currentIndex - 1;
10518
+ }
10519
+ } else {
10520
+ nextIndex = forward ? 0 : optionsLength - 1;
10521
+ }
10522
+ this.navigateToOption(enabledOptions, nextIndex);
10523
+ },
10524
+ navigateToOption(enabledOptions, index) {
10525
+ const optionsLength = enabledOptions.length;
10526
+ if (enabledOptions.length === 0 || index < 0 || index >= optionsLength) {
10527
+ return;
10528
+ }
10529
+ const option = enabledOptions[index];
10530
+ option.focus();
10531
+ },
10532
+ handleKeyboardNavigation(e, enabledOptions) {
10533
+ switch (e.code) {
10534
+ case "Space":
10535
+ case "Enter": {
10536
+ e.preventDefault();
10537
+ const target = getTargetByAttribute(e, "value");
10538
+ if (target !== null) {
10539
+ target.click();
10540
+ }
10541
+ break;
10542
+ }
10543
+ case "ArrowLeft":
10544
+ case "ArrowRight": {
10545
+ e.preventDefault();
10546
+ this.navigateToNextOption(enabledOptions, e.code === "ArrowRight");
10547
+ break;
10548
+ }
10549
+ case "Home": {
10550
+ e.preventDefault();
10551
+ this.navigateToOption(enabledOptions, 0);
10552
+ break;
10553
+ }
10554
+ case "End": {
10555
+ e.preventDefault();
10556
+ this.navigateToOption(enabledOptions, enabledOptions.length - 1);
10557
+ break;
10558
+ }
10559
+ }
10560
+ }
10561
+ };
10562
+ }
10497
10563
  const templateHTML$k = '<style>:host{display:block;outline:0}#wrapper{display:flex;flex-direction:row;width:100%;box-sizing:border-box;position:relative;z-index:0}</style><div id="wrapper"><slot></slot></div>';
10498
10564
  const template$k = document.createElement("template");
10499
10565
  template$k.innerHTML = templateHTML$k;
10500
10566
  class SegmentedControl extends NectaryElement {
10501
10567
  #$slot;
10502
10568
  #controller = null;
10569
+ #enabledOptions = [];
10570
+ #keyboardNav = createKeyboardNavigation();
10503
10571
  constructor() {
10504
10572
  super();
10505
10573
  const shadowRoot = this.attachShadow();
@@ -10511,10 +10579,12 @@ class SegmentedControl extends NectaryElement {
10511
10579
  const { signal } = this.#controller;
10512
10580
  const options = { signal };
10513
10581
  this.setAttribute("role", "tablist");
10582
+ this.setAttribute("aria-orientation", "horizontal");
10514
10583
  this.#$slot.addEventListener("slotchange", this.#onSlotChange, options);
10515
10584
  this.#$slot.addEventListener("click", this.#onOptionClick, options);
10516
10585
  this.#$slot.addEventListener("keydown", this.#onOptionKeydown, options);
10517
10586
  this.addEventListener("-change", this.#onChangeReactHandler);
10587
+ this.#updateEnabledOptions();
10518
10588
  }
10519
10589
  disconnectedCallback() {
10520
10590
  this.#controller.abort();
@@ -10537,8 +10607,14 @@ class SegmentedControl extends NectaryElement {
10537
10607
  get value() {
10538
10608
  return getAttribute(this, "value", "");
10539
10609
  }
10610
+ #updateEnabledOptions = () => {
10611
+ this.#enabledOptions = Array.from(this.#$slot.assignedElements()).filter(
10612
+ (option) => !getBooleanAttribute(option, "disabled")
10613
+ );
10614
+ };
10540
10615
  #onSlotChange = () => {
10541
10616
  this.#onValueChange(this.value);
10617
+ this.#updateEnabledOptions();
10542
10618
  };
10543
10619
  #onOptionClick = (e) => {
10544
10620
  const target = getTargetByAttribute(e, "value");
@@ -10551,16 +10627,7 @@ class SegmentedControl extends NectaryElement {
10551
10627
  );
10552
10628
  };
10553
10629
  #onOptionKeydown = (e) => {
10554
- switch (e.code) {
10555
- case "Space":
10556
- case "Enter": {
10557
- e.preventDefault();
10558
- const target = getTargetByAttribute(e, "value");
10559
- if (target !== null) {
10560
- target.click();
10561
- }
10562
- }
10563
- }
10630
+ this.#keyboardNav.handleKeyboardNavigation(e, this.#enabledOptions);
10564
10631
  };
10565
10632
  #onValueChange(value) {
10566
10633
  for (const $option of this.#$slot.assignedElements()) {
@@ -10656,6 +10723,8 @@ template$i.innerHTML = templateHTML$i;
10656
10723
  class SegmentedIconControl extends NectaryElement {
10657
10724
  #$slot;
10658
10725
  #controller = null;
10726
+ #keyboardNav = createKeyboardNavigation();
10727
+ #enabledOptions = [];
10659
10728
  constructor() {
10660
10729
  super();
10661
10730
  const shadowRoot = this.attachShadow();
@@ -10667,10 +10736,12 @@ class SegmentedIconControl extends NectaryElement {
10667
10736
  const { signal } = this.#controller;
10668
10737
  const options = { signal };
10669
10738
  this.setAttribute("role", "tablist");
10739
+ this.setAttribute("aria-orientation", "horizontal");
10670
10740
  this.#$slot.addEventListener("slotchange", this.#onSlotChange, options);
10671
10741
  this.#$slot.addEventListener("click", this.#onOptionClick, options);
10672
10742
  this.#$slot.addEventListener("keydown", this.#onOptionKeydown, options);
10673
10743
  this.addEventListener("-change", this.#onChangeReactHandler, options);
10744
+ this.#updateEnabledOptions();
10674
10745
  }
10675
10746
  disconnectedCallback() {
10676
10747
  this.#controller.abort();
@@ -10699,8 +10770,14 @@ class SegmentedIconControl extends NectaryElement {
10699
10770
  get multiple() {
10700
10771
  return getBooleanAttribute(this, "multiple");
10701
10772
  }
10773
+ #updateEnabledOptions = () => {
10774
+ this.#enabledOptions = Array.from(this.#$slot.assignedElements()).filter(
10775
+ (option) => !getBooleanAttribute(option, "disabled")
10776
+ );
10777
+ };
10702
10778
  #onSlotChange = () => {
10703
10779
  this.#onValueChange(this.value);
10780
+ this.#updateEnabledOptions();
10704
10781
  };
10705
10782
  #onOptionClick = (e) => {
10706
10783
  const target = getTargetByAttribute(e, "value");
@@ -10714,16 +10791,7 @@ class SegmentedIconControl extends NectaryElement {
10714
10791
  );
10715
10792
  };
10716
10793
  #onOptionKeydown = (e) => {
10717
- switch (e.code) {
10718
- case "Space":
10719
- case "Enter": {
10720
- e.preventDefault();
10721
- const target = getTargetByAttribute(e, "value");
10722
- if (target !== null) {
10723
- target.click();
10724
- }
10725
- }
10726
- }
10794
+ this.#keyboardNav.handleKeyboardNavigation(e, this.#enabledOptions);
10727
10795
  };
10728
10796
  #onValueChange(csv) {
10729
10797
  if (this.multiple) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nectary/components",
3
- "version": "5.1.2",
3
+ "version": "5.1.3",
4
4
  "files": [
5
5
  "**/*/*.css",
6
6
  "**/*/*.json",
@@ -2,12 +2,15 @@ import { updateAttribute, getAttribute, getBooleanAttribute, updateBooleanAttrib
2
2
  import { defineCustomElement, NectaryElement } from "../utils/element.js";
3
3
  import { getReactEventHandler } from "../utils/get-react-event-handler.js";
4
4
  import { getTargetByAttribute } from "../utils/event-target.js";
5
+ import { createKeyboardNavigation } from "../utils/control-keyboard-navigation.js";
5
6
  const templateHTML = '<style>:host{display:block;outline:0}#wrapper{display:flex;flex-direction:row;width:100%;box-sizing:border-box;position:relative;z-index:0}</style><div id="wrapper"><slot></slot></div>';
6
7
  const template = document.createElement("template");
7
8
  template.innerHTML = templateHTML;
8
9
  class SegmentedControl extends NectaryElement {
9
10
  #$slot;
10
11
  #controller = null;
12
+ #enabledOptions = [];
13
+ #keyboardNav = createKeyboardNavigation();
11
14
  constructor() {
12
15
  super();
13
16
  const shadowRoot = this.attachShadow();
@@ -19,10 +22,12 @@ class SegmentedControl extends NectaryElement {
19
22
  const { signal } = this.#controller;
20
23
  const options = { signal };
21
24
  this.setAttribute("role", "tablist");
25
+ this.setAttribute("aria-orientation", "horizontal");
22
26
  this.#$slot.addEventListener("slotchange", this.#onSlotChange, options);
23
27
  this.#$slot.addEventListener("click", this.#onOptionClick, options);
24
28
  this.#$slot.addEventListener("keydown", this.#onOptionKeydown, options);
25
29
  this.addEventListener("-change", this.#onChangeReactHandler);
30
+ this.#updateEnabledOptions();
26
31
  }
27
32
  disconnectedCallback() {
28
33
  this.#controller.abort();
@@ -45,8 +50,14 @@ class SegmentedControl extends NectaryElement {
45
50
  get value() {
46
51
  return getAttribute(this, "value", "");
47
52
  }
53
+ #updateEnabledOptions = () => {
54
+ this.#enabledOptions = Array.from(this.#$slot.assignedElements()).filter(
55
+ (option) => !getBooleanAttribute(option, "disabled")
56
+ );
57
+ };
48
58
  #onSlotChange = () => {
49
59
  this.#onValueChange(this.value);
60
+ this.#updateEnabledOptions();
50
61
  };
51
62
  #onOptionClick = (e) => {
52
63
  const target = getTargetByAttribute(e, "value");
@@ -59,16 +70,7 @@ class SegmentedControl extends NectaryElement {
59
70
  );
60
71
  };
61
72
  #onOptionKeydown = (e) => {
62
- switch (e.code) {
63
- case "Space":
64
- case "Enter": {
65
- e.preventDefault();
66
- const target = getTargetByAttribute(e, "value");
67
- if (target !== null) {
68
- target.click();
69
- }
70
- }
71
- }
73
+ this.#keyboardNav.handleKeyboardNavigation(e, this.#enabledOptions);
72
74
  };
73
75
  #onValueChange(value) {
74
76
  for (const $option of this.#$slot.assignedElements()) {
@@ -3,12 +3,15 @@ import { updateAttribute, getAttribute, updateBooleanAttribute, getBooleanAttrib
3
3
  import { defineCustomElement, NectaryElement } from "../utils/element.js";
4
4
  import { getReactEventHandler } from "../utils/get-react-event-handler.js";
5
5
  import { getTargetByAttribute } from "../utils/event-target.js";
6
+ import { createKeyboardNavigation } from "../utils/control-keyboard-navigation.js";
6
7
  const templateHTML = '<style>:host{display:block;outline:0}#wrapper{display:flex;flex-direction:row;width:100%;box-sizing:border-box;position:relative;z-index:0}</style><div id="wrapper"><slot></slot></div>';
7
8
  const template = document.createElement("template");
8
9
  template.innerHTML = templateHTML;
9
10
  class SegmentedIconControl extends NectaryElement {
10
11
  #$slot;
11
12
  #controller = null;
13
+ #keyboardNav = createKeyboardNavigation();
14
+ #enabledOptions = [];
12
15
  constructor() {
13
16
  super();
14
17
  const shadowRoot = this.attachShadow();
@@ -20,10 +23,12 @@ class SegmentedIconControl extends NectaryElement {
20
23
  const { signal } = this.#controller;
21
24
  const options = { signal };
22
25
  this.setAttribute("role", "tablist");
26
+ this.setAttribute("aria-orientation", "horizontal");
23
27
  this.#$slot.addEventListener("slotchange", this.#onSlotChange, options);
24
28
  this.#$slot.addEventListener("click", this.#onOptionClick, options);
25
29
  this.#$slot.addEventListener("keydown", this.#onOptionKeydown, options);
26
30
  this.addEventListener("-change", this.#onChangeReactHandler, options);
31
+ this.#updateEnabledOptions();
27
32
  }
28
33
  disconnectedCallback() {
29
34
  this.#controller.abort();
@@ -52,8 +57,14 @@ class SegmentedIconControl extends NectaryElement {
52
57
  get multiple() {
53
58
  return getBooleanAttribute(this, "multiple");
54
59
  }
60
+ #updateEnabledOptions = () => {
61
+ this.#enabledOptions = Array.from(this.#$slot.assignedElements()).filter(
62
+ (option) => !getBooleanAttribute(option, "disabled")
63
+ );
64
+ };
55
65
  #onSlotChange = () => {
56
66
  this.#onValueChange(this.value);
67
+ this.#updateEnabledOptions();
57
68
  };
58
69
  #onOptionClick = (e) => {
59
70
  const target = getTargetByAttribute(e, "value");
@@ -67,16 +78,7 @@ class SegmentedIconControl extends NectaryElement {
67
78
  );
68
79
  };
69
80
  #onOptionKeydown = (e) => {
70
- switch (e.code) {
71
- case "Space":
72
- case "Enter": {
73
- e.preventDefault();
74
- const target = getTargetByAttribute(e, "value");
75
- if (target !== null) {
76
- target.click();
77
- }
78
- }
79
- }
81
+ this.#keyboardNav.handleKeyboardNavigation(e, this.#enabledOptions);
80
82
  };
81
83
  #onValueChange(csv) {
82
84
  if (this.multiple) {
@@ -0,0 +1,5 @@
1
+ export declare function createKeyboardNavigation(): {
2
+ navigateToNextOption(enabledOptions: Element[], forward: boolean): void;
3
+ navigateToOption(enabledOptions: Element[], index: number): void;
4
+ handleKeyboardNavigation(e: KeyboardEvent, enabledOptions: Element[]): void;
5
+ };
@@ -0,0 +1,70 @@
1
+ import { getTargetByAttribute } from "./event-target.js";
2
+ function getActualActiveElement() {
3
+ let activeElement = document.activeElement;
4
+ while (activeElement?.shadowRoot?.activeElement != null) {
5
+ activeElement = activeElement.shadowRoot.activeElement;
6
+ }
7
+ return activeElement;
8
+ }
9
+ function createKeyboardNavigation() {
10
+ return {
11
+ navigateToNextOption(enabledOptions, forward) {
12
+ const optionsLength = enabledOptions.length;
13
+ if (optionsLength === 0) {
14
+ return;
15
+ }
16
+ const currentIndex = enabledOptions.findIndex((option) => option === getActualActiveElement());
17
+ let nextIndex;
18
+ if (currentIndex !== -1) {
19
+ if (forward) {
20
+ nextIndex = (currentIndex + 1) % optionsLength;
21
+ } else {
22
+ nextIndex = currentIndex === 0 ? optionsLength - 1 : currentIndex - 1;
23
+ }
24
+ } else {
25
+ nextIndex = forward ? 0 : optionsLength - 1;
26
+ }
27
+ this.navigateToOption(enabledOptions, nextIndex);
28
+ },
29
+ navigateToOption(enabledOptions, index) {
30
+ const optionsLength = enabledOptions.length;
31
+ if (enabledOptions.length === 0 || index < 0 || index >= optionsLength) {
32
+ return;
33
+ }
34
+ const option = enabledOptions[index];
35
+ option.focus();
36
+ },
37
+ handleKeyboardNavigation(e, enabledOptions) {
38
+ switch (e.code) {
39
+ case "Space":
40
+ case "Enter": {
41
+ e.preventDefault();
42
+ const target = getTargetByAttribute(e, "value");
43
+ if (target !== null) {
44
+ target.click();
45
+ }
46
+ break;
47
+ }
48
+ case "ArrowLeft":
49
+ case "ArrowRight": {
50
+ e.preventDefault();
51
+ this.navigateToNextOption(enabledOptions, e.code === "ArrowRight");
52
+ break;
53
+ }
54
+ case "Home": {
55
+ e.preventDefault();
56
+ this.navigateToOption(enabledOptions, 0);
57
+ break;
58
+ }
59
+ case "End": {
60
+ e.preventDefault();
61
+ this.navigateToOption(enabledOptions, enabledOptions.length - 1);
62
+ break;
63
+ }
64
+ }
65
+ }
66
+ };
67
+ }
68
+ export {
69
+ createKeyboardNavigation
70
+ };