├── .github
└── workflows
│ └── test.yml
├── .gitignore
├── .npmrc
├── .prettierignore
├── .prettierrc
├── .vscode
└── settings.json
├── LICENSE
├── PUBLISH_STEPS.md
├── README.md
├── package.json
├── playwright.config.ts
├── src
├── app.d.ts
├── app.html
├── lib
│ ├── chromeAutofill.ts
│ ├── components
│ │ ├── Hint.svelte
│ │ └── HintGroup.svelte
│ ├── index.ts
│ ├── models
│ │ ├── form.ts
│ │ ├── formControl.ts
│ │ ├── formControlElement.ts
│ │ ├── formProperties.ts
│ │ └── validator.ts
│ ├── stores
│ │ └── formReferences.ts
│ ├── useForm.ts
│ ├── validators.ts
│ └── validatorsAction.ts
└── routes
│ ├── +layout.svelte
│ ├── +page.svelte
│ ├── NavigationBar.svelte
│ ├── examples
│ ├── ReuseFormDialog.svelte
│ ├── async
│ │ └── +page.svelte
│ ├── custom-validator
│ │ └── +page.svelte
│ ├── dynamic-form
│ │ └── +page.svelte
│ ├── dynamic-typing
│ │ └── +page.svelte
│ ├── dynamic-validator
│ │ └── +page.svelte
│ ├── email-validator
│ │ └── +page.svelte
│ ├── ignore-attribute
│ │ └── +page.svelte
│ ├── login
│ │ └── +page.svelte
│ ├── multi-forms
│ │ └── +page.svelte
│ ├── reset-form
│ │ └── +page.svelte
│ └── reuse-form
│ │ └── +page.svelte
│ └── test
│ └── hint
│ └── +page.svelte
├── static
└── favicon.png
├── svelte-use-form.svg
├── svelte.config.js
├── tests
├── Hint.spec.ts
├── dynamic-validators.spec.ts
├── email-validator.spec.ts
├── ignore.spec.ts
├── login.spec.ts
└── multiforms.spec.ts
├── tsconfig.json
├── vite.config.ts
└── yarn.lock
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | on:
2 | push:
3 | branches:
4 | - master
5 |
6 | jobs:
7 | e2e:
8 | name: Run end-to-end tests
9 | runs-on: ubuntu-latest
10 | steps:
11 | - uses: actions/checkout@v3
12 | - uses: actions/setup-node@v3
13 | - name: Install dependencies
14 | run: yarn install --frozen-lockfile
15 | - name: Build App
16 | run: yarn build
17 | - name: Install playwright browsers
18 | run: npx playwright install --with-deps
19 | - name: Run tests
20 | run: npx playwright test --reporter github
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /.svelte-kit
4 | /package
5 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | engine-strict=true
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /build
4 | /.svelte-kit
5 | /package
6 | .env
7 | .env.*
8 | !.env.example
9 |
10 | # Ignore files for PNPM, NPM and YARN
11 | pnpm-lock.yaml
12 | package-lock.json
13 | yarn.lock
14 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "plugins": ["prettier-plugin-svelte"],
3 | "pluginSearchDirs": ["."],
4 | "overrides": [{ "files": "*.svelte", "options": { "parser": "svelte" } }]
5 | }
6 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "workbench.editor.labelFormat": "short"
3 | }
4 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Noah Versace Salvi
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/PUBLISH_STEPS.md:
--------------------------------------------------------------------------------
1 | # Workflow to publish to npm
2 |
3 | - Run `npm version [patch/minor/major]`. This will create a commit and bump the version in `package.json`.
4 | - Run `yarn package` to bulid the project as a package.
5 | - Run `cd package` and then `npm publish` to publish it to the registry.
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | svelte-use-form
5 |
6 |
7 | A Svelte form library that enables you to create complicated forms with minimal effort. As for Svelte, the focus is **DX** 💻✨
8 |
9 | ```bash
10 | npm i -D svelte-use-form
11 | ```
12 |
13 | 
14 | 
15 | [](https://paypal.me/noahvsalvi)
16 |
17 | **Features:**
18 |
19 | - Minimalistic - Don't write more than necessary.
20 | - No new components, bindings or callbacks required
21 | - Validators included and custom validator support
22 | - Automagically binds to inputs
23 |
24 | # Usage
25 |
26 | It's pretty self-explanatory… check out the examples below 😉
27 |
28 | Just make sure to prefix the form with `$`, when accessing its state.
29 |
30 | **Minimal Example** [REPL](https://svelte.dev/repl/faf5a9ab763640ed830028c970421f72?version=3.35.0)
31 |
32 | ```svelte
33 |
38 |
39 |
47 | ```
48 |
49 | or you could also print the error message like this:
50 |
51 | ```svelte
52 | ...
53 | {#if $form.title?.touched && $form.title?.errors.minLength}
54 | The title requires at least {$form.title.errors.minLength} characters.
55 | {/if}
56 | ```
57 |
58 | ## More Examples
59 |
60 |
61 |
62 |
63 | Login Example
64 |
65 | REPL
66 |
67 |
68 |
69 |
70 | ```svelte
71 |
83 |
84 |
98 | ```
99 |
100 |
101 |
102 |
103 |
104 |
105 | Registration Example
106 |
107 | REPL
108 |
109 |
110 |
111 |
112 | ```svelte
113 |
141 |
142 |
143 |
184 |
185 | {JSON.stringify($form, null, 1)}
186 |
187 |
188 |
189 |
206 | ```
207 |
208 |
209 |
210 | **[Edge Cases REPL](https://svelte.dev/repl/d4fc021f688d4ad0b3ceb9a1c44c9be9?version=3.34.0)**
211 |
212 | # API
213 |
214 | ## `useForm(properties: FormProperties?, formName?: string)`
215 |
216 | useForm() returns a svelte `store` (Observable) that is also an `action`. (That's what I call [svelte](https://www.dictionary.com/browse/svelte) 😆)
217 |
218 | ### Why specify `FormProperties`?
219 |
220 | Providing the names of the properties as arguments allows us to initialize all form controls in the form before the site is actually rendered. Thus you won't need to null-check them when accessing them.
221 |
222 | ```svelte
223 | const form = useForm({ firstName: {} });
224 | $form.firstName // Works as expected
225 | $form?.lastName // lastName would be null on page load
226 | ```
227 |
228 | ### Why specify `formName`?
229 |
230 | By providing a name you'll have the ability to specifically reference a form from a `Hint` component instead of inferring it from context.
231 |
232 | `useForm({}, "form-1")` --> ``.
233 |
234 | This allows you to use multiple `useForm` instances in a single component.
235 |
236 | ### `$form`
237 |
238 | Subscribe to the form with `$`-prefix to access the state of the form. It returns a `Form` instance.
239 |
240 | ### `Form`
241 |
242 | **Remark**: In reality the "Form" is an union of multiple types and its self.
243 |
244 | ```typescript
245 | class Form {
246 | valid: boolean;
247 | touched: boolean;
248 | values: {
249 | [controlName: string]: string;
250 | };
251 | reset(): void;
252 | [controlName: string]: FormControl | undefined;
253 | }
254 | ```
255 |
256 | ### `FormProperties` (Optional)
257 |
258 | ```typescript
259 | export type FormProperties = {
260 | [key: string]: {
261 | /** Initial value of the form control */
262 | initial?: string;
263 | /** The validators that will be checked when the input changes */
264 | validators?: Validator[];
265 | /**
266 | * The map through which validation errors will be mapped.
267 | * You can either pass a string or a function returning a new error value
268 | *
269 | * **Think of it as a translation map. 😆**
270 | */
271 | errorMap?: ErrorMap;
272 | };
273 | };
274 | ```
275 |
276 | ## `FormControl`
277 |
278 | A FormControl represents an input of the form. (input, textarea, radio, select...)
279 |
280 | **Important**:
281 | Every control in the form will be accessible through `$form` directly via the `name` attribute.
282 |
283 | e.g. ` ` --> `$form.email`
284 |
285 | ```typescript
286 | class FormControl {
287 | value: string;
288 | touched: boolean;
289 | validators: Validator[];
290 | /** Does the FormControl pass all given validators? */
291 | valid: boolean;
292 | /** The initial value of the FormControl. */
293 | initial: string;
294 | /** The DOM elements representing this control*/
295 | elements: FormControlElement[];
296 |
297 | /** Returns an object containing possible validation errors */
298 | errors: ValidationErrors;
299 | /**
300 | * Contains a map of values, that will be shown
301 | * in place of the original validation error.
302 | */
303 | errorMap: ErrorMap;
304 |
305 | error(errors: ValidationErrors): void;
306 | /** Change the value and the value of all HTML-Elements associated with this control */
307 | change(value: any): void;
308 | /** Validate the FormControl by querying through the given validators. */
309 | validate(): boolean;
310 | /** Reset the form control value to its initial value or `{ value }` and untouch it */
311 | reset({ value }?: { value?: string | null }): void;
312 | }
313 | ```
314 |
315 | ## `use:validators` (Action)
316 |
317 | Takes in the validators that should be used on the form control.
318 | e.g.
319 |
320 | ```svelte
321 |
322 | ```
323 |
324 | ## ` `
325 |
326 | Helper component for displaying information based on errors in an input.
327 |
328 | **Properties:**
329 |
330 | - `for="name_of_input"` - Name of concerning input
331 | - `on="error"` - The error which should trigger it
332 |
333 | **Optional attributes:**
334 |
335 | - `form="form-name"` - Name of the form. Defaults to form from context.
336 | - `hideWhen="different_error"` - Hides the hint if the error is active
337 | - `hideWhenRequired` - Shortcut for hideWhen="required"
338 | - `showWhenUntouched` - Display Hint even if the field hasn't been touched yet
339 | - `let:value` - Returns the value of the error
340 |
341 | ## ` `
342 |
343 | You can omit the Hint `for` property when wrapping it with a `HintGroup`.
344 |
345 | **Properties:**
346 |
347 | - `for="name_of_input"`
348 |
349 | **Optional Properties:**
350 |
351 | - `form="form-name"` - Name of the form. Defaults to form from context.
352 |
353 | ## Ignore a form control
354 |
355 | You can ignore a form control element by adding the `data-suf-ignore` attribute to it.
356 |
357 | ```svelte
358 |
359 | ```
360 |
361 | This will prevent it from being added to the form elements.
362 | And thus it won't be validated or observed for changes.
363 |
364 | ## Validators
365 |
366 | - `required`
367 | - `minLength(n)`
368 | - `maxLength(n)`
369 | - `number`
370 | - `email`
371 | - `emailWithTld`
372 | - `url`
373 | - `pattern(regExp)`
374 |
375 | ### Custom Validator
376 |
377 | A validator needs to be a function that returns null if valid else an object with the key being the name of the error. The value of the object will be accessible through the error. e.g. $form.title.errors.name_of_error -> 'info'.
378 |
379 | ```typescript
380 | const passwordMatch: Validator = (value, form) => {
381 | return value === form.password?.value
382 | ? null
383 | : { passwordMatch: "Passwords are not matching" };
384 | };
385 | ```
386 |
387 | ```
388 | ... use:validators={[passwordMatch]}
389 | or
390 | ... passwordConfirmation: { validators: [passwordMatch] } }
391 |
392 | ... $form.title.errors.passwordMatch
393 | ```
394 |
395 | An example with [validator.js](https://www.npmjs.com/package/validator) [REPL](https://svelte.dev/repl/21fc7637645d4917994ad4140b54b871?version=3.35.0)
396 |
397 | # Remarks
398 |
399 | ## Chrome Autofill Solution
400 |
401 | When Chrome autofills the form on page load, it will always register all inputs as valid. After clicking anywhere on the site, pressing a key or pressing the submit button it will then reevaluate all fields and update the state of the form.
402 |
403 | This solution was needed due to Chrome's way of autofilling forms without actually setting the value of the inputs until the page gets a click or key event.
404 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "svelte-use-form",
3 | "version": "2.10.0",
4 | "description": "The most svelte form library! 🥷",
5 | "author": "Noah Versace Salvi",
6 | "homepage": "https://github.com/noahsalvi/svelte-use-form#readme",
7 | "license": "MIT",
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/noahsalvi/svelte-use-form.git"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/noahsalvi/svelte-use-form/issues"
14 | },
15 | "keywords": [
16 | "svelte",
17 | "form",
18 | "validator",
19 | "validation",
20 | "hint",
21 | "action"
22 | ],
23 | "scripts": {
24 | "dev": "vite dev",
25 | "build": "vite build",
26 | "package": "svelte-kit sync && svelte-package",
27 | "preview": "vite preview",
28 | "test": "npx playwright test",
29 | "prepublishOnly": "echo 'Did you mean to publish `./package/`, instead of `./`?' && exit 1",
30 | "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
31 | "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
32 | "lint": "prettier --plugin-search-dir . --check .",
33 | "format": "prettier --plugin-search-dir . --write ."
34 | },
35 | "peerDependencies": {
36 | "svelte": ">=3.46.0 < 5"
37 | },
38 | "devDependencies": {
39 | "@playwright/test": "^1.36.1",
40 | "@sveltejs/adapter-auto": "next",
41 | "@sveltejs/kit": "next",
42 | "@sveltejs/package": "^1.0.0-next.6",
43 | "prettier": "^2.7.1",
44 | "prettier-plugin-svelte": "^2.8.1",
45 | "svelte": "^3.46.0",
46 | "svelte-check": "^2.7.1",
47 | "svelte-preprocess": "^4.10.6",
48 | "tslib": "^2.3.1",
49 | "typescript": "^4.7.4",
50 | "vite": "^3.1.0"
51 | },
52 | "type": "module"
53 | }
54 |
--------------------------------------------------------------------------------
/playwright.config.ts:
--------------------------------------------------------------------------------
1 | import type { PlaywrightTestConfig } from "@playwright/test";
2 |
3 | // Run 'npm start build' before running the tests
4 |
5 | const config: PlaywrightTestConfig = {
6 | webServer: {
7 | command: "npx vite preview --port 4175",
8 | port: 4175,
9 | reuseExistingServer: !process.env.CI,
10 | },
11 | };
12 |
13 | export default config;
14 |
--------------------------------------------------------------------------------
/src/app.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | // See https://kit.svelte.dev/docs/types#app
4 | // for information about these interfaces
5 | // and what to do when importing types
6 | declare namespace App {
7 | // interface Locals {}
8 | // interface PageData {}
9 | // interface Error {}
10 | // interface Platform {}
11 | }
12 |
--------------------------------------------------------------------------------
/src/app.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | %sveltekit.head%
8 |
9 |
10 |
11 | %sveltekit.body%
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/lib/chromeAutofill.ts:
--------------------------------------------------------------------------------
1 | import type { FormControl } from "./models/formControl";
2 | import type { TextElement } from "./models/formControlElement";
3 |
4 | const isChrome = () =>
5 | /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
6 | const animationName = "svelte-use-form-webkit-autofill";
7 | const css = `
8 | @keyframes ${animationName} {
9 | from {}
10 | }
11 |
12 | input:-webkit-autofill {
13 | animation-name: svelte-use-form-webkit-autofill;
14 | }
15 | `;
16 |
17 | function startAnimationWhenAutofilled() {
18 | if (
19 | document.getElementById("svelte-use-form-chrome-autofill-styles") === null
20 | ) {
21 | const style = document.createElement("style");
22 | style.setAttribute("id", "svelte-use-form-chrome-autofill-styles");
23 | style.setAttribute("type", "text/css");
24 | style.appendChild(document.createTextNode(css));
25 | document.head.appendChild(style);
26 | }
27 | }
28 |
29 | export function handleChromeAutofill(
30 | textElement: TextElement,
31 | control: FormControl,
32 | callback: Function
33 | ) {
34 | if (!isChrome()) return;
35 |
36 | function handleAnimationStart(event: AnimationEvent) {
37 | if (event.animationName.includes(animationName)) {
38 | const currentValue = textElement.value;
39 | // If chrome did not actually fill the value of the input
40 | if (!currentValue) {
41 | control.valid = true;
42 | callback();
43 | }
44 | }
45 | }
46 |
47 | textElement.addEventListener("animationstart", handleAnimationStart);
48 | startAnimationWhenAutofilled();
49 | }
50 |
--------------------------------------------------------------------------------
/src/lib/components/Hint.svelte:
--------------------------------------------------------------------------------
1 |
55 |
56 | {#if !(hideWhenRequired && requiredError) && !hideWhenError}
57 | {#if (touched || showWhenUntouched) && value}
58 |
59 |
60 |
61 | {/if}
62 | {/if}
63 |
--------------------------------------------------------------------------------
/src/lib/components/HintGroup.svelte:
--------------------------------------------------------------------------------
1 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/src/lib/index.ts:
--------------------------------------------------------------------------------
1 | export { default as Hint } from "./components/Hint.svelte";
2 | export { default as HintGroup } from "./components/HintGroup.svelte";
3 | export { useForm } from "./useForm";
4 | export { validators, ValidatorsActionError } from "./validatorsAction";
5 | export { Form, FormControlMissingError } from "./models/form";
6 | export { FormControl } from "./models/formControl";
7 | export type {
8 | FormControlsSpecified,
9 | FormControlsUnspecified,
10 | } from "./models/form";
11 | export {
12 | required,
13 | minLength,
14 | maxLength,
15 | email,
16 | emailWithTld,
17 | url,
18 | number,
19 | pattern,
20 | } from "./validators";
21 | export type { Validator, ValidationErrors, ErrorMap } from "./models/validator";
22 |
--------------------------------------------------------------------------------
/src/lib/models/form.ts:
--------------------------------------------------------------------------------
1 | import { FormControl } from "./formControl";
2 | import type { FormControlElement } from "./formControlElement";
3 | import type { FormProperties } from "./formProperties";
4 | import type { ErrorMap, Validator } from "./validator";
5 |
6 | export class Form {
7 | /**
8 | * Function for creating a Form
9 | * @remarks This allows us to specify the index signatures in the class
10 | */
11 | static create(
12 | initialData: FormProperties,
13 | notifyListeners: Function
14 | ) {
15 | return new Form(initialData, notifyListeners) as Form &
16 | FormControlsSpecified &
17 | FormControlsUnspecified;
18 | }
19 | private readonly _notifyListeners: Function;
20 |
21 | get valid(): boolean {
22 | let valid = true;
23 | this.forEachControl((formControl) => {
24 | if (!formControl.valid) valid = false;
25 | });
26 | return valid;
27 | }
28 |
29 | get touched(): boolean {
30 | let touched = true;
31 | this.forEachControl((formControl) => {
32 | if (!formControl.touched) touched = false;
33 | });
34 | return touched;
35 | }
36 |
37 | get values(): FormValues {
38 | let values = {} as any;
39 | this.forEachControl((formControl, key) => {
40 | values[key] = formControl.value;
41 | });
42 |
43 | return values;
44 | }
45 |
46 | set touched(value: boolean) {
47 | this.forEachControl((formControl) => {
48 | formControl.touched = value;
49 | });
50 |
51 | this._notifyListeners();
52 | }
53 |
54 | private constructor(initialData: FormProperties, notifyListeners: Function) {
55 | for (const [k, v] of Object.entries(initialData)) {
56 | this._addControl(k, v.initial, v.validators, [], v.errorMap);
57 | }
58 |
59 | this._notifyListeners = notifyListeners;
60 | }
61 |
62 | /** Reset the whole form */
63 | reset() {
64 | this.forEachControl((formControl) => formControl.reset());
65 | }
66 |
67 | /** @internal Add a form conrol to the Form */
68 | _addControl(
69 | name: string,
70 | initial: string = "",
71 | validators: Validator[] = [],
72 | elements: FormControlElement[] = [],
73 | errorMap: ErrorMap = {}
74 | ) {
75 | (this as any)[name] = new FormControl({
76 | value: initial,
77 | validators: validators,
78 | elements: elements,
79 | errorMap: errorMap,
80 | formRef: () => this,
81 | });
82 | }
83 |
84 | private forEachControl(
85 | callback: (formControl: FormControl, key: string) => void
86 | ) {
87 | for (const [key, value] of Object.entries(this)) {
88 | if (value instanceof FormControl) {
89 | callback(value, key);
90 | }
91 | }
92 | }
93 | }
94 |
95 | export class FormControlMissingError extends Error {}
96 |
97 | // We do not use utility types here, since they would hide the name of the type
98 | export type FormControlsUnspecified = {
99 | [key: string]: FormControl | undefined;
100 | };
101 | export type FormControlsSpecified = {
102 | [K in Keys]: FormControl;
103 | };
104 | export type FormValues = Partial<
105 | Record
106 | > &
107 | Record;
108 |
--------------------------------------------------------------------------------
/src/lib/models/formControl.ts:
--------------------------------------------------------------------------------
1 | import type { Form } from "./form";
2 | import type { FormControlElement } from "./formControlElement";
3 | import type { Validator, ValidationErrors, ErrorMap } from "./validator";
4 |
5 | /** A FormControl represents the state of a {@link FormControlElement} like (input, textarea...) */
6 | export class FormControl {
7 | validators: Validator[];
8 |
9 | /**
10 | * Returns an object containing possible validation errors
11 | * @example
12 | * (All validators are throwing an error)
13 | * `{ required: true, minLength: 4, maxLength: 20 }`
14 | * (Only required is invalid)
15 | * `{ required: true }`
16 | */
17 | errors: ValidationErrors = {};
18 |
19 | /**
20 | * Contains a map of values, that will be shown
21 | * in place of the original validation error.
22 | */
23 | errorMap: ErrorMap = {};
24 |
25 | /**
26 | * The DOM elements representing this control
27 | */
28 | elements: FormControlElement[] = [];
29 |
30 | /** Does the FormControl pass all given validators? */
31 | valid = true;
32 |
33 | /**
34 | * If the FormControl has been interacted with.
35 | * (triggered by blur event)
36 | */
37 | _touched = false;
38 |
39 | /** The initial value of the FormControl. Defaults to `""` if not set via `useForm(params)`. */
40 | initial: string;
41 |
42 | // TODO can we get the Form via Svelte context?
43 | private readonly formRef: () => Form;
44 |
45 | private _value: string = "";
46 |
47 | get value() {
48 | return this._value;
49 | }
50 |
51 | get touched() {
52 | return this._touched;
53 | }
54 |
55 | /**
56 | * This will only change the internal value of the control, not the one displayed in the actual HTML-Element
57 | *
58 | * See `change(value: String)` for doing both
59 | */
60 | set value(value: string) {
61 | this._value = value;
62 | this.validate();
63 | }
64 |
65 | set touched(value: boolean) {
66 | this._touched = value;
67 | this.elements.forEach((element) => {
68 | if (value) element.classList.add("touched");
69 | else element.classList.remove("touched");
70 | });
71 | }
72 |
73 | constructor(formControl: {
74 | value: string;
75 | validators: Validator[];
76 | errorMap: ErrorMap;
77 | elements: FormControlElement[];
78 | formRef: () => Form;
79 | }) {
80 | this.formRef = formControl.formRef;
81 | this.validators = formControl.validators;
82 | this.errorMap = formControl.errorMap;
83 | this.initial = formControl.value;
84 | this.elements = formControl.elements;
85 | this.value = formControl.value;
86 | }
87 |
88 | /**
89 | * Set an error manually.
90 | *
91 | * The error will be removed after changes to the value or on validate()
92 | *
93 | * Used for setting an error that would be difficult to implement with a validator.
94 | * @example Backend Response returning Login failed
95 | * ``` typescript
96 | * function submit() {
97 | * apiLogin($form.values).then(response => {})
98 | * .catch(error => {
99 | * if (error.status === 403) {
100 | * $form.password.error({ login: "Password or username is incorrect" });
101 | * }
102 | * })
103 | * }
104 | * ```
105 | */
106 | error(errors: ValidationErrors) {
107 | this.errors = { ...errors, ...this.errors };
108 | this.valid = false;
109 |
110 | // Update the $form
111 | this.formRef()["_notifyListeners"]();
112 | }
113 |
114 | /** Change the value and the value of all HTML-Elements associated with this control */
115 | change(value: any) {
116 | this.value = value;
117 | this.elements.forEach((element) => (element.value = value));
118 |
119 | // Update the $form
120 | this.formRef()["_notifyListeners"]();
121 | }
122 |
123 | /** Validate the FormControl by querying through the given validators. */
124 | validate() {
125 | let valid = true;
126 | this.errors = {};
127 |
128 | for (const validator of this.validators) {
129 | const errors = validator(this.value, this.formRef() as any, this);
130 | if (!errors) continue;
131 |
132 | valid = false;
133 | for (const error of Object.entries(errors)) {
134 | let [key, value] = error;
135 |
136 | // If there is a map for the error, use it
137 | const errorMapping = this.errorMap[key];
138 | if (errorMapping) {
139 | value =
140 | typeof errorMapping === "function"
141 | ? errorMapping(value)
142 | : errorMapping;
143 | }
144 |
145 | this.errors[key] = value;
146 | }
147 | }
148 |
149 | this.valid = valid;
150 | this.elements.forEach((element) =>
151 | element.setCustomValidity(valid ? "" : "Field is invalid")
152 | );
153 |
154 | return valid;
155 | }
156 |
157 | /** Reset the form control value to its initial value or `{ value }` and untouch it */
158 | reset({ value }: { value?: string | null } = {}) {
159 | const resetValue = value == null ? this.initial : value;
160 | this.elements.forEach((element) => (element.value = resetValue));
161 | this.value = resetValue;
162 | this.touched = false;
163 |
164 | // Updating the $form
165 | this.formRef()["_notifyListeners"]();
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/src/lib/models/formControlElement.ts:
--------------------------------------------------------------------------------
1 | export function isTextElement(node: any): node is TextElement {
2 | return (
3 | node instanceof HTMLInputElement || node instanceof HTMLTextAreaElement
4 | );
5 | }
6 |
7 | export function isFormControlElement(node: any): node is FormControlElement {
8 | return (
9 | node instanceof HTMLInputElement ||
10 | node instanceof HTMLTextAreaElement ||
11 | node instanceof HTMLSelectElement
12 | );
13 | }
14 |
15 | /* This function checks the node if it has an attribute `data-suf-ignore`
16 | It's used to ignore elements that should not be part of the form
17 | */
18 | export function isIgnoredElement(node: any): boolean {
19 | return (
20 | (node.hasAttribute("data-suf-ignore") &&
21 | node.getAttribute("data-suf-ignore") === "true") || //
22 | node.getAttribute("data-suf-ignore") === true //
or
23 | );
24 | }
25 |
26 | export type TextElement = HTMLInputElement | HTMLTextAreaElement;
27 | export type FormControlElement = TextElement | HTMLSelectElement;
28 |
--------------------------------------------------------------------------------
/src/lib/models/formProperties.ts:
--------------------------------------------------------------------------------
1 | import type { ErrorMap, Validator } from "./validator";
2 |
3 | export type FormProperties = {
4 | [key: string]: {
5 | /** Initial value of the form control */
6 | initial?: string;
7 | /** The validators that will be checked when the input changes */
8 | validators?: Validator[];
9 | /**
10 | * The map through which validation errors will be mapped.
11 | * You can either pass a string or a function returning a new error value
12 | *
13 | * **Think of it as a translation map. 😆**
14 | */
15 | errorMap?: ErrorMap;
16 | };
17 | };
18 |
--------------------------------------------------------------------------------
/src/lib/models/validator.ts:
--------------------------------------------------------------------------------
1 | import type { Form, FormControlsUnspecified } from "./form";
2 | import type { FormControl } from "./formControl";
3 |
4 | /**
5 | * A function that depending on the control's validity either returns:
6 | * - `null | undefined` when **valid**
7 | * - {@link ValidationErrors} when **invalid**.
8 | */
9 | export type Validator = (
10 | /** The value of the control. */
11 | value: string,
12 | /** The containing form. */
13 | form: Form
& FormControlsUnspecified,
14 | /** The control this validator was assigned to. */
15 | control: FormControl
16 | ) => ValidationErrors | (null | undefined);
17 |
18 | /** An object that contains errors thrown by the validator. */
19 | export type ValidationErrors = { [errorName: string]: any };
20 |
21 | export type ErrorMap = { [errorName: string]: string | ((error: any) => any) };
22 |
--------------------------------------------------------------------------------
/src/lib/stores/formReferences.ts:
--------------------------------------------------------------------------------
1 | import { writable } from "svelte/store";
2 | import type { Form, FormControlsUnspecified } from "../models/form";
3 |
4 | export type FormReference = {
5 | form: Form & FormControlsUnspecified;
6 | node: HTMLFormElement;
7 | notifyListeners: Function;
8 | };
9 |
10 | export const formReferences = writable([]);
11 |
--------------------------------------------------------------------------------
/src/lib/useForm.ts:
--------------------------------------------------------------------------------
1 | import { setContext } from "svelte";
2 | import { handleChromeAutofill } from "./chromeAutofill";
3 | import { Form, FormControlMissingError } from "./models/form";
4 | import {
5 | isIgnoredElement,
6 | isFormControlElement,
7 | isTextElement,
8 | } from "./models/formControlElement";
9 | import type {
10 | FormControlElement,
11 | TextElement,
12 | } from "./models/formControlElement";
13 | import { formReferences } from "./stores/formReferences";
14 | import type { FormControl } from "./models/formControl";
15 | import type { FormProperties } from "./models/formProperties";
16 |
17 | interface EventListener {
18 | node: HTMLElement;
19 | event: string;
20 | listener: EventListenerOrEventListenerObject;
21 | }
22 | /** Create a new form.
23 | *
24 | * You can either pass a default configuration for the form.
25 | *
26 | * ----
27 | * ``` svelte
28 | *
33 | *
34 | *
47 | * ```
48 | */
49 | export function useForm<
50 | Keys extends keyof T = "",
51 | T extends FormProperties = any
52 | >(
53 | properties: T | FormProperties = {} as FormProperties,
54 | formName: string = "svelte-use-form"
55 | ) {
56 | const subscribers: Function[] = [];
57 |
58 | let eventListeners: EventListener[] = [];
59 |
60 | let state = Form.create(properties, notifyListeners);
61 |
62 | let observer: MutationObserver;
63 |
64 | action.subscribe = subscribe;
65 | action.set = set;
66 |
67 | // Passing state via context to subcomponents like Hint
68 | setContext(formName, action);
69 |
70 | /**
71 | * ### The store and action of a form.
72 | *
73 | * Use the `$` prefix to access the state of the form;
74 | */
75 | function action(node: HTMLFormElement) {
76 | // Bootstrap form
77 | setupForm(node);
78 |
79 | // Add form reference to global internal store
80 | formReferences.update((values) => [
81 | ...values,
82 | { node, form: state, notifyListeners },
83 | ]);
84 |
85 | return {
86 | update: () => {},
87 | destroy: () => {
88 | unmountEventListeners();
89 | observer.disconnect();
90 | },
91 | };
92 | }
93 |
94 | function setupForm(node: HTMLFormElement) {
95 | const inputElements = [
96 | ...getNodeElementsByTagName(node, "input"),
97 | ];
98 | const textareaElements = [
99 | ...getNodeElementsByTagName(node, "textarea"),
100 | ];
101 | const selectElements = [
102 | ...getNodeElementsByTagName(node, "select"),
103 | ];
104 | const textElements = [...inputElements, ...textareaElements];
105 |
106 | setupTextElements(textElements);
107 | setupSelectElements(selectElements);
108 | hideNotRepresentedFormControls([...textElements, ...selectElements]);
109 | setupFormObserver(node);
110 |
111 | notifyListeners();
112 | }
113 |
114 | function setupTextElements(textElements: TextElement[]) {
115 | for (const textElement of textElements) {
116 | const name = textElement.name;
117 | let formControl = state[name];
118 | // TextElement doesn't have FormControl yet (TextElement wasn't statically provided)
119 | if (!formControl) {
120 | const initial = getInitialValueFromTextElement(textElement);
121 | state._addControl(name, initial, [], [textElement], {});
122 | formControl = state[name]!;
123 | } else {
124 | formControl.elements.push(textElement);
125 | if (
126 | textElement.type === "radio" &&
127 | textElement instanceof HTMLInputElement &&
128 | textElement.checked
129 | ) {
130 | formControl.initial = textElement.value;
131 | }
132 | }
133 |
134 | switch (textElement.type) {
135 | case "checkbox":
136 | case "radio":
137 | mountEventListener(textElement, "click", handleBlurOrClick);
138 | break;
139 | default:
140 | setInitialValue(textElement, formControl!);
141 | handleAutofill(textElement, formControl!);
142 | mountEventListener(textElement, "blur", handleBlurOrClick);
143 | }
144 |
145 | mountEventListener(textElement, "input", handleInput);
146 | }
147 | }
148 |
149 | function setupSelectElements(selectElements: HTMLSelectElement[]) {
150 | for (const selectElement of selectElements) {
151 | const name = selectElement.name;
152 | const formControl = state[name];
153 |
154 | if (!formControl) {
155 | const initial = selectElement.value;
156 | state._addControl(name, initial, [], [selectElement], {});
157 | } else {
158 | formControl.elements.push(selectElement);
159 | setInitialValue(selectElement, formControl);
160 | }
161 |
162 | mountEventListener(selectElement, "input", handleInput);
163 | mountEventListener(selectElement, "input", handleBlurOrClick);
164 | mountEventListener(selectElement, "blur", handleBlurOrClick);
165 | }
166 | }
167 |
168 | function setupFormObserver(form: HTMLFormElement) {
169 | observer = new MutationObserver(observeForm);
170 | observer.observe(form, { childList: true, subtree: true });
171 | }
172 |
173 | function observeForm(mutations: MutationRecord[]) {
174 | for (const mutation of mutations) {
175 | if (mutation.type !== "childList") continue;
176 |
177 | // If node gets added
178 | for (const node of mutation.addedNodes) {
179 | if (!(isFormControlElement(node) && !isIgnoredElement(node))) continue;
180 | const initialFormControlProperty = properties[node.name];
181 | if (!state[node.name] && initialFormControlProperty) {
182 | state._addControl(
183 | node.name,
184 | initialFormControlProperty.initial,
185 | initialFormControlProperty.validators,
186 | [], // The setup function will add this node to the form control
187 | initialFormControlProperty.errorMap
188 | );
189 | }
190 | if (isTextElement(node)) setupTextElements([node]);
191 | else if (node instanceof HTMLSelectElement) setupSelectElements([node]);
192 | }
193 |
194 | // If node gets removed
195 | for (const node of mutation.removedNodes) {
196 | if (!(node instanceof HTMLElement)) continue; // We only handle HTML elements
197 |
198 | // The observer will only return the direct elements that were removed, and not for example a nested input
199 | const elements = isFormControlElement(node)
200 | ? [node]
201 | : getAllFormControlElements(node);
202 |
203 | elements.forEach((element) => {
204 | delete state[element.name];
205 | eventListeners = eventListeners.filter(
206 | (eventListener) => eventListener.node !== element
207 | );
208 | });
209 | }
210 | }
211 |
212 | notifyListeners();
213 | }
214 |
215 | function mountEventListener(
216 | node: HTMLElement,
217 | event: string,
218 | listener: EventListenerOrEventListenerObject
219 | ) {
220 | node.addEventListener(event, listener);
221 | eventListeners.push({ node, event, listener });
222 | }
223 |
224 | function unmountEventListeners() {
225 | for (const { node, event, listener } of eventListeners) {
226 | node.removeEventListener(event, listener);
227 | }
228 | }
229 |
230 | function handleAutofill(textElement: TextElement, formControl: FormControl) {
231 | // Chrome sometimes fills the input visually without actually writing a value to it, this combats it
232 | handleChromeAutofill(textElement, formControl, notifyListeners);
233 |
234 | // If the browser writes a value without triggering an event
235 | function handleNoEventAutofilling() {
236 | if (textElement.value !== formControl.initial) {
237 | handleBlurOrClick({ target: textElement } as any);
238 | return true;
239 | }
240 | return false;
241 | }
242 |
243 | const autofillingWithoutEventInstantly = handleNoEventAutofilling();
244 |
245 | // In a SPA App the form is sometimes not filled instantly so we wait 100ms
246 | if (!autofillingWithoutEventInstantly)
247 | setTimeout(() => handleNoEventAutofilling(), 100);
248 | }
249 |
250 | function handleInput({ target: node }: Event) {
251 | if (isFormControlElement(node)) {
252 | const name = node.name;
253 | const formControl = state[name];
254 | if (!formControl) throw new FormControlMissingError();
255 |
256 | let value: string;
257 | if (node.type === "checkbox" && node instanceof HTMLInputElement) {
258 | value = node.checked ? "checked" : "";
259 | } else {
260 | value = node.value;
261 | }
262 |
263 | formControl.value = value;
264 |
265 | notifyListeners();
266 | }
267 | }
268 |
269 | function handleBlurOrClick({ target: node }: Event) {
270 | if (isFormControlElement(node)) {
271 | const formControl = state[node.name];
272 | if (!formControl) throw new FormControlMissingError();
273 |
274 | if (!formControl.touched) handleInput({ target: node } as any);
275 |
276 | formControl.touched = true;
277 | node.classList.add("touched");
278 |
279 | notifyListeners();
280 | }
281 | }
282 |
283 | // TODO do we still need this?
284 | function hideNotRepresentedFormControls(nodes: FormControlElement[]) {
285 | for (const key of Object.keys(properties)) {
286 | let isFormControlRepresentedInDom = false;
287 |
288 | for (const node of nodes) {
289 | if (key === node.name) isFormControlRepresentedInDom = true;
290 | }
291 |
292 | if (!isFormControlRepresentedInDom) delete state[key];
293 | }
294 | }
295 |
296 | function setInitialValue(
297 | element: FormControlElement,
298 | formControl: FormControl
299 | ) {
300 | if (formControl.initial) element.value = formControl.initial;
301 | }
302 |
303 | function notifyListeners() {
304 | for (const callback of subscribers) callback(state);
305 | }
306 |
307 | function subscribe(callback: (form: typeof state) => void) {
308 | subscribers.push(callback);
309 | callback(state);
310 |
311 | return { unsubscribe: () => unsubscribe(callback) };
312 | }
313 |
314 | function unsubscribe(subscriber: Function) {
315 | const index = subscribers.indexOf(subscriber);
316 | subscribers.splice(index, 1);
317 | }
318 |
319 | function set(value: typeof state) {
320 | // TODO investigage what happens when different Keys are passed
321 | state = value;
322 |
323 | notifyListeners();
324 | }
325 |
326 | return action;
327 | }
328 |
329 | function getInitialValueFromTextElement(textElement: TextElement) {
330 | let initial: string;
331 |
332 | // Handle Radio button initial values
333 | if (textElement.type === "radio" && textElement instanceof HTMLInputElement) {
334 | initial = textElement.checked ? textElement.value : "";
335 | } else if (
336 | textElement.type === "checkbox" &&
337 | textElement instanceof HTMLInputElement
338 | ) {
339 | initial = textElement.checked ? "checked" : "";
340 | } else {
341 | initial = textElement.value;
342 | }
343 | return initial;
344 | }
345 |
346 | /*
347 | Scan the DOM for a set of form elements by tag name and
348 | return the elements which are not ignored by `data-suf-ignore` attribute.
349 | */
350 | function getNodeElementsByTagName(
351 | node: HTMLFormElement | HTMLElement,
352 | tagName: string
353 | ): T[] {
354 | return Array.from(node.getElementsByTagName(tagName)).filter(
355 | (element) => !isIgnoredElement(element)
356 | ) as T[];
357 | }
358 |
359 | function getAllFormControlElements(node: HTMLElement): FormControlElement[] {
360 | const inputs = getNodeElementsByTagName(node, "input");
361 | const textareas = getNodeElementsByTagName(
362 | node,
363 | "textarea"
364 | );
365 | const selects = getNodeElementsByTagName(node, "select");
366 | return [...inputs, ...textareas, ...selects];
367 | }
368 |
--------------------------------------------------------------------------------
/src/lib/validators.ts:
--------------------------------------------------------------------------------
1 | import type { Validator } from "./models/validator";
2 |
3 | export const required: Validator = (value) => {
4 | return value.trim() ? null : { required: "Required" };
5 | };
6 |
7 | export function maxLength(length: number): Validator {
8 | return (value) => {
9 | if (value.trim().length > length) return { maxLength: length };
10 | };
11 | }
12 |
13 | export function minLength(length: number): Validator {
14 | return (value) => {
15 | if (value.trim().length < length) return { minLength: length };
16 | };
17 | }
18 |
19 | export const email: Validator = (value) => {
20 | if (
21 | /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(
22 | value
23 | )
24 | ) {
25 | return null;
26 | }
27 | return { email: {} };
28 | };
29 |
30 | /**
31 | * A variation of the `email` validator that requires a [TLD](https://en.wikipedia.org/wiki/Top-level_domain) component. Verifying
32 | * the validity of the TLD is not the responsibility of this validation library.
33 | */
34 | export const emailWithTld: Validator = (value) => {
35 | if (
36 | /^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)+$/.test(
37 | value
38 | )
39 | ) {
40 | return null;
41 | }
42 | return { emailWithTld: {} };
43 | };
44 |
45 | export const url: Validator = (value) => {
46 | // https://stackoverflow.com/a/5717133/13475809
47 | var pattern = new RegExp(
48 | "^(https?:\\/\\/)?" + // protocol
49 | "((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name
50 | "((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
51 | "(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
52 | "(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
53 | "(\\#[-a-z\\d_]*)?$", // fragment locator
54 | "i"
55 | );
56 | if (pattern.test(value)) {
57 | return null;
58 | }
59 | return { url: "URL is not valid" };
60 | };
61 |
62 | export const number: Validator = (value) => {
63 | if (/^[0-9]+$/.test(value)) {
64 | return null;
65 | }
66 | return { number: {} };
67 | };
68 |
69 | export function pattern(regExp: string | RegExp): Validator {
70 | const r = typeof regExp === "string" ? new RegExp(regExp) : regExp;
71 | return (value) => (r.test(value) ? null : { pattern: "Pattern error" });
72 | }
73 |
--------------------------------------------------------------------------------
/src/lib/validatorsAction.ts:
--------------------------------------------------------------------------------
1 | import { tick } from "svelte";
2 | import { get } from "svelte/store";
3 | import { FormControl } from "./models/formControl";
4 | import type { FormControlElement } from "./models/formControlElement";
5 | import type { Validator } from "./models/validator";
6 | import { formReferences, type FormReference } from "./stores/formReferences";
7 |
8 | /**
9 | * Add validators to form control
10 | * @example
11 | * ``` svelte
12 | *
13 | * ```
14 | */
15 | export function validators(
16 | element: FormControlElement,
17 | validators: Validator[]
18 | ) {
19 | let formControl: FormControl | undefined;
20 | let formReference: FormReference | undefined;
21 |
22 | setupValidators();
23 |
24 | return {
25 | update: updateValidators,
26 | };
27 |
28 | async function setupValidators() {
29 | const formElement = element.form;
30 | if (!formElement)
31 | throw new ValidatorsActionError(
32 | "HTML element doesn't have an ancestral form"
33 | );
34 |
35 | await tick();
36 | const possibleFormReference = get(formReferences).find(
37 | (form) => form.node === formElement
38 | );
39 |
40 | if (!possibleFormReference)
41 | throw new ValidatorsActionError(
42 | "HTML form doesn't have a svelte-use-form binded. (use:form)"
43 | );
44 |
45 | formReference = possibleFormReference;
46 |
47 | let possibleFormControl = formReference.form[element.name];
48 | if (!(possibleFormControl instanceof FormControl))
49 | throw new ValidatorsActionError(
50 | `Form Control [${element.name}] doesn't exist.`
51 | );
52 |
53 | formControl = possibleFormControl;
54 | formControl.validators.push(...validators);
55 | formControl.validate();
56 | formReference.notifyListeners();
57 | }
58 |
59 | function updateValidators(updatedValidators: Validator[]) {
60 | if (!formControl || !formReference) return;
61 |
62 | // Get the static validators (The validators set via useForm({...}))
63 | const newValidators = formControl.validators.filter(
64 | (validator) => !validators.find((v) => v === validator)
65 | );
66 |
67 | // Add the new validators to it
68 | newValidators.push(...updatedValidators);
69 |
70 | formControl.validators = newValidators;
71 | formControl.validate();
72 | formReference.notifyListeners();
73 | }
74 | }
75 |
76 | export class ValidatorsActionError extends Error {}
77 |
--------------------------------------------------------------------------------
/src/routes/+layout.svelte:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
88 |
--------------------------------------------------------------------------------
/src/routes/+page.svelte:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noahsalvi/svelte-use-form/1f16ce8ed1a645ec500947d5c90090f371e0e128/src/routes/+page.svelte
--------------------------------------------------------------------------------
/src/routes/NavigationBar.svelte:
--------------------------------------------------------------------------------
1 |
23 |
24 |
25 | Svelte Use Form
26 | Examples:
27 |
32 |
33 |
34 |
60 |
--------------------------------------------------------------------------------
/src/routes/examples/ReuseFormDialog.svelte:
--------------------------------------------------------------------------------
1 |
24 |
25 | {#if showDialog}
26 |
58 | {/if}
59 |
60 |
68 |
--------------------------------------------------------------------------------
/src/routes/examples/async/+page.svelte:
--------------------------------------------------------------------------------
1 |
35 |
36 |
61 |
62 |
71 |
--------------------------------------------------------------------------------
/src/routes/examples/custom-validator/+page.svelte:
--------------------------------------------------------------------------------
1 |
20 |
21 |
51 |
--------------------------------------------------------------------------------
/src/routes/examples/dynamic-form/+page.svelte:
--------------------------------------------------------------------------------
1 |
8 |
9 |
35 |
36 |
37 | {JSON.stringify($form, null, " ")}
38 |
39 |
--------------------------------------------------------------------------------
/src/routes/examples/dynamic-typing/+page.svelte:
--------------------------------------------------------------------------------
1 |
26 |
27 |
56 |
57 | {JSON.stringify($form, null, "\t")}
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/routes/examples/dynamic-validator/+page.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
31 |
32 | {JSON.stringify($form.copy?.errors, null, " ")}
33 |
34 |
35 |
41 |
--------------------------------------------------------------------------------
/src/routes/examples/email-validator/+page.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
19 |
--------------------------------------------------------------------------------
/src/routes/examples/ignore-attribute/+page.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 | test
8 |
47 |
--------------------------------------------------------------------------------
/src/routes/examples/login/+page.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
43 |
--------------------------------------------------------------------------------
/src/routes/examples/multi-forms/+page.svelte:
--------------------------------------------------------------------------------
1 |
14 |
15 |
39 |
40 |
67 |
--------------------------------------------------------------------------------
/src/routes/examples/reset-form/+page.svelte:
--------------------------------------------------------------------------------
1 |
21 |
22 |
55 |
--------------------------------------------------------------------------------
/src/routes/examples/reuse-form/+page.svelte:
--------------------------------------------------------------------------------
1 |
28 |
29 | Svelte-use-form "reusing form" test
30 | New person
31 |
32 |
33 |
34 | ID
35 | Nome
36 | Ativo
37 |
38 |
39 |
40 |
41 | {#each persons as p}
42 |
43 | {p.id}
44 | {p.nome}
45 | {p.ativo ? "Yes" : "No"}
46 |
47 | editPerson(p)}>Edit
48 |
49 |
50 | {/each}
51 |
52 |
53 |
54 |
55 |
56 | {#key selectedPerson}
57 |
58 | {/key}
59 |
60 |
68 |
--------------------------------------------------------------------------------
/src/routes/test/hint/+page.svelte:
--------------------------------------------------------------------------------
1 |
6 |
7 |
13 |
--------------------------------------------------------------------------------
/static/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/noahsalvi/svelte-use-form/1f16ce8ed1a645ec500947d5c90090f371e0e128/static/favicon.png
--------------------------------------------------------------------------------
/svelte-use-form.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/svelte.config.js:
--------------------------------------------------------------------------------
1 | import adapter from "@sveltejs/adapter-auto";
2 | import preprocess from "svelte-preprocess";
3 |
4 | /** @type {import('@sveltejs/kit').Config} */
5 | const config = {
6 | // Consult https://github.com/sveltejs/svelte-preprocess
7 | // for more information about preprocessors
8 | preprocess: preprocess(),
9 |
10 | kit: {
11 | adapter: adapter(),
12 | },
13 | };
14 |
15 | export default config;
16 |
--------------------------------------------------------------------------------
/tests/Hint.spec.ts:
--------------------------------------------------------------------------------
1 | import { expect, test } from "@playwright/test";
2 |
3 | test("Hint.svelte", async ({ page }) => {
4 | await page.goto("/test/hint");
5 | const input = page.getByRole("textbox");
6 | const requiredHint = page.getByText("Required");
7 | const emailHint = page.getByText("Email");
8 |
9 | await expect(requiredHint).not.toBeVisible();
10 | await expect(emailHint).not.toBeVisible();
11 |
12 | await input.focus();
13 | await input.blur();
14 | await expect(requiredHint).toBeVisible();
15 |
16 | await input.type("max");
17 | await expect(requiredHint).not.toBeVisible();
18 |
19 | await input.type("@example.com");
20 | await expect(emailHint).not.toBeVisible();
21 | });
22 |
--------------------------------------------------------------------------------
/tests/dynamic-validators.spec.ts:
--------------------------------------------------------------------------------
1 | import test, { expect } from "@playwright/test";
2 |
3 | test("Dynamic Validators (Validators with changing parameters)", async ({
4 | page,
5 | }) => {
6 | await page.goto("examples/dynamic-validator");
7 |
8 | const inputA = page.locator("#inputA");
9 | const inputB = page.locator("#inputB");
10 | const isMatching = page.locator("#is-matching");
11 | const isRequiredErrorTriggered = page.locator("#is-empty");
12 |
13 | expect(inputB).not.toHaveText(await inputA.inputValue());
14 | await expect(isMatching).toHaveText("No");
15 |
16 | await inputB.type(await inputA.inputValue());
17 | await expect(isMatching).toHaveText("Yes");
18 |
19 | await inputB.type("A");
20 | await expect(isMatching).toHaveText("No");
21 |
22 | // Focus important so that the cursor is placed at the end
23 | await inputA.focus();
24 | await inputA.type("A");
25 | await expect(isMatching).toHaveText("Yes");
26 | await expect(isRequiredErrorTriggered).not.toBeChecked();
27 |
28 | await inputB.clear();
29 | await expect(isRequiredErrorTriggered).toBeChecked();
30 | });
31 |
--------------------------------------------------------------------------------
/tests/email-validator.spec.ts:
--------------------------------------------------------------------------------
1 | import test, { expect } from "@playwright/test";
2 |
3 | const validEmails = [
4 | "email@test.com",
5 | "email@test.xyz",
6 | "email@test.a",
7 | "example.address@t.co",
8 | "example.address@a.b.c.d.e.f.g",
9 | "example_address@a.b.c",
10 | "example_address_2@a.b.c",
11 | ];
12 |
13 | test(`Verify valid emails are marked as valid by the emailWithTld validator`, async ({
14 | page,
15 | }) => {
16 | for (const address of validEmails) {
17 | await page.goto("examples/email-validator");
18 |
19 | const input = page.locator("#testEmailField");
20 | const isRequiredErrorTriggered = page.locator("#isValid");
21 |
22 | await input.clear();
23 | await input.type(address);
24 | await expect(isRequiredErrorTriggered).not.toBeChecked();
25 | }
26 | });
27 |
28 | const invalidEmails = [
29 | "email@test",
30 | "example.address@noTLD",
31 | "example.com@a",
32 | "example.com@a@.com",
33 | "example@a@.com",
34 | "example.com@.com",
35 | "example@.com",
36 | "example.com",
37 | "example",
38 | "@example.com",
39 | ];
40 |
41 | test(`Verify invalid emails are marked as invalid by the emailWithTld validator`, async ({
42 | page,
43 | }) => {
44 | for (const address of invalidEmails) {
45 | await page.goto("examples/email-validator");
46 |
47 | const input = page.locator("#testEmailField");
48 | const isRequiredErrorTriggered = page.locator("#isValid");
49 |
50 | await input.clear();
51 | await input.type(address);
52 | await expect(isRequiredErrorTriggered).toBeChecked();
53 | }
54 | });
55 |
--------------------------------------------------------------------------------
/tests/ignore.spec.ts:
--------------------------------------------------------------------------------
1 | import test, { expect } from "@playwright/test";
2 |
3 | test("suf-ignore", async ({ page }) => {
4 | await page.goto("examples/ignore-attribute");
5 | const registeredList = page.locator('[id="registered"]');
6 | await expect(registeredList).toHaveText("emailname"); // as emailname is the list content: - email - name
7 | });
8 |
--------------------------------------------------------------------------------
/tests/login.spec.ts:
--------------------------------------------------------------------------------
1 | import test, { expect } from "@playwright/test";
2 |
3 | test("Login Example", async ({ page }) => {
4 | await page.goto("examples/login");
5 | const email = page.getByPlaceholder("Email");
6 | const password = page.getByPlaceholder("password");
7 | const button = page.locator("button");
8 |
9 | await expect(button).toBeDisabled();
10 | await expect(email).not.toHaveClass("touched");
11 | await expect(password).not.toHaveClass("touched");
12 |
13 | await email.type("max@");
14 | await expect(email).toHaveClass("touched");
15 | await expect(password).not.toHaveClass("touched");
16 | await password.type("random pass");
17 | await expect(button).toBeDisabled();
18 |
19 | await email.type("example.com");
20 | await expect(password).toHaveClass("touched");
21 | await expect(button).toBeEnabled();
22 | });
23 |
--------------------------------------------------------------------------------
/tests/multiforms.spec.ts:
--------------------------------------------------------------------------------
1 | import test, { expect } from "@playwright/test";
2 |
3 | test("multi-forms", async ({ page }) => {
4 | await page.goto("examples/multi-forms");
5 |
6 | // Forms
7 | const form1 = page.locator('[id="form-one"]');
8 | const form2 = page.locator('[id="form-two"]');
9 |
10 | // Form 1
11 | const form1Input = form1.locator('[id="input-form-one"]');
12 | const form1Submit = form1.locator('[id="submit-form-one"]');
13 | const form1HintMin = form1.locator('[id="hint-min-form-one"]');
14 | const form1HintMax = form1.locator('[id="hint-max-form-one"]');
15 |
16 | // Form 2
17 | const form2Input = form2.locator('[id="input-form-two"]');
18 | const form2Submit = form2.locator('[id="submit-form-two"]');
19 | const form2HintMin = form2.locator('[id="hint-min-form-two"]');
20 | const form2HintMax = form2.locator('[id="hint-max-form-two"]');
21 |
22 | // Form one tests:
23 | await expect(form1Submit).toBeDisabled();
24 | await expect(form1HintMin).toBeHidden();
25 | await expect(form1HintMax).toBeHidden();
26 | await form1Input.fill("12");
27 | await form1Input.blur();
28 | await expect(form1HintMin).toBeVisible();
29 | await expect(form1HintMax).toBeHidden();
30 | // Form 2 hints should be hidden as well
31 | await expect(form2HintMin).toBeHidden();
32 | await expect(form2HintMax).toBeHidden();
33 | await form1Input.fill("123456");
34 | await form1Input.blur();
35 | await expect(form1HintMin).toBeHidden();
36 | await expect(form1HintMax).toBeVisible();
37 | // Form 2 hints should be hidden as well
38 | await expect(form2HintMin).toBeHidden();
39 | await expect(form2HintMax).toBeHidden();
40 | await form1Input.fill("1234");
41 | await form1Input.blur();
42 | await expect(form1HintMin).toBeHidden();
43 | await expect(form1HintMax).toBeHidden();
44 | await expect(form1Submit).toBeEnabled();
45 |
46 | // Form two tests:
47 | await expect(form2Submit).toBeDisabled();
48 | await expect(form2HintMin).toBeHidden();
49 | await expect(form2HintMax).toBeHidden();
50 | await form2Input.fill("12");
51 | await form2Input.blur();
52 | await expect(form2HintMin).toBeVisible();
53 | await expect(form2HintMax).toBeHidden();
54 | // Form 1 hints should be hidden as well
55 | await expect(form1HintMin).toBeHidden();
56 | await expect(form1HintMax).toBeHidden();
57 | await form2Input.fill("12345678");
58 | await form2Input.blur();
59 | await expect(form2HintMin).toBeHidden();
60 | await expect(form2HintMax).toBeVisible();
61 | // Form 1 hints should be hidden as well
62 | await expect(form1HintMin).toBeHidden();
63 | await expect(form1HintMax).toBeHidden();
64 | await form2Input.fill("123456");
65 | await form2Input.blur();
66 | await expect(form2HintMin).toBeHidden();
67 | await expect(form2HintMax).toBeHidden();
68 | await expect(form2Submit).toBeEnabled();
69 | });
70 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./.svelte-kit/tsconfig.json",
3 | "compilerOptions": {
4 | "allowJs": true,
5 | "checkJs": true,
6 | "esModuleInterop": true,
7 | "forceConsistentCasingInFileNames": true,
8 | "resolveJsonModule": true,
9 | "skipLibCheck": true,
10 | "sourceMap": true,
11 | "strict": true
12 | }
13 | // Path aliases are handled by https://kit.svelte.dev/docs/configuration#alias
14 | //
15 | // If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
16 | // from the referenced tsconfig.json - TypeScript does not merge them in
17 | }
18 |
--------------------------------------------------------------------------------
/vite.config.ts:
--------------------------------------------------------------------------------
1 | import { sveltekit } from "@sveltejs/kit/vite";
2 | import type { UserConfig } from "vite";
3 |
4 | const config: UserConfig = {
5 | plugins: [sveltekit()],
6 | };
7 |
8 | export default config;
9 |
--------------------------------------------------------------------------------
/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@esbuild/android-arm@0.15.14":
6 | version "0.15.14"
7 | resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.15.14.tgz#5d0027f920eeeac313c01fd6ecb8af50c306a466"
8 | integrity sha512-+Rb20XXxRGisNu2WmNKk+scpanb7nL5yhuI1KR9wQFiC43ddPj/V1fmNyzlFC9bKiG4mYzxW7egtoHVcynr+OA==
9 |
10 | "@esbuild/linux-loong64@0.15.14":
11 | version "0.15.14"
12 | resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.15.14.tgz#1221684955c44385f8af34f7240088b7dc08d19d"
13 | integrity sha512-eQi9rosGNVQFJyJWV0HCA5WZae/qWIQME7s8/j8DMvnylfBv62Pbu+zJ2eUDqNf2O4u3WB+OEXyfkpBoe194sg==
14 |
15 | "@jridgewell/resolve-uri@3.1.0":
16 | version "3.1.0"
17 | resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
18 | integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
19 |
20 | "@jridgewell/sourcemap-codec@1.4.14":
21 | version "1.4.14"
22 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
23 | integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
24 |
25 | "@jridgewell/trace-mapping@^0.3.9":
26 | version "0.3.17"
27 | resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985"
28 | integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==
29 | dependencies:
30 | "@jridgewell/resolve-uri" "3.1.0"
31 | "@jridgewell/sourcemap-codec" "1.4.14"
32 |
33 | "@nodelib/fs.scandir@2.1.5":
34 | version "2.1.5"
35 | resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
36 | integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
37 | dependencies:
38 | "@nodelib/fs.stat" "2.0.5"
39 | run-parallel "^1.1.9"
40 |
41 | "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
42 | version "2.0.5"
43 | resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
44 | integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
45 |
46 | "@nodelib/fs.walk@^1.2.3":
47 | version "1.2.8"
48 | resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
49 | integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
50 | dependencies:
51 | "@nodelib/fs.scandir" "2.1.5"
52 | fastq "^1.6.0"
53 |
54 | "@playwright/test@^1.36.1":
55 | version "1.36.1"
56 | resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.36.1.tgz#0b1247d279f142ac0876ce25e7daf15439d5367b"
57 | integrity sha512-YK7yGWK0N3C2QInPU6iaf/L3N95dlGdbsezLya4n0ZCh3IL7VgPGxC6Gnznh9ApWdOmkJeleT2kMTcWPRZvzqg==
58 | dependencies:
59 | "@types/node" "*"
60 | playwright-core "1.36.1"
61 | optionalDependencies:
62 | fsevents "2.3.2"
63 |
64 | "@polka/url@^1.0.0-next.20":
65 | version "1.0.0-next.21"
66 | resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1"
67 | integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==
68 |
69 | "@sveltejs/adapter-auto@next":
70 | version "1.0.0-next.88"
71 | resolved "https://registry.yarnpkg.com/@sveltejs/adapter-auto/-/adapter-auto-1.0.0-next.88.tgz#21fd124443252bd3b9e164da7013cea1696034e1"
72 | integrity sha512-WcbELnu0Dz/72T1gbhDHxmNevVMEnQeNVi2IYrEj/uEKSMet4LOIrPv3DAAl8NETgBfzNJbMQpE7r9yL67CMTA==
73 | dependencies:
74 | import-meta-resolve "^2.1.0"
75 |
76 | "@sveltejs/kit@next":
77 | version "1.0.0-next.553"
78 | resolved "https://registry.yarnpkg.com/@sveltejs/kit/-/kit-1.0.0-next.553.tgz#5c3a4b013a80a8d8c542087245d7723ab8350101"
79 | integrity sha512-6gUPzmry+j/0uFJnGAJNru3lUpGY12We1Vsom5nDeRaG/Cs2wCwK8gMO2B5taZsHmryIB8zA+v0QCcu5kClmWA==
80 | dependencies:
81 | "@sveltejs/vite-plugin-svelte" "^1.1.0"
82 | "@types/cookie" "^0.5.1"
83 | cookie "^0.5.0"
84 | devalue "^4.2.0"
85 | kleur "^4.1.5"
86 | magic-string "^0.26.7"
87 | mime "^3.0.0"
88 | sade "^1.8.1"
89 | set-cookie-parser "^2.5.1"
90 | sirv "^2.0.2"
91 | tiny-glob "^0.2.9"
92 | undici "5.12.0"
93 |
94 | "@sveltejs/package@^1.0.0-next.6":
95 | version "1.0.0-next.6"
96 | resolved "https://registry.yarnpkg.com/@sveltejs/package/-/package-1.0.0-next.6.tgz#b6acf4e6b81eda66e9efd897d54a85e212533daf"
97 | integrity sha512-EwekVYRnD1r0deTt+9OKRvDopdX59FdfMVV+sN/LtIRO8+mEYuuDkj4uurt5yA9o2sMsv9xiAIWT5qKrq0rXuQ==
98 | dependencies:
99 | chokidar "^3.5.3"
100 | kleur "^4.1.5"
101 | sade "^1.8.1"
102 | svelte2tsx "~0.5.20"
103 |
104 | "@sveltejs/vite-plugin-svelte@^1.1.0":
105 | version "1.2.0"
106 | resolved "https://registry.yarnpkg.com/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-1.2.0.tgz#35755db7ab4e2fa9b47f0c5ca8fd439d62f34546"
107 | integrity sha512-DT2oUkWAloH1tO7X5cQ4uDxQofaIS76skyFMElKtoqT6HJao+D82LI5i+0jPaSSmO7ex3Pa6jGYMlWy9ZJ1cdQ==
108 | dependencies:
109 | debug "^4.3.4"
110 | deepmerge "^4.2.2"
111 | kleur "^4.1.5"
112 | magic-string "^0.26.7"
113 | svelte-hmr "^0.15.1"
114 | vitefu "^0.2.1"
115 |
116 | "@types/cookie@^0.5.1":
117 | version "0.5.1"
118 | resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.5.1.tgz#b29aa1f91a59f35e29ff8f7cb24faf1a3a750554"
119 | integrity sha512-COUnqfB2+ckwXXSFInsFdOAWQzCCx+a5hq2ruyj+Vjund94RJQd4LG2u9hnvJrTgunKAaax7ancBYlDrNYxA0g==
120 |
121 | "@types/node@*":
122 | version "18.11.9"
123 | resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.9.tgz#02d013de7058cea16d36168ef2fc653464cfbad4"
124 | integrity sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==
125 |
126 | "@types/pug@^2.0.4":
127 | version "2.0.6"
128 | resolved "https://registry.yarnpkg.com/@types/pug/-/pug-2.0.6.tgz#f830323c88172e66826d0bde413498b61054b5a6"
129 | integrity sha512-SnHmG9wN1UVmagJOnyo/qkk0Z7gejYxOYYmaAwr5u2yFYfsupN3sg10kyzN8Hep/2zbHxCnsumxOoRIRMBwKCg==
130 |
131 | "@types/sass@^1.16.0":
132 | version "1.43.1"
133 | resolved "https://registry.yarnpkg.com/@types/sass/-/sass-1.43.1.tgz#86bb0168e9e881d7dade6eba16c9ed6d25dc2f68"
134 | integrity sha512-BPdoIt1lfJ6B7rw35ncdwBZrAssjcwzI5LByIrYs+tpXlj/CAkuVdRsgZDdP4lq5EjyWzwxZCqAoFyHKFwp32g==
135 | dependencies:
136 | "@types/node" "*"
137 |
138 | anymatch@~3.1.2:
139 | version "3.1.2"
140 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
141 | integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
142 | dependencies:
143 | normalize-path "^3.0.0"
144 | picomatch "^2.0.4"
145 |
146 | balanced-match@^1.0.0:
147 | version "1.0.2"
148 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
149 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
150 |
151 | binary-extensions@^2.0.0:
152 | version "2.2.0"
153 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
154 | integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
155 |
156 | brace-expansion@^1.1.7:
157 | version "1.1.11"
158 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
159 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
160 | dependencies:
161 | balanced-match "^1.0.0"
162 | concat-map "0.0.1"
163 |
164 | braces@^3.0.2, braces@~3.0.2:
165 | version "3.0.2"
166 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
167 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
168 | dependencies:
169 | fill-range "^7.0.1"
170 |
171 | buffer-crc32@^0.2.5:
172 | version "0.2.13"
173 | resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
174 | integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
175 |
176 | busboy@^1.6.0:
177 | version "1.6.0"
178 | resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
179 | integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
180 | dependencies:
181 | streamsearch "^1.1.0"
182 |
183 | callsites@^3.0.0:
184 | version "3.1.0"
185 | resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
186 | integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
187 |
188 | chokidar@^3.4.1, chokidar@^3.5.3:
189 | version "3.5.3"
190 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
191 | integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
192 | dependencies:
193 | anymatch "~3.1.2"
194 | braces "~3.0.2"
195 | glob-parent "~5.1.2"
196 | is-binary-path "~2.1.0"
197 | is-glob "~4.0.1"
198 | normalize-path "~3.0.0"
199 | readdirp "~3.6.0"
200 | optionalDependencies:
201 | fsevents "~2.3.2"
202 |
203 | concat-map@0.0.1:
204 | version "0.0.1"
205 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
206 | integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
207 |
208 | cookie@^0.5.0:
209 | version "0.5.0"
210 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b"
211 | integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==
212 |
213 | debug@^4.3.4:
214 | version "4.3.4"
215 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
216 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
217 | dependencies:
218 | ms "2.1.2"
219 |
220 | dedent-js@^1.0.1:
221 | version "1.0.1"
222 | resolved "https://registry.yarnpkg.com/dedent-js/-/dedent-js-1.0.1.tgz#bee5fb7c9e727d85dffa24590d10ec1ab1255305"
223 | integrity sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==
224 |
225 | deepmerge@^4.2.2:
226 | version "4.2.2"
227 | resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
228 | integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
229 |
230 | detect-indent@^6.0.0:
231 | version "6.1.0"
232 | resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6"
233 | integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==
234 |
235 | devalue@^4.2.0:
236 | version "4.2.0"
237 | resolved "https://registry.yarnpkg.com/devalue/-/devalue-4.2.0.tgz#3f542d7c828e317bab5fd3bcecde210af8f83d4b"
238 | integrity sha512-mbjoAaCL2qogBKgeFxFPOXAUsZchircF+B/79LD4sHH0+NHfYm8gZpQrskKDn5gENGt35+5OI1GUF7hLVnkPDw==
239 |
240 | es6-promise@^3.1.2:
241 | version "3.3.1"
242 | resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613"
243 | integrity sha512-SOp9Phqvqn7jtEUxPWdWfWoLmyt2VaJ6MpvP9Comy1MceMXqE6bxvaTu4iaxpYYPzhny28Lc+M87/c2cPK6lDg==
244 |
245 | esbuild-android-64@0.15.14:
246 | version "0.15.14"
247 | resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.15.14.tgz#114e55b0d58fb7b45d7fa3d93516bd13fc8869cc"
248 | integrity sha512-HuilVIb4rk9abT4U6bcFdU35UHOzcWVGLSjEmC58OVr96q5UiRqzDtWjPlCMugjhgUGKEs8Zf4ueIvYbOStbIg==
249 |
250 | esbuild-android-arm64@0.15.14:
251 | version "0.15.14"
252 | resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.15.14.tgz#8541f38a9aacf88e574fb13f5ad4ca51a04c12bb"
253 | integrity sha512-/QnxRVxsR2Vtf3XottAHj7hENAMW2wCs6S+OZcAbc/8nlhbAL/bCQRCVD78VtI5mdwqWkVi3wMqM94kScQCgqg==
254 |
255 | esbuild-darwin-64@0.15.14:
256 | version "0.15.14"
257 | resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.15.14.tgz#b40b334db81ff1e3677a6712b23761748a157c57"
258 | integrity sha512-ToNuf1uifu8hhwWvoZJGCdLIX/1zpo8cOGnT0XAhDQXiKOKYaotVNx7pOVB1f+wHoWwTLInrOmh3EmA7Fd+8Vg==
259 |
260 | esbuild-darwin-arm64@0.15.14:
261 | version "0.15.14"
262 | resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.14.tgz#44b5c1477bb7bdb852dd905e906f68765e2828bc"
263 | integrity sha512-KgGP+y77GszfYJgceO0Wi/PiRtYo5y2Xo9rhBUpxTPaBgWDJ14gqYN0+NMbu+qC2fykxXaipHxN4Scaj9tUS1A==
264 |
265 | esbuild-freebsd-64@0.15.14:
266 | version "0.15.14"
267 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.14.tgz#8c57315d238690f34b6ed0c94e5cfc04c858247a"
268 | integrity sha512-xr0E2n5lyWw3uFSwwUXHc0EcaBDtsal/iIfLioflHdhAe10KSctV978Te7YsfnsMKzcoGeS366+tqbCXdqDHQA==
269 |
270 | esbuild-freebsd-arm64@0.15.14:
271 | version "0.15.14"
272 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.14.tgz#2e92acca09258daa849e635565f52469266f0b7b"
273 | integrity sha512-8XH96sOQ4b1LhMlO10eEWOjEngmZ2oyw3pW4o8kvBcpF6pULr56eeYVP5radtgw54g3T8nKHDHYEI5AItvskZg==
274 |
275 | esbuild-linux-32@0.15.14:
276 | version "0.15.14"
277 | resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.15.14.tgz#ca5ed3e9dff82df486ddde362d7e00775a597dfd"
278 | integrity sha512-6ssnvwaTAi8AzKN8By2V0nS+WF5jTP7SfuK6sStGnDP7MCJo/4zHgM9oE1eQTS2jPmo3D673rckuCzRlig+HMA==
279 |
280 | esbuild-linux-64@0.15.14:
281 | version "0.15.14"
282 | resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.15.14.tgz#42952e1d08a299d5f573c567639fb37b033befbf"
283 | integrity sha512-ONySx3U0wAJOJuxGUlXBWxVKFVpWv88JEv0NZ6NlHknmDd1yCbf4AEdClSgLrqKQDXYywmw4gYDvdLsS6z0hcw==
284 |
285 | esbuild-linux-arm64@0.15.14:
286 | version "0.15.14"
287 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.14.tgz#0c0d788099703327ec0ae70758cb2639ef6c5d88"
288 | integrity sha512-kle2Ov6a1e5AjlHlMQl1e+c4myGTeggrRzArQFmWp6O6JoqqB9hT+B28EW4tjFWgV/NxUq46pWYpgaWXsXRPAg==
289 |
290 | esbuild-linux-arm@0.15.14:
291 | version "0.15.14"
292 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.15.14.tgz#751a5ca5042cd60f669b07c3bcec3dd6c4f8151c"
293 | integrity sha512-D2LImAIV3QzL7lHURyCHBkycVFbKwkDb1XEUWan+2fb4qfW7qAeUtul7ZIcIwFKZgPcl+6gKZmvLgPSj26RQ2Q==
294 |
295 | esbuild-linux-mips64le@0.15.14:
296 | version "0.15.14"
297 | resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.14.tgz#da8ac35f2704de0b52bf53a99c12f604fbe9b916"
298 | integrity sha512-FVdMYIzOLXUq+OE7XYKesuEAqZhmAIV6qOoYahvUp93oXy0MOVTP370ECbPfGXXUdlvc0TNgkJa3YhEwyZ6MRA==
299 |
300 | esbuild-linux-ppc64le@0.15.14:
301 | version "0.15.14"
302 | resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.14.tgz#a315b5016917429080c3d32e03319f1ff876ac55"
303 | integrity sha512-2NzH+iuzMDA+jjtPjuIz/OhRDf8tzbQ1tRZJI//aT25o1HKc0reMMXxKIYq/8nSHXiJSnYV4ODzTiv45s+h73w==
304 |
305 | esbuild-linux-riscv64@0.15.14:
306 | version "0.15.14"
307 | resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.14.tgz#9f2e0a935e5086d398fc19c7ff5d217bfefe3e12"
308 | integrity sha512-VqxvutZNlQxmUNS7Ac+aczttLEoHBJ9e3OYGqnULrfipRvG97qLrAv9EUY9iSrRKBqeEbSvS9bSfstZqwz0T4Q==
309 |
310 | esbuild-linux-s390x@0.15.14:
311 | version "0.15.14"
312 | resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.14.tgz#53108112faff5a4e1bad17f7b0b0ffa1df4b7efb"
313 | integrity sha512-+KVHEUshX5n6VP6Vp/AKv9fZIl5kr2ph8EUFmQUJnDpHwcfTSn2AQgYYm0HTBR2Mr4d0Wlr0FxF/Cs5pbFgiOw==
314 |
315 | esbuild-netbsd-64@0.15.14:
316 | version "0.15.14"
317 | resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.14.tgz#5330efc41fe4f1c2bab5462bcfe7a4ffce7ba00a"
318 | integrity sha512-6D/dr17piEgevIm1xJfZP2SjB9Z+g8ERhNnBdlZPBWZl+KSPUKLGF13AbvC+nzGh8IxOH2TyTIdRMvKMP0nEzQ==
319 |
320 | esbuild-openbsd-64@0.15.14:
321 | version "0.15.14"
322 | resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.14.tgz#ee64944d863e937611fc31adf349e9bb4f5f7eac"
323 | integrity sha512-rREQBIlMibBetgr2E9Lywt2Qxv2ZdpmYahR4IUlAQ1Efv/A5gYdO0/VIN3iowDbCNTLxp0bb57Vf0LFcffD6kA==
324 |
325 | esbuild-sunos-64@0.15.14:
326 | version "0.15.14"
327 | resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.15.14.tgz#29b0b20de6fe6ef50f9fbe533ec20dc4b595f9aa"
328 | integrity sha512-DNVjSp/BY4IfwtdUAvWGIDaIjJXY5KI4uD82+15v6k/w7px9dnaDaJJ2R6Mu+KCgr5oklmFc0KjBjh311Gxl9Q==
329 |
330 | esbuild-windows-32@0.15.14:
331 | version "0.15.14"
332 | resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.15.14.tgz#05e9b159d664809f7a4a8a68ed048d193457b27d"
333 | integrity sha512-pHBWrcA+/oLgvViuG9FO3kNPO635gkoVrRQwe6ZY1S0jdET07xe2toUvQoJQ8KT3/OkxqUasIty5hpuKFLD+eg==
334 |
335 | esbuild-windows-64@0.15.14:
336 | version "0.15.14"
337 | resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.15.14.tgz#d5ae086728ab30b72969e40ed0a7a0d9082f2cdd"
338 | integrity sha512-CszIGQVk/P8FOS5UgAH4hKc9zOaFo69fe+k1rqgBHx3CSK3Opyk5lwYriIamaWOVjBt7IwEP6NALz+tkVWdFog==
339 |
340 | esbuild-windows-arm64@0.15.14:
341 | version "0.15.14"
342 | resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.14.tgz#8eb50ab9a0ecaf058593fbad17502749306f801d"
343 | integrity sha512-KW9W4psdZceaS9A7Jsgl4WialOznSURvqX/oHZk3gOP7KbjtHLSsnmSvNdzagGJfxbAe30UVGXRe8q8nDsOSQw==
344 |
345 | esbuild@^0.15.9:
346 | version "0.15.14"
347 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.15.14.tgz#09202b811f1710363d5088a3401a351351c79875"
348 | integrity sha512-pJN8j42fvWLFWwSMG4luuupl2Me7mxciUOsMegKvwCmhEbJ2covUdFnihxm0FMIBV+cbwbtMoHgMCCI+pj1btQ==
349 | optionalDependencies:
350 | "@esbuild/android-arm" "0.15.14"
351 | "@esbuild/linux-loong64" "0.15.14"
352 | esbuild-android-64 "0.15.14"
353 | esbuild-android-arm64 "0.15.14"
354 | esbuild-darwin-64 "0.15.14"
355 | esbuild-darwin-arm64 "0.15.14"
356 | esbuild-freebsd-64 "0.15.14"
357 | esbuild-freebsd-arm64 "0.15.14"
358 | esbuild-linux-32 "0.15.14"
359 | esbuild-linux-64 "0.15.14"
360 | esbuild-linux-arm "0.15.14"
361 | esbuild-linux-arm64 "0.15.14"
362 | esbuild-linux-mips64le "0.15.14"
363 | esbuild-linux-ppc64le "0.15.14"
364 | esbuild-linux-riscv64 "0.15.14"
365 | esbuild-linux-s390x "0.15.14"
366 | esbuild-netbsd-64 "0.15.14"
367 | esbuild-openbsd-64 "0.15.14"
368 | esbuild-sunos-64 "0.15.14"
369 | esbuild-windows-32 "0.15.14"
370 | esbuild-windows-64 "0.15.14"
371 | esbuild-windows-arm64 "0.15.14"
372 |
373 | fast-glob@^3.2.7:
374 | version "3.2.12"
375 | resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.12.tgz#7f39ec99c2e6ab030337142da9e0c18f37afae80"
376 | integrity sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==
377 | dependencies:
378 | "@nodelib/fs.stat" "^2.0.2"
379 | "@nodelib/fs.walk" "^1.2.3"
380 | glob-parent "^5.1.2"
381 | merge2 "^1.3.0"
382 | micromatch "^4.0.4"
383 |
384 | fastq@^1.6.0:
385 | version "1.13.0"
386 | resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
387 | integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==
388 | dependencies:
389 | reusify "^1.0.4"
390 |
391 | fill-range@^7.0.1:
392 | version "7.0.1"
393 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
394 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
395 | dependencies:
396 | to-regex-range "^5.0.1"
397 |
398 | fs.realpath@^1.0.0:
399 | version "1.0.0"
400 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
401 | integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
402 |
403 | fsevents@2.3.2, fsevents@~2.3.2:
404 | version "2.3.2"
405 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
406 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
407 |
408 | function-bind@^1.1.1:
409 | version "1.1.1"
410 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
411 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
412 |
413 | glob-parent@^5.1.2, glob-parent@~5.1.2:
414 | version "5.1.2"
415 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
416 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
417 | dependencies:
418 | is-glob "^4.0.1"
419 |
420 | glob@^7.1.3:
421 | version "7.2.3"
422 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
423 | integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
424 | dependencies:
425 | fs.realpath "^1.0.0"
426 | inflight "^1.0.4"
427 | inherits "2"
428 | minimatch "^3.1.1"
429 | once "^1.3.0"
430 | path-is-absolute "^1.0.0"
431 |
432 | globalyzer@0.1.0:
433 | version "0.1.0"
434 | resolved "https://registry.yarnpkg.com/globalyzer/-/globalyzer-0.1.0.tgz#cb76da79555669a1519d5a8edf093afaa0bf1465"
435 | integrity sha512-40oNTM9UfG6aBmuKxk/giHn5nQ8RVz/SS4Ir6zgzOv9/qC3kKZ9v4etGTcJbEl/NyVQH7FGU7d+X1egr57Md2Q==
436 |
437 | globrex@^0.1.2:
438 | version "0.1.2"
439 | resolved "https://registry.yarnpkg.com/globrex/-/globrex-0.1.2.tgz#dd5d9ec826232730cd6793a5e33a9302985e6098"
440 | integrity sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==
441 |
442 | graceful-fs@^4.1.3:
443 | version "4.2.10"
444 | resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
445 | integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
446 |
447 | has@^1.0.3:
448 | version "1.0.3"
449 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
450 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
451 | dependencies:
452 | function-bind "^1.1.1"
453 |
454 | import-fresh@^3.2.1:
455 | version "3.3.0"
456 | resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
457 | integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
458 | dependencies:
459 | parent-module "^1.0.0"
460 | resolve-from "^4.0.0"
461 |
462 | import-meta-resolve@^2.1.0:
463 | version "2.1.0"
464 | resolved "https://registry.yarnpkg.com/import-meta-resolve/-/import-meta-resolve-2.1.0.tgz#c8952d331ed6e9bb6ad524a7549deb3d34af41ce"
465 | integrity sha512-yG9pxkWJVTy4cmRsNWE3ztFdtFuYIV8G4N+cbCkO8b+qngkLyIUhxQFuZ0qJm67+0nUOxjMPT7nfksPKza1v2g==
466 |
467 | inflight@^1.0.4:
468 | version "1.0.6"
469 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
470 | integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
471 | dependencies:
472 | once "^1.3.0"
473 | wrappy "1"
474 |
475 | inherits@2:
476 | version "2.0.4"
477 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
478 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
479 |
480 | is-binary-path@~2.1.0:
481 | version "2.1.0"
482 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09"
483 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==
484 | dependencies:
485 | binary-extensions "^2.0.0"
486 |
487 | is-core-module@^2.9.0:
488 | version "2.11.0"
489 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144"
490 | integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==
491 | dependencies:
492 | has "^1.0.3"
493 |
494 | is-extglob@^2.1.1:
495 | version "2.1.1"
496 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
497 | integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
498 |
499 | is-glob@^4.0.1, is-glob@~4.0.1:
500 | version "4.0.3"
501 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
502 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
503 | dependencies:
504 | is-extglob "^2.1.1"
505 |
506 | is-number@^7.0.0:
507 | version "7.0.0"
508 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
509 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
510 |
511 | kleur@^4.1.5:
512 | version "4.1.5"
513 | resolved "https://registry.yarnpkg.com/kleur/-/kleur-4.1.5.tgz#95106101795f7050c6c650f350c683febddb1780"
514 | integrity sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==
515 |
516 | lower-case@^2.0.2:
517 | version "2.0.2"
518 | resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
519 | integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==
520 | dependencies:
521 | tslib "^2.0.3"
522 |
523 | magic-string@^0.25.7:
524 | version "0.25.9"
525 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c"
526 | integrity sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==
527 | dependencies:
528 | sourcemap-codec "^1.4.8"
529 |
530 | magic-string@^0.26.7:
531 | version "0.26.7"
532 | resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.26.7.tgz#caf7daf61b34e9982f8228c4527474dac8981d6f"
533 | integrity sha512-hX9XH3ziStPoPhJxLq1syWuZMxbDvGNbVchfrdCtanC7D13888bMFow61x8axrx+GfHLtVeAx2kxL7tTGRl+Ow==
534 | dependencies:
535 | sourcemap-codec "^1.4.8"
536 |
537 | merge2@^1.3.0:
538 | version "1.4.1"
539 | resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
540 | integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
541 |
542 | micromatch@^4.0.4:
543 | version "4.0.5"
544 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
545 | integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
546 | dependencies:
547 | braces "^3.0.2"
548 | picomatch "^2.3.1"
549 |
550 | mime@^3.0.0:
551 | version "3.0.0"
552 | resolved "https://registry.yarnpkg.com/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7"
553 | integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==
554 |
555 | min-indent@^1.0.0:
556 | version "1.0.1"
557 | resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869"
558 | integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==
559 |
560 | minimatch@^3.1.1:
561 | version "3.1.2"
562 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
563 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
564 | dependencies:
565 | brace-expansion "^1.1.7"
566 |
567 | minimist@^1.2.0, minimist@^1.2.6:
568 | version "1.2.7"
569 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.7.tgz#daa1c4d91f507390437c6a8bc01078e7000c4d18"
570 | integrity sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==
571 |
572 | mkdirp@^0.5.1:
573 | version "0.5.6"
574 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6"
575 | integrity sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==
576 | dependencies:
577 | minimist "^1.2.6"
578 |
579 | mri@^1.1.0:
580 | version "1.2.0"
581 | resolved "https://registry.yarnpkg.com/mri/-/mri-1.2.0.tgz#6721480fec2a11a4889861115a48b6cbe7cc8f0b"
582 | integrity sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==
583 |
584 | mrmime@^1.0.0:
585 | version "1.0.1"
586 | resolved "https://registry.yarnpkg.com/mrmime/-/mrmime-1.0.1.tgz#5f90c825fad4bdd41dc914eff5d1a8cfdaf24f27"
587 | integrity sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==
588 |
589 | ms@2.1.2:
590 | version "2.1.2"
591 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
592 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
593 |
594 | nanoid@^3.3.4:
595 | version "3.3.4"
596 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
597 | integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
598 |
599 | no-case@^3.0.4:
600 | version "3.0.4"
601 | resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
602 | integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==
603 | dependencies:
604 | lower-case "^2.0.2"
605 | tslib "^2.0.3"
606 |
607 | normalize-path@^3.0.0, normalize-path@~3.0.0:
608 | version "3.0.0"
609 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
610 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
611 |
612 | once@^1.3.0:
613 | version "1.4.0"
614 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
615 | integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
616 | dependencies:
617 | wrappy "1"
618 |
619 | parent-module@^1.0.0:
620 | version "1.0.1"
621 | resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
622 | integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
623 | dependencies:
624 | callsites "^3.0.0"
625 |
626 | pascal-case@^3.1.1:
627 | version "3.1.2"
628 | resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb"
629 | integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==
630 | dependencies:
631 | no-case "^3.0.4"
632 | tslib "^2.0.3"
633 |
634 | path-is-absolute@^1.0.0:
635 | version "1.0.1"
636 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
637 | integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
638 |
639 | path-parse@^1.0.7:
640 | version "1.0.7"
641 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
642 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
643 |
644 | picocolors@^1.0.0:
645 | version "1.0.0"
646 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
647 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
648 |
649 | picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
650 | version "2.3.1"
651 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
652 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
653 |
654 | playwright-core@1.36.1:
655 | version "1.36.1"
656 | resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.36.1.tgz#f5f275d70548768ca892583519c89b237a381c77"
657 | integrity sha512-7+tmPuMcEW4xeCL9cp9KxmYpQYHKkyjwoXRnoeTowaeNat8PoBMk/HwCYhqkH2fRkshfKEOiVus/IhID2Pg8kg==
658 |
659 | postcss@^8.4.18:
660 | version "8.4.19"
661 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.19.tgz#61178e2add236b17351897c8bcc0b4c8ecab56fc"
662 | integrity sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==
663 | dependencies:
664 | nanoid "^3.3.4"
665 | picocolors "^1.0.0"
666 | source-map-js "^1.0.2"
667 |
668 | prettier-plugin-svelte@^2.8.1:
669 | version "2.8.1"
670 | resolved "https://registry.yarnpkg.com/prettier-plugin-svelte/-/prettier-plugin-svelte-2.8.1.tgz#5b7df5bd0d953b133281607295a3e5131052bb8b"
671 | integrity sha512-KA3K1J3/wKDnCxW7ZDRA/QL2Q67N7Xs3gOERqJ5X1qFjq1DdnN3K1R29scSKwh+kA8FF67pXbYytUpvN/i3iQw==
672 |
673 | prettier@^2.7.1:
674 | version "2.7.1"
675 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64"
676 | integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==
677 |
678 | queue-microtask@^1.2.2:
679 | version "1.2.3"
680 | resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
681 | integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
682 |
683 | readdirp@~3.6.0:
684 | version "3.6.0"
685 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7"
686 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==
687 | dependencies:
688 | picomatch "^2.2.1"
689 |
690 | resolve-from@^4.0.0:
691 | version "4.0.0"
692 | resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
693 | integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
694 |
695 | resolve@^1.22.1:
696 | version "1.22.1"
697 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
698 | integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
699 | dependencies:
700 | is-core-module "^2.9.0"
701 | path-parse "^1.0.7"
702 | supports-preserve-symlinks-flag "^1.0.0"
703 |
704 | reusify@^1.0.4:
705 | version "1.0.4"
706 | resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
707 | integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
708 |
709 | rimraf@^2.5.2:
710 | version "2.7.1"
711 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
712 | integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
713 | dependencies:
714 | glob "^7.1.3"
715 |
716 | rollup@^2.79.1:
717 | version "2.79.1"
718 | resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.79.1.tgz#bedee8faef7c9f93a2647ac0108748f497f081c7"
719 | integrity sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==
720 | optionalDependencies:
721 | fsevents "~2.3.2"
722 |
723 | run-parallel@^1.1.9:
724 | version "1.2.0"
725 | resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
726 | integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
727 | dependencies:
728 | queue-microtask "^1.2.2"
729 |
730 | sade@^1.7.4, sade@^1.8.1:
731 | version "1.8.1"
732 | resolved "https://registry.yarnpkg.com/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701"
733 | integrity sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==
734 | dependencies:
735 | mri "^1.1.0"
736 |
737 | sander@^0.5.0:
738 | version "0.5.1"
739 | resolved "https://registry.yarnpkg.com/sander/-/sander-0.5.1.tgz#741e245e231f07cafb6fdf0f133adfa216a502ad"
740 | integrity sha512-3lVqBir7WuKDHGrKRDn/1Ye3kwpXaDOMsiRP1wd6wpZW56gJhsbp5RqQpA6JG/P+pkXizygnr1dKR8vzWaVsfA==
741 | dependencies:
742 | es6-promise "^3.1.2"
743 | graceful-fs "^4.1.3"
744 | mkdirp "^0.5.1"
745 | rimraf "^2.5.2"
746 |
747 | set-cookie-parser@^2.5.1:
748 | version "2.5.1"
749 | resolved "https://registry.yarnpkg.com/set-cookie-parser/-/set-cookie-parser-2.5.1.tgz#ddd3e9a566b0e8e0862aca974a6ac0e01349430b"
750 | integrity sha512-1jeBGaKNGdEq4FgIrORu/N570dwoPYio8lSoYLWmX7sQ//0JY08Xh9o5pBcgmHQ/MbsYp/aZnOe1s1lIsbLprQ==
751 |
752 | sirv@^2.0.2:
753 | version "2.0.2"
754 | resolved "https://registry.yarnpkg.com/sirv/-/sirv-2.0.2.tgz#128b9a628d77568139cff85703ad5497c46a4760"
755 | integrity sha512-4Qog6aE29nIjAOKe/wowFTxOdmbEZKb+3tsLljaBRzJwtqto0BChD2zzH0LhgCSXiI+V7X+Y45v14wBZQ1TK3w==
756 | dependencies:
757 | "@polka/url" "^1.0.0-next.20"
758 | mrmime "^1.0.0"
759 | totalist "^3.0.0"
760 |
761 | sorcery@^0.10.0:
762 | version "0.10.0"
763 | resolved "https://registry.yarnpkg.com/sorcery/-/sorcery-0.10.0.tgz#8ae90ad7d7cb05fc59f1ab0c637845d5c15a52b7"
764 | integrity sha512-R5ocFmKZQFfSTstfOtHjJuAwbpGyf9qjQa1egyhvXSbM7emjrtLXtGdZsDJDABC85YBfVvrOiGWKSYXPKdvP1g==
765 | dependencies:
766 | buffer-crc32 "^0.2.5"
767 | minimist "^1.2.0"
768 | sander "^0.5.0"
769 | sourcemap-codec "^1.3.0"
770 |
771 | source-map-js@^1.0.2:
772 | version "1.0.2"
773 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
774 | integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
775 |
776 | sourcemap-codec@^1.3.0, sourcemap-codec@^1.4.8:
777 | version "1.4.8"
778 | resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
779 | integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
780 |
781 | streamsearch@^1.1.0:
782 | version "1.1.0"
783 | resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
784 | integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
785 |
786 | strip-indent@^3.0.0:
787 | version "3.0.0"
788 | resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001"
789 | integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==
790 | dependencies:
791 | min-indent "^1.0.0"
792 |
793 | supports-preserve-symlinks-flag@^1.0.0:
794 | version "1.0.0"
795 | resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
796 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
797 |
798 | svelte-check@^2.7.1:
799 | version "2.9.2"
800 | resolved "https://registry.yarnpkg.com/svelte-check/-/svelte-check-2.9.2.tgz#6581b750aa51cf476e82139a674a9bfa4de2ec2d"
801 | integrity sha512-DRi8HhnCiqiGR2YF9ervPGvtoYrheE09cXieCTEqeTPOTJzfoa54Py8rovIBv4bH4n5HgZYIyTQ3DDLHQLl2uQ==
802 | dependencies:
803 | "@jridgewell/trace-mapping" "^0.3.9"
804 | chokidar "^3.4.1"
805 | fast-glob "^3.2.7"
806 | import-fresh "^3.2.1"
807 | picocolors "^1.0.0"
808 | sade "^1.7.4"
809 | svelte-preprocess "^4.0.0"
810 | typescript "*"
811 |
812 | svelte-hmr@^0.15.1:
813 | version "0.15.1"
814 | resolved "https://registry.yarnpkg.com/svelte-hmr/-/svelte-hmr-0.15.1.tgz#d11d878a0bbb12ec1cba030f580cd2049f4ec86b"
815 | integrity sha512-BiKB4RZ8YSwRKCNVdNxK/GfY+r4Kjgp9jCLEy0DuqAKfmQtpL38cQK3afdpjw4sqSs4PLi3jIPJIFp259NkZtA==
816 |
817 | svelte-preprocess@^4.0.0, svelte-preprocess@^4.10.6:
818 | version "4.10.7"
819 | resolved "https://registry.yarnpkg.com/svelte-preprocess/-/svelte-preprocess-4.10.7.tgz#3626de472f51ffe20c9bc71eff5a3da66797c362"
820 | integrity sha512-sNPBnqYD6FnmdBrUmBCaqS00RyCsCpj2BG58A1JBswNF7b0OKviwxqVrOL/CKyJrLSClrSeqQv5BXNg2RUbPOw==
821 | dependencies:
822 | "@types/pug" "^2.0.4"
823 | "@types/sass" "^1.16.0"
824 | detect-indent "^6.0.0"
825 | magic-string "^0.25.7"
826 | sorcery "^0.10.0"
827 | strip-indent "^3.0.0"
828 |
829 | svelte2tsx@~0.5.20:
830 | version "0.5.20"
831 | resolved "https://registry.yarnpkg.com/svelte2tsx/-/svelte2tsx-0.5.20.tgz#0ac5411b53d6b6913b12e12a02c03ce9eb13e945"
832 | integrity sha512-yNHmN/uoAnJ7d1XqVohiNA6TMFOxibHyEddUAHVt1PiLXtbwAJF3WaGYlg8QbOdoXzOVsVNCAlqRUIdULUm+OA==
833 | dependencies:
834 | dedent-js "^1.0.1"
835 | pascal-case "^3.1.1"
836 |
837 | svelte@^3.46.0:
838 | version "3.59.2"
839 | resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.59.2.tgz#a137b28e025a181292b2ae2e3dca90bf8ec73aec"
840 | integrity sha512-vzSyuGr3eEoAtT/A6bmajosJZIUWySzY2CzB3w2pgPvnkUjGqlDnsNnA0PMO+mMAhuyMul6C2uuZzY6ELSkzyA==
841 |
842 | tiny-glob@^0.2.9:
843 | version "0.2.9"
844 | resolved "https://registry.yarnpkg.com/tiny-glob/-/tiny-glob-0.2.9.tgz#2212d441ac17928033b110f8b3640683129d31e2"
845 | integrity sha512-g/55ssRPUjShh+xkfx9UPDXqhckHEsHr4Vd9zX55oSdGZc/MD0m3sferOkwWtp98bv+kcVfEHtRJgBVJzelrzg==
846 | dependencies:
847 | globalyzer "0.1.0"
848 | globrex "^0.1.2"
849 |
850 | to-regex-range@^5.0.1:
851 | version "5.0.1"
852 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
853 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
854 | dependencies:
855 | is-number "^7.0.0"
856 |
857 | totalist@^3.0.0:
858 | version "3.0.0"
859 | resolved "https://registry.yarnpkg.com/totalist/-/totalist-3.0.0.tgz#4ef9c58c5f095255cdc3ff2a0a55091c57a3a1bd"
860 | integrity sha512-eM+pCBxXO/njtF7vdFsHuqb+ElbxqtI4r5EAvk6grfAFyJ6IvWlSkfZ5T9ozC6xWw3Fj1fGoSmrl0gUs46JVIw==
861 |
862 | tslib@^2.0.3, tslib@^2.3.1:
863 | version "2.4.1"
864 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.4.1.tgz#0d0bfbaac2880b91e22df0768e55be9753a5b17e"
865 | integrity sha512-tGyy4dAjRIEwI7BzsB0lynWgOpfqjUdq91XXAlIWD2OwKBH7oCl/GZG/HT4BOHrTlPMOASlMQ7veyTqpmRcrNA==
866 |
867 | typescript@*, typescript@^4.7.4:
868 | version "4.9.3"
869 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.3.tgz#3aea307c1746b8c384435d8ac36b8a2e580d85db"
870 | integrity sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==
871 |
872 | undici@5.12.0:
873 | version "5.12.0"
874 | resolved "https://registry.yarnpkg.com/undici/-/undici-5.12.0.tgz#c758ffa704fbcd40d506e4948860ccaf4099f531"
875 | integrity sha512-zMLamCG62PGjd9HHMpo05bSLvvwWOZgGeiWlN/vlqu3+lRo3elxktVGEyLMX+IO7c2eflLjcW74AlkhEZm15mg==
876 | dependencies:
877 | busboy "^1.6.0"
878 |
879 | vite@^3.1.0:
880 | version "3.2.4"
881 | resolved "https://registry.yarnpkg.com/vite/-/vite-3.2.4.tgz#d8c7892dd4268064e04fffbe7d866207dd24166e"
882 | integrity sha512-Z2X6SRAffOUYTa+sLy3NQ7nlHFU100xwanq1WDwqaiFiCe+25zdxP1TfCS5ojPV2oDDcXudHIoPnI1Z/66B7Yw==
883 | dependencies:
884 | esbuild "^0.15.9"
885 | postcss "^8.4.18"
886 | resolve "^1.22.1"
887 | rollup "^2.79.1"
888 | optionalDependencies:
889 | fsevents "~2.3.2"
890 |
891 | vitefu@^0.2.1:
892 | version "0.2.1"
893 | resolved "https://registry.yarnpkg.com/vitefu/-/vitefu-0.2.1.tgz#9dcd78737c77b366206706dac2403a751903907d"
894 | integrity sha512-clkvXTAeUf+XQKm3bhWUhT4pye+3acm6YCTGaWhxxIvZZ/QjnA3JA8Zud+z/mO5y5XYvJJhevs5Sjkv/FI8nRw==
895 |
896 | wrappy@1:
897 | version "1.0.2"
898 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
899 | integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
900 |
--------------------------------------------------------------------------------