diff --git a/src/index.ts b/src/index.ts index 772b21f7..d6eb4e3a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -647,7 +647,7 @@ function cleanUp(el: Element, styles?: Partial) { setTimeout(() => { if (DEL in el) delete (el as any)[DEL] Object.defineProperty(el, NEW, { value: true, configurable: true }) - if (styles && el instanceof HTMLElement) { + if (styles && (el instanceof HTMLElement || el instanceof SVGElement)) { for (const style in styles) { ;(el.style as any)[style as any] = "" } @@ -877,11 +877,11 @@ export interface AutoAnimationPlugin { * A function that automatically adds animation effects to itself and its * immediate children. Specifically it adds effects for adding, moving, and * removing DOM elements. - * @param el - A parent element to add animations to. + * @param el - An HTML or SVG parent element to add animations to. * @param options - An optional object of options. */ export default function autoAnimate( - el: HTMLElement, + el: HTMLElement | SVGElement, config: Partial | AutoAnimationPlugin = {}, ): AnimationController { if (supportedBrowser && resize) { @@ -978,7 +978,7 @@ export { autoAnimate } */ export const vAutoAnimate = { mounted: ( - el: HTMLElement, + el: HTMLElement | SVGElement, binding: { value: Partial | AutoAnimationPlugin | undefined }, @@ -986,7 +986,7 @@ export const vAutoAnimate = { const ctl = autoAnimate(el, binding.value || {}) Object.defineProperty(el, "__aa_ctl", { value: ctl, configurable: true }) }, - unmounted: (el: HTMLElement) => { + unmounted: (el: HTMLElement | SVGElement) => { const ctl = (el as any)["__aa_ctl"] as AnimationController | undefined ctl?.destroy?.() try { diff --git a/src/marko/auto-animate.marko b/src/marko/auto-animate.marko index 39d17ffe..1c05b8f8 100644 --- a/src/marko/auto-animate.marko +++ b/src/marko/auto-animate.marko @@ -8,7 +8,7 @@ import type { export interface Input { // A reference to the parent element whose *direct children* will be animated. // Pass a native element tag-variable, e.g. `
    ` then `parent=listRef`. - parent: () => HTMLElement; + parent: () => HTMLElement | SVGElement; // auto-animate options (duration / easing / disrespectUserMotionPreference) // OR a plugin function. Read once when the tag mounts; the core has no setOptions, // so changing this later has no effect (remount the tag to apply new options). diff --git a/src/preact/index.ts b/src/preact/index.ts index 7710e601..5fa79885 100644 --- a/src/preact/index.ts +++ b/src/preact/index.ts @@ -23,7 +23,10 @@ export function useAutoAnimate( } } useEffect(() => { - if (element.current instanceof HTMLElement) + if ( + element.current instanceof HTMLElement || + element.current instanceof SVGElement + ) setController(autoAnimate(element.current, options || {})) }, []) useEffect(() => { diff --git a/src/qwik/index.ts b/src/qwik/index.ts index 831aea93..e9dfaddb 100644 --- a/src/qwik/index.ts +++ b/src/qwik/index.ts @@ -20,7 +20,7 @@ import autoAnimate, { * @param options - Auto animate options or a plugin * @returns */ -export function useAutoAnimate( +export function useAutoAnimate( options?: Partial | AutoAnimationPlugin ): [ Signal, diff --git a/src/react/index.ts b/src/react/index.ts index 76b40835..cbcddf35 100644 --- a/src/react/index.ts +++ b/src/react/index.ts @@ -19,7 +19,7 @@ export function useAutoAnimate( const memoizedOptions = useMemo(() => options, []) const element = useCallback( (node: T) => { - if (node instanceof HTMLElement) { + if (node instanceof HTMLElement || node instanceof SVGElement) { setController(autoAnimate(node, memoizedOptions)) } else { setController(undefined) diff --git a/src/solid/index.ts b/src/solid/index.ts index d627b872..436c8a72 100644 --- a/src/solid/index.ts +++ b/src/solid/index.ts @@ -13,7 +13,7 @@ declare module "solid-js" { } } -export const createAutoAnimate = ( +export const createAutoAnimate = ( options: Partial | AutoAnimationPlugin = {} ): [Setter, (enabled: boolean) => void] => { const [element, setElement] = createSignal(null) @@ -43,7 +43,7 @@ export const createAutoAnimate = ( export const createAutoAnimateDirective = () => { return ( - el: HTMLElement, + el: HTMLElement | SVGElement, options: Accessor | AutoAnimationPlugin | true> ) => { let optionsValue = options() diff --git a/src/vue/index.ts b/src/vue/index.ts index e225cc18..0915fdb2 100644 --- a/src/vue/index.ts +++ b/src/vue/index.ts @@ -8,10 +8,10 @@ import autoAnimate, { } from "../index" export const vAutoAnimate: Directive< - HTMLElement | Component, + HTMLElement | SVGElement | Component, Partial > = autoAnimateDirective as unknown as Directive< - HTMLElement | Component, + HTMLElement | SVGElement | Component, Partial > @@ -20,7 +20,10 @@ export const vAutoAnimate: Directive< */ export function createVAutoAnimate( defaults?: Partial | AutoAnimationPlugin -): Directive | AutoAnimationPlugin> { +): Directive< + HTMLElement | SVGElement, + Partial | AutoAnimationPlugin +> { return { mounted(el, binding) { let resolved: Partial | AutoAnimationPlugin = {} @@ -44,7 +47,7 @@ export function createVAutoAnimate( }, getSSRProps: () => ({}), } as unknown as Directive< - HTMLElement, + HTMLElement | SVGElement, Partial | AutoAnimationPlugin > } @@ -73,13 +76,17 @@ export function useAutoAnimate( } onMounted(() => { watchEffect((onCleanup) => { - let el: HTMLElement | undefined - if (element.value instanceof HTMLElement) { + let el: HTMLElement | SVGElement | undefined + if ( + element.value instanceof HTMLElement || + element.value instanceof SVGElement + ) { el = element.value } else if ( element.value && "$el" in element.value && - element.value.$el instanceof HTMLElement + (element.value.$el instanceof HTMLElement || + element.value.$el instanceof SVGElement) ) { el = element.value.$el } diff --git a/tests/e2e/svg-types.spec.ts b/tests/e2e/svg-types.spec.ts new file mode 100644 index 00000000..3be3b6e8 --- /dev/null +++ b/tests/e2e/svg-types.spec.ts @@ -0,0 +1,23 @@ +import { test, expect } from '@playwright/test' +import { execFileSync } from 'node:child_process' +import { fileURLToPath } from 'node:url' +import path from 'node:path' + +test.describe('SVG type assignability (issue #100)', () => { + test('autoAnimate and vAutoAnimate accept SVG elements', () => { + const root = fileURLToPath(new URL('../..', import.meta.url)) + const tsc = path.join(root, 'node_modules/typescript/bin/tsc') + let output = '' + try { + output = execFileSync( + tsc, + ['--pretty', 'false', '-p', 'tests/types/tsconfig.json'], + { encoding: 'utf8', cwd: root }, + ) + } catch (err) { + const error = err as { stdout?: string; stderr?: string } + throw new Error(error.stdout || error.stderr || String(err)) + } + expect(output).toBe('') + }) +}) diff --git a/tests/types/svg-element.ts b/tests/types/svg-element.ts new file mode 100644 index 00000000..56fff9a9 --- /dev/null +++ b/tests/types/svg-element.ts @@ -0,0 +1,17 @@ +import autoAnimate, { vAutoAnimate } from "../../src/index" + +declare const svg: SVGSVGElement +declare const html: HTMLElement +declare const group: SVGGElement + +autoAnimate(svg) +autoAnimate(html) +autoAnimate(group) + +const binding = { + value: undefined as undefined, +} +vAutoAnimate.mounted(svg, binding) +vAutoAnimate.mounted(html, binding) +vAutoAnimate.unmounted(svg) +vAutoAnimate.unmounted(html) diff --git a/tests/types/tsconfig.json b/tests/types/tsconfig.json new file mode 100644 index 00000000..a2d84c2e --- /dev/null +++ b/tests/types/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "noEmit": true, + "noUnusedLocals": false, + "noUnusedParameters": false + }, + "include": ["./svg-element.ts"] +}