immerser-react 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vladimir Lysov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,232 @@
1
+ # Immerser React
2
+
3
+ React adapter for declaring Immerser markup with components instead of writing `data-immerser-*` DOM by hand.
4
+
5
+ The project exposes a small component set:
6
+
7
+ - `ImmerserProvider` - Owns the core `Immerser` controller lifecycle and shares its scroll state with React components.
8
+ - `ImmerserRoot` - Renders the fixed root container and the per-layer mask structure driven by the core controller.
9
+ - `ImmerserLayer` - Marks a real section as an immerser layer.
10
+ - `ImmerserSolid` - Declares content positioned inside `ImmerserRoot`, usually absolutely positioned within that root.
11
+ - `ImmerserPager` - Builds a pager solid inside `ImmerserRoot` from provider layer ids.
12
+ - `ImmerserSynchroLink` - Anchor with synchronized hover state across layer clones.
13
+
14
+ ## Terms
15
+
16
+ - `Immerser` (here `ImmerserRoot` component) is the fixed root node.
17
+ - `Solid` is a piece of fixed UI rendered inside the root and copied into every layer mask.
18
+ - `Layer` is a real scroll section that drives mask transitions.
19
+ - `Mask` is internal clipping markup rendered by the adapter.
20
+ - `Synchro` means shared state between solid copies.
21
+
22
+ ## Install
23
+
24
+ ```ts
25
+ import { ImmerserLayer, ImmerserProvider, ImmerserRoot, ImmerserSolid } from '@immerser/react';
26
+ ```
27
+
28
+ ## How to Use
29
+
30
+ Wrap the page in `ImmerserProvider`, render fixed solids inside `ImmerserRoot`, then render scroll sections with `ImmerserLayer`.
31
+
32
+ `ImmerserProvider` owns the controller lifecycle. Provider props are adapter-specific props plus the runtime controls exposed by the React adapter.
33
+
34
+ `solidClassnamesByLayerId` is the central config. Its top-level keys must match `ImmerserLayer id` values. Layer and pager order comes from the DOM order of `ImmerserLayer` elements, not from config key order. Each layer value maps solid names to CSS classes applied to the copied solids inside that layer mask, so fixed content stays readable when the fixed container overlaps that layer.
35
+
36
+ `ImmerserLayer` elements must have real layout height. Prefer content-driven height, or define CSS such as `min-height`; zero-height layers cannot be measured correctly.
37
+
38
+ ```tsx
39
+ import {
40
+ ImmerserLayer,
41
+ ImmerserPager,
42
+ ImmerserProvider,
43
+ ImmerserRoot,
44
+ ImmerserSolid,
45
+ ImmerserSynchroLink,
46
+ } from '@immerser/react';
47
+
48
+ export const Page = () => {
49
+ return (
50
+ <ImmerserProvider
51
+ solidClassnamesByLayerId={{
52
+ intro: {
53
+ logo: 'logo--light',
54
+ pager: 'pager--light',
55
+ },
56
+ details: {
57
+ menu: 'menu--dark',
58
+ pager: 'pager--dark',
59
+ },
60
+ }}
61
+ fromViewportWidth={1024}
62
+ scrollAdjustThreshold={50}
63
+ scrollAdjustDelay={600}
64
+ updateLocationHash={(layerId) => window.history.replaceState(null, '', `#${layerId}`)}
65
+ on={{
66
+ layerProgressChange(layerProgressArray) {
67
+ console.log(layerProgressArray);
68
+ },
69
+ }}
70
+ >
71
+ <ImmerserRoot className="fixed">
72
+ <ImmerserPager className="fixed__pager pager" activeClassName="pager__link--active" />
73
+
74
+ <ImmerserSolid name="logo" className="fixed__logo logo">
75
+ <ImmerserSynchroLink href="#intro" hoverClassName="_hover" synchroId="logo">
76
+ immerser
77
+ </ImmerserSynchroLink>
78
+ </ImmerserSolid>
79
+
80
+ <ImmerserSolid name="menu" className="fixed__menu menu">
81
+ <ImmerserSynchroLink href="#intro" hoverClassName="_hover" synchroId="intro">
82
+ Intro
83
+ </ImmerserSynchroLink>
84
+ <ImmerserSynchroLink href="#details" hoverClassName="_hover" synchroId="details">
85
+ Details
86
+ </ImmerserSynchroLink>
87
+ </ImmerserSolid>
88
+ </ImmerserRoot>
89
+
90
+ <ImmerserLayer id="intro" className="section section--intro">
91
+ <h1>Intro</h1>
92
+ <p>Section content gives the layer real layout height.</p>
93
+ </ImmerserLayer>
94
+ <ImmerserLayer id="details" className="section section--details">
95
+ <h2>Details</h2>
96
+ <p>Immerser measures this layout to calculate mask transitions.</p>
97
+ </ImmerserLayer>
98
+ </ImmerserProvider>
99
+ );
100
+ };
101
+ ```
102
+
103
+ ## Styling
104
+
105
+ Position `ImmerserRoot` and every solid with CSS. Do not pass `style` to adapter components: the adapter reserves inline styles for technical Immerser styles and replaces any user-provided style prop. Child content inside adapter components is regular React.
106
+
107
+ ```css
108
+ .fixed {
109
+ position: fixed;
110
+ inset: 32px;
111
+ z-index: 10;
112
+ }
113
+
114
+ .fixed__logo,
115
+ .fixed__menu,
116
+ .fixed__pager {
117
+ position: absolute;
118
+ }
119
+
120
+ .fixed__logo {
121
+ left: 0;
122
+ top: 0;
123
+ }
124
+
125
+ .fixed__menu {
126
+ right: 0;
127
+ top: 0;
128
+ }
129
+
130
+ .fixed__pager {
131
+ left: 0;
132
+ top: 50%;
133
+ transform: translateY(-50%);
134
+ }
135
+
136
+ .logo--light,
137
+ .pager--light {
138
+ color: white;
139
+ }
140
+
141
+ .menu--dark,
142
+ .pager--dark {
143
+ color: black;
144
+ }
145
+
146
+ .section {
147
+ min-height: 100vh;
148
+ padding: 120px 32px;
149
+ }
150
+ ```
151
+
152
+ ## Component API
153
+
154
+ This section is generated from current component files and their JSDoc comments.
155
+
156
+ ## ImmerserProvider
157
+
158
+ Owns the core `Immerser` controller lifecycle and shares its scroll state with React components. Provider props are adapter-specific props plus the runtime controls exposed by the React adapter. Event handlers passed through `on` are init-only and registered when the controller is created. `selectorRoot` recreates the core controller when changed. Runtime controls are forwarded to the underlying controller. `solidClassnamesByLayerId` keys must match `ImmerserLayer` ids. The React adapter uses `solidClassnamesByLayerId` to render solid copies inside each layer mask itself. Init-only and adapter-owned controller options are not exposed. This keeps DOM measurement, mask rendering and scroll listeners in one place while the rest of the API stays declarative.
159
+
160
+ | prop | required | type | description |
161
+ | - | - | - | - |
162
+ | children | no | `ReactNode` | React tree that declares an immerser root, its absolute solids and scroll layers. |
163
+ | on | no | `Record<EventName, Handler>` | Initial event handlers registered when the core controller is created. Changing this prop does not update the current controller. See [core events](https://github.com/dubaua/immerser#events). |
164
+ | selectorRoot | no | `ParentNode` | Parent node used for selector discovery. Changing it recreates the core controller. |
165
+ | solidClassnamesByLayerId | yes | `layer id -> solid id -> classname` | Map of layer ids to solid ids to CSS classes. Layer ids must match `ImmerserLayer id` values; solid ids must match `ImmerserSolid name` values. |
166
+ | debug | no | `boolean` | Enables runtime reporting of warnings and errors. Default: `false`. |
167
+ | fromViewportWidth | no | `number` | Minimal viewport width (px) at which immerser mounts runtime; below it unmounts runtime. Default: `0`. |
168
+ | updateLocationHash | no | `UpdateLocationHashHandler` | Handles active layer id when it should be pushed into location hash. Default: `undefined`. |
169
+ | pagerThreshold | no | `number` | Portion of viewport height that must overlap the next layer before pager switches (0–1). Default: `0.5`. |
170
+ | scrollAdjustDelay | no | `number` | Delay in ms before running scroll snapping after user scroll stops. Default: `600`. |
171
+ | scrollAdjustThreshold | no | `number` | Pixel threshold near section edges that triggers scroll snapping when exceeded, if 0 - no adjusting. Default: `0`. |
172
+
173
+ ## ImmerserRoot
174
+
175
+ Renders the fixed root container and the per-layer mask structure driven by the core controller. Direct children must be `ImmerserSolid` or `ImmerserPager` so each layer can receive its own solid classnames. Fragments and wrapper components are not accepted as direct children. In React mode, the core measures layer masks and moves their transitions; React owns the mask markup itself.
176
+
177
+ This component has no adapter-specific props.
178
+
179
+ ## ImmerserLayer
180
+
181
+ Marks a real section as an immerser layer. The core uses these nodes to calculate layer bounds, progress and active index. Render one layer component for every scroll section that should drive solid class changes.
182
+
183
+ | prop | required | type | description |
184
+ | - | - | - | - |
185
+ | as | no | `T` | Element used for the layer; defaults to `div`. |
186
+ | children | no | `ReactNode` | Layer content that defines the page section measured by the core controller. |
187
+ | id | yes | `string` | Stable layer id used for hash links, pager links and solid classname lookup. Must match a `solidClassnamesByLayerId` key. |
188
+
189
+ ## ImmerserSolid
190
+
191
+ Declares content positioned inside `ImmerserRoot`, usually absolutely positioned within that root. React renders a copy into each mask and applies layer-specific classnames by solid name.
192
+
193
+ | prop | required | type | description |
194
+ | - | - | - | - |
195
+ | name | yes | `string` | Solid id used to read the matching classname from each layer configuration. |
196
+ | as | no | `T` | Element or component used to render the solid inside `ImmerserRoot`; defaults to `div`. |
197
+ | children | no | `ReactNode` | Interactive content rendered inside every layer mask. Position it with your own CSS. |
198
+
199
+ ## ImmerserPager
200
+
201
+ Builds a pager solid inside `ImmerserRoot` from provider layer ids. Renders one link per DOM layer as a solid named `pager`, ordered by `ImmerserLayer` DOM order. Add `pager` classnames to layer configs when the pager needs per-layer visual changes. It mirrors core pager behavior in React so active state comes from context instead of DOM class mutation. In default mode each generated link receives `linkClassName`, `href="#layerId"`, `hoverClassName` with `_hover` as the default, and `synchroId="pager-${layerIndex}"`. Custom render mode receives `isActive`, `layerId` and `layerIndex` and does not add those generated-link props.
202
+
203
+ | prop | required | type | description |
204
+ | - | - | - | - |
205
+ | as | no | `T` | Element used for the pager wrapper; defaults to `nav`. |
206
+
207
+ ### Default mode
208
+
209
+ | prop | required | type | description |
210
+ | - | - | - | - |
211
+ | activeClassName | yes | `string` | Classname applied to the generated link for the currently active layer. |
212
+ | linkClassName | yes | `string` | Classname applied to each generated pager link. |
213
+ | hoverClassName | no | `string` | Classname applied to generated link copies while any of them is hovered. |
214
+ | renderLink | no | `never` | - |
215
+
216
+ ### Custom render mode
217
+
218
+ | prop | required | type | description |
219
+ | - | - | - | - |
220
+ | renderLink | yes | `(props: RenderLinkProps) => ReactNode` | Renders custom content for each configured layer. |
221
+ | activeClassName | no | `never` | - |
222
+ | linkClassName | no | `never` | - |
223
+ | hoverClassName | no | `never` | - |
224
+
225
+ ## ImmerserSynchroLink
226
+
227
+ Anchor with synchronized hover state across layer clones. One source link is rendered into multiple layer-mask copies; `synchroId` keeps only those generated copies in the same hover group. This mirrors the core `data-immerser-synchro-hover` feature without relying on cloned DOM event wiring.
228
+
229
+ | prop | required | type | description |
230
+ | - | - | - | - |
231
+ | hoverClassName | yes | `string` | Classname applied to generated copies of this link while any one of them is hovered. |
232
+ | synchroId | yes | `string` | Stable hover group id for this source link. `ImmerserRoot` copies the link into every layer mask, so use the same `synchroId` for links that should share hover state, and different values for independent hover groups. |
@@ -0,0 +1 @@
1
+ "use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const e=require("react/jsx-runtime"),r=require("immerser"),s=require("react"),t=require("classnames"),n=s.createContext(null),a=s.createContext(null),l=s.createContext({activeSynchroId:null,setActiveSynchroId:()=>{}}),ImmerserProvider=({children:t,on:o,solidClassnamesByLayerId:i,selectorRoot:c,...u})=>{const[d,m]=s.useState(-1),[y,I]=s.useState(null),[h,v]=s.useState(null),p=s.useRef(d),g=s.useRef(null),f=s.useRef(u),x=(()=>{const e=s.useRef(new Map),[r,t]=s.useState([]),[,n]=s.useState(0),a=r.length>0&&e.current.size===r.length&&r.every(r=>e.current.has(r)),l=s.useCallback(e=>{t(r=>r.includes(e)?r:[...r,e])},[]),o=s.useCallback(e=>{t(r=>r.filter(r=>r!==e))},[]),i=s.useCallback((r,s)=>{if(s){if(e.current.get(r)===s)return;e.current.set(r,s)}else{if(!e.current.has(r))return;e.current.delete(r)}n(e=>e+1)},[]);return{isReady:a,layerIds:r,maskInnerNodesRef:e,registerLayer:l,unregisterLayer:o,registerMaskInner:i}})(),L=x.layerIds,{debug:N,fromViewportWidth:R,updateLocationHash:S,pagerThreshold:C,scrollAdjustDelay:j,scrollAdjustThreshold:k}=u,E=s.useRef(L);function M(e){-1===e.activeIndex&&0===e.layerProgressArray.length&&E.current.length>0||p.current===e.activeIndex||(p.current=e.activeIndex,m(e.activeIndex))}f.current=u,E.current=L,s.useLayoutEffect(()=>()=>{const e=g.current;e&&(e.destroy(),g.current=null,-1!==p.current&&(p.current=-1,m(-1)))},[h,c]),s.useLayoutEffect(()=>{if(g.current||!h||!x.isReady)return;const e=new r({...f.current,hasExternalRenderer:!0,on:o,selectorRoot:c??h.parentNode??document});g.current=e,e.on("stateChange",M),M(e)},[h,c,x.isReady]),s.useLayoutEffect(()=>{var e;x.isReady&&(null==(e=g.current)||e.render())},[t,L,x.isReady,i]),s.useEffect(()=>{var e;const r=f.current;null==(e=g.current)||e.updateOptions(r)},[N,R,S,C,j,k]);const P=s.useMemo(()=>({layerIds:L,registerLayer:x.registerLayer,registerMaskInner:x.registerMaskInner,setRendererRootNode:v,solidClassnamesByLayerId:i,unregisterLayer:x.unregisterLayer}),[L,x.registerLayer,x.registerMaskInner,x.unregisterLayer,i]),b=s.useMemo(()=>({activeSynchroId:y,setActiveSynchroId:I}),[y]);return e.jsx(n.Provider,{value:P,children:e.jsx(a.Provider,{value:d,children:e.jsx(l.Provider,{value:b,children:t})})})};ImmerserProvider.displayName="ImmerserProvider";const o=e=>{throw new Error(`${e} must be used inside <ImmerserProvider>.`)},i=e=>{const r=s.useContext(n);return r||o(e),r},ImmerserSolid=({as:s,children:t,className:n,name:a,style:l,...o})=>{i("ImmerserSolid");const c=s??"div";return e.jsx(c,{...o,className:n,"data-immerser-solid":a,style:r.InteractiveStyles,children:t})};ImmerserSolid.displayName="ImmerserSolid";const ImmerserSynchroLink=({className:r,hoverClassName:n,onMouseEnter:a,onMouseLeave:o,synchroId:i,...c})=>{const{activeSynchroId:u,setActiveSynchroId:d}=s.useContext(l);return e.jsx("a",{...c,className:t(r,{[n]:u===i}),onMouseEnter:function(e){d(i),null==a||a(e)},onMouseLeave:function(e){d(e=>e===i?null:e),null==o||o(e)}})};ImmerserSynchroLink.displayName="ImmerserSynchroLink";const ImmerserPager=({activeClassName:r,className:n,linkClassName:l,as:c="nav",hoverClassName:u="_hover",renderLink:d,...m})=>{const{layerIds:y}=i("ImmerserPager"),I=(e=>{const r=s.useContext(a);return null===r&&o(e),r})("ImmerserPager");return e.jsx(ImmerserSolid,{...m,as:c,className:n,"data-immerser-pager":"",name:"pager",children:y.map((n,a)=>{const o=a===I;return d?e.jsx(s.Fragment,{children:d({isActive:o,layerId:n,layerIndex:a})},n):e.jsx(ImmerserSynchroLink,{className:t(l,{[r]:o}),href:`#${n}`,hoverClassName:u,synchroId:`pager-${a}`},n)})})};ImmerserPager.displayName="ImmerserPager";const c=(e,r={})=>s.Children.map(e,e=>{if(null==e||!1===e)return e;if(!s.isValidElement(e)||e.type!==ImmerserSolid&&e.type!==ImmerserPager)throw new Error("ImmerserRoot accepts only ImmerserSolid or ImmerserPager as direct children.");const n=e.props.name??"pager";return s.cloneElement(e,{className:t(e.props.className,r[n])})}),u={...r.CroppedFullAbsoluteStyles},d=({children:t,style:n,...a})=>{const{layerIds:l,registerMaskInner:o,setRendererRootNode:d,solidClassnamesByLayerId:m}=i("ImmerserRoot"),y=s.useRef(null);return s.useLayoutEffect(()=>(d(y.current),()=>{d(null)}),[d]),e.jsx("div",{ref:y,...a,"data-immerser":"",style:r.NotInteractiveStyles,children:l.map((r,s)=>e.jsx("div",{"aria-hidden":0!==s||void 0,"data-immerser-layer-id":r,"data-immerser-mask":"",style:u,children:e.jsx("div",{ref:e=>o(r,e),"data-immerser-mask-inner":"",style:u,children:c(t,m[r])})},r))})};d.displayName="ImmerserRoot";const ImmerserLayer=({as:r,children:t,id:n,style:a,...l})=>{const{registerLayer:o,unregisterLayer:c}=i("ImmerserLayer"),u=r??"div";return s.useLayoutEffect(()=>(o(n),()=>{c(n)}),[n,o,c]),e.jsx(u,{id:n,...l,"data-immerser-layer":!0,children:t})};ImmerserLayer.displayName="ImmerserLayer",exports.ImmerserLayer=ImmerserLayer,exports.ImmerserPager=ImmerserPager,exports.ImmerserProvider=ImmerserProvider,exports.ImmerserRoot=d,exports.ImmerserSolid=ImmerserSolid,exports.ImmerserSynchroLink=ImmerserSynchroLink;
@@ -0,0 +1,184 @@
1
+ import { ComponentPropsWithoutRef } from 'react';
2
+ import { ElementType } from 'react';
3
+ import { HTMLAttributes } from 'react';
4
+ import { JSX } from 'react/jsx-runtime';
5
+ import { Options } from 'immerser';
6
+ import { ReactNode } from 'react';
7
+ import { RuntimeOptions } from 'immerser';
8
+
9
+ declare type CustomRenderModeProps = {
10
+ /** Renders custom content for each configured layer. */
11
+ renderLink: (props: RenderLinkProps) => ReactNode;
12
+ activeClassName?: never;
13
+ linkClassName?: never;
14
+ hoverClassName?: never;
15
+ };
16
+
17
+ declare type DefaultModeProps = {
18
+ /** Classname applied to the generated link for the currently active layer. */
19
+ activeClassName: string;
20
+ /** Classname applied to each generated pager link. */
21
+ linkClassName: string;
22
+ /** Classname applied to generated link copies while any of them is hovered. */
23
+ hoverClassName?: string;
24
+ renderLink?: never;
25
+ };
26
+
27
+ declare type DeniedStyleProp = {
28
+ /**
29
+ * Inline styles are not supported by Immerser React components.
30
+ * The adapter reserves the style attribute for technical Immerser styles.
31
+ */
32
+ style?: never;
33
+ };
34
+
35
+ /**
36
+ * Marks a real section as an immerser layer.
37
+ * The core uses these nodes to calculate layer bounds, progress and active index.
38
+ * Render one layer component for every scroll section that should drive solid class changes.
39
+ *
40
+ * @public
41
+ */
42
+ export declare const ImmerserLayer: {
43
+ <T extends ElementType = "div">({ as, children, id, style: _style, ...rest }: Props_3<T>): JSX.Element;
44
+ displayName: string;
45
+ };
46
+
47
+ /**
48
+ * Builds a pager solid inside `ImmerserRoot` from provider layer ids.
49
+ * Renders one link per DOM layer as a solid named `pager`, ordered by `ImmerserLayer` DOM order.
50
+ * Add `pager` classnames to layer configs when the pager needs per-layer visual changes.
51
+ * It mirrors core pager behavior in React so active state comes from context instead of DOM class mutation.
52
+ * In default mode each generated link receives `linkClassName`, `href="#layerId"`,
53
+ * `hoverClassName` with `_hover` as the default, and `synchroId="pager-${layerIndex}"`.
54
+ * Custom render mode receives `isActive`, `layerId` and `layerIndex`
55
+ * and does not add those generated-link props.
56
+ *
57
+ * @public
58
+ */
59
+ export declare const ImmerserPager: {
60
+ ({ activeClassName, className, linkClassName, as, hoverClassName, renderLink, ...rest }: Props_4): JSX.Element;
61
+ displayName: string;
62
+ };
63
+
64
+ /**
65
+ * Owns the core `Immerser` controller lifecycle and shares its scroll state with React components.
66
+ * Provider props are adapter-specific props plus `Partial<RuntimeOptions>` from `immerser`.
67
+ * `RuntimeOptions` is the source of hot core options accepted by the React adapter.
68
+ * Event handlers passed through `on` are init-only and registered when the controller is created.
69
+ * `selectorRoot` recreates the core controller when changed.
70
+ * Runtime options are forwarded through `updateOptions`.
71
+ * See [core options docs](https://github.com/dubaua/immerser#options).
72
+ * `solidClassnamesByLayerId` keys must match `ImmerserLayer` ids.
73
+ * `solidClassnamesByLayerId` keeps the same shape as the constructor option,
74
+ * but the adapter uses it to render solid copies inside each layer mask and does not forward it as-is.
75
+ * Init-only and adapter-owned core options are not exposed:
76
+ * `autoMount`, `hasExternalScroll`, `hasExternalRenderer`, `pagerLinkActiveClassname`
77
+ * and the core `solidClassnamesByLayerId` contract.
78
+ * This keeps DOM measurement, mask rendering and scroll listeners in one place
79
+ * while the rest of the API stays declarative.
80
+ *
81
+ * @public
82
+ */
83
+ export declare const ImmerserProvider: {
84
+ ({ children, on, solidClassnamesByLayerId, selectorRoot, ...options }: Props): JSX.Element;
85
+ displayName: string;
86
+ };
87
+
88
+ /**
89
+ * Renders the fixed root container and the per-layer mask structure driven by the core controller.
90
+ * Direct children must be `ImmerserSolid` or `ImmerserPager` so each layer can receive its own solid classnames.
91
+ * Fragments and wrapper components are not accepted as direct children.
92
+ * In React mode, the core measures layer masks and moves their transitions; React owns the mask markup itself.
93
+ *
94
+ * @public
95
+ */
96
+ export declare const ImmerserRoot: {
97
+ ({ children, style: _style, ...rest }: Props_2): JSX.Element;
98
+ displayName: string;
99
+ };
100
+
101
+ /**
102
+ * Declares content positioned inside `ImmerserRoot`, usually absolutely positioned within that root.
103
+ * React renders a copy into each mask and applies layer-specific classnames by solid name.
104
+ *
105
+ * @public
106
+ */
107
+ export declare const ImmerserSolid: {
108
+ <T extends ElementType = "div">({ as, children, className, name, style: _style, ...rest }: Props_5<T>): JSX.Element;
109
+ displayName: string;
110
+ };
111
+
112
+ /**
113
+ * Anchor with synchronized hover state across layer clones.
114
+ * One source link is rendered into multiple layer-mask copies;
115
+ * `synchroId` keeps only those generated copies in the same hover group.
116
+ * This mirrors the core `data-immerser-synchro-hover` feature without relying on cloned DOM event wiring.
117
+ *
118
+ * @public
119
+ */
120
+ export declare const ImmerserSynchroLink: {
121
+ ({ className, hoverClassName, onMouseEnter, onMouseLeave, synchroId, ...rest }: Props_6): JSX.Element;
122
+ displayName: string;
123
+ };
124
+
125
+ declare type Props = {
126
+ /** React tree that declares an immerser root, its absolute solids and scroll layers. */
127
+ children?: ReactNode;
128
+ /**
129
+ * Initial event handlers registered when the core controller is created.
130
+ * Changing this prop does not update the current controller.
131
+ */
132
+ on?: Options['on'];
133
+ /** Parent node used for selector discovery. Changing it recreates the core controller. */
134
+ selectorRoot?: Options['selectorRoot'];
135
+ /**
136
+ * React-only per-layer solid modifiers keyed by layer id.
137
+ * This is intentionally not passed to the core controller, even though the core has a similarly named option.
138
+ * The React adapter uses it to render masked solid clones itself.
139
+ */
140
+ solidClassnamesByLayerId: Options['solidClassnamesByLayerId'];
141
+ } & Partial<RuntimeOptions>;
142
+
143
+ declare type Props_2 = Omit<HTMLAttributes<HTMLDivElement>, 'style'> & DeniedStyleProp;
144
+
145
+ declare type Props_3<T extends ElementType = 'div'> = {
146
+ /** Element used for the layer; defaults to `div`. */
147
+ as?: T;
148
+ /** Layer content that defines the page section measured by the core controller. */
149
+ children?: ReactNode;
150
+ /** Stable layer id used for hash links, pager links and solid classname lookup.
151
+ * Must match a `solidClassnamesByLayerId` key. */
152
+ id: string;
153
+ } & Omit<ComponentPropsWithoutRef<T>, 'as' | 'children' | 'id' | 'style'> & DeniedStyleProp;
154
+
155
+ declare type Props_4<T extends ElementType = 'nav'> = {
156
+ /** Element used for the pager wrapper; defaults to `nav`. */
157
+ as?: T;
158
+ } & (DefaultModeProps | CustomRenderModeProps) & Omit<ComponentPropsWithoutRef<T>, 'activeClassName' | 'children' | 'style'>;
159
+
160
+ declare type Props_5<T extends ElementType = 'div'> = {
161
+ /** Solid id used to read the matching classname from each layer configuration. */
162
+ name: string;
163
+ /** Element or component used to render the solid inside `ImmerserRoot`; defaults to `div`. */
164
+ as?: T;
165
+ /** Interactive content rendered inside every layer mask. Position it with your own CSS. */
166
+ children?: ReactNode;
167
+ } & Omit<ComponentPropsWithoutRef<T>, 'as' | 'children' | 'name' | 'style'> & DeniedStyleProp;
168
+
169
+ declare type Props_6 = {
170
+ /** Classname applied to generated copies of this link while any one of them is hovered. */
171
+ hoverClassName: string;
172
+ /** Stable hover group id for this source link. `ImmerserRoot` copies the link into every layer mask,
173
+ * so use the same `synchroId` for links that should share hover state,
174
+ * and different values for independent hover groups. */
175
+ synchroId: string;
176
+ } & Omit<ComponentPropsWithoutRef<'a'>, 'style'> & DeniedStyleProp;
177
+
178
+ declare type RenderLinkProps = {
179
+ isActive: boolean;
180
+ layerId: string;
181
+ layerIndex: number;
182
+ };
183
+
184
+ export { }