turbo-mount 0.2.3 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0318ba2497956b4a56bc035a5a39e617bf0b171b039823fd61fe131294ca5815'
4
- data.tar.gz: 6a078a433b9766915496ac9d86d9df81782eec209c495ccefcb978643c810a39
3
+ metadata.gz: 6b844cfcb457df944a2483bda24a48e5e3ac40628bcef8ed9683fc46c38415db
4
+ data.tar.gz: 2a921f84036cfd9ff8b92e0e4c17e38451e0b948c033068b6a975b6252a7bbde
5
5
  SHA512:
6
- metadata.gz: 75dd9c6f8b0760791c40d3db5b07cf95056bc199550d95c5a86cec1686a8f11eb0594ad7e1d4d4c015867289371f18e2b1cc7f1ccb6738e24b8342a42e7b8f1c
7
- data.tar.gz: 1ac86acfbb7d0e5c01e61c791e6ba058d78ae01092bdd1dc5cdd0059a5309922888a59cd0afad63541c2c517c478f159d351ef2e0da326ddddf1b48d6c9de534
6
+ metadata.gz: c96cd642b8c68e23f1dc5f91b77d25b7465abd860625c45d9bfcda5b53fa3db2484308a79b3e2768cd1fbd57fa58b9b00b60a2ffb41bdbc63b31ab8393d49db9
7
+ data.tar.gz: cf4876ceda803c1ba4a023d8d13d18e6b7a764e866d84963d9a516ff03b26a7bd7d1e013b5f49595e6c4c32585f8c4f3485696e1f38a1bec78e33f7098db99d8
data/CHANGELOG.md CHANGED
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning].
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.3.1] - 2024-06-16
11
+
12
+ ### Fixed
13
+
14
+ - Fix entrypoint issue in the install generator. ([@skryukov])
15
+
16
+ ## [0.3.0] - 2024-05-31
17
+
18
+ ### Added
19
+
20
+ - Installation script. ([@skryukov])
21
+
22
+ ### Changed
23
+
24
+ - [BREAKING] New API without controller inheritance. ([@skryukov])
25
+ To migrate to the new API:
26
+ - Replace `new TurboMountReact()` (or any other framework specific constructor) with `new TurboMount()`
27
+ - Replace `turboMount.register(...)` with `registerComponent(turboMount, ...)`
28
+ - Replace `turbo_mount_react_component` (or any other framework specific helper) with `turbo_mount`
29
+ - Also see the new API for plugins and custom controllers in the README.
30
+
10
31
  ## [0.2.3] - 2024-05-12
11
32
 
12
33
  ### Added
@@ -36,10 +57,12 @@ and this project adheres to [Semantic Versioning].
36
57
 
37
58
  [@skryukov]: https://github.com/skryukov
38
59
 
39
- [Unreleased]: https://github.com/skryukov/turbo-mount/compare/v0.2.3...HEAD
40
- [0.2.3]: https://github.com/skryukov/turbo-mount/commits/v0.2.3
41
- [0.2.2]: https://github.com/skryukov/turbo-mount/commits/v0.2.2
42
- [0.2.0]: https://github.com/skryukov/turbo-mount/commits/v0.2.0
60
+ [Unreleased]: https://github.com/skryukov/turbo-mount/compare/v0.3.1...HEAD
61
+ [0.3.1]: https://github.com/skryukov/turbo-mount/compare/v0.3.0...v0.3.1
62
+ [0.3.0]: https://github.com/skryukov/turbo-mount/compare/v0.2.3...v0.3.0
63
+ [0.2.3]: https://github.com/skryukov/turbo-mount/compare/v0.2.2...v0.2.3
64
+ [0.2.2]: https://github.com/skryukov/turbo-mount/compare/v0.2.0...v0.2.2
65
+ [0.2.0]: https://github.com/skryukov/turbo-mount/compare/v0.1.0...v0.2.0
43
66
  [0.1.0]: https://github.com/skryukov/turbo-mount/commits/v0.1.0
44
67
 
45
68
  [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
data/README.md CHANGED
@@ -1,72 +1,114 @@
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
- Add the following line to your Gemfile:
28
+ To install Turbo Mount, add the following line to your `Gemfile` and run `bundle install`:
10
29
 
11
30
  ```ruby
12
31
  gem "turbo-mount"
13
32
  ```
14
33
 
15
- For projects utilizing build tools such as [Vite](http://vite-ruby.netlify.app), also install `turbo-mount` package:
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:
16
49
 
17
50
  ```bash
18
51
  npm install turbo-mount
19
52
  # or with yarn
20
53
  yarn add turbo-mount
54
+
55
+ # and the desired framework
56
+ npm install react react-dom
57
+ # or
58
+ npm install vue
59
+ # or
60
+ npm install svelte
21
61
  ```
22
62
 
23
- ## Usage
63
+ If you're using Vite, don't forget to add [framework-specific plugins](https://vitejs.dev/plugins) to your `vite.config.js`.
24
64
 
25
- ### Initialization
65
+ ### Importmaps
66
+ To use `TurboMount` with importmaps, you need to pin the necessary JavaScript files in your `config/importmap.rb`:
26
67
 
27
- To begin using `TurboMount`, start by initializing the library and registering the components you intend to use. Below are the steps to set up `TurboMount` with different configurations.
68
+ ```ruby
69
+ pin "turbo-mount", to: "turbo-mount.min.js"
70
+ pin "turbo-mount/react", to: "turbo-mount/react.min.js"
71
+ ```
28
72
 
29
- #### Standard Initialization
73
+ This ensures that `turbo-mount` and its plugins are available in your application.
30
74
 
31
- Import the necessary modules and initialize ```TurboMount``` with your application and the desired plugin. Here's how to set it up with a React plugin:
75
+ Also pin the desired framework:
32
76
 
33
- ```js
34
- import { Application } from "@hotwired/stimulus";
35
- import { TurboMount } from "turbo-mount";
36
- import plugin from "turbo-mount/react";
37
- import { SketchPicker } from 'react-color';
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
+ ```
38
84
 
39
- const application = Application.start();
40
- const turboMount = new TurboMount({ application, plugin });
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.
41
86
 
42
- turboMount.register('SketchPicker', SketchPicker);
43
- ```
87
+ ## Usage
44
88
 
45
- #### Simplified Initialization
89
+ ### Initialization
46
90
 
47
- If you prefer not to specify the `application` explicitly, `TurboMount` can automatically detect or initialize it. This approach uses the `window.Stimulus` if available; otherwise, it initializes a new Stimulus application:
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:
48
92
 
49
93
  ```js
94
+ // app/javascript/turbo-mount.js
95
+
50
96
  import { TurboMount } from "turbo-mount";
51
- import plugin from "turbo-mount/react";
52
- import { SketchPicker } from 'react-color';
97
+ import { registerComponent } from "turbo-mount/react";
98
+ import { HexColorPicker } from 'react-colorful';
53
99
 
54
- const turboMount = new TurboMount({ plugin });
100
+ const turboMount = new TurboMount(); // or new TurboMount({ application })
55
101
 
56
- turboMount.register('SketchPicker', SketchPicker);
102
+ registerComponent(turboMount, "HexColorPicker", HexColorPicker);
57
103
  ```
58
104
 
59
- #### Plugin-Specific Initialization
60
-
61
- For a more streamlined setup, you can directly import a specialized version of `TurboMount`:
105
+ 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.
62
106
 
107
+ Make sure your `application.js` is importing `turbo-mount.js`:
63
108
  ```js
64
- import { TurboMountReact } from "turbo-mount/react";
65
- import { SketchPicker } from 'react-color';
66
-
67
- const turboMount = new TurboMountReact();
68
-
69
- turboMount.register('SketchPicker', SketchPicker);
109
+ import "@hotwired/turbo-rails"
110
+ import "./controllers"
111
+ import "./turbo-mount" // <------
70
112
  ```
71
113
 
72
114
  ### View Helpers
@@ -74,31 +116,37 @@ turboMount.register('SketchPicker', SketchPicker);
74
116
  Use the following helpers to mount components in your views:
75
117
 
76
118
  ```erb
77
- <%= turbo_mount_component("SketchPicker", framework: "react", props: {color: "#034"}) %>
119
+ <%= turbo_mount("HexColorPicker", props: {color: "#034"}, class: "mb-5") %>
120
+ ```
78
121
 
79
- <%# or using alias %>
122
+ This will generate the following HTML:
80
123
 
81
- <%= turbo_mount_react_component("SketchPicker", props: {color: "#430"}) %>
124
+ ```html
125
+ <div data-controller="turbo-mount"
126
+ data-turbo-mount-component-value="HexColorPicker"
127
+ data-turbo-mount-props-value="{&quot;color&quot;:&quot;#034&quot;}"
128
+ class="mb-5">
129
+ </div>
82
130
  ```
83
131
 
84
132
  ### Supported Frameworks
85
133
 
86
134
  `TurboMount` supports the following frameworks:
87
135
 
88
- - React `"turbo-mount/react"`
89
- - Vue `"turbo-mount/vue"`
90
- - Svelte `"turbo-mount/svelte"`
136
+ - React: `"turbo-mount/react"`
137
+ - Vue: `"turbo-mount/vue"`
138
+ - Svelte: `"turbo-mount/svelte"`
91
139
 
92
- It's possible to add support for other frameworks by creating custom controller class extending `TurboMountController` and providing a plugin. See included plugins for examples.
140
+ To add support for other frameworks, create a custom plugin. See included plugins for examples.
93
141
 
94
142
  ### Custom Controllers
95
143
 
96
144
  To customize component behavior or pass functions as props, create a custom controller:
97
145
 
98
146
  ```js
99
- import { TurboMountReactController } from "turbo-mount"
147
+ import { TurboMountController } from "turbo-mount";
100
148
 
101
- export default class extends TurboMountReactController {
149
+ export default class extends TurboMountController {
102
150
  get componentProps() {
103
151
  return {
104
152
  ...this.propsValue,
@@ -107,62 +155,51 @@ export default class extends TurboMountReactController {
107
155
  }
108
156
 
109
157
  onChange = (color) => {
110
- this.propsValue = { ...this.propsValue, color: color.hex };
158
+ // same as this.propsValue = { ...this.propsValue, color };
159
+ // but skips the rerendering of the component:
160
+ this.componentProps = { ...this.propsValue, color };
111
161
  };
112
162
  }
113
163
  ```
114
164
 
115
- Then pass this controller the register method:
165
+ Then pass this controller to the `registerComponent` method:
116
166
 
117
167
  ```js
118
- import SketchController from "controllers/turbo_mount/sketch_picker_controller"
168
+ import HexColorPickerController from "controllers/turbo_mount/hex_color_picker_controller";
119
169
 
120
- turboMount.register('SketchPicker', SketchPicker, SketchController);
170
+ registerComponent(turboMount, "HexColorPicker", HexColorPicker, HexColorPickerController);
121
171
  ```
122
172
 
123
173
  ### Vite Integration
124
174
 
125
- `TurboMount` includes a `registerComponents` function that automates the loading of components (requires `stimulus-vite-helpers` package). It also accepts an optional `controllers` property to autoload customized controllers:
175
+ `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:
126
176
 
127
177
  ```js
128
178
  import { TurboMount } from "turbo-mount/react";
129
- import { registerComponents } from "turbo-mount/vite";
179
+ import { registerComponents } from "turbo-mount/registerComponents/react";
130
180
 
131
181
  const controllers = import.meta.glob("./**/*_controller.js", { eager: true });
132
- const components = import.meta.glob(`/components/**/*.jsx`, { eager: true });
182
+ const components = import.meta.glob("/components/**/*.jsx", { eager: true });
133
183
 
134
184
  const turboMount = new TurboMount();
135
185
  registerComponents({ turboMount, components, controllers });
136
186
  ```
137
187
 
138
188
  The `registerComponents` helper searches for controllers in the following paths:
139
- - `controllers/turbo-mount/${framework}/${controllerName}`
140
- - `controllers/turbo-mount/${framework}-${controllerName}`
141
- - `controllers/turbo-mount-${framework}-${controllerName}`
142
189
  - `controllers/turbo-mount/${controllerName}`
143
190
  - `controllers/turbo-mount-${controllerName}`
144
191
 
145
- ### Mount target
192
+ ### Mount Target
146
193
 
147
194
  To specify a non-root mount target, use the `data-<%= controller_name %>-target="mount"` attribute:
148
195
 
149
196
  ```erb
150
- <%= turbo_mount_react_component("SketchPicker", props: {color: "#430"}) do |controller_name| %>
197
+ <%= turbo_mount("HexColorPicker", props: {color: "#430"}) do |controller_name| %>
151
198
  <h3>Color picker</h3>
152
199
  <div data-<%= controller_name %>-target="mount"></div>
153
200
  <% end %>
154
201
  ```
155
202
 
156
- ## Development
157
-
158
- 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.
159
-
160
- 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).
161
-
162
- ## Contributing
163
-
164
- Bug reports and pull requests are welcome on GitHub at https://github.com/skryukov/turbo-mount.
165
-
166
203
  ## License
167
204
 
168
205
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -1,29 +1,18 @@
1
- import { TurboMountController, TurboMount } from 'turbo-mount';
1
+ import { buildRegisterFunction } from 'turbo-mount';
2
+ export { TurboMount } from 'turbo-mount';
2
3
  import { createElement } from 'react';
3
4
  import { createRoot } from 'react-dom/client';
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
11
  return () => {
14
12
  root.unmount();
15
13
  };
16
- }
17
- }
18
-
19
- const plugin = {
20
- framework: "react",
21
- controller: TurboMountReactController,
14
+ },
22
15
  };
23
- class TurboMountReact extends TurboMount {
24
- constructor(props) {
25
- super(Object.assign(Object.assign({}, props), { plugin }));
26
- }
27
- }
16
+ const registerComponent = buildRegisterFunction(plugin);
28
17
 
29
- export { TurboMountReact as TurboMount, TurboMountReact, plugin as default };
18
+ export { plugin as default, registerComponent };
@@ -1,2 +1,2 @@
1
- import{TurboMountController as r,TurboMount as t}from"turbo-mount";import{createElement as o}from"react";import{createRoot as e}from"react-dom/client";const n={framework:"react",controller:class extends r{constructor(){super(...arguments),this.framework="react"}mountComponent(r,t,n){const s=e(r);return s.render(o(t,n)),()=>{s.unmount()}}}};class s extends t{constructor(r){super(Object.assign(Object.assign({},r),{plugin:n}))}}export{s as TurboMount,s as TurboMountReact,n as default};
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, TurboMount, TurboMountProps } from \"turbo-mount\";\n\nimport { TurboMountReactController } from \"./turbo-mount-react-controller\";\n\nconst plugin: Plugin = {\n framework: \"react\",\n controller: TurboMountReactController,\n};\n\nexport class TurboMountReact<T> extends TurboMount<T> {\n constructor(props: Omit<TurboMountProps, \"plugin\">) {\n super({ ...props, plugin });\n }\n}\n\nexport { TurboMountReact as TurboMount };\n\nexport default plugin;\n","import { ComponentType, createElement } from \"react\";\nimport { createRoot } from \"react-dom/client\";\nimport { TurboMountController } from \"turbo-mount\";\n\nexport class TurboMountReactController extends TurboMountController<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 () => {\n root.unmount();\n };\n }\n}\n"],"names":["plugin","framework","controller","TurboMountController","constructor","this","mountComponent","el","Component","props","root","createRoot","render","createElement","unmount","TurboMountReact","TurboMount","super","Object","assign"],"mappings":"uJAIA,MAAMA,EAAiB,CACrBC,UAAW,QACXC,WCFI,cAAyCC,EAA/C,WAAAC,uBACEC,KAASJ,UAAG,OAUb,CARC,cAAAK,CAAeC,EAAaC,EAA0BC,GACpD,MAAMC,EAAOC,EAAWJ,GAGxB,OAFAG,EAAKE,OAAOC,EAAcL,EAAWC,IAE9B,KACLC,EAAKI,SAAS,CAEjB,IDLG,MAAOC,UAA2BC,EACtC,WAAAZ,CAAYK,GACVQ,MAAWC,OAAAC,OAAAD,OAAAC,OAAA,GAAAV,GAAO,CAAAT,WACnB"}
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,26 +1,15 @@
1
- import { TurboMountController, TurboMount } from 'turbo-mount';
1
+ import { buildRegisterFunction } from 'turbo-mount';
2
+ export { TurboMount } from 'turbo-mount';
2
3
 
3
- class TurboMountSvelteController extends TurboMountController {
4
- constructor() {
5
- super(...arguments);
6
- this.framework = "svelte";
7
- }
8
- mountComponent(el, Component, props) {
4
+ const plugin = {
5
+ mountComponent: (mountProps) => {
6
+ const { el, Component, props } = mountProps;
9
7
  const component = new Component({ target: el, props });
10
8
  return () => {
11
9
  component.$destroy();
12
10
  };
13
- }
14
- }
15
-
16
- const plugin = {
17
- framework: "svelte",
18
- controller: TurboMountSvelteController,
11
+ },
19
12
  };
20
- class TurboMountSvelte extends TurboMount {
21
- constructor(props) {
22
- super(Object.assign(Object.assign({}, props), { plugin }));
23
- }
24
- }
13
+ const registerComponent = buildRegisterFunction(plugin);
25
14
 
26
- export { TurboMountSvelte as TurboMount, TurboMountSvelte, plugin as default };
15
+ export { plugin as default, registerComponent };
@@ -1,2 +1,2 @@
1
- import{TurboMountController as t,TurboMount as s}from"turbo-mount";const e={framework:"svelte",controller:class extends t{constructor(){super(...arguments),this.framework="svelte"}mountComponent(t,s,e){const o=new s({target:t,props:e});return()=>{o.$destroy()}}}};class o extends s{constructor(t){super(Object.assign(Object.assign({},t),{plugin:e}))}}export{o as TurboMount,o as TurboMountSvelte,e as default};
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, TurboMount, TurboMountProps } from \"turbo-mount\";\n\nimport { TurboMountSvelteController } from \"./turbo-mount-svelte-controller\";\n\nconst plugin: Plugin = {\n framework: \"svelte\",\n controller: TurboMountSvelteController,\n};\n\nexport class TurboMountSvelte<T> extends TurboMount<T> {\n constructor(props: Omit<TurboMountProps, \"plugin\">) {\n super({ ...props, plugin });\n }\n}\n\nexport { TurboMountSvelte as TurboMount };\nexport default plugin;\n","import { ComponentType } from \"svelte\";\nimport { TurboMountController } from \"turbo-mount\";\n\nexport class TurboMountSvelteController extends TurboMountController<ComponentType> {\n framework = \"svelte\";\n\n mountComponent(el: Element, Component: ComponentType, props: object) {\n const component = new Component({ target: el, props });\n\n return () => {\n component.$destroy();\n };\n }\n}\n"],"names":["plugin","framework","controller","TurboMountController","constructor","this","mountComponent","el","Component","props","component","target","$destroy","TurboMountSvelte","TurboMount","super","Object","assign"],"mappings":"mEAIA,MAAMA,EAAiB,CACrBC,UAAW,SACXC,WCHI,cAA0CC,EAAhD,WAAAC,uBACEC,KAASJ,UAAG,QASb,CAPC,cAAAK,CAAeC,EAAaC,EAA0BC,GACpD,MAAMC,EAAY,IAAIF,EAAU,CAAEG,OAAQJ,EAAIE,UAE9C,MAAO,KACLC,EAAUE,UAAU,CAEvB,IDHG,MAAOC,UAA4BC,EACvC,WAAAV,CAAYK,GACVM,MAAWC,OAAAC,OAAAD,OAAAC,OAAA,GAAAR,GAAO,CAAAT,WACnB"}
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,28 +1,17 @@
1
- import { TurboMountController, TurboMount } from 'turbo-mount';
1
+ import { buildRegisterFunction } from 'turbo-mount';
2
+ export { TurboMount } from 'turbo-mount';
2
3
  import { createApp } from 'vue';
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
10
  return () => {
13
11
  app.unmount();
14
12
  };
15
- }
16
- }
17
-
18
- const plugin = {
19
- framework: "vue",
20
- controller: TurboMountVueController,
13
+ },
21
14
  };
22
- class TurboMountVue extends TurboMount {
23
- constructor(props) {
24
- super(Object.assign(Object.assign({}, props), { plugin }));
25
- }
26
- }
15
+ const registerComponent = buildRegisterFunction(plugin);
27
16
 
28
- export { TurboMountVue as TurboMount, TurboMountVue, plugin as default };
17
+ export { plugin as default, registerComponent };
@@ -1,2 +1,2 @@
1
- import{TurboMountController as o,TurboMount as t}from"turbo-mount";import{createApp as r}from"vue";const n={framework:"vue",controller:class extends o{constructor(){super(...arguments),this.framework="vue"}mountComponent(o,t,n){const s=r(t,n);return s.mount(o),()=>{s.unmount()}}}};class s extends t{constructor(o){super(Object.assign(Object.assign({},o),{plugin:n}))}}export{s as TurboMount,s as TurboMountVue,n as default};
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, TurboMount, TurboMountProps } from \"turbo-mount\";\n\nimport { TurboMountVueController } from \"./turbo-mount-vue-controller\";\n\nconst plugin: Plugin = {\n framework: \"vue\",\n controller: TurboMountVueController,\n};\n\nexport class TurboMountVue<T> extends TurboMount<T> {\n constructor(props: Omit<TurboMountProps, \"plugin\">) {\n super({ ...props, plugin });\n }\n}\n\nexport { TurboMountVue as TurboMount };\n\nexport default plugin;\n","import { createApp, App } from \"vue\";\nimport { TurboMountController } from \"turbo-mount\";\n\nexport class TurboMountVueController extends TurboMountController<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 () => {\n app.unmount();\n };\n }\n}\n"],"names":["plugin","framework","controller","TurboMountController","constructor","this","mountComponent","el","Component","props","app","createApp","mount","unmount","TurboMountVue","TurboMount","super","Object","assign"],"mappings":"mGAIA,MAAMA,EAAiB,CACrBC,UAAW,MACXC,WCHI,cAAuCC,EAA7C,WAAAC,uBACEC,KAASJ,UAAG,KAUb,CARC,cAAAK,CAAeC,EAAaC,EAAgBC,GAC1C,MAAMC,EAAMC,EAAUH,EAAWC,GAGjC,OAFAC,EAAIE,MAAML,GAEH,KACLG,EAAIG,SAAS,CAEhB,IDJG,MAAOC,UAAyBC,EACpC,WAAAX,CAAYK,GACVO,MAAWC,OAAAC,OAAAD,OAAAC,OAAA,GAAAT,GAAO,CAAAT,WACnB"}
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
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
  }
@@ -18,15 +26,25 @@ class TurboMountController extends Controller {
18
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 = {
@@ -40,34 +58,30 @@ const camelToKebabCase = (str) => {
40
58
  };
41
59
 
42
60
  class TurboMount {
43
- constructor({ application, plugin }) {
44
- var _a;
61
+ constructor(props = {}) {
45
62
  this.components = new Map();
46
- this.application = this.findOrStartApplication(application);
47
- this.framework = plugin.framework;
48
- this.baseController = plugin.controller;
49
- (_a = this.application).turboMount || (_a.turboMount = {});
50
- this.application.turboMount[this.framework] = this;
51
- if (this.baseController) {
52
- this.application.register(`turbo-mount-${this.framework}`, this.baseController);
53
- }
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
+ });
54
76
  }
55
- findOrStartApplication(hydratedApp) {
56
- let application = hydratedApp || window.Stimulus;
57
- if (!application) {
58
- application = Application.start();
59
- window.Stimulus = application;
60
- }
61
- return application;
62
- }
63
- register(name, component, controller) {
64
- controller || (controller = this.baseController);
77
+ register(plugin, name, component, controller) {
78
+ controller || (controller = TurboMountController);
65
79
  if (this.components.has(name)) {
66
80
  throw new Error(`Component '${name}' is already registered.`);
67
81
  }
68
- this.components.set(name, component);
82
+ this.components.set(name, { component, plugin });
69
83
  if (controller) {
70
- const controllerName = `turbo-mount-${this.framework}-${camelToKebabCase(name)}`;
84
+ const controllerName = `turbo-mount-${camelToKebabCase(name)}`;
71
85
  this.application.register(controllerName, controller);
72
86
  }
73
87
  }
@@ -78,6 +92,19 @@ class TurboMount {
78
92
  }
79
93
  return component;
80
94
  }
95
+ findOrStartApplication(hydratedApp) {
96
+ let application = hydratedApp || window.Stimulus;
97
+ if (!application) {
98
+ application = Application.start();
99
+ window.Stimulus = application;
100
+ }
101
+ return application;
102
+ }
103
+ }
104
+ function buildRegisterFunction(plugin) {
105
+ return (turboMount, name, component, controller) => {
106
+ turboMount.register(plugin, name, component, controller);
107
+ };
81
108
  }
82
109
 
83
- export { TurboMount, TurboMountController };
110
+ export { TurboMount, TurboMountController, buildRegisterFunction };
@@ -1,2 +1,2 @@
1
- import{Controller as t,Application as o}from"@hotwired/stimulus";class n extends t{connect(){this._umountComponentCallback||(this._umountComponentCallback=this.mountComponent(this.mountElement,this.resolvedComponent,this.componentProps))}disconnect(){this.umountComponent()}propsValueChanged(){this.umountComponent(),this._umountComponentCallback||(this._umountComponentCallback=this.mountComponent(this.mountElement,this.resolvedComponent,this.componentProps))}get componentProps(){return this.propsValue}get mountElement(){return this.hasMountTarget?this.mountTarget:this.element}get resolvedComponent(){return this.resolveComponent(this.componentValue)}umountComponent(){this._umountComponentCallback&&this._umountComponentCallback(),this._umountComponentCallback=void 0}resolveComponent(t){return this.application.turboMount[this.framework].resolve(t)}}n.values={props:Object,component:String},n.targets=["mount"];class e{constructor({application:t,plugin:o}){var n;this.components=new Map,this.application=this.findOrStartApplication(t),this.framework=o.framework,this.baseController=o.controller,(n=this.application).turboMount||(n.turboMount={}),this.application.turboMount[this.framework]=this,this.baseController&&this.application.register(`turbo-mount-${this.framework}`,this.baseController)}findOrStartApplication(t){let n=t||window.Stimulus;return n||(n=o.start(),window.Stimulus=n),n}register(t,o,n){if(n||(n=this.baseController),this.components.has(t))throw new Error(`Component '${t}' is already registered.`);if(this.components.set(t,o),n){const o=`turbo-mount-${this.framework}-${e=t,e.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase()}`;this.application.register(o,n)}var e}resolve(t){const o=this.components.get(t);if(!o)throw new Error(`Unknown component: ${t}`);return o}}export{e as TurboMount,n as TurboMountController};
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","../src/helpers.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 static targets = [\"mount\"];\n\n declare readonly propsValue: object;\n declare readonly componentValue: string;\n declare readonly hasMountTarget: boolean;\n declare readonly mountTarget: Element;\n\n abstract framework: string;\n\n abstract mountComponent(el: Element, Component: T, props: object): () => void;\n\n _umountComponentCallback?: () => void;\n\n connect() {\n this._umountComponentCallback ||= this.mountComponent(\n this.mountElement,\n this.resolvedComponent,\n this.componentProps,\n );\n }\n\n disconnect() {\n this.umountComponent();\n }\n\n propsValueChanged() {\n this.umountComponent();\n this._umountComponentCallback ||= this.mountComponent(\n this.mountElement,\n this.resolvedComponent,\n this.componentProps,\n );\n }\n\n get componentProps() {\n return this.propsValue;\n }\n\n get mountElement() {\n return this.hasMountTarget ? this.mountTarget : this.element;\n }\n\n get resolvedComponent() {\n return this.resolveComponent(this.componentValue);\n }\n\n umountComponent() {\n this._umountComponentCallback && this._umountComponentCallback();\n this._umountComponentCallback = undefined;\n }\n\n resolveComponent(component: string): T {\n const app = this.application as ApplicationWithTurboMount<T>;\n return app.turboMount[this.framework].resolve(component);\n }\n}\n","import { Application, ControllerConstructor } from \"@hotwired/stimulus\";\n\nimport { camelToKebabCase } from \"./helpers\";\n\ndeclare global {\n interface Window {\n Stimulus?: Application;\n }\n}\n\nexport interface ApplicationWithTurboMount<T> extends Application {\n turboMount: { [framework: string]: TurboMount<T> };\n}\n\nexport type Plugin = {\n framework: string;\n controller: ControllerConstructor;\n};\n\nexport type TurboMountProps = {\n application?: Application;\n plugin: Plugin;\n};\n\nexport class TurboMount<T> {\n components: Map<string, T>;\n application: ApplicationWithTurboMount<T>;\n framework: string;\n baseController?: ControllerConstructor;\n\n constructor({ application, plugin }: TurboMountProps) {\n this.components = new Map();\n this.application = this.findOrStartApplication(application);\n this.framework = plugin.framework;\n this.baseController = plugin.controller;\n\n this.application.turboMount ||= {};\n this.application.turboMount[this.framework] = this;\n\n if (this.baseController) {\n this.application.register(\n `turbo-mount-${this.framework}`,\n this.baseController,\n );\n }\n }\n\n private findOrStartApplication(hydratedApp?: Application) {\n let application = hydratedApp || window.Stimulus;\n\n if (!application) {\n application = Application.start();\n window.Stimulus = application;\n }\n return application as ApplicationWithTurboMount<T>;\n }\n\n register(name: string, component: T, controller?: ControllerConstructor) {\n controller ||= this.baseController;\n if (this.components.has(name)) {\n throw new Error(`Component '${name}' is already registered.`);\n }\n this.components.set(name, component);\n\n if (controller) {\n const controllerName = `turbo-mount-${this.framework}-${camelToKebabCase(name)}`;\n this.application.register(controllerName, controller);\n }\n }\n\n resolve(name: string) {\n const component = this.components.get(name);\n if (!component) {\n throw new Error(`Unknown component: ${name}`);\n }\n return component;\n }\n}\n","export const camelToKebabCase = (str: string) => {\n return str.replace(/([a-z])([A-Z])/g, \"$1-$2\").toLowerCase();\n};\n"],"names":["TurboMountController","Controller","connect","this","_umountComponentCallback","mountComponent","mountElement","resolvedComponent","componentProps","disconnect","umountComponent","propsValueChanged","propsValue","hasMountTarget","mountTarget","element","resolveComponent","componentValue","undefined","component","application","turboMount","framework","resolve","values","props","Object","String","targets","TurboMount","constructor","plugin","components","Map","findOrStartApplication","baseController","controller","_a","register","hydratedApp","window","Stimulus","Application","start","name","has","Error","set","controllerName","str","replace","toLowerCase","get"],"mappings":"iEAGM,MAAgBA,UAAgCC,EAkBpD,OAAAC,GACEC,KAAKC,2BAALD,KAAKC,yBAA6BD,KAAKE,eACrCF,KAAKG,aACLH,KAAKI,kBACLJ,KAAKK,gBAER,CAED,UAAAC,GACEN,KAAKO,iBACN,CAED,iBAAAC,GACER,KAAKO,kBACLP,KAAKC,2BAALD,KAAKC,yBAA6BD,KAAKE,eACrCF,KAAKG,aACLH,KAAKI,kBACLJ,KAAKK,gBAER,CAED,kBAAIA,GACF,OAAOL,KAAKS,UACb,CAED,gBAAIN,GACF,OAAOH,KAAKU,eAAiBV,KAAKW,YAAcX,KAAKY,OACtD,CAED,qBAAIR,GACF,OAAOJ,KAAKa,iBAAiBb,KAAKc,eACnC,CAED,eAAAP,GACEP,KAAKC,0BAA4BD,KAAKC,2BACtCD,KAAKC,8BAA2Bc,CACjC,CAED,gBAAAF,CAAiBG,GAEf,OADYhB,KAAKiB,YACNC,WAAWlB,KAAKmB,WAAWC,QAAQJ,EAC/C,EA1DMnB,EAAAwB,OAAS,CACdC,MAAOC,OACPP,UAAWQ,QAEN3B,EAAA4B,QAAU,CAAC,eCgBPC,EAMX,WAAAC,EAAYV,YAAEA,EAAWW,OAAEA,UACzB5B,KAAK6B,WAAa,IAAIC,IACtB9B,KAAKiB,YAAcjB,KAAK+B,uBAAuBd,GAC/CjB,KAAKmB,UAAYS,EAAOT,UACxBnB,KAAKgC,eAAiBJ,EAAOK,YAE7BC,EAAAlC,KAAKiB,aAAYC,aAAAgB,EAAAhB,WAAe,CAAA,GAChClB,KAAKiB,YAAYC,WAAWlB,KAAKmB,WAAanB,KAE1CA,KAAKgC,gBACPhC,KAAKiB,YAAYkB,SACf,eAAenC,KAAKmB,YACpBnB,KAAKgC,eAGV,CAEO,sBAAAD,CAAuBK,GAC7B,IAAInB,EAAcmB,GAAeC,OAAOC,SAMxC,OAJKrB,IACHA,EAAcsB,EAAYC,QAC1BH,OAAOC,SAAWrB,GAEbA,CACR,CAED,QAAAkB,CAASM,EAAczB,EAAciB,GAEnC,GADAA,IAAAA,EAAejC,KAAKgC,gBAChBhC,KAAK6B,WAAWa,IAAID,GACtB,MAAM,IAAIE,MAAM,cAAcF,6BAIhC,GAFAzC,KAAK6B,WAAWe,IAAIH,EAAMzB,GAEtBiB,EAAY,CACd,MAAMY,EAAiB,eAAe7C,KAAKmB,aCjEhB2B,EDiE8CL,EChEtEK,EAAIC,QAAQ,kBAAmB,SAASC,gBDiE3ChD,KAAKiB,YAAYkB,SAASU,EAAgBZ,EAC3C,CCnE2B,IAACa,CDoE9B,CAED,OAAA1B,CAAQqB,GACN,MAAMzB,EAAYhB,KAAK6B,WAAWoB,IAAIR,GACtC,IAAKzB,EACH,MAAM,IAAI2B,MAAM,sBAAsBF,KAExC,OAAOzB,CACR"}
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,124 @@
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
+ begin
58
+ append_to_file js_entrypoint, %(import "./turbo-mount"\n)
59
+ rescue
60
+ say 'Could not find the application entrypoint, please add `import "./turbo-mount"` manually.', :yellow
61
+ end
62
+ warn_about_vite_plugin if vite?
63
+ end
64
+
65
+ def install_importmap
66
+ say "Creating Turbo Mount initializer"
67
+ template "turbo-mount.js", File.join("app/javascript/turbo-mount-initializer.js")
68
+ append_to_file "app/javascript/application.js", %(import "turbo-mount-initializer"\n)
69
+
70
+ say "Pinning Turbo Mount to the importmap"
71
+ append_to_file "config/importmap.rb", %(pin "turbo-mount", to: "turbo-mount.min.js"\n)
72
+ append_to_file "config/importmap.rb", %(pin "turbo-mount/#{framework}", to: "turbo-mount/#{framework}.min.js"\n)
73
+ append_to_file "config/importmap.rb", %(pin "turbo-mount-initializer"\n)
74
+
75
+ say "Pinning framework dependencies to the importmap"
76
+ run "bin/importmap pin #{FRAMEWORKS[framework][:pins]}"
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"
93
+ end
94
+
95
+ def warn_about_vite_plugin
96
+ say "Make sure to install and add #{FRAMEWORKS[framework][:vite_plugin]} to your Vite config", :yellow
97
+ end
98
+
99
+ def build_tool
100
+ return @build_tool if defined?(@build_tool)
101
+
102
+ @build_tool = detect_build_tool
103
+ end
104
+
105
+ def detect_build_tool
106
+ if Rails.root.join("package.json").exist?
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
117
+ end
118
+
119
+ def framework
120
+ @framework ||= ask("What framework do you want to use with Turbo Mount?", limited_to: FRAMEWORKS.keys, default: "react")
121
+ end
122
+ end
123
+ end
124
+ end
@@ -3,10 +3,10 @@
3
3
  module Turbo
4
4
  module Mount
5
5
  module Helpers
6
- def turbo_mount_component(component_name, framework:, props: {}, tag: "div", **attrs, &block)
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-#{framework}-#{component_name.underscore.dasherize}"
9
+ controller_name = "turbo-mount-#{component_name.underscore.dasherize}"
10
10
  attrs["data-controller"] = controller_name
11
11
  prefix = "data-#{controller_name}"
12
12
  attrs["#{prefix}-component-value"] = component_name
@@ -16,18 +16,7 @@ module Turbo
16
16
 
17
17
  content_tag(tag, nil, attrs) { capture(controller_name, &block) }
18
18
  end
19
-
20
- def turbo_mount_react_component(component_name, **attrs, &block)
21
- turbo_mount_component(component_name, framework: "react", **attrs, &block)
22
- end
23
-
24
- def turbo_mount_svelte_component(component_name, **attrs, &block)
25
- turbo_mount_component(component_name, framework: "svelte", **attrs, &block)
26
- end
27
-
28
- def turbo_mount_vue_component(component_name, **attrs, &block)
29
- turbo_mount_component(component_name, framework: "vue", **attrs, &block)
30
- end
19
+ alias_method :turbo_mount_component, :turbo_mount
31
20
  end
32
21
  end
33
22
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Turbo
4
4
  module Mount
5
- VERSION = "0.2.3"
5
+ VERSION = "0.3.1"
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.3
4
+ version: 0.3.1
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-12 00:00:00.000000000 Z
11
+ date: 2024-06-16 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