turbo-mount 0.2.3 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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