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.
- package/README.md +21 -2
- package/dist/forms/base-input-wrapper.svelte +0 -2
- package/dist/forms/check-box/check-box-group.svelte +2 -2
- package/dist/forms/date-box/date-box.svelte +15 -4
- package/dist/forms/file-box/file-box.svelte +1 -1
- package/dist/forms/form-field/form-field.svelte +86 -0
- package/dist/forms/form-field/form-field.svelte.d.ts +16 -0
- package/dist/forms/form-footer.svelte +7 -5
- package/dist/forms/form-label/form-label.svelte +30 -0
- package/dist/forms/form-label/form-label.svelte.d.ts +9 -0
- package/dist/forms/form-row/form-row.svelte +29 -0
- package/dist/forms/form-row/form-row.svelte.d.ts +7 -0
- package/dist/forms/form-section/form-section.svelte +36 -0
- package/dist/forms/form-section/form-section.svelte.d.ts +10 -0
- package/dist/forms/index.d.ts +4 -4
- package/dist/forms/index.js +4 -4
- package/dist/forms/info-box/info-box.svelte +1 -1
- package/dist/forms/list-box/list-box.svelte +3 -3
- package/dist/forms/money-box/money-box.svelte +27 -17
- package/dist/forms/number-box/number-box.svelte +5 -2
- package/dist/forms/phone-box/phone-box.svelte +25 -13
- package/dist/forms/radio-group/radio-group.svelte +1 -1
- package/dist/forms/slider/slider.svelte +15 -15
- package/dist/forms/text-area/text-area.svelte +1 -1
- package/dist/forms/text-box/text-box.svelte +1 -1
- package/dist/forms/time-box/time-box.svelte +42 -17
- package/dist/generic/avatar/avatar.svelte +2 -0
- package/dist/generic/chip/chip.svelte +2 -0
- package/dist/navigation/context-menu/README.md +2 -0
- package/dist/navigation/context-menu/context-menu-divider.svelte +2 -0
- package/dist/sveltacular.css +5 -1
- 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
|
-
|
|
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
|
|
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,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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
10
|
-
{@render children?.()}
|
|
11
|
-
</FlexRow>
|
|
8
|
+
{@render children?.()}
|
|
12
9
|
</div>
|
|
13
10
|
|
|
14
11
|
<style>
|
|
15
12
|
div {
|
|
16
|
-
|
|
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,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;
|
package/dist/forms/index.d.ts
CHANGED
|
@@ -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';
|
package/dist/forms/index.js
CHANGED
|
@@ -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
|
-
|
|
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 =
|
|
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 (
|
|
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 (
|
|
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 =
|
|
201
|
-
else value = dollarValue +
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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-
|
|
201
|
-
font-size:
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
|
45
|
+
<style>.input {
|
|
46
|
+
display: flex;
|
|
47
|
+
align-items: center;
|
|
48
|
+
justify-content: flex-start;
|
|
49
|
+
position: relative;
|
|
44
50
|
width: 100%;
|
|
45
|
-
|
|
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
|
|
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
|
|
60
|
-
|
|
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
|
-
|
package/dist/sveltacular.css
CHANGED
|
@@ -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
|
}
|