@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.
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-title.svelte +7 -7
  53. package/dist/Modal/modal.svelte +86 -26
  54. package/dist/Modal/modal.svelte.js +15 -0
  55. package/dist/Nav/nav-link.svelte +12 -8
  56. package/dist/Navbar/navbar-collapse.svelte +18 -7
  57. package/dist/Navbar/navbar-collapse.svelte.d.ts +1 -1
  58. package/dist/Navbar/navbar-nav.svelte +1 -1
  59. package/dist/Navbar/navbar-toggler.svelte +9 -7
  60. package/dist/Navbar/navbar-toggler.svelte.d.ts +1 -1
  61. package/dist/Navbar/types.d.ts +3 -2
  62. package/dist/Offcanvas/offcanvas.svelte +38 -7
  63. package/dist/Pagination/pagination-item.svelte +27 -20
  64. package/dist/Pagination/pagination-link.svelte +20 -5
  65. package/dist/Pagination/pagination.svelte.js +28 -8
  66. package/dist/Placeholder/placeholder-item.svelte +7 -2
  67. package/dist/Placeholder/placeholder-item.svelte.d.ts +1 -1
  68. package/dist/Placeholder/types.d.ts +1 -0
  69. package/dist/Portal/portal.svelte +6 -1
  70. package/dist/Progress/progress-bar.svelte +22 -9
  71. package/dist/Row/row.svelte +9 -10
  72. package/dist/Row/types.d.ts +8 -7
  73. package/dist/Scrollspy/scrollspy-attachment.svelte.js +47 -28
  74. package/dist/Tooltip/tooltip.svelte +57 -10
  75. package/dist/common/css.js +96 -24
  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 +3 -1
@@ -1,3 +1,46 @@
1
+ const hasValidTransitionDuration = (transitionDuration) => transitionDuration !== undefined && Number.isFinite(transitionDuration) && transitionDuration >= 0;
2
+ const resolveTransitionDuration = (node, transitionDuration) => hasValidTransitionDuration(transitionDuration) ? transitionDuration : getTransitionDuration(node);
3
+ const transitionTimingStates = new WeakMap();
4
+ const applyTransitionTiming = (node, duration) => {
5
+ const owner = {};
6
+ let state = transitionTimingStates.get(node);
7
+ if (!state) {
8
+ state = {
9
+ duration: {
10
+ value: node.style.getPropertyValue('transition-duration'),
11
+ priority: node.style.getPropertyPriority('transition-duration')
12
+ },
13
+ delay: {
14
+ value: node.style.getPropertyValue('transition-delay'),
15
+ priority: node.style.getPropertyPriority('transition-delay')
16
+ },
17
+ owner
18
+ };
19
+ transitionTimingStates.set(node, state);
20
+ }
21
+ else {
22
+ state.owner = owner;
23
+ }
24
+ node.style.setProperty('transition-duration', `${duration}ms`);
25
+ node.style.setProperty('transition-delay', '0ms');
26
+ return () => {
27
+ if (transitionTimingStates.get(node)?.owner !== owner)
28
+ return;
29
+ transitionTimingStates.delete(node);
30
+ if (state.duration.value) {
31
+ node.style.setProperty('transition-duration', state.duration.value, state.duration.priority);
32
+ }
33
+ else {
34
+ node.style.removeProperty('transition-duration');
35
+ }
36
+ if (state.delay.value) {
37
+ node.style.setProperty('transition-delay', state.delay.value, state.delay.priority);
38
+ }
39
+ else {
40
+ node.style.removeProperty('transition-delay');
41
+ }
42
+ };
43
+ };
1
44
  export function collapseIn(node, params) {
2
45
  const isHorizontal = params.isHorizontal;
3
46
  // Choose dimension to animate: width for horizontal, height for vertical
@@ -6,7 +49,16 @@ export function collapseIn(node, params) {
6
49
  node.classList.add('collapsing');
7
50
  node.classList.remove('collapse', 'show');
8
51
  node.style[stylePropName] = '0';
9
- const duration = getTransitionDuration(node);
52
+ const duration = resolveTransitionDuration(node, params.transitionDuration);
53
+ const restoreTransitionTiming = hasValidTransitionDuration(params.transitionDuration) ? applyTransitionTiming(node, duration) : () => undefined;
54
+ // Svelte skips transition ticks when duration is zero, so complete the
55
+ // Bootstrap class state ourselves for explicit no-animation transitions.
56
+ if (duration === 0) {
57
+ node.classList.remove('collapsing');
58
+ node.classList.add('collapse', 'show');
59
+ node.style[stylePropName] = '';
60
+ restoreTransitionTiming();
61
+ }
10
62
  return {
11
63
  duration,
12
64
  css: (_progress, _remaining) => 'overflow: hidden;',
@@ -26,6 +78,7 @@ export function collapseIn(node, params) {
26
78
  node.classList.remove('collapsing');
27
79
  node.classList.add('collapse', 'show');
28
80
  node.style[stylePropName] = '';
81
+ restoreTransitionTiming();
29
82
  }
30
83
  }
31
84
  };
@@ -38,7 +91,15 @@ export function collapseOut(node, params) {
38
91
  // Bootstrap collapse semantics: start with "collapsing" state, remove "collapse" and "show"
39
92
  node.classList.add('collapsing');
40
93
  node.classList.remove('collapse', 'show');
41
- const duration = getTransitionDuration(node);
94
+ const duration = resolveTransitionDuration(node, params.transitionDuration);
95
+ const restoreTransitionTiming = hasValidTransitionDuration(params.transitionDuration) ? applyTransitionTiming(node, duration) : () => undefined;
96
+ // See collapseIn: a zero-duration transition does not receive a final tick.
97
+ if (duration === 0) {
98
+ node.classList.remove('collapsing');
99
+ node.classList.add('collapse');
100
+ node.style[dimension] = '';
101
+ restoreTransitionTiming();
102
+ }
42
103
  return {
43
104
  duration,
44
105
  css: (_progress, _remaining) => 'overflow: hidden;',
@@ -52,6 +113,7 @@ export function collapseOut(node, params) {
52
113
  // Completion: remove "collapsing", add "collapse" (but not "show")
53
114
  node.classList.remove('collapsing');
54
115
  node.classList.add('collapse');
116
+ restoreTransitionTiming();
55
117
  }
56
118
  }
57
119
  };
@@ -59,17 +121,18 @@ export function collapseOut(node, params) {
59
121
  export function getTransitionDuration(element) {
60
122
  if (!element)
61
123
  return 0;
62
- // Read CSS transition-duration and transition-delay from computed styles
63
- let { transitionDuration, transitionDelay } = window.getComputedStyle(element);
64
- const floatTransitionDuration = Number.parseFloat(transitionDuration);
65
- const floatTransitionDelay = Number.parseFloat(transitionDelay);
66
- // Return 0 if no transition is configured
67
- if (!floatTransitionDuration && !floatTransitionDelay) {
68
- return 0;
69
- }
70
- // If multiple durations are defined (comma-separated), take the first one
71
- transitionDuration = transitionDuration.split(',')[0];
72
- transitionDelay = transitionDelay.split(',')[0];
73
- // Return total duration in milliseconds (duration + delay)
74
- return (Number.parseFloat(transitionDuration) + Number.parseFloat(transitionDelay)) * 1000;
124
+ const { transitionDuration, transitionDelay } = window.getComputedStyle(element);
125
+ const parseTime = (value, allowNegative) => {
126
+ const firstValue = value.split(',')[0]?.trim() ?? '';
127
+ const match = /^([+-]?(?:\d+(?:\.\d*)?|\.\d+))(ms|s)$/i.exec(firstValue);
128
+ if (!match)
129
+ return 0;
130
+ const amount = Number(match[1]);
131
+ if (!Number.isFinite(amount) || (!allowNegative && amount < 0))
132
+ return 0;
133
+ return match[2]?.toLowerCase() === 'ms' ? amount : amount * 1000;
134
+ };
135
+ const duration = parseTime(transitionDuration, false);
136
+ const delay = parseTime(transitionDelay, true);
137
+ return Math.max(0, duration + delay);
75
138
  }
@@ -2,6 +2,7 @@ import type { DivElement } from '../common/types.js';
2
2
  export type CollapseTransitionStylePropName = 'width' | 'height';
3
3
  export type CollapseTransitionParams = {
4
4
  isHorizontal?: boolean;
5
+ transitionDuration?: number;
5
6
  };
6
7
  export type CollapseRootProps = DivElement & {
7
8
  isHorizontal?: boolean;
@@ -22,6 +22,7 @@ A clickable item within a dropdown menu.
22
22
  <script lang="ts">/* eslint-disable @typescript-eslint/no-explicit-any -- restOfProps spread targets a union element type that is not spreadable without as any */
23
23
  import { uniqueClsx } from '../common/css.js';
24
24
  import { noop } from '../common/noop.js';
25
+ import { onDestroy, onMount } from 'svelte';
25
26
  import { DropdownItemState, initDropdownItemState } from './dropdown.svelte.js';
26
27
  // Generate a unique ID for the dropdown item
27
28
  const uid = $props.id();
@@ -40,6 +41,8 @@ const itemState = initDropdownItemState({
40
41
  return isDisabled;
41
42
  }
42
43
  });
44
+ onMount(() => itemState.root.scheduleReorderItems());
45
+ onDestroy(() => itemState.root.unregisterItem(itemState));
43
46
  let isAnchor = $derived(Boolean(href));
44
47
  let elementType = $derived(isAnchor ? 'a' : 'button');
45
48
  let classes = $derived(uniqueClsx('dropdown-item', {
@@ -47,6 +50,10 @@ let classes = $derived(uniqueClsx('dropdown-item', {
47
50
  disabled: isDisabled
48
51
  }, classValues));
49
52
  const handleClick = (event) => {
53
+ if (isDisabled) {
54
+ event.preventDefault();
55
+ return;
56
+ }
50
57
  if (href && href === '#!') {
51
58
  event.preventDefault();
52
59
  }
@@ -13,6 +13,7 @@ export class DropdownRootState {
13
13
  #isNavItem = $state(false);
14
14
  #isShown = $state(false);
15
15
  #items = $state([]);
16
+ #reorderScheduled = false;
16
17
  ariaLabelledBy = $state(undefined); // Used for accessibility, linking the dropdown to a label.
17
18
  constructor(props) {
18
19
  this.props = props;
@@ -25,7 +26,10 @@ export class DropdownRootState {
25
26
  this.focusOnNextItem = this.focusOnNextItem.bind(this);
26
27
  this.focusOnPreviousItem = this.focusOnPreviousItem.bind(this);
27
28
  this.registerItem = this.registerItem.bind(this);
29
+ this.reorderItems = this.reorderItems.bind(this);
30
+ this.scheduleReorderItems = this.scheduleReorderItems.bind(this);
28
31
  this.toggleIsShown = this.toggleIsShown.bind(this);
32
+ this.unregisterItem = this.unregisterItem.bind(this);
29
33
  }
30
34
  get activeItemIndex() {
31
35
  return this.#activeItemIndex;
@@ -79,22 +83,50 @@ export class DropdownRootState {
79
83
  return this.#isShown;
80
84
  }
81
85
  focusOnNextItem() {
82
- if (this.#items.length === 0) {
83
- return;
84
- }
85
- this.#activeItemIndex = (this.#activeItemIndex + 1) % this.#items.length;
86
- this.#items[this.#activeItemIndex]?.props.elementRef?.focus();
86
+ this.focusOnItemInDirection(1);
87
87
  }
88
88
  focusOnPreviousItem() {
89
- if (this.#items.length === 0) {
90
- return;
91
- }
92
- this.#activeItemIndex = (this.#activeItemIndex - 1 + this.#items.length) % this.#items.length;
93
- this.#items[this.#activeItemIndex]?.props.elementRef?.focus();
89
+ this.focusOnItemInDirection(-1);
94
90
  }
95
91
  registerItem(item) {
96
92
  item.itemIndex = this.#items.length; // Set the item's index based on current length.
97
- this.#items.push(item);
93
+ this.#items = [...this.#items, item];
94
+ }
95
+ unregisterItem(item) {
96
+ const removedIndex = this.#items.indexOf(item);
97
+ if (removedIndex === -1) {
98
+ return;
99
+ }
100
+ const activeItem = this.#items[this.#activeItemIndex];
101
+ const remainingItems = this.#items.filter((registeredItem) => registeredItem !== item);
102
+ remainingItems.forEach((registeredItem, index) => (registeredItem.itemIndex = index));
103
+ this.#items = remainingItems;
104
+ this.#activeItemIndex = activeItem && activeItem !== item ? remainingItems.indexOf(activeItem) : -1;
105
+ }
106
+ reorderItems() {
107
+ const activeItem = this.#items[this.#activeItemIndex];
108
+ const orderedItems = [...this.#items].sort((first, second) => {
109
+ const firstElement = first.props.elementRef;
110
+ const secondElement = second.props.elementRef;
111
+ if (!firstElement || !secondElement || firstElement === secondElement) {
112
+ return firstElement ? -1 : secondElement ? 1 : 0;
113
+ }
114
+ const position = firstElement.compareDocumentPosition(secondElement);
115
+ return position & Node.DOCUMENT_POSITION_FOLLOWING ? -1 : position & Node.DOCUMENT_POSITION_PRECEDING ? 1 : 0;
116
+ });
117
+ orderedItems.forEach((item, index) => (item.itemIndex = index));
118
+ this.#items = orderedItems;
119
+ this.#activeItemIndex = activeItem ? orderedItems.indexOf(activeItem) : -1;
120
+ }
121
+ scheduleReorderItems() {
122
+ if (this.#reorderScheduled) {
123
+ return;
124
+ }
125
+ this.#reorderScheduled = true;
126
+ queueMicrotask(() => {
127
+ this.#reorderScheduled = false;
128
+ this.reorderItems();
129
+ });
98
130
  }
99
131
  toggleIsShown(event) {
100
132
  if (!this.#isShown) {
@@ -112,6 +144,24 @@ export class DropdownRootState {
112
144
  this.props.onHidden?.(event);
113
145
  }
114
146
  }
147
+ focusOnItemInDirection(direction) {
148
+ this.reorderItems();
149
+ let itemIndex = this.#activeItemIndex === -1
150
+ ? direction === 1
151
+ ? 0
152
+ : this.#items.length - 1
153
+ : (this.#activeItemIndex + direction + this.#items.length) % this.#items.length;
154
+ for (let count = 0; count < this.#items.length; count += 1) {
155
+ const item = this.#items[itemIndex];
156
+ if (item && !item.props.isDisabled) {
157
+ this.#activeItemIndex = itemIndex;
158
+ item.props.elementRef?.focus();
159
+ return;
160
+ }
161
+ itemIndex = (itemIndex + direction + this.#items.length) % this.#items.length;
162
+ }
163
+ this.#activeItemIndex = -1;
164
+ }
115
165
  }
116
166
  export class DropdownToggleState {
117
167
  props;
@@ -210,15 +260,9 @@ export class DropdownMenuState {
210
260
  if (!this.isShown) {
211
261
  return;
212
262
  }
213
- if (!this.root.props.id) {
214
- return;
215
- }
216
263
  const target = event.target;
217
- // Check if the click occurred within this specific dropdown instance.
218
- // This prevents closing if a click happens inside the dropdown menu or on its toggle.
219
- const nearestDropdown = target.closest(`#${CSS.escape(this.root.props.id)}`);
220
- if (nearestDropdown && nearestDropdown.id === this.root.props.id) {
221
- // The click is within the current dropdown, so do nothing.
264
+ if (this.#elementRef?.contains(target) || this.#rootElementRef?.contains(target)) {
265
+ // The click is within this menu or its toggle/root, so do nothing.
222
266
  return;
223
267
  }
224
268
  // Only close on outside clicks if autoClose is true or 'outside'
@@ -351,6 +395,9 @@ export class DropdownItemState {
351
395
  }
352
396
  // When a dropdown item is clicked, it should typically close the dropdown.
353
397
  onclick(event) {
398
+ if (this.props.isDisabled) {
399
+ return;
400
+ }
354
401
  // Only close on item clicks if autoClose is true or 'inside'
355
402
  // false: only close on toggle click (ignore item clicks)
356
403
  // 'inside': close on item/toggle clicks (ignore body clicks)
@@ -44,7 +44,7 @@ Bootstrap-styled checkbox input component for boolean selections.
44
44
  - `isValid` (boolean): Optional. Indicates whether the input is valid. If undefined, no validation styles are applied.
45
45
  -->
46
46
  <script lang="ts">import { noop, uniqueClsx } from '../common/index.js';
47
- let { checked = $bindable(false), class: classValues, elementRef = $bindable(null), group = $bindable(null), isIndeterminate, isInvalid, isValid, onchange = noop, value = undefined, ...restOfProps } = $props();
47
+ let { 'aria-invalid': ariaInvalid, checked = $bindable(false), class: classValues, elementRef = $bindable(null), group = $bindable(null), isIndeterminate, isInvalid, isValid, onchange = noop, value = undefined, ...restOfProps } = $props();
48
48
  let classes = $derived(uniqueClsx('form-check-input', {
49
49
  'is-valid': isValid,
50
50
  'is-invalid': isInvalid
@@ -70,8 +70,8 @@ const handleChange = (event) => {
70
70
  }
71
71
  };
72
72
  $effect(() => {
73
- if (elementRef && isIndeterminate !== undefined) {
74
- elementRef.indeterminate = isIndeterminate;
73
+ if (elementRef) {
74
+ elementRef.indeterminate = isIndeterminate === true;
75
75
  }
76
76
  });
77
77
  $effect(() => {
@@ -82,11 +82,11 @@ $effect(() => {
82
82
  </script>
83
83
 
84
84
  <input
85
- aria-invalid={isInvalid === true ? 'true' : isValid === true ? 'false' : undefined}
85
+ {...restOfProps}
86
+ aria-invalid={isInvalid === true ? 'true' : isValid === true ? 'false' : ariaInvalid}
86
87
  bind:this={elementRef}
87
88
  bind:checked
88
89
  class={classes}
89
90
  onchange={handleChange}
90
- type="checkbox"
91
91
  {value}
92
- {...restOfProps} />
92
+ type="checkbox" />
@@ -26,7 +26,8 @@ Bootstrap-styled color picker input component.
26
26
  - `value` (string): Optional. The color value in hex format (e.g., "#563d7c"). If undefined, the input is empty.
27
27
  -->
28
28
  <script lang="ts">import { uniqueClsx } from '../common/css.js';
29
- let { class: classValues, elementRef = $bindable(null), isInvalid, isValid, sizing, value = $bindable('#000000'), ...restOfProps } = $props();
29
+ import { noop } from '../common/noop.js';
30
+ let { 'aria-invalid': ariaInvalid, class: classValues, elementRef = $bindable(null), isInvalid, isValid, oninput = noop, sizing, value = $bindable('#000000'), ...restOfProps } = $props();
30
31
  let classes = $derived(uniqueClsx('form-control', 'form-control-color', {
31
32
  [`form-control-${sizing}`]: !!sizing,
32
33
  'is-valid': isValid,
@@ -43,12 +44,16 @@ $effect(() => {
43
44
  input.value = normalized;
44
45
  }
45
46
  });
47
+ const handleInput = (event) => {
48
+ value = event.currentTarget.value;
49
+ oninput?.(event);
50
+ };
46
51
  </script>
47
52
 
48
53
  <input
49
54
  {...restOfProps}
50
- aria-invalid={isInvalid === true ? 'true' : isValid === true ? 'false' : undefined}
55
+ aria-invalid={isInvalid === true ? 'true' : isValid === true ? 'false' : ariaInvalid}
51
56
  bind:this={elementRef}
52
57
  class={classes}
53
- oninput={(event) => (value = event.currentTarget.value)}
58
+ oninput={handleInput}
54
59
  type="color" />
@@ -42,7 +42,7 @@ Bootstrap-styled date input component for selecting dates.
42
42
  - `value` (string): Optional. The date value in the format "YYYY-MM-DD". If undefined, the input is empty.
43
43
  -->
44
44
  <script lang="ts">import { uniqueClsx } from '../common/css.js';
45
- let { autocomplete = 'off', class: classValues, 'data-form-type': dataFormType = 'email', 'data-lpignore': dataLpignore = undefined, elementRef = $bindable(null), isInvalid, isPlaintext = false, isValid, sizing, value = $bindable(undefined), ...restOfProps } = $props();
45
+ let { 'aria-invalid': ariaInvalid, autocomplete = 'off', class: classValues, 'data-form-type': dataFormType = 'email', 'data-lpignore': dataLpignore = undefined, elementRef = $bindable(null), isInvalid, isPlaintext = false, isValid, sizing, value = $bindable(undefined), ...restOfProps } = $props();
46
46
  let classes = $derived(uniqueClsx({
47
47
  'form-control': !isPlaintext,
48
48
  'form-control-plaintext': isPlaintext,
@@ -53,12 +53,12 @@ let classes = $derived(uniqueClsx({
53
53
  </script>
54
54
 
55
55
  <input
56
- aria-invalid={isInvalid === true ? 'true' : isValid === true ? 'false' : undefined}
56
+ {...restOfProps}
57
+ aria-invalid={isInvalid === true ? 'true' : isValid === true ? 'false' : ariaInvalid}
57
58
  {autocomplete}
58
59
  bind:this={elementRef}
59
60
  bind:value
60
61
  class={classes}
61
62
  data-form-type={autocomplete == 'off' ? 'other' : dataFormType}
62
63
  data-lpignore={autocomplete == 'off' ? 'true' : dataLpignore}
63
- type="date"
64
- {...restOfProps} />
64
+ type="date" />
@@ -41,7 +41,7 @@ Bootstrap-styled datetime-local input component for selecting both date and time
41
41
  - `value` (string): Optional. The datetime-local value in the format "YYYY-MM-DDTHH:MM". If undefined, the input is empty.
42
42
  -->
43
43
  <script lang="ts">import { uniqueClsx } from '../common/css.js';
44
- let { class: classValues, elementRef = $bindable(null), isInvalid, isPlaintext = false, isValid, sizing, value = $bindable(undefined), ...restOfProps } = $props();
44
+ let { 'aria-invalid': ariaInvalid, class: classValues, elementRef = $bindable(null), isInvalid, isPlaintext = false, isValid, sizing, value = $bindable(undefined), ...restOfProps } = $props();
45
45
  let classes = $derived(uniqueClsx({
46
46
  'form-control': !isPlaintext,
47
47
  'form-control-plaintext': isPlaintext,
@@ -52,9 +52,9 @@ let classes = $derived(uniqueClsx({
52
52
  </script>
53
53
 
54
54
  <input
55
- aria-invalid={isInvalid === true ? 'true' : isValid === true ? 'false' : undefined}
55
+ {...restOfProps}
56
+ aria-invalid={isInvalid === true ? 'true' : isValid === true ? 'false' : ariaInvalid}
56
57
  bind:this={elementRef}
57
58
  bind:value
58
59
  class={classes}
59
- type="datetime-local"
60
- {...restOfProps} />
60
+ type="datetime-local" />
@@ -42,7 +42,7 @@ Bootstrap-styled email input component for email address entry.
42
42
  - `value` (string): Optional. The email address value. If undefined, the input is empty.
43
43
  -->
44
44
  <script lang="ts">import { uniqueClsx } from '../common/css.js';
45
- let { autocomplete = 'off', class: classValues, 'data-form-type': dataFormType = 'email', 'data-lpignore': dataLpignore = undefined, elementRef = $bindable(null), isInvalid, isPlaintext = false, isValid, sizing, value = $bindable(undefined), ...restOfProps } = $props();
45
+ let { 'aria-invalid': ariaInvalid, autocomplete = 'off', class: classValues, 'data-form-type': dataFormType = 'email', 'data-lpignore': dataLpignore = undefined, elementRef = $bindable(null), isInvalid, isPlaintext = false, isValid, sizing, value = $bindable(undefined), ...restOfProps } = $props();
46
46
  let classes = $derived(uniqueClsx({
47
47
  'form-control': !isPlaintext,
48
48
  'form-control-plaintext': isPlaintext,
@@ -53,12 +53,12 @@ let classes = $derived(uniqueClsx({
53
53
  </script>
54
54
 
55
55
  <input
56
- aria-invalid={isInvalid === true ? 'true' : isValid === true ? 'false' : undefined}
56
+ {...restOfProps}
57
+ aria-invalid={isInvalid === true ? 'true' : isValid === true ? 'false' : ariaInvalid}
57
58
  {autocomplete}
58
59
  bind:this={elementRef}
59
60
  bind:value
60
61
  class={classes}
61
62
  data-form-type={autocomplete == 'off' ? 'other' : dataFormType}
62
63
  data-lpignore={autocomplete == 'off' ? 'true' : dataLpignore}
63
- type="email"
64
- {...restOfProps} />
64
+ type="email" />
@@ -23,21 +23,61 @@ Bootstrap-styled file input component for file uploads.
23
23
  - `isInvalid` (boolean): Optional. Indicates whether the input is invalid. If undefined, no validation styles are applied.
24
24
  - `isValid` (boolean): Optional. Indicates whether the input is valid. If undefined, no validation styles are applied.
25
25
  - `sizing` ('sm' | 'lg'): Optional. Controls the size of the input element.
26
- - `value` (File | File[]): Optional. The file(s) selected in the input. If undefined, the input is empty.
26
+ - `files` (FileList | null): Optional. Binds the platform file list. Defaults to null.
27
+ - `value` (string): Deprecated compatibility binding. Prefer `files`; non-empty parent values are ignored by the platform input.
27
28
  -->
28
29
  <script lang="ts">import { uniqueClsx } from '../common/css.js';
29
- let { class: classValues, elementRef = $bindable(null), isInvalid, isValid, sizing, value = $bindable(undefined), ...restOfProps } = $props();
30
+ let { 'aria-invalid': ariaInvalid, class: classValues, elementRef = $bindable(null), files = $bindable(null), isInvalid, isValid, onchange, sizing, value = $bindable(undefined), ...restOfProps } = $props();
30
31
  let classes = $derived(uniqueClsx('form-control', {
31
32
  [`form-control-${sizing}`]: !!sizing,
32
33
  'is-valid': isValid,
33
34
  'is-invalid': isInvalid
34
35
  }, classValues));
36
+ const handleChange = (event) => {
37
+ files = event.currentTarget.files;
38
+ value = event.currentTarget.value;
39
+ onchange?.(event);
40
+ };
41
+ $effect(() => {
42
+ const input = elementRef;
43
+ const requestedValue = value;
44
+ if (input && requestedValue === '' && input.value !== '') {
45
+ input.value = '';
46
+ if (files !== null) {
47
+ files = null;
48
+ }
49
+ }
50
+ if (input && files === null) {
51
+ if (input.value !== '') {
52
+ input.value = '';
53
+ if (value !== '') {
54
+ value = '';
55
+ }
56
+ }
57
+ return;
58
+ }
59
+ if (input && input.files !== files) {
60
+ try {
61
+ // Let the platform setter validate FileList identity so genuine
62
+ // cross-realm FileLists are not rejected by an instanceof check.
63
+ input.files = files;
64
+ }
65
+ catch {
66
+ input.value = '';
67
+ value = '';
68
+ if (files !== null) {
69
+ files = null;
70
+ }
71
+ }
72
+ }
73
+ });
35
74
  </script>
36
75
 
37
76
  <input
38
- aria-invalid={isInvalid === true ? 'true' : isValid === true ? 'false' : undefined}
77
+ {...restOfProps}
78
+ aria-invalid={isInvalid === true ? 'true' : isValid === true ? 'false' : ariaInvalid}
39
79
  bind:this={elementRef}
40
- bind:value
41
80
  class={classes}
42
- type="file"
43
- {...restOfProps} />
81
+ onchange={handleChange}
82
+ value={undefined}
83
+ type="file" />
@@ -23,8 +23,9 @@ import type { Form } from './index.js';
23
23
  * - `isInvalid` (boolean): Optional. Indicates whether the input is invalid. If undefined, no validation styles are applied.
24
24
  * - `isValid` (boolean): Optional. Indicates whether the input is valid. If undefined, no validation styles are applied.
25
25
  * - `sizing` ('sm' | 'lg'): Optional. Controls the size of the input element.
26
- * - `value` (File | File[]): Optional. The file(s) selected in the input. If undefined, the input is empty.
26
+ * - `files` (FileList | null): Optional. Binds the platform file list. Defaults to null.
27
+ * - `value` (string): Deprecated compatibility binding. Prefer `files`; non-empty parent values are ignored by the platform input.
27
28
  */
28
- declare const FormFileInput: import("svelte").Component<Form.FileInputProps, {}, "value" | "elementRef">;
29
+ declare const FormFileInput: import("svelte").Component<Form.FileInputProps, {}, "value" | "elementRef" | "files">;
29
30
  type FormFileInput = ReturnType<typeof FormFileInput>;
30
31
  export default FormFileInput;
@@ -15,4 +15,4 @@ Bootstrap-styled hidden input component for passing data in forms without visual
15
15
  export {};
16
16
  </script>
17
17
 
18
- <input bind:this={elementRef} bind:value type="hidden" {...restOfProps} />
18
+ <input {...restOfProps} bind:this={elementRef} bind:value type="hidden" />
@@ -11,6 +11,6 @@ import type { Form } from './index.js';
11
11
  * - `elementRef` (HTMLInputElement): Optional. Reference to the input DOM element.
12
12
  * - `value` (any): Optional. The value of the hidden input. If undefined, the input is empty.
13
13
  */
14
- declare const FormHiddenInput: import("svelte").Component<Form.FileInputProps, {}, "value" | "elementRef">;
14
+ declare const FormHiddenInput: import("svelte").Component<Form.HiddenInputProps, {}, "value" | "elementRef">;
15
15
  type FormHiddenInput = ReturnType<typeof FormHiddenInput>;
16
16
  export default FormHiddenInput;
@@ -27,4 +27,4 @@ let { class: classValues, elementRef = $bindable(null), ...restOfProps } = $prop
27
27
  let classes = $derived(uniqueClsx(classValues));
28
28
  </script>
29
29
 
30
- <input bind:this={elementRef} class={classes} type="image" {...restOfProps} />
30
+ <input {...restOfProps} bind:this={elementRef} class={classes} type="image" />
@@ -1,3 +1,4 @@
1
+ import type { Form } from './index.js';
1
2
  /**
2
3
  * ## Form.ImageInput
3
4
  * Bootstrap-styled image input component that acts as a graphical submit button.
@@ -21,6 +22,6 @@
21
22
  * - `class` (string): Optional. Additional CSS classes to apply to the image input.
22
23
  * - `elementRef` (HTMLInputElement): Optional. Reference to the input DOM element.
23
24
  */
24
- declare const FormImageInput: import("svelte").Component<import("../index.js").InputElement, {}, "elementRef">;
25
+ declare const FormImageInput: import("svelte").Component<Form.ImageInputProps, {}, "elementRef">;
25
26
  type FormImageInput = ReturnType<typeof FormImageInput>;
26
27
  export default FormImageInput;
@@ -44,7 +44,7 @@ Bootstrap-styled month input component for selecting a specific month and year.
44
44
  - `value` (string): Optional. The month value in the format "YYYY-MM". If undefined, the input is empty.
45
45
  -->
46
46
  <script lang="ts">import { uniqueClsx } from '../common/css.js';
47
- let { class: classValues, elementRef = $bindable(null), isInvalid, isPlaintext = false, isValid, sizing, value = $bindable(undefined), ...restOfProps } = $props();
47
+ let { 'aria-invalid': ariaInvalid, class: classValues, elementRef = $bindable(null), isInvalid, isPlaintext = false, isValid, sizing, value = $bindable(undefined), ...restOfProps } = $props();
48
48
  let classes = $derived(uniqueClsx({
49
49
  'form-control': !isPlaintext,
50
50
  'form-control-plaintext': isPlaintext,
@@ -55,9 +55,9 @@ let classes = $derived(uniqueClsx({
55
55
  </script>
56
56
 
57
57
  <input
58
- aria-invalid={isInvalid === true ? 'true' : isValid === true ? 'false' : undefined}
58
+ {...restOfProps}
59
+ aria-invalid={isInvalid === true ? 'true' : isValid === true ? 'false' : ariaInvalid}
59
60
  bind:this={elementRef}
60
61
  bind:value
61
62
  class={classes}
62
- type="month"
63
- {...restOfProps} />
63
+ type="month" />
@@ -43,7 +43,7 @@ Bootstrap-styled numeric input component for entering numbers.
43
43
  - `value` (number): Optional. The numeric value of the input. If undefined, the input is empty.
44
44
  -->
45
45
  <script lang="ts">import { uniqueClsx } from '../common/css.js';
46
- let { class: classValues, elementRef = $bindable(null), isInvalid, isPlaintext = false, isValid, sizing, value = $bindable(undefined), ...restOfProps } = $props();
46
+ let { 'aria-invalid': ariaInvalid, class: classValues, elementRef = $bindable(null), isInvalid, isPlaintext = false, isValid, sizing, value = $bindable(undefined), ...restOfProps } = $props();
47
47
  let classes = $derived(uniqueClsx({
48
48
  'form-control': !isPlaintext,
49
49
  'form-control-plaintext': isPlaintext,
@@ -54,9 +54,9 @@ let classes = $derived(uniqueClsx({
54
54
  </script>
55
55
 
56
56
  <input
57
- aria-invalid={isInvalid === true ? 'true' : isValid === true ? 'false' : undefined}
57
+ {...restOfProps}
58
+ aria-invalid={isInvalid === true ? 'true' : isValid === true ? 'false' : ariaInvalid}
58
59
  bind:this={elementRef}
59
60
  bind:value
60
61
  class={classes}
61
- type="number"
62
- {...restOfProps} />
62
+ type="number" />
@@ -37,7 +37,7 @@ Bootstrap-styled password input component with support for various states.
37
37
  - `value` (string): Optional. The password value. If undefined, the input is empty.
38
38
  -->
39
39
  <script lang="ts">import { uniqueClsx } from '../common/css.js';
40
- let { autocomplete = 'off', class: classValues, 'data-form-type': dataFormType = 'password', 'data-lpignore': dataLpignore = undefined, elementRef = $bindable(null), isInvalid, isPlaintext = false, isValid, sizing, showPassword = false, value = $bindable(undefined), ...restOfProps } = $props();
40
+ let { 'aria-invalid': ariaInvalid, autocomplete = 'off', class: classValues, 'data-form-type': dataFormType = 'password', 'data-lpignore': dataLpignore = undefined, elementRef = $bindable(null), isInvalid, isPlaintext = false, isValid, sizing, showPassword = false, value = $bindable(undefined), ...restOfProps } = $props();
41
41
  let classes = $derived(uniqueClsx({
42
42
  'form-control': !isPlaintext,
43
43
  'form-control-plaintext': isPlaintext,
@@ -49,12 +49,12 @@ let inputType = $derived(showPassword ? 'text' : 'password');
49
49
  </script>
50
50
 
51
51
  <input
52
- aria-invalid={isInvalid === true ? 'true' : isValid === true ? 'false' : undefined}
52
+ {...restOfProps}
53
+ aria-invalid={isInvalid === true ? 'true' : isValid === true ? 'false' : ariaInvalid}
53
54
  {autocomplete}
54
55
  bind:this={elementRef}
55
56
  bind:value
56
57
  class={classes}
57
58
  data-form-type={autocomplete == 'off' ? 'other' : dataFormType}
58
59
  data-lpignore={autocomplete == 'off' ? 'true' : dataLpignore}
59
- type={inputType}
60
- {...restOfProps} />
60
+ type={inputType} />