From 9436206cb9ed9121dbc216ad096c73e8806c6f47 Mon Sep 17 00:00:00 2001 From: Renato Moor Date: Mon, 28 Oct 2024 09:46:43 +0100 Subject: [PATCH] feat: add svelte 5 package --- package.json | 5 +- packages/svelte/.gitignore | 24 ++++ packages/svelte/LICENSE.md | 21 +++ packages/svelte/README.md | 31 +++++ packages/svelte/package.json | 40 ++++++ packages/svelte/src/index.svelte.ts | 198 ++++++++++++++++++++++++++++ packages/svelte/src/types.ts | 24 ++++ packages/svelte/tsconfig.json | 15 +++ 8 files changed, 356 insertions(+), 2 deletions(-) create mode 100644 packages/svelte/.gitignore create mode 100644 packages/svelte/LICENSE.md create mode 100644 packages/svelte/README.md create mode 100644 packages/svelte/package.json create mode 100644 packages/svelte/src/index.svelte.ts create mode 100644 packages/svelte/src/types.ts create mode 100644 packages/svelte/tsconfig.json diff --git a/package.json b/package.json index ff11e35..5a01e57 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,11 @@ "packages/react-inertia", "packages/vue", "packages/vue-inertia", - "packages/alpine" + "packages/alpine", + "packages/svelte" ], "scripts": { - "watch": "npx concurrently \"npm run watch --workspace=packages/core\" \"npm run watch --workspace=packages/react\" \"npm run watch --workspace=packages/react-inertia\" \"npm run watch --workspace=packages/vue\" \"npm run watch --workspace=packages/vue-inertia\" \"npm run watch --workspace=packages/alpine\" --names=core,react,react-inertia,vue,vue-inertia,alpine", + "watch": "npx concurrently \"npm run watch --workspace=packages/core\" \"npm run watch --workspace=packages/react\" \"npm run watch --workspace=packages/react-inertia\" \"npm run watch --workspace=packages/vue\" \"npm run watch --workspace=packages/vue-inertia\" \"npm run watch --workspace=packages/alpine\" \"npm run watch --workspace=packages/svelte\" --names=core,react,react-inertia,vue,vue-inertia,alpine,svelte", "build": "npm run build --workspaces", "link": "npm link --workspaces", "typeCheck": "npm run typeCheck --workspaces", diff --git a/packages/svelte/.gitignore b/packages/svelte/.gitignore new file mode 100644 index 0000000..16b6089 --- /dev/null +++ b/packages/svelte/.gitignore @@ -0,0 +1,24 @@ +node_modules + +# Output +.output +.vercel +/.svelte-kit +/build +/dist + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* + +pnpm-lock.yaml diff --git a/packages/svelte/LICENSE.md b/packages/svelte/LICENSE.md new file mode 100644 index 0000000..79810c8 --- /dev/null +++ b/packages/svelte/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) Taylor Otwell + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/packages/svelte/README.md b/packages/svelte/README.md new file mode 100644 index 0000000..0b20012 --- /dev/null +++ b/packages/svelte/README.md @@ -0,0 +1,31 @@ +# Laravel Precognition + +Test Status +Build Status +Total Downloads +Latest Stable Version +License + +## Introduction + +Laravel Precognition allows you to anticipate the outcome of a future HTTP request. One of the primary use cases of Precognition is the ability to provide "live" validation in your frontend application. + +## Official Documentation + +Documentation for Laravel Precognition can be found on the [Laravel website](https://laravel.com/docs/precognition). + +## Contributing + +Thank you for considering contributing to Laravel Precognition! The contribution guide can be found in the [Laravel documentation](https://laravel.com/docs/contributions). + +## Code of Conduct + +In order to ensure that the Laravel community is welcoming to all, please review and abide by the [Code of Conduct](https://laravel.com/docs/contributions#code-of-conduct). + +## Security Vulnerabilities + +Please review [our security policy](https://github.com/laravel/precognition/security/policy) on how to report security vulnerabilities. + +## License + +Laravel Precognition is open-sourced software licensed under the [MIT license](LICENSE.md). diff --git a/packages/svelte/package.json b/packages/svelte/package.json new file mode 100644 index 0000000..b574591 --- /dev/null +++ b/packages/svelte/package.json @@ -0,0 +1,40 @@ +{ + "name": "laravel-precognition-svelte", + "version": "0.0.1", + "description": "Laravel Precognition (Svelte).", + "keywords": [ + "laravel", + "precognition", + "svelte" + ], + "homepage": "https://github.com/laravel/precognition", + "type": "module", + "repository": { + "type": "git", + "url": "https://github.com/laravel/precognition" + }, + "license": "MIT", + "author": "Laravel", + "main": "dist/index.svelte.js", + "files": [ + "/dist" + ], + "scripts": { + "watch": "rm -rf dist && tsc --watch", + "build": "rm -rf dist && tsc", + "typeCheck": "tsc --noEmit", + "prepublishOnly": "npm run build", + "version": "npm pkg set dependencies.laravel-precognition=$npm_package_version" + }, + "peerDependencies": { + "svelte": "^5.0.0" + }, + "dependencies": { + "laravel-precognition": "0.5.11", + "lodash-es": "^4.17.21" + }, + "devDependencies": { + "@types/lodash-es": "^4.17.12", + "typescript": "^5.0.0" + } +} diff --git a/packages/svelte/src/index.svelte.ts b/packages/svelte/src/index.svelte.ts new file mode 100644 index 0000000..9152288 --- /dev/null +++ b/packages/svelte/src/index.svelte.ts @@ -0,0 +1,198 @@ +import { get, cloneDeep, set } from 'lodash-es' + +import { + client, + createValidator, + type RequestMethod, + resolveName, + toSimpleValidationErrors, + type ValidationConfig, + resolveUrl, + resolveMethod, +} from 'laravel-precognition' + +import { Form } from './types.js' + +export { client } +export const useForm = >(method: RequestMethod | (() => RequestMethod), url: string | (() => string), inputs: Data, config: ValidationConfig = {}): Data & Form => { + /** + * The original data. + */ + const originalData = cloneDeep(inputs) + + /** + * The original input names. + */ + const originalInputs: (keyof Data)[] = Object.keys(originalData) + + /** + * Reactive valid state. + */ + // @ts-ignore + let valid = $state([]) + + /** + * Reactive touched state. + */ + // @ts-ignore + let touched = $state([]) + + /** + * Reactive errors. + */ + // @ts-ignore + let errors = $state>({}) + + /** + * Reactive hasErrors. + */ + // @ts-ignore + let hasErrors = $state(false) + + /** + * Reactive Validating. + */ + // @ts-ignore + let validating = $state(false) + + /** + * Reactive Processing. + */ + // @ts-ignore + let processing = $state(false) + + /** + * Reactive Data state + */ + // @ts-ignore + const data = $state(cloneDeep(originalData)) + + /** + * The validator instance. + */ + const validator = createValidator((client) => client[resolveMethod(method)](resolveUrl(url), form.getData(), config), originalData) + .on('validatingChanged', () => { + validating = validator.validating() + }) + .on('validatedChanged', () => { + valid = validator.valid() + }) + .on('touchedChanged', () => { + touched = validator.touched() + }) + .on('errorsChanged', () => { + hasErrors = validator.hasErrors() + errors = toSimpleValidationErrors(validator.errors()) + valid = validator.valid() + }) + + /** + * Resolve the config for a form submission. + */ + const resolveSubmitConfig = (config: any) => ({ + ...config, + precognitive: false, + onStart: () => { + processing = true + config.onStart?.() + }, + onFinish: () => { + processing = false + config.onFinish?.() + }, + onValidationError: (response: any, error: any) => { + validator.setErrors(response.data.errors) + return config.onValidationError + ? config.onValidationError(response) + // @ts-ignore + : Promise.reject(error) + }, + }) + + /** + * Create a new form instance. + */ + const form: Record = { + data, + setData(newData: Record) { + Object.keys(newData).forEach((input) => { + // @ts-ignore + data[input] = newData[input] + }) + return form + }, + touched(name: string) { + return touched.includes(resolveName(name)) + }, + touch(name: string) { + validator.touch(name) + return form + }, + validate(name: string | undefined, config: any) { + if (name === undefined) { + validator.validate(config) + } else { + validator.validate(name, get(data, name), config) + } + return form + }, + valid(name: string) { + return valid.includes(resolveName(name)) + }, + invalid(name: string) { + return typeof form.errors[name] !== 'undefined' + }, + setErrors(newErrors: any) { + validator.setErrors(newErrors) + return form + }, + forgetError(name: string) { + validator.forgetError(name) + return form + }, + reset(...names: string[]) { + const original = cloneDeep(originalData) + + if (names.length === 0) { + originalInputs.forEach((name) => (data[name] = original[name])) + } else { + names.forEach((name : string) => set(data, name, get(original, name))) + } + + validator.reset(...names) + + return form + }, + setValidationTimeout(duration: number) { + validator.setTimeout(duration) + return form + }, + submit(config = {}) { + return client[resolveMethod(method)](resolveUrl(url), form.getData(), resolveSubmitConfig(config)) + }, + validateFiles() { + validator.validateFiles() + return form + }, + validator() { + return validator + }, + get validating() { + return validating + }, + get processing() { + return processing + }, + get errors() { + return errors + }, + get hasErrors() { + return hasErrors + }, + getData() { + return cloneDeep(data) + }, + } + + return form as Data & Form +} diff --git a/packages/svelte/src/types.ts b/packages/svelte/src/types.ts new file mode 100644 index 0000000..7de9385 --- /dev/null +++ b/packages/svelte/src/types.ts @@ -0,0 +1,24 @@ +import { client, type RequestMethod, type ValidationConfig, type Config, type NamedInputEvent, type Validator } from 'laravel-precognition' +export { client } +export interface Form> { + processing: boolean; + validating: boolean; + errors: Partial>; + hasErrors: boolean; + touched(name: keyof Data): boolean; + touch(name: string | NamedInputEvent | Array): Data & Form; + data: Data; + setData(data: Record): Data & Form; + valid(name: keyof Data): boolean; + invalid(name: keyof Data): boolean; + validate(name?: (keyof Data | NamedInputEvent) | ValidationConfig, config?: ValidationConfig): Data & Form; + setErrors(errors: Partial>): Data & Form; + forgetError(string: keyof Data | NamedInputEvent): Data & Form; + setValidationTimeout(duration: number): Data & Form; + submit(config?: Config): Promise; + reset(...keys: (keyof Partial)[]): Data & Form; + validateFiles(): Data & Form; + validator(): Validator; + getData(): Data; +} +export declare const useForm: >(method: RequestMethod | (() => RequestMethod), url: string | (() => string), inputs: Data, config?: ValidationConfig) => Data & Form diff --git a/packages/svelte/tsconfig.json b/packages/svelte/tsconfig.json new file mode 100644 index 0000000..1020288 --- /dev/null +++ b/packages/svelte/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "outDir": "./dist", + "target": "ES2020", + "module": "ES2020", + "moduleResolution": "node", + "resolveJsonModule": true, + "strict": true, + "declaration": true, + "esModuleInterop": true + }, + "include": [ + "./src/index.svelte.ts" + ] +}