@winkintel/bootstrap-svelte 1.0.5 → 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.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 +1 -1
package/dist/Modal/modal.svelte
CHANGED
|
@@ -48,16 +48,21 @@ Add dialogs to your site for lightboxes, user notifications, or completely custo
|
|
|
48
48
|
- `useFade` (boolean): Optional. Controls whether the modal uses a fade transition.
|
|
49
49
|
-->
|
|
50
50
|
<script lang="ts">import { acquireBodyScrollLock, noop, releaseBodyScrollLock, uniqueClsx } from '../common/index.js';
|
|
51
|
+
import { focusTopOverlay, isFocusInTopOverlay, isTopOverlay, registerOverlay, unregisterOverlay } from '../common/overlay-stack.js';
|
|
51
52
|
import { Portal } from '../index.js';
|
|
52
53
|
import { onDestroy, onMount, tick } from 'svelte';
|
|
53
54
|
import { fade, fly } from 'svelte/transition';
|
|
54
|
-
import { initModalRootState, ModalRootState } from './modal.svelte.js';
|
|
55
|
+
import { acquireModalOpenClass, initModalRootState, ModalRootState, releaseModalOpenClass } from './modal.svelte.js';
|
|
55
56
|
// Generate a unique ID for the modal element, in case one is not provided...
|
|
56
57
|
const uid = $props.id();
|
|
57
58
|
let { children, class: classValues, elementRef = $bindable(null), id = `modal-${uid}`, isShown = false, isKeyboardDismissible = true, isFocusable = true, onHide = noop, onHidePrevented = noop, onHidden = noop, onShow = noop, onShown = noop, useBackdrop = true, useFade = true, ...restOfProps } = $props();
|
|
58
59
|
// Initialize the root state of the modal component...
|
|
59
60
|
let bodyElement = $state(null);
|
|
61
|
+
let holdsModalOpenClass = false;
|
|
60
62
|
let holdsScrollLock = false;
|
|
63
|
+
let hidePreventedTimeout;
|
|
64
|
+
let overlayZIndex = $state(undefined);
|
|
65
|
+
let backdropZIndex = $state(undefined);
|
|
61
66
|
let previouslyFocusedElement = null;
|
|
62
67
|
const unset = Symbol('unset');
|
|
63
68
|
let previousIsShown = unset;
|
|
@@ -71,6 +76,17 @@ const rootState = initModalRootState({
|
|
|
71
76
|
return useBackdrop;
|
|
72
77
|
}
|
|
73
78
|
});
|
|
79
|
+
const overlayEntry = {
|
|
80
|
+
baseBackdropZIndex: 1050,
|
|
81
|
+
baseOverlayZIndex: 1055,
|
|
82
|
+
containsActiveElement: () => !!elementRef?.contains(document.activeElement),
|
|
83
|
+
focus: focusModal,
|
|
84
|
+
onKeydown: handleKeydown,
|
|
85
|
+
setLayers: (nextOverlayZIndex, nextBackdropZIndex) => {
|
|
86
|
+
overlayZIndex = nextOverlayZIndex;
|
|
87
|
+
backdropZIndex = nextBackdropZIndex;
|
|
88
|
+
}
|
|
89
|
+
};
|
|
74
90
|
// Derived classes for the modal component...
|
|
75
91
|
let isHidePrevented = $state(false);
|
|
76
92
|
let backDropClasses = $derived(uniqueClsx('modal-backdrop', { fade: useFade, show: rootState.isShown }));
|
|
@@ -106,11 +122,19 @@ $effect(() => {
|
|
|
106
122
|
});
|
|
107
123
|
$effect(() => {
|
|
108
124
|
if (rootState.isShown && !previousRootIsShown) {
|
|
125
|
+
registerOverlay(overlayEntry);
|
|
109
126
|
previouslyFocusedElement = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
110
127
|
focusModal();
|
|
111
128
|
}
|
|
112
129
|
else if (!rootState.isShown && previousRootIsShown) {
|
|
113
|
-
|
|
130
|
+
const wasTopOverlay = isTopOverlay(overlayEntry);
|
|
131
|
+
unregisterOverlay(overlayEntry);
|
|
132
|
+
if (wasTopOverlay) {
|
|
133
|
+
restoreFocus();
|
|
134
|
+
if (!isFocusInTopOverlay()) {
|
|
135
|
+
focusTopOverlay();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
114
138
|
}
|
|
115
139
|
previousRootIsShown = rootState.isShown;
|
|
116
140
|
});
|
|
@@ -120,17 +144,21 @@ const handleOnShow = (event) => {
|
|
|
120
144
|
acquireBodyScrollLock(bodyElement);
|
|
121
145
|
holdsScrollLock = true;
|
|
122
146
|
}
|
|
123
|
-
|
|
147
|
+
if (!holdsModalOpenClass) {
|
|
148
|
+
acquireModalOpenClass(bodyElement);
|
|
149
|
+
holdsModalOpenClass = true;
|
|
150
|
+
}
|
|
124
151
|
}
|
|
125
152
|
onShow(event);
|
|
126
153
|
};
|
|
127
154
|
const handleOnHidden = (event) => {
|
|
155
|
+
if (bodyElement && holdsModalOpenClass) {
|
|
156
|
+
releaseModalOpenClass(bodyElement);
|
|
157
|
+
holdsModalOpenClass = false;
|
|
158
|
+
}
|
|
128
159
|
if (bodyElement && holdsScrollLock) {
|
|
129
|
-
|
|
160
|
+
releaseBodyScrollLock(bodyElement);
|
|
130
161
|
holdsScrollLock = false;
|
|
131
|
-
if (remainingLocks === 0) {
|
|
132
|
-
bodyElement.classList.remove('modal-open');
|
|
133
|
-
}
|
|
134
162
|
}
|
|
135
163
|
onHidden(event);
|
|
136
164
|
};
|
|
@@ -152,12 +180,28 @@ function getFocusableElements() {
|
|
|
152
180
|
return [];
|
|
153
181
|
return Array.from(elementRef.querySelectorAll('a[href], button:not([disabled]), input:not([disabled]), select:not([disabled]), textarea:not([disabled]), [tabindex]:not([tabindex="-1"])')).filter((element) => !element.hasAttribute('disabled') && element.getAttribute('aria-hidden') !== 'true');
|
|
154
182
|
}
|
|
183
|
+
function triggerHidePrevented(event) {
|
|
184
|
+
onHidePrevented(event);
|
|
185
|
+
isHidePrevented = true;
|
|
186
|
+
if (hidePreventedTimeout) {
|
|
187
|
+
clearTimeout(hidePreventedTimeout);
|
|
188
|
+
}
|
|
189
|
+
hidePreventedTimeout = setTimeout(() => {
|
|
190
|
+
isHidePrevented = false;
|
|
191
|
+
hidePreventedTimeout = undefined;
|
|
192
|
+
}, 300);
|
|
193
|
+
}
|
|
155
194
|
function trapFocus(event) {
|
|
156
195
|
const focusableElements = getFocusableElements();
|
|
157
196
|
const firstElement = focusableElements[0] ?? elementRef;
|
|
158
197
|
const lastElement = focusableElements[focusableElements.length - 1] ?? elementRef;
|
|
159
198
|
if (!firstElement || !lastElement)
|
|
160
199
|
return;
|
|
200
|
+
if (!elementRef?.contains(document.activeElement)) {
|
|
201
|
+
event.preventDefault();
|
|
202
|
+
(event.shiftKey ? lastElement : firstElement).focus();
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
161
205
|
if (event.shiftKey && document.activeElement === firstElement) {
|
|
162
206
|
event.preventDefault();
|
|
163
207
|
lastElement.focus();
|
|
@@ -176,12 +220,24 @@ onMount(() => {
|
|
|
176
220
|
// Release a held scroll lock if the modal is destroyed while shown, so a
|
|
177
221
|
// removed-while-open modal doesn't permanently disable body scrolling...
|
|
178
222
|
onDestroy(() => {
|
|
223
|
+
const wasTopOverlay = isTopOverlay(overlayEntry);
|
|
224
|
+
unregisterOverlay(overlayEntry);
|
|
225
|
+
if (wasTopOverlay) {
|
|
226
|
+
restoreFocus();
|
|
227
|
+
if (!isFocusInTopOverlay()) {
|
|
228
|
+
focusTopOverlay();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (hidePreventedTimeout) {
|
|
232
|
+
clearTimeout(hidePreventedTimeout);
|
|
233
|
+
}
|
|
234
|
+
if (holdsModalOpenClass && bodyElement) {
|
|
235
|
+
releaseModalOpenClass(bodyElement);
|
|
236
|
+
holdsModalOpenClass = false;
|
|
237
|
+
}
|
|
179
238
|
if (holdsScrollLock && bodyElement) {
|
|
180
|
-
|
|
239
|
+
releaseBodyScrollLock(bodyElement);
|
|
181
240
|
holdsScrollLock = false;
|
|
182
|
-
if (remainingLocks === 0) {
|
|
183
|
-
bodyElement.classList.remove('modal-open');
|
|
184
|
-
}
|
|
185
241
|
}
|
|
186
242
|
});
|
|
187
243
|
// Dismiss the modal when the backdrop is clicked...
|
|
@@ -194,11 +250,7 @@ const handleBackdropMouseDown = (event) => {
|
|
|
194
250
|
}
|
|
195
251
|
if (rootState.isShown && useBackdrop === 'static') {
|
|
196
252
|
event.stopPropagation();
|
|
197
|
-
|
|
198
|
-
isHidePrevented = true;
|
|
199
|
-
setTimeout(() => {
|
|
200
|
-
isHidePrevented = false;
|
|
201
|
-
}, 300);
|
|
253
|
+
triggerHidePrevented(event);
|
|
202
254
|
return;
|
|
203
255
|
}
|
|
204
256
|
if (rootState.isShown && useBackdrop !== 'static') {
|
|
@@ -207,22 +259,24 @@ const handleBackdropMouseDown = (event) => {
|
|
|
207
259
|
}
|
|
208
260
|
};
|
|
209
261
|
// Handle keyboard events to dismiss modal on Escape key press
|
|
210
|
-
|
|
211
|
-
const keyboardEvent = event;
|
|
262
|
+
function handleKeydown(event) {
|
|
212
263
|
if (!rootState.isShown)
|
|
213
264
|
return;
|
|
214
|
-
if (
|
|
215
|
-
trapFocus(
|
|
265
|
+
if (event.key === 'Tab') {
|
|
266
|
+
trapFocus(event);
|
|
216
267
|
return;
|
|
217
268
|
}
|
|
218
|
-
if (
|
|
219
|
-
|
|
269
|
+
if (event.key === 'Escape') {
|
|
270
|
+
if (isKeyboardDismissible) {
|
|
271
|
+
rootState.toggleIsShown();
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
triggerHidePrevented(event);
|
|
275
|
+
}
|
|
220
276
|
}
|
|
221
|
-
}
|
|
277
|
+
}
|
|
222
278
|
</script>
|
|
223
279
|
|
|
224
|
-
<svelte:window onkeydown={handleKeydown} />
|
|
225
|
-
|
|
226
280
|
{#if rootState.isShown}
|
|
227
281
|
<div
|
|
228
282
|
aria-labelledby={rootState.titleId}
|
|
@@ -231,6 +285,7 @@ const handleKeydown = (event) => {
|
|
|
231
285
|
class={classes}
|
|
232
286
|
{id}
|
|
233
287
|
role="dialog"
|
|
288
|
+
style:z-index={overlayZIndex}
|
|
234
289
|
tabindex={isFocusable ? -1 : undefined}
|
|
235
290
|
onmousedown={handleBackdropMouseDown}
|
|
236
291
|
onintrostart={handleOnShow}
|
|
@@ -245,7 +300,12 @@ const handleKeydown = (event) => {
|
|
|
245
300
|
|
|
246
301
|
<Portal target="body">
|
|
247
302
|
{#if rootState.isBackdropShown}
|
|
248
|
-
<div
|
|
303
|
+
<div
|
|
304
|
+
class={backDropClasses}
|
|
305
|
+
onmousedown={handleBackdropMouseDown}
|
|
306
|
+
role="presentation"
|
|
307
|
+
style:z-index={backdropZIndex}
|
|
308
|
+
transition:fade={{ duration: useFade ? 150 : 0 }}>
|
|
249
309
|
</div>
|
|
250
310
|
{/if}
|
|
251
311
|
</Portal>
|
|
@@ -58,6 +58,21 @@ export class ModalHeaderState {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
const ModalRootContext = new Context('modal-root');
|
|
61
|
+
const modalOpenCounts = new WeakMap();
|
|
62
|
+
export function acquireModalOpenClass(bodyElement) {
|
|
63
|
+
const count = (modalOpenCounts.get(bodyElement) ?? 0) + 1;
|
|
64
|
+
modalOpenCounts.set(bodyElement, count);
|
|
65
|
+
bodyElement.classList.add('modal-open');
|
|
66
|
+
}
|
|
67
|
+
export function releaseModalOpenClass(bodyElement) {
|
|
68
|
+
const count = modalOpenCounts.get(bodyElement) ?? 0;
|
|
69
|
+
if (count <= 1) {
|
|
70
|
+
modalOpenCounts.delete(bodyElement);
|
|
71
|
+
bodyElement.classList.remove('modal-open');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
modalOpenCounts.set(bodyElement, count - 1);
|
|
75
|
+
}
|
|
61
76
|
/**
|
|
62
77
|
* Creates a new ModalRootState and sets it in the ModalRootContext.
|
|
63
78
|
* This is used to manage the state of the Modal component and its children.
|
package/dist/Nav/nav-link.svelte
CHANGED
|
@@ -22,7 +22,7 @@ import { initNavLinkState, NavLinkState } from '../common/nav-tab.svelte.js';
|
|
|
22
22
|
import { noop } from '../common/noop.js';
|
|
23
23
|
// Generate a unique ID for the link, in case one is not provided...
|
|
24
24
|
const uid = $props.id();
|
|
25
|
-
let { children, class: classValues, elementRef = $bindable(null), href = '#!', id = `nav-link-${uid}`, isActive, isDisabled = false, onclick = noop, role, ...restOfProps } = $props();
|
|
25
|
+
let { 'aria-controls': ariaControls, 'aria-current': ariaCurrent, 'aria-disabled': ariaDisabled, 'aria-selected': ariaSelected, children, class: classValues, elementRef = $bindable(null), href = '#!', id = `nav-link-${uid}`, isActive, isDisabled = false, onclick = noop, role, tabindex: consumerTabIndex, ...restOfProps } = $props();
|
|
26
26
|
const linkState = initNavLinkState({
|
|
27
27
|
get href() {
|
|
28
28
|
return href;
|
|
@@ -40,6 +40,10 @@ let defaultRole = $derived(linkState.isTab ? 'tab' : role);
|
|
|
40
40
|
let isActiveState = $derived(isActive !== undefined ? isActive : linkState.isActive);
|
|
41
41
|
let classes = $derived(uniqueClsx('nav-link', { active: isActiveState, disabled: isDisabled }, classValues));
|
|
42
42
|
const handleClick = (event) => {
|
|
43
|
+
if (isDisabled) {
|
|
44
|
+
event.preventDefault();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
43
47
|
linkState.onclick(event);
|
|
44
48
|
onclick(event);
|
|
45
49
|
};
|
|
@@ -61,17 +65,17 @@ $effect(() => {
|
|
|
61
65
|
</script>
|
|
62
66
|
|
|
63
67
|
<a
|
|
64
|
-
|
|
65
|
-
aria-
|
|
66
|
-
aria-
|
|
67
|
-
aria-
|
|
68
|
+
{...restOfProps}
|
|
69
|
+
aria-controls={linkState.ariaControls ?? ariaControls}
|
|
70
|
+
aria-current={isActiveState ? 'page' : ariaCurrent}
|
|
71
|
+
aria-disabled={isDisabled ? 'true' : ariaDisabled}
|
|
72
|
+
aria-selected={linkState.isTab ? isActiveState : ariaSelected}
|
|
68
73
|
bind:this={elementRef}
|
|
69
74
|
class={classes}
|
|
70
75
|
{href}
|
|
71
76
|
{id}
|
|
72
77
|
onclick={handleClick}
|
|
73
|
-
tabindex={isDisabled ? -1 :
|
|
74
|
-
role={defaultRole}
|
|
75
|
-
{...restOfProps}>
|
|
78
|
+
tabindex={isDisabled ? -1 : consumerTabIndex}
|
|
79
|
+
role={defaultRole}>
|
|
76
80
|
{@render children?.()}
|
|
77
81
|
</a>
|
|
@@ -44,7 +44,7 @@ Collapsible container for navbar content that toggles when the navbar toggler is
|
|
|
44
44
|
- `children`: Optional. Content to render inside the collapsible container.
|
|
45
45
|
- `class` (string): Optional. Additional CSS classes to apply to the container.
|
|
46
46
|
- `elementRef` (HTMLElement): Optional. Reference to the DOM element.
|
|
47
|
-
- `id` (string): Optional. Unique ID for the container. Defaults to `navbar-collapse
|
|
47
|
+
- `id` (string): Optional. Unique ID for the container. Defaults to `{navbar-id}-collapse`.
|
|
48
48
|
- `onCollapse` (function): Optional. Handler called when the container starts collapsing.
|
|
49
49
|
- `onCollapsed` (function): Optional. Handler called when the container has finished collapsing.
|
|
50
50
|
- `onExpand` (function): Optional. Handler called when the container starts expanding.
|
|
@@ -53,17 +53,18 @@ Collapsible container for navbar content that toggles when the navbar toggler is
|
|
|
53
53
|
<script lang="ts">import { noop, uniqueClsx } from '../common/index.js';
|
|
54
54
|
import { initNavbarCollapseState, NavbarCollapseState } from '../common/navbar-offcanvas.svelte.js';
|
|
55
55
|
import { Collapse } from '../index.js';
|
|
56
|
-
|
|
57
|
-
const uid = $props.id();
|
|
58
|
-
let { children, class: classValues, elementRef = $bindable(null), id = `navbar-collapse-${uid}`, onCollapse: _onCollapse = noop, onCollapsed = noop, onExpand: _onExpand = noop, onExpanded: _onExpanded = noop, ...restOfProps } = $props();
|
|
56
|
+
let { children, class: classValues, elementRef = $bindable(null), id: providedId, onCollapse = noop, onCollapsed = noop, onExpand = noop, onExpanded = noop, transitionDuration: transitionDurationOverride, ...restOfProps } = $props();
|
|
59
57
|
const collapseState = initNavbarCollapseState({
|
|
60
58
|
get id() {
|
|
61
|
-
return
|
|
59
|
+
return providedId;
|
|
62
60
|
}
|
|
63
61
|
});
|
|
64
62
|
let classes = $derived(uniqueClsx('navbar-collapse', classValues));
|
|
65
|
-
let transitionDuration = $derived(collapseState.root.transitionDuration);
|
|
63
|
+
let transitionDuration = $derived(transitionDurationOverride ?? collapseState.root.transitionDuration);
|
|
66
64
|
let isExpandedState = $derived(collapseState.isExpanded);
|
|
65
|
+
$effect(() => {
|
|
66
|
+
collapseState.root.ariaControls = collapseState.id;
|
|
67
|
+
});
|
|
67
68
|
function handleOnCollapsed(event) {
|
|
68
69
|
// This will ensure that not transition animation will occur when the Navbar is expanded...
|
|
69
70
|
collapseState.root.transitionDuration = 0;
|
|
@@ -71,6 +72,16 @@ function handleOnCollapsed(event) {
|
|
|
71
72
|
}
|
|
72
73
|
</script>
|
|
73
74
|
|
|
74
|
-
<Collapse
|
|
75
|
+
<Collapse
|
|
76
|
+
{...restOfProps}
|
|
77
|
+
bind:elementRef
|
|
78
|
+
class={classes}
|
|
79
|
+
id={collapseState.id}
|
|
80
|
+
isExpanded={isExpandedState}
|
|
81
|
+
{transitionDuration}
|
|
82
|
+
{onCollapse}
|
|
83
|
+
onCollapsed={handleOnCollapsed}
|
|
84
|
+
{onExpand}
|
|
85
|
+
{onExpanded}>
|
|
75
86
|
{@render children?.()}
|
|
76
87
|
</Collapse>
|
|
@@ -44,7 +44,7 @@ import type { Navbar } from './index.js';
|
|
|
44
44
|
* - `children`: Optional. Content to render inside the collapsible container.
|
|
45
45
|
* - `class` (string): Optional. Additional CSS classes to apply to the container.
|
|
46
46
|
* - `elementRef` (HTMLElement): Optional. Reference to the DOM element.
|
|
47
|
-
* - `id` (string): Optional. Unique ID for the container. Defaults to `navbar-collapse
|
|
47
|
+
* - `id` (string): Optional. Unique ID for the container. Defaults to `{navbar-id}-collapse`.
|
|
48
48
|
* - `onCollapse` (function): Optional. Handler called when the container starts collapsing.
|
|
49
49
|
* - `onCollapsed` (function): Optional. Handler called when the container has finished collapsing.
|
|
50
50
|
* - `onExpand` (function): Optional. Handler called when the container starts expanding.
|
|
@@ -58,7 +58,7 @@ const _rootState = initNavRootState({
|
|
|
58
58
|
return id;
|
|
59
59
|
}
|
|
60
60
|
});
|
|
61
|
-
let classes = $derived(uniqueClsx('navbar-nav', { 'navbar-
|
|
61
|
+
let classes = $derived(uniqueClsx('navbar-nav', { 'navbar-nav-scroll': isVerticalScrolling }, classValues));
|
|
62
62
|
</script>
|
|
63
63
|
|
|
64
64
|
<ul bind:this={elementRef} class={classes} {id} {...restOfProps}>
|
|
@@ -41,35 +41,37 @@ Toggle button for collapsible navbar content. Controls the expansion/collapse of
|
|
|
41
41
|
- `class` (string): Optional. Additional CSS classes to apply to the toggle button.
|
|
42
42
|
- `elementRef` (HTMLButtonElement): Optional. Reference to the DOM button element.
|
|
43
43
|
- `id` (string): Optional. Unique ID for the toggle button. Defaults to `navbar-toggler-{uid}`.
|
|
44
|
-
- `type` (
|
|
44
|
+
- `type` ('button'): Optional compatibility prop. The toggler always renders `type="button"`.
|
|
45
45
|
-->
|
|
46
|
-
<script lang="ts">import { uniqueClsx } from '../common/index.js';
|
|
46
|
+
<script lang="ts">import { noop, uniqueClsx } from '../common/index.js';
|
|
47
47
|
import { initNavbarTogglerState, NavbarTogglerState } from '../common/navbar-offcanvas.svelte.js';
|
|
48
48
|
// Generate a unique ID for the component, in case one is not provided...
|
|
49
49
|
const uid = $props.id();
|
|
50
|
-
let {
|
|
50
|
+
let { 'aria-controls': consumerAriaControls, 'aria-expanded': _consumerAriaExpanded, 'aria-label': nativeAriaLabel, ariaLabel, children, class: classValues, elementRef = $bindable(null), id = `navbar-toggler-${uid}`, onclick = noop, type: _type, ...restOfProps } = $props();
|
|
51
51
|
const togglerState = initNavbarTogglerState({
|
|
52
52
|
get id() {
|
|
53
53
|
return id;
|
|
54
54
|
}
|
|
55
55
|
});
|
|
56
|
-
let ariaControls = $derived(togglerState.root.ariaControls);
|
|
56
|
+
let ariaControls = $derived(consumerAriaControls ?? togglerState.root.ariaControls);
|
|
57
|
+
let resolvedAriaLabel = $derived(ariaLabel ?? nativeAriaLabel ?? 'Toggle navigation');
|
|
57
58
|
let isExpanded = $derived(togglerState.isExpanded);
|
|
58
59
|
let classes = $derived(uniqueClsx('navbar-toggler', { collapsed: !isExpanded }, classValues));
|
|
59
60
|
const handleClick = (_event) => {
|
|
60
61
|
togglerState.onclick();
|
|
62
|
+
onclick?.(_event);
|
|
61
63
|
};
|
|
62
64
|
</script>
|
|
63
65
|
|
|
64
66
|
<button
|
|
67
|
+
{...restOfProps}
|
|
65
68
|
aria-controls={ariaControls}
|
|
66
69
|
aria-expanded={isExpanded}
|
|
67
|
-
aria-label={
|
|
70
|
+
aria-label={resolvedAriaLabel}
|
|
68
71
|
bind:this={elementRef}
|
|
69
72
|
class={classes}
|
|
70
73
|
{id}
|
|
71
74
|
onclick={handleClick}
|
|
72
|
-
type="button"
|
|
73
|
-
{...restOfProps}>
|
|
75
|
+
type="button">
|
|
74
76
|
{@render children?.()}
|
|
75
77
|
</button>
|
|
@@ -41,7 +41,7 @@ import type { Navbar } from './index.js';
|
|
|
41
41
|
* - `class` (string): Optional. Additional CSS classes to apply to the toggle button.
|
|
42
42
|
* - `elementRef` (HTMLButtonElement): Optional. Reference to the DOM button element.
|
|
43
43
|
* - `id` (string): Optional. Unique ID for the toggle button. Defaults to `navbar-toggler-{uid}`.
|
|
44
|
-
* - `type` (
|
|
44
|
+
* - `type` ('button'): Optional compatibility prop. The toggler always renders `type="button"`.
|
|
45
45
|
*/
|
|
46
46
|
declare const NavbarToggler: import("svelte").Component<Navbar.TogglerProps, {}, "elementRef">;
|
|
47
47
|
type NavbarToggler = ReturnType<typeof NavbarToggler>;
|
package/dist/Navbar/types.d.ts
CHANGED
|
@@ -17,7 +17,8 @@ export type NavbarNavProps = UListElement & {
|
|
|
17
17
|
};
|
|
18
18
|
export type NavbarTextProps = DivElement;
|
|
19
19
|
export type NavbarTogglerIconProps = SpanElement;
|
|
20
|
-
export type NavbarTogglerProps = ButtonElement & {
|
|
20
|
+
export type NavbarTogglerProps = Omit<ButtonElement, 'type'> & {
|
|
21
21
|
ariaLabel?: string;
|
|
22
|
-
|
|
22
|
+
/** Retained for compatibility; Navbar.Toggler always renders a non-submitting button. */
|
|
23
|
+
type?: 'button';
|
|
23
24
|
};
|
|
@@ -58,6 +58,7 @@ Build hidden sidebars into your project for navigation, shopping carts, and more
|
|
|
58
58
|
-->
|
|
59
59
|
<script lang="ts">import { noop, uniqueClsx } from '../common/index.js';
|
|
60
60
|
import { initOffcanvasRootState, OffcanvasRootState } from '../common/navbar-offcanvas.svelte.js';
|
|
61
|
+
import { isTopOverlay, registerOverlay, unregisterOverlay } from '../common/overlay-stack.js';
|
|
61
62
|
import { acquireBodyScrollLock, releaseBodyScrollLock } from '../common/scrollbar.js';
|
|
62
63
|
import { onDestroy, onMount, tick } from 'svelte';
|
|
63
64
|
import { fade, fly } from 'svelte/transition';
|
|
@@ -67,8 +68,11 @@ let { children, class: classValues, dir = 'ltr', elementRef = $bindable(null), i
|
|
|
67
68
|
// Initialize the root state of the offcanvas component...
|
|
68
69
|
let bodyElement = $state(null);
|
|
69
70
|
let holdsScrollLock = false;
|
|
71
|
+
let overlayZIndex = $state(undefined);
|
|
72
|
+
let backdropZIndex = $state(undefined);
|
|
70
73
|
const unset = Symbol('unset');
|
|
71
74
|
let previousIsShown = unset;
|
|
75
|
+
let previousRootIsOverlayShown = false;
|
|
72
76
|
let previousShowOnBreakpoint = unset;
|
|
73
77
|
let previousUseBackdrop = unset;
|
|
74
78
|
const rootState = initOffcanvasRootState({
|
|
@@ -85,6 +89,15 @@ const rootState = initOffcanvasRootState({
|
|
|
85
89
|
return useBackdrop;
|
|
86
90
|
}
|
|
87
91
|
});
|
|
92
|
+
const overlayEntry = {
|
|
93
|
+
baseBackdropZIndex: 1040,
|
|
94
|
+
baseOverlayZIndex: 1045,
|
|
95
|
+
onKeydown: handleKeydown,
|
|
96
|
+
setLayers: (nextOverlayZIndex, nextBackdropZIndex) => {
|
|
97
|
+
overlayZIndex = nextOverlayZIndex;
|
|
98
|
+
backdropZIndex = nextBackdropZIndex;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
88
101
|
// Set up the derived state for the offcanvas component...
|
|
89
102
|
let flyX = $derived.by(() => {
|
|
90
103
|
if (placement === 'start' && dir === 'ltr')
|
|
@@ -144,6 +157,16 @@ $effect(() => {
|
|
|
144
157
|
}
|
|
145
158
|
previousShowOnBreakpoint = showOnBreakpoint;
|
|
146
159
|
});
|
|
160
|
+
$effect(() => {
|
|
161
|
+
const isOverlayShown = rootState.isShown && !rootState.isMediaQueryMatched;
|
|
162
|
+
if (isOverlayShown && !previousRootIsOverlayShown) {
|
|
163
|
+
registerOverlay(overlayEntry);
|
|
164
|
+
}
|
|
165
|
+
else if (!isOverlayShown && previousRootIsOverlayShown) {
|
|
166
|
+
unregisterOverlay(overlayEntry);
|
|
167
|
+
}
|
|
168
|
+
previousRootIsOverlayShown = isOverlayShown;
|
|
169
|
+
});
|
|
147
170
|
// Get the body element when the component is mounted...
|
|
148
171
|
onMount(() => {
|
|
149
172
|
if (typeof window !== 'undefined') {
|
|
@@ -170,6 +193,7 @@ const handleOnHidden = (event) => {
|
|
|
170
193
|
// so a removed-while-open offcanvas doesn't permanently disable body
|
|
171
194
|
// scrolling...
|
|
172
195
|
onDestroy(() => {
|
|
196
|
+
unregisterOverlay(overlayEntry);
|
|
173
197
|
if (holdsScrollLock && bodyElement) {
|
|
174
198
|
releaseBodyScrollLock(bodyElement);
|
|
175
199
|
holdsScrollLock = false;
|
|
@@ -177,6 +201,8 @@ onDestroy(() => {
|
|
|
177
201
|
});
|
|
178
202
|
// Dismiss the offcanvas when the backdrop is clicked...
|
|
179
203
|
const handleBackdropMouseDown = (event) => {
|
|
204
|
+
if (!isTopOverlay(overlayEntry))
|
|
205
|
+
return;
|
|
180
206
|
const target = event.target;
|
|
181
207
|
const nearestOffcanvas = target.closest('.offcanvas');
|
|
182
208
|
if (nearestOffcanvas && nearestOffcanvas.id === id) {
|
|
@@ -194,19 +220,17 @@ const handleBackdropMouseDown = (event) => {
|
|
|
194
220
|
}
|
|
195
221
|
};
|
|
196
222
|
// Dismiss the offcanvas on Escape key press
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (rootState.isShown && !isKeyboardDismissible && useBackdrop === 'static') {
|
|
223
|
+
function handleKeydown(event) {
|
|
224
|
+
if (rootState.isShown && !isKeyboardDismissible && useBackdrop === 'static' && event.key === 'Escape') {
|
|
200
225
|
onHidePrevented(event);
|
|
201
226
|
return;
|
|
202
227
|
}
|
|
203
|
-
if (rootState.isShown && isKeyboardDismissible &&
|
|
228
|
+
if (rootState.isShown && isKeyboardDismissible && event.key === 'Escape') {
|
|
204
229
|
rootState.toggleIsShown();
|
|
205
230
|
}
|
|
206
|
-
}
|
|
231
|
+
}
|
|
207
232
|
</script>
|
|
208
233
|
|
|
209
|
-
<svelte:window on:keydown={handleKeydown} />
|
|
210
234
|
<svelte:body on:mousedown={handleBackdropMouseDown} />
|
|
211
235
|
|
|
212
236
|
{#if rootState.isShown}
|
|
@@ -220,6 +244,7 @@ const handleKeydown = (event) => {
|
|
|
220
244
|
onoutrostart={onHide}
|
|
221
245
|
onoutroend={handleOnHidden}
|
|
222
246
|
role={rootState.isShown ? 'dialog' : undefined}
|
|
247
|
+
style:z-index={overlayZIndex}
|
|
223
248
|
tabindex="-1"
|
|
224
249
|
transition:fly={{ duration: rootState.transitionDuration, x: flyX, y: flyY, opacity: 1 }}
|
|
225
250
|
{...restOfProps}>
|
|
@@ -228,5 +253,11 @@ const handleKeydown = (event) => {
|
|
|
228
253
|
{/if}
|
|
229
254
|
|
|
230
255
|
{#if rootState.isBackdropShown}
|
|
231
|
-
<div
|
|
256
|
+
<div
|
|
257
|
+
class={backDropClasses}
|
|
258
|
+
onmousedown={handleBackdropMouseDown}
|
|
259
|
+
role="presentation"
|
|
260
|
+
style:z-index={backdropZIndex}
|
|
261
|
+
transition:fade={{ duration: 150 }}>
|
|
262
|
+
</div>
|
|
232
263
|
{/if}
|
|
@@ -34,7 +34,7 @@ Pagination item component to wrap Pagination.Link components.
|
|
|
34
34
|
import { initPaginationItemState, PaginationItemState } from './pagination.svelte.js';
|
|
35
35
|
// Generate a unique ID for the collapse element, in case one is not provided...
|
|
36
36
|
const uid = $props.id();
|
|
37
|
-
let { children, class: classValues, elementRef = $bindable(null), id = `page-item-${uid}`, isActive = false, isDisabled = false, ...restOfProps } = $props();
|
|
37
|
+
let { children, class: classValues, elementRef = $bindable(null), id = `page-item-${uid}`, isActive = false, isDisabled = false, 'aria-current': ariaCurrent, ...restOfProps } = $props();
|
|
38
38
|
const itemState = initPaginationItemState({
|
|
39
39
|
get id() {
|
|
40
40
|
return id;
|
|
@@ -51,31 +51,38 @@ let classes = $derived(uniqueClsx('page-item', {
|
|
|
51
51
|
disabled: itemState.isDisabled
|
|
52
52
|
}, classValues));
|
|
53
53
|
$effect(() => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
itemState.root.setPaginationItemActive(id);
|
|
59
|
-
}
|
|
60
|
-
else if (!isActive && isItemActive) {
|
|
61
|
-
itemState.root.setPaginationItemActive('');
|
|
62
|
-
}
|
|
54
|
+
const itemId = id;
|
|
55
|
+
const itemIsActive = isActive;
|
|
56
|
+
if (!itemId) {
|
|
57
|
+
return;
|
|
63
58
|
}
|
|
59
|
+
if (itemIsActive) {
|
|
60
|
+
itemState.root.setPaginationItemActive(itemId);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
itemState.root.removePaginationItemActive(itemId);
|
|
64
|
+
}
|
|
65
|
+
return () => {
|
|
66
|
+
// The root may have activated this identity through a stateful link
|
|
67
|
+
// click even when the declarative isActive prop is false.
|
|
68
|
+
itemState.root.removePaginationItemActive(itemId);
|
|
69
|
+
};
|
|
64
70
|
});
|
|
65
71
|
$effect(() => {
|
|
66
|
-
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
const itemId = id;
|
|
73
|
+
if (!itemId) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (isDisabled) {
|
|
77
|
+
itemState.root.setPaginationItemDisabled(itemId);
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
itemState.root.removePaginationItemDisabled(itemId);
|
|
75
81
|
}
|
|
82
|
+
return () => itemState.root.removePaginationItemDisabled(itemId);
|
|
76
83
|
});
|
|
77
84
|
</script>
|
|
78
85
|
|
|
79
|
-
<li aria-current={isActive ? 'page' :
|
|
86
|
+
<li {...restOfProps} aria-current={itemState.isActive ? 'page' : ariaCurrent} bind:this={elementRef} class={classes} {id}>
|
|
80
87
|
{@render children?.()}
|
|
81
88
|
</li>
|
|
@@ -25,21 +25,36 @@ import { noop } from '../common/noop.js';
|
|
|
25
25
|
import { initPaginationLinkState, PaginationLinkState } from './pagination.svelte.js';
|
|
26
26
|
// Generate a unique ID for the collapse element, in case one is not provided...
|
|
27
27
|
const uid = $props.id();
|
|
28
|
-
let { 'aria-label': ariaLabel, children, class: classValues, elementRef = $bindable(null), href = '#!', id = `page-link-${uid}`, onclick = noop, ...restOfProps } = $props();
|
|
28
|
+
let { 'aria-label': ariaLabel, 'aria-disabled': ariaDisabled, children, class: classValues, elementRef = $bindable(null), href = '#!', id = `page-link-${uid}`, isDisabled = false, onclick = noop, tabindex: consumerTabIndex, ...restOfProps } = $props();
|
|
29
29
|
const linkState = initPaginationLinkState({
|
|
30
30
|
get id() {
|
|
31
31
|
return id;
|
|
32
|
+
},
|
|
33
|
+
get isDisabled() {
|
|
34
|
+
return isDisabled;
|
|
32
35
|
}
|
|
33
36
|
});
|
|
34
|
-
let classes = $derived(uniqueClsx('page-link', classValues));
|
|
35
|
-
let
|
|
36
|
-
let tabIndex = $derived(isDisabled ? -1 : 0);
|
|
37
|
+
let classes = $derived(uniqueClsx('page-link', { disabled: linkState.isDisabled }, classValues));
|
|
38
|
+
let tabIndex = $derived(linkState.isDisabled ? -1 : (consumerTabIndex ?? 0));
|
|
37
39
|
const handleClick = (event) => {
|
|
40
|
+
if (linkState.isDisabled) {
|
|
41
|
+
event.preventDefault();
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
38
44
|
linkState.onclick();
|
|
39
45
|
onclick(event);
|
|
40
46
|
};
|
|
41
47
|
</script>
|
|
42
48
|
|
|
43
|
-
<a
|
|
49
|
+
<a
|
|
50
|
+
{...restOfProps}
|
|
51
|
+
aria-label={ariaLabel}
|
|
52
|
+
aria-disabled={linkState.isDisabled ? 'true' : ariaDisabled}
|
|
53
|
+
bind:this={elementRef}
|
|
54
|
+
class={classes}
|
|
55
|
+
{href}
|
|
56
|
+
{id}
|
|
57
|
+
onclick={handleClick}
|
|
58
|
+
tabindex={tabIndex}>
|
|
44
59
|
{@render children?.()}
|
|
45
60
|
</a>
|