turbo-mount 0.3.2 → 0.4.0
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 -2
- data/README.md +31 -3
- data/app/assets/javascripts/turbo-mount/svelte.js +3 -2
- data/app/assets/javascripts/turbo-mount/svelte.min.js +1 -1
- data/app/assets/javascripts/turbo-mount/svelte.min.js.map +1 -1
- data/app/assets/javascripts/turbo-mount/svelte4.js +15 -0
- data/app/assets/javascripts/turbo-mount/svelte4.min.js +2 -0
- data/app/assets/javascripts/turbo-mount/svelte4.min.js.map +1 -0
- data/app/assets/javascripts/turbo-mount.js +5 -7
- 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/frameworks.yml +23 -0
- data/lib/generators/turbo_mount/helpers.rb +61 -0
- data/lib/generators/turbo_mount/install/turbo-mount.js.tt +1 -1
- data/lib/generators/turbo_mount/install_generator.rb +33 -66
- data/lib/generators/turbo_mount/js_package_manager.rb +57 -0
- data/lib/turbo/mount/helpers.rb +1 -1
- data/lib/turbo/mount/version.rb +1 -1
- metadata +9 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a1e390ef4fc7a3d329693a294437fa8f5d27c9157654b3a9b21621b26dd6c7c5
|
|
4
|
+
data.tar.gz: 591ea18f8b02da05da9a9d6dba5249eccdf01ec2b4d00477341df76166c0bd7d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fd306af35603685bebc4c2a7ef0fe31909fa56b521f0808562aeee3f8c90f2d5098aa846e6633356b716ced5aa51b8c5e6131252b97c5b91ccf14242aa90baff
|
|
7
|
+
data.tar.gz: 8a7221dad2c96c5c8c873e841a4007f1b45bcf6adada668af008692b035589590a230c411756f1cac86918d74521d0f6cec47d8c2125feddb1b5bfca91187ec3
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning].
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.0] - 2024-11-03
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- [BREAKING] Support Svelte 5. ([@skryukov])
|
|
15
|
+
`turbo-mount/svelte` is now the Svelte 5 plugin. The Svelte 4 plugin is now `turbo-mount/svelte4`.
|
|
16
|
+
|
|
17
|
+
## [0.3.3] - 2024-09-24
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Support components in nested directories. ([@skryukov])
|
|
22
|
+
- Support pnpm build tool. ([@jkogara])
|
|
23
|
+
|
|
10
24
|
## [0.3.2] - 2024-06-24
|
|
11
25
|
|
|
12
26
|
### Fixed
|
|
@@ -65,10 +79,13 @@ and this project adheres to [Semantic Versioning].
|
|
|
65
79
|
|
|
66
80
|
- Initial implementation. ([@skryukov])
|
|
67
81
|
|
|
82
|
+
[@jkogara]: https://github.com/jkogara
|
|
68
83
|
[@skryukov]: https://github.com/skryukov
|
|
69
84
|
|
|
70
|
-
[Unreleased]: https://github.com/skryukov/turbo-mount/compare/v0.
|
|
71
|
-
[0.
|
|
85
|
+
[Unreleased]: https://github.com/skryukov/turbo-mount/compare/v0.4.0...HEAD
|
|
86
|
+
[0.4.0]: https://github.com/skryukov/turbo-mount/compare/v0.3.3...v0.4.0
|
|
87
|
+
[0.3.3]: https://github.com/skryukov/turbo-mount/compare/v0.3.2...v0.3.3
|
|
88
|
+
[0.3.2]: https://github.com/skryukov/turbo-mount/compare/v0.3.1...v0.3.2
|
|
72
89
|
[0.3.1]: https://github.com/skryukov/turbo-mount/compare/v0.3.0...v0.3.1
|
|
73
90
|
[0.3.0]: https://github.com/skryukov/turbo-mount/compare/v0.2.3...v0.3.0
|
|
74
91
|
[0.2.3]: https://github.com/skryukov/turbo-mount/compare/v0.2.2...v0.2.3
|
data/README.md
CHANGED
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="./assets/logo.svg" title="Turbo Mount logo" width="220" height="45">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">Turbo Mount</h1>
|
|
2
6
|
|
|
3
7
|
[](https://rubygems.org/gems/turbo-mount)
|
|
4
8
|
|
|
5
|
-
`TurboMount` is a simple library that allows you to add highly interactive components from React, Vue, Svelte, and other
|
|
9
|
+
`TurboMount` is a simple library that allows you to add highly interactive components from React, Vue, Svelte, and other frameworks to your Hotwire application.
|
|
6
10
|
|
|
7
11
|
### Learn more
|
|
8
12
|
|
|
@@ -21,6 +25,7 @@
|
|
|
21
25
|
- [Supported Frameworks](#supported-frameworks)
|
|
22
26
|
- [Custom Controllers](#custom-controllers)
|
|
23
27
|
- [Auto-Loading Components](#auto-loading-components)
|
|
28
|
+
- [Components in Nested Directories](#components-in-nested-directories)
|
|
24
29
|
- [Vite Integration](#vite-integration)
|
|
25
30
|
- [ESBuild Integration](#esbuild-integration)
|
|
26
31
|
- [Mount Target](#mount-target)
|
|
@@ -142,7 +147,8 @@ This will generate the following HTML:
|
|
|
142
147
|
|
|
143
148
|
- React: `"turbo-mount/react"`
|
|
144
149
|
- Vue: `"turbo-mount/vue"`
|
|
145
|
-
- Svelte: `"turbo-mount/
|
|
150
|
+
- Svelte 4: `"turbo-mount/svelte4"`
|
|
151
|
+
- Svelte 5: `"turbo-mount/svelte"`
|
|
146
152
|
|
|
147
153
|
To add support for other frameworks, create a custom plugin. See included plugins for examples.
|
|
148
154
|
|
|
@@ -185,6 +191,28 @@ The `registerComponents` helpers search for controllers in the following paths:
|
|
|
185
191
|
- `controllers/turbo-mount/${controllerName}`
|
|
186
192
|
- `controllers/turbo-mount-${controllerName}`
|
|
187
193
|
|
|
194
|
+
#### Components in Nested Directories
|
|
195
|
+
|
|
196
|
+
Turbo Mount supports components located in nested directories. For example, if you have a component structure like:
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
components/
|
|
200
|
+
├── Dashboard/
|
|
201
|
+
│ └── WeatherWidget.tsx
|
|
202
|
+
└── ...
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
You can use the following helper to mount the component:
|
|
206
|
+
|
|
207
|
+
```erb
|
|
208
|
+
<%= turbo_mount("Dashboard/WeatherWidget") %>
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
For nested components, controllers are searched in these paths:
|
|
212
|
+
|
|
213
|
+
- `controllers/turbo_mount/dashboard/weather_widget_controller.js`
|
|
214
|
+
- `controllers/turbo_mount_dashboard__weather_widget_controller.js`
|
|
215
|
+
|
|
188
216
|
#### Vite Integration
|
|
189
217
|
|
|
190
218
|
Vite helper requires the `stimulus-vite-helpers` package to load components and controllers. Here's how to set it up:
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { buildRegisterFunction } from 'turbo-mount';
|
|
2
2
|
export { TurboMount } from 'turbo-mount';
|
|
3
|
+
import { mount, unmount } from 'svelte';
|
|
3
4
|
|
|
4
5
|
const plugin = {
|
|
5
6
|
mountComponent: (mountProps) => {
|
|
6
7
|
const { el, Component, props } = mountProps;
|
|
7
|
-
const component =
|
|
8
|
+
const component = mount(Component, { target: el, props });
|
|
8
9
|
return () => {
|
|
9
|
-
component
|
|
10
|
+
unmount(component);
|
|
10
11
|
};
|
|
11
12
|
},
|
|
12
13
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{buildRegisterFunction as o}from"turbo-mount";export{TurboMount}from"turbo-mount";const
|
|
1
|
+
import{buildRegisterFunction as o}from"turbo-mount";export{TurboMount}from"turbo-mount";import{mount as t,unmount as r}from"svelte";const n={mountComponent:o=>{const{el:n,Component:e,props:m}=o,p=t(e,{target:n,props:m});return()=>{r(p)}}},e=o(n);export{n as default,e as registerComponent};
|
|
2
2
|
//# sourceMappingURL=svelte.min.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"svelte.min.js","sources":["../../src/plugins/svelte/index.ts"],"sourcesContent":["import { buildRegisterFunction, Plugin, TurboMount } from \"turbo-mount\";\nimport {
|
|
1
|
+
{"version":3,"file":"svelte.min.js","sources":["../../src/plugins/svelte/index.ts"],"sourcesContent":["import { buildRegisterFunction, Plugin, TurboMount } from \"turbo-mount\";\nimport { Component, mount, unmount } from \"svelte\";\n\nconst plugin: Plugin<Component> = {\n mountComponent: (mountProps) => {\n const { el, Component, props } = mountProps;\n const component = mount(Component, { target: el, props });\n return () => {\n unmount(component);\n };\n },\n};\n\nconst registerComponent = buildRegisterFunction(plugin);\n\nexport { TurboMount, registerComponent };\n\nexport default plugin;\n"],"names":["plugin","mountComponent","mountProps","el","Component","props","component","mount","target","unmount","registerComponent","buildRegisterFunction"],"mappings":"oIAGA,MAAMA,EAA4B,CAChCC,eAAiBC,IACf,MAAMC,GAAEA,EAAEC,UAAEA,EAASC,MAAEA,GAAUH,EAC3BI,EAAYC,EAAMH,EAAW,CAAEI,OAAQL,EAAIE,UACjD,MAAO,KACLI,EAAQH,EAAU,CACnB,GAICI,EAAoBC,EAAsBX"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { buildRegisterFunction } from 'turbo-mount';
|
|
2
|
+
export { TurboMount } from 'turbo-mount';
|
|
3
|
+
|
|
4
|
+
const plugin = {
|
|
5
|
+
mountComponent: (mountProps) => {
|
|
6
|
+
const { el, Component, props } = mountProps;
|
|
7
|
+
const component = new Component({ target: el, props });
|
|
8
|
+
return () => {
|
|
9
|
+
component.$destroy();
|
|
10
|
+
};
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
const registerComponent = buildRegisterFunction(plugin);
|
|
14
|
+
|
|
15
|
+
export { plugin as default, registerComponent };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{buildRegisterFunction as o}from"turbo-mount";export{TurboMount}from"turbo-mount";const t={mountComponent:o=>{const{el:t,Component:r,props:n}=o,e=new r({target:t,props:n});return()=>{e.$destroy()}}},r=o(t);export{t as default,r as registerComponent};
|
|
2
|
+
//# sourceMappingURL=svelte4.min.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"svelte4.min.js","sources":["../../src/plugins/svelte4/index.ts"],"sourcesContent":["import { buildRegisterFunction, Plugin, TurboMount } from \"turbo-mount\";\nimport { ComponentType } from \"svelte\";\n\nconst plugin: Plugin<ComponentType> = {\n mountComponent: (mountProps) => {\n const { el, Component, props } = mountProps;\n\n const component = new Component({ target: el, props });\n\n return () => {\n component.$destroy();\n };\n },\n};\n\nconst registerComponent = buildRegisterFunction(plugin);\n\nexport { TurboMount, registerComponent };\n\nexport default plugin;\n"],"names":["plugin","mountComponent","mountProps","el","Component","props","component","target","$destroy","registerComponent","buildRegisterFunction"],"mappings":"wFAGA,MAAMA,EAAgC,CACpCC,eAAiBC,IACf,MAAMC,GAAEA,EAAEC,UAAEA,EAASC,MAAEA,GAAUH,EAE3BI,EAAY,IAAIF,EAAU,CAAEG,OAAQJ,EAAIE,UAE9C,MAAO,KACLC,EAAUE,UAAU,CACrB,GAICC,EAAoBC,EAAsBV"}
|
|
@@ -64,10 +64,9 @@ class TurboMount {
|
|
|
64
64
|
this.application.turboMount = this;
|
|
65
65
|
this.application.register("turbo-mount", TurboMountController);
|
|
66
66
|
document.addEventListener("turbo:before-morph-element", (event) => {
|
|
67
|
-
var _a;
|
|
68
67
|
const turboMorphEvent = event;
|
|
69
68
|
const { target, detail } = turboMorphEvent;
|
|
70
|
-
if (
|
|
69
|
+
if (target.getAttribute("data-controller")?.includes("turbo-mount")) {
|
|
71
70
|
target.setAttribute("data-turbo-mount-props-value", detail.newElement.getAttribute("data-turbo-mount-props-value") ||
|
|
72
71
|
"{}");
|
|
73
72
|
event.preventDefault();
|
|
@@ -81,7 +80,7 @@ class TurboMount {
|
|
|
81
80
|
}
|
|
82
81
|
this.components.set(name, { component, plugin });
|
|
83
82
|
if (controller) {
|
|
84
|
-
const controllerName = `turbo-mount-${camelToKebabCase(name)}`;
|
|
83
|
+
const controllerName = `turbo-mount-${camelToKebabCase(name).replace("/", "--")}`;
|
|
85
84
|
this.application.register(controllerName, controller);
|
|
86
85
|
}
|
|
87
86
|
}
|
|
@@ -108,19 +107,18 @@ function buildRegisterFunction(plugin) {
|
|
|
108
107
|
}
|
|
109
108
|
|
|
110
109
|
const identifierNames = (name) => {
|
|
111
|
-
const controllerName = camelToKebabCase(name);
|
|
110
|
+
const controllerName = camelToKebabCase(name).replace("/", "--");
|
|
112
111
|
return [`turbo-mount--${controllerName}`, `turbo-mount-${controllerName}`];
|
|
113
112
|
};
|
|
114
113
|
const registerComponentsBase = ({ plugin, turboMount, components, controllers, }) => {
|
|
115
|
-
|
|
116
|
-
const controllerModules = controllers !== null && controllers !== void 0 ? controllers : [];
|
|
114
|
+
const controllerModules = controllers ?? [];
|
|
117
115
|
for (const { module, filename } of components) {
|
|
118
116
|
const name = filename
|
|
119
117
|
.replace(/\.\w*$/, "")
|
|
120
118
|
.replace(/^[./]*components\//, "");
|
|
121
119
|
const identifiers = identifierNames(name);
|
|
122
120
|
const controller = controllerModules.find(({ identifier }) => identifiers.includes(identifier));
|
|
123
|
-
const component =
|
|
121
|
+
const component = module.default ?? module;
|
|
124
122
|
if (controller) {
|
|
125
123
|
turboMount.register(plugin, name, component, controller.controllerConstructor);
|
|
126
124
|
}
|
|
@@ -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"];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=>{
|
|
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=>{const o=t,{target:n,detail:e}=o;n.getAttribute("data-controller")?.includes("turbo-mount")&&(n.setAttribute("data-turbo-mount-props-value",e.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).replace("/","--")}`;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).replace("/","--");return[`turbo-mount--${o}`,`turbo-mount-${o}`]},u=({plugin:t,turboMount:o,components:n,controllers:e})=>{const r=e??[];for(const{module:e,filename:s}of n){const n=s.replace(/\.\w*$/,"").replace(/^[./]*components\//,""),u=i(n),p=r.find((({identifier:t})=>u.includes(t))),l=e.default??e;p?o.register(t,n,l,p.controllerConstructor):o.register(t,n,l)}};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/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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"}
|
|
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).replace(\"/\", \"--\")}`;\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).replace(\"/\", \"--\");\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","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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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
react:
|
|
2
|
+
pins: "react react-dom react-dom/client"
|
|
3
|
+
npm_packages: "react react-dom"
|
|
4
|
+
vite_plugin: "@vitejs/plugin-react"
|
|
5
|
+
extension: "jsx"
|
|
6
|
+
|
|
7
|
+
vue:
|
|
8
|
+
pins: "vue"
|
|
9
|
+
npm_packages: "vue"
|
|
10
|
+
vite_plugin: "@vitejs/plugin-vue"
|
|
11
|
+
extension: "vue"
|
|
12
|
+
|
|
13
|
+
svelte4:
|
|
14
|
+
pins: "svelte"
|
|
15
|
+
npm_packages: "svelte@4"
|
|
16
|
+
vite_plugin: "@sveltejs/vite-plugin-svelte"
|
|
17
|
+
extension: "svelte"
|
|
18
|
+
|
|
19
|
+
svelte:
|
|
20
|
+
pins: "svelte"
|
|
21
|
+
npm_packages: "svelte@5"
|
|
22
|
+
vite_plugin: "@sveltejs/vite-plugin-svelte"
|
|
23
|
+
extension: "svelte"
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module TurboMount
|
|
2
|
+
module Generators
|
|
3
|
+
module Helpers
|
|
4
|
+
### FS Helpers
|
|
5
|
+
def js_destination_path
|
|
6
|
+
return ViteRuby.config.source_code_dir if defined?(ViteRuby)
|
|
7
|
+
|
|
8
|
+
if file?("config/vite.json")
|
|
9
|
+
source_code_dir = JSON.parse(File.read(file_path("config/vite.json"))).dig("all", "sourceCodeDir")
|
|
10
|
+
return source_code_dir || "app/frontend"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
"app/javascript"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def js_destination_root
|
|
17
|
+
file_path(js_destination_path)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def js_entrypoint
|
|
21
|
+
if vite?
|
|
22
|
+
js_file_path "entrypoints/application.js"
|
|
23
|
+
else
|
|
24
|
+
js_file_path "application.js"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def js_file_path(*relative_path)
|
|
29
|
+
File.join(js_destination_root, *relative_path)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def file?(*relative_path)
|
|
33
|
+
File.file?(file_path(*relative_path))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def file_path(*relative_path)
|
|
37
|
+
File.join(destination_root, *relative_path)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def vite?
|
|
41
|
+
file?("config/vite.json") && Dir.glob(file_path("vite.config.*")).any?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Interactivity Helpers
|
|
45
|
+
def ask(*)
|
|
46
|
+
unless options[:interactive]
|
|
47
|
+
say_error "Specify all options when running the generator non-interactively.", :red
|
|
48
|
+
exit(1)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
super
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def yes?(*)
|
|
55
|
+
return false unless options[:interactive]
|
|
56
|
+
|
|
57
|
+
super
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -13,6 +13,6 @@ const turboMount = new TurboMount();
|
|
|
13
13
|
// If you want to automatically register components use:
|
|
14
14
|
// import { registerComponents } from "turbo-mount/registerComponents/<%= framework %>";
|
|
15
15
|
// const controllers = import.meta.glob("/controllers/**/*_controller.js", { eager: true });
|
|
16
|
-
// const components = import.meta.glob("/components
|
|
16
|
+
// const components = import.meta.glob("/components/**/*.<%= extension %>", { eager: true });
|
|
17
17
|
// registerComponents({ turboMount, components, controllers });
|
|
18
18
|
<%- end -%>
|
|
@@ -1,37 +1,39 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "rails/generators"
|
|
5
|
+
require "rails/generators/base"
|
|
6
|
+
|
|
7
|
+
require_relative "helpers"
|
|
8
|
+
require_relative "js_package_manager"
|
|
9
|
+
|
|
3
10
|
module TurboMount
|
|
4
11
|
module Generators
|
|
5
12
|
class InstallGenerator < Rails::Generators::Base
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
npm_packages: "react react-dom",
|
|
10
|
-
vite_plugin: "@vitejs/plugin-react"
|
|
11
|
-
},
|
|
12
|
-
"vue" => {
|
|
13
|
-
pins: "vue",
|
|
14
|
-
npm_packages: "vue",
|
|
15
|
-
vite_plugin: "@vitejs/plugin-vue"
|
|
16
|
-
},
|
|
17
|
-
"svelte" => {
|
|
18
|
-
pins: "svelte",
|
|
19
|
-
npm_packages: "svelte",
|
|
20
|
-
vite_plugin: "@sveltejs/vite-plugin-svelte"
|
|
21
|
-
}
|
|
22
|
-
}.freeze
|
|
13
|
+
include Helpers
|
|
14
|
+
|
|
15
|
+
FRAMEWORKS = YAML.load_file(File.expand_path("./frameworks.yml", __dir__))
|
|
23
16
|
|
|
24
17
|
source_root File.expand_path("install", __dir__)
|
|
25
18
|
|
|
19
|
+
class_option :framework, type: :string,
|
|
20
|
+
desc: "The framework you want to use with Turbo Mount",
|
|
21
|
+
enum: FRAMEWORKS.keys,
|
|
22
|
+
default: nil
|
|
23
|
+
|
|
24
|
+
class_option :package_manager, type: :string, default: nil,
|
|
25
|
+
enum: JSPackageManager.package_managers,
|
|
26
|
+
desc: "The package manager you want to use to install Turbo Mount"
|
|
27
|
+
|
|
28
|
+
class_option :verbose, type: :boolean, default: false,
|
|
29
|
+
desc: "Run the generator in verbose mode"
|
|
30
|
+
|
|
26
31
|
def install
|
|
27
32
|
say "Installing Turbo Mount"
|
|
28
33
|
|
|
29
|
-
|
|
30
|
-
say "Could not find a package.json or config/importmap.rb file to add the turbo-mount dependency to, please add it manually.", :red
|
|
31
|
-
exit!
|
|
32
|
-
end
|
|
34
|
+
package_manager.validate!
|
|
33
35
|
|
|
34
|
-
if importmap?
|
|
36
|
+
if package_manager.importmap?
|
|
35
37
|
install_importmap
|
|
36
38
|
else
|
|
37
39
|
install_nodejs
|
|
@@ -43,17 +45,10 @@ module TurboMount
|
|
|
43
45
|
private
|
|
44
46
|
|
|
45
47
|
def install_nodejs
|
|
46
|
-
|
|
47
|
-
when "npm"
|
|
48
|
-
run "npm install turbo-mount #{FRAMEWORKS[framework][:npm_packages]}"
|
|
49
|
-
when "yarn"
|
|
50
|
-
run "yarn add turbo-mount #{FRAMEWORKS[framework][:npm_packages]}"
|
|
51
|
-
when "bun"
|
|
52
|
-
run "bun add turbo-mount #{FRAMEWORKS[framework][:npm_packages]}"
|
|
53
|
-
end
|
|
48
|
+
package_manager.add_dependencies("turbo-mount", FRAMEWORKS[framework][:npm_packages])
|
|
54
49
|
|
|
55
50
|
say "Creating Turbo Mount initializer"
|
|
56
|
-
template "turbo-mount.js",
|
|
51
|
+
template "turbo-mount.js", js_file_path("turbo-mount.js")
|
|
57
52
|
begin
|
|
58
53
|
append_to_file js_entrypoint, %(import "./turbo-mount"\n)
|
|
59
54
|
rescue
|
|
@@ -64,7 +59,7 @@ module TurboMount
|
|
|
64
59
|
|
|
65
60
|
def install_importmap
|
|
66
61
|
say "Creating Turbo Mount initializer"
|
|
67
|
-
template "turbo-mount.js",
|
|
62
|
+
template "turbo-mount.js", js_file_path("turbo-mount-initializer.js")
|
|
68
63
|
append_to_file "app/javascript/application.js", %(import "turbo-mount-initializer"\n)
|
|
69
64
|
|
|
70
65
|
say "Pinning Turbo Mount to the importmap"
|
|
@@ -73,51 +68,23 @@ module TurboMount
|
|
|
73
68
|
append_to_file "config/importmap.rb", %(pin "turbo-mount-initializer"\n)
|
|
74
69
|
|
|
75
70
|
say "Pinning framework dependencies to the importmap"
|
|
76
|
-
|
|
77
|
-
end
|
|
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
|
-
|
|
87
|
-
def vite?
|
|
88
|
-
Dir.glob(Rails.root.join("vite.config.*")).any?
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def importmap?
|
|
92
|
-
build_tool == "importmap"
|
|
71
|
+
package_manager.add_dependencies(FRAMEWORKS[framework][:pins])
|
|
93
72
|
end
|
|
94
73
|
|
|
95
74
|
def warn_about_vite_plugin
|
|
96
75
|
say "Make sure to install and add #{FRAMEWORKS[framework][:vite_plugin]} to your Vite config", :yellow
|
|
97
76
|
end
|
|
98
77
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
@build_tool = detect_build_tool
|
|
78
|
+
def package_manager
|
|
79
|
+
@package_manager ||= JSPackageManager.new(self)
|
|
103
80
|
end
|
|
104
81
|
|
|
105
|
-
def
|
|
106
|
-
|
|
107
|
-
if Rails.root.join("package-lock.json").exist?
|
|
108
|
-
"npm"
|
|
109
|
-
elsif Rails.root.join("bun.config.js").exist?
|
|
110
|
-
"bun"
|
|
111
|
-
else
|
|
112
|
-
"yarn"
|
|
113
|
-
end
|
|
114
|
-
elsif Rails.root.join("config/importmap.rb").exist?
|
|
115
|
-
"importmap"
|
|
116
|
-
end
|
|
82
|
+
def extension
|
|
83
|
+
FRAMEWORKS[framework][:extension]
|
|
117
84
|
end
|
|
118
85
|
|
|
119
86
|
def framework
|
|
120
|
-
@framework ||= ask("What framework do you want to use with Turbo Mount?", limited_to: FRAMEWORKS.keys, default: "react")
|
|
87
|
+
@framework ||= options[:framework] || ask("What framework do you want to use with Turbo Mount?", :green, limited_to: FRAMEWORKS.keys, default: "react")
|
|
121
88
|
end
|
|
122
89
|
end
|
|
123
90
|
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module TurboMount
|
|
2
|
+
module Generators
|
|
3
|
+
class JSPackageManager
|
|
4
|
+
def self.package_managers
|
|
5
|
+
%w[npm yarn bun pnpm importmap]
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def initialize(generator)
|
|
9
|
+
@generator = generator
|
|
10
|
+
@package_manager = generator.options[:package_manager] || detect_package_manager
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def validate!
|
|
14
|
+
return if @package_manager.present?
|
|
15
|
+
|
|
16
|
+
@generator.say "Could not find a package.json or config/importmap.rb file to add the turbo-mount dependency to, please add it manually.", :red
|
|
17
|
+
exit(1)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def importmap?
|
|
21
|
+
@package_manager == "importmap"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def add_dependencies(*dependencies)
|
|
25
|
+
cmd =
|
|
26
|
+
if importmap?
|
|
27
|
+
"bin/importmap pin #{dependencies.join(" ")}"
|
|
28
|
+
else
|
|
29
|
+
"#{@package_manager} add #{dependencies.join(" ")}#{@generator.options[:verbose] ? "" : " --silent"}"
|
|
30
|
+
end
|
|
31
|
+
@generator.in_root { @generator.run cmd }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def detect_package_manager
|
|
37
|
+
if file?("package.json")
|
|
38
|
+
if file?("package-lock.json")
|
|
39
|
+
"npm"
|
|
40
|
+
elsif file?("pnpm-lock.yaml")
|
|
41
|
+
"pnpm"
|
|
42
|
+
elsif file?("bun.lockb")
|
|
43
|
+
"bun"
|
|
44
|
+
else
|
|
45
|
+
"yarn"
|
|
46
|
+
end
|
|
47
|
+
elsif file?("config/importmap.rb")
|
|
48
|
+
"importmap"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def file?(*relative_path)
|
|
53
|
+
@generator.file?(*relative_path)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/turbo/mount/helpers.rb
CHANGED
|
@@ -6,7 +6,7 @@ module Turbo
|
|
|
6
6
|
def turbo_mount(component_name, props: {}, tag: "div", **attrs, &block)
|
|
7
7
|
raise TypeError, "Component name expected" unless component_name.is_a? String
|
|
8
8
|
|
|
9
|
-
controller_name = "turbo-mount-#{component_name.underscore.dasherize}"
|
|
9
|
+
controller_name = "turbo-mount-#{component_name.underscore.dasherize.gsub("/", "--")}"
|
|
10
10
|
attrs["data-controller"] = controller_name
|
|
11
11
|
prefix = "data-#{controller_name}"
|
|
12
12
|
attrs["#{prefix}-component-value"] = component_name
|
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.
|
|
4
|
+
version: 0.4.0
|
|
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-11-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: railties
|
|
@@ -44,11 +44,17 @@ files:
|
|
|
44
44
|
- app/assets/javascripts/turbo-mount/svelte.js
|
|
45
45
|
- app/assets/javascripts/turbo-mount/svelte.min.js
|
|
46
46
|
- app/assets/javascripts/turbo-mount/svelte.min.js.map
|
|
47
|
+
- app/assets/javascripts/turbo-mount/svelte4.js
|
|
48
|
+
- app/assets/javascripts/turbo-mount/svelte4.min.js
|
|
49
|
+
- app/assets/javascripts/turbo-mount/svelte4.min.js.map
|
|
47
50
|
- app/assets/javascripts/turbo-mount/vue.js
|
|
48
51
|
- app/assets/javascripts/turbo-mount/vue.min.js
|
|
49
52
|
- app/assets/javascripts/turbo-mount/vue.min.js.map
|
|
53
|
+
- lib/generators/turbo_mount/frameworks.yml
|
|
54
|
+
- lib/generators/turbo_mount/helpers.rb
|
|
50
55
|
- lib/generators/turbo_mount/install/turbo-mount.js.tt
|
|
51
56
|
- lib/generators/turbo_mount/install_generator.rb
|
|
57
|
+
- lib/generators/turbo_mount/js_package_manager.rb
|
|
52
58
|
- lib/turbo/mount.rb
|
|
53
59
|
- lib/turbo/mount/engine.rb
|
|
54
60
|
- lib/turbo/mount/helpers.rb
|
|
@@ -78,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
78
84
|
- !ruby/object:Gem::Version
|
|
79
85
|
version: '0'
|
|
80
86
|
requirements: []
|
|
81
|
-
rubygems_version: 3.5.
|
|
87
|
+
rubygems_version: 3.5.17
|
|
82
88
|
signing_key:
|
|
83
89
|
specification_version: 4
|
|
84
90
|
summary: Use React, Vue, Svelte, and other components with Hotwire.
|