@fiddle-digital/string-tune
Version:
StringTune is a cutting-edge JavaScript library designed to deliver high-performance, modular web effects. Whether you're looking to add smooth parallax scrolling, dynamic cursor interactions, progress tracking, or autoplay videos, StringTune empowers dev
1 lines • 576 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/core/controllers/CursorController.ts","../src/core/managers/EventManager.ts","../src/core/managers/ModuleManager.ts","../src/objects/StringMirrorObject.ts","../src/objects/StringObject.ts","../src/core/managers/ObjectManager.ts","../src/core/controllers/ScrollController.ts","../src/core/controllers/StringScrollDefault.ts","../src/core/controllers/StringScrollDisable.ts","../src/models/scroll/ScrollHTMLClass.ts","../src/core/controllers/StringScrollSmooth.ts","../src/core/managers/ScrollManager.ts","../src/states/CursorState.ts","../src/states/RenderState.ts","../src/states/ScrollState.ts","../src/states/SystemState.ts","../src/states/TimeState.ts","../src/states/ViewportState.ts","../src/core/StringData.ts","../src/models/IModuleLifecyclePermissions.ts","../src/core/StringModule.ts","../src/tools/BoundingClientRectTool.ts","../src/tools/DOMAttributeTool.ts","../src/tools/RecordAttributeTool.ts","../src/tools/TransformNullifyTool.ts","../src/tools/RelativePositionTool.ts","../src/tools/LerpTool.ts","../src/tools/UnitParserTool.ts","../src/tools/AdaptiveLerpTool.ts","../src/tools/OriginParserTool.ts","../src/tools/ColorParserTool.ts","../src/tools/EasingFunctionTool.ts","../src/tools/MagneticPullTool.ts","../src/tools/LerpColorTool.ts","../src/tools/LerpVector2Tool.ts","../src/tools/TransformScaleParserTool.ts","../src/tools/SplitOptionsParserTool.ts","../src/tools/RuleParserTool.ts","../src/tools/ValidationTool.ts","../src/core/StringToolsContainer.ts","../src/utils/isCoarsePointer.ts","../src/utils/style-txn.ts","../src/modules/cursor/StringCursor.ts","../src/modules/cursor/StringImpulse.ts","../src/modules/cursor/StringMagnetic.ts","../src/utils/frame-dom.ts","../src/modules/cursor/CursorReactiveModule.ts","../src/modules/cursor/StringSpotlight.ts","../src/modules/loading/StringLazy.ts","../src/modules/loading/StringLoading.ts","../src/modules/screen/StringInview.ts","../src/modules/screen/StringResponsive.ts","../src/modules/scroll/StringAnchor.ts","../src/modules/scroll/StringGlide.ts","../src/modules/scroll/StringLerp.ts","../src/modules/scroll/StringProgress.ts","../src/modules/scroll/StringParallax.ts","../src/modules/scrollbar/StringScrollbarHorizontal.ts","../src/modules/scrollbar/StringScrollbarVertical.ts","../src/modules/scrollbar/StringScrollbar.ts","../src/models/text/SplitElementClass.ts","../src/utils/text/BuildDOMTree.ts","../src/utils/text/BuildTokens.ts","../src/utils/text/CanvasKerningApplier.ts","../src/utils/text/LayoutMeasurer.ts","../src/utils/text/SplitMeasuredTokens.ts","../src/modules/text/StringSplit.ts","../src/modules/tracker/StringDelayLerpTracker.ts","../src/modules/tracker/StringFPSTracker.ts","../src/modules/tracker/StringLerpTracker.ts","../src/modules/tracker/StringPositionTracker.ts","../src/utils/Debounce.ts","../src/utils/StringFPS.ts","../src/modules/loading/StringVideoAutoplay.ts","../src/models/slider/SequenceState.ts","../src/modules/slider/StringSequence.ts","../src/modules/input/StringForm.ts","../src/core/managers/CenterCache.ts","../src/core/managers/HoverTracker.ts","../src/modules/scroll/StringScroller.ts","../src/utils/ParsePartOf.ts","../src/modules/scroll/StringProgressPart.ts"],"sourcesContent":["import { CursorController } from \"./core/controllers/CursorController\";\r\nimport { IStringModule } from \"./core/IStringModule\";\r\nimport { EventManager } from \"./core/managers/EventManager\";\r\nimport { ModuleManager } from \"./core/managers/ModuleManager\";\r\nimport { ObjectManager } from \"./core/managers/ObjectManager\";\r\nimport { ScrollManager } from \"./core/managers/ScrollManager\";\r\nimport { StringContext } from \"./core/StringContext\";\r\nimport { StringData } from \"./core/StringData\";\r\nimport { StringModule } from \"./core/StringModule\";\r\nimport { DefaultToolsContainer } from \"./core/StringToolsContainer\";\r\nimport { StringCursor } from \"./modules/cursor/StringCursor\";\r\nimport { StringImpulse } from \"./modules/cursor/StringImpulse\";\r\nimport { StringMagnetic } from \"./modules/cursor/StringMagnetic\";\r\nimport { StringSpotlight } from \"./modules/cursor/StringSpotlight\";\r\nimport { StringLazy } from \"./modules/loading/StringLazy\";\r\nimport { StringLoading } from \"./modules/loading/StringLoading\";\r\nimport { StringInview } from \"./modules/screen/StringInview\";\r\nimport { StringResponsive } from \"./modules/screen/StringResponsive\";\r\nimport { StringAnchor } from \"./modules/scroll/StringAnchor\";\r\nimport { StringGlide } from \"./modules/scroll/StringGlide\";\r\nimport { StringLerp } from \"./modules/scroll/StringLerp\";\r\nimport { StringParallax } from \"./modules/scroll/StringParallax\";\r\nimport { StringProgress } from \"./modules/scroll/StringProgress\";\r\nimport { StringScrollbar } from \"./modules/scrollbar/StringScrollbar\";\r\nimport { StringSplit } from \"./modules/text/StringSplit\";\r\nimport { StringDelayLerpTracker } from \"./modules/tracker/StringDelayLerpTracker\";\r\nimport { StringFPSTracker } from \"./modules/tracker/StringFPSTracker\";\r\nimport { StringLerpTracker } from \"./modules/tracker/StringLerpTracker\";\r\nimport { StringPositionTracker } from \"./modules/tracker/StringPositionTracker\";\r\nimport { StringObject } from \"./objects/StringObject\";\r\nimport { ScrollMode } from \"./states/ScrollState\";\r\nimport { Debounce } from \"./utils/Debounce\";\r\nimport { EventCallback } from \"./models/event/EventCallback\";\r\nimport { StringFPS } from \"./utils/StringFPS\";\r\nimport { StringSettings } from \"./utils/StringSettings\";\r\nimport { StringVideoAutoplay } from \"./modules/loading/StringVideoAutoplay\";\r\nimport { ScrollMarkRule } from \"./models/scroll/ScrollTriggerRule\";\r\nimport { StringSequence } from \"./modules/slider/StringSequence\";\r\nimport { StringForm } from \"./modules/input/StringForm\";\r\nimport { ISettingsChangeData } from \"./models/event/ISettingsChangeData\";\r\nimport { CenterCache } from \"./core/managers/CenterCache\";\r\nimport { HoverTracker } from \"./core/managers/HoverTracker\";\r\nimport { CursorReactiveModule } from \"./modules/cursor/CursorReactiveModule\";\r\nimport { StringScroller } from \"./modules/scroll/StringScroller\";\r\nimport { parsePartOf } from \"./utils/ParsePartOf\";\r\nimport { StringProgressPart } from \"./modules/scroll/StringProgressPart\";\r\nimport { frameDOM } from \"./utils/frame-dom\";\r\nimport { styleTxn } from \"./utils/style-txn\";\r\n\r\nfunction isTouchDevice() {\r\n return \"ontouchstart\" in window || navigator.maxTouchPoints > 0;\r\n}\r\nfunction isSafari(): boolean {\r\n let ua = navigator.userAgent.toLowerCase();\r\n if (ua.indexOf(\"safari\") != -1) {\r\n if (ua.indexOf(\"chrome\") > -1) {\r\n return false;\r\n } else {\r\n return true;\r\n }\r\n } else {\r\n return false;\r\n }\r\n}\r\n\r\nclass StringTune {\r\n /** Bound handler for the scroll start event */\r\n private onScrollStartBind: any;\r\n\r\n /** Bound handler for the scroll stop event */\r\n private onScrollStopBind: any;\r\n\r\n /** Bound handler for the scroll direction change event */\r\n private onDirectionChangeBind: any;\r\n\r\n /** Bound wheel event handler */\r\n private onWheelBind: any;\r\n\r\n /** Bound scroll event handler */\r\n private onScrollBind: any;\r\n\r\n /** Bound resize event handler */\r\n private onResizeBind: any;\r\n\r\n /** Bound mouse move handler */\r\n private onMouseMoveBind: any;\r\n\r\n private onContainerTransitionEndBind: any;\r\n private onResizeObserverBind: any;\r\n\r\n /** Singleton instance of StringTune */\r\n private static i: StringTune;\r\n\r\n /** Root scrollable element (typically <body>) */\r\n private root: any;\r\n\r\n /** Window object (used for event bindings and dimensions) */\r\n private window: any;\r\n\r\n /** Previous window width for resize diff check */\r\n private prevWidth: number = 0;\r\n\r\n /** Previous window height for resize diff check */\r\n private prevHeight: number = 0;\r\n\r\n /** Manages all modules registered in the system */\r\n private moduleManager: ModuleManager;\r\n\r\n /** Manages scroll modes and active scroll engine */\r\n private scrollManager: ScrollManager;\r\n\r\n /** Manages all interactive objects (elements with `string-*` attributes) */\r\n private objectManager: ObjectManager;\r\n\r\n /** Central event manager for internal pub-sub logic */\r\n private eventManager: EventManager;\r\n\r\n /** Handles custom cursor logic (if enabled) */\r\n private cursorController: CursorController;\r\n\r\n /** Provides default utility tools (parsers, interpolation, etc.) */\r\n private tools: DefaultToolsContainer;\r\n\r\n /** Main loop used for frame updates (with fixed FPS) */\r\n private loop: StringFPS = new StringFPS();\r\n\r\n /** Global reactive data store (scroll, viewport, etc.) */\r\n private data: StringData;\r\n\r\n /** Context shared across all modules (events, data, tools, settings) */\r\n private context: StringContext;\r\n\r\n /** Caches the center positions of string objects. */\r\n private centers: CenterCache;\r\n\r\n /** Tracks hover states of string objects. */\r\n private hoverManager: HoverTracker;\r\n\r\n private observerContainerResize: ResizeObserver | null = null;\r\n\r\n public canRebuild: boolean = true;\r\n\r\n /**\r\n * Sets the scroll position manually.\r\n * This overrides all internal scroll states including target and lerped values.\r\n * Useful for programmatic jumps or syncing scroll externally.\r\n *\r\n * @param value The new scroll position in pixels.\r\n */\r\n public set scrollPosition(value: number) {\r\n this.data.scroll.current = value;\r\n this.data.scroll.target = value;\r\n this.data.scroll.transformedCurrent =\r\n this.data.scroll.current * this.data.viewport.transformScale;\r\n this.data.scroll.delta = 0;\r\n this.data.scroll.lerped = 0;\r\n this.scrollManager.updatePosition();\r\n this.moduleManager.onScroll();\r\n this.objectManager.checkInview();\r\n }\r\n\r\n /**\r\n * Configures the container element(s) used for scroll tracking.\r\n * Accepts either the `Window` object or an `HTMLElement`.\r\n * Determines the appropriate internal element references based on the input type\r\n * and triggers a resize calculation.\r\n *\r\n * @param {Window | HTMLElement | any} container The target window or HTML element to associate with scrolling.\r\n * Handles `Window`, `HTMLElement`, and potentially other types via fallback.\r\n */\r\n public set scrollContainer(container: any) {\r\n this.observerContainerResize?.unobserve(this.context.data.scroll.container);\r\n\r\n this.data.scroll.elementContainer.removeEventListener(\r\n \"transitionend\",\r\n this.onContainerTransitionEndBind\r\n );\r\n if (container instanceof Window) {\r\n this.data.scroll.container = document.body;\r\n this.data.scroll.elementContainer = document.documentElement;\r\n this.data.scroll.scrollContainer = container;\r\n } else if (container instanceof HTMLElement) {\r\n this.data.scroll.container = container;\r\n this.data.scroll.elementContainer = container;\r\n this.data.scroll.scrollContainer = container;\r\n } else {\r\n // Fallback case\r\n this.data.scroll.container = document.body;\r\n this.data.scroll.elementContainer = document.documentElement;\r\n this.data.scroll.scrollContainer = container;\r\n }\r\n this.data.scroll.elementContainer.addEventListener(\r\n \"transitionend\",\r\n this.onContainerTransitionEndBind\r\n );\r\n this.observerContainerResize?.observe(this.context.data.scroll.container);\r\n\r\n this.debouncedResize();\r\n }\r\n\r\n /**\r\n * Gets the current scroll position in pixels.\r\n * This is typically updated every frame.\r\n */\r\n public get scrollPosition() {\r\n return this.data.scroll.current;\r\n }\r\n\r\n public get scrollHeight() {\r\n return this.data.viewport.contentHeight;\r\n }\r\n\r\n public get containerHeight() {\r\n return this.data.viewport.windowHeight;\r\n }\r\n\r\n /**\r\n * Sets the base scroll speed for smooth scrolling.\r\n * Typically a value between 0 and 1.\r\n */\r\n public set speed(value: number) {\r\n this.data.scroll.speed = value;\r\n }\r\n\r\n /**\r\n * Sets the scroll acceleration using a normalized value from 0 to 1.\r\n * Internally maps it to a real acceleration value between 0.1 and 0.5.\r\n *\r\n * @param speed A normalized acceleration factor (0 to 1).\r\n */\r\n public set speedAccelerate(speed: number) {\r\n const min = 0.1;\r\n const max = 0.5;\r\n this.data.scroll.speedAccelerate = min + (max - min) * speed;\r\n }\r\n\r\n /**\r\n * Sets the scroll mode for desktop devices.\r\n * Can be 'smooth', 'default', or 'disable'.\r\n */\r\n public set scrollDesktopMode(mode: ScrollMode) {\r\n this.scrollManager.setDesktopMode(mode);\r\n }\r\n\r\n /**\r\n * Sets the scroll mode for mobile devices.\r\n * Can be 'smooth', 'default', or 'disable'.\r\n */\r\n public set scrollMobileMode(mode: ScrollMode) {\r\n this.scrollManager.setMobileMode(mode);\r\n }\r\n\r\n public set FPSTrackerVisible(visible: boolean) {\r\n this.data.system.fpsTracker = visible;\r\n this.eventManager.emit(\"tracker:fps:visible\", visible);\r\n }\r\n\r\n public set PositionTrackerVisible(visible: boolean) {\r\n this.data.system.positionTracker = visible;\r\n this.eventManager.emit(\"tracker:position:visible\", visible);\r\n }\r\n\r\n private debouncedResize = Debounce(this.onResize, 30);\r\n\r\n private constructor() {\r\n this.root = document.body;\r\n this.window = window;\r\n\r\n this.tools = new DefaultToolsContainer();\r\n this.data = new StringData();\r\n this.eventManager = new EventManager();\r\n this.moduleManager = new ModuleManager(this.data);\r\n this.objectManager = new ObjectManager(\r\n this.data,\r\n this.moduleManager,\r\n this.eventManager,\r\n this.tools\r\n );\r\n\r\n this.centers = new CenterCache();\r\n this.hoverManager = new HoverTracker();\r\n\r\n this.context = {\r\n events: this.eventManager,\r\n data: this.data,\r\n tools: this.tools,\r\n settings: {},\r\n centers: this.centers,\r\n hover: this.hoverManager,\r\n };\r\n\r\n this.cursorController = new CursorController(1, this.context);\r\n this.scrollManager = new ScrollManager(this.context);\r\n\r\n this.setupSettings({\r\n \"global-class\": false,\r\n \"offset-top\": \"0%\",\r\n \"offset-bottom\": \"0%\",\r\n key: \"--progress\",\r\n \"inview-top\": \"0%\",\r\n \"inview-bottom\": \"0%\",\r\n \"enter-el\": \"top\",\r\n \"enter-vp\": \"bottom\",\r\n \"exit-el\": \"bottom\",\r\n \"exit-vp\": \"top\",\r\n \"parallax-bias\": \"0.0\",\r\n parallax: \"0.2\",\r\n lerp: \"0.2\",\r\n \"cursor-lerp\": \"0.75\",\r\n radius: \"150\",\r\n strength: \"0.3\",\r\n glide: \"1\",\r\n anchor: \"center center\",\r\n timeout: 900,\r\n alignment: \"center\",\r\n \"target-disable\": \"false\",\r\n \"target-style-disable\": \"false\",\r\n \"target-class\": \"\",\r\n active: \"false\",\r\n fixed: \"false\",\r\n repeat: \"false\",\r\n \"self-disable\": \"false\",\r\n abs: \"false\",\r\n easing: \"cubic-bezier(0.25, 0.25, 0.25, 0.25)\",\r\n \"glide-base-velocity\": 0.00125,\r\n \"glide-reduce-velocity\": 0.0000625,\r\n \"glide-negative-velocity\": -0.0001,\r\n\r\n \"position-strength\": 3,\r\n \"position-tension\": 0.05,\r\n \"position-friction\": 0.15,\r\n \"position-max-velocity\": 10,\r\n \"position-update-threshold\": 0.1,\r\n \"rotation-strength\": 0.75,\r\n \"rotation-tension\": 0.06,\r\n \"rotation-friction\": 0.18,\r\n \"rotation-max-angular-velocity\": 6,\r\n \"rotation-max-angle\": 18,\r\n \"rotation-update-threshold\": 0.15,\r\n \"max-offset\": 220,\r\n \"sleep-epsilon\": 0.01,\r\n \"continuous-push\": true,\r\n });\r\n\r\n this.onContainerTransitionEndBind = this.onContainerTransitionEnd.bind(this);\r\n\r\n this.onResizeObserverBind = this.onResizeObserverEvent.bind(this);\r\n this.observerContainerResize = new ResizeObserver(this.onResizeObserverBind);\r\n this.observerContainerResize.observe(this.context.data.scroll.container);\r\n\r\n this.onWheelBind = this.onWheelEvent.bind(this);\r\n this.onScrollBind = this.onScrollEvent.bind(this);\r\n this.onResizeBind = this.onResize.bind(this);\r\n this.onMouseMoveBind = this.onMouseMoveEvent.bind(this);\r\n\r\n this.onScrollStartBind = this.onScrollStart.bind(this);\r\n this.onScrollStopBind = this.onScrollStop.bind(this);\r\n this.onDirectionChangeBind = this.onDirectionChange.bind(this);\r\n\r\n this.eventManager.on(`wheel`, this.onWheelBind);\r\n\r\n this.scrollManager.bindEvents({\r\n onScrollStart: this.onScrollStartBind,\r\n onScrollStop: this.onScrollStopBind,\r\n onDirectionChange: this.onDirectionChangeBind,\r\n });\r\n\r\n this.loop.setOnFrame((time: number) => {\r\n this.data.time.delta = time - this.data.time.now;\r\n this.data.time.previous = this.data.time.now;\r\n this.data.time.now = time;\r\n this.data.time.elapsed += this.data.time.delta;\r\n this.onUpdateEvent();\r\n });\r\n this.on(\"image:load:all\", () => {\r\n this.onResize();\r\n });\r\n\r\n this.scrollContainer = window;\r\n }\r\n\r\n /**\r\n * Returns the singleton instance of StringTune.\r\n * If not already created, initializes it.\r\n */\r\n public static getInstance(): StringTune {\r\n if (!StringTune.i) {\r\n StringTune.i = new StringTune();\r\n }\r\n return StringTune.i;\r\n }\r\n\r\n /**\r\n * Finds and returns an existing module by its class.\r\n * Useful for reusing a module instance without re-registering.\r\n *\r\n * @template T The type of the module to retrieve.\r\n * @param type The module class constructor.\r\n * @returns The module instance if found, otherwise undefined.\r\n */\r\n public reuse<T>(type: new (...args: any[]) => T): T | undefined {\r\n return this.moduleManager.find(type);\r\n }\r\n\r\n /**\r\n * Instantiates and registers a new module.\r\n * Accepts optional per-instance settings that override global settings.\r\n *\r\n * @param objectClass The module class to instantiate.\r\n * @param settings Optional settings specific to this module.\r\n */\r\n public use(objectClass: typeof StringModule, settings: any = null) {\r\n const effectiveSettings = {\r\n ...this.context.settings,\r\n ...settings,\r\n };\r\n const module = new objectClass({\r\n events: this.eventManager,\r\n data: this.data,\r\n tools: this.tools,\r\n settings: effectiveSettings,\r\n centers: this.centers,\r\n hover: this.hoverManager,\r\n });\r\n this.moduleManager.register(module);\r\n }\r\n\r\n /**\r\n * Subscribes to a global event within the system.\r\n *\r\n * @param eventName The name of the event to listen for.\r\n * @param callback The function to call when the event is triggered.\r\n * @param id Optional subscription ID (for easier management).\r\n */\r\n public on(eventName: string, callback: EventCallback<any>, id: string = \"\") {\r\n this.eventManager.on(eventName, callback, id);\r\n }\r\n public emit(eventName: string, data: any) {\r\n this.eventManager.emit(eventName, data);\r\n }\r\n\r\n /**\r\n * Unsubscribes from a global event.\r\n *\r\n * @param eventName The name of the event.\r\n * @param callback The previously registered callback.\r\n * @param id Optional ID used during subscription.\r\n */\r\n public off(eventName: string, callback: EventCallback<any>, id: string = \"\") {\r\n this.eventManager.off(eventName, callback, id);\r\n }\r\n\r\n /**\r\n * Adds a scroll trigger rule that activates when the user scrolls past a defined offset\r\n * in a specific direction. This can be used to toggle CSS classes or execute callbacks\r\n * when elements come into view or go out of view.\r\n *\r\n * @param rule - The scroll trigger configuration object.\r\n * - `id`: A unique identifier for this rule.\r\n * - `offset`: The vertical scroll offset (in pixels) where the rule should activate.\r\n * - `direction`: Defines the scroll direction required to activate the rule.\r\n * Can be `\"forward\"`, `\"backward\"`, or `\"any\"`.\r\n * - `onEnter`: (Optional) A function that will be called when the scroll position enters the trigger zone\r\n * in the specified direction.\r\n * - `onLeave`: (Optional) A function that will be called when the scroll position leaves the trigger zone\r\n * or scrolls in the opposite direction.\r\n * - `toggleClass`: (Optional) An object defining a class toggle behavior.\r\n * It contains a target element and a class name to be added when the trigger is active\r\n * and removed when it's not.\r\n */\r\n public addScrollMark(rule: ScrollMarkRule) {\r\n this.scrollManager.addScrollMark(rule);\r\n }\r\n\r\n /**\r\n * Removes a scroll trigger by its unique identifier.\r\n *\r\n * @param id - The unique identifier of the scroll trigger to be removed.\r\n */\r\n public removeScrollMark(id: string) {\r\n this.scrollManager.removeScrollMark(id);\r\n }\r\n\r\n /**\r\n * Starts the scroll engine and initializes all listeners, observers, and modules.\r\n *\r\n * @param fps Desired frames per second for the update loop.\r\n */\r\n public start(fps: number) {\r\n this.data.scroll.scrollContainer?.addEventListener(\"scroll\", this.onScrollBind);\r\n this.data.scroll.container?.addEventListener(\"wheel\", this.onWheelBind, {\r\n passive: false,\r\n });\r\n\r\n window.addEventListener(\"resize\", this.onResizeBind);\r\n this.root.addEventListener(\"mousemove\", this.onMouseMoveBind);\r\n\r\n const observerContainerMutation = new MutationObserver(\r\n (mutationsList: MutationRecord[], observer: MutationObserver) => {\r\n for (const mutation of mutationsList) {\r\n if (\r\n mutation.type === \"attributes\" &&\r\n (mutation.attributeName === \"style\" || mutation.attributeName === \"class\")\r\n ) {\r\n this.onResize();\r\n }\r\n }\r\n }\r\n );\r\n const config: MutationObserverInit = {\r\n attributes: true,\r\n attributeFilter: [\"style\", \"class\"],\r\n };\r\n observerContainerMutation.observe(this.context.data.scroll.container, config);\r\n\r\n this.use(StringInview);\r\n\r\n const htmlFontSize = window.getComputedStyle(document.documentElement).fontSize;\r\n const fontSizeNumber = parseFloat(htmlFontSize);\r\n this.context.data.viewport.baseRem = fontSizeNumber;\r\n\r\n document.documentElement.classList.add(\"-string\");\r\n this.moduleManager.onInit();\r\n this.onResize();\r\n this.initObjects();\r\n this.objectManager.observeDOM();\r\n\r\n this.loop.start(fps);\r\n this.eventManager.emit(`start`, null);\r\n }\r\n\r\n /**\r\n * Initializes all DOM elements with `string` or `string-copy-from` attributes.\r\n * Registers them with the object manager and triggers resize/scroll/frame hooks.\r\n */\r\n private initObjects() {\r\n document.querySelectorAll(\"[string],[data-string]\").forEach((element) => {\r\n this.objectManager.add(element as HTMLElement);\r\n });\r\n document.querySelectorAll(\"[string-copy-from],[data-string-copy-from]\").forEach((element) => {\r\n let connectTargetId = this.tools.domAttribute.process({\r\n element: element as HTMLElement,\r\n key: \"copy-from\",\r\n fallback: \"\",\r\n });\r\n\r\n if (connectTargetId && connectTargetId.length > 0) {\r\n this.objectManager.linkMirror(connectTargetId, element as HTMLElement);\r\n }\r\n });\r\n // document.querySelectorAll(\"[string-part-of],[data-string-part-of]\").forEach((element) => {\r\n // let partOfTargetId = this.tools.domAttribute.process({\r\n // element: element as HTMLElement,\r\n // key: \"part-of\",\r\n // fallback: \"\",\r\n // });\r\n // let partOfData = parsePartOf(partOfTargetId as string);\r\n // if (partOfData) {\r\n // if (partOfData.id && partOfData.id.length > 0) {\r\n // if (this.objectManager.all.has(partOfData.id)) {\r\n // let partOfObject = new StringObject(\"\", element as HTMLElement);\r\n // partOfObject.setProperty(\"part-of-id\", partOfData.id);\r\n // partOfObject.setProperty(\"start\", partOfData.start);\r\n // partOfObject.setProperty(\"end\", partOfData.end);\r\n\r\n // this.objectManager.all.get(partOfData.id)!.partOf.push(partOfObject);\r\n // } else {\r\n // this.objectManager.enqueuePartOf(partOfData.id, element as HTMLElement);\r\n // }\r\n // }\r\n // }\r\n // });\r\n this.moduleManager.onResize();\r\n this.moduleManager.onScroll();\r\n this.moduleManager.onFrame();\r\n }\r\n\r\n /**\r\n * Sets global fallback settings for all modules.\r\n * These can be overridden by module-specific settings during `use(...)`.\r\n *\r\n * @param settings A key-value map of default settings (e.g. 'offset-top': '-10%').\r\n */\r\n public setupSettings(settings: StringSettings): void {\r\n this.context.settings = {\r\n ...this.context.settings,\r\n ...settings,\r\n };\r\n this.onSettingsChange({\r\n isDesktop: this.data.viewport.windowWidth > 1024,\r\n widthChanged: true,\r\n heightChanged: true,\r\n scrollHeightChanged: true,\r\n isForceRebuild: false,\r\n });\r\n }\r\n\r\n private onResizeObserverEvent() {\r\n this.debouncedResize();\r\n }\r\n\r\n private onContainerTransitionEnd(event: TransitionEvent) {\r\n if (event.target === this.context.data.scroll.container) {\r\n this.onResize();\r\n this.moduleManager.onResize();\r\n this.moduleManager.onScroll();\r\n this.moduleManager.onFrame();\r\n }\r\n }\r\n\r\n /**\r\n * Handles mouse move event and dispatches it to cursor and modules.\r\n * @param e Native mouse move event.\r\n */\r\n private onMouseMoveEvent(e: MouseEvent) {\r\n this.cursorController.onMouseMove(e);\r\n this.moduleManager.onMouseMove(e);\r\n frameDOM.measure(() => {\r\n this.moduleManager.onMouseMoveMeasure();\r\n });\r\n }\r\n\r\n /**\r\n * Handles wheel scroll event and passes it to the scroll engine and modules.\r\n * @param e Native wheel event.\r\n */\r\n private onWheelEvent(e: WheelEvent) {\r\n const target = e.target as HTMLElement;\r\n const isModal = target.closest(\"[string-isolation],[data-string-isolation]\");\r\n if (isModal != null) return;\r\n\r\n this.scrollManager.get().onWheel(e);\r\n this.moduleManager.onWheel(e);\r\n }\r\n\r\n /**\r\n * Called when scrolling begins.\r\n * Triggers module scroll start lifecycle hook.\r\n */\r\n private onScrollStart() {\r\n this.moduleManager.onScrollStart();\r\n this.eventManager.emit(`scroll:start`, null);\r\n }\r\n\r\n /**\r\n * Called when scrolling ends.\r\n * Triggers module scroll stop lifecycle hook.\r\n */\r\n private onScrollStop() {\r\n this.moduleManager.onScrollStop();\r\n this.eventManager.emit(`scroll:stop`, null);\r\n }\r\n\r\n /**\r\n * Called when scrolling ends.\r\n * Triggers module scroll stop lifecycle hook.\r\n */\r\n private onDirectionChange() {\r\n this.moduleManager.onDirectionChange();\r\n }\r\n\r\n /**\r\n * Called when global or module settings are updated.\r\n * Notifies all managers and modules to re-read new settings.\r\n */\r\n private onSettingsChange(data: ISettingsChangeData) {\r\n this.cursorController.onSettingsChange(data);\r\n this.objectManager.onSettingsChange(data);\r\n this.moduleManager.onSettingsChange(data);\r\n }\r\n\r\n /**\r\n * Handles native scroll event.\r\n * Prevents default behavior and triggers internal scroll logic and event emissions.\r\n *\r\n * @param e The native scroll event.\r\n */\r\n private onScrollEvent(e: Event) {\r\n e.preventDefault();\r\n\r\n this.context.centers.invalidateAll();\r\n\r\n this.scrollManager.get().onScroll(e);\r\n this.moduleManager.onScroll();\r\n this.objectManager.checkInview();\r\n this.eventManager.emit(`lerp`, this.data.scroll.lerped);\r\n this.eventManager.emit(`scroll`, this.data.scroll.current);\r\n\r\n frameDOM.measure(() => {\r\n this.moduleManager.onScrollMeasure();\r\n });\r\n return false;\r\n }\r\n\r\n /**\r\n * Called every frame by the update loop.\r\n * Triggers scroll engine, modules, and global `update` event.\r\n */\r\n private onUpdateEvent() {\r\n this.cursorController.onFrame();\r\n this.scrollManager.get().onFrame();\r\n this.moduleManager.onFrame();\r\n\r\n frameDOM.mutate(() => {\r\n styleTxn.begin();\r\n this.moduleManager.onMutate();\r\n styleTxn.commit();\r\n });\r\n\r\n this.eventManager.emit(`update`, null);\r\n }\r\n\r\n /**\r\n * Handles resize events from scroll container or window.\r\n * Ignores height-only changes on mobile to prevent layout jumps.\r\n * Rebuilds layout and triggers module resize if size really changed.\r\n */\r\n public onResize(force: boolean = false): void {\r\n if (this.canRebuild == false) {\r\n return;\r\n }\r\n\r\n const container = this.data.scroll.container;\r\n const scroll = this.context.data.scroll;\r\n let width = 0;\r\n let height = 0;\r\n var newScrollHeight;\r\n var newContainerTopPosition = 0;\r\n const rect = container.getBoundingClientRect();\r\n\r\n if (container.tagName == \"BODY\") {\r\n width = window.innerWidth;\r\n height = window.innerHeight;\r\n } else {\r\n width = rect.width;\r\n height = rect.height;\r\n }\r\n\r\n newContainerTopPosition = rect.top;\r\n newScrollHeight = scroll.container.scrollHeight;\r\n const transformScale = this.tools.transformScaleParser.process({\r\n value: window.getComputedStyle(container).transform,\r\n });\r\n this.context.data.viewport.transformScale =\r\n window.getComputedStyle(container).scale == \"none\"\r\n ? transformScale\r\n : Number(window.getComputedStyle(container).scale);\r\n this.context.data.scroll.transformedCurrent =\r\n this.context.data.scroll.current * this.context.data.viewport.transformScale;\r\n const isDesktop = width > 1024;\r\n\r\n const widthChanged = this.prevWidth !== width;\r\n const heightChanged = this.prevHeight !== height;\r\n const scrollHeightChanged = this.context.data.viewport.contentHeight !== newScrollHeight;\r\n\r\n const shouldRebuild = widthChanged || (isDesktop && heightChanged) || scrollHeightChanged;\r\n\r\n this.context.data.scroll.topPosition = Math.floor(newContainerTopPosition);\r\n this.context.data.viewport.contentWidth = width;\r\n this.context.data.viewport.contentHeight = newScrollHeight;\r\n\r\n this.prevWidth = width;\r\n this.prevHeight = height;\r\n\r\n this.context.data.viewport.windowWidth = width;\r\n this.context.data.viewport.windowHeight = height;\r\n\r\n const htmlFontSize = window.getComputedStyle(document.documentElement).fontSize;\r\n const fontSizeNumber = parseFloat(htmlFontSize);\r\n this.context.data.viewport.baseRem = fontSizeNumber * transformScale;\r\n\r\n scroll.bottomPosition = this.context.data.viewport.contentHeight - height;\r\n\r\n if (widthChanged || (typeof force === \"boolean\" && force)) {\r\n this.moduleManager.onResizeWidth();\r\n }\r\n if (shouldRebuild || (typeof force === \"boolean\" && force)) {\r\n if (this.context.data.scroll.container.scrollTop > 0) {\r\n this.context.data.scroll.current = this.context.data.scroll.container.scrollTop;\r\n this.context.data.scroll.target = this.context.data.scroll.container.scrollTop;\r\n }\r\n\r\n this.scrollManager.updateResponsiveMode();\r\n this.moduleManager.onResize();\r\n this.onSettingsChange({\r\n isDesktop,\r\n widthChanged,\r\n heightChanged,\r\n scrollHeightChanged,\r\n isForceRebuild: force === true,\r\n });\r\n this.moduleManager.onScroll();\r\n this.moduleManager.onScrollMeasure();\r\n this.moduleManager.onFrame();\r\n this.objectManager.checkInview();\r\n }\r\n }\r\n\r\n /**\r\n * Scrolls the container to the specified element identified by a CSS selector, applying an optional offset.\r\n *\r\n * Calculates the vertical position of the target element relative to the scroll container and updates the scroll delta accordingly.\r\n * If the element is not found, a warning is logged to the console.\r\n *\r\n * @param selector - The CSS selector string used to identify the target element.\r\n * @param offset - Optional. The number of pixels to offset from the target element's top position. Defaults to 0.\r\n */\r\n public scrollToElement(selector: string, offset: number = 0) {\r\n const element = document.querySelector(selector);\r\n if (element) {\r\n const rect = element.getBoundingClientRect();\r\n const scrollTop =\r\n rect.top + window.pageYOffset - this.data.scroll.container.clientTop - offset;\r\n this.context.data.scroll.delta = scrollTop - this.data.scroll.current;\r\n } else {\r\n console.warn(`Element not found: ${selector}`);\r\n }\r\n }\r\n\r\n public scrollTo(position: number) {\r\n this.scrollManager.get().scrollTo(position);\r\n }\r\n\r\n public invalidateCenter(id: string): void {\r\n this.centers.invalidate(id);\r\n }\r\n\r\n /**\r\n * Forces center cache recalculation for all tracked objects.\r\n * Useful when DOM geometry changes outside of StringTune's control.\r\n */\r\n public invalidateCenters(): void {\r\n this.centers.invalidateAll();\r\n }\r\n\r\n /**\r\n * Cleans up the system, removes all event listeners, stops the loop,\r\n * and destroys modules and event subscriptions.\r\n */\r\n public destroy() {\r\n this.data.scroll.scrollContainer?.removeEventListener(\"scroll\", this.onScrollBind);\r\n this.data.scroll.container?.removeEventListener(\"wheel\", this.onWheelBind);\r\n this.data.scroll.elementContainer.removeEventListener(\r\n \"transitionend\",\r\n this.onContainerTransitionEndBind\r\n );\r\n this.window.removeEventListener(\"resize\", this.onResizeBind);\r\n this.root.removeEventListener(\"mousemove\", this.onMouseMoveBind);\r\n this.loop.stop();\r\n this.moduleManager.destroy();\r\n this.eventManager.clearAll();\r\n this.eventManager.off(`wheel`, this.onWheelBind);\r\n }\r\n}\r\n\r\nexport {\r\n StringTune as default,\r\n StringCursor,\r\n StringDelayLerpTracker,\r\n StringFPSTracker,\r\n StringGlide,\r\n StringLazy,\r\n StringLerp,\r\n StringLerpTracker,\r\n StringLoading,\r\n StringForm,\r\n StringImpulse,\r\n StringMagnetic,\r\n StringParallax,\r\n StringPositionTracker,\r\n StringProgress,\r\n StringResponsive,\r\n StringSpotlight,\r\n StringScrollbar,\r\n StringSplit,\r\n StringAnchor,\r\n StringTune as StringTune,\r\n StringVideoAutoplay,\r\n StringModule,\r\n StringObject,\r\n StringData,\r\n StringSequence,\r\n StringScroller,\r\n StringProgressPart,\r\n CursorReactiveModule,\r\n frameDOM,\r\n styleTxn,\r\n type ScrollMarkRule as ScrollTriggerRule,\r\n type StringContext,\r\n};\r\n","import { ISettingsChangeData } from \"../../models/event/ISettingsChangeData\";\r\nimport { CursorState } from \"../../states/CursorState\";\r\nimport { EventManager } from \"../managers/EventManager\";\r\nimport { StringContext } from \"../StringContext\";\r\nimport { StringData } from \"../StringData\";\r\nimport { StringToolsContainer } from \"../StringToolsContainer\";\r\n\r\n/**\r\n * Manages virtual cursor logic: smoothing, updating, and syncing with mouse events.\r\n *\r\n * This controller handles cursor position tracking with smoothing (lerp) logic.\r\n * Useful for animated cursor effects and interaction modules.\r\n */\r\nexport class CursorController {\r\n /** Context providing access to shared data, tools, and settings. */\r\n protected context: StringContext;\r\n\r\n /** Threshold below which cursor is considered settled (no movement). */\r\n private readonly SETTLE_THRESHOLD = 0.1;\r\n\r\n /** Smoothing factor used to interpolate cursor movement. */\r\n private smoothingFactor: number;\r\n\r\n private lastMouseX: number = 0;\r\n private lastMouseY: number = 0;\r\n private lastMouseTime: number = 0;\r\n\r\n /**\r\n * Constructs a new `CursorController` instance.\r\n * @param smoothing The initial lerp smoothing factor (0 to 1).\r\n * @param context The shared context containing state and tools.\r\n */\r\n constructor(smoothing: number = 0.1, context: StringContext) {\r\n this.smoothingFactor = smoothing;\r\n this.context = context;\r\n\r\n this.onSettingsChange({\r\n isDesktop: context.data.viewport.windowWidth > 1024,\r\n isForceRebuild: false,\r\n widthChanged: true,\r\n heightChanged: true,\r\n scrollHeightChanged: true,\r\n });\r\n }\r\n\r\n /**\r\n * Updates the target cursor position from a mouse event.\r\n * This is the raw position that smoothing will interpolate toward.\r\n * @param e MouseEvent with current cursor position.\r\n */\r\n public onMouseMove(e: MouseEvent): void {\r\n this.context.data.cursor.targetX = e.clientX;\r\n this.context.data.cursor.targetY = e.clientY;\r\n\r\n const now = performance.now();\r\n const dt = now - this.lastMouseTime;\r\n\r\n if (dt > 0) {\r\n this.context.data.cursor.velocityX = (e.clientX - this.lastMouseX) / dt;\r\n this.context.data.cursor.velocityY = (e.clientY - this.lastMouseY) / dt;\r\n }\r\n\r\n this.lastMouseX = e.clientX;\r\n this.lastMouseY = e.clientY;\r\n this.lastMouseTime = now;\r\n }\r\n\r\n /**\r\n * Updates smoothed cursor position using linear interpolation (lerp).\r\n * Should be called on every animation frame.\r\n * Handles snapping when movement is below threshold.\r\n */\r\n public onFrame(): void {\r\n const { targetX, targetY, smoothedX, smoothedY } = this.context.data.cursor;\r\n\r\n const stepX = this.context.tools.lerp.process({\r\n from: smoothedX,\r\n to: targetX,\r\n progress: this.smoothingFactor,\r\n });\r\n const stepY = this.context.tools.lerp.process({\r\n from: smoothedY,\r\n to: targetY,\r\n progress: this.smoothingFactor,\r\n });\r\n\r\n const distance = this.getStepDistance(stepX, stepY);\r\n\r\n if (this.isSettled(distance)) {\r\n this.snapToTarget();\r\n } else {\r\n this.applyStep(stepX, stepY);\r\n }\r\n }\r\n\r\n /**\r\n * Called when global settings change.\r\n * Updates the internal lerp factor from context settings.\r\n */\r\n public onSettingsChange(data: ISettingsChangeData): void {\r\n let lerp = Number(this.context.settings[\"cursor-lerp\"]);\r\n this.setLerpFactor(lerp);\r\n }\r\n\r\n /**\r\n * Dynamically adjusts the smoothing factor using adaptive mapping.\r\n * @param t The raw input lerp value (usually from 0 to 1).\r\n */\r\n public setLerpFactor(t: number): void {\r\n this.smoothingFactor = this.context.tools.adaptiveLerp.process({\r\n value: t,\r\n inMin: 0.1,\r\n inMax: 1.0,\r\n outMin: 0.05,\r\n outMax: 0.65,\r\n });\r\n }\r\n\r\n /**\r\n * Calculates the Euclidean distance from the cursor step.\r\n * @param x Step in X direction.\r\n * @param y Step in Y direction.\r\n * @returns The length of the movement vector.\r\n */\r\n private getStepDistance(x: number, y: number): number {\r\n return Math.hypot(x, y);\r\n }\r\n\r\n /**\r\n * Determines whether the movement is below the settle threshold.\r\n * @param distance Distance between smoothed and target positions.\r\n * @returns Whether the cursor should snap to target.\r\n */\r\n private isSettled(distance: number): boolean {\r\n return distance < this.SETTLE_THRESHOLD;\r\n }\r\n\r\n /**\r\n * Immediately sets smoothed position to the target and zeroes deltas.\r\n */\r\n private snapToTarget(): void {\r\n this.context.data.cursor.smoothedX = this.context.data.cursor.targetX;\r\n this.context.data.cursor.smoothedY = this.context.data.cursor.targetY;\r\n this.context.data.cursor.stepX = 0;\r\n this.context.data.cursor.stepY = 0;\r\n }\r\n\r\n /**\r\n * Applies lerped movement step to smoothed position and stores delta.\r\n * @param x Step in X direction.\r\n * @param y Step in Y direction.\r\n */\r\n private applyStep(x: number, y: number): void {\r\n this.context.data.cursor.smoothedX += x;\r\n this.context.data.cursor.smoothedY += y;\r\n this.context.data.cursor.stepX = x;\r\n this.context.data.cursor.stepY = y;\r\n }\r\n}\r\n","import { EventCallback } from \"../../models/event/EventCallback\";\r\n\r\n/**\r\n * Manages custom event subscriptions and dispatching.\r\n * Allows multiple listeners per event and supports optional `id` suffixing.\r\n */\r\nexport class EventManager {\r\n private listeners: Record<string, Set<EventCallback<any>>> = {};\r\n private stateEvents: Set<string> = new Set();\r\n private lastPayloads: Record<string, any> = {};\r\n\r\n constructor() {\r\n this.stateEvents.add(\"screen:mobile\");\r\n this.stateEvents.add(\"screen:tablet\");\r\n this.stateEvents.add(\"screen:laptop\");\r\n this.stateEvents.add(\"screen:desktop\");\r\n this.stateEvents.add(\"start\");\r\n }\r\n\r\n /**\r\n * Subscribes to an event.\r\n * Optionally appends an `id` to the event name for namespacing.\r\n *\r\n * @param eventName The base event name (e.g. \"scroll\", \"update\").\r\n * @param callback The function to call when the event is emitted.\r\n * @param id Optional unique identifier to scope the event (e.g. element ID).\r\n */\r\n on<T = any>(\r\n eventName: string,\r\n callback: EventCallback<T>,\r\n id?: string | null\r\n ): void {\r\n const fullEvent = id ? `${eventName}:${id}` : eventName;\r\n\r\n if (!this.listeners[fullEvent]) {\r\n this.listeners[fullEvent] = new Set();\r\n }\r\n this.listeners[fullEvent].add(callback);\r\n if (\r\n this.stateEvents.has(fullEvent) &&\r\n this.lastPayloads[fullEvent] !== undefined\r\n ) {\r\n callback(this.lastPayloads[fullEvent]);\r\n }\r\n }\r\n\r\n /**\r\n * Unsubscribes from a specific event listener.\r\n * Must match the original `eventName`, `callback`, and optional `id`.\r\n *\r\n * @param eventName The base event name to unsubscribe from.\r\n * @param callback The callback function to remove.\r\n * @param id Optional identifier used when subscribing.\r\n */\r\n off<T = any>(\r\n eventName: string,\r\n callback: EventCallback<T>,\r\n id?: string\r\n ): void {\r\n const fullEvent = id ? `${eventName}:${id}` : eventName;\r\n\r\n if (this.listeners[fullEvent]) {\r\n this.listeners[fullEvent].delete(callback);\r\n }\r\n }\r\n\r\n /**\r\n * Emits an event with an optional payload.\r\n * All matching listeners will be called.\r\n *\r\n * @param eventName The full event name (must include `id` if used).\r\n * @param payload Optional data passed to event listeners.\r\n */\r\n emit<T = any>(eventName: string, payload?: T): void {\r\n if (this.stateEvents.has(eventName)) {\r\n this.lastPayloads[eventName] = payload;\r\n }\r\n const set = this.listeners[eventName];\r\n if (!set) return;\r\n for (const callback of set) {\r\n callback(payload as T);\r\n }\r\n }\r\n\r\n /**\r\n * Subscribes to a per-object progress event.\r\n * @param id The object ID.\r\n * @param callback The callback to handle progress value.\r\n */\r\n onProgress(id: string, callback: EventCallback<number>): void {\r\n this.on(`progress:${id}`, callback);\r\n }\r\n\r\n /**\r\n * Emits a per-object progress event.\r\n * @param id The object ID.\r\n * @param value The progress value.\r\n */\r\n emitProgress(id: string, value: number): void {\r\n this.emit(`progress:${id}`, value);\r\n }\r\n\r\n /**\r\n * Subscribes to a per-object in-view event.\r\n * @param id The object ID.\r\n * @param callback The callback to handle visibility.\r\n */\r\n onInview(id: string, callback: EventCallback<boolean>): void {\r\n this.on(`object:inview:${id}`, callback);\r\n }\r\n\r\n /**\r\n * Emits a per-object in-view event.\r\n * @param id The object ID.\r\n * @param visible Whether the object is visible.\r\n */\r\n emitInview(id: string, visible: boolean): void {\r\n this.emit(`object:inview:${id}`, visible);\r\n }\r\n\r\n /**\r\n * Subscribes to the global scroll event.\r\n * @param callback The callback to handle scroll value.\r\n */\r\n onScroll(callback: EventCallback<number>): void {\r\n this.on(`scroll`, callback);\r\n }\r\n\r\n /**\r\n * Emits the global scroll event.\r\n * @param value The scroll value.\r\n */\r\n emitScroll(value: number): void {\r\n this.emit(`scroll`, value);\r\n }\r\n\r\n /**\r\n * Subscribes to the global update event.\r\n * @param callback The callback to handle update.\r\n */\r\n onUpdate(callback: EventCallback<void>): void {\r\n this.on(`update`, callback);\r\n }\r\n\r\n /**\r\n * Emits the global update event.\r\n */\r\n emitUpdate(): void {\r\n this.emit(`update`);\r\n }\r\n\r\n /**\r\n * Clears all listeners for a specific event.\r\n *\r\n * @param eventName The full event name (including optional `id`).\r\n */\r\n clear(eventName: string): void {\r\n delete this.listeners[eventName];\r\n }\r\n\r\n /**\r\n * Clears all registered events.\r\n */\r\n clearAll(): void {\r\n this.listeners = {};\r\n }\r\n}\r\n","import { StringData } from \"../..\";\r\nimport { ISettingsChangeData } from \"../../models/event/ISettingsChangeData\";\r\nimport { IStringModule } from \"../IStringModule\";\r\nimport { StringModule } from \"../StringModule\";\r\n\r\ntype LifecycleName =\r\n | \"destroy\"\r\n | \"onInit\"\r\n | \"onFrame\"\r\n | \"onMutate\"\r\n | \"onScrollMeasure\"\r\n | \"onMouseMoveMeasure\"\r\n | \"onScroll\"\r\n | \"onResizeWidth\"\r\n | \"onResize\"\r\n | \"onMouseMove\"\r\n | \"onWheel\"\r\n | \"onDirectionChange\"\r\n | \"onScrollStart\"\r\n | \"onScrollStop\"\r\n | \"onAxisChange\"\r\n | \"onDeviceChange\"\r\n | \"onScrollConfigChange\"\r\n | \"onSettingsChange\"\r\n | \"onDOMMutate\";\r\n\r\nfunction toLifecycleArgs(\r\n lifecycle: LifecycleName,\r\n data: StringData,\r\n arg?: unknown,\r\n arg2?: unknown\r\n): unknown[] {\r\n switch (lifecycle) {\r\n case \"onFrame\":\r\n case \"onMutate\":\r\n case \"onScrollMeasure\":\r\n case \"onMouseMoveMeasure\":\r\n case \"onScroll\":\r\n return [data];\r\n case \"onMouseMove\":\r\n case \"onWheel\":\r\n return arg ? [arg] : [];\r\n case \"onDOMMutate\":\r\n return arg && arg2 ? [arg, arg2] : [];\r\n case \"onSettingsChange\":\r\n return [];\r\n default:\r\n return [];\r\n }\r\n}\r\n\r\nexport class ModuleManager {\r\n private modules: StringModule[] = [];\r\n private uiModules: StringModule[] = [];\r\n private allModules: StringModule[] = [];\r\n\r\n constructor(private data: StringData) {}\r\n\r\n register(module: StringModule): void {\r\n if (module.type === 1) {\r\n this.modules.push(module);\r\n } else if (module.type === 2) {\r\n this.uiModules.push(module);\r\n }\r\n\r\n this.rebuildAllModules();\r\n }\r\n\r\n find<T>(type: new (...args: any[]) => T): T | undefined {\r\n return this.modules.find((m) => m instanceof type) as T | undefined;\r\n }\r\n\r\n onInit(): void {\r\n this.callAll(\"onInit\");\r\n }\r\n\r\n onFrame(): void {\r\n this.callAll(\"onFrame\");\r\n }\r\n\r\n onMutate(): void {\r\n this.callAll(\"onMutate\");\r\n }\r\n\r\n onScrollMeasure(): void {\r\n this.callAll(\"onScrollMeasure\");\r\n }\r\n\r\n onMouseMoveMeasure(): void {\r\n this.callAll(\"onMouseMoveMeasure\");\r\n }\r\n\r\n onScroll(): void {\r\n this.callAll(\"onScroll\");\r\n }\r\n\r\n onResizeWidth(): void {\r\n this.callAll(\"onResizeWidth\");\r\n }\r\n\r\n onResize(): void {\r\n this.callAll(\"onResize\");\r\n }\r\n\r\n onMouseMove(e: MouseEvent): void {\r\n this.callAll(\"onMouseMove\", e);\r\n }\r\n\r\n onWheel(e: WheelEvent): void {\r\n this.callAll(\"onWheel\", e);\r\n }\r\n\r\n onDirectionChange(): void {\r\n this.callAll(\"onDirectionChange\");\r\n }\r\n\r\n onScrollStart(): void {\r\n this.callAll(\"onScrollStart\");\r\n }\r\n\r\n onScrollStop(): void {\r\n this.callAll(\"onScrollStop\");\r\n }\r\n\r\n onAxisChange(): void {\r\n this.callAll(\"onAxisChange\");\r\n }\r\n\r\n onDeviceChange(): void {\r\n this.callAll(\"onDeviceChange\");\r\n }\r\n\r\n onScrollConfigChange(): void {\r\n this.callAll(\"onScrollConfigChange\");\r\n }\r\n\r\n onSettingsChange(_data: ISettingsChangeData): void {\r\n this.callAll(\"onSettingsChange\");\r\n }\r\n\r\n onDOMMutate(added: NodeList, removed: NodeLis