turbo-mount 0.3.0 → 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3565dc0d8e80907e11600573cfb18049fe5360c9551facefb9a22d871bebad2e
4
- data.tar.gz: 2e82006304c22c3b663511abbefaee3a0fc8e96aa972b044e1fca640cfd9c262
3
+ metadata.gz: 2a99bcba9d009ca4dc95d68bd54a8f98e724f487c8d25eff234f406feb01c56e
4
+ data.tar.gz: 186eb9a15926d54e14999febfe36c809dbf988d30857cf3d0ba29fd58b73eafc
5
5
  SHA512:
6
- metadata.gz: 2490534f21cf102b00c00524457be4b6b6f3d048e2d0477ad20bdbf2818704d5b39580bc9f71d5f33de6f39fcdd97faa20e0c0cc05a3c338225091e1096fa428
7
- data.tar.gz: 6154769b9a73b2bf3e65e0c6a395a154109466d698f79dc5483378ed7f68cce060c41183725f240fff942a3cf09e03ac909b2f0d0f0da70a574a6712e28e1251
6
+ metadata.gz: 5ec1ca4c48650c82e92c1ff0419df6ef9d3c5f1ebaa9c8ac31a41c0ac043fe22c78169d60ce9589ee3067e5d7643b851fcd3b93118ee3e8e64e2caad1dda552a
7
+ data.tar.gz: c5c7639b39dcaa2443d29188e213c3592473eb3c9d83ca2d7278f37440e40f4daa7cdee4f357449a8ef9e08f9c4b0b0caa1f9f5837e0a810d09235cccc36f528
data/CHANGELOG.md CHANGED
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning].
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.2] - 2024-06-24
11
+
12
+ ### Fixed
13
+
14
+ - Fix typescript exports. ([@skryukov])
15
+
16
+ ## [0.3.1] - 2024-06-16
17
+
18
+ ### Added
19
+
20
+ - Add `registerComponents` helper for ESBuild. ([@skryukov])
21
+
22
+ ### Fixed
23
+
24
+ - Fix entrypoint issue in the install generator. ([@skryukov])
25
+
10
26
  ## [0.3.0] - 2024-05-31
11
27
 
12
28
  ### Added
@@ -51,7 +67,9 @@ and this project adheres to [Semantic Versioning].
51
67
 
52
68
  [@skryukov]: https://github.com/skryukov
53
69
 
54
- [Unreleased]: https://github.com/skryukov/turbo-mount/compare/v0.3.0...HEAD
70
+ [Unreleased]: https://github.com/skryukov/turbo-mount/compare/v0.3.2...HEAD
71
+ [0.3.1]: https://github.com/skryukov/turbo-mount/compare/v0.3.1...v0.3.2
72
+ [0.3.1]: https://github.com/skryukov/turbo-mount/compare/v0.3.0...v0.3.1
55
73
  [0.3.0]: https://github.com/skryukov/turbo-mount/compare/v0.2.3...v0.3.0
56
74
  [0.2.3]: https://github.com/skryukov/turbo-mount/compare/v0.2.2...v0.2.3
57
75
  [0.2.2]: https://github.com/skryukov/turbo-mount/compare/v0.2.0...v0.2.2
data/README.md CHANGED
@@ -2,7 +2,12 @@
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/turbo-mount.svg)](https://rubygems.org/gems/turbo-mount)
4
4
 
5
- `TurboMount` is a simple library that allows you to add highly interactive components from React, Vue, Svelte, and other frameworks to your Hotwire application.
5
+ `TurboMount` is a simple library that allows you to add highly interactive components from React, Vue, Svelte, and other gframeworks to your Hotwire application.
6
+
7
+ ### Learn more
8
+
9
+ - [The art of Turbo Mount: Hotwire meets modern JS frameworks](https://evilmartians.com/chronicles/the-art-of-turbo-mount-hotwire-meets-modern-js-frameworks)
10
+ - [Excel-lent palettes: a demo application for the Turbo Mount gem](https://github.com/skryukov/excellent-palettes)
6
11
 
7
12
  ## Table of Contents
8
13
  - [Installation](#installation)
@@ -15,7 +20,9 @@
15
20
  - [View Helpers](#view-helpers)
16
21
  - [Supported Frameworks](#supported-frameworks)
17
22
  - [Custom Controllers](#custom-controllers)
18
- - [Vite Integration](#vite-integration)
23
+ - [Auto-Loading Components](#auto-loading-components)
24
+ - [Vite Integration](#vite-integration)
25
+ - [ESBuild Integration](#esbuild-integration)
19
26
  - [Mount Target](#mount-target)
20
27
  - [License](#license)
21
28
 
@@ -91,6 +98,8 @@ Note: Importmap-only mode is quite limited in terms of JavaScript dependencies.
91
98
  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:
92
99
 
93
100
  ```js
101
+ // app/javascript/turbo-mount.js
102
+
94
103
  import { TurboMount } from "turbo-mount";
95
104
  import { registerComponent } from "turbo-mount/react";
96
105
  import { HexColorPicker } from 'react-colorful';
@@ -100,7 +109,14 @@ const turboMount = new TurboMount(); // or new TurboMount({ application })
100
109
  registerComponent(turboMount, "HexColorPicker", HexColorPicker);
101
110
  ```
102
111
 
103
- 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.
112
+ 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.
113
+
114
+ Make sure your `application.js` is importing `turbo-mount.js`:
115
+ ```js
116
+ import "@hotwired/turbo-rails"
117
+ import "./controllers"
118
+ import "./turbo-mount" // <------
119
+ ```
104
120
 
105
121
  ### View Helpers
106
122
 
@@ -113,9 +129,9 @@ Use the following helpers to mount components in your views:
113
129
  This will generate the following HTML:
114
130
 
115
131
  ```html
116
- <div data-controller="turbo-mount-hex-color-picker"
117
- data-turbo-mount-hex-color-picker-component-value="HexColorPicker"
118
- data-turbo-mount-hex-color-picker-props-value="{&quot;color&quot;:&quot;#034&quot;}"
132
+ <div data-controller="turbo-mount"
133
+ data-turbo-mount-component-value="HexColorPicker"
134
+ data-turbo-mount-props-value="{&quot;color&quot;:&quot;#034&quot;}"
119
135
  class="mb-5">
120
136
  </div>
121
137
  ```
@@ -148,7 +164,7 @@ export default class extends TurboMountController {
148
164
  onChange = (color) => {
149
165
  // same as this.propsValue = { ...this.propsValue, color };
150
166
  // but skips the rerendering of the component:
151
- this.componentProps = { ...this.propsValue, color };
167
+ this.setComponentProps({ ...this.propsValue, color });
152
168
  };
153
169
  }
154
170
  ```
@@ -161,24 +177,50 @@ import HexColorPickerController from "controllers/turbo_mount/hex_color_picker_c
161
177
  registerComponent(turboMount, "HexColorPicker", HexColorPicker, HexColorPickerController);
162
178
  ```
163
179
 
164
- ### Vite Integration
180
+ ### Auto-Loading Components
165
181
 
166
- `TurboMount` includes a `registerComponents` function that automates the loading of components (requires the `stimulus-vite-helpers` package). It also accepts an optional `controllers` property to autoload customized controllers:
182
+ `TurboMount` includes a `registerComponents` functions that automates the loading of components. `registerComponents` also accepts an optional `controllers` property to autoload customized controllers.
183
+
184
+ The `registerComponents` helpers search for controllers in the following paths:
185
+ - `controllers/turbo-mount/${controllerName}`
186
+ - `controllers/turbo-mount-${controllerName}`
187
+
188
+ #### Vite Integration
189
+
190
+ Vite helper requires the `stimulus-vite-helpers` package to load components and controllers. Here's how to set it up:
191
+
192
+ ```bash
193
+ npm install stimulus-vite-helpers
194
+ ```
195
+
196
+ Then use the `registerComponents` helper to autoload components and controllers:
167
197
 
168
198
  ```js
169
- import { TurboMount } from "turbo-mount/react";
170
- import { registerComponents } from "turbo-mount/registerComponents/react";
199
+ import plugin, { TurboMount } from "turbo-mount/react";
200
+ import { registerComponents } from "turbo-mount/registerComponents/vite";
171
201
 
172
202
  const controllers = import.meta.glob("./**/*_controller.js", { eager: true });
173
203
  const components = import.meta.glob("/components/**/*.jsx", { eager: true });
174
204
 
175
205
  const turboMount = new TurboMount();
176
- registerComponents({ turboMount, components, controllers });
206
+ registerComponents({ plugin, turboMount, components, controllers });
177
207
  ```
178
208
 
179
- The `registerComponents` helper searches for controllers in the following paths:
180
- - `controllers/turbo-mount/${controllerName}`
181
- - `controllers/turbo-mount-${controllerName}`
209
+ #### ESBuild Integration
210
+
211
+ ESBuild helper requires the `esbuild-rails` package to load components and controllers. Read the [ESBuild Rails README](https://github.com/excid3/esbuild-rails) for more information on how to set it up.
212
+
213
+ ```js
214
+ import plugin, { TurboMount } from "turbo-mount/react";
215
+ import { registerComponents } from "turbo-mount/registerComponents/esbuild";
216
+
217
+ const turboMount = new TurboMount();
218
+
219
+ import controllers from "./controllers/**/*_controller.js";
220
+ import components from "./components/**/*.jsx";
221
+
222
+ registerComponents({ plugin, turboMount, components, controllers });
223
+ ```
182
224
 
183
225
  ### Mount Target
184
226
 
@@ -107,4 +107,27 @@ function buildRegisterFunction(plugin) {
107
107
  };
108
108
  }
109
109
 
110
- export { TurboMount, TurboMountController, buildRegisterFunction };
110
+ const identifierNames = (name) => {
111
+ const controllerName = camelToKebabCase(name);
112
+ return [`turbo-mount--${controllerName}`, `turbo-mount-${controllerName}`];
113
+ };
114
+ const registerComponentsBase = ({ plugin, turboMount, components, controllers, }) => {
115
+ var _a;
116
+ const controllerModules = controllers !== null && controllers !== void 0 ? controllers : [];
117
+ for (const { module, filename } of components) {
118
+ const name = filename
119
+ .replace(/\.\w*$/, "")
120
+ .replace(/^[./]*components\//, "");
121
+ const identifiers = identifierNames(name);
122
+ const controller = controllerModules.find(({ identifier }) => identifiers.includes(identifier));
123
+ const component = (_a = module.default) !== null && _a !== void 0 ? _a : module;
124
+ if (controller) {
125
+ turboMount.register(plugin, name, component, controller.controllerConstructor);
126
+ }
127
+ else {
128
+ turboMount.register(plugin, name, component);
129
+ }
130
+ }
131
+ };
132
+
133
+ export { TurboMount, TurboMountController, buildRegisterFunction, registerComponentsBase };
@@ -1,2 +1,2 @@
1
- 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};
1
+ 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"];const e=t=>t.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase();class r{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:r}=n;(null===(o=e.getAttribute("data-controller"))||void 0===o?void 0:o.includes("turbo-mount"))&&(e.setAttribute("data-turbo-mount-props-value",r.newElement.getAttribute("data-turbo-mount-props-value")||"{}"),t.preventDefault())}))}register(t,o,r,s){if(s||(s=n),this.components.has(o))throw new Error(`Component '${o}' is already registered.`);if(this.components.set(o,{component:r,plugin:t}),s){const t=`turbo-mount-${e(o)}`;this.application.register(t,s)}}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,r)=>{o.register(t,n,e,r)}}const i=t=>{const o=e(t);return[`turbo-mount--${o}`,`turbo-mount-${o}`]},u=({plugin:t,turboMount:o,components:n,controllers:e})=>{var r;const s=null!=e?e:[];for(const{module:e,filename:u}of n){const n=u.replace(/\.\w*$/,"").replace(/^[./]*components\//,""),l=i(n),p=s.find((({identifier:t})=>l.includes(t))),a=null!==(r=e.default)&&void 0!==r?r:e;p?o.register(t,n,a,p.controllerConstructor):o.register(t,n,a)}};export{r as TurboMount,n as TurboMountController,s as buildRegisterFunction,u as registerComponentsBase};
2
2
  //# sourceMappingURL=turbo-mount.min.js.map
@@ -1 +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 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<T> = {\n el: Element;\n Component: T;\n props: object;\n};\n\nexport type Plugin<T> = {\n mountComponent: (props: MountComponentProps<T>) => () => void;\n};\n\nexport type TurboMountProps = {\n application?: Application;\n};\n\ntype TurboMountComponents<T> = Map<string, { component: T; plugin: Plugin<T> }>;\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<any>;\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<T>(\n plugin: Plugin<T>,\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<T>(plugin: Plugin<T>) {\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"}
1
+ {"version":3,"file":"turbo-mount.min.js","sources":["../src/turbo-mount-controller.ts","../src/helpers.ts","../src/turbo-mount.ts","../src/registerComponentsBase.ts"],"sourcesContent":["import { Controller } from \"@hotwired/stimulus\";\nimport { ApplicationWithTurboMount } from \"./turbo-mount\";\n\nexport class TurboMountController extends Controller {\n static values = {\n props: Object,\n component: String,\n };\n static targets = [\"mount\"];\n\n private skipPropsChangeCallback = false;\n\n declare propsValue: object;\n declare componentValue: string;\n declare readonly hasMountTarget: boolean;\n declare readonly mountTarget: Element;\n\n _umountComponentCallback?: () => void;\n\n connect() {\n this._umountComponentCallback ||= this.mountComponent(\n this.mountElement,\n this.resolvedComponent,\n this.componentProps,\n );\n }\n\n disconnect() {\n this.umountComponent();\n }\n\n propsValueChanged() {\n // Prevent re-mounting the component if the props are being set by the component itself\n if (this.skipPropsChangeCallback) {\n this.skipPropsChangeCallback = false;\n return;\n }\n\n this.umountComponent();\n this._umountComponentCallback ||= this.mountComponent(\n this.mountElement,\n this.resolvedComponent,\n this.componentProps,\n );\n }\n\n get componentProps() {\n return this.propsValue;\n }\n\n get mountElement() {\n return this.hasMountTarget ? this.mountTarget : this.element;\n }\n\n get resolvedComponent() {\n return this.resolveMounted(this.componentValue).component;\n }\n\n get resolvedPlugin() {\n return this.resolveMounted(this.componentValue).plugin;\n }\n\n umountComponent() {\n this._umountComponentCallback && this._umountComponentCallback();\n this._umountComponentCallback = undefined;\n }\n\n mountComponent(el: Element, Component: unknown, props: object) {\n return this.resolvedPlugin.mountComponent({ el, Component, props });\n }\n\n resolveMounted(component: string) {\n const app = this.application as ApplicationWithTurboMount;\n return app.turboMount.resolve(component);\n }\n\n setComponentProps(props: object) {\n this.skipPropsChangeCallback = true;\n this.propsValue = props;\n }\n}\n","export const camelToKebabCase = (str: string) => {\n return str.replace(/([a-z])([A-Z])/g, \"$1-$2\").toLowerCase();\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<T> = {\n el: Element;\n Component: T;\n props: object;\n};\n\nexport type Plugin<T> = {\n mountComponent: (props: MountComponentProps<T>) => () => void;\n};\n\nexport type TurboMountProps = {\n application?: Application;\n};\n\ntype TurboMountComponents<T> = Map<string, { component: T; plugin: Plugin<T> }>;\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<any>;\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<T>(\n plugin: Plugin<T>,\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<T>(plugin: Plugin<T>) {\n return (\n turboMount: TurboMount,\n name: string,\n component: T,\n controller?: ControllerConstructor,\n ) => {\n turboMount.register(plugin, name, component, controller);\n };\n}\n","import { Definition } from \"@hotwired/stimulus\";\n\nimport { TurboMount, Plugin } from \"./turbo-mount\";\nimport { camelToKebabCase } from \"./helpers\";\n\nexport type ComponentModule = { default: never } | never;\n\nexport type ComponentDefinition = {\n filename: string;\n module: ComponentModule;\n};\n\ntype RegisterComponentsProps<T> = {\n plugin: Plugin<T>;\n turboMount: TurboMount;\n components: ComponentDefinition[];\n controllers?: Definition[];\n};\n\nconst identifierNames = (name: string) => {\n const controllerName = camelToKebabCase(name);\n\n return [`turbo-mount--${controllerName}`, `turbo-mount-${controllerName}`];\n};\n\nexport const registerComponentsBase = <T>({\n plugin,\n turboMount,\n components,\n controllers,\n}: RegisterComponentsProps<T>) => {\n const controllerModules = controllers ?? [];\n\n for (const { module, filename } of components) {\n const name = filename\n .replace(/\\.\\w*$/, \"\")\n .replace(/^[./]*components\\//, \"\");\n\n const identifiers = identifierNames(name);\n\n const controller = controllerModules.find(({ identifier }) =>\n identifiers.includes(identifier),\n );\n const component = module.default ?? module;\n\n if (controller) {\n turboMount.register(\n plugin,\n name,\n component,\n controller.controllerConstructor,\n );\n } else {\n turboMount.register(plugin, name, component);\n }\n }\n};\n"],"names":["TurboMountController","Controller","constructor","this","skipPropsChangeCallback","connect","_umountComponentCallback","mountComponent","mountElement","resolvedComponent","componentProps","disconnect","umountComponent","propsValueChanged","propsValue","hasMountTarget","mountTarget","element","resolveMounted","componentValue","component","resolvedPlugin","plugin","undefined","el","Component","props","application","turboMount","resolve","setComponentProps","values","Object","String","targets","camelToKebabCase","str","replace","toLowerCase","TurboMount","components","Map","findOrStartApplication","register","document","addEventListener","event","turboMorphEvent","target","detail","_a","getAttribute","includes","setAttribute","newElement","preventDefault","name","controller","has","Error","set","controllerName","get","hydratedApp","window","Stimulus","Application","start","buildRegisterFunction","identifierNames","registerComponentsBase","controllers","controllerModules","module","filename","identifiers","find","identifier","default","controllerConstructor"],"mappings":"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"}
@@ -54,7 +54,11 @@ module TurboMount
54
54
 
55
55
  say "Creating Turbo Mount initializer"
56
56
  template "turbo-mount.js", File.join("app/javascript/turbo-mount.js")
57
- append_to_file "app/javascript/entrypoints/application.js", %(import "./turbo-mount"\n)
57
+ begin
58
+ append_to_file js_entrypoint, %(import "./turbo-mount"\n)
59
+ rescue
60
+ say 'Could not find the application entrypoint, please add `import "./turbo-mount"` manually.', :yellow
61
+ end
58
62
  warn_about_vite_plugin if vite?
59
63
  end
60
64
 
@@ -72,6 +76,14 @@ module TurboMount
72
76
  run "bin/importmap pin #{FRAMEWORKS[framework][:pins]}"
73
77
  end
74
78
 
79
+ def js_entrypoint
80
+ if vite?
81
+ "app/javascript/entrypoints/application.js"
82
+ else
83
+ "app/javascript/application.js"
84
+ end
85
+ end
86
+
75
87
  def vite?
76
88
  Dir.glob(Rails.root.join("vite.config.*")).any?
77
89
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Turbo
4
4
  module Mount
5
- VERSION = "0.3.0"
5
+ VERSION = "0.3.2"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: turbo-mount
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svyatoslav Kryukov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-05-31 00:00:00.000000000 Z
11
+ date: 2024-06-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties