sveltacular 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 (47) hide show
  1. package/README.md +232 -28
  2. package/dist/forms/bool-box/bool-box.svelte +21 -2
  3. package/dist/forms/bool-box/bool-box.svelte.d.ts +5 -0
  4. package/dist/forms/check-box/check-box-group.svelte +1 -0
  5. package/dist/forms/check-box/check-box.svelte +73 -31
  6. package/dist/forms/check-box/check-box.svelte.d.ts +7 -0
  7. package/dist/forms/date-box/date-box.svelte +7 -3
  8. package/dist/forms/date-box/date-box.svelte.d.ts +3 -0
  9. package/dist/forms/file-box/file-box.svelte +33 -7
  10. package/dist/forms/form-field/form-field.svelte +128 -33
  11. package/dist/forms/form-field/form-field.svelte.d.ts +9 -3
  12. package/dist/forms/form-label/form-label.svelte +4 -2
  13. package/dist/forms/form-label/form-label.svelte.d.ts +2 -2
  14. package/dist/forms/index.d.ts +6 -2
  15. package/dist/forms/index.js +6 -3
  16. package/dist/forms/info-box/info-box.svelte +9 -7
  17. package/dist/forms/list-box/list-box.svelte +270 -89
  18. package/dist/forms/list-box/list-box.svelte.d.ts +3 -0
  19. package/dist/forms/money-box/money-box.svelte +20 -16
  20. package/dist/forms/number-box/number-box.svelte +16 -3
  21. package/dist/forms/number-box/number-box.svelte.d.ts +6 -0
  22. package/dist/forms/number-range-box/number-range-box.svelte +218 -0
  23. package/dist/forms/number-range-box/number-range-box.svelte.d.ts +21 -0
  24. package/dist/forms/phone-box/phone-box.svelte +17 -3
  25. package/dist/forms/phone-box/phone-box.svelte.d.ts +5 -0
  26. package/dist/forms/radio-group/radio-box.svelte +11 -2
  27. package/dist/forms/radio-group/radio-box.svelte.d.ts +1 -0
  28. package/dist/forms/radio-group/radio-group.svelte +10 -4
  29. package/dist/forms/radio-group/radio-group.svelte.d.ts +4 -0
  30. package/dist/forms/switch-box/switch-box.svelte +33 -13
  31. package/dist/forms/switch-box/switch-box.svelte.d.ts +6 -0
  32. package/dist/forms/tag-input-box/tag-input-box.svelte +204 -0
  33. package/dist/forms/tag-input-box/tag-input-box.svelte.d.ts +18 -0
  34. package/dist/forms/text-area/text-area.svelte +19 -3
  35. package/dist/forms/text-area/text-area.svelte.d.ts +7 -0
  36. package/dist/forms/text-box/text-box.svelte +18 -15
  37. package/dist/forms/text-box/text-box.svelte.d.ts +2 -2
  38. package/dist/forms/time-box/time-box.svelte +7 -3
  39. package/dist/forms/time-box/time-box.svelte.d.ts +3 -0
  40. package/dist/forms/url-box/url-box.svelte +31 -1
  41. package/dist/forms/url-box/url-box.svelte.d.ts +10 -0
  42. package/dist/generic/avatar/avatar.svelte +2 -0
  43. package/dist/generic/chip/chip.svelte +2 -0
  44. package/dist/generic/menu/menu.svelte +2 -3
  45. package/dist/navigation/context-menu/README.md +2 -0
  46. package/dist/navigation/context-menu/context-menu-divider.svelte +2 -0
  47. package/package.json +1 -1
@@ -32,7 +32,7 @@
32
32
  </script>
33
33
 
34
34
  <FormField {size} {label} {id} {required} {disabled}>
35
- <div>
35
+ <div class="input">
36
36
  <input
37
37
  {id}
38
38
  {placeholder}
@@ -48,21 +48,47 @@
48
48
  </div>
49
49
  </FormField>
50
50
 
51
- <style>input {
51
+ <style>.input {
52
+ display: flex;
53
+ align-items: center;
54
+ justify-content: flex-start;
55
+ position: relative;
52
56
  width: 100%;
53
- padding: var(--spacing-sm) var(--spacing-base);
57
+ height: 100%;
58
+ box-sizing: border-box;
54
59
  border-radius: var(--radius-md);
55
60
  border: var(--border-thin) solid var(--form-input-border);
56
61
  background-color: var(--form-input-bg);
57
62
  color: var(--form-input-fg);
58
- font-size: var(--font-base);
63
+ font-size: var(--font-md);
59
64
  font-weight: 500;
60
- line-height: 1.25rem;
61
- transition: background-color 0.2s ease-in-out, border-color 0.2s ease-in-out, color 0.2s ease-in-out, fill 0.2s ease-in-out, stroke 0.2s ease-in-out;
65
+ line-height: 2rem;
66
+ 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);
62
67
  user-select: none;
63
68
  white-space: nowrap;
64
69
  }
65
- input::placeholder {
70
+ .input input {
71
+ width: 100%;
72
+ height: 100%;
73
+ box-sizing: border-box;
74
+ padding: 0;
75
+ padding-left: var(--spacing-base);
76
+ padding-right: var(--spacing-base);
77
+ margin: 0;
78
+ background-color: transparent;
79
+ border: none;
80
+ line-height: 2rem;
81
+ font-size: var(--font-md);
82
+ color: inherit;
83
+ }
84
+ .input input:focus {
85
+ outline: none;
86
+ }
87
+ .input input:focus-visible {
88
+ outline: 2px solid var(--focus-ring, #007bff);
89
+ outline-offset: 2px;
90
+ }
91
+ .input input::placeholder {
66
92
  color: var(--form-input-placeholder, #888);
67
93
  font-style: italic;
68
94
  }</style>
@@ -1,18 +1,41 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import type { ComponentSize } from '../../types/size.js';
4
- import { getMaxWidth, getDisplayType } from '../../types/size.js';
5
4
  import FormLabel from '../form-label/form-label.svelte';
5
+ import type { AriaRole } from 'svelte/elements';
6
+
7
+ export type FormFieldMessage = {
8
+ text: string;
9
+ isError?: boolean;
10
+ };
11
+
12
+ export type FormFieldFeedback = FormFieldMessage & {
13
+ details?: FormFieldMessage[];
14
+ };
15
+
16
+ /**
17
+ * Maps size to flex-grow value for relative sizing in flexbox containers (FormRow).
18
+ * The size prop controls how much space the field takes relative to its siblings.
19
+ */
20
+ const getFlexGrow = (size: ComponentSize): number => {
21
+ const flexMap: Record<ComponentSize, number> = {
22
+ sm: 1,
23
+ md: 2,
24
+ lg: 3,
25
+ xl: 4,
26
+ full: 4 // Map full to same as xl for backwards compatibility
27
+ };
28
+ return flexMap[size];
29
+ };
6
30
 
7
31
  let {
8
- size = 'full',
32
+ size = 'md',
9
33
  label = undefined,
10
34
  id = undefined,
11
35
  required = false,
12
36
  disabled = false,
13
- helperText = undefined,
14
- errorText = undefined,
15
- successText = undefined,
37
+ helperText,
38
+ feedback,
16
39
  children
17
40
  }: {
18
41
  size?: ComponentSize;
@@ -20,36 +43,70 @@
20
43
  id?: string | undefined;
21
44
  required?: boolean;
22
45
  disabled?: boolean;
23
- helperText?: string | undefined;
24
- errorText?: string | undefined;
25
- successText?: string | undefined;
46
+ helperText?: string;
47
+ feedback?: FormFieldFeedback;
26
48
  children: Snippet;
27
49
  } = $props();
28
50
 
29
- let displayType = $derived(getDisplayType(size));
30
- let maxWidth = $derived(getMaxWidth(size));
51
+ let flexGrow = $derived(getFlexGrow(size));
52
+
53
+ // Determine which message to show: feedback takes precedence over helperText
54
+ let message = $derived.by<FormFieldFeedback | undefined>(() => {
55
+ if (feedback) return feedback;
56
+ if (helperText) {
57
+ return { text: helperText, isError: false };
58
+ }
59
+ return undefined;
60
+ });
61
+
62
+ // Determine if we're showing helper text (no feedback) or actual feedback
63
+ let isHelperText = $derived(!feedback && !!helperText);
64
+
65
+ let ariaLive = $derived<'assertive' | 'polite' | undefined>(
66
+ message && !isHelperText ? (message.isError ? 'assertive' : 'polite') : undefined
67
+ );
68
+
69
+ let ariaRole = $derived<AriaRole | undefined>(
70
+ message && !isHelperText ? (message.isError ? 'alert' : 'status') : undefined
71
+ );
72
+
73
+ let messageClass = $derived(
74
+ message ? (isHelperText ? 'helper' : message.isError ? 'error' : 'success') : undefined
75
+ );
31
76
 
32
- let showHelperText = $derived(!!helperText && !errorText && !successText);
33
- let showSuccessText = $derived(!!successText && !errorText);
34
- let showErrorText = $derived(!!errorText);
77
+ let messageId = $derived(
78
+ message && id
79
+ ? isHelperText
80
+ ? `${id}-helper`
81
+ : message.isError
82
+ ? `${id}-error`
83
+ : `${id}-success`
84
+ : undefined
85
+ );
35
86
  </script>
36
87
 
37
- <div class="form-field {size} {displayType} {maxWidth}">
88
+ <div class="form-field {size}" style="--flex-grow: {flexGrow};">
38
89
  {#if label}
39
90
  <FormLabel {id} {required} {disabled} {label} />
40
91
  {/if}
41
92
  {@render children?.()}
42
- {#if showHelperText}
43
- <div class="helper-text" id="{id}-helper">{helperText}</div>
44
- {/if}
45
- {#if showSuccessText}
46
- <div class="success-text" id="{id}-success" role="status" aria-live="polite">
47
- {successText}
48
- </div>
49
- {/if}
50
- {#if showErrorText}
51
- <div class="error-text" id="{id}-error" role="alert" aria-live="assertive">
52
- {errorText}
93
+ {#if message}
94
+ <div
95
+ class="message-container {messageClass}"
96
+ role={ariaRole}
97
+ aria-live={ariaLive}
98
+ id={messageId}
99
+ >
100
+ <div class="message-text">{message.text}</div>
101
+ {#if message.details && message.details.length > 0}
102
+ <ul class="message-details">
103
+ {#each message.details as detail}
104
+ <li class="message-detail" class:error={detail.isError} class:success={!detail.isError}>
105
+ {detail.text}
106
+ </li>
107
+ {/each}
108
+ </ul>
109
+ {/if}
53
110
  </div>
54
111
  {/if}
55
112
  </div>
@@ -61,7 +118,9 @@
61
118
  display: flex;
62
119
  flex-direction: column;
63
120
  gap: 0.25rem;
64
- flex: 1;
121
+ flex-grow: var(--flex-grow, 1);
122
+ flex-shrink: 1;
123
+ flex-basis: 0;
65
124
  }
66
125
  @media (max-width: 479.98px) {
67
126
  .form-field {
@@ -69,18 +128,54 @@
69
128
  }
70
129
  }
71
130
 
72
- .success-text {
131
+ .message-container {
132
+ display: flex;
133
+ flex-direction: column;
134
+ gap: 0.5rem;
73
135
  font-size: var(--font-sm);
74
136
  line-height: 1.25rem;
75
137
  padding: var(--spacing-xs);
76
- color: var(--success, #28a745);
138
+ transition: color var(--transition-base) var(--ease-in-out), opacity var(--transition-base) var(--ease-in-out);
139
+ }
140
+ .message-container.helper {
141
+ color: var(--form-input-helper-text-fg, var(--body-fg-muted));
142
+ }
143
+ .message-container.success {
144
+ color: var(--color-success, #28a745);
145
+ font-weight: 500;
146
+ }
147
+ .message-container.error {
148
+ color: var(--color-error, #dc3545);
77
149
  font-weight: 500;
78
150
  }
79
151
 
80
- .error-text {
81
- font-size: var(--font-sm);
152
+ .message-text {
82
153
  line-height: 1.25rem;
83
- padding: var(--spacing-xs);
84
- color: var(--danger, #dc3545);
85
- font-weight: 500;
154
+ }
155
+
156
+ .message-details {
157
+ list-style: none;
158
+ margin: 0;
159
+ padding: 0;
160
+ padding-left: var(--spacing-sm);
161
+ display: flex;
162
+ flex-direction: column;
163
+ gap: 0.1rem;
164
+ }
165
+
166
+ .message-detail {
167
+ font-size: var(--font-sm);
168
+ margin: 0;
169
+ padding: 0;
170
+ transition: color var(--transition-base) var(--ease-in-out), opacity var(--transition-base) var(--ease-in-out);
171
+ }
172
+ .message-detail::before {
173
+ content: "- ";
174
+ border-radius: 50%;
175
+ }
176
+ .message-detail.error {
177
+ color: var(--color-error, #dc3545);
178
+ }
179
+ .message-detail.success {
180
+ color: var(--color-success, #28a745);
86
181
  }</style>
@@ -1,14 +1,20 @@
1
1
  import type { Snippet } from 'svelte';
2
2
  import type { ComponentSize } from '../../types/size.js';
3
+ export type FormFieldMessage = {
4
+ text: string;
5
+ isError?: boolean;
6
+ };
7
+ export type FormFieldFeedback = FormFieldMessage & {
8
+ details?: FormFieldMessage[];
9
+ };
3
10
  type $$ComponentProps = {
4
11
  size?: ComponentSize;
5
12
  label?: string | undefined;
6
13
  id?: string | undefined;
7
14
  required?: boolean;
8
15
  disabled?: boolean;
9
- helperText?: string | undefined;
10
- errorText?: string | undefined;
11
- successText?: string | undefined;
16
+ helperText?: string;
17
+ feedback?: FormFieldFeedback;
12
18
  children: Snippet;
13
19
  };
14
20
  declare const FormField: import("svelte").Component<$$ComponentProps, {}, "">;
@@ -5,10 +5,10 @@
5
5
  disabled = false,
6
6
  label = ''
7
7
  }: {
8
- id?: string | undefined;
8
+ id?: string;
9
9
  required?: boolean;
10
10
  disabled?: boolean;
11
- label?: string;
11
+ label: string;
12
12
  } = $props();
13
13
  </script>
14
14
 
@@ -19,10 +19,12 @@
19
19
  margin-bottom: var(--spacing-sm);
20
20
  font-weight: 500;
21
21
  font-size: var(--font-base);
22
+ margin: 0;
22
23
  }
23
24
  label.required::after {
24
25
  content: "*";
25
26
  margin-left: var(--spacing-xs);
27
+ color: var(--color-danger);
26
28
  }
27
29
  label.disabled {
28
30
  opacity: 0.5;
@@ -1,8 +1,8 @@
1
1
  type $$ComponentProps = {
2
- id?: string | undefined;
2
+ id?: string;
3
3
  required?: boolean;
4
4
  disabled?: boolean;
5
- label?: string;
5
+ label: string;
6
6
  };
7
7
  declare const FormLabel: import("svelte").Component<$$ComponentProps, {}, "">;
8
8
  type FormLabel = ReturnType<typeof FormLabel>;
@@ -7,8 +7,14 @@ export { default as InfoBox } from './info-box/info-box.svelte';
7
7
  export { default as MoneyBox } from './money-box/money-box.svelte';
8
8
  export { default as NewOrExistingCombo } from './combo/new-or-existing-combo.svelte';
9
9
  export { default as NumberBox } from './number-box/number-box.svelte';
10
+ export { default as NumberRangeBox } from './number-range-box/number-range-box.svelte';
11
+ export { default as PhoneBox } from './phone-box/phone-box.svelte';
12
+ export { default as Slider } from './slider/slider.svelte';
13
+ export { default as SwitchBox } from './switch-box/switch-box.svelte';
14
+ export { default as TagInputBox } from './tag-input-box/tag-input-box.svelte';
10
15
  export { default as TextArea } from './text-area/text-area.svelte';
11
16
  export { default as TextBox } from './text-box/text-box.svelte';
17
+ export { default as TimeBox } from './time-box/time-box.svelte';
12
18
  export { default as UrlBox } from './url-box/url-box.svelte';
13
19
  export * from './check-box/index.js';
14
20
  export * from './list-box/index.js';
@@ -21,6 +27,4 @@ export { default as FormHeader } from './form-header.svelte';
21
27
  export { default as FormLabel } from './form-label/form-label.svelte';
22
28
  export { default as FormSection } from './form-section/form-section.svelte';
23
29
  export { default as FormRow } from './form-row/form-row.svelte';
24
- export { default as Slider } from './slider/slider.svelte';
25
- export { default as TimeBox } from './time-box/time-box.svelte';
26
30
  export * from './validation.js';
@@ -8,8 +8,14 @@ export { default as InfoBox } from './info-box/info-box.svelte';
8
8
  export { default as MoneyBox } from './money-box/money-box.svelte';
9
9
  export { default as NewOrExistingCombo } from './combo/new-or-existing-combo.svelte';
10
10
  export { default as NumberBox } from './number-box/number-box.svelte';
11
+ export { default as NumberRangeBox } from './number-range-box/number-range-box.svelte';
12
+ export { default as PhoneBox } from './phone-box/phone-box.svelte';
13
+ export { default as Slider } from './slider/slider.svelte';
14
+ export { default as SwitchBox } from './switch-box/switch-box.svelte';
15
+ export { default as TagInputBox } from './tag-input-box/tag-input-box.svelte';
11
16
  export { default as TextArea } from './text-area/text-area.svelte';
12
17
  export { default as TextBox } from './text-box/text-box.svelte';
18
+ export { default as TimeBox } from './time-box/time-box.svelte';
13
19
  export { default as UrlBox } from './url-box/url-box.svelte';
14
20
  // Form components with barrel files
15
21
  export * from './check-box/index.js';
@@ -24,8 +30,5 @@ export { default as FormHeader } from './form-header.svelte';
24
30
  export { default as FormLabel } from './form-label/form-label.svelte';
25
31
  export { default as FormSection } from './form-section/form-section.svelte';
26
32
  export { default as FormRow } from './form-row/form-row.svelte';
27
- // New form components
28
- export { default as Slider } from './slider/slider.svelte';
29
- export { default as TimeBox } from './time-box/time-box.svelte';
30
33
  // Validation utilities
31
34
  export * from './validation.js';
@@ -30,16 +30,18 @@
30
30
  </FormField>
31
31
 
32
32
  <style>.input {
33
- background-color: var(--form-input-disabled-bg, #ccc);
34
- color: var(--form-input-fg, #000);
35
- font-size: 1rem;
33
+ background-color: var(--form-input-disabled-bg);
34
+ color: var(--form-input-fg);
35
+ font-size: var(--font-md);
36
36
  width: 100%;
37
- padding-left: 0.5rem;
38
- border: solid 1px var(--form-input-border, black);
37
+ height: 100%;
38
+ padding-left: var(--spacing-base);
39
+ border-radius: var(--radius-md);
40
+ border: var(--border-thin) solid var(--form-input-border);
39
41
  display: flex;
40
42
  align-items: center;
41
43
  justify-content: flex-start;
42
- gap: 0.5rem;
44
+ gap: var(--spacing-sm);
43
45
  }
44
46
  .input .icon {
45
47
  display: block;
@@ -50,7 +52,7 @@
50
52
  .input .text {
51
53
  line-height: 2rem;
52
54
  flex-grow: 1;
53
- font-size: 1rem;
55
+ font-size: var(--font-md);
54
56
  }
55
57
  .input a {
56
58
  color: var(--form-input-fg, #000);