sveltacular 1.0.18 → 1.0.21

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 (42) hide show
  1. package/README.md +1 -2
  2. package/dist/forms/bool-box/bool-box.svelte +392 -13
  3. package/dist/forms/bool-box/bool-box.svelte.d.ts +2 -0
  4. package/dist/forms/bool-box/index.d.ts +2 -0
  5. package/dist/forms/bool-box/index.js +2 -0
  6. package/dist/forms/date-box/date-box.svelte +104 -53
  7. package/dist/forms/date-box/date-box.svelte.d.ts +8 -3
  8. package/dist/forms/dimension-box/dimension-box.svelte +15 -50
  9. package/dist/forms/file-box/file-box.svelte +7 -25
  10. package/dist/forms/form-input-wrapper/form-input-wrapper.svelte +203 -0
  11. package/dist/forms/form-input-wrapper/form-input-wrapper.svelte.d.ts +16 -0
  12. package/dist/forms/form-input-wrapper/index.d.ts +2 -0
  13. package/dist/forms/form-input-wrapper/index.js +2 -0
  14. package/dist/forms/index.d.ts +0 -1
  15. package/dist/forms/index.js +0 -1
  16. package/dist/forms/list-box/list-box.svelte +26 -9
  17. package/dist/forms/list-box/list-box.svelte.d.ts +3 -0
  18. package/dist/forms/money-box/money-box.svelte +104 -65
  19. package/dist/forms/money-box/money-box.svelte.d.ts +6 -0
  20. package/dist/forms/number-box/number-box.svelte +93 -49
  21. package/dist/forms/number-box/number-box.svelte.d.ts +6 -0
  22. package/dist/forms/number-range-box/number-range-box.svelte +22 -58
  23. package/dist/forms/phone-box/phone-box.svelte +101 -38
  24. package/dist/forms/phone-box/phone-box.svelte.d.ts +6 -1
  25. package/dist/forms/slider/slider.svelte +13 -6
  26. package/dist/forms/slider/slider.svelte.d.ts +6 -2
  27. package/dist/forms/tag-box/tag-box.svelte +5 -3
  28. package/dist/forms/text-area/text-area.svelte +22 -2
  29. package/dist/forms/text-area/text-area.svelte.d.ts +4 -0
  30. package/dist/forms/text-box/text-box.svelte +97 -131
  31. package/dist/forms/text-box/text-box.svelte.d.ts +7 -2
  32. package/dist/forms/time-box/time-box.svelte +106 -37
  33. package/dist/forms/time-box/time-box.svelte.d.ts +10 -3
  34. package/dist/forms/url-box/url-box.svelte +26 -5
  35. package/dist/forms/url-box/url-box.svelte.d.ts +7 -1
  36. package/dist/generic/theme-provider/theme-provider-demo.svelte +7 -2
  37. package/dist/navigation/dropdown-button/dropdown-button.svelte +102 -3
  38. package/dist/navigation/dropdown-button/dropdown-manager.svelte.d.ts +47 -0
  39. package/dist/navigation/dropdown-button/dropdown-manager.svelte.js +47 -0
  40. package/dist/tables/table-cell.svelte +2 -0
  41. package/dist/tables/table-row.svelte +1 -0
  42. package/package.json +1 -1
@@ -1,9 +1,8 @@
1
1
  <script lang="ts">
2
2
  import { untrack } from 'svelte';
3
3
  import { uniqueId } from '../../helpers/unique-id.js';
4
- import { animateShake, animateScaleIn } from '../../helpers/animations.js';
5
4
  import FormField, { type FormFieldFeedback } from '../form-field/form-field.svelte';
6
- import Icon from '../../icons/icon.svelte';
5
+ import FormInputWrapper from '../form-input-wrapper';
7
6
  import type { AllowedTextInputTypes, FormFieldSizeOptions } from '../../types/form.js';
8
7
 
9
8
  const id = uniqueId();
@@ -20,6 +19,7 @@
20
19
  disabled = false,
21
20
  required = false,
22
21
  readonly = false,
22
+ nullable = false,
23
23
  maxlength = undefined,
24
24
  minlength = undefined,
25
25
  pattern = undefined,
@@ -30,8 +30,12 @@
30
30
  allowLetters = true,
31
31
  textCase = undefined,
32
32
  onChange = undefined,
33
+ onCheckChanged = undefined,
33
34
  onInput = undefined,
34
- label = undefined
35
+ onFocus = undefined,
36
+ onBlur = undefined,
37
+ label = undefined,
38
+ nullText = ''
35
39
  }: {
36
40
  value?: string | null;
37
41
  placeholder?: string;
@@ -44,6 +48,7 @@
44
48
  disabled?: boolean;
45
49
  required?: boolean;
46
50
  readonly?: boolean;
51
+ nullable?: boolean;
47
52
  maxlength?: number | undefined;
48
53
  minlength?: number | undefined;
49
54
  pattern?: string | undefined;
@@ -53,16 +58,27 @@
53
58
  allowNumbers?: boolean;
54
59
  allowLetters?: boolean;
55
60
  textCase?: 'lower' | 'upper' | undefined;
56
- onChange?: ((value: string) => void) | undefined;
57
- onInput?: ((value: string) => void) | undefined;
61
+ onChange?: ((value: string | null) => void) | undefined;
62
+ onCheckChanged?: ((isChecked: boolean) => void) | undefined;
63
+ onInput?: ((value: string | null) => void) | undefined;
64
+ onFocus?: ((value: string | null) => void) | undefined;
65
+ onBlur?: ((value: string | null) => void) | undefined;
58
66
  label?: string;
67
+ nullText?: string;
59
68
  } = $props();
60
69
 
70
+ // Track whether the nullable checkbox is checked (i.e., whether field has a value)
71
+ let isChecked = $state(untrack(() => !!value));
72
+
73
+ // Remember the last non-null value so we can restore it when re-checking
74
+ let lastValue = $state<string | undefined>(undefined);
75
+
76
+ // Derive the actual disabled state: disabled prop OR (nullable and unchecked)
77
+ let inputDisabled = $derived(disabled || (nullable && !isChecked));
78
+
61
79
  let hasError = $derived(!!feedback?.isError);
62
80
  let hasSuccess = $derived(!!feedback && !feedback.isError);
63
81
  let describedByIds = $state<string[]>([]);
64
- let inputElement: HTMLDivElement | null = $state(null);
65
- let successIconElement: HTMLDivElement | null = $state(null);
66
82
  let characterCount = $derived((value || '').length);
67
83
  let characterLimitClass = $derived(
68
84
  maxlength && characterCount > maxlength * 0.9
@@ -93,32 +109,8 @@
93
109
  });
94
110
  });
95
111
 
96
- // Trigger shake animation when error appears (track previous error state)
97
- let prevHasError = $state(false);
98
- $effect(() => {
99
- if (hasError && !prevHasError && inputElement) {
100
- // Use untrack to prevent animation from triggering effect again
101
- untrack(() => {
102
- animateShake(inputElement!); // Already checked above
103
- });
104
- }
105
- prevHasError = hasError;
106
- });
107
-
108
- // Trigger scale-in animation when success appears (track previous success state)
109
- let prevHasSuccess = $state(false);
110
- $effect(() => {
111
- if (hasSuccess && !prevHasSuccess && successIconElement) {
112
- // Use untrack to prevent animation from triggering effect again
113
- untrack(() => {
114
- animateScaleIn(successIconElement!); // Already checked above
115
- });
116
- }
117
- prevHasSuccess = hasSuccess;
118
- });
119
-
120
112
  // Don't allow certain characters to be typed into the input
121
- const onKeyPress = (e: KeyboardEvent) => {
113
+ const handleKeyPress = (e: KeyboardEvent) => {
122
114
  if (!allowSpaces && e.key === ' ') {
123
115
  e.preventDefault();
124
116
  }
@@ -130,6 +122,33 @@
130
122
  }
131
123
  };
132
124
 
125
+ const checkChanged = () => {
126
+ if (nullable) {
127
+ if (isChecked) {
128
+ // Restore last value if available, otherwise use empty string
129
+ value = lastValue || '';
130
+ } else {
131
+ // Store current value before clearing
132
+ if (value) {
133
+ lastValue = value;
134
+ }
135
+ value = '';
136
+ }
137
+ }
138
+ onCheckChanged?.(isChecked);
139
+ handleInputChange();
140
+ };
141
+
142
+ const handleInputChange = () => {
143
+ const currentValue = !nullable || isChecked ? value : null;
144
+ // Remember the value if it's not empty
145
+ if (isChecked && value) {
146
+ lastValue = value;
147
+ }
148
+ onChange?.(currentValue);
149
+ onInput?.(currentValue);
150
+ };
151
+
133
152
  // When the value changes, make sure it is in the correct case
134
153
  const handleInput = (e: Event) => {
135
154
  const cleanValue = String(value);
@@ -147,27 +166,52 @@
147
166
  if (type === 'url') {
148
167
  value = cleanValue.replace(/\s/g, '');
149
168
  }
150
- onInput?.(cleanValue);
151
- onChange?.(cleanValue);
169
+ handleInputChange();
170
+ };
171
+
172
+ const handleFocus = (e: FocusEvent) => {
173
+ onFocus?.(value);
174
+ };
175
+
176
+ const handleBlur = (e: FocusEvent) => {
177
+ onBlur?.(value);
152
178
  };
179
+
180
+ $effect(() => {
181
+ if (!value) {
182
+ // Use untrack to prevent writes to isChecked/value from triggering this effect again
183
+ untrack(() => {
184
+ if (nullable) isChecked = false;
185
+ });
186
+ } else {
187
+ // Initialize lastValue if we have an initial value
188
+ if (!lastValue) {
189
+ lastValue = value;
190
+ }
191
+ }
192
+ });
193
+
194
+ let effectiveNullText = $derived(nullText || placeholder || '--');
153
195
  </script>
154
196
 
155
197
  <FormField {size} {label} {id} {required} {disabled} {helperText} {feedback}>
156
- <div
157
- class="input {disabled ? 'disabled' : 'enabled'}"
158
- class:error={hasError}
159
- class:success={hasSuccess}
160
- bind:this={inputElement}
198
+ <FormInputWrapper
199
+ {disabled}
200
+ error={hasError}
201
+ success={hasSuccess}
202
+ {prefix}
203
+ {suffix}
204
+ {nullable}
205
+ nullText={effectiveNullText}
206
+ {isLoading}
207
+ onCheckChanged={checkChanged}
161
208
  >
162
- {#if prefix}
163
- <div class="prefix">{prefix}</div>
164
- {/if}
165
209
  <input
166
210
  {id}
167
211
  {placeholder}
168
212
  bind:value
169
213
  {...{ type }}
170
- {disabled}
214
+ disabled={inputDisabled}
171
215
  {readonly}
172
216
  {required}
173
217
  {maxlength}
@@ -177,22 +221,12 @@
177
221
  aria-required={required}
178
222
  aria-invalid={hasError}
179
223
  aria-busy={isLoading}
180
- onkeypress={onKeyPress}
224
+ onkeypress={handleKeyPress}
181
225
  oninput={handleInput}
226
+ onfocus={handleFocus}
227
+ onblur={handleBlur}
182
228
  />
183
- {#if isLoading}
184
- <div class="loading-indicator" aria-label="Loading">
185
- <div class="spinner"></div>
186
- </div>
187
- {:else if hasSuccess}
188
- <div class="success-indicator" bind:this={successIconElement}>
189
- <Icon type="check" size="sm" />
190
- </div>
191
- {/if}
192
- {#if suffix}
193
- <div class="suffix">{suffix}</div>
194
- {/if}
195
- </div>
229
+ </FormInputWrapper>
196
230
  {#if showCharacterCount && maxlength}
197
231
  <div class="character-count {characterLimitClass}">
198
232
  {characterCount} / {maxlength}
@@ -200,92 +234,24 @@
200
234
  {/if}
201
235
  </FormField>
202
236
 
203
- <style>.input {
204
- display: flex;
205
- align-items: center;
206
- justify-content: flex-start;
207
- position: relative;
208
- width: 100%;
209
- height: 100%;
210
- border-radius: var(--radius-md);
211
- border: var(--border-thin) solid var(--form-input-border);
212
- background-color: var(--form-input-bg);
213
- color: var(--form-input-fg);
214
- font-size: var(--font-md);
215
- font-weight: 500;
216
- line-height: 2rem;
217
- transition: background-color var(--transition-base) var(--ease-in-out), border-color var(--transition-base) var(--ease-in-out), color var(--transition-base) var(--ease-in-out), fill var(--transition-base) var(--ease-in-out), stroke var(--transition-base) var(--ease-in-out);
218
- user-select: none;
219
- white-space: nowrap;
220
- }
221
- .input.disabled {
222
- opacity: 0.5;
223
- }
224
- .input.error {
225
- border-color: var(--danger, #dc3545);
226
- }
227
- .input.success {
228
- border-color: var(--success, #28a745);
229
- }
230
- .input .loading-indicator,
231
- .input .success-indicator {
232
- display: flex;
233
- align-items: center;
234
- justify-content: center;
235
- padding: 0 var(--spacing-base);
236
- }
237
- .input .loading-indicator .spinner {
238
- width: 1rem;
239
- height: 1rem;
240
- border: 2px solid var(--form-input-border);
241
- border-top-color: var(--primary-500, #3b82f6);
242
- border-radius: 50%;
243
- animation: spin 0.6s linear infinite;
244
- }
245
- @keyframes spin {
246
- to {
247
- transform: rotate(360deg);
248
- }
249
- }
250
- .input .success-indicator {
251
- color: var(--success, #28a745);
252
- width: 1.5rem;
253
- height: 1.5rem;
254
- }
255
- .input .success-indicator :global(svg) {
256
- width: 100%;
257
- height: 100%;
258
- }
259
- .input input {
237
+ <style>input {
260
238
  background-color: transparent;
261
239
  border: none;
262
240
  line-height: 2rem;
263
241
  font-size: var(--font-md);
264
242
  width: 100%;
265
243
  flex-grow: 1;
266
- padding-left: var(--spacing-base);
244
+ padding: 0 0 0 var(--spacing-base);
267
245
  }
268
- .input input:focus {
246
+ input:focus {
269
247
  outline: none;
270
248
  }
271
- .input input:focus-visible {
249
+ input:focus-visible {
272
250
  outline: 2px solid var(--focus-ring, #007bff);
273
251
  outline-offset: 2px;
274
252
  }
275
- .input .prefix,
276
- .input .suffix {
277
- font-size: var(--font-md);
278
- line-height: 2rem;
279
- padding-left: var(--spacing-base);
280
- padding-right: var(--spacing-base);
281
- background-color: var(--form-input-accent-bg);
282
- color: var(--form-input-accent-fg);
283
- }
284
- .input .prefix {
285
- border-right: var(--border-thin) solid var(--form-input-border);
286
- }
287
- .input .suffix {
288
- border-left: var(--border-thin) solid var(--form-input-border);
253
+ input:disabled {
254
+ cursor: not-allowed;
289
255
  }
290
256
 
291
257
  .character-count {
@@ -12,6 +12,7 @@ type $$ComponentProps = {
12
12
  disabled?: boolean;
13
13
  required?: boolean;
14
14
  readonly?: boolean;
15
+ nullable?: boolean;
15
16
  maxlength?: number | undefined;
16
17
  minlength?: number | undefined;
17
18
  pattern?: string | undefined;
@@ -21,9 +22,13 @@ type $$ComponentProps = {
21
22
  allowNumbers?: boolean;
22
23
  allowLetters?: boolean;
23
24
  textCase?: 'lower' | 'upper' | undefined;
24
- onChange?: ((value: string) => void) | undefined;
25
- onInput?: ((value: string) => void) | undefined;
25
+ onChange?: ((value: string | null) => void) | undefined;
26
+ onCheckChanged?: ((isChecked: boolean) => void) | undefined;
27
+ onInput?: ((value: string | null) => void) | undefined;
28
+ onFocus?: ((value: string | null) => void) | undefined;
29
+ onBlur?: ((value: string | null) => void) | undefined;
26
30
  label?: string;
31
+ nullText?: string;
27
32
  };
28
33
  declare const TextBox: import("svelte").Component<$$ComponentProps, {}, "value">;
29
34
  type TextBox = ReturnType<typeof TextBox>;
@@ -1,92 +1,161 @@
1
1
  <script lang="ts">
2
+ import { untrack } from 'svelte';
2
3
  import { uniqueId } from '../../helpers/unique-id.js';
3
4
  import FormField, { type FormFieldFeedback } from '../form-field/form-field.svelte';
4
- import type { ComponentSize } from '../../types/size.js';
5
+ import FormInputWrapper from '../form-input-wrapper';
6
+ import type { FormFieldSizeOptions } from '../../types/form.js';
5
7
 
6
8
  const id = uniqueId();
7
9
 
8
10
  let {
9
11
  value = $bindable('' as string | null),
10
- size = 'full' as ComponentSize,
12
+ size = 'full' as FormFieldSizeOptions,
13
+ nullable = false,
11
14
  disabled = false,
12
15
  required = false,
16
+ readonly = false,
13
17
  onChange = undefined,
18
+ onCheckChanged = undefined,
19
+ onInput = undefined,
20
+ onFocus = undefined,
21
+ onBlur = undefined,
14
22
  label = undefined,
15
23
  helperText = undefined,
24
+ nullText = '-- : -- : --',
16
25
  feedback = undefined
17
26
  }: {
18
27
  value?: string | null;
19
- size?: ComponentSize;
28
+ size?: FormFieldSizeOptions;
29
+ nullable?: boolean;
20
30
  disabled?: boolean;
21
31
  required?: boolean;
22
- onChange?: ((value: string) => void) | undefined;
32
+ readonly?: boolean;
33
+ onChange?: ((value: string | null) => void) | undefined;
34
+ onCheckChanged?: ((isChecked: boolean) => void) | undefined;
35
+ onInput?: ((value: string | null) => void) | undefined;
36
+ onFocus?: ((e: FocusEvent) => void) | undefined;
37
+ onBlur?: ((e: FocusEvent) => void) | undefined;
23
38
  label?: string;
24
39
  helperText?: string;
40
+ nullText?: string;
25
41
  feedback?: FormFieldFeedback;
26
42
  } = $props();
27
43
 
44
+ // Track whether the nullable checkbox is checked (i.e., whether field has a value)
45
+ let isChecked = $state(untrack(() => !!value));
46
+
47
+ // Remember the last non-null value so we can restore it when re-checking
48
+ let lastValue = $state<string | undefined>(undefined);
49
+
50
+ // Derive the actual disabled state: disabled prop OR (nullable and unchecked)
51
+ let inputDisabled = $derived(disabled || (nullable && !isChecked));
52
+
53
+ const getCurrentTime = () => {
54
+ const now = new Date();
55
+ return `${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
56
+ };
57
+
58
+ const checkChanged = () => {
59
+ if (nullable) {
60
+ if (isChecked) {
61
+ // Restore last value if available, otherwise use current time
62
+ value = lastValue || getCurrentTime();
63
+ } else {
64
+ // Store current value before clearing
65
+ if (value) {
66
+ lastValue = value;
67
+ }
68
+ value = '';
69
+ }
70
+ }
71
+ onCheckChanged?.(isChecked);
72
+ handleInputChange();
73
+ };
74
+
75
+ const handleInputChange = () => {
76
+ const currentValue = !nullable || isChecked ? value : null;
77
+ // Remember the value if it's not empty
78
+ if (isChecked && value) {
79
+ lastValue = value;
80
+ }
81
+ onChange?.(currentValue);
82
+ onInput?.(currentValue);
83
+ };
84
+
28
85
  const handleInput = (e: Event) => {
29
86
  const target = e.target as HTMLInputElement;
30
87
  value = target.value;
31
- onChange?.(target.value);
88
+ handleInputChange();
89
+ };
90
+
91
+ const handleChange = (e: Event) => {
92
+ const target = e.target as HTMLInputElement;
93
+ value = target.value;
94
+ handleInputChange();
32
95
  };
96
+
97
+ $effect(() => {
98
+ if (!value) {
99
+ // Use untrack to prevent writes to isChecked/value from triggering this effect again
100
+ untrack(() => {
101
+ if (nullable) isChecked = false;
102
+ });
103
+ } else {
104
+ // Initialize lastValue if we have an initial value
105
+ if (!lastValue) {
106
+ lastValue = value;
107
+ }
108
+ }
109
+ });
110
+
111
+ let showInput = $derived(!nullable || isChecked);
33
112
  </script>
34
113
 
35
114
  <FormField {size} {label} {id} {required} {disabled} {helperText} {feedback}>
36
- <div class="input" class:disabled>
115
+ <FormInputWrapper
116
+ {disabled}
117
+ {nullable}
118
+ nullText={nullText || '-- : -- : --'}
119
+ onCheckChanged={checkChanged}
120
+ >
37
121
  <input
38
122
  {id}
39
123
  type="time"
40
124
  bind:value
41
- {disabled}
125
+ disabled={inputDisabled}
42
126
  {required}
127
+ {readonly}
43
128
  oninput={handleInput}
129
+ onchange={handleChange}
130
+ onfocus={onFocus}
131
+ onblur={onBlur}
44
132
  aria-required={required}
45
133
  />
46
- </div>
134
+ </FormInputWrapper>
47
135
  </FormField>
48
136
 
49
- <style>.input {
50
- display: flex;
51
- align-items: center;
52
- justify-content: flex-start;
53
- position: relative;
54
- width: 100%;
55
- height: 100%;
56
- border-radius: var(--radius-md);
57
- border: var(--border-thin) solid var(--form-input-border);
58
- background-color: var(--form-input-bg);
59
- color: var(--form-input-fg);
60
- font-size: var(--font-md);
61
- font-weight: 500;
62
- line-height: 2rem;
63
- transition: background-color var(--transition-base) var(--ease-in-out), border-color var(--transition-base) var(--ease-in-out), color var(--transition-base) var(--ease-in-out);
64
- user-select: none;
65
- white-space: nowrap;
66
- }
67
- .input.disabled {
68
- opacity: 0.5;
69
- }
70
- .input input {
137
+ <style>input[type=time] {
71
138
  background-color: transparent;
72
139
  border: none;
73
140
  line-height: 2rem;
141
+ height: 2rem;
74
142
  font-size: var(--font-md);
75
143
  width: 100%;
76
144
  flex-grow: 1;
77
- padding-left: var(--spacing-base);
78
- padding-right: var(--spacing-base);
145
+ padding: 0 var(--spacing-base);
146
+ margin: 0;
147
+ box-sizing: border-box;
79
148
  }
80
- .input input:focus {
149
+ input[type=time]:focus {
81
150
  outline: none;
82
151
  }
83
- .input input:focus-visible {
152
+ input[type=time]:focus-visible {
84
153
  outline: 2px solid var(--focus-ring, #007bff);
85
154
  outline-offset: 2px;
86
155
  }
87
- .input input:disabled {
156
+ input[type=time]:disabled {
88
157
  cursor: not-allowed;
89
158
  }
90
- .input:focus-within {
91
- border-color: var(--form-input-border-focus, #3182ce);
159
+ input[type=time]:read-only {
160
+ cursor: default;
92
161
  }</style>
@@ -1,13 +1,20 @@
1
1
  import { type FormFieldFeedback } from '../form-field/form-field.svelte';
2
- import type { ComponentSize } from '../../types/size.js';
2
+ import type { FormFieldSizeOptions } from '../../types/form.js';
3
3
  type $$ComponentProps = {
4
4
  value?: string | null;
5
- size?: ComponentSize;
5
+ size?: FormFieldSizeOptions;
6
+ nullable?: boolean;
6
7
  disabled?: boolean;
7
8
  required?: boolean;
8
- onChange?: ((value: string) => void) | undefined;
9
+ readonly?: boolean;
10
+ onChange?: ((value: string | null) => void) | undefined;
11
+ onCheckChanged?: ((isChecked: boolean) => void) | undefined;
12
+ onInput?: ((value: string | null) => void) | undefined;
13
+ onFocus?: ((e: FocusEvent) => void) | undefined;
14
+ onBlur?: ((e: FocusEvent) => void) | undefined;
9
15
  label?: string;
10
16
  helperText?: string;
17
+ nullText?: string;
11
18
  feedback?: FormFieldFeedback;
12
19
  };
13
20
  declare const TimeBox: import("svelte").Component<$$ComponentProps, {}, "value">;
@@ -14,11 +14,17 @@
14
14
  disabled = false,
15
15
  required = false,
16
16
  readonly = false,
17
+ nullable = false,
17
18
  maxlength = undefined,
18
19
  minlength = undefined,
19
20
  pattern = undefined,
20
21
  isLoading = false,
21
- onChange = undefined
22
+ onChange = undefined,
23
+ onCheckChanged = undefined,
24
+ onInput = undefined,
25
+ onFocus = undefined,
26
+ onBlur = undefined,
27
+ nullText = ''
22
28
  }: {
23
29
  protocol?: HttpProtocol;
24
30
  value?: string | null;
@@ -30,22 +36,32 @@
30
36
  disabled?: boolean;
31
37
  required?: boolean;
32
38
  readonly?: boolean;
39
+ nullable?: boolean;
33
40
  maxlength?: number | undefined;
34
41
  minlength?: number | undefined;
35
42
  pattern?: string | undefined;
36
43
  isLoading?: boolean;
37
- onChange?: ((value: string) => void) | undefined;
44
+ onChange?: ((value: string | null) => void) | undefined;
45
+ onCheckChanged?: ((isChecked: boolean) => void) | undefined;
46
+ onInput?: ((value: string | null) => void) | undefined;
47
+ onFocus?: ((e: FocusEvent) => void) | undefined;
48
+ onBlur?: ((e: FocusEvent) => void) | undefined;
49
+ nullText?: string;
38
50
  } = $props();
39
51
 
40
52
  // On input, parse the value and set the protocol
41
- const handleInput = (inputValue: string) => {
53
+ const handleInput = (inputValue: string | null) => {
42
54
  const cleanValue = inputValue ?? '';
43
55
  const urlParts = cleanValue.split('://');
44
56
  if (['http', 'https'].includes(urlParts[0])) {
45
57
  protocol = urlParts[0] as HttpProtocol;
46
58
  value = urlParts[1];
47
59
  }
48
- onChange?.(cleanValue);
60
+ onInput?.(inputValue);
61
+ };
62
+
63
+ const handleChange = (inputValue: string | null) => {
64
+ onChange?.(inputValue);
49
65
  };
50
66
  </script>
51
67
 
@@ -56,7 +72,10 @@
56
72
  prefix={protocol + '://'}
57
73
  {size}
58
74
  onInput={handleInput}
59
- onChange={handleInput}
75
+ onChange={handleChange}
76
+ {onCheckChanged}
77
+ {onFocus}
78
+ {onBlur}
60
79
  allowSpaces={false}
61
80
  {label}
62
81
  {helperText}
@@ -64,8 +83,10 @@
64
83
  {disabled}
65
84
  {required}
66
85
  {readonly}
86
+ {nullable}
67
87
  {maxlength}
68
88
  {minlength}
69
89
  {pattern}
70
90
  {isLoading}
91
+ {nullText}
71
92
  />
@@ -11,11 +11,17 @@ type $$ComponentProps = {
11
11
  disabled?: boolean;
12
12
  required?: boolean;
13
13
  readonly?: boolean;
14
+ nullable?: boolean;
14
15
  maxlength?: number | undefined;
15
16
  minlength?: number | undefined;
16
17
  pattern?: string | undefined;
17
18
  isLoading?: boolean;
18
- onChange?: ((value: string) => void) | undefined;
19
+ onChange?: ((value: string | null) => void) | undefined;
20
+ onCheckChanged?: ((isChecked: boolean) => void) | undefined;
21
+ onInput?: ((value: string | null) => void) | undefined;
22
+ onFocus?: ((e: FocusEvent) => void) | undefined;
23
+ onBlur?: ((e: FocusEvent) => void) | undefined;
24
+ nullText?: string;
19
25
  };
20
26
  declare const UrlBox: import("svelte").Component<$$ComponentProps, {}, "value" | "protocol">;
21
27
  type UrlBox = ReturnType<typeof UrlBox>;
@@ -13,7 +13,7 @@
13
13
  import TextBox from '../../forms/text-box/text-box.svelte';
14
14
  import TextArea from '../../forms/text-area/text-area.svelte';
15
15
  import CheckBox from '../../forms/check-box/check-box.svelte';
16
- import SwitchBox from '../../forms/switch-box/switch-box.svelte';
16
+ import BoolBox from '../../forms/bool-box/bool-box.svelte';
17
17
 
18
18
  const theme = useTheme();
19
19
 
@@ -125,7 +125,12 @@
125
125
 
126
126
  <CheckBox bind:isChecked={checked} label="Check Box Option" />
127
127
 
128
- <SwitchBox bind:checked={switchOn}>Switch Option</SwitchBox>
128
+ <BoolBox
129
+ variant="switch"
130
+ bind:value={switchOn}
131
+ options={['On', 'Off']}
132
+ label="Switch Option"
133
+ />
129
134
  </div>
130
135
  </div>
131
136
  </Card>