@tonybonet/magnetic 0.1.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-05-15
9
+
10
+ ### Added
11
+
12
+ - `useMagnetic` hook — headless primitive for pointer-tracking magnetic elements
13
+ - `<Magnetic>` component — JSX convenience wrapper with built-in LazyMotion boundary
14
+ - Shared orchestrator — single `pointermove` listener, one rAF loop, IntersectionObserver gating
15
+ - Spring presets: `TACTILE`, `SMOOTH`, `BOUNCY_SNAP`, `LINEAR_RESET`
16
+ - `prefers-reduced-motion` support — automatically disables when user prefers reduced motion
17
+ - Subpath exports: `@tonybonet/magnetic`, `/component`, `/presets`
18
+ - ESM + CJS dual format with TypeScript declarations
package/README.md ADDED
@@ -0,0 +1,168 @@
1
+ # @tonybonet/magnetic
2
+
3
+ > Pointer-tracking magnetic UI elements with a shared orchestrator.
4
+ > One listener. One rAF loop. Zero compromises.
5
+
6
+ **[Playground →](#)** · **[GitHub](https://github.com/tonyblu331/magnetic)**
7
+
8
+ Pepper your page with magnetic buttons, CTAs, and tiles — each one subtly pulls toward the cursor. The shared orchestrator means **one** `pointermove` listener, **one** `requestAnimationFrame` loop, and `IntersectionObserver` gating regardless of how many elements you mount. No per-element listeners. No frame-budget tax.
9
+
10
+ ## Install
11
+
12
+ ```sh
13
+ npm install @tonybonet/magnetic
14
+ # or
15
+ pnpm add @tonybonet/magnetic
16
+ # or
17
+ yarn add @tonybonet/magnetic
18
+ ```
19
+
20
+ **Peer dependencies:** `react` (≥19.0.0) and `motion` (≥12.0.0).
21
+
22
+ ## Quickstart
23
+
24
+ ### Component (JSX)
25
+
26
+ ```tsx
27
+ import { Magnetic } from "@tonybonet/magnetic/component";
28
+
29
+ export function CTA() {
30
+ return (
31
+ <Magnetic options={{ maxOffset: 20 }}>
32
+ <button>Hover me</button>
33
+ </Magnetic>
34
+ );
35
+ }
36
+ ```
37
+
38
+ `<Magnetic>` owns its own `<LazyMotion>` boundary. If your app already wraps with `LazyMotion` at the root, Motion deduplicates — the wider feature set wins.
39
+
40
+ ### Headless hook
41
+
42
+ ```tsx
43
+ import { m } from "motion/react";
44
+ import { useMagnetic } from "@tonybonet/magnetic";
45
+
46
+ function Tile() {
47
+ const magnetic = useMagnetic({ maxOffset: 20 });
48
+ return (
49
+ <m.div ref={magnetic.ref} style={magnetic.style}>
50
+ Tile content
51
+ </m.div>
52
+ );
53
+ }
54
+ ```
55
+
56
+ ### Spring presets
57
+
58
+ ```tsx
59
+ import { TACTILE, SMOOTH, BOUNCY_SNAP } from "@tonybonet/magnetic/presets";
60
+
61
+ <Magnetic options={{ smoothSpring: TACTILE, snapSpring: BOUNCY_SNAP }}>
62
+ <button>Tactile feel</button>
63
+ </Magnetic>;
64
+ ```
65
+
66
+ ## Exports
67
+
68
+ | Subpath | What |
69
+ | ----------------------------------- | -------------------------------------------------------------------------------------------- |
70
+ | `@tonybonet/magnetic` | `useMagnetic` hook + `UseMagneticResult` type |
71
+ | `@tonybonet/magnetic/component` | `Magnetic` JSX component + `MagneticProps` type |
72
+ | `@tonybonet/magnetic/presets` | `TACTILE`, `SMOOTH`, `BOUNCY_SNAP`, `LINEAR_RESET`, `MAGNET_DEFAULTS` + `MagnetOptions` type |
73
+
74
+ ## Why a Shared Orchestrator?
75
+
76
+ The naive approach: every magnetic element attaches its own `pointermove` listener. 50 buttons = 50 listeners + 50 reflows per frame. Budget blown.
77
+
78
+ Magnetic uses a **singleton orchestrator** that shares:
79
+
80
+ - **One** `window.pointermove` listener (passive)
81
+ - **One** `requestAnimationFrame` loop (frame-coalesced)
82
+ - **One** `IntersectionObserver` (gates off-screen elements out of the per-frame cost)
83
+ - **One** `ResizeObserver` (invalidates cached bounding rects on layout change)
84
+
85
+ ```
86
+ ┌─────────────────────────────────────────────┐
87
+ │ MagnetOrchestrator │
88
+ │ ┌───────────┐ ┌──────┐ ┌───────────────┐ │
89
+ │ │pointermove│ │ rAF │ │IntersectionObs│ │
90
+ │ └─────┬─────┘ └──┬───┘ └───────┬───────┘ │
91
+ │ └───────────┼──────────────┘ │
92
+ │ ▼ │
93
+ │ ┌─────────────────────┐ │
94
+ │ │ Active Elements │ │
95
+ │ │ (on-screen only) │ │
96
+ │ └─────────────────────┘ │
97
+ └─────────────────────────────────────────────┘
98
+ ```
99
+
100
+ Window `blur` and `document.visibilitychange` reset all active elements to avoid stuck offsets when the user tabs away.
101
+
102
+ ## API
103
+
104
+ ### `useMagnetic(options?)`
105
+
106
+ Primary hook. Returns `{ ref, x, y, style, enabled, isComposing, reset }`.
107
+
108
+ | Field | Type | Description |
109
+ | ------------- | --------------------------- | ---------------------------------------------------- |
110
+ | `ref` | `(node: T \| null) => void` | Attach to the element that should track the pointer |
111
+ | `x` | `MotionValue<number>` | Spring-animated X offset |
112
+ | `y` | `MotionValue<number>` | Spring-animated Y offset |
113
+ | `style` | `{ x, y }` | Pass directly to a `motion` component's `style` prop |
114
+ | `enabled` | `boolean` | Whether the magnetic effect is active |
115
+ | `isComposing` | `boolean` | Whether the pointer is within `maxDistance` |
116
+ | `reset` | `() => void` | Reset to origin (stable identity across renders) |
117
+
118
+ ### `<Magnetic options? style? magneticKey? ...divProps>`
119
+
120
+ JSX convenience wrapper. Renders a `motion.div` with magnetic behavior.
121
+
122
+ | Prop | Type | Description |
123
+ | ------------- | ------------------------ | ------------------------------------------------------ |
124
+ | `options` | `MagnetOptions` | Configuration (see below) |
125
+ | `style` | `MotionStyle` | Additional motion styles to apply |
126
+ | `magneticKey` | `Key` | Override the derived structural key for forced remount |
127
+ | `...rest` | `HTMLMotionProps<"div">` | All standard motion.div props |
128
+
129
+ ### `MagnetOptions`
130
+
131
+ | Option | Type | Default | Category | Description |
132
+ | -------------- | --------------- | ------------- | ---------- | --------------------------------------------- |
133
+ | `enabled` | `boolean` | `true` | Structural | Turns the effect on/off (triggers remount) |
134
+ | `maxDistance` | `number` | `320` | Structural | Radius from center where pull begins |
135
+ | `maxOffset` | `number` | `64` | Structural | Maximum translation toward the pointer |
136
+ | `originX` | `number` | `0.5` | Structural | Horizontal anchor (`0` = left, `1` = right) |
137
+ | `originY` | `number` | `0.5` | Structural | Vertical anchor (`0` = top, `1` = bottom) |
138
+ | `elastic` | `boolean` | `true` | Structural | Use sine-curve mapping for pull strength |
139
+ | `smooth` | `boolean` | `true` | Visual | `true` = SMOOTH preset, `false` = TACTILE |
140
+ | `snap` | `boolean` | `true` | Visual | `true` = bounce back, `false` = linear return |
141
+ | `smoothSpring` | `SpringOptions` | `SMOOTH` | Visual | Override the hover spring |
142
+ | `snapSpring` | `SpringOptions` | `BOUNCY_SNAP` | Visual | Override the return spring |
143
+
144
+ **Structural** options trigger a remount when changed. **Visual** options update live.
145
+
146
+ ### Spring Presets
147
+
148
+ | Preset | stiffness | damping | mass | Use case |
149
+ | -------------- | --------- | ------- | ---- | ------------------------ |
150
+ | `TACTILE` | 400 | 30 | 0.5 | Snappy, responsive feel |
151
+ | `SMOOTH` | 150 | 20 | 1.2 | Gentle, fluid pull |
152
+ | `BOUNCY_SNAP` | 400 | 20 | 0.5 | Overshoot on return |
153
+ | `LINEAR_RESET` | 200 | 50 | 1 | Direct, no-bounce return |
154
+
155
+ ## Accessibility
156
+
157
+ `useMagnetic` calls `useReducedMotion()` from Motion and disables itself when the user has `prefers-reduced-motion: reduce` set. No configuration needed.
158
+
159
+ ## Compatibility
160
+
161
+ | Package | Range |
162
+ | -------- | --------- |
163
+ | `react` | `^19.0.0` |
164
+ | `motion` | `^12.0.0` |
165
+
166
+ ## License
167
+
168
+ MIT
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(`./types.cjs`),t=require(`./useMagnetic-hzN5_pe4.cjs`);let n=require(`motion/react`),r=require(`react`),i=require(`react/jsx-runtime`);function a(e){return!!e&&typeof e==`object`&&`current`in e}function o(e,t){if(typeof e==`function`)return e(t);a(e)&&(e.current=t)}function s(t){return`${t?.enabled??e.MAGNET_DEFAULTS.enabled}|${t?.maxDistance??e.MAGNET_DEFAULTS.maxDistance}|${t?.maxOffset??e.MAGNET_DEFAULTS.maxOffset}|${t?.elastic??e.MAGNET_DEFAULTS.elastic}|${t?.originX??e.MAGNET_DEFAULTS.originX}|${t?.originY??e.MAGNET_DEFAULTS.originY}`}function c({options:e,style:a,children:s,forwardedRef:c,...l}){let u=t.t(e),d=(0,r.useRef)(null);d.current===null?d.current={externalRef:c,magneticRef:u.ref}:(d.current.externalRef=c,d.current.magneticRef=u.ref);let f=(0,r.useRef)(null);f.current===null&&(f.current=e=>{let t=d.current,n=o(t.externalRef,e),r=t.magneticRef(e);return()=>{typeof r==`function`&&r(),typeof n==`function`&&n()}});let p=f.current;return(0,i.jsx)(n.m.div,{...l,ref:p,style:{...a,...u.style},children:s})}function l({children:e}){return(0,i.jsx)(n.LazyMotion,{features:n.domAnimation,strict:!0,children:e})}function u({options:t,magneticKey:r,style:a,children:o,ref:u,...d}){let f=(0,n.useReducedMotion)();if(!((t?.enabled??e.MAGNET_DEFAULTS.enabled)&&!f))return(0,i.jsx)(l,{children:(0,i.jsx)(n.m.div,{...d,ref:u,style:a,children:o})});let p=r??s(t);return(0,i.jsx)(l,{children:(0,i.jsx)(c,{options:t,style:a,forwardedRef:u,...d,children:o},p)})}u.displayName=`Magnetic`,exports.Magnetic=u;
2
+ //# sourceMappingURL=Magnetic.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Magnetic.cjs","names":["MAGNET_DEFAULTS","useMagnetic","m","LazyMotion","domAnimation"],"sources":["../src/Magnetic.tsx"],"sourcesContent":["import { useRef, type Key, type MutableRefObject, type ReactNode, type Ref } from \"react\";\nimport {\n LazyMotion,\n domAnimation,\n m,\n useReducedMotion,\n type HTMLMotionProps,\n type MotionStyle,\n} from \"motion/react\";\nimport { useMagnetic } from \"./useMagnetic\";\nimport { MAGNET_DEFAULTS, type MagnetOptions } from \"./types\";\n\ntype MagneticDivProps = HTMLMotionProps<\"div\">;\n\nexport interface MagneticProps extends Omit<MagneticDivProps, \"style\"> {\n options?: MagnetOptions;\n style?: MotionStyle;\n magneticKey?: Key;\n}\n\ntype RefCleanup = void | (() => void);\ntype CleanupRefCallback<T> = (instance: T | null) => RefCleanup;\n\nfunction isMutableRefObject<T>(ref: Ref<T> | undefined): ref is MutableRefObject<T | null> {\n return !!ref && typeof ref === \"object\" && \"current\" in ref;\n}\n\nfunction setForwardedRef<T>(ref: Ref<T> | undefined, node: T | null): RefCleanup {\n if (typeof ref === \"function\") {\n return (ref as CleanupRefCallback<T>)(node);\n }\n\n if (isMutableRefObject(ref)) {\n ref.current = node;\n }\n}\n\nfunction composeStructuralKey(options: MagnetOptions | undefined): string {\n const enabled = options?.enabled ?? MAGNET_DEFAULTS.enabled;\n const maxDistance = options?.maxDistance ?? MAGNET_DEFAULTS.maxDistance;\n const maxOffset = options?.maxOffset ?? MAGNET_DEFAULTS.maxOffset;\n const elastic = options?.elastic ?? MAGNET_DEFAULTS.elastic;\n const originX = options?.originX ?? MAGNET_DEFAULTS.originX;\n const originY = options?.originY ?? MAGNET_DEFAULTS.originY;\n\n return `${enabled}|${maxDistance}|${maxOffset}|${elastic}|${originX}|${originY}`;\n}\n\ninterface MagneticEnabledInnerProps extends Omit<MagneticProps, \"magneticKey\" | \"ref\"> {\n forwardedRef: Ref<HTMLDivElement> | undefined;\n}\n\ninterface MergedRefState {\n externalRef: Ref<HTMLDivElement> | undefined;\n magneticRef: (node: HTMLDivElement | null) => RefCleanup;\n}\n\n/**\n * Inner magnetic branch.\n * Structural options are already hardened by the outer key.\n */\nfunction MagneticEnabledInner({\n options,\n style,\n children,\n forwardedRef,\n ...rest\n}: MagneticEnabledInnerProps) {\n const magnetic = useMagnetic<HTMLDivElement>(options);\n\n const mergedRefStateRef = useRef<MergedRefState | null>(null);\n if (mergedRefStateRef.current === null) {\n mergedRefStateRef.current = {\n externalRef: forwardedRef,\n magneticRef: magnetic.ref,\n };\n } else {\n mergedRefStateRef.current.externalRef = forwardedRef;\n mergedRefStateRef.current.magneticRef = magnetic.ref;\n }\n\n const mergedRefRef = useRef<CleanupRefCallback<HTMLDivElement> | null>(null);\n\n if (mergedRefRef.current === null) {\n mergedRefRef.current = (node) => {\n const state = mergedRefStateRef.current!;\n const externalCleanup = setForwardedRef(state.externalRef, node);\n const magneticCleanup = state.magneticRef(node);\n\n return () => {\n if (typeof magneticCleanup === \"function\") magneticCleanup();\n if (typeof externalCleanup === \"function\") externalCleanup();\n };\n };\n }\n\n const mergedRef = mergedRefRef.current;\n\n return (\n <m.div {...rest} ref={mergedRef} style={{ ...style, ...magnetic.style }}>\n {children}\n </m.div>\n );\n}\n\n/**\n * Wraps children with a `<LazyMotion features={domAnimation} strict>` boundary.\n *\n * Motion deduplicates / unions nested `LazyMotion` providers, so an ancestor\n * `LazyMotion features={domMax}` (or any wider set) supplied by the host app\n * keeps its features inside this subtree.\n *\n * This removes the historical contract that callers must wrap with LazyMotion\n * themselves — `<Magnetic>` is now self-contained.\n */\nfunction MagneticLazyMotion({ children }: { children: ReactNode }) {\n return (\n <LazyMotion features={domAnimation} strict>\n {children}\n </LazyMotion>\n );\n}\n\n/**\n * Public primitive.\n *\n * Structural option changes remount the magnetic instance automatically.\n * `magneticKey` overrides the derived structural key when provided.\n *\n * Owns its own `<LazyMotion features={domAnimation} strict>` boundary; callers\n * no longer need to provide one.\n */\nexport function Magnetic({\n options,\n magneticKey,\n style,\n children,\n ref: forwardedRef,\n ...rest\n}: MagneticProps) {\n const reducedMotion = useReducedMotion();\n const isEnabled = (options?.enabled ?? MAGNET_DEFAULTS.enabled) && !reducedMotion;\n\n if (!isEnabled) {\n return (\n <MagneticLazyMotion>\n <m.div {...rest} ref={forwardedRef} style={style}>\n {children}\n </m.div>\n </MagneticLazyMotion>\n );\n }\n\n const resolvedMagneticKey = magneticKey ?? composeStructuralKey(options);\n\n return (\n <MagneticLazyMotion>\n <MagneticEnabledInner\n key={resolvedMagneticKey}\n options={options}\n style={style}\n forwardedRef={forwardedRef}\n {...rest}\n >\n {children}\n </MagneticEnabledInner>\n </MagneticLazyMotion>\n );\n}\n\nMagnetic.displayName = \"Magnetic\";\n"],"mappings":"0NAuBA,SAAS,EAAsB,EAA4D,CACzF,MAAO,CAAC,CAAC,GAAO,OAAO,GAAQ,UAAY,YAAa,EAG1D,SAAS,EAAmB,EAAyB,EAA4B,CAC/E,GAAI,OAAO,GAAQ,WACjB,OAAQ,EAA8B,EAAK,CAGzC,EAAmB,EAAI,GACzB,EAAI,QAAU,GAIlB,SAAS,EAAqB,EAA4C,CAQxE,MAAO,GAPS,GAAS,SAAWA,EAAAA,gBAAgB,QAOlC,GANE,GAAS,aAAeA,EAAAA,gBAAgB,YAM3B,GALf,GAAS,WAAaA,EAAAA,gBAAgB,UAKV,GAJ9B,GAAS,SAAWA,EAAAA,gBAAgB,QAIK,GAHzC,GAAS,SAAWA,EAAAA,gBAAgB,QAGgB,GAFpD,GAAS,SAAWA,EAAAA,gBAAgB,UAkBtD,SAAS,EAAqB,CAC5B,UACA,QACA,WACA,eACA,GAAG,GACyB,CAC5B,IAAM,EAAWC,EAAAA,EAA4B,EAAQ,CAE/C,GAAA,EAAA,EAAA,QAAkD,KAAK,CACzD,EAAkB,UAAY,KAChC,EAAkB,QAAU,CAC1B,YAAa,EACb,YAAa,EAAS,IACvB,EAED,EAAkB,QAAQ,YAAc,EACxC,EAAkB,QAAQ,YAAc,EAAS,KAGnD,IAAM,GAAA,EAAA,EAAA,QAAiE,KAAK,CAExE,EAAa,UAAY,OAC3B,EAAa,QAAW,GAAS,CAC/B,IAAM,EAAQ,EAAkB,QAC1B,EAAkB,EAAgB,EAAM,YAAa,EAAK,CAC1D,EAAkB,EAAM,YAAY,EAAK,CAE/C,UAAa,CACP,OAAO,GAAoB,YAAY,GAAiB,CACxD,OAAO,GAAoB,YAAY,GAAiB,IAKlE,IAAM,EAAY,EAAa,QAE/B,OACE,EAAA,EAAA,KAACC,EAAAA,EAAE,IAAH,CAAO,GAAI,EAAM,IAAK,EAAW,MAAO,CAAE,GAAG,EAAO,GAAG,EAAS,MAAO,CACpE,WACK,CAAA,CAcZ,SAAS,EAAmB,CAAE,YAAqC,CACjE,OACE,EAAA,EAAA,KAACC,EAAAA,WAAD,CAAY,SAAUC,EAAAA,aAAc,OAAA,GACjC,WACU,CAAA,CAajB,SAAgB,EAAS,CACvB,UACA,cACA,QACA,WACA,IAAK,EACL,GAAG,GACa,CAChB,IAAM,GAAA,EAAA,EAAA,mBAAkC,CAGxC,GAAI,GAFe,GAAS,SAAWJ,EAAAA,gBAAgB,UAAY,CAAC,GAGlE,OACE,EAAA,EAAA,KAAC,EAAD,CAAA,UACE,EAAA,EAAA,KAACE,EAAAA,EAAE,IAAH,CAAO,GAAI,EAAM,IAAK,EAAqB,QACxC,WACK,CAAA,CACW,CAAA,CAIzB,IAAM,EAAsB,GAAe,EAAqB,EAAQ,CAExE,OACE,EAAA,EAAA,KAAC,EAAD,CAAA,UACE,EAAA,EAAA,KAAC,EAAD,CAEW,UACF,QACO,eACd,GAAI,EAEH,WACoB,CAPhB,EAOgB,CACJ,CAAA,CAIzB,EAAS,YAAc"}
@@ -0,0 +1,35 @@
1
+ import { MagnetOptions } from "./types.cjs";
2
+ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
3
+ import { Key } from "react";
4
+ import { HTMLMotionProps, MotionStyle } from "motion/react";
5
+
6
+ //#region src/Magnetic.d.ts
7
+ type MagneticDivProps = HTMLMotionProps<"div">;
8
+ interface MagneticProps extends Omit<MagneticDivProps, "style"> {
9
+ options?: MagnetOptions;
10
+ style?: MotionStyle;
11
+ magneticKey?: Key;
12
+ }
13
+ /**
14
+ * Public primitive.
15
+ *
16
+ * Structural option changes remount the magnetic instance automatically.
17
+ * `magneticKey` overrides the derived structural key when provided.
18
+ *
19
+ * Owns its own `<LazyMotion features={domAnimation} strict>` boundary; callers
20
+ * no longer need to provide one.
21
+ */
22
+ declare function Magnetic({
23
+ options,
24
+ magneticKey,
25
+ style,
26
+ children,
27
+ ref: forwardedRef,
28
+ ...rest
29
+ }: MagneticProps): _$react_jsx_runtime0.JSX.Element;
30
+ declare namespace Magnetic {
31
+ var displayName: string;
32
+ }
33
+ //#endregion
34
+ export { Magnetic, MagneticProps };
35
+ //# sourceMappingURL=Magnetic.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Magnetic.d.cts","names":[],"sources":["../src/Magnetic.tsx"],"mappings":";;;;;;KAYK,gBAAA,GAAmB,eAAA;AAAA,UAEP,aAAA,SAAsB,IAAA,CAAK,gBAAA;EAC1C,OAAA,GAAU,aAAA;EACV,KAAA,GAAQ,WAAA;EACR,WAAA,GAAc,GAAA;AAAA;;;AAHhB;;;;;;;iBAsHgB,QAAA,CAAA;EACd,OAAA;EACA,WAAA;EACA,KAAA;EACA,QAAA;EACA,GAAA,EAAK,YAAA;EAAA,GACF;AAAA,GACF,aAAA,GAAa,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,kBAPA,QAAA;EAAA,IAAQ,WAAA;AAAA"}
@@ -0,0 +1,35 @@
1
+ import { MagnetOptions } from "./types.mjs";
2
+ import { HTMLMotionProps, MotionStyle } from "motion/react";
3
+ import { Key } from "react";
4
+ import * as _$react_jsx_runtime0 from "react/jsx-runtime";
5
+
6
+ //#region src/Magnetic.d.ts
7
+ type MagneticDivProps = HTMLMotionProps<"div">;
8
+ interface MagneticProps extends Omit<MagneticDivProps, "style"> {
9
+ options?: MagnetOptions;
10
+ style?: MotionStyle;
11
+ magneticKey?: Key;
12
+ }
13
+ /**
14
+ * Public primitive.
15
+ *
16
+ * Structural option changes remount the magnetic instance automatically.
17
+ * `magneticKey` overrides the derived structural key when provided.
18
+ *
19
+ * Owns its own `<LazyMotion features={domAnimation} strict>` boundary; callers
20
+ * no longer need to provide one.
21
+ */
22
+ declare function Magnetic({
23
+ options,
24
+ magneticKey,
25
+ style,
26
+ children,
27
+ ref: forwardedRef,
28
+ ...rest
29
+ }: MagneticProps): _$react_jsx_runtime0.JSX.Element;
30
+ declare namespace Magnetic {
31
+ var displayName: string;
32
+ }
33
+ //#endregion
34
+ export { Magnetic, MagneticProps };
35
+ //# sourceMappingURL=Magnetic.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Magnetic.d.mts","names":[],"sources":["../src/Magnetic.tsx"],"mappings":";;;;;;KAYK,gBAAA,GAAmB,eAAA;AAAA,UAEP,aAAA,SAAsB,IAAA,CAAK,gBAAA;EAC1C,OAAA,GAAU,aAAA;EACV,KAAA,GAAQ,WAAA;EACR,WAAA,GAAc,GAAA;AAAA;;;AAHhB;;;;;;;iBAsHgB,QAAA,CAAA;EACd,OAAA;EACA,WAAA;EACA,KAAA;EACA,QAAA;EACA,GAAA,EAAK,YAAA;EAAA,GACF;AAAA,GACF,aAAA,GAAa,oBAAA,CAAA,GAAA,CAAA,OAAA;AAAA,kBAPA,QAAA;EAAA,IAAQ,WAAA;AAAA"}
@@ -0,0 +1,2 @@
1
+ import{MAGNET_DEFAULTS as e}from"./types.mjs";import{t}from"./useMagnetic-Cih9JSy3.mjs";import{LazyMotion as n,domAnimation as r,m as i,useReducedMotion as a}from"motion/react";import{useRef as o}from"react";import{jsx as s}from"react/jsx-runtime";function c(e){return!!e&&typeof e==`object`&&`current`in e}function l(e,t){if(typeof e==`function`)return e(t);c(e)&&(e.current=t)}function u(t){return`${t?.enabled??e.enabled}|${t?.maxDistance??e.maxDistance}|${t?.maxOffset??e.maxOffset}|${t?.elastic??e.elastic}|${t?.originX??e.originX}|${t?.originY??e.originY}`}function d({options:e,style:n,children:r,forwardedRef:a,...c}){let u=t(e),d=o(null);d.current===null?d.current={externalRef:a,magneticRef:u.ref}:(d.current.externalRef=a,d.current.magneticRef=u.ref);let f=o(null);f.current===null&&(f.current=e=>{let t=d.current,n=l(t.externalRef,e),r=t.magneticRef(e);return()=>{typeof r==`function`&&r(),typeof n==`function`&&n()}});let p=f.current;return s(i.div,{...c,ref:p,style:{...n,...u.style},children:r})}function f({children:e}){return s(n,{features:r,strict:!0,children:e})}function p({options:t,magneticKey:n,style:r,children:o,ref:c,...l}){let p=a();if(!((t?.enabled??e.enabled)&&!p))return s(f,{children:s(i.div,{...l,ref:c,style:r,children:o})});let m=n??u(t);return s(f,{children:s(d,{options:t,style:r,forwardedRef:c,...l,children:o},m)})}p.displayName=`Magnetic`;export{p as Magnetic};
2
+ //# sourceMappingURL=Magnetic.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Magnetic.mjs","names":[],"sources":["../src/Magnetic.tsx"],"sourcesContent":["import { useRef, type Key, type MutableRefObject, type ReactNode, type Ref } from \"react\";\nimport {\n LazyMotion,\n domAnimation,\n m,\n useReducedMotion,\n type HTMLMotionProps,\n type MotionStyle,\n} from \"motion/react\";\nimport { useMagnetic } from \"./useMagnetic\";\nimport { MAGNET_DEFAULTS, type MagnetOptions } from \"./types\";\n\ntype MagneticDivProps = HTMLMotionProps<\"div\">;\n\nexport interface MagneticProps extends Omit<MagneticDivProps, \"style\"> {\n options?: MagnetOptions;\n style?: MotionStyle;\n magneticKey?: Key;\n}\n\ntype RefCleanup = void | (() => void);\ntype CleanupRefCallback<T> = (instance: T | null) => RefCleanup;\n\nfunction isMutableRefObject<T>(ref: Ref<T> | undefined): ref is MutableRefObject<T | null> {\n return !!ref && typeof ref === \"object\" && \"current\" in ref;\n}\n\nfunction setForwardedRef<T>(ref: Ref<T> | undefined, node: T | null): RefCleanup {\n if (typeof ref === \"function\") {\n return (ref as CleanupRefCallback<T>)(node);\n }\n\n if (isMutableRefObject(ref)) {\n ref.current = node;\n }\n}\n\nfunction composeStructuralKey(options: MagnetOptions | undefined): string {\n const enabled = options?.enabled ?? MAGNET_DEFAULTS.enabled;\n const maxDistance = options?.maxDistance ?? MAGNET_DEFAULTS.maxDistance;\n const maxOffset = options?.maxOffset ?? MAGNET_DEFAULTS.maxOffset;\n const elastic = options?.elastic ?? MAGNET_DEFAULTS.elastic;\n const originX = options?.originX ?? MAGNET_DEFAULTS.originX;\n const originY = options?.originY ?? MAGNET_DEFAULTS.originY;\n\n return `${enabled}|${maxDistance}|${maxOffset}|${elastic}|${originX}|${originY}`;\n}\n\ninterface MagneticEnabledInnerProps extends Omit<MagneticProps, \"magneticKey\" | \"ref\"> {\n forwardedRef: Ref<HTMLDivElement> | undefined;\n}\n\ninterface MergedRefState {\n externalRef: Ref<HTMLDivElement> | undefined;\n magneticRef: (node: HTMLDivElement | null) => RefCleanup;\n}\n\n/**\n * Inner magnetic branch.\n * Structural options are already hardened by the outer key.\n */\nfunction MagneticEnabledInner({\n options,\n style,\n children,\n forwardedRef,\n ...rest\n}: MagneticEnabledInnerProps) {\n const magnetic = useMagnetic<HTMLDivElement>(options);\n\n const mergedRefStateRef = useRef<MergedRefState | null>(null);\n if (mergedRefStateRef.current === null) {\n mergedRefStateRef.current = {\n externalRef: forwardedRef,\n magneticRef: magnetic.ref,\n };\n } else {\n mergedRefStateRef.current.externalRef = forwardedRef;\n mergedRefStateRef.current.magneticRef = magnetic.ref;\n }\n\n const mergedRefRef = useRef<CleanupRefCallback<HTMLDivElement> | null>(null);\n\n if (mergedRefRef.current === null) {\n mergedRefRef.current = (node) => {\n const state = mergedRefStateRef.current!;\n const externalCleanup = setForwardedRef(state.externalRef, node);\n const magneticCleanup = state.magneticRef(node);\n\n return () => {\n if (typeof magneticCleanup === \"function\") magneticCleanup();\n if (typeof externalCleanup === \"function\") externalCleanup();\n };\n };\n }\n\n const mergedRef = mergedRefRef.current;\n\n return (\n <m.div {...rest} ref={mergedRef} style={{ ...style, ...magnetic.style }}>\n {children}\n </m.div>\n );\n}\n\n/**\n * Wraps children with a `<LazyMotion features={domAnimation} strict>` boundary.\n *\n * Motion deduplicates / unions nested `LazyMotion` providers, so an ancestor\n * `LazyMotion features={domMax}` (or any wider set) supplied by the host app\n * keeps its features inside this subtree.\n *\n * This removes the historical contract that callers must wrap with LazyMotion\n * themselves — `<Magnetic>` is now self-contained.\n */\nfunction MagneticLazyMotion({ children }: { children: ReactNode }) {\n return (\n <LazyMotion features={domAnimation} strict>\n {children}\n </LazyMotion>\n );\n}\n\n/**\n * Public primitive.\n *\n * Structural option changes remount the magnetic instance automatically.\n * `magneticKey` overrides the derived structural key when provided.\n *\n * Owns its own `<LazyMotion features={domAnimation} strict>` boundary; callers\n * no longer need to provide one.\n */\nexport function Magnetic({\n options,\n magneticKey,\n style,\n children,\n ref: forwardedRef,\n ...rest\n}: MagneticProps) {\n const reducedMotion = useReducedMotion();\n const isEnabled = (options?.enabled ?? MAGNET_DEFAULTS.enabled) && !reducedMotion;\n\n if (!isEnabled) {\n return (\n <MagneticLazyMotion>\n <m.div {...rest} ref={forwardedRef} style={style}>\n {children}\n </m.div>\n </MagneticLazyMotion>\n );\n }\n\n const resolvedMagneticKey = magneticKey ?? composeStructuralKey(options);\n\n return (\n <MagneticLazyMotion>\n <MagneticEnabledInner\n key={resolvedMagneticKey}\n options={options}\n style={style}\n forwardedRef={forwardedRef}\n {...rest}\n >\n {children}\n </MagneticEnabledInner>\n </MagneticLazyMotion>\n );\n}\n\nMagnetic.displayName = \"Magnetic\";\n"],"mappings":"wPAuBA,SAAS,EAAsB,EAA4D,CACzF,MAAO,CAAC,CAAC,GAAO,OAAO,GAAQ,UAAY,YAAa,EAG1D,SAAS,EAAmB,EAAyB,EAA4B,CAC/E,GAAI,OAAO,GAAQ,WACjB,OAAQ,EAA8B,EAAK,CAGzC,EAAmB,EAAI,GACzB,EAAI,QAAU,GAIlB,SAAS,EAAqB,EAA4C,CAQxE,MAAO,GAPS,GAAS,SAAW,EAAgB,QAOlC,GANE,GAAS,aAAe,EAAgB,YAM3B,GALf,GAAS,WAAa,EAAgB,UAKV,GAJ9B,GAAS,SAAW,EAAgB,QAIK,GAHzC,GAAS,SAAW,EAAgB,QAGgB,GAFpD,GAAS,SAAW,EAAgB,UAkBtD,SAAS,EAAqB,CAC5B,UACA,QACA,WACA,eACA,GAAG,GACyB,CAC5B,IAAM,EAAW,EAA4B,EAAQ,CAE/C,EAAoB,EAA8B,KAAK,CACzD,EAAkB,UAAY,KAChC,EAAkB,QAAU,CAC1B,YAAa,EACb,YAAa,EAAS,IACvB,EAED,EAAkB,QAAQ,YAAc,EACxC,EAAkB,QAAQ,YAAc,EAAS,KAGnD,IAAM,EAAe,EAAkD,KAAK,CAExE,EAAa,UAAY,OAC3B,EAAa,QAAW,GAAS,CAC/B,IAAM,EAAQ,EAAkB,QAC1B,EAAkB,EAAgB,EAAM,YAAa,EAAK,CAC1D,EAAkB,EAAM,YAAY,EAAK,CAE/C,UAAa,CACP,OAAO,GAAoB,YAAY,GAAiB,CACxD,OAAO,GAAoB,YAAY,GAAiB,IAKlE,IAAM,EAAY,EAAa,QAE/B,OACE,EAAC,EAAE,IAAH,CAAO,GAAI,EAAM,IAAK,EAAW,MAAO,CAAE,GAAG,EAAO,GAAG,EAAS,MAAO,CACpE,WACK,CAAA,CAcZ,SAAS,EAAmB,CAAE,YAAqC,CACjE,OACE,EAAC,EAAD,CAAY,SAAU,EAAc,OAAA,GACjC,WACU,CAAA,CAajB,SAAgB,EAAS,CACvB,UACA,cACA,QACA,WACA,IAAK,EACL,GAAG,GACa,CAChB,IAAM,EAAgB,GAAkB,CAGxC,GAAI,GAFe,GAAS,SAAW,EAAgB,UAAY,CAAC,GAGlE,OACE,EAAC,EAAD,CAAA,SACE,EAAC,EAAE,IAAH,CAAO,GAAI,EAAM,IAAK,EAAqB,QACxC,WACK,CAAA,CACW,CAAA,CAIzB,IAAM,EAAsB,GAAe,EAAqB,EAAQ,CAExE,OACE,EAAC,EAAD,CAAA,SACE,EAAC,EAAD,CAEW,UACF,QACO,eACd,GAAI,EAEH,WACoB,CAPhB,EAOgB,CACJ,CAAA,CAIzB,EAAS,YAAc"}
package/dist/types.cjs ADDED
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e={stiffness:400,damping:30,mass:.5},t={stiffness:150,damping:20,mass:1.2},n={stiffness:400,damping:20,mass:.5,restDelta:.001},r={stiffness:200,damping:50,mass:1},i={enabled:!0,maxDistance:320,maxOffset:64,originX:.5,originY:.5,elastic:!0,smooth:!0,snap:!0,smoothSpring:t,snapSpring:n,trigger:`distance`};exports.BOUNCY_SNAP=n,exports.LINEAR_RESET=r,exports.MAGNET_DEFAULTS=i,exports.SMOOTH=t,exports.TACTILE=e;
2
+ //# sourceMappingURL=types.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.cjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["import type { SpringOptions } from \"motion/react\";\n\n/**\n * Configuration options for the Magnetic component.\n *\n * Structural options define the identity of a magnetic instance.\n * Changing them requires a remount.\n *\n * Visual options only affect how the instance feels.\n * They can update live without remounting.\n */\nexport interface MagnetOptions {\n /**\n * **Structural Option**\n *\n * Turns the magnetic effect on or off.\n * Changing this triggers a remount to ensure a clean state.\n *\n * @default true\n */\n enabled?: boolean;\n\n /**\n * **Structural Option**\n *\n * Maximum distance (radius) from center where pull begins.\n * Changing this triggers a remount.\n *\n * @default 320\n */\n maxDistance?: number;\n\n /**\n * **Structural Option**\n *\n * Maximum translation toward the pointer.\n * Changing this triggers a remount.\n *\n * @default 64\n */\n maxOffset?: number;\n\n /**\n * **Structural Option**\n *\n * Horizontal anchor for the magnetic origin inside the element bounds (`0` = left edge, `1` = right).\n * Changing this triggers a remount.\n *\n * @default 0.5\n */\n originX?: number;\n\n /**\n * **Structural Option**\n *\n * Vertical anchor for the magnetic origin (`0` = top, `1` = bottom). Values above `0.5` bias the\n * pull point downward (useful for tall controls where the “mass” should feel lower).\n * Changing this triggers a remount.\n *\n * @default 0.5\n */\n originY?: number;\n\n /**\n * **Structural Option**\n *\n * Whether pull translates using an elastic curve mapping.\n * Changing this triggers a remount.\n *\n * @default true\n */\n elastic?: boolean;\n\n /**\n * **Visual Option**\n *\n * Selects the hover preset.\n * - `true`: uses `SMOOTH`\n * - `false`: uses `TACTILE`\n *\n * Can update live without remounting.\n *\n * @default true\n */\n smooth?: boolean;\n\n /**\n * **Visual Option**\n *\n * Controls return behavior.\n * - `true`: use snap-back physics (`BOUNCY_SNAP`)\n * - `false`: use linear reset (`LINEAR_RESET`)\n *\n * Can update live without remounting.\n *\n * @default true\n */\n snap?: boolean;\n\n /**\n * **Visual Option**\n *\n * Overrides the hover spring.\n */\n smoothSpring?: SpringOptions;\n\n /**\n * **Visual Option**\n *\n * Overrides the return spring.\n * Only used when `snap !== false`.\n */\n snapSpring?: SpringOptions;\n\n /**\n * **Structural Option**\n *\n * How the magnetic effect is triggered.\n * - `\"distance\"`: activates when pointer is within `maxDistance` (default, shared orchestrator)\n * - `\"hover\"`: activates only when pointer is directly over the element\n * - `\"click\"`: click to lock and follow cursor, click again to release\n *\n * Changing this triggers a remount.\n *\n * @default \"distance\"\n */\n trigger?: \"distance\" | \"hover\" | \"click\";\n}\n\nexport const TACTILE = {\n stiffness: 400,\n damping: 30,\n mass: 0.5,\n} satisfies SpringOptions;\n\nexport const SMOOTH = {\n stiffness: 150,\n damping: 20,\n mass: 1.2,\n} satisfies SpringOptions;\n\nexport const BOUNCY_SNAP = {\n stiffness: 400,\n damping: 20,\n mass: 0.5,\n restDelta: 0.001,\n} satisfies SpringOptions;\n\nexport const LINEAR_RESET = {\n stiffness: 200,\n damping: 50,\n mass: 1,\n} satisfies SpringOptions;\n\nexport const MAGNET_DEFAULTS = {\n enabled: true,\n maxDistance: 320,\n maxOffset: 64,\n originX: 0.5,\n originY: 0.5,\n elastic: true,\n smooth: true,\n snap: true,\n smoothSpring: SMOOTH,\n snapSpring: BOUNCY_SNAP,\n trigger: \"distance\",\n} satisfies Required<MagnetOptions>;\n"],"mappings":"mEAiIA,MAAa,EAAU,CACrB,UAAW,IACX,QAAS,GACT,KAAM,GACP,CAEY,EAAS,CACpB,UAAW,IACX,QAAS,GACT,KAAM,IACP,CAEY,EAAc,CACzB,UAAW,IACX,QAAS,GACT,KAAM,GACN,UAAW,KACZ,CAEY,EAAe,CAC1B,UAAW,IACX,QAAS,GACT,KAAM,EACP,CAEY,EAAkB,CAC7B,QAAS,GACT,YAAa,IACb,UAAW,GACX,QAAS,GACT,QAAS,GACT,QAAS,GACT,OAAQ,GACR,KAAM,GACN,aAAc,EACd,WAAY,EACZ,QAAS,WACV"}
@@ -0,0 +1,165 @@
1
+ import { SpringOptions } from "motion/react";
2
+
3
+ //#region src/types.d.ts
4
+ /**
5
+ * Configuration options for the Magnetic component.
6
+ *
7
+ * Structural options define the identity of a magnetic instance.
8
+ * Changing them requires a remount.
9
+ *
10
+ * Visual options only affect how the instance feels.
11
+ * They can update live without remounting.
12
+ */
13
+ interface MagnetOptions {
14
+ /**
15
+ * **Structural Option**
16
+ *
17
+ * Turns the magnetic effect on or off.
18
+ * Changing this triggers a remount to ensure a clean state.
19
+ *
20
+ * @default true
21
+ */
22
+ enabled?: boolean;
23
+ /**
24
+ * **Structural Option**
25
+ *
26
+ * Maximum distance (radius) from center where pull begins.
27
+ * Changing this triggers a remount.
28
+ *
29
+ * @default 320
30
+ */
31
+ maxDistance?: number;
32
+ /**
33
+ * **Structural Option**
34
+ *
35
+ * Maximum translation toward the pointer.
36
+ * Changing this triggers a remount.
37
+ *
38
+ * @default 64
39
+ */
40
+ maxOffset?: number;
41
+ /**
42
+ * **Structural Option**
43
+ *
44
+ * Horizontal anchor for the magnetic origin inside the element bounds (`0` = left edge, `1` = right).
45
+ * Changing this triggers a remount.
46
+ *
47
+ * @default 0.5
48
+ */
49
+ originX?: number;
50
+ /**
51
+ * **Structural Option**
52
+ *
53
+ * Vertical anchor for the magnetic origin (`0` = top, `1` = bottom). Values above `0.5` bias the
54
+ * pull point downward (useful for tall controls where the “mass” should feel lower).
55
+ * Changing this triggers a remount.
56
+ *
57
+ * @default 0.5
58
+ */
59
+ originY?: number;
60
+ /**
61
+ * **Structural Option**
62
+ *
63
+ * Whether pull translates using an elastic curve mapping.
64
+ * Changing this triggers a remount.
65
+ *
66
+ * @default true
67
+ */
68
+ elastic?: boolean;
69
+ /**
70
+ * **Visual Option**
71
+ *
72
+ * Selects the hover preset.
73
+ * - `true`: uses `SMOOTH`
74
+ * - `false`: uses `TACTILE`
75
+ *
76
+ * Can update live without remounting.
77
+ *
78
+ * @default true
79
+ */
80
+ smooth?: boolean;
81
+ /**
82
+ * **Visual Option**
83
+ *
84
+ * Controls return behavior.
85
+ * - `true`: use snap-back physics (`BOUNCY_SNAP`)
86
+ * - `false`: use linear reset (`LINEAR_RESET`)
87
+ *
88
+ * Can update live without remounting.
89
+ *
90
+ * @default true
91
+ */
92
+ snap?: boolean;
93
+ /**
94
+ * **Visual Option**
95
+ *
96
+ * Overrides the hover spring.
97
+ */
98
+ smoothSpring?: SpringOptions;
99
+ /**
100
+ * **Visual Option**
101
+ *
102
+ * Overrides the return spring.
103
+ * Only used when `snap !== false`.
104
+ */
105
+ snapSpring?: SpringOptions;
106
+ /**
107
+ * **Structural Option**
108
+ *
109
+ * How the magnetic effect is triggered.
110
+ * - `"distance"`: activates when pointer is within `maxDistance` (default, shared orchestrator)
111
+ * - `"hover"`: activates only when pointer is directly over the element
112
+ * - `"click"`: click to lock and follow cursor, click again to release
113
+ *
114
+ * Changing this triggers a remount.
115
+ *
116
+ * @default "distance"
117
+ */
118
+ trigger?: "distance" | "hover" | "click";
119
+ }
120
+ declare const TACTILE: {
121
+ stiffness: number;
122
+ damping: number;
123
+ mass: number;
124
+ };
125
+ declare const SMOOTH: {
126
+ stiffness: number;
127
+ damping: number;
128
+ mass: number;
129
+ };
130
+ declare const BOUNCY_SNAP: {
131
+ stiffness: number;
132
+ damping: number;
133
+ mass: number;
134
+ restDelta: number;
135
+ };
136
+ declare const LINEAR_RESET: {
137
+ stiffness: number;
138
+ damping: number;
139
+ mass: number;
140
+ };
141
+ declare const MAGNET_DEFAULTS: {
142
+ enabled: true;
143
+ maxDistance: number;
144
+ maxOffset: number;
145
+ originX: number;
146
+ originY: number;
147
+ elastic: true;
148
+ smooth: true;
149
+ snap: true;
150
+ smoothSpring: {
151
+ stiffness: number;
152
+ damping: number;
153
+ mass: number;
154
+ };
155
+ snapSpring: {
156
+ stiffness: number;
157
+ damping: number;
158
+ mass: number;
159
+ restDelta: number;
160
+ };
161
+ trigger: "distance";
162
+ };
163
+ //#endregion
164
+ export { BOUNCY_SNAP, LINEAR_RESET, MAGNET_DEFAULTS, MagnetOptions, SMOOTH, TACTILE };
165
+ //# sourceMappingURL=types.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.cts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;AAWA;;;;;;;UAAiB,aAAA;EAkDf;;;;;;;;EAzCA,OAAA;EA0GO;;AAGT;;;;;;EAnGE,WAAA;;;AAyGF;;;;;;EA/FE,SAAA;;;AAqGF;;;;;;EA3FE,OAAA;;;;AAkGF;;;;;;EAvFE,OAAA;;;AA6FF;;;;;;EAnFE,OAAA;;;;;;;;;;;;EAaA,MAAA;;;;;;;;;;;;EAaA,IAAA;;;;;;EAOA,YAAA,GAAe,aAAA;;;;;;;EAQf,UAAA,GAAa,aAAA;;;;;;;;;;;;;EAcb,OAAA;AAAA;AAAA,cAGW,OAAA;;;;;cAMA,MAAA;;;;;cAMA,WAAA;;;;;;cAOA,YAAA;;;;;cAMA,eAAA"}
@@ -0,0 +1,165 @@
1
+ import { SpringOptions } from "motion/react";
2
+
3
+ //#region src/types.d.ts
4
+ /**
5
+ * Configuration options for the Magnetic component.
6
+ *
7
+ * Structural options define the identity of a magnetic instance.
8
+ * Changing them requires a remount.
9
+ *
10
+ * Visual options only affect how the instance feels.
11
+ * They can update live without remounting.
12
+ */
13
+ interface MagnetOptions {
14
+ /**
15
+ * **Structural Option**
16
+ *
17
+ * Turns the magnetic effect on or off.
18
+ * Changing this triggers a remount to ensure a clean state.
19
+ *
20
+ * @default true
21
+ */
22
+ enabled?: boolean;
23
+ /**
24
+ * **Structural Option**
25
+ *
26
+ * Maximum distance (radius) from center where pull begins.
27
+ * Changing this triggers a remount.
28
+ *
29
+ * @default 320
30
+ */
31
+ maxDistance?: number;
32
+ /**
33
+ * **Structural Option**
34
+ *
35
+ * Maximum translation toward the pointer.
36
+ * Changing this triggers a remount.
37
+ *
38
+ * @default 64
39
+ */
40
+ maxOffset?: number;
41
+ /**
42
+ * **Structural Option**
43
+ *
44
+ * Horizontal anchor for the magnetic origin inside the element bounds (`0` = left edge, `1` = right).
45
+ * Changing this triggers a remount.
46
+ *
47
+ * @default 0.5
48
+ */
49
+ originX?: number;
50
+ /**
51
+ * **Structural Option**
52
+ *
53
+ * Vertical anchor for the magnetic origin (`0` = top, `1` = bottom). Values above `0.5` bias the
54
+ * pull point downward (useful for tall controls where the “mass” should feel lower).
55
+ * Changing this triggers a remount.
56
+ *
57
+ * @default 0.5
58
+ */
59
+ originY?: number;
60
+ /**
61
+ * **Structural Option**
62
+ *
63
+ * Whether pull translates using an elastic curve mapping.
64
+ * Changing this triggers a remount.
65
+ *
66
+ * @default true
67
+ */
68
+ elastic?: boolean;
69
+ /**
70
+ * **Visual Option**
71
+ *
72
+ * Selects the hover preset.
73
+ * - `true`: uses `SMOOTH`
74
+ * - `false`: uses `TACTILE`
75
+ *
76
+ * Can update live without remounting.
77
+ *
78
+ * @default true
79
+ */
80
+ smooth?: boolean;
81
+ /**
82
+ * **Visual Option**
83
+ *
84
+ * Controls return behavior.
85
+ * - `true`: use snap-back physics (`BOUNCY_SNAP`)
86
+ * - `false`: use linear reset (`LINEAR_RESET`)
87
+ *
88
+ * Can update live without remounting.
89
+ *
90
+ * @default true
91
+ */
92
+ snap?: boolean;
93
+ /**
94
+ * **Visual Option**
95
+ *
96
+ * Overrides the hover spring.
97
+ */
98
+ smoothSpring?: SpringOptions;
99
+ /**
100
+ * **Visual Option**
101
+ *
102
+ * Overrides the return spring.
103
+ * Only used when `snap !== false`.
104
+ */
105
+ snapSpring?: SpringOptions;
106
+ /**
107
+ * **Structural Option**
108
+ *
109
+ * How the magnetic effect is triggered.
110
+ * - `"distance"`: activates when pointer is within `maxDistance` (default, shared orchestrator)
111
+ * - `"hover"`: activates only when pointer is directly over the element
112
+ * - `"click"`: click to lock and follow cursor, click again to release
113
+ *
114
+ * Changing this triggers a remount.
115
+ *
116
+ * @default "distance"
117
+ */
118
+ trigger?: "distance" | "hover" | "click";
119
+ }
120
+ declare const TACTILE: {
121
+ stiffness: number;
122
+ damping: number;
123
+ mass: number;
124
+ };
125
+ declare const SMOOTH: {
126
+ stiffness: number;
127
+ damping: number;
128
+ mass: number;
129
+ };
130
+ declare const BOUNCY_SNAP: {
131
+ stiffness: number;
132
+ damping: number;
133
+ mass: number;
134
+ restDelta: number;
135
+ };
136
+ declare const LINEAR_RESET: {
137
+ stiffness: number;
138
+ damping: number;
139
+ mass: number;
140
+ };
141
+ declare const MAGNET_DEFAULTS: {
142
+ enabled: true;
143
+ maxDistance: number;
144
+ maxOffset: number;
145
+ originX: number;
146
+ originY: number;
147
+ elastic: true;
148
+ smooth: true;
149
+ snap: true;
150
+ smoothSpring: {
151
+ stiffness: number;
152
+ damping: number;
153
+ mass: number;
154
+ };
155
+ snapSpring: {
156
+ stiffness: number;
157
+ damping: number;
158
+ mass: number;
159
+ restDelta: number;
160
+ };
161
+ trigger: "distance";
162
+ };
163
+ //#endregion
164
+ export { BOUNCY_SNAP, LINEAR_RESET, MAGNET_DEFAULTS, MagnetOptions, SMOOTH, TACTILE };
165
+ //# sourceMappingURL=types.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.mts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;AAWA;;;;;;;UAAiB,aAAA;EAkDf;;;;;;;;EAzCA,OAAA;EA0GO;;AAGT;;;;;;EAnGE,WAAA;;;AAyGF;;;;;;EA/FE,SAAA;;;AAqGF;;;;;;EA3FE,OAAA;;;;AAkGF;;;;;;EAvFE,OAAA;;;AA6FF;;;;;;EAnFE,OAAA;;;;;;;;;;;;EAaA,MAAA;;;;;;;;;;;;EAaA,IAAA;;;;;;EAOA,YAAA,GAAe,aAAA;;;;;;;EAQf,UAAA,GAAa,aAAA;;;;;;;;;;;;;EAcb,OAAA;AAAA;AAAA,cAGW,OAAA;;;;;cAMA,MAAA;;;;;cAMA,WAAA;;;;;;cAOA,YAAA;;;;;cAMA,eAAA"}
package/dist/types.mjs ADDED
@@ -0,0 +1,2 @@
1
+ const e={stiffness:400,damping:30,mass:.5},t={stiffness:150,damping:20,mass:1.2},n={stiffness:400,damping:20,mass:.5,restDelta:.001},r={stiffness:200,damping:50,mass:1},i={enabled:!0,maxDistance:320,maxOffset:64,originX:.5,originY:.5,elastic:!0,smooth:!0,snap:!0,smoothSpring:t,snapSpring:n,trigger:`distance`};export{n as BOUNCY_SNAP,r as LINEAR_RESET,i as MAGNET_DEFAULTS,t as SMOOTH,e as TACTILE};
2
+ //# sourceMappingURL=types.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.mjs","names":[],"sources":["../src/types.ts"],"sourcesContent":["import type { SpringOptions } from \"motion/react\";\n\n/**\n * Configuration options for the Magnetic component.\n *\n * Structural options define the identity of a magnetic instance.\n * Changing them requires a remount.\n *\n * Visual options only affect how the instance feels.\n * They can update live without remounting.\n */\nexport interface MagnetOptions {\n /**\n * **Structural Option**\n *\n * Turns the magnetic effect on or off.\n * Changing this triggers a remount to ensure a clean state.\n *\n * @default true\n */\n enabled?: boolean;\n\n /**\n * **Structural Option**\n *\n * Maximum distance (radius) from center where pull begins.\n * Changing this triggers a remount.\n *\n * @default 320\n */\n maxDistance?: number;\n\n /**\n * **Structural Option**\n *\n * Maximum translation toward the pointer.\n * Changing this triggers a remount.\n *\n * @default 64\n */\n maxOffset?: number;\n\n /**\n * **Structural Option**\n *\n * Horizontal anchor for the magnetic origin inside the element bounds (`0` = left edge, `1` = right).\n * Changing this triggers a remount.\n *\n * @default 0.5\n */\n originX?: number;\n\n /**\n * **Structural Option**\n *\n * Vertical anchor for the magnetic origin (`0` = top, `1` = bottom). Values above `0.5` bias the\n * pull point downward (useful for tall controls where the “mass” should feel lower).\n * Changing this triggers a remount.\n *\n * @default 0.5\n */\n originY?: number;\n\n /**\n * **Structural Option**\n *\n * Whether pull translates using an elastic curve mapping.\n * Changing this triggers a remount.\n *\n * @default true\n */\n elastic?: boolean;\n\n /**\n * **Visual Option**\n *\n * Selects the hover preset.\n * - `true`: uses `SMOOTH`\n * - `false`: uses `TACTILE`\n *\n * Can update live without remounting.\n *\n * @default true\n */\n smooth?: boolean;\n\n /**\n * **Visual Option**\n *\n * Controls return behavior.\n * - `true`: use snap-back physics (`BOUNCY_SNAP`)\n * - `false`: use linear reset (`LINEAR_RESET`)\n *\n * Can update live without remounting.\n *\n * @default true\n */\n snap?: boolean;\n\n /**\n * **Visual Option**\n *\n * Overrides the hover spring.\n */\n smoothSpring?: SpringOptions;\n\n /**\n * **Visual Option**\n *\n * Overrides the return spring.\n * Only used when `snap !== false`.\n */\n snapSpring?: SpringOptions;\n\n /**\n * **Structural Option**\n *\n * How the magnetic effect is triggered.\n * - `\"distance\"`: activates when pointer is within `maxDistance` (default, shared orchestrator)\n * - `\"hover\"`: activates only when pointer is directly over the element\n * - `\"click\"`: click to lock and follow cursor, click again to release\n *\n * Changing this triggers a remount.\n *\n * @default \"distance\"\n */\n trigger?: \"distance\" | \"hover\" | \"click\";\n}\n\nexport const TACTILE = {\n stiffness: 400,\n damping: 30,\n mass: 0.5,\n} satisfies SpringOptions;\n\nexport const SMOOTH = {\n stiffness: 150,\n damping: 20,\n mass: 1.2,\n} satisfies SpringOptions;\n\nexport const BOUNCY_SNAP = {\n stiffness: 400,\n damping: 20,\n mass: 0.5,\n restDelta: 0.001,\n} satisfies SpringOptions;\n\nexport const LINEAR_RESET = {\n stiffness: 200,\n damping: 50,\n mass: 1,\n} satisfies SpringOptions;\n\nexport const MAGNET_DEFAULTS = {\n enabled: true,\n maxDistance: 320,\n maxOffset: 64,\n originX: 0.5,\n originY: 0.5,\n elastic: true,\n smooth: true,\n snap: true,\n smoothSpring: SMOOTH,\n snapSpring: BOUNCY_SNAP,\n trigger: \"distance\",\n} satisfies Required<MagnetOptions>;\n"],"mappings":"AAiIA,MAAa,EAAU,CACrB,UAAW,IACX,QAAS,GACT,KAAM,GACP,CAEY,EAAS,CACpB,UAAW,IACX,QAAS,GACT,KAAM,IACP,CAEY,EAAc,CACzB,UAAW,IACX,QAAS,GACT,KAAM,GACN,UAAW,KACZ,CAEY,EAAe,CAC1B,UAAW,IACX,QAAS,GACT,KAAM,EACP,CAEY,EAAkB,CAC7B,QAAS,GACT,YAAa,IACb,UAAW,GACX,QAAS,GACT,QAAS,GACT,QAAS,GACT,OAAQ,GACR,KAAM,GACN,aAAc,EACd,WAAY,EACZ,QAAS,WACV"}
@@ -0,0 +1,2 @@
1
+ import{BOUNCY_SNAP as e,LINEAR_RESET as t,MAGNET_DEFAULTS as n,SMOOTH as r,TACTILE as i}from"./types.mjs";import{useMotionValue as a,useReducedMotion as o,useSpring as s}from"motion/react";import{useLayoutEffect as c,useRef as l,useState as u}from"react";var d=class{elements=new Map;activeElements=new Set;intersectionObserver=null;resizeObserver=null;trackedCount=0;distanceCount=0;listenersAttached=!1;rafId=null;framePending=!1;needsInvalidate=!1;hasPointer=!1;pointerX=0;pointerY=0;constructor(){typeof window>`u`||(typeof IntersectionObserver<`u`&&(this.intersectionObserver=new IntersectionObserver(e=>{for(let t of e){let e=t.target,n=this.elements.get(e);if(n){if(n.isIntersecting=t.isIntersecting,t.isIntersecting){n.options.trigger===`distance`&&this.activeElements.add(e),n.rect=null,this.requestFrame();continue}this.activeElements.delete(e),this.setActive(n,!1),this.reset(n)}}},{root:null,rootMargin:`80px`,threshold:0})),typeof ResizeObserver<`u`&&(this.resizeObserver=new ResizeObserver(e=>{for(let t of e){let e=this.elements.get(t.target);e&&(e.rect=null,this.requestFrame())}})),this.handlePointerMove=this.handlePointerMove.bind(this),this.handleInvalidate=this.handleInvalidate.bind(this),this.handleWindowBlur=this.handleWindowBlur.bind(this),this.handleVisibilityChange=this.handleVisibilityChange.bind(this),this.tick=this.tick.bind(this))}register(e,t,r,i={},a){this.elements.has(e)&&this.unregister(e);let o={...n,...i},s={x:t,y:r,options:o,rect:null,isIntersecting:!this.intersectionObserver,isActive:!1,onActiveChange:a};this.elements.set(e,s),this.trackedCount+=1,o.trigger===`distance`?(this.distanceCount+=1,s.isIntersecting&&this.activeElements.add(e)):o.trigger===`hover`?this.setupHoverMode(e,s):o.trigger===`click`&&this.setupClickMode(e,s),this.intersectionObserver?.observe(e),this.resizeObserver?.observe(e),this.trackedCount===1&&this.start()}unregister(e){let t=this.elements.get(e);t&&(this.intersectionObserver?.unobserve(e),this.resizeObserver?.unobserve(e),this.activeElements.delete(e),t.options.trigger===`distance`&&(this.distanceCount=Math.max(0,this.distanceCount-1)),t.cleanup?.(),this.setActive(t,!1),this.reset(t),this.elements.delete(e),this.trackedCount=Math.max(0,this.trackedCount-1),this.trackedCount===0&&this.stop())}setupHoverMode(e,t){let n=n=>{let r=e.getBoundingClientRect(),i=n.clientX>=r.left&&n.clientX<=r.right&&n.clientY>=r.top&&n.clientY<=r.bottom;i&&!t.isActive?(t.rect=r,this.setActive(t,!0)):!i&&t.isActive&&(this.setActive(t,!1),this.reset(t),t.rect=null),t.isActive&&this.updateMagnetic(e,t,n.clientX,n.clientY)},r=()=>{t.isActive&&(this.setActive(t,!1),this.reset(t),t.rect=null)};window.addEventListener(`pointermove`,n),e.addEventListener(`pointerleave`,r),t.cleanup=()=>{window.removeEventListener(`pointermove`,n),e.removeEventListener(`pointerleave`,r)}}setupClickMode(e,t){let n=!1,r=0,i=0,a=0,o=0,s=s=>{if(e.contains(s.target)){if(n){n=!1,e.style.cursor=`grab`,this.setActive(t,!1),this.reset(t),t.rect=null;return}t.rect=e.getBoundingClientRect(),a=t.rect.left+t.rect.width/2,o=t.rect.top+t.rect.height/2,r=s.clientX-a,i=s.clientY-o,n=!0,e.style.cursor=`grabbing`,this.setActive(t,!0)}},c=e=>{if(!n)return;let s=e.clientX-r-a,c=e.clientY-i-o;this.setTarget(t,s,c)},l=()=>{n&&(n=!1,e.style.cursor=`grab`,this.setActive(t,!1),this.reset(t),t.rect=null)};e.style.cursor=`grab`,window.addEventListener(`pointerdown`,s),window.addEventListener(`pointermove`,c),window.addEventListener(`pointerup`,l),t.cleanup=()=>{e.style.cursor=``,window.removeEventListener(`pointerdown`,s),window.removeEventListener(`pointermove`,c),window.removeEventListener(`pointerup`,l)}}updateMagnetic(e,t,n,r){t.rect||=e.getBoundingClientRect();let i=t.rect,{maxDistance:a,maxOffset:o,elastic:s,originX:c,originY:l}=t.options,u=i.left+i.width*c,d=i.top+i.height*l,f=n-u,p=r-d,m=f*f+p*p;if(m>=a*a){this.setActive(t,!1),this.reset(t);return}this.setActive(t,!0);let h=Math.sqrt(m),g=Math.min(1,h/a),_=0,v=0;if(s){let e=Math.sin(Math.PI/2*g)*o,t=f/(h||1),n=p/(h||1);_=t*e,v=n*e}else _=f/a*o,v=p/a*o;this.setTarget(t,_,v)}start(){this.listenersAttached||(this.listenersAttached=!0,window.addEventListener(`pointermove`,this.handlePointerMove,{passive:!0}),window.addEventListener(`scroll`,this.handleInvalidate,{passive:!0,capture:!0}),window.addEventListener(`resize`,this.handleInvalidate,{passive:!0}),window.addEventListener(`blur`,this.handleWindowBlur),document.addEventListener(`visibilitychange`,this.handleVisibilityChange))}stop(){this.listenersAttached&&(this.listenersAttached=!1,window.removeEventListener(`pointermove`,this.handlePointerMove),window.removeEventListener(`scroll`,this.handleInvalidate,{capture:!0}),window.removeEventListener(`resize`,this.handleInvalidate),window.removeEventListener(`blur`,this.handleWindowBlur),document.removeEventListener(`visibilitychange`,this.handleVisibilityChange),this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.framePending=!1,this.needsInvalidate=!1,this.hasPointer=!1)}handlePointerMove(e){this.pointerX=e.clientX,this.pointerY=e.clientY,this.hasPointer=!0,this.requestFrame()}handleInvalidate(){this.needsInvalidate=!0,this.requestFrame()}handleWindowBlur(){this.hasPointer=!1,this.resetAllActive()}handleVisibilityChange(){document.hidden&&(this.hasPointer=!1,this.resetAllActive())}requestFrame(){!this.listenersAttached||this.framePending||(this.framePending=!0,this.rafId=requestAnimationFrame(this.tick))}tick(){if(this.framePending=!1,this.rafId=null,this.listenersAttached&&(this.needsInvalidate&&this.invalidateVisibleRects(),!(!this.hasPointer||this.distanceCount===0)))for(let e of this.activeElements){let t=this.elements.get(e);if(!t||t.options.trigger!==`distance`||!t.isIntersecting)continue;t.rect||=e.getBoundingClientRect();let n=t.rect,{maxDistance:r,maxOffset:i,elastic:a,originX:o,originY:s}=t.options,c=n.left+n.width*o,l=n.top+n.height*s,u=this.pointerX-c,d=this.pointerY-l,f=u*u+d*d;if(f>=r*r){this.setActive(t,!1),this.reset(t);continue}this.setActive(t,!0);let p=Math.sqrt(f),m=Math.min(1,p/r),h=0,g=0;if(a){let e=Math.sin(Math.PI/2*m)*i,t=u/(p||1),n=d/(p||1);h=t*e,g=n*e}else h=u/r*i,g=d/r*i;this.setTarget(t,h,g)}}setActive(e,t){e.isActive!==t&&(e.isActive=t,e.onActiveChange?.(t))}setTarget(e,t,n){e.x.set(t),e.y.set(n)}reset(e){this.setTarget(e,0,0)}invalidateVisibleRects(){this.needsInvalidate=!1;for(let e of this.activeElements){let t=this.elements.get(e);t&&(t.rect=null)}}resetAllActive(){for(let e of this.activeElements){let t=this.elements.get(e);t&&(this.setActive(t,!1),this.reset(t),t.rect=null)}}};let f=null;function p(){return f===null&&(f=new d),f}const m={register(e,t,n,r,i){p().register(e,t,n,r,i)},unregister(e){p().unregister(e)}};function h(e){return{enabled:e?.enabled??n.enabled,maxDistance:e?.maxDistance??n.maxDistance,maxOffset:e?.maxOffset??n.maxOffset,elastic:e?.elastic??n.elastic,originX:e?.originX??n.originX,originY:e?.originY??n.originY,trigger:e?.trigger??n.trigger}}function g(a){let o=a?.smooth??n.smooth;return{hoverSpring:a?.smoothSpring??(o?r:i),returnSpring:a?.snap??n.snap?a?.snapSpring??e:t}}function _(e){let t=o(),n=h(e),r=g(e),i=n.enabled&&!t,[d,f]=u(!1),p=a(0),_=a(0),v=s(p,d?r.hoverSpring:r.returnSpring),y=s(_,d?r.hoverSpring:r.returnSpring),b=l(null),x=l(null),S=l(null),C=l(null),w=l(p),T=l(_),E=l(f);w.current=p,T.current=_,E.current=f;let D=l(()=>{w.current.set(0),T.current.set(0),E.current(!1)}).current,O=l(null);O.current===null?O.current={enabled:i,structural:n,rawX:p,rawY:_,reset:D,setIsComposing:f,registeredNode:null}:(O.current.enabled=i,O.current.rawX=p,O.current.rawY=_,O.current.reset=D,O.current.setIsComposing=f,O.current.registeredNode===null&&(O.current.structural=n));let k=l(null);return k.current===null&&(k.current=e=>{S.current=e;let t=O.current,n=x.current;if(e===t.registeredNode)return n??void 0;if(e===null){n?.(),x.current=null,t.registeredNode=null,b.current=null,t.reset();return}if(n?.(),x.current=null,t.registeredNode=null,!t.enabled){b.current=null,t.reset();return}let r=b.current??t.structural;b.current=r,t.registeredNode=e,m.register(e,t.rawX,t.rawY,r,t.setIsComposing);let i=()=>{m.unregister(e),x.current=null,t.registeredNode===e&&(t.registeredNode=null),b.current=null,t.reset()};return x.current=i,i}),c(()=>{let e=S.current,t=O.current;if(C.current===null){C.current=i;return}if(C.current===i||(C.current=i,!e))return;if(x.current?.(),x.current=null,t.registeredNode=null,!t.enabled){b.current=null,t.reset();return}let n=b.current??t.structural;b.current=n,t.registeredNode=e,m.register(e,t.rawX,t.rawY,n,t.setIsComposing),x.current=()=>{m.unregister(e),x.current=null,t.registeredNode===e&&(t.registeredNode=null),b.current=null,t.reset()}},[i]),{ref:k.current,x:i?v:p,y:i?y:_,style:i?{x:v,y}:{x:p,y:_},enabled:i,isComposing:i&&d,reset:D}}export{_ as t};
2
+ //# sourceMappingURL=useMagnetic-Cih9JSy3.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMagnetic-Cih9JSy3.mjs","names":[],"sources":["../src/orchestrator.ts","../src/useMagnetic.ts"],"sourcesContent":["import type { MotionValue } from \"motion/react\";\nimport { MAGNET_DEFAULTS, type MagnetOptions } from \"./types\";\n\ninterface MagnetMetadata {\n x: MotionValue<number>;\n y: MotionValue<number>;\n options: Required<MagnetOptions>;\n rect: DOMRect | null;\n isIntersecting: boolean;\n isActive: boolean;\n onActiveChange?: (isActive: boolean) => void;\n cleanup?: () => void;\n}\n\nclass MagnetOrchestrator {\n private elements = new Map<HTMLElement, MagnetMetadata>();\n private activeElements = new Set<HTMLElement>();\n private intersectionObserver: IntersectionObserver | null = null;\n private resizeObserver: ResizeObserver | null = null;\n private trackedCount = 0;\n private distanceCount = 0;\n private listenersAttached = false;\n private rafId: number | null = null;\n private framePending = false;\n private needsInvalidate = false;\n private hasPointer = false;\n private pointerX = 0;\n private pointerY = 0;\n\n constructor() {\n if (typeof window === \"undefined\") return;\n\n if (typeof IntersectionObserver !== \"undefined\") {\n this.intersectionObserver = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n const el = entry.target as HTMLElement;\n const meta = this.elements.get(el);\n if (!meta) continue;\n\n meta.isIntersecting = entry.isIntersecting;\n\n if (entry.isIntersecting) {\n if (meta.options.trigger === \"distance\") {\n this.activeElements.add(el);\n }\n meta.rect = null;\n this.requestFrame();\n continue;\n }\n\n this.activeElements.delete(el);\n this.setActive(meta, false);\n this.reset(meta);\n }\n },\n { root: null, rootMargin: \"80px\", threshold: 0 },\n );\n }\n\n if (typeof ResizeObserver !== \"undefined\") {\n this.resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const meta = this.elements.get(entry.target as HTMLElement);\n if (!meta) continue;\n meta.rect = null;\n this.requestFrame();\n }\n });\n }\n\n this.handlePointerMove = this.handlePointerMove.bind(this);\n this.handleInvalidate = this.handleInvalidate.bind(this);\n this.handleWindowBlur = this.handleWindowBlur.bind(this);\n this.handleVisibilityChange = this.handleVisibilityChange.bind(this);\n this.tick = this.tick.bind(this);\n }\n\n public register(\n el: HTMLElement,\n x: MotionValue<number>,\n y: MotionValue<number>,\n options: MagnetOptions = {},\n onActiveChange?: (isActive: boolean) => void,\n ) {\n if (this.elements.has(el)) this.unregister(el);\n\n const resolved = { ...MAGNET_DEFAULTS, ...options };\n const meta: MagnetMetadata = {\n x,\n y,\n options: resolved,\n rect: null,\n isIntersecting: !this.intersectionObserver,\n isActive: false,\n onActiveChange,\n };\n\n this.elements.set(el, meta);\n this.trackedCount += 1;\n\n if (resolved.trigger === \"distance\") {\n this.distanceCount += 1;\n if (meta.isIntersecting) {\n this.activeElements.add(el);\n }\n } else if (resolved.trigger === \"hover\") {\n this.setupHoverMode(el, meta);\n } else if (resolved.trigger === \"click\") {\n this.setupClickMode(el, meta);\n }\n\n this.intersectionObserver?.observe(el);\n this.resizeObserver?.observe(el);\n\n if (this.trackedCount === 1) this.start();\n }\n\n public unregister(el: HTMLElement) {\n const meta = this.elements.get(el);\n if (!meta) return;\n\n this.intersectionObserver?.unobserve(el);\n this.resizeObserver?.unobserve(el);\n this.activeElements.delete(el);\n\n if (meta.options.trigger === \"distance\") {\n this.distanceCount = Math.max(0, this.distanceCount - 1);\n }\n\n meta.cleanup?.();\n\n this.setActive(meta, false);\n this.reset(meta);\n\n this.elements.delete(el);\n this.trackedCount = Math.max(0, this.trackedCount - 1);\n\n if (this.trackedCount === 0) this.stop();\n }\n\n private setupHoverMode(el: HTMLElement, meta: MagnetMetadata) {\n const handleMove = (e: PointerEvent) => {\n const rect = el.getBoundingClientRect();\n const inside =\n e.clientX >= rect.left &&\n e.clientX <= rect.right &&\n e.clientY >= rect.top &&\n e.clientY <= rect.bottom;\n\n if (inside && !meta.isActive) {\n meta.rect = rect;\n this.setActive(meta, true);\n } else if (!inside && meta.isActive) {\n this.setActive(meta, false);\n this.reset(meta);\n meta.rect = null;\n }\n\n if (meta.isActive) {\n this.updateMagnetic(el, meta, e.clientX, e.clientY);\n }\n };\n\n const handleLeave = () => {\n if (meta.isActive) {\n this.setActive(meta, false);\n this.reset(meta);\n meta.rect = null;\n }\n };\n\n window.addEventListener(\"pointermove\", handleMove);\n el.addEventListener(\"pointerleave\", handleLeave);\n\n meta.cleanup = () => {\n window.removeEventListener(\"pointermove\", handleMove);\n el.removeEventListener(\"pointerleave\", handleLeave);\n };\n }\n\n private setupClickMode(el: HTMLElement, meta: MagnetMetadata) {\n let locked = false;\n let grabOffsetX = 0;\n let grabOffsetY = 0;\n let naturalCenterX = 0;\n let naturalCenterY = 0;\n\n const handlePointerDown = (e: PointerEvent) => {\n if (!el.contains(e.target as Node)) return;\n if (locked) {\n locked = false;\n el.style.cursor = \"grab\";\n this.setActive(meta, false);\n this.reset(meta);\n meta.rect = null;\n return;\n }\n meta.rect = el.getBoundingClientRect();\n naturalCenterX = meta.rect.left + meta.rect.width / 2;\n naturalCenterY = meta.rect.top + meta.rect.height / 2;\n grabOffsetX = e.clientX - naturalCenterX;\n grabOffsetY = e.clientY - naturalCenterY;\n locked = true;\n el.style.cursor = \"grabbing\";\n this.setActive(meta, true);\n };\n\n const handlePointerMove = (e: PointerEvent) => {\n if (!locked) return;\n const targetX = e.clientX - grabOffsetX - naturalCenterX;\n const targetY = e.clientY - grabOffsetY - naturalCenterY;\n this.setTarget(meta, targetX, targetY);\n };\n\n const handleGlobalUp = () => {\n if (locked) {\n locked = false;\n el.style.cursor = \"grab\";\n this.setActive(meta, false);\n this.reset(meta);\n meta.rect = null;\n }\n };\n\n el.style.cursor = \"grab\";\n window.addEventListener(\"pointerdown\", handlePointerDown);\n window.addEventListener(\"pointermove\", handlePointerMove);\n window.addEventListener(\"pointerup\", handleGlobalUp);\n\n meta.cleanup = () => {\n el.style.cursor = \"\";\n window.removeEventListener(\"pointerdown\", handlePointerDown);\n window.removeEventListener(\"pointermove\", handlePointerMove);\n window.removeEventListener(\"pointerup\", handleGlobalUp);\n };\n }\n\n private updateMagnetic(el: HTMLElement, meta: MagnetMetadata, px: number, py: number) {\n if (!meta.rect) meta.rect = el.getBoundingClientRect();\n\n const r = meta.rect;\n const { maxDistance, maxOffset, elastic, originX, originY } = meta.options;\n\n const cx = r.left + r.width * originX;\n const cy = r.top + r.height * originY;\n const dx = px - cx;\n const dy = py - cy;\n const dSq = dx * dx + dy * dy;\n\n if (dSq >= maxDistance * maxDistance) {\n this.setActive(meta, false);\n this.reset(meta);\n return;\n }\n\n this.setActive(meta, true);\n\n const dist = Math.sqrt(dSq);\n const progress = Math.min(1, dist / maxDistance);\n\n let targetX = 0;\n let targetY = 0;\n\n if (elastic) {\n const strength = Math.sin(progress * (Math.PI / 2)) * maxOffset;\n const dirX = dx / (dist || 1);\n const dirY = dy / (dist || 1);\n targetX = dirX * strength;\n targetY = dirY * strength;\n } else {\n targetX = (dx / maxDistance) * maxOffset;\n targetY = (dy / maxDistance) * maxOffset;\n }\n\n this.setTarget(meta, targetX, targetY);\n }\n\n private start() {\n if (this.listenersAttached) return;\n this.listenersAttached = true;\n\n window.addEventListener(\"pointermove\", this.handlePointerMove, { passive: true });\n window.addEventListener(\"scroll\", this.handleInvalidate, { passive: true, capture: true });\n window.addEventListener(\"resize\", this.handleInvalidate, { passive: true });\n window.addEventListener(\"blur\", this.handleWindowBlur);\n document.addEventListener(\"visibilitychange\", this.handleVisibilityChange);\n }\n\n private stop() {\n if (!this.listenersAttached) return;\n this.listenersAttached = false;\n\n window.removeEventListener(\"pointermove\", this.handlePointerMove);\n window.removeEventListener(\"scroll\", this.handleInvalidate, { capture: true });\n window.removeEventListener(\"resize\", this.handleInvalidate);\n window.removeEventListener(\"blur\", this.handleWindowBlur);\n document.removeEventListener(\"visibilitychange\", this.handleVisibilityChange);\n\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n\n this.framePending = false;\n this.needsInvalidate = false;\n this.hasPointer = false;\n }\n\n private handlePointerMove(e: PointerEvent) {\n this.pointerX = e.clientX;\n this.pointerY = e.clientY;\n this.hasPointer = true;\n this.requestFrame();\n }\n\n private handleInvalidate() {\n this.needsInvalidate = true;\n this.requestFrame();\n }\n\n private handleWindowBlur() {\n this.hasPointer = false;\n this.resetAllActive();\n }\n\n private handleVisibilityChange() {\n if (document.hidden) {\n this.hasPointer = false;\n this.resetAllActive();\n }\n }\n\n private requestFrame() {\n if (!this.listenersAttached || this.framePending) return;\n this.framePending = true;\n this.rafId = requestAnimationFrame(this.tick);\n }\n\n private tick() {\n this.framePending = false;\n this.rafId = null;\n\n if (!this.listenersAttached) return;\n\n if (this.needsInvalidate) {\n this.invalidateVisibleRects();\n }\n\n if (!this.hasPointer || this.distanceCount === 0) return;\n\n for (const el of this.activeElements) {\n const meta = this.elements.get(el);\n if (!meta || meta.options.trigger !== \"distance\" || !meta.isIntersecting) continue;\n\n if (!meta.rect) meta.rect = el.getBoundingClientRect();\n\n const r = meta.rect;\n const { maxDistance, maxOffset, elastic, originX, originY } = meta.options;\n\n const cx = r.left + r.width * originX;\n const cy = r.top + r.height * originY;\n const dx = this.pointerX - cx;\n const dy = this.pointerY - cy;\n const dSq = dx * dx + dy * dy;\n\n if (dSq >= maxDistance * maxDistance) {\n this.setActive(meta, false);\n this.reset(meta);\n continue;\n }\n\n this.setActive(meta, true);\n\n const dist = Math.sqrt(dSq);\n const progress = Math.min(1, dist / maxDistance);\n\n let targetX = 0;\n let targetY = 0;\n\n if (elastic) {\n const strength = Math.sin(progress * (Math.PI / 2)) * maxOffset;\n const dirX = dx / (dist || 1);\n const dirY = dy / (dist || 1);\n targetX = dirX * strength;\n targetY = dirY * strength;\n } else {\n targetX = (dx / maxDistance) * maxOffset;\n targetY = (dy / maxDistance) * maxOffset;\n }\n\n this.setTarget(meta, targetX, targetY);\n }\n }\n\n private setActive(meta: MagnetMetadata, next: boolean) {\n if (meta.isActive === next) return;\n meta.isActive = next;\n meta.onActiveChange?.(next);\n }\n\n private setTarget(meta: MagnetMetadata, x: number, y: number) {\n meta.x.set(x);\n meta.y.set(y);\n }\n\n private reset(meta: MagnetMetadata) {\n this.setTarget(meta, 0, 0);\n }\n\n private invalidateVisibleRects() {\n this.needsInvalidate = false;\n for (const el of this.activeElements) {\n const meta = this.elements.get(el);\n if (meta) meta.rect = null;\n }\n }\n\n private resetAllActive() {\n for (const el of this.activeElements) {\n const meta = this.elements.get(el);\n if (!meta) continue;\n this.setActive(meta, false);\n this.reset(meta);\n meta.rect = null;\n }\n }\n}\n\nlet _instance: MagnetOrchestrator | null = null;\n\nfunction get(): MagnetOrchestrator {\n if (_instance === null) {\n _instance = new MagnetOrchestrator();\n }\n return _instance;\n}\n\ninterface MagnetOrchestratorFacade {\n register: MagnetOrchestrator[\"register\"];\n unregister: MagnetOrchestrator[\"unregister\"];\n}\n\nexport const magnetOrchestrator: MagnetOrchestratorFacade = {\n register(el, x, y, options, onActiveChange) {\n get().register(el, x, y, options, onActiveChange);\n },\n unregister(el) {\n get().unregister(el);\n },\n};\n","import {\n type MotionValue,\n type SpringOptions,\n useMotionValue,\n useReducedMotion,\n useSpring,\n} from \"motion/react\";\nimport { useLayoutEffect, useRef, useState } from \"react\";\nimport { magnetOrchestrator } from \"./orchestrator\";\nimport {\n BOUNCY_SNAP,\n LINEAR_RESET,\n MAGNET_DEFAULTS,\n SMOOTH,\n TACTILE,\n type MagnetOptions,\n} from \"./types\";\n\ntype MagneticRefCleanup = void | (() => void);\ntype MagneticRefCallback<T extends HTMLElement> = (node: T | null) => MagneticRefCleanup;\n\nexport interface UseMagneticResult<T extends HTMLElement = HTMLElement> {\n ref: MagneticRefCallback<T>;\n x: MotionValue<number>;\n y: MotionValue<number>;\n style: {\n x: MotionValue<number>;\n y: MotionValue<number>;\n };\n enabled: boolean;\n isComposing: boolean;\n reset: () => void;\n}\n\ninterface VisualOptions {\n hoverSpring: SpringOptions;\n returnSpring: SpringOptions;\n}\n\ninterface StructuralOptions {\n enabled: boolean;\n maxDistance: number;\n maxOffset: number;\n elastic: boolean;\n originX: number;\n originY: number;\n trigger: \"distance\" | \"hover\" | \"click\";\n}\n\ninterface RefState<T extends HTMLElement> {\n enabled: boolean;\n structural: StructuralOptions;\n rawX: MotionValue<number>;\n rawY: MotionValue<number>;\n reset: () => void;\n setIsComposing: (value: boolean) => void;\n registeredNode: T | null;\n}\n\nfunction normalizeStructuralOptions(options: MagnetOptions | undefined): StructuralOptions {\n return {\n enabled: options?.enabled ?? MAGNET_DEFAULTS.enabled,\n maxDistance: options?.maxDistance ?? MAGNET_DEFAULTS.maxDistance,\n maxOffset: options?.maxOffset ?? MAGNET_DEFAULTS.maxOffset,\n elastic: options?.elastic ?? MAGNET_DEFAULTS.elastic,\n originX: options?.originX ?? MAGNET_DEFAULTS.originX,\n originY: options?.originY ?? MAGNET_DEFAULTS.originY,\n trigger: options?.trigger ?? MAGNET_DEFAULTS.trigger,\n };\n}\n\nfunction normalizeVisualOptions(options: MagnetOptions | undefined): VisualOptions {\n const isSmooth = options?.smooth ?? MAGNET_DEFAULTS.smooth;\n const hoverSpring = options?.smoothSpring ?? (isSmooth ? SMOOTH : TACTILE);\n\n const shouldSnap = options?.snap ?? MAGNET_DEFAULTS.snap;\n const returnSpring = shouldSnap ? (options?.snapSpring ?? BOUNCY_SNAP) : LINEAR_RESET;\n\n return { hoverSpring, returnSpring };\n}\n\n/**\n * Expert primitive for headless magnetic interactions.\n *\n * ### The Headless Contract\n * 1. **Structural Options** (`maxDistance`, `maxOffset`, `elastic`, origins) are construction-time\n * while a node is registered. **`enabled` is special**: it may toggle on the same DOM node; the\n * hook syncs orchestrator registration on `enabled` changes via `useLayoutEffect`.\n * 2. **Visual Options** (`smooth`, `snap`, springs) can update live.\n * 3. **Ref Management**: You MUST attach `magnetic.ref` to the element that should track the pointer.\n * 4. **Style Attachment**: You MUST apply `magnetic.style` to a `motion` component.\n *\n * @example\n * ```tsx\n * const magnetic = useMagnetic({ maxOffset: 20 });\n * return <motion.div ref={magnetic.ref} style={magnetic.style} />\n * ```\n */\nexport function useMagnetic<T extends HTMLElement = HTMLElement>(\n options?: MagnetOptions,\n): UseMagneticResult<T> {\n const reducedMotion = useReducedMotion();\n const structural = normalizeStructuralOptions(options);\n const visual = normalizeVisualOptions(options);\n const enabled = structural.enabled && !reducedMotion;\n\n const [isComposing, setIsComposing] = useState(false);\n\n const rawX = useMotionValue(0);\n const rawY = useMotionValue(0);\n\n const x = useSpring(rawX, isComposing ? visual.hoverSpring : visual.returnSpring);\n const y = useSpring(rawY, isComposing ? visual.hoverSpring : visual.returnSpring);\n\n const mountedStructuralRef = useRef<StructuralOptions | null>(null);\n const cleanupRef = useRef<(() => void) | null>(null);\n const domNodeRef = useRef<T | null>(null);\n const layoutSyncPrevEnabledRef = useRef<boolean | null>(null);\n\n const rawXRef = useRef(rawX);\n const rawYRef = useRef(rawY);\n const setIsComposingRef = useRef(setIsComposing);\n rawXRef.current = rawX;\n rawYRef.current = rawY;\n setIsComposingRef.current = setIsComposing;\n\n const reset = useRef(() => {\n rawXRef.current.set(0);\n rawYRef.current.set(0);\n setIsComposingRef.current(false);\n }).current;\n\n const refStateRef = useRef<RefState<T> | null>(null);\n if (refStateRef.current === null) {\n refStateRef.current = {\n enabled,\n structural,\n rawX,\n rawY,\n reset,\n setIsComposing,\n registeredNode: null,\n };\n } else {\n refStateRef.current.enabled = enabled;\n refStateRef.current.rawX = rawX;\n refStateRef.current.rawY = rawY;\n refStateRef.current.reset = reset;\n refStateRef.current.setIsComposing = setIsComposing;\n\n if (refStateRef.current.registeredNode === null) {\n refStateRef.current.structural = structural;\n }\n }\n\n const refRef = useRef<MagneticRefCallback<T> | null>(null);\n\n if (refRef.current === null) {\n refRef.current = (node) => {\n domNodeRef.current = node;\n\n const state = refStateRef.current!;\n const currentCleanup = cleanupRef.current;\n const currentNode = state.registeredNode;\n\n if (node === currentNode) {\n return currentCleanup ?? undefined;\n }\n\n if (node === null) {\n currentCleanup?.();\n cleanupRef.current = null;\n state.registeredNode = null;\n mountedStructuralRef.current = null;\n state.reset();\n return;\n }\n\n currentCleanup?.();\n cleanupRef.current = null;\n state.registeredNode = null;\n\n if (!state.enabled) {\n mountedStructuralRef.current = null;\n state.reset();\n return;\n }\n\n const structuralAtMount = mountedStructuralRef.current ?? state.structural;\n mountedStructuralRef.current = structuralAtMount;\n state.registeredNode = node;\n\n magnetOrchestrator.register(\n node,\n state.rawX,\n state.rawY,\n structuralAtMount,\n state.setIsComposing,\n );\n\n const cleanup = () => {\n magnetOrchestrator.unregister(node);\n cleanupRef.current = null;\n if (state.registeredNode === node) {\n state.registeredNode = null;\n }\n mountedStructuralRef.current = null;\n state.reset();\n };\n\n cleanupRef.current = cleanup;\n return cleanup;\n };\n }\n\n useLayoutEffect(() => {\n const node = domNodeRef.current;\n const state = refStateRef.current!;\n\n if (layoutSyncPrevEnabledRef.current === null) {\n layoutSyncPrevEnabledRef.current = enabled;\n return;\n }\n\n if (layoutSyncPrevEnabledRef.current === enabled) {\n return;\n }\n\n layoutSyncPrevEnabledRef.current = enabled;\n\n if (!node) {\n return;\n }\n\n cleanupRef.current?.();\n cleanupRef.current = null;\n state.registeredNode = null;\n\n if (!state.enabled) {\n mountedStructuralRef.current = null;\n state.reset();\n return;\n }\n\n const structuralAtMount = mountedStructuralRef.current ?? state.structural;\n mountedStructuralRef.current = structuralAtMount;\n state.registeredNode = node;\n\n magnetOrchestrator.register(\n node,\n state.rawX,\n state.rawY,\n structuralAtMount,\n state.setIsComposing,\n );\n\n const cleanup = () => {\n magnetOrchestrator.unregister(node);\n cleanupRef.current = null;\n if (state.registeredNode === node) {\n state.registeredNode = null;\n }\n mountedStructuralRef.current = null;\n state.reset();\n };\n\n cleanupRef.current = cleanup;\n }, [enabled]);\n\n const ref = refRef.current;\n\n return {\n ref,\n x: enabled ? x : rawX,\n y: enabled ? y : rawY,\n style: enabled ? { x, y } : { x: rawX, y: rawY },\n enabled,\n isComposing: enabled && isComposing,\n reset,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require(`./types.cjs`);let t=require(`motion/react`),n=require(`react`);var r=class{elements=new Map;activeElements=new Set;intersectionObserver=null;resizeObserver=null;trackedCount=0;distanceCount=0;listenersAttached=!1;rafId=null;framePending=!1;needsInvalidate=!1;hasPointer=!1;pointerX=0;pointerY=0;constructor(){typeof window>`u`||(typeof IntersectionObserver<`u`&&(this.intersectionObserver=new IntersectionObserver(e=>{for(let t of e){let e=t.target,n=this.elements.get(e);if(n){if(n.isIntersecting=t.isIntersecting,t.isIntersecting){n.options.trigger===`distance`&&this.activeElements.add(e),n.rect=null,this.requestFrame();continue}this.activeElements.delete(e),this.setActive(n,!1),this.reset(n)}}},{root:null,rootMargin:`80px`,threshold:0})),typeof ResizeObserver<`u`&&(this.resizeObserver=new ResizeObserver(e=>{for(let t of e){let e=this.elements.get(t.target);e&&(e.rect=null,this.requestFrame())}})),this.handlePointerMove=this.handlePointerMove.bind(this),this.handleInvalidate=this.handleInvalidate.bind(this),this.handleWindowBlur=this.handleWindowBlur.bind(this),this.handleVisibilityChange=this.handleVisibilityChange.bind(this),this.tick=this.tick.bind(this))}register(t,n,r,i={},a){this.elements.has(t)&&this.unregister(t);let o={...e.MAGNET_DEFAULTS,...i},s={x:n,y:r,options:o,rect:null,isIntersecting:!this.intersectionObserver,isActive:!1,onActiveChange:a};this.elements.set(t,s),this.trackedCount+=1,o.trigger===`distance`?(this.distanceCount+=1,s.isIntersecting&&this.activeElements.add(t)):o.trigger===`hover`?this.setupHoverMode(t,s):o.trigger===`click`&&this.setupClickMode(t,s),this.intersectionObserver?.observe(t),this.resizeObserver?.observe(t),this.trackedCount===1&&this.start()}unregister(e){let t=this.elements.get(e);t&&(this.intersectionObserver?.unobserve(e),this.resizeObserver?.unobserve(e),this.activeElements.delete(e),t.options.trigger===`distance`&&(this.distanceCount=Math.max(0,this.distanceCount-1)),t.cleanup?.(),this.setActive(t,!1),this.reset(t),this.elements.delete(e),this.trackedCount=Math.max(0,this.trackedCount-1),this.trackedCount===0&&this.stop())}setupHoverMode(e,t){let n=n=>{let r=e.getBoundingClientRect(),i=n.clientX>=r.left&&n.clientX<=r.right&&n.clientY>=r.top&&n.clientY<=r.bottom;i&&!t.isActive?(t.rect=r,this.setActive(t,!0)):!i&&t.isActive&&(this.setActive(t,!1),this.reset(t),t.rect=null),t.isActive&&this.updateMagnetic(e,t,n.clientX,n.clientY)},r=()=>{t.isActive&&(this.setActive(t,!1),this.reset(t),t.rect=null)};window.addEventListener(`pointermove`,n),e.addEventListener(`pointerleave`,r),t.cleanup=()=>{window.removeEventListener(`pointermove`,n),e.removeEventListener(`pointerleave`,r)}}setupClickMode(e,t){let n=!1,r=0,i=0,a=0,o=0,s=s=>{if(e.contains(s.target)){if(n){n=!1,e.style.cursor=`grab`,this.setActive(t,!1),this.reset(t),t.rect=null;return}t.rect=e.getBoundingClientRect(),a=t.rect.left+t.rect.width/2,o=t.rect.top+t.rect.height/2,r=s.clientX-a,i=s.clientY-o,n=!0,e.style.cursor=`grabbing`,this.setActive(t,!0)}},c=e=>{if(!n)return;let s=e.clientX-r-a,c=e.clientY-i-o;this.setTarget(t,s,c)},l=()=>{n&&(n=!1,e.style.cursor=`grab`,this.setActive(t,!1),this.reset(t),t.rect=null)};e.style.cursor=`grab`,window.addEventListener(`pointerdown`,s),window.addEventListener(`pointermove`,c),window.addEventListener(`pointerup`,l),t.cleanup=()=>{e.style.cursor=``,window.removeEventListener(`pointerdown`,s),window.removeEventListener(`pointermove`,c),window.removeEventListener(`pointerup`,l)}}updateMagnetic(e,t,n,r){t.rect||=e.getBoundingClientRect();let i=t.rect,{maxDistance:a,maxOffset:o,elastic:s,originX:c,originY:l}=t.options,u=i.left+i.width*c,d=i.top+i.height*l,f=n-u,p=r-d,m=f*f+p*p;if(m>=a*a){this.setActive(t,!1),this.reset(t);return}this.setActive(t,!0);let h=Math.sqrt(m),g=Math.min(1,h/a),_=0,v=0;if(s){let e=Math.sin(Math.PI/2*g)*o,t=f/(h||1),n=p/(h||1);_=t*e,v=n*e}else _=f/a*o,v=p/a*o;this.setTarget(t,_,v)}start(){this.listenersAttached||(this.listenersAttached=!0,window.addEventListener(`pointermove`,this.handlePointerMove,{passive:!0}),window.addEventListener(`scroll`,this.handleInvalidate,{passive:!0,capture:!0}),window.addEventListener(`resize`,this.handleInvalidate,{passive:!0}),window.addEventListener(`blur`,this.handleWindowBlur),document.addEventListener(`visibilitychange`,this.handleVisibilityChange))}stop(){this.listenersAttached&&(this.listenersAttached=!1,window.removeEventListener(`pointermove`,this.handlePointerMove),window.removeEventListener(`scroll`,this.handleInvalidate,{capture:!0}),window.removeEventListener(`resize`,this.handleInvalidate),window.removeEventListener(`blur`,this.handleWindowBlur),document.removeEventListener(`visibilitychange`,this.handleVisibilityChange),this.rafId!==null&&(cancelAnimationFrame(this.rafId),this.rafId=null),this.framePending=!1,this.needsInvalidate=!1,this.hasPointer=!1)}handlePointerMove(e){this.pointerX=e.clientX,this.pointerY=e.clientY,this.hasPointer=!0,this.requestFrame()}handleInvalidate(){this.needsInvalidate=!0,this.requestFrame()}handleWindowBlur(){this.hasPointer=!1,this.resetAllActive()}handleVisibilityChange(){document.hidden&&(this.hasPointer=!1,this.resetAllActive())}requestFrame(){!this.listenersAttached||this.framePending||(this.framePending=!0,this.rafId=requestAnimationFrame(this.tick))}tick(){if(this.framePending=!1,this.rafId=null,this.listenersAttached&&(this.needsInvalidate&&this.invalidateVisibleRects(),!(!this.hasPointer||this.distanceCount===0)))for(let e of this.activeElements){let t=this.elements.get(e);if(!t||t.options.trigger!==`distance`||!t.isIntersecting)continue;t.rect||=e.getBoundingClientRect();let n=t.rect,{maxDistance:r,maxOffset:i,elastic:a,originX:o,originY:s}=t.options,c=n.left+n.width*o,l=n.top+n.height*s,u=this.pointerX-c,d=this.pointerY-l,f=u*u+d*d;if(f>=r*r){this.setActive(t,!1),this.reset(t);continue}this.setActive(t,!0);let p=Math.sqrt(f),m=Math.min(1,p/r),h=0,g=0;if(a){let e=Math.sin(Math.PI/2*m)*i,t=u/(p||1),n=d/(p||1);h=t*e,g=n*e}else h=u/r*i,g=d/r*i;this.setTarget(t,h,g)}}setActive(e,t){e.isActive!==t&&(e.isActive=t,e.onActiveChange?.(t))}setTarget(e,t,n){e.x.set(t),e.y.set(n)}reset(e){this.setTarget(e,0,0)}invalidateVisibleRects(){this.needsInvalidate=!1;for(let e of this.activeElements){let t=this.elements.get(e);t&&(t.rect=null)}}resetAllActive(){for(let e of this.activeElements){let t=this.elements.get(e);t&&(this.setActive(t,!1),this.reset(t),t.rect=null)}}};let i=null;function a(){return i===null&&(i=new r),i}const o={register(e,t,n,r,i){a().register(e,t,n,r,i)},unregister(e){a().unregister(e)}};function s(t){return{enabled:t?.enabled??e.MAGNET_DEFAULTS.enabled,maxDistance:t?.maxDistance??e.MAGNET_DEFAULTS.maxDistance,maxOffset:t?.maxOffset??e.MAGNET_DEFAULTS.maxOffset,elastic:t?.elastic??e.MAGNET_DEFAULTS.elastic,originX:t?.originX??e.MAGNET_DEFAULTS.originX,originY:t?.originY??e.MAGNET_DEFAULTS.originY,trigger:t?.trigger??e.MAGNET_DEFAULTS.trigger}}function c(t){let n=t?.smooth??e.MAGNET_DEFAULTS.smooth;return{hoverSpring:t?.smoothSpring??(n?e.SMOOTH:e.TACTILE),returnSpring:t?.snap??e.MAGNET_DEFAULTS.snap?t?.snapSpring??e.BOUNCY_SNAP:e.LINEAR_RESET}}function l(e){let r=(0,t.useReducedMotion)(),i=s(e),a=c(e),l=i.enabled&&!r,[u,d]=(0,n.useState)(!1),f=(0,t.useMotionValue)(0),p=(0,t.useMotionValue)(0),m=(0,t.useSpring)(f,u?a.hoverSpring:a.returnSpring),h=(0,t.useSpring)(p,u?a.hoverSpring:a.returnSpring),g=(0,n.useRef)(null),_=(0,n.useRef)(null),v=(0,n.useRef)(null),y=(0,n.useRef)(null),b=(0,n.useRef)(f),x=(0,n.useRef)(p),S=(0,n.useRef)(d);b.current=f,x.current=p,S.current=d;let C=(0,n.useRef)(()=>{b.current.set(0),x.current.set(0),S.current(!1)}).current,w=(0,n.useRef)(null);w.current===null?w.current={enabled:l,structural:i,rawX:f,rawY:p,reset:C,setIsComposing:d,registeredNode:null}:(w.current.enabled=l,w.current.rawX=f,w.current.rawY=p,w.current.reset=C,w.current.setIsComposing=d,w.current.registeredNode===null&&(w.current.structural=i));let T=(0,n.useRef)(null);return T.current===null&&(T.current=e=>{v.current=e;let t=w.current,n=_.current;if(e===t.registeredNode)return n??void 0;if(e===null){n?.(),_.current=null,t.registeredNode=null,g.current=null,t.reset();return}if(n?.(),_.current=null,t.registeredNode=null,!t.enabled){g.current=null,t.reset();return}let r=g.current??t.structural;g.current=r,t.registeredNode=e,o.register(e,t.rawX,t.rawY,r,t.setIsComposing);let i=()=>{o.unregister(e),_.current=null,t.registeredNode===e&&(t.registeredNode=null),g.current=null,t.reset()};return _.current=i,i}),(0,n.useLayoutEffect)(()=>{let e=v.current,t=w.current;if(y.current===null){y.current=l;return}if(y.current===l||(y.current=l,!e))return;if(_.current?.(),_.current=null,t.registeredNode=null,!t.enabled){g.current=null,t.reset();return}let n=g.current??t.structural;g.current=n,t.registeredNode=e,o.register(e,t.rawX,t.rawY,n,t.setIsComposing),_.current=()=>{o.unregister(e),_.current=null,t.registeredNode===e&&(t.registeredNode=null),g.current=null,t.reset()}},[l]),{ref:T.current,x:l?m:f,y:l?h:p,style:l?{x:m,y:h}:{x:f,y:p},enabled:l,isComposing:l&&u,reset:C}}Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return l}});
2
+ //# sourceMappingURL=useMagnetic-hzN5_pe4.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMagnetic-hzN5_pe4.cjs","names":["MAGNET_DEFAULTS","MAGNET_DEFAULTS","SMOOTH","TACTILE","BOUNCY_SNAP","LINEAR_RESET"],"sources":["../src/orchestrator.ts","../src/useMagnetic.ts"],"sourcesContent":["import type { MotionValue } from \"motion/react\";\nimport { MAGNET_DEFAULTS, type MagnetOptions } from \"./types\";\n\ninterface MagnetMetadata {\n x: MotionValue<number>;\n y: MotionValue<number>;\n options: Required<MagnetOptions>;\n rect: DOMRect | null;\n isIntersecting: boolean;\n isActive: boolean;\n onActiveChange?: (isActive: boolean) => void;\n cleanup?: () => void;\n}\n\nclass MagnetOrchestrator {\n private elements = new Map<HTMLElement, MagnetMetadata>();\n private activeElements = new Set<HTMLElement>();\n private intersectionObserver: IntersectionObserver | null = null;\n private resizeObserver: ResizeObserver | null = null;\n private trackedCount = 0;\n private distanceCount = 0;\n private listenersAttached = false;\n private rafId: number | null = null;\n private framePending = false;\n private needsInvalidate = false;\n private hasPointer = false;\n private pointerX = 0;\n private pointerY = 0;\n\n constructor() {\n if (typeof window === \"undefined\") return;\n\n if (typeof IntersectionObserver !== \"undefined\") {\n this.intersectionObserver = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n const el = entry.target as HTMLElement;\n const meta = this.elements.get(el);\n if (!meta) continue;\n\n meta.isIntersecting = entry.isIntersecting;\n\n if (entry.isIntersecting) {\n if (meta.options.trigger === \"distance\") {\n this.activeElements.add(el);\n }\n meta.rect = null;\n this.requestFrame();\n continue;\n }\n\n this.activeElements.delete(el);\n this.setActive(meta, false);\n this.reset(meta);\n }\n },\n { root: null, rootMargin: \"80px\", threshold: 0 },\n );\n }\n\n if (typeof ResizeObserver !== \"undefined\") {\n this.resizeObserver = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const meta = this.elements.get(entry.target as HTMLElement);\n if (!meta) continue;\n meta.rect = null;\n this.requestFrame();\n }\n });\n }\n\n this.handlePointerMove = this.handlePointerMove.bind(this);\n this.handleInvalidate = this.handleInvalidate.bind(this);\n this.handleWindowBlur = this.handleWindowBlur.bind(this);\n this.handleVisibilityChange = this.handleVisibilityChange.bind(this);\n this.tick = this.tick.bind(this);\n }\n\n public register(\n el: HTMLElement,\n x: MotionValue<number>,\n y: MotionValue<number>,\n options: MagnetOptions = {},\n onActiveChange?: (isActive: boolean) => void,\n ) {\n if (this.elements.has(el)) this.unregister(el);\n\n const resolved = { ...MAGNET_DEFAULTS, ...options };\n const meta: MagnetMetadata = {\n x,\n y,\n options: resolved,\n rect: null,\n isIntersecting: !this.intersectionObserver,\n isActive: false,\n onActiveChange,\n };\n\n this.elements.set(el, meta);\n this.trackedCount += 1;\n\n if (resolved.trigger === \"distance\") {\n this.distanceCount += 1;\n if (meta.isIntersecting) {\n this.activeElements.add(el);\n }\n } else if (resolved.trigger === \"hover\") {\n this.setupHoverMode(el, meta);\n } else if (resolved.trigger === \"click\") {\n this.setupClickMode(el, meta);\n }\n\n this.intersectionObserver?.observe(el);\n this.resizeObserver?.observe(el);\n\n if (this.trackedCount === 1) this.start();\n }\n\n public unregister(el: HTMLElement) {\n const meta = this.elements.get(el);\n if (!meta) return;\n\n this.intersectionObserver?.unobserve(el);\n this.resizeObserver?.unobserve(el);\n this.activeElements.delete(el);\n\n if (meta.options.trigger === \"distance\") {\n this.distanceCount = Math.max(0, this.distanceCount - 1);\n }\n\n meta.cleanup?.();\n\n this.setActive(meta, false);\n this.reset(meta);\n\n this.elements.delete(el);\n this.trackedCount = Math.max(0, this.trackedCount - 1);\n\n if (this.trackedCount === 0) this.stop();\n }\n\n private setupHoverMode(el: HTMLElement, meta: MagnetMetadata) {\n const handleMove = (e: PointerEvent) => {\n const rect = el.getBoundingClientRect();\n const inside =\n e.clientX >= rect.left &&\n e.clientX <= rect.right &&\n e.clientY >= rect.top &&\n e.clientY <= rect.bottom;\n\n if (inside && !meta.isActive) {\n meta.rect = rect;\n this.setActive(meta, true);\n } else if (!inside && meta.isActive) {\n this.setActive(meta, false);\n this.reset(meta);\n meta.rect = null;\n }\n\n if (meta.isActive) {\n this.updateMagnetic(el, meta, e.clientX, e.clientY);\n }\n };\n\n const handleLeave = () => {\n if (meta.isActive) {\n this.setActive(meta, false);\n this.reset(meta);\n meta.rect = null;\n }\n };\n\n window.addEventListener(\"pointermove\", handleMove);\n el.addEventListener(\"pointerleave\", handleLeave);\n\n meta.cleanup = () => {\n window.removeEventListener(\"pointermove\", handleMove);\n el.removeEventListener(\"pointerleave\", handleLeave);\n };\n }\n\n private setupClickMode(el: HTMLElement, meta: MagnetMetadata) {\n let locked = false;\n let grabOffsetX = 0;\n let grabOffsetY = 0;\n let naturalCenterX = 0;\n let naturalCenterY = 0;\n\n const handlePointerDown = (e: PointerEvent) => {\n if (!el.contains(e.target as Node)) return;\n if (locked) {\n locked = false;\n el.style.cursor = \"grab\";\n this.setActive(meta, false);\n this.reset(meta);\n meta.rect = null;\n return;\n }\n meta.rect = el.getBoundingClientRect();\n naturalCenterX = meta.rect.left + meta.rect.width / 2;\n naturalCenterY = meta.rect.top + meta.rect.height / 2;\n grabOffsetX = e.clientX - naturalCenterX;\n grabOffsetY = e.clientY - naturalCenterY;\n locked = true;\n el.style.cursor = \"grabbing\";\n this.setActive(meta, true);\n };\n\n const handlePointerMove = (e: PointerEvent) => {\n if (!locked) return;\n const targetX = e.clientX - grabOffsetX - naturalCenterX;\n const targetY = e.clientY - grabOffsetY - naturalCenterY;\n this.setTarget(meta, targetX, targetY);\n };\n\n const handleGlobalUp = () => {\n if (locked) {\n locked = false;\n el.style.cursor = \"grab\";\n this.setActive(meta, false);\n this.reset(meta);\n meta.rect = null;\n }\n };\n\n el.style.cursor = \"grab\";\n window.addEventListener(\"pointerdown\", handlePointerDown);\n window.addEventListener(\"pointermove\", handlePointerMove);\n window.addEventListener(\"pointerup\", handleGlobalUp);\n\n meta.cleanup = () => {\n el.style.cursor = \"\";\n window.removeEventListener(\"pointerdown\", handlePointerDown);\n window.removeEventListener(\"pointermove\", handlePointerMove);\n window.removeEventListener(\"pointerup\", handleGlobalUp);\n };\n }\n\n private updateMagnetic(el: HTMLElement, meta: MagnetMetadata, px: number, py: number) {\n if (!meta.rect) meta.rect = el.getBoundingClientRect();\n\n const r = meta.rect;\n const { maxDistance, maxOffset, elastic, originX, originY } = meta.options;\n\n const cx = r.left + r.width * originX;\n const cy = r.top + r.height * originY;\n const dx = px - cx;\n const dy = py - cy;\n const dSq = dx * dx + dy * dy;\n\n if (dSq >= maxDistance * maxDistance) {\n this.setActive(meta, false);\n this.reset(meta);\n return;\n }\n\n this.setActive(meta, true);\n\n const dist = Math.sqrt(dSq);\n const progress = Math.min(1, dist / maxDistance);\n\n let targetX = 0;\n let targetY = 0;\n\n if (elastic) {\n const strength = Math.sin(progress * (Math.PI / 2)) * maxOffset;\n const dirX = dx / (dist || 1);\n const dirY = dy / (dist || 1);\n targetX = dirX * strength;\n targetY = dirY * strength;\n } else {\n targetX = (dx / maxDistance) * maxOffset;\n targetY = (dy / maxDistance) * maxOffset;\n }\n\n this.setTarget(meta, targetX, targetY);\n }\n\n private start() {\n if (this.listenersAttached) return;\n this.listenersAttached = true;\n\n window.addEventListener(\"pointermove\", this.handlePointerMove, { passive: true });\n window.addEventListener(\"scroll\", this.handleInvalidate, { passive: true, capture: true });\n window.addEventListener(\"resize\", this.handleInvalidate, { passive: true });\n window.addEventListener(\"blur\", this.handleWindowBlur);\n document.addEventListener(\"visibilitychange\", this.handleVisibilityChange);\n }\n\n private stop() {\n if (!this.listenersAttached) return;\n this.listenersAttached = false;\n\n window.removeEventListener(\"pointermove\", this.handlePointerMove);\n window.removeEventListener(\"scroll\", this.handleInvalidate, { capture: true });\n window.removeEventListener(\"resize\", this.handleInvalidate);\n window.removeEventListener(\"blur\", this.handleWindowBlur);\n document.removeEventListener(\"visibilitychange\", this.handleVisibilityChange);\n\n if (this.rafId !== null) {\n cancelAnimationFrame(this.rafId);\n this.rafId = null;\n }\n\n this.framePending = false;\n this.needsInvalidate = false;\n this.hasPointer = false;\n }\n\n private handlePointerMove(e: PointerEvent) {\n this.pointerX = e.clientX;\n this.pointerY = e.clientY;\n this.hasPointer = true;\n this.requestFrame();\n }\n\n private handleInvalidate() {\n this.needsInvalidate = true;\n this.requestFrame();\n }\n\n private handleWindowBlur() {\n this.hasPointer = false;\n this.resetAllActive();\n }\n\n private handleVisibilityChange() {\n if (document.hidden) {\n this.hasPointer = false;\n this.resetAllActive();\n }\n }\n\n private requestFrame() {\n if (!this.listenersAttached || this.framePending) return;\n this.framePending = true;\n this.rafId = requestAnimationFrame(this.tick);\n }\n\n private tick() {\n this.framePending = false;\n this.rafId = null;\n\n if (!this.listenersAttached) return;\n\n if (this.needsInvalidate) {\n this.invalidateVisibleRects();\n }\n\n if (!this.hasPointer || this.distanceCount === 0) return;\n\n for (const el of this.activeElements) {\n const meta = this.elements.get(el);\n if (!meta || meta.options.trigger !== \"distance\" || !meta.isIntersecting) continue;\n\n if (!meta.rect) meta.rect = el.getBoundingClientRect();\n\n const r = meta.rect;\n const { maxDistance, maxOffset, elastic, originX, originY } = meta.options;\n\n const cx = r.left + r.width * originX;\n const cy = r.top + r.height * originY;\n const dx = this.pointerX - cx;\n const dy = this.pointerY - cy;\n const dSq = dx * dx + dy * dy;\n\n if (dSq >= maxDistance * maxDistance) {\n this.setActive(meta, false);\n this.reset(meta);\n continue;\n }\n\n this.setActive(meta, true);\n\n const dist = Math.sqrt(dSq);\n const progress = Math.min(1, dist / maxDistance);\n\n let targetX = 0;\n let targetY = 0;\n\n if (elastic) {\n const strength = Math.sin(progress * (Math.PI / 2)) * maxOffset;\n const dirX = dx / (dist || 1);\n const dirY = dy / (dist || 1);\n targetX = dirX * strength;\n targetY = dirY * strength;\n } else {\n targetX = (dx / maxDistance) * maxOffset;\n targetY = (dy / maxDistance) * maxOffset;\n }\n\n this.setTarget(meta, targetX, targetY);\n }\n }\n\n private setActive(meta: MagnetMetadata, next: boolean) {\n if (meta.isActive === next) return;\n meta.isActive = next;\n meta.onActiveChange?.(next);\n }\n\n private setTarget(meta: MagnetMetadata, x: number, y: number) {\n meta.x.set(x);\n meta.y.set(y);\n }\n\n private reset(meta: MagnetMetadata) {\n this.setTarget(meta, 0, 0);\n }\n\n private invalidateVisibleRects() {\n this.needsInvalidate = false;\n for (const el of this.activeElements) {\n const meta = this.elements.get(el);\n if (meta) meta.rect = null;\n }\n }\n\n private resetAllActive() {\n for (const el of this.activeElements) {\n const meta = this.elements.get(el);\n if (!meta) continue;\n this.setActive(meta, false);\n this.reset(meta);\n meta.rect = null;\n }\n }\n}\n\nlet _instance: MagnetOrchestrator | null = null;\n\nfunction get(): MagnetOrchestrator {\n if (_instance === null) {\n _instance = new MagnetOrchestrator();\n }\n return _instance;\n}\n\ninterface MagnetOrchestratorFacade {\n register: MagnetOrchestrator[\"register\"];\n unregister: MagnetOrchestrator[\"unregister\"];\n}\n\nexport const magnetOrchestrator: MagnetOrchestratorFacade = {\n register(el, x, y, options, onActiveChange) {\n get().register(el, x, y, options, onActiveChange);\n },\n unregister(el) {\n get().unregister(el);\n },\n};\n","import {\n type MotionValue,\n type SpringOptions,\n useMotionValue,\n useReducedMotion,\n useSpring,\n} from \"motion/react\";\nimport { useLayoutEffect, useRef, useState } from \"react\";\nimport { magnetOrchestrator } from \"./orchestrator\";\nimport {\n BOUNCY_SNAP,\n LINEAR_RESET,\n MAGNET_DEFAULTS,\n SMOOTH,\n TACTILE,\n type MagnetOptions,\n} from \"./types\";\n\ntype MagneticRefCleanup = void | (() => void);\ntype MagneticRefCallback<T extends HTMLElement> = (node: T | null) => MagneticRefCleanup;\n\nexport interface UseMagneticResult<T extends HTMLElement = HTMLElement> {\n ref: MagneticRefCallback<T>;\n x: MotionValue<number>;\n y: MotionValue<number>;\n style: {\n x: MotionValue<number>;\n y: MotionValue<number>;\n };\n enabled: boolean;\n isComposing: boolean;\n reset: () => void;\n}\n\ninterface VisualOptions {\n hoverSpring: SpringOptions;\n returnSpring: SpringOptions;\n}\n\ninterface StructuralOptions {\n enabled: boolean;\n maxDistance: number;\n maxOffset: number;\n elastic: boolean;\n originX: number;\n originY: number;\n trigger: \"distance\" | \"hover\" | \"click\";\n}\n\ninterface RefState<T extends HTMLElement> {\n enabled: boolean;\n structural: StructuralOptions;\n rawX: MotionValue<number>;\n rawY: MotionValue<number>;\n reset: () => void;\n setIsComposing: (value: boolean) => void;\n registeredNode: T | null;\n}\n\nfunction normalizeStructuralOptions(options: MagnetOptions | undefined): StructuralOptions {\n return {\n enabled: options?.enabled ?? MAGNET_DEFAULTS.enabled,\n maxDistance: options?.maxDistance ?? MAGNET_DEFAULTS.maxDistance,\n maxOffset: options?.maxOffset ?? MAGNET_DEFAULTS.maxOffset,\n elastic: options?.elastic ?? MAGNET_DEFAULTS.elastic,\n originX: options?.originX ?? MAGNET_DEFAULTS.originX,\n originY: options?.originY ?? MAGNET_DEFAULTS.originY,\n trigger: options?.trigger ?? MAGNET_DEFAULTS.trigger,\n };\n}\n\nfunction normalizeVisualOptions(options: MagnetOptions | undefined): VisualOptions {\n const isSmooth = options?.smooth ?? MAGNET_DEFAULTS.smooth;\n const hoverSpring = options?.smoothSpring ?? (isSmooth ? SMOOTH : TACTILE);\n\n const shouldSnap = options?.snap ?? MAGNET_DEFAULTS.snap;\n const returnSpring = shouldSnap ? (options?.snapSpring ?? BOUNCY_SNAP) : LINEAR_RESET;\n\n return { hoverSpring, returnSpring };\n}\n\n/**\n * Expert primitive for headless magnetic interactions.\n *\n * ### The Headless Contract\n * 1. **Structural Options** (`maxDistance`, `maxOffset`, `elastic`, origins) are construction-time\n * while a node is registered. **`enabled` is special**: it may toggle on the same DOM node; the\n * hook syncs orchestrator registration on `enabled` changes via `useLayoutEffect`.\n * 2. **Visual Options** (`smooth`, `snap`, springs) can update live.\n * 3. **Ref Management**: You MUST attach `magnetic.ref` to the element that should track the pointer.\n * 4. **Style Attachment**: You MUST apply `magnetic.style` to a `motion` component.\n *\n * @example\n * ```tsx\n * const magnetic = useMagnetic({ maxOffset: 20 });\n * return <motion.div ref={magnetic.ref} style={magnetic.style} />\n * ```\n */\nexport function useMagnetic<T extends HTMLElement = HTMLElement>(\n options?: MagnetOptions,\n): UseMagneticResult<T> {\n const reducedMotion = useReducedMotion();\n const structural = normalizeStructuralOptions(options);\n const visual = normalizeVisualOptions(options);\n const enabled = structural.enabled && !reducedMotion;\n\n const [isComposing, setIsComposing] = useState(false);\n\n const rawX = useMotionValue(0);\n const rawY = useMotionValue(0);\n\n const x = useSpring(rawX, isComposing ? visual.hoverSpring : visual.returnSpring);\n const y = useSpring(rawY, isComposing ? visual.hoverSpring : visual.returnSpring);\n\n const mountedStructuralRef = useRef<StructuralOptions | null>(null);\n const cleanupRef = useRef<(() => void) | null>(null);\n const domNodeRef = useRef<T | null>(null);\n const layoutSyncPrevEnabledRef = useRef<boolean | null>(null);\n\n const rawXRef = useRef(rawX);\n const rawYRef = useRef(rawY);\n const setIsComposingRef = useRef(setIsComposing);\n rawXRef.current = rawX;\n rawYRef.current = rawY;\n setIsComposingRef.current = setIsComposing;\n\n const reset = useRef(() => {\n rawXRef.current.set(0);\n rawYRef.current.set(0);\n setIsComposingRef.current(false);\n }).current;\n\n const refStateRef = useRef<RefState<T> | null>(null);\n if (refStateRef.current === null) {\n refStateRef.current = {\n enabled,\n structural,\n rawX,\n rawY,\n reset,\n setIsComposing,\n registeredNode: null,\n };\n } else {\n refStateRef.current.enabled = enabled;\n refStateRef.current.rawX = rawX;\n refStateRef.current.rawY = rawY;\n refStateRef.current.reset = reset;\n refStateRef.current.setIsComposing = setIsComposing;\n\n if (refStateRef.current.registeredNode === null) {\n refStateRef.current.structural = structural;\n }\n }\n\n const refRef = useRef<MagneticRefCallback<T> | null>(null);\n\n if (refRef.current === null) {\n refRef.current = (node) => {\n domNodeRef.current = node;\n\n const state = refStateRef.current!;\n const currentCleanup = cleanupRef.current;\n const currentNode = state.registeredNode;\n\n if (node === currentNode) {\n return currentCleanup ?? undefined;\n }\n\n if (node === null) {\n currentCleanup?.();\n cleanupRef.current = null;\n state.registeredNode = null;\n mountedStructuralRef.current = null;\n state.reset();\n return;\n }\n\n currentCleanup?.();\n cleanupRef.current = null;\n state.registeredNode = null;\n\n if (!state.enabled) {\n mountedStructuralRef.current = null;\n state.reset();\n return;\n }\n\n const structuralAtMount = mountedStructuralRef.current ?? state.structural;\n mountedStructuralRef.current = structuralAtMount;\n state.registeredNode = node;\n\n magnetOrchestrator.register(\n node,\n state.rawX,\n state.rawY,\n structuralAtMount,\n state.setIsComposing,\n );\n\n const cleanup = () => {\n magnetOrchestrator.unregister(node);\n cleanupRef.current = null;\n if (state.registeredNode === node) {\n state.registeredNode = null;\n }\n mountedStructuralRef.current = null;\n state.reset();\n };\n\n cleanupRef.current = cleanup;\n return cleanup;\n };\n }\n\n useLayoutEffect(() => {\n const node = domNodeRef.current;\n const state = refStateRef.current!;\n\n if (layoutSyncPrevEnabledRef.current === null) {\n layoutSyncPrevEnabledRef.current = enabled;\n return;\n }\n\n if (layoutSyncPrevEnabledRef.current === enabled) {\n return;\n }\n\n layoutSyncPrevEnabledRef.current = enabled;\n\n if (!node) {\n return;\n }\n\n cleanupRef.current?.();\n cleanupRef.current = null;\n state.registeredNode = null;\n\n if (!state.enabled) {\n mountedStructuralRef.current = null;\n state.reset();\n return;\n }\n\n const structuralAtMount = mountedStructuralRef.current ?? state.structural;\n mountedStructuralRef.current = structuralAtMount;\n state.registeredNode = node;\n\n magnetOrchestrator.register(\n node,\n state.rawX,\n state.rawY,\n structuralAtMount,\n state.setIsComposing,\n );\n\n const cleanup = () => {\n magnetOrchestrator.unregister(node);\n cleanupRef.current = null;\n if (state.registeredNode === node) {\n state.registeredNode = null;\n }\n mountedStructuralRef.current = null;\n state.reset();\n };\n\n cleanupRef.current = cleanup;\n }, [enabled]);\n\n const ref = refRef.current;\n\n return {\n ref,\n x: enabled ? x : rawX,\n y: enabled ? y : rawY,\n style: enabled ? { x, y } : { x: rawX, y: rawY },\n enabled,\n isComposing: enabled && isComposing,\n reset,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`}),require(`./types.cjs`);const e=require(`./useMagnetic-hzN5_pe4.cjs`);exports.useMagnetic=e.t;
@@ -0,0 +1,39 @@
1
+ import { MagnetOptions } from "./types.cjs";
2
+ import { MotionValue } from "motion/react";
3
+
4
+ //#region src/useMagnetic.d.ts
5
+ type MagneticRefCleanup = void | (() => void);
6
+ type MagneticRefCallback<T extends HTMLElement> = (node: T | null) => MagneticRefCleanup;
7
+ interface UseMagneticResult<T extends HTMLElement = HTMLElement> {
8
+ ref: MagneticRefCallback<T>;
9
+ x: MotionValue<number>;
10
+ y: MotionValue<number>;
11
+ style: {
12
+ x: MotionValue<number>;
13
+ y: MotionValue<number>;
14
+ };
15
+ enabled: boolean;
16
+ isComposing: boolean;
17
+ reset: () => void;
18
+ }
19
+ /**
20
+ * Expert primitive for headless magnetic interactions.
21
+ *
22
+ * ### The Headless Contract
23
+ * 1. **Structural Options** (`maxDistance`, `maxOffset`, `elastic`, origins) are construction-time
24
+ * while a node is registered. **`enabled` is special**: it may toggle on the same DOM node; the
25
+ * hook syncs orchestrator registration on `enabled` changes via `useLayoutEffect`.
26
+ * 2. **Visual Options** (`smooth`, `snap`, springs) can update live.
27
+ * 3. **Ref Management**: You MUST attach `magnetic.ref` to the element that should track the pointer.
28
+ * 4. **Style Attachment**: You MUST apply `magnetic.style` to a `motion` component.
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * const magnetic = useMagnetic({ maxOffset: 20 });
33
+ * return <motion.div ref={magnetic.ref} style={magnetic.style} />
34
+ * ```
35
+ */
36
+ declare function useMagnetic<T extends HTMLElement = HTMLElement>(options?: MagnetOptions): UseMagneticResult<T>;
37
+ //#endregion
38
+ export { UseMagneticResult, useMagnetic };
39
+ //# sourceMappingURL=useMagnetic.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMagnetic.d.cts","names":[],"sources":["../src/useMagnetic.ts"],"mappings":";;;;KAkBK,kBAAA;AAAA,KACA,mBAAA,WAA8B,WAAA,KAAgB,IAAA,EAAM,CAAA,YAAa,kBAAA;AAAA,UAErD,iBAAA,WAA4B,WAAA,GAAc,WAAA;EACzD,GAAA,EAAK,mBAAA,CAAoB,CAAA;EACzB,CAAA,EAAG,WAAA;EACH,CAAA,EAAG,WAAA;EACH,KAAA;IACE,CAAA,EAAG,WAAA;IACH,CAAA,EAAG,WAAA;EAAA;EAEL,OAAA;EACA,WAAA;EACA,KAAA;AAAA;;;;;;;;;AAVF;;;;;;;;;iBA6EgB,WAAA,WAAsB,WAAA,GAAc,WAAA,CAAA,CAClD,OAAA,GAAU,aAAA,GACT,iBAAA,CAAkB,CAAA"}
@@ -0,0 +1,39 @@
1
+ import { MagnetOptions } from "./types.mjs";
2
+ import { MotionValue } from "motion/react";
3
+
4
+ //#region src/useMagnetic.d.ts
5
+ type MagneticRefCleanup = void | (() => void);
6
+ type MagneticRefCallback<T extends HTMLElement> = (node: T | null) => MagneticRefCleanup;
7
+ interface UseMagneticResult<T extends HTMLElement = HTMLElement> {
8
+ ref: MagneticRefCallback<T>;
9
+ x: MotionValue<number>;
10
+ y: MotionValue<number>;
11
+ style: {
12
+ x: MotionValue<number>;
13
+ y: MotionValue<number>;
14
+ };
15
+ enabled: boolean;
16
+ isComposing: boolean;
17
+ reset: () => void;
18
+ }
19
+ /**
20
+ * Expert primitive for headless magnetic interactions.
21
+ *
22
+ * ### The Headless Contract
23
+ * 1. **Structural Options** (`maxDistance`, `maxOffset`, `elastic`, origins) are construction-time
24
+ * while a node is registered. **`enabled` is special**: it may toggle on the same DOM node; the
25
+ * hook syncs orchestrator registration on `enabled` changes via `useLayoutEffect`.
26
+ * 2. **Visual Options** (`smooth`, `snap`, springs) can update live.
27
+ * 3. **Ref Management**: You MUST attach `magnetic.ref` to the element that should track the pointer.
28
+ * 4. **Style Attachment**: You MUST apply `magnetic.style` to a `motion` component.
29
+ *
30
+ * @example
31
+ * ```tsx
32
+ * const magnetic = useMagnetic({ maxOffset: 20 });
33
+ * return <motion.div ref={magnetic.ref} style={magnetic.style} />
34
+ * ```
35
+ */
36
+ declare function useMagnetic<T extends HTMLElement = HTMLElement>(options?: MagnetOptions): UseMagneticResult<T>;
37
+ //#endregion
38
+ export { UseMagneticResult, useMagnetic };
39
+ //# sourceMappingURL=useMagnetic.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useMagnetic.d.mts","names":[],"sources":["../src/useMagnetic.ts"],"mappings":";;;;KAkBK,kBAAA;AAAA,KACA,mBAAA,WAA8B,WAAA,KAAgB,IAAA,EAAM,CAAA,YAAa,kBAAA;AAAA,UAErD,iBAAA,WAA4B,WAAA,GAAc,WAAA;EACzD,GAAA,EAAK,mBAAA,CAAoB,CAAA;EACzB,CAAA,EAAG,WAAA;EACH,CAAA,EAAG,WAAA;EACH,KAAA;IACE,CAAA,EAAG,WAAA;IACH,CAAA,EAAG,WAAA;EAAA;EAEL,OAAA;EACA,WAAA;EACA,KAAA;AAAA;;;;;;;;;AAVF;;;;;;;;;iBA6EgB,WAAA,WAAsB,WAAA,GAAc,WAAA,CAAA,CAClD,OAAA,GAAU,aAAA,GACT,iBAAA,CAAkB,CAAA"}
@@ -0,0 +1 @@
1
+ import"./types.mjs";import{t as e}from"./useMagnetic-Cih9JSy3.mjs";export{e as useMagnetic};
package/package.json ADDED
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "@tonybonet/magnetic",
3
+ "version": "0.1.0",
4
+ "description": "Pointer-tracking magnetic UI elements for React with spring physics and a shared orchestrator.",
5
+ "license": "MIT",
6
+ "author": "Antonio Bonet",
7
+ "homepage": "https://github.com/tonyblu331/magnetic#readme",
8
+ "bugs": {
9
+ "url": "https://github.com/tonyblu331/magnetic/issues"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "git+https://github.com/tonyblu331/magnetic.git",
14
+ "directory": "packages/magnetic"
15
+ },
16
+ "keywords": [
17
+ "magnetic",
18
+ "cursor",
19
+ "pointer",
20
+ "animation",
21
+ "react",
22
+ "motion",
23
+ "spring",
24
+ "physics",
25
+ "ui",
26
+ "interaction"
27
+ ],
28
+ "files": [
29
+ "dist",
30
+ "README.md",
31
+ "CHANGELOG.md"
32
+ ],
33
+ "type": "module",
34
+ "sideEffects": false,
35
+ "main": "./dist/useMagnetic.cjs",
36
+ "module": "./dist/useMagnetic.mjs",
37
+ "types": "./dist/useMagnetic.d.mts",
38
+ "exports": {
39
+ ".": {
40
+ "import": {
41
+ "types": "./dist/useMagnetic.d.mts",
42
+ "default": "./dist/useMagnetic.mjs"
43
+ },
44
+ "require": {
45
+ "types": "./dist/useMagnetic.d.cts",
46
+ "default": "./dist/useMagnetic.cjs"
47
+ }
48
+ },
49
+ "./component": {
50
+ "import": {
51
+ "types": "./dist/Magnetic.d.mts",
52
+ "default": "./dist/Magnetic.mjs"
53
+ },
54
+ "require": {
55
+ "types": "./dist/Magnetic.d.cts",
56
+ "default": "./dist/Magnetic.cjs"
57
+ }
58
+ },
59
+ "./presets": {
60
+ "import": {
61
+ "types": "./dist/types.d.mts",
62
+ "default": "./dist/types.mjs"
63
+ },
64
+ "require": {
65
+ "types": "./dist/types.d.cts",
66
+ "default": "./dist/types.cjs"
67
+ }
68
+ },
69
+ "./package.json": "./package.json"
70
+ },
71
+ "publishConfig": {
72
+ "access": "public"
73
+ },
74
+ "scripts": {
75
+ "build": "vp pack src/useMagnetic.ts src/Magnetic.tsx src/types.ts --dts --format esm --format cjs --deps.never-bundle react --deps.never-bundle motion/react --minify --sourcemap --clean",
76
+ "check": "vp check",
77
+ "test": "vp test"
78
+ },
79
+ "devDependencies": {
80
+ "@testing-library/react": "catalog:",
81
+ "@types/node": "catalog:",
82
+ "@types/react": "catalog:",
83
+ "jsdom": "catalog:",
84
+ "motion": "catalog:",
85
+ "react": "catalog:",
86
+ "typescript": "catalog:",
87
+ "vite-plus": "catalog:"
88
+ },
89
+ "peerDependencies": {
90
+ "motion": "^12.0.0",
91
+ "react": "^19.0.0"
92
+ },
93
+ "packageManager": "pnpm@11.1.1"
94
+ }