diff --git a/packages/core/src/debug.ts b/packages/core/src/debug.ts index 91798bda0..20bae020d 100644 --- a/packages/core/src/debug.ts +++ b/packages/core/src/debug.ts @@ -2,8 +2,7 @@ export const stackTrace = (autolog = true) => { try { throw new Error() } catch (e) { - // @ts-ignore - const stack = e.stack + const stack = (e as Error).stack if (!autolog) { return stack diff --git a/packages/react/src/Form.ts b/packages/react/src/Form.ts index fb4a117b7..a67f38903 100644 --- a/packages/react/src/Form.ts +++ b/packages/react/src/Form.ts @@ -70,7 +70,7 @@ const Form = forwardRef( ref, ) => { const form = useForm>({}) - const formElement = useRef(null) + const formElement = useRef(undefined) const resolvedMethod = useMemo(() => { return isUrlMethodPair(action) ? action.method : (method.toLowerCase() as Method) @@ -96,13 +96,15 @@ const Form = forwardRef( const formEvents: Array = ['input', 'change', 'reset'] - formEvents.forEach((e) => formElement.current.addEventListener(e, updateDirtyState)) + formEvents.forEach((e) => formElement.current!.addEventListener(e, updateDirtyState)) return () => formEvents.forEach((e) => formElement.current?.removeEventListener(e, updateDirtyState)) }, []) const reset = (...fields: string[]) => { - resetFormFields(formElement.current, defaultData.current, fields) + if (formElement.current) { + resetFormFields(formElement.current, defaultData.current, fields) + } } const resetAndClearErrors = (...fields: string[]) => { diff --git a/packages/react/src/InfiniteScroll.ts b/packages/react/src/InfiniteScroll.ts index ca5bcb140..986c64aa3 100644 --- a/packages/react/src/InfiniteScroll.ts +++ b/packages/react/src/InfiniteScroll.ts @@ -181,8 +181,8 @@ const InfiniteScroll = forwardRef( // Elements getTriggerMargin: () => callbackPropsRef.current.buffer, - getStartElement: () => resolvedStartElement, - getEndElement: () => resolvedEndElement, + getStartElement: () => resolvedStartElement!, + getEndElement: () => resolvedEndElement!, getItemsElement: () => resolvedItemsElement, getScrollableParent: () => scrollableParent, diff --git a/packages/react/src/Link.ts b/packages/react/src/Link.ts index a16d25dba..8b10c3021 100755 --- a/packages/react/src/Link.ts +++ b/packages/react/src/Link.ts @@ -9,6 +9,7 @@ import { router, shouldIntercept, shouldNavigate, + VisitOptions, } from '@inertiajs/core' import { createElement, ElementType, forwardRef, useEffect, useMemo, useRef, useState } from 'react' @@ -59,7 +60,7 @@ const Link = forwardRef( ref, ) => { const [inFlightCount, setInFlightCount] = useState(0) - const hoverTimeout = useRef(null) + const hoverTimeout = useRef(undefined) const _method = useMemo(() => { return isUrlMethodPair(href) ? href.method : (method.toLowerCase() as Method) @@ -82,7 +83,7 @@ const Link = forwardRef( const url = useMemo(() => mergeDataArray[0], [mergeDataArray]) const _data = useMemo(() => mergeDataArray[1], [mergeDataArray]) - const baseParams = useMemo( + const baseParams = useMemo( () => ({ data: _data, method: _method, @@ -98,7 +99,7 @@ const Link = forwardRef( [_data, _method, preserveScroll, preserveState, preserveUrl, replace, only, except, headers, async], ) - const visitParams = useMemo( + const visitParams = useMemo( () => ({ ...baseParams, onCancelToken, diff --git a/packages/react/src/PageContext.ts b/packages/react/src/PageContext.ts index eacc4a3c2..c12e8b8d8 100755 --- a/packages/react/src/PageContext.ts +++ b/packages/react/src/PageContext.ts @@ -1,6 +1,7 @@ +import { Page } from '@inertiajs/core' import { createContext } from 'react' -const pageContext = createContext(undefined) +const pageContext = createContext(null) pageContext.displayName = 'InertiaPageContext' export default pageContext diff --git a/packages/react/src/useForm.ts b/packages/react/src/useForm.ts index f40b26beb..28fc19fd7 100644 --- a/packages/react/src/useForm.ts +++ b/packages/react/src/useForm.ts @@ -1,5 +1,6 @@ import { CancelToken, + Errors, ErrorValue, FormDataErrors, FormDataKeys, @@ -7,6 +8,7 @@ import { FormDataValues, Method, Progress, + RequestPayload, router, UrlMethodPair, VisitOptions, @@ -27,6 +29,8 @@ export type SetDataAction> = SetDataByObject type FormOptions = Omit +type SubmitArgs = [Method, string, FormOptions?] | [UrlMethodPair, FormOptions?] +type TransformCallback = (data: TForm) => object export interface InertiaFormProps { data: TForm @@ -38,7 +42,7 @@ export interface InertiaFormProps { wasSuccessful: boolean recentlySuccessful: boolean setData: SetDataAction - transform: (callback: (data: TForm) => object) => void + transform: (callback: TransformCallback) => void setDefaults(): void setDefaults>(field: T, value: FormDataValues): void setDefaults(fields: Partial): void @@ -47,7 +51,7 @@ export interface InertiaFormProps { resetAndClearErrors>(...fields: K[]): void setError>(field: K, value: ErrorValue): void setError(errors: FormDataErrors): void - submit: (...args: [Method, string, FormOptions?] | [UrlMethodPair, FormOptions?]) => void + submit: (...args: SubmitArgs) => void get: (url: string, options?: FormOptions) => void patch: (url: string, options?: FormOptions) => void post: (url: string, options?: FormOptions) => void @@ -66,23 +70,23 @@ export default function useForm>( rememberKeyOrInitialValues?: string | TForm | (() => TForm), maybeInitialValues?: TForm | (() => TForm), ): InertiaFormProps { - const isMounted = useRef(null) + const isMounted = useRef(false) const rememberKey = typeof rememberKeyOrInitialValues === 'string' ? rememberKeyOrInitialValues : null const [defaults, setDefaults] = useState( (typeof rememberKeyOrInitialValues === 'string' ? maybeInitialValues : rememberKeyOrInitialValues) || ({} as TForm), ) const cancelToken = useRef(null) - const recentlySuccessfulTimeoutId = useRef(null) + const recentlySuccessfulTimeoutId = useRef(undefined) const [data, setData] = rememberKey ? useRemember(defaults, `${rememberKey}:data`) : useState(defaults) const [errors, setErrors] = rememberKey ? useRemember({} as FormDataErrors, `${rememberKey}:errors`) : useState({} as FormDataErrors) const [hasErrors, setHasErrors] = useState(false) const [processing, setProcessing] = useState(false) - const [progress, setProgress] = useState(null) + const [progress, setProgress] = useState(null) const [wasSuccessful, setWasSuccessful] = useState(false) const [recentlySuccessful, setRecentlySuccessful] = useState(false) - const transform = useRef((data) => data) + const transform = useRef>((data) => data) const isDirty = useMemo(() => !isEqual(data, defaults), [data, defaults]) useEffect(() => { @@ -97,16 +101,16 @@ export default function useForm>( const setDefaultsCalledInOnSuccess = useRef(false) const submit = useCallback( - (...args) => { + (...args: SubmitArgs) => { const objectPassed = args[0] !== null && typeof args[0] === 'object' - const method = objectPassed ? args[0].method : args[0] - const url = objectPassed ? args[0].url : args[1] + const method = objectPassed ? args[0].method : (args[0] as Method) + const url = objectPassed ? args[0].url : (args[1] as string) const options = (objectPassed ? args[1] : args[2]) ?? {} setDefaultsCalledInOnSuccess.current = false - const _options = { + const _options: VisitOptions = { ...options, onCancelToken: (token) => { cancelToken.current = token @@ -132,7 +136,7 @@ export default function useForm>( } }, onProgress: (event) => { - setProgress(event) + setProgress(event || null) if (options.onProgress) { return options.onProgress(event) @@ -168,7 +172,7 @@ export default function useForm>( if (isMounted.current) { setProcessing(false) setProgress(null) - setErrors(errors) + setErrors(errors as FormDataErrors) setHasErrors(true) } @@ -200,10 +204,12 @@ export default function useForm>( }, } + const transformedData = transform.current(data) as RequestPayload + if (method === 'delete') { - router.delete(url, { ..._options, data: transform.current(data) }) + router.delete(url, { ..._options, data: transformedData }) } else { - router[method](url, transform.current(data), _options) + router[method](url, transformedData, _options) } }, [data, setErrors, transform], @@ -266,7 +272,7 @@ export default function useForm>( }, [dataAsDefaults]) const reset = useCallback( - (...fields) => { + (...fields: string[]) => { if (fields.length === 0) { setData(defaults) } else { @@ -300,12 +306,12 @@ export default function useForm>( ) const clearErrors = useCallback( - (...fields) => { + (...fields: string[]) => { setErrors((errors) => { const newErrors = Object.keys(errors).reduce( (carry, field) => ({ ...carry, - ...(fields.length > 0 && !fields.includes(field) ? { [field]: errors[field] } : {}), + ...(fields.length > 0 && !fields.includes(field) ? { [field]: (errors as Errors)[field] } : {}), }), {}, ) @@ -317,16 +323,18 @@ export default function useForm>( ) const resetAndClearErrors = useCallback( - (...fields) => { + (...fields: string[]) => { reset(...fields) clearErrors(...fields) }, [reset, clearErrors], ) - const createSubmitMethod = (method) => (url, options) => { - submit(method, url, options) - } + const createSubmitMethod = + (method: Method) => + (url: string, options: VisitOptions = {}) => { + submit(method, url, options) + } const getMethod = useCallback(createSubmitMethod('get'), [submit]) const post = useCallback(createSubmitMethod('post'), [submit]) const put = useCallback(createSubmitMethod('put'), [submit]) @@ -339,7 +347,7 @@ export default function useForm>( } }, []) - const transformFunction = useCallback((callback) => { + const transformFunction = useCallback((callback: TransformCallback) => { transform.current = callback }, []) diff --git a/packages/svelte/package.json b/packages/svelte/package.json index b476e56d5..a71a3320f 100644 --- a/packages/svelte/package.json +++ b/packages/svelte/package.json @@ -35,7 +35,7 @@ } }, "scripts": { - "build": "pnpm package && publint", + "build": "pnpm package && svelte-check --tsconfig ./tsconfig.json && publint", "build:with-deps": "svelte-kit sync && vite build --config vite-with-deps.config.js", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", diff --git a/packages/svelte/src/components/Form.svelte b/packages/svelte/src/components/Form.svelte index 194e4cf1e..2afc9fb8c 100644 --- a/packages/svelte/src/components/Form.svelte +++ b/packages/svelte/src/components/Form.svelte @@ -5,6 +5,7 @@ mergeDataIntoQueryString, type Errors, type FormComponentProps, + type Method, type FormDataConvertible, type VisitOptions, isUrlMethodPair, @@ -45,8 +46,8 @@ let isDirty = false let defaultData: FormData = new FormData() - $: _method = isUrlMethodPair(action) ? action.method : (method.toLowerCase() as FormComponentProps['method']) - $: _action = isUrlMethodPair(action) ? action.url : action + $: _method = isUrlMethodPair(action) ? action.method : ((method ?? 'get').toLowerCase() as Method) + $: _action = isUrlMethodPair(action) ? action.url : (action as string) function getFormData(): FormData { return new FormData(formElement) @@ -117,7 +118,7 @@ ...options, } - $form.transform(() => transform(_data)).submit(_method, url, submitOptions) + $form.transform(() => transform!(_data)).submit(_method, url, submitOptions) } function handleSubmit(event: Event) { diff --git a/packages/svelte/src/components/InfiniteScroll.svelte b/packages/svelte/src/components/InfiniteScroll.svelte index 9ffb2e336..c21cad621 100644 --- a/packages/svelte/src/components/InfiniteScroll.svelte +++ b/packages/svelte/src/components/InfiniteScroll.svelte @@ -9,7 +9,7 @@ } from '@inertiajs/core' import { onDestroy, onMount } from 'svelte' - export let data: InfiniteScrollComponentBaseProps['data'] = undefined + export let data: InfiniteScrollComponentBaseProps['data'] export let buffer: InfiniteScrollComponentBaseProps['buffer'] = 0 export let as: InfiniteScrollComponentBaseProps['as'] = 'div' export let manual: InfiniteScrollComponentBaseProps['manual'] = false @@ -126,28 +126,28 @@ infiniteScrollInstance = useInfiniteScroll({ // Data - getPropName: () => data, - inReverseMode: () => reverse, + getPropName: () => data!, + inReverseMode: () => reverse ?? false, shouldFetchNext: () => !onlyPrevious, shouldFetchPrevious: () => !onlyNext, - shouldPreserveUrl: () => preserveUrl, + shouldPreserveUrl: () => preserveUrl ?? false, // Elements - getTriggerMargin: () => buffer, - getStartElement: () => resolvedStartElement, - getEndElement: () => resolvedEndElement, - getItemsElement: () => resolvedItemsElement, + getTriggerMargin: () => buffer ?? 0, + getStartElement: () => resolvedStartElement!, + getEndElement: () => resolvedEndElement!, + getItemsElement: () => resolvedItemsElement!, getScrollableParent: () => (resolvedItemsElement ? getScrollableParent(resolvedItemsElement) : null), // Request callbacks onBeforePreviousRequest: () => (loadingPrevious = true), onBeforeNextRequest: () => (loadingNext = true), onCompletePreviousRequest: () => { - requestCount = infiniteScrollInstance.dataManager.getRequestCount() + requestCount = infiniteScrollInstance!.dataManager.getRequestCount() loadingPrevious = false }, onCompleteNextRequest: () => { - requestCount = infiniteScrollInstance.dataManager.getRequestCount() + requestCount = infiniteScrollInstance!.dataManager.getRequestCount() loadingNext = false }, }) @@ -167,7 +167,7 @@ } } - $: manualMode = manual || (manualAfter > 0 && requestCount >= manualAfter) + $: manualMode = manual || (manualAfter !== undefined && manualAfter > 0 && requestCount >= manualAfter) $: autoLoad = !manualMode $: headerAutoMode = autoLoad && !onlyNext diff --git a/packages/svelte/src/link.ts b/packages/svelte/src/link.ts index 2cf0b5598..d3b1cb262 100644 --- a/packages/svelte/src/link.ts +++ b/packages/svelte/src/link.ts @@ -136,7 +136,7 @@ function link( return 30_000 })() - cacheTags = cacheTagValues + cacheTags = Array.isArray(cacheTagValues) ? cacheTagValues : [cacheTagValues] method = isUrlMethodPair(params.href) ? params.href.method : (params.method?.toLowerCase() as Method) || 'get' ;[href, data] = hrefAndData(method, params) diff --git a/packages/svelte/src/useForm.ts b/packages/svelte/src/useForm.ts index fe1c15586..eba526757 100644 --- a/packages/svelte/src/useForm.ts +++ b/packages/svelte/src/useForm.ts @@ -23,6 +23,8 @@ import { writable, type Writable } from 'svelte/store' type InertiaFormStore = Writable> & InertiaForm type FormOptions = Omit +type SubmitArgs = [Method, string, FormOptions?] | [UrlMethodPair, FormOptions?] +type TransformCallback = (data: TForm) => object export interface InertiaFormProps { isDirty: boolean @@ -35,7 +37,7 @@ export interface InertiaFormProps { setStore(data: TForm): void setStore>(key: T, value: FormDataValues): void data(): TForm - transform(callback: (data: TForm) => object): this + transform(callback: TransformCallback): this defaults(): this defaults(fields: Partial): this defaults>(field: T, value: FormDataValues): this @@ -44,7 +46,7 @@ export interface InertiaFormProps { resetAndClearErrors>(...fields: K[]): this setError>(field: K, value: ErrorValue): this setError(errors: FormDataErrors): this - submit: (...args: [Method, string, FormOptions?] | [UrlMethodPair, FormOptions?]) => void + submit: (...args: SubmitArgs) => void get(url: string, options?: FormOptions): void post(url: string, options?: FormOptions): void put(url: string, options?: FormOptions): void @@ -78,6 +80,11 @@ export default function useForm>( // overriding user's custom defaults with automatic behavior. let defaultsCalledInOnSuccess = false + // Internal helper to update form state properties + const setFormState = >(key: K, value: InertiaFormProps[K]) => { + store.update((form) => ({ ...form, [key]: value })) + } + const store = writable>({ ...(restored ? restored.data : data), isDirty: false, @@ -97,7 +104,7 @@ export default function useForm>( return set(carry, key, get(this, key)) }, {} as TForm) }, - transform(callback) { + transform(callback: TransformCallback) { transform = callback return this }, @@ -115,7 +122,7 @@ export default function useForm>( return this }, - reset(...fields) { + reset(...fields: Array>) { const clonedData = cloneDeep(defaults) if (fields.length === 0) { this.setStore(clonedData) @@ -132,32 +139,32 @@ export default function useForm>( return this }, setError(fieldOrFields: FormDataKeys | FormDataErrors, maybeValue?: ErrorValue) { - this.setStore('errors', { + setFormState('errors', { ...this.errors, ...((typeof fieldOrFields === 'string' ? { [fieldOrFields]: maybeValue } : fieldOrFields) as Errors), }) return this }, - clearErrors(...fields) { - this.setStore( + clearErrors(...fields: string[]) { + setFormState( 'errors', Object.keys(this.errors).reduce( (carry, field) => ({ ...carry, - ...(fields.length > 0 && !fields.includes(field) ? { [field]: this.errors[field] } : {}), + ...(fields.length > 0 && !fields.includes(field) ? { [field]: (this.errors as Errors)[field] } : {}), }), {}, - ) as Errors, + ) as FormDataErrors, ) return this }, - resetAndClearErrors(...fields) { + resetAndClearErrors(...fields: Array>) { this.reset(...fields) this.clearErrors(...fields) return this }, - submit(...args) { + submit(...args: SubmitArgs) { const objectPassed = args[0] !== null && typeof args[0] === 'object' const method = objectPassed ? args[0].method : args[0] @@ -178,8 +185,8 @@ export default function useForm>( } }, onBefore: (visit: PendingVisit) => { - this.setStore('wasSuccessful', false) - this.setStore('recentlySuccessful', false) + setFormState('wasSuccessful', false) + setFormState('recentlySuccessful', false) if (recentlySuccessfulTimeoutId) { clearTimeout(recentlySuccessfulTimeoutId) } @@ -189,55 +196,58 @@ export default function useForm>( } }, onStart: (visit: PendingVisit) => { - this.setStore('processing', true) + setFormState('processing', true) if (options.onStart) { return options.onStart(visit) } }, onProgress: (event?: AxiosProgressEvent) => { - this.setStore('progress', event as any) + setFormState('progress', event || null) if (options.onProgress) { return options.onProgress(event) } }, onSuccess: async (page: Page) => { - this.setStore('processing', false) - this.setStore('progress', null) + setFormState('processing', false) + setFormState('progress', null) this.clearErrors() - this.setStore('wasSuccessful', true) - this.setStore('recentlySuccessful', true) - recentlySuccessfulTimeoutId = setTimeout(() => this.setStore('recentlySuccessful', false), 2000) + setFormState('wasSuccessful', true) + setFormState('recentlySuccessful', true) + recentlySuccessfulTimeoutId = setTimeout(() => setFormState('recentlySuccessful', false), 2000) const onSuccess = options.onSuccess ? await options.onSuccess(page) : null if (!defaultsCalledInOnSuccess) { - this.defaults(cloneDeep(this.data())) + store.update((form) => { + this.defaults(cloneDeep(form.data())) + return form + }) } return onSuccess }, onError: (errors: Errors) => { - this.setStore('processing', false) - this.setStore('progress', null) - this.clearErrors().setError(errors) + setFormState('processing', false) + setFormState('progress', null) + this.clearErrors().setError(errors as FormDataErrors) if (options.onError) { return options.onError(errors) } }, onCancel: () => { - this.setStore('processing', false) - this.setStore('progress', null) + setFormState('processing', false) + setFormState('progress', null) if (options.onCancel) { return options.onCancel() } }, onFinish: (visit: ActiveVisit) => { - this.setStore('processing', false) - this.setStore('progress', null) + setFormState('processing', false) + setFormState('progress', null) cancelToken = null if (options.onFinish) { @@ -252,19 +262,19 @@ export default function useForm>( router[method](url, data, _options) } }, - get(url, options) { + get(url: string, options: VisitOptions) { this.submit('get', url, options) }, - post(url, options) { + post(url: string, options: VisitOptions) { this.submit('post', url, options) }, - put(url, options) { + put(url: string, options: VisitOptions) { this.submit('put', url, options) }, - patch(url, options) { + patch(url: string, options: VisitOptions) { this.submit('patch', url, options) }, - delete(url, options) { + delete(url: string, options: VisitOptions) { this.submit('delete', url, options) }, cancel() { @@ -274,12 +284,12 @@ export default function useForm>( store.subscribe((form) => { if (form.isDirty === isEqual(form.data(), defaults)) { - form.setStore('isDirty', !form.isDirty) + setFormState('isDirty', !form.isDirty) } const hasErrors = Object.keys(form.errors).length > 0 if (form.hasErrors !== hasErrors) { - form.setStore('hasErrors', !form.hasErrors) + setFormState('hasErrors', !form.hasErrors) } if (rememberKey) { @@ -287,5 +297,5 @@ export default function useForm>( } }) - return store + return store as InertiaFormStore } diff --git a/packages/vue3/src/deferred.ts b/packages/vue3/src/deferred.ts index 89aa1f0b2..9557d5a33 100644 --- a/packages/vue3/src/deferred.ts +++ b/packages/vue3/src/deferred.ts @@ -15,6 +15,6 @@ export default defineComponent({ throw new Error('`` requires a `