@nectary/components 5.28.0 → 5.29.1

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
@@ -5038,6 +5038,7 @@ class StopEvents extends HTMLElement {
5038
5038
  }
5039
5039
  defineCustomElement("sinch-stop-events", StopEvents);
5040
5040
  const bodyEl = document.body;
5041
+ const isScrollDisabled = () => bodyEl.__dialog_counter__ > 0;
5041
5042
  const disableScroll = () => {
5042
5043
  bodyEl.__dialog_counter__ = (bodyEl.__dialog_counter__ ?? 0) + 1;
5043
5044
  if (bodyEl.__dialog_counter__ === 1) {
@@ -5055,7 +5056,7 @@ const enableScroll = () => {
5055
5056
  bodyEl.style.removeProperty("padding-right");
5056
5057
  }
5057
5058
  };
5058
- const templateHTML$P = '<style>:host{display:contents;--sinch-comp-dialog-max-width:512px;--sinch-comp-dialog-max-height:90vh;--sinch-comp-dialog-width:fit-content;--sinch-dialog-close-button-display:unset}#dialog{position:fixed;left:0;right:0;margin:auto;display:flex;flex-direction:column;padding:24px 0;width:var(--sinch-comp-dialog-width);max-width:var(--sinch-comp-dialog-max-width);max-height:var(--sinch-comp-dialog-max-height);border-radius:var(--sinch-comp-dialog-shape-radius);box-sizing:border-box;contain:content;background-color:var(--sinch-comp-dialog-color-default-background-initial);border:none;box-shadow:var(--sinch-comp-dialog-shadow);outline:0}#dialog:not([open]){display:none}dialog::backdrop{background-color:#000;opacity:.55}#header{display:flex;flex-direction:row;align-items:flex-start;margin-bottom:12px;padding:0 24px;gap:8px;--sinch-global-size-icon:24px;--sinch-global-color-icon:var(--sinch-comp-dialog-color-default-icon-initial)}#caption{--sinch-global-color-text:var(--sinch-comp-dialog-color-default-title-initial);--sinch-comp-title-font:var(--sinch-comp-dialog-font-title)}#content{min-height:0;overflow:auto;padding:4px 24px}#action{display:flex;flex-direction:row;justify-content:flex-end;gap:16px;margin-top:20px;padding:0 24px}#action.empty{display:none}#close{display:var(--sinch-dialog-close-button-display,initial);position:relative;left:4px;top:-4px;margin-left:auto}</style><dialog id="dialog"><div id="header"><slot id="icon" name="icon"></slot><sinch-title id="caption" type="m" level="3"></sinch-title><sinch-button id="close" size="s"><sinch-icon icons-version="2" name="fa-xmark" id="icon-close" slot="icon"></sinch-icon></sinch-button></div><div id="content"><sinch-stop-events events="close"><slot name="content"></slot></sinch-stop-events></div><div id="action"><sinch-stop-events events="close"><slot name="buttons"></slot></sinch-stop-events></div></dialog>';
5059
+ const templateHTML$P = '<style>:host{display:contents;--sinch-comp-dialog-max-width:512px;--sinch-comp-dialog-max-height:90vh;--sinch-comp-dialog-width:fit-content;--sinch-dialog-close-button-display:unset}#dialog{position:fixed;left:0;right:0;margin:auto;display:flex;flex-direction:column;padding:24px 0;width:var(--sinch-comp-dialog-width);max-width:var(--sinch-comp-dialog-max-width);max-height:var(--sinch-comp-dialog-max-height);border-radius:var(--sinch-comp-dialog-shape-radius);box-sizing:border-box;contain:content;background-color:var(--sinch-comp-dialog-color-default-background-initial);border:none;box-shadow:var(--sinch-comp-dialog-shadow);outline:0}#dialog:not([open]){display:none}dialog::backdrop{background-color:#000;opacity:.55}#header{display:flex;flex-direction:row;align-items:flex-start;margin-bottom:12px;padding:0 24px;gap:8px;--sinch-global-size-icon:24px;--sinch-global-color-icon:var(--sinch-comp-dialog-color-default-icon-initial)}#caption{--sinch-global-color-text:var(--sinch-comp-dialog-color-default-title-initial);--sinch-comp-title-font:var(--sinch-comp-dialog-font-title)}#content{min-height:0;overflow:auto;padding:4px 24px}#action{display:flex;flex-direction:row;justify-content:flex-end;gap:16px;margin-top:20px;padding:0 24px}#action.empty{display:none}#close{display:var(--sinch-dialog-close-button-display,initial);position:relative;left:4px;top:-4px;margin-left:auto}:host([hide-close-button]) #close{display:none}</style><dialog id="dialog"><div id="header"><slot id="icon" name="icon"></slot><sinch-title id="caption" type="m" level="3"></sinch-title><sinch-button id="close" size="s"><sinch-icon icons-version="2" name="fa-xmark" id="icon-close" slot="icon"></sinch-icon></sinch-button></div><div id="content"><sinch-stop-events events="close"><slot name="content"></slot></sinch-stop-events></div><div id="action"><sinch-stop-events events="close"><slot name="buttons"></slot></sinch-stop-events></div></dialog>';
5059
5060
  const template$P = document.createElement("template");
5060
5061
  template$P.innerHTML = templateHTML$P;
5061
5062
  class Dialog extends NectaryElement {
@@ -5089,12 +5090,18 @@ class Dialog extends NectaryElement {
5089
5090
  options
5090
5091
  );
5091
5092
  this.#$dialog.addEventListener("cancel", this.#onCancel, options);
5093
+ this.#$dialog.addEventListener("close", this.#onDialogClose, options);
5092
5094
  this.#$actionSlot.addEventListener(
5093
5095
  "slotchange",
5094
5096
  this.#onActionSlotChange,
5095
5097
  options
5096
5098
  );
5097
5099
  this.addEventListener("-close", this.#onCloseReactHandler, options);
5100
+ this.addEventListener(
5101
+ "-dismiss-blocked",
5102
+ this.#onDismissBlockedReactHandler,
5103
+ options
5104
+ );
5098
5105
  this.#onActionSlotChange();
5099
5106
  if (this.open) {
5100
5107
  this.#onExpand();
@@ -5107,7 +5114,13 @@ class Dialog extends NectaryElement {
5107
5114
  this.#controller = null;
5108
5115
  }
5109
5116
  static get observedAttributes() {
5110
- return ["caption", "open", "close-aria-label"];
5117
+ return [
5118
+ "caption",
5119
+ "open",
5120
+ "close-aria-label",
5121
+ "hide-close-button",
5122
+ "prevent-close"
5123
+ ];
5111
5124
  }
5112
5125
  attributeChangedCallback(name, oldVal, newVal) {
5113
5126
  if (isAttrEqual(oldVal, newVal)) {
@@ -5134,6 +5147,10 @@ class Dialog extends NectaryElement {
5134
5147
  updateAttribute(this.#$closeButton, "aria-label", newVal);
5135
5148
  break;
5136
5149
  }
5150
+ case "hide-close-button": {
5151
+ updateBooleanAttribute(this, name, isAttrTrue(newVal));
5152
+ break;
5153
+ }
5137
5154
  }
5138
5155
  }
5139
5156
  set caption(caption) {
@@ -5148,6 +5165,18 @@ class Dialog extends NectaryElement {
5148
5165
  get open() {
5149
5166
  return getBooleanAttribute(this, "open");
5150
5167
  }
5168
+ set hideCloseButton(isHidden) {
5169
+ updateBooleanAttribute(this, "hide-close-button", isHidden);
5170
+ }
5171
+ get hideCloseButton() {
5172
+ return getBooleanAttribute(this, "hide-close-button");
5173
+ }
5174
+ set preventClose(isPrevented) {
5175
+ updateBooleanAttribute(this, "prevent-close", isPrevented);
5176
+ }
5177
+ get preventClose() {
5178
+ return getBooleanAttribute(this, "prevent-close");
5179
+ }
5151
5180
  get dialogRect() {
5152
5181
  return getRect(this.#$dialog);
5153
5182
  }
@@ -5155,6 +5184,12 @@ class Dialog extends NectaryElement {
5155
5184
  return getRect(this.#$closeButton);
5156
5185
  }
5157
5186
  #onCancel = (e) => {
5187
+ if (this.preventClose) {
5188
+ e.preventDefault();
5189
+ e.stopPropagation();
5190
+ this.#dispatchDismissBlockedEvent("escape");
5191
+ return;
5192
+ }
5158
5193
  if (e.cancelable) {
5159
5194
  e.preventDefault();
5160
5195
  } else {
@@ -5163,7 +5198,22 @@ class Dialog extends NectaryElement {
5163
5198
  e.stopPropagation();
5164
5199
  this.#dispatchCloseEvent("escape", e.cancelable);
5165
5200
  };
5201
+ #onDialogClose = () => {
5202
+ if (!this.isDomConnected || !this.preventClose || !this.open) {
5203
+ return;
5204
+ }
5205
+ if (!this.#$dialog.open) {
5206
+ this.#$dialog.showModal();
5207
+ if (!isScrollDisabled()) {
5208
+ disableScroll();
5209
+ }
5210
+ }
5211
+ };
5166
5212
  #onCloseClick = () => {
5213
+ if (this.preventClose) {
5214
+ this.#dispatchDismissBlockedEvent("close");
5215
+ return;
5216
+ }
5167
5217
  this.#dispatchCloseEvent("close", true);
5168
5218
  };
5169
5219
  #onBackdropMouseDown = (e) => {
@@ -5171,6 +5221,11 @@ class Dialog extends NectaryElement {
5171
5221
  const rect = this.dialogRect;
5172
5222
  const isInside = e.x >= rect.x && e.x < rect.x + rect.width && e.y >= rect.y && e.y < rect.y + rect.height;
5173
5223
  if (!isInside) {
5224
+ if (this.preventClose) {
5225
+ e.stopPropagation();
5226
+ this.#dispatchDismissBlockedEvent("backdrop");
5227
+ return;
5228
+ }
5174
5229
  e.stopPropagation();
5175
5230
  this.#dispatchCloseEvent("backdrop", e.cancelable);
5176
5231
  }
@@ -5180,9 +5235,16 @@ class Dialog extends NectaryElement {
5180
5235
  getReactEventHandler(this, "on-close")?.(e);
5181
5236
  getReactEventHandler(this, "onClose")?.(e);
5182
5237
  };
5238
+ #onDismissBlockedReactHandler = (e) => {
5239
+ getReactEventHandler(this, "on-dismiss-blocked")?.(e);
5240
+ getReactEventHandler(this, "onDismissBlocked")?.(e);
5241
+ };
5183
5242
  #dispatchCloseEvent(detail, cancelable) {
5184
5243
  this.dispatchEvent(new CustomEvent("-close", { detail, cancelable }));
5185
5244
  }
5245
+ #dispatchDismissBlockedEvent(detail) {
5246
+ this.dispatchEvent(new CustomEvent("-dismiss-blocked", { detail }));
5247
+ }
5186
5248
  #onExpand() {
5187
5249
  if (!this.isDomConnected || this.#$dialog.open || !this.open) {
5188
5250
  return;
@@ -8285,7 +8347,7 @@ class Pagination extends NectaryElement {
8285
8347
  }
8286
8348
  }
8287
8349
  defineCustomElement("sinch-pagination", Pagination);
8288
- const templateHTML$v = '<style>#persisted-dialog{--sinch-dialog-close-button-display:none}::slotted(*){display:block}</style><sinch-dialog id="persisted-dialog"><div slot="icon"><slot id="icon" name="icon"></slot></div><div slot="content"><slot name="content"></slot></div><div slot="buttons"><slot name="buttons"></slot></div></sinch-dialog>';
8350
+ const templateHTML$v = '<style>#persisted-dialog{--sinch-dialog-close-button-display:none}::slotted(*){display:block}</style><sinch-dialog id="persisted-dialog" prevent-close><div slot="icon"><slot id="icon" name="icon"></slot></div><div slot="content"><slot name="content"></slot></div><div slot="buttons"><slot name="buttons"></slot></div></sinch-dialog>';
8289
8351
  const template$v = document.createElement("template");
8290
8352
  template$v.innerHTML = templateHTML$v;
8291
8353
  function isVisible(elementStyle) {
@@ -8338,6 +8400,8 @@ class PersistentOverlay extends NectaryElement {
8338
8400
  this.#controller = new AbortController();
8339
8401
  const { signal } = this.#controller;
8340
8402
  this.addEventListener("-visibility-altered", this.#onVisibilityAlteredReactHandler, { signal });
8403
+ this.addEventListener("-dismiss-blocked", this.#onDismissBlockedReactHandler, { signal });
8404
+ this.#$sinchDialog.addEventListener("-dismiss-blocked", this.#onDismissBlocked, { signal });
8341
8405
  if (this.open) {
8342
8406
  this.#startObservingAlteration();
8343
8407
  }
@@ -8373,10 +8437,22 @@ class PersistentOverlay extends NectaryElement {
8373
8437
  #onVisibilityAltered() {
8374
8438
  this.dispatchEvent(new CustomEvent("-visibility-altered"));
8375
8439
  }
8440
+ #onDismissBlocked = (e) => {
8441
+ this.#dispatchDismissBlockedEvent(
8442
+ e.detail
8443
+ );
8444
+ };
8376
8445
  #onVisibilityAlteredReactHandler = (e) => {
8377
8446
  getReactEventHandler(this, "on-visibility-altered")?.(e);
8378
8447
  getReactEventHandler(this, "onVisibilityAltered")?.(e);
8379
8448
  };
8449
+ #onDismissBlockedReactHandler = (e) => {
8450
+ getReactEventHandler(this, "on-dismiss-blocked")?.(e);
8451
+ getReactEventHandler(this, "onDismissBlocked")?.(e);
8452
+ };
8453
+ #dispatchDismissBlockedEvent(detail) {
8454
+ this.dispatchEvent(new CustomEvent("-dismiss-blocked", { detail }));
8455
+ }
8380
8456
  }
8381
8457
  defineCustomElement("sinch-persistent-overlay", PersistentOverlay);
8382
8458
  const ATTR_PROGRESS_STEPPER_ITEM_CHECKED = "data-checked";
@@ -12943,7 +13019,7 @@ class SheetTitle extends NectaryElement {
12943
13019
  };
12944
13020
  }
12945
13021
  defineCustomElement("sinch-sheet-title", SheetTitle);
12946
- const templateHTML$e = '<style>:host{display:block;box-sizing:border-box;--sinch-local-shape-radius:var(--sinch-sys-shape-radius-m, 4px)}#content{width:100%;height:100%;min-height:100%;box-sizing:border-box;background-color:var(--sinch-sys-color-border-subtle);overflow:hidden;position:relative;border-radius:var(--sinch-local-shape-radius)}:host([size=xs]){height:var(--sinch-sys-size-xs,24px);--sinch-local-shape-radius:var(--sinch-sys-shape-radius-xs, 4px)}:host([size="s"]){height:var(--sinch-sys-size-s,32px);--sinch-local-shape-radius:var(--sinch-sys-shape-radius-s, 4px)}:host(:not([size])),:host([size="m"]){height:var(--sinch-sys-size-m,40px);--sinch-local-shape-radius:var(--sinch-sys-shape-radius-m, 4px)}:host([size="l"]){height:var(--sinch-sys-size-l,48px);--sinch-local-shape-radius:var(--sinch-sys-shape-radius-l, 4px)}</style><div id="content"></div>';
13022
+ const templateHTML$e = '<style>:host{display:block;box-sizing:border-box;--sinch-local-shape-radius:var(--sinch-sys-shape-radius-m, 4px)}#content{width:100%;height:100%;min-height:100%;box-sizing:border-box;background-color:var(--sinch-comp-skeleton-color-background);overflow:hidden;position:relative;border-radius:var(--sinch-local-shape-radius)}:host([size=xs]){height:var(--sinch-sys-size-xs,24px);--sinch-local-shape-radius:var(--sinch-sys-shape-radius-xs, 4px)}:host([size="s"]){height:var(--sinch-sys-size-s,32px);--sinch-local-shape-radius:var(--sinch-sys-shape-radius-s, 4px)}:host(:not([size])),:host([size="m"]){height:var(--sinch-sys-size-m,40px);--sinch-local-shape-radius:var(--sinch-sys-shape-radius-m, 4px)}:host([size="l"]){height:var(--sinch-sys-size-l,48px);--sinch-local-shape-radius:var(--sinch-sys-shape-radius-l, 4px)}</style><div id="content"></div>';
12947
13023
  const template$e = document.createElement("template");
12948
13024
  template$e.innerHTML = templateHTML$e;
12949
13025
  class SkeletonItem extends NectaryElement {
@@ -12967,7 +13043,7 @@ const getUid = () => crypto.getRandomValues(new Uint8Array(21)).reduce((id, byte
12967
13043
  }
12968
13044
  return `${id}_`;
12969
13045
  }, "");
12970
- const templateHTML$d = '<style>:host{display:block;position:relative}#wrapper{position:relative;display:flex;flex-direction:column;gap:16px;overflow:hidden;box-sizing:border-box}:host([card]:not([card=false])) #wrapper{background-color:var(--sinch-sys-color-surface-primary-default);border:1px solid var(--sinch-sys-color-border-subtle);border-radius:var(--sinch-sys-shape-radius-l);padding:16px}#shimmer{position:absolute;inset:0;overflow:hidden;pointer-events:none}#shimmer-inner{position:absolute;left:0;top:0;width:400%;height:100%;background-image:linear-gradient(90deg,transparent 0,transparent 47%,var(--sinch-sys-color-surface-tertiary-default) 50%,transparent 53%,transparent 100%);background-size:100% 100%}#shimmer.animated #shimmer-inner{animation:nectary-skeleton-shimmer 2s linear infinite}@media (prefers-reduced-motion:reduce){#shimmer.animated #shimmer-inner{animation:none}}@keyframes nectary-skeleton-shimmer{0%{transform:translateX(-75%)}100%{transform:translateX(0)}}#svg{display:block;width:0;height:0}</style><svg id="svg"><defs><clipPath id="clip"></clipPath></defs></svg><div id="wrapper"><slot></slot><div id="shimmer"><div id="shimmer-inner"></div></div></div>';
13046
+ const templateHTML$d = '<style>:host{display:block;position:relative}#wrapper{position:relative;display:flex;flex-direction:column;gap:16px;overflow:hidden;box-sizing:border-box}:host([card]:not([card=false])) #wrapper{background-color:var(--sinch-sys-color-surface-primary-default);border:1px solid var(--sinch-sys-color-border-subtle);border-radius:var(--sinch-sys-shape-radius-l);padding:16px}#shimmer{position:absolute;inset:0;overflow:hidden;pointer-events:none}#shimmer-inner{position:absolute;left:0;top:0;width:400%;height:100%;background-image:linear-gradient(90deg,transparent 0,transparent 30%,var(--sinch-comp-skeleton-color-shimmer) 50%,transparent 53%,transparent 100%);background-size:100% 100%}#shimmer.animated #shimmer-inner{animation:nectary-skeleton-shimmer 2s linear infinite}@media (prefers-reduced-motion:reduce){#shimmer.animated #shimmer-inner{animation:none}}@keyframes nectary-skeleton-shimmer{0%{transform:translateX(-75%)}100%{transform:translateX(0)}}#svg{display:block;width:0;height:0}</style><svg id="svg"><defs><clipPath id="clip"></clipPath></defs></svg><div id="wrapper"><slot></slot><div id="shimmer"><div id="shimmer-inner"></div></div></div>';
12971
13047
  const template$d = document.createElement("template");
12972
13048
  template$d.innerHTML = templateHTML$d;
12973
13049
  const BORDER_WIDTH = 1;
@@ -13948,13 +14024,14 @@ class TimePicker extends NectaryElement {
13948
14024
  this.#$needleHour.addEventListener("keydown", this.#onHoursKeydown, options);
13949
14025
  this.#$needleMinute.addEventListener("keydown", this.#onMinutesKeydown, options);
13950
14026
  this.addEventListener("-change", this.#onChangeReactHandler, options);
14027
+ this.addEventListener("-submit", this.#onSubmitReactHandler, options);
13951
14028
  }
13952
14029
  disconnectedCallback() {
13953
14030
  this.#controller.abort();
13954
14031
  this.#controller = null;
13955
14032
  }
13956
14033
  static get observedAttributes() {
13957
- return ["value", "ampm", "submit-aria-label"];
14034
+ return ["value", "ampm", "submit-aria-label", "no-submit"];
13958
14035
  }
13959
14036
  attributeChangedCallback(name, prevValue, newVal) {
13960
14037
  if (isAttrEqual(prevValue, newVal)) {
@@ -13980,6 +14057,12 @@ class TimePicker extends NectaryElement {
13980
14057
  updateAttribute(this.#$submitButton, "aria-label", newVal);
13981
14058
  break;
13982
14059
  }
14060
+ case "no-submit": {
14061
+ const isNoSubmit = isAttrTrue(newVal);
14062
+ updateBooleanAttribute(this, "no-submit", isNoSubmit);
14063
+ this.#render();
14064
+ break;
14065
+ }
13983
14066
  }
13984
14067
  }
13985
14068
  set value(value) {
@@ -14000,6 +14083,12 @@ class TimePicker extends NectaryElement {
14000
14083
  get submitAriaLabel() {
14001
14084
  return getAttribute(this, "submit-aria-label", "");
14002
14085
  }
14086
+ set noSubmit(value) {
14087
+ updateBooleanAttribute(this, "no-submit", value);
14088
+ }
14089
+ get noSubmit() {
14090
+ return getBooleanAttribute(this, "no-submit");
14091
+ }
14003
14092
  get submitButtonRect() {
14004
14093
  return getRect(this.#$submitButton);
14005
14094
  }
@@ -14066,6 +14155,7 @@ class TimePicker extends NectaryElement {
14066
14155
  this.#$headerMinutes.focus();
14067
14156
  }
14068
14157
  this.#render();
14158
+ this.#onChange();
14069
14159
  };
14070
14160
  #render() {
14071
14161
  const is24 = getBooleanAttribute(this, "ampm") === false;
@@ -14073,6 +14163,7 @@ class TimePicker extends NectaryElement {
14073
14163
  this.#selectMinute();
14074
14164
  this.#$headerHoursText.textContent = stringifyHour(this.#hour, is24);
14075
14165
  this.#$headerMinutesText.textContent = stringifyMinute(this.#minute);
14166
+ this.#$submitButton.style.display = this.noSubmit ? "none" : "";
14076
14167
  updateAttribute(this.#$headerHours, "aria-valuenow", this.#hour);
14077
14168
  updateAttribute(this.#$headerHours, "aria-valuetext", this.#hour);
14078
14169
  updateAttribute(this.#$headerMinutes, "aria-valuenow", this.#minute);
@@ -14122,6 +14213,7 @@ class TimePicker extends NectaryElement {
14122
14213
  if (this.#hour >= 12) {
14123
14214
  this.#hour -= 12;
14124
14215
  this.#render();
14216
+ this.#onChange();
14125
14217
  }
14126
14218
  break;
14127
14219
  }
@@ -14129,31 +14221,41 @@ class TimePicker extends NectaryElement {
14129
14221
  if (this.#hour < 12) {
14130
14222
  this.#hour += 12;
14131
14223
  this.#render();
14224
+ this.#onChange();
14132
14225
  }
14133
14226
  break;
14134
14227
  }
14135
14228
  }
14136
14229
  };
14137
- #onSubmitButtonClick = () => {
14230
+ #onEventEmit = (eventName) => {
14138
14231
  const value = stringifyTime(this.#hour, this.#minute);
14139
14232
  this.dispatchEvent(
14140
- new CustomEvent("-change", {
14233
+ new CustomEvent(eventName, {
14141
14234
  detail: value
14142
14235
  })
14143
14236
  );
14144
14237
  };
14238
+ #onChange = () => {
14239
+ this.#onEventEmit("-change");
14240
+ };
14241
+ #onSubmitButtonClick = () => {
14242
+ this.#onEventEmit("-submit");
14243
+ this.#onEventEmit("-change");
14244
+ };
14145
14245
  #onHoursKeydown = (e) => {
14146
14246
  switch (e.key) {
14147
14247
  case "ArrowUp": {
14148
14248
  e.preventDefault();
14149
14249
  this.#hour = (this.#hour + 1) % 24;
14150
14250
  this.#render();
14251
+ this.#onChange();
14151
14252
  break;
14152
14253
  }
14153
14254
  case "ArrowDown": {
14154
14255
  e.preventDefault();
14155
14256
  this.#hour = (this.#hour + 23) % 24;
14156
14257
  this.#render();
14258
+ this.#onChange();
14157
14259
  break;
14158
14260
  }
14159
14261
  }
@@ -14164,12 +14266,14 @@ class TimePicker extends NectaryElement {
14164
14266
  e.preventDefault();
14165
14267
  this.#minute = (this.#minute + 1) % 60;
14166
14268
  this.#render();
14269
+ this.#onChange();
14167
14270
  break;
14168
14271
  }
14169
14272
  case "ArrowDown": {
14170
14273
  e.preventDefault();
14171
14274
  this.#minute = (this.#minute + 59) % 60;
14172
14275
  this.#render();
14276
+ this.#onChange();
14173
14277
  break;
14174
14278
  }
14175
14279
  }
@@ -14178,13 +14282,19 @@ class TimePicker extends NectaryElement {
14178
14282
  getReactEventHandler(this, "on-change")?.(e);
14179
14283
  getReactEventHandler(this, "onChange")?.(e);
14180
14284
  };
14285
+ #onSubmitReactHandler = (e) => {
14286
+ getReactEventHandler(this, "on-submit")?.(e);
14287
+ getReactEventHandler(this, "onSubmit")?.(e);
14288
+ };
14181
14289
  #onHourDigitClick = (hour) => {
14182
14290
  this.#hour = hour;
14183
14291
  this.#render();
14292
+ this.#onChange();
14184
14293
  };
14185
14294
  #onMinuteDigitClick = (minute) => {
14186
14295
  this.#minute = minute;
14187
14296
  this.#render();
14297
+ this.#onChange();
14188
14298
  };
14189
14299
  #onDigitKeydown = (e, callback) => {
14190
14300
  if (e.key === "Enter" || e.key === " ") {
package/dialog/index.d.ts CHANGED
@@ -14,6 +14,10 @@ export declare class Dialog extends NectaryElement {
14
14
  get caption(): string;
15
15
  set open(isOpen: boolean);
16
16
  get open(): boolean;
17
+ set hideCloseButton(isHidden: boolean);
18
+ get hideCloseButton(): boolean;
19
+ set preventClose(isPrevented: boolean);
20
+ get preventClose(): boolean;
17
21
  get dialogRect(): import("../types").TRect;
18
22
  get closeButtonRect(): import("../types").TRect;
19
23
  }
package/dialog/index.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import "../icon/index.js";
2
2
  import "../stop-events/index.js";
3
3
  import "../title/index.js";
4
- import { isAttrEqual, updateAttribute, updateBooleanAttribute, getAttribute, getBooleanAttribute, setClass, isAttrTrue } from "../utils/dom.js";
4
+ import { isAttrEqual, updateBooleanAttribute, isAttrTrue, updateAttribute, getAttribute, getBooleanAttribute, setClass } 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 { isTargetEqual } from "../utils/event-target.js";
9
- import { disableScroll, enableScroll } from "../utils/scroll-lock.js";
10
- const templateHTML = '<style>:host{display:contents;--sinch-comp-dialog-max-width:512px;--sinch-comp-dialog-max-height:90vh;--sinch-comp-dialog-width:fit-content;--sinch-dialog-close-button-display:unset}#dialog{position:fixed;left:0;right:0;margin:auto;display:flex;flex-direction:column;padding:24px 0;width:var(--sinch-comp-dialog-width);max-width:var(--sinch-comp-dialog-max-width);max-height:var(--sinch-comp-dialog-max-height);border-radius:var(--sinch-comp-dialog-shape-radius);box-sizing:border-box;contain:content;background-color:var(--sinch-comp-dialog-color-default-background-initial);border:none;box-shadow:var(--sinch-comp-dialog-shadow);outline:0}#dialog:not([open]){display:none}dialog::backdrop{background-color:#000;opacity:.55}#header{display:flex;flex-direction:row;align-items:flex-start;margin-bottom:12px;padding:0 24px;gap:8px;--sinch-global-size-icon:24px;--sinch-global-color-icon:var(--sinch-comp-dialog-color-default-icon-initial)}#caption{--sinch-global-color-text:var(--sinch-comp-dialog-color-default-title-initial);--sinch-comp-title-font:var(--sinch-comp-dialog-font-title)}#content{min-height:0;overflow:auto;padding:4px 24px}#action{display:flex;flex-direction:row;justify-content:flex-end;gap:16px;margin-top:20px;padding:0 24px}#action.empty{display:none}#close{display:var(--sinch-dialog-close-button-display,initial);position:relative;left:4px;top:-4px;margin-left:auto}</style><dialog id="dialog"><div id="header"><slot id="icon" name="icon"></slot><sinch-title id="caption" type="m" level="3"></sinch-title><sinch-button id="close" size="s"><sinch-icon icons-version="2" name="fa-xmark" id="icon-close" slot="icon"></sinch-icon></sinch-button></div><div id="content"><sinch-stop-events events="close"><slot name="content"></slot></sinch-stop-events></div><div id="action"><sinch-stop-events events="close"><slot name="buttons"></slot></sinch-stop-events></div></dialog>';
9
+ import { isScrollDisabled, disableScroll, enableScroll } from "../utils/scroll-lock.js";
10
+ const templateHTML = '<style>:host{display:contents;--sinch-comp-dialog-max-width:512px;--sinch-comp-dialog-max-height:90vh;--sinch-comp-dialog-width:fit-content;--sinch-dialog-close-button-display:unset}#dialog{position:fixed;left:0;right:0;margin:auto;display:flex;flex-direction:column;padding:24px 0;width:var(--sinch-comp-dialog-width);max-width:var(--sinch-comp-dialog-max-width);max-height:var(--sinch-comp-dialog-max-height);border-radius:var(--sinch-comp-dialog-shape-radius);box-sizing:border-box;contain:content;background-color:var(--sinch-comp-dialog-color-default-background-initial);border:none;box-shadow:var(--sinch-comp-dialog-shadow);outline:0}#dialog:not([open]){display:none}dialog::backdrop{background-color:#000;opacity:.55}#header{display:flex;flex-direction:row;align-items:flex-start;margin-bottom:12px;padding:0 24px;gap:8px;--sinch-global-size-icon:24px;--sinch-global-color-icon:var(--sinch-comp-dialog-color-default-icon-initial)}#caption{--sinch-global-color-text:var(--sinch-comp-dialog-color-default-title-initial);--sinch-comp-title-font:var(--sinch-comp-dialog-font-title)}#content{min-height:0;overflow:auto;padding:4px 24px}#action{display:flex;flex-direction:row;justify-content:flex-end;gap:16px;margin-top:20px;padding:0 24px}#action.empty{display:none}#close{display:var(--sinch-dialog-close-button-display,initial);position:relative;left:4px;top:-4px;margin-left:auto}:host([hide-close-button]) #close{display:none}</style><dialog id="dialog"><div id="header"><slot id="icon" name="icon"></slot><sinch-title id="caption" type="m" level="3"></sinch-title><sinch-button id="close" size="s"><sinch-icon icons-version="2" name="fa-xmark" id="icon-close" slot="icon"></sinch-icon></sinch-button></div><div id="content"><sinch-stop-events events="close"><slot name="content"></slot></sinch-stop-events></div><div id="action"><sinch-stop-events events="close"><slot name="buttons"></slot></sinch-stop-events></div></dialog>';
11
11
  const template = document.createElement("template");
12
12
  template.innerHTML = templateHTML;
13
13
  class Dialog extends NectaryElement {
@@ -41,12 +41,18 @@ class Dialog extends NectaryElement {
41
41
  options
42
42
  );
43
43
  this.#$dialog.addEventListener("cancel", this.#onCancel, options);
44
+ this.#$dialog.addEventListener("close", this.#onDialogClose, options);
44
45
  this.#$actionSlot.addEventListener(
45
46
  "slotchange",
46
47
  this.#onActionSlotChange,
47
48
  options
48
49
  );
49
50
  this.addEventListener("-close", this.#onCloseReactHandler, options);
51
+ this.addEventListener(
52
+ "-dismiss-blocked",
53
+ this.#onDismissBlockedReactHandler,
54
+ options
55
+ );
50
56
  this.#onActionSlotChange();
51
57
  if (this.open) {
52
58
  this.#onExpand();
@@ -59,7 +65,13 @@ class Dialog extends NectaryElement {
59
65
  this.#controller = null;
60
66
  }
61
67
  static get observedAttributes() {
62
- return ["caption", "open", "close-aria-label"];
68
+ return [
69
+ "caption",
70
+ "open",
71
+ "close-aria-label",
72
+ "hide-close-button",
73
+ "prevent-close"
74
+ ];
63
75
  }
64
76
  attributeChangedCallback(name, oldVal, newVal) {
65
77
  if (isAttrEqual(oldVal, newVal)) {
@@ -86,6 +98,10 @@ class Dialog extends NectaryElement {
86
98
  updateAttribute(this.#$closeButton, "aria-label", newVal);
87
99
  break;
88
100
  }
101
+ case "hide-close-button": {
102
+ updateBooleanAttribute(this, name, isAttrTrue(newVal));
103
+ break;
104
+ }
89
105
  }
90
106
  }
91
107
  set caption(caption) {
@@ -100,6 +116,18 @@ class Dialog extends NectaryElement {
100
116
  get open() {
101
117
  return getBooleanAttribute(this, "open");
102
118
  }
119
+ set hideCloseButton(isHidden) {
120
+ updateBooleanAttribute(this, "hide-close-button", isHidden);
121
+ }
122
+ get hideCloseButton() {
123
+ return getBooleanAttribute(this, "hide-close-button");
124
+ }
125
+ set preventClose(isPrevented) {
126
+ updateBooleanAttribute(this, "prevent-close", isPrevented);
127
+ }
128
+ get preventClose() {
129
+ return getBooleanAttribute(this, "prevent-close");
130
+ }
103
131
  get dialogRect() {
104
132
  return getRect(this.#$dialog);
105
133
  }
@@ -107,6 +135,12 @@ class Dialog extends NectaryElement {
107
135
  return getRect(this.#$closeButton);
108
136
  }
109
137
  #onCancel = (e) => {
138
+ if (this.preventClose) {
139
+ e.preventDefault();
140
+ e.stopPropagation();
141
+ this.#dispatchDismissBlockedEvent("escape");
142
+ return;
143
+ }
110
144
  if (e.cancelable) {
111
145
  e.preventDefault();
112
146
  } else {
@@ -115,7 +149,22 @@ class Dialog extends NectaryElement {
115
149
  e.stopPropagation();
116
150
  this.#dispatchCloseEvent("escape", e.cancelable);
117
151
  };
152
+ #onDialogClose = () => {
153
+ if (!this.isDomConnected || !this.preventClose || !this.open) {
154
+ return;
155
+ }
156
+ if (!this.#$dialog.open) {
157
+ this.#$dialog.showModal();
158
+ if (!isScrollDisabled()) {
159
+ disableScroll();
160
+ }
161
+ }
162
+ };
118
163
  #onCloseClick = () => {
164
+ if (this.preventClose) {
165
+ this.#dispatchDismissBlockedEvent("close");
166
+ return;
167
+ }
119
168
  this.#dispatchCloseEvent("close", true);
120
169
  };
121
170
  #onBackdropMouseDown = (e) => {
@@ -123,6 +172,11 @@ class Dialog extends NectaryElement {
123
172
  const rect = this.dialogRect;
124
173
  const isInside = e.x >= rect.x && e.x < rect.x + rect.width && e.y >= rect.y && e.y < rect.y + rect.height;
125
174
  if (!isInside) {
175
+ if (this.preventClose) {
176
+ e.stopPropagation();
177
+ this.#dispatchDismissBlockedEvent("backdrop");
178
+ return;
179
+ }
126
180
  e.stopPropagation();
127
181
  this.#dispatchCloseEvent("backdrop", e.cancelable);
128
182
  }
@@ -132,9 +186,16 @@ class Dialog extends NectaryElement {
132
186
  getReactEventHandler(this, "on-close")?.(e);
133
187
  getReactEventHandler(this, "onClose")?.(e);
134
188
  };
189
+ #onDismissBlockedReactHandler = (e) => {
190
+ getReactEventHandler(this, "on-dismiss-blocked")?.(e);
191
+ getReactEventHandler(this, "onDismissBlocked")?.(e);
192
+ };
135
193
  #dispatchCloseEvent(detail, cancelable) {
136
194
  this.dispatchEvent(new CustomEvent("-close", { detail, cancelable }));
137
195
  }
196
+ #dispatchDismissBlockedEvent(detail) {
197
+ this.dispatchEvent(new CustomEvent("-dismiss-blocked", { detail }));
198
+ }
138
199
  #onExpand() {
139
200
  if (!this.isDomConnected || this.#$dialog.open || !this.open) {
140
201
  return;
package/dialog/types.d.ts CHANGED
@@ -9,12 +9,18 @@ export type TSinchDialogProps = {
9
9
  'aria-label': string;
10
10
  /** Close button label that is used for a11y */
11
11
  'close-aria-label': string;
12
+ /** Hides the close button when set */
13
+ 'hide-close-button'?: boolean;
14
+ /** Prevents closing via close button, backdrop, or Escape key. You should provide an explicit action to close the dialog if using this. */
15
+ 'prevent-close'?: boolean;
12
16
  readonly dialogRect?: TRect;
13
17
  readonly closeButtonRect?: TRect;
14
18
  };
15
19
  export type TSinchDialogEvents = {
16
20
  /** close event handler */
17
21
  '-close'?: (e: CustomEvent<TSinchDialogCloseDetail>) => void;
22
+ /** Dismiss blocked event handler (when prevent-close is true). Consumers can use this to provide inline guidance or a polite live-region announcement for instance. */
23
+ '-dismiss-blocked'?: (e: CustomEvent<TSinchDialogCloseDetail>) => void;
18
24
  };
19
25
  export type TSinchDialogStyle = {
20
26
  '--sinch-comp-dialog-max-width'?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nectary/components",
3
- "version": "5.28.0",
3
+ "version": "5.29.1",
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.6.8"
27
+ "@nectary/assets": "3.6.9"
28
28
  },
29
29
  "devDependencies": {
30
30
  "@babel/cli": "^7.22.15",
@@ -40,6 +40,6 @@
40
40
  "vite": "^7.0.6"
41
41
  },
42
42
  "peerDependencies": {
43
- "@nectary/theme-base": "1.15.0"
43
+ "@nectary/theme-base": "1.15.1"
44
44
  }
45
45
  }
@@ -2,7 +2,7 @@ import "../dialog/index.js";
2
2
  import { updateAttribute, getAttribute, updateBooleanAttribute, getBooleanAttribute, isAttrTrue } from "../utils/dom.js";
3
3
  import { defineCustomElement, NectaryElement } from "../utils/element.js";
4
4
  import { getReactEventHandler } from "../utils/get-react-event-handler.js";
5
- const templateHTML = '<style>#persisted-dialog{--sinch-dialog-close-button-display:none}::slotted(*){display:block}</style><sinch-dialog id="persisted-dialog"><div slot="icon"><slot id="icon" name="icon"></slot></div><div slot="content"><slot name="content"></slot></div><div slot="buttons"><slot name="buttons"></slot></div></sinch-dialog>';
5
+ const templateHTML = '<style>#persisted-dialog{--sinch-dialog-close-button-display:none}::slotted(*){display:block}</style><sinch-dialog id="persisted-dialog" prevent-close><div slot="icon"><slot id="icon" name="icon"></slot></div><div slot="content"><slot name="content"></slot></div><div slot="buttons"><slot name="buttons"></slot></div></sinch-dialog>';
6
6
  const template = document.createElement("template");
7
7
  template.innerHTML = templateHTML;
8
8
  function isVisible(elementStyle) {
@@ -55,6 +55,8 @@ class PersistentOverlay extends NectaryElement {
55
55
  this.#controller = new AbortController();
56
56
  const { signal } = this.#controller;
57
57
  this.addEventListener("-visibility-altered", this.#onVisibilityAlteredReactHandler, { signal });
58
+ this.addEventListener("-dismiss-blocked", this.#onDismissBlockedReactHandler, { signal });
59
+ this.#$sinchDialog.addEventListener("-dismiss-blocked", this.#onDismissBlocked, { signal });
58
60
  if (this.open) {
59
61
  this.#startObservingAlteration();
60
62
  }
@@ -90,10 +92,22 @@ class PersistentOverlay extends NectaryElement {
90
92
  #onVisibilityAltered() {
91
93
  this.dispatchEvent(new CustomEvent("-visibility-altered"));
92
94
  }
95
+ #onDismissBlocked = (e) => {
96
+ this.#dispatchDismissBlockedEvent(
97
+ e.detail
98
+ );
99
+ };
93
100
  #onVisibilityAlteredReactHandler = (e) => {
94
101
  getReactEventHandler(this, "on-visibility-altered")?.(e);
95
102
  getReactEventHandler(this, "onVisibilityAltered")?.(e);
96
103
  };
104
+ #onDismissBlockedReactHandler = (e) => {
105
+ getReactEventHandler(this, "on-dismiss-blocked")?.(e);
106
+ getReactEventHandler(this, "onDismissBlocked")?.(e);
107
+ };
108
+ #dispatchDismissBlockedEvent(detail) {
109
+ this.dispatchEvent(new CustomEvent("-dismiss-blocked", { detail }));
110
+ }
97
111
  }
98
112
  defineCustomElement("sinch-persistent-overlay", PersistentOverlay);
99
113
  export {
@@ -1,4 +1,4 @@
1
- import type { TSinchDialogProps } from '../dialog/types';
1
+ import type { TSinchDialogCloseDetail, TSinchDialogProps } from '../dialog/types';
2
2
  import type { NectaryComponentReactByType, NectaryComponentVanillaByType, NectaryComponentReact, NectaryComponentVanilla } from '../types';
3
3
  export type TSinchPersistentOverlayProps = {
4
4
  /** Controls whether the dialog should be open */
@@ -10,7 +10,9 @@ export type TSinchPersistentOverlayProps = {
10
10
  };
11
11
  export type TSinchPersistentOverlayEvents = {
12
12
  /** visibility altered event handler */
13
- '-visibility-altered': (e: CustomEvent) => void;
13
+ '-visibility-altered'?: (e: CustomEvent) => void;
14
+ /** Forwarded when the wrapped dialog blocks a dismissal attempt. Consumers can use this to provide inline guidance or a polite live-region announcement. */
15
+ '-dismiss-blocked'?: (e: CustomEvent<TSinchDialogCloseDetail>) => void;
14
16
  };
15
17
  export type TSinchPersistentOverlayStyle = {
16
18
  '--sinch-dialog-close-button-display'?: string;
package/skeleton/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { isAttrTrue, shouldReduceMotion, getCssVar, attrValueToInteger } from "../utils/dom.js";
2
2
  import { defineCustomElement, NectaryElement } from "../utils/element.js";
3
3
  import { getUid } from "../utils/uid.js";
4
- const templateHTML = '<style>:host{display:block;position:relative}#wrapper{position:relative;display:flex;flex-direction:column;gap:16px;overflow:hidden;box-sizing:border-box}:host([card]:not([card=false])) #wrapper{background-color:var(--sinch-sys-color-surface-primary-default);border:1px solid var(--sinch-sys-color-border-subtle);border-radius:var(--sinch-sys-shape-radius-l);padding:16px}#shimmer{position:absolute;inset:0;overflow:hidden;pointer-events:none}#shimmer-inner{position:absolute;left:0;top:0;width:400%;height:100%;background-image:linear-gradient(90deg,transparent 0,transparent 47%,var(--sinch-sys-color-surface-tertiary-default) 50%,transparent 53%,transparent 100%);background-size:100% 100%}#shimmer.animated #shimmer-inner{animation:nectary-skeleton-shimmer 2s linear infinite}@media (prefers-reduced-motion:reduce){#shimmer.animated #shimmer-inner{animation:none}}@keyframes nectary-skeleton-shimmer{0%{transform:translateX(-75%)}100%{transform:translateX(0)}}#svg{display:block;width:0;height:0}</style><svg id="svg"><defs><clipPath id="clip"></clipPath></defs></svg><div id="wrapper"><slot></slot><div id="shimmer"><div id="shimmer-inner"></div></div></div>';
4
+ const templateHTML = '<style>:host{display:block;position:relative}#wrapper{position:relative;display:flex;flex-direction:column;gap:16px;overflow:hidden;box-sizing:border-box}:host([card]:not([card=false])) #wrapper{background-color:var(--sinch-sys-color-surface-primary-default);border:1px solid var(--sinch-sys-color-border-subtle);border-radius:var(--sinch-sys-shape-radius-l);padding:16px}#shimmer{position:absolute;inset:0;overflow:hidden;pointer-events:none}#shimmer-inner{position:absolute;left:0;top:0;width:400%;height:100%;background-image:linear-gradient(90deg,transparent 0,transparent 30%,var(--sinch-comp-skeleton-color-shimmer) 50%,transparent 53%,transparent 100%);background-size:100% 100%}#shimmer.animated #shimmer-inner{animation:nectary-skeleton-shimmer 2s linear infinite}@media (prefers-reduced-motion:reduce){#shimmer.animated #shimmer-inner{animation:none}}@keyframes nectary-skeleton-shimmer{0%{transform:translateX(-75%)}100%{transform:translateX(0)}}#svg{display:block;width:0;height:0}</style><svg id="svg"><defs><clipPath id="clip"></clipPath></defs></svg><div id="wrapper"><slot></slot><div id="shimmer"><div id="shimmer-inner"></div></div></div>';
5
5
  const template = document.createElement("template");
6
6
  template.innerHTML = templateHTML;
7
7
  const BORDER_WIDTH = 1;
@@ -1,5 +1,5 @@
1
1
  import { defineCustomElement, NectaryElement } from "../utils/element.js";
2
- const templateHTML = '<style>:host{display:block;box-sizing:border-box;--sinch-local-shape-radius:var(--sinch-sys-shape-radius-m, 4px)}#content{width:100%;height:100%;min-height:100%;box-sizing:border-box;background-color:var(--sinch-sys-color-border-subtle);overflow:hidden;position:relative;border-radius:var(--sinch-local-shape-radius)}:host([size=xs]){height:var(--sinch-sys-size-xs,24px);--sinch-local-shape-radius:var(--sinch-sys-shape-radius-xs, 4px)}:host([size="s"]){height:var(--sinch-sys-size-s,32px);--sinch-local-shape-radius:var(--sinch-sys-shape-radius-s, 4px)}:host(:not([size])),:host([size="m"]){height:var(--sinch-sys-size-m,40px);--sinch-local-shape-radius:var(--sinch-sys-shape-radius-m, 4px)}:host([size="l"]){height:var(--sinch-sys-size-l,48px);--sinch-local-shape-radius:var(--sinch-sys-shape-radius-l, 4px)}</style><div id="content"></div>';
2
+ const templateHTML = '<style>:host{display:block;box-sizing:border-box;--sinch-local-shape-radius:var(--sinch-sys-shape-radius-m, 4px)}#content{width:100%;height:100%;min-height:100%;box-sizing:border-box;background-color:var(--sinch-comp-skeleton-color-background);overflow:hidden;position:relative;border-radius:var(--sinch-local-shape-radius)}:host([size=xs]){height:var(--sinch-sys-size-xs,24px);--sinch-local-shape-radius:var(--sinch-sys-shape-radius-xs, 4px)}:host([size="s"]){height:var(--sinch-sys-size-s,32px);--sinch-local-shape-radius:var(--sinch-sys-shape-radius-s, 4px)}:host(:not([size])),:host([size="m"]){height:var(--sinch-sys-size-m,40px);--sinch-local-shape-radius:var(--sinch-sys-shape-radius-m, 4px)}:host([size="l"]){height:var(--sinch-sys-size-l,48px);--sinch-local-shape-radius:var(--sinch-sys-shape-radius-l, 4px)}</style><div id="content"></div>';
3
3
  const template = document.createElement("template");
4
4
  template.innerHTML = templateHTML;
5
5
  class SkeletonItem extends NectaryElement {
@@ -17,6 +17,8 @@ export declare class TimePicker extends NectaryElement {
17
17
  get ampm(): boolean;
18
18
  set submitAriaLabel(value: string);
19
19
  get submitAriaLabel(): string;
20
+ set noSubmit(value: boolean);
21
+ get noSubmit(): boolean;
20
22
  get submitButtonRect(): TRect;
21
23
  get amButtonRect(): TRect | null;
22
24
  get pmButtonRect(): TRect | null;
@@ -1,7 +1,7 @@
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, getAttribute, getBooleanAttribute, setClass, isAttrTrue } from "../utils/dom.js";
4
+ import { isAttrEqual, updateBooleanAttribute, updateAttribute, 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";
@@ -119,13 +119,14 @@ class TimePicker extends NectaryElement {
119
119
  this.#$needleHour.addEventListener("keydown", this.#onHoursKeydown, options);
120
120
  this.#$needleMinute.addEventListener("keydown", this.#onMinutesKeydown, options);
121
121
  this.addEventListener("-change", this.#onChangeReactHandler, options);
122
+ this.addEventListener("-submit", this.#onSubmitReactHandler, options);
122
123
  }
123
124
  disconnectedCallback() {
124
125
  this.#controller.abort();
125
126
  this.#controller = null;
126
127
  }
127
128
  static get observedAttributes() {
128
- return ["value", "ampm", "submit-aria-label"];
129
+ return ["value", "ampm", "submit-aria-label", "no-submit"];
129
130
  }
130
131
  attributeChangedCallback(name, prevValue, newVal) {
131
132
  if (isAttrEqual(prevValue, newVal)) {
@@ -151,6 +152,12 @@ class TimePicker extends NectaryElement {
151
152
  updateAttribute(this.#$submitButton, "aria-label", newVal);
152
153
  break;
153
154
  }
155
+ case "no-submit": {
156
+ const isNoSubmit = isAttrTrue(newVal);
157
+ updateBooleanAttribute(this, "no-submit", isNoSubmit);
158
+ this.#render();
159
+ break;
160
+ }
154
161
  }
155
162
  }
156
163
  set value(value) {
@@ -171,6 +178,12 @@ class TimePicker extends NectaryElement {
171
178
  get submitAriaLabel() {
172
179
  return getAttribute(this, "submit-aria-label", "");
173
180
  }
181
+ set noSubmit(value) {
182
+ updateBooleanAttribute(this, "no-submit", value);
183
+ }
184
+ get noSubmit() {
185
+ return getBooleanAttribute(this, "no-submit");
186
+ }
174
187
  get submitButtonRect() {
175
188
  return getRect(this.#$submitButton);
176
189
  }
@@ -237,6 +250,7 @@ class TimePicker extends NectaryElement {
237
250
  this.#$headerMinutes.focus();
238
251
  }
239
252
  this.#render();
253
+ this.#onChange();
240
254
  };
241
255
  #render() {
242
256
  const is24 = getBooleanAttribute(this, "ampm") === false;
@@ -244,6 +258,7 @@ class TimePicker extends NectaryElement {
244
258
  this.#selectMinute();
245
259
  this.#$headerHoursText.textContent = stringifyHour(this.#hour, is24);
246
260
  this.#$headerMinutesText.textContent = stringifyMinute(this.#minute);
261
+ this.#$submitButton.style.display = this.noSubmit ? "none" : "";
247
262
  updateAttribute(this.#$headerHours, "aria-valuenow", this.#hour);
248
263
  updateAttribute(this.#$headerHours, "aria-valuetext", this.#hour);
249
264
  updateAttribute(this.#$headerMinutes, "aria-valuenow", this.#minute);
@@ -293,6 +308,7 @@ class TimePicker extends NectaryElement {
293
308
  if (this.#hour >= 12) {
294
309
  this.#hour -= 12;
295
310
  this.#render();
311
+ this.#onChange();
296
312
  }
297
313
  break;
298
314
  }
@@ -300,31 +316,41 @@ class TimePicker extends NectaryElement {
300
316
  if (this.#hour < 12) {
301
317
  this.#hour += 12;
302
318
  this.#render();
319
+ this.#onChange();
303
320
  }
304
321
  break;
305
322
  }
306
323
  }
307
324
  };
308
- #onSubmitButtonClick = () => {
325
+ #onEventEmit = (eventName) => {
309
326
  const value = stringifyTime(this.#hour, this.#minute);
310
327
  this.dispatchEvent(
311
- new CustomEvent("-change", {
328
+ new CustomEvent(eventName, {
312
329
  detail: value
313
330
  })
314
331
  );
315
332
  };
333
+ #onChange = () => {
334
+ this.#onEventEmit("-change");
335
+ };
336
+ #onSubmitButtonClick = () => {
337
+ this.#onEventEmit("-submit");
338
+ this.#onEventEmit("-change");
339
+ };
316
340
  #onHoursKeydown = (e) => {
317
341
  switch (e.key) {
318
342
  case "ArrowUp": {
319
343
  e.preventDefault();
320
344
  this.#hour = (this.#hour + 1) % 24;
321
345
  this.#render();
346
+ this.#onChange();
322
347
  break;
323
348
  }
324
349
  case "ArrowDown": {
325
350
  e.preventDefault();
326
351
  this.#hour = (this.#hour + 23) % 24;
327
352
  this.#render();
353
+ this.#onChange();
328
354
  break;
329
355
  }
330
356
  }
@@ -335,12 +361,14 @@ class TimePicker extends NectaryElement {
335
361
  e.preventDefault();
336
362
  this.#minute = (this.#minute + 1) % 60;
337
363
  this.#render();
364
+ this.#onChange();
338
365
  break;
339
366
  }
340
367
  case "ArrowDown": {
341
368
  e.preventDefault();
342
369
  this.#minute = (this.#minute + 59) % 60;
343
370
  this.#render();
371
+ this.#onChange();
344
372
  break;
345
373
  }
346
374
  }
@@ -349,13 +377,19 @@ class TimePicker extends NectaryElement {
349
377
  getReactEventHandler(this, "on-change")?.(e);
350
378
  getReactEventHandler(this, "onChange")?.(e);
351
379
  };
380
+ #onSubmitReactHandler = (e) => {
381
+ getReactEventHandler(this, "on-submit")?.(e);
382
+ getReactEventHandler(this, "onSubmit")?.(e);
383
+ };
352
384
  #onHourDigitClick = (hour) => {
353
385
  this.#hour = hour;
354
386
  this.#render();
387
+ this.#onChange();
355
388
  };
356
389
  #onMinuteDigitClick = (minute) => {
357
390
  this.#minute = minute;
358
391
  this.#render();
392
+ this.#onChange();
359
393
  };
360
394
  #onDigitKeydown = (e, callback) => {
361
395
  if (e.key === "Enter" || e.key === " ") {
@@ -6,6 +6,8 @@ export type TSinchTimePickerProps = {
6
6
  ampm?: boolean;
7
7
  /** Label that is used for a11y */
8
8
  'aria-label': string;
9
+ /** Hide submit button */
10
+ 'no-submit'?: boolean;
9
11
  /** Submit button label that is used for a11y */
10
12
  'submit-aria-label': string;
11
13
  readonly submitButtonRect?: TRect;
@@ -19,6 +21,8 @@ export type TSinchTimePickerMethods = {
19
21
  export type TSinchTimePickerEvents = {
20
22
  /** Change value handler, return time in ISO 8601 format */
21
23
  '-change'?: (e: CustomEvent<string>) => void;
24
+ /** Submit value handler, return time in ISO 8601 format */
25
+ '-submit'?: (e: CustomEvent<string>) => void;
22
26
  };
23
27
  export type TSinchTimePickerStyle = {
24
28
  '--sinch-comp-time-picker-header-font'?: string;
@@ -1,2 +1,3 @@
1
+ export declare const isScrollDisabled: () => boolean;
1
2
  export declare const disableScroll: () => void;
2
3
  export declare const enableScroll: () => void;
@@ -1,4 +1,5 @@
1
1
  const bodyEl = document.body;
2
+ const isScrollDisabled = () => bodyEl.__dialog_counter__ > 0;
2
3
  const disableScroll = () => {
3
4
  bodyEl.__dialog_counter__ = (bodyEl.__dialog_counter__ ?? 0) + 1;
4
5
  if (bodyEl.__dialog_counter__ === 1) {
@@ -18,5 +19,6 @@ const enableScroll = () => {
18
19
  };
19
20
  export {
20
21
  disableScroll,
21
- enableScroll
22
+ enableScroll,
23
+ isScrollDisabled
22
24
  };