@functionalcms/svelte-components 5.4.0 → 5.4.3

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.
@@ -1,271 +1,269 @@
1
- <script lang="ts">
2
- import {
3
- type Field,
4
- type SelectField,
5
- type CollectionField,
6
- type TextareaField,
7
- type InputField,
8
- type FilePickerField,
9
- type PhoneInputField,
10
- type TimePickerField,
11
- SubmitResult,
12
- type ButtonConfig,
13
- type HiddenField,
14
-
15
- DatePickerField
16
-
17
- } from './form.js';
18
- import {
19
- Button,
20
- Label,
21
- Fileupload,
22
- Select,
23
- MultiSelect,
24
- Textarea,
25
- Toggle,
26
- Input,
27
- FloatingLabelInput,
28
- Timepicker,
29
- PhoneInput,
30
- Datepicker
31
- } from 'flowbite-svelte';
32
- import { fade } from 'svelte/transition';
33
-
34
- interface Props {
35
- action: string;
36
- class?: string;
37
- successMessage?: string;
38
- fields?: Array<Field>;
39
- onMessageSubmitted?: (result: SubmitResult) => void;
40
- buttons: Array<ButtonConfig>;
41
- }
42
-
43
- let {
44
- action,
45
- fields = [],
46
- class: css = '',
47
- successMessage,
48
- onMessageSubmitted,
49
- buttons = [
50
- {
51
- label: 'Submit',
52
- type: 'submit',
53
- action: undefined
54
- }
55
- ]
56
- }: Props = $props();
57
-
58
- let form: HTMLFormElement;
59
- let showMessage = $state(false);
60
- let isSendingMessage = $state(false);
61
- let loading = $state(false);
62
-
63
- const handleSubmit = async (event: Event) => {
64
- event.preventDefault();
65
- loading = true;
66
- try {
67
- if (!isSendingMessage) {
68
- isSendingMessage = true;
69
- const formData = new FormData(form);
70
- const response = await fetch(action, {
71
- method: 'POST',
72
- body: formData
73
- });
74
- showMessage = response.status === 200;
75
- onMessageSubmitted?.(showMessage ? SubmitResult.Success : SubmitResult.Error);
76
- setTimeout(() => {
77
- showMessage = false;
78
- }, 3000);
79
- }
80
- } catch (error) {
81
- console.error('Error submitting form:', error);
82
- } finally {
83
- isSendingMessage = false;
84
- }
85
- loading = false;
86
- };
87
- </script>
88
-
89
- {#snippet renderInput(field: InputField)}
90
- {#if field.isFloat}
91
- <FloatingLabelInput
92
- id={field.id}
93
- name={field.id}
94
- required={field.required}
95
- readonly={field.readonly}
96
- disabled={field.disabled}
97
- class={field.class}
98
- size={field.size == 'sm' ? 'small' : 'default'}
99
- type={field.type}
100
- variant={field.floatVariant}
101
- placeholder={field.placeholder}
102
- bind:value={field.value}
103
- >
104
- {field.label}
105
- </FloatingLabelInput>
106
- {:else}
107
- <Label for={field.id} class={field.labelClass}>{field.label}</Label>
108
- <Input
109
- id={field.id}
110
- name={field.id}
111
- required={field.required}
112
- readonly={field.readonly}
113
- disabled={field.disabled}
114
- class={field.class}
115
- size={field.size}
116
- type={field.type}
117
- placeholder={field.placeholder}
118
- bind:value={field.value}
119
- />
120
- {/if}
121
- {/snippet}
122
-
123
- {#snippet renderTextarea(field: TextareaField)}
124
- <Label for={field.id} class={field.labelClass}>{field.label}</Label>
125
- <Textarea
126
- id={field.id}
127
- name={field.id}
128
- required={field.required}
129
- readonly={field.readonly}
130
- disabled={field.disabled}
131
- class={field.class}
132
- rows={field.rows}
133
- cols={field.cols}
134
- placeholder={field.placeholder}
135
- bind:value={field.value}
136
- />
137
- {/snippet}
138
-
139
- {#snippet renderSelect(field: SelectField)}
140
- <Label class={field.labelClass}
141
- >{field.label}
142
- {#if field.type === 'single'}
143
- <Select
144
- id={field.id}
145
- name={field.id}
146
- required={field.required}
147
- readonly={field.readonly}
148
- disabled={field.disabled}
149
- class={field.class}
150
- items={field.options}
151
- bind:value={field.value}
152
- />
153
- {:else}
154
- <MultiSelect class={field.class} items={field.options} bind:value={field.value} />
155
- {/if}
156
- </Label>
157
- {/snippet}
158
-
159
- {#snippet renderCollection(field: CollectionField)}
160
- {#if field.type === 'checkbox'}{:else if field.type === 'radio'}{:else if field.type === 'toggle'}
161
- <Toggle
162
- id={field.id}
163
- name={field.id}
164
- required={field.required}
165
- readonly={field.readonly}
166
- disabled={field.disabled}
167
- class={field.class}
168
- checked={field.checked}
169
- >
170
- {field.label}
171
- </Toggle>
172
- {/if}
173
- {/snippet}
174
-
175
- {#snippet renderFile(field: FilePickerField)}
176
- <Label class={field.labelClass}>{field.label}</Label>
177
- <Fileupload
178
- id={field.id}
179
- name={field.id}
180
- required={field.required}
181
- readonly={field.readonly}
182
- disabled={field.disabled}
183
- class={field.class}
184
- multiple={field.multiple}
185
- />
186
- {/snippet}
187
-
188
- {#snippet renderPhoneInput(field: PhoneInputField)}
189
- <Label class={field.labelClass}>{field.label}</Label>
190
- <PhoneInput
191
- id={field.id}
192
- name={field.id}
193
- required={field.required}
194
- readonly={field.readonly}
195
- disabled={field.disabled}
196
- class={field.class}
197
- placeholder={field.placeholder}
198
- />
199
- {/snippet}
200
-
201
- {#snippet renderTimePicker(field: TimePickerField)}
202
- <Label for={field.id} class={field.labelClass}>{field.label}</Label>
203
- <Timepicker id={field.id} required={field.required} disabled={field.disabled} />
204
- {/snippet}
205
-
206
- {#snippet renderDatePicker(field: DatePickerField)}
207
- <Label for={field.id} class={field.labelClass}>{field.label}</Label>
208
- <Datepicker required={field.required} bind:value={field.value} />
209
- <input type="hidden" name={field.id} bind:value={field.value} />
210
- {/snippet}
211
-
212
- {#snippet renderHidden(field: HiddenField)}
213
- <input type="hidden" id={field.id} name={field.id} bind:value={field.value} />
214
- {/snippet}
215
-
216
- {#if showMessage}
217
- <div class="alert alert-success" transition:fade={{ duration: 2000 }}>
218
- {successMessage}
219
- </div>
220
- {:else}
221
- <form method="POST" {action} class={css} bind:this={form}>
222
- <fieldset>
223
- {#each fields as field}
224
- {#if field.typeOfField === 'input'}
225
- {@render renderInput(field)}
226
- {:else if field.typeOfField === 'textarea'}
227
- {@render renderTextarea(field)}
228
- {:else if field.typeOfField === 'select'}
229
- {@render renderSelect(field)}
230
- {:else if field.typeOfField === 'collection'}
231
- {@render renderCollection(field)}
232
- {:else if field.typeOfField === 'file'}
233
- {@render renderFile(field)}
234
- {:else if field.typeOfField === 'phoneInput'}
235
- {@render renderPhoneInput(field)}
236
- {:else if field.typeOfField === 'timepicker'}
237
- {@render renderTimePicker(field)}
238
- {:else if field.typeOfField === 'hidden'}
239
- {@render renderHidden(field)}
240
- {:else if field.typeOfField === 'date'}
241
- {@render renderDatePicker(field)}
242
- {/if}
243
- {/each}
244
-
245
- <div class="buttons">
246
- {#each buttons as button}
247
- {#if button.type === 'submit'}
248
- <Button
249
- type="button"
250
- class={button.class}
251
- color={button.color}
252
- onclick={handleSubmit}
253
- {loading}
254
- >
255
- {button.label}
256
- </Button>
257
- {:else}
258
- <Button
259
- type={button.type}
260
- class={button.class}
261
- color={button.color}
262
- onclick={button.action}
263
- >
264
- {button.label}
265
- </Button>
266
- {/if}
267
- {/each}
268
- </div>
269
- </fieldset>
270
- </form>
271
- {/if}
1
+ <script lang="ts">
2
+ import {
3
+ type Field,
4
+ type SelectField,
5
+ type CollectionField,
6
+ type TextareaField,
7
+ type InputField,
8
+ type FilePickerField,
9
+ type PhoneInputField,
10
+ type TimePickerField,
11
+ type ButtonConfig,
12
+ type HiddenField,
13
+ type DatePickerField,
14
+ SubmitResult,
15
+ } from './form.js';
16
+ import {
17
+ Button,
18
+ Label,
19
+ Fileupload,
20
+ Select,
21
+ MultiSelect,
22
+ Textarea,
23
+ Toggle,
24
+ Input,
25
+ FloatingLabelInput,
26
+ Timepicker,
27
+ PhoneInput,
28
+ Datepicker
29
+ } from 'flowbite-svelte';
30
+ import { fade } from 'svelte/transition';
31
+
32
+ interface Props {
33
+ action: string;
34
+ class?: string;
35
+ successMessage?: string;
36
+ fields?: Array<Field>;
37
+ onMessageSubmitted?: (result: SubmitResult) => void;
38
+ buttons: Array<ButtonConfig>;
39
+ }
40
+
41
+ let {
42
+ action,
43
+ fields = [],
44
+ class: css = '',
45
+ successMessage,
46
+ onMessageSubmitted,
47
+ buttons = [
48
+ {
49
+ label: 'Submit',
50
+ type: 'submit',
51
+ action: undefined
52
+ }
53
+ ]
54
+ }: Props = $props();
55
+
56
+ let form: HTMLFormElement;
57
+ let showMessage = $state(false);
58
+ let isSendingMessage = $state(false);
59
+ let loading = $state(false);
60
+
61
+ const handleSubmit = async (event: Event) => {
62
+ event.preventDefault();
63
+ loading = true;
64
+ try {
65
+ if (!isSendingMessage) {
66
+ isSendingMessage = true;
67
+ const formData = new FormData(form);
68
+ const response = await fetch(action, {
69
+ method: 'POST',
70
+ body: formData
71
+ });
72
+ showMessage = response.status === 200;
73
+ onMessageSubmitted?.(showMessage ? SubmitResult.Success : SubmitResult.Error);
74
+ setTimeout(() => {
75
+ showMessage = false;
76
+ }, 3000);
77
+ }
78
+ } catch (error) {
79
+ console.error('Error submitting form:', error);
80
+ } finally {
81
+ isSendingMessage = false;
82
+ }
83
+ loading = false;
84
+ };
85
+ </script>
86
+
87
+ {#snippet renderInput(field: InputField)}
88
+ {#if field.isFloat}
89
+ <FloatingLabelInput
90
+ id={field.id}
91
+ name={field.id}
92
+ required={field.required}
93
+ readonly={field.readonly}
94
+ disabled={field.disabled}
95
+ class={field.class}
96
+ size={field.size == 'sm' ? 'small' : 'default'}
97
+ type={field.type}
98
+ variant={field.floatVariant}
99
+ placeholder={field.placeholder}
100
+ bind:value={field.value}
101
+ >
102
+ {field.label}
103
+ </FloatingLabelInput>
104
+ {:else}
105
+ <Label for={field.id} class={field.labelClass}>{field.label}</Label>
106
+ <Input
107
+ id={field.id}
108
+ name={field.id}
109
+ required={field.required}
110
+ readonly={field.readonly}
111
+ disabled={field.disabled}
112
+ class={field.class}
113
+ size={field.size}
114
+ type={field.type}
115
+ placeholder={field.placeholder}
116
+ bind:value={field.value}
117
+ />
118
+ {/if}
119
+ {/snippet}
120
+
121
+ {#snippet renderTextarea(field: TextareaField)}
122
+ <Label for={field.id} class={field.labelClass}>{field.label}</Label>
123
+ <Textarea
124
+ id={field.id}
125
+ name={field.id}
126
+ required={field.required}
127
+ readonly={field.readonly}
128
+ disabled={field.disabled}
129
+ class={field.class}
130
+ rows={field.rows}
131
+ cols={field.cols}
132
+ placeholder={field.placeholder}
133
+ bind:value={field.value}
134
+ />
135
+ {/snippet}
136
+
137
+ {#snippet renderSelect(field: SelectField)}
138
+ <Label class={field.labelClass}
139
+ >{field.label}
140
+ {#if field.type === 'single'}
141
+ <Select
142
+ id={field.id}
143
+ name={field.id}
144
+ required={field.required}
145
+ readonly={field.readonly}
146
+ disabled={field.disabled}
147
+ class={field.class}
148
+ items={field.options}
149
+ bind:value={field.value}
150
+ />
151
+ {:else}
152
+ <MultiSelect class={field.class} items={field.options} bind:value={field.value} />
153
+ {/if}
154
+ </Label>
155
+ {/snippet}
156
+
157
+ {#snippet renderCollection(field: CollectionField)}
158
+ {#if field.type === 'checkbox'}{:else if field.type === 'radio'}{:else if field.type === 'toggle'}
159
+ <Toggle
160
+ id={field.id}
161
+ name={field.id}
162
+ required={field.required}
163
+ readonly={field.readonly}
164
+ disabled={field.disabled}
165
+ class={field.class}
166
+ checked={field.checked}
167
+ >
168
+ {field.label}
169
+ </Toggle>
170
+ {/if}
171
+ {/snippet}
172
+
173
+ {#snippet renderFile(field: FilePickerField)}
174
+ <Label class={field.labelClass}>{field.label}</Label>
175
+ <Fileupload
176
+ id={field.id}
177
+ name={field.id}
178
+ required={field.required}
179
+ readonly={field.readonly}
180
+ disabled={field.disabled}
181
+ class={field.class}
182
+ multiple={field.multiple}
183
+ />
184
+ {/snippet}
185
+
186
+ {#snippet renderPhoneInput(field: PhoneInputField)}
187
+ <Label class={field.labelClass}>{field.label}</Label>
188
+ <PhoneInput
189
+ id={field.id}
190
+ name={field.id}
191
+ required={field.required}
192
+ readonly={field.readonly}
193
+ disabled={field.disabled}
194
+ class={field.class}
195
+ placeholder={field.placeholder}
196
+ />
197
+ {/snippet}
198
+
199
+ {#snippet renderTimePicker(field: TimePickerField)}
200
+ <Label for={field.id} class={field.labelClass}>{field.label}</Label>
201
+ <Timepicker id={field.id} required={field.required} disabled={field.disabled} />
202
+ {/snippet}
203
+
204
+ {#snippet renderDatePicker(field: DatePickerField)}
205
+ <Label for={field.id} class={field.labelClass}>{field.label}</Label>
206
+ <Datepicker required={field.required} bind:value={field.value} />
207
+ <input type="hidden" name={field.id} bind:value={field.value} />
208
+ {/snippet}
209
+
210
+ {#snippet renderHidden(field: HiddenField)}
211
+ <input type="hidden" id={field.id} name={field.id} bind:value={field.value} />
212
+ {/snippet}
213
+
214
+ {#if showMessage}
215
+ <div class="alert alert-success" transition:fade={{ duration: 2000 }}>
216
+ {successMessage}
217
+ </div>
218
+ {:else}
219
+ <form method="POST" {action} class={css} bind:this={form}>
220
+ <fieldset>
221
+ {#each fields as field}
222
+ {#if field.typeOfField === 'input'}
223
+ {@render renderInput(field)}
224
+ {:else if field.typeOfField === 'textarea'}
225
+ {@render renderTextarea(field)}
226
+ {:else if field.typeOfField === 'select'}
227
+ {@render renderSelect(field)}
228
+ {:else if field.typeOfField === 'collection'}
229
+ {@render renderCollection(field)}
230
+ {:else if field.typeOfField === 'file'}
231
+ {@render renderFile(field)}
232
+ {:else if field.typeOfField === 'phoneInput'}
233
+ {@render renderPhoneInput(field)}
234
+ {:else if field.typeOfField === 'timepicker'}
235
+ {@render renderTimePicker(field)}
236
+ {:else if field.typeOfField === 'hidden'}
237
+ {@render renderHidden(field)}
238
+ {:else if field.typeOfField === 'date'}
239
+ {@render renderDatePicker(field)}
240
+ {/if}
241
+ {/each}
242
+
243
+ <div class="buttons">
244
+ {#each buttons as button}
245
+ {#if button.type === 'submit'}
246
+ <Button
247
+ type="button"
248
+ class={button.class}
249
+ color={button.color}
250
+ onclick={handleSubmit}
251
+ {loading}
252
+ >
253
+ {button.label}
254
+ </Button>
255
+ {:else}
256
+ <Button
257
+ type={button.type}
258
+ class={button.class}
259
+ color={button.color}
260
+ onclick={button.action}
261
+ >
262
+ {button.label}
263
+ </Button>
264
+ {/if}
265
+ {/each}
266
+ </div>
267
+ </fieldset>
268
+ </form>
269
+ {/if}
@@ -1,4 +1,4 @@
1
- import { type Field, SubmitResult, type ButtonConfig } from './form.js';
1
+ import { type Field, type ButtonConfig, SubmitResult } from './form.js';
2
2
  interface Props {
3
3
  action: string;
4
4
  class?: string;
@@ -1,20 +1,20 @@
1
- <script lang="ts">
2
- interface Props {
3
- fonts: Array<{ name: string; values: string }>;
4
- }
5
-
6
- let { fonts = [] }: Partial<Props> = $props();
7
-
8
- const fontQuery = $derived(() =>
9
- fonts.map((font) => `family=${font.name}:${font.values}`).join('&')
10
- );
11
- </script>
12
-
13
- <svelte:head>
14
- <link rel="preconnect" href="https://fonts.googleapis.com" />
15
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" />
16
- <link
17
- rel="stylesheet"
18
- href={`https://fonts.googleapis.com/css2?${fontQuery}&display=swap`}
19
- />
20
- </svelte:head>
1
+ <script lang="ts">
2
+ interface Props {
3
+ fonts: Array<{ name: string; values: string }>;
4
+ }
5
+
6
+ let { fonts = [] }: Partial<Props> = $props();
7
+
8
+ const fontQuery = $derived(() =>
9
+ fonts.map((font) => `family=${font.name}:${font.values}`).join('&')
10
+ );
11
+ </script>
12
+
13
+ <svelte:head>
14
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
15
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="" />
16
+ <link
17
+ rel="stylesheet"
18
+ href={`https://fonts.googleapis.com/css2?${fontQuery}&display=swap`}
19
+ />
20
+ </svelte:head>
@@ -1,28 +1,28 @@
1
- <script lang="ts">
2
- interface MetaProps {
3
- title: string;
4
- description: string;
5
- keywords: string;
6
- robots: string;
7
- charset: string;
8
- viewport: string;
9
- }
10
-
11
- let {
12
- title = '',
13
- description = '',
14
- keywords = '',
15
- robots = 'index, follow',
16
- charset = 'UTF-8',
17
- viewport = 'width=device-width, initial-scale=1.0'
18
- }: Partial<MetaProps> = $props();
19
- </script>
20
-
21
- <svelte:head>
22
- <title>{title}</title>
23
- <meta name="description" content={description} />
24
- <meta name="keywords" content={keywords} />
25
- <meta name="robots" content={robots} />
26
- <meta name="viewport" content={viewport} />
27
- <meta {charset} />
28
- </svelte:head>
1
+ <script lang="ts">
2
+ interface MetaProps {
3
+ title: string;
4
+ description: string;
5
+ keywords: string;
6
+ robots: string;
7
+ charset: string;
8
+ viewport: string;
9
+ }
10
+
11
+ let {
12
+ title = '',
13
+ description = '',
14
+ keywords = '',
15
+ robots = 'index, follow',
16
+ charset = 'UTF-8',
17
+ viewport = 'width=device-width, initial-scale=1.0'
18
+ }: Partial<MetaProps> = $props();
19
+ </script>
20
+
21
+ <svelte:head>
22
+ <title>{title}</title>
23
+ <meta name="description" content={description} />
24
+ <meta name="keywords" content={keywords} />
25
+ <meta name="robots" content={robots} />
26
+ <meta name="viewport" content={viewport} />
27
+ <meta {charset} />
28
+ </svelte:head>