@winkintel/bootstrap-svelte 1.0.4 → 1.0.6
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/README.md +1 -1
- package/dist/Alert/alert.svelte +4 -2
- package/dist/Badge/badge.svelte +6 -4
- package/dist/Badge/badge.svelte.d.ts +2 -2
- package/dist/Badge/types.d.ts +2 -1
- package/dist/Badge/types.js +3 -11
- package/dist/Button/button.svelte +29 -9
- package/dist/ButtonGroup/button-group.svelte +3 -2
- package/dist/ButtonGroup/button-toolbar.svelte +3 -2
- package/dist/ButtonGroup/types.contract.d.ts +1 -0
- package/dist/ButtonGroup/types.contract.js +15 -0
- package/dist/ButtonGroup/types.d.ts +2 -2
- package/dist/Carousel/carousel-indicator-button.svelte +8 -1
- package/dist/Carousel/carousel-item.svelte +29 -9
- package/dist/Carousel/carousel.svelte +12 -5
- package/dist/Carousel/carousel.svelte.js +200 -42
- package/dist/Col/col.svelte +30 -22
- package/dist/Collapse/collapse.svelte +3 -3
- package/dist/Collapse/transition.js +78 -15
- package/dist/Collapse/types.d.ts +1 -0
- package/dist/Dropdown/dropdown-item.svelte +7 -0
- package/dist/Dropdown/dropdown.svelte.js +66 -19
- package/dist/Form/form-check-input.svelte +6 -6
- package/dist/Form/form-color-input.svelte +8 -3
- package/dist/Form/form-date-input.svelte +4 -4
- package/dist/Form/form-datetime-local-input.svelte +4 -4
- package/dist/Form/form-email-input.svelte +4 -4
- package/dist/Form/form-file-input.svelte +46 -6
- package/dist/Form/form-file-input.svelte.d.ts +3 -2
- package/dist/Form/form-hidden-input.svelte +1 -1
- package/dist/Form/form-hidden-input.svelte.d.ts +1 -1
- package/dist/Form/form-image-input.svelte +1 -1
- package/dist/Form/form-image-input.svelte.d.ts +2 -1
- package/dist/Form/form-month-input.svelte +4 -4
- package/dist/Form/form-number-input.svelte +4 -4
- package/dist/Form/form-password-input.svelte +4 -4
- package/dist/Form/form-radio-input.svelte +53 -3
- package/dist/Form/form-radio-input.svelte.d.ts +3 -2
- package/dist/Form/form-range-input.svelte +1 -1
- package/dist/Form/form-range-input.svelte.d.ts +2 -1
- package/dist/Form/form-search-input.svelte +4 -4
- package/dist/Form/form-select.svelte +7 -7
- package/dist/Form/form-switch-input.svelte +4 -4
- package/dist/Form/form-telephone-input.svelte +4 -4
- package/dist/Form/form-text-area.svelte +4 -4
- package/dist/Form/form-text-input.svelte +4 -4
- package/dist/Form/form-time-input.svelte +4 -4
- package/dist/Form/form-url-input.svelte +4 -4
- package/dist/Form/form-week-Input.svelte +4 -4
- package/dist/Form/types.d.ts +26 -20
- package/dist/ListGroup/list-group-item-action.svelte +14 -7
- package/dist/Modal/modal-title.svelte +7 -7
- package/dist/Modal/modal.svelte +86 -26
- package/dist/Modal/modal.svelte.js +15 -0
- package/dist/Nav/nav-link.svelte +12 -8
- package/dist/Navbar/navbar-collapse.svelte +18 -7
- package/dist/Navbar/navbar-collapse.svelte.d.ts +1 -1
- package/dist/Navbar/navbar-nav.svelte +1 -1
- package/dist/Navbar/navbar-toggler.svelte +9 -7
- package/dist/Navbar/navbar-toggler.svelte.d.ts +1 -1
- package/dist/Navbar/types.d.ts +3 -2
- package/dist/Offcanvas/offcanvas.svelte +38 -7
- package/dist/Pagination/pagination-item.svelte +27 -20
- package/dist/Pagination/pagination-link.svelte +20 -5
- package/dist/Pagination/pagination.svelte.js +28 -8
- package/dist/Placeholder/placeholder-item.svelte +7 -2
- package/dist/Placeholder/placeholder-item.svelte.d.ts +1 -1
- package/dist/Placeholder/types.d.ts +1 -0
- package/dist/Portal/portal.svelte +6 -1
- package/dist/Progress/progress-bar.svelte +22 -9
- package/dist/Row/row.svelte +9 -10
- package/dist/Row/types.d.ts +8 -7
- package/dist/Scrollspy/scrollspy-attachment.svelte.js +47 -28
- package/dist/Tooltip/tooltip.svelte +57 -10
- package/dist/common/css.js +96 -24
- package/dist/common/navbar-offcanvas.svelte.d.ts +2 -0
- package/dist/common/navbar-offcanvas.svelte.js +5 -4
- package/dist/common/overlay-stack.d.ts +13 -0
- package/dist/common/overlay-stack.js +49 -0
- package/dist/common/scrollbar.js +24 -8
- package/package.json +3 -1
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { Context } from '../common/index.js';
|
|
2
|
+
import { untrack } from 'svelte';
|
|
3
|
+
import { SvelteSet } from 'svelte/reactivity';
|
|
2
4
|
import { Pagination } from './index.js';
|
|
3
5
|
/**
|
|
4
6
|
* Manages the state for the Pagination root component.
|
|
@@ -8,13 +10,15 @@ import { Pagination } from './index.js';
|
|
|
8
10
|
export class PaginationRootState {
|
|
9
11
|
props;
|
|
10
12
|
#activePaginationItemId = $state('');
|
|
11
|
-
#
|
|
13
|
+
#disabledPaginationItemIds = new SvelteSet();
|
|
12
14
|
constructor(props) {
|
|
13
15
|
this.props = props;
|
|
14
16
|
this.isPaginationItemActive = this.isPaginationItemActive.bind(this);
|
|
15
17
|
this.setPaginationItemActive = this.setPaginationItemActive.bind(this);
|
|
18
|
+
this.removePaginationItemActive = this.removePaginationItemActive.bind(this);
|
|
16
19
|
this.isPaginationItemDisabled = this.isPaginationItemDisabled.bind(this);
|
|
17
20
|
this.setPaginationItemDisabled = this.setPaginationItemDisabled.bind(this);
|
|
21
|
+
this.removePaginationItemDisabled = this.removePaginationItemDisabled.bind(this);
|
|
18
22
|
}
|
|
19
23
|
isPaginationItemActive(itemId) {
|
|
20
24
|
return this.#activePaginationItemId === itemId;
|
|
@@ -22,11 +26,29 @@ export class PaginationRootState {
|
|
|
22
26
|
setPaginationItemActive(itemId) {
|
|
23
27
|
this.#activePaginationItemId = itemId;
|
|
24
28
|
}
|
|
29
|
+
removePaginationItemActive(itemId) {
|
|
30
|
+
untrack(() => {
|
|
31
|
+
if (this.#activePaginationItemId === itemId) {
|
|
32
|
+
this.#activePaginationItemId = '';
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
}
|
|
25
36
|
isPaginationItemDisabled(itemId) {
|
|
26
|
-
return this.#
|
|
37
|
+
return this.#disabledPaginationItemIds.has(itemId);
|
|
27
38
|
}
|
|
28
39
|
setPaginationItemDisabled(itemId) {
|
|
29
|
-
|
|
40
|
+
untrack(() => {
|
|
41
|
+
if (itemId && !this.#disabledPaginationItemIds.has(itemId)) {
|
|
42
|
+
this.#disabledPaginationItemIds.add(itemId);
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
removePaginationItemDisabled(itemId) {
|
|
47
|
+
untrack(() => {
|
|
48
|
+
if (itemId && this.#disabledPaginationItemIds.has(itemId)) {
|
|
49
|
+
this.#disabledPaginationItemIds.delete(itemId);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
30
52
|
}
|
|
31
53
|
}
|
|
32
54
|
/**
|
|
@@ -37,17 +59,14 @@ export class PaginationRootState {
|
|
|
37
59
|
export class PaginationItemState {
|
|
38
60
|
props;
|
|
39
61
|
root;
|
|
40
|
-
isActive = $derived.by(() => this.root.isPaginationItemActive(this.props.id || ''));
|
|
41
|
-
isDisabled = $derived.by(() => this.root.isPaginationItemDisabled(this.props.id || ''));
|
|
62
|
+
isActive = $derived.by(() => this.props.isActive || this.root.isPaginationItemActive(this.props.id || ''));
|
|
63
|
+
isDisabled = $derived.by(() => this.props.isDisabled || this.root.isPaginationItemDisabled(this.props.id || ''));
|
|
42
64
|
constructor(props, root) {
|
|
43
65
|
this.props = props;
|
|
44
66
|
this.root = root;
|
|
45
67
|
if (this.props.isActive) {
|
|
46
68
|
this.root.setPaginationItemActive(this.props.id || '');
|
|
47
69
|
}
|
|
48
|
-
if (this.props.isDisabled) {
|
|
49
|
-
this.root.setPaginationItemDisabled(this.props.id || '');
|
|
50
|
-
}
|
|
51
70
|
}
|
|
52
71
|
}
|
|
53
72
|
/**
|
|
@@ -59,6 +78,7 @@ export class PaginationLinkState {
|
|
|
59
78
|
props;
|
|
60
79
|
root;
|
|
61
80
|
item;
|
|
81
|
+
isDisabled = $derived.by(() => this.item.isDisabled || this.props.isDisabled);
|
|
62
82
|
constructor(props, root, item) {
|
|
63
83
|
this.props = props;
|
|
64
84
|
this.root = root;
|
|
@@ -40,14 +40,19 @@ A component that represents an individual placeholder element.
|
|
|
40
40
|
- `colorVariant` (string): Optional. Color variant for the placeholder ('primary', 'success', etc.).
|
|
41
41
|
- `elementRef` (HTMLSpanElement): Optional. Reference to the placeholder item DOM element.
|
|
42
42
|
- `id` (string): Optional. ID for the placeholder item element.
|
|
43
|
+
- `isAnimated` (boolean): Deprecated compatibility no-op. Set `type` on `Placeholder.Root` to animate a placeholder group.
|
|
43
44
|
- `size` ('xs' | 'sm' | 'lg'): Optional. Size of the placeholder.
|
|
44
|
-
- `isAnimated` (boolean): Optional. Whether the placeholder should animate, defaults to true.
|
|
45
45
|
-->
|
|
46
46
|
<script lang="ts">import { uniqueClsx } from '../common/css.js';
|
|
47
47
|
let { children, class: classValues, colorVariant, elementRef = $bindable(null), size, ...restOfProps } = $props();
|
|
48
|
+
let sanitizedRestOfProps = $derived.by(() => {
|
|
49
|
+
// Keep accepting the legacy no-op without forwarding it as an invalid DOM attribute.
|
|
50
|
+
const { isAnimated: _isAnimated, ...sanitizedProps } = restOfProps;
|
|
51
|
+
return sanitizedProps;
|
|
52
|
+
});
|
|
48
53
|
let classes = $derived(uniqueClsx('placeholder', size && `placeholder-${size}`, colorVariant && `bg-${colorVariant}`, classValues));
|
|
49
54
|
</script>
|
|
50
55
|
|
|
51
|
-
<span bind:this={elementRef} class={classes} {...
|
|
56
|
+
<span bind:this={elementRef} class={classes} {...sanitizedRestOfProps}>
|
|
52
57
|
{@render children?.()}
|
|
53
58
|
</span>
|
|
@@ -40,8 +40,8 @@ import type { Placeholder } from './index.js';
|
|
|
40
40
|
* - `colorVariant` (string): Optional. Color variant for the placeholder ('primary', 'success', etc.).
|
|
41
41
|
* - `elementRef` (HTMLSpanElement): Optional. Reference to the placeholder item DOM element.
|
|
42
42
|
* - `id` (string): Optional. ID for the placeholder item element.
|
|
43
|
+
* - `isAnimated` (boolean): Deprecated compatibility no-op. Set `type` on `Placeholder.Root` to animate a placeholder group.
|
|
43
44
|
* - `size` ('xs' | 'sm' | 'lg'): Optional. Size of the placeholder.
|
|
44
|
-
* - `isAnimated` (boolean): Optional. Whether the placeholder should animate, defaults to true.
|
|
45
45
|
*/
|
|
46
46
|
declare const PlaceholderItem: import("svelte").Component<Placeholder.ItemProps, {}, "elementRef">;
|
|
47
47
|
type PlaceholderItem = ReturnType<typeof PlaceholderItem>;
|
|
@@ -7,6 +7,7 @@ export type PlaceholderRootProps = DivElement & {
|
|
|
7
7
|
};
|
|
8
8
|
export type PlaceholderItemProps = SpanElement & {
|
|
9
9
|
colorVariant?: PlaceholderItemColorVariant;
|
|
10
|
+
/** @deprecated Placeholder animation belongs on Placeholder.Root. Retained as a no-op for compatibility. */
|
|
10
11
|
isAnimated?: boolean;
|
|
11
12
|
size?: PlaceholderItemSize;
|
|
12
13
|
};
|
|
@@ -36,7 +36,7 @@ Supports percentage-based progress visualization with customizable styling, them
|
|
|
36
36
|
-->
|
|
37
37
|
<script lang="ts">import { fromStyle, toStyle, uniqueClsx } from '../common/css.js';
|
|
38
38
|
import { initProgressState, ProgressState } from './progress.svelte.js';
|
|
39
|
-
let {
|
|
39
|
+
let { 'aria-label': nativeAriaLabel, ariaLabel, backgroundColorVariant, children, class: classValues, barProps = {}, elementRef = $bindable(null), style, valueMax = 100, valueMin = 0, valueNow = 0, ...restOfProps } = $props();
|
|
40
40
|
let { class: barClassValues, isAnimated = false, isStriped = false, style: barStyle, ...restOfBarProps } = $derived(barProps);
|
|
41
41
|
// Initialize state and detect if this progress bar is part of a stacked container
|
|
42
42
|
const barState = initProgressState({
|
|
@@ -47,6 +47,19 @@ const barState = initProgressState({
|
|
|
47
47
|
// Determine where to apply width - when in a stacked layout, width is on the root element,
|
|
48
48
|
// otherwise it's applied to the inner bar element
|
|
49
49
|
let widthPlacement = $derived(barState.isStacked ? 'root' : 'bar');
|
|
50
|
+
let resolvedAriaLabel = $derived(ariaLabel ?? nativeAriaLabel ?? 'Progress Bar');
|
|
51
|
+
let normalizedProgress = $derived.by(() => {
|
|
52
|
+
if (!Number.isFinite(valueNow) || !Number.isFinite(valueMin) || !Number.isFinite(valueMax) || valueMax <= valueMin) {
|
|
53
|
+
return { max: 100, min: 0, now: 0, width: 0 };
|
|
54
|
+
}
|
|
55
|
+
const now = Math.min(Math.max(valueNow, valueMin), valueMax);
|
|
56
|
+
return {
|
|
57
|
+
max: valueMax,
|
|
58
|
+
min: valueMin,
|
|
59
|
+
now,
|
|
60
|
+
width: ((now - valueMin) / (valueMax - valueMin)) * 100
|
|
61
|
+
};
|
|
62
|
+
});
|
|
50
63
|
// Root element...
|
|
51
64
|
let classes = $derived(uniqueClsx('progress', classValues));
|
|
52
65
|
// Conditionally apply width to the root element's style if this bar is part of a stacked layout
|
|
@@ -55,7 +68,7 @@ let styles = $derived.by(() => {
|
|
|
55
68
|
let cssProps = fromStyle(style);
|
|
56
69
|
return toStyle({
|
|
57
70
|
...cssProps,
|
|
58
|
-
width: `${
|
|
71
|
+
width: `${normalizedProgress.width}%`
|
|
59
72
|
});
|
|
60
73
|
}
|
|
61
74
|
return style;
|
|
@@ -72,7 +85,7 @@ let barStyles = $derived.by(() => {
|
|
|
72
85
|
let cssProps = fromStyle(barStyle);
|
|
73
86
|
return toStyle({
|
|
74
87
|
...cssProps,
|
|
75
|
-
width: `${
|
|
88
|
+
width: `${normalizedProgress.width}%`
|
|
76
89
|
});
|
|
77
90
|
}
|
|
78
91
|
return barStyle;
|
|
@@ -86,15 +99,15 @@ let theme = $derived.by(() => {
|
|
|
86
99
|
</script>
|
|
87
100
|
|
|
88
101
|
<div
|
|
89
|
-
|
|
90
|
-
aria-
|
|
91
|
-
aria-
|
|
92
|
-
aria-
|
|
102
|
+
{...restOfProps}
|
|
103
|
+
aria-label={resolvedAriaLabel}
|
|
104
|
+
aria-valuemax={normalizedProgress.max}
|
|
105
|
+
aria-valuemin={normalizedProgress.min}
|
|
106
|
+
aria-valuenow={normalizedProgress.now}
|
|
93
107
|
bind:this={elementRef}
|
|
94
108
|
class={classes}
|
|
95
109
|
role="progressbar"
|
|
96
|
-
style={styles}
|
|
97
|
-
{...restOfProps}>
|
|
110
|
+
style={styles}>
|
|
98
111
|
<div class={barClasses} data-bs-theme={theme} style={barStyles} {...restOfBarProps}>
|
|
99
112
|
{@render children?.()}
|
|
100
113
|
</div>
|
package/dist/Row/row.svelte
CHANGED
|
@@ -43,25 +43,24 @@ A component that implements Bootstrap's grid row functionality.
|
|
|
43
43
|
-->
|
|
44
44
|
<script lang="ts">import { uniqueClsx } from '../common/css.js';
|
|
45
45
|
let { children, class: classValues, cols, elementRef = $bindable(null), ...restOfProps } = $props();
|
|
46
|
+
const breakpoints = new Set(['xs', 'sm', 'md', 'lg', 'xl', 'xxl']);
|
|
47
|
+
const isRowColsValue = (value) => value === 'auto' || (typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= 6);
|
|
46
48
|
// Generate cols classes based on the cols prop
|
|
47
49
|
let colsClasses = $derived.by(() => {
|
|
48
50
|
// If cols is a string (e.g., 'auto'), return a single row-cols-auto class
|
|
49
|
-
if (
|
|
50
|
-
return [`row-cols-${cols}`];
|
|
51
|
-
}
|
|
52
|
-
// If cols is a number (OneToSix type), generate a simple row-cols-n class
|
|
53
|
-
if (typeof cols === 'number') {
|
|
51
|
+
if (isRowColsValue(cols)) {
|
|
54
52
|
return [`row-cols-${cols}`];
|
|
55
53
|
}
|
|
56
54
|
// If cols is an object (RowColsMap type), generate breakpoint-specific classes
|
|
57
55
|
else if (cols && typeof cols === 'object') {
|
|
58
|
-
return Object.entries(cols).
|
|
59
|
-
|
|
56
|
+
return Object.entries(cols).flatMap(([breakpoint, value]) => {
|
|
57
|
+
if (!breakpoints.has(breakpoint) || !isRowColsValue(value)) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
60
|
if (breakpoint === 'xs') {
|
|
61
|
-
return `row-
|
|
61
|
+
return [`row-cols-${value}`];
|
|
62
62
|
}
|
|
63
|
-
|
|
64
|
-
return `row-cols-${breakpoint}-${value}`;
|
|
63
|
+
return [`row-cols-${breakpoint}-${value}`];
|
|
65
64
|
});
|
|
66
65
|
}
|
|
67
66
|
// Default: no row-cols classes
|
package/dist/Row/types.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import type { DivElement, OneToSix } from '../common/types.js';
|
|
2
|
+
export type RowColsValue = OneToSix | 'auto';
|
|
2
3
|
export type RowColsMap = {
|
|
3
|
-
xs?:
|
|
4
|
-
sm?:
|
|
5
|
-
md?:
|
|
6
|
-
lg?:
|
|
7
|
-
xl?:
|
|
8
|
-
xxl?:
|
|
4
|
+
xs?: RowColsValue;
|
|
5
|
+
sm?: RowColsValue;
|
|
6
|
+
md?: RowColsValue;
|
|
7
|
+
lg?: RowColsValue;
|
|
8
|
+
xl?: RowColsValue;
|
|
9
|
+
xxl?: RowColsValue;
|
|
9
10
|
};
|
|
10
11
|
export type RowRootProps = DivElement & {
|
|
11
|
-
cols?: RowColsMap |
|
|
12
|
+
cols?: RowColsMap | RowColsValue;
|
|
12
13
|
};
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { isDisabled } from '../common/dom.js';
|
|
2
|
-
import { SvelteMap } from 'svelte/reactivity';
|
|
2
|
+
import { SvelteMap, SvelteSet } from 'svelte/reactivity';
|
|
3
|
+
function resolveHashTarget(hash) {
|
|
4
|
+
const rawId = hash.startsWith('#') ? hash.slice(1) : hash;
|
|
5
|
+
if (!rawId) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
8
|
+
let decodedId = rawId;
|
|
9
|
+
try {
|
|
10
|
+
decodedId = decodeURIComponent(rawId);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
// A literal percent sequence can be a valid element ID even when it is
|
|
14
|
+
// not a valid URI escape. Fall back to the exact fragment text.
|
|
15
|
+
}
|
|
16
|
+
return document.getElementById(decodedId) ?? (decodedId === rawId ? null : document.getElementById(rawId));
|
|
17
|
+
}
|
|
3
18
|
/**
|
|
4
19
|
* Creates a scrollspy attachment that monitors elements matching a target selector
|
|
5
20
|
* and triggers a callback when they intersect with the scrolling container.
|
|
@@ -20,14 +35,26 @@ export function scrollspy(options) {
|
|
|
20
35
|
throw new Error('Scrollspy: callback must be a function');
|
|
21
36
|
}
|
|
22
37
|
// Get the target element from options targetSelector...
|
|
23
|
-
|
|
38
|
+
let targetElement;
|
|
39
|
+
try {
|
|
40
|
+
targetElement = document.querySelector(options.targetSelector);
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
throw new Error(`Scrollspy: invalid target selector "${options.targetSelector}"`);
|
|
44
|
+
}
|
|
24
45
|
if (!targetElement) {
|
|
25
46
|
throw new Error(`Scrollspy: target element not found for selector "${options.targetSelector}"`);
|
|
26
47
|
}
|
|
48
|
+
const observerOptions = {
|
|
49
|
+
root: element,
|
|
50
|
+
rootMargin: '0px 0px -25%',
|
|
51
|
+
threshold: [0.1, 0.5, 1],
|
|
52
|
+
...options.observerOptions
|
|
53
|
+
};
|
|
27
54
|
// Smooth scroll functionality
|
|
28
55
|
const handleClick = (event) => {
|
|
29
56
|
const target = event.target;
|
|
30
|
-
if (!(target instanceof
|
|
57
|
+
if (!(target instanceof Element)) {
|
|
31
58
|
return;
|
|
32
59
|
}
|
|
33
60
|
// Only handle clicks on anchor links with hash
|
|
@@ -36,32 +63,32 @@ export function scrollspy(options) {
|
|
|
36
63
|
return;
|
|
37
64
|
}
|
|
38
65
|
// Find the target element that corresponds to the clicked link's hash
|
|
39
|
-
const targetElement =
|
|
40
|
-
if (targetElement) {
|
|
66
|
+
const targetElement = resolveHashTarget(anchor.hash);
|
|
67
|
+
if (targetElement && element.contains(targetElement)) {
|
|
41
68
|
event.preventDefault();
|
|
42
|
-
//
|
|
43
|
-
const root =
|
|
69
|
+
// A null observer root represents the viewport/window.
|
|
70
|
+
const root = observerOptions.root;
|
|
71
|
+
const scrollContainer = root instanceof HTMLElement ? root : null;
|
|
44
72
|
// Calculate the scroll position
|
|
45
73
|
const elementRect = targetElement.getBoundingClientRect();
|
|
46
|
-
const containerRect = root.getBoundingClientRect();
|
|
47
74
|
let scrollTop;
|
|
48
|
-
if (
|
|
75
|
+
if (scrollContainer) {
|
|
49
76
|
// Scrolling within a container element
|
|
50
77
|
// Cast targetElement to HTMLElement to use offsetTop
|
|
51
78
|
scrollTop = targetElement.offsetTop - element.offsetTop;
|
|
52
79
|
}
|
|
53
80
|
else {
|
|
54
81
|
// Scrolling the window
|
|
55
|
-
scrollTop = elementRect.top + window.pageYOffset
|
|
82
|
+
scrollTop = elementRect.top + window.pageYOffset;
|
|
56
83
|
}
|
|
57
84
|
// Perform smooth scroll
|
|
58
|
-
if (
|
|
59
|
-
if (
|
|
60
|
-
|
|
85
|
+
if (scrollContainer) {
|
|
86
|
+
if (scrollContainer.scrollTo) {
|
|
87
|
+
scrollContainer.scrollTo({ top: scrollTop, behavior: 'smooth' });
|
|
61
88
|
}
|
|
62
89
|
else {
|
|
63
90
|
// Fallback for older browsers
|
|
64
|
-
|
|
91
|
+
scrollContainer.scrollTop = scrollTop;
|
|
65
92
|
}
|
|
66
93
|
}
|
|
67
94
|
else {
|
|
@@ -78,7 +105,7 @@ export function scrollspy(options) {
|
|
|
78
105
|
}
|
|
79
106
|
};
|
|
80
107
|
// Maps to store links and their target sections
|
|
81
|
-
const targetLinks = new
|
|
108
|
+
const targetLinks = new SvelteSet();
|
|
82
109
|
const observableSections = new SvelteMap();
|
|
83
110
|
// Find all the anchor links within the target element...
|
|
84
111
|
const anchors = targetElement.querySelectorAll('[href]');
|
|
@@ -87,24 +114,16 @@ export function scrollspy(options) {
|
|
|
87
114
|
if (!anchor.hash || ['#', '#!'].includes(anchor.hash) || isDisabled(anchor)) {
|
|
88
115
|
continue;
|
|
89
116
|
}
|
|
90
|
-
const observableSection =
|
|
91
|
-
if (observableSection !== null) {
|
|
92
|
-
targetLinks.
|
|
117
|
+
const observableSection = resolveHashTarget(anchor.hash);
|
|
118
|
+
if (observableSection !== null && element.contains(observableSection)) {
|
|
119
|
+
targetLinks.add(anchor);
|
|
93
120
|
observableSections.set(anchor.hash, observableSection);
|
|
94
121
|
// attach the click handler to the anchor
|
|
95
122
|
anchor.addEventListener('click', handleClick);
|
|
96
123
|
}
|
|
97
124
|
}
|
|
98
|
-
// Set default options for the IntersectionObserver...
|
|
99
|
-
const defaultBootstrapObserverOptions = {
|
|
100
|
-
root: element,
|
|
101
|
-
rootMargin: '0px 0px -25%',
|
|
102
|
-
threshold: [0.1, 0.5, 1]
|
|
103
|
-
};
|
|
104
|
-
// Ensure observerOptions exists, create it if it doesn't
|
|
105
|
-
options.observerOptions = { ...defaultBootstrapObserverOptions, ...options.observerOptions };
|
|
106
125
|
// Create an IntersectionObserver that will invoke the callback when targets intersect
|
|
107
|
-
const observer = new IntersectionObserver(options.callback,
|
|
126
|
+
const observer = new IntersectionObserver(options.callback, observerOptions);
|
|
108
127
|
// Start observing each target element for intersection events
|
|
109
128
|
for (const section of observableSections.values()) {
|
|
110
129
|
observer.observe(section);
|
|
@@ -112,7 +131,7 @@ export function scrollspy(options) {
|
|
|
112
131
|
// Return cleanup function that will be called when the attachment is removed
|
|
113
132
|
return () => {
|
|
114
133
|
// Remove click event listener
|
|
115
|
-
for (const anchor of targetLinks
|
|
134
|
+
for (const anchor of targetLinks) {
|
|
116
135
|
anchor.removeEventListener('click', handleClick);
|
|
117
136
|
}
|
|
118
137
|
// Disconnect the observer to prevent memory leaks
|
|
@@ -66,6 +66,9 @@ let previousTrigger = unset;
|
|
|
66
66
|
let popperInstance = null;
|
|
67
67
|
let popperPlacement = $state('');
|
|
68
68
|
let referenceElement = $state(null);
|
|
69
|
+
let isClickActive = $state(false);
|
|
70
|
+
let isHovering = $state(false);
|
|
71
|
+
let isFocusing = $state(false);
|
|
69
72
|
let currentPopperPlacement = $derived(popperPlacement || effectivePlacement || 'top');
|
|
70
73
|
let triggers = $derived.by(() => effectiveTrigger.split(' ')); // support space-delimited list of triggers
|
|
71
74
|
let classes = $derived(uniqueClsx(type, {
|
|
@@ -78,14 +81,41 @@ let classes = $derived(uniqueClsx(type, {
|
|
|
78
81
|
show: isShown
|
|
79
82
|
}, classValues));
|
|
80
83
|
// Operations...
|
|
81
|
-
const hide = () => (isShown = false);
|
|
82
84
|
const show = () => (isShown = true);
|
|
85
|
+
const hide = () => (isShown = false);
|
|
86
|
+
const hideWhenInactive = () => {
|
|
87
|
+
if (!isClickActive && !isHovering && !isFocusing) {
|
|
88
|
+
hide();
|
|
89
|
+
}
|
|
90
|
+
};
|
|
83
91
|
const toggle = () => {
|
|
84
92
|
if (triggers.includes('focus') && hasFocus(referenceElement)) {
|
|
85
93
|
// If the reference element is focused, we don't want to toggle the tooltip
|
|
86
94
|
return;
|
|
87
95
|
}
|
|
88
|
-
|
|
96
|
+
isClickActive = !isClickActive;
|
|
97
|
+
if (isClickActive) {
|
|
98
|
+
show();
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
hideWhenInactive();
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
const handleMouseEnter = () => {
|
|
105
|
+
isHovering = true;
|
|
106
|
+
show();
|
|
107
|
+
};
|
|
108
|
+
const handleMouseLeave = () => {
|
|
109
|
+
isHovering = false;
|
|
110
|
+
hideWhenInactive();
|
|
111
|
+
};
|
|
112
|
+
const handleFocusIn = () => {
|
|
113
|
+
isFocusing = true;
|
|
114
|
+
show();
|
|
115
|
+
};
|
|
116
|
+
const handleFocusOut = () => {
|
|
117
|
+
isFocusing = false;
|
|
118
|
+
hideWhenInactive();
|
|
89
119
|
};
|
|
90
120
|
const addEventListeners = () => {
|
|
91
121
|
if (!referenceElement) {
|
|
@@ -98,12 +128,12 @@ const addEventListeners = () => {
|
|
|
98
128
|
referenceElement.addEventListener('click', toggle);
|
|
99
129
|
}
|
|
100
130
|
if (triggers.includes('hover')) {
|
|
101
|
-
referenceElement.addEventListener('mouseenter',
|
|
102
|
-
referenceElement.addEventListener('mouseleave',
|
|
131
|
+
referenceElement.addEventListener('mouseenter', handleMouseEnter);
|
|
132
|
+
referenceElement.addEventListener('mouseleave', handleMouseLeave);
|
|
103
133
|
}
|
|
104
134
|
if (triggers.includes('focus')) {
|
|
105
|
-
referenceElement.addEventListener('focusin',
|
|
106
|
-
referenceElement.addEventListener('focusout',
|
|
135
|
+
referenceElement.addEventListener('focusin', handleFocusIn);
|
|
136
|
+
referenceElement.addEventListener('focusout', handleFocusOut);
|
|
107
137
|
}
|
|
108
138
|
};
|
|
109
139
|
const destroyPopper = () => {
|
|
@@ -118,10 +148,26 @@ const removeEventListeners = () => {
|
|
|
118
148
|
return;
|
|
119
149
|
}
|
|
120
150
|
referenceElement.removeEventListener('click', toggle);
|
|
121
|
-
referenceElement.removeEventListener('mouseenter',
|
|
122
|
-
referenceElement.removeEventListener('mouseleave',
|
|
123
|
-
referenceElement.removeEventListener('focusin',
|
|
124
|
-
referenceElement.removeEventListener('focusout',
|
|
151
|
+
referenceElement.removeEventListener('mouseenter', handleMouseEnter);
|
|
152
|
+
referenceElement.removeEventListener('mouseleave', handleMouseLeave);
|
|
153
|
+
referenceElement.removeEventListener('focusin', handleFocusIn);
|
|
154
|
+
referenceElement.removeEventListener('focusout', handleFocusOut);
|
|
155
|
+
};
|
|
156
|
+
const reconcileTriggerActivity = () => {
|
|
157
|
+
isClickActive = triggers.includes('click') && isClickActive;
|
|
158
|
+
isHovering = triggers.includes('hover') && !!referenceElement?.matches(':hover');
|
|
159
|
+
if (triggers.includes('focus')) {
|
|
160
|
+
isFocusing = hasFocus(referenceElement);
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
isFocusing = false;
|
|
164
|
+
}
|
|
165
|
+
if (isClickActive || isHovering || isFocusing) {
|
|
166
|
+
show();
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
hide();
|
|
170
|
+
}
|
|
125
171
|
};
|
|
126
172
|
// Event listeners...
|
|
127
173
|
const handleOnHidden = (event) => {
|
|
@@ -149,6 +195,7 @@ onMount(() => {
|
|
|
149
195
|
$effect(() => {
|
|
150
196
|
if (previousTrigger !== unset && previousTrigger !== effectiveTrigger) {
|
|
151
197
|
removeEventListeners();
|
|
198
|
+
reconcileTriggerActivity();
|
|
152
199
|
addEventListeners();
|
|
153
200
|
}
|
|
154
201
|
previousTrigger = effectiveTrigger;
|