sveltacular 1.0.1 → 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 +63 -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 +3 -0
- package/dist/generic/chip/chip.svelte +3 -0
- package/dist/navigation/context-menu/README.md +3 -0
- package/dist/navigation/context-menu/context-menu-divider.svelte +3 -0
- package/dist/sveltacular.css +522 -0
- package/package.json +11 -5
package/README.md
CHANGED
|
@@ -12,6 +12,28 @@ npm i sveltacular
|
|
|
12
12
|
|
|
13
13
|
## Quick Start
|
|
14
14
|
|
|
15
|
+
### 1. Import the default stylesheet (once, in your app root)
|
|
16
|
+
|
|
17
|
+
**For SvelteKit**, add this to `src/routes/+layout.svelte`:
|
|
18
|
+
|
|
19
|
+
```svelte
|
|
20
|
+
<script lang="ts">
|
|
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
|
+
|
|
15
37
|
```svelte
|
|
16
38
|
<script lang="ts">
|
|
17
39
|
import { Button } from 'sveltacular';
|
|
@@ -20,9 +42,12 @@ npm i sveltacular
|
|
|
20
42
|
<Button variant="primary" label="Hello World" />
|
|
21
43
|
```
|
|
22
44
|
|
|
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).
|
|
46
|
+
|
|
23
47
|
## Component Catalog
|
|
24
48
|
|
|
25
49
|
### Forms
|
|
50
|
+
|
|
26
51
|
- **Button** - Multiple variants (primary, secondary, positive, danger, outline)
|
|
27
52
|
- **TextBox** - Text input with validation and formatting options
|
|
28
53
|
- **NumberBox** - Number input with min/max/decimals
|
|
@@ -38,6 +63,7 @@ npm i sveltacular
|
|
|
38
63
|
- **Form** - Form container with validation
|
|
39
64
|
|
|
40
65
|
### Generic Components
|
|
66
|
+
|
|
41
67
|
- **Card** - Card container
|
|
42
68
|
- **Pill** - Badge/pill component
|
|
43
69
|
- **Badge** - Notification badge
|
|
@@ -52,6 +78,7 @@ npm i sveltacular
|
|
|
52
78
|
- **List** - Styled list component
|
|
53
79
|
|
|
54
80
|
### Navigation
|
|
81
|
+
|
|
55
82
|
- **AppBar** - Application bar
|
|
56
83
|
- **SideBar** - Side navigation
|
|
57
84
|
- **Breadcrumbs** - Breadcrumb navigation
|
|
@@ -62,16 +89,19 @@ npm i sveltacular
|
|
|
62
89
|
- **Drawer** - Slide-out drawer
|
|
63
90
|
|
|
64
91
|
### Modals
|
|
92
|
+
|
|
65
93
|
- **Modal** - Generic modal dialog
|
|
66
94
|
- **Alert** - Alert dialog
|
|
67
95
|
- **Confirm** - Confirmation dialog
|
|
68
96
|
- **Prompt** - Input prompt dialog
|
|
69
97
|
|
|
70
98
|
### Tables
|
|
99
|
+
|
|
71
100
|
- **Table** - Table component with header/body/footer
|
|
72
101
|
- **DataGrid** - Advanced data grid
|
|
73
102
|
|
|
74
103
|
### Typography
|
|
104
|
+
|
|
75
105
|
- **Headline** - Heading component
|
|
76
106
|
- **Subtitle** - Subtitle component
|
|
77
107
|
- **Text** - Text component
|
|
@@ -79,6 +109,7 @@ npm i sveltacular
|
|
|
79
109
|
- **CodeBlock** - Code block
|
|
80
110
|
|
|
81
111
|
### Layout
|
|
112
|
+
|
|
82
113
|
- **FlexRow** / **FlexCol** - Flexbox layout
|
|
83
114
|
- **Grid** - Grid layout
|
|
84
115
|
|
|
@@ -102,11 +133,19 @@ import { CheckBox, CheckBoxGroup } from 'sveltacular/forms/check-box';
|
|
|
102
133
|
|
|
103
134
|
## Theming
|
|
104
135
|
|
|
105
|
-
Sveltacular uses CSS variables for theming.
|
|
136
|
+
Sveltacular uses CSS variables for theming. When you import `sveltacular/styles.css`, all default CSS variables are included. You can override any of these variables to customize the appearance of components.
|
|
106
137
|
|
|
107
|
-
|
|
138
|
+
See [THEMING.md](./THEMING.md) for a complete list of available CSS variables.
|
|
139
|
+
|
|
140
|
+
### Customizing the Theme
|
|
141
|
+
|
|
142
|
+
Override CSS variables in your own stylesheet (after importing the default styles):
|
|
108
143
|
|
|
109
144
|
```css
|
|
145
|
+
/* Import default styles first */
|
|
146
|
+
@import 'sveltacular/styles.css';
|
|
147
|
+
|
|
148
|
+
/* Then override variables as needed */
|
|
110
149
|
:root {
|
|
111
150
|
--button-primary-bg: #1e88e5;
|
|
112
151
|
--form-input-border: #e0e0e0;
|
|
@@ -114,6 +153,27 @@ Example:
|
|
|
114
153
|
}
|
|
115
154
|
```
|
|
116
155
|
|
|
156
|
+
Or in a Svelte component:
|
|
157
|
+
|
|
158
|
+
```svelte
|
|
159
|
+
<script>
|
|
160
|
+
import 'sveltacular/styles.css';
|
|
161
|
+
import { Button } from 'sveltacular';
|
|
162
|
+
</script>
|
|
163
|
+
|
|
164
|
+
<style>
|
|
165
|
+
:global(:root) {
|
|
166
|
+
--button-primary-bg: #1e88e5;
|
|
167
|
+
--form-input-border: #e0e0e0;
|
|
168
|
+
--base-color-bg: #ffffff;
|
|
169
|
+
}
|
|
170
|
+
</style>
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Providing Your Own Theme
|
|
174
|
+
|
|
175
|
+
If you prefer not to use the default stylesheet, you can define all CSS variables yourself. See [THEMING.md](./THEMING.md) for the complete list of required variables.
|
|
176
|
+
|
|
117
177
|
## Form Validation
|
|
118
178
|
|
|
119
179
|
Sveltacular includes a validation system:
|
|
@@ -136,6 +196,7 @@ if (!result.isValid) {
|
|
|
136
196
|
## Accessibility
|
|
137
197
|
|
|
138
198
|
Sveltacular components include:
|
|
199
|
+
|
|
139
200
|
- ARIA attributes for screen readers
|
|
140
201
|
- Keyboard navigation support
|
|
141
202
|
- Focus management utilities
|
|
@@ -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}>
|