sonner-wc 0.1.2 → 0.3.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/dist/sonner-wc.js CHANGED
@@ -14,8 +14,6 @@ var WARNING_ICON = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"
14
14
  var INFO_ICON = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" height="20" width="20"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.25.25 0 01.244.304l-.459 2.066A1.75 1.75 0 0010.747 15H11a.75.75 0 000-1.5h-.253a.25.25 0 01-.244-.304l.459-2.066A1.75 1.75 0 009.253 9H9z" clip-rule="evenodd"/></svg>';
15
15
  var ERROR_ICON = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" height="20" width="20"><path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-5a.75.75 0 01.75.75v4.5a.75.75 0 01-1.5 0v-4.5A.75.75 0 0110 5zm0 10a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"/></svg>';
16
16
  var CLOSE_ICON = '<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>';
17
- var SPINNER_BARS = Array.from({ length: 12 }, () => '<div class="sonner-loading-bar"></div>').join("");
18
- var LOADING_SPINNER = '<div class="sonner-loading-wrapper" data-visible="true"><div class="sonner-spinner">' + SPINNER_BARS + "</div></div>";
19
17
  function getTypeIcon(type) {
20
18
  switch (type) {
21
19
  case "success":
@@ -68,6 +66,10 @@ function dismissAllToasts() {
68
66
  t.dismissAll();
69
67
  }
70
68
 
69
+ // src/ssr.ts
70
+ var HTMLElementCtor = typeof HTMLElement !== "undefined" ? HTMLElement : class {
71
+ };
72
+
71
73
  // src/styles/toaster.css
72
74
  var toaster_default = `:host {
73
75
  position: fixed;
@@ -119,15 +121,30 @@ var toaster_default = `:host {
119
121
  --toast-close-button-start: 0;
120
122
  --toast-close-button-end: unset;
121
123
  --toast-close-button-transform: translate(-35%, -35%);
124
+ inset: unset;
125
+ border: none;
126
+ overflow: visible;
127
+ background: transparent;
122
128
  box-sizing: border-box;
123
129
  padding: 0;
124
130
  margin: 0;
125
131
  outline: none;
132
+ contain: layout style;
126
133
  z-index: 999999999;
127
134
  transition: transform 400ms ease;
128
135
  display: block;
129
136
  }
130
137
 
138
+ ::backdrop {
139
+ display: none;
140
+ }
141
+
142
+ :host(:focus-visible) {
143
+ outline: 2px solid var(--gray9);
144
+ outline-offset: 4px;
145
+ border-radius: var(--border-radius);
146
+ }
147
+
131
148
  [data-frame] {
132
149
  display: block;
133
150
  width: 100%;
@@ -245,6 +262,8 @@ var toast_default = `:host {
245
262
  position: absolute;
246
263
  opacity: 0;
247
264
  transform: var(--y);
265
+ contain: layout style;
266
+ will-change: transform, opacity;
248
267
  touch-action: none;
249
268
  transition:
250
269
  transform 400ms,
@@ -399,16 +418,14 @@ var toast_default = `:host {
399
418
  animation: sonner-fade-in 300ms ease forwards;
400
419
  }
401
420
 
402
- [data-button],
403
- ::slotted(button[slot='action']),
404
- ::slotted(button[slot='cancel']) {
421
+ [data-button] {
405
422
  border-radius: 4px;
406
423
  padding-left: 8px;
407
424
  padding-right: 8px;
408
425
  height: 24px;
409
426
  font-size: 12px;
410
427
  background: var(--normal-text);
411
- color: contrast-color(var(--normal-text));
428
+ color: var(--normal-bg);
412
429
  margin-left: var(--toast-button-margin-start);
413
430
  margin-right: var(--toast-button-margin-end);
414
431
  border: none;
@@ -426,8 +443,7 @@ var toast_default = `:host {
426
443
  [data-button]:focus-visible {
427
444
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.4);
428
445
  }
429
- [data-cancel],
430
- ::slotted(button[slot='cancel']) {
446
+ [data-cancel] {
431
447
  color: var(--normal-text);
432
448
  background: var(--cancel-bg, rgba(0, 0, 0, 0.08));
433
449
  }
@@ -814,16 +830,34 @@ function getToastSheet() {
814
830
  }
815
831
 
816
832
  // src/sonner-toast.ts
817
- var HTMLElementCtor = typeof HTMLElement !== "undefined" ? HTMLElement : class {
818
- };
819
833
  function isUrgentType(type) {
820
834
  return type === "error" || type === "warning";
821
835
  }
822
- function setContent(host, slotName, value) {
836
+ function boolAttr(host, name) {
837
+ return host.hasAttribute(name) && host.getAttribute(name) !== "false";
838
+ }
839
+ function toggleAttr(host, name, on) {
840
+ if (on)
841
+ host.setAttribute(name, "");
842
+ else
843
+ host.removeAttribute(name);
844
+ }
845
+ function clearSlot(host, slotName) {
823
846
  for (const child of Array.from(host.children)) {
824
847
  if (child.getAttribute("slot") === slotName)
825
848
  host.removeChild(child);
826
849
  }
850
+ }
851
+ function slotElementFromHTML(html, slotName) {
852
+ const tpl = document.createElement("template");
853
+ tpl.innerHTML = html;
854
+ const el = tpl.content.firstElementChild;
855
+ if (el)
856
+ el.setAttribute("slot", slotName);
857
+ return el;
858
+ }
859
+ function setContent(host, slotName, value) {
860
+ clearSlot(host, slotName);
827
861
  if (value == null || value === "")
828
862
  return;
829
863
  const resolved = typeof value === "function" ? value() : value;
@@ -838,10 +872,14 @@ function setContent(host, slotName, value) {
838
872
  host.appendChild(span);
839
873
  }
840
874
  }
841
- function setButtonSlot(host, slotName, value, defaultCloseHandler) {
842
- for (const child of Array.from(host.children)) {
843
- if (child.getAttribute("slot") === slotName)
844
- host.removeChild(child);
875
+ function setButtonSlot(host, shadow, slotName, value, defaultCloseHandler) {
876
+ clearSlot(host, slotName);
877
+ const container = shadow.querySelector(`[data-${slotName}-host]`);
878
+ if (container) {
879
+ for (const child of Array.from(container.children)) {
880
+ if (child.tagName === "BUTTON")
881
+ container.removeChild(child);
882
+ }
845
883
  }
846
884
  if (!value)
847
885
  return;
@@ -850,13 +888,12 @@ function setButtonSlot(host, slotName, value, defaultCloseHandler) {
850
888
  host.appendChild(value);
851
889
  return;
852
890
  }
891
+ if (!container)
892
+ return;
853
893
  const btn = document.createElement("button");
854
- btn.setAttribute("slot", slotName);
855
894
  btn.setAttribute("type", "button");
856
- if (slotName === "cancel")
857
- btn.setAttribute("data-cancel", "");
858
- else
859
- btn.setAttribute("data-action", "");
895
+ btn.setAttribute("data-button", "");
896
+ btn.setAttribute(slotName === "cancel" ? "data-cancel" : "data-action", "");
860
897
  btn.textContent = value.label;
861
898
  btn.addEventListener("click", (e) => {
862
899
  value.onClick?.(e);
@@ -864,27 +901,33 @@ function setButtonSlot(host, slotName, value, defaultCloseHandler) {
864
901
  return;
865
902
  defaultCloseHandler();
866
903
  });
867
- host.appendChild(btn);
904
+ container.appendChild(btn);
868
905
  }
869
906
  var SPINNER_BARS_HTML = Array.from({ length: 12 }, () => '<div class="sonner-loading-bar"></div>').join("");
870
- var SHADOW_TEMPLATE = `
871
- <div data-frame part="frame">
872
- <button type="button" data-close-button part="close-button" hidden aria-label="Close toast"></button>
873
- <div data-icon part="icon">
874
- <slot name="icon"></slot>
875
- <div class="sonner-loading-wrapper" aria-hidden="true">
876
- <div class="sonner-spinner">${SPINNER_BARS_HTML}</div>
907
+ var SHADOW_TEMPLATE = (() => {
908
+ if (typeof document === "undefined")
909
+ return null;
910
+ const tpl = document.createElement("template");
911
+ tpl.innerHTML = `
912
+ <div data-frame part="frame">
913
+ <button type="button" data-close-button part="close-button" hidden aria-label="Close toast"></button>
914
+ <div data-icon part="icon">
915
+ <slot name="icon"></slot>
916
+ <div class="sonner-loading-wrapper" aria-hidden="true">
917
+ <div class="sonner-spinner">${SPINNER_BARS_HTML}</div>
918
+ </div>
877
919
  </div>
920
+ <div data-content part="content">
921
+ <div data-title part="title"><slot name="title"></slot></div>
922
+ <div data-description part="description"><slot name="description"></slot></div>
923
+ </div>
924
+ <div data-cancel-host part="cancel"><slot name="cancel"></slot></div>
925
+ <div data-action-host part="action"><slot name="action"></slot></div>
926
+ <slot part="custom"></slot>
878
927
  </div>
879
- <div data-content part="content">
880
- <div data-title part="title"><slot name="title"></slot></div>
881
- <div data-description part="description"><slot name="description"></slot></div>
882
- </div>
883
- <slot name="cancel"></slot>
884
- <slot name="action"></slot>
885
- <slot part="custom"></slot>
886
- </div>
887
- `;
928
+ `;
929
+ return tpl;
930
+ })();
888
931
 
889
932
  class SonnerToast extends HTMLElementCtor {
890
933
  static get observedAttributes() {
@@ -916,9 +959,7 @@ class SonnerToast extends HTMLElementCtor {
916
959
  super();
917
960
  this.#shadow = this.attachShadow({ mode: "open" });
918
961
  this.#shadow.adoptedStyleSheets = [getToastSheet()];
919
- const tpl = document.createElement("template");
920
- tpl.innerHTML = SHADOW_TEMPLATE;
921
- this.#shadow.appendChild(tpl.content.cloneNode(true));
962
+ this.#shadow.appendChild(SHADOW_TEMPLATE.content.cloneNode(true));
922
963
  this.#closeBtn = this.#shadow.querySelector("[data-close-button]");
923
964
  this.#iconWrap = this.#shadow.querySelector("[data-icon]");
924
965
  this.#titleWrap = this.#shadow.querySelector("[data-title]");
@@ -1061,32 +1102,16 @@ class SonnerToast extends HTMLElementCtor {
1061
1102
  this.#remainingTime = 0;
1062
1103
  timerNeedsReset = true;
1063
1104
  }
1064
- if (options.dismissible !== undefined) {
1065
- if (options.dismissible)
1066
- this.setAttribute("dismissible", "");
1067
- else
1068
- this.removeAttribute("dismissible");
1069
- }
1105
+ if (options.dismissible !== undefined)
1106
+ toggleAttr(this, "dismissible", options.dismissible);
1070
1107
  if (options.position !== undefined)
1071
1108
  this.setAttribute("position", options.position);
1072
- if (options.closeButton !== undefined) {
1073
- if (options.closeButton)
1074
- this.setAttribute("close-button", "");
1075
- else
1076
- this.removeAttribute("close-button");
1077
- }
1078
- if (options.richColors !== undefined) {
1079
- if (options.richColors)
1080
- this.setAttribute("rich-colors", "");
1081
- else
1082
- this.removeAttribute("rich-colors");
1083
- }
1084
- if (options.invert !== undefined) {
1085
- if (options.invert)
1086
- this.setAttribute("invert", "");
1087
- else
1088
- this.removeAttribute("invert");
1089
- }
1109
+ if (options.closeButton !== undefined)
1110
+ toggleAttr(this, "close-button", options.closeButton);
1111
+ if (options.richColors !== undefined)
1112
+ toggleAttr(this, "rich-colors", options.richColors);
1113
+ if (options.invert !== undefined)
1114
+ toggleAttr(this, "invert", options.invert);
1090
1115
  if (options.icon !== undefined)
1091
1116
  this.setIcon(options.icon);
1092
1117
  if (options.title !== undefined)
@@ -1094,10 +1119,10 @@ class SonnerToast extends HTMLElementCtor {
1094
1119
  if (options.description !== undefined)
1095
1120
  this.setDescription(options.description);
1096
1121
  if (options.action !== undefined) {
1097
- setButtonSlot(this, "action", options.action, () => this.dismiss());
1122
+ setButtonSlot(this, this.#shadow, "action", options.action, () => this.dismiss());
1098
1123
  }
1099
1124
  if (options.cancel !== undefined) {
1100
- setButtonSlot(this, "cancel", options.cancel, () => this.dismiss());
1125
+ setButtonSlot(this, this.#shadow, "cancel", options.cancel, () => this.dismiss());
1101
1126
  }
1102
1127
  if (options.className)
1103
1128
  this.className = options.className;
@@ -1114,8 +1139,7 @@ class SonnerToast extends HTMLElementCtor {
1114
1139
  if (timerNeedsReset)
1115
1140
  this.#resetTimer();
1116
1141
  if (this.#mounted && typeChanged && !wasUrgent && isUrgentType(this.toastType)) {
1117
- const titleEl = Array.from(this.children).find((c) => c.getAttribute("slot") === "title");
1118
- const text = titleEl?.textContent?.trim();
1142
+ const text = this.#getTitleText();
1119
1143
  if (text) {
1120
1144
  const toaster = this.closest("sonner-toaster");
1121
1145
  toaster?.announceUrgent(text);
@@ -1127,23 +1151,23 @@ class SonnerToast extends HTMLElementCtor {
1127
1151
  setContent(this, "title", value);
1128
1152
  this.#applyCloseButtonAriaLabel();
1129
1153
  }
1154
+ #getTitleText() {
1155
+ const titleEl = Array.from(this.children).find((c) => c.getAttribute("slot") === "title");
1156
+ return titleEl?.textContent?.trim() ?? "";
1157
+ }
1130
1158
  #applyCloseButtonAriaLabel() {
1131
1159
  if (this.#closeButtonAriaLabel) {
1132
1160
  this.#closeBtn.setAttribute("aria-label", this.#closeButtonAriaLabel);
1133
1161
  return;
1134
1162
  }
1135
- const titleEl = Array.from(this.children).find((c) => c.getAttribute("slot") === "title");
1136
- const text = titleEl?.textContent?.trim();
1163
+ const text = this.#getTitleText();
1137
1164
  this.#closeBtn.setAttribute("aria-label", text ? `Close: ${text}` : "Close toast");
1138
1165
  }
1139
1166
  setDescription(value) {
1140
1167
  setContent(this, "description", value);
1141
1168
  }
1142
1169
  setIcon(value) {
1143
- for (const child of Array.from(this.children)) {
1144
- if (child.getAttribute("slot") === "icon")
1145
- this.removeChild(child);
1146
- }
1170
+ clearSlot(this, "icon");
1147
1171
  if (value == null)
1148
1172
  return;
1149
1173
  if (value instanceof Node) {
@@ -1151,13 +1175,9 @@ class SonnerToast extends HTMLElementCtor {
1151
1175
  value.setAttribute("slot", "icon");
1152
1176
  this.appendChild(value);
1153
1177
  } else {
1154
- const tpl = document.createElement("template");
1155
- tpl.innerHTML = value;
1156
- const el = tpl.content.firstElementChild;
1157
- if (el) {
1158
- el.setAttribute("slot", "icon");
1178
+ const el = slotElementFromHTML(value, "icon");
1179
+ if (el)
1159
1180
  this.appendChild(el);
1160
- }
1161
1181
  }
1162
1182
  }
1163
1183
  setHandlers(handlers) {
@@ -1183,36 +1203,25 @@ class SonnerToast extends HTMLElementCtor {
1183
1203
  const urgent = isUrgentType(type);
1184
1204
  this.setAttribute("role", urgent ? "alert" : "status");
1185
1205
  this.setAttribute("aria-live", urgent ? "assertive" : "polite");
1206
+ for (const c of Array.from(this.children)) {
1207
+ if (c.getAttribute("slot") === "icon" && c.hasAttribute("data-sonner-default-icon"))
1208
+ this.removeChild(c);
1209
+ }
1186
1210
  if (type === "loading") {
1187
1211
  this.setAttribute("data-promise", "true");
1188
- for (const c of Array.from(this.children)) {
1189
- if (c.getAttribute("slot") === "icon" && c.hasAttribute("data-sonner-default-icon"))
1190
- this.removeChild(c);
1191
- }
1192
- } else {
1193
- this.removeAttribute("data-promise");
1194
- const hasUserIcon = Array.from(this.children).some((c) => {
1195
- if (c.getAttribute("slot") !== "icon")
1196
- return false;
1197
- return !c.hasAttribute("data-sonner-default-icon");
1198
- });
1199
- if (!hasUserIcon) {
1200
- for (const c of Array.from(this.children)) {
1201
- if (c.getAttribute("slot") === "icon" && c.hasAttribute("data-sonner-default-icon"))
1202
- this.removeChild(c);
1203
- }
1204
- const builtin = getTypeIcon(type);
1205
- if (builtin) {
1206
- const tpl = document.createElement("template");
1207
- tpl.innerHTML = builtin;
1208
- const el = tpl.content.firstElementChild;
1209
- if (el) {
1210
- el.setAttribute("slot", "icon");
1211
- el.setAttribute("data-sonner-default-icon", "");
1212
- this.appendChild(el);
1213
- }
1214
- }
1215
- }
1212
+ return;
1213
+ }
1214
+ this.removeAttribute("data-promise");
1215
+ const hasUserIcon = Array.from(this.children).some((c) => c.getAttribute("slot") === "icon");
1216
+ if (hasUserIcon)
1217
+ return;
1218
+ const builtin = getTypeIcon(type);
1219
+ if (!builtin)
1220
+ return;
1221
+ const el = slotElementFromHTML(builtin, "icon");
1222
+ if (el) {
1223
+ el.setAttribute("data-sonner-default-icon", "");
1224
+ this.appendChild(el);
1216
1225
  }
1217
1226
  }
1218
1227
  #applyDismissible() {
@@ -1230,16 +1239,13 @@ class SonnerToast extends HTMLElementCtor {
1230
1239
  this.setAttribute("data-x-position", x);
1231
1240
  }
1232
1241
  #applyCloseButton() {
1233
- const show = this.hasAttribute("close-button") && this.getAttribute("close-button") !== "false";
1234
- this.#closeBtn.hidden = !show;
1242
+ this.#closeBtn.hidden = !boolAttr(this, "close-button");
1235
1243
  }
1236
1244
  #applyRichColors() {
1237
- const rich = this.hasAttribute("rich-colors") && this.getAttribute("rich-colors") !== "false";
1238
- this.setAttribute("data-rich-colors", String(rich));
1245
+ this.setAttribute("data-rich-colors", String(boolAttr(this, "rich-colors")));
1239
1246
  }
1240
1247
  #applyInvert() {
1241
- const invert = this.hasAttribute("invert") && this.getAttribute("invert") !== "false";
1242
- this.setAttribute("data-invert", String(invert));
1248
+ this.setAttribute("data-invert", String(boolAttr(this, "invert")));
1243
1249
  }
1244
1250
  #readDuration() {
1245
1251
  const raw = this.getAttribute("duration");
@@ -1248,7 +1254,7 @@ class SonnerToast extends HTMLElementCtor {
1248
1254
  if (raw === "Infinity")
1249
1255
  return Infinity;
1250
1256
  const n = Number(raw);
1251
- return Number.isFinite(n) || n === Infinity ? n : null;
1257
+ return Number.isFinite(n) ? n : null;
1252
1258
  }
1253
1259
  #effectiveDuration() {
1254
1260
  const explicit = this.#duration ?? this.#readDuration();
@@ -1410,8 +1416,6 @@ if (typeof customElements !== "undefined" && !customElements.get("sonner-toast")
1410
1416
  customElements.define("sonner-toast", SonnerToast);
1411
1417
  }
1412
1418
  // src/sonner-toaster.ts
1413
- var HTMLElementCtor2 = typeof HTMLElement !== "undefined" ? HTMLElement : class {
1414
- };
1415
1419
  function parseOffsetValue(v, fallback) {
1416
1420
  if (v == null)
1417
1421
  return fallback;
@@ -1432,7 +1436,7 @@ function applyOffsetVars(host, opts, prefix, fallback) {
1432
1436
  }
1433
1437
  }
1434
1438
 
1435
- class SonnerToaster extends HTMLElementCtor2 {
1439
+ class SonnerToaster extends HTMLElementCtor {
1436
1440
  static get observedAttributes() {
1437
1441
  return [
1438
1442
  "position",
@@ -1501,12 +1505,18 @@ class SonnerToaster extends HTMLElementCtor2 {
1501
1505
  this.addEventListener("sonner-toast-mounted", () => this.#reflow({ remeasure: true }));
1502
1506
  }
1503
1507
  connectedCallback() {
1508
+ if (typeof this.showPopover === "function") {
1509
+ if (!this.hasAttribute("popover"))
1510
+ this.setAttribute("popover", "manual");
1511
+ this.showPopover();
1512
+ }
1504
1513
  if (!this.hasAttribute("tabindex"))
1505
1514
  this.tabIndex = -1;
1506
1515
  if (!this.hasAttribute("role"))
1507
1516
  this.setAttribute("role", "region");
1508
1517
  if (!this.hasAttribute("aria-label"))
1509
1518
  this.setAttribute("aria-label", this.getAttribute("container-aria-label") ?? "Notifications");
1519
+ this.#applyHotkeyHints();
1510
1520
  this.setAttribute("data-sonner-toaster", "");
1511
1521
  this.#applyPosition();
1512
1522
  this.#applyTheme();
@@ -1528,6 +1538,11 @@ class SonnerToaster extends HTMLElementCtor2 {
1528
1538
  this.#reflow({ remeasure: true });
1529
1539
  }
1530
1540
  disconnectedCallback() {
1541
+ if (typeof this.hidePopover === "function") {
1542
+ try {
1543
+ this.hidePopover();
1544
+ } catch {}
1545
+ }
1531
1546
  this.#childObserver.disconnect();
1532
1547
  this.#resizeObserver.disconnect();
1533
1548
  document.removeEventListener("keydown", this.#onKeyDown);
@@ -1577,6 +1592,9 @@ class SonnerToaster extends HTMLElementCtor2 {
1577
1592
  case "container-aria-label":
1578
1593
  this.setAttribute("aria-label", this.getAttribute("container-aria-label") ?? "Notifications");
1579
1594
  break;
1595
+ case "hotkey":
1596
+ this.#applyHotkeyHints();
1597
+ break;
1580
1598
  }
1581
1599
  }
1582
1600
  addToast(el) {
@@ -1598,12 +1616,15 @@ class SonnerToaster extends HTMLElementCtor2 {
1598
1616
  #getPosition() {
1599
1617
  return this.getAttribute("position") || "bottom-right";
1600
1618
  }
1601
- #applyPosition() {
1619
+ #applyPositionTo(target) {
1602
1620
  const [y, x] = this.#getPosition().split("-");
1603
1621
  if (y)
1604
- this.setAttribute("data-y-position", y);
1622
+ target.setAttribute("data-y-position", y);
1605
1623
  if (x)
1606
- this.setAttribute("data-x-position", x);
1624
+ target.setAttribute("data-x-position", x);
1625
+ }
1626
+ #applyPosition() {
1627
+ this.#applyPositionTo(this);
1607
1628
  }
1608
1629
  #applyTheme() {
1609
1630
  if (this.#themeMql && this.#themeMqlHandler) {
@@ -1647,13 +1668,8 @@ class SonnerToaster extends HTMLElementCtor2 {
1647
1668
  }
1648
1669
  #applyToastPositions() {
1649
1670
  for (const t of this.#toasts) {
1650
- if (!t.hasAttribute("position")) {
1651
- const [y, x] = this.#getPosition().split("-");
1652
- if (y)
1653
- t.setAttribute("data-y-position", y);
1654
- if (x)
1655
- t.setAttribute("data-x-position", x);
1656
- }
1671
+ if (!t.hasAttribute("position"))
1672
+ this.#applyPositionTo(t);
1657
1673
  }
1658
1674
  }
1659
1675
  #propagateBoolean(attr) {
@@ -1668,13 +1684,8 @@ class SonnerToaster extends HTMLElementCtor2 {
1668
1684
  }
1669
1685
  }
1670
1686
  #decorateToast(toast) {
1671
- if (!toast.hasAttribute("position")) {
1672
- const [y, x] = this.#getPosition().split("-");
1673
- if (y)
1674
- toast.setAttribute("data-y-position", y);
1675
- if (x)
1676
- toast.setAttribute("data-x-position", x);
1677
- }
1687
+ if (!toast.hasAttribute("position"))
1688
+ this.#applyPositionTo(toast);
1678
1689
  if (!toast.hasAttribute("duration")) {
1679
1690
  const inherit = this.getAttribute("duration");
1680
1691
  if (inherit)
@@ -1765,6 +1776,8 @@ class SonnerToaster extends HTMLElementCtor2 {
1765
1776
  };
1766
1777
  #onWindowResize = () => this.#reflow({ remeasure: true });
1767
1778
  #onKeyDown = (event) => {
1779
+ if (event.defaultPrevented)
1780
+ return;
1768
1781
  const target = event.target;
1769
1782
  if (target) {
1770
1783
  const tag = target.tagName;
@@ -1773,12 +1786,13 @@ class SonnerToaster extends HTMLElementCtor2 {
1773
1786
  }
1774
1787
  }
1775
1788
  const hotkey = this.#hotkey();
1776
- const matches = hotkey.every((key) => {
1777
- if (key === "altKey" || key === "ctrlKey" || key === "shiftKey" || key === "metaKey") {
1789
+ const allModifiers = ["altKey", "ctrlKey", "shiftKey", "metaKey"];
1790
+ const matches = hotkey.length > 0 && hotkey.every((key) => {
1791
+ if (allModifiers.includes(key)) {
1778
1792
  return event[key];
1779
1793
  }
1780
1794
  return event.code === key;
1781
- });
1795
+ }) && allModifiers.every((m) => hotkey.includes(m) || !event[m]);
1782
1796
  if (matches) {
1783
1797
  this.#expanded = true;
1784
1798
  for (const t of this.#toasts)
@@ -1796,10 +1810,56 @@ class SonnerToaster extends HTMLElementCtor2 {
1796
1810
  };
1797
1811
  #hotkey() {
1798
1812
  const raw = this.getAttribute("hotkey");
1799
- if (!raw)
1813
+ if (raw === null)
1800
1814
  return DEFAULT_HOTKEY;
1801
- return raw.split("+").map((s) => s.trim());
1815
+ const trimmed = raw.trim();
1816
+ if (trimmed === "" || trimmed.toLowerCase() === "none")
1817
+ return [];
1818
+ return trimmed.split("+").map((s) => s.trim());
1819
+ }
1820
+ #applyHotkeyHints() {
1821
+ const formatted = formatHotkeyForAria(this.#hotkey());
1822
+ if (!formatted) {
1823
+ if (this.#ownsAriaKeyshortcuts) {
1824
+ this.removeAttribute("aria-keyshortcuts");
1825
+ this.#ownsAriaKeyshortcuts = false;
1826
+ }
1827
+ if (this.#ownsTitle) {
1828
+ this.removeAttribute("title");
1829
+ this.#ownsTitle = false;
1830
+ }
1831
+ return;
1832
+ }
1833
+ if (!this.hasAttribute("aria-keyshortcuts") || this.#ownsAriaKeyshortcuts) {
1834
+ this.setAttribute("aria-keyshortcuts", formatted);
1835
+ this.#ownsAriaKeyshortcuts = true;
1836
+ }
1837
+ if (!this.hasAttribute("title") || this.#ownsTitle) {
1838
+ this.setAttribute("title", `Press ${formatted} to expand notifications`);
1839
+ this.#ownsTitle = true;
1840
+ }
1802
1841
  }
1842
+ #ownsAriaKeyshortcuts = false;
1843
+ #ownsTitle = false;
1844
+ }
1845
+ function formatHotkeyForAria(tokens) {
1846
+ if (tokens.length === 0)
1847
+ return "";
1848
+ return tokens.map((t) => {
1849
+ if (t === "altKey")
1850
+ return "Alt";
1851
+ if (t === "ctrlKey")
1852
+ return "Control";
1853
+ if (t === "shiftKey")
1854
+ return "Shift";
1855
+ if (t === "metaKey")
1856
+ return "Meta";
1857
+ if (/^Key[A-Z]$/.test(t))
1858
+ return t.slice(3);
1859
+ if (/^Digit[0-9]$/.test(t))
1860
+ return t.slice(5);
1861
+ return t;
1862
+ }).join("+");
1803
1863
  }
1804
1864
  if (typeof customElements !== "undefined" && !customElements.get("sonner-toaster")) {
1805
1865
  customElements.define("sonner-toaster", SonnerToaster);
@@ -1958,5 +2018,5 @@ export {
1958
2018
  SonnerToast
1959
2019
  };
1960
2020
 
1961
- //# debugId=038A38E1BC01A40B64756E2164756E21
2021
+ //# debugId=6DA0059CEA8E1A6464756E2164756E21
1962
2022
  //# sourceMappingURL=sonner-wc.js.map