@winkintel/bootstrap-svelte 1.0.5 → 1.0.7

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.
Files changed (81) hide show
  1. package/README.md +1 -1
  2. package/dist/Alert/alert.svelte +4 -2
  3. package/dist/Badge/badge.svelte +6 -4
  4. package/dist/Badge/badge.svelte.d.ts +2 -2
  5. package/dist/Badge/types.d.ts +2 -1
  6. package/dist/Badge/types.js +3 -11
  7. package/dist/Button/button.svelte +29 -9
  8. package/dist/ButtonGroup/button-group.svelte +3 -2
  9. package/dist/ButtonGroup/button-toolbar.svelte +3 -2
  10. package/dist/ButtonGroup/types.contract.d.ts +1 -0
  11. package/dist/ButtonGroup/types.contract.js +15 -0
  12. package/dist/ButtonGroup/types.d.ts +2 -2
  13. package/dist/Carousel/carousel-indicator-button.svelte +8 -1
  14. package/dist/Carousel/carousel-item.svelte +29 -9
  15. package/dist/Carousel/carousel.svelte +12 -5
  16. package/dist/Carousel/carousel.svelte.js +200 -42
  17. package/dist/Col/col.svelte +30 -22
  18. package/dist/Collapse/collapse.svelte +3 -3
  19. package/dist/Collapse/transition.js +78 -15
  20. package/dist/Collapse/types.d.ts +1 -0
  21. package/dist/Dropdown/dropdown-item.svelte +7 -0
  22. package/dist/Dropdown/dropdown.svelte.js +66 -19
  23. package/dist/Form/form-check-input.svelte +6 -6
  24. package/dist/Form/form-color-input.svelte +8 -3
  25. package/dist/Form/form-date-input.svelte +4 -4
  26. package/dist/Form/form-datetime-local-input.svelte +4 -4
  27. package/dist/Form/form-email-input.svelte +4 -4
  28. package/dist/Form/form-file-input.svelte +46 -6
  29. package/dist/Form/form-file-input.svelte.d.ts +3 -2
  30. package/dist/Form/form-hidden-input.svelte +1 -1
  31. package/dist/Form/form-hidden-input.svelte.d.ts +1 -1
  32. package/dist/Form/form-image-input.svelte +1 -1
  33. package/dist/Form/form-image-input.svelte.d.ts +2 -1
  34. package/dist/Form/form-month-input.svelte +4 -4
  35. package/dist/Form/form-number-input.svelte +4 -4
  36. package/dist/Form/form-password-input.svelte +4 -4
  37. package/dist/Form/form-radio-input.svelte +53 -3
  38. package/dist/Form/form-radio-input.svelte.d.ts +3 -2
  39. package/dist/Form/form-range-input.svelte +1 -1
  40. package/dist/Form/form-range-input.svelte.d.ts +2 -1
  41. package/dist/Form/form-search-input.svelte +4 -4
  42. package/dist/Form/form-select.svelte +7 -7
  43. package/dist/Form/form-switch-input.svelte +4 -4
  44. package/dist/Form/form-telephone-input.svelte +4 -4
  45. package/dist/Form/form-text-area.svelte +4 -4
  46. package/dist/Form/form-text-input.svelte +4 -4
  47. package/dist/Form/form-time-input.svelte +4 -4
  48. package/dist/Form/form-url-input.svelte +4 -4
  49. package/dist/Form/form-week-Input.svelte +4 -4
  50. package/dist/Form/types.d.ts +26 -20
  51. package/dist/ListGroup/list-group-item-action.svelte +14 -7
  52. package/dist/Modal/modal.svelte +86 -26
  53. package/dist/Modal/modal.svelte.js +15 -0
  54. package/dist/Nav/nav-link.svelte +12 -8
  55. package/dist/Navbar/navbar-collapse.svelte +18 -7
  56. package/dist/Navbar/navbar-collapse.svelte.d.ts +1 -1
  57. package/dist/Navbar/navbar-nav.svelte +1 -1
  58. package/dist/Navbar/navbar-toggler.svelte +9 -7
  59. package/dist/Navbar/navbar-toggler.svelte.d.ts +1 -1
  60. package/dist/Navbar/types.d.ts +3 -2
  61. package/dist/Offcanvas/offcanvas.svelte +38 -7
  62. package/dist/Pagination/pagination-item.svelte +27 -20
  63. package/dist/Pagination/pagination-link.svelte +20 -5
  64. package/dist/Pagination/pagination.svelte.js +28 -8
  65. package/dist/Placeholder/placeholder-item.svelte +7 -2
  66. package/dist/Placeholder/placeholder-item.svelte.d.ts +1 -1
  67. package/dist/Placeholder/types.d.ts +1 -0
  68. package/dist/Portal/portal.svelte +6 -1
  69. package/dist/Progress/progress-bar.svelte +22 -9
  70. package/dist/Row/row.svelte +9 -10
  71. package/dist/Row/types.d.ts +8 -7
  72. package/dist/Scrollspy/scrollspy-attachment.svelte.js +47 -28
  73. package/dist/Tooltip/tooltip.svelte +57 -10
  74. package/dist/common/css.d.ts +3 -1
  75. package/dist/common/css.js +97 -25
  76. package/dist/common/navbar-offcanvas.svelte.d.ts +2 -0
  77. package/dist/common/navbar-offcanvas.svelte.js +5 -4
  78. package/dist/common/overlay-stack.d.ts +13 -0
  79. package/dist/common/overlay-stack.js +49 -0
  80. package/dist/common/scrollbar.js +24 -8
  81. package/package.json +21 -21
@@ -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
- restoreFocus();
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
- bodyElement.classList.add('modal-open');
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
- const remainingLocks = releaseBodyScrollLock(bodyElement);
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
- const remainingLocks = releaseBodyScrollLock(bodyElement);
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
- onHidePrevented(event);
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
- const handleKeydown = (event) => {
211
- const keyboardEvent = event;
262
+ function handleKeydown(event) {
212
263
  if (!rootState.isShown)
213
264
  return;
214
- if (keyboardEvent.key === 'Tab') {
215
- trapFocus(keyboardEvent);
265
+ if (event.key === 'Tab') {
266
+ trapFocus(event);
216
267
  return;
217
268
  }
218
- if (rootState.isShown && isKeyboardDismissible && keyboardEvent.key === 'Escape') {
219
- rootState.toggleIsShown();
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 class={backDropClasses} onmousedown={handleBackdropMouseDown} role="presentation" transition:fade={{ duration: useFade ? 150 : 0 }}>
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.
@@ -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
- aria-controls={linkState.ariaControls}
65
- aria-current={isActiveState ? 'page' : undefined}
66
- aria-disabled={isDisabled ? 'true' : undefined}
67
- aria-selected={linkState.isTab ? isActiveState : undefined}
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 : undefined}
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-{uid}`.
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
- // Generate a unique ID for the component, in case one is not provided...
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 id;
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 bind:elementRef class={classes} {id} isExpanded={isExpandedState} {transitionDuration} onCollapsed={handleOnCollapsed} {...restOfProps}>
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-{uid}`.
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-scrolling': isVerticalScrolling }, classValues));
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` (string): Optional. Button type attribute.
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 { ariaLabel = 'Toggle navigation', children, class: classValues, elementRef = $bindable(null), id = `navbar-toggler-${uid}`, type: _type, ...restOfProps } = $props();
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={ariaLabel}
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` (string): Optional. Button type attribute.
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>;
@@ -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
- type?: HTMLButtonElement['type'];
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
- const handleKeydown = (event) => {
198
- const keyboardEvent = event;
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 && keyboardEvent.key === 'Escape') {
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 class={backDropClasses} onmousedown={handleBackdropMouseDown} role="presentation" transition:fade={{ duration: 150 }}></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
- // Update the state when the isActive prop changes...
55
- if (id && itemState.isActive !== isActive) {
56
- const isItemActive = itemState.root.isPaginationItemActive(id);
57
- if (isActive && !isItemActive) {
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
- // Update the state when the isDisabled prop changes...
67
- if (id && itemState.isDisabled !== isDisabled) {
68
- const isItemDisabled = itemState.root.isPaginationItemDisabled(id);
69
- if (isDisabled && !isItemDisabled) {
70
- itemState.root.setPaginationItemDisabled(id);
71
- }
72
- else if (!isDisabled && isItemDisabled) {
73
- itemState.root.setPaginationItemDisabled('');
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' : undefined} bind:this={elementRef} class={classes} {id} {...restOfProps}>
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 isDisabled = $derived(linkState.item.isDisabled);
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 aria-label={ariaLabel} bind:this={elementRef} class={classes} {href} {id} onclick={handleClick} tabindex={tabIndex} {...restOfProps}>
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>