diff --git a/CHANGELOG.md b/CHANGELOG.md index 44a10f7..a514945 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ and this project adheres to [Semantic Versioning]. - Add a mount target to the base controller. ([@skryukov]) - Add `registerComponents` helper for vite. ([@skryukov]) +- Allow to omit the `application` property in the constructor. ([@skryukov]) + `TurboMount` will try to find the application in the `window.Stimulus` and will initialize new one if not found. ## [0.2.2] - 2024-05-09 diff --git a/README.md b/README.md index 5af2ef7..69b8c39 100644 --- a/README.md +++ b/README.md @@ -24,16 +24,47 @@ yarn add turbo-mount ### Initialization -First, initialize `TurboMount` and register the components you wish to use: +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' +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}); +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: + +```js +import { TurboMount } from "turbo-mount"; +import plugin from "turbo-mount/react"; +import { SketchPicker } from 'react-color'; + +const turboMount = new TurboMount({ plugin }); + +turboMount.register('SketchPicker', SketchPicker); +``` + +#### 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); ``` @@ -91,22 +122,17 @@ turboMount.register('SketchPicker', SketchPicker, SketchController); ### Vite Integration -`TurboMount` includes a `registerComponents` function that automates the loading of components. It also accepts an optional `controllers` property to autoload customized controllers: +`TurboMount` includes a `registerComponents` function that automates the loading of components (requires `stimulus-vite-helpers` package). It also accepts an optional `controllers` property to autoload customized controllers: ```js -import { application } from "./application" -import { registerControllers } from "stimulus-vite-helpers"; -import { TurboMount } from "turbo-mount"; +import { TurboMount } from "turbo-mount/react"; import { registerComponents } from "turbo-mount/vite"; -import plugin from "turbo-mount/react"; const controllers = import.meta.glob("./**/*_controller.js", { eager: true }); -const components = import.meta.glob(`/components/**/*.jsx`, {eager: true}); - -registerControllers(application, controllers); +const components = import.meta.glob(`/components/**/*.jsx`, { eager: true }); -const turboMount = new TurboMount({application, plugin}); -registerComponents({turboMount, components, controllers}); +const turboMount = new TurboMount(); +registerComponents({ turboMount, components, controllers }); ``` The `registerComponents` helper searches for controllers in the following paths: diff --git a/app/assets/javascripts/turbo-mount.js b/app/assets/javascripts/turbo-mount.js index 42cffc5..d32ff3c 100644 --- a/app/assets/javascripts/turbo-mount.js +++ b/app/assets/javascripts/turbo-mount.js @@ -1,4 +1,4 @@ -import { Controller } from '@hotwired/stimulus'; +import { Controller, Application } from '@hotwired/stimulus'; class TurboMountController extends Controller { connect() { @@ -40,18 +40,26 @@ const camelToKebabCase = (str) => { }; class TurboMount { - constructor(props) { + constructor({ application, plugin }) { var _a; this.components = new Map(); - this.application = props.application; - this.framework = props.plugin.framework; - this.baseController = props.plugin.controller; + 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); } } + 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); if (this.components.has(name)) { diff --git a/app/assets/javascripts/turbo-mount.min.js b/app/assets/javascripts/turbo-mount.min.js index 26dc25b..25fc46f 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}from"@hotwired/stimulus";class o 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)}}o.values={props:Object,component:String},o.targets=["mount"];class n{constructor(t){var o;this.components=new Map,this.application=t.application,this.framework=t.plugin.framework,this.baseController=t.plugin.controller,(o=this.application).turboMount||(o.turboMount={}),this.application.turboMount[this.framework]=this,this.baseController&&this.application.register(`turbo-mount-${this.framework}`,this.baseController)}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{n as TurboMount,o as TurboMountController}; +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}; //# 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 f1656cd..f567b2d 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\nexport interface ApplicationWithTurboMount extends Application {\n turboMount: { [framework: string]: TurboMount };\n}\n\nexport type Plugin = {\n framework: string;\n controller: ControllerConstructor;\n};\n\nexport class TurboMount {\n components: Map;\n application: ApplicationWithTurboMount;\n framework: string;\n baseController?: ControllerConstructor;\n\n constructor(props: { application: Application; plugin: Plugin }) {\n this.components = new Map();\n this.application = props.application as ApplicationWithTurboMount;\n this.framework = props.plugin.framework;\n this.baseController = props.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 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","components","Map","plugin","baseController","controller","_a","register","name","has","Error","set","controllerName","str","replace","toLowerCase","get"],"mappings":"gDAGM,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,eCKPC,EAMX,WAAAC,CAAYL,SACVtB,KAAK4B,WAAa,IAAIC,IACtB7B,KAAKiB,YAAcK,EAAML,YACzBjB,KAAKmB,UAAYG,EAAMQ,OAAOX,UAC9BnB,KAAK+B,eAAiBT,EAAMQ,OAAOE,YAEnCC,EAAAjC,KAAKiB,aAAYC,aAAAe,EAAAf,WAAe,CAAA,GAChClB,KAAKiB,YAAYC,WAAWlB,KAAKmB,WAAanB,KAE1CA,KAAK+B,gBACP/B,KAAKiB,YAAYiB,SACf,eAAelC,KAAKmB,YACpBnB,KAAK+B,eAGV,CAED,QAAAG,CAASC,EAAcnB,EAAcgB,GAEnC,GADAA,IAAAA,EAAehC,KAAK+B,gBAChB/B,KAAK4B,WAAWQ,IAAID,GACtB,MAAM,IAAIE,MAAM,cAAcF,6BAIhC,GAFAnC,KAAK4B,WAAWU,IAAIH,EAAMnB,GAEtBgB,EAAY,CACd,MAAMO,EAAiB,eAAevC,KAAKmB,aC5ChBqB,ED4C8CL,EC3CtEK,EAAIC,QAAQ,kBAAmB,SAASC,gBD4C3C1C,KAAKiB,YAAYiB,SAASK,EAAgBP,EAC3C,CC9C2B,IAACQ,CD+C9B,CAED,OAAApB,CAAQe,GACN,MAAMnB,EAAYhB,KAAK4B,WAAWe,IAAIR,GACtC,IAAKnB,EACH,MAAM,IAAIqB,MAAM,sBAAsBF,KAExC,OAAOnB,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 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 diff --git a/app/assets/javascripts/turbo-mount/react.js b/app/assets/javascripts/turbo-mount/react.js index b0cc48b..390e6c7 100644 --- a/app/assets/javascripts/turbo-mount/react.js +++ b/app/assets/javascripts/turbo-mount/react.js @@ -1,6 +1,6 @@ +import { TurboMountController, TurboMount } from 'turbo-mount'; import { createElement } from 'react'; import { createRoot } from 'react-dom/client'; -import { TurboMountController } from 'turbo-mount'; class TurboMountReactController extends TurboMountController { constructor() { @@ -20,5 +20,10 @@ const plugin = { framework: "react", controller: TurboMountReactController, }; +class TurboMountReact extends TurboMount { + constructor(props) { + super(Object.assign(Object.assign({}, props), { plugin })); + } +} -export { plugin as default }; +export { TurboMountReact as TurboMount, TurboMountReact, plugin as default }; diff --git a/app/assets/javascripts/turbo-mount/react.min.js b/app/assets/javascripts/turbo-mount/react.min.js index 1616b7c..516c748 100644 --- a/app/assets/javascripts/turbo-mount/react.min.js +++ b/app/assets/javascripts/turbo-mount/react.min.js @@ -1,2 +1,2 @@ -import{createElement as r}from"react";import{createRoot as t}from"react-dom/client";import{TurboMountController as o}from"turbo-mount";const e={framework:"react",controller:class extends o{constructor(){super(...arguments),this.framework="react"}mountComponent(o,e,n){const m=t(o);return m.render(r(e,n)),()=>{m.unmount()}}}};export{e as default}; +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}; //# 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 e55cffc..64ce64a 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 } from \"turbo-mount\";\n\nimport { TurboMountReactController } from \"./turbo-mount-react-controller\";\n\nconst plugin: Plugin = {\n framework: \"react\",\n controller: TurboMountReactController,\n};\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"],"mappings":"uIAIA,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"} \ No newline at end of file +{"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 diff --git a/app/assets/javascripts/turbo-mount/svelte.js b/app/assets/javascripts/turbo-mount/svelte.js index 8de8f26..3f357fb 100644 --- a/app/assets/javascripts/turbo-mount/svelte.js +++ b/app/assets/javascripts/turbo-mount/svelte.js @@ -1,4 +1,4 @@ -import { TurboMountController } from 'turbo-mount'; +import { TurboMountController, TurboMount } from 'turbo-mount'; class TurboMountSvelteController extends TurboMountController { constructor() { @@ -17,5 +17,10 @@ const plugin = { framework: "svelte", controller: TurboMountSvelteController, }; +class TurboMountSvelte extends TurboMount { + constructor(props) { + super(Object.assign(Object.assign({}, props), { plugin })); + } +} -export { plugin as default }; +export { TurboMountSvelte as TurboMount, TurboMountSvelte, plugin as default }; diff --git a/app/assets/javascripts/turbo-mount/svelte.min.js b/app/assets/javascripts/turbo-mount/svelte.min.js index fbfea06..fc8fa73 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}from"turbo-mount";const o={framework:"svelte",controller:class extends t{constructor(){super(...arguments),this.framework="svelte"}mountComponent(t,o,r){const e=new o({target:t,props:r});return()=>{e.$destroy()}}}};export{o as default}; +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}; //# 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 12f30ef..3adaf68 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 } from \"turbo-mount\";\n\nimport { TurboMountSvelteController } from \"./turbo-mount-svelte-controller\";\n\nconst plugin: Plugin = {\n framework: \"svelte\",\n controller: TurboMountSvelteController,\n};\n\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"],"mappings":"mDAIA,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"} \ No newline at end of file +{"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 diff --git a/app/assets/javascripts/turbo-mount/vue.js b/app/assets/javascripts/turbo-mount/vue.js index 784f2ff..104c25e 100644 --- a/app/assets/javascripts/turbo-mount/vue.js +++ b/app/assets/javascripts/turbo-mount/vue.js @@ -1,5 +1,5 @@ +import { TurboMountController, TurboMount } from 'turbo-mount'; import { createApp } from 'vue'; -import { TurboMountController } from 'turbo-mount'; class TurboMountVueController extends TurboMountController { constructor() { @@ -19,5 +19,10 @@ const plugin = { framework: "vue", controller: TurboMountVueController, }; +class TurboMountVue extends TurboMount { + constructor(props) { + super(Object.assign(Object.assign({}, props), { plugin })); + } +} -export { plugin as default }; +export { TurboMountVue as TurboMount, TurboMountVue, plugin as default }; diff --git a/app/assets/javascripts/turbo-mount/vue.min.js b/app/assets/javascripts/turbo-mount/vue.min.js index 2026dd8..966f1a4 100644 --- a/app/assets/javascripts/turbo-mount/vue.min.js +++ b/app/assets/javascripts/turbo-mount/vue.min.js @@ -1,2 +1,2 @@ -import{createApp as o}from"vue";import{TurboMountController as t}from"turbo-mount";const r={framework:"vue",controller:class extends t{constructor(){super(...arguments),this.framework="vue"}mountComponent(t,r,e){const n=o(r,e);return n.mount(t),()=>{n.unmount()}}}};export{r as default}; +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}; //# 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 42cc1c0..ebc9cf0 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 } from \"turbo-mount\";\n\nimport { TurboMountVueController } from \"./turbo-mount-vue-controller\";\n\nconst plugin: Plugin = {\n framework: \"vue\",\n controller: TurboMountVueController,\n};\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"],"mappings":"mFAIA,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"} \ No newline at end of file +{"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 diff --git a/packages/turbo-mount/src/plugins/react/index.ts b/packages/turbo-mount/src/plugins/react/index.ts index 51d48a6..13d8c90 100644 --- a/packages/turbo-mount/src/plugins/react/index.ts +++ b/packages/turbo-mount/src/plugins/react/index.ts @@ -1,4 +1,4 @@ -import { Plugin } from "turbo-mount"; +import { Plugin, TurboMount, TurboMountProps } from "turbo-mount"; import { TurboMountReactController } from "./turbo-mount-react-controller"; @@ -7,4 +7,12 @@ const plugin: Plugin = { controller: TurboMountReactController, }; +export class TurboMountReact extends TurboMount { + constructor(props: Omit) { + super({ ...props, plugin }); + } +} + +export { TurboMountReact as TurboMount }; + export default plugin; diff --git a/packages/turbo-mount/src/plugins/svelte/index.ts b/packages/turbo-mount/src/plugins/svelte/index.ts index 304eb50..ec91879 100644 --- a/packages/turbo-mount/src/plugins/svelte/index.ts +++ b/packages/turbo-mount/src/plugins/svelte/index.ts @@ -1,4 +1,4 @@ -import { Plugin } from "turbo-mount"; +import { Plugin, TurboMount, TurboMountProps } from "turbo-mount"; import { TurboMountSvelteController } from "./turbo-mount-svelte-controller"; @@ -7,4 +7,11 @@ const plugin: Plugin = { controller: TurboMountSvelteController, }; +export class TurboMountSvelte extends TurboMount { + constructor(props: Omit) { + super({ ...props, plugin }); + } +} + +export { TurboMountSvelte as TurboMount }; export default plugin; diff --git a/packages/turbo-mount/src/plugins/vue/index.ts b/packages/turbo-mount/src/plugins/vue/index.ts index f87331f..28112b4 100644 --- a/packages/turbo-mount/src/plugins/vue/index.ts +++ b/packages/turbo-mount/src/plugins/vue/index.ts @@ -1,4 +1,4 @@ -import { Plugin } from "turbo-mount"; +import { Plugin, TurboMount, TurboMountProps } from "turbo-mount"; import { TurboMountVueController } from "./turbo-mount-vue-controller"; @@ -7,4 +7,12 @@ const plugin: Plugin = { controller: TurboMountVueController, }; +export class TurboMountVue extends TurboMount { + constructor(props: Omit) { + super({ ...props, plugin }); + } +} + +export { TurboMountVue as TurboMount }; + export default plugin; diff --git a/packages/turbo-mount/src/turbo-mount.ts b/packages/turbo-mount/src/turbo-mount.ts index 0b3a850..8e3d86a 100644 --- a/packages/turbo-mount/src/turbo-mount.ts +++ b/packages/turbo-mount/src/turbo-mount.ts @@ -2,6 +2,12 @@ import { Application, ControllerConstructor } from "@hotwired/stimulus"; import { camelToKebabCase } from "./helpers"; +declare global { + interface Window { + Stimulus?: Application; + } +} + export interface ApplicationWithTurboMount extends Application { turboMount: { [framework: string]: TurboMount }; } @@ -11,17 +17,22 @@ export type Plugin = { controller: ControllerConstructor; }; +export type TurboMountProps = { + application?: Application; + plugin: Plugin; +}; + export class TurboMount { components: Map; application: ApplicationWithTurboMount; framework: string; baseController?: ControllerConstructor; - constructor(props: { application: Application; plugin: Plugin }) { + constructor({ application, plugin }: TurboMountProps) { this.components = new Map(); - this.application = props.application as ApplicationWithTurboMount; - this.framework = props.plugin.framework; - this.baseController = props.plugin.controller; + this.application = this.findOrStartApplication(application); + this.framework = plugin.framework; + this.baseController = plugin.controller; this.application.turboMount ||= {}; this.application.turboMount[this.framework] = this; @@ -34,6 +45,16 @@ export class TurboMount { } } + private findOrStartApplication(hydratedApp?: Application) { + let application = hydratedApp || window.Stimulus; + + if (!application) { + application = Application.start(); + window.Stimulus = application; + } + return application as ApplicationWithTurboMount; + } + register(name: string, component: T, controller?: ControllerConstructor) { controller ||= this.baseController; if (this.components.has(name)) {