svelte-multiselect 11.5.1 → 11.6.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.
@@ -1,42 +1,43 @@
1
1
  import type { Page } from '@sveltejs/kit';
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { HTMLAttributes } from 'svelte/elements';
4
- declare function $$render<Route extends string | [string, string] | [string, string[]]>(): {
5
- props: {
6
- routes: Route[];
7
- children?: Snippet<[{
8
- is_open: boolean;
9
- panel_id: string;
10
- routes: Route[];
11
- }]>;
12
- link?: Snippet<[{
13
- href: string;
14
- label: string;
15
- }]>;
16
- menu_props?: HTMLAttributes<HTMLDivElement>;
17
- link_props?: HTMLAttributes<HTMLAnchorElement>;
18
- page?: Page;
19
- labels?: Record<string, string>;
20
- } & Omit<HTMLAttributes<HTMLElement>, "children">;
21
- exports: {};
22
- bindings: "";
23
- slots: {};
24
- events: {};
25
- };
26
- declare class __sveltets_Render<Route extends string | [string, string] | [string, string[]]> {
27
- props(): ReturnType<typeof $$render<Route>>['props'];
28
- events(): ReturnType<typeof $$render<Route>>['events'];
29
- slots(): ReturnType<typeof $$render<Route>>['slots'];
30
- bindings(): "";
31
- exports(): {};
4
+ import type { TooltipOptions } from './attachments';
5
+ import type { NavRoute, NavRouteObject } from './types';
6
+ interface ItemSnippetParams {
7
+ route: NavRouteObject;
8
+ href: string;
9
+ label: string;
10
+ is_active: boolean;
11
+ is_dropdown: boolean;
12
+ render_default: Snippet;
32
13
  }
33
- interface $$IsomorphicComponent {
34
- new <Route extends string | [string, string] | [string, string[]]>(options: import('svelte').ComponentConstructorOptions<ReturnType<__sveltets_Render<Route>['props']>>): import('svelte').SvelteComponent<ReturnType<__sveltets_Render<Route>['props']>, ReturnType<__sveltets_Render<Route>['events']>, ReturnType<__sveltets_Render<Route>['slots']>> & {
35
- $$bindings?: ReturnType<__sveltets_Render<Route>['bindings']>;
36
- } & ReturnType<__sveltets_Render<Route>['exports']>;
37
- <Route extends string | [string, string] | [string, string[]]>(internal: unknown, props: ReturnType<__sveltets_Render<Route>['props']> & {}): ReturnType<__sveltets_Render<Route>['exports']>;
38
- z_$$bindings?: ReturnType<__sveltets_Render<any>['bindings']>;
39
- }
40
- declare const Nav: $$IsomorphicComponent;
41
- type Nav<Route extends string | [string, string] | [string, string[]]> = InstanceType<typeof Nav<Route>>;
14
+ type $$ComponentProps = {
15
+ routes: NavRoute[];
16
+ children?: Snippet<[{
17
+ is_open: boolean;
18
+ panel_id: string;
19
+ routes: NavRoute[];
20
+ }]>;
21
+ item?: Snippet<[ItemSnippetParams]>;
22
+ link?: Snippet<[{
23
+ href: string;
24
+ label: string;
25
+ }]>;
26
+ menu_props?: HTMLAttributes<HTMLDivElement>;
27
+ link_props?: HTMLAttributes<HTMLAnchorElement>;
28
+ page?: Page;
29
+ labels?: Record<string, string>;
30
+ tooltips?: Record<string, string | Omit<TooltipOptions, `disabled`>>;
31
+ tooltip_options?: Omit<TooltipOptions, `content`>;
32
+ breakpoint?: number;
33
+ onnavigate?: (data: {
34
+ href: string;
35
+ event: MouseEvent;
36
+ route: NavRouteObject;
37
+ }) => void | false;
38
+ onopen?: () => void;
39
+ onclose?: () => void;
40
+ } & Omit<HTMLAttributes<HTMLElementTagNameMap[`nav`]>, `children`>;
41
+ declare const Nav: import("svelte").Component<$$ComponentProps, {}, "">;
42
+ type Nav = ReturnType<typeof Nav>;
42
43
  export default Nav;
@@ -1,6 +1,11 @@
1
1
  <script lang="ts">import { Spring } from 'svelte/motion';
2
- let { wiggle = $bindable(false), angle = 0, scale = 1, dx = 0, dy = 0, duration = 200, stiffness = 0.05, damping = 0.1, children, } = $props();
3
- const store = Spring.of(() => (wiggle ? { scale, angle, dx, dy } : { angle: 0, scale: 1, dx: 0, dy: 0 }), { stiffness, damping });
2
+ let { wiggle = $bindable(false), angle = 0, scale = 1, dx = 0, dy = 0, duration = 200, spring_options = $bindable({ stiffness: 0.05, damping: 0.1 }), children, ...rest } = $props();
3
+ const store = Spring.of(() => (wiggle ? { scale, angle, dx, dy } : { angle: 0, scale: 1, dx: 0, dy: 0 }), spring_options);
4
+ // update spring physics when options change
5
+ $effect(() => {
6
+ store.stiffness = spring_options.stiffness;
7
+ store.damping = spring_options.damping;
8
+ });
4
9
  $effect.pre(() => {
5
10
  if (wiggle)
6
11
  setTimeout(() => (wiggle = false), duration);
@@ -8,6 +13,8 @@ $effect.pre(() => {
8
13
  </script>
9
14
 
10
15
  <span
16
+ {...rest}
17
+ style:display="inline-block"
11
18
  style:transform="rotate({store.current.angle}deg) scale({store.current.scale})
12
19
  translate({store.current.dx}px, {store.current.dy}px)"
13
20
  >
@@ -1,15 +1,18 @@
1
1
  import type { Snippet } from 'svelte';
2
- type $$ComponentProps = {
2
+ import type { HTMLAttributes } from 'svelte/elements';
3
+ type $$ComponentProps = HTMLAttributes<HTMLSpanElement> & {
3
4
  wiggle?: boolean;
4
5
  angle?: number;
5
6
  scale?: number;
6
7
  dx?: number;
7
8
  dy?: number;
8
9
  duration?: number;
9
- stiffness?: number;
10
- damping?: number;
10
+ spring_options?: {
11
+ stiffness: number;
12
+ damping: number;
13
+ };
11
14
  children?: Snippet;
12
15
  };
13
- declare const Wiggle: import("svelte").Component<$$ComponentProps, {}, "wiggle">;
16
+ declare const Wiggle: import("svelte").Component<$$ComponentProps, {}, "wiggle" | "spring_options">;
14
17
  type Wiggle = ReturnType<typeof Wiggle>;
15
18
  export default Wiggle;
@@ -1,4 +1,6 @@
1
1
  import { type Attachment } from 'svelte/attachments';
2
+ import { get_uuid } from './utils';
3
+ export { get_uuid };
2
4
  declare global {
3
5
  interface CSS {
4
6
  highlights: HighlightRegistry;
@@ -56,8 +58,12 @@ export interface TooltipOptions {
56
58
  content?: string;
57
59
  placement?: `top` | `bottom` | `left` | `right`;
58
60
  delay?: number;
59
- disabled?: boolean;
61
+ hide_delay?: number;
62
+ disabled?: boolean | `touch-devices`;
60
63
  style?: string;
64
+ show_arrow?: boolean;
65
+ offset?: number;
66
+ allow_html?: boolean;
61
67
  }
62
68
  export declare const tooltip: (options?: TooltipOptions) => Attachment;
63
69
  export type ClickOutsideConfig<T extends HTMLElement> = {
@@ -1,4 +1,7 @@
1
1
  import {} from 'svelte/attachments';
2
+ import { get_uuid } from './utils';
3
+ // Re-export get_uuid for backwards compatibility
4
+ export { get_uuid };
2
5
  // Svelte 5 attachment factory to make an element draggable
3
6
  // @param options - Configuration options for dragging behavior
4
7
  // @returns Attachment function that sets up dragging on an element
@@ -358,35 +361,46 @@ function clear_tooltip() {
358
361
  if (hide_timeout)
359
362
  clearTimeout(hide_timeout);
360
363
  if (current_tooltip) {
364
+ // Remove aria-describedby from the trigger element
365
+ current_tooltip._owner?.removeAttribute(`aria-describedby`);
361
366
  current_tooltip.remove();
362
367
  current_tooltip = null;
363
368
  }
364
369
  }
365
370
  export const tooltip = (options = {}) => (node) => {
366
- // Handle null/undefined elements
367
- if (!node || !(node instanceof HTMLElement))
371
+ // SSR guard + element validation
372
+ if (typeof document === `undefined` || !(node instanceof HTMLElement))
368
373
  return;
369
- // Handle null/undefined options
370
- const safe_options = options || {};
371
374
  const cleanup_functions = [];
375
+ // Handle disabled option
376
+ if (options.disabled === true)
377
+ return;
378
+ // Track current input method for 'touch-devices' option (runtime detection, not capability sniffing)
379
+ // This allows tooltips on hybrid devices (Surface, iPad with mouse) when using mouse/stylus
380
+ let last_pointer_type = `mouse`;
381
+ const track_pointer = (event) => {
382
+ last_pointer_type = event.pointerType;
383
+ };
384
+ if (options.disabled === `touch-devices`) {
385
+ document.addEventListener(`pointerdown`, track_pointer, true);
386
+ cleanup_functions.push(() => document.removeEventListener(`pointerdown`, track_pointer, true));
387
+ }
372
388
  function setup_tooltip(element) {
373
- if (!element || safe_options.disabled)
374
- return;
375
389
  // Use let so content can be updated reactively
376
- let content = safe_options.content || element.title ||
390
+ let content = options.content || element.title ||
377
391
  element.getAttribute(`aria-label`) || element.getAttribute(`data-title`);
378
392
  if (!content)
379
393
  return;
380
394
  // Store original title and remove it to prevent default tooltip
381
395
  // Only store title if we're not using custom content
382
- if (element.title && !safe_options.content) {
396
+ if (element.title && !options.content) {
383
397
  element.setAttribute(`data-original-title`, element.title);
384
398
  element.removeAttribute(`title`);
385
399
  }
386
400
  // Reactively update content when tooltip attributes change
387
401
  const tooltip_attrs = [`title`, `aria-label`, `data-title`];
388
402
  const observer = new MutationObserver((mutations) => {
389
- if (safe_options.content)
403
+ if (options.content)
390
404
  return; // custom content takes precedence
391
405
  for (const { type, attributeName } of mutations) {
392
406
  if (type !== `attributes` || !attributeName)
@@ -408,20 +422,33 @@ export const tooltip = (options = {}) => (node) => {
408
422
  content = new_content;
409
423
  // Only update tooltip if this element owns it
410
424
  if (current_tooltip?._owner === element) {
411
- current_tooltip.innerHTML = content.replace(/\r/g, `<br/>`);
425
+ if (options.allow_html !== false) {
426
+ current_tooltip.innerHTML = content.replace(/\r/g, `<br/>`);
427
+ }
428
+ else {
429
+ current_tooltip.textContent = content;
430
+ }
412
431
  }
413
432
  }
414
433
  });
415
434
  observer.observe(element, { attributes: true, attributeFilter: tooltip_attrs });
416
435
  function show_tooltip() {
436
+ // Skip tooltip on touch input when 'touch-devices' option is set
437
+ if (options.disabled === `touch-devices` && last_pointer_type === `touch`)
438
+ return;
417
439
  clear_tooltip();
418
440
  show_timeout = setTimeout(() => {
419
- const tooltip = document.createElement(`div`);
420
- tooltip.className = `custom-tooltip`;
421
- const placement = safe_options.placement || `bottom`;
422
- tooltip.setAttribute(`data-placement`, placement);
441
+ const tooltip_el = document.createElement(`div`);
442
+ tooltip_el.className = `custom-tooltip`;
443
+ const placement = options.placement || `bottom`;
444
+ tooltip_el.setAttribute(`data-placement`, placement);
445
+ // Accessibility: link tooltip to trigger element
446
+ const tooltip_id = `tooltip-${get_uuid()}`;
447
+ tooltip_el.id = tooltip_id;
448
+ tooltip_el.setAttribute(`role`, `tooltip`);
449
+ element.setAttribute(`aria-describedby`, tooltip_id);
423
450
  // Apply base styles
424
- tooltip.style.cssText = `
451
+ tooltip_el.style.cssText = `
425
452
  position: absolute; z-index: 9999; opacity: 0;
426
453
  background: var(--tooltip-bg, #333); color: var(--text-color, white); border: var(--tooltip-border, none);
427
454
  padding: var(--tooltip-padding, 6px 10px); border-radius: var(--tooltip-radius, 6px); font-size: var(--tooltip-font-size, 13px); line-height: 1.4;
@@ -429,16 +456,22 @@ export const tooltip = (options = {}) => (node) => {
429
456
  filter: var(--tooltip-shadow, drop-shadow(0 2px 8px rgba(0,0,0,0.25))); transition: opacity 0.15s ease-out;
430
457
  `;
431
458
  // Apply custom styles if provided (these will override base styles due to CSS specificity)
432
- if (safe_options.style) {
459
+ if (options.style) {
433
460
  // Parse and apply custom styles as individual properties for better control
434
- const custom_styles = safe_options.style.split(`;`).filter((style) => style.trim());
461
+ const custom_styles = options.style.split(`;`).filter((style) => style.trim());
435
462
  custom_styles.forEach((style) => {
436
463
  const [property, value] = style.split(`:`).map((s) => s.trim());
437
464
  if (property && value)
438
- tooltip.style.setProperty(property, value);
465
+ tooltip_el.style.setProperty(property, value);
439
466
  });
440
467
  }
441
- tooltip.innerHTML = content?.replace(/\r/g, `<br/>`) ?? ``;
468
+ // Security: allow_html defaults to true; set to false for plain text rendering
469
+ if (options.allow_html !== false) {
470
+ tooltip_el.innerHTML = content?.replace(/\r/g, `<br/>`) ?? ``;
471
+ }
472
+ else {
473
+ tooltip_el.textContent = content ?? ``;
474
+ }
442
475
  // Mirror CSS custom properties from the trigger node onto the tooltip element
443
476
  const trigger_styles = getComputedStyle(element);
444
477
  [
@@ -455,97 +488,100 @@ export const tooltip = (options = {}) => (node) => {
455
488
  ].forEach((name) => {
456
489
  const value = trigger_styles.getPropertyValue(name).trim();
457
490
  if (value)
458
- tooltip.style.setProperty(name, value);
491
+ tooltip_el.style.setProperty(name, value);
459
492
  });
460
493
  // Append early so we can read computed border styles for arrow border
461
- document.body.appendChild(tooltip);
462
- // Arrow elements: optional border triangle behind fill triangle
463
- const tooltip_styles = getComputedStyle(tooltip);
464
- const arrow = document.createElement(`div`);
465
- arrow.className = `custom-tooltip-arrow`;
466
- arrow.style.cssText =
467
- `position: absolute; width: 0; height: 0; pointer-events: none;`;
468
- const arrow_size_raw = trigger_styles.getPropertyValue(`--tooltip-arrow-size`)
469
- .trim();
470
- const arrow_size_num = Number.parseInt(arrow_size_raw || ``, 10);
471
- const arrow_px = Number.isFinite(arrow_size_num) ? arrow_size_num : 6;
472
- const border_color = tooltip_styles.borderTopColor;
473
- const border_width_num = Number.parseFloat(tooltip_styles.borderTopWidth || `0`);
474
- const has_border = !!border_color && border_color !== `rgba(0, 0, 0, 0)` &&
475
- border_width_num > 0;
476
- const maybe_append_border_arrow = () => {
477
- if (!has_border)
478
- return;
479
- const border_arrow = document.createElement(`div`);
480
- border_arrow.className = `custom-tooltip-arrow-border`;
481
- border_arrow.style.cssText =
494
+ document.body.appendChild(tooltip_el);
495
+ // Create arrow elements only if show_arrow is not false
496
+ if (options.show_arrow !== false) {
497
+ const tooltip_styles = getComputedStyle(tooltip_el);
498
+ const arrow = document.createElement(`div`);
499
+ arrow.className = `custom-tooltip-arrow`;
500
+ arrow.style.cssText =
482
501
  `position: absolute; width: 0; height: 0; pointer-events: none;`;
483
- const border_size = arrow_px + (border_width_num * 1.4);
502
+ const arrow_size_raw = trigger_styles.getPropertyValue(`--tooltip-arrow-size`)
503
+ .trim();
504
+ const arrow_size_num = Number.parseInt(arrow_size_raw || ``, 10);
505
+ const arrow_px = Number.isFinite(arrow_size_num) ? arrow_size_num : 6;
506
+ const border_color = tooltip_styles.borderTopColor;
507
+ const border_width_num = Number.parseFloat(tooltip_styles.borderTopWidth || `0`);
508
+ const has_border = !!border_color && border_color !== `rgba(0, 0, 0, 0)` &&
509
+ border_width_num > 0;
510
+ // Helper to create border arrow behind fill arrow
511
+ const append_border_arrow = () => {
512
+ if (!has_border)
513
+ return;
514
+ const border_arrow = document.createElement(`div`);
515
+ border_arrow.className = `custom-tooltip-arrow-border`;
516
+ border_arrow.style.cssText =
517
+ `position: absolute; width: 0; height: 0; pointer-events: none;`;
518
+ const border_size = arrow_px + (border_width_num * 1.4);
519
+ if (placement === `top`) {
520
+ border_arrow.style.left = `calc(50% - ${border_size}px)`;
521
+ border_arrow.style.bottom = `-${border_size}px`;
522
+ border_arrow.style.borderLeft = `${border_size}px solid transparent`;
523
+ border_arrow.style.borderRight = `${border_size}px solid transparent`;
524
+ border_arrow.style.borderTop = `${border_size}px solid ${border_color}`;
525
+ }
526
+ else if (placement === `left`) {
527
+ border_arrow.style.top = `calc(50% - ${border_size}px)`;
528
+ border_arrow.style.right = `-${border_size}px`;
529
+ border_arrow.style.borderTop = `${border_size}px solid transparent`;
530
+ border_arrow.style.borderBottom = `${border_size}px solid transparent`;
531
+ border_arrow.style.borderLeft = `${border_size}px solid ${border_color}`;
532
+ }
533
+ else if (placement === `right`) {
534
+ border_arrow.style.top = `calc(50% - ${border_size}px)`;
535
+ border_arrow.style.left = `-${border_size}px`;
536
+ border_arrow.style.borderTop = `${border_size}px solid transparent`;
537
+ border_arrow.style.borderBottom = `${border_size}px solid transparent`;
538
+ border_arrow.style.borderRight = `${border_size}px solid ${border_color}`;
539
+ }
540
+ else { // bottom
541
+ border_arrow.style.left = `calc(50% - ${border_size}px)`;
542
+ border_arrow.style.top = `-${border_size}px`;
543
+ border_arrow.style.borderLeft = `${border_size}px solid transparent`;
544
+ border_arrow.style.borderRight = `${border_size}px solid transparent`;
545
+ border_arrow.style.borderBottom = `${border_size}px solid ${border_color}`;
546
+ }
547
+ tooltip_el.appendChild(border_arrow);
548
+ };
549
+ // Style and position fill arrow
484
550
  if (placement === `top`) {
485
- border_arrow.style.left = `calc(50% - ${border_size}px)`;
486
- border_arrow.style.bottom = `-${border_size}px`;
487
- border_arrow.style.borderLeft = `${border_size}px solid transparent`;
488
- border_arrow.style.borderRight = `${border_size}px solid transparent`;
489
- border_arrow.style.borderTop = `${border_size}px solid ${border_color}`;
551
+ arrow.style.left = `calc(50% - ${arrow_px}px)`;
552
+ arrow.style.bottom = `-${arrow_px}px`;
553
+ arrow.style.borderLeft = `${arrow_px}px solid transparent`;
554
+ arrow.style.borderRight = `${arrow_px}px solid transparent`;
555
+ arrow.style.borderTop = `${arrow_px}px solid var(--tooltip-bg, #333)`;
490
556
  }
491
557
  else if (placement === `left`) {
492
- border_arrow.style.top = `calc(50% - ${border_size}px)`;
493
- border_arrow.style.right = `-${border_size}px`;
494
- border_arrow.style.borderTop = `${border_size}px solid transparent`;
495
- border_arrow.style.borderBottom = `${border_size}px solid transparent`;
496
- border_arrow.style.borderLeft = `${border_size}px solid ${border_color}`;
558
+ arrow.style.top = `calc(50% - ${arrow_px}px)`;
559
+ arrow.style.right = `-${arrow_px}px`;
560
+ arrow.style.borderTop = `${arrow_px}px solid transparent`;
561
+ arrow.style.borderBottom = `${arrow_px}px solid transparent`;
562
+ arrow.style.borderLeft = `${arrow_px}px solid var(--tooltip-bg, #333)`;
497
563
  }
498
564
  else if (placement === `right`) {
499
- border_arrow.style.top = `calc(50% - ${border_size}px)`;
500
- border_arrow.style.left = `-${border_size}px`;
501
- border_arrow.style.borderTop = `${border_size}px solid transparent`;
502
- border_arrow.style.borderBottom = `${border_size}px solid transparent`;
503
- border_arrow.style.borderRight = `${border_size}px solid ${border_color}`;
565
+ arrow.style.top = `calc(50% - ${arrow_px}px)`;
566
+ arrow.style.left = `-${arrow_px}px`;
567
+ arrow.style.borderTop = `${arrow_px}px solid transparent`;
568
+ arrow.style.borderBottom = `${arrow_px}px solid transparent`;
569
+ arrow.style.borderRight = `${arrow_px}px solid var(--tooltip-bg, #333)`;
504
570
  }
505
571
  else { // bottom
506
- border_arrow.style.left = `calc(50% - ${border_size}px)`;
507
- border_arrow.style.top = `-${border_size}px`;
508
- border_arrow.style.borderLeft = `${border_size}px solid transparent`;
509
- border_arrow.style.borderRight = `${border_size}px solid transparent`;
510
- border_arrow.style.borderBottom = `${border_size}px solid ${border_color}`;
572
+ arrow.style.left = `calc(50% - ${arrow_px}px)`;
573
+ arrow.style.top = `-${arrow_px}px`;
574
+ arrow.style.borderLeft = `${arrow_px}px solid transparent`;
575
+ arrow.style.borderRight = `${arrow_px}px solid transparent`;
576
+ arrow.style.borderBottom = `${arrow_px}px solid var(--tooltip-bg, #333)`;
511
577
  }
512
- tooltip.appendChild(border_arrow);
513
- };
514
- // Create the fill arrow on top
515
- if (placement === `top`) {
516
- arrow.style.left = `calc(50% - ${arrow_px}px)`;
517
- arrow.style.bottom = `-${arrow_px}px`;
518
- arrow.style.borderLeft = `${arrow_px}px solid transparent`;
519
- arrow.style.borderRight = `${arrow_px}px solid transparent`;
520
- arrow.style.borderTop = `${arrow_px}px solid var(--tooltip-bg, #333)`;
578
+ append_border_arrow();
579
+ tooltip_el.appendChild(arrow);
521
580
  }
522
- else if (placement === `left`) {
523
- arrow.style.top = `calc(50% - ${arrow_px}px)`;
524
- arrow.style.right = `-${arrow_px}px`;
525
- arrow.style.borderTop = `${arrow_px}px solid transparent`;
526
- arrow.style.borderBottom = `${arrow_px}px solid transparent`;
527
- arrow.style.borderLeft = `${arrow_px}px solid var(--tooltip-bg, #333)`;
528
- }
529
- else if (placement === `right`) {
530
- arrow.style.top = `calc(50% - ${arrow_px}px)`;
531
- arrow.style.left = `-${arrow_px}px`;
532
- arrow.style.borderTop = `${arrow_px}px solid transparent`;
533
- arrow.style.borderBottom = `${arrow_px}px solid transparent`;
534
- arrow.style.borderRight = `${arrow_px}px solid var(--tooltip-bg, #333)`;
535
- }
536
- else { // bottom
537
- arrow.style.left = `calc(50% - ${arrow_px}px)`;
538
- arrow.style.top = `-${arrow_px}px`;
539
- arrow.style.borderLeft = `${arrow_px}px solid transparent`;
540
- arrow.style.borderRight = `${arrow_px}px solid transparent`;
541
- arrow.style.borderBottom = `${arrow_px}px solid var(--tooltip-bg, #333)`;
542
- }
543
- maybe_append_border_arrow();
544
- tooltip.appendChild(arrow);
545
581
  // Position tooltip
546
582
  const rect = element.getBoundingClientRect();
547
- const tooltip_rect = tooltip.getBoundingClientRect();
548
- const margin = 12;
583
+ const tooltip_rect = tooltip_el.getBoundingClientRect();
584
+ const margin = options.offset ?? 12;
549
585
  let top = 0, left = 0;
550
586
  if (placement === `top`) {
551
587
  top = rect.top - tooltip_rect.height - margin;
@@ -566,22 +602,26 @@ export const tooltip = (options = {}) => (node) => {
566
602
  // Keep in viewport
567
603
  left = Math.max(8, Math.min(left, globalThis.innerWidth - tooltip_rect.width - 8));
568
604
  top = Math.max(8, Math.min(top, globalThis.innerHeight - tooltip_rect.height - 8));
569
- tooltip.style.left = `${left + globalThis.scrollX}px`;
570
- tooltip.style.top = `${top + globalThis.scrollY}px`;
605
+ tooltip_el.style.left = `${left + globalThis.scrollX}px`;
606
+ tooltip_el.style.top = `${top + globalThis.scrollY}px`;
571
607
  const custom_opacity = trigger_styles.getPropertyValue(`--tooltip-opacity`).trim();
572
- tooltip.style.opacity = custom_opacity || `1`;
573
- current_tooltip = Object.assign(tooltip, { _owner: element });
574
- }, safe_options.delay || 100);
608
+ tooltip_el.style.opacity = custom_opacity || `1`;
609
+ current_tooltip = Object.assign(tooltip_el, { _owner: element });
610
+ }, options.delay || 100);
611
+ }
612
+ function handle_keydown(event) {
613
+ if (event.key === `Escape` && current_tooltip?._owner === element)
614
+ clear_tooltip();
575
615
  }
576
616
  function hide_tooltip() {
577
- clear_tooltip();
578
- if (current_tooltip) {
579
- current_tooltip.style.opacity = `0`;
580
- if (current_tooltip) {
581
- current_tooltip.remove();
582
- current_tooltip = null;
583
- }
617
+ if (show_timeout)
618
+ clearTimeout(show_timeout);
619
+ const delay = options.hide_delay ?? 0;
620
+ if (delay > 0) {
621
+ hide_timeout = setTimeout(() => clear_tooltip(), delay);
584
622
  }
623
+ else
624
+ clear_tooltip();
585
625
  }
586
626
  function handle_scroll(event) {
587
627
  // Hide if document or any ancestor scrolls (would move element). Skip internal element scrolls.
@@ -595,10 +635,26 @@ export const tooltip = (options = {}) => (node) => {
595
635
  events.forEach((event, idx) => element.addEventListener(event, handlers[idx]));
596
636
  // Hide tooltip when user scrolls the page (not element-level scrolls like input fields)
597
637
  globalThis.addEventListener(`scroll`, handle_scroll, true);
638
+ // Add Escape key listener to dismiss tooltip
639
+ document.addEventListener(`keydown`, handle_keydown);
640
+ // Watch for element removal from DOM to prevent orphaned tooltips
641
+ const removal_observer = new MutationObserver((mutations) => {
642
+ const was_removed = mutations.some((mut) => Array.from(mut.removedNodes).some((node) => node === element || (node instanceof Element && node.contains(element))));
643
+ if (was_removed && current_tooltip?._owner === element)
644
+ clear_tooltip();
645
+ });
646
+ if (element.parentElement) {
647
+ removal_observer.observe(element.parentElement, { childList: true, subtree: true });
648
+ }
598
649
  return () => {
599
650
  observer.disconnect();
651
+ removal_observer.disconnect();
600
652
  events.forEach((event, idx) => element.removeEventListener(event, handlers[idx]));
601
653
  globalThis.removeEventListener(`scroll`, handle_scroll, true);
654
+ document.removeEventListener(`keydown`, handle_keydown);
655
+ // Clear tooltip if this element owns it (also removes aria-describedby)
656
+ if (current_tooltip?._owner === element)
657
+ clear_tooltip();
602
658
  const original_title = element.getAttribute(`data-original-title`);
603
659
  if (original_title) {
604
660
  element.setAttribute(`title`, original_title);
@@ -0,0 +1,14 @@
1
+ /** @type {() => import('svelte/compiler').PreprocessorGroup} */
2
+ export declare function heading_ids(): {
3
+ name: string;
4
+ markup({ content }: {
5
+ content: string;
6
+ }): {
7
+ code: string;
8
+ };
9
+ };
10
+ export interface HeadingAnchorsOptions {
11
+ selector?: string;
12
+ icon_svg?: string;
13
+ }
14
+ export declare const heading_anchors: (options?: HeadingAnchorsOptions) => (node: Element) => (() => void) | undefined;