turbo-mount 0.2.2 → 0.3.0

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: a4e82785211d3df4bf66f934af1bdc026e9f7a9b0f3074749eda539fedca318b
4
- data.tar.gz: 93f5c90a4e336851ded6f763fb5636b4423323594ce4db06a610761e8e30efd5
3
+ metadata.gz: 3565dc0d8e80907e11600573cfb18049fe5360c9551facefb9a22d871bebad2e
4
+ data.tar.gz: 2e82006304c22c3b663511abbefaee3a0fc8e96aa972b044e1fca640cfd9c262
5
5
  SHA512:
6
- metadata.gz: 2eba5a80216047b8a5d1f20b938f1eb5b49ca34646dfb555e4563e053bde2029e62ad10fc88880b2256ee1dcabc0d77b66270740130c45d17d19e065200778f4
7
- data.tar.gz: 82a3843e93c3b9c405344fd85145985ad4e2bf7cdf48bad43526fd264a004bb87c67ea79213066bf7b9c74ec25dc161dcc24123eb8828e590ffff95d1ba9fc48
6
+ metadata.gz: 2490534f21cf102b00c00524457be4b6b6f3d048e2d0477ad20bdbf2818704d5b39580bc9f71d5f33de6f39fcdd97faa20e0c0cc05a3c338225091e1096fa428
7
+ data.tar.gz: 6154769b9a73b2bf3e65e0c6a395a154109466d698f79dc5483378ed7f68cce060c41183725f240fff942a3cf09e03ac909b2f0d0f0da70a574a6712e28e1251
data/CHANGELOG.md CHANGED
@@ -7,6 +7,30 @@ and this project adheres to [Semantic Versioning].
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.0] - 2024-05-31
11
+
12
+ ### Added
13
+
14
+ - Installation script. ([@skryukov])
15
+
16
+ ### Changed
17
+
18
+ - [BREAKING] New API without controller inheritance. ([@skryukov])
19
+ To migrate to the new API:
20
+ - Replace `new TurboMountReact()` (or any other framework specific constructor) with `new TurboMount()`
21
+ - Replace `turboMount.register(...)` with `registerComponent(turboMount, ...)`
22
+ - Replace `turbo_mount_react_component` (or any other framework specific helper) with `turbo_mount`
23
+ - Also see the new API for plugins and custom controllers in the README.
24
+
25
+ ## [0.2.3] - 2024-05-12
26
+
27
+ ### Added
28
+
29
+ - Add a mount target to the base controller. ([@skryukov])
30
+ - Add `registerComponents` helper for vite. ([@skryukov])
31
+ - Allow to omit the `application` property in the constructor. ([@skryukov])
32
+ `TurboMount` will try to find the application in the `window.Stimulus` and will initialize new one if not found.
33
+
10
34
  ## [0.2.2] - 2024-05-09
11
35
 
12
36
  ### Fixed
@@ -27,9 +51,11 @@ and this project adheres to [Semantic Versioning].
27
51
 
28
52
  [@skryukov]: https://github.com/skryukov
29
53
 
30
- [Unreleased]: https://github.com/skryukov/turbo-mount/compare/v0.2.2...HEAD
31
- [0.2.2]: https://github.com/skryukov/turbo-mount/commits/v0.2.2
32
- [0.2.0]: https://github.com/skryukov/turbo-mount/commits/v0.2.0
54
+ [Unreleased]: https://github.com/skryukov/turbo-mount/compare/v0.3.0...HEAD
55
+ [0.3.0]: https://github.com/skryukov/turbo-mount/compare/v0.2.3...v0.3.0
56
+ [0.2.3]: https://github.com/skryukov/turbo-mount/compare/v0.2.2...v0.2.3
57
+ [0.2.2]: https://github.com/skryukov/turbo-mount/compare/v0.2.0...v0.2.2
58
+ [0.2.0]: https://github.com/skryukov/turbo-mount/compare/v0.1.0...v0.2.0
33
59
  [0.1.0]: https://github.com/skryukov/turbo-mount/commits/v0.1.0
34
60
 
35
61
  [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
data/README.md CHANGED
@@ -1,57 +1,143 @@
1
- # Turbo::Mount
1
+ # Turbo Mount
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/turbo-mount.svg)](https://rubygems.org/gems/turbo-mount)
4
4
 
5
- `Turbo::Mount` is a simple gem 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 frameworks to your Hotwire application.
6
+
7
+ ## Table of Contents
8
+ - [Installation](#installation)
9
+ - [Importmaps](#importmaps)
10
+ - [Usage](#usage)
11
+ - [Initialization](#initialization)
12
+ - [Standard Initialization](#standard-initialization)
13
+ - [Simplified Initialization](#simplified-initialization)
14
+ - [Plugin-Specific Initialization](#plugin-specific-initialization)
15
+ - [View Helpers](#view-helpers)
16
+ - [Supported Frameworks](#supported-frameworks)
17
+ - [Custom Controllers](#custom-controllers)
18
+ - [Vite Integration](#vite-integration)
19
+ - [Mount Target](#mount-target)
20
+ - [License](#license)
21
+
22
+ <a href="https://evilmartians.com/?utm_source=turbo-mount&utm_campaign=project_page">
23
+ <img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Built by Evil Martians" width="236" height="54">
24
+ </a>
6
25
 
7
26
  ## Installation
8
27
 
9
- Install the gem and add to the application's Gemfile by executing:
28
+ To install Turbo Mount, add the following line to your `Gemfile` and run `bundle install`:
10
29
 
11
- $ bundle add turbo-mount
30
+ ```ruby
31
+ gem "turbo-mount"
32
+ ```
33
+
34
+ ### Automatic Installation
35
+
36
+ Run the following command to install the necessary files:
37
+
38
+ ```bash
39
+ bin/rails generate turbo_mount:install
40
+ ```
41
+
42
+ This will add `turbo-mount` package and framework dependencies to your `package.json` or `importmap.rb`, and create the Turbo Mount initialization file.
43
+
44
+ ### Manual Installation
45
+
46
+ You can also install the necessary JavaScript files manually.
47
+
48
+ If your project utilizes build tools such as [Vite](http://vite-ruby.netlify.app), also install the `turbo-mount` package:
49
+
50
+ ```bash
51
+ npm install turbo-mount
52
+ # or with yarn
53
+ yarn add turbo-mount
12
54
 
13
- If bundler is not being used to manage dependencies, install the gem by executing:
55
+ # and the desired framework
56
+ npm install react react-dom
57
+ # or
58
+ npm install vue
59
+ # or
60
+ npm install svelte
61
+ ```
62
+
63
+ If you're using Vite, don't forget to add [framework-specific plugins](https://vitejs.dev/plugins) to your `vite.config.js`.
64
+
65
+ ### Importmaps
66
+ To use `TurboMount` with importmaps, you need to pin the necessary JavaScript files in your `config/importmap.rb`:
67
+
68
+ ```ruby
69
+ pin "turbo-mount", to: "turbo-mount.min.js"
70
+ pin "turbo-mount/react", to: "turbo-mount/react.min.js"
71
+ ```
72
+
73
+ This ensures that `turbo-mount` and its plugins are available in your application.
74
+
75
+ Also pin the desired framework:
76
+
77
+ ```bash
78
+ bin/importmap pin react react-dom react-dom/client
79
+ # or
80
+ bin/importmap pin vue
81
+ # or
82
+ bin/importmap pin svelte
83
+ ```
14
84
 
15
- $ gem install turbo-mount
85
+ Note: Importmap-only mode is quite limited in terms of JavaScript dependencies. If you're using a more complex setup, consider using a bundler like Vite.
16
86
 
17
87
  ## Usage
18
88
 
19
- First, you need to initialize `TurboMount` and register the components you want to use:
89
+ ### Initialization
90
+
91
+ 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:
20
92
 
21
93
  ```js
22
- import { Application } from "@hotwired/stimulus"
94
+ import { TurboMount } from "turbo-mount";
95
+ import { registerComponent } from "turbo-mount/react";
96
+ import { HexColorPicker } from 'react-colorful';
23
97
 
24
- const application = Application.start()
98
+ const turboMount = new TurboMount(); // or new TurboMount({ application })
25
99
 
26
- import { TurboMount } from "turbo-mount"
27
- import plugin from "turbo-mount/react"
100
+ registerComponent(turboMount, "HexColorPicker", HexColorPicker);
101
+ ```
28
102
 
29
- // Initialize TurboMount and register the react stimulus controller
30
- const turboMount = new TurboMount({application, plugin});
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.
31
104
 
32
- // Register the components you want to use
33
- import { SketchPicker } from 'react-color'
34
- turboMount.register('SketchPicker', SketchPicker);
35
- ```
105
+ ### View Helpers
36
106
 
37
- Now you can use view helpers to mount the components:
107
+ Use the following helpers to mount components in your views:
38
108
 
39
109
  ```erb
40
- <%= turbo_mount_component("SketchPicker", framework: "react", props: {color: "#034"}) %>
110
+ <%= turbo_mount("HexColorPicker", props: {color: "#034"}, class: "mb-5") %>
111
+ ```
41
112
 
42
- <%# or using alias %>
113
+ This will generate the following HTML:
43
114
 
44
- <%= turbo_mount_react_component("SketchPicker", props: {color: "#430"}) %>
115
+ ```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;}"
119
+ class="mb-5">
120
+ </div>
45
121
  ```
46
122
 
47
- In case you need to customize the component's behavior, or pass functions as props, you can create a custom controller:
123
+ ### Supported Frameworks
48
124
 
49
- ```js
50
- // javascript/controllers/turbo_mount_react_sketch_picker_controller.js
125
+ `TurboMount` supports the following frameworks:
51
126
 
52
- import { TurboMountReactController } from "turbo-mount"
127
+ - React: `"turbo-mount/react"`
128
+ - Vue: `"turbo-mount/vue"`
129
+ - Svelte: `"turbo-mount/svelte"`
130
+
131
+ To add support for other frameworks, create a custom plugin. See included plugins for examples.
132
+
133
+ ### Custom Controllers
134
+
135
+ To customize component behavior or pass functions as props, create a custom controller:
136
+
137
+ ```js
138
+ import { TurboMountController } from "turbo-mount";
53
139
 
54
- export default class extends TurboMountReactController {
140
+ export default class extends TurboMountController {
55
141
  get componentProps() {
56
142
  return {
57
143
  ...this.propsValue,
@@ -60,26 +146,50 @@ export default class extends TurboMountReactController {
60
146
  }
61
147
 
62
148
  onChange = (color) => {
63
- this.propsValue = { ...this.propsValue, color: color.hex };
149
+ // same as this.propsValue = { ...this.propsValue, color };
150
+ // but skips the rerendering of the component:
151
+ this.componentProps = { ...this.propsValue, color };
64
152
  };
65
153
  }
66
154
  ```
67
155
 
68
- Then pass this controller the register method:
156
+ Then pass this controller to the `registerComponent` method:
69
157
 
70
158
  ```js
71
- turboMount.register('SketchPicker', SketchPicker, TurboMountReactController);
159
+ import HexColorPickerController from "controllers/turbo_mount/hex_color_picker_controller";
160
+
161
+ registerComponent(turboMount, "HexColorPicker", HexColorPicker, HexColorPickerController);
72
162
  ```
73
163
 
74
- ## Development
164
+ ### Vite Integration
165
+
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:
75
167
 
76
- After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
168
+ ```js
169
+ import { TurboMount } from "turbo-mount/react";
170
+ import { registerComponents } from "turbo-mount/registerComponents/react";
77
171
 
78
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
172
+ const controllers = import.meta.glob("./**/*_controller.js", { eager: true });
173
+ const components = import.meta.glob("/components/**/*.jsx", { eager: true });
174
+
175
+ const turboMount = new TurboMount();
176
+ registerComponents({ turboMount, components, controllers });
177
+ ```
79
178
 
80
- ## Contributing
179
+ The `registerComponents` helper searches for controllers in the following paths:
180
+ - `controllers/turbo-mount/${controllerName}`
181
+ - `controllers/turbo-mount-${controllerName}`
81
182
 
82
- Bug reports and pull requests are welcome on GitHub at https://github.com/skryukov/turbo-mount.
183
+ ### Mount Target
184
+
185
+ To specify a non-root mount target, use the `data-<%= controller_name %>-target="mount"` attribute:
186
+
187
+ ```erb
188
+ <%= turbo_mount("HexColorPicker", props: {color: "#430"}) do |controller_name| %>
189
+ <h3>Color picker</h3>
190
+ <div data-<%= controller_name %>-target="mount"></div>
191
+ <% end %>
192
+ ```
83
193
 
84
194
  ## License
85
195
 
@@ -1,22 +1,18 @@
1
+ import { buildRegisterFunction } from 'turbo-mount';
2
+ export { TurboMount } from 'turbo-mount';
1
3
  import { createElement } from 'react';
2
4
  import { createRoot } from 'react-dom/client';
3
- import { TurboMountController } from 'turbo-mount';
4
5
 
5
- class TurboMountReactController extends TurboMountController {
6
- constructor() {
7
- super(...arguments);
8
- this.framework = "react";
9
- }
10
- mountComponent(el, Component, props) {
6
+ const plugin = {
7
+ mountComponent: (mountProps) => {
8
+ const { el, Component, props } = mountProps;
11
9
  const root = createRoot(el);
12
10
  root.render(createElement(Component, props));
13
- return () => { root.unmount(); };
14
- }
15
- }
16
-
17
- const plugin = {
18
- framework: "react",
19
- controller: TurboMountReactController
11
+ return () => {
12
+ root.unmount();
13
+ };
14
+ },
20
15
  };
16
+ const registerComponent = buildRegisterFunction(plugin);
21
17
 
22
- export { plugin as default };
18
+ export { plugin as default, registerComponent };
@@ -1,2 +1,2 @@
1
- import{createElement as r}from"react";import{createRoot as t}from"react-dom/client";import{TurboMountController as o}from"turbo-mount";const e={framework:"react",controller:class extends o{constructor(){super(...arguments),this.framework="react"}mountComponent(o,e,n){const m=t(o);return m.render(r(e,n)),()=>{m.unmount()}}}};export{e as default};
1
+ import{buildRegisterFunction as o}from"turbo-mount";export{TurboMount}from"turbo-mount";import{createElement as t}from"react";import{createRoot as r}from"react-dom/client";const n={mountComponent:o=>{const{el:n,Component:m,props:e}=o,u=r(n);return u.render(t(m,e)),()=>{u.unmount()}}},m=o(n);export{n as default,m as registerComponent};
2
2
  //# sourceMappingURL=react.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"react.min.js","sources":["../../src/plugins/react/index.ts","../../src/plugins/react/turbo-mount-react-controller.ts"],"sourcesContent":["import {Plugin} from \"turbo-mount\";\n\nimport {TurboMountReactController} from \"./turbo-mount-react-controller\";\n\nconst plugin: Plugin = {\n framework: \"react\",\n controller: TurboMountReactController\n}\n\nexport default plugin;\n","import {ComponentType, createElement} from \"react\";\nimport {createRoot} from \"react-dom/client\";\nimport {TurboMountController} from \"turbo-mount\";\n\nexport class TurboMountReactController extends TurboMountController<ComponentType> {\n framework = \"react\"\n\n mountComponent(el: Element, Component: ComponentType, props: object) {\n const root = createRoot(el);\n root.render(createElement(Component, props))\n\n return () => { root.unmount() }\n }\n}\n"],"names":["plugin","framework","controller","TurboMountController","constructor","this","mountComponent","el","Component","props","root","createRoot","render","createElement","unmount"],"mappings":"uIAIA,MAAMA,EAAiB,CACnBC,UAAW,QACXC,WCFE,cAAyCC,EAA/C,WAAAC,uBACIC,KAASJ,UAAG,OAQf,CANG,cAAAK,CAAeC,EAAaC,EAA0BC,GAClD,MAAMC,EAAOC,EAAWJ,GAGxB,OAFAG,EAAKE,OAAOC,EAAcL,EAAWC,IAE9B,KAAQC,EAAKI,SAAS,CAChC"}
1
+ {"version":3,"file":"react.min.js","sources":["../../src/plugins/react/index.ts"],"sourcesContent":["import { buildRegisterFunction, Plugin, TurboMount } from \"turbo-mount\";\nimport { ComponentType, createElement } from \"react\";\nimport { createRoot } from \"react-dom/client\";\n\nconst plugin: Plugin<ComponentType> = {\n mountComponent: (mountProps) => {\n const { el, Component, props } = mountProps;\n const root = createRoot(el);\n root.render(createElement(Component, props));\n\n return () => {\n root.unmount();\n };\n },\n};\n\nconst registerComponent = buildRegisterFunction(plugin);\n\nexport { TurboMount, registerComponent };\n\nexport default plugin;\n"],"names":["plugin","mountComponent","mountProps","el","Component","props","root","createRoot","render","createElement","unmount","registerComponent","buildRegisterFunction"],"mappings":"4KAIA,MAAMA,EAAgC,CACpCC,eAAiBC,IACf,MAAMC,GAAEA,EAAEC,UAAEA,EAASC,MAAEA,GAAUH,EAC3BI,EAAOC,EAAWJ,GAGxB,OAFAG,EAAKE,OAAOC,EAAcL,EAAWC,IAE9B,KACLC,EAAKI,SAAS,CACf,GAICC,EAAoBC,EAAsBZ"}
@@ -1,19 +1,15 @@
1
- import { TurboMountController } from 'turbo-mount';
2
-
3
- class TurboMountSvelteController extends TurboMountController {
4
- constructor() {
5
- super(...arguments);
6
- this.framework = "svelte";
7
- }
8
- mountComponent(el, Component, props) {
9
- const component = new Component({ target: el, props });
10
- return () => { component.$destroy(); };
11
- }
12
- }
1
+ import { buildRegisterFunction } from 'turbo-mount';
2
+ export { TurboMount } from 'turbo-mount';
13
3
 
14
4
  const plugin = {
15
- framework: "svelte",
16
- controller: TurboMountSvelteController
5
+ mountComponent: (mountProps) => {
6
+ const { el, Component, props } = mountProps;
7
+ const component = new Component({ target: el, props });
8
+ return () => {
9
+ component.$destroy();
10
+ };
11
+ },
17
12
  };
13
+ const registerComponent = buildRegisterFunction(plugin);
18
14
 
19
- export { plugin as default };
15
+ export { plugin as default, registerComponent };
@@ -1,2 +1,2 @@
1
- import{TurboMountController as t}from"turbo-mount";const o={framework:"svelte",controller:class extends t{constructor(){super(...arguments),this.framework="svelte"}mountComponent(t,o,r){const e=new o({target:t,props:r});return()=>{e.$destroy()}}}};export{o as default};
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
2
  //# sourceMappingURL=svelte.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"svelte.min.js","sources":["../../src/plugins/svelte/index.ts","../../src/plugins/svelte/turbo-mount-svelte-controller.ts"],"sourcesContent":["import {Plugin} from \"turbo-mount\";\n\nimport {TurboMountSvelteController} from \"./turbo-mount-svelte-controller\";\n\nconst plugin: Plugin = {\n framework: \"svelte\",\n controller: TurboMountSvelteController\n}\n\nexport default plugin;\n","import {ComponentType} from \"svelte\";\nimport {TurboMountController} from \"turbo-mount\";\n\nexport class TurboMountSvelteController extends TurboMountController<ComponentType> {\n framework = \"svelte\"\n\n mountComponent(el: Element, Component: ComponentType, props: object) {\n const component = new Component({ target: el, props })\n\n return () => { component.$destroy() }\n }\n}\n"],"names":["plugin","framework","controller","TurboMountController","constructor","this","mountComponent","el","Component","props","component","target","$destroy"],"mappings":"mDAIA,MAAMA,EAAiB,CACnBC,UAAW,SACXC,WCHE,cAA0CC,EAAhD,WAAAC,uBACIC,KAASJ,UAAG,QAOf,CALG,cAAAK,CAAeC,EAAaC,EAA0BC,GAClD,MAAMC,EAAY,IAAIF,EAAU,CAAEG,OAAQJ,EAAIE,UAE9C,MAAO,KAAQC,EAAUE,UAAU,CACtC"}
1
+ {"version":3,"file":"svelte.min.js","sources":["../../src/plugins/svelte/index.ts"],"sourcesContent":["import { buildRegisterFunction, Plugin, TurboMount } from \"turbo-mount\";\nimport { ComponentType } from \"svelte\";\n\nconst plugin: Plugin<ComponentType> = {\n mountComponent: (mountProps) => {\n const { el, Component, props } = mountProps;\n const component = new Component({ target: el, props });\n\n return () => {\n component.$destroy();\n };\n },\n};\n\nconst registerComponent = buildRegisterFunction(plugin);\n\nexport { TurboMount, registerComponent };\n\nexport default plugin;\n"],"names":["plugin","mountComponent","mountProps","el","Component","props","component","target","$destroy","registerComponent","buildRegisterFunction"],"mappings":"wFAGA,MAAMA,EAAgC,CACpCC,eAAiBC,IACf,MAAMC,GAAEA,EAAEC,UAAEA,EAASC,MAAEA,GAAUH,EAC3BI,EAAY,IAAIF,EAAU,CAAEG,OAAQJ,EAAIE,UAE9C,MAAO,KACLC,EAAUE,UAAU,CACrB,GAICC,EAAoBC,EAAsBV"}
@@ -1,21 +1,17 @@
1
+ import { buildRegisterFunction } from 'turbo-mount';
2
+ export { TurboMount } from 'turbo-mount';
1
3
  import { createApp } from 'vue';
2
- import { TurboMountController } from 'turbo-mount';
3
4
 
4
- class TurboMountVueController extends TurboMountController {
5
- constructor() {
6
- super(...arguments);
7
- this.framework = "vue";
8
- }
9
- mountComponent(el, Component, props) {
5
+ const plugin = {
6
+ mountComponent: (mountProps) => {
7
+ const { el, Component, props } = mountProps;
10
8
  const app = createApp(Component, props);
11
9
  app.mount(el);
12
- return () => { app.unmount(); };
13
- }
14
- }
15
-
16
- const plugin = {
17
- framework: "vue",
18
- controller: TurboMountVueController
10
+ return () => {
11
+ app.unmount();
12
+ };
13
+ },
19
14
  };
15
+ const registerComponent = buildRegisterFunction(plugin);
20
16
 
21
- export { plugin as default };
17
+ export { plugin as default, registerComponent };
@@ -1,2 +1,2 @@
1
- import{createApp as o}from"vue";import{TurboMountController as t}from"turbo-mount";const r={framework:"vue",controller:class extends t{constructor(){super(...arguments),this.framework="vue"}mountComponent(t,r,e){const n=o(r,e);return n.mount(t),()=>{n.unmount()}}}};export{r as default};
1
+ import{buildRegisterFunction as o}from"turbo-mount";export{TurboMount}from"turbo-mount";import{createApp as t}from"vue";const n={mountComponent:o=>{const{el:n,Component:r,props:u}=o,m=t(r,u);return m.mount(n),()=>{m.unmount()}}},r=o(n);export{n as default,r as registerComponent};
2
2
  //# sourceMappingURL=vue.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"vue.min.js","sources":["../../src/plugins/vue/index.ts","../../src/plugins/vue/turbo-mount-vue-controller.ts"],"sourcesContent":["import {Plugin} from \"turbo-mount\";\n\nimport {TurboMountVueController} from \"./turbo-mount-vue-controller\";\n\nconst plugin: Plugin = {\n framework: \"vue\",\n controller: TurboMountVueController\n}\n\nexport default plugin;\n","import { createApp, App } from \"vue\";\nimport {TurboMountController} from \"turbo-mount\";\n\nexport class TurboMountVueController extends TurboMountController<App> {\n framework = \"vue\"\n\n mountComponent(el: Element, Component: App, props: object) {\n const app = createApp(Component, props as Record<string, unknown>);\n app.mount(el)\n\n return () => { app.unmount() }\n }\n}\n"],"names":["plugin","framework","controller","TurboMountController","constructor","this","mountComponent","el","Component","props","app","createApp","mount","unmount"],"mappings":"mFAIA,MAAMA,EAAiB,CACnBC,UAAW,MACXC,WCHE,cAAuCC,EAA7C,WAAAC,uBACIC,KAASJ,UAAG,KAQf,CANG,cAAAK,CAAeC,EAAaC,EAAgBC,GACxC,MAAMC,EAAMC,EAAUH,EAAWC,GAGjC,OAFAC,EAAIE,MAAML,GAEH,KAAQG,EAAIG,SAAS,CAC/B"}
1
+ {"version":3,"file":"vue.min.js","sources":["../../src/plugins/vue/index.ts"],"sourcesContent":["import { buildRegisterFunction, Plugin, TurboMount } from \"turbo-mount\";\nimport { App, createApp } from \"vue\";\n\nconst plugin: Plugin<App> = {\n mountComponent: (mountProps) => {\n const { el, Component, props } = mountProps;\n const app = createApp(Component, props as Record<string, unknown>);\n app.mount(el);\n\n return () => {\n app.unmount();\n };\n },\n};\n\nconst registerComponent = buildRegisterFunction(plugin);\n\nexport { TurboMount, registerComponent };\n\nexport default plugin;\n"],"names":["plugin","mountComponent","mountProps","el","Component","props","app","createApp","mount","unmount","registerComponent","buildRegisterFunction"],"mappings":"wHAGA,MAAMA,EAAsB,CAC1BC,eAAiBC,IACf,MAAMC,GAAEA,EAAEC,UAAEA,EAASC,MAAEA,GAAUH,EAC3BI,EAAMC,EAAUH,EAAWC,GAGjC,OAFAC,EAAIE,MAAML,GAEH,KACLG,EAAIG,SAAS,CACd,GAICC,EAAoBC,EAAsBX"}
@@ -1,6 +1,10 @@
1
- import { Controller } from '@hotwired/stimulus';
1
+ import { Controller, Application } from '@hotwired/stimulus';
2
2
 
3
3
  class TurboMountController extends Controller {
4
+ constructor() {
5
+ super(...arguments);
6
+ this.skipPropsChangeCallback = false;
7
+ }
4
8
  connect() {
5
9
  this._umountComponentCallback || (this._umountComponentCallback = this.mountComponent(this.mountElement, this.resolvedComponent, this.componentProps));
6
10
  }
@@ -8,6 +12,10 @@ class TurboMountController extends Controller {
8
12
  this.umountComponent();
9
13
  }
10
14
  propsValueChanged() {
15
+ if (this.skipPropsChangeCallback) {
16
+ this.skipPropsChangeCallback = false;
17
+ return;
18
+ }
11
19
  this.umountComponent();
12
20
  this._umountComponentCallback || (this._umountComponentCallback = this.mountComponent(this.mountElement, this.resolvedComponent, this.componentProps));
13
21
  }
@@ -15,46 +23,65 @@ class TurboMountController extends Controller {
15
23
  return this.propsValue;
16
24
  }
17
25
  get mountElement() {
18
- return this.element;
26
+ return this.hasMountTarget ? this.mountTarget : this.element;
19
27
  }
20
28
  get resolvedComponent() {
21
- return this.resolveComponent(this.componentValue);
29
+ return this.resolveMounted(this.componentValue).component;
30
+ }
31
+ get resolvedPlugin() {
32
+ return this.resolveMounted(this.componentValue).plugin;
22
33
  }
23
34
  umountComponent() {
24
35
  this._umountComponentCallback && this._umountComponentCallback();
25
36
  this._umountComponentCallback = undefined;
26
37
  }
27
- resolveComponent(component) {
38
+ mountComponent(el, Component, props) {
39
+ return this.resolvedPlugin.mountComponent({ el, Component, props });
40
+ }
41
+ resolveMounted(component) {
28
42
  const app = this.application;
29
- return app.turboMount[this.framework].resolve(component);
43
+ return app.turboMount.resolve(component);
44
+ }
45
+ setComponentProps(props) {
46
+ this.skipPropsChangeCallback = true;
47
+ this.propsValue = props;
30
48
  }
31
49
  }
32
50
  TurboMountController.values = {
33
51
  props: Object,
34
- component: String
52
+ component: String,
53
+ };
54
+ TurboMountController.targets = ["mount"];
55
+
56
+ const camelToKebabCase = (str) => {
57
+ return str.replace(/([a-z])([A-Z])/g, "$1-$2").toLowerCase();
35
58
  };
36
59
 
37
60
  class TurboMount {
38
- constructor(props) {
39
- var _a;
61
+ constructor(props = {}) {
40
62
  this.components = new Map();
41
- this.application = props.application;
42
- this.framework = props.plugin.framework;
43
- this.baseController = props.plugin.controller;
44
- (_a = this.application).turboMount || (_a.turboMount = {});
45
- this.application.turboMount[this.framework] = this;
46
- if (this.baseController) {
47
- this.application.register(`turbo-mount-${this.framework}`, this.baseController);
48
- }
63
+ this.application = this.findOrStartApplication(props.application);
64
+ this.application.turboMount = this;
65
+ this.application.register("turbo-mount", TurboMountController);
66
+ document.addEventListener("turbo:before-morph-element", (event) => {
67
+ var _a;
68
+ const turboMorphEvent = event;
69
+ const { target, detail } = turboMorphEvent;
70
+ if ((_a = target.getAttribute("data-controller")) === null || _a === void 0 ? void 0 : _a.includes("turbo-mount")) {
71
+ target.setAttribute("data-turbo-mount-props-value", detail.newElement.getAttribute("data-turbo-mount-props-value") ||
72
+ "{}");
73
+ event.preventDefault();
74
+ }
75
+ });
49
76
  }
50
- register(name, component, controller) {
51
- controller || (controller = this.baseController);
77
+ register(plugin, name, component, controller) {
78
+ controller || (controller = TurboMountController);
52
79
  if (this.components.has(name)) {
53
80
  throw new Error(`Component '${name}' is already registered.`);
54
81
  }
55
- this.components.set(name, component);
82
+ this.components.set(name, { component, plugin });
56
83
  if (controller) {
57
- const controllerName = `turbo-mount-${this.framework}-${this.camelToKebabCase(name)}`;
84
+ const controllerName = `turbo-mount-${camelToKebabCase(name)}`;
58
85
  this.application.register(controllerName, controller);
59
86
  }
60
87
  }
@@ -65,9 +92,19 @@ class TurboMount {
65
92
  }
66
93
  return component;
67
94
  }
68
- camelToKebabCase(str) {
69
- return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
95
+ findOrStartApplication(hydratedApp) {
96
+ let application = hydratedApp || window.Stimulus;
97
+ if (!application) {
98
+ application = Application.start();
99
+ window.Stimulus = application;
100
+ }
101
+ return application;
70
102
  }
71
103
  }
104
+ function buildRegisterFunction(plugin) {
105
+ return (turboMount, name, component, controller) => {
106
+ turboMount.register(plugin, name, component, controller);
107
+ };
108
+ }
72
109
 
73
- export { TurboMount, TurboMountController };
110
+ export { TurboMount, TurboMountController, buildRegisterFunction };
@@ -1,2 +1,2 @@
1
- import{Controller as t}from"@hotwired/stimulus";class o extends t{connect(){this._umountComponentCallback||(this._umountComponentCallback=this.mountComponent(this.mountElement,this.resolvedComponent,this.componentProps))}disconnect(){this.umountComponent()}propsValueChanged(){this.umountComponent(),this._umountComponentCallback||(this._umountComponentCallback=this.mountComponent(this.mountElement,this.resolvedComponent,this.componentProps))}get componentProps(){return this.propsValue}get mountElement(){return this.element}get resolvedComponent(){return this.resolveComponent(this.componentValue)}umountComponent(){this._umountComponentCallback&&this._umountComponentCallback(),this._umountComponentCallback=void 0}resolveComponent(t){return this.application.turboMount[this.framework].resolve(t)}}o.values={props:Object,component:String};class n{constructor(t){var o;this.components=new Map,this.application=t.application,this.framework=t.plugin.framework,this.baseController=t.plugin.controller,(o=this.application).turboMount||(o.turboMount={}),this.application.turboMount[this.framework]=this,this.baseController&&this.application.register(`turbo-mount-${this.framework}`,this.baseController)}register(t,o,n){if(n||(n=this.baseController),this.components.has(t))throw new Error(`Component '${t}' is already registered.`);if(this.components.set(t,o),n){const o=`turbo-mount-${this.framework}-${this.camelToKebabCase(t)}`;this.application.register(o,n)}}resolve(t){const o=this.components.get(t);if(!o)throw new Error(`Unknown component: ${t}`);return o}camelToKebabCase(t){return t.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}}export{n as TurboMount,o as TurboMountController};
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};
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"],"sourcesContent":["import {Controller} from \"@hotwired/stimulus\"\nimport {ApplicationWithTurboMount} from \"./turbo-mount\";\n\nexport abstract class TurboMountController<T> extends Controller {\n static values = {\n props: Object,\n component: String\n }\n declare readonly propsValue: object;\n declare readonly componentValue: string;\n\n abstract framework: string;\n\n abstract mountComponent(el: Element, Component: T, props: object): () => void;\n\n _umountComponentCallback?: () => void;\n\n connect() {\n this._umountComponentCallback ||= this.mountComponent(this.mountElement, this.resolvedComponent, this.componentProps);\n }\n\n disconnect() {\n this.umountComponent();\n }\n\n propsValueChanged() {\n this.umountComponent();\n this._umountComponentCallback ||= this.mountComponent(this.mountElement, this.resolvedComponent, this.componentProps);\n }\n\n get componentProps() {\n return this.propsValue;\n }\n\n get mountElement() {\n return this.element;\n }\n\n get resolvedComponent() {\n return this.resolveComponent(this.componentValue);\n }\n\n umountComponent() {\n this._umountComponentCallback && this._umountComponentCallback();\n this._umountComponentCallback = undefined;\n }\n\n resolveComponent(component: string): T {\n const app = this.application as ApplicationWithTurboMount<T>\n return app.turboMount[this.framework].resolve(component);\n }\n}\n\n\n","import {Application, ControllerConstructor} from '@hotwired/stimulus';\n\nexport interface ApplicationWithTurboMount<T> extends Application {\n turboMount: { [framework: string]: TurboMount<T> };\n}\n\nexport type Plugin = {\n framework: string;\n controller: ControllerConstructor;\n}\n\nexport class TurboMount<T> {\n components: Map<string, T>;\n application: ApplicationWithTurboMount<T>;\n framework: string;\n baseController?: ControllerConstructor;\n\n constructor(props: { application: Application, plugin: Plugin }) {\n this.components = new Map();\n this.application = props.application as ApplicationWithTurboMount<T>;\n this.framework = props.plugin.framework;\n this.baseController = props.plugin.controller;\n\n this.application.turboMount ||= {};\n this.application.turboMount[this.framework] = this;\n\n if (this.baseController) {\n this.application.register(`turbo-mount-${this.framework}`, this.baseController);\n }\n }\n\n register(name: string, component: T, controller?: ControllerConstructor) {\n controller ||= this.baseController;\n if (this.components.has(name)) {\n throw new Error(`Component '${name}' is already registered.`);\n }\n this.components.set(name, component);\n\n if (controller) {\n const controllerName = `turbo-mount-${this.framework}-${this.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 camelToKebabCase(str: string) {\n return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();\n }\n}\n"],"names":["TurboMountController","Controller","connect","this","_umountComponentCallback","mountComponent","mountElement","resolvedComponent","componentProps","disconnect","umountComponent","propsValueChanged","propsValue","element","resolveComponent","componentValue","undefined","component","application","turboMount","framework","resolve","values","props","Object","String","TurboMount","constructor","components","Map","plugin","baseController","controller","_a","register","name","has","Error","set","controllerName","camelToKebabCase","get","str","replace","toLowerCase"],"mappings":"gDAGM,MAAgBA,UAAgCC,EAclD,OAAAC,GACIC,KAAKC,2BAALD,KAAKC,yBAA6BD,KAAKE,eAAeF,KAAKG,aAAcH,KAAKI,kBAAmBJ,KAAKK,gBACzG,CAED,UAAAC,GACIN,KAAKO,iBACR,CAED,iBAAAC,GACIR,KAAKO,kBACLP,KAAKC,2BAALD,KAAKC,yBAA6BD,KAAKE,eAAeF,KAAKG,aAAcH,KAAKI,kBAAmBJ,KAAKK,gBACzG,CAED,kBAAIA,GACA,OAAOL,KAAKS,UACf,CAED,gBAAIN,GACA,OAAOH,KAAKU,OACf,CAED,qBAAIN,GACA,OAAOJ,KAAKW,iBAAiBX,KAAKY,eACrC,CAED,eAAAL,GACIP,KAAKC,0BAA4BD,KAAKC,2BACtCD,KAAKC,8BAA2BY,CACnC,CAED,gBAAAF,CAAiBG,GAEb,OADYd,KAAKe,YACNC,WAAWhB,KAAKiB,WAAWC,QAAQJ,EACjD,EA9CMjB,EAAAsB,OAAS,CACZC,MAAOC,OACPP,UAAWQ,cCKNC,EAMT,WAAAC,CAAYJ,SACRpB,KAAKyB,WAAa,IAAIC,IACtB1B,KAAKe,YAAcK,EAAML,YACzBf,KAAKiB,UAAYG,EAAMO,OAAOV,UAC9BjB,KAAK4B,eAAiBR,EAAMO,OAAOE,YAEnCC,EAAA9B,KAAKe,aAAYC,aAAAc,EAAAd,WAAe,CAAA,GAChChB,KAAKe,YAAYC,WAAWhB,KAAKiB,WAAajB,KAE1CA,KAAK4B,gBACL5B,KAAKe,YAAYgB,SAAS,eAAe/B,KAAKiB,YAAajB,KAAK4B,eAEvE,CAED,QAAAG,CAASC,EAAclB,EAAce,GAEjC,GADAA,IAAAA,EAAe7B,KAAK4B,gBAChB5B,KAAKyB,WAAWQ,IAAID,GACpB,MAAM,IAAIE,MAAM,cAAcF,6BAIlC,GAFAhC,KAAKyB,WAAWU,IAAIH,EAAMlB,GAEtBe,EAAY,CACZ,MAAMO,EAAiB,eAAepC,KAAKiB,aAAajB,KAAKqC,iBAAiBL,KAC9EhC,KAAKe,YAAYgB,SAASK,EAAgBP,EAC7C,CACJ,CAED,OAAAX,CAAQc,GACJ,MAAMlB,EAAYd,KAAKyB,WAAWa,IAAIN,GACtC,IAAKlB,EACD,MAAM,IAAIoB,MAAM,sBAAsBF,KAE1C,OAAOlB,CACV,CAED,gBAAAuB,CAAiBE,GACb,OAAOA,EAAIC,QAAQ,kBAAmB,SAASC,aAClD"}
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"}
@@ -0,0 +1,18 @@
1
+ import { TurboMount } from "turbo-mount";
2
+ import { registerComponent } from "turbo-mount/<%= framework %>";
3
+
4
+ const turboMount = new TurboMount();
5
+
6
+ // to register a component use:
7
+ // registerComponent(turboMount, "Hello", Hello); // where Hello is the imported the component
8
+
9
+ // to override the default controller use:
10
+ // registerComponent(turboMount, "Hello", Hello, HelloController); // where HelloController is a Stimulus controller extended from TurboMountController
11
+ <%- if vite? -%>
12
+
13
+ // If you want to automatically register components use:
14
+ // import { registerComponents } from "turbo-mount/registerComponents/<%= framework %>";
15
+ // const controllers = import.meta.glob("/controllers/**/*_controller.js", { eager: true });
16
+ // const components = import.meta.glob("/components/**/*.jsx", { eager: true });
17
+ // registerComponents({ turboMount, components, controllers });
18
+ <%- end -%>
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TurboMount
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ FRAMEWORKS = {
7
+ "react" => {
8
+ pins: "react react-dom react-dom/client",
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
23
+
24
+ source_root File.expand_path("install", __dir__)
25
+
26
+ def install
27
+ say "Installing Turbo Mount"
28
+
29
+ if build_tool.nil?
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
33
+
34
+ if importmap?
35
+ install_importmap
36
+ else
37
+ install_nodejs
38
+ end
39
+
40
+ say "Turbo Mount successfully installed", :green
41
+ end
42
+
43
+ private
44
+
45
+ def install_nodejs
46
+ case build_tool
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
54
+
55
+ say "Creating Turbo Mount initializer"
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)
58
+ warn_about_vite_plugin if vite?
59
+ end
60
+
61
+ def install_importmap
62
+ say "Creating Turbo Mount initializer"
63
+ template "turbo-mount.js", File.join("app/javascript/turbo-mount-initializer.js")
64
+ append_to_file "app/javascript/application.js", %(import "turbo-mount-initializer"\n)
65
+
66
+ say "Pinning Turbo Mount to the importmap"
67
+ append_to_file "config/importmap.rb", %(pin "turbo-mount", to: "turbo-mount.min.js"\n)
68
+ append_to_file "config/importmap.rb", %(pin "turbo-mount/#{framework}", to: "turbo-mount/#{framework}.min.js"\n)
69
+ append_to_file "config/importmap.rb", %(pin "turbo-mount-initializer"\n)
70
+
71
+ say "Pinning framework dependencies to the importmap"
72
+ run "bin/importmap pin #{FRAMEWORKS[framework][:pins]}"
73
+ end
74
+
75
+ def vite?
76
+ Dir.glob(Rails.root.join("vite.config.*")).any?
77
+ end
78
+
79
+ def importmap?
80
+ build_tool == "importmap"
81
+ end
82
+
83
+ def warn_about_vite_plugin
84
+ say "Make sure to install and add #{FRAMEWORKS[framework][:vite_plugin]} to your Vite config", :yellow
85
+ end
86
+
87
+ def build_tool
88
+ return @build_tool if defined?(@build_tool)
89
+
90
+ @build_tool = detect_build_tool
91
+ end
92
+
93
+ def detect_build_tool
94
+ if Rails.root.join("package.json").exist?
95
+ if Rails.root.join("package-lock.json").exist?
96
+ "npm"
97
+ elsif Rails.root.join("bun.config.js").exist?
98
+ "bun"
99
+ else
100
+ "yarn"
101
+ end
102
+ elsif Rails.root.join("config/importmap.rb").exist?
103
+ "importmap"
104
+ end
105
+ end
106
+
107
+ def framework
108
+ @framework ||= ask("What framework do you want to use with Turbo Mount?", limited_to: FRAMEWORKS.keys, default: "react")
109
+ end
110
+ end
111
+ end
112
+ end
@@ -3,28 +3,20 @@
3
3
  module Turbo
4
4
  module Mount
5
5
  module Helpers
6
- def turbo_mount_component(component_name, framework:, props: {}, tag: "div", **attrs)
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
- attrs["data-controller"] = "turbo-mount-#{framework}-#{component_name.underscore.dasherize}"
10
- prefix = "data-#{attrs["data-controller"]}"
9
+ controller_name = "turbo-mount-#{component_name.underscore.dasherize}"
10
+ attrs["data-controller"] = controller_name
11
+ prefix = "data-#{controller_name}"
11
12
  attrs["#{prefix}-component-value"] = component_name
12
13
  attrs["#{prefix}-props-value"] = json_escape(props.to_json) if props.present?
13
14
 
14
- content_tag(tag, nil, attrs)
15
- end
16
-
17
- def turbo_mount_react_component(component_name, **attrs)
18
- turbo_mount_component(component_name, framework: "react", **attrs)
19
- end
20
-
21
- def turbo_mount_svelte_component(component_name, **attrs)
22
- turbo_mount_component(component_name, framework: "svelte", **attrs)
23
- end
15
+ return content_tag(tag, nil, attrs) unless block
24
16
 
25
- def turbo_mount_vue_component(component_name, **attrs)
26
- turbo_mount_component(component_name, framework: "vue", **attrs)
17
+ content_tag(tag, nil, attrs) { capture(controller_name, &block) }
27
18
  end
19
+ alias_method :turbo_mount_component, :turbo_mount
28
20
  end
29
21
  end
30
22
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Turbo
4
4
  module Mount
5
- VERSION = "0.2.2"
5
+ VERSION = "0.3.0"
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.2.2
4
+ version: 0.3.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-05-09 00:00:00.000000000 Z
11
+ date: 2024-05-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -47,6 +47,8 @@ files:
47
47
  - app/assets/javascripts/turbo-mount/vue.js
48
48
  - app/assets/javascripts/turbo-mount/vue.min.js
49
49
  - app/assets/javascripts/turbo-mount/vue.min.js.map
50
+ - lib/generators/turbo_mount/install/turbo-mount.js.tt
51
+ - lib/generators/turbo_mount/install_generator.rb
50
52
  - lib/turbo/mount.rb
51
53
  - lib/turbo/mount/engine.rb
52
54
  - lib/turbo/mount/helpers.rb