diff --git a/CHANGELOG.md b/CHANGELOG.md
index d1a3bae..d307dc0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning].
## [Unreleased]
+### Added
+
+- Installation script. ([@skryukov])
+
+### Changed
+
+- [BREAKING] New API without controller inheritance. ([@skryukov])
+ To migrate to the new API:
+ - Replace `new TurboMountReact()` (or any other framework specific constructor) with `new TurboMount()`
+ - Replace `turboMount.register(...)` with `registerComponent(turboMount, ...)`
+ - Replace `turbo_mount_react_component` (or any other framework specific helper) with `turbo_mount`
+ - Also see the new API for plugins and custom controllers in the README.
+
## [0.2.3] - 2024-05-12
### Added
diff --git a/README.md b/README.md
index c2d04b3..76c9b18 100644
--- a/README.md
+++ b/README.md
@@ -88,69 +88,35 @@ Note: Importmap-only mode is quite limited in terms of JavaScript dependencies.
### Initialization
-To begin using `TurboMount`, start by initializing the library and registering the components you intend to use. Below are the steps to set up `TurboMount` with different configurations.
-
-#### Standard Initialization
-
-Import the necessary modules and initialize `TurboMount` with your application and the desired plugin. Here's how to set it up with a React plugin:
-
-```js
-import { Application } from "@hotwired/stimulus";
-import { TurboMount } from "turbo-mount";
-import plugin from "turbo-mount/react";
-import { SketchPicker } from 'react-color';
-
-const application = Application.start();
-const turboMount = new TurboMount({ application, plugin });
-
-turboMount.register('SketchPicker', SketchPicker);
-```
-
-#### Simplified Initialization
-
-If you prefer not to specify the `application` explicitly, `TurboMount` can automatically detect or initialize it. This approach uses the `window.Stimulus` if available; otherwise, it initializes a new Stimulus application:
+To begin using `TurboMount`, start by initializing the library and registering the components you intend to use. Here's how to set it up with a React plugin:
```js
import { TurboMount } from "turbo-mount";
-import plugin from "turbo-mount/react";
-import { SketchPicker } from 'react-color';
+import { registerComponent } from "turbo-mount/react";
+import { HexColorPicker } from 'react-colorful';
-const turboMount = new TurboMount({ plugin });
+const turboMount = new TurboMount(); // or new TurboMount({ application })
-turboMount.register('SketchPicker', SketchPicker);
+registerComponent(turboMount, "HexColorPicker", HexColorPicker);
```
-#### Plugin-Specific Initialization
-
-For a more streamlined setup, you can directly import a specialized version of `TurboMount`:
-
-```js
-import { TurboMountReact } from "turbo-mount/react";
-import { SketchPicker } from 'react-color';
-
-const turboMount = new TurboMountReact();
-
-turboMount.register('SketchPicker', SketchPicker);
-```
+If you prefer not to specify the `application` explicitly, `TurboMount` can automatically detect or initialize it. Turbo Mount uses the `window.Stimulus` if available; otherwise, it initializes a new Stimulus application.
### View Helpers
Use the following helpers to mount components in your views:
```erb
-<%= turbo_mount_component("SketchPicker", framework: "react", props: {color: "#034"}) %>
-
-<%# or using alias %>
-
-<%= turbo_mount_react_component("SketchPicker", props: {color: "#430"}) %>
+<%= turbo_mount("HexColorPicker", props: {color: "#034"}, class: "mb-5") %>
```
This will generate the following HTML:
```html
-
+
```
@@ -162,16 +128,16 @@ This will generate the following HTML:
- Vue: `"turbo-mount/vue"`
- Svelte: `"turbo-mount/svelte"`
-To add support for other frameworks, create a custom controller class extending `TurboMountController` and provide a plugin. See included plugins for examples.
+To add support for other frameworks, create a custom plugin. See included plugins for examples.
### Custom Controllers
To customize component behavior or pass functions as props, create a custom controller:
```js
-import { TurboMountReactController } from "turbo-mount";
+import { TurboMountController } from "turbo-mount";
-export default class extends TurboMountReactController {
+export default class extends TurboMountController {
get componentProps() {
return {
...this.propsValue,
@@ -180,17 +146,19 @@ export default class extends TurboMountReactController {
}
onChange = (color) => {
- this.propsValue = { ...this.propsValue, color: color.hex };
+ // same as this.propsValue = { ...this.propsValue, color };
+ // but skips the rerendering of the component:
+ this.componentProps = { ...this.propsValue, color };
};
}
```
-Then pass this controller to the register method:
+Then pass this controller to the `registerComponent` method:
```js
-import SketchController from "controllers/turbo_mount/sketch_picker_controller";
+import HexColorPickerController from "controllers/turbo_mount/hex_color_picker_controller";
-turboMount.register('SketchPicker', SketchPicker, SketchController);
+registerComponent(turboMount, "HexColorPicker", HexColorPicker, HexColorPickerController);
```
### Vite Integration
@@ -199,7 +167,7 @@ turboMount.register('SketchPicker', SketchPicker, SketchController);
```js
import { TurboMount } from "turbo-mount/react";
-import { registerComponents } from "turbo-mount/vite";
+import { registerComponents } from "turbo-mount/registerComponents/react";
const controllers = import.meta.glob("./**/*_controller.js", { eager: true });
const components = import.meta.glob("/components/**/*.jsx", { eager: true });
@@ -209,9 +177,6 @@ registerComponents({ turboMount, components, controllers });
```
The `registerComponents` helper searches for controllers in the following paths:
-- `controllers/turbo-mount/${framework}/${controllerName}`
-- `controllers/turbo-mount/${framework}-${controllerName}`
-- `controllers/turbo-mount-${framework}-${controllerName}`
- `controllers/turbo-mount/${controllerName}`
- `controllers/turbo-mount-${controllerName}`
@@ -220,7 +185,7 @@ The `registerComponents` helper searches for controllers in the following paths:
To specify a non-root mount target, use the `data-<%= controller_name %>-target="mount"` attribute:
```erb
-<%= turbo_mount_react_component("SketchPicker", props: {color: "#430"}) do |controller_name| %>
+<%= turbo_mount("HexColorPicker", props: {color: "#430"}) do |controller_name| %>
Color picker
-target="mount">
<% end %>
diff --git a/app/assets/javascripts/turbo-mount.js b/app/assets/javascripts/turbo-mount.js
index d32ff3c..f84f56c 100644
--- a/app/assets/javascripts/turbo-mount.js
+++ b/app/assets/javascripts/turbo-mount.js
@@ -1,6 +1,10 @@
import { Controller, Application } from '@hotwired/stimulus';
class TurboMountController extends Controller {
+ constructor() {
+ super(...arguments);
+ this.skipPropsChangeCallback = false;
+ }
connect() {
this._umountComponentCallback || (this._umountComponentCallback = this.mountComponent(this.mountElement, this.resolvedComponent, this.componentProps));
}
@@ -8,6 +12,10 @@ class TurboMountController extends Controller {
this.umountComponent();
}
propsValueChanged() {
+ if (this.skipPropsChangeCallback) {
+ this.skipPropsChangeCallback = false;
+ return;
+ }
this.umountComponent();
this._umountComponentCallback || (this._umountComponentCallback = this.mountComponent(this.mountElement, this.resolvedComponent, this.componentProps));
}
@@ -18,15 +26,25 @@ class TurboMountController extends Controller {
return this.hasMountTarget ? this.mountTarget : this.element;
}
get resolvedComponent() {
- return this.resolveComponent(this.componentValue);
+ return this.resolveMounted(this.componentValue).component;
+ }
+ get resolvedPlugin() {
+ return this.resolveMounted(this.componentValue).plugin;
}
umountComponent() {
this._umountComponentCallback && this._umountComponentCallback();
this._umountComponentCallback = undefined;
}
- resolveComponent(component) {
+ mountComponent(el, Component, props) {
+ return this.resolvedPlugin.mountComponent({ el, Component, props });
+ }
+ resolveMounted(component) {
const app = this.application;
- return app.turboMount[this.framework].resolve(component);
+ return app.turboMount.resolve(component);
+ }
+ setComponentProps(props) {
+ this.skipPropsChangeCallback = true;
+ this.propsValue = props;
}
}
TurboMountController.values = {
@@ -40,34 +58,30 @@ const camelToKebabCase = (str) => {
};
class TurboMount {
- constructor({ application, plugin }) {
- var _a;
+ constructor(props = {}) {
this.components = new Map();
- this.application = this.findOrStartApplication(application);
- this.framework = plugin.framework;
- this.baseController = plugin.controller;
- (_a = this.application).turboMount || (_a.turboMount = {});
- this.application.turboMount[this.framework] = this;
- if (this.baseController) {
- this.application.register(`turbo-mount-${this.framework}`, this.baseController);
- }
+ this.application = this.findOrStartApplication(props.application);
+ this.application.turboMount = this;
+ this.application.register("turbo-mount", TurboMountController);
+ document.addEventListener("turbo:before-morph-element", (event) => {
+ var _a;
+ const turboMorphEvent = event;
+ const { target, detail } = turboMorphEvent;
+ if ((_a = target.getAttribute("data-controller")) === null || _a === void 0 ? void 0 : _a.includes("turbo-mount")) {
+ target.setAttribute("data-turbo-mount-props-value", detail.newElement.getAttribute("data-turbo-mount-props-value") ||
+ "{}");
+ event.preventDefault();
+ }
+ });
}
- findOrStartApplication(hydratedApp) {
- let application = hydratedApp || window.Stimulus;
- if (!application) {
- application = Application.start();
- window.Stimulus = application;
- }
- return application;
- }
- register(name, component, controller) {
- controller || (controller = this.baseController);
+ register(plugin, name, component, controller) {
+ controller || (controller = TurboMountController);
if (this.components.has(name)) {
throw new Error(`Component '${name}' is already registered.`);
}
- this.components.set(name, component);
+ this.components.set(name, { component, plugin });
if (controller) {
- const controllerName = `turbo-mount-${this.framework}-${camelToKebabCase(name)}`;
+ const controllerName = `turbo-mount-${camelToKebabCase(name)}`;
this.application.register(controllerName, controller);
}
}
@@ -78,6 +92,19 @@ class TurboMount {
}
return component;
}
+ findOrStartApplication(hydratedApp) {
+ let application = hydratedApp || window.Stimulus;
+ if (!application) {
+ application = Application.start();
+ window.Stimulus = application;
+ }
+ return application;
+ }
+}
+function buildRegisterFunction(plugin) {
+ return (turboMount, name, component, controller) => {
+ turboMount.register(plugin, name, component, controller);
+ };
}
-export { TurboMount, TurboMountController };
+export { TurboMount, TurboMountController, buildRegisterFunction };
diff --git a/app/assets/javascripts/turbo-mount.min.js b/app/assets/javascripts/turbo-mount.min.js
index 25fc46f..c5d7575 100644
--- a/app/assets/javascripts/turbo-mount.min.js
+++ b/app/assets/javascripts/turbo-mount.min.js
@@ -1,2 +1,2 @@
-import{Controller as t,Application as o}from"@hotwired/stimulus";class n extends t{connect(){this._umountComponentCallback||(this._umountComponentCallback=this.mountComponent(this.mountElement,this.resolvedComponent,this.componentProps))}disconnect(){this.umountComponent()}propsValueChanged(){this.umountComponent(),this._umountComponentCallback||(this._umountComponentCallback=this.mountComponent(this.mountElement,this.resolvedComponent,this.componentProps))}get componentProps(){return this.propsValue}get mountElement(){return this.hasMountTarget?this.mountTarget:this.element}get resolvedComponent(){return this.resolveComponent(this.componentValue)}umountComponent(){this._umountComponentCallback&&this._umountComponentCallback(),this._umountComponentCallback=void 0}resolveComponent(t){return this.application.turboMount[this.framework].resolve(t)}}n.values={props:Object,component:String},n.targets=["mount"];class e{constructor({application:t,plugin:o}){var n;this.components=new Map,this.application=this.findOrStartApplication(t),this.framework=o.framework,this.baseController=o.controller,(n=this.application).turboMount||(n.turboMount={}),this.application.turboMount[this.framework]=this,this.baseController&&this.application.register(`turbo-mount-${this.framework}`,this.baseController)}findOrStartApplication(t){let n=t||window.Stimulus;return n||(n=o.start(),window.Stimulus=n),n}register(t,o,n){if(n||(n=this.baseController),this.components.has(t))throw new Error(`Component '${t}' is already registered.`);if(this.components.set(t,o),n){const o=`turbo-mount-${this.framework}-${e=t,e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}`;this.application.register(o,n)}var e}resolve(t){const o=this.components.get(t);if(!o)throw new Error(`Unknown component: ${t}`);return o}}export{e as TurboMount,n as TurboMountController};
+import{Controller as t,Application as o}from"@hotwired/stimulus";class n extends t{constructor(){super(...arguments),this.skipPropsChangeCallback=!1}connect(){this._umountComponentCallback||(this._umountComponentCallback=this.mountComponent(this.mountElement,this.resolvedComponent,this.componentProps))}disconnect(){this.umountComponent()}propsValueChanged(){this.skipPropsChangeCallback?this.skipPropsChangeCallback=!1:(this.umountComponent(),this._umountComponentCallback||(this._umountComponentCallback=this.mountComponent(this.mountElement,this.resolvedComponent,this.componentProps)))}get componentProps(){return this.propsValue}get mountElement(){return this.hasMountTarget?this.mountTarget:this.element}get resolvedComponent(){return this.resolveMounted(this.componentValue).component}get resolvedPlugin(){return this.resolveMounted(this.componentValue).plugin}umountComponent(){this._umountComponentCallback&&this._umountComponentCallback(),this._umountComponentCallback=void 0}mountComponent(t,o,n){return this.resolvedPlugin.mountComponent({el:t,Component:o,props:n})}resolveMounted(t){return this.application.turboMount.resolve(t)}setComponentProps(t){this.skipPropsChangeCallback=!0,this.propsValue=t}}n.values={props:Object,component:String},n.targets=["mount"];class e{constructor(t={}){this.components=new Map,this.application=this.findOrStartApplication(t.application),this.application.turboMount=this,this.application.register("turbo-mount",n),document.addEventListener("turbo:before-morph-element",(t=>{var o;const n=t,{target:e,detail:s}=n;(null===(o=e.getAttribute("data-controller"))||void 0===o?void 0:o.includes("turbo-mount"))&&(e.setAttribute("data-turbo-mount-props-value",s.newElement.getAttribute("data-turbo-mount-props-value")||"{}"),t.preventDefault())}))}register(t,o,e,s){if(s||(s=n),this.components.has(o))throw new Error(`Component '${o}' is already registered.`);if(this.components.set(o,{component:e,plugin:t}),s){const t=`turbo-mount-${r=o,r.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}`;this.application.register(t,s)}var r}resolve(t){const o=this.components.get(t);if(!o)throw new Error(`Unknown component: ${t}`);return o}findOrStartApplication(t){let n=t||window.Stimulus;return n||(n=o.start(),window.Stimulus=n),n}}function s(t){return(o,n,e,s)=>{o.register(t,n,e,s)}}export{e as TurboMount,n as TurboMountController,s as buildRegisterFunction};
//# sourceMappingURL=turbo-mount.min.js.map
diff --git a/app/assets/javascripts/turbo-mount.min.js.map b/app/assets/javascripts/turbo-mount.min.js.map
index f567b2d..4e09d65 100644
--- a/app/assets/javascripts/turbo-mount.min.js.map
+++ b/app/assets/javascripts/turbo-mount.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"turbo-mount.min.js","sources":["../src/turbo-mount-controller.ts","../src/turbo-mount.ts","../src/helpers.ts"],"sourcesContent":["import { Controller } from \"@hotwired/stimulus\";\nimport { ApplicationWithTurboMount } from \"./turbo-mount\";\n\nexport abstract class TurboMountController
extends Controller {\n static values = {\n props: Object,\n component: String,\n };\n static targets = [\"mount\"];\n\n declare readonly propsValue: object;\n declare readonly componentValue: string;\n declare readonly hasMountTarget: boolean;\n declare readonly mountTarget: Element;\n\n abstract framework: string;\n\n abstract mountComponent(el: Element, Component: T, props: object): () => void;\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 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.resolveComponent(this.componentValue);\n }\n\n umountComponent() {\n this._umountComponentCallback && this._umountComponentCallback();\n this._umountComponentCallback = undefined;\n }\n\n resolveComponent(component: string): T {\n const app = this.application as ApplicationWithTurboMount;\n return app.turboMount[this.framework].resolve(component);\n }\n}\n","import { Application, ControllerConstructor } from \"@hotwired/stimulus\";\n\nimport { camelToKebabCase } from \"./helpers\";\n\ndeclare global {\n interface Window {\n Stimulus?: Application;\n }\n}\n\nexport interface ApplicationWithTurboMount extends Application {\n turboMount: { [framework: string]: TurboMount };\n}\n\nexport type Plugin = {\n framework: string;\n controller: ControllerConstructor;\n};\n\nexport type TurboMountProps = {\n application?: Application;\n plugin: Plugin;\n};\n\nexport class TurboMount {\n components: Map;\n application: ApplicationWithTurboMount;\n framework: string;\n baseController?: ControllerConstructor;\n\n constructor({ application, plugin }: TurboMountProps) {\n this.components = new Map();\n this.application = this.findOrStartApplication(application);\n this.framework = plugin.framework;\n this.baseController = plugin.controller;\n\n this.application.turboMount ||= {};\n this.application.turboMount[this.framework] = this;\n\n if (this.baseController) {\n this.application.register(\n `turbo-mount-${this.framework}`,\n this.baseController,\n );\n }\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 register(name: string, component: T, controller?: ControllerConstructor) {\n controller ||= this.baseController;\n if (this.components.has(name)) {\n throw new Error(`Component '${name}' is already registered.`);\n }\n this.components.set(name, component);\n\n if (controller) {\n const controllerName = `turbo-mount-${this.framework}-${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","export const camelToKebabCase = (str: string) => {\n return str.replace(/([a-z])([A-Z])/g, \"$1-$2\").toLowerCase();\n};\n"],"names":["TurboMountController","Controller","connect","this","_umountComponentCallback","mountComponent","mountElement","resolvedComponent","componentProps","disconnect","umountComponent","propsValueChanged","propsValue","hasMountTarget","mountTarget","element","resolveComponent","componentValue","undefined","component","application","turboMount","framework","resolve","values","props","Object","String","targets","TurboMount","constructor","plugin","components","Map","findOrStartApplication","baseController","controller","_a","register","hydratedApp","window","Stimulus","Application","start","name","has","Error","set","controllerName","str","replace","toLowerCase","get"],"mappings":"iEAGM,MAAgBA,UAAgCC,EAkBpD,OAAAC,GACEC,KAAKC,2BAALD,KAAKC,yBAA6BD,KAAKE,eACrCF,KAAKG,aACLH,KAAKI,kBACLJ,KAAKK,gBAER,CAED,UAAAC,GACEN,KAAKO,iBACN,CAED,iBAAAC,GACER,KAAKO,kBACLP,KAAKC,2BAALD,KAAKC,yBAA6BD,KAAKE,eACrCF,KAAKG,aACLH,KAAKI,kBACLJ,KAAKK,gBAER,CAED,kBAAIA,GACF,OAAOL,KAAKS,UACb,CAED,gBAAIN,GACF,OAAOH,KAAKU,eAAiBV,KAAKW,YAAcX,KAAKY,OACtD,CAED,qBAAIR,GACF,OAAOJ,KAAKa,iBAAiBb,KAAKc,eACnC,CAED,eAAAP,GACEP,KAAKC,0BAA4BD,KAAKC,2BACtCD,KAAKC,8BAA2Bc,CACjC,CAED,gBAAAF,CAAiBG,GAEf,OADYhB,KAAKiB,YACNC,WAAWlB,KAAKmB,WAAWC,QAAQJ,EAC/C,EA1DMnB,EAAAwB,OAAS,CACdC,MAAOC,OACPP,UAAWQ,QAEN3B,EAAA4B,QAAU,CAAC,eCgBPC,EAMX,WAAAC,EAAYV,YAAEA,EAAWW,OAAEA,UACzB5B,KAAK6B,WAAa,IAAIC,IACtB9B,KAAKiB,YAAcjB,KAAK+B,uBAAuBd,GAC/CjB,KAAKmB,UAAYS,EAAOT,UACxBnB,KAAKgC,eAAiBJ,EAAOK,YAE7BC,EAAAlC,KAAKiB,aAAYC,aAAAgB,EAAAhB,WAAe,CAAA,GAChClB,KAAKiB,YAAYC,WAAWlB,KAAKmB,WAAanB,KAE1CA,KAAKgC,gBACPhC,KAAKiB,YAAYkB,SACf,eAAenC,KAAKmB,YACpBnB,KAAKgC,eAGV,CAEO,sBAAAD,CAAuBK,GAC7B,IAAInB,EAAcmB,GAAeC,OAAOC,SAMxC,OAJKrB,IACHA,EAAcsB,EAAYC,QAC1BH,OAAOC,SAAWrB,GAEbA,CACR,CAED,QAAAkB,CAASM,EAAczB,EAAciB,GAEnC,GADAA,IAAAA,EAAejC,KAAKgC,gBAChBhC,KAAK6B,WAAWa,IAAID,GACtB,MAAM,IAAIE,MAAM,cAAcF,6BAIhC,GAFAzC,KAAK6B,WAAWe,IAAIH,EAAMzB,GAEtBiB,EAAY,CACd,MAAMY,EAAiB,eAAe7C,KAAKmB,aCjEhB2B,EDiE8CL,EChEtEK,EAAIC,QAAQ,kBAAmB,SAASC,gBDiE3ChD,KAAKiB,YAAYkB,SAASU,EAAgBZ,EAC3C,CCnE2B,IAACa,CDoE9B,CAED,OAAA1B,CAAQqB,GACN,MAAMzB,EAAYhB,KAAK6B,WAAWoB,IAAIR,GACtC,IAAKzB,EACH,MAAM,IAAI2B,MAAM,sBAAsBF,KAExC,OAAOzB,CACR"}
\ No newline at end of file
+{"version":3,"file":"turbo-mount.min.js","sources":["../src/turbo-mount-controller.ts","../src/turbo-mount.ts","../src/helpers.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 && 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","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 if (target.getAttribute(\"data-controller\")?.includes(\"turbo-mount\")) {\n target.setAttribute(\n \"data-turbo-mount-props-value\",\n detail.newElement.getAttribute(\"data-turbo-mount-props-value\") ||\n \"{}\",\n );\n event.preventDefault();\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","export const camelToKebabCase = (str: string) => {\n return str.replace(/([a-z])([A-Z])/g, \"$1-$2\").toLowerCase();\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","TurboMount","components","Map","findOrStartApplication","register","document","addEventListener","event","turboMorphEvent","target","detail","_a","getAttribute","includes","setAttribute","newElement","preventDefault","name","controller","has","Error","set","controllerName","str","replace","toLowerCase","get","hydratedApp","window","Stimulus","Application","start","buildRegisterFunction"],"mappings":"iEAGM,MAAOA,UAA6BC,EAA1C,WAAAC,uBAOUC,KAAuBC,yBAAG,CAsEnC,CA7DC,OAAAC,GACEF,KAAKG,2BAALH,KAAKG,yBAA6BH,KAAKI,eACrCJ,KAAKK,aACLL,KAAKM,kBACLN,KAAKO,gBAER,CAED,UAAAC,GACER,KAAKS,iBACN,CAED,iBAAAC,GAEMV,KAAKC,wBACPD,KAAKC,yBAA0B,GAIjCD,KAAKS,kBACLT,KAAKG,2BAALH,KAAKG,yBAA6BH,KAAKI,eACrCJ,KAAKK,aACLL,KAAKM,kBACLN,KAAKO,iBAER,CAED,kBAAIA,GACF,OAAOP,KAAKW,UACb,CAED,gBAAIN,GACF,OAAOL,KAAKY,eAAiBZ,KAAKa,YAAcb,KAAKc,OACtD,CAED,qBAAIR,GACF,OAAON,KAAKe,eAAef,KAAKgB,gBAAgBC,SACjD,CAED,kBAAIC,GACF,OAAOlB,KAAKe,eAAef,KAAKgB,gBAAgBG,MACjD,CAED,eAAAV,GACET,KAAKG,0BAA4BH,KAAKG,2BACtCH,KAAKG,8BAA2BiB,CACjC,CAED,cAAAhB,CAAeiB,EAAaC,EAAoBC,GAC9C,OAAOvB,KAAKkB,eAAed,eAAe,CAAEiB,KAAIC,YAAWC,SAC5D,CAED,cAAAR,CAAeE,GAEb,OADYjB,KAAKwB,YACNC,WAAWC,QAAQT,EAC/B,CAED,iBAAAU,CAAkBJ,GAChBvB,KAAKC,yBAA0B,EAC/BD,KAAKW,WAAaY,CACnB,EA3EM1B,EAAA+B,OAAS,CACdL,MAAOM,OACPZ,UAAWa,QAENjC,EAAAkC,QAAU,CAAC,eC8BPC,EAKX,WAAAjC,CAAYwB,EAAyB,IACnCvB,KAAKiC,WAAa,IAAIC,IACtBlC,KAAKwB,YAAcxB,KAAKmC,uBAAuBZ,EAAMC,aACrDxB,KAAKwB,YAAYC,WAAazB,KAC9BA,KAAKwB,YAAYY,SAAS,cAAevC,GAEzCwC,SAASC,iBAAiB,8BAA+BC,UACvD,MAAMC,EAAkBD,GAClBE,OAAEA,EAAMC,OAAEA,GAAWF,GAEe,QAAtCG,EAAAF,EAAOG,aAAa,0BAAkB,IAAAD,OAAA,EAAAA,EAAEE,SAAS,kBACnDJ,EAAOK,aACL,+BACAJ,EAAOK,WAAWH,aAAa,iCAC7B,MAEJL,EAAMS,iBACP,GAEJ,CAED,QAAAZ,CACEjB,EACA8B,EACAhC,EACAiC,GAGA,GADAA,IAAAA,EAAerD,GACXG,KAAKiC,WAAWkB,IAAIF,GACtB,MAAM,IAAIG,MAAM,cAAcH,6BAIhC,GAFAjD,KAAKiC,WAAWoB,IAAIJ,EAAM,CAAEhC,YAAWE,WAEnC+B,EAAY,CACd,MAAMI,EAAiB,eC7EIC,ED6E4BN,EC5EpDM,EAAIC,QAAQ,kBAAmB,SAASC,gBD6E3CzD,KAAKwB,YAAYY,SAASkB,EAAgBJ,EAC3C,CC/E2B,IAACK,CDgF9B,CAED,OAAA7B,CAAQuB,GACN,MAAMhC,EAAYjB,KAAKiC,WAAWyB,IAAIT,GACtC,IAAKhC,EACH,MAAM,IAAImC,MAAM,sBAAsBH,KAExC,OAAOhC,CACR,CAEO,sBAAAkB,CAAuBwB,GAC7B,IAAInC,EAAcmC,GAAeC,OAAOC,SAMxC,OAJKrC,IACHA,EAAcsC,EAAYC,QAC1BH,OAAOC,SAAWrC,GAEbA,CACR,EAGG,SAAUwC,EAAyB7C,GACvC,MAAO,CACLM,EACAwB,EACAhC,EACAiC,KAEAzB,EAAWW,SAASjB,EAAQ8B,EAAMhC,EAAWiC,EAAW,CAE5D"}
\ No newline at end of file
diff --git a/app/assets/javascripts/turbo-mount/react.js b/app/assets/javascripts/turbo-mount/react.js
index 390e6c7..7588ca6 100644
--- a/app/assets/javascripts/turbo-mount/react.js
+++ b/app/assets/javascripts/turbo-mount/react.js
@@ -1,29 +1,18 @@
-import { TurboMountController, TurboMount } from 'turbo-mount';
+import { buildRegisterFunction } from 'turbo-mount';
+export { TurboMount } from 'turbo-mount';
import { createElement } from 'react';
import { createRoot } from 'react-dom/client';
-class TurboMountReactController extends TurboMountController {
- constructor() {
- super(...arguments);
- this.framework = "react";
- }
- mountComponent(el, Component, props) {
+const plugin = {
+ mountComponent: (mountProps) => {
+ const { el, Component, props } = mountProps;
const root = createRoot(el);
root.render(createElement(Component, props));
return () => {
root.unmount();
};
- }
-}
-
-const plugin = {
- framework: "react",
- controller: TurboMountReactController,
+ },
};
-class TurboMountReact extends TurboMount {
- constructor(props) {
- super(Object.assign(Object.assign({}, props), { plugin }));
- }
-}
+const registerComponent = buildRegisterFunction(plugin);
-export { TurboMountReact as TurboMount, TurboMountReact, plugin as default };
+export { plugin as default, registerComponent };
diff --git a/app/assets/javascripts/turbo-mount/react.min.js b/app/assets/javascripts/turbo-mount/react.min.js
index 516c748..fa48a5b 100644
--- a/app/assets/javascripts/turbo-mount/react.min.js
+++ b/app/assets/javascripts/turbo-mount/react.min.js
@@ -1,2 +1,2 @@
-import{TurboMountController as r,TurboMount as t}from"turbo-mount";import{createElement as o}from"react";import{createRoot as e}from"react-dom/client";const n={framework:"react",controller:class extends r{constructor(){super(...arguments),this.framework="react"}mountComponent(r,t,n){const s=e(r);return s.render(o(t,n)),()=>{s.unmount()}}}};class s extends t{constructor(r){super(Object.assign(Object.assign({},r),{plugin:n}))}}export{s as TurboMount,s as TurboMountReact,n as default};
+import{buildRegisterFunction as o}from"turbo-mount";export{TurboMount}from"turbo-mount";import{createElement as t}from"react";import{createRoot as r}from"react-dom/client";const n={mountComponent:o=>{const{el:n,Component:m,props:e}=o,u=r(n);return u.render(t(m,e)),()=>{u.unmount()}}},m=o(n);export{n as default,m as registerComponent};
//# sourceMappingURL=react.min.js.map
diff --git a/app/assets/javascripts/turbo-mount/react.min.js.map b/app/assets/javascripts/turbo-mount/react.min.js.map
index 64ce64a..55e0d47 100644
--- a/app/assets/javascripts/turbo-mount/react.min.js.map
+++ b/app/assets/javascripts/turbo-mount/react.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"react.min.js","sources":["../../src/plugins/react/index.ts","../../src/plugins/react/turbo-mount-react-controller.ts"],"sourcesContent":["import { Plugin, TurboMount, TurboMountProps } from \"turbo-mount\";\n\nimport { TurboMountReactController } from \"./turbo-mount-react-controller\";\n\nconst plugin: Plugin = {\n framework: \"react\",\n controller: TurboMountReactController,\n};\n\nexport class TurboMountReact extends TurboMount {\n constructor(props: Omit) {\n super({ ...props, plugin });\n }\n}\n\nexport { TurboMountReact as TurboMount };\n\nexport default plugin;\n","import { ComponentType, createElement } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport { TurboMountController } from \"turbo-mount\";\n\nexport class TurboMountReactController extends TurboMountController {\n framework = \"react\";\n\n mountComponent(el: Element, Component: ComponentType, props: object) {\n const root = createRoot(el);\n root.render(createElement(Component, props));\n\n return () => {\n root.unmount();\n };\n }\n}\n"],"names":["plugin","framework","controller","TurboMountController","constructor","this","mountComponent","el","Component","props","root","createRoot","render","createElement","unmount","TurboMountReact","TurboMount","super","Object","assign"],"mappings":"uJAIA,MAAMA,EAAiB,CACrBC,UAAW,QACXC,WCFI,cAAyCC,EAA/C,WAAAC,uBACEC,KAASJ,UAAG,OAUb,CARC,cAAAK,CAAeC,EAAaC,EAA0BC,GACpD,MAAMC,EAAOC,EAAWJ,GAGxB,OAFAG,EAAKE,OAAOC,EAAcL,EAAWC,IAE9B,KACLC,EAAKI,SAAS,CAEjB,IDLG,MAAOC,UAA2BC,EACtC,WAAAZ,CAAYK,GACVQ,MAAWC,OAAAC,OAAAD,OAAAC,OAAA,GAAAV,GAAO,CAAAT,WACnB"}
\ No newline at end of file
+{"version":3,"file":"react.min.js","sources":["../../src/plugins/react/index.ts"],"sourcesContent":["import { buildRegisterFunction, Plugin, TurboMount } from \"turbo-mount\";\nimport { ComponentType, createElement } from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\nconst plugin: Plugin = {\n mountComponent: (mountProps) => {\n const { el, Component, props } = mountProps;\n const root = createRoot(el);\n root.render(createElement(Component, props));\n\n return () => {\n root.unmount();\n };\n },\n};\n\nconst registerComponent = buildRegisterFunction(plugin);\n\nexport { TurboMount, registerComponent };\n\nexport default plugin;\n"],"names":["plugin","mountComponent","mountProps","el","Component","props","root","createRoot","render","createElement","unmount","registerComponent","buildRegisterFunction"],"mappings":"4KAIA,MAAMA,EAAgC,CACpCC,eAAiBC,IACf,MAAMC,GAAEA,EAAEC,UAAEA,EAASC,MAAEA,GAAUH,EAC3BI,EAAOC,EAAWJ,GAGxB,OAFAG,EAAKE,OAAOC,EAAcL,EAAWC,IAE9B,KACLC,EAAKI,SAAS,CACf,GAICC,EAAoBC,EAAsBZ"}
\ No newline at end of file
diff --git a/app/assets/javascripts/turbo-mount/svelte.js b/app/assets/javascripts/turbo-mount/svelte.js
index 3f357fb..ce0bb4e 100644
--- a/app/assets/javascripts/turbo-mount/svelte.js
+++ b/app/assets/javascripts/turbo-mount/svelte.js
@@ -1,26 +1,15 @@
-import { TurboMountController, TurboMount } from 'turbo-mount';
+import { buildRegisterFunction } from 'turbo-mount';
+export { TurboMount } from 'turbo-mount';
-class TurboMountSvelteController extends TurboMountController {
- constructor() {
- super(...arguments);
- this.framework = "svelte";
- }
- mountComponent(el, Component, props) {
+const plugin = {
+ mountComponent: (mountProps) => {
+ const { el, Component, props } = mountProps;
const component = new Component({ target: el, props });
return () => {
component.$destroy();
};
- }
-}
-
-const plugin = {
- framework: "svelte",
- controller: TurboMountSvelteController,
+ },
};
-class TurboMountSvelte extends TurboMount {
- constructor(props) {
- super(Object.assign(Object.assign({}, props), { plugin }));
- }
-}
+const registerComponent = buildRegisterFunction(plugin);
-export { TurboMountSvelte as TurboMount, TurboMountSvelte, plugin as default };
+export { plugin as default, registerComponent };
diff --git a/app/assets/javascripts/turbo-mount/svelte.min.js b/app/assets/javascripts/turbo-mount/svelte.min.js
index fc8fa73..d6bea7b 100644
--- a/app/assets/javascripts/turbo-mount/svelte.min.js
+++ b/app/assets/javascripts/turbo-mount/svelte.min.js
@@ -1,2 +1,2 @@
-import{TurboMountController as t,TurboMount as s}from"turbo-mount";const e={framework:"svelte",controller:class extends t{constructor(){super(...arguments),this.framework="svelte"}mountComponent(t,s,e){const o=new s({target:t,props:e});return()=>{o.$destroy()}}}};class o extends s{constructor(t){super(Object.assign(Object.assign({},t),{plugin:e}))}}export{o as TurboMount,o as TurboMountSvelte,e as default};
+import{buildRegisterFunction as o}from"turbo-mount";export{TurboMount}from"turbo-mount";const t={mountComponent:o=>{const{el:t,Component:r,props:n}=o,e=new r({target:t,props:n});return()=>{e.$destroy()}}},r=o(t);export{t as default,r as registerComponent};
//# sourceMappingURL=svelte.min.js.map
diff --git a/app/assets/javascripts/turbo-mount/svelte.min.js.map b/app/assets/javascripts/turbo-mount/svelte.min.js.map
index 3adaf68..bc60d2d 100644
--- a/app/assets/javascripts/turbo-mount/svelte.min.js.map
+++ b/app/assets/javascripts/turbo-mount/svelte.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"svelte.min.js","sources":["../../src/plugins/svelte/index.ts","../../src/plugins/svelte/turbo-mount-svelte-controller.ts"],"sourcesContent":["import { Plugin, TurboMount, TurboMountProps } from \"turbo-mount\";\n\nimport { TurboMountSvelteController } from \"./turbo-mount-svelte-controller\";\n\nconst plugin: Plugin = {\n framework: \"svelte\",\n controller: TurboMountSvelteController,\n};\n\nexport class TurboMountSvelte extends TurboMount {\n constructor(props: Omit) {\n super({ ...props, plugin });\n }\n}\n\nexport { TurboMountSvelte as TurboMount };\nexport default plugin;\n","import { ComponentType } from \"svelte\";\nimport { TurboMountController } from \"turbo-mount\";\n\nexport class TurboMountSvelteController extends TurboMountController {\n framework = \"svelte\";\n\n mountComponent(el: Element, Component: ComponentType, props: object) {\n const component = new Component({ target: el, props });\n\n return () => {\n component.$destroy();\n };\n }\n}\n"],"names":["plugin","framework","controller","TurboMountController","constructor","this","mountComponent","el","Component","props","component","target","$destroy","TurboMountSvelte","TurboMount","super","Object","assign"],"mappings":"mEAIA,MAAMA,EAAiB,CACrBC,UAAW,SACXC,WCHI,cAA0CC,EAAhD,WAAAC,uBACEC,KAASJ,UAAG,QASb,CAPC,cAAAK,CAAeC,EAAaC,EAA0BC,GACpD,MAAMC,EAAY,IAAIF,EAAU,CAAEG,OAAQJ,EAAIE,UAE9C,MAAO,KACLC,EAAUE,UAAU,CAEvB,IDHG,MAAOC,UAA4BC,EACvC,WAAAV,CAAYK,GACVM,MAAWC,OAAAC,OAAAD,OAAAC,OAAA,GAAAR,GAAO,CAAAT,WACnB"}
\ No newline at end of file
+{"version":3,"file":"svelte.min.js","sources":["../../src/plugins/svelte/index.ts"],"sourcesContent":["import { buildRegisterFunction, Plugin, TurboMount } from \"turbo-mount\";\nimport { ComponentType } from \"svelte\";\n\nconst plugin: Plugin = {\n mountComponent: (mountProps) => {\n const { el, Component, props } = mountProps;\n const component = new Component({ target: el, props });\n\n return () => {\n component.$destroy();\n };\n },\n};\n\nconst registerComponent = buildRegisterFunction(plugin);\n\nexport { TurboMount, registerComponent };\n\nexport default plugin;\n"],"names":["plugin","mountComponent","mountProps","el","Component","props","component","target","$destroy","registerComponent","buildRegisterFunction"],"mappings":"wFAGA,MAAMA,EAAgC,CACpCC,eAAiBC,IACf,MAAMC,GAAEA,EAAEC,UAAEA,EAASC,MAAEA,GAAUH,EAC3BI,EAAY,IAAIF,EAAU,CAAEG,OAAQJ,EAAIE,UAE9C,MAAO,KACLC,EAAUE,UAAU,CACrB,GAICC,EAAoBC,EAAsBV"}
\ No newline at end of file
diff --git a/app/assets/javascripts/turbo-mount/vue.js b/app/assets/javascripts/turbo-mount/vue.js
index 104c25e..d8eb055 100644
--- a/app/assets/javascripts/turbo-mount/vue.js
+++ b/app/assets/javascripts/turbo-mount/vue.js
@@ -1,28 +1,17 @@
-import { TurboMountController, TurboMount } from 'turbo-mount';
+import { buildRegisterFunction } from 'turbo-mount';
+export { TurboMount } from 'turbo-mount';
import { createApp } from 'vue';
-class TurboMountVueController extends TurboMountController {
- constructor() {
- super(...arguments);
- this.framework = "vue";
- }
- mountComponent(el, Component, props) {
+const plugin = {
+ mountComponent: (mountProps) => {
+ const { el, Component, props } = mountProps;
const app = createApp(Component, props);
app.mount(el);
return () => {
app.unmount();
};
- }
-}
-
-const plugin = {
- framework: "vue",
- controller: TurboMountVueController,
+ },
};
-class TurboMountVue extends TurboMount {
- constructor(props) {
- super(Object.assign(Object.assign({}, props), { plugin }));
- }
-}
+const registerComponent = buildRegisterFunction(plugin);
-export { TurboMountVue as TurboMount, TurboMountVue, plugin as default };
+export { plugin as default, registerComponent };
diff --git a/app/assets/javascripts/turbo-mount/vue.min.js b/app/assets/javascripts/turbo-mount/vue.min.js
index 966f1a4..48a1777 100644
--- a/app/assets/javascripts/turbo-mount/vue.min.js
+++ b/app/assets/javascripts/turbo-mount/vue.min.js
@@ -1,2 +1,2 @@
-import{TurboMountController as o,TurboMount as t}from"turbo-mount";import{createApp as r}from"vue";const n={framework:"vue",controller:class extends o{constructor(){super(...arguments),this.framework="vue"}mountComponent(o,t,n){const s=r(t,n);return s.mount(o),()=>{s.unmount()}}}};class s extends t{constructor(o){super(Object.assign(Object.assign({},o),{plugin:n}))}}export{s as TurboMount,s as TurboMountVue,n as default};
+import{buildRegisterFunction as o}from"turbo-mount";export{TurboMount}from"turbo-mount";import{createApp as t}from"vue";const n={mountComponent:o=>{const{el:n,Component:r,props:u}=o,m=t(r,u);return m.mount(n),()=>{m.unmount()}}},r=o(n);export{n as default,r as registerComponent};
//# sourceMappingURL=vue.min.js.map
diff --git a/app/assets/javascripts/turbo-mount/vue.min.js.map b/app/assets/javascripts/turbo-mount/vue.min.js.map
index ebc9cf0..9cc6a29 100644
--- a/app/assets/javascripts/turbo-mount/vue.min.js.map
+++ b/app/assets/javascripts/turbo-mount/vue.min.js.map
@@ -1 +1 @@
-{"version":3,"file":"vue.min.js","sources":["../../src/plugins/vue/index.ts","../../src/plugins/vue/turbo-mount-vue-controller.ts"],"sourcesContent":["import { Plugin, TurboMount, TurboMountProps } from \"turbo-mount\";\n\nimport { TurboMountVueController } from \"./turbo-mount-vue-controller\";\n\nconst plugin: Plugin = {\n framework: \"vue\",\n controller: TurboMountVueController,\n};\n\nexport class TurboMountVue extends TurboMount {\n constructor(props: Omit) {\n super({ ...props, plugin });\n }\n}\n\nexport { TurboMountVue as TurboMount };\n\nexport default plugin;\n","import { createApp, App } from \"vue\";\nimport { TurboMountController } from \"turbo-mount\";\n\nexport class TurboMountVueController extends TurboMountController {\n framework = \"vue\";\n\n mountComponent(el: Element, Component: App, props: object) {\n const app = createApp(Component, props as Record);\n app.mount(el);\n\n return () => {\n app.unmount();\n };\n }\n}\n"],"names":["plugin","framework","controller","TurboMountController","constructor","this","mountComponent","el","Component","props","app","createApp","mount","unmount","TurboMountVue","TurboMount","super","Object","assign"],"mappings":"mGAIA,MAAMA,EAAiB,CACrBC,UAAW,MACXC,WCHI,cAAuCC,EAA7C,WAAAC,uBACEC,KAASJ,UAAG,KAUb,CARC,cAAAK,CAAeC,EAAaC,EAAgBC,GAC1C,MAAMC,EAAMC,EAAUH,EAAWC,GAGjC,OAFAC,EAAIE,MAAML,GAEH,KACLG,EAAIG,SAAS,CAEhB,IDJG,MAAOC,UAAyBC,EACpC,WAAAX,CAAYK,GACVO,MAAWC,OAAAC,OAAAD,OAAAC,OAAA,GAAAT,GAAO,CAAAT,WACnB"}
\ No newline at end of file
+{"version":3,"file":"vue.min.js","sources":["../../src/plugins/vue/index.ts"],"sourcesContent":["import { buildRegisterFunction, Plugin, TurboMount } from \"turbo-mount\";\nimport { App, createApp } from \"vue\";\n\nconst plugin: Plugin = {\n mountComponent: (mountProps) => {\n const { el, Component, props } = mountProps;\n const app = createApp(Component, props as Record);\n app.mount(el);\n\n return () => {\n app.unmount();\n };\n },\n};\n\nconst registerComponent = buildRegisterFunction(plugin);\n\nexport { TurboMount, registerComponent };\n\nexport default plugin;\n"],"names":["plugin","mountComponent","mountProps","el","Component","props","app","createApp","mount","unmount","registerComponent","buildRegisterFunction"],"mappings":"wHAGA,MAAMA,EAAsB,CAC1BC,eAAiBC,IACf,MAAMC,GAAEA,EAAEC,UAAEA,EAASC,MAAEA,GAAUH,EAC3BI,EAAMC,EAAUH,EAAWC,GAGjC,OAFAC,EAAIE,MAAML,GAEH,KACLG,EAAIG,SAAS,CACd,GAICC,EAAoBC,EAAsBX"}
\ No newline at end of file
diff --git a/lib/generators/turbo_mount/install/turbo-mount.js.tt b/lib/generators/turbo_mount/install/turbo-mount.js.tt
index fcfc4cb..c04765a 100644
--- a/lib/generators/turbo_mount/install/turbo-mount.js.tt
+++ b/lib/generators/turbo_mount/install/turbo-mount.js.tt
@@ -1,15 +1,17 @@
-import { TurboMount<%= framework.capitalize %> } from "turbo-mount/<%= framework %>";
+import { TurboMount } from "turbo-mount";
+import { registerComponent } from "turbo-mount/<%= framework %>";
-const turboMount = new TurboMount<%= framework.capitalize %>();
+const turboMount = new TurboMount();
// to register a component use:
-// turboMount.register("Hello", Hello); // where Hello is the imported the component
+// registerComponent(turboMount, "Hello", Hello); // where Hello is the imported the component
// to override the default controller use:
-// turboMount.register("Hello", Hello, HelloController); // where HelloController is a Stimulus controller extended from TurboMount<%= framework.capitalize %>Controller
+// registerComponent(turboMount, "Hello", Hello, HelloController); // where HelloController is a Stimulus controller extended from TurboMountController
<%- if vite? -%>
// If you want to automatically register components use:
+// import { registerComponents } from "turbo-mount/registerComponents/<%= framework %>";
// const controllers = import.meta.glob("/controllers/**/*_controller.js", { eager: true });
// const components = import.meta.glob("/components/**/*.jsx", { eager: true });
// registerComponents({ turboMount, components, controllers });
diff --git a/lib/generators/turbo_mount/install_generator.rb b/lib/generators/turbo_mount/install_generator.rb
index 2df7475..dbaacad 100644
--- a/lib/generators/turbo_mount/install_generator.rb
+++ b/lib/generators/turbo_mount/install_generator.rb
@@ -65,8 +65,8 @@ def install_importmap
say "Pinning Turbo Mount to the importmap"
append_to_file "config/importmap.rb", %(pin "turbo-mount", to: "turbo-mount.min.js"\n)
- append_to_file "config/importmap.rb", %(pin "turbo-mount-initializer"\n)
append_to_file "config/importmap.rb", %(pin "turbo-mount/#{framework}", to: "turbo-mount/#{framework}.min.js"\n)
+ append_to_file "config/importmap.rb", %(pin "turbo-mount-initializer"\n)
say "Pinning framework dependencies to the importmap"
run "bin/importmap pin #{FRAMEWORKS[framework][:pins]}"
diff --git a/lib/turbo/mount/helpers.rb b/lib/turbo/mount/helpers.rb
index 4726411..391840e 100644
--- a/lib/turbo/mount/helpers.rb
+++ b/lib/turbo/mount/helpers.rb
@@ -3,10 +3,10 @@
module Turbo
module Mount
module Helpers
- def turbo_mount_component(component_name, framework:, props: {}, tag: "div", **attrs, &block)
+ def turbo_mount(component_name, props: {}, tag: "div", **attrs, &block)
raise TypeError, "Component name expected" unless component_name.is_a? String
- controller_name = "turbo-mount-#{framework}-#{component_name.underscore.dasherize}"
+ controller_name = "turbo-mount-#{component_name.underscore.dasherize}"
attrs["data-controller"] = controller_name
prefix = "data-#{controller_name}"
attrs["#{prefix}-component-value"] = component_name
@@ -16,18 +16,7 @@ def turbo_mount_component(component_name, framework:, props: {}, tag: "div", **a
content_tag(tag, nil, attrs) { capture(controller_name, &block) }
end
-
- def turbo_mount_react_component(component_name, **attrs, &block)
- turbo_mount_component(component_name, framework: "react", **attrs, &block)
- end
-
- def turbo_mount_svelte_component(component_name, **attrs, &block)
- turbo_mount_component(component_name, framework: "svelte", **attrs, &block)
- end
-
- def turbo_mount_vue_component(component_name, **attrs, &block)
- turbo_mount_component(component_name, framework: "vue", **attrs, &block)
- end
+ alias_method :turbo_mount_component, :turbo_mount
end
end
end
diff --git a/packages/turbo-mount/package.json b/packages/turbo-mount/package.json
index c52e572..7b9c330 100644
--- a/packages/turbo-mount/package.json
+++ b/packages/turbo-mount/package.json
@@ -28,7 +28,10 @@
"./react": "./dist/plugins/react.js",
"./svelte": "./dist/plugins/svelte.js",
"./vue": "./dist/plugins/vue.js",
- "./vite": "./dist/vite.js",
+ "./registerComponents/react": "./dist/registerComponents/react.js",
+ "./registerComponents/svelte": "./dist/registerComponents/svelte.js",
+ "./registerComponents/vue": "./dist/registerComponents/vue.js",
+ "./registerComponents": "./dist/registerComponents.js",
".": "./dist/turbo-mount.js"
},
"files": [
diff --git a/packages/turbo-mount/rollup.config.js b/packages/turbo-mount/rollup.config.js
index bc2cc2b..00f3192 100644
--- a/packages/turbo-mount/rollup.config.js
+++ b/packages/turbo-mount/rollup.config.js
@@ -4,13 +4,17 @@ import alias from '@rollup/plugin-alias';
import typescript from "@rollup/plugin-typescript";
import {terser} from "rollup-plugin-terser";
+const pluginsPath = path.resolve(__dirname, 'src', 'plugins');
+
const external = [
"@hotwired/stimulus",
"react",
"react-dom/client",
"vue",
"turbo-mount",
- "stimulus-vite-helpers"
+ "turbo-mount/registerComponents",
+ "stimulus-vite-helpers",
+ ...fs.readdirSync(pluginsPath).map(plugin => `turbo-mount/${plugin}`)
]
const plugins = [
@@ -22,19 +26,25 @@ const plugins = [
}),
]
-const pluginsPath = path.resolve(__dirname, 'src', 'plugins');
-const entrypoints = fs.readdirSync(pluginsPath).map(plugin => {
- const input = path.join(pluginsPath, plugin, 'index.ts');
- const output = path.join('dist', 'plugins', `${plugin}.js`);
- return {input, output}
+const entrypoints = fs.readdirSync(pluginsPath).flatMap(plugin => {
+ return [
+ {
+ input: path.join(pluginsPath, plugin, 'index.ts'),
+ output: path.join('dist', 'plugins', `${plugin}.js`)
+ },
+ {
+ input: path.join(pluginsPath, plugin, 'registerComponents.ts'),
+ output: path.join('dist', 'registerComponents', `${plugin}.js`)
+ }
+ ]
});
entrypoints.unshift({
input: path.join("src", "index.ts"),
output: path.join("dist", "turbo-mount.js")
-},{
- input: path.join("src", "vite.ts"),
- output: path.join("dist", "vite.js")
+}, {
+ input: path.join("src", "registerComponents.ts"),
+ output: path.join("dist", "registerComponents.js")
})
const config = entrypoints.flatMap(({input, output}) => (
diff --git a/packages/turbo-mount/src/plugins/react/index.ts b/packages/turbo-mount/src/plugins/react/index.ts
index 13d8c90..1d2693c 100644
--- a/packages/turbo-mount/src/plugins/react/index.ts
+++ b/packages/turbo-mount/src/plugins/react/index.ts
@@ -1,18 +1,21 @@
-import { Plugin, TurboMount, TurboMountProps } from "turbo-mount";
+import { buildRegisterFunction, Plugin, TurboMount } from "turbo-mount";
+import { ComponentType, createElement } from "react";
+import { createRoot } from "react-dom/client";
-import { TurboMountReactController } from "./turbo-mount-react-controller";
+const plugin: Plugin = {
+ mountComponent: (mountProps) => {
+ const { el, Component, props } = mountProps;
+ const root = createRoot(el);
+ root.render(createElement(Component, props));
-const plugin: Plugin = {
- framework: "react",
- controller: TurboMountReactController,
+ return () => {
+ root.unmount();
+ };
+ },
};
-export class TurboMountReact extends TurboMount {
- constructor(props: Omit) {
- super({ ...props, plugin });
- }
-}
+const registerComponent = buildRegisterFunction(plugin);
-export { TurboMountReact as TurboMount };
+export { TurboMount, registerComponent };
export default plugin;
diff --git a/packages/turbo-mount/src/plugins/react/registerComponents.ts b/packages/turbo-mount/src/plugins/react/registerComponents.ts
new file mode 100644
index 0000000..1087464
--- /dev/null
+++ b/packages/turbo-mount/src/plugins/react/registerComponents.ts
@@ -0,0 +1,6 @@
+import { buildRegisterComponentsFunction } from "turbo-mount/registerComponents";
+import plugin from "turbo-mount/react";
+
+const registerComponents = buildRegisterComponentsFunction(plugin);
+
+export { registerComponents };
diff --git a/packages/turbo-mount/src/plugins/react/turbo-mount-react-controller.ts b/packages/turbo-mount/src/plugins/react/turbo-mount-react-controller.ts
deleted file mode 100644
index a83a63a..0000000
--- a/packages/turbo-mount/src/plugins/react/turbo-mount-react-controller.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import { ComponentType, createElement } from "react";
-import { createRoot } from "react-dom/client";
-import { TurboMountController } from "turbo-mount";
-
-export class TurboMountReactController extends TurboMountController {
- framework = "react";
-
- mountComponent(el: Element, Component: ComponentType, props: object) {
- const root = createRoot(el);
- root.render(createElement(Component, props));
-
- return () => {
- root.unmount();
- };
- }
-}
diff --git a/packages/turbo-mount/src/plugins/svelte/index.ts b/packages/turbo-mount/src/plugins/svelte/index.ts
index ec91879..2e07f8b 100644
--- a/packages/turbo-mount/src/plugins/svelte/index.ts
+++ b/packages/turbo-mount/src/plugins/svelte/index.ts
@@ -1,17 +1,19 @@
-import { Plugin, TurboMount, TurboMountProps } from "turbo-mount";
+import { buildRegisterFunction, Plugin, TurboMount } from "turbo-mount";
+import { ComponentType } from "svelte";
-import { TurboMountSvelteController } from "./turbo-mount-svelte-controller";
+const plugin: Plugin = {
+ mountComponent: (mountProps) => {
+ const { el, Component, props } = mountProps;
+ const component = new Component({ target: el, props });
-const plugin: Plugin = {
- framework: "svelte",
- controller: TurboMountSvelteController,
+ return () => {
+ component.$destroy();
+ };
+ },
};
-export class TurboMountSvelte extends TurboMount {
- constructor(props: Omit) {
- super({ ...props, plugin });
- }
-}
+const registerComponent = buildRegisterFunction(plugin);
+
+export { TurboMount, registerComponent };
-export { TurboMountSvelte as TurboMount };
export default plugin;
diff --git a/packages/turbo-mount/src/plugins/svelte/registerComponents.ts b/packages/turbo-mount/src/plugins/svelte/registerComponents.ts
new file mode 100644
index 0000000..d4c243a
--- /dev/null
+++ b/packages/turbo-mount/src/plugins/svelte/registerComponents.ts
@@ -0,0 +1,6 @@
+import { buildRegisterComponentsFunction } from "turbo-mount/registerComponents";
+import plugin from "turbo-mount/svelte";
+
+const registerComponents = buildRegisterComponentsFunction(plugin);
+
+export { registerComponents };
diff --git a/packages/turbo-mount/src/plugins/svelte/turbo-mount-svelte-controller.ts b/packages/turbo-mount/src/plugins/svelte/turbo-mount-svelte-controller.ts
deleted file mode 100644
index af14e1d..0000000
--- a/packages/turbo-mount/src/plugins/svelte/turbo-mount-svelte-controller.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import { ComponentType } from "svelte";
-import { TurboMountController } from "turbo-mount";
-
-export class TurboMountSvelteController extends TurboMountController {
- framework = "svelte";
-
- mountComponent(el: Element, Component: ComponentType, props: object) {
- const component = new Component({ target: el, props });
-
- return () => {
- component.$destroy();
- };
- }
-}
diff --git a/packages/turbo-mount/src/plugins/vue/index.ts b/packages/turbo-mount/src/plugins/vue/index.ts
index 28112b4..efd152b 100644
--- a/packages/turbo-mount/src/plugins/vue/index.ts
+++ b/packages/turbo-mount/src/plugins/vue/index.ts
@@ -1,18 +1,20 @@
-import { Plugin, TurboMount, TurboMountProps } from "turbo-mount";
+import { buildRegisterFunction, Plugin, TurboMount } from "turbo-mount";
+import { App, createApp } from "vue";
-import { TurboMountVueController } from "./turbo-mount-vue-controller";
+const plugin: Plugin = {
+ mountComponent: (mountProps) => {
+ const { el, Component, props } = mountProps;
+ const app = createApp(Component, props as Record);
+ app.mount(el);
-const plugin: Plugin = {
- framework: "vue",
- controller: TurboMountVueController,
+ return () => {
+ app.unmount();
+ };
+ },
};
-export class TurboMountVue extends TurboMount {
- constructor(props: Omit) {
- super({ ...props, plugin });
- }
-}
+const registerComponent = buildRegisterFunction(plugin);
-export { TurboMountVue as TurboMount };
+export { TurboMount, registerComponent };
export default plugin;
diff --git a/packages/turbo-mount/src/plugins/vue/registerComponents.ts b/packages/turbo-mount/src/plugins/vue/registerComponents.ts
new file mode 100644
index 0000000..b71517a
--- /dev/null
+++ b/packages/turbo-mount/src/plugins/vue/registerComponents.ts
@@ -0,0 +1,6 @@
+import { buildRegisterComponentsFunction } from "turbo-mount/registerComponents";
+import plugin from "turbo-mount/vue";
+
+const registerComponents = buildRegisterComponentsFunction(plugin);
+
+export { registerComponents };
diff --git a/packages/turbo-mount/src/plugins/vue/turbo-mount-vue-controller.ts b/packages/turbo-mount/src/plugins/vue/turbo-mount-vue-controller.ts
deleted file mode 100644
index d380717..0000000
--- a/packages/turbo-mount/src/plugins/vue/turbo-mount-vue-controller.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import { createApp, App } from "vue";
-import { TurboMountController } from "turbo-mount";
-
-export class TurboMountVueController extends TurboMountController {
- framework = "vue";
-
- mountComponent(el: Element, Component: App, props: object) {
- const app = createApp(Component, props as Record);
- app.mount(el);
-
- return () => {
- app.unmount();
- };
- }
-}
diff --git a/packages/turbo-mount/src/vite.ts b/packages/turbo-mount/src/registerComponents.ts
similarity index 54%
rename from packages/turbo-mount/src/vite.ts
rename to packages/turbo-mount/src/registerComponents.ts
index 30be994..db2e2a1 100644
--- a/packages/turbo-mount/src/vite.ts
+++ b/packages/turbo-mount/src/registerComponents.ts
@@ -1,36 +1,31 @@
import { definitionsFromGlob } from "stimulus-vite-helpers";
import { Definition } from "@hotwired/stimulus";
-import { TurboMount } from "turbo-mount";
+import { TurboMount, Plugin } from "turbo-mount";
import { camelToKebabCase } from "./helpers";
type ComponentModule = { default: never } | never;
-type RegisterComponentsProps = {
- turboMount: TurboMount;
+type RegisterComponentsProps = {
+ plugin: Plugin;
+ turboMount: TurboMount;
components: Record;
controllers?: Record;
};
-const identifierNames = (name: string, turboMount: TurboMount) => {
+const identifierNames = (name: string) => {
const controllerName = camelToKebabCase(name);
- const framework = turboMount.framework;
-
- return [
- `turbo-mount--${framework}--${controllerName}`,
- `turbo-mount--${framework}-${controllerName}`,
- `turbo-mount-${framework}-${controllerName}`,
- `turbo-mount--${controllerName}`,
- `turbo-mount-${controllerName}`,
- ];
+
+ return [`turbo-mount--${controllerName}`, `turbo-mount-${controllerName}`];
};
-export const registerComponents = ({
+export const registerComponents = ({
+ plugin,
turboMount,
components,
controllers,
-}: RegisterComponentsProps) => {
+}: RegisterComponentsProps) => {
const controllerModules = controllers ? definitionsFromGlob(controllers) : [];
for (const [componentPath, componentModule] of Object.entries(components)) {
@@ -38,7 +33,7 @@ export const registerComponents = ({
.replace(/\.\w*$/, "")
.replace(/^[./]*components\//, "");
- const identifiers = identifierNames(name, turboMount);
+ const identifiers = identifierNames(name);
const controller = controllerModules.find(({ identifier }) =>
identifiers.includes(identifier),
@@ -46,9 +41,19 @@ export const registerComponents = ({
const component = componentModule.default ?? componentModule;
if (controller) {
- turboMount.register(name, component, controller.controllerConstructor);
+ turboMount.register(
+ plugin,
+ name,
+ component,
+ controller.controllerConstructor,
+ );
} else {
- turboMount.register(name, component);
+ turboMount.register(plugin, name, component);
}
}
};
+
+export const buildRegisterComponentsFunction = (plugin: Plugin) => {
+ return (props: Omit, "plugin">) =>
+ registerComponents({ plugin, ...props });
+};
diff --git a/packages/turbo-mount/src/turbo-mount-controller.ts b/packages/turbo-mount/src/turbo-mount-controller.ts
index 3f58d21..c2dcac7 100644
--- a/packages/turbo-mount/src/turbo-mount-controller.ts
+++ b/packages/turbo-mount/src/turbo-mount-controller.ts
@@ -1,22 +1,20 @@
import { Controller } from "@hotwired/stimulus";
import { ApplicationWithTurboMount } from "./turbo-mount";
-export abstract class TurboMountController extends Controller {
+export class TurboMountController extends Controller {
static values = {
props: Object,
component: String,
};
static targets = ["mount"];
- declare readonly propsValue: object;
- declare readonly componentValue: string;
+ private skipPropsChangeCallback = false;
+
+ declare propsValue: object;
+ declare componentValue: string;
declare readonly hasMountTarget: boolean;
declare readonly mountTarget: Element;
- abstract framework: string;
-
- abstract mountComponent(el: Element, Component: T, props: object): () => void;
-
_umountComponentCallback?: () => void;
connect() {
@@ -32,6 +30,12 @@ export abstract class TurboMountController extends Controller {
}
propsValueChanged() {
+ // Prevent re-mounting the component if the props are being set by the component itself
+ if (this.skipPropsChangeCallback) {
+ this.skipPropsChangeCallback = false;
+ return;
+ }
+
this.umountComponent();
this._umountComponentCallback ||= this.mountComponent(
this.mountElement,
@@ -49,7 +53,11 @@ export abstract class TurboMountController extends Controller {
}
get resolvedComponent() {
- return this.resolveComponent(this.componentValue);
+ return this.resolveMounted(this.componentValue).component;
+ }
+
+ get resolvedPlugin() {
+ return this.resolveMounted(this.componentValue).plugin;
}
umountComponent() {
@@ -57,8 +65,17 @@ export abstract class TurboMountController extends Controller {
this._umountComponentCallback = undefined;
}
- resolveComponent(component: string): T {
- const app = this.application as ApplicationWithTurboMount;
- return app.turboMount[this.framework].resolve(component);
+ mountComponent(el: Element, Component: unknown, props: object) {
+ return this.resolvedPlugin.mountComponent({ el, Component, props });
+ }
+
+ resolveMounted(component: string) {
+ const app = this.application as ApplicationWithTurboMount;
+ return app.turboMount.resolve(component);
+ }
+
+ setComponentProps(props: object) {
+ this.skipPropsChangeCallback = true;
+ this.propsValue = props;
}
}
diff --git a/packages/turbo-mount/src/turbo-mount.ts b/packages/turbo-mount/src/turbo-mount.ts
index 8e3d86a..eb03971 100644
--- a/packages/turbo-mount/src/turbo-mount.ts
+++ b/packages/turbo-mount/src/turbo-mount.ts
@@ -1,6 +1,7 @@
import { Application, ControllerConstructor } from "@hotwired/stimulus";
import { camelToKebabCase } from "./helpers";
+import { TurboMountController } from "./turbo-mount-controller";
declare global {
interface Window {
@@ -8,62 +9,73 @@ declare global {
}
}
-export interface ApplicationWithTurboMount extends Application {
- turboMount: { [framework: string]: TurboMount };
+export interface ApplicationWithTurboMount extends Application {
+ turboMount: TurboMount;
}
-export type Plugin = {
- framework: string;
- controller: ControllerConstructor;
+export type MountComponentProps = {
+ el: Element;
+ Component: T;
+ props: object;
+};
+
+export type Plugin = {
+ mountComponent: (props: MountComponentProps) => () => void;
};
export type TurboMountProps = {
application?: Application;
- plugin: Plugin;
};
-export class TurboMount {
- components: Map;
- application: ApplicationWithTurboMount;
- framework: string;
- baseController?: ControllerConstructor;
+type TurboMountComponents = Map }>;
+
+interface TurboMorphEvent extends CustomEvent {
+ target: Element;
+ detail: {
+ newElement: Element;
+ };
+}
+
+export class TurboMount {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ components: TurboMountComponents;
+ application: ApplicationWithTurboMount;
- constructor({ application, plugin }: TurboMountProps) {
+ constructor(props: TurboMountProps = {}) {
this.components = new Map();
- this.application = this.findOrStartApplication(application);
- this.framework = plugin.framework;
- this.baseController = plugin.controller;
-
- this.application.turboMount ||= {};
- this.application.turboMount[this.framework] = this;
-
- if (this.baseController) {
- this.application.register(
- `turbo-mount-${this.framework}`,
- this.baseController,
- );
- }
- }
+ this.application = this.findOrStartApplication(props.application);
+ this.application.turboMount = this;
+ this.application.register("turbo-mount", TurboMountController);
- private findOrStartApplication(hydratedApp?: Application) {
- let application = hydratedApp || window.Stimulus;
+ document.addEventListener("turbo:before-morph-element", (event) => {
+ const turboMorphEvent = event as unknown as TurboMorphEvent;
+ const { target, detail } = turboMorphEvent;
- if (!application) {
- application = Application.start();
- window.Stimulus = application;
- }
- return application as ApplicationWithTurboMount;
+ if (target.getAttribute("data-controller")?.includes("turbo-mount")) {
+ target.setAttribute(
+ "data-turbo-mount-props-value",
+ detail.newElement.getAttribute("data-turbo-mount-props-value") ||
+ "{}",
+ );
+ event.preventDefault();
+ }
+ });
}
- register(name: string, component: T, controller?: ControllerConstructor) {
- controller ||= this.baseController;
+ register(
+ plugin: Plugin,
+ name: string,
+ component: T,
+ controller?: ControllerConstructor,
+ ) {
+ controller ||= TurboMountController;
if (this.components.has(name)) {
throw new Error(`Component '${name}' is already registered.`);
}
- this.components.set(name, component);
+ this.components.set(name, { component, plugin });
if (controller) {
- const controllerName = `turbo-mount-${this.framework}-${camelToKebabCase(name)}`;
+ const controllerName = `turbo-mount-${camelToKebabCase(name)}`;
this.application.register(controllerName, controller);
}
}
@@ -75,4 +87,25 @@ export class TurboMount {
}
return component;
}
+
+ private findOrStartApplication(hydratedApp?: Application) {
+ let application = hydratedApp || window.Stimulus;
+
+ if (!application) {
+ application = Application.start();
+ window.Stimulus = application;
+ }
+ return application as ApplicationWithTurboMount;
+ }
+}
+
+export function buildRegisterFunction(plugin: Plugin) {
+ return (
+ turboMount: TurboMount,
+ name: string,
+ component: T,
+ controller?: ControllerConstructor,
+ ) => {
+ turboMount.register(plugin, name, component, controller);
+ };
}
diff --git a/packages/turbo-mount/tsconfig.json b/packages/turbo-mount/tsconfig.json
index fa960fe..60f7eb4 100644
--- a/packages/turbo-mount/tsconfig.json
+++ b/packages/turbo-mount/tsconfig.json
@@ -16,7 +16,9 @@
"noEmit": false,
"declaration": false,
"paths": {
- "turbo-mount": ["src/index"]
+ "turbo-mount": ["src/index"],
+ "turbo-mount/registerComponents": ["src/registerComponents"],
+ "turbo-mount/*": ["src/plugins/*"]
},
"types": [
"vite/client"