@codehz/auto-transition 0.0.2 → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { ComponentPropsWithoutRef, ElementType, ForwardedRef, ReactElement, ReactNode } from "react";
1
+ import { ComponentProps, ComponentPropsWithoutRef, ElementType, FC, ForwardedRef, ReactElement, ReactNode } from "react";
2
2
  import * as react_jsx_runtime0 from "react/jsx-runtime";
3
3
 
4
4
  //#region src/AutoTransition.d.ts
@@ -106,6 +106,15 @@ type TransitionPlugin = {
106
106
  /** Play when element is resized; not invoked by current implementation. */
107
107
  resize?(el: Element, current: Dimensions, previous: Dimensions): Animation;
108
108
  };
109
+ /**
110
+ * A higher-order component that wraps a component with `AutoTransition`.
111
+ *
112
+ * @template T - Element type of the component to wrap.
113
+ * @param Component - The component to wrap.
114
+ * @param options - Default props to pass to `AutoTransition`.
115
+ * @returns A new component that automatically applies transitions.
116
+ */
117
+ declare function withAutoTransition<T extends ElementType, R extends ElementType>(Component: T, options?: Omit<AutoTransitionProps<R>, "children">): FC<ComponentProps<T>>;
109
118
  //#endregion
110
- export { AutoTransition, Dimensions, Rect, TransitionPlugin };
119
+ export { AutoTransition, AutoTransitionProps, Dimensions, Rect, TransitionPlugin, withAutoTransition };
111
120
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -201,7 +201,25 @@ function AutoTransition({ as, children, transition, ref: externalRef, ...rest })
201
201
  children
202
202
  });
203
203
  }
204
+ /**
205
+ * A higher-order component that wraps a component with `AutoTransition`.
206
+ *
207
+ * @template T - Element type of the component to wrap.
208
+ * @param Component - The component to wrap.
209
+ * @param options - Default props to pass to `AutoTransition`.
210
+ * @returns A new component that automatically applies transitions.
211
+ */
212
+ function withAutoTransition(Component, options) {
213
+ const WithAutoTransition = (props) => {
214
+ return /* @__PURE__ */ jsx(AutoTransition, {
215
+ ...options,
216
+ children: /* @__PURE__ */ jsx(Component, { ...props })
217
+ });
218
+ };
219
+ WithAutoTransition.displayName = `withAutoTransition(${typeof Component === "string" ? Component : Component.displayName || Component.name || "Component"})`;
220
+ return WithAutoTransition;
221
+ }
204
222
 
205
223
  //#endregion
206
- export { AutoTransition };
224
+ export { AutoTransition, withAutoTransition };
207
225
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["cache: T | undefined","animation: Animation"],"sources":["../src/microcache.ts","../src/useForkRef.ts","../src/AutoTransition.tsx"],"sourcesContent":["export function microcache<T, Args extends unknown[]>(create: (...args: Args) => T, cleanup?: (old: T) => void) {\n let cache: T | undefined;\n return (...args: Args) => {\n if (cache) {\n return cache;\n }\n cache = create(...args);\n queueMicrotask(() => {\n const old = cache!;\n cache = undefined;\n cleanup?.(old);\n });\n return cache;\n };\n}\n","import { useCallback, type Ref } from \"react\";\n\nexport function useForkRef<T>(...refs: (Ref<T> | null | undefined)[]): (node: T | null) => void {\n return useCallback((node: T | null) => {\n refs.forEach((ref) => {\n if (ref) {\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref.current !== undefined) {\n ref.current = node;\n }\n }\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, refs);\n}\n","import { Slot } from \"@radix-ui/react-slot\";\nimport {\n useEffect,\n useRef,\n type ComponentPropsWithoutRef,\n type ElementType,\n type ForwardedRef,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport { microcache } from \"./microcache.ts\";\nimport { useForkRef } from \"./useForkRef.ts\";\n\n/**\n * A rectangle describing an element's position and size relative to the measured\n * parent used by `AutoTransition` for layout calculations.\n *\n * - `x`/`y` are the left/top offsets relative to the measurement parent's content box.\n * - `width`/`height` are the element's layout size in pixels.\n */\nexport type Rect = {\n x: number;\n y: number;\n width: number;\n height: number;\n};\n\n/**\n * Simple size pair used for resize transitions (width/height in pixels).\n */\nexport type Dimensions = {\n width: number;\n height: number;\n};\n\n/**\n * Common props for `AutoTransition`.\n *\n * @template T - Element type to render as (e.g., \"div\", \"ul\").\n */\ntype AutoTransitionBaseProps<T extends ElementType | undefined> = {\n as?: T;\n transition?: TransitionPlugin;\n ref?: ForwardedRef<HTMLElement>;\n};\n\ntype AutoTransitionProps<T extends ElementType | undefined> = T extends ElementType\n ? AutoTransitionBaseProps<T> &\n Omit<ComponentPropsWithoutRef<T>, keyof AutoTransitionBaseProps<T>> & {\n children?: ReactNode;\n }\n : AutoTransitionBaseProps<T> & {\n children: ReactElement;\n };\n\n/**\n * AutoTransition\n *\n * A small container component that provides automatic enter/exit/move\n * animations for its child `Element` nodes. The component intercepts\n * low-level DOM operations (`appendChild`, `insertBefore`, `removeChild`)\n * performed on the container and plays animations (via the Web Animations\n * API) before applying DOM changes such as removing an element.\n *\n * If a `transition` plugin is not provided, AutoTransition applies its\n * default animations:\n * - enter: fade in (opacity 0 -> 1), 250ms ease-out\n * - exit: keep element size and position while fading out, 250ms ease-in\n * - move: translate + scale from previous rect to new rect, 250ms ease-in\n *\n * Notes:\n * - This component is client-only (relies on DOM measurement & Web Animations API).\n * - It only animates `Element` nodes; text nodes use native DOM operations.\n * - In exit path, the provided animation's finish triggers removal from the DOM.\n *\n * Example usage:\n * ```tsx\n * <AutoTransition as=\"div\" className=\"grid gap-2\">\n * {items.map((it) => (\n * <Card key={it.id}>{it.title}</Card>\n * ))}\n * </AutoTransition>\n *\n * // with custom transition plugin\n * <AutoTransition transition={FloatingPanelTransition} as=\"div\">\n * {isOpen && <PanelContent/>}\n * </AutoTransition>\n * ```\n *\n * @template T - Element type to render as (e.g. \"div\")\n * @param props - props as defined by `AutoTransitionProps<T>`\n */\nexport function AutoTransition<T extends ElementType | undefined>({\n as,\n children,\n transition,\n ref: externalRef,\n ...rest\n}: AutoTransitionProps<T>) {\n const Component = as ?? Slot;\n const ref = useRef<HTMLElement>(null);\n useEffect(() => {\n const removed = new Set<Element>();\n const target = ref.current!;\n let measureTarget = target;\n let styles = getComputedStyle(measureTarget);\n while (styles.display === \"contents\" || (styles.position === \"static\" && measureTarget !== document.body)) {\n measureTarget = measureTarget.parentElement!;\n styles = getComputedStyle(measureTarget);\n }\n const parentRect = microcache(() => {\n const borderBox = measureTarget.getBoundingClientRect();\n return {\n left: borderBox.left + parseFloat(styles.borderLeftWidth || \"0\"),\n top: borderBox.top + parseFloat(styles.borderTopWidth || \"0\"),\n };\n });\n const snapshot = microcache(\n () => {\n const parent = parentRect();\n const result = new Map<Element, Rect>();\n for (const child of target.children) {\n if (child instanceof Element) {\n const rect = child.getBoundingClientRect();\n result.set(child, {\n x: rect.left - parent.left,\n y: rect.top - parent.top,\n width: rect.width,\n height: rect.height,\n });\n }\n }\n return result;\n },\n (old) => {\n for (const child of target.children) {\n if (child instanceof Element) {\n if (removed.has(child)) continue;\n const rect = getRelativePosition(child);\n const oldRect = old.get(child);\n if (!oldRect) continue;\n if (\n rect.x !== oldRect.x ||\n rect.y !== oldRect.y ||\n rect.width !== oldRect.width ||\n rect.height !== oldRect.height\n ) {\n animateNodeMove(child, rect, oldRect);\n }\n }\n }\n },\n );\n target.removeChild = function removeChild<T extends Node>(node: T) {\n if (node instanceof Element) {\n if (removed.has(node)) return node;\n removed.add(node);\n const rect = snapshot().get(node) ?? getRelativePosition(node);\n animateNodeExit(node, rect);\n return node;\n }\n return Element.prototype.removeChild.call(this, node) as T;\n };\n target.insertBefore = function insertBefore<T extends Node>(node: T, child: Node | null) {\n snapshot();\n if (!(node instanceof Element)) {\n return Element.prototype.insertBefore.call(this, node, child) as T;\n }\n Element.prototype.insertBefore.call(this, node, child);\n animateNodeEnter(node);\n return node;\n };\n target.appendChild = function appendChild<T extends Node>(node: T) {\n snapshot();\n if (!(node instanceof Element)) {\n return Element.prototype.appendChild.call(this, node) as T;\n }\n Element.prototype.appendChild.call(this, node);\n animateNodeEnter(node);\n return node;\n };\n return () => {\n target.removeChild = Element.prototype.removeChild;\n target.insertBefore = Element.prototype.insertBefore;\n target.appendChild = Element.prototype.appendChild;\n };\n\n function animateNodeExit(node: Element, rect: Rect) {\n let animation: Animation;\n if (transition?.exit) {\n animation = transition.exit(node, rect);\n } else {\n const width = `${rect.width}px`;\n const height = `${rect.height}px`;\n const translate = `translate(${rect.x}px, ${rect.y}px)`;\n animation = node.animate(\n {\n position: [\"absolute\", \"absolute\"],\n opacity: [1, 0],\n top: [\"0\", \"0\"],\n left: [\"0\", \"0\"],\n transform: [translate, translate],\n width: [width, width],\n height: [height, height],\n margin: [\"0\", \"0\"],\n },\n { duration: 250, easing: \"ease-in\" },\n );\n }\n animation.finished.then(() => node.remove());\n return animation;\n }\n\n function animateNodeEnter(node: Element) {\n if (transition?.enter) {\n transition.enter(node);\n } else {\n node.animate({ opacity: [0, 1] }, { duration: 250, easing: \"ease-out\" });\n }\n }\n\n function animateNodeMove(node: Element, rect: Rect, oldRect: Rect) {\n if (transition?.move) {\n transition.move(node, rect, oldRect);\n } else {\n const dx = oldRect.x - rect.x;\n const dy = oldRect.y - rect.y;\n const sx = oldRect.width / rect.width;\n const sy = oldRect.height / rect.height;\n node.animate(\n {\n transformOrigin: [\"0 0\", \"0 0\"],\n transform: [`translate(${dx}px, ${dy}px) scale(${sx}, ${sy})`, `translate(0, 0) scale(1, 1)`],\n },\n { duration: 250, easing: \"ease-in\" },\n );\n }\n }\n\n function getRelativePosition(node: Element, parent = parentRect()): Rect {\n const rect = node.getBoundingClientRect();\n return {\n x: rect.left - parent.left,\n y: rect.top - parent.top,\n width: rect.width,\n height: rect.height,\n };\n }\n }, [transition]);\n const forkedRef = useForkRef(ref, externalRef);\n return (\n <Component ref={forkedRef} {...rest}>\n {children}\n </Component>\n );\n}\n\n/**\n * A plugin interface to provide custom animations for enter/exit/move/resize.\n * Implementations should return a Web Animations API `Animation` instance.\n *\n * - `enter` receives the element being inserted.\n * - `exit` receives the element being removed and its last-known rectangle\n * relative to the measurement parent — useful for leaving the element in\n * place while animating out.\n * - `move` receives the element that's moved and both current/previous rects\n * to allow translation/scale-based transitions.\n * - `resize` receives the element and previous/new dimensions — note: the\n * current component implementation doesn't automatically call `resize`,\n * but implementations may document this hook for future use.\n */\nexport type TransitionPlugin = {\n /** Play when an element enters/was inserted into the container. */\n enter?(el: Element): Animation;\n /** Play when an element is removed; `rect` is the element's rect at removal time. */\n exit?(el: Element, rect: Rect): Animation;\n /** Play when an element moves within the container (position or size changes). */\n move?(el: Element, current: Rect, previous: Rect): Animation;\n /** Play when element is resized; not invoked by current implementation. */\n resize?(el: Element, current: Dimensions, previous: Dimensions): Animation;\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["cache: T | undefined","animation: Animation"],"sources":["../src/microcache.ts","../src/useForkRef.ts","../src/AutoTransition.tsx"],"sourcesContent":["export function microcache<T, Args extends unknown[]>(create: (...args: Args) => T, cleanup?: (old: T) => void) {\n let cache: T | undefined;\n return (...args: Args) => {\n if (cache) {\n return cache;\n }\n cache = create(...args);\n queueMicrotask(() => {\n const old = cache!;\n cache = undefined;\n cleanup?.(old);\n });\n return cache;\n };\n}\n","import { useCallback, type Ref } from \"react\";\n\nexport function useForkRef<T>(...refs: (Ref<T> | null | undefined)[]): (node: T | null) => void {\n return useCallback((node: T | null) => {\n refs.forEach((ref) => {\n if (ref) {\n if (typeof ref === \"function\") {\n ref(node);\n } else if (ref.current !== undefined) {\n ref.current = node;\n }\n }\n });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, refs);\n}\n","import { Slot } from \"@radix-ui/react-slot\";\nimport {\n useEffect,\n useRef,\n type ComponentProps,\n type ComponentPropsWithoutRef,\n type ElementType,\n type FC,\n type ForwardedRef,\n type ReactElement,\n type ReactNode,\n} from \"react\";\nimport { microcache } from \"./microcache.ts\";\nimport { useForkRef } from \"./useForkRef.ts\";\n\n/**\n * A rectangle describing an element's position and size relative to the measured\n * parent used by `AutoTransition` for layout calculations.\n *\n * - `x`/`y` are the left/top offsets relative to the measurement parent's content box.\n * - `width`/`height` are the element's layout size in pixels.\n */\nexport type Rect = {\n x: number;\n y: number;\n width: number;\n height: number;\n};\n\n/**\n * Simple size pair used for resize transitions (width/height in pixels).\n */\nexport type Dimensions = {\n width: number;\n height: number;\n};\n\n/**\n * Common props for `AutoTransition`.\n *\n * @template T - Element type to render as (e.g., \"div\", \"ul\").\n */\ntype AutoTransitionBaseProps<T extends ElementType | undefined> = {\n as?: T;\n transition?: TransitionPlugin;\n ref?: ForwardedRef<HTMLElement>;\n};\n\nexport type AutoTransitionProps<T extends ElementType | undefined> = T extends ElementType\n ? AutoTransitionBaseProps<T> &\n Omit<ComponentPropsWithoutRef<T>, keyof AutoTransitionBaseProps<T>> & {\n children?: ReactNode;\n }\n : AutoTransitionBaseProps<T> & {\n children: ReactElement;\n };\n\n/**\n * AutoTransition\n *\n * A small container component that provides automatic enter/exit/move\n * animations for its child `Element` nodes. The component intercepts\n * low-level DOM operations (`appendChild`, `insertBefore`, `removeChild`)\n * performed on the container and plays animations (via the Web Animations\n * API) before applying DOM changes such as removing an element.\n *\n * If a `transition` plugin is not provided, AutoTransition applies its\n * default animations:\n * - enter: fade in (opacity 0 -> 1), 250ms ease-out\n * - exit: keep element size and position while fading out, 250ms ease-in\n * - move: translate + scale from previous rect to new rect, 250ms ease-in\n *\n * Notes:\n * - This component is client-only (relies on DOM measurement & Web Animations API).\n * - It only animates `Element` nodes; text nodes use native DOM operations.\n * - In exit path, the provided animation's finish triggers removal from the DOM.\n *\n * Example usage:\n * ```tsx\n * <AutoTransition as=\"div\" className=\"grid gap-2\">\n * {items.map((it) => (\n * <Card key={it.id}>{it.title}</Card>\n * ))}\n * </AutoTransition>\n *\n * // with custom transition plugin\n * <AutoTransition transition={FloatingPanelTransition} as=\"div\">\n * {isOpen && <PanelContent/>}\n * </AutoTransition>\n * ```\n *\n * @template T - Element type to render as (e.g. \"div\")\n * @param props - props as defined by `AutoTransitionProps<T>`\n */\nexport function AutoTransition<T extends ElementType | undefined>({\n as,\n children,\n transition,\n ref: externalRef,\n ...rest\n}: AutoTransitionProps<T>) {\n const Component = as ?? Slot;\n const ref = useRef<HTMLElement>(null);\n useEffect(() => {\n const removed = new Set<Element>();\n const target = ref.current!;\n let measureTarget = target;\n let styles = getComputedStyle(measureTarget);\n while (styles.display === \"contents\" || (styles.position === \"static\" && measureTarget !== document.body)) {\n measureTarget = measureTarget.parentElement!;\n styles = getComputedStyle(measureTarget);\n }\n const parentRect = microcache(() => {\n const borderBox = measureTarget.getBoundingClientRect();\n return {\n left: borderBox.left + parseFloat(styles.borderLeftWidth || \"0\"),\n top: borderBox.top + parseFloat(styles.borderTopWidth || \"0\"),\n };\n });\n const snapshot = microcache(\n () => {\n const parent = parentRect();\n const result = new Map<Element, Rect>();\n for (const child of target.children) {\n if (child instanceof Element) {\n const rect = child.getBoundingClientRect();\n result.set(child, {\n x: rect.left - parent.left,\n y: rect.top - parent.top,\n width: rect.width,\n height: rect.height,\n });\n }\n }\n return result;\n },\n (old) => {\n for (const child of target.children) {\n if (child instanceof Element) {\n if (removed.has(child)) continue;\n const rect = getRelativePosition(child);\n const oldRect = old.get(child);\n if (!oldRect) continue;\n if (\n rect.x !== oldRect.x ||\n rect.y !== oldRect.y ||\n rect.width !== oldRect.width ||\n rect.height !== oldRect.height\n ) {\n animateNodeMove(child, rect, oldRect);\n }\n }\n }\n },\n );\n target.removeChild = function removeChild<T extends Node>(node: T) {\n if (node instanceof Element) {\n if (removed.has(node)) return node;\n removed.add(node);\n const rect = snapshot().get(node) ?? getRelativePosition(node);\n animateNodeExit(node, rect);\n return node;\n }\n return Element.prototype.removeChild.call(this, node) as T;\n };\n target.insertBefore = function insertBefore<T extends Node>(node: T, child: Node | null) {\n snapshot();\n if (!(node instanceof Element)) {\n return Element.prototype.insertBefore.call(this, node, child) as T;\n }\n Element.prototype.insertBefore.call(this, node, child);\n animateNodeEnter(node);\n return node;\n };\n target.appendChild = function appendChild<T extends Node>(node: T) {\n snapshot();\n if (!(node instanceof Element)) {\n return Element.prototype.appendChild.call(this, node) as T;\n }\n Element.prototype.appendChild.call(this, node);\n animateNodeEnter(node);\n return node;\n };\n return () => {\n target.removeChild = Element.prototype.removeChild;\n target.insertBefore = Element.prototype.insertBefore;\n target.appendChild = Element.prototype.appendChild;\n };\n\n function animateNodeExit(node: Element, rect: Rect) {\n let animation: Animation;\n if (transition?.exit) {\n animation = transition.exit(node, rect);\n } else {\n const width = `${rect.width}px`;\n const height = `${rect.height}px`;\n const translate = `translate(${rect.x}px, ${rect.y}px)`;\n animation = node.animate(\n {\n position: [\"absolute\", \"absolute\"],\n opacity: [1, 0],\n top: [\"0\", \"0\"],\n left: [\"0\", \"0\"],\n transform: [translate, translate],\n width: [width, width],\n height: [height, height],\n margin: [\"0\", \"0\"],\n },\n { duration: 250, easing: \"ease-in\" },\n );\n }\n animation.finished.then(() => node.remove());\n return animation;\n }\n\n function animateNodeEnter(node: Element) {\n if (transition?.enter) {\n transition.enter(node);\n } else {\n node.animate({ opacity: [0, 1] }, { duration: 250, easing: \"ease-out\" });\n }\n }\n\n function animateNodeMove(node: Element, rect: Rect, oldRect: Rect) {\n if (transition?.move) {\n transition.move(node, rect, oldRect);\n } else {\n const dx = oldRect.x - rect.x;\n const dy = oldRect.y - rect.y;\n const sx = oldRect.width / rect.width;\n const sy = oldRect.height / rect.height;\n node.animate(\n {\n transformOrigin: [\"0 0\", \"0 0\"],\n transform: [`translate(${dx}px, ${dy}px) scale(${sx}, ${sy})`, `translate(0, 0) scale(1, 1)`],\n },\n { duration: 250, easing: \"ease-in\" },\n );\n }\n }\n\n function getRelativePosition(node: Element, parent = parentRect()): Rect {\n const rect = node.getBoundingClientRect();\n return {\n x: rect.left - parent.left,\n y: rect.top - parent.top,\n width: rect.width,\n height: rect.height,\n };\n }\n }, [transition]);\n const forkedRef = useForkRef(ref, externalRef);\n return (\n <Component ref={forkedRef} {...rest}>\n {children}\n </Component>\n );\n}\n\n/**\n * A plugin interface to provide custom animations for enter/exit/move/resize.\n * Implementations should return a Web Animations API `Animation` instance.\n *\n * - `enter` receives the element being inserted.\n * - `exit` receives the element being removed and its last-known rectangle\n * relative to the measurement parent — useful for leaving the element in\n * place while animating out.\n * - `move` receives the element that's moved and both current/previous rects\n * to allow translation/scale-based transitions.\n * - `resize` receives the element and previous/new dimensions — note: the\n * current component implementation doesn't automatically call `resize`,\n * but implementations may document this hook for future use.\n */\nexport type TransitionPlugin = {\n /** Play when an element enters/was inserted into the container. */\n enter?(el: Element): Animation;\n /** Play when an element is removed; `rect` is the element's rect at removal time. */\n exit?(el: Element, rect: Rect): Animation;\n /** Play when an element moves within the container (position or size changes). */\n move?(el: Element, current: Rect, previous: Rect): Animation;\n /** Play when element is resized; not invoked by current implementation. */\n resize?(el: Element, current: Dimensions, previous: Dimensions): Animation;\n};\n\n/**\n * A higher-order component that wraps a component with `AutoTransition`.\n *\n * @template T - Element type of the component to wrap.\n * @param Component - The component to wrap.\n * @param options - Default props to pass to `AutoTransition`.\n * @returns A new component that automatically applies transitions.\n */\nexport function withAutoTransition<T extends ElementType, R extends ElementType>(\n Component: T,\n options?: Omit<AutoTransitionProps<R>, \"children\">,\n): FC<ComponentProps<T>> {\n const WithAutoTransition = (props: ComponentProps<T>) => {\n return (\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n <AutoTransition {...(options as any)}>\n {/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}\n <Component {...(props as any)} />\n </AutoTransition>\n );\n };\n const componentName =\n typeof Component === \"string\"\n ? Component\n : (Component as { displayName?: string; name?: string }).displayName ||\n (Component as { displayName?: string; name?: string }).name ||\n \"Component\";\n WithAutoTransition.displayName = `withAutoTransition(${componentName})`;\n return WithAutoTransition;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,11 +1,15 @@
1
1
  {
2
2
  "name": "@codehz/auto-transition",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "A lightweight React component for automatic enter/exit/move transitions using Web Animations API",
5
5
  "license": "MIT",
6
6
  "author": "codehz",
7
7
  "module": "dist/index.mjs",
8
8
  "type": "module",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/codehz/auto-transition"
12
+ },
9
13
  "devDependencies": {
10
14
  "@types/bun": "latest",
11
15
  "@types/react": "^19.2.7",