@joyautomation/salt 0.1.0 → 0.1.1

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 CHANGED
@@ -1,58 +1,361 @@
1
- # create-svelte
1
+ # @joyautomation/salt
2
2
 
3
- Everything you need to build a Svelte library, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
3
+ A Svelte 5 component library with theming, forms, icons, and notifications.
4
4
 
5
- Read more about creating a library [in the docs](https://kit.svelte.dev/docs/packaging).
5
+ ## Installation
6
6
 
7
- ## Creating a project
7
+ ```bash
8
+ pnpm add @joyautomation/salt
9
+ ```
8
10
 
9
- If you're seeing this, you've probably already done this step. Congrats!
11
+ Import the styles in your root layout or app entry point:
10
12
 
11
- ```bash
12
- # create a new project in the current directory
13
- npm create svelte@latest
13
+ ```svelte
14
+ <script>
15
+ import '@joyautomation/salt/styles.scss'
16
+ </script>
17
+ ```
18
+
19
+ ## Theming
20
+
21
+ Salt includes a light/dark theme system using CSS custom variables. Apply the default theme class to `<body>` in your `app.html`:
14
22
 
15
- # create a new project in my-app
16
- npm create svelte@latest my-app
23
+ ```html
24
+ <body class="themeLight" data-sveltekit-preload-data="hover">
25
+ %sveltekit.body%
26
+ </body>
17
27
  ```
18
28
 
19
- ## Developing
29
+ ### ThemeButton
20
30
 
21
- Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
31
+ A toggle button that switches between light and dark mode via a SvelteKit form action.
22
32
 
23
- ```bash
24
- npm run dev
33
+ ```svelte
34
+ <script>
35
+ import { ThemeButton } from '@joyautomation/salt'
36
+ </script>
25
37
 
26
- # or start the server and open the app in a new browser tab
27
- npm run dev -- --open
38
+ <ThemeButton theme="themeLight" />
28
39
  ```
29
40
 
30
- Everything inside `src/lib` is part of your library, everything inside `src/routes` can be used as a showcase or preview app.
41
+ Wire up the server action in your `+layout.server.ts`:
31
42
 
32
- ## Building
43
+ ```ts
44
+ import { actions as saltActions } from '@joyautomation/salt'
45
+ import type { Actions } from '@sveltejs/kit'
33
46
 
34
- To build your library:
47
+ export const actions: Actions = {
48
+ setTheme: saltActions.setTheme
49
+ }
50
+ ```
35
51
 
36
- ```bash
37
- npm run package
52
+ ### ThemeSwitch
53
+
54
+ A three-option selector (System / Light / Dark):
55
+
56
+ ```svelte
57
+ <script>
58
+ import { ThemeSwitch } from '@joyautomation/salt'
59
+ </script>
60
+
61
+ <ThemeSwitch />
62
+ ```
63
+
64
+ ### Theme State
65
+
66
+ Programmatic access to the current theme:
67
+
68
+ ```ts
69
+ import { themeState, getEffectiveTheme } from '@joyautomation/salt'
70
+
71
+ // Read current theme
72
+ themeState.value // 'themeSystem' | 'themeLight' | 'themeDark'
73
+
74
+ // Get resolved theme (resolves 'themeSystem' to actual preference)
75
+ getEffectiveTheme() // 'themeLight' | 'themeDark'
76
+
77
+ // Set theme programmatically
78
+ themeState.set('themeDark')
79
+
80
+ // Initialize from server cookie
81
+ themeState.initialize(serverTheme)
82
+ ```
83
+
84
+ ## Toast Notifications
85
+
86
+ Drop-in toast notification system that auto-displays messages returned from SvelteKit form actions.
87
+
88
+ ### Setup
89
+
90
+ Add the `Toast` component to your root layout:
91
+
92
+ ```svelte
93
+ <script>
94
+ import { Toast } from '@joyautomation/salt'
95
+ </script>
96
+
97
+ <slot />
98
+ <Toast />
99
+ ```
100
+
101
+ ### Triggering Toasts from Form Actions
102
+
103
+ Return an object with `message` and `type` from any form action:
104
+
105
+ ```ts
106
+ // +page.server.ts
107
+ export const actions = {
108
+ save: async () => {
109
+ return {
110
+ message: 'Changes saved successfully',
111
+ type: 'success' // 'success' | 'error' | 'warning'
112
+ }
113
+ }
114
+ }
115
+ ```
116
+
117
+ ### Triggering Toasts Programmatically
118
+
119
+ ```ts
120
+ import { state } from '@joyautomation/salt'
121
+
122
+ state.addNotification({
123
+ message: 'Something happened',
124
+ type: 'warning'
125
+ })
126
+ ```
127
+
128
+ Notifications auto-dismiss after 5 seconds.
129
+
130
+ ## Forms
131
+
132
+ A JSON-driven form system using a 2D array layout where each inner array represents a row of fields.
133
+
134
+ ### Basic Usage
135
+
136
+ ```svelte
137
+ <script>
138
+ import { forms } from '@joyautomation/salt'
139
+ import type { FormInputs } from '@joyautomation/salt'
140
+
141
+ const inputs: FormInputs = $state([
142
+ // Row 1: two fields side by side
143
+ [
144
+ {
145
+ id: 'name',
146
+ name: 'name',
147
+ label: 'Name',
148
+ type: 'text',
149
+ value: '',
150
+ validations: [[(v) => v.trim().length === 0, 'Name is required']]
151
+ },
152
+ {
153
+ id: 'email',
154
+ name: 'email',
155
+ label: 'Email',
156
+ type: 'email',
157
+ value: '',
158
+ validations: [
159
+ [(v) => v.trim().length === 0, 'Email is required'],
160
+ [(v) => !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v), 'Enter a valid email']
161
+ ]
162
+ }
163
+ ],
164
+ // Row 2: select dropdown
165
+ [
166
+ {
167
+ id: 'role',
168
+ name: 'role',
169
+ label: 'Role',
170
+ type: 'select',
171
+ value: 'user',
172
+ validations: [],
173
+ options: [
174
+ { value: 'user', label: 'User' },
175
+ { value: 'admin', label: 'Admin' }
176
+ ]
177
+ }
178
+ ],
179
+ // Row 3: textarea
180
+ [
181
+ {
182
+ id: 'notes',
183
+ name: 'notes',
184
+ label: 'Notes',
185
+ type: 'textarea',
186
+ value: '',
187
+ validations: []
188
+ }
189
+ ]
190
+ ])
191
+ </script>
192
+
193
+ <forms.Form inputs={inputs} action="?/save" buttonText="Save" />
38
194
  ```
39
195
 
40
- To create a production version of your showcase app:
196
+ ### Field Types
197
+
198
+ Set `type` on an `InputProps` entry:
199
+
200
+ - **Text inputs**: `text`, `password`, `email`, `number`, `tel`, `datetime-local`, etc.
201
+ - **Textarea**: `textarea` — renders a resizable text area
202
+ - **Select**: `select` — renders a dropdown, requires `options: { value: string, label: string }[]`
203
+
204
+ ### Validation
205
+
206
+ Each field has a `validations` array of `[testFn, errorMessage]` tuples. The test function returns `true` when the value is **invalid**:
207
+
208
+ ```ts
209
+ validations: [
210
+ [(value) => value.length === 0, 'Required'],
211
+ [(value) => value.length < 3, 'Must be at least 3 characters'],
212
+ // Cross-field validation — second param gives access to all form inputs
213
+ [(value, inputs) => {
214
+ const other = inputs.flat().find(i => i.name === 'password')
215
+ return value !== other?.value
216
+ }, 'Passwords must match']
217
+ ]
218
+ ```
219
+
220
+ The submit button is disabled until all validations pass.
221
+
222
+ ### Form Component
223
+
224
+ ```svelte
225
+ <forms.Form
226
+ inputs={formInputs}
227
+ action="?/submit"
228
+ buttonText="Submit"
229
+ onsubmitstart={() => console.log('submitting...')}
230
+ onsubmitend={() => console.log('done')}
231
+ />
232
+ ```
233
+
234
+ Props:
235
+ - `inputs` — `FormInputs` (2D array of `InputProps`)
236
+ - `action` — SvelteKit form action path
237
+ - `buttonText` — submit button label (default: `'Submit'`)
238
+ - `onsubmitstart` / `onsubmitend` — optional callbacks
239
+
240
+ ### Individual Components
241
+
242
+ You can also use form components standalone:
243
+
244
+ ```svelte
245
+ <forms.Input
246
+ id="name"
247
+ name="name"
248
+ label="Name"
249
+ type="text"
250
+ bind:value={name}
251
+ validations={[[(v) => v.length === 0, 'Required']]}
252
+ touched={true}
253
+ />
254
+
255
+ <forms.Select
256
+ id="color"
257
+ name="color"
258
+ label="Color"
259
+ bind:value={color}
260
+ validations={[]}
261
+ options={[{ value: 'red', label: 'Red' }, { value: 'blue', label: 'Blue' }]}
262
+ />
263
+ ```
264
+
265
+ ### SearchableSelect
266
+
267
+ A filterable dropdown with keyboard navigation, independent of the form system:
268
+
269
+ ```svelte
270
+ <script>
271
+ import { forms } from '@joyautomation/salt'
272
+
273
+ let selected = $state('')
274
+ </script>
275
+
276
+ <forms.SearchableSelect
277
+ options={[
278
+ { value: 'svelte', label: 'Svelte', sublabel: 'Cybernetically enhanced' },
279
+ { value: 'react', label: 'React', sublabel: 'A JavaScript library' }
280
+ ]}
281
+ placeholder="Choose a framework..."
282
+ onSelect={(option) => (selected = option.value)}
283
+ disabled={false}
284
+ />
285
+ ```
286
+
287
+ ### Types
288
+
289
+ ```ts
290
+ import type { InputProps, FormInputs, FormInputsPartial } from '@joyautomation/salt'
291
+
292
+ // InputProps — single field configuration
293
+ // FormInputs — InputProps[][] (2D array, each inner array is a row)
294
+ // FormInputsPartial — partial version for merging/patching field configs
295
+ ```
296
+
297
+ ## Icons
298
+
299
+ All 648 [Heroicons](https://heroicons.com) (324 outline + 324 solid) are available as tree-shakeable Svelte components.
300
+
301
+ ### Usage
302
+
303
+ ```svelte
304
+ <script>
305
+ // Outline icons (default export)
306
+ import { AcademicCap, ArrowPath, Trash } from '@joyautomation/salt/icons'
307
+
308
+ // Style-specific imports
309
+ import { AcademicCap } from '@joyautomation/salt/icons/outline'
310
+ import { AcademicCap } from '@joyautomation/salt/icons/solid'
311
+ </script>
312
+
313
+ <!-- Default size is 1.5rem -->
314
+ <AcademicCap />
315
+
316
+ <!-- Custom size -->
317
+ <ArrowPath size="2rem" />
318
+ <Trash size="1rem" />
319
+ ```
320
+
321
+ Icons inherit `currentColor` so they match the surrounding text color.
322
+
323
+ ### Regenerating Icons
324
+
325
+ If you update the SVGs in `src/lib/components/icons/svg/`, regenerate with:
41
326
 
42
327
  ```bash
43
- npm run build
328
+ npm run generate:icons
44
329
  ```
45
330
 
46
- You can preview the production build with `npm run preview`.
331
+ ## Toggle
47
332
 
48
- > To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
333
+ A toggle switch component with hidden form input:
49
334
 
50
- ## Publishing
335
+ ```svelte
336
+ <script>
337
+ import { Toggle } from '@joyautomation/salt'
338
+ </script>
51
339
 
52
- Go into the `package.json` and give your package the desired name through the `"name"` option. Also consider adding a `"license"` field and point it to a `LICENSE` file which you can create from a template (one popular option is the [MIT license](https://opensource.org/license/mit/)).
340
+ <Toggle id="notifications" name="notifications" checked={true} />
341
+ ```
342
+
343
+ Props:
344
+ - `id` — input ID
345
+ - `name` — form field name
346
+ - `checked` — boolean state
347
+ - `buttonType` — `'button'` (default) or `'submit'`
348
+ - `selector` / `selectorName` — optional additional hidden input
53
349
 
54
- To publish your library to [npm](https://www.npmjs.com):
350
+ ## Development
55
351
 
56
352
  ```bash
57
- npm publish
353
+ pnpm install
354
+ pnpm dev # starts dev server on port 3014
355
+ pnpm run check # type check
356
+ pnpm run package # build the library
58
357
  ```
358
+
359
+ ## Publishing
360
+
361
+ Create a [GitHub release](https://github.com/joyautomation/salt/releases) with a tag like `v0.2.0`. The CI workflow will automatically set the version, build, and publish to npm.
@@ -1,9 +1,9 @@
1
1
  <script lang="ts">
2
- import { enhance } from '$app/forms';
3
- import Input from './Input.svelte';
4
- import Select from './Select.svelte';
5
- import type { FormInputs } from './types.js';
6
- import { slide } from 'svelte/transition';
2
+ import { enhance } from '$app/forms'
3
+ import Input from './Input.svelte'
4
+ import Select from './Select.svelte'
5
+ import type { FormInputs } from './types.js'
6
+ import { slide } from 'svelte/transition'
7
7
 
8
8
  const {
9
9
  inputs: propInputs,
@@ -12,29 +12,29 @@
12
12
  onsubmitstart,
13
13
  onsubmitend
14
14
  }: {
15
- inputs: FormInputs;
16
- action: string;
17
- buttonText?: string;
18
- onsubmitstart?: () => void;
19
- onsubmitend?: () => void;
20
- } = $props();
15
+ inputs: FormInputs
16
+ action: string
17
+ buttonText?: string
18
+ onsubmitstart?: () => void
19
+ onsubmitend?: () => void
20
+ } = $props()
21
21
 
22
- let inputs = $state(propInputs);
23
- let submitting = $state(false);
22
+ let inputs = $state(propInputs)
23
+ let submitting = $state(false)
24
24
 
25
25
  $effect(() => {
26
- inputs = propInputs;
27
- });
26
+ inputs = propInputs
27
+ })
28
28
 
29
29
  const valid = $derived(
30
30
  inputs.every((row) =>
31
31
  row.every((input) => {
32
- return input.validations.every(([validation, _message]) => {
33
- return !validation(input.value, inputs);
34
- });
32
+ return input.validations.every(([validation]) => {
33
+ return !validation(input.value, inputs)
34
+ })
35
35
  })
36
36
  )
37
- );
37
+ )
38
38
  </script>
39
39
 
40
40
  <form
@@ -42,13 +42,13 @@
42
42
  method="post"
43
43
  {action}
44
44
  use:enhance={() => {
45
- submitting = true;
46
- onsubmitstart?.();
45
+ submitting = true
46
+ onsubmitstart?.()
47
47
  return async ({ update }) => {
48
- submitting = false;
49
- onsubmitend?.();
50
- update({ reset: false });
51
- };
48
+ submitting = false
49
+ onsubmitend?.()
50
+ update({ reset: false })
51
+ }
52
52
  }}
53
53
  >
54
54
  {#each inputs as row}
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
- import { slide } from 'svelte/transition';
3
- import type { FormInputs, InputProps } from './types.js';
2
+ import { slide } from 'svelte/transition'
3
+ import type { FormInputs, InputProps } from './types.js'
4
4
 
5
5
  let {
6
6
  id,
@@ -13,12 +13,12 @@
13
13
  inputs,
14
14
  touched = false,
15
15
  onblur = () => {}
16
- }: InputProps & { inputs?: FormInputs; touched?: boolean; onblur?: () => void } = $props();
16
+ }: InputProps & { inputs?: FormInputs; touched?: boolean; onblur?: () => void } = $props()
17
17
  const validationResult = $derived(
18
- validations.find(([validation, _message]) => {
19
- return validation(value, inputs ?? []);
18
+ validations.find(([validation]) => {
19
+ return validation(value, inputs ?? [])
20
20
  })?.[1] || null
21
- );
21
+ )
22
22
  </script>
23
23
 
24
24
  <div class="input" class:input--invalid={touched && validationResult != null}>
@@ -1,121 +1,121 @@
1
1
  <script lang="ts">
2
- import ChevronDown from '../icons/outline/ChevronDown.svelte';
2
+ import ChevronDown from '../icons/outline/ChevronDown.svelte'
3
3
 
4
4
  type Option = {
5
- value: string;
6
- label: string;
7
- sublabel?: string;
8
- };
5
+ value: string
6
+ label: string
7
+ sublabel?: string
8
+ }
9
9
 
10
10
  type Props = {
11
- options: Option[];
12
- placeholder?: string;
13
- onSelect: (option: Option) => void;
14
- disabled?: boolean;
15
- };
11
+ options: Option[]
12
+ placeholder?: string
13
+ onSelect: (option: Option) => void
14
+ disabled?: boolean
15
+ }
16
16
 
17
- let { options, placeholder = 'Select...', onSelect, disabled = false }: Props = $props();
17
+ let { options, placeholder = 'Select...', onSelect, disabled = false }: Props = $props()
18
18
 
19
- let isOpen = $state(false);
20
- let searchQuery = $state('');
21
- let highlightedIndex = $state(-1);
22
- let inputRef: HTMLInputElement | undefined = $state();
23
- let dropdownRef: HTMLDivElement | undefined = $state();
19
+ let isOpen = $state(false)
20
+ let searchQuery = $state('')
21
+ let highlightedIndex = $state(-1)
22
+ let inputRef: HTMLInputElement | undefined = $state()
23
+ let dropdownRef: HTMLDivElement | undefined = $state()
24
24
 
25
25
  let filteredOptions = $derived(
26
26
  searchQuery.trim() === ''
27
27
  ? options
28
28
  : options.filter((opt) => {
29
- const query = searchQuery.toLowerCase();
29
+ const query = searchQuery.toLowerCase()
30
30
  return (
31
31
  opt.label.toLowerCase().includes(query) ||
32
32
  (opt.sublabel && opt.sublabel.toLowerCase().includes(query))
33
- );
33
+ )
34
34
  })
35
- );
35
+ )
36
36
 
37
37
  function openDropdown() {
38
- if (disabled) return;
39
- isOpen = true;
40
- highlightedIndex = -1;
41
- setTimeout(() => inputRef?.focus(), 0);
38
+ if (disabled) return
39
+ isOpen = true
40
+ highlightedIndex = -1
41
+ setTimeout(() => inputRef?.focus(), 0)
42
42
  }
43
43
 
44
44
  function closeDropdown() {
45
- isOpen = false;
46
- searchQuery = '';
47
- highlightedIndex = -1;
45
+ isOpen = false
46
+ searchQuery = ''
47
+ highlightedIndex = -1
48
48
  }
49
49
 
50
50
  function selectOption(option: Option) {
51
- onSelect(option);
52
- closeDropdown();
51
+ onSelect(option)
52
+ closeDropdown()
53
53
  }
54
54
 
55
55
  function handleKeydown(e: KeyboardEvent) {
56
56
  if (!isOpen) {
57
57
  if (e.key === 'Enter' || e.key === ' ' || e.key === 'ArrowDown') {
58
- e.preventDefault();
59
- openDropdown();
58
+ e.preventDefault()
59
+ openDropdown()
60
60
  }
61
- return;
61
+ return
62
62
  }
63
63
 
64
64
  switch (e.key) {
65
65
  case 'ArrowDown':
66
- e.preventDefault();
67
- highlightedIndex = Math.min(highlightedIndex + 1, filteredOptions.length - 1);
68
- scrollToHighlighted();
69
- break;
66
+ e.preventDefault()
67
+ highlightedIndex = Math.min(highlightedIndex + 1, filteredOptions.length - 1)
68
+ scrollToHighlighted()
69
+ break
70
70
  case 'ArrowUp':
71
- e.preventDefault();
72
- highlightedIndex = Math.max(highlightedIndex - 1, 0);
73
- scrollToHighlighted();
74
- break;
71
+ e.preventDefault()
72
+ highlightedIndex = Math.max(highlightedIndex - 1, 0)
73
+ scrollToHighlighted()
74
+ break
75
75
  case 'Enter':
76
- e.preventDefault();
76
+ e.preventDefault()
77
77
  if (highlightedIndex >= 0 && highlightedIndex < filteredOptions.length) {
78
- selectOption(filteredOptions[highlightedIndex]);
78
+ selectOption(filteredOptions[highlightedIndex])
79
79
  }
80
- break;
80
+ break
81
81
  case 'Escape':
82
- e.preventDefault();
83
- closeDropdown();
84
- break;
82
+ e.preventDefault()
83
+ closeDropdown()
84
+ break
85
85
  case 'Tab':
86
- closeDropdown();
87
- break;
86
+ closeDropdown()
87
+ break
88
88
  }
89
89
  }
90
90
 
91
91
  function scrollToHighlighted() {
92
- if (!dropdownRef || highlightedIndex < 0) return;
93
- const items = dropdownRef.querySelectorAll('.searchable-select__option');
94
- const item = items[highlightedIndex] as HTMLElement;
92
+ if (!dropdownRef || highlightedIndex < 0) return
93
+ const items = dropdownRef.querySelectorAll('.searchable-select__option')
94
+ const item = items[highlightedIndex] as HTMLElement
95
95
  if (item) {
96
- item.scrollIntoView({ block: 'nearest' });
96
+ item.scrollIntoView({ block: 'nearest' })
97
97
  }
98
98
  }
99
99
 
100
100
  function handleClickOutside(e: MouseEvent) {
101
- const target = e.target as Node;
101
+ const target = e.target as Node
102
102
  if (dropdownRef && !dropdownRef.contains(target)) {
103
- closeDropdown();
103
+ closeDropdown()
104
104
  }
105
105
  }
106
106
 
107
107
  $effect(() => {
108
108
  if (isOpen) {
109
- document.addEventListener('click', handleClickOutside, true);
110
- return () => document.removeEventListener('click', handleClickOutside, true);
109
+ document.addEventListener('click', handleClickOutside, true)
110
+ return () => document.removeEventListener('click', handleClickOutside, true)
111
111
  }
112
- });
112
+ })
113
113
 
114
114
  $effect(() => {
115
115
  if (searchQuery) {
116
- highlightedIndex = filteredOptions.length > 0 ? 0 : -1;
116
+ highlightedIndex = filteredOptions.length > 0 ? 0 : -1
117
117
  }
118
- });
118
+ })
119
119
  </script>
120
120
 
121
121
  <div class="searchable-select" class:searchable-select--disabled={disabled}>
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
- import { slide } from 'svelte/transition';
3
- import type { FormInputs, InputProps } from './types.js';
2
+ import { slide } from 'svelte/transition'
3
+ import type { FormInputs, InputProps } from './types.js'
4
4
 
5
5
  let {
6
6
  id,
@@ -10,12 +10,12 @@
10
10
  validations,
11
11
  inputs,
12
12
  options
13
- }: InputProps & { inputs?: FormInputs; options: { value: string; label: string }[] } = $props();
13
+ }: InputProps & { inputs?: FormInputs; options: { value: string; label: string }[] } = $props()
14
14
  const validationResult = $derived(
15
- validations.find(([validation, _message]) => {
16
- return validation(value, inputs ?? []);
15
+ validations.find(([validation]) => {
16
+ return validation(value, inputs ?? [])
17
17
  })?.[1] || null
18
- );
18
+ )
19
19
  </script>
20
20
 
21
21
  <div class="select-field" class:select-field--invalid={validationResult != null}>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@joyautomation/salt",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "scripts": {
5
5
  "generate:icons": "node scripts/generate-icons.js",
6
6
  "dev": "vite dev",
@@ -34,7 +34,7 @@
34
34
  ],
35
35
  "peerDependencies": {
36
36
  "@sveltejs/kit": "^2.5.26",
37
- "svelte": "5.0.0-next.244"
37
+ "svelte": "^5.0.0"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@eslint/js": "^9.25.1",
@@ -54,7 +54,7 @@
54
54
  "prettier": "^3.5.3",
55
55
  "prettier-plugin-svelte": "^3.3.3",
56
56
  "publint": "^0.3.12",
57
- "svelte": "5.28.2",
57
+ "svelte": "5.55.0",
58
58
  "svelte-check": "^4.1.6",
59
59
  "tslib": "^2.8.1",
60
60
  "typescript": "^5.8.3",