@nectary/components 5.1.2 → 5.1.4

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
@@ -1054,7 +1054,7 @@ class CodeTag extends NectaryElement {
1054
1054
  }
1055
1055
  }
1056
1056
  defineCustomElement("sinch-code-tag", CodeTag);
1057
- const templateHTML$13 = '<style>:host{display:inline}a{font:var(--sinch-comp-link-default-font-initial);font-size:inherit;line-height:inherit;text-decoration:var(--sinch-comp-link-default-text-decoration-initial);color:var(--sinch-comp-link-color-default-text-initial);border-radius:.5em;white-space:nowrap;--sinch-global-color-icon:var(--sinch-comp-link-color-default-icon-initial)}a:hover{text-decoration:var(--sinch-comp-link-default-text-decoration-hover);color:var(--sinch-comp-link-color-default-text-hover);--sinch-global-color-icon:var(--sinch-comp-link-color-default-icon-hover)}a:focus-visible{outline:2px solid var(--sinch-comp-link-color-default-outline-focus);outline-offset:2px}:host([standalone]){display:block}:host([standalone]) a{display:block;font:var(--sinch-comp-link-standalone-font-initial);font-size:inherit;line-height:inherit;text-decoration:none;width:fit-content}#external-icon,#standalone-icon{display:none;height:1em}#icon-prefix{display:none;margin-left:-.25em}:host([external]:not([standalone])) #external-icon{display:inline-block;margin-left:.25em;vertical-align:-.2em;--sinch-global-size-icon:1em}:host([standalone][external]) #external-icon{display:inline-block;vertical-align:-.4em;--sinch-global-size-icon:1.5em}:host([standalone]) #icon-prefix{display:inline}:host([standalone]:not([external])) #standalone-icon{display:inline-block;vertical-align:-.4em;--sinch-global-size-icon:1.5em}:host([disabled]) a{color:var(--sinch-comp-link-color-disabled-text-initial);pointer-events:none;cursor:initial;text-decoration:var(--sinch-comp-link-default-text-decoration-disabled);--sinch-global-color-icon:var(--sinch-comp-link-color-disabled-icon-initial)}#content{white-space:var(--sinch-global-text-white-space,normal)}</style><a referrerpolicy="no-referer" aria-hidden="true"><span id="content"></span> <span id="icon-prefix">&nbsp;</span><sinch-icon icons-version="2" name="fa-arrow-up-right" id="external-icon"></sinch-icon><sinch-icon icons-version="2" name="fa-arrow-right" id="standalone-icon"></sinch-icon></a>';
1057
+ const templateHTML$13 = '<style>:host{display:inline}a{font:var(--sinch-comp-link-default-font-initial);font-size:inherit;line-height:inherit;text-decoration:var(--sinch-comp-link-default-text-decoration-initial);color:var(--sinch-comp-link-color-default-text-initial);border-radius:.5em;white-space:nowrap;--sinch-global-color-icon:var(--sinch-comp-link-color-default-icon-initial)}a:hover{text-decoration:var(--sinch-comp-link-default-text-decoration-hover);color:var(--sinch-comp-link-color-default-text-hover);--sinch-global-color-icon:var(--sinch-comp-link-color-default-icon-hover)}a:focus-visible{outline:2px solid var(--sinch-comp-link-color-default-outline-focus);outline-offset:2px}:host([standalone]){display:block}:host([standalone]) a{display:block;font:var(--sinch-comp-link-standalone-font-initial);font-size:inherit;line-height:inherit;text-decoration:none;width:fit-content}#external-icon,#standalone-icon{display:none;height:1em}#icon-prefix{display:none;margin-left:-.25em}:host([external]:not([standalone])) #external-icon{display:inline-block;margin-left:.25em;vertical-align:-.2em;--sinch-global-size-icon:1em}:host([standalone][external]) #external-icon{display:inline-block;vertical-align:-.4em;--sinch-global-size-icon:1.5em}:host([standalone]) #icon-prefix{display:inline}:host([standalone]:not([external])) #standalone-icon{display:inline-block;vertical-align:-.4em;--sinch-global-size-icon:1.5em}:host([disabled]) a{color:var(--sinch-comp-link-color-disabled-text-initial);pointer-events:none;cursor:initial;text-decoration:var(--sinch-comp-link-default-text-decoration-disabled);--sinch-global-color-icon:var(--sinch-comp-link-color-disabled-icon-initial)}#content{white-space:var(--sinch-global-text-white-space,normal)}</style><a referrerpolicy="no-referer"><span id="content"></span> <span id="icon-prefix">&nbsp;</span><sinch-icon icons-version="2" name="fa-arrow-up-right" id="external-icon"></sinch-icon><sinch-icon icons-version="2" name="fa-arrow-right" id="standalone-icon"></sinch-icon></a>';
1058
1058
  const template$13 = document.createElement("template");
1059
1059
  template$13.innerHTML = templateHTML$13;
1060
1060
  class Link extends NectaryElement {
@@ -1068,7 +1068,6 @@ class Link extends NectaryElement {
1068
1068
  this.#$text = shadowRoot.querySelector("#content");
1069
1069
  }
1070
1070
  connectedCallback() {
1071
- this.setAttribute("role", "link");
1072
1071
  this.#$anchor.addEventListener("click", this.#onAnchorClick);
1073
1072
  this.#$anchor.addEventListener("focus", this.#onAnchorFocus);
1074
1073
  this.#$anchor.addEventListener("blur", this.#onAnchorBlur);
@@ -10494,12 +10493,80 @@ class SegmentedControlOption extends NectaryElement {
10494
10493
  };
10495
10494
  }
10496
10495
  defineCustomElement("sinch-segmented-control-option", SegmentedControlOption);
10496
+ function getActualActiveElement() {
10497
+ let activeElement = document.activeElement;
10498
+ while (activeElement?.shadowRoot?.activeElement != null) {
10499
+ activeElement = activeElement.shadowRoot.activeElement;
10500
+ }
10501
+ return activeElement;
10502
+ }
10503
+ function createKeyboardNavigation() {
10504
+ return {
10505
+ navigateToNextOption(enabledOptions, forward) {
10506
+ const optionsLength = enabledOptions.length;
10507
+ if (optionsLength === 0) {
10508
+ return;
10509
+ }
10510
+ const currentIndex = enabledOptions.findIndex((option) => option === getActualActiveElement());
10511
+ let nextIndex;
10512
+ if (currentIndex !== -1) {
10513
+ if (forward) {
10514
+ nextIndex = (currentIndex + 1) % optionsLength;
10515
+ } else {
10516
+ nextIndex = currentIndex === 0 ? optionsLength - 1 : currentIndex - 1;
10517
+ }
10518
+ } else {
10519
+ nextIndex = forward ? 0 : optionsLength - 1;
10520
+ }
10521
+ this.navigateToOption(enabledOptions, nextIndex);
10522
+ },
10523
+ navigateToOption(enabledOptions, index) {
10524
+ const optionsLength = enabledOptions.length;
10525
+ if (enabledOptions.length === 0 || index < 0 || index >= optionsLength) {
10526
+ return;
10527
+ }
10528
+ const option = enabledOptions[index];
10529
+ option.focus();
10530
+ },
10531
+ handleKeyboardNavigation(e, enabledOptions) {
10532
+ switch (e.code) {
10533
+ case "Space":
10534
+ case "Enter": {
10535
+ e.preventDefault();
10536
+ const target = getTargetByAttribute(e, "value");
10537
+ if (target !== null) {
10538
+ target.click();
10539
+ }
10540
+ break;
10541
+ }
10542
+ case "ArrowLeft":
10543
+ case "ArrowRight": {
10544
+ e.preventDefault();
10545
+ this.navigateToNextOption(enabledOptions, e.code === "ArrowRight");
10546
+ break;
10547
+ }
10548
+ case "Home": {
10549
+ e.preventDefault();
10550
+ this.navigateToOption(enabledOptions, 0);
10551
+ break;
10552
+ }
10553
+ case "End": {
10554
+ e.preventDefault();
10555
+ this.navigateToOption(enabledOptions, enabledOptions.length - 1);
10556
+ break;
10557
+ }
10558
+ }
10559
+ }
10560
+ };
10561
+ }
10497
10562
  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
10563
  const template$k = document.createElement("template");
10499
10564
  template$k.innerHTML = templateHTML$k;
10500
10565
  class SegmentedControl extends NectaryElement {
10501
10566
  #$slot;
10502
10567
  #controller = null;
10568
+ #enabledOptions = [];
10569
+ #keyboardNav = createKeyboardNavigation();
10503
10570
  constructor() {
10504
10571
  super();
10505
10572
  const shadowRoot = this.attachShadow();
@@ -10511,10 +10578,12 @@ class SegmentedControl extends NectaryElement {
10511
10578
  const { signal } = this.#controller;
10512
10579
  const options = { signal };
10513
10580
  this.setAttribute("role", "tablist");
10581
+ this.setAttribute("aria-orientation", "horizontal");
10514
10582
  this.#$slot.addEventListener("slotchange", this.#onSlotChange, options);
10515
10583
  this.#$slot.addEventListener("click", this.#onOptionClick, options);
10516
10584
  this.#$slot.addEventListener("keydown", this.#onOptionKeydown, options);
10517
10585
  this.addEventListener("-change", this.#onChangeReactHandler);
10586
+ this.#updateEnabledOptions();
10518
10587
  }
10519
10588
  disconnectedCallback() {
10520
10589
  this.#controller.abort();
@@ -10537,8 +10606,14 @@ class SegmentedControl extends NectaryElement {
10537
10606
  get value() {
10538
10607
  return getAttribute(this, "value", "");
10539
10608
  }
10609
+ #updateEnabledOptions = () => {
10610
+ this.#enabledOptions = Array.from(this.#$slot.assignedElements()).filter(
10611
+ (option) => !getBooleanAttribute(option, "disabled")
10612
+ );
10613
+ };
10540
10614
  #onSlotChange = () => {
10541
10615
  this.#onValueChange(this.value);
10616
+ this.#updateEnabledOptions();
10542
10617
  };
10543
10618
  #onOptionClick = (e) => {
10544
10619
  const target = getTargetByAttribute(e, "value");
@@ -10551,16 +10626,7 @@ class SegmentedControl extends NectaryElement {
10551
10626
  );
10552
10627
  };
10553
10628
  #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
- }
10629
+ this.#keyboardNav.handleKeyboardNavigation(e, this.#enabledOptions);
10564
10630
  };
10565
10631
  #onValueChange(value) {
10566
10632
  for (const $option of this.#$slot.assignedElements()) {
@@ -10656,6 +10722,8 @@ template$i.innerHTML = templateHTML$i;
10656
10722
  class SegmentedIconControl extends NectaryElement {
10657
10723
  #$slot;
10658
10724
  #controller = null;
10725
+ #keyboardNav = createKeyboardNavigation();
10726
+ #enabledOptions = [];
10659
10727
  constructor() {
10660
10728
  super();
10661
10729
  const shadowRoot = this.attachShadow();
@@ -10667,10 +10735,12 @@ class SegmentedIconControl extends NectaryElement {
10667
10735
  const { signal } = this.#controller;
10668
10736
  const options = { signal };
10669
10737
  this.setAttribute("role", "tablist");
10738
+ this.setAttribute("aria-orientation", "horizontal");
10670
10739
  this.#$slot.addEventListener("slotchange", this.#onSlotChange, options);
10671
10740
  this.#$slot.addEventListener("click", this.#onOptionClick, options);
10672
10741
  this.#$slot.addEventListener("keydown", this.#onOptionKeydown, options);
10673
10742
  this.addEventListener("-change", this.#onChangeReactHandler, options);
10743
+ this.#updateEnabledOptions();
10674
10744
  }
10675
10745
  disconnectedCallback() {
10676
10746
  this.#controller.abort();
@@ -10699,8 +10769,14 @@ class SegmentedIconControl extends NectaryElement {
10699
10769
  get multiple() {
10700
10770
  return getBooleanAttribute(this, "multiple");
10701
10771
  }
10772
+ #updateEnabledOptions = () => {
10773
+ this.#enabledOptions = Array.from(this.#$slot.assignedElements()).filter(
10774
+ (option) => !getBooleanAttribute(option, "disabled")
10775
+ );
10776
+ };
10702
10777
  #onSlotChange = () => {
10703
10778
  this.#onValueChange(this.value);
10779
+ this.#updateEnabledOptions();
10704
10780
  };
10705
10781
  #onOptionClick = (e) => {
10706
10782
  const target = getTargetByAttribute(e, "value");
@@ -10714,16 +10790,7 @@ class SegmentedIconControl extends NectaryElement {
10714
10790
  );
10715
10791
  };
10716
10792
  #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
- }
10793
+ this.#keyboardNav.handleKeyboardNavigation(e, this.#enabledOptions);
10727
10794
  };
10728
10795
  #onValueChange(csv) {
10729
10796
  if (this.multiple) {
package/link/index.js CHANGED
@@ -2,7 +2,7 @@ import "../icon/index.js";
2
2
  import { isAttrEqual, updateAttribute, updateBooleanAttribute, isAttrTrue, getAttribute, getBooleanAttribute } from "../utils/dom.js";
3
3
  import { defineCustomElement, NectaryElement } from "../utils/element.js";
4
4
  import { getReactEventHandler } from "../utils/get-react-event-handler.js";
5
- const templateHTML = '<style>:host{display:inline}a{font:var(--sinch-comp-link-default-font-initial);font-size:inherit;line-height:inherit;text-decoration:var(--sinch-comp-link-default-text-decoration-initial);color:var(--sinch-comp-link-color-default-text-initial);border-radius:.5em;white-space:nowrap;--sinch-global-color-icon:var(--sinch-comp-link-color-default-icon-initial)}a:hover{text-decoration:var(--sinch-comp-link-default-text-decoration-hover);color:var(--sinch-comp-link-color-default-text-hover);--sinch-global-color-icon:var(--sinch-comp-link-color-default-icon-hover)}a:focus-visible{outline:2px solid var(--sinch-comp-link-color-default-outline-focus);outline-offset:2px}:host([standalone]){display:block}:host([standalone]) a{display:block;font:var(--sinch-comp-link-standalone-font-initial);font-size:inherit;line-height:inherit;text-decoration:none;width:fit-content}#external-icon,#standalone-icon{display:none;height:1em}#icon-prefix{display:none;margin-left:-.25em}:host([external]:not([standalone])) #external-icon{display:inline-block;margin-left:.25em;vertical-align:-.2em;--sinch-global-size-icon:1em}:host([standalone][external]) #external-icon{display:inline-block;vertical-align:-.4em;--sinch-global-size-icon:1.5em}:host([standalone]) #icon-prefix{display:inline}:host([standalone]:not([external])) #standalone-icon{display:inline-block;vertical-align:-.4em;--sinch-global-size-icon:1.5em}:host([disabled]) a{color:var(--sinch-comp-link-color-disabled-text-initial);pointer-events:none;cursor:initial;text-decoration:var(--sinch-comp-link-default-text-decoration-disabled);--sinch-global-color-icon:var(--sinch-comp-link-color-disabled-icon-initial)}#content{white-space:var(--sinch-global-text-white-space,normal)}</style><a referrerpolicy="no-referer" aria-hidden="true"><span id="content"></span> <span id="icon-prefix">&nbsp;</span><sinch-icon icons-version="2" name="fa-arrow-up-right" id="external-icon"></sinch-icon><sinch-icon icons-version="2" name="fa-arrow-right" id="standalone-icon"></sinch-icon></a>';
5
+ const templateHTML = '<style>:host{display:inline}a{font:var(--sinch-comp-link-default-font-initial);font-size:inherit;line-height:inherit;text-decoration:var(--sinch-comp-link-default-text-decoration-initial);color:var(--sinch-comp-link-color-default-text-initial);border-radius:.5em;white-space:nowrap;--sinch-global-color-icon:var(--sinch-comp-link-color-default-icon-initial)}a:hover{text-decoration:var(--sinch-comp-link-default-text-decoration-hover);color:var(--sinch-comp-link-color-default-text-hover);--sinch-global-color-icon:var(--sinch-comp-link-color-default-icon-hover)}a:focus-visible{outline:2px solid var(--sinch-comp-link-color-default-outline-focus);outline-offset:2px}:host([standalone]){display:block}:host([standalone]) a{display:block;font:var(--sinch-comp-link-standalone-font-initial);font-size:inherit;line-height:inherit;text-decoration:none;width:fit-content}#external-icon,#standalone-icon{display:none;height:1em}#icon-prefix{display:none;margin-left:-.25em}:host([external]:not([standalone])) #external-icon{display:inline-block;margin-left:.25em;vertical-align:-.2em;--sinch-global-size-icon:1em}:host([standalone][external]) #external-icon{display:inline-block;vertical-align:-.4em;--sinch-global-size-icon:1.5em}:host([standalone]) #icon-prefix{display:inline}:host([standalone]:not([external])) #standalone-icon{display:inline-block;vertical-align:-.4em;--sinch-global-size-icon:1.5em}:host([disabled]) a{color:var(--sinch-comp-link-color-disabled-text-initial);pointer-events:none;cursor:initial;text-decoration:var(--sinch-comp-link-default-text-decoration-disabled);--sinch-global-color-icon:var(--sinch-comp-link-color-disabled-icon-initial)}#content{white-space:var(--sinch-global-text-white-space,normal)}</style><a referrerpolicy="no-referer"><span id="content"></span> <span id="icon-prefix">&nbsp;</span><sinch-icon icons-version="2" name="fa-arrow-up-right" id="external-icon"></sinch-icon><sinch-icon icons-version="2" name="fa-arrow-right" id="standalone-icon"></sinch-icon></a>';
6
6
  const template = document.createElement("template");
7
7
  template.innerHTML = templateHTML;
8
8
  class Link extends NectaryElement {
@@ -16,7 +16,6 @@ class Link extends NectaryElement {
16
16
  this.#$text = shadowRoot.querySelector("#content");
17
17
  }
18
18
  connectedCallback() {
19
- this.setAttribute("role", "link");
20
19
  this.#$anchor.addEventListener("click", this.#onAnchorClick);
21
20
  this.#$anchor.addEventListener("focus", this.#onAnchorFocus);
22
21
  this.#$anchor.addEventListener("blur", this.#onAnchorBlur);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nectary/components",
3
- "version": "5.1.2",
3
+ "version": "5.1.4",
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
+ };