@nectary/components 5.27.0 → 5.29.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 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;
@@ -7948,7 +8010,7 @@ class HelpTooltip extends NectaryElement {
7948
8010
  }
7949
8011
  defineCustomElement("sinch-help-tooltip", HelpTooltip);
7950
8012
  const typeValues$1 = ["info", "success", "warn", "error"];
7951
- const templateHTML$z = '<style>:host{display:block}#wrapper{display:flex;flex-direction:row;align-items:flex-start;padding:16px;border-radius:var(--sinch-comp-inline-alert-shape-radius);box-sizing:border-box;width:100%}:host([type=success]) #wrapper{background-color:var(--sinch-comp-inline-alert-color-success-default-background)}:host([type=warn]) #wrapper{background-color:var(--sinch-comp-inline-alert-color-warning-default-background)}:host([type=error]) #wrapper{background-color:var(--sinch-comp-inline-alert-color-error-default-background)}:host([type=info]) #wrapper{background-color:var(--sinch-comp-inline-alert-color-info-default-background)}:host([type=success]) #icon{--sinch-global-color-icon:var(--sinch-comp-inline-alert-color-success-default-icon)}:host([type=warn]) #icon{--sinch-global-color-icon:var(--sinch-comp-inline-alert-color-warning-default-icon)}:host([type=error]) #icon{--sinch-global-color-icon:var(--sinch-comp-inline-alert-color-error-default-icon)}:host([type=info]) #icon{--sinch-global-color-icon:var(--sinch-comp-inline-alert-color-info-default-icon)}#body-wrapper{display:flex;flex-direction:column;align-items:flex-start;margin-left:8px;min-width:0;flex:1}#caption{align-self:stretch;--sinch-comp-title-font:var(--sinch-comp-inline-alert-font-title)}:host([type=success]) #caption{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-success-default-text)}:host([type=warn]) #caption{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-warning-default-text)}:host([type=error]) #caption{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-error-default-text)}:host([type=info]) #caption{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-info-default-text)}#text{display:flex;flex-direction:column;gap:8px;margin-top:0;align-self:stretch;--sinch-comp-rich-text-font:var(--sinch-comp-inline-alert-font-body)}:host([type=success]) #text{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-success-default-text)}:host([type=warn]) #text{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-warning-default-text)}:host([type=error]) #text{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-error-default-text)}:host([type=info]) #text{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-info-default-text)}:host([caption=""]) #text,:host([caption]) #text{margin-top:4px}#action{width:100%;display:flex;margin-top:16px;min-width:0;gap:16px}#action.empty{display:none}#close{margin-left:16px}#close.empty{display:none}</style><div id="wrapper"><sinch-icon icons-version="2" name="" id="icon"></sinch-icon><div id="body-wrapper"><sinch-title id="caption" level="3" type="s"></sinch-title><sinch-rich-text id="text"></sinch-rich-text><div id="action"><slot name="action"></slot></div></div><div id="close"><slot name="close"></slot></div></div>';
8013
+ const templateHTML$z = '<style>:host{display:block}#wrapper{display:flex;flex-direction:row;align-items:flex-start;padding:16px;border-radius:var(--sinch-comp-inline-alert-shape-radius);box-sizing:border-box;width:100%}:host([type=success]) #wrapper{background-color:var(--sinch-comp-inline-alert-color-success-default-background)}:host([type=warn]) #wrapper{background-color:var(--sinch-comp-inline-alert-color-warning-default-background)}:host([type=error]) #wrapper{background-color:var(--sinch-comp-inline-alert-color-error-default-background)}:host([type=info]) #wrapper{background-color:var(--sinch-comp-inline-alert-color-info-default-background)}#spinner{display:none}:host([type=success]) #icon,:host([type=success]) #spinner{--sinch-global-color-icon:var(--sinch-comp-inline-alert-color-success-default-icon)}:host([type=warn]) #icon,:host([type=warn]) #spinner{--sinch-global-color-icon:var(--sinch-comp-inline-alert-color-warning-default-icon)}:host([type=error]) #icon,:host([type=error]) #spinner{--sinch-global-color-icon:var(--sinch-comp-inline-alert-color-error-default-icon)}:host([type=info]) #icon,:host([type=info]) #spinner{--sinch-global-color-icon:var(--sinch-comp-inline-alert-color-info-default-icon)}:host([loading]) #icon{display:none}:host([loading]) #spinner{display:block}#body-wrapper{display:flex;flex-direction:column;align-items:flex-start;margin-left:8px;min-width:0;flex:1}#caption{align-self:stretch;--sinch-comp-title-font:var(--sinch-comp-inline-alert-font-title)}:host([type=success]) #caption{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-success-default-text)}:host([type=warn]) #caption{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-warning-default-text)}:host([type=error]) #caption{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-error-default-text)}:host([type=info]) #caption{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-info-default-text)}#text{display:flex;flex-direction:column;gap:8px;margin-top:0;align-self:stretch;--sinch-comp-rich-text-font:var(--sinch-comp-inline-alert-font-body)}:host([type=success]) #text{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-success-default-text)}:host([type=warn]) #text{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-warning-default-text)}:host([type=error]) #text{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-error-default-text)}:host([type=info]) #text{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-info-default-text)}:host([caption=""]) #text,:host([caption]) #text{margin-top:4px}#action{width:100%;display:flex;margin-top:16px;min-width:0;gap:16px}#action.empty{display:none}#close{margin-left:16px}#close.empty{display:none}</style><div id="wrapper"><sinch-spinner id="spinner" size="m"></sinch-spinner><sinch-icon icons-version="2" name="" id="icon"></sinch-icon><div id="body-wrapper"><sinch-title id="caption" level="3" type="s"></sinch-title><sinch-rich-text id="text"></sinch-rich-text><div id="action"><slot name="action"></slot></div></div><div id="close"><slot name="close"></slot></div></div>';
7952
8014
  const template$z = document.createElement("template");
7953
8015
  template$z.innerHTML = templateHTML$z;
7954
8016
  class InlineAlert extends NectaryElement {
@@ -7975,6 +8037,7 @@ class InlineAlert extends NectaryElement {
7975
8037
  super.connectedCallback();
7976
8038
  this.setAttribute("aria-atomic", "true");
7977
8039
  this.setAttribute("aria-live", "polite");
8040
+ updateAttribute(this, "aria-busy", this.loading ? "true" : "false");
7978
8041
  this.#$closeSlot.addEventListener("slotchange", this.#onCloseSlotChange);
7979
8042
  this.#$actionSlot.addEventListener(
7980
8043
  "slotchange",
@@ -7995,7 +8058,7 @@ class InlineAlert extends NectaryElement {
7995
8058
  );
7996
8059
  }
7997
8060
  static get observedAttributes() {
7998
- return ["text", "caption", "type", "icon"];
8061
+ return ["text", "caption", "type", "icon", "loading"];
7999
8062
  }
8000
8063
  attributeChangedCallback(name, _, newVal) {
8001
8064
  switch (name) {
@@ -8015,6 +8078,10 @@ class InlineAlert extends NectaryElement {
8015
8078
  updateAttribute(this.#$icon, "name", newVal);
8016
8079
  break;
8017
8080
  }
8081
+ case "loading": {
8082
+ updateExplicitBooleanAttribute(this, "aria-busy", isAttrTrue(newVal));
8083
+ break;
8084
+ }
8018
8085
  }
8019
8086
  }
8020
8087
  get type() {
@@ -8041,6 +8108,12 @@ class InlineAlert extends NectaryElement {
8041
8108
  set icon(value) {
8042
8109
  updateAttribute(this, "icon", value);
8043
8110
  }
8111
+ get loading() {
8112
+ return getBooleanAttribute(this, "loading");
8113
+ }
8114
+ set loading(value) {
8115
+ updateBooleanAttribute(this, "loading", value);
8116
+ }
8044
8117
  #onCloseSlotChange = () => {
8045
8118
  setClass(
8046
8119
  this.#$closeWrapper,
@@ -8274,7 +8347,7 @@ class Pagination extends NectaryElement {
8274
8347
  }
8275
8348
  }
8276
8349
  defineCustomElement("sinch-pagination", Pagination);
8277
- 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>';
8278
8351
  const template$v = document.createElement("template");
8279
8352
  template$v.innerHTML = templateHTML$v;
8280
8353
  function isVisible(elementStyle) {
@@ -8327,6 +8400,8 @@ class PersistentOverlay extends NectaryElement {
8327
8400
  this.#controller = new AbortController();
8328
8401
  const { signal } = this.#controller;
8329
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 });
8330
8405
  if (this.open) {
8331
8406
  this.#startObservingAlteration();
8332
8407
  }
@@ -8362,10 +8437,22 @@ class PersistentOverlay extends NectaryElement {
8362
8437
  #onVisibilityAltered() {
8363
8438
  this.dispatchEvent(new CustomEvent("-visibility-altered"));
8364
8439
  }
8440
+ #onDismissBlocked = (e) => {
8441
+ this.#dispatchDismissBlockedEvent(
8442
+ e.detail
8443
+ );
8444
+ };
8365
8445
  #onVisibilityAlteredReactHandler = (e) => {
8366
8446
  getReactEventHandler(this, "on-visibility-altered")?.(e);
8367
8447
  getReactEventHandler(this, "onVisibilityAltered")?.(e);
8368
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
+ }
8369
8456
  }
8370
8457
  defineCustomElement("sinch-persistent-overlay", PersistentOverlay);
8371
8458
  const ATTR_PROGRESS_STEPPER_ITEM_CHECKED = "data-checked";
@@ -13937,13 +14024,14 @@ class TimePicker extends NectaryElement {
13937
14024
  this.#$needleHour.addEventListener("keydown", this.#onHoursKeydown, options);
13938
14025
  this.#$needleMinute.addEventListener("keydown", this.#onMinutesKeydown, options);
13939
14026
  this.addEventListener("-change", this.#onChangeReactHandler, options);
14027
+ this.addEventListener("-submit", this.#onSubmitReactHandler, options);
13940
14028
  }
13941
14029
  disconnectedCallback() {
13942
14030
  this.#controller.abort();
13943
14031
  this.#controller = null;
13944
14032
  }
13945
14033
  static get observedAttributes() {
13946
- return ["value", "ampm", "submit-aria-label"];
14034
+ return ["value", "ampm", "submit-aria-label", "no-submit"];
13947
14035
  }
13948
14036
  attributeChangedCallback(name, prevValue, newVal) {
13949
14037
  if (isAttrEqual(prevValue, newVal)) {
@@ -13969,6 +14057,12 @@ class TimePicker extends NectaryElement {
13969
14057
  updateAttribute(this.#$submitButton, "aria-label", newVal);
13970
14058
  break;
13971
14059
  }
14060
+ case "no-submit": {
14061
+ const isNoSubmit = isAttrTrue(newVal);
14062
+ updateBooleanAttribute(this, "no-submit", isNoSubmit);
14063
+ this.#render();
14064
+ break;
14065
+ }
13972
14066
  }
13973
14067
  }
13974
14068
  set value(value) {
@@ -13989,6 +14083,12 @@ class TimePicker extends NectaryElement {
13989
14083
  get submitAriaLabel() {
13990
14084
  return getAttribute(this, "submit-aria-label", "");
13991
14085
  }
14086
+ set noSubmit(value) {
14087
+ updateBooleanAttribute(this, "no-submit", value);
14088
+ }
14089
+ get noSubmit() {
14090
+ return getBooleanAttribute(this, "no-submit");
14091
+ }
13992
14092
  get submitButtonRect() {
13993
14093
  return getRect(this.#$submitButton);
13994
14094
  }
@@ -14055,6 +14155,7 @@ class TimePicker extends NectaryElement {
14055
14155
  this.#$headerMinutes.focus();
14056
14156
  }
14057
14157
  this.#render();
14158
+ this.#onChange();
14058
14159
  };
14059
14160
  #render() {
14060
14161
  const is24 = getBooleanAttribute(this, "ampm") === false;
@@ -14062,6 +14163,7 @@ class TimePicker extends NectaryElement {
14062
14163
  this.#selectMinute();
14063
14164
  this.#$headerHoursText.textContent = stringifyHour(this.#hour, is24);
14064
14165
  this.#$headerMinutesText.textContent = stringifyMinute(this.#minute);
14166
+ this.#$submitButton.style.display = this.noSubmit ? "none" : "";
14065
14167
  updateAttribute(this.#$headerHours, "aria-valuenow", this.#hour);
14066
14168
  updateAttribute(this.#$headerHours, "aria-valuetext", this.#hour);
14067
14169
  updateAttribute(this.#$headerMinutes, "aria-valuenow", this.#minute);
@@ -14111,6 +14213,7 @@ class TimePicker extends NectaryElement {
14111
14213
  if (this.#hour >= 12) {
14112
14214
  this.#hour -= 12;
14113
14215
  this.#render();
14216
+ this.#onChange();
14114
14217
  }
14115
14218
  break;
14116
14219
  }
@@ -14118,31 +14221,41 @@ class TimePicker extends NectaryElement {
14118
14221
  if (this.#hour < 12) {
14119
14222
  this.#hour += 12;
14120
14223
  this.#render();
14224
+ this.#onChange();
14121
14225
  }
14122
14226
  break;
14123
14227
  }
14124
14228
  }
14125
14229
  };
14126
- #onSubmitButtonClick = () => {
14230
+ #onEventEmit = (eventName) => {
14127
14231
  const value = stringifyTime(this.#hour, this.#minute);
14128
14232
  this.dispatchEvent(
14129
- new CustomEvent("-change", {
14233
+ new CustomEvent(eventName, {
14130
14234
  detail: value
14131
14235
  })
14132
14236
  );
14133
14237
  };
14238
+ #onChange = () => {
14239
+ this.#onEventEmit("-change");
14240
+ };
14241
+ #onSubmitButtonClick = () => {
14242
+ this.#onEventEmit("-submit");
14243
+ this.#onEventEmit("-change");
14244
+ };
14134
14245
  #onHoursKeydown = (e) => {
14135
14246
  switch (e.key) {
14136
14247
  case "ArrowUp": {
14137
14248
  e.preventDefault();
14138
14249
  this.#hour = (this.#hour + 1) % 24;
14139
14250
  this.#render();
14251
+ this.#onChange();
14140
14252
  break;
14141
14253
  }
14142
14254
  case "ArrowDown": {
14143
14255
  e.preventDefault();
14144
14256
  this.#hour = (this.#hour + 23) % 24;
14145
14257
  this.#render();
14258
+ this.#onChange();
14146
14259
  break;
14147
14260
  }
14148
14261
  }
@@ -14153,12 +14266,14 @@ class TimePicker extends NectaryElement {
14153
14266
  e.preventDefault();
14154
14267
  this.#minute = (this.#minute + 1) % 60;
14155
14268
  this.#render();
14269
+ this.#onChange();
14156
14270
  break;
14157
14271
  }
14158
14272
  case "ArrowDown": {
14159
14273
  e.preventDefault();
14160
14274
  this.#minute = (this.#minute + 59) % 60;
14161
14275
  this.#render();
14276
+ this.#onChange();
14162
14277
  break;
14163
14278
  }
14164
14279
  }
@@ -14167,13 +14282,19 @@ class TimePicker extends NectaryElement {
14167
14282
  getReactEventHandler(this, "on-change")?.(e);
14168
14283
  getReactEventHandler(this, "onChange")?.(e);
14169
14284
  };
14285
+ #onSubmitReactHandler = (e) => {
14286
+ getReactEventHandler(this, "on-submit")?.(e);
14287
+ getReactEventHandler(this, "onSubmit")?.(e);
14288
+ };
14170
14289
  #onHourDigitClick = (hour) => {
14171
14290
  this.#hour = hour;
14172
14291
  this.#render();
14292
+ this.#onChange();
14173
14293
  };
14174
14294
  #onMinuteDigitClick = (minute) => {
14175
14295
  this.#minute = minute;
14176
14296
  this.#render();
14297
+ this.#onChange();
14177
14298
  };
14178
14299
  #onDigitKeydown = (e, callback) => {
14179
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;
@@ -1,4 +1,5 @@
1
1
  import '../icon';
2
+ import '../spinner';
2
3
  import '../rich-text';
3
4
  import '../text';
4
5
  import '../title';
@@ -21,4 +22,6 @@ export declare class InlineAlert extends NectaryElement {
21
22
  set caption(value: string);
22
23
  get icon(): TSinchIcons;
23
24
  set icon(value: TSinchIcons);
25
+ get loading(): boolean;
26
+ set loading(value: boolean);
24
27
  }
@@ -1,11 +1,12 @@
1
1
  import "../icon/index.js";
2
+ import "../spinner/index.js";
2
3
  import "../rich-text/index.js";
3
4
  import "../text/index.js";
4
5
  import "../title/index.js";
5
- import { updateAttribute, getLiteralAttribute, updateLiteralAttribute, getAttribute, setClass } from "../utils/dom.js";
6
+ import { updateAttribute, updateExplicitBooleanAttribute, isAttrTrue, getLiteralAttribute, updateLiteralAttribute, getAttribute, getBooleanAttribute, updateBooleanAttribute, setClass } from "../utils/dom.js";
6
7
  import { defineCustomElement, NectaryElement } from "../utils/element.js";
7
8
  import { typeValues } from "./utils.js";
8
- const templateHTML = '<style>:host{display:block}#wrapper{display:flex;flex-direction:row;align-items:flex-start;padding:16px;border-radius:var(--sinch-comp-inline-alert-shape-radius);box-sizing:border-box;width:100%}:host([type=success]) #wrapper{background-color:var(--sinch-comp-inline-alert-color-success-default-background)}:host([type=warn]) #wrapper{background-color:var(--sinch-comp-inline-alert-color-warning-default-background)}:host([type=error]) #wrapper{background-color:var(--sinch-comp-inline-alert-color-error-default-background)}:host([type=info]) #wrapper{background-color:var(--sinch-comp-inline-alert-color-info-default-background)}:host([type=success]) #icon{--sinch-global-color-icon:var(--sinch-comp-inline-alert-color-success-default-icon)}:host([type=warn]) #icon{--sinch-global-color-icon:var(--sinch-comp-inline-alert-color-warning-default-icon)}:host([type=error]) #icon{--sinch-global-color-icon:var(--sinch-comp-inline-alert-color-error-default-icon)}:host([type=info]) #icon{--sinch-global-color-icon:var(--sinch-comp-inline-alert-color-info-default-icon)}#body-wrapper{display:flex;flex-direction:column;align-items:flex-start;margin-left:8px;min-width:0;flex:1}#caption{align-self:stretch;--sinch-comp-title-font:var(--sinch-comp-inline-alert-font-title)}:host([type=success]) #caption{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-success-default-text)}:host([type=warn]) #caption{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-warning-default-text)}:host([type=error]) #caption{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-error-default-text)}:host([type=info]) #caption{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-info-default-text)}#text{display:flex;flex-direction:column;gap:8px;margin-top:0;align-self:stretch;--sinch-comp-rich-text-font:var(--sinch-comp-inline-alert-font-body)}:host([type=success]) #text{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-success-default-text)}:host([type=warn]) #text{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-warning-default-text)}:host([type=error]) #text{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-error-default-text)}:host([type=info]) #text{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-info-default-text)}:host([caption=""]) #text,:host([caption]) #text{margin-top:4px}#action{width:100%;display:flex;margin-top:16px;min-width:0;gap:16px}#action.empty{display:none}#close{margin-left:16px}#close.empty{display:none}</style><div id="wrapper"><sinch-icon icons-version="2" name="" id="icon"></sinch-icon><div id="body-wrapper"><sinch-title id="caption" level="3" type="s"></sinch-title><sinch-rich-text id="text"></sinch-rich-text><div id="action"><slot name="action"></slot></div></div><div id="close"><slot name="close"></slot></div></div>';
9
+ const templateHTML = '<style>:host{display:block}#wrapper{display:flex;flex-direction:row;align-items:flex-start;padding:16px;border-radius:var(--sinch-comp-inline-alert-shape-radius);box-sizing:border-box;width:100%}:host([type=success]) #wrapper{background-color:var(--sinch-comp-inline-alert-color-success-default-background)}:host([type=warn]) #wrapper{background-color:var(--sinch-comp-inline-alert-color-warning-default-background)}:host([type=error]) #wrapper{background-color:var(--sinch-comp-inline-alert-color-error-default-background)}:host([type=info]) #wrapper{background-color:var(--sinch-comp-inline-alert-color-info-default-background)}#spinner{display:none}:host([type=success]) #icon,:host([type=success]) #spinner{--sinch-global-color-icon:var(--sinch-comp-inline-alert-color-success-default-icon)}:host([type=warn]) #icon,:host([type=warn]) #spinner{--sinch-global-color-icon:var(--sinch-comp-inline-alert-color-warning-default-icon)}:host([type=error]) #icon,:host([type=error]) #spinner{--sinch-global-color-icon:var(--sinch-comp-inline-alert-color-error-default-icon)}:host([type=info]) #icon,:host([type=info]) #spinner{--sinch-global-color-icon:var(--sinch-comp-inline-alert-color-info-default-icon)}:host([loading]) #icon{display:none}:host([loading]) #spinner{display:block}#body-wrapper{display:flex;flex-direction:column;align-items:flex-start;margin-left:8px;min-width:0;flex:1}#caption{align-self:stretch;--sinch-comp-title-font:var(--sinch-comp-inline-alert-font-title)}:host([type=success]) #caption{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-success-default-text)}:host([type=warn]) #caption{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-warning-default-text)}:host([type=error]) #caption{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-error-default-text)}:host([type=info]) #caption{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-info-default-text)}#text{display:flex;flex-direction:column;gap:8px;margin-top:0;align-self:stretch;--sinch-comp-rich-text-font:var(--sinch-comp-inline-alert-font-body)}:host([type=success]) #text{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-success-default-text)}:host([type=warn]) #text{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-warning-default-text)}:host([type=error]) #text{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-error-default-text)}:host([type=info]) #text{--sinch-global-color-text:var(--sinch-comp-inline-alert-color-info-default-text)}:host([caption=""]) #text,:host([caption]) #text{margin-top:4px}#action{width:100%;display:flex;margin-top:16px;min-width:0;gap:16px}#action.empty{display:none}#close{margin-left:16px}#close.empty{display:none}</style><div id="wrapper"><sinch-spinner id="spinner" size="m"></sinch-spinner><sinch-icon icons-version="2" name="" id="icon"></sinch-icon><div id="body-wrapper"><sinch-title id="caption" level="3" type="s"></sinch-title><sinch-rich-text id="text"></sinch-rich-text><div id="action"><slot name="action"></slot></div></div><div id="close"><slot name="close"></slot></div></div>';
9
10
  const template = document.createElement("template");
10
11
  template.innerHTML = templateHTML;
11
12
  class InlineAlert extends NectaryElement {
@@ -32,6 +33,7 @@ class InlineAlert extends NectaryElement {
32
33
  super.connectedCallback();
33
34
  this.setAttribute("aria-atomic", "true");
34
35
  this.setAttribute("aria-live", "polite");
36
+ updateAttribute(this, "aria-busy", this.loading ? "true" : "false");
35
37
  this.#$closeSlot.addEventListener("slotchange", this.#onCloseSlotChange);
36
38
  this.#$actionSlot.addEventListener(
37
39
  "slotchange",
@@ -52,7 +54,7 @@ class InlineAlert extends NectaryElement {
52
54
  );
53
55
  }
54
56
  static get observedAttributes() {
55
- return ["text", "caption", "type", "icon"];
57
+ return ["text", "caption", "type", "icon", "loading"];
56
58
  }
57
59
  attributeChangedCallback(name, _, newVal) {
58
60
  switch (name) {
@@ -72,6 +74,10 @@ class InlineAlert extends NectaryElement {
72
74
  updateAttribute(this.#$icon, "name", newVal);
73
75
  break;
74
76
  }
77
+ case "loading": {
78
+ updateExplicitBooleanAttribute(this, "aria-busy", isAttrTrue(newVal));
79
+ break;
80
+ }
75
81
  }
76
82
  }
77
83
  get type() {
@@ -98,6 +104,12 @@ class InlineAlert extends NectaryElement {
98
104
  set icon(value) {
99
105
  updateAttribute(this, "icon", value);
100
106
  }
107
+ get loading() {
108
+ return getBooleanAttribute(this, "loading");
109
+ }
110
+ set loading(value) {
111
+ updateBooleanAttribute(this, "loading", value);
112
+ }
101
113
  #onCloseSlotChange = () => {
102
114
  setClass(
103
115
  this.#$closeWrapper,
@@ -6,6 +6,7 @@ export type TSinchInlineAlertProps = {
6
6
  text?: string | null;
7
7
  caption?: string;
8
8
  icon?: TSinchIcons;
9
+ loading?: boolean;
9
10
  };
10
11
  export type TSinchInlineAlertStyle = {
11
12
  '--sinch-comp-inline-alert-shape-radius'?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nectary/components",
3
- "version": "5.27.0",
3
+ "version": "5.29.0",
4
4
  "files": [
5
5
  "**/*/*.css",
6
6
  "**/*/*.json",
@@ -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;
@@ -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
  };