turbo-mount 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +19 -1
- data/README.md +57 -15
- data/app/assets/javascripts/turbo-mount.js +24 -1
- data/app/assets/javascripts/turbo-mount.min.js +1 -1
- data/app/assets/javascripts/turbo-mount.min.js.map +1 -1
- data/lib/generators/turbo_mount/install_generator.rb +13 -1
- data/lib/turbo/mount/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a99bcba9d009ca4dc95d68bd54a8f98e724f487c8d25eff234f406feb01c56e
|
4
|
+
data.tar.gz: 186eb9a15926d54e14999febfe36c809dbf988d30857cf3d0ba29fd58b73eafc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
[](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
|
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
|
-
- [
|
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
|
117
|
-
data-turbo-mount-
|
118
|
-
data-turbo-mount-
|
132
|
+
<div data-controller="turbo-mount"
|
133
|
+
data-turbo-mount-component-value="HexColorPicker"
|
134
|
+
data-turbo-mount-props-value="{"color":"#034"}"
|
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.
|
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
|
-
###
|
180
|
+
### Auto-Loading Components
|
165
181
|
|
166
|
-
`TurboMount` includes a `registerComponents`
|
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/
|
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
|
-
|
180
|
-
|
181
|
-
-
|
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
|
-
|
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
|
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
|
-
|
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
|
data/lib/turbo/mount/version.rb
CHANGED
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.
|
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-
|
11
|
+
date: 2024-06-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: railties
|