turbo-mount 0.2.3 → 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: '0318ba2497956b4a56bc035a5a39e617bf0b171b039823fd61fe131294ca5815'
4
- data.tar.gz: 6a078a433b9766915496ac9d86d9df81782eec209c495ccefcb978643c810a39
3
+ metadata.gz: 3565dc0d8e80907e11600573cfb18049fe5360c9551facefb9a22d871bebad2e
4
+ data.tar.gz: 2e82006304c22c3b663511abbefaee3a0fc8e96aa972b044e1fca640cfd9c262
5
5
  SHA512:
6
- metadata.gz: 75dd9c6f8b0760791c40d3db5b07cf95056bc199550d95c5a86cec1686a8f11eb0594ad7e1d4d4c015867289371f18e2b1cc7f1ccb6738e24b8342a42e7b8f1c
7
- data.tar.gz: 1ac86acfbb7d0e5c01e61c791e6ba058d78ae01092bdd1dc5cdd0059a5309922888a59cd0afad63541c2c517c478f159d351ef2e0da326ddddf1b48d6c9de534
6
+ metadata.gz: 2490534f21cf102b00c00524457be4b6b6f3d048e2d0477ad20bdbf2818704d5b39580bc9f71d5f33de6f39fcdd97faa20e0c0cc05a3c338225091e1096fa428
7
+ data.tar.gz: 6154769b9a73b2bf3e65e0c6a395a154109466d698f79dc5483378ed7f68cce060c41183725f240fff942a3cf09e03ac909b2f0d0f0da70a574a6712e28e1251
data/CHANGELOG.md CHANGED
@@ -7,6 +7,21 @@ 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
+
10
25
  ## [0.2.3] - 2024-05-12
11
26
 
12
27
  ### Added
@@ -36,10 +51,11 @@ and this project adheres to [Semantic Versioning].
36
51
 
37
52
  [@skryukov]: https://github.com/skryukov
38
53
 
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
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
43
59
  [0.1.0]: https://github.com/skryukov/turbo-mount/commits/v0.1.0
44
60
 
45
61
  [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
data/README.md CHANGED
@@ -1,104 +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
- 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:
16
37
 
17
38
  ```bash
18
- npm install turbo-mount
19
- # or with yarn
20
- yarn add turbo-mount
39
+ bin/rails generate turbo_mount:install
21
40
  ```
22
41
 
23
- ## Usage
42
+ This will add `turbo-mount` package and framework dependencies to your `package.json` or `importmap.rb`, and create the Turbo Mount initialization file.
24
43
 
25
- ### Initialization
44
+ ### Manual Installation
26
45
 
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.
46
+ You can also install the necessary JavaScript files manually.
28
47
 
29
- #### Standard Initialization
48
+ If your project utilizes build tools such as [Vite](http://vite-ruby.netlify.app), also install the `turbo-mount` package:
30
49
 
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:
50
+ ```bash
51
+ npm install turbo-mount
52
+ # or with yarn
53
+ yarn add turbo-mount
32
54
 
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';
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`.
38
64
 
39
- const application = Application.start();
40
- const turboMount = new TurboMount({ application, plugin });
65
+ ### Importmaps
66
+ To use `TurboMount` with importmaps, you need to pin the necessary JavaScript files in your `config/importmap.rb`:
41
67
 
42
- turboMount.register('SketchPicker', SketchPicker);
68
+ ```ruby
69
+ pin "turbo-mount", to: "turbo-mount.min.js"
70
+ pin "turbo-mount/react", to: "turbo-mount/react.min.js"
43
71
  ```
44
72
 
45
- #### Simplified Initialization
73
+ This ensures that `turbo-mount` and its plugins are available in your application.
46
74
 
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:
75
+ Also pin the desired framework:
48
76
 
49
- ```js
50
- import { TurboMount } from "turbo-mount";
51
- import plugin from "turbo-mount/react";
52
- 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
+ ```
53
84
 
54
- const turboMount = new TurboMount({ 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.
55
86
 
56
- turboMount.register('SketchPicker', SketchPicker);
57
- ```
87
+ ## Usage
58
88
 
59
- #### Plugin-Specific Initialization
89
+ ### Initialization
60
90
 
61
- For a more streamlined setup, you can directly import a specialized version of `TurboMount`:
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:
62
92
 
63
93
  ```js
64
- import { TurboMountReact } from "turbo-mount/react";
65
- import { SketchPicker } from 'react-color';
94
+ import { TurboMount } from "turbo-mount";
95
+ import { registerComponent } from "turbo-mount/react";
96
+ import { HexColorPicker } from 'react-colorful';
66
97
 
67
- const turboMount = new TurboMountReact();
98
+ const turboMount = new TurboMount(); // or new TurboMount({ application })
68
99
 
69
- turboMount.register('SketchPicker', SketchPicker);
100
+ registerComponent(turboMount, "HexColorPicker", HexColorPicker);
70
101
  ```
71
102
 
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.
104
+
72
105
  ### View Helpers
73
106
 
74
107
  Use the following helpers to mount components in your views:
75
108
 
76
109
  ```erb
77
- <%= turbo_mount_component("SketchPicker", framework: "react", props: {color: "#034"}) %>
110
+ <%= turbo_mount("HexColorPicker", props: {color: "#034"}, class: "mb-5") %>
111
+ ```
78
112
 
79
- <%# or using alias %>
113
+ This will generate the following HTML:
80
114
 
81
- <%= 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>
82
121
  ```
83
122
 
84
123
  ### Supported Frameworks
85
124
 
86
125
  `TurboMount` supports the following frameworks:
87
126
 
88
- - React `"turbo-mount/react"`
89
- - Vue `"turbo-mount/vue"`
90
- - Svelte `"turbo-mount/svelte"`
127
+ - React: `"turbo-mount/react"`
128
+ - Vue: `"turbo-mount/vue"`
129
+ - Svelte: `"turbo-mount/svelte"`
91
130
 
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.
131
+ To add support for other frameworks, create a custom plugin. See included plugins for examples.
93
132
 
94
133
  ### Custom Controllers
95
134
 
96
135
  To customize component behavior or pass functions as props, create a custom controller:
97
136
 
98
137
  ```js
99
- import { TurboMountReactController } from "turbo-mount"
138
+ import { TurboMountController } from "turbo-mount";
100
139
 
101
- export default class extends TurboMountReactController {
140
+ export default class extends TurboMountController {
102
141
  get componentProps() {
103
142
  return {
104
143
  ...this.propsValue,
@@ -107,62 +146,51 @@ export default class extends TurboMountReactController {
107
146
  }
108
147
 
109
148
  onChange = (color) => {
110
- 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 };
111
152
  };
112
153
  }
113
154
  ```
114
155
 
115
- Then pass this controller the register method:
156
+ Then pass this controller to the `registerComponent` method:
116
157
 
117
158
  ```js
118
- import SketchController from "controllers/turbo_mount/sketch_picker_controller"
159
+ import HexColorPickerController from "controllers/turbo_mount/hex_color_picker_controller";
119
160
 
120
- turboMount.register('SketchPicker', SketchPicker, SketchController);
161
+ registerComponent(turboMount, "HexColorPicker", HexColorPicker, HexColorPickerController);
121
162
  ```
122
163
 
123
164
  ### Vite Integration
124
165
 
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:
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:
126
167
 
127
168
  ```js
128
169
  import { TurboMount } from "turbo-mount/react";
129
- import { registerComponents } from "turbo-mount/vite";
170
+ import { registerComponents } from "turbo-mount/registerComponents/react";
130
171
 
131
172
  const controllers = import.meta.glob("./**/*_controller.js", { eager: true });
132
- const components = import.meta.glob(`/components/**/*.jsx`, { eager: true });
173
+ const components = import.meta.glob("/components/**/*.jsx", { eager: true });
133
174
 
134
175
  const turboMount = new TurboMount();
135
176
  registerComponents({ turboMount, components, controllers });
136
177
  ```
137
178
 
138
179
  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
180
  - `controllers/turbo-mount/${controllerName}`
143
181
  - `controllers/turbo-mount-${controllerName}`
144
182
 
145
- ### Mount target
183
+ ### Mount Target
146
184
 
147
185
  To specify a non-root mount target, use the `data-<%= controller_name %>-target="mount"` attribute:
148
186
 
149
187
  ```erb
150
- <%= turbo_mount_react_component("SketchPicker", props: {color: "#430"}) do |controller_name| %>
188
+ <%= turbo_mount("HexColorPicker", props: {color: "#430"}) do |controller_name| %>
151
189
  <h3>Color picker</h3>
152
190
  <div data-<%= controller_name %>-target="mount"></div>
153
191
  <% end %>
154
192
  ```
155
193
 
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
194
  ## License
167
195
 
168
196
  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,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,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.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.3
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-12 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