spoko-design-system 1.35.0 → 1.36.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/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [1.36.0](https://github.com/polo-blue/sds/compare/v1.35.1...v1.36.0) (2026-03-24)
2
+
3
+ ### Features
4
+
5
+ * add interactive tooltip mode ([#410](https://github.com/polo-blue/sds/issues/410)) ([473a586](https://github.com/polo-blue/sds/commit/473a5869a765af1b347b2e909f4019bacf3255fd))
6
+
7
+ ## [1.35.1](https://github.com/polo-blue/sds/compare/v1.35.0...v1.35.1) (2026-03-24)
8
+
9
+ ### Bug Fixes
10
+
11
+ * tooltip listeners lost after Astro View Transitions ([#409](https://github.com/polo-blue/sds/issues/409)) ([4fe9dd4](https://github.com/polo-blue/sds/commit/4fe9dd4d80a15edad47c277c233fb153072a2619))
12
+
1
13
  ## [1.35.0](https://github.com/polo-blue/sds/compare/v1.34.26...v1.35.0) (2026-03-24)
2
14
 
3
15
  ### ⚠ BREAKING CHANGES
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spoko-design-system",
3
- "version": "1.35.0",
3
+ "version": "1.36.0",
4
4
  "type": "module",
5
5
  "private": false,
6
6
  "main": "./index.ts",
@@ -2,12 +2,13 @@
2
2
  * SDS Tooltip Engine
3
3
  * Powered by Floating UI — replaces tippy.js
4
4
  *
5
- * Uses event delegation on document.body for performance.
5
+ * Uses event delegation on document (capture phase) for performance.
6
6
  * Handles tooltips for any element with [data-sds-tooltip] attribute.
7
7
  *
8
8
  * Supported attributes:
9
- * - data-sds-tooltip — HTML content to display
10
- * - data-sds-tooltip-placement — placement override (default: 'top')
9
+ * - data-sds-tooltip — HTML content to display
10
+ * - data-sds-tooltip-placement — placement override (default: 'top')
11
+ * - data-sds-tooltip-interactive — allow hovering over tooltip (for clickable links)
11
12
  */
12
13
 
13
14
  import { computePosition, autoUpdate, offset, flip, shift, arrow, type Placement } from '@floating-ui/dom';
@@ -19,6 +20,7 @@ const ARROW_SIZE = 8;
19
20
  const SHIFT_PADDING = 5;
20
21
  const SHOW_DELAY = 80;
21
22
  const HIDE_DELAY = 60;
23
+ const INTERACTIVE_HIDE_DELAY = 150;
22
24
 
23
25
  let tooltipEl: HTMLElement | null = null;
24
26
  let arrowEl: HTMLElement | null = null;
@@ -28,6 +30,7 @@ let currentTarget: HTMLElement | null = null;
28
30
  let showTimer: ReturnType<typeof setTimeout> | null = null;
29
31
  let hideTimer: ReturnType<typeof setTimeout> | null = null;
30
32
  let initialized = false;
33
+ let isInteractive = false;
31
34
 
32
35
  const OPPOSITE_SIDE: Record<string, string> = {
33
36
  top: 'bottom',
@@ -125,6 +128,10 @@ export function showTooltip(target: HTMLElement) {
125
128
  tooltip.style.display = 'block';
126
129
  currentTarget = target;
127
130
 
131
+ // Interactive mode: allow hovering over tooltip for clickable content
132
+ isInteractive = target.hasAttribute('data-sds-tooltip-interactive');
133
+ tooltip.classList.toggle('sds-tooltip--interactive', isInteractive);
134
+
128
135
  // Start auto-updating position
129
136
  cleanupAutoUpdate?.();
130
137
  cleanupAutoUpdate = autoUpdate(target, tooltip, () => {
@@ -155,7 +162,18 @@ export function hideTooltip() {
155
162
  }
156
163
 
157
164
  function handleMouseEnter(e: Event) {
158
- const target = (e.target as HTMLElement).closest?.(SELECTOR);
165
+ const el = e.target as HTMLElement;
166
+
167
+ // Hovering over the tooltip itself — cancel pending hide
168
+ if (isInteractive && tooltipEl && (el === tooltipEl || tooltipEl.contains(el))) {
169
+ if (hideTimer) {
170
+ clearTimeout(hideTimer);
171
+ hideTimer = null;
172
+ }
173
+ return;
174
+ }
175
+
176
+ const target = el.closest?.(SELECTOR);
159
177
  if (!(target instanceof HTMLElement)) return;
160
178
 
161
179
  if (hideTimer) {
@@ -171,7 +189,18 @@ function handleMouseEnter(e: Event) {
171
189
  }
172
190
 
173
191
  function handleMouseLeave(e: Event) {
174
- const target = (e.target as HTMLElement).closest?.(SELECTOR);
192
+ const el = e.target as HTMLElement;
193
+
194
+ // Leaving the tooltip itself — schedule hide
195
+ if (isInteractive && tooltipEl && (el === tooltipEl || tooltipEl.contains(el))) {
196
+ hideTimer = setTimeout(() => {
197
+ hideTooltip();
198
+ hideTimer = null;
199
+ }, INTERACTIVE_HIDE_DELAY);
200
+ return;
201
+ }
202
+
203
+ const target = el.closest?.(SELECTOR);
175
204
  if (!(target instanceof HTMLElement)) return;
176
205
 
177
206
  if (showTimer) {
@@ -179,10 +208,11 @@ function handleMouseLeave(e: Event) {
179
208
  showTimer = null;
180
209
  }
181
210
 
211
+ const delay = isInteractive ? INTERACTIVE_HIDE_DELAY : HIDE_DELAY;
182
212
  hideTimer = setTimeout(() => {
183
213
  hideTooltip();
184
214
  hideTimer = null;
185
- }, HIDE_DELAY);
215
+ }, delay);
186
216
  }
187
217
 
188
218
  function handleFocusIn(e: Event) {
@@ -203,10 +233,13 @@ export function initTooltips() {
203
233
  if (initialized) return;
204
234
  initialized = true;
205
235
 
206
- document.body.addEventListener('mouseenter', handleMouseEnter, true);
207
- document.body.addEventListener('mouseleave', handleMouseLeave, true);
208
- document.body.addEventListener('focusin', handleFocusIn, true);
209
- document.body.addEventListener('focusout', handleFocusOut, true);
236
+ // Use `document` instead of `document.body` Astro View Transitions
237
+ // replace the <body> element during swap, which would lose listeners.
238
+ // `document` persists across navigations.
239
+ document.addEventListener('mouseenter', handleMouseEnter, true);
240
+ document.addEventListener('mouseleave', handleMouseLeave, true);
241
+ document.addEventListener('focusin', handleFocusIn, true);
242
+ document.addEventListener('focusout', handleFocusOut, true);
210
243
  }
211
244
 
212
245
  function cleanup() {
@@ -30,6 +30,15 @@
30
30
  opacity: 1;
31
31
  }
32
32
 
33
+ /* Interactive mode — allow hovering over tooltip for clickable content */
34
+ .sds-tooltip--interactive {
35
+ pointer-events: auto;
36
+ }
37
+
38
+ .sds-tooltip--interactive a {
39
+ pointer-events: auto;
40
+ }
41
+
33
42
  /* Arrow */
34
43
  .sds-tooltip-arrow {
35
44
  position: absolute;