sveltacular 1.0.4 → 1.0.5

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 (32) hide show
  1. package/README.md +21 -2
  2. package/dist/forms/base-input-wrapper.svelte +0 -2
  3. package/dist/forms/check-box/check-box-group.svelte +2 -2
  4. package/dist/forms/date-box/date-box.svelte +15 -4
  5. package/dist/forms/file-box/file-box.svelte +1 -1
  6. package/dist/forms/form-field/form-field.svelte +86 -0
  7. package/dist/forms/form-field/form-field.svelte.d.ts +16 -0
  8. package/dist/forms/form-footer.svelte +7 -5
  9. package/dist/forms/form-label/form-label.svelte +30 -0
  10. package/dist/forms/form-label/form-label.svelte.d.ts +9 -0
  11. package/dist/forms/form-row/form-row.svelte +29 -0
  12. package/dist/forms/form-row/form-row.svelte.d.ts +7 -0
  13. package/dist/forms/form-section/form-section.svelte +36 -0
  14. package/dist/forms/form-section/form-section.svelte.d.ts +10 -0
  15. package/dist/forms/index.d.ts +4 -4
  16. package/dist/forms/index.js +4 -4
  17. package/dist/forms/info-box/info-box.svelte +1 -1
  18. package/dist/forms/list-box/list-box.svelte +3 -3
  19. package/dist/forms/money-box/money-box.svelte +27 -17
  20. package/dist/forms/number-box/number-box.svelte +5 -2
  21. package/dist/forms/phone-box/phone-box.svelte +25 -13
  22. package/dist/forms/radio-group/radio-group.svelte +1 -1
  23. package/dist/forms/slider/slider.svelte +15 -15
  24. package/dist/forms/text-area/text-area.svelte +1 -1
  25. package/dist/forms/text-box/text-box.svelte +1 -1
  26. package/dist/forms/time-box/time-box.svelte +42 -17
  27. package/dist/generic/avatar/avatar.svelte +2 -0
  28. package/dist/generic/chip/chip.svelte +2 -0
  29. package/dist/navigation/context-menu/README.md +2 -0
  30. package/dist/navigation/context-menu/context-menu-divider.svelte +2 -0
  31. package/dist/sveltacular.css +5 -1
  32. package/package.json +1 -1
package/README.md CHANGED
@@ -12,18 +12,37 @@ npm i sveltacular
12
12
 
13
13
  ## Quick Start
14
14
 
15
- First, import the default styles to get all CSS variables and base styles:
15
+ ### 1. Import the default stylesheet (once, in your app root)
16
+
17
+ **For SvelteKit**, add this to `src/routes/+layout.svelte`:
16
18
 
17
19
  ```svelte
18
20
  <script lang="ts">
19
21
  import 'sveltacular/styles.css';
22
+ </script>
23
+
24
+ <slot />
25
+ ```
26
+
27
+ **For regular Svelte**, add it to your main `App.svelte` or root component:
28
+
29
+ ```svelte
30
+ <script lang="ts">
31
+ import 'sveltacular/styles.css';
32
+ </script>
33
+ ```
34
+
35
+ ### 2. Use components anywhere
36
+
37
+ ```svelte
38
+ <script lang="ts">
20
39
  import { Button } from 'sveltacular';
21
40
  </script>
22
41
 
23
42
  <Button variant="primary" label="Hello World" />
24
43
  ```
25
44
 
26
- **Note**: The styles import is required for components to render with default styling. If you prefer to provide your own theme, you can skip this import and define your own CSS variables (see [Theming](#theming) below).
45
+ **Note**: The stylesheet import is required for components to render with default styling. Import it once at the app level (not in individual components). If you prefer to provide your own theme, you can skip this import and define your own CSS variables (see [Theming](#theming) below).
27
46
 
28
47
  ## Component Catalog
29
48
 
@@ -1,6 +1,5 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import type { FormFieldSizeOptions } from '../types/form.js';
4
3
 
5
4
  let {
6
5
  id,
@@ -96,4 +95,3 @@
96
95
  padding: var(--spacing-xs);
97
96
  color: var(--form-input-error-fg);
98
97
  }</style>
99
-
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { untrack } from 'svelte';
3
3
  import type { DropdownOption, FormFieldSizeOptions } from '../../types/form.js';
4
- import FormField from '../form-field.svelte';
4
+ import FormField from '../form-field/form-field.svelte';
5
5
  import CheckBox from './check-box.svelte';
6
6
  import { uniqueId } from '../../helpers/unique-id.js';
7
7
 
@@ -34,7 +34,7 @@
34
34
  // Track items and group as dependencies
35
35
  const currentItems = items;
36
36
  const currentGroup = group;
37
-
37
+
38
38
  // Use untrack to prevent writing to itemsWithState from triggering this effect again
39
39
  untrack(() => {
40
40
  // Rebuild itemsWithState from items, using group to determine checked state
@@ -1,12 +1,18 @@
1
1
  <script lang="ts">
2
2
  import { untrack } from 'svelte';
3
- import { addUnits, currentDateTime, isDateString, isDateOrDateTimeString, isDateTimeString } from '../../helpers/date.js';
3
+ import {
4
+ addUnits,
5
+ currentDateTime,
6
+ isDateString,
7
+ isDateOrDateTimeString,
8
+ isDateTimeString
9
+ } from '../../helpers/date.js';
4
10
  import { uniqueId } from '../../helpers/unique-id.js';
5
- import FormField from '../form-field.svelte';
11
+ import FormField from '../form-field/form-field.svelte';
6
12
  import type { DateUnit, FormFieldSizeOptions } from '../../index.js';
7
13
  import Button from '../button/button.svelte';
8
14
 
9
- type DateIncrementStep = { label: string; value: number, unit: DateUnit };
15
+ type DateIncrementStep = { label: string; value: number; unit: DateUnit };
10
16
 
11
17
  const id = uniqueId();
12
18
 
@@ -91,7 +97,12 @@
91
97
  {#if steps.length > 0}
92
98
  <span class="steps">
93
99
  {#each steps as step}
94
- <Button noMargin={true} collapse={true} onClick={() => incrementValue(step)} label={step.label} />
100
+ <Button
101
+ noMargin={true}
102
+ collapse={true}
103
+ onClick={() => incrementValue(step)}
104
+ label={step.label}
105
+ />
95
106
  {/each}
96
107
  </span>
97
108
  {/if}
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
3
  import { uniqueId } from '../../helpers/unique-id.js';
4
- import FormField from '../form-field.svelte';
4
+ import FormField from '../form-field/form-field.svelte';
5
5
  import type { FormFieldSizeOptions } from '../../types/form.js';
6
6
 
7
7
  const id = uniqueId();
@@ -0,0 +1,86 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { ComponentSize } from '../../types/size.js';
4
+ import { getMaxWidth, getDisplayType } from '../../types/size.js';
5
+ import FormLabel from '../form-label/form-label.svelte';
6
+
7
+ let {
8
+ size = 'full',
9
+ label = undefined,
10
+ id = undefined,
11
+ required = false,
12
+ disabled = false,
13
+ helperText = undefined,
14
+ errorText = undefined,
15
+ successText = undefined,
16
+ children
17
+ }: {
18
+ size?: ComponentSize;
19
+ label?: string | undefined;
20
+ id?: string | undefined;
21
+ required?: boolean;
22
+ disabled?: boolean;
23
+ helperText?: string | undefined;
24
+ errorText?: string | undefined;
25
+ successText?: string | undefined;
26
+ children: Snippet;
27
+ } = $props();
28
+
29
+ let displayType = $derived(getDisplayType(size));
30
+ let maxWidth = $derived(getMaxWidth(size));
31
+
32
+ let showHelperText = $derived(!!helperText && !errorText && !successText);
33
+ let showSuccessText = $derived(!!successText && !errorText);
34
+ let showErrorText = $derived(!!errorText);
35
+ </script>
36
+
37
+ <div class="form-field {size} {displayType} {maxWidth}">
38
+ {#if label}
39
+ <FormLabel {id} {required} {disabled} {label} />
40
+ {/if}
41
+ {@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}
53
+ </div>
54
+ {/if}
55
+ </div>
56
+
57
+ <style>/* ============================================
58
+ BREAKPOINTS - Responsive Design
59
+ ============================================ */
60
+ .form-field {
61
+ display: flex;
62
+ flex-direction: column;
63
+ gap: 0.25rem;
64
+ flex: 1;
65
+ }
66
+ @media (max-width: 479.98px) {
67
+ .form-field {
68
+ width: 100%;
69
+ }
70
+ }
71
+
72
+ .success-text {
73
+ font-size: var(--font-sm);
74
+ line-height: 1.25rem;
75
+ padding: var(--spacing-xs);
76
+ color: var(--success, #28a745);
77
+ font-weight: 500;
78
+ }
79
+
80
+ .error-text {
81
+ font-size: var(--font-sm);
82
+ line-height: 1.25rem;
83
+ padding: var(--spacing-xs);
84
+ color: var(--danger, #dc3545);
85
+ font-weight: 500;
86
+ }</style>
@@ -0,0 +1,16 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { ComponentSize } from '../../types/size.js';
3
+ type $$ComponentProps = {
4
+ size?: ComponentSize;
5
+ label?: string | undefined;
6
+ id?: string | undefined;
7
+ required?: boolean;
8
+ disabled?: boolean;
9
+ helperText?: string | undefined;
10
+ errorText?: string | undefined;
11
+ successText?: string | undefined;
12
+ children: Snippet;
13
+ };
14
+ declare const FormField: import("svelte").Component<$$ComponentProps, {}, "">;
15
+ type FormField = ReturnType<typeof FormField>;
16
+ export default FormField;
@@ -1,18 +1,20 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import FlexRow from '../layout/flex-row.svelte';
4
3
 
5
4
  let { children }: { children: Snippet } = $props();
6
5
  </script>
7
6
 
8
7
  <div>
9
- <FlexRow justifyContent="stretch">
10
- {@render children?.()}
11
- </FlexRow>
8
+ {@render children?.()}
12
9
  </div>
13
10
 
14
11
  <style>
15
12
  div {
16
- margin-top: 1.5rem;
13
+ display: flex;
14
+ flex-direction: row;
15
+ gap: var(--spacing-base);
16
+ justify-content: flex-end;
17
+ margin-top: var(--spacing-lg);
18
+ margin-top: var(--spacing-xl);
17
19
  }
18
20
  </style>
@@ -0,0 +1,30 @@
1
+ <script lang="ts">
2
+ let {
3
+ id = undefined,
4
+ required = false,
5
+ disabled = false,
6
+ label = ''
7
+ }: {
8
+ id?: string | undefined;
9
+ required?: boolean;
10
+ disabled?: boolean;
11
+ label?: string;
12
+ } = $props();
13
+ </script>
14
+
15
+ <label for={id} class:required class:disabled aria-required={required}>{label}</label>
16
+
17
+ <style>label {
18
+ display: block;
19
+ margin-bottom: var(--spacing-sm);
20
+ font-weight: 500;
21
+ font-size: var(--font-base);
22
+ }
23
+ label.required::after {
24
+ content: "*";
25
+ margin-left: var(--spacing-xs);
26
+ }
27
+ label.disabled {
28
+ opacity: 0.5;
29
+ cursor: not-allowed;
30
+ }</style>
@@ -0,0 +1,9 @@
1
+ type $$ComponentProps = {
2
+ id?: string | undefined;
3
+ required?: boolean;
4
+ disabled?: boolean;
5
+ label?: string;
6
+ };
7
+ declare const FormLabel: import("svelte").Component<$$ComponentProps, {}, "">;
8
+ type FormLabel = ReturnType<typeof FormLabel>;
9
+ export default FormLabel;
@@ -0,0 +1,29 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+
4
+ interface Props {
5
+ children: Snippet;
6
+ }
7
+
8
+ let { children }: Props = $props();
9
+ </script>
10
+
11
+ <div class="form-row">
12
+ {@render children()}
13
+ </div>
14
+
15
+ <style>/* ============================================
16
+ BREAKPOINTS - Responsive Design
17
+ ============================================ */
18
+ .form-row {
19
+ display: flex;
20
+ flex-direction: row;
21
+ gap: var(--spacing-base);
22
+ align-items: flex-start;
23
+ }
24
+ @media (max-width: 479.98px) {
25
+ .form-row {
26
+ flex-direction: column;
27
+ align-items: stretch;
28
+ }
29
+ }</style>
@@ -0,0 +1,7 @@
1
+ import type { Snippet } from 'svelte';
2
+ interface Props {
3
+ children: Snippet;
4
+ }
5
+ declare const FormRow: import("svelte").Component<Props, {}, "">;
6
+ type FormRow = ReturnType<typeof FormRow>;
7
+ export default FormRow;
@@ -0,0 +1,36 @@
1
+ <script lang="ts">
2
+ import type { Snippet } from 'svelte';
3
+ import type { SectionLevel } from '../../types/generic.js';
4
+
5
+ let {
6
+ title = undefined,
7
+ level = 4,
8
+ children
9
+ }: {
10
+ title?: string | undefined;
11
+ level?: SectionLevel;
12
+ children: Snippet;
13
+ } = $props();
14
+ </script>
15
+
16
+ <fieldset>
17
+ {#if title}
18
+ <legend aria-level={level}>{title}</legend>
19
+ {/if}
20
+ {@render children?.()}
21
+ </fieldset>
22
+
23
+ <style>fieldset {
24
+ display: flex;
25
+ flex-direction: column;
26
+ gap: var(--spacing-base);
27
+ border: var(--border-thin) solid var(--form-input-border);
28
+ border-radius: var(--radius-md);
29
+ padding: var(--spacing-base);
30
+ }
31
+ fieldset legend {
32
+ font-size: var(--font-lg);
33
+ font-weight: 500;
34
+ line-height: var(--line-height-base);
35
+ color: var(--form-input-fg);
36
+ }</style>
@@ -0,0 +1,10 @@
1
+ import type { Snippet } from 'svelte';
2
+ import type { SectionLevel } from '../../types/generic.js';
3
+ type $$ComponentProps = {
4
+ title?: string | undefined;
5
+ level?: SectionLevel;
6
+ children: Snippet;
7
+ };
8
+ declare const FormSection: import("svelte").Component<$$ComponentProps, {}, "">;
9
+ type FormSection = ReturnType<typeof FormSection>;
10
+ export default FormSection;
@@ -11,16 +11,16 @@ export { default as TextArea } from './text-area/text-area.svelte';
11
11
  export { default as TextBox } from './text-box/text-box.svelte';
12
12
  export { default as UrlBox } from './url-box/url-box.svelte';
13
13
  export * from './check-box/index.js';
14
- export * from './combo-box/index.js';
15
14
  export * from './list-box/index.js';
16
15
  export * from './phone-box/index.js';
17
16
  export * from './radio-group/index.js';
18
17
  export { default as Form } from './form.svelte';
19
- export { default as FormField } from './form-field.svelte';
18
+ export { default as FormField } from './form-field/form-field.svelte';
20
19
  export { default as FormFooter } from './form-footer.svelte';
21
20
  export { default as FormHeader } from './form-header.svelte';
22
- export { default as FormLabel } from './form-label.svelte';
23
- export { default as FormSection } from './form-section.svelte';
21
+ export { default as FormLabel } from './form-label/form-label.svelte';
22
+ export { default as FormSection } from './form-section/form-section.svelte';
23
+ export { default as FormRow } from './form-row/form-row.svelte';
24
24
  export { default as Slider } from './slider/slider.svelte';
25
25
  export { default as TimeBox } from './time-box/time-box.svelte';
26
26
  export * from './validation.js';
@@ -13,17 +13,17 @@ export { default as TextBox } from './text-box/text-box.svelte';
13
13
  export { default as UrlBox } from './url-box/url-box.svelte';
14
14
  // Form components with barrel files
15
15
  export * from './check-box/index.js';
16
- export * from './combo-box/index.js';
17
16
  export * from './list-box/index.js';
18
17
  export * from './phone-box/index.js';
19
18
  export * from './radio-group/index.js';
20
19
  // Form structure components
21
20
  export { default as Form } from './form.svelte';
22
- export { default as FormField } from './form-field.svelte';
21
+ export { default as FormField } from './form-field/form-field.svelte';
23
22
  export { default as FormFooter } from './form-footer.svelte';
24
23
  export { default as FormHeader } from './form-header.svelte';
25
- export { default as FormLabel } from './form-label.svelte';
26
- export { default as FormSection } from './form-section.svelte';
24
+ export { default as FormLabel } from './form-label/form-label.svelte';
25
+ export { default as FormSection } from './form-section/form-section.svelte';
26
+ export { default as FormRow } from './form-row/form-row.svelte';
27
27
  // New form components
28
28
  export { default as Slider } from './slider/slider.svelte';
29
29
  export { default as TimeBox } from './time-box/time-box.svelte';
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import LinkIcon from '../../icons/link-icon.svelte';
3
3
  import type { FormFieldSizeOptions } from '../../index.js';
4
- import FormField from '../form-field.svelte';
4
+ import FormField from '../form-field/form-field.svelte';
5
5
 
6
6
  let {
7
7
  size = 'md' as FormFieldSizeOptions,
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { DropdownOption, FormFieldSizeOptions, MenuOption } from '../../types/form.js';
3
- import FormField from '../form-field.svelte';
3
+ import FormField from '../form-field/form-field.svelte';
4
4
  import { uniqueId } from '../../helpers/unique-id.js';
5
5
  import Menu from '../../generic/menu/menu.svelte';
6
6
  import AngleUpIcon from '../../icons/angle-up-icon.svelte';
@@ -45,7 +45,7 @@
45
45
  let highlightIndex = $state(-1);
46
46
  let filteredItems = $state<MenuOption[]>([]);
47
47
  let isSeachable = $derived(searchable || !!search);
48
-
48
+
49
49
  // Get the ID of the highlighted option for ARIA
50
50
  let activeDescendant = $derived(
51
51
  highlightIndex >= 0 && filteredItems[highlightIndex]
@@ -201,7 +201,7 @@
201
201
  {open}
202
202
  closeAfterSelect={false}
203
203
  searchText={text}
204
- onSelect={onSelect}
204
+ {onSelect}
205
205
  size="full"
206
206
  bind:highlightIndex
207
207
  bind:value
@@ -1,8 +1,8 @@
1
1
  <script lang="ts">
2
2
  import { uniqueId, type FormFieldSizeOptions } from '../../index.js';
3
- import FormField from '../form-field.svelte';
3
+ import FormField from '../form-field/form-field.svelte';
4
4
  import { untrack } from 'svelte';
5
-
5
+
6
6
  const id = uniqueId();
7
7
 
8
8
  let {
@@ -35,18 +35,18 @@
35
35
 
36
36
  let isValueInCents = $derived(units === 'cents');
37
37
  const fieldOrder = ['dollars', 'cents'];
38
-
38
+
39
39
  const getDollarsFromValue = () => {
40
40
  if (!value) return '0';
41
41
  if (isValueInCents) return String(Math.abs(Math.floor(value / 100)));
42
42
  return String(Math.abs(Math.floor(value)));
43
- }
43
+ };
44
44
 
45
45
  const getCentsFromValue = () => {
46
46
  if (!value) return '00';
47
47
  if (isValueInCents) return String(Math.abs(Math.round(value % 100))).padStart(2, '0');
48
48
  return String(Math.abs(Math.round((value % 1) * 100))).padStart(2, '0');
49
- }
49
+ };
50
50
 
51
51
  let dollars = $state(getDollarsFromValue());
52
52
  let cents = $state(getCentsFromValue());
@@ -74,9 +74,10 @@
74
74
  const selection = [target.selectionStart ?? 0, target.selectionEnd ?? 0];
75
75
  const key = e instanceof KeyboardEvent ? e.key : '';
76
76
  const isNumber = !isNaN(Number(key));
77
- const isDecimal =key === '.';
77
+ const isDecimal = key === '.';
78
78
  const isBackspace = key === 'Backspace';
79
- const isAllowed = isNumber || isDecimal || ['Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(key);
79
+ const isAllowed =
80
+ isNumber || isDecimal || ['Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(key);
80
81
  return {
81
82
  element: target,
82
83
  name,
@@ -91,8 +92,8 @@
91
92
  isAllowed,
92
93
  lastState: lastState[index],
93
94
  value: target.value,
94
- next: nextName ? document.getElementById(`${id}-${nextName}`) as HTMLInputElement : null,
95
- previous: prevName ? document.getElementById(`${id}-${prevName}`) as HTMLInputElement : null
95
+ next: nextName ? (document.getElementById(`${id}-${nextName}`) as HTMLInputElement) : null,
96
+ previous: prevName ? (document.getElementById(`${id}-${prevName}`) as HTMLInputElement) : null
96
97
  };
97
98
  };
98
99
 
@@ -116,8 +117,8 @@
116
117
 
117
118
  const moveExtraCentsToDollars = (centsValue: string, append = true) => {
118
119
  if (centsValue.length > 2 && isNumericString(centsValue) && Number(centsValue) > 0) {
119
- const whole = centsValue.substring(0, centsValue.length -2);
120
- const decimal = centsValue.substring(centsValue.length -2);
120
+ const whole = centsValue.substring(0, centsValue.length - 2);
121
+ const decimal = centsValue.substring(centsValue.length - 2);
121
122
  dollars = append ? `${dollars}${whole}` : whole;
122
123
  cents = decimal;
123
124
  }
@@ -130,7 +131,7 @@
130
131
  e.preventDefault();
131
132
  if (target.next && allowCents) focusAndHighlightText(target.next);
132
133
  return;
133
- };
134
+ }
134
135
  if (target.name === 'cents' && target.value.length >= 2 && !target.isHighligted) {
135
136
  if (target.isNumber) moveExtraCentsToDollars(`${target.value}${e.key}`);
136
137
  return e.preventDefault();
@@ -141,14 +142,24 @@
141
142
  const onKeyUp = (e: KeyboardEvent) => {
142
143
  const target = getTargetProperties(e);
143
144
  // Back arrow
144
- if (target.key === 'ArrowLeft' && !target.isHighligted && target.previous && target.lastState.selectionStart === 0) {
145
+ if (
146
+ target.key === 'ArrowLeft' &&
147
+ !target.isHighligted &&
148
+ target.previous &&
149
+ target.lastState.selectionStart === 0
150
+ ) {
145
151
  const preservedValue = String(target.previous.value);
146
152
  focusAndHighlightText(target.previous);
147
153
  target.previous.value = preservedValue;
148
154
  return e.preventDefault();
149
155
  }
150
156
  // Right arrow
151
- if (target.key === 'ArrowRight' && !target.isHighligted && target.next && target.lastState.selectionStart === target.value.length) {
157
+ if (
158
+ target.key === 'ArrowRight' &&
159
+ !target.isHighligted &&
160
+ target.next &&
161
+ target.lastState.selectionStart === target.value.length
162
+ ) {
152
163
  focusAndHighlightText(target.next);
153
164
  return e.preventDefault();
154
165
  }
@@ -197,8 +208,8 @@
197
208
  let centValue = Math.abs(isNumericString(cents) ? Number(cents) : 0);
198
209
  let dollarValue = Math.abs(isNumericString(dollars) ? Number(dollars) : 0);
199
210
  // Update value
200
- if (isValueInCents) value = (dollarValue * 100) + centValue;
201
- else value = dollarValue + (centValue / 100);
211
+ if (isValueInCents) value = dollarValue * 100 + centValue;
212
+ else value = dollarValue + centValue / 100;
202
213
  // Enforce min and max
203
214
  if (min && value < min) value = min;
204
215
  if (max && value > max) value = max;
@@ -206,7 +217,6 @@
206
217
  cents = String(centValue).padStart(2, '0');
207
218
  onChange?.(value);
208
219
  };
209
-
210
220
  </script>
211
221
 
212
222
  <FormField {size} {label} {id}>
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { roundToDecimals } from '../../helpers/round-to-decimals.js';
3
3
  import { uniqueId } from '../../helpers/unique-id.js';
4
- import FormField from '../form-field.svelte';
4
+ import FormField from '../form-field/form-field.svelte';
5
5
  import type { FormFieldSizeOptions } from '../../types/form.js';
6
6
  const id = uniqueId();
7
7
 
@@ -54,7 +54,10 @@
54
54
  const onKeyPress = (e: KeyboardEvent) => {
55
55
  const isNumber = !isNaN(Number(e.key));
56
56
  const isDecimal = e.key === '.';
57
- const isAllowed = isNumber || isDecimal || ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(e.key);
57
+ const isAllowed =
58
+ isNumber ||
59
+ isDecimal ||
60
+ ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab'].includes(e.key);
58
61
  if (!isAllowed) return e.preventDefault();
59
62
  if (isDecimal && decimals === 0) return e.preventDefault();
60
63
  const newValue = `${value}${e.key}`;
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { untrack } from 'svelte';
3
3
  import { uniqueId, type FormFieldSizeOptions } from '../../index.js';
4
- import FormField from '../form-field.svelte';
4
+ import FormField from '../form-field/form-field.svelte';
5
5
 
6
6
  let {
7
7
  value = $bindable('' as string | null),
@@ -191,16 +191,24 @@
191
191
  </FormField>
192
192
 
193
193
  <style>.input {
194
- background-color: var(--form-input-bg, #fff);
195
- color: var(--form-input-fg, #000);
196
- font-size: 1rem;
197
- width: 100%;
198
- padding-left: 0.5rem;
199
- border: solid 1px var(--form-input-border, black);
200
194
  display: flex;
201
195
  align-items: center;
202
196
  justify-content: flex-start;
203
- gap: 0.5rem;
197
+ position: relative;
198
+ width: 100%;
199
+ height: 100%;
200
+ border-radius: var(--radius-md);
201
+ border: var(--border-thin) solid var(--form-input-border);
202
+ background-color: var(--form-input-bg);
203
+ color: var(--form-input-fg);
204
+ font-size: var(--font-md);
205
+ font-weight: 500;
206
+ line-height: 2rem;
207
+ padding-left: var(--spacing-base);
208
+ gap: var(--spacing-sm);
209
+ 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);
210
+ user-select: none;
211
+ white-space: nowrap;
204
212
  }
205
213
  .input .segment {
206
214
  position: relative;
@@ -218,17 +226,21 @@
218
226
  flex-basis: 140px;
219
227
  }
220
228
  .input input {
229
+ background-color: transparent;
230
+ border: none;
221
231
  line-height: 2rem;
232
+ font-size: var(--font-md);
233
+ width: 100%;
222
234
  flex-grow: 1;
223
- font-size: 1rem;
224
- border: none;
225
- background-color: transparent;
226
- color: inherit;
227
235
  padding: 0;
228
236
  margin: 0;
229
237
  text-align: center;
230
- width: 100%;
238
+ color: inherit;
231
239
  }
232
240
  .input input:focus {
233
241
  outline: none;
242
+ }
243
+ .input input:focus-visible {
244
+ outline: 2px solid var(--focus-ring, #007bff);
245
+ outline-offset: 2px;
234
246
  }</style>
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { DropdownOption, FormFieldSizeOptions } from '../../types/form.js';
3
- import FormField from '../form-field.svelte';
3
+ import FormField from '../form-field/form-field.svelte';
4
4
  import { uniqueId } from '../../helpers/unique-id.js';
5
5
  import RadioBox from './radio-box.svelte';
6
6
 
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { uniqueId } from '../../helpers/unique-id.js';
3
- import FormField from '../form-field.svelte';
3
+ import FormField from '../form-field/form-field.svelte';
4
4
  import type { ComponentSize } from '../../types/size.js';
5
5
 
6
6
  const id = uniqueId();
@@ -76,12 +76,7 @@
76
76
  aria-valuetext={displayValue}
77
77
  />
78
78
  {#if showTooltip && (isDragging || sliderRef === document.activeElement)}
79
- <div
80
- class="slider-tooltip"
81
- style="left: {percentage}%;"
82
- role="tooltip"
83
- aria-live="polite"
84
- >
79
+ <div class="slider-tooltip" style="left: {percentage}%;" role="tooltip" aria-live="polite">
85
80
  {displayValue}
86
81
  </div>
87
82
  {/if}
@@ -95,22 +90,26 @@
95
90
  <style>.slider-wrapper {
96
91
  position: relative;
97
92
  width: 100%;
98
- padding: 0.5rem 0;
93
+ display: flex;
94
+ align-items: center;
95
+ min-height: 2rem;
99
96
  }
100
97
  .slider-wrapper .slider-track-container {
101
98
  position: relative;
102
- padding-top: 2rem;
99
+ width: 100%;
100
+ flex: 1;
103
101
  }
104
102
  .slider-wrapper input[type=range] {
105
103
  width: 100%;
106
104
  height: 0.5rem;
107
- border-radius: 0.25rem;
105
+ border-radius: var(--radius-md);
108
106
  background: var(--form-input-border, #ccc);
109
107
  outline: none;
110
108
  -webkit-appearance: none;
111
109
  appearance: none;
112
110
  position: relative;
113
111
  z-index: 1;
112
+ margin: 0;
114
113
  }
115
114
  .slider-wrapper input[type=range]::-webkit-slider-thumb {
116
115
  -webkit-appearance: none;
@@ -160,7 +159,7 @@
160
159
  }
161
160
  .slider-wrapper .slider-tooltip {
162
161
  position: absolute;
163
- top: 0;
162
+ top: -2.5rem;
164
163
  transform: translateX(-50%);
165
164
  padding: 0.375rem 0.625rem;
166
165
  background-color: var(--tooltip-bg, #000);
@@ -197,10 +196,11 @@
197
196
  }
198
197
  .slider-wrapper .value-display {
199
198
  text-align: center;
200
- margin-top: 0.5rem;
201
- font-size: 0.875rem;
199
+ margin-left: var(--spacing-base);
200
+ font-size: var(--font-md);
202
201
  color: var(--form-input-fg, #000);
203
202
  font-weight: 500;
203
+ line-height: 2rem;
204
+ min-width: 3rem;
205
+ flex-shrink: 0;
204
206
  }</style>
205
-
206
-
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { untrack } from 'svelte';
3
3
  import { uniqueId } from '../../helpers/unique-id.js';
4
- import FormField from '../form-field.svelte';
4
+ import FormField from '../form-field/form-field.svelte';
5
5
  import type { FormFieldSizeOptions } from '../../types/form.js';
6
6
 
7
7
  const id = uniqueId();
@@ -2,7 +2,7 @@
2
2
  import { untrack } from 'svelte';
3
3
  import { uniqueId } from '../../helpers/unique-id.js';
4
4
  import { animateShake, animateScaleIn } from '../../helpers/animations.js';
5
- import FormField from '../form-field.svelte';
5
+ import FormField from '../form-field/form-field.svelte';
6
6
  import CheckIcon from '../../icons/check-icon.svelte';
7
7
  import type { AllowedTextInputTypes, FormFieldSizeOptions } from '../../types/form.js';
8
8
 
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { uniqueId } from '../../helpers/unique-id.js';
3
- import FormField from '../form-field.svelte';
3
+ import FormField from '../form-field/form-field.svelte';
4
4
  import type { ComponentSize } from '../../types/size.js';
5
5
 
6
6
  const id = uniqueId();
@@ -29,20 +29,26 @@
29
29
  </script>
30
30
 
31
31
  <FormField {size} {label} {id} {required} {disabled}>
32
- <input
33
- {id}
34
- type="time"
35
- bind:value
36
- {disabled}
37
- {required}
38
- oninput={handleInput}
39
- aria-required={required}
40
- />
32
+ <div class="input" class:disabled>
33
+ <input
34
+ {id}
35
+ type="time"
36
+ bind:value
37
+ {disabled}
38
+ {required}
39
+ oninput={handleInput}
40
+ aria-required={required}
41
+ />
42
+ </div>
41
43
  </FormField>
42
44
 
43
- <style>input[type=time] {
45
+ <style>.input {
46
+ display: flex;
47
+ align-items: center;
48
+ justify-content: flex-start;
49
+ position: relative;
44
50
  width: 100%;
45
- padding: var(--spacing-sm) var(--spacing-base);
51
+ height: 100%;
46
52
  border-radius: var(--radius-md);
47
53
  border: var(--border-thin) solid var(--form-input-border);
48
54
  background-color: var(--form-input-bg);
@@ -51,13 +57,32 @@
51
57
  font-weight: 500;
52
58
  line-height: 2rem;
53
59
  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);
60
+ user-select: none;
61
+ white-space: nowrap;
54
62
  }
55
- input[type=time]:focus {
63
+ .input.disabled {
64
+ opacity: 0.5;
65
+ }
66
+ .input input {
67
+ background-color: transparent;
68
+ border: none;
69
+ line-height: 2rem;
70
+ font-size: var(--font-md);
71
+ width: 100%;
72
+ flex-grow: 1;
73
+ padding-left: var(--spacing-base);
74
+ padding-right: var(--spacing-base);
75
+ }
76
+ .input input:focus {
56
77
  outline: none;
57
- border-color: var(--form-input-border-focus, #3182ce);
58
78
  }
59
- input[type=time]:disabled {
60
- opacity: 0.5;
79
+ .input input:focus-visible {
80
+ outline: 2px solid var(--focus-ring, #007bff);
81
+ outline-offset: 2px;
82
+ }
83
+ .input input:disabled {
61
84
  cursor: not-allowed;
85
+ }
86
+ .input:focus-within {
87
+ border-color: var(--form-input-border-focus, #3182ce);
62
88
  }</style>
63
-
@@ -146,3 +146,5 @@ Due to limitations in the Storybook Svelte CSF parser with advanced Svelte 5 syn
146
146
 
147
147
 
148
148
 
149
+
150
+
@@ -457,6 +457,11 @@
457
457
  --body-fg: var(--base-color-fg);
458
458
  }
459
459
 
460
+ /*
461
+ * Global Styles
462
+ *
463
+ * Base styles using design tokens for consistency
464
+ */
460
465
  /**
461
466
  * Focus Ring Utilities
462
467
  *
@@ -468,7 +473,6 @@
468
473
  outline: none;
469
474
  transition: box-shadow var(--transition-fast) var(--ease-out);
470
475
  }
471
-
472
476
  .focus-ring:focus-visible, .focus-ring-lg:focus-visible, .focus-ring-sm:focus-visible {
473
477
  box-shadow: var(--focus-ring);
474
478
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sveltacular",
3
- "version": "1.0.4",
3
+ "version": "1.0.5",
4
4
  "description": "A Svelte component library",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",