@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"
|
|
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"> </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
|
-
|
|
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
|
-
|
|
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"
|
|
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"> </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
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
+
};
|