@nectary/components 5.14.2 → 5.14.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
@@ -5390,8 +5390,7 @@ class Input extends NectaryElement {
5390
5390
  }
5391
5391
  connectedCallback() {
5392
5392
  super.connectedCallback();
5393
- const role = this.type === "number" ? "spinbutton" : "textbox";
5394
- this.#setRole(role);
5393
+ this.#updateInputRole();
5395
5394
  if (this.#controller === null) {
5396
5395
  this.#controller = new AbortController();
5397
5396
  }
@@ -5479,6 +5478,7 @@ class Input extends NectaryElement {
5479
5478
  if (min !== null && !isNaN(parseFloat(min))) {
5480
5479
  this.#$input.value = min;
5481
5480
  setFormValue(this.#internals, min);
5481
+ this.#updateAriaValueAttributes();
5482
5482
  this.dispatchEvent(
5483
5483
  new CustomEvent("-change", {
5484
5484
  detail: min
@@ -5494,6 +5494,7 @@ class Input extends NectaryElement {
5494
5494
  if (max !== null && !isNaN(parseFloat(max))) {
5495
5495
  this.#$input.value = max;
5496
5496
  setFormValue(this.#internals, max);
5497
+ this.#updateAriaValueAttributes();
5497
5498
  this.dispatchEvent(
5498
5499
  new CustomEvent("-change", {
5499
5500
  detail: max
@@ -5539,8 +5540,8 @@ class Input extends NectaryElement {
5539
5540
  case "type": {
5540
5541
  updateLiteralAttribute(this.#$input, inputTypes, "type", newVal);
5541
5542
  updateAttribute(this.#$input, "spellcheck", newVal === "password" ? "false" : null);
5542
- const role = newVal === "number" ? "spinbutton" : "textbox";
5543
- this.#setRole(role);
5543
+ this.#updateInputRole();
5544
+ this.#updateAriaValueAttributes();
5544
5545
  if (newVal === "number") {
5545
5546
  this.#resetAriaPlaceholder();
5546
5547
  }
@@ -5568,6 +5569,7 @@ class Input extends NectaryElement {
5568
5569
  if (nextVal !== prevVal) {
5569
5570
  this.#$input.value = nextVal;
5570
5571
  setFormValue(this.#internals, nextVal);
5572
+ this.#updateAriaValueAttributes();
5571
5573
  if (isElementFocused(this.#$input)) {
5572
5574
  this.#setSelectionRange(this.#selectionEnd, this.#selectionEnd);
5573
5575
  }
@@ -5587,9 +5589,9 @@ class Input extends NectaryElement {
5587
5589
  return;
5588
5590
  }
5589
5591
  const isInvalid = isAttrTrue(newVal);
5590
- this.ariaInvalid = isInvalid.toString();
5591
- this.#$input.ariaInvalid = this.ariaInvalid;
5592
- this.#internals.ariaInvalid = this.ariaInvalid;
5592
+ const ariaInvalidValue = isInvalid.toString();
5593
+ this.#$input.ariaInvalid = ariaInvalidValue;
5594
+ this.#internals.ariaInvalid = ariaInvalidValue;
5593
5595
  updateBooleanAttribute(this, name, isInvalid);
5594
5596
  break;
5595
5597
  }
@@ -5627,11 +5629,11 @@ class Input extends NectaryElement {
5627
5629
  case "min":
5628
5630
  case "step": {
5629
5631
  updateAttribute(this.#$input, name, newVal);
5632
+ this.#updateAriaValueAttributes();
5630
5633
  break;
5631
5634
  }
5632
5635
  case "aria-label": {
5633
5636
  this.#$input.ariaLabel = newVal;
5634
- this.#internals.ariaLabel = newVal;
5635
5637
  break;
5636
5638
  }
5637
5639
  }
@@ -6028,8 +6030,7 @@ class Input extends NectaryElement {
6028
6030
  const value = this.placeholder;
6029
6031
  this.#$input.placeholder = value ?? "";
6030
6032
  if (this.type !== "number") {
6031
- this.#internals.ariaPlaceholder = value ?? "";
6032
- updateAttribute(this, "aria-placeholder", value);
6033
+ this.#$input.ariaPlaceholder = value ?? "";
6033
6034
  }
6034
6035
  } else {
6035
6036
  this.#$input.placeholder = "";
@@ -6037,8 +6038,7 @@ class Input extends NectaryElement {
6037
6038
  }
6038
6039
  }
6039
6040
  #resetAriaPlaceholder() {
6040
- updateAttribute(this, "aria-placeholder", null);
6041
- this.#internals.ariaPlaceholder = "";
6041
+ this.#$input.ariaPlaceholder = "";
6042
6042
  }
6043
6043
  #onIconSlotChange = () => {
6044
6044
  const isEmpty = this.#$iconSlot.assignedElements().length === 0;
@@ -6091,10 +6091,40 @@ class Input extends NectaryElement {
6091
6091
  #onWheelReactHandler = (e) => {
6092
6092
  getReactEventHandler(this, "on-wheel")?.(e);
6093
6093
  };
6094
- #setRole = (role) => {
6095
- this.setAttribute("role", role);
6096
- this.#internals.role = role;
6097
- };
6094
+ #updateInputRole() {
6095
+ if (this.type === "number") {
6096
+ this.#$input.setAttribute("role", "spinbutton");
6097
+ this.#updateAriaValueAttributes();
6098
+ } else {
6099
+ this.#$input.removeAttribute("role");
6100
+ this.#$input.removeAttribute("aria-valuenow");
6101
+ this.#$input.removeAttribute("aria-valuemin");
6102
+ this.#$input.removeAttribute("aria-valuemax");
6103
+ }
6104
+ }
6105
+ #updateAriaValueAttributes() {
6106
+ if (this.type !== "number") {
6107
+ return;
6108
+ }
6109
+ const value = this.#$input.value;
6110
+ const min = getAttribute(this, "min");
6111
+ const max = getAttribute(this, "max");
6112
+ if (value !== "") {
6113
+ this.#$input.setAttribute("aria-valuenow", value);
6114
+ } else {
6115
+ this.#$input.removeAttribute("aria-valuenow");
6116
+ }
6117
+ if (min !== null && !isNaN(parseFloat(min))) {
6118
+ this.#$input.setAttribute("aria-valuemin", min);
6119
+ } else {
6120
+ this.#$input.removeAttribute("aria-valuemin");
6121
+ }
6122
+ if (max !== null && !isNaN(parseFloat(max))) {
6123
+ this.#$input.setAttribute("aria-valuemax", max);
6124
+ } else {
6125
+ this.#$input.removeAttribute("aria-valuemax");
6126
+ }
6127
+ }
6098
6128
  }
6099
6129
  defineCustomElement("sinch-input", Input);
6100
6130
  const orientationValues = [
@@ -12847,7 +12877,7 @@ const getShortestCssDeg = (currentDeg, nextDeg) => {
12847
12877
  }
12848
12878
  return currentDeg + diff;
12849
12879
  };
12850
- const templateHTML$3 = '<style>:host{display:block;outline:0}#wrapper{display:flex;flex-direction:column;width:248px;padding:16px;box-sizing:border-box;gap:16px}#header{position:relative;width:100%;height:48px;font:var(--sinch-comp-time-picker-header-font);line-height:48px;user-select:none;color:var(--sinch-comp-time-picker-header-color-default-text-initial)}#footer{display:flex;justify-content:center;width:100%;height:32px}#picker{position:relative;width:216px;height:216px;border-radius:50%;box-sizing:border-box;border:1px solid var(--sinch-comp-time-picker-watch-face-color-default-border-initial);background-color:var(--sinch-comp-time-picker-watch-face-color-default-background-initial)}#picker-hours,#picker-minutes{position:absolute;left:0;top:0;width:100%;height:100%;border-radius:50%;pointer-events:none;user-select:none}.digit-hour-12,.digit-hour-24,.digit-minute{position:absolute;width:28px;height:28px;text-align:center;top:calc(50% - 14px);left:calc(50% - 14px);z-index:1;cursor:pointer}.digit-hour-12{font:var(--sinch-comp-time-picker-digit-font-default-h12);color:var(--sinch-comp-time-picker-digit-color-default-h12-initial);line-height:28px}.digit-hour-12.selected{font:var(--sinch-comp-time-picker-digit-font-checked-h12);color:var(--sinch-comp-time-picker-digit-color-checked-h12-default)}.digit-hour-24{font:var(--sinch-comp-time-picker-digit-font-default-h24);color:var(--sinch-comp-time-picker-digit-color-default-h24-initial);line-height:28px}.digit-hour-24.selected{font:var(--sinch-comp-time-picker-digit-font-checked-h24);color:var(--sinch-comp-time-picker-digit-color-checked-h24-initial)}.digit-minute{font:var(--sinch-comp-time-picker-digit-font-default-minutes);color:var(--sinch-comp-time-picker-digit-color-default-minute-initial);line-height:28px}.digit-minute.selected{font:var(--sinch-comp-time-picker-digit-font-checked-minutes);color:var(--sinch-comp-time-picker-digit-color-checked-minute-initial)}#picker-touch{position:absolute;left:0;top:0;width:100%;height:100%;cursor:pointer;border-radius:50%}#needle-hour,#needle-minute,#picker-touch::after{background-color:var(--sinch-comp-time-picker-needle-color-default-background-initial)}#needle-hour,#needle-minute{position:absolute;transform-origin:bottom center;transform:rotate(0);bottom:50%;height:50px;transition-duration:.25s;transition-timing-function:ease-in-out;transition-property:transform height;z-index:2;outline:0}@media (prefers-reduced-motion){#needle-hour,#needle-minute{transition:none}}#needle-hour{width:4px;left:calc(50% - 2px);border-radius:2px}#needle-minute{width:2px;left:calc(50% - 1px);border-radius:1px}#needle-hour:focus-visible,#needle-minute:focus-visible{background-color:var(--sinch-comp-time-picker-needle-color-default-background-focus)}#needle-minute:not(.selected)::after{content:"";position:absolute;transform:translateX(-50%);left:0;top:-16px;width:4px;height:4px;border-radius:50%;background-color:var(--sinch-comp-time-picker-digit-color-checked-minute-initial)}#picker-touch::after{content:"";position:absolute;top:50%;left:50%;width:12px;height:12px;border-radius:50%;transform:translate(-50%,-50%)}#header-hours,#header-minutes{position:absolute;padding:0 4px;width:50px;outline:0}#header-hours{right:calc(50% + 8px);text-align:right}#header-minutes{left:calc(50% + 8px)}#header-colon{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%)}#submit{position:absolute;right:0;top:50%;transform:translateY(-50%)}#submit-icon{--sinch-global-color-icon:var(--sinch-comp-time-picker-header-color-default-icon-initial)}:host([ampm]) .digit-hour-24{display:none}:host(:not([ampm])) #footer{display:none}</style><div id="wrapper"><div id="header"><div id="header-hours" role="meter" aria-valuemin="0" aria-valuemax="23" aria-valuenow="0" aria-valuetext="0"><span>00</span></div><div id="header-colon">&colon;</div><div id="header-minutes" role="meter" aria-valuemin="0" aria-valuemax="59" aria-valuenow="0" aria-valuetext="0"><span>00</span></div><sinch-button id="submit" size="s" aria-label="Submit"><sinch-icon icons-version="2" name="fa-check" id="icon-submit" slot="icon"></sinch-icon></sinch-button></div><div id="picker" aria-hidden="true"><div id="picker-hours"></div><div id="picker-minutes"></div><div id="picker-touch"><div id="needle-hour" tabindex="0"></div><div id="needle-minute" tabindex="0"></div></div></div><div id="footer"><sinch-segmented-control id="ampm"><sinch-segmented-control-option value="am" text="AM" aria-label="AM"></sinch-segmented-control-option><sinch-segmented-control-option value="pm" text="PM" aria-label="PM"></sinch-segmented-control-option></sinch-segmented-control></div></div>';
12880
+ const templateHTML$3 = '<style>:host{display:block;outline:0}#wrapper{display:flex;flex-direction:column;width:248px;padding:16px;box-sizing:border-box;gap:16px}#header{position:relative;width:100%;height:48px;font:var(--sinch-comp-time-picker-header-font);line-height:48px;user-select:none;color:var(--sinch-comp-time-picker-header-color-default-text-initial)}#footer{display:flex;justify-content:center;width:100%;height:32px}#picker{position:relative;width:216px;height:216px;border-radius:50%;box-sizing:border-box;border:1px solid var(--sinch-comp-time-picker-watch-face-color-default-border-initial);background-color:var(--sinch-comp-time-picker-watch-face-color-default-background-initial)}#picker-hours,#picker-minutes{position:absolute;left:0;top:0;width:100%;height:100%;border-radius:50%;pointer-events:none;user-select:none}.digit-hour-12,.digit-hour-24,.digit-minute{position:absolute;width:28px;height:28px;text-align:center;top:calc(50% - 14px);left:calc(50% - 14px);z-index:1;cursor:pointer}.digit-hour-12{font:var(--sinch-comp-time-picker-digit-font-default-h12);color:var(--sinch-comp-time-picker-digit-color-default-h12-initial);line-height:28px}.digit-hour-12.selected{font:var(--sinch-comp-time-picker-digit-font-checked-h12);color:var(--sinch-comp-time-picker-digit-color-checked-h12-default)}.digit-hour-24{font:var(--sinch-comp-time-picker-digit-font-default-h24);color:var(--sinch-comp-time-picker-digit-color-default-h24-initial);line-height:28px}.digit-hour-24.selected{font:var(--sinch-comp-time-picker-digit-font-checked-h24);color:var(--sinch-comp-time-picker-digit-color-checked-h24-initial)}.digit-minute{font:var(--sinch-comp-time-picker-digit-font-default-minutes);color:var(--sinch-comp-time-picker-digit-color-default-minute-initial);line-height:28px}.digit-minute.selected{font:var(--sinch-comp-time-picker-digit-font-checked-minutes);color:var(--sinch-comp-time-picker-digit-color-checked-minute-initial)}#picker-touch{position:absolute;left:0;top:0;width:100%;height:100%;cursor:pointer;border-radius:50%}#needle-hour,#needle-minute,#picker-touch::after{background-color:var(--sinch-comp-time-picker-needle-color-default-background-initial)}#needle-hour,#needle-minute{position:absolute;transform-origin:bottom center;transform:rotate(0);bottom:50%;height:50px;transition-duration:.25s;transition-timing-function:ease-in-out;transition-property:transform height;z-index:2;outline:0}@media (prefers-reduced-motion){#needle-hour,#needle-minute{transition:none}}#needle-hour{width:4px;left:calc(50% - 2px);border-radius:2px}#needle-minute{width:2px;left:calc(50% - 1px);border-radius:1px}#needle-hour:focus-visible,#needle-minute:focus-visible{background-color:var(--sinch-comp-time-picker-needle-color-default-background-focus)}#needle-minute:not(.selected)::after{content:"";position:absolute;transform:translateX(-50%);left:0;top:-16px;width:4px;height:4px;border-radius:50%;background-color:var(--sinch-comp-time-picker-digit-color-checked-minute-initial)}#picker-touch::after{content:"";position:absolute;top:50%;left:50%;width:12px;height:12px;border-radius:50%;transform:translate(-50%,-50%)}#header-hours,#header-minutes{position:absolute;padding:0 4px;width:50px;outline:0}#header-hours{right:calc(50% + 8px);text-align:right}#header-minutes{left:calc(50% + 8px)}#header-colon{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%)}#submit{position:absolute;right:0;top:50%;transform:translateY(-50%)}#submit-icon{--sinch-global-color-icon:var(--sinch-comp-time-picker-header-color-default-icon-initial)}:host([ampm]) .digit-hour-24{display:none}:host(:not([ampm])) #footer{display:none}</style><div id="wrapper"><div id="header"><div id="header-hours" role="meter" aria-valuemin="0" aria-valuemax="23" aria-valuenow="0" aria-valuetext="0"><span>00</span></div><div id="header-colon">&colon;</div><div id="header-minutes" role="meter" aria-valuemin="0" aria-valuemax="59" aria-valuenow="0" aria-valuetext="0"><span>00</span></div><sinch-button id="submit" size="s" aria-label="Submit"><sinch-icon icons-version="2" name="fa-check" id="icon-submit" slot="icon"></sinch-icon></sinch-button></div><div id="picker" role="group" aria-label="Time picker clock face"><div id="picker-hours"></div><div id="picker-minutes"></div><div id="picker-touch"><div id="needle-hour" tabindex="0" role="slider" aria-label="Hour selector" aria-valuemin="0" aria-valuemax="23" aria-valuenow="0"></div><div id="needle-minute" tabindex="0" role="slider" aria-label="Minute selector" aria-valuemin="0" aria-valuemax="59" aria-valuenow="0"></div></div></div><div id="footer"><sinch-segmented-control id="ampm"><sinch-segmented-control-option value="am" text="AM" aria-label="AM"></sinch-segmented-control-option><sinch-segmented-control-option value="pm" text="PM" aria-label="PM"></sinch-segmented-control-option></sinch-segmented-control></div></div>';
12851
12881
  const template$3 = document.createElement("template");
12852
12882
  template$3.innerHTML = templateHTML$3;
12853
12883
  const PICKER_RADIUS = 216 / 2;
@@ -12905,6 +12935,11 @@ class TimePicker extends NectaryElement {
12905
12935
  el.className = "digit-hour-12";
12906
12936
  el.style.transform = `translate(${x}px, ${y}px)`;
12907
12937
  el.textContent = hourDisplayValue;
12938
+ el.setAttribute("role", "button");
12939
+ el.setAttribute("tabindex", "-1");
12940
+ el.setAttribute("aria-label", `${hourDisplayValue} o'clock`);
12941
+ el.addEventListener("click", () => this.#onHourDigitClick(i));
12942
+ el.addEventListener("keydown", (e) => this.#onDigitKeydown(e, () => this.#onHourDigitClick(i)));
12908
12943
  hours12Frag.appendChild(el);
12909
12944
  }
12910
12945
  this.#$pickerHours.appendChild(hours12Frag);
@@ -12918,6 +12953,11 @@ class TimePicker extends NectaryElement {
12918
12953
  el.className = "digit-hour-24";
12919
12954
  el.style.transform = `translate(${x}px, ${y}px)`;
12920
12955
  el.textContent = hourDisplayValue;
12956
+ el.setAttribute("role", "button");
12957
+ el.setAttribute("tabindex", "-1");
12958
+ el.setAttribute("aria-label", `${hourDisplayValue} o'clock`);
12959
+ el.addEventListener("click", () => this.#onHourDigitClick(i));
12960
+ el.addEventListener("keydown", (e) => this.#onDigitKeydown(e, () => this.#onHourDigitClick(i)));
12921
12961
  hours24Frag.appendChild(el);
12922
12962
  }
12923
12963
  this.#$pickerHours.appendChild(hours24Frag);
@@ -12930,6 +12970,11 @@ class TimePicker extends NectaryElement {
12930
12970
  el.className = "digit-minute";
12931
12971
  el.style.transform = `translate(${x}px, ${y}px)`;
12932
12972
  el.textContent = stringifyMinute(i);
12973
+ el.setAttribute("role", "button");
12974
+ el.setAttribute("tabindex", "-1");
12975
+ el.setAttribute("aria-label", `${i} minutes`);
12976
+ el.addEventListener("click", () => this.#onMinuteDigitClick(i));
12977
+ el.addEventListener("keydown", (e) => this.#onDigitKeydown(e, () => this.#onMinuteDigitClick(i)));
12933
12978
  minutesFrag.appendChild(el);
12934
12979
  }
12935
12980
  this.#$pickerMinutes.appendChild(minutesFrag);
@@ -12966,8 +13011,11 @@ class TimePicker extends NectaryElement {
12966
13011
  break;
12967
13012
  }
12968
13013
  case "ampm": {
13014
+ const isAMPM = isAttrTrue(newVal);
13015
+ updateBooleanAttribute(this, "ampm", isAMPM);
13016
+ const hourMax = isAMPM ? 12 : 23;
13017
+ updateAttribute(this.#$needleHour, "aria-valuemax", hourMax);
12969
13018
  this.#render();
12970
- updateBooleanAttribute(this, "ampm", isAttrTrue(newVal));
12971
13019
  break;
12972
13020
  }
12973
13021
  case "submit-aria-label": {
@@ -13071,6 +13119,10 @@ class TimePicker extends NectaryElement {
13071
13119
  updateAttribute(this.#$headerHours, "aria-valuetext", this.#hour);
13072
13120
  updateAttribute(this.#$headerMinutes, "aria-valuenow", this.#minute);
13073
13121
  updateAttribute(this.#$headerMinutes, "aria-valuetext", this.#minute);
13122
+ updateAttribute(this.#$needleHour, "aria-valuenow", this.#hour);
13123
+ updateAttribute(this.#$needleHour, "aria-valuetext", `${this.#hour} o'clock`);
13124
+ updateAttribute(this.#$needleMinute, "aria-valuenow", this.#minute);
13125
+ updateAttribute(this.#$needleMinute, "aria-valuetext", `${this.#minute} minutes`);
13074
13126
  }
13075
13127
  #selectHour(is24) {
13076
13128
  const $hours = this.#$pickerHours.children;
@@ -13168,6 +13220,20 @@ class TimePicker extends NectaryElement {
13168
13220
  getReactEventHandler(this, "on-change")?.(e);
13169
13221
  getReactEventHandler(this, "onChange")?.(e);
13170
13222
  };
13223
+ #onHourDigitClick = (hour) => {
13224
+ this.#hour = hour;
13225
+ this.#render();
13226
+ };
13227
+ #onMinuteDigitClick = (minute) => {
13228
+ this.#minute = minute;
13229
+ this.#render();
13230
+ };
13231
+ #onDigitKeydown = (e, callback) => {
13232
+ if (e.key === "Enter" || e.key === " ") {
13233
+ e.preventDefault();
13234
+ callback();
13235
+ }
13236
+ };
13171
13237
  }
13172
13238
  defineCustomElement("sinch-time-picker", TimePicker);
13173
13239
  const typeValues = ["info", "success", "warn", "error"];
package/input/index.js CHANGED
@@ -48,8 +48,7 @@ class Input extends NectaryElement {
48
48
  }
49
49
  connectedCallback() {
50
50
  super.connectedCallback();
51
- const role = this.type === "number" ? "spinbutton" : "textbox";
52
- this.#setRole(role);
51
+ this.#updateInputRole();
53
52
  if (this.#controller === null) {
54
53
  this.#controller = new AbortController();
55
54
  }
@@ -137,6 +136,7 @@ class Input extends NectaryElement {
137
136
  if (min !== null && !isNaN(parseFloat(min))) {
138
137
  this.#$input.value = min;
139
138
  setFormValue(this.#internals, min);
139
+ this.#updateAriaValueAttributes();
140
140
  this.dispatchEvent(
141
141
  new CustomEvent("-change", {
142
142
  detail: min
@@ -152,6 +152,7 @@ class Input extends NectaryElement {
152
152
  if (max !== null && !isNaN(parseFloat(max))) {
153
153
  this.#$input.value = max;
154
154
  setFormValue(this.#internals, max);
155
+ this.#updateAriaValueAttributes();
155
156
  this.dispatchEvent(
156
157
  new CustomEvent("-change", {
157
158
  detail: max
@@ -197,8 +198,8 @@ class Input extends NectaryElement {
197
198
  case "type": {
198
199
  updateLiteralAttribute(this.#$input, inputTypes, "type", newVal);
199
200
  updateAttribute(this.#$input, "spellcheck", newVal === "password" ? "false" : null);
200
- const role = newVal === "number" ? "spinbutton" : "textbox";
201
- this.#setRole(role);
201
+ this.#updateInputRole();
202
+ this.#updateAriaValueAttributes();
202
203
  if (newVal === "number") {
203
204
  this.#resetAriaPlaceholder();
204
205
  }
@@ -226,6 +227,7 @@ class Input extends NectaryElement {
226
227
  if (nextVal !== prevVal) {
227
228
  this.#$input.value = nextVal;
228
229
  setFormValue(this.#internals, nextVal);
230
+ this.#updateAriaValueAttributes();
229
231
  if (isElementFocused(this.#$input)) {
230
232
  this.#setSelectionRange(this.#selectionEnd, this.#selectionEnd);
231
233
  }
@@ -245,9 +247,9 @@ class Input extends NectaryElement {
245
247
  return;
246
248
  }
247
249
  const isInvalid = isAttrTrue(newVal);
248
- this.ariaInvalid = isInvalid.toString();
249
- this.#$input.ariaInvalid = this.ariaInvalid;
250
- this.#internals.ariaInvalid = this.ariaInvalid;
250
+ const ariaInvalidValue = isInvalid.toString();
251
+ this.#$input.ariaInvalid = ariaInvalidValue;
252
+ this.#internals.ariaInvalid = ariaInvalidValue;
251
253
  updateBooleanAttribute(this, name, isInvalid);
252
254
  break;
253
255
  }
@@ -285,11 +287,11 @@ class Input extends NectaryElement {
285
287
  case "min":
286
288
  case "step": {
287
289
  updateAttribute(this.#$input, name, newVal);
290
+ this.#updateAriaValueAttributes();
288
291
  break;
289
292
  }
290
293
  case "aria-label": {
291
294
  this.#$input.ariaLabel = newVal;
292
- this.#internals.ariaLabel = newVal;
293
295
  break;
294
296
  }
295
297
  }
@@ -686,8 +688,7 @@ class Input extends NectaryElement {
686
688
  const value = this.placeholder;
687
689
  this.#$input.placeholder = value ?? "";
688
690
  if (this.type !== "number") {
689
- this.#internals.ariaPlaceholder = value ?? "";
690
- updateAttribute(this, "aria-placeholder", value);
691
+ this.#$input.ariaPlaceholder = value ?? "";
691
692
  }
692
693
  } else {
693
694
  this.#$input.placeholder = "";
@@ -695,8 +696,7 @@ class Input extends NectaryElement {
695
696
  }
696
697
  }
697
698
  #resetAriaPlaceholder() {
698
- updateAttribute(this, "aria-placeholder", null);
699
- this.#internals.ariaPlaceholder = "";
699
+ this.#$input.ariaPlaceholder = "";
700
700
  }
701
701
  #onIconSlotChange = () => {
702
702
  const isEmpty = this.#$iconSlot.assignedElements().length === 0;
@@ -749,10 +749,40 @@ class Input extends NectaryElement {
749
749
  #onWheelReactHandler = (e) => {
750
750
  getReactEventHandler(this, "on-wheel")?.(e);
751
751
  };
752
- #setRole = (role) => {
753
- this.setAttribute("role", role);
754
- this.#internals.role = role;
755
- };
752
+ #updateInputRole() {
753
+ if (this.type === "number") {
754
+ this.#$input.setAttribute("role", "spinbutton");
755
+ this.#updateAriaValueAttributes();
756
+ } else {
757
+ this.#$input.removeAttribute("role");
758
+ this.#$input.removeAttribute("aria-valuenow");
759
+ this.#$input.removeAttribute("aria-valuemin");
760
+ this.#$input.removeAttribute("aria-valuemax");
761
+ }
762
+ }
763
+ #updateAriaValueAttributes() {
764
+ if (this.type !== "number") {
765
+ return;
766
+ }
767
+ const value = this.#$input.value;
768
+ const min = getAttribute(this, "min");
769
+ const max = getAttribute(this, "max");
770
+ if (value !== "") {
771
+ this.#$input.setAttribute("aria-valuenow", value);
772
+ } else {
773
+ this.#$input.removeAttribute("aria-valuenow");
774
+ }
775
+ if (min !== null && !isNaN(parseFloat(min))) {
776
+ this.#$input.setAttribute("aria-valuemin", min);
777
+ } else {
778
+ this.#$input.removeAttribute("aria-valuemin");
779
+ }
780
+ if (max !== null && !isNaN(parseFloat(max))) {
781
+ this.#$input.setAttribute("aria-valuemax", max);
782
+ } else {
783
+ this.#$input.removeAttribute("aria-valuemax");
784
+ }
785
+ }
756
786
  }
757
787
  defineCustomElement("sinch-input", Input);
758
788
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nectary/components",
3
- "version": "5.14.2",
3
+ "version": "5.14.4",
4
4
  "files": [
5
5
  "**/*/*.css",
6
6
  "**/*/*.json",
@@ -24,7 +24,7 @@
24
24
  },
25
25
  "dependencies": {
26
26
  "@babel/runtime": "^7.22.15",
27
- "@nectary/assets": "3.3.4"
27
+ "@nectary/assets": "3.4.0"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@babel/cli": "^7.22.15",
@@ -1,12 +1,12 @@
1
1
  import "../icon/index.js";
2
2
  import "../segmented-control/index.js";
3
3
  import "../segmented-control-option/index.js";
4
- import { isAttrEqual, updateAttribute, updateBooleanAttribute, isAttrTrue, getAttribute, getBooleanAttribute, setClass } from "../utils/dom.js";
4
+ import { isAttrEqual, updateAttribute, updateBooleanAttribute, getAttribute, getBooleanAttribute, setClass, isAttrTrue } from "../utils/dom.js";
5
5
  import { defineCustomElement, NectaryElement } from "../utils/element.js";
6
6
  import { getRect } from "../utils/rect.js";
7
7
  import { getReactEventHandler } from "../utils/get-react-event-handler.js";
8
8
  import { stringifyHourFace, stringifyMinute, parseTime, hourToIndex, stringifyHour, getNeedleRotationDeg, getShortestCssDeg, stringifyTime } from "./utils.js";
9
- const templateHTML = '<style>:host{display:block;outline:0}#wrapper{display:flex;flex-direction:column;width:248px;padding:16px;box-sizing:border-box;gap:16px}#header{position:relative;width:100%;height:48px;font:var(--sinch-comp-time-picker-header-font);line-height:48px;user-select:none;color:var(--sinch-comp-time-picker-header-color-default-text-initial)}#footer{display:flex;justify-content:center;width:100%;height:32px}#picker{position:relative;width:216px;height:216px;border-radius:50%;box-sizing:border-box;border:1px solid var(--sinch-comp-time-picker-watch-face-color-default-border-initial);background-color:var(--sinch-comp-time-picker-watch-face-color-default-background-initial)}#picker-hours,#picker-minutes{position:absolute;left:0;top:0;width:100%;height:100%;border-radius:50%;pointer-events:none;user-select:none}.digit-hour-12,.digit-hour-24,.digit-minute{position:absolute;width:28px;height:28px;text-align:center;top:calc(50% - 14px);left:calc(50% - 14px);z-index:1;cursor:pointer}.digit-hour-12{font:var(--sinch-comp-time-picker-digit-font-default-h12);color:var(--sinch-comp-time-picker-digit-color-default-h12-initial);line-height:28px}.digit-hour-12.selected{font:var(--sinch-comp-time-picker-digit-font-checked-h12);color:var(--sinch-comp-time-picker-digit-color-checked-h12-default)}.digit-hour-24{font:var(--sinch-comp-time-picker-digit-font-default-h24);color:var(--sinch-comp-time-picker-digit-color-default-h24-initial);line-height:28px}.digit-hour-24.selected{font:var(--sinch-comp-time-picker-digit-font-checked-h24);color:var(--sinch-comp-time-picker-digit-color-checked-h24-initial)}.digit-minute{font:var(--sinch-comp-time-picker-digit-font-default-minutes);color:var(--sinch-comp-time-picker-digit-color-default-minute-initial);line-height:28px}.digit-minute.selected{font:var(--sinch-comp-time-picker-digit-font-checked-minutes);color:var(--sinch-comp-time-picker-digit-color-checked-minute-initial)}#picker-touch{position:absolute;left:0;top:0;width:100%;height:100%;cursor:pointer;border-radius:50%}#needle-hour,#needle-minute,#picker-touch::after{background-color:var(--sinch-comp-time-picker-needle-color-default-background-initial)}#needle-hour,#needle-minute{position:absolute;transform-origin:bottom center;transform:rotate(0);bottom:50%;height:50px;transition-duration:.25s;transition-timing-function:ease-in-out;transition-property:transform height;z-index:2;outline:0}@media (prefers-reduced-motion){#needle-hour,#needle-minute{transition:none}}#needle-hour{width:4px;left:calc(50% - 2px);border-radius:2px}#needle-minute{width:2px;left:calc(50% - 1px);border-radius:1px}#needle-hour:focus-visible,#needle-minute:focus-visible{background-color:var(--sinch-comp-time-picker-needle-color-default-background-focus)}#needle-minute:not(.selected)::after{content:"";position:absolute;transform:translateX(-50%);left:0;top:-16px;width:4px;height:4px;border-radius:50%;background-color:var(--sinch-comp-time-picker-digit-color-checked-minute-initial)}#picker-touch::after{content:"";position:absolute;top:50%;left:50%;width:12px;height:12px;border-radius:50%;transform:translate(-50%,-50%)}#header-hours,#header-minutes{position:absolute;padding:0 4px;width:50px;outline:0}#header-hours{right:calc(50% + 8px);text-align:right}#header-minutes{left:calc(50% + 8px)}#header-colon{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%)}#submit{position:absolute;right:0;top:50%;transform:translateY(-50%)}#submit-icon{--sinch-global-color-icon:var(--sinch-comp-time-picker-header-color-default-icon-initial)}:host([ampm]) .digit-hour-24{display:none}:host(:not([ampm])) #footer{display:none}</style><div id="wrapper"><div id="header"><div id="header-hours" role="meter" aria-valuemin="0" aria-valuemax="23" aria-valuenow="0" aria-valuetext="0"><span>00</span></div><div id="header-colon">&colon;</div><div id="header-minutes" role="meter" aria-valuemin="0" aria-valuemax="59" aria-valuenow="0" aria-valuetext="0"><span>00</span></div><sinch-button id="submit" size="s" aria-label="Submit"><sinch-icon icons-version="2" name="fa-check" id="icon-submit" slot="icon"></sinch-icon></sinch-button></div><div id="picker" aria-hidden="true"><div id="picker-hours"></div><div id="picker-minutes"></div><div id="picker-touch"><div id="needle-hour" tabindex="0"></div><div id="needle-minute" tabindex="0"></div></div></div><div id="footer"><sinch-segmented-control id="ampm"><sinch-segmented-control-option value="am" text="AM" aria-label="AM"></sinch-segmented-control-option><sinch-segmented-control-option value="pm" text="PM" aria-label="PM"></sinch-segmented-control-option></sinch-segmented-control></div></div>';
9
+ const templateHTML = '<style>:host{display:block;outline:0}#wrapper{display:flex;flex-direction:column;width:248px;padding:16px;box-sizing:border-box;gap:16px}#header{position:relative;width:100%;height:48px;font:var(--sinch-comp-time-picker-header-font);line-height:48px;user-select:none;color:var(--sinch-comp-time-picker-header-color-default-text-initial)}#footer{display:flex;justify-content:center;width:100%;height:32px}#picker{position:relative;width:216px;height:216px;border-radius:50%;box-sizing:border-box;border:1px solid var(--sinch-comp-time-picker-watch-face-color-default-border-initial);background-color:var(--sinch-comp-time-picker-watch-face-color-default-background-initial)}#picker-hours,#picker-minutes{position:absolute;left:0;top:0;width:100%;height:100%;border-radius:50%;pointer-events:none;user-select:none}.digit-hour-12,.digit-hour-24,.digit-minute{position:absolute;width:28px;height:28px;text-align:center;top:calc(50% - 14px);left:calc(50% - 14px);z-index:1;cursor:pointer}.digit-hour-12{font:var(--sinch-comp-time-picker-digit-font-default-h12);color:var(--sinch-comp-time-picker-digit-color-default-h12-initial);line-height:28px}.digit-hour-12.selected{font:var(--sinch-comp-time-picker-digit-font-checked-h12);color:var(--sinch-comp-time-picker-digit-color-checked-h12-default)}.digit-hour-24{font:var(--sinch-comp-time-picker-digit-font-default-h24);color:var(--sinch-comp-time-picker-digit-color-default-h24-initial);line-height:28px}.digit-hour-24.selected{font:var(--sinch-comp-time-picker-digit-font-checked-h24);color:var(--sinch-comp-time-picker-digit-color-checked-h24-initial)}.digit-minute{font:var(--sinch-comp-time-picker-digit-font-default-minutes);color:var(--sinch-comp-time-picker-digit-color-default-minute-initial);line-height:28px}.digit-minute.selected{font:var(--sinch-comp-time-picker-digit-font-checked-minutes);color:var(--sinch-comp-time-picker-digit-color-checked-minute-initial)}#picker-touch{position:absolute;left:0;top:0;width:100%;height:100%;cursor:pointer;border-radius:50%}#needle-hour,#needle-minute,#picker-touch::after{background-color:var(--sinch-comp-time-picker-needle-color-default-background-initial)}#needle-hour,#needle-minute{position:absolute;transform-origin:bottom center;transform:rotate(0);bottom:50%;height:50px;transition-duration:.25s;transition-timing-function:ease-in-out;transition-property:transform height;z-index:2;outline:0}@media (prefers-reduced-motion){#needle-hour,#needle-minute{transition:none}}#needle-hour{width:4px;left:calc(50% - 2px);border-radius:2px}#needle-minute{width:2px;left:calc(50% - 1px);border-radius:1px}#needle-hour:focus-visible,#needle-minute:focus-visible{background-color:var(--sinch-comp-time-picker-needle-color-default-background-focus)}#needle-minute:not(.selected)::after{content:"";position:absolute;transform:translateX(-50%);left:0;top:-16px;width:4px;height:4px;border-radius:50%;background-color:var(--sinch-comp-time-picker-digit-color-checked-minute-initial)}#picker-touch::after{content:"";position:absolute;top:50%;left:50%;width:12px;height:12px;border-radius:50%;transform:translate(-50%,-50%)}#header-hours,#header-minutes{position:absolute;padding:0 4px;width:50px;outline:0}#header-hours{right:calc(50% + 8px);text-align:right}#header-minutes{left:calc(50% + 8px)}#header-colon{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%)}#submit{position:absolute;right:0;top:50%;transform:translateY(-50%)}#submit-icon{--sinch-global-color-icon:var(--sinch-comp-time-picker-header-color-default-icon-initial)}:host([ampm]) .digit-hour-24{display:none}:host(:not([ampm])) #footer{display:none}</style><div id="wrapper"><div id="header"><div id="header-hours" role="meter" aria-valuemin="0" aria-valuemax="23" aria-valuenow="0" aria-valuetext="0"><span>00</span></div><div id="header-colon">&colon;</div><div id="header-minutes" role="meter" aria-valuemin="0" aria-valuemax="59" aria-valuenow="0" aria-valuetext="0"><span>00</span></div><sinch-button id="submit" size="s" aria-label="Submit"><sinch-icon icons-version="2" name="fa-check" id="icon-submit" slot="icon"></sinch-icon></sinch-button></div><div id="picker" role="group" aria-label="Time picker clock face"><div id="picker-hours"></div><div id="picker-minutes"></div><div id="picker-touch"><div id="needle-hour" tabindex="0" role="slider" aria-label="Hour selector" aria-valuemin="0" aria-valuemax="23" aria-valuenow="0"></div><div id="needle-minute" tabindex="0" role="slider" aria-label="Minute selector" aria-valuemin="0" aria-valuemax="59" aria-valuenow="0"></div></div></div><div id="footer"><sinch-segmented-control id="ampm"><sinch-segmented-control-option value="am" text="AM" aria-label="AM"></sinch-segmented-control-option><sinch-segmented-control-option value="pm" text="PM" aria-label="PM"></sinch-segmented-control-option></sinch-segmented-control></div></div>';
10
10
  const template = document.createElement("template");
11
11
  template.innerHTML = templateHTML;
12
12
  const PICKER_RADIUS = 216 / 2;
@@ -64,6 +64,11 @@ class TimePicker extends NectaryElement {
64
64
  el.className = "digit-hour-12";
65
65
  el.style.transform = `translate(${x}px, ${y}px)`;
66
66
  el.textContent = hourDisplayValue;
67
+ el.setAttribute("role", "button");
68
+ el.setAttribute("tabindex", "-1");
69
+ el.setAttribute("aria-label", `${hourDisplayValue} o'clock`);
70
+ el.addEventListener("click", () => this.#onHourDigitClick(i));
71
+ el.addEventListener("keydown", (e) => this.#onDigitKeydown(e, () => this.#onHourDigitClick(i)));
67
72
  hours12Frag.appendChild(el);
68
73
  }
69
74
  this.#$pickerHours.appendChild(hours12Frag);
@@ -77,6 +82,11 @@ class TimePicker extends NectaryElement {
77
82
  el.className = "digit-hour-24";
78
83
  el.style.transform = `translate(${x}px, ${y}px)`;
79
84
  el.textContent = hourDisplayValue;
85
+ el.setAttribute("role", "button");
86
+ el.setAttribute("tabindex", "-1");
87
+ el.setAttribute("aria-label", `${hourDisplayValue} o'clock`);
88
+ el.addEventListener("click", () => this.#onHourDigitClick(i));
89
+ el.addEventListener("keydown", (e) => this.#onDigitKeydown(e, () => this.#onHourDigitClick(i)));
80
90
  hours24Frag.appendChild(el);
81
91
  }
82
92
  this.#$pickerHours.appendChild(hours24Frag);
@@ -89,6 +99,11 @@ class TimePicker extends NectaryElement {
89
99
  el.className = "digit-minute";
90
100
  el.style.transform = `translate(${x}px, ${y}px)`;
91
101
  el.textContent = stringifyMinute(i);
102
+ el.setAttribute("role", "button");
103
+ el.setAttribute("tabindex", "-1");
104
+ el.setAttribute("aria-label", `${i} minutes`);
105
+ el.addEventListener("click", () => this.#onMinuteDigitClick(i));
106
+ el.addEventListener("keydown", (e) => this.#onDigitKeydown(e, () => this.#onMinuteDigitClick(i)));
92
107
  minutesFrag.appendChild(el);
93
108
  }
94
109
  this.#$pickerMinutes.appendChild(minutesFrag);
@@ -125,8 +140,11 @@ class TimePicker extends NectaryElement {
125
140
  break;
126
141
  }
127
142
  case "ampm": {
143
+ const isAMPM = isAttrTrue(newVal);
144
+ updateBooleanAttribute(this, "ampm", isAMPM);
145
+ const hourMax = isAMPM ? 12 : 23;
146
+ updateAttribute(this.#$needleHour, "aria-valuemax", hourMax);
128
147
  this.#render();
129
- updateBooleanAttribute(this, "ampm", isAttrTrue(newVal));
130
148
  break;
131
149
  }
132
150
  case "submit-aria-label": {
@@ -230,6 +248,10 @@ class TimePicker extends NectaryElement {
230
248
  updateAttribute(this.#$headerHours, "aria-valuetext", this.#hour);
231
249
  updateAttribute(this.#$headerMinutes, "aria-valuenow", this.#minute);
232
250
  updateAttribute(this.#$headerMinutes, "aria-valuetext", this.#minute);
251
+ updateAttribute(this.#$needleHour, "aria-valuenow", this.#hour);
252
+ updateAttribute(this.#$needleHour, "aria-valuetext", `${this.#hour} o'clock`);
253
+ updateAttribute(this.#$needleMinute, "aria-valuenow", this.#minute);
254
+ updateAttribute(this.#$needleMinute, "aria-valuetext", `${this.#minute} minutes`);
233
255
  }
234
256
  #selectHour(is24) {
235
257
  const $hours = this.#$pickerHours.children;
@@ -327,6 +349,20 @@ class TimePicker extends NectaryElement {
327
349
  getReactEventHandler(this, "on-change")?.(e);
328
350
  getReactEventHandler(this, "onChange")?.(e);
329
351
  };
352
+ #onHourDigitClick = (hour) => {
353
+ this.#hour = hour;
354
+ this.#render();
355
+ };
356
+ #onMinuteDigitClick = (minute) => {
357
+ this.#minute = minute;
358
+ this.#render();
359
+ };
360
+ #onDigitKeydown = (e, callback) => {
361
+ if (e.key === "Enter" || e.key === " ") {
362
+ e.preventDefault();
363
+ callback();
364
+ }
365
+ };
330
366
  }
331
367
  defineCustomElement("sinch-time-picker", TimePicker);
332
368
  export {