@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 • 1.58 MB
Source Map (JSON)
{"version":3,"sources":["../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/DOMBatcher.ts","../src/models/IModuleLifecyclePermissions.ts","../src/core/StringModule.ts","../src/core/managers/ObjectManager.ts","../src/models/scroll/ScrollHTMLClass.ts","../src/core/controllers/ScrollController.ts","../src/core/controllers/StringScrollDefault.ts","../src/core/controllers/StringScrollDisable.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/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/utils/style-txn.ts","../src/core/StringToolsContainer.ts","../src/utils/isCoarsePointer.ts","../src/modules/cursor/StringCursor.ts","../src/modules/cursor/StringImpulse.ts","../src/utils/frame-dom.ts","../src/modules/layout/StringMasonry.ts","../src/modules/cursor/StringMagnetic.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/layout-measure/FlexMeasureAdapter.ts","../src/utils/text/layout-measure/InlineFlowMeasureAdapter.ts","../src/utils/text/layout-measure/LayoutMeasureRouter.ts","../src/utils/text/layout-measure/measureTokens.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/dev-tools/core/StringDevtoolsIcons.ts","../src/modules/dev-tools/core/StringDevIconRegistry.ts","../src/modules/dev-tools/core/StringDevElements.ts","../src/modules/dev-tools/core/StringDevCoreStyles.css.ts","../src/modules/dev-tools/core/StringDevStyleSystem.ts","../src/modules/dev-tools/core/StringDevViewportPolicy.ts","../src/modules/dev-tools/core/StringDevStorageScope.ts","../src/modules/dev-tools/core/StringDevtoolsDock.ts","../src/core/managers/DevtoolsManager.ts","../src/modules/scroll/StringScroller.ts","../src/modules/scroll/StringScrollContainer.ts","../src/utils/ParsePartOf.ts","../src/modules/scroll/StringProgressPart.ts","../src/modules/random/StringRandom.ts","../src/modules/dev-tools/core/StringDevViewportLayer.ts","../src/modules/dev-tools/core/StringDevOverlayRegistry.ts","../src/modules/dev-tools/core/StringDevModule.ts","../src/modules/dev-tools/layout/models/GridInstance.ts","../src/modules/dev-tools/layout/GridManager.ts","../src/modules/dev-tools/layout/GridOverlay.ts","../src/modules/dev-tools/core/ui/fields/StringDevUIField.ts","../src/modules/dev-tools/core/ui/fields/StringDevFieldNumber.ts","../src/modules/dev-tools/core/ui/fields/StringDevFieldRange.ts","../src/modules/dev-tools/core/ui/fields/StringDevFieldColor.ts","../src/modules/dev-tools/core/ui/fields/StringDevFieldSelect.ts","../src/modules/dev-tools/core/ui/fields/StringDevFieldToggle.ts","../src/modules/dev-tools/core/ui/StringDevUIBuilder.ts","../src/modules/dev-tools/layout/GridHUD.ts","../src/modules/dev-tools/layout/GridSerializer.ts","../src/modules/dev-tools/layout/adapters/GridAdapter.ts","../src/modules/dev-tools/layout/adapters/ColumnsAdapter.ts","../src/modules/dev-tools/layout/adapters/RowsAdapter.ts","../src/modules/dev-tools/layout/adapters/CenterAdapter.ts","../src/modules/dev-tools/layout/adapters/RuleOfThirdsAdapter.ts","../src/modules/dev-tools/layout/adapters/GoldenRectangleAdapter.ts","../src/modules/dev-tools/layout/adapters/DotGridAdapter.ts","../src/modules/dev-tools/core/StringDevtoolsOverlayLayout.ts","../src/modules/dev-tools/layout/StringGrid.css.ts","../src/modules/dev-tools/layout/StringGrid.ts","../src/modules/dev-tools/rulers/StringRulers.css.ts","../src/modules/dev-tools/rulers/models/RulerLine.ts","../src/modules/dev-tools/rulers/RulersManager.ts","../src/modules/dev-tools/rulers/SnapEngine.ts","../src/modules/dev-tools/rulers/models/RulerMode.ts","../src/modules/dev-tools/core/startPointerDrag.ts","../src/utils/interaction-lock.ts","../src/modules/dev-tools/rulers/RulersOverlay.ts","../src/modules/dev-tools/core/StringDevPersistedState.ts","../src/modules/dev-tools/rulers/StringRulers.ts","../src/modules/dev-tools/core/StringDevOverlayModule.ts","../src/modules/dev-tools/core/StringDevBadgeOverlayModule.ts","../src/modules/dev-tools/inview/StringDevInview.css.ts","../src/modules/dev-tools/inview/StringDevInview.ts","../src/modules/dev-tools/core/bindOutsideClick.ts","../src/modules/dev-tools/progress/StringDevProgress.css.ts","../src/modules/dev-tools/progress/StringDevProgress.ts","../src/models/devtools/StringDevtool.ts","../src/index.ts"],"sourcesContent":["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\nexport class CursorController {\r\n protected context: StringContext;\r\n\r\n private readonly SETTLE_THRESHOLD_SQ = 0.01;\r\n\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 private _lerpStepXArgs = { from: 0, to: 0, progress: 0 };\r\n private _lerpStepYArgs = { from: 0, to: 0, progress: 0 };\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 public onMouseMove(e: MouseEvent): void {\r\n const cursorData = this.context.data.cursor;\r\n\r\n cursorData.targetX = e.clientX;\r\n cursorData.targetY = e.clientY;\r\n\r\n const now = performance.now();\r\n\r\n const dt = Math.max(1, now - this.lastMouseTime);\r\n\r\n cursorData.velocityX = (e.clientX - this.lastMouseX) / dt;\r\n cursorData.velocityY = (e.clientY - this.lastMouseY) / dt;\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 public onFrame(): void {\r\n const cursorData = this.context.data.cursor;\r\n\r\n const tX = cursorData.targetX;\r\n const tY = cursorData.targetY;\r\n const sX = cursorData.smoothedX;\r\n const sY = cursorData.smoothedY;\r\n\r\n this._lerpStepXArgs.from = sX;\r\n this._lerpStepXArgs.to = tX;\r\n this._lerpStepXArgs.progress = this.smoothingFactor;\r\n\r\n this._lerpStepYArgs.from = sY;\r\n this._lerpStepYArgs.to = tY;\r\n this._lerpStepYArgs.progress = this.smoothingFactor;\r\n\r\n const stepX = this.context.tools.lerp.process(this._lerpStepXArgs);\r\n const stepY = this.context.tools.lerp.process(this._lerpStepYArgs);\r\n const distSq = stepX * stepX + stepY * stepY;\r\n if (distSq < this.SETTLE_THRESHOLD_SQ) {\r\n cursorData.smoothedX = tX;\r\n cursorData.smoothedY = tY;\r\n cursorData.stepX = 0;\r\n cursorData.stepY = 0;\r\n } else {\r\n cursorData.smoothedX += stepX;\r\n cursorData.smoothedY += stepY;\r\n cursorData.stepX = stepX;\r\n cursorData.stepY = stepY;\r\n }\r\n }\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 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","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 * Marks an event as stateful so the last payload is cached and replayed to new listeners.\r\n * Optionally seeds the initial payload.\r\n */\r\n registerStateEvent(eventName: string, initialPayload?: any): void {\r\n this.stateEvents.add(eventName);\r\n if (initialPayload !== undefined) {\r\n this.lastPayloads[eventName] = initialPayload;\r\n }\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>(eventName: string, callback: EventCallback<T>, id?: string | null): 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 (this.stateEvents.has(fullEvent) && this.lastPayloads[fullEvent] !== undefined) {\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>(eventName: string, callback: EventCallback<T>, id?: string): 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 | \"onSubscribe\"\r\n | \"onUnsubscribe\"\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 | \"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\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 if (module.cssProperties && module.cssProperties.length > 0) {\r\n if (typeof window.CSS !== \"undefined\" && \"registerProperty\" in window.CSS) {\r\n for (let i = 0; i < module.cssProperties.length; i++) {\r\n try {\r\n (window.CSS as any).registerProperty(module.cssProperties[i]);\r\n } catch (e) {\r\n // Property might already be registered by another instance of the same module type\r\n }\r\n }\r\n }\r\n }\r\n\r\n module.onSubscribe();\r\n this.rebuildAllModules();\r\n }\r\n\r\n find<T>(type: new (...args: any[]) => T): T | undefined {\n for (let i = 0; i < this.allModules.length; i++) {\n const module = this.allModules[i];\n if (module instanceof type) {\n return module as T;\n }\n }\n return undefined;\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: NodeList): void {\r\n this.callAll(\"onDOMMutate\", added, removed);\r\n }\r\n\r\n destroy(): void {\r\n this.callAll(\"onUnsubscribe\");\r\n this.callAll(\"destroy\");\r\n this.modules = [];\r\n this.uiModules = [];\r\n this.allModules = [];\r\n }\r\n\r\n get all(): IStringModule[] {\r\n return this.allModules;\r\n }\r\n\r\n get core(): IStringModule[] {\r\n return this.modules;\r\n }\r\n\r\n get ui(): IStringModule[] {\r\n return this.uiModules;\r\n }\r\n\r\n private callAll(lifecycle: LifecycleName, arg?: unknown, arg2?: unknown): void {\r\n if (this.modules.length > 0) {\r\n this.callLifecycleStrict(this.modules, lifecycle, arg, arg2);\r\n }\r\n if (this.uiModules.length > 0) {\r\n this.callLifecycleStrict(this.uiModules, lifecycle, arg, arg2);\r\n }\r\n }\r\n\r\n private callLifecycleStrict(\r\n modules: StringModule[],\r\n lifecycle: LifecycleName,\r\n arg?: unknown,\r\n arg2?: unknown\r\n ): void {\r\n const len = modules.length;\r\n\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 // Single argument: StringData\r\n for (let i = 0; i < len; i++) {\r\n const mod = modules[i];\r\n if (mod) (mod as any)[lifecycle](this.data);\r\n }\r\n break;\r\n\r\n case \"onDOMMutate\":\r\n // Two arguments: addedNodes, removedNodes\r\n for (let i = 0; i < len; i++) {\r\n const mod = modules[i];\r\n if (mod) (mod as any)[lifecycle](arg, arg2);\r\n }\r\n break;\r\n\r\n case \"onMouseMove\":\r\n case \"onWheel\":\r\n // Single argument: Event\r\n for (let i = 0; i < len; i++) {\r\n const mod = modules[i];\r\n if (mod) (mod as any)[lifecycle](arg);\r\n }\r\n break;\r\n\r\n default:\r\n // No arguments\r\n for (let i = 0; i < len; i++) {\r\n const mod = modules[i];\r\n if (mod) (mod as any)[lifecycle]();\r\n }\r\n break;\r\n }\r\n }\r\n\r\n private rebuildAllModules(): void {\r\n this.allModules.length = 0;\r\n for (let i = 0; i < this.modules.length; i++) {\r\n this.allModules.push(this.modules[i]);\r\n }\r\n for (let i = 0; i < this.uiModules.length; i++) {\r\n this.allModules.push(this.uiModules[i]);\r\n }\r\n }\r\n}\r\n","import type { StringObject } from \"./StringObject\";\r\n\r\nexport type MirrorEasingFn = (value: number) => number;\r\n\r\n/**\r\n * Lightweight wrapper that mirrors a primary StringObject while keeping\r\n * its own easing and state. Intended for elements linked via\r\n * `[string-copy-from]`.\r\n */\r\nexport class StringMirrorObject {\r\n public readonly id: string;\r\n public readonly htmlElement: HTMLElement;\r\n\r\n private properties = new Map<string, any>();\r\n private easingFn?: MirrorEasingFn;\r\n\r\n constructor(id: string, element: HTMLElement, private parent: StringObject) {\r\n this.id = id;\r\n this.htmlElement = element;\r\n }\r\n\r\n public get parentObject(): StringObject {\r\n return this.parent;\r\n }\r\n\r\n public setProperty<T>(key: string, value: T): void {\r\n this.properties.set(key, value);\r\n }\r\n\r\n public getProperty<T>(key: string): T {\r\n return this.properties.get(key) ?? null;\r\n }\r\n\r\n public setEasing(easing: MirrorEasingFn | null | undefined): void {\r\n this.easingFn = easing ?? undefined;\r\n }\r\n\r\n public getEasing(): MirrorEasingFn | undefined {\r\n return this.easingFn;\r\n }\r\n\r\n /**\r\n * Returns eased progress using mirror easing (if set) or fallback.\r\n */\r\n public applyProgress(rawProgress: number, fallback?: MirrorEasingFn): number {\r\n const easing = this.easingFn ?? fallback;\r\n return easing ? easing(rawProgress) : rawProgress;\r\n }\r\n}\r\n","import { IStringModule } from \"../core/IStringModule\";\r\nimport { EventManager } from \"../core/managers/EventManager\";\r\nimport { StringToken } from \"../models/string/StringToken\";\r\nimport { StringMirrorObject } from \"./StringMirrorObject\";\r\n\r\n/**\r\n * Internal class representing a DOM-bound interactive object.\r\n * Connected to modules and holds its own internal state.\r\n */\r\nexport class StringObject {\r\n /**\r\n * The DOM element this object wraps.\r\n */\r\n public htmlElement: HTMLElement;\r\n\r\n /**\r\n * Unique global ID assigned by the system.\r\n */\r\n public id: string = \"\";\r\n\r\n /**\r\n * Space-separated list of all attribute keys associated with this object.\r\n */\r\n public keys: string[] = [];\r\n public tokens: StringToken[] = [];\r\n\r\n /**\r\n * Mirror objects linked via `string-copy-from`.\r\n */\r\n private mirrors = new Map<string, StringMirrorObject>();\r\n private _cachedMirrorObjects: StringMirrorObject[] = [];\r\n private _cachedConnects: HTMLElement[] = [];\r\n private _mirrorsDirty: boolean = false;\r\n\r\n /**\r\n * Internal key-value store of dynamic object properties (like offsets, progress, etc.).\r\n */\r\n private properties: Map<string, any> = new Map();\r\n\r\n /**\r\n * Modules currently connected to this object.\r\n */\r\n private modules: IStringModule[] = [];\r\n\r\n /**\r\n * Manages and handles events for the object.\r\n * Provides functionality to register, trigger, and manage event listeners.\r\n */\r\n events: EventManager = new EventManager();\r\n private eventNameCache: Map<string, string> = new Map();\r\n private eventNameSuffixCache: Map<string, Map<string, string>> = new Map();\r\n\r\n constructor(id: string, element: HTMLElement) {\r\n this.htmlElement = element;\r\n this.id = id;\r\n }\r\n\r\n /**\r\n * Returns a cached event name in one of these forms:\r\n * - `${prefix}:${id}`\r\n * - `${prefix}:${id}:${suffix}`\r\n */\r\n public getScopedEventName(prefix: string, suffix?: string): string {\r\n if (suffix == null) {\r\n const cached = this.eventNameCache.get(prefix);\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n const eventName = `${prefix}:${this.id}`;\r\n this.eventNameCache.set(prefix, eventName);\r\n return eventName;\r\n }\r\n\r\n let suffixMap = this.eventNameSuffixCache.get(prefix);\r\n if (!suffixMap) {\r\n suffixMap = new Map<string, string>();\r\n this.eventNameSuffixCache.set(prefix, suffixMap);\r\n }\r\n\r\n const cachedWithSuffix = suffixMap.get(suffix);\r\n if (cachedWithSuffix) {\r\n return cachedWithSuffix;\r\n }\r\n\r\n const eventName = `${prefix}:${this.id}:${suffix}`;\r\n suffixMap.set(suffix, eventName);\r\n return eventName;\r\n }\r\n\r\n /**\r\n * Stores a property value for this object.\r\n * @param key - Property name\r\n * @param value - Value to store\r\n */\r\n public setProperty<T>(key: string, value: T): void {\r\n this.properties.set(key, value);\r\n }\r\n\r\n /**\r\n * Retrieves a previously stored property value.\r\n * @param key - Property name\r\n * @returns The value or null if not set\r\n */\r\n public getProperty<T>(key: string): T {\r\n return this.properties.get(key) ?? null;\r\n }\r\n\r\n /**\r\n * Marks this object as \"active\" (usually on intersection/scroll enter).\r\n */\r\n public enter(): void {\r\n this.events.emit(\"enter\", this);\r\n this.setProperty(\"active\", true);\r\n this.modules.forEach((module) => {\r\n module.enterObject(this.id, this);\r\n });\r\n }\r\n\r\n /**\r\n * Marks this object as \"inactive\" (usually on intersection/scroll leave).\r\n */\r\n public leave(): void {\r\n this.events.emit(\"leave\", this);\r\n this.setProperty(\"active\", false);\r\n this.modules.forEach((module) => {\r\n module.exitObject(this.id);\r\n });\r\n }\r\n\r\n /**\r\n * Removes the current object by iterating through all associated modules\r\n * and invoking their `removeObject` method with the object's ID.\r\n *\r\n * This method ensures that the object is properly removed from all\r\n * modules it is associated with.\r\n */\r\n public remove(): void {\r\n this.modules.forEach((module) => {\r\n module.removeObject(this.id);\r\n });\r\n }\r\n\r\n public setInviewAutoBlocked(blocked: boolean): void {\r\n this.setProperty(\"inview-auto-blocked\", blocked);\r\n }\r\n\r\n public isInviewAutoBlocked(): boolean {\r\n return this.getProperty<boolean>(\"inview-auto-blocked\") === true;\r\n }\r\n\r\n public setInviewManualActive(active: boolean): void {\r\n this.setProperty(\"inview-manual-active\", active);\r\n }\r\n\r\n public isInviewManualActive(): boolean {\r\n return this.getProperty<boolean>(\"inview-manual-active\") === true;\r\n }\r\n\r\n public syncInviewClass(): void {\r\n if (this.isInviewAutoBlocked()) {\r\n this.htmlElement.classList.remove(\"-inview\");\r\n return;\r\n }\r\n\r\n if (this.isInviewManualActive()) {\r\n this.htmlElement.classList.add(\"-inview\");\r\n return;\r\n }\r\n\r\n const isInView = this.getProperty<boolean>(\"is-inview\") === true;\r\n if (isInView) {\r\n this.htmlElement.classList.add(\"-inview\");\r\n return;\r\n }\r\n\r\n const shouldRepeat = this.getProperty<boolean>(\"repeat\");\r\n if (shouldRepeat) {\r\n this.htmlElement.classList.remove(\"-inview\");\r\n }\r\n }\r\n\r\n /**\r\n * Shows the object, applies visual class and notifies connected modules.\r\n */\r\n public show(): void {\r\n if (this.isInviewAutoBlocked()) {\r\n return;\r\n }\r\n this.htmlElement.classList.add(\"-inview\");\r\n }\r\n\r\n /**\r\n * Hides the object, removes visual class (if repeat is enabled), and notifies modules.\r\n */\r\n public hide(): void {\r\n if (this.isInviewAutoBlocked()) {\r\n this.htmlElement.classList.remove(\"-inview\");\r\n return;\r\n }\r\n\r\n if (this.isInviewManualActive()) {\r\n return;\r\n }\r\n\r\n const shouldRepeat = this.getProperty<boolean>(\"repeat\");\r\n if (shouldRepeat) {\r\n this.htmlElement.classList.remove(\"-inview\");\r\n }\r\n }\r\n\r\n /**\r\n * Connects a module to this object if not already connected.\r\n * @param module - The module to connect\r\n */\r\n public connect(module: IStringModule): boolean {\r\n if (!this.modules.includes(module)) {\r\n this.modules.push(module);\r\n return true;\r\n }\r\n return false;\r\n }\r\n\r\n public disconnect(module: IStringModule): boolean {\r\n const index = this.modules.indexOf(module);\r\n if (index === -1) {\r\n return false;\r\n }\r\n this.modules.splice(index, 1);\r\n return true;\r\n }\r\n\r\n public isConnectedTo(module: IStringModule): boolean {\r\n return this.modules.includes(module);\r\n }\r\n\r\n public setTokens(tokens: StringToken[]): void {\r\n this.tokens = tokens;\r\n this.keys = tokens.map((token) => token.key);\r\n }\r\n\r\n public getToken(key: string): StringToken | null {\r\n for (let i = 0; i < this.tokens.length; i++) {\r\n if (this.tokens[i].key === key) {\r\n return this.tokens[i];\r\n }\r\n }\r\n return null;\r\n }\r\n\r\n private updateMirrorsCache(): void {\r\n if (this._mirrorsDirty) {\r\n this._cachedMirrorObjects = Array.from(this.mirrors.values());\r\n const len = this._cachedMirrorObjects.length;\r\n this._cachedConnects = new Array(len);\r\n for (let i = 0; i < len; i++) {\r\n this._cachedConnects[i] = this._cachedMirrorObjects[i].htmlElement;\r\n }\r\n this._mirrorsDirty = false;\r\n }\r\n }\r\n\r\n public addMirror(mirror: StringMirrorObject): void {\r\n if (!this.mirrors.has(mirror.id)) {\r\n this.mirrors.set(mirror.id, mirror);\r\n this._mirrorsDirty = true;\r\n }\r\n }\r\n\r\n public removeMirror(id: string): void {\r\n if (this.mirrors.delete(id)) {\r\n this._mirrorsDirty = true;\r\n }\r\n }\r\n\r\n public get mirrorObjects(): StringMirrorObject[] {\r\n this.updateMirrorsCache();\r\n return this._cachedMirrorObjects;\r\n }\r\n\r\n public get connects(): HTMLElement[] {\r\n this.updateMirrorsCache();\r\n return this._cachedConnects;\r\n }\r\n}\r\n","import { StringObject } from \"../../objects/StringObject\";\r\nimport { StringModule } from \"../StringModule\";\r\n\r\ninterface BatchTask {\r\n priority: number;\r\n execute: () => void;\r\n}\r\n\r\ninterface ModuleBatchContext {\r\n module: StringModule;\r\n object: StringObject;\r\n element: HTMLElement;\r\n attributes: Record<string, any>;\r\n globalId: number;\r\n windowSize: number;\r\n}\r\n\r\nexport class DOMBatcher {\r\n private readQueue: BatchTask[] = [];\r\n private writeQueue: BatchTask[] = [];\r\n private computeQueue: BatchTask[] = [];\r\n private isProcessing = false;\r\n private pendingFrame: number | null = null;\r\n private rectCache = new WeakMap<HTMLElement, DOMRect>();\r\n private dimensionCache = new WeakMap<HTMLElement, { width: number; height: number }>();\r\n\r\n public scheduleRead(task: () => void, priority: number = 0): void {\r\n this.readQueue.push({ priority, execute: task });\r\n this.scheduleFlush();\r\n }\r\n\r\n public scheduleCompute(task: () => void, priority: number = 0): void {\r\n this.computeQueue.push({ priority, execute: task });\r\n this.scheduleFlush();\r\n }\r\n\r\n public scheduleWrite(task: () => void, priority: number = 0): void {\r\n this.writeQueue.push({ priority, execute: task });\r\n this.scheduleFlush();\r\n }\r\n\r\n public batchModuleInitialization(contexts: ModuleBatchContext[]): void {\r\n contexts.forEach(({ module, object, element, attributes, globalId }) => {\r\n this.scheduleRead(() => {\r\n const rect = element.getBoundingClientRect();\r\n this.rectCache.set(element, rect);\r\n this.dimensionCache.set(element, {\r\n width: element.offsetWidth || element.clientWidth || rect.width,\r\n height: element.offsetHeight || element.clientHeight || rect.height,\r\n });\r\n module.initializeObject(globalId, object, element, attributes);\r\n }, 1);\r\n });\r\n\r\n contexts.forEach(({ module, object, windowSize }) => {\r\n this.scheduleCompute(() => {\r\n module.calculatePositions(object, windowSize);\r\n }, 2);\r\n });\r\n\r\n contexts.forEach(({ module, object }) => {\r\n this.scheduleWrite(() => {\r\n module.connectObject(object);\r\n module.addObject(object.id, object);\r\n }, 3);\r\n });\r\n }\r\n\r\n public getCachedRect(element: HTMLElement): DOMRect | undefined {\r\n return this.rectCache.get(element);\r\n }\r\n\r\n public getCachedDimensions(element: HTMLElement): { width: number; height: number } | undefined {\r\n return this.dimensionCache.get(element);\r\n }\r\n\r\n private scheduleFlush(): void {\r\n if (this.pendingFrame !== null || this.isProcessing) {\r\n return;\r\n }\r\n this.pendingFrame = requestAnimationFrame(() => {\r\n this.flush();\r\n });\r\n }\r\n\r\n private flush(): void {\r\n this.isProcessing = true;\r\n this.pendingFrame = null;\r\n const sortByPriority = (a: BatchTask, b: BatchTask) => b.priority - a.priority;\r\n\r\n try {\r\n const reads = [...this.readQueue].sort(sortByPriority);\r\n reads.forEach((task) => {\r\n try {\r\n task.execute();\r\n } catch (error) {\r\n console.error(\"[DOMBatcher] Read task error:\", error);\r\n }\r\n });\r\n this.readQueue = [];\r\n\r\n const computes = [...this.computeQueue].sort(sortByPriority);\r\n computes.forEach((task) => {\r\n try {\r\n task.execute();\r\n } catch (error) {\r\n console.error(\"[DOMBatcher] Compute task error:\", error);\r\n }\r\n });\r\n this.computeQueue = [];\r\n\r\n const writes = [...this.writeQueue].sort(sortByPriority);\r\n writes.forEach((task) => {\r\n try {\r\n task.execute();\r\n } catch (error) {\r\n console.error(\"[DOMBatcher] Write task error:\", error);\r\n }\r\n });\r\n this.writeQueue = [];\r\n } finally {\r\n this.rectCache = new WeakMap();\r\n this.dimensionCache = new WeakMap();\r\n this.isProcessing = false;\r\n }\r\n }\r\n\r\n public flushSync(): void {\r\n if (this.pendingFrame !== null) {\r\n cancelAnimationFrame(this.pendingFrame);\r\n this.pendingFrame = null;\r\n }\r\n this.flush();\r\n }\r\n\r\n public clear(): void {\r\n if (this.pendingFrame !== null) {\r\n cancelAnimationFrame(this.pendingFrame);\r\n this.pendingFrame = null;\r\n }\r\n this.readQueue = [];\r\n this.writeQueue = [];\r\n this.computeQueue = [];\r\n this.rectCache = new WeakMap();\r\n this.dimensionCache = new WeakMap();\r\n }\r\n}\r\n","export interface ModuleLifecyclePermissionsItem {\r\n rebuild: {\r\n width: boolean;\r\n height: boolean;\r\n scrollHeight: boolean;\r\n };\r\n}\r\nexport class ModuleLifecyclePermissions {\r\n desktop: ModuleLifecyclePermissionsItem = {\r\n rebuild: {\r\n width: true,\r\n height: true,\r\n scrollHeight: true,\r\n },\r\n };\r\n mobile: ModuleLifecyclePermissionsItem = {\r\n rebuild: {\r\n width: true,\r\n height: true,\r\n scrollHeight: true,\r\n },\r\n };\r\n}\r\n","import { IStringModule } from \"./IStringModule\";\r\nimport { StringObject } from \"../objects/StringObject\";\r\nimport { StringMirrorObject } from \"../objects/StringMirrorObject\";\r\nimport { StringToolsContainer } from \"./StringToolsContainer\";\r\nimport { StringData } from \"./StringData\";\r\nimport { AttributeType } from \"../models/attribute/AttributeType\";\r\nimport { StringContext } from \"./StringContext\";\r\nimport { EventManager } from \"./managers/EventManager\";\r\nimport { AttributeMapping } from \"../models/attribute/AttributeMapping\";\r\nimport { ModuleLifecyclePermissions } from \"../models/IModuleLifecyclePermissions\";\r\nimport { StringToken } from \"../models/string/StringToken\";\r\nimport { CenterCache } from \"./managers/CenterCache\";\r\nimport { HoverTracker } from \"./managers/HoverTracker\";\r\nimport { ObjectManager } from \"./managers/ObjectManager\";\r\nimport { ScrollMode } from \"../states/ScrollState\";\r\n\r\nconst EMPTY_CONTEXT = Object.freeze({});\r\n\r\ninterface ParseContext {\r\n element?: HTMLElement;\r\n boundingRect?: DOMRect;\r\n viewportHeight?: number;\r\n baseRem?: number;\r\n}\r\n\r\n/**\r\n * Base class for a module used in the string-tune system.\r\n * Extend this class to create custom modules that respond to scroll, resize, input, etc.\r\n */\r\nexport class StringModule implements IStringModule {\r\n /**\r\n * List of attribute names this module should automatically read\r\n * from the DOM element and assign to the object properties.\r\n * Example: [\"offset-top\", \"offset-bottom\"]\r\n */\r\n protected attributesToMap: AttributeMapping[];\r\n\r\n /**\r\n * Defines CSS Custom Properties (Houdini) strictly required by this module.\r\n * ModuleManager will automatically register these properties on initialization\r\n * for Typed Object Model hardware acceleration.\r\n */\r\n public cssProperties: Array<{\r\n name: string;\r\n syntax: string;\r\n initialValue: string;\r\n inherits: boolean;\r\n }> = [];\r\n\r\n /**\r\n * A map that associates string keys with `StringObject` instances.\r\n * This map is used to manage and track `StringObject` instances on a page.\r\n */\r\n protected objectMapOnPage: Map<string, StringObject> = new Map();\r\n protected allObjectMapOnPage: Map<string, StringObject> = new Map();\r\n\r\n /**\r\n * A protected array that holds the collection of `StringObject` instances\r\n * currently present on the page.\r\n */\r\n protected objectsOnPage: StringObject[] = [];\r\n protected allObjectsOnPage: StringObject[] = [];\r\n\r\n /**\r\n * A map of all entered objects by their unique ID.\r\n */\r\n protected objectMap: Map<string, StringObject> = new Map();\r\n protected allObjectMap: Map<string, StringObject> = new Map();\r\n\r\n /**\r\n * A flat array of all connected objects.\r\n */\r\n protected objects: StringObject[] = [];\r\n protected allObjects: StringObject[] = [];\r\n\r\n /**\r\n * The HTML attribute key that identifies objects this module is responsible for.\r\n */\r\n protected htmlKey: string = \"\";\r\n protected defaultModeScope: \"all\" | ScrollMode[] = \"all\";\r\n\r\n /**\r\n * Module type ID used internally to categorize module behavior.\r\n */\r\n protected _type: number = 1;\r\n\r\n /**\r\n * Returns the type of the module.\r\n * Type 1 = core module, type 2 = UI module.\r\n */\r\n public get type(): number {\r\n return this._type;\r\n }\r\n\r\n public get key(): string {\r\n return this.htmlKey;\r\n }\r\n\r\n /**\r\n * Tools container providing utilities for attribute parsing, unit conversion, etc.\r\n * Acts as a dependency injection hub for core IStringTool implementations.\r\n */\r\n protected tools: StringToolsContainer;\r\n\r\n /**\r\n * Shared global data object containing scroll state, viewport info, cursor position, etc.\r\n * Used for calculations within lifecycle hooks like `onScroll`, `onFrame`, and `onResize`.\r\n */\r\n protected data: StringData;\r\n\r\n /**\r\n * Configuration object specific to the current module.\r\n * Passed in during module registration or initialization.\r\n */\r\n protected settings: Record<string, any>;\r\n\r\n /**\r\n * Event hub for communication between modules or systems.\r\n * Supports custom event emitting, listening, and unsubscription.\r\n */\r\n protected events: EventManager;\r\n\r\n /**\r\n * Cache for storing and managing object centers.\r\n */\r\n protected centers: CenterCache;\r\n\r\n /**\r\n * Tracker for managing hover states of objects.\r\n */\r\n protected hover: HoverTracker;\r\n\r\n /**\r\n * Object manager for layout refreshes and in-view recalculation.\r\n */\r\n protected objectManager: ObjectManager;\r\n\r\n public permissions: ModuleLifecyclePermissions = new ModuleLifecyclePermissions();\r\n\r\n constructor(context: StringContext) {\r\n this.tools = context.tools;\r\n this.data = context.data;\r\n this.settings = context.settings;\r\n this.events = context.events;\r\n this.centers = context.centers;\r\n this.hover = context.hover;\r\n this.objectManager = context.objectManager;\r\n\r\n this.attributesToMap = [\r\n { key: \"active\", type: \"boolean\", fallback: this.settings[\"active\"] },\r\n { key: \"fixed\", type: \"boolean\", fallback: this.settings[\"fixed\"] },\r\n { key: \"outside-container\", type: \"boolean\", fallback: this.settings[\"outside-container\"] },\r\n { key: \"repeat\", type: \"boolean\", fallback: this.settings[\"repeat\"] },\r\n {\r\n key: \"self-disable\",\r\n type: \"boolean\",\r\n fallback: this.settings[\"self-disable\"],\r\n },\r\n { key: \"abs\", type: \"boolean\", fallback: this.settings[\"abs\"] },\r\n { key: \"key\", type: \"string\", fallback: this.settings[\"key\"] },\r\n {\r\n key: \"offset-top\",\r\n type: \"dimension\",\r\n fallback: this.settings[\"offset-top\"],\r\n },\r\n {\r\n key: \"offset-bottom\",\r\n type: \"dimension\",\r\n fallback: this.settings[\"offset-bottom\"],\r\n },\r\n {\r\n key: \"offset-enter\",\r\n type: \"dimension\",\r\n fallback: this.settings[\"offset-enter\"],\r\n },\r\n {\r\n key: \"offset-exit\",\r\n type: \"dimension\",\r\n fallback: this.settings[\"offset-exit\"],\r\n },\r\n {\r\n key: \"inview-top\",\r\n type: \"dimension\",\r\n fallback: this.settings[\"inview-top\"],\r\n },\r\n {\r\n key: \"inview-bottom\",\r\n type: \"dimension\",\r\n fallback: this.settings[\"inview-bottom\"],\r\n },\r\n {\r\n key: \"start\",\r\n type: \"number\",\r\n fallback: (element: HTMLElement, object: StringObject, boundingRect: DOMRect) => {\r\n const top = boundingRect.top;\r\n return Math.floor(top) + this.data.scroll.transformedCurrent;\r\n },\r\n },\r\n {\r\n key: \"end\",\r\n type: \"number\",\r\n fallback: (element: HTMLElement, object: StringObject, boundingRect: DOMRect) => {\r\n const top = boundingRect.top;\r\n const height = boundingRect.height;\r\n return top + height - this.data.scroll.transformedCurrent;\r\n },\r\n },\r\n {\r\n key: \"size\",\r\n type: \"number\",\r\n fallback: (element: HTMLElement, object: StringObject, boundingRect: DOMRect) => {\r\n return boundingRect.height;\r\n },\r\n },\r\n {\r\n key: \"half-width\",\r\n type: \"number\",\r\n fallback: (element: HTMLElement, object: StringObject, boundingRect: DOMRect) => {\r\n return boundingRect.width / 2;\r\n },\r\n },\r\n {\r\n key: \"half-height\",\r\n type: \"number\",\r\n fallback: (element: HTMLElement, object: StringObject, boundingRect: DOMRect) => {\r\n return boundingRect.height / 2;\r\n },\r\n },\r\n { key: \"enter-el\", type: \"string\", fallback: this.settings[\"enter-el\"] },\r\n { key: \"enter-vp\", type: \"string\", fallback: this.settings[\"enter-vp\"] },\r\n { key: \"exit-el\", type: \"string\", fallback: this.settings[\"exit-el\"] },\r\n { key: \"exit-vp\", type: \"string\", fallback: this.settings[\"exit-vp\"] },\r\n ];\r\n }\r\n\r\n /**\r\n * Initializes a `StringObject` by mapping attributes from an HTML element\r\n * and applying transformations as needed.\r\n *\r\n * @param globalId - A unique identifier for the object being initialized.\r\n * @param object - The `StringObject` instance to be initialized.\r\n * @param element - The HTML element from which attributes are extracted.\r\n * @param attributes - A record of additional attributes to be used during initialization.\r\n *\r\n * The method performs the following steps:\r\n * 1. Retrieves the bounding rectangle of the provided HTML element.\r\n * 2. Iterates over a predefined list of attributes to map.\r\n * 3. Resolves fallback values for attributes, either from the provided attributes,\r\n * settings, or a fallback function.\r\n * 4. Processes and parses the raw attribute values based on their expected type.\r\n * 5. Applies optional transformations to the parsed values.\r\n * 6. Sets the processed attributes as properties on the `StringObject` instance.\r\n */\r\n initializeObject(\r\n globalId: number,\r\n object: StringObject,\r\n element: HTMLElement,\r\n attributes: Record<string, any>,\r\n ): void {\r\n let boundingRect = this.tools.boundingClientRect.process({ element });\r\n for (const { key, type, fallback, transform } of this.attributesToMap) {\r\n const resolvedFallback =\r\n typeof fallback === \"function\" ? fallback(element, object, boundingRect) : fallback;\r\n const attributeFallback =\r\n attributes[key] ?? attributes[`string-${key}`] ?? attributes[`data-string-${key}`];\r\n const raw = this.tools.domAttribute.process({\r\n element,\r\n key,\r\n fallback: attributeFallback ?? this.settings[key] ?? resolvedFallback,\r\n });\r\n\r\n let parsed = this.parseAttribute(raw, type, {\r\n element,\r\n boundingRect,\r\n viewportHeight: this.data.viewport.windowHeight,\r\n baseRem: this.data.viewport.baseRem,\r\n });\r\n\r\n if (transform) {\r\n parsed = transform(parsed);\r\n }\r\n object.setProperty(key, parsed);\r\n }\r\n\r\n this.cacheLayoutSnapshot(object, element);\r\n }\r\n\r\n protected cacheLayoutSnapshot(object: StringObject, element: HTMLElement): void {\r\n const host = this.data.scroll.container ?? document.body ?? document.documentElement;\r\n const scroller = this.data.scroll.elementContainer ?? document.documentElement;\r\n const rect = this.tools.transformNullify.process({ element });\r\n const computedStyle = window.getComputedStyle(element);\r\n const fallbackSize = this.getOffsetSize(element, computedStyle);\r\n\r\n let docLeft = 0;\r\n let docTop = 0;\r\n let width = rect.width;\r\n let height = rect.height;\r\n\r\n if (!Number.isFinite(width) || width <= 0) {\r\n width = fallbackSize.width;\r\n }\r\n if (!Number.isFinite(height) || height <= 0) {\r\n height = fallbackSize.height;\r\n }\r\n\r\n if (\r\n Number.isFinite(rect.left) &&\r\n Number.isFinite(rect.top) &&\r\n rect.width > 0 &&\r\n rect.height > 0\r\n ) {\r\n if (host === document.body || host === document.documentElement) {\r\n docLeft = rect.left + scroller.scrollLeft;\r\n docTop = rect.top + scroller.scrollTop;\r\n } else {\r\n const hostRect = host.getBoundingClientRect();\r\n docLeft = rect.left - hostRect.left + host.scrollLeft;\r\n docTop = rect.top - hostRect.top + host.scrollTop;\r\n }\r\n } else {\r\n const elementOffset = this.getOffsetChainPosition(element);\r\n if (host === document.body || host === document.documentElement) {\r\n docLeft = elementOffset.left;\r\n docTop = elementOffset.top;\r\n } else {\r\n const hostOffset = this.getOffsetChainPosition(host);\r\n docLeft = elementOffset.left - hostOffset.left + host.scrollLeft;\r\n docTop = elementOffset.top - hostOffset.top + host.scrollTop;\r\n }\r\n }\r\n\r\n object.setProperty<number>(\"layout-doc-left\", docLeft);\r\n object.setProperty<number>(\"layout-doc-top\", docTop);\r\n object.setProperty<number>(\"layout-width\", width);\r\n object.setProperty<number>(\"layout-height\", height);\r\n object.setProperty<string>(\"layout-border-radius\", computedStyle.borderRadius || \"0px\");\r\n }\r\n\r\n private getOffsetSize(\r\n element: HTMLElement,\r\n computedStyle: CSSStyleDeclaration,\r\n ): { width: number; height: number } {\r\n const parsedWidth = parseFloat(computedStyle.width);\r\n const parsedHeight = parseFloat(computedStyle.height);\r\n\r\n return {\r\n width:\r\n element.offsetWidth ||\r\n element.clientWidth ||\r\n (Number.isFinite(parsedWidth) ? parsedWidth : 0),\r\n height:\r\n element.offsetHeight ||\r\n element.clientHeight ||\r\n (Number.isFinite(parsedHeight) ? parsedHe