@birdapi/velinstyle 0.6.1 → 0.7.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.
@@ -10,10 +10,15 @@
10
10
  "summary",
11
11
  "details"
12
12
  ].join(", ");
13
+ function isFocusable(el) {
14
+ if (el.hasAttribute("disabled") || el.getAttribute("aria-hidden") === "true") return false;
15
+ if (el.closest("[inert]")) return false;
16
+ const style = el.ownerDocument.defaultView?.getComputedStyle(el);
17
+ if (style && (style.visibility === "hidden" || style.display === "none")) return false;
18
+ return el.getClientRects().length > 0;
19
+ }
13
20
  function getFocusableElements(root) {
14
- return [...root.querySelectorAll(FOCUSABLE_SELECTOR)].filter(
15
- (el) => !el.hasAttribute("disabled") && el.offsetParent !== null
16
- );
21
+ return [...root.querySelectorAll(FOCUSABLE_SELECTOR)].filter(isFocusable);
17
22
  }
18
23
  function trapFocus(root, event) {
19
24
  if (event.key !== "Tab") return;
@@ -69,6 +74,23 @@
69
74
  element.focus();
70
75
  }
71
76
  }
77
+ var _inertSiblings = [];
78
+ function setBackgroundInert(except) {
79
+ _inertSiblings = [];
80
+ for (const child of document.body.children) {
81
+ if (child === except || child.contains(except)) continue;
82
+ if (!child.hasAttribute("inert")) {
83
+ child.setAttribute("inert", "");
84
+ _inertSiblings.push(child);
85
+ }
86
+ }
87
+ }
88
+ function clearBackgroundInert() {
89
+ for (const el of _inertSiblings) {
90
+ el.removeAttribute("inert");
91
+ }
92
+ _inertSiblings = [];
93
+ }
72
94
 
73
95
  // components/sanitize.js
74
96
  var ESCAPE_MAP = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
@@ -221,6 +243,7 @@
221
243
  }
222
244
  _open() {
223
245
  this._previouslyFocused = saveFocus();
246
+ setBackgroundInert(this);
224
247
  document.addEventListener("keydown", this._onKeydown);
225
248
  document.body.style.overflow = "hidden";
226
249
  requestAnimationFrame(() => {
@@ -231,6 +254,7 @@
231
254
  _close() {
232
255
  document.removeEventListener("keydown", this._onKeydown);
233
256
  document.body.style.overflow = "";
257
+ clearBackgroundInert();
234
258
  restoreFocus(this._previouslyFocused);
235
259
  }
236
260
  _onKeydown(event) {
@@ -309,6 +333,8 @@
309
333
  this.attachShadow({ mode: "open", delegatesFocus: true });
310
334
  this._onDocClick = this._onDocClick.bind(this);
311
335
  this._onKeydown = this._onKeydown.bind(this);
336
+ this._typeahead = "";
337
+ this._typeaheadTimer = null;
312
338
  }
313
339
  connectedCallback() {
314
340
  const align = this.getAttribute("align") || "start";
@@ -320,16 +346,29 @@
320
346
  </div>
321
347
  `;
322
348
  const triggerSlot = this.shadowRoot.querySelector('slot[name="trigger"]');
349
+ const menuSlot = this.shadowRoot.querySelector("slot:not([name])");
323
350
  triggerSlot.addEventListener("click", () => this.toggle());
324
351
  triggerSlot.addEventListener("slotchange", () => {
325
352
  const trigger = triggerSlot.assignedElements()[0];
326
353
  if (trigger) {
327
354
  trigger.setAttribute("aria-haspopup", "menu");
328
355
  trigger.setAttribute("aria-expanded", this.hasAttribute("open") ? "true" : "false");
356
+ const menuId = this._menuId || (this._menuId = `velin-dropdown-menu-${Math.random().toString(36).slice(2, 9)}`);
357
+ trigger.setAttribute("aria-controls", menuId);
358
+ this.shadowRoot.querySelector(".menu")?.setAttribute("id", menuId);
329
359
  }
330
360
  });
361
+ menuSlot?.addEventListener("slotchange", () => this._normalizeMenuItems());
362
+ this._normalizeMenuItems();
331
363
  this.addEventListener("keydown", this._onKeydown);
332
364
  }
365
+ _normalizeMenuItems() {
366
+ const items = this._getMenuItems();
367
+ items.forEach((el, i) => {
368
+ if (!el.hasAttribute("role")) el.setAttribute("role", "menuitem");
369
+ el.setAttribute("tabindex", i === 0 ? "0" : "-1");
370
+ });
371
+ }
333
372
  toggle() {
334
373
  if (this.hasAttribute("open")) {
335
374
  this.close();
@@ -373,9 +412,24 @@
373
412
  return;
374
413
  }
375
414
  const items = this._getMenuItems();
376
- if (items.length > 0) {
377
- rovingTabindex(this, items, event);
415
+ if (items.length === 0) return;
416
+ if (event.key.length === 1 && /[a-z0-9]/i.test(event.key)) {
417
+ clearTimeout(this._typeaheadTimer);
418
+ this._typeahead += event.key.toLowerCase();
419
+ this._typeaheadTimer = setTimeout(() => {
420
+ this._typeahead = "";
421
+ }, 500);
422
+ const match = items.find(
423
+ (el) => (el.textContent?.trim().toLowerCase() || "").startsWith(this._typeahead)
424
+ );
425
+ if (match) {
426
+ event.preventDefault();
427
+ items.forEach((item) => item.setAttribute("tabindex", item === match ? "0" : "-1"));
428
+ match.focus();
429
+ }
430
+ return;
378
431
  }
432
+ rovingTabindex(this, items, event);
379
433
  }
380
434
  disconnectedCallback() {
381
435
  document.removeEventListener("click", this._onDocClick, true);
@@ -423,12 +477,25 @@
423
477
  connectedCallback() {
424
478
  this.shadowRoot.innerHTML = `
425
479
  <style>${styles3}</style>
426
- <div role="region" part="region"><slot></slot></div>
480
+ <slot></slot>
427
481
  `;
428
482
  this._exclusive = this.hasAttribute("exclusive");
483
+ this._wireDetails();
429
484
  this.addEventListener("toggle", this._onToggle, true);
430
485
  this.addEventListener("keydown", this._onKeydown.bind(this));
431
486
  }
487
+ _wireDetails() {
488
+ let panelIndex = 0;
489
+ for (const details of this.querySelectorAll("details")) {
490
+ const summary = details.querySelector("summary");
491
+ const panel = details.querySelector(":scope > :not(summary)");
492
+ const panelId = panel?.id || `velin-accordion-panel-${++panelIndex}`;
493
+ if (panel && !panel.id) panel.id = panelId;
494
+ if (summary && panel) {
495
+ summary.setAttribute("aria-controls", panelId);
496
+ }
497
+ }
498
+ }
432
499
  _onToggle(event) {
433
500
  if (!this._exclusive) return;
434
501
  const openedDetail = event.target;
@@ -886,12 +953,13 @@
886
953
  connectedCallback() {
887
954
  const title = this.getAttribute("title") || "";
888
955
  const safeTitle = escapeHTML(title);
956
+ const titleId = "velin-drawer-title";
889
957
  this.shadowRoot.innerHTML = `
890
958
  <style>${styles6}</style>
891
959
  <div class="overlay" part="overlay"></div>
892
- <div class="drawer" role="dialog" aria-modal="true" aria-label="${safeTitle}" part="drawer">
960
+ <div class="drawer" role="dialog" aria-modal="true" aria-labelledby="${titleId}" part="drawer">
893
961
  <div class="header" part="header">
894
- <h2 class="title">${safeTitle}</h2>
962
+ <h2 class="title" id="${titleId}">${safeTitle}</h2>
895
963
  <button class="close-btn" aria-label="Close" part="close">&#215;</button>
896
964
  </div>
897
965
  <div class="body" part="body"><slot></slot></div>
@@ -912,6 +980,7 @@
912
980
  }
913
981
  _open() {
914
982
  this._prev = saveFocus();
983
+ setBackgroundInert(this);
915
984
  document.addEventListener("keydown", this._onKey);
916
985
  document.body.style.overflow = "hidden";
917
986
  requestAnimationFrame(() => {
@@ -922,6 +991,7 @@
922
991
  _close() {
923
992
  document.removeEventListener("keydown", this._onKey);
924
993
  document.body.style.overflow = "";
994
+ clearBackgroundInert();
925
995
  restoreFocus(this._prev);
926
996
  }
927
997
  _onKey(e) {
@@ -1039,7 +1109,6 @@
1039
1109
  transition: opacity 150ms ease, visibility 150ms ease;
1040
1110
  }
1041
1111
  :host([open]) .popover { opacity: 1; visibility: visible; }
1042
- /* Placement */
1043
1112
  .popover--top { inset-block-end: calc(100% + 0.5rem); inset-inline-start: 50%; transform: translateX(-50%); }
1044
1113
  .popover--bottom { inset-block-start: calc(100% + 0.5rem); inset-inline-start: 50%; transform: translateX(-50%); }
1045
1114
  .popover--start { inset-inline-end: calc(100% + 0.5rem); inset-block-start: 50%; transform: translateY(-50%); }
@@ -1052,6 +1121,7 @@
1052
1121
  }
1053
1122
  @media (prefers-reduced-motion: reduce) { .popover { transition: none; } }
1054
1123
  `;
1124
+ var popoverId = 0;
1055
1125
  var VelinPopover = class extends HTMLElement {
1056
1126
  static get observedAttributes() {
1057
1127
  return ["open"];
@@ -1059,42 +1129,52 @@
1059
1129
  constructor() {
1060
1130
  super();
1061
1131
  this.attachShadow({ mode: "open" });
1132
+ this._popoverId = `velin-popover-${++popoverId}`;
1062
1133
  this._onOutside = this._onOutside.bind(this);
1063
1134
  this._onKey = this._onKey.bind(this);
1135
+ this._prevFocus = null;
1136
+ this._isDialog = false;
1064
1137
  }
1065
1138
  connectedCallback() {
1066
1139
  const placement = this.getAttribute("placement") || "bottom";
1067
1140
  const triggerType = this.getAttribute("trigger") || "click";
1068
1141
  const title = this.getAttribute("title") || "";
1069
1142
  const role = triggerType === "hover" ? "tooltip" : "dialog";
1143
+ this._isDialog = role === "dialog";
1070
1144
  this.shadowRoot.innerHTML = `
1071
1145
  <style>${styles8}</style>
1072
1146
  <slot name="trigger"></slot>
1073
- <div class="popover popover--${placement}" role="${role}" part="popover">
1147
+ <div class="popover popover--${placement}" id="${this._popoverId}" role="${role}" part="popover">
1074
1148
  ${title ? `<div class="popover__title" part="title">${escapeHTML(title)}</div>` : ""}
1075
1149
  <slot></slot>
1076
1150
  </div>
1077
1151
  `;
1078
1152
  const triggerSlot = this.shadowRoot.querySelector('slot[name="trigger"]');
1079
- triggerSlot.addEventListener("slotchange", () => {
1080
- const trigger = triggerSlot.assignedElements()[0];
1081
- if (!trigger) return;
1082
- trigger.setAttribute("aria-haspopup", role === "tooltip" ? "true" : "dialog");
1083
- trigger.setAttribute("aria-expanded", "false");
1084
- if (triggerType === "click") {
1085
- trigger.addEventListener("click", () => this.toggle());
1086
- } else if (triggerType === "hover") {
1087
- this.addEventListener("mouseenter", () => this.open());
1088
- this.addEventListener("mouseleave", () => this.close());
1089
- this.addEventListener("focusin", () => this.open());
1090
- this.addEventListener("focusout", (e) => {
1091
- if (!this.contains(e.relatedTarget)) this.close();
1092
- });
1093
- } else if (triggerType === "focus") {
1094
- trigger.addEventListener("focusin", () => this.open());
1095
- trigger.addEventListener("focusout", () => this.close());
1096
- }
1097
- });
1153
+ triggerSlot.addEventListener("slotchange", () => this._wireTrigger(triggerType));
1154
+ this._wireTrigger(triggerType);
1155
+ }
1156
+ _wireTrigger(triggerType) {
1157
+ const trigger = this.shadowRoot.querySelector('slot[name="trigger"]')?.assignedElements()[0];
1158
+ if (!trigger) return;
1159
+ const isHover = triggerType === "hover";
1160
+ trigger.setAttribute("aria-haspopup", isHover ? "true" : "dialog");
1161
+ trigger.setAttribute("aria-expanded", this.hasAttribute("open") ? "true" : "false");
1162
+ if (this._isDialog) {
1163
+ trigger.setAttribute("aria-controls", this._popoverId);
1164
+ }
1165
+ if (triggerType === "click") {
1166
+ trigger.onclick = () => this.toggle();
1167
+ } else if (triggerType === "hover") {
1168
+ this.onmouseenter = () => this.open();
1169
+ this.onmouseleave = () => this.close();
1170
+ this.onfocusin = () => this.open();
1171
+ this.onfocusout = (e) => {
1172
+ if (!this.contains(e.relatedTarget)) this.close();
1173
+ };
1174
+ } else if (triggerType === "focus") {
1175
+ trigger.onfocusin = () => this.open();
1176
+ trigger.onfocusout = () => this.close();
1177
+ }
1098
1178
  }
1099
1179
  open() {
1100
1180
  this.setAttribute("open", "");
@@ -1102,6 +1182,14 @@
1102
1182
  if (trigger) trigger.setAttribute("aria-expanded", "true");
1103
1183
  document.addEventListener("click", this._onOutside, true);
1104
1184
  document.addEventListener("keydown", this._onKey);
1185
+ if (this._isDialog) {
1186
+ this._prevFocus = saveFocus();
1187
+ requestAnimationFrame(() => {
1188
+ const focusable = getFocusableElements(this.shadowRoot.querySelector(".popover"));
1189
+ if (focusable.length) focusable[0].focus();
1190
+ else this.shadowRoot.querySelector(".popover")?.focus();
1191
+ });
1192
+ }
1105
1193
  }
1106
1194
  close() {
1107
1195
  this.removeAttribute("open");
@@ -1109,6 +1197,10 @@
1109
1197
  if (trigger) trigger.setAttribute("aria-expanded", "false");
1110
1198
  document.removeEventListener("click", this._onOutside, true);
1111
1199
  document.removeEventListener("keydown", this._onKey);
1200
+ if (this._isDialog && this._prevFocus) {
1201
+ restoreFocus(this._prevFocus);
1202
+ this._prevFocus = null;
1203
+ }
1112
1204
  }
1113
1205
  toggle() {
1114
1206
  this.hasAttribute("open") ? this.close() : this.open();
@@ -1117,7 +1209,15 @@
1117
1209
  if (!this.contains(e.target)) this.close();
1118
1210
  }
1119
1211
  _onKey(e) {
1120
- if (e.key === "Escape") this.close();
1212
+ if (e.key === "Escape") {
1213
+ this.close();
1214
+ const trigger = this.querySelector('[slot="trigger"]');
1215
+ if (trigger) trigger.focus();
1216
+ return;
1217
+ }
1218
+ if (this._isDialog && this.hasAttribute("open")) {
1219
+ trapFocus(this.shadowRoot.querySelector(".popover"), e);
1220
+ }
1121
1221
  }
1122
1222
  disconnectedCallback() {
1123
1223
  document.removeEventListener("click", this._onOutside, true);
@@ -1281,9 +1381,15 @@
1281
1381
  button:disabled { opacity: 0.3; cursor: not-allowed; }
1282
1382
  svg { width: 1.25rem; height: 1.25rem; }
1283
1383
  .indicators {
1284
- display: flex; justify-content: center; gap: var(--velin-space-2, 0.5rem);
1384
+ display: flex; justify-content: center; align-items: center; gap: var(--velin-space-2, 0.5rem);
1285
1385
  padding-block: var(--velin-space-3, 0.75rem);
1286
1386
  }
1387
+ .pause-btn {
1388
+ font-size: var(--velin-text-xs, 0.75rem);
1389
+ padding-inline: var(--velin-space-3, 0.75rem);
1390
+ min-inline-size: auto;
1391
+ border-radius: var(--velin-radius-md, 0.375rem);
1392
+ }
1287
1393
  .dot {
1288
1394
  display: inline-flex; align-items: center; justify-content: center;
1289
1395
  min-width: 2.75rem; min-height: 2.75rem;
@@ -1310,6 +1416,7 @@
1310
1416
  this._index = 0;
1311
1417
  this._timer = null;
1312
1418
  this._startX = 0;
1419
+ this._autoplayPaused = false;
1313
1420
  }
1314
1421
  connectedCallback() {
1315
1422
  this.shadowRoot.innerHTML = `
@@ -1323,7 +1430,7 @@
1323
1430
  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 6 15 12 9 18"/></svg>
1324
1431
  </button>
1325
1432
  </div>
1326
- <div class="indicators" role="tablist" aria-label="Slide indicators" part="indicators"></div>
1433
+ <div class="indicators" role="group" aria-label="Slide indicators" part="indicators"></div>
1327
1434
  `;
1328
1435
  this.shadowRoot.querySelector(".prev").addEventListener("click", () => this.prev());
1329
1436
  this.shadowRoot.querySelector(".next").addEventListener("click", () => this.next());
@@ -1402,12 +1509,37 @@
1402
1509
  c.innerHTML = "";
1403
1510
  this._slides.forEach((_, i) => {
1404
1511
  const d = document.createElement("button");
1512
+ d.type = "button";
1405
1513
  d.className = "dot";
1406
- d.setAttribute("role", "tab");
1407
- d.setAttribute("aria-label", `Slide ${i + 1}`);
1514
+ d.setAttribute("aria-label", `Go to slide ${i + 1}`);
1408
1515
  d.addEventListener("click", () => this.goTo(i));
1409
1516
  c.appendChild(d);
1410
1517
  });
1518
+ if (this.hasAttribute("autoplay")) {
1519
+ const pause = document.createElement("button");
1520
+ pause.type = "button";
1521
+ pause.className = "pause-btn";
1522
+ pause.setAttribute("aria-pressed", "false");
1523
+ pause.setAttribute("aria-label", "Pause automatic slide show");
1524
+ pause.textContent = "Pause";
1525
+ pause.addEventListener("click", () => this._toggleAutoplayPause(pause));
1526
+ c.appendChild(pause);
1527
+ }
1528
+ this._update();
1529
+ }
1530
+ _toggleAutoplayPause(btn) {
1531
+ this._autoplayPaused = !this._autoplayPaused;
1532
+ if (this._autoplayPaused) {
1533
+ this._pause();
1534
+ btn.setAttribute("aria-pressed", "true");
1535
+ btn.setAttribute("aria-label", "Resume automatic slide show");
1536
+ btn.textContent = "Play";
1537
+ } else {
1538
+ this._resume();
1539
+ btn.setAttribute("aria-pressed", "false");
1540
+ btn.setAttribute("aria-label", "Pause automatic slide show");
1541
+ btn.textContent = "Pause";
1542
+ }
1411
1543
  }
1412
1544
  _emit() {
1413
1545
  this.dispatchEvent(new CustomEvent("velin-slide-change", { bubbles: true, detail: { index: this._index } }));
@@ -1423,7 +1555,7 @@
1423
1555
  }
1424
1556
  }
1425
1557
  _resume() {
1426
- if (this.hasAttribute("autoplay") && !this._timer) this._startAutoplay();
1558
+ if (this.hasAttribute("autoplay") && !this._timer && !this._autoplayPaused) this._startAutoplay();
1427
1559
  }
1428
1560
  disconnectedCallback() {
1429
1561
  this._pause();
@@ -1445,6 +1577,11 @@
1445
1577
  .inner { min-height: 0; }
1446
1578
  @media (prefers-reduced-motion: reduce) { .content { transition: none; } }
1447
1579
  `;
1580
+ var collapseId = 0;
1581
+ function isButtonLike(el) {
1582
+ const tag = el.tagName;
1583
+ return tag === "BUTTON" || tag === "A" && el.hasAttribute("href") || el.getAttribute("role") === "button";
1584
+ }
1448
1585
  var VelinCollapse = class extends HTMLElement {
1449
1586
  static get observedAttributes() {
1450
1587
  return ["open"];
@@ -1452,28 +1589,41 @@
1452
1589
  constructor() {
1453
1590
  super();
1454
1591
  this.attachShadow({ mode: "open" });
1592
+ this._contentId = `velin-collapse-panel-${++collapseId}`;
1593
+ this._onTriggerKey = this._onTriggerKey.bind(this);
1455
1594
  }
1456
1595
  connectedCallback() {
1457
- this.shadowRoot.innerHTML = `
1458
- <style>${styles12}</style>
1459
- <slot name="trigger"></slot>
1460
- <div class="content" role="region" part="content">
1461
- <div class="inner"><slot></slot></div>
1462
- </div>
1463
- `;
1596
+ const panelId = this._contentId;
1597
+ this.shadowRoot.innerHTML = "<style>" + styles12 + '</style><slot name="trigger"></slot><div class="content" id="' + panelId + '" part="content"><div class="inner"><slot></slot></div></div>';
1464
1598
  const triggerSlot = this.shadowRoot.querySelector('slot[name="trigger"]');
1465
- triggerSlot.addEventListener("slotchange", () => {
1466
- const trigger = triggerSlot.assignedElements()[0];
1467
- if (trigger) {
1468
- trigger.addEventListener("click", () => this.toggle());
1469
- trigger.setAttribute("aria-expanded", this.hasAttribute("open"));
1470
- }
1471
- });
1599
+ triggerSlot.addEventListener("slotchange", () => this._wireTrigger());
1600
+ this._wireTrigger();
1601
+ }
1602
+ _wireTrigger() {
1603
+ const trigger = this.shadowRoot.querySelector('slot[name="trigger"]')?.assignedElements()[0];
1604
+ if (!trigger) return;
1605
+ if (!isButtonLike(trigger)) {
1606
+ trigger.setAttribute("role", "button");
1607
+ if (!trigger.hasAttribute("tabindex")) trigger.setAttribute("tabindex", "0");
1608
+ }
1609
+ trigger.setAttribute("aria-controls", this._contentId);
1610
+ trigger.setAttribute("aria-expanded", this.hasAttribute("open") ? "true" : "false");
1611
+ trigger.removeEventListener("click", this._onClick);
1612
+ trigger.removeEventListener("keydown", this._onTriggerKey);
1613
+ this._onClick = () => this.toggle();
1614
+ trigger.addEventListener("click", this._onClick);
1615
+ trigger.addEventListener("keydown", this._onTriggerKey);
1616
+ }
1617
+ _onTriggerKey(e) {
1618
+ if (e.key === "Enter" || e.key === " ") {
1619
+ e.preventDefault();
1620
+ this.toggle();
1621
+ }
1472
1622
  }
1473
1623
  attributeChangedCallback(name) {
1474
1624
  if (name === "open") {
1475
1625
  const trigger = this.shadowRoot.querySelector('slot[name="trigger"]')?.assignedElements()[0];
1476
- if (trigger) trigger.setAttribute("aria-expanded", this.hasAttribute("open"));
1626
+ if (trigger) trigger.setAttribute("aria-expanded", this.hasAttribute("open") ? "true" : "false");
1477
1627
  }
1478
1628
  }
1479
1629
  toggle() {
@@ -1561,6 +1711,7 @@
1561
1711
  .tip[data-placement="end"] { left: calc(100% + 6px); top: 50%; transform: translateY(-50%); }
1562
1712
  @media (prefers-reduced-motion: reduce) { .tip { transition: none; } }
1563
1713
  `;
1714
+ var tooltipId = 0;
1564
1715
  var VelinTooltipWC = class extends HTMLElement {
1565
1716
  static get observedAttributes() {
1566
1717
  return ["content", "placement"];
@@ -1568,13 +1719,15 @@
1568
1719
  constructor() {
1569
1720
  super();
1570
1721
  this.attachShadow({ mode: "open" });
1722
+ this._tipId = `velin-tooltip-${++tooltipId}`;
1571
1723
  }
1572
1724
  connectedCallback() {
1573
1725
  const placement = this.getAttribute("placement") || "top";
1726
+ const D = "div";
1574
1727
  this.shadowRoot.innerHTML = `
1575
1728
  <style>${styles13}</style>
1576
1729
  <slot></slot>
1577
- <div class="tip" role="tooltip" data-placement="${escapeHTML(placement)}" part="tip">${escapeHTML(this.getAttribute("content") || "")}</div>
1730
+ <${D} class="tip" id="${this._tipId}" role="tooltip" data-placement="${escapeHTML(placement)}" part="tip">${escapeHTML(this.getAttribute("content") || "")}</${D}>
1578
1731
  `;
1579
1732
  this.addEventListener("mouseenter", () => this._show());
1580
1733
  this.addEventListener("mouseleave", () => this._hide());
@@ -1583,13 +1736,29 @@
1583
1736
  this.addEventListener("keydown", (e) => {
1584
1737
  if (e.key === "Escape") this._hide();
1585
1738
  });
1739
+ const slot = this.shadowRoot.querySelector("slot");
1740
+ slot.addEventListener("slotchange", () => this._linkTrigger());
1741
+ this._linkTrigger();
1742
+ }
1743
+ _linkTrigger() {
1744
+ const trigger = this.shadowRoot.querySelector("slot")?.assignedElements()[0];
1745
+ if (!trigger) return;
1746
+ if (this.hasAttribute("visible")) {
1747
+ trigger.setAttribute("aria-describedby", this._tipId);
1748
+ } else {
1749
+ trigger.removeAttribute("aria-describedby");
1750
+ }
1586
1751
  }
1587
1752
  _show() {
1588
1753
  this.setAttribute("visible", "");
1754
+ const trigger = this.shadowRoot.querySelector("slot")?.assignedElements()[0];
1755
+ if (trigger) trigger.setAttribute("aria-describedby", this._tipId);
1589
1756
  this._flip();
1590
1757
  }
1591
1758
  _hide() {
1592
1759
  this.removeAttribute("visible");
1760
+ const trigger = this.shadowRoot.querySelector("slot")?.assignedElements()[0];
1761
+ if (trigger) trigger.removeAttribute("aria-describedby");
1593
1762
  }
1594
1763
  _flip() {
1595
1764
  const tip = this.shadowRoot.querySelector(".tip");