@nectary/components 5.37.7 → 5.38.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.
- package/bundle.js +227 -85
- package/field/index.d.ts +3 -0
- package/field/index.js +15 -1
- package/field/types.d.ts +2 -0
- package/package.json +1 -1
- package/tabs/index.js +101 -9
- package/tabs-icon-option/index.js +28 -10
- package/tabs-option/index.js +26 -8
- package/utils/control-keyboard-navigation.d.ts +1 -0
- package/utils/control-keyboard-navigation.js +9 -3
- package/utils/index.d.ts +1 -0
- package/utils/index.js +2 -0
package/bundle.js
CHANGED
|
@@ -7378,12 +7378,80 @@ class Popover extends NectaryElement {
|
|
|
7378
7378
|
};
|
|
7379
7379
|
}
|
|
7380
7380
|
defineCustomElement("sinch-popover", Popover);
|
|
7381
|
+
const isActivationKey = (key) => key === "Enter" || key === " ";
|
|
7382
|
+
function createKeyboardNavigation() {
|
|
7383
|
+
return {
|
|
7384
|
+
navigateToNextOption(enabledOptions, forward) {
|
|
7385
|
+
const optionsLength = enabledOptions.length;
|
|
7386
|
+
if (optionsLength === 0) {
|
|
7387
|
+
return;
|
|
7388
|
+
}
|
|
7389
|
+
const currentIndex = enabledOptions.findIndex((option) => option === getDeepActiveElement());
|
|
7390
|
+
let nextIndex;
|
|
7391
|
+
if (currentIndex !== -1) {
|
|
7392
|
+
if (forward) {
|
|
7393
|
+
nextIndex = (currentIndex + 1) % optionsLength;
|
|
7394
|
+
} else {
|
|
7395
|
+
nextIndex = currentIndex === 0 ? optionsLength - 1 : currentIndex - 1;
|
|
7396
|
+
}
|
|
7397
|
+
} else {
|
|
7398
|
+
nextIndex = forward ? 0 : optionsLength - 1;
|
|
7399
|
+
}
|
|
7400
|
+
this.navigateToOption(enabledOptions, nextIndex);
|
|
7401
|
+
},
|
|
7402
|
+
navigateToOption(enabledOptions, index) {
|
|
7403
|
+
const optionsLength = enabledOptions.length;
|
|
7404
|
+
if (enabledOptions.length === 0 || index < 0 || index >= optionsLength) {
|
|
7405
|
+
return;
|
|
7406
|
+
}
|
|
7407
|
+
const option = enabledOptions[index];
|
|
7408
|
+
option.focus();
|
|
7409
|
+
},
|
|
7410
|
+
handleKeyboardNavigation(e, enabledOptions) {
|
|
7411
|
+
switch (e.code) {
|
|
7412
|
+
case "Space":
|
|
7413
|
+
case "Enter": {
|
|
7414
|
+
e.preventDefault();
|
|
7415
|
+
const target = getTargetByAttribute(e, "value");
|
|
7416
|
+
if (target !== null) {
|
|
7417
|
+
target.click();
|
|
7418
|
+
}
|
|
7419
|
+
break;
|
|
7420
|
+
}
|
|
7421
|
+
case "ArrowLeft": {
|
|
7422
|
+
e.preventDefault();
|
|
7423
|
+
this.navigateToNextOption(enabledOptions, false);
|
|
7424
|
+
break;
|
|
7425
|
+
}
|
|
7426
|
+
case "ArrowRight": {
|
|
7427
|
+
e.preventDefault();
|
|
7428
|
+
this.navigateToNextOption(enabledOptions, true);
|
|
7429
|
+
break;
|
|
7430
|
+
}
|
|
7431
|
+
case "Home": {
|
|
7432
|
+
e.preventDefault();
|
|
7433
|
+
this.navigateToOption(enabledOptions, 0);
|
|
7434
|
+
break;
|
|
7435
|
+
}
|
|
7436
|
+
case "End": {
|
|
7437
|
+
e.preventDefault();
|
|
7438
|
+
this.navigateToOption(enabledOptions, enabledOptions.length - 1);
|
|
7439
|
+
break;
|
|
7440
|
+
}
|
|
7441
|
+
}
|
|
7442
|
+
}
|
|
7443
|
+
};
|
|
7444
|
+
}
|
|
7381
7445
|
const templateHTML$P = '<style>:host{display:block}#wrapper{display:flex;width:100%;height:40px;border-bottom:1px solid var(--sinch-comp-tab-color-default-border-initial);box-sizing:border-box}</style><div id="wrapper"><slot></slot></div>';
|
|
7382
7446
|
const template$P = document.createElement("template");
|
|
7383
7447
|
template$P.innerHTML = templateHTML$P;
|
|
7384
7448
|
class Tabs extends NectaryElement {
|
|
7385
7449
|
#$slot;
|
|
7386
7450
|
#controller = null;
|
|
7451
|
+
#enabledOptions = [];
|
|
7452
|
+
#keyboardNav = createKeyboardNavigation();
|
|
7453
|
+
#observer = null;
|
|
7454
|
+
#rovingValue = null;
|
|
7387
7455
|
constructor() {
|
|
7388
7456
|
super();
|
|
7389
7457
|
const shadowRoot = this.attachShadow();
|
|
@@ -7393,14 +7461,28 @@ class Tabs extends NectaryElement {
|
|
|
7393
7461
|
connectedCallback() {
|
|
7394
7462
|
this.#controller = new AbortController();
|
|
7395
7463
|
const { signal } = this.#controller;
|
|
7464
|
+
const options = { signal };
|
|
7396
7465
|
this.setAttribute("role", "tablist");
|
|
7397
|
-
this
|
|
7398
|
-
this.#$slot.addEventListener("
|
|
7399
|
-
this.addEventListener("
|
|
7466
|
+
this.setAttribute("aria-orientation", "horizontal");
|
|
7467
|
+
this.#$slot.addEventListener("option-change", this.#onOptionChange, options);
|
|
7468
|
+
this.#$slot.addEventListener("slotchange", this.#onSlotChange, options);
|
|
7469
|
+
this.#$slot.addEventListener("focusin", this.#onOptionFocusIn, options);
|
|
7470
|
+
this.#$slot.addEventListener("keydown", this.#onOptionKeydown, options);
|
|
7471
|
+
this.addEventListener("-change", this.#onChangeReactHandler, options);
|
|
7472
|
+
this.#observer = new MutationObserver(this.#onOptionMutation);
|
|
7473
|
+
this.#observer.observe(this, {
|
|
7474
|
+
attributes: true,
|
|
7475
|
+
attributeFilter: ["disabled", "value"],
|
|
7476
|
+
childList: true,
|
|
7477
|
+
subtree: true
|
|
7478
|
+
});
|
|
7479
|
+
this.#syncOptions();
|
|
7400
7480
|
}
|
|
7401
7481
|
disconnectedCallback() {
|
|
7402
7482
|
this.#controller.abort();
|
|
7403
7483
|
this.#controller = null;
|
|
7484
|
+
this.#observer?.disconnect();
|
|
7485
|
+
this.#observer = null;
|
|
7404
7486
|
}
|
|
7405
7487
|
static get observedAttributes() {
|
|
7406
7488
|
return ["value"];
|
|
@@ -7426,19 +7508,91 @@ class Tabs extends NectaryElement {
|
|
|
7426
7508
|
}
|
|
7427
7509
|
return null;
|
|
7428
7510
|
}
|
|
7511
|
+
#getOptions() {
|
|
7512
|
+
return this.#$slot.assignedElements();
|
|
7513
|
+
}
|
|
7514
|
+
#updateEnabledOptions(options = this.#getOptions()) {
|
|
7515
|
+
this.#enabledOptions = options.filter((option) => !getBooleanAttribute(option, "disabled"));
|
|
7516
|
+
}
|
|
7517
|
+
#getFocusableOption() {
|
|
7518
|
+
if (this.#enabledOptions.length === 0) {
|
|
7519
|
+
return null;
|
|
7520
|
+
}
|
|
7521
|
+
if (this.#rovingValue !== null) {
|
|
7522
|
+
const rovingOption = this.#enabledOptions.find(
|
|
7523
|
+
(option) => getAttribute(option, "value", "") === this.#rovingValue
|
|
7524
|
+
);
|
|
7525
|
+
if (rovingOption != null) {
|
|
7526
|
+
return rovingOption;
|
|
7527
|
+
}
|
|
7528
|
+
}
|
|
7529
|
+
const selectedOption = this.#enabledOptions.find(
|
|
7530
|
+
(option) => getAttribute(option, "value", "") === this.value
|
|
7531
|
+
);
|
|
7532
|
+
return selectedOption ?? this.#enabledOptions[0];
|
|
7533
|
+
}
|
|
7534
|
+
#syncOptions() {
|
|
7535
|
+
const options = this.#getOptions();
|
|
7536
|
+
this.#updateEnabledOptions(options);
|
|
7537
|
+
const focusableOption = this.#getFocusableOption();
|
|
7538
|
+
this.#rovingValue = focusableOption == null ? null : getAttribute(focusableOption, "value", "");
|
|
7539
|
+
for (const $option of options) {
|
|
7540
|
+
const isDisabled = getBooleanAttribute($option, "disabled");
|
|
7541
|
+
const isChecked = !isDisabled && this.value === getAttribute($option, "value", "");
|
|
7542
|
+
updateBooleanAttribute($option, "data-checked", isChecked);
|
|
7543
|
+
$option.tabIndex = !isDisabled && $option === focusableOption ? 0 : -1;
|
|
7544
|
+
}
|
|
7545
|
+
}
|
|
7429
7546
|
#onSlotChange = () => {
|
|
7430
|
-
this.#
|
|
7547
|
+
this.#syncOptions();
|
|
7431
7548
|
};
|
|
7432
7549
|
#onOptionChange = (e) => {
|
|
7433
7550
|
e.stopPropagation();
|
|
7434
|
-
|
|
7551
|
+
const value = e.detail;
|
|
7552
|
+
this.#rovingValue = value;
|
|
7553
|
+
this.#dispatchChangeEvent(value);
|
|
7435
7554
|
};
|
|
7436
7555
|
#onValueChange(value) {
|
|
7437
|
-
|
|
7438
|
-
|
|
7439
|
-
updateBooleanAttribute($option, "data-checked", isChecked);
|
|
7440
|
-
}
|
|
7556
|
+
this.#rovingValue = value;
|
|
7557
|
+
this.#syncOptions();
|
|
7441
7558
|
}
|
|
7559
|
+
#onOptionFocusIn = (e) => {
|
|
7560
|
+
const target = getTargetByAttribute(e, "value");
|
|
7561
|
+
if (target === null || getBooleanAttribute(target, "disabled")) {
|
|
7562
|
+
return;
|
|
7563
|
+
}
|
|
7564
|
+
this.#rovingValue = getAttribute(target, "value", "");
|
|
7565
|
+
this.#syncOptions();
|
|
7566
|
+
};
|
|
7567
|
+
#onOptionKeydown = (e) => {
|
|
7568
|
+
switch (e.code) {
|
|
7569
|
+
case "ArrowLeft": {
|
|
7570
|
+
e.preventDefault();
|
|
7571
|
+
this.#keyboardNav.navigateToNextOption(this.#enabledOptions, false);
|
|
7572
|
+
break;
|
|
7573
|
+
}
|
|
7574
|
+
case "ArrowRight": {
|
|
7575
|
+
e.preventDefault();
|
|
7576
|
+
this.#keyboardNav.navigateToNextOption(this.#enabledOptions, true);
|
|
7577
|
+
break;
|
|
7578
|
+
}
|
|
7579
|
+
case "Home": {
|
|
7580
|
+
e.preventDefault();
|
|
7581
|
+
this.#keyboardNav.navigateToOption(this.#enabledOptions, 0);
|
|
7582
|
+
break;
|
|
7583
|
+
}
|
|
7584
|
+
case "End": {
|
|
7585
|
+
e.preventDefault();
|
|
7586
|
+
this.#keyboardNav.navigateToOption(this.#enabledOptions, this.#enabledOptions.length - 1);
|
|
7587
|
+
break;
|
|
7588
|
+
}
|
|
7589
|
+
}
|
|
7590
|
+
};
|
|
7591
|
+
#onOptionMutation = (mutations) => {
|
|
7592
|
+
if (mutations.some((mutation) => mutation.target !== this)) {
|
|
7593
|
+
this.#syncOptions();
|
|
7594
|
+
}
|
|
7595
|
+
};
|
|
7442
7596
|
#dispatchChangeEvent(value) {
|
|
7443
7597
|
this.dispatchEvent(
|
|
7444
7598
|
new CustomEvent("-change", { detail: value })
|
|
@@ -7450,25 +7604,26 @@ class Tabs extends NectaryElement {
|
|
|
7450
7604
|
};
|
|
7451
7605
|
}
|
|
7452
7606
|
defineCustomElement("sinch-tabs", Tabs);
|
|
7453
|
-
const templateHTML$O = '<style>:host{display:block;outline:0}#button{all:initial;position:relative;display:flex;flex-direction:column;padding:12px 16px 0;box-sizing:border-box;cursor:pointer;background-color:var(--sinch-comp-tab-color-default-background-initial);border-top-left-radius:var(--sinch-comp-tab-shape-radius);border-top-right-radius:var(--sinch-comp-tab-shape-radius);height:39px;--sinch-global-color-icon:var(--sinch-comp-tab-color-default-icon-initial);--sinch-global-size-icon:var(--sinch-comp-tab-size-icon)}
|
|
7607
|
+
const templateHTML$O = '<style>:host{display:block;outline:0}#button{all:initial;position:relative;display:flex;flex-direction:column;padding:12px 16px 0;box-sizing:border-box;cursor:pointer;background-color:var(--sinch-comp-tab-color-default-background-initial);border-top-left-radius:var(--sinch-comp-tab-shape-radius);border-top-right-radius:var(--sinch-comp-tab-shape-radius);height:39px;--sinch-global-color-icon:var(--sinch-comp-tab-color-default-icon-initial);--sinch-global-size-icon:var(--sinch-comp-tab-size-icon)}:host([disabled]) #button{cursor:unset;pointer-events:none;--sinch-global-color-icon:var(--sinch-comp-tab-color-disabled-icon-initial)}:host([data-checked]) #button{--sinch-global-color-icon:var(--sinch-comp-tab-color-checked-icon-initial)}:host([data-checked]) #button::before{content:"";position:absolute;left:0;right:0;bottom:-1px;pointer-events:none;border-top:2px solid var(--sinch-comp-tab-color-checked-border-initial)}:host(:hover:not([disabled])) #button{background-color:var(--sinch-comp-tab-color-default-background-hover)}:host(:focus-visible) #button::after{content:"";position:absolute;inset:0;bottom:-3px;border:2px solid var(--sinch-comp-tab-color-default-outline-focus);border-top-left-radius:var(--sinch-comp-tab-shape-radius);border-top-right-radius:var(--sinch-comp-tab-shape-radius);pointer-events:none}::slotted(*){display:block;pointer-events:none}</style><sinch-tooltip id="tooltip"><div id="button"><slot name="icon"></slot></div></sinch-tooltip>';
|
|
7454
7608
|
const template$O = document.createElement("template");
|
|
7455
7609
|
template$O.innerHTML = templateHTML$O;
|
|
7456
7610
|
class TabsIconOption extends NectaryElement {
|
|
7457
|
-
#$button;
|
|
7458
7611
|
#$tooltip;
|
|
7459
7612
|
constructor() {
|
|
7460
7613
|
super();
|
|
7461
|
-
const shadowRoot = this.attachShadow(
|
|
7614
|
+
const shadowRoot = this.attachShadow();
|
|
7462
7615
|
shadowRoot.appendChild(template$O.content.cloneNode(true));
|
|
7463
|
-
this.#$button = shadowRoot.querySelector("#button");
|
|
7464
7616
|
this.#$tooltip = shadowRoot.querySelector("#tooltip");
|
|
7465
7617
|
}
|
|
7466
7618
|
connectedCallback() {
|
|
7467
7619
|
this.setAttribute("role", "tab");
|
|
7468
|
-
this
|
|
7620
|
+
this.addEventListener("click", this.#onClick);
|
|
7621
|
+
this.addEventListener("keydown", this.#onKeyDown);
|
|
7622
|
+
this.#updateTabIndex();
|
|
7469
7623
|
}
|
|
7470
7624
|
disconnectedCallback() {
|
|
7471
|
-
this
|
|
7625
|
+
this.removeEventListener("click", this.#onClick);
|
|
7626
|
+
this.removeEventListener("keydown", this.#onKeyDown);
|
|
7472
7627
|
}
|
|
7473
7628
|
static get observedAttributes() {
|
|
7474
7629
|
return ["data-checked", "disabled", "aria-label"];
|
|
@@ -7496,7 +7651,8 @@ class TabsIconOption extends NectaryElement {
|
|
|
7496
7651
|
}
|
|
7497
7652
|
case "disabled": {
|
|
7498
7653
|
const isDisabled = isAttrTrue(newVal);
|
|
7499
|
-
this
|
|
7654
|
+
this.#updateTabIndex();
|
|
7655
|
+
updateExplicitBooleanAttribute(this, "aria-disabled", isDisabled);
|
|
7500
7656
|
updateBooleanAttribute(this, name, isDisabled);
|
|
7501
7657
|
break;
|
|
7502
7658
|
}
|
|
@@ -7510,17 +7666,32 @@ class TabsIconOption extends NectaryElement {
|
|
|
7510
7666
|
return true;
|
|
7511
7667
|
}
|
|
7512
7668
|
focus() {
|
|
7513
|
-
|
|
7669
|
+
HTMLElement.prototype.focus.call(this);
|
|
7514
7670
|
}
|
|
7515
7671
|
blur() {
|
|
7516
|
-
|
|
7672
|
+
HTMLElement.prototype.blur.call(this);
|
|
7673
|
+
}
|
|
7674
|
+
#updateTabIndex() {
|
|
7675
|
+
if (this.disabled) {
|
|
7676
|
+
this.tabIndex = -1;
|
|
7677
|
+
}
|
|
7517
7678
|
}
|
|
7518
7679
|
#onClick = (e) => {
|
|
7680
|
+
if (this.disabled) {
|
|
7681
|
+
return;
|
|
7682
|
+
}
|
|
7519
7683
|
e.stopPropagation();
|
|
7520
7684
|
this.dispatchEvent(
|
|
7521
7685
|
new CustomEvent("option-change", { bubbles: true, detail: this.value })
|
|
7522
7686
|
);
|
|
7523
7687
|
};
|
|
7688
|
+
#onKeyDown = (e) => {
|
|
7689
|
+
if (this.disabled || !isActivationKey(e.key)) {
|
|
7690
|
+
return;
|
|
7691
|
+
}
|
|
7692
|
+
e.preventDefault();
|
|
7693
|
+
this.#onClick(e);
|
|
7694
|
+
};
|
|
7524
7695
|
}
|
|
7525
7696
|
defineCustomElement("sinch-tabs-icon-option", TabsIconOption);
|
|
7526
7697
|
const createDebounce = (delayFn, cancelFn) => (cb) => {
|
|
@@ -7857,13 +8028,14 @@ class EmojiPicker extends NectaryElement {
|
|
|
7857
8028
|
}
|
|
7858
8029
|
}
|
|
7859
8030
|
defineCustomElement("sinch-emoji-picker", EmojiPicker);
|
|
7860
|
-
const templateHTML$M = '<style>:host{display:block}#wrapper{display:flex;flex-direction:column;width:100%}#bottom,#top{display:flex;align-items:baseline}#top{height:24px;margin-bottom:2px}#top.empty{display:none}#additional,#invalid,#label,#optional{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}#label{font:var(--sinch-comp-field-font-label);color:var(--sinch-comp-field-color-default-label-initial)}#optional{flex:1;font:var(--sinch-comp-field-font-optional);color:var(--sinch-comp-field-color-default-optional-initial);text-align:right}#additional{flex:1;text-align:right;font:var(--sinch-comp-field-font-additional);color:var(--sinch-comp-field-color-default-additional-initial);line-height:20px;margin-top:2px}#additional:empty{display:none}#invalid{font:var(--sinch-comp-field-font-invalid);color:var(--sinch-comp-field-color-invalid-text-initial);line-height:20px;margin-top:2px}#invalid:empty{display:none}#tooltip{align-self:center;margin:0 8px;display:flex}#tooltip.empty{display:none}:host([disabled]) #label{color:var(--sinch-comp-field-color-disabled-label-initial)}:host([disabled]) #additional{color:var(--sinch-comp-field-color-disabled-additional-initial)}:host([disabled]) #optional{color:var(--sinch-comp-field-color-disabled-optional-initial)}</style><div id="wrapper"><div id="top"><label id="label" for="input"></label><div id="tooltip"><slot name="tooltip"></slot></div><span id="optional"></span></div><slot name="input"></slot><div id="bottom"><div id="invalid"></div><div id="additional"></div></div></div>';
|
|
8031
|
+
const templateHTML$M = '<style>:host{display:block}#wrapper{display:flex;flex-direction:column;width:100%}#bottom,#top{display:flex;align-items:baseline}#top{height:24px;margin-bottom:2px}#top.empty{display:none}#additional,#invalid,#label,#optional{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}#label{font:var(--sinch-comp-field-font-label);color:var(--sinch-comp-field-color-default-label-initial)}#optional{flex:1;font:var(--sinch-comp-field-font-optional);color:var(--sinch-comp-field-color-default-optional-initial);text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}#optional:empty{display:none}#additional{flex:1;text-align:right;font:var(--sinch-comp-field-font-additional);color:var(--sinch-comp-field-color-default-additional-initial);line-height:20px;margin-top:2px}#additional:empty{display:none}#invalid{font:var(--sinch-comp-field-font-invalid);color:var(--sinch-comp-field-color-invalid-text-initial);line-height:20px;margin-top:2px}#invalid:empty{display:none}#tooltip{align-self:center;margin:0 8px;display:flex}#tooltip.empty{display:none}:host([disabled]) #label{color:var(--sinch-comp-field-color-disabled-label-initial)}:host([disabled]) #additional{color:var(--sinch-comp-field-color-disabled-additional-initial)}:host([disabled]) #optional{color:var(--sinch-comp-field-color-disabled-optional-initial)}</style><div id="wrapper"><div id="top"><label id="label" for="input"></label><div id="tooltip"><slot name="tooltip"></slot></div><sinch-tooltip id="optional-tooltip" allow-scroll><span id="optional"></span></sinch-tooltip></div><slot name="input"></slot><div id="bottom"><div id="invalid"></div><div id="additional"></div></div></div>';
|
|
7861
8032
|
const template$M = document.createElement("template");
|
|
7862
8033
|
template$M.innerHTML = templateHTML$M;
|
|
7863
8034
|
class Field extends NectaryElement {
|
|
7864
8035
|
topSection;
|
|
7865
8036
|
#$label;
|
|
7866
8037
|
#$optionalText;
|
|
8038
|
+
#$optionalTooltip;
|
|
7867
8039
|
#$additionalText;
|
|
7868
8040
|
#$invalidText;
|
|
7869
8041
|
#$inputSlot;
|
|
@@ -7877,6 +8049,7 @@ class Field extends NectaryElement {
|
|
|
7877
8049
|
this.topSection = shadowRoot.querySelector("#top");
|
|
7878
8050
|
this.#$label = shadowRoot.querySelector("#label");
|
|
7879
8051
|
this.#$optionalText = shadowRoot.querySelector("#optional");
|
|
8052
|
+
this.#$optionalTooltip = shadowRoot.querySelector("#optional-tooltip");
|
|
7880
8053
|
this.#$additionalText = shadowRoot.querySelector("#additional");
|
|
7881
8054
|
this.#$invalidText = shadowRoot.querySelector("#invalid");
|
|
7882
8055
|
this.#$inputSlot = shadowRoot.querySelector('slot[name="input"]');
|
|
@@ -7900,6 +8073,7 @@ class Field extends NectaryElement {
|
|
|
7900
8073
|
return [
|
|
7901
8074
|
"label",
|
|
7902
8075
|
"optionaltext",
|
|
8076
|
+
"optionaltooltip",
|
|
7903
8077
|
"additionaltext",
|
|
7904
8078
|
"invalidtext",
|
|
7905
8079
|
"disabled"
|
|
@@ -7920,6 +8094,10 @@ class Field extends NectaryElement {
|
|
|
7920
8094
|
this.#$optionalText.textContent = newVal;
|
|
7921
8095
|
break;
|
|
7922
8096
|
}
|
|
8097
|
+
case "optionaltooltip": {
|
|
8098
|
+
updateAttribute(this.#$optionalTooltip, "text", newVal);
|
|
8099
|
+
break;
|
|
8100
|
+
}
|
|
7923
8101
|
case "additionaltext": {
|
|
7924
8102
|
this.#$additionalText.textContent = newVal;
|
|
7925
8103
|
break;
|
|
@@ -7950,6 +8128,12 @@ class Field extends NectaryElement {
|
|
|
7950
8128
|
get optionalText() {
|
|
7951
8129
|
return getAttribute(this, "optionaltext");
|
|
7952
8130
|
}
|
|
8131
|
+
set optionalTooltip(value) {
|
|
8132
|
+
updateAttribute(this, "optionaltooltip", value);
|
|
8133
|
+
}
|
|
8134
|
+
get optionalTooltip() {
|
|
8135
|
+
return getAttribute(this, "optionaltooltip");
|
|
8136
|
+
}
|
|
7953
8137
|
set additionalText(value) {
|
|
7954
8138
|
updateAttribute(this, "additionaltext", value);
|
|
7955
8139
|
}
|
|
@@ -13058,65 +13242,6 @@ class SegmentedControlOption extends NectaryElement {
|
|
|
13058
13242
|
};
|
|
13059
13243
|
}
|
|
13060
13244
|
defineCustomElement("sinch-segmented-control-option", SegmentedControlOption);
|
|
13061
|
-
function createKeyboardNavigation() {
|
|
13062
|
-
return {
|
|
13063
|
-
navigateToNextOption(enabledOptions, forward) {
|
|
13064
|
-
const optionsLength = enabledOptions.length;
|
|
13065
|
-
if (optionsLength === 0) {
|
|
13066
|
-
return;
|
|
13067
|
-
}
|
|
13068
|
-
const currentIndex = enabledOptions.findIndex((option) => option === getDeepActiveElement());
|
|
13069
|
-
let nextIndex;
|
|
13070
|
-
if (currentIndex !== -1) {
|
|
13071
|
-
if (forward) {
|
|
13072
|
-
nextIndex = (currentIndex + 1) % optionsLength;
|
|
13073
|
-
} else {
|
|
13074
|
-
nextIndex = currentIndex === 0 ? optionsLength - 1 : currentIndex - 1;
|
|
13075
|
-
}
|
|
13076
|
-
} else {
|
|
13077
|
-
nextIndex = forward ? 0 : optionsLength - 1;
|
|
13078
|
-
}
|
|
13079
|
-
this.navigateToOption(enabledOptions, nextIndex);
|
|
13080
|
-
},
|
|
13081
|
-
navigateToOption(enabledOptions, index) {
|
|
13082
|
-
const optionsLength = enabledOptions.length;
|
|
13083
|
-
if (enabledOptions.length === 0 || index < 0 || index >= optionsLength) {
|
|
13084
|
-
return;
|
|
13085
|
-
}
|
|
13086
|
-
const option = enabledOptions[index];
|
|
13087
|
-
option.focus();
|
|
13088
|
-
},
|
|
13089
|
-
handleKeyboardNavigation(e, enabledOptions) {
|
|
13090
|
-
switch (e.code) {
|
|
13091
|
-
case "Space":
|
|
13092
|
-
case "Enter": {
|
|
13093
|
-
e.preventDefault();
|
|
13094
|
-
const target = getTargetByAttribute(e, "value");
|
|
13095
|
-
if (target !== null) {
|
|
13096
|
-
target.click();
|
|
13097
|
-
}
|
|
13098
|
-
break;
|
|
13099
|
-
}
|
|
13100
|
-
case "ArrowLeft":
|
|
13101
|
-
case "ArrowRight": {
|
|
13102
|
-
e.preventDefault();
|
|
13103
|
-
this.navigateToNextOption(enabledOptions, e.code === "ArrowRight");
|
|
13104
|
-
break;
|
|
13105
|
-
}
|
|
13106
|
-
case "Home": {
|
|
13107
|
-
e.preventDefault();
|
|
13108
|
-
this.navigateToOption(enabledOptions, 0);
|
|
13109
|
-
break;
|
|
13110
|
-
}
|
|
13111
|
-
case "End": {
|
|
13112
|
-
e.preventDefault();
|
|
13113
|
-
this.navigateToOption(enabledOptions, enabledOptions.length - 1);
|
|
13114
|
-
break;
|
|
13115
|
-
}
|
|
13116
|
-
}
|
|
13117
|
-
}
|
|
13118
|
-
};
|
|
13119
|
-
}
|
|
13120
13245
|
const templateHTML$m = '<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>';
|
|
13121
13246
|
const template$m = document.createElement("template");
|
|
13122
13247
|
template$m.innerHTML = templateHTML$m;
|
|
@@ -14695,25 +14820,26 @@ class Table extends NectaryElement {
|
|
|
14695
14820
|
}
|
|
14696
14821
|
}
|
|
14697
14822
|
defineCustomElement("sinch-table", Table);
|
|
14698
|
-
const templateHTML$6 = '<style>:host{display:block}#button{all:initial;position:relative;display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:12px 16px;box-sizing:border-box;cursor:pointer;background-color:var(--sinch-comp-tab-color-default-background-initial);border-top-left-radius:var(--sinch-comp-tab-shape-radius);border-top-right-radius:var(--sinch-comp-tab-shape-radius);height:39px;--sinch-global-color-text:var(--sinch-comp-tab-color-default-text-initial);--sinch-global-color-icon:var(--sinch-comp-tab-color-default-icon-initial);--sinch-global-size-icon:var(--sinch-comp-tab-size-icon)}
|
|
14823
|
+
const templateHTML$6 = '<style>:host{display:block;outline:0}#button{all:initial;position:relative;display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:12px 16px;box-sizing:border-box;cursor:pointer;background-color:var(--sinch-comp-tab-color-default-background-initial);border-top-left-radius:var(--sinch-comp-tab-shape-radius);border-top-right-radius:var(--sinch-comp-tab-shape-radius);height:39px;--sinch-global-color-text:var(--sinch-comp-tab-color-default-text-initial);--sinch-global-color-icon:var(--sinch-comp-tab-color-default-icon-initial);--sinch-global-size-icon:var(--sinch-comp-tab-size-icon)}:host([disabled]) #button{cursor:unset;pointer-events:none;--sinch-global-color-text:var(--sinch-comp-tab-color-disabled-text-initial);--sinch-global-color-icon:var(--sinch-comp-tab-color-disabled-icon-initial)}:host([data-checked]) #button{--sinch-global-color-text:var(--sinch-comp-tab-color-checked-text-initial);--sinch-global-color-icon:var(--sinch-comp-tab-color-checked-icon-initial)}:host([data-checked]) #button::before{content:"";position:absolute;left:0;right:0;bottom:-1px;pointer-events:none;border-top:2px solid var(--sinch-comp-tab-color-checked-border-initial)}:host(:hover:not([disabled])) #button{background-color:var(--sinch-comp-tab-color-default-background-hover)}:host(:focus-visible) #button::after{content:"";position:absolute;inset:0;bottom:-3px;border:2px solid var(--sinch-comp-tab-color-default-outline-focus);border-top-left-radius:var(--sinch-comp-tab-shape-radius);border-top-right-radius:var(--sinch-comp-tab-shape-radius);pointer-events:none}#text{flex-shrink:1;flex-basis:auto;min-width:0;--sinch-comp-text-font:var(--sinch-comp-tab-font-label)}::slotted(*){display:block;pointer-events:none}</style><div id="button"><slot name="icon"></slot><sinch-text id="text" type="m" ellipsis></sinch-text></div>';
|
|
14699
14824
|
const template$6 = document.createElement("template");
|
|
14700
14825
|
template$6.innerHTML = templateHTML$6;
|
|
14701
14826
|
class TabsOption extends NectaryElement {
|
|
14702
|
-
#$button;
|
|
14703
14827
|
#$text;
|
|
14704
14828
|
constructor() {
|
|
14705
14829
|
super();
|
|
14706
|
-
const shadowRoot = this.attachShadow(
|
|
14830
|
+
const shadowRoot = this.attachShadow();
|
|
14707
14831
|
shadowRoot.appendChild(template$6.content.cloneNode(true));
|
|
14708
|
-
this.#$button = shadowRoot.querySelector("#button");
|
|
14709
14832
|
this.#$text = shadowRoot.querySelector("#text");
|
|
14710
14833
|
}
|
|
14711
14834
|
connectedCallback() {
|
|
14712
14835
|
this.setAttribute("role", "tab");
|
|
14713
14836
|
this.addEventListener("click", this.#onClick);
|
|
14837
|
+
this.addEventListener("keydown", this.#onKeyDown);
|
|
14838
|
+
this.#updateTabIndex();
|
|
14714
14839
|
}
|
|
14715
14840
|
disconnectedCallback() {
|
|
14716
14841
|
this.removeEventListener("click", this.#onClick);
|
|
14842
|
+
this.removeEventListener("keydown", this.#onKeyDown);
|
|
14717
14843
|
}
|
|
14718
14844
|
static get observedAttributes() {
|
|
14719
14845
|
return ["data-checked", "disabled", "text"];
|
|
@@ -14733,7 +14859,8 @@ class TabsOption extends NectaryElement {
|
|
|
14733
14859
|
}
|
|
14734
14860
|
case "disabled": {
|
|
14735
14861
|
const isDisabled = isAttrTrue(newVal);
|
|
14736
|
-
this
|
|
14862
|
+
this.#updateTabIndex();
|
|
14863
|
+
updateExplicitBooleanAttribute(this, "aria-disabled", isDisabled);
|
|
14737
14864
|
updateBooleanAttribute(this, name, isDisabled);
|
|
14738
14865
|
break;
|
|
14739
14866
|
}
|
|
@@ -14761,17 +14888,32 @@ class TabsOption extends NectaryElement {
|
|
|
14761
14888
|
return true;
|
|
14762
14889
|
}
|
|
14763
14890
|
focus() {
|
|
14764
|
-
|
|
14891
|
+
HTMLElement.prototype.focus.call(this);
|
|
14765
14892
|
}
|
|
14766
14893
|
blur() {
|
|
14767
|
-
|
|
14894
|
+
HTMLElement.prototype.blur.call(this);
|
|
14895
|
+
}
|
|
14896
|
+
#updateTabIndex() {
|
|
14897
|
+
if (this.disabled) {
|
|
14898
|
+
this.tabIndex = -1;
|
|
14899
|
+
}
|
|
14768
14900
|
}
|
|
14769
14901
|
#onClick = (e) => {
|
|
14902
|
+
if (this.disabled) {
|
|
14903
|
+
return;
|
|
14904
|
+
}
|
|
14770
14905
|
e.stopPropagation();
|
|
14771
14906
|
this.dispatchEvent(
|
|
14772
14907
|
new CustomEvent("option-change", { bubbles: true, detail: this.value })
|
|
14773
14908
|
);
|
|
14774
14909
|
};
|
|
14910
|
+
#onKeyDown = (e) => {
|
|
14911
|
+
if (this.disabled || !isActivationKey(e.key)) {
|
|
14912
|
+
return;
|
|
14913
|
+
}
|
|
14914
|
+
e.preventDefault();
|
|
14915
|
+
this.#onClick(e);
|
|
14916
|
+
};
|
|
14775
14917
|
}
|
|
14776
14918
|
defineCustomElement("sinch-tabs-option", TabsOption);
|
|
14777
14919
|
const templateHTML$5 = '<style>:host{display:inline-block;vertical-align:middle;outline:0}:host([ellipsis]){display:inline}#wrapper{display:flex;flex-direction:row;align-items:center;gap:4px;width:100%;height:var(--sinch-comp-tag-size-container-m);padding:0 9px;border:1px solid var(--sinch-comp-tag-border);border-radius:var(--sinch-comp-tag-shape-radius);background-color:var(--sinch-comp-tag-color-default-background);box-sizing:border-box;user-select:none;--sinch-global-color-text:var(--sinch-comp-tag-color-default-foreground);--sinch-global-color-icon:var(--sinch-comp-tag-color-default-foreground);--sinch-global-size-icon:var(--sinch-comp-tag-size-icon-m)}:host([small]) #wrapper{height:var(--sinch-comp-tag-size-container-s);padding:0 8px;--sinch-global-size-icon:var(--sinch-comp-tag-size-icon-s)}#text{flex:1;--sinch-comp-text-font:var(--sinch-comp-tag-font-size-m-label)}:host([small]) #text{--sinch-comp-text-font:var(--sinch-comp-tag-font-size-s-label)}::slotted(*){margin-left:-4px}</style><sinch-tooltip id="tooltip" type="fast"><div id="wrapper"><slot name="icon"></slot><sinch-text id="text" type="s" ellipsis></sinch-text></div></sinch-tooltip>';
|
package/field/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import '../tooltip';
|
|
1
2
|
import { NectaryElement } from '../utils';
|
|
2
3
|
export * from './types';
|
|
3
4
|
export declare class Field extends NectaryElement {
|
|
@@ -13,6 +14,8 @@ export declare class Field extends NectaryElement {
|
|
|
13
14
|
get label(): string | null;
|
|
14
15
|
set optionalText(value: string | null);
|
|
15
16
|
get optionalText(): string | null;
|
|
17
|
+
set optionalTooltip(value: string | null);
|
|
18
|
+
get optionalTooltip(): string | null;
|
|
16
19
|
set additionalText(value: string | null);
|
|
17
20
|
get additionalText(): string | null;
|
|
18
21
|
set invalidText(value: string | null);
|
package/field/index.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
|
+
import "../tooltip/index.js";
|
|
1
2
|
import { getAttribute, setClass, isAttrEqual, updateBooleanAttribute, isAttrTrue, updateAttribute, getBooleanAttribute } from "../utils/dom.js";
|
|
2
3
|
import { defineCustomElement, NectaryElement } from "../utils/element.js";
|
|
3
4
|
import { getFirstSlotElement } from "../utils/slot.js";
|
|
4
|
-
const templateHTML = '<style>:host{display:block}#wrapper{display:flex;flex-direction:column;width:100%}#bottom,#top{display:flex;align-items:baseline}#top{height:24px;margin-bottom:2px}#top.empty{display:none}#additional,#invalid,#label,#optional{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}#label{font:var(--sinch-comp-field-font-label);color:var(--sinch-comp-field-color-default-label-initial)}#optional{flex:1;font:var(--sinch-comp-field-font-optional);color:var(--sinch-comp-field-color-default-optional-initial);text-align:right}#additional{flex:1;text-align:right;font:var(--sinch-comp-field-font-additional);color:var(--sinch-comp-field-color-default-additional-initial);line-height:20px;margin-top:2px}#additional:empty{display:none}#invalid{font:var(--sinch-comp-field-font-invalid);color:var(--sinch-comp-field-color-invalid-text-initial);line-height:20px;margin-top:2px}#invalid:empty{display:none}#tooltip{align-self:center;margin:0 8px;display:flex}#tooltip.empty{display:none}:host([disabled]) #label{color:var(--sinch-comp-field-color-disabled-label-initial)}:host([disabled]) #additional{color:var(--sinch-comp-field-color-disabled-additional-initial)}:host([disabled]) #optional{color:var(--sinch-comp-field-color-disabled-optional-initial)}</style><div id="wrapper"><div id="top"><label id="label" for="input"></label><div id="tooltip"><slot name="tooltip"></slot></div><span id="optional"></span></div><slot name="input"></slot><div id="bottom"><div id="invalid"></div><div id="additional"></div></div></div>';
|
|
5
|
+
const templateHTML = '<style>:host{display:block}#wrapper{display:flex;flex-direction:column;width:100%}#bottom,#top{display:flex;align-items:baseline}#top{height:24px;margin-bottom:2px}#top.empty{display:none}#additional,#invalid,#label,#optional{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}#label{font:var(--sinch-comp-field-font-label);color:var(--sinch-comp-field-color-default-label-initial)}#optional{flex:1;font:var(--sinch-comp-field-font-optional);color:var(--sinch-comp-field-color-default-optional-initial);text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}#optional:empty{display:none}#additional{flex:1;text-align:right;font:var(--sinch-comp-field-font-additional);color:var(--sinch-comp-field-color-default-additional-initial);line-height:20px;margin-top:2px}#additional:empty{display:none}#invalid{font:var(--sinch-comp-field-font-invalid);color:var(--sinch-comp-field-color-invalid-text-initial);line-height:20px;margin-top:2px}#invalid:empty{display:none}#tooltip{align-self:center;margin:0 8px;display:flex}#tooltip.empty{display:none}:host([disabled]) #label{color:var(--sinch-comp-field-color-disabled-label-initial)}:host([disabled]) #additional{color:var(--sinch-comp-field-color-disabled-additional-initial)}:host([disabled]) #optional{color:var(--sinch-comp-field-color-disabled-optional-initial)}</style><div id="wrapper"><div id="top"><label id="label" for="input"></label><div id="tooltip"><slot name="tooltip"></slot></div><sinch-tooltip id="optional-tooltip" allow-scroll><span id="optional"></span></sinch-tooltip></div><slot name="input"></slot><div id="bottom"><div id="invalid"></div><div id="additional"></div></div></div>';
|
|
5
6
|
const template = document.createElement("template");
|
|
6
7
|
template.innerHTML = templateHTML;
|
|
7
8
|
class Field extends NectaryElement {
|
|
8
9
|
topSection;
|
|
9
10
|
#$label;
|
|
10
11
|
#$optionalText;
|
|
12
|
+
#$optionalTooltip;
|
|
11
13
|
#$additionalText;
|
|
12
14
|
#$invalidText;
|
|
13
15
|
#$inputSlot;
|
|
@@ -21,6 +23,7 @@ class Field extends NectaryElement {
|
|
|
21
23
|
this.topSection = shadowRoot.querySelector("#top");
|
|
22
24
|
this.#$label = shadowRoot.querySelector("#label");
|
|
23
25
|
this.#$optionalText = shadowRoot.querySelector("#optional");
|
|
26
|
+
this.#$optionalTooltip = shadowRoot.querySelector("#optional-tooltip");
|
|
24
27
|
this.#$additionalText = shadowRoot.querySelector("#additional");
|
|
25
28
|
this.#$invalidText = shadowRoot.querySelector("#invalid");
|
|
26
29
|
this.#$inputSlot = shadowRoot.querySelector('slot[name="input"]');
|
|
@@ -44,6 +47,7 @@ class Field extends NectaryElement {
|
|
|
44
47
|
return [
|
|
45
48
|
"label",
|
|
46
49
|
"optionaltext",
|
|
50
|
+
"optionaltooltip",
|
|
47
51
|
"additionaltext",
|
|
48
52
|
"invalidtext",
|
|
49
53
|
"disabled"
|
|
@@ -64,6 +68,10 @@ class Field extends NectaryElement {
|
|
|
64
68
|
this.#$optionalText.textContent = newVal;
|
|
65
69
|
break;
|
|
66
70
|
}
|
|
71
|
+
case "optionaltooltip": {
|
|
72
|
+
updateAttribute(this.#$optionalTooltip, "text", newVal);
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
67
75
|
case "additionaltext": {
|
|
68
76
|
this.#$additionalText.textContent = newVal;
|
|
69
77
|
break;
|
|
@@ -94,6 +102,12 @@ class Field extends NectaryElement {
|
|
|
94
102
|
get optionalText() {
|
|
95
103
|
return getAttribute(this, "optionaltext");
|
|
96
104
|
}
|
|
105
|
+
set optionalTooltip(value) {
|
|
106
|
+
updateAttribute(this, "optionaltooltip", value);
|
|
107
|
+
}
|
|
108
|
+
get optionalTooltip() {
|
|
109
|
+
return getAttribute(this, "optionaltooltip");
|
|
110
|
+
}
|
|
97
111
|
set additionalText(value) {
|
|
98
112
|
updateAttribute(this, "additionaltext", value);
|
|
99
113
|
}
|
package/field/types.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ export type TSinchFieldProps = {
|
|
|
4
4
|
label?: string;
|
|
5
5
|
/** Optional text */
|
|
6
6
|
optionalText?: string;
|
|
7
|
+
/** Tooltip text for optional text */
|
|
8
|
+
optionalTooltip?: string;
|
|
7
9
|
/** Additional text */
|
|
8
10
|
additionalText?: string;
|
|
9
11
|
/** Invalid text, controls the overall invalid state of the text field */
|
package/package.json
CHANGED
package/tabs/index.js
CHANGED
|
@@ -2,12 +2,18 @@ import { updateAttribute, getAttribute, getBooleanAttribute, updateBooleanAttrib
|
|
|
2
2
|
import { defineCustomElement, NectaryElement } from "../utils/element.js";
|
|
3
3
|
import { getRect } from "../utils/rect.js";
|
|
4
4
|
import { getReactEventHandler } from "../utils/get-react-event-handler.js";
|
|
5
|
+
import { getTargetByAttribute } from "../utils/event-target.js";
|
|
6
|
+
import { createKeyboardNavigation } from "../utils/control-keyboard-navigation.js";
|
|
5
7
|
const templateHTML = '<style>:host{display:block}#wrapper{display:flex;width:100%;height:40px;border-bottom:1px solid var(--sinch-comp-tab-color-default-border-initial);box-sizing:border-box}</style><div id="wrapper"><slot></slot></div>';
|
|
6
8
|
const template = document.createElement("template");
|
|
7
9
|
template.innerHTML = templateHTML;
|
|
8
10
|
class Tabs extends NectaryElement {
|
|
9
11
|
#$slot;
|
|
10
12
|
#controller = null;
|
|
13
|
+
#enabledOptions = [];
|
|
14
|
+
#keyboardNav = createKeyboardNavigation();
|
|
15
|
+
#observer = null;
|
|
16
|
+
#rovingValue = null;
|
|
11
17
|
constructor() {
|
|
12
18
|
super();
|
|
13
19
|
const shadowRoot = this.attachShadow();
|
|
@@ -17,14 +23,28 @@ class Tabs extends NectaryElement {
|
|
|
17
23
|
connectedCallback() {
|
|
18
24
|
this.#controller = new AbortController();
|
|
19
25
|
const { signal } = this.#controller;
|
|
26
|
+
const options = { signal };
|
|
20
27
|
this.setAttribute("role", "tablist");
|
|
21
|
-
this
|
|
22
|
-
this.#$slot.addEventListener("
|
|
23
|
-
this.addEventListener("
|
|
28
|
+
this.setAttribute("aria-orientation", "horizontal");
|
|
29
|
+
this.#$slot.addEventListener("option-change", this.#onOptionChange, options);
|
|
30
|
+
this.#$slot.addEventListener("slotchange", this.#onSlotChange, options);
|
|
31
|
+
this.#$slot.addEventListener("focusin", this.#onOptionFocusIn, options);
|
|
32
|
+
this.#$slot.addEventListener("keydown", this.#onOptionKeydown, options);
|
|
33
|
+
this.addEventListener("-change", this.#onChangeReactHandler, options);
|
|
34
|
+
this.#observer = new MutationObserver(this.#onOptionMutation);
|
|
35
|
+
this.#observer.observe(this, {
|
|
36
|
+
attributes: true,
|
|
37
|
+
attributeFilter: ["disabled", "value"],
|
|
38
|
+
childList: true,
|
|
39
|
+
subtree: true
|
|
40
|
+
});
|
|
41
|
+
this.#syncOptions();
|
|
24
42
|
}
|
|
25
43
|
disconnectedCallback() {
|
|
26
44
|
this.#controller.abort();
|
|
27
45
|
this.#controller = null;
|
|
46
|
+
this.#observer?.disconnect();
|
|
47
|
+
this.#observer = null;
|
|
28
48
|
}
|
|
29
49
|
static get observedAttributes() {
|
|
30
50
|
return ["value"];
|
|
@@ -50,19 +70,91 @@ class Tabs extends NectaryElement {
|
|
|
50
70
|
}
|
|
51
71
|
return null;
|
|
52
72
|
}
|
|
73
|
+
#getOptions() {
|
|
74
|
+
return this.#$slot.assignedElements();
|
|
75
|
+
}
|
|
76
|
+
#updateEnabledOptions(options = this.#getOptions()) {
|
|
77
|
+
this.#enabledOptions = options.filter((option) => !getBooleanAttribute(option, "disabled"));
|
|
78
|
+
}
|
|
79
|
+
#getFocusableOption() {
|
|
80
|
+
if (this.#enabledOptions.length === 0) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
if (this.#rovingValue !== null) {
|
|
84
|
+
const rovingOption = this.#enabledOptions.find(
|
|
85
|
+
(option) => getAttribute(option, "value", "") === this.#rovingValue
|
|
86
|
+
);
|
|
87
|
+
if (rovingOption != null) {
|
|
88
|
+
return rovingOption;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const selectedOption = this.#enabledOptions.find(
|
|
92
|
+
(option) => getAttribute(option, "value", "") === this.value
|
|
93
|
+
);
|
|
94
|
+
return selectedOption ?? this.#enabledOptions[0];
|
|
95
|
+
}
|
|
96
|
+
#syncOptions() {
|
|
97
|
+
const options = this.#getOptions();
|
|
98
|
+
this.#updateEnabledOptions(options);
|
|
99
|
+
const focusableOption = this.#getFocusableOption();
|
|
100
|
+
this.#rovingValue = focusableOption == null ? null : getAttribute(focusableOption, "value", "");
|
|
101
|
+
for (const $option of options) {
|
|
102
|
+
const isDisabled = getBooleanAttribute($option, "disabled");
|
|
103
|
+
const isChecked = !isDisabled && this.value === getAttribute($option, "value", "");
|
|
104
|
+
updateBooleanAttribute($option, "data-checked", isChecked);
|
|
105
|
+
$option.tabIndex = !isDisabled && $option === focusableOption ? 0 : -1;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
53
108
|
#onSlotChange = () => {
|
|
54
|
-
this.#
|
|
109
|
+
this.#syncOptions();
|
|
55
110
|
};
|
|
56
111
|
#onOptionChange = (e) => {
|
|
57
112
|
e.stopPropagation();
|
|
58
|
-
|
|
113
|
+
const value = e.detail;
|
|
114
|
+
this.#rovingValue = value;
|
|
115
|
+
this.#dispatchChangeEvent(value);
|
|
59
116
|
};
|
|
60
117
|
#onValueChange(value) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
updateBooleanAttribute($option, "data-checked", isChecked);
|
|
64
|
-
}
|
|
118
|
+
this.#rovingValue = value;
|
|
119
|
+
this.#syncOptions();
|
|
65
120
|
}
|
|
121
|
+
#onOptionFocusIn = (e) => {
|
|
122
|
+
const target = getTargetByAttribute(e, "value");
|
|
123
|
+
if (target === null || getBooleanAttribute(target, "disabled")) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
this.#rovingValue = getAttribute(target, "value", "");
|
|
127
|
+
this.#syncOptions();
|
|
128
|
+
};
|
|
129
|
+
#onOptionKeydown = (e) => {
|
|
130
|
+
switch (e.code) {
|
|
131
|
+
case "ArrowLeft": {
|
|
132
|
+
e.preventDefault();
|
|
133
|
+
this.#keyboardNav.navigateToNextOption(this.#enabledOptions, false);
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
case "ArrowRight": {
|
|
137
|
+
e.preventDefault();
|
|
138
|
+
this.#keyboardNav.navigateToNextOption(this.#enabledOptions, true);
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
case "Home": {
|
|
142
|
+
e.preventDefault();
|
|
143
|
+
this.#keyboardNav.navigateToOption(this.#enabledOptions, 0);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
case "End": {
|
|
147
|
+
e.preventDefault();
|
|
148
|
+
this.#keyboardNav.navigateToOption(this.#enabledOptions, this.#enabledOptions.length - 1);
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
#onOptionMutation = (mutations) => {
|
|
154
|
+
if (mutations.some((mutation) => mutation.target !== this)) {
|
|
155
|
+
this.#syncOptions();
|
|
156
|
+
}
|
|
157
|
+
};
|
|
66
158
|
#dispatchChangeEvent(value) {
|
|
67
159
|
this.dispatchEvent(
|
|
68
160
|
new CustomEvent("-change", { detail: value })
|
|
@@ -1,24 +1,26 @@
|
|
|
1
|
-
import { updateAttribute, getAttribute, updateBooleanAttribute, getBooleanAttribute, isAttrEqual,
|
|
1
|
+
import { updateAttribute, getAttribute, updateBooleanAttribute, getBooleanAttribute, isAttrEqual, updateExplicitBooleanAttribute, isAttrTrue } from "../utils/dom.js";
|
|
2
2
|
import { defineCustomElement, NectaryElement } from "../utils/element.js";
|
|
3
|
-
|
|
3
|
+
import { isActivationKey } from "../utils/control-keyboard-navigation.js";
|
|
4
|
+
const templateHTML = '<style>:host{display:block;outline:0}#button{all:initial;position:relative;display:flex;flex-direction:column;padding:12px 16px 0;box-sizing:border-box;cursor:pointer;background-color:var(--sinch-comp-tab-color-default-background-initial);border-top-left-radius:var(--sinch-comp-tab-shape-radius);border-top-right-radius:var(--sinch-comp-tab-shape-radius);height:39px;--sinch-global-color-icon:var(--sinch-comp-tab-color-default-icon-initial);--sinch-global-size-icon:var(--sinch-comp-tab-size-icon)}:host([disabled]) #button{cursor:unset;pointer-events:none;--sinch-global-color-icon:var(--sinch-comp-tab-color-disabled-icon-initial)}:host([data-checked]) #button{--sinch-global-color-icon:var(--sinch-comp-tab-color-checked-icon-initial)}:host([data-checked]) #button::before{content:"";position:absolute;left:0;right:0;bottom:-1px;pointer-events:none;border-top:2px solid var(--sinch-comp-tab-color-checked-border-initial)}:host(:hover:not([disabled])) #button{background-color:var(--sinch-comp-tab-color-default-background-hover)}:host(:focus-visible) #button::after{content:"";position:absolute;inset:0;bottom:-3px;border:2px solid var(--sinch-comp-tab-color-default-outline-focus);border-top-left-radius:var(--sinch-comp-tab-shape-radius);border-top-right-radius:var(--sinch-comp-tab-shape-radius);pointer-events:none}::slotted(*){display:block;pointer-events:none}</style><sinch-tooltip id="tooltip"><div id="button"><slot name="icon"></slot></div></sinch-tooltip>';
|
|
4
5
|
const template = document.createElement("template");
|
|
5
6
|
template.innerHTML = templateHTML;
|
|
6
7
|
class TabsIconOption extends NectaryElement {
|
|
7
|
-
#$button;
|
|
8
8
|
#$tooltip;
|
|
9
9
|
constructor() {
|
|
10
10
|
super();
|
|
11
|
-
const shadowRoot = this.attachShadow(
|
|
11
|
+
const shadowRoot = this.attachShadow();
|
|
12
12
|
shadowRoot.appendChild(template.content.cloneNode(true));
|
|
13
|
-
this.#$button = shadowRoot.querySelector("#button");
|
|
14
13
|
this.#$tooltip = shadowRoot.querySelector("#tooltip");
|
|
15
14
|
}
|
|
16
15
|
connectedCallback() {
|
|
17
16
|
this.setAttribute("role", "tab");
|
|
18
|
-
this
|
|
17
|
+
this.addEventListener("click", this.#onClick);
|
|
18
|
+
this.addEventListener("keydown", this.#onKeyDown);
|
|
19
|
+
this.#updateTabIndex();
|
|
19
20
|
}
|
|
20
21
|
disconnectedCallback() {
|
|
21
|
-
this
|
|
22
|
+
this.removeEventListener("click", this.#onClick);
|
|
23
|
+
this.removeEventListener("keydown", this.#onKeyDown);
|
|
22
24
|
}
|
|
23
25
|
static get observedAttributes() {
|
|
24
26
|
return ["data-checked", "disabled", "aria-label"];
|
|
@@ -46,7 +48,8 @@ class TabsIconOption extends NectaryElement {
|
|
|
46
48
|
}
|
|
47
49
|
case "disabled": {
|
|
48
50
|
const isDisabled = isAttrTrue(newVal);
|
|
49
|
-
this
|
|
51
|
+
this.#updateTabIndex();
|
|
52
|
+
updateExplicitBooleanAttribute(this, "aria-disabled", isDisabled);
|
|
50
53
|
updateBooleanAttribute(this, name, isDisabled);
|
|
51
54
|
break;
|
|
52
55
|
}
|
|
@@ -60,17 +63,32 @@ class TabsIconOption extends NectaryElement {
|
|
|
60
63
|
return true;
|
|
61
64
|
}
|
|
62
65
|
focus() {
|
|
63
|
-
|
|
66
|
+
HTMLElement.prototype.focus.call(this);
|
|
64
67
|
}
|
|
65
68
|
blur() {
|
|
66
|
-
|
|
69
|
+
HTMLElement.prototype.blur.call(this);
|
|
70
|
+
}
|
|
71
|
+
#updateTabIndex() {
|
|
72
|
+
if (this.disabled) {
|
|
73
|
+
this.tabIndex = -1;
|
|
74
|
+
}
|
|
67
75
|
}
|
|
68
76
|
#onClick = (e) => {
|
|
77
|
+
if (this.disabled) {
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
69
80
|
e.stopPropagation();
|
|
70
81
|
this.dispatchEvent(
|
|
71
82
|
new CustomEvent("option-change", { bubbles: true, detail: this.value })
|
|
72
83
|
);
|
|
73
84
|
};
|
|
85
|
+
#onKeyDown = (e) => {
|
|
86
|
+
if (this.disabled || !isActivationKey(e.key)) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
e.preventDefault();
|
|
90
|
+
this.#onClick(e);
|
|
91
|
+
};
|
|
74
92
|
}
|
|
75
93
|
defineCustomElement("sinch-tabs-icon-option", TabsIconOption);
|
|
76
94
|
export {
|
package/tabs-option/index.js
CHANGED
|
@@ -1,25 +1,27 @@
|
|
|
1
1
|
import "../text/index.js";
|
|
2
|
-
import { isAttrEqual,
|
|
2
|
+
import { isAttrEqual, updateExplicitBooleanAttribute, updateBooleanAttribute, isAttrTrue, updateAttribute, getAttribute, getBooleanAttribute } from "../utils/dom.js";
|
|
3
3
|
import { defineCustomElement, NectaryElement } from "../utils/element.js";
|
|
4
|
-
|
|
4
|
+
import { isActivationKey } from "../utils/control-keyboard-navigation.js";
|
|
5
|
+
const templateHTML = '<style>:host{display:block;outline:0}#button{all:initial;position:relative;display:flex;align-items:center;justify-content:center;gap:8px;width:100%;padding:12px 16px;box-sizing:border-box;cursor:pointer;background-color:var(--sinch-comp-tab-color-default-background-initial);border-top-left-radius:var(--sinch-comp-tab-shape-radius);border-top-right-radius:var(--sinch-comp-tab-shape-radius);height:39px;--sinch-global-color-text:var(--sinch-comp-tab-color-default-text-initial);--sinch-global-color-icon:var(--sinch-comp-tab-color-default-icon-initial);--sinch-global-size-icon:var(--sinch-comp-tab-size-icon)}:host([disabled]) #button{cursor:unset;pointer-events:none;--sinch-global-color-text:var(--sinch-comp-tab-color-disabled-text-initial);--sinch-global-color-icon:var(--sinch-comp-tab-color-disabled-icon-initial)}:host([data-checked]) #button{--sinch-global-color-text:var(--sinch-comp-tab-color-checked-text-initial);--sinch-global-color-icon:var(--sinch-comp-tab-color-checked-icon-initial)}:host([data-checked]) #button::before{content:"";position:absolute;left:0;right:0;bottom:-1px;pointer-events:none;border-top:2px solid var(--sinch-comp-tab-color-checked-border-initial)}:host(:hover:not([disabled])) #button{background-color:var(--sinch-comp-tab-color-default-background-hover)}:host(:focus-visible) #button::after{content:"";position:absolute;inset:0;bottom:-3px;border:2px solid var(--sinch-comp-tab-color-default-outline-focus);border-top-left-radius:var(--sinch-comp-tab-shape-radius);border-top-right-radius:var(--sinch-comp-tab-shape-radius);pointer-events:none}#text{flex-shrink:1;flex-basis:auto;min-width:0;--sinch-comp-text-font:var(--sinch-comp-tab-font-label)}::slotted(*){display:block;pointer-events:none}</style><div id="button"><slot name="icon"></slot><sinch-text id="text" type="m" ellipsis></sinch-text></div>';
|
|
5
6
|
const template = document.createElement("template");
|
|
6
7
|
template.innerHTML = templateHTML;
|
|
7
8
|
class TabsOption extends NectaryElement {
|
|
8
|
-
#$button;
|
|
9
9
|
#$text;
|
|
10
10
|
constructor() {
|
|
11
11
|
super();
|
|
12
|
-
const shadowRoot = this.attachShadow(
|
|
12
|
+
const shadowRoot = this.attachShadow();
|
|
13
13
|
shadowRoot.appendChild(template.content.cloneNode(true));
|
|
14
|
-
this.#$button = shadowRoot.querySelector("#button");
|
|
15
14
|
this.#$text = shadowRoot.querySelector("#text");
|
|
16
15
|
}
|
|
17
16
|
connectedCallback() {
|
|
18
17
|
this.setAttribute("role", "tab");
|
|
19
18
|
this.addEventListener("click", this.#onClick);
|
|
19
|
+
this.addEventListener("keydown", this.#onKeyDown);
|
|
20
|
+
this.#updateTabIndex();
|
|
20
21
|
}
|
|
21
22
|
disconnectedCallback() {
|
|
22
23
|
this.removeEventListener("click", this.#onClick);
|
|
24
|
+
this.removeEventListener("keydown", this.#onKeyDown);
|
|
23
25
|
}
|
|
24
26
|
static get observedAttributes() {
|
|
25
27
|
return ["data-checked", "disabled", "text"];
|
|
@@ -39,7 +41,8 @@ class TabsOption extends NectaryElement {
|
|
|
39
41
|
}
|
|
40
42
|
case "disabled": {
|
|
41
43
|
const isDisabled = isAttrTrue(newVal);
|
|
42
|
-
this
|
|
44
|
+
this.#updateTabIndex();
|
|
45
|
+
updateExplicitBooleanAttribute(this, "aria-disabled", isDisabled);
|
|
43
46
|
updateBooleanAttribute(this, name, isDisabled);
|
|
44
47
|
break;
|
|
45
48
|
}
|
|
@@ -67,17 +70,32 @@ class TabsOption extends NectaryElement {
|
|
|
67
70
|
return true;
|
|
68
71
|
}
|
|
69
72
|
focus() {
|
|
70
|
-
|
|
73
|
+
HTMLElement.prototype.focus.call(this);
|
|
71
74
|
}
|
|
72
75
|
blur() {
|
|
73
|
-
|
|
76
|
+
HTMLElement.prototype.blur.call(this);
|
|
77
|
+
}
|
|
78
|
+
#updateTabIndex() {
|
|
79
|
+
if (this.disabled) {
|
|
80
|
+
this.tabIndex = -1;
|
|
81
|
+
}
|
|
74
82
|
}
|
|
75
83
|
#onClick = (e) => {
|
|
84
|
+
if (this.disabled) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
76
87
|
e.stopPropagation();
|
|
77
88
|
this.dispatchEvent(
|
|
78
89
|
new CustomEvent("option-change", { bubbles: true, detail: this.value })
|
|
79
90
|
);
|
|
80
91
|
};
|
|
92
|
+
#onKeyDown = (e) => {
|
|
93
|
+
if (this.disabled || !isActivationKey(e.key)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
e.preventDefault();
|
|
97
|
+
this.#onClick(e);
|
|
98
|
+
};
|
|
81
99
|
}
|
|
82
100
|
defineCustomElement("sinch-tabs-option", TabsOption);
|
|
83
101
|
export {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
export declare const isActivationKey: (key: string) => key is " " | "Enter";
|
|
1
2
|
export declare function createKeyboardNavigation(): {
|
|
2
3
|
navigateToNextOption(enabledOptions: Element[], forward: boolean): void;
|
|
3
4
|
navigateToOption(enabledOptions: Element[], index: number): void;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getDeepActiveElement } from "./dom.js";
|
|
2
2
|
import { getTargetByAttribute } from "./event-target.js";
|
|
3
|
+
const isActivationKey = (key) => key === "Enter" || key === " ";
|
|
3
4
|
function createKeyboardNavigation() {
|
|
4
5
|
return {
|
|
5
6
|
navigateToNextOption(enabledOptions, forward) {
|
|
@@ -39,10 +40,14 @@ function createKeyboardNavigation() {
|
|
|
39
40
|
}
|
|
40
41
|
break;
|
|
41
42
|
}
|
|
42
|
-
case "ArrowLeft":
|
|
43
|
+
case "ArrowLeft": {
|
|
44
|
+
e.preventDefault();
|
|
45
|
+
this.navigateToNextOption(enabledOptions, false);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
43
48
|
case "ArrowRight": {
|
|
44
49
|
e.preventDefault();
|
|
45
|
-
this.navigateToNextOption(enabledOptions,
|
|
50
|
+
this.navigateToNextOption(enabledOptions, true);
|
|
46
51
|
break;
|
|
47
52
|
}
|
|
48
53
|
case "Home": {
|
|
@@ -60,5 +65,6 @@ function createKeyboardNavigation() {
|
|
|
60
65
|
};
|
|
61
66
|
}
|
|
62
67
|
export {
|
|
63
|
-
createKeyboardNavigation
|
|
68
|
+
createKeyboardNavigation,
|
|
69
|
+
isActivationKey
|
|
64
70
|
};
|
package/utils/index.d.ts
CHANGED
package/utils/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import { debounceAnimationFrame, debounceTimeout } from "./debounce.js";
|
|
|
9
9
|
import { getReactEventHandler } from "./get-react-event-handler.js";
|
|
10
10
|
import { isEmojiString, parseMarkdown } from "./markdown.js";
|
|
11
11
|
import { getTargetAttribute, getTargetByAttribute, getTargetIndexInParent, isTargetEqual } from "./event-target.js";
|
|
12
|
+
import { isActivationKey } from "./control-keyboard-navigation.js";
|
|
12
13
|
import { getUid } from "./uid.js";
|
|
13
14
|
export {
|
|
14
15
|
CSV_DELIMITER,
|
|
@@ -42,6 +43,7 @@ export {
|
|
|
42
43
|
getTransformedAncestor,
|
|
43
44
|
getUid,
|
|
44
45
|
hasClass,
|
|
46
|
+
isActivationKey,
|
|
45
47
|
isAttrEqual,
|
|
46
48
|
isAttrTrue,
|
|
47
49
|
isElementFocused,
|