{"version":3,"file":"turbo-mount.min.js","sources":["../src/turbo-mount-controller.ts","../src/helpers.ts","../src/turbo-mount.ts","../src/registerComponentsBase.ts"],"sourcesContent":["import { Controller } from \"@hotwired/stimulus\";\nimport { ApplicationWithTurboMount } from \"./turbo-mount\";\n\nexport class TurboMountController extends Controller {\n static values = {\n props: Object,\n component: String,\n };\n static targets = [\"mount\"];\n\n private skipPropsChangeCallback = false;\n\n declare propsValue: object;\n declare componentValue: string;\n declare readonly hasMountTarget: boolean;\n declare readonly mountTarget: Element;\n\n _umountComponentCallback?: () => void;\n\n connect() {\n this._umountComponentCallback ||= this.mountComponent(\n this.mountElement,\n this.resolvedComponent,\n this.componentProps,\n );\n }\n\n disconnect() {\n this.umountComponent();\n }\n\n propsValueChanged() {\n // Prevent re-mounting the component if the props are being set by the component itself\n if (this.skipPropsChangeCallback) {\n this.skipPropsChangeCallback = false;\n return;\n }\n\n this.umountComponent();\n this._umountComponentCallback ||= this.mountComponent(\n this.mountElement,\n this.resolvedComponent,\n this.componentProps,\n );\n }\n\n get componentProps() {\n return this.propsValue;\n }\n\n get mountElement() {\n return this.hasMountTarget ? this.mountTarget : this.element;\n }\n\n get resolvedComponent() {\n return this.resolveMounted(this.componentValue).component;\n }\n\n get resolvedPlugin() {\n return this.resolveMounted(this.componentValue).plugin;\n }\n\n umountComponent() {\n this._umountComponentCallback?.();\n this._umountComponentCallback = undefined;\n }\n\n mountComponent(el: Element, Component: unknown, props: object) {\n return this.resolvedPlugin.mountComponent({ el, Component, props });\n }\n\n resolveMounted(component: string) {\n const app = this.application as ApplicationWithTurboMount;\n return app.turboMount.resolve(component);\n }\n\n setComponentProps(props: object) {\n this.skipPropsChangeCallback = true;\n this.propsValue = props;\n }\n}\n","export const camelToKebabCase = (str: string) => {\n return str\n .replace(/([a-z])([A-Z])/g, \"$1-$2\")\n .replace(/_/g, \"-\")\n .replace(/\\//g, \"--\")\n .toLowerCase();\n};\n\n// Normalizes a component filename into a standardized component name.\n// Example: './components/users/UserProfile.tsx' -> 'users/UserProfile'\n// Example: 'global/utility/debounce_button.js' -> 'global/utility/debounce_button'\nexport const normalizeFilenameToComponentName = (filename: string): string => {\n return filename.replace(/\\.\\w*$/, \"\").replace(/^[./]*components\\//, \"\");\n};\n\nexport const generateStimulusIdentifiers = (\n componentName: string,\n): string[] => {\n const kebabCaseName = camelToKebabCase(componentName);\n\n return [`turbo-mount--${kebabCaseName}`, `turbo-mount-${kebabCaseName}`];\n};\n\nexport const getShortNameForIndexComponent = (\n componentName: string,\n): string | null => {\n if (componentName.endsWith(\"/index\")) {\n const shortName = componentName.replace(/\\/index$/, \"\");\n return shortName || null;\n }\n return null;\n};\n","import { Application, ControllerConstructor } from \"@hotwired/stimulus\";\n\nimport { camelToKebabCase } from \"./helpers\";\nimport { TurboMountController } from \"./turbo-mount-controller\";\n\ndeclare global {\n interface Window {\n Stimulus?: Application;\n }\n}\n\nexport interface ApplicationWithTurboMount extends Application {\n turboMount: TurboMount;\n}\n\nexport type MountComponentProps = {\n el: Element;\n Component: T;\n props: object;\n};\n\nexport type Plugin = {\n mountComponent: (props: MountComponentProps) => () => void;\n};\n\nexport type TurboMountProps = {\n application?: Application;\n};\n\ntype TurboMountComponents = Map }>;\n\ninterface TurboMorphEvent extends CustomEvent {\n target: Element;\n detail: {\n newElement: Element;\n };\n}\n\nexport class TurboMount {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n components: TurboMountComponents;\n application: ApplicationWithTurboMount;\n\n constructor(props: TurboMountProps = {}) {\n this.components = new Map();\n this.application = this.findOrStartApplication(props.application);\n this.application.turboMount = this;\n this.application.register(\"turbo-mount\", TurboMountController);\n\n document.addEventListener(\"turbo:before-morph-element\", (event) => {\n const turboMorphEvent = event as unknown as TurboMorphEvent;\n const { target, detail } = turboMorphEvent;\n\n const controllerAttr = target.getAttribute(\"data-controller\");\n if (controllerAttr?.includes(\"turbo-mount\")) {\n const turboMountController = controllerAttr\n .split(/\\s+/)\n .find((name) => name.startsWith(\"turbo-mount\"));\n\n if (turboMountController) {\n const propsAttrName = `data-${turboMountController}-props-value`;\n const newPropsValue =\n detail.newElement.getAttribute(propsAttrName) || \"{}\";\n target.setAttribute(propsAttrName, newPropsValue);\n event.preventDefault();\n }\n }\n });\n }\n\n register(\n plugin: Plugin,\n name: string,\n component: T,\n controller?: ControllerConstructor,\n ) {\n controller ||= TurboMountController;\n if (this.components.has(name)) {\n throw new Error(`Component '${name}' is already registered.`);\n }\n this.components.set(name, { component, plugin });\n\n if (controller) {\n const controllerName = `turbo-mount-${camelToKebabCase(name)}`;\n this.application.register(controllerName, controller);\n }\n }\n\n resolve(name: string) {\n const component = this.components.get(name);\n if (!component) {\n throw new Error(`Unknown component: ${name}`);\n }\n return component;\n }\n\n private findOrStartApplication(hydratedApp?: Application) {\n let application = hydratedApp || window.Stimulus;\n\n if (!application) {\n application = Application.start();\n window.Stimulus = application;\n }\n return application as ApplicationWithTurboMount;\n }\n}\n\nexport function buildRegisterFunction(plugin: Plugin) {\n return (\n turboMount: TurboMount,\n name: string,\n component: T,\n controller?: ControllerConstructor,\n ) => {\n turboMount.register(plugin, name, component, controller);\n };\n}\n","import { Definition } from \"@hotwired/stimulus\";\n\nimport { TurboMount, Plugin } from \"./turbo-mount\";\nimport {\n getShortNameForIndexComponent,\n normalizeFilenameToComponentName,\n generateStimulusIdentifiers,\n} from \"./helpers\";\n\nexport type ComponentModule = { default: never } | never;\n\nexport type ComponentDefinition = {\n filename: string;\n module: ComponentModule;\n};\n\ntype RegisterComponentsProps = {\n plugin: Plugin;\n turboMount: TurboMount;\n components: ComponentDefinition[];\n controllers?: Definition[];\n};\n\n// Registers multiple components with TurboMount, potentially linking them\n// to Stimulus controllers based on naming conventions. Handles index\n// components by registering them under both their full path and the parent\n// directory name if available.\nexport const registerComponentsBase = ({\n plugin,\n turboMount,\n components,\n controllers = [],\n}: RegisterComponentsProps) => {\n const registeredNames = new Set();\n const indexComponentsToRegisterLater: Array<{\n name: string;\n module: ComponentModule;\n }> = [];\n\n for (const { filename, module } of components) {\n const componentName = normalizeFilenameToComponentName(filename);\n const component = module.default ?? module;\n\n registerSingleComponent({\n plugin,\n turboMount,\n availableControllers: controllers,\n componentName,\n component,\n });\n registeredNames.add(componentName);\n\n // If component path ends with /index, prepare for possible registration\n // under the shorter directory name in the second pass.\n const shortName = getShortNameForIndexComponent(componentName);\n if (shortName) {\n indexComponentsToRegisterLater.push({ name: shortName, module });\n }\n }\n\n // Second Pass: Register 'index' components using their shorter directory name\n // This pass ensures that an explicit component (e.g., 'button.js') takes\n // precedence over an index component (e.g., 'button/index.js') if both\n // would resolve to the same short name ('button').\n for (const { name: shortName, module } of indexComponentsToRegisterLater) {\n if (!registeredNames.has(shortName)) {\n const component = module.default ?? module;\n\n registerSingleComponent({\n plugin,\n turboMount,\n availableControllers: controllers,\n componentName: shortName,\n component,\n });\n registeredNames.add(shortName);\n }\n }\n};\n\nconst registerSingleComponent = ({\n plugin,\n turboMount,\n availableControllers,\n componentName,\n component,\n}: {\n plugin: Plugin;\n turboMount: TurboMount;\n availableControllers: Definition[];\n componentName: string;\n component: T;\n}) => {\n const potentialIdentifiers = generateStimulusIdentifiers(componentName);\n\n const controllerDefinition = availableControllers.find(({ identifier }) =>\n potentialIdentifiers.includes(identifier),\n );\n\n if (controllerDefinition) {\n turboMount.register(\n plugin,\n componentName,\n component,\n controllerDefinition.controllerConstructor,\n );\n } else {\n turboMount.register(plugin, componentName, component);\n }\n};\n"],"names":["TurboMountController","Controller","constructor","this","skipPropsChangeCallback","connect","_umountComponentCallback","mountComponent","mountElement","resolvedComponent","componentProps","disconnect","umountComponent","propsValueChanged","propsValue","hasMountTarget","mountTarget","element","resolveMounted","componentValue","component","resolvedPlugin","plugin","undefined","el","Component","props","application","turboMount","resolve","setComponentProps","values","Object","String","targets","camelToKebabCase","str","replace","toLowerCase","normalizeFilenameToComponentName","filename","getShortNameForIndexComponent","componentName","endsWith","TurboMount","components","Map","findOrStartApplication","register","document","addEventListener","event","turboMorphEvent","target","detail","controllerAttr","getAttribute","includes","turboMountController","split","find","name","startsWith","propsAttrName","newPropsValue","newElement","setAttribute","preventDefault","controller","has","Error","set","controllerName","get","hydratedApp","window","Stimulus","Application","start","buildRegisterFunction","registerComponentsBase","controllers","registeredNames","Set","indexComponentsToRegisterLater","module","default","registerSingleComponent","availableControllers","add","shortName","push","potentialIdentifiers","kebabCaseName","generateStimulusIdentifiers","controllerDefinition","identifier","controllerConstructor"],"mappings":"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"}