turbo-mount 0.3.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6385a766c9e0173e886eb33c27946e222dba5ee810744af4c6d317a93d80b315
4
- data.tar.gz: 0c4402e72e9a5b6a98b8219e0ad8749ee3b2716f99412ebe3fcd35783798be7a
3
+ metadata.gz: a1e390ef4fc7a3d329693a294437fa8f5d27c9157654b3a9b21621b26dd6c7c5
4
+ data.tar.gz: 591ea18f8b02da05da9a9d6dba5249eccdf01ec2b4d00477341df76166c0bd7d
5
5
  SHA512:
6
- metadata.gz: bf22bb88a3042c20b96774167c25b500146f1ba002f2224609e4444afd968e68527c664970ca823c1b73f09e74c685cd7d51f7354fd838bceb8d1b00a5f13d14
7
- data.tar.gz: e4c5cb4ee14f437e35d49d4d36ec5e423649f8e5685319dec43c1c6f35f1fca8f7231eb1bf016ce9b0fb274eab184145049446d6ccfa579657fce151bffc3fcf
6
+ metadata.gz: fd306af35603685bebc4c2a7ef0fe31909fa56b521f0808562aeee3f8c90f2d5098aa846e6633356b716ced5aa51b8c5e6131252b97c5b91ccf14242aa90baff
7
+ data.tar.gz: 8a7221dad2c96c5c8c873e841a4007f1b45bcf6adada668af008692b035589590a230c411756f1cac86918d74521d0f6cec47d8c2125feddb1b5bfca91187ec3
data/CHANGELOG.md CHANGED
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning].
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.4.0] - 2024-11-03
11
+
12
+ ### Added
13
+
14
+ - [BREAKING] Support Svelte 5. ([@skryukov])
15
+ `turbo-mount/svelte` is now the Svelte 5 plugin. The Svelte 4 plugin is now `turbo-mount/svelte4`.
16
+
10
17
  ## [0.3.3] - 2024-09-24
11
18
 
12
19
  ### Added
@@ -75,7 +82,8 @@ and this project adheres to [Semantic Versioning].
75
82
  [@jkogara]: https://github.com/jkogara
76
83
  [@skryukov]: https://github.com/skryukov
77
84
 
78
- [Unreleased]: https://github.com/skryukov/turbo-mount/compare/v0.3.3...HEAD
85
+ [Unreleased]: https://github.com/skryukov/turbo-mount/compare/v0.4.0...HEAD
86
+ [0.4.0]: https://github.com/skryukov/turbo-mount/compare/v0.3.3...v0.4.0
79
87
  [0.3.3]: https://github.com/skryukov/turbo-mount/compare/v0.3.2...v0.3.3
80
88
  [0.3.2]: https://github.com/skryukov/turbo-mount/compare/v0.3.1...v0.3.2
81
89
  [0.3.1]: https://github.com/skryukov/turbo-mount/compare/v0.3.0...v0.3.1
data/README.md CHANGED
@@ -1,4 +1,8 @@
1
- # Turbo Mount
1
+ <p align="center">
2
+ <img src="./assets/logo.svg" title="Turbo Mount logo" width="220" height="45">
3
+ </p>
4
+
5
+ <h1 align="center">Turbo Mount</h1>
2
6
 
3
7
  [![Gem Version](https://badge.fury.io/rb/turbo-mount.svg)](https://rubygems.org/gems/turbo-mount)
4
8
 
@@ -143,7 +147,8 @@ This will generate the following HTML:
143
147
 
144
148
  - React: `"turbo-mount/react"`
145
149
  - Vue: `"turbo-mount/vue"`
146
- - Svelte: `"turbo-mount/svelte"`
150
+ - Svelte 4: `"turbo-mount/svelte4"`
151
+ - Svelte 5: `"turbo-mount/svelte"`
147
152
 
148
153
  To add support for other frameworks, create a custom plugin. See included plugins for examples.
149
154
 
@@ -1,12 +1,13 @@
1
1
  import { buildRegisterFunction } from 'turbo-mount';
2
2
  export { TurboMount } from 'turbo-mount';
3
+ import { mount, unmount } from 'svelte';
3
4
 
4
5
  const plugin = {
5
6
  mountComponent: (mountProps) => {
6
7
  const { el, Component, props } = mountProps;
7
- const component = new Component({ target: el, props });
8
+ const component = mount(Component, { target: el, props });
8
9
  return () => {
9
- component.$destroy();
10
+ unmount(component);
10
11
  };
11
12
  },
12
13
  };
@@ -1,2 +1,2 @@
1
- import{buildRegisterFunction as o}from"turbo-mount";export{TurboMount}from"turbo-mount";const 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};
1
+ import{buildRegisterFunction as o}from"turbo-mount";export{TurboMount}from"turbo-mount";import{mount as t,unmount as r}from"svelte";const n={mountComponent:o=>{const{el:n,Component:e,props:m}=o,p=t(e,{target:n,props:m});return()=>{r(p)}}},e=o(n);export{n as default,e as registerComponent};
2
2
  //# sourceMappingURL=svelte.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"svelte.min.js","sources":["../../src/plugins/svelte/index.ts"],"sourcesContent":["import { buildRegisterFunction, Plugin, TurboMount } from \"turbo-mount\";\nimport { 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
+ {"version":3,"file":"svelte.min.js","sources":["../../src/plugins/svelte/index.ts"],"sourcesContent":["import { buildRegisterFunction, Plugin, TurboMount } from \"turbo-mount\";\nimport { Component, mount, unmount } from \"svelte\";\n\nconst plugin: Plugin<Component> = {\n mountComponent: (mountProps) => {\n const { el, Component, props } = mountProps;\n const component = mount(Component, { target: el, props });\n return () => {\n unmount(component);\n };\n },\n};\n\nconst registerComponent = buildRegisterFunction(plugin);\n\nexport { TurboMount, registerComponent };\n\nexport default plugin;\n"],"names":["plugin","mountComponent","mountProps","el","Component","props","component","mount","target","unmount","registerComponent","buildRegisterFunction"],"mappings":"oIAGA,MAAMA,EAA4B,CAChCC,eAAiBC,IACf,MAAMC,GAAEA,EAAEC,UAAEA,EAASC,MAAEA,GAAUH,EAC3BI,EAAYC,EAAMH,EAAW,CAAEI,OAAQL,EAAIE,UACjD,MAAO,KACLI,EAAQH,EAAU,CACnB,GAICI,EAAoBC,EAAsBX"}
@@ -0,0 +1,15 @@
1
+ import { buildRegisterFunction } from 'turbo-mount';
2
+ export { TurboMount } from 'turbo-mount';
3
+
4
+ const plugin = {
5
+ mountComponent: (mountProps) => {
6
+ const { el, Component, props } = mountProps;
7
+ const component = new Component({ target: el, props });
8
+ return () => {
9
+ component.$destroy();
10
+ };
11
+ },
12
+ };
13
+ const registerComponent = buildRegisterFunction(plugin);
14
+
15
+ export { plugin as default, registerComponent };
@@ -0,0 +1,2 @@
1
+ import{buildRegisterFunction as o}from"turbo-mount";export{TurboMount}from"turbo-mount";const t={mountComponent:o=>{const{el:t,Component:r,props:n}=o,e=new r({target:t,props:n});return()=>{e.$destroy()}}},r=o(t);export{t as default,r as registerComponent};
2
+ //# sourceMappingURL=svelte4.min.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"svelte4.min.js","sources":["../../src/plugins/svelte4/index.ts"],"sourcesContent":["import { buildRegisterFunction, Plugin, TurboMount } from \"turbo-mount\";\nimport { ComponentType } from \"svelte\";\n\nconst plugin: Plugin<ComponentType> = {\n mountComponent: (mountProps) => {\n const { el, Component, props } = mountProps;\n\n const component = new Component({ target: el, props });\n\n return () => {\n component.$destroy();\n };\n },\n};\n\nconst registerComponent = buildRegisterFunction(plugin);\n\nexport { TurboMount, registerComponent };\n\nexport default plugin;\n"],"names":["plugin","mountComponent","mountProps","el","Component","props","component","target","$destroy","registerComponent","buildRegisterFunction"],"mappings":"wFAGA,MAAMA,EAAgC,CACpCC,eAAiBC,IACf,MAAMC,GAAEA,EAAEC,UAAEA,EAASC,MAAEA,GAAUH,EAE3BI,EAAY,IAAIF,EAAU,CAAEG,OAAQJ,EAAIE,UAE9C,MAAO,KACLC,EAAUE,UAAU,CACrB,GAICC,EAAoBC,EAAsBV"}
@@ -64,10 +64,9 @@ class TurboMount {
64
64
  this.application.turboMount = this;
65
65
  this.application.register("turbo-mount", TurboMountController);
66
66
  document.addEventListener("turbo:before-morph-element", (event) => {
67
- var _a;
68
67
  const turboMorphEvent = event;
69
68
  const { target, detail } = turboMorphEvent;
70
- if ((_a = target.getAttribute("data-controller")) === null || _a === void 0 ? void 0 : _a.includes("turbo-mount")) {
69
+ if (target.getAttribute("data-controller")?.includes("turbo-mount")) {
71
70
  target.setAttribute("data-turbo-mount-props-value", detail.newElement.getAttribute("data-turbo-mount-props-value") ||
72
71
  "{}");
73
72
  event.preventDefault();
@@ -112,15 +111,14 @@ const identifierNames = (name) => {
112
111
  return [`turbo-mount--${controllerName}`, `turbo-mount-${controllerName}`];
113
112
  };
114
113
  const registerComponentsBase = ({ plugin, turboMount, components, controllers, }) => {
115
- var _a;
116
- const controllerModules = controllers !== null && controllers !== void 0 ? controllers : [];
114
+ const controllerModules = controllers ?? [];
117
115
  for (const { module, filename } of components) {
118
116
  const name = filename
119
117
  .replace(/\.\w*$/, "")
120
118
  .replace(/^[./]*components\//, "");
121
119
  const identifiers = identifierNames(name);
122
120
  const controller = controllerModules.find(({ identifier }) => identifiers.includes(identifier));
123
- const component = (_a = module.default) !== null && _a !== void 0 ? _a : module;
121
+ const component = module.default ?? module;
124
122
  if (controller) {
125
123
  turboMount.register(plugin, name, component, controller.controllerConstructor);
126
124
  }
@@ -1,2 +1,2 @@
1
- import{Controller as t,Application as o}from"@hotwired/stimulus";class n extends t{constructor(){super(...arguments),this.skipPropsChangeCallback=!1}connect(){this._umountComponentCallback||(this._umountComponentCallback=this.mountComponent(this.mountElement,this.resolvedComponent,this.componentProps))}disconnect(){this.umountComponent()}propsValueChanged(){this.skipPropsChangeCallback?this.skipPropsChangeCallback=!1:(this.umountComponent(),this._umountComponentCallback||(this._umountComponentCallback=this.mountComponent(this.mountElement,this.resolvedComponent,this.componentProps)))}get componentProps(){return this.propsValue}get mountElement(){return this.hasMountTarget?this.mountTarget:this.element}get resolvedComponent(){return this.resolveMounted(this.componentValue).component}get resolvedPlugin(){return this.resolveMounted(this.componentValue).plugin}umountComponent(){this._umountComponentCallback&&this._umountComponentCallback(),this._umountComponentCallback=void 0}mountComponent(t,o,n){return this.resolvedPlugin.mountComponent({el:t,Component:o,props:n})}resolveMounted(t){return this.application.turboMount.resolve(t)}setComponentProps(t){this.skipPropsChangeCallback=!0,this.propsValue=t}}n.values={props:Object,component:String},n.targets=["mount"];const e=t=>t.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase();class r{constructor(t={}){this.components=new Map,this.application=this.findOrStartApplication(t.application),this.application.turboMount=this,this.application.register("turbo-mount",n),document.addEventListener("turbo:before-morph-element",(t=>{var o;const n=t,{target:e,detail:r}=n;(null===(o=e.getAttribute("data-controller"))||void 0===o?void 0:o.includes("turbo-mount"))&&(e.setAttribute("data-turbo-mount-props-value",r.newElement.getAttribute("data-turbo-mount-props-value")||"{}"),t.preventDefault())}))}register(t,o,r,s){if(s||(s=n),this.components.has(o))throw new Error(`Component '${o}' is already registered.`);if(this.components.set(o,{component:r,plugin:t}),s){const t=`turbo-mount-${e(o).replace("/","--")}`;this.application.register(t,s)}}resolve(t){const o=this.components.get(t);if(!o)throw new Error(`Unknown component: ${t}`);return o}findOrStartApplication(t){let n=t||window.Stimulus;return n||(n=o.start(),window.Stimulus=n),n}}function s(t){return(o,n,e,r)=>{o.register(t,n,e,r)}}const i=t=>{const o=e(t).replace("/","--");return[`turbo-mount--${o}`,`turbo-mount-${o}`]},u=({plugin:t,turboMount:o,components:n,controllers:e})=>{var r;const s=null!=e?e:[];for(const{module:e,filename:u}of n){const n=u.replace(/\.\w*$/,"").replace(/^[./]*components\//,""),l=i(n),p=s.find((({identifier:t})=>l.includes(t))),a=null!==(r=e.default)&&void 0!==r?r:e;p?o.register(t,n,a,p.controllerConstructor):o.register(t,n,a)}};export{r as TurboMount,n as TurboMountController,s as buildRegisterFunction,u as registerComponentsBase};
1
+ import{Controller as t,Application as o}from"@hotwired/stimulus";class n extends t{constructor(){super(...arguments),this.skipPropsChangeCallback=!1}connect(){this._umountComponentCallback||(this._umountComponentCallback=this.mountComponent(this.mountElement,this.resolvedComponent,this.componentProps))}disconnect(){this.umountComponent()}propsValueChanged(){this.skipPropsChangeCallback?this.skipPropsChangeCallback=!1:(this.umountComponent(),this._umountComponentCallback||(this._umountComponentCallback=this.mountComponent(this.mountElement,this.resolvedComponent,this.componentProps)))}get componentProps(){return this.propsValue}get mountElement(){return this.hasMountTarget?this.mountTarget:this.element}get resolvedComponent(){return this.resolveMounted(this.componentValue).component}get resolvedPlugin(){return this.resolveMounted(this.componentValue).plugin}umountComponent(){this._umountComponentCallback&&this._umountComponentCallback(),this._umountComponentCallback=void 0}mountComponent(t,o,n){return this.resolvedPlugin.mountComponent({el:t,Component:o,props:n})}resolveMounted(t){return this.application.turboMount.resolve(t)}setComponentProps(t){this.skipPropsChangeCallback=!0,this.propsValue=t}}n.values={props:Object,component:String},n.targets=["mount"];const e=t=>t.replace(/([a-z])([A-Z])/g,"$1-$2").toLowerCase();class r{constructor(t={}){this.components=new Map,this.application=this.findOrStartApplication(t.application),this.application.turboMount=this,this.application.register("turbo-mount",n),document.addEventListener("turbo:before-morph-element",(t=>{const o=t,{target:n,detail:e}=o;n.getAttribute("data-controller")?.includes("turbo-mount")&&(n.setAttribute("data-turbo-mount-props-value",e.newElement.getAttribute("data-turbo-mount-props-value")||"{}"),t.preventDefault())}))}register(t,o,r,s){if(s||(s=n),this.components.has(o))throw new Error(`Component '${o}' is already registered.`);if(this.components.set(o,{component:r,plugin:t}),s){const t=`turbo-mount-${e(o).replace("/","--")}`;this.application.register(t,s)}}resolve(t){const o=this.components.get(t);if(!o)throw new Error(`Unknown component: ${t}`);return o}findOrStartApplication(t){let n=t||window.Stimulus;return n||(n=o.start(),window.Stimulus=n),n}}function s(t){return(o,n,e,r)=>{o.register(t,n,e,r)}}const i=t=>{const o=e(t).replace("/","--");return[`turbo-mount--${o}`,`turbo-mount-${o}`]},u=({plugin:t,turboMount:o,components:n,controllers:e})=>{const r=e??[];for(const{module:e,filename:s}of n){const n=s.replace(/\.\w*$/,"").replace(/^[./]*components\//,""),u=i(n),p=r.find((({identifier:t})=>u.includes(t))),l=e.default??e;p?o.register(t,n,l,p.controllerConstructor):o.register(t,n,l)}};export{r as TurboMount,n as TurboMountController,s as buildRegisterFunction,u as registerComponentsBase};
2
2
  //# sourceMappingURL=turbo-mount.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"turbo-mount.min.js","sources":["../src/turbo-mount-controller.ts","../src/helpers.ts","../src/turbo-mount.ts","../src/registerComponentsBase.ts"],"sourcesContent":["import { Controller } from \"@hotwired/stimulus\";\nimport { ApplicationWithTurboMount } from \"./turbo-mount\";\n\nexport class TurboMountController extends Controller {\n static values = {\n props: Object,\n component: String,\n };\n static targets = [\"mount\"];\n\n private skipPropsChangeCallback = false;\n\n declare propsValue: object;\n declare componentValue: string;\n declare readonly hasMountTarget: boolean;\n declare readonly mountTarget: Element;\n\n _umountComponentCallback?: () => void;\n\n connect() {\n this._umountComponentCallback ||= this.mountComponent(\n this.mountElement,\n this.resolvedComponent,\n this.componentProps,\n );\n }\n\n disconnect() {\n this.umountComponent();\n }\n\n propsValueChanged() {\n // Prevent re-mounting the component if the props are being set by the component itself\n if (this.skipPropsChangeCallback) {\n this.skipPropsChangeCallback = false;\n return;\n }\n\n this.umountComponent();\n this._umountComponentCallback ||= this.mountComponent(\n this.mountElement,\n this.resolvedComponent,\n this.componentProps,\n );\n }\n\n get componentProps() {\n return this.propsValue;\n }\n\n get mountElement() {\n return this.hasMountTarget ? this.mountTarget : this.element;\n }\n\n get resolvedComponent() {\n return this.resolveMounted(this.componentValue).component;\n }\n\n get resolvedPlugin() {\n return this.resolveMounted(this.componentValue).plugin;\n }\n\n umountComponent() {\n this._umountComponentCallback && this._umountComponentCallback();\n this._umountComponentCallback = undefined;\n }\n\n mountComponent(el: Element, Component: unknown, props: object) {\n return this.resolvedPlugin.mountComponent({ el, Component, props });\n }\n\n resolveMounted(component: string) {\n const app = this.application as ApplicationWithTurboMount;\n return app.turboMount.resolve(component);\n }\n\n setComponentProps(props: object) {\n this.skipPropsChangeCallback = true;\n this.propsValue = props;\n }\n}\n","export const camelToKebabCase = (str: string) => {\n return str.replace(/([a-z])([A-Z])/g, \"$1-$2\").toLowerCase();\n};\n","import { Application, ControllerConstructor } from \"@hotwired/stimulus\";\n\nimport { camelToKebabCase } from \"./helpers\";\nimport { TurboMountController } from \"./turbo-mount-controller\";\n\ndeclare global {\n interface Window {\n Stimulus?: Application;\n }\n}\n\nexport interface ApplicationWithTurboMount extends Application {\n turboMount: TurboMount;\n}\n\nexport type MountComponentProps<T> = {\n el: Element;\n Component: T;\n props: object;\n};\n\nexport type Plugin<T> = {\n mountComponent: (props: MountComponentProps<T>) => () => void;\n};\n\nexport type TurboMountProps = {\n application?: Application;\n};\n\ntype TurboMountComponents<T> = Map<string, { component: T; plugin: Plugin<T> }>;\n\ninterface TurboMorphEvent extends CustomEvent {\n target: Element;\n detail: {\n newElement: Element;\n };\n}\n\nexport class TurboMount {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n components: TurboMountComponents<any>;\n application: ApplicationWithTurboMount;\n\n constructor(props: TurboMountProps = {}) {\n this.components = new Map();\n this.application = this.findOrStartApplication(props.application);\n this.application.turboMount = this;\n this.application.register(\"turbo-mount\", TurboMountController);\n\n document.addEventListener(\"turbo:before-morph-element\", (event) => {\n const turboMorphEvent = event as unknown as TurboMorphEvent;\n const { target, detail } = turboMorphEvent;\n\n if (target.getAttribute(\"data-controller\")?.includes(\"turbo-mount\")) {\n target.setAttribute(\n \"data-turbo-mount-props-value\",\n detail.newElement.getAttribute(\"data-turbo-mount-props-value\") ||\n \"{}\",\n );\n event.preventDefault();\n }\n });\n }\n\n register<T>(\n plugin: Plugin<T>,\n name: string,\n component: T,\n controller?: ControllerConstructor,\n ) {\n controller ||= TurboMountController;\n if (this.components.has(name)) {\n throw new Error(`Component '${name}' is already registered.`);\n }\n this.components.set(name, { component, plugin });\n\n if (controller) {\n const controllerName = `turbo-mount-${camelToKebabCase(name).replace(\"/\", \"--\")}`;\n this.application.register(controllerName, controller);\n }\n }\n\n resolve(name: string) {\n const component = this.components.get(name);\n if (!component) {\n throw new Error(`Unknown component: ${name}`);\n }\n return component;\n }\n\n private findOrStartApplication(hydratedApp?: Application) {\n let application = hydratedApp || window.Stimulus;\n\n if (!application) {\n application = Application.start();\n window.Stimulus = application;\n }\n return application as ApplicationWithTurboMount;\n }\n}\n\nexport function buildRegisterFunction<T>(plugin: Plugin<T>) {\n return (\n turboMount: TurboMount,\n name: string,\n component: T,\n controller?: ControllerConstructor,\n ) => {\n turboMount.register(plugin, name, component, controller);\n };\n}\n","import { Definition } from \"@hotwired/stimulus\";\n\nimport { TurboMount, Plugin } from \"./turbo-mount\";\nimport { camelToKebabCase } from \"./helpers\";\n\nexport type ComponentModule = { default: never } | never;\n\nexport type ComponentDefinition = {\n filename: string;\n module: ComponentModule;\n};\n\ntype RegisterComponentsProps<T> = {\n plugin: Plugin<T>;\n turboMount: TurboMount;\n components: ComponentDefinition[];\n controllers?: Definition[];\n};\n\nconst identifierNames = (name: string) => {\n const controllerName = camelToKebabCase(name).replace(\"/\", \"--\");\n\n return [`turbo-mount--${controllerName}`, `turbo-mount-${controllerName}`];\n};\n\nexport const registerComponentsBase = <T>({\n plugin,\n turboMount,\n components,\n controllers,\n}: RegisterComponentsProps<T>) => {\n const controllerModules = controllers ?? [];\n\n for (const { module, filename } of components) {\n const name = filename\n .replace(/\\.\\w*$/, \"\")\n .replace(/^[./]*components\\//, \"\");\n\n const identifiers = identifierNames(name);\n\n const controller = controllerModules.find(({ identifier }) =>\n identifiers.includes(identifier),\n );\n const component = module.default ?? module;\n\n if (controller) {\n turboMount.register(\n plugin,\n name,\n component,\n controller.controllerConstructor,\n );\n } else {\n turboMount.register(plugin, name, component);\n }\n }\n};\n"],"names":["TurboMountController","Controller","constructor","this","skipPropsChangeCallback","connect","_umountComponentCallback","mountComponent","mountElement","resolvedComponent","componentProps","disconnect","umountComponent","propsValueChanged","propsValue","hasMountTarget","mountTarget","element","resolveMounted","componentValue","component","resolvedPlugin","plugin","undefined","el","Component","props","application","turboMount","resolve","setComponentProps","values","Object","String","targets","camelToKebabCase","str","replace","toLowerCase","TurboMount","components","Map","findOrStartApplication","register","document","addEventListener","event","turboMorphEvent","target","detail","_a","getAttribute","includes","setAttribute","newElement","preventDefault","name","controller","has","Error","set","controllerName","get","hydratedApp","window","Stimulus","Application","start","buildRegisterFunction","identifierNames","registerComponentsBase","controllers","controllerModules","module","filename","identifiers","find","identifier","default","controllerConstructor"],"mappings":"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"}
1
+ {"version":3,"file":"turbo-mount.min.js","sources":["../src/turbo-mount-controller.ts","../src/helpers.ts","../src/turbo-mount.ts","../src/registerComponentsBase.ts"],"sourcesContent":["import { Controller } from \"@hotwired/stimulus\";\nimport { ApplicationWithTurboMount } from \"./turbo-mount\";\n\nexport class TurboMountController extends Controller {\n static values = {\n props: Object,\n component: String,\n };\n static targets = [\"mount\"];\n\n private skipPropsChangeCallback = false;\n\n declare propsValue: object;\n declare componentValue: string;\n declare readonly hasMountTarget: boolean;\n declare readonly mountTarget: Element;\n\n _umountComponentCallback?: () => void;\n\n connect() {\n this._umountComponentCallback ||= this.mountComponent(\n this.mountElement,\n this.resolvedComponent,\n this.componentProps,\n );\n }\n\n disconnect() {\n this.umountComponent();\n }\n\n propsValueChanged() {\n // Prevent re-mounting the component if the props are being set by the component itself\n if (this.skipPropsChangeCallback) {\n this.skipPropsChangeCallback = false;\n return;\n }\n\n this.umountComponent();\n this._umountComponentCallback ||= this.mountComponent(\n this.mountElement,\n this.resolvedComponent,\n this.componentProps,\n );\n }\n\n get componentProps() {\n return this.propsValue;\n }\n\n get mountElement() {\n return this.hasMountTarget ? this.mountTarget : this.element;\n }\n\n get resolvedComponent() {\n return this.resolveMounted(this.componentValue).component;\n }\n\n get resolvedPlugin() {\n return this.resolveMounted(this.componentValue).plugin;\n }\n\n umountComponent() {\n this._umountComponentCallback && this._umountComponentCallback();\n this._umountComponentCallback = undefined;\n }\n\n mountComponent(el: Element, Component: unknown, props: object) {\n return this.resolvedPlugin.mountComponent({ el, Component, props });\n }\n\n resolveMounted(component: string) {\n const app = this.application as ApplicationWithTurboMount;\n return app.turboMount.resolve(component);\n }\n\n setComponentProps(props: object) {\n this.skipPropsChangeCallback = true;\n this.propsValue = props;\n }\n}\n","export const camelToKebabCase = (str: string) => {\n return str.replace(/([a-z])([A-Z])/g, \"$1-$2\").toLowerCase();\n};\n","import { Application, ControllerConstructor } from \"@hotwired/stimulus\";\n\nimport { camelToKebabCase } from \"./helpers\";\nimport { TurboMountController } from \"./turbo-mount-controller\";\n\ndeclare global {\n interface Window {\n Stimulus?: Application;\n }\n}\n\nexport interface ApplicationWithTurboMount extends Application {\n turboMount: TurboMount;\n}\n\nexport type MountComponentProps<T> = {\n el: Element;\n Component: T;\n props: object;\n};\n\nexport type Plugin<T> = {\n mountComponent: (props: MountComponentProps<T>) => () => void;\n};\n\nexport type TurboMountProps = {\n application?: Application;\n};\n\ntype TurboMountComponents<T> = Map<string, { component: T; plugin: Plugin<T> }>;\n\ninterface TurboMorphEvent extends CustomEvent {\n target: Element;\n detail: {\n newElement: Element;\n };\n}\n\nexport class TurboMount {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n components: TurboMountComponents<any>;\n application: ApplicationWithTurboMount;\n\n constructor(props: TurboMountProps = {}) {\n this.components = new Map();\n this.application = this.findOrStartApplication(props.application);\n this.application.turboMount = this;\n this.application.register(\"turbo-mount\", TurboMountController);\n\n document.addEventListener(\"turbo:before-morph-element\", (event) => {\n const turboMorphEvent = event as unknown as TurboMorphEvent;\n const { target, detail } = turboMorphEvent;\n\n if (target.getAttribute(\"data-controller\")?.includes(\"turbo-mount\")) {\n target.setAttribute(\n \"data-turbo-mount-props-value\",\n detail.newElement.getAttribute(\"data-turbo-mount-props-value\") ||\n \"{}\",\n );\n event.preventDefault();\n }\n });\n }\n\n register<T>(\n plugin: Plugin<T>,\n name: string,\n component: T,\n controller?: ControllerConstructor,\n ) {\n controller ||= TurboMountController;\n if (this.components.has(name)) {\n throw new Error(`Component '${name}' is already registered.`);\n }\n this.components.set(name, { component, plugin });\n\n if (controller) {\n const controllerName = `turbo-mount-${camelToKebabCase(name).replace(\"/\", \"--\")}`;\n this.application.register(controllerName, controller);\n }\n }\n\n resolve(name: string) {\n const component = this.components.get(name);\n if (!component) {\n throw new Error(`Unknown component: ${name}`);\n }\n return component;\n }\n\n private findOrStartApplication(hydratedApp?: Application) {\n let application = hydratedApp || window.Stimulus;\n\n if (!application) {\n application = Application.start();\n window.Stimulus = application;\n }\n return application as ApplicationWithTurboMount;\n }\n}\n\nexport function buildRegisterFunction<T>(plugin: Plugin<T>) {\n return (\n turboMount: TurboMount,\n name: string,\n component: T,\n controller?: ControllerConstructor,\n ) => {\n turboMount.register(plugin, name, component, controller);\n };\n}\n","import { Definition } from \"@hotwired/stimulus\";\n\nimport { TurboMount, Plugin } from \"./turbo-mount\";\nimport { camelToKebabCase } from \"./helpers\";\n\nexport type ComponentModule = { default: never } | never;\n\nexport type ComponentDefinition = {\n filename: string;\n module: ComponentModule;\n};\n\ntype RegisterComponentsProps<T> = {\n plugin: Plugin<T>;\n turboMount: TurboMount;\n components: ComponentDefinition[];\n controllers?: Definition[];\n};\n\nconst identifierNames = (name: string) => {\n const controllerName = camelToKebabCase(name).replace(\"/\", \"--\");\n\n return [`turbo-mount--${controllerName}`, `turbo-mount-${controllerName}`];\n};\n\nexport const registerComponentsBase = <T>({\n plugin,\n turboMount,\n components,\n controllers,\n}: RegisterComponentsProps<T>) => {\n const controllerModules = controllers ?? [];\n\n for (const { module, filename } of components) {\n const name = filename\n .replace(/\\.\\w*$/, \"\")\n .replace(/^[./]*components\\//, \"\");\n\n const identifiers = identifierNames(name);\n\n const controller = controllerModules.find(({ identifier }) =>\n identifiers.includes(identifier),\n );\n const component = module.default ?? module;\n\n if (controller) {\n turboMount.register(\n plugin,\n name,\n component,\n controller.controllerConstructor,\n );\n } else {\n turboMount.register(plugin, name, component);\n }\n }\n};\n"],"names":["TurboMountController","Controller","constructor","this","skipPropsChangeCallback","connect","_umountComponentCallback","mountComponent","mountElement","resolvedComponent","componentProps","disconnect","umountComponent","propsValueChanged","propsValue","hasMountTarget","mountTarget","element","resolveMounted","componentValue","component","resolvedPlugin","plugin","undefined","el","Component","props","application","turboMount","resolve","setComponentProps","values","Object","String","targets","camelToKebabCase","str","replace","toLowerCase","TurboMount","components","Map","findOrStartApplication","register","document","addEventListener","event","turboMorphEvent","target","detail","getAttribute","includes","setAttribute","newElement","preventDefault","name","controller","has","Error","set","controllerName","get","hydratedApp","window","Stimulus","Application","start","buildRegisterFunction","identifierNames","registerComponentsBase","controllers","controllerModules","module","filename","identifiers","find","identifier","default","controllerConstructor"],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ react:
2
+ pins: "react react-dom react-dom/client"
3
+ npm_packages: "react react-dom"
4
+ vite_plugin: "@vitejs/plugin-react"
5
+ extension: "jsx"
6
+
7
+ vue:
8
+ pins: "vue"
9
+ npm_packages: "vue"
10
+ vite_plugin: "@vitejs/plugin-vue"
11
+ extension: "vue"
12
+
13
+ svelte4:
14
+ pins: "svelte"
15
+ npm_packages: "svelte@4"
16
+ vite_plugin: "@sveltejs/vite-plugin-svelte"
17
+ extension: "svelte"
18
+
19
+ svelte:
20
+ pins: "svelte"
21
+ npm_packages: "svelte@5"
22
+ vite_plugin: "@sveltejs/vite-plugin-svelte"
23
+ extension: "svelte"
@@ -0,0 +1,61 @@
1
+ module TurboMount
2
+ module Generators
3
+ module Helpers
4
+ ### FS Helpers
5
+ def js_destination_path
6
+ return ViteRuby.config.source_code_dir if defined?(ViteRuby)
7
+
8
+ if file?("config/vite.json")
9
+ source_code_dir = JSON.parse(File.read(file_path("config/vite.json"))).dig("all", "sourceCodeDir")
10
+ return source_code_dir || "app/frontend"
11
+ end
12
+
13
+ "app/javascript"
14
+ end
15
+
16
+ def js_destination_root
17
+ file_path(js_destination_path)
18
+ end
19
+
20
+ def js_entrypoint
21
+ if vite?
22
+ js_file_path "entrypoints/application.js"
23
+ else
24
+ js_file_path "application.js"
25
+ end
26
+ end
27
+
28
+ def js_file_path(*relative_path)
29
+ File.join(js_destination_root, *relative_path)
30
+ end
31
+
32
+ def file?(*relative_path)
33
+ File.file?(file_path(*relative_path))
34
+ end
35
+
36
+ def file_path(*relative_path)
37
+ File.join(destination_root, *relative_path)
38
+ end
39
+
40
+ def vite?
41
+ file?("config/vite.json") && Dir.glob(file_path("vite.config.*")).any?
42
+ end
43
+
44
+ # Interactivity Helpers
45
+ def ask(*)
46
+ unless options[:interactive]
47
+ say_error "Specify all options when running the generator non-interactively.", :red
48
+ exit(1)
49
+ end
50
+
51
+ super
52
+ end
53
+
54
+ def yes?(*)
55
+ return false unless options[:interactive]
56
+
57
+ super
58
+ end
59
+ end
60
+ end
61
+ end
@@ -13,6 +13,6 @@ const turboMount = new TurboMount();
13
13
  // If you want to automatically register components use:
14
14
  // import { registerComponents } from "turbo-mount/registerComponents/<%= framework %>";
15
15
  // const controllers = import.meta.glob("/controllers/**/*_controller.js", { eager: true });
16
- // const components = import.meta.glob("/components/**/*.jsx", { eager: true });
16
+ // const components = import.meta.glob("/components/**/*.<%= extension %>", { eager: true });
17
17
  // registerComponents({ turboMount, components, controllers });
18
18
  <%- end -%>
@@ -1,37 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "yaml"
4
+ require "rails/generators"
5
+ require "rails/generators/base"
6
+
7
+ require_relative "helpers"
8
+ require_relative "js_package_manager"
9
+
3
10
  module TurboMount
4
11
  module Generators
5
12
  class InstallGenerator < Rails::Generators::Base
6
- 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
13
+ include Helpers
14
+
15
+ FRAMEWORKS = YAML.load_file(File.expand_path("./frameworks.yml", __dir__))
23
16
 
24
17
  source_root File.expand_path("install", __dir__)
25
18
 
19
+ class_option :framework, type: :string,
20
+ desc: "The framework you want to use with Turbo Mount",
21
+ enum: FRAMEWORKS.keys,
22
+ default: nil
23
+
24
+ class_option :package_manager, type: :string, default: nil,
25
+ enum: JSPackageManager.package_managers,
26
+ desc: "The package manager you want to use to install Turbo Mount"
27
+
28
+ class_option :verbose, type: :boolean, default: false,
29
+ desc: "Run the generator in verbose mode"
30
+
26
31
  def install
27
32
  say "Installing Turbo Mount"
28
33
 
29
- 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
34
+ package_manager.validate!
33
35
 
34
- if importmap?
36
+ if package_manager.importmap?
35
37
  install_importmap
36
38
  else
37
39
  install_nodejs
@@ -43,19 +45,10 @@ module TurboMount
43
45
  private
44
46
 
45
47
  def install_nodejs
46
- case build_tool
47
- when "npm"
48
- run "npm install turbo-mount #{FRAMEWORKS[framework][:npm_packages]}"
49
- when "pnpm"
50
- run "pnpm install turbo-mount #{FRAMEWORKS[framework][:npm_packages]}"
51
- when "yarn"
52
- run "yarn add turbo-mount #{FRAMEWORKS[framework][:npm_packages]}"
53
- when "bun"
54
- run "bun add turbo-mount #{FRAMEWORKS[framework][:npm_packages]}"
55
- end
48
+ package_manager.add_dependencies("turbo-mount", FRAMEWORKS[framework][:npm_packages])
56
49
 
57
50
  say "Creating Turbo Mount initializer"
58
- template "turbo-mount.js", File.join("app/javascript/turbo-mount.js")
51
+ template "turbo-mount.js", js_file_path("turbo-mount.js")
59
52
  begin
60
53
  append_to_file js_entrypoint, %(import "./turbo-mount"\n)
61
54
  rescue
@@ -66,7 +59,7 @@ module TurboMount
66
59
 
67
60
  def install_importmap
68
61
  say "Creating Turbo Mount initializer"
69
- template "turbo-mount.js", File.join("app/javascript/turbo-mount-initializer.js")
62
+ template "turbo-mount.js", js_file_path("turbo-mount-initializer.js")
70
63
  append_to_file "app/javascript/application.js", %(import "turbo-mount-initializer"\n)
71
64
 
72
65
  say "Pinning Turbo Mount to the importmap"
@@ -75,53 +68,23 @@ module TurboMount
75
68
  append_to_file "config/importmap.rb", %(pin "turbo-mount-initializer"\n)
76
69
 
77
70
  say "Pinning framework dependencies to the importmap"
78
- run "bin/importmap pin #{FRAMEWORKS[framework][:pins]}"
79
- end
80
-
81
- def js_entrypoint
82
- if vite?
83
- "app/javascript/entrypoints/application.js"
84
- else
85
- "app/javascript/application.js"
86
- end
87
- end
88
-
89
- def vite?
90
- Dir.glob(Rails.root.join("vite.config.*")).any?
91
- end
92
-
93
- def importmap?
94
- build_tool == "importmap"
71
+ package_manager.add_dependencies(FRAMEWORKS[framework][:pins])
95
72
  end
96
73
 
97
74
  def warn_about_vite_plugin
98
75
  say "Make sure to install and add #{FRAMEWORKS[framework][:vite_plugin]} to your Vite config", :yellow
99
76
  end
100
77
 
101
- def build_tool
102
- return @build_tool if defined?(@build_tool)
103
-
104
- @build_tool = detect_build_tool
78
+ def package_manager
79
+ @package_manager ||= JSPackageManager.new(self)
105
80
  end
106
81
 
107
- def detect_build_tool
108
- if Rails.root.join("package.json").exist?
109
- if Rails.root.join("package-lock.json").exist?
110
- "npm"
111
- elsif Rails.root.join("pnpm-lock.yaml").exist?
112
- "pnpm"
113
- elsif Rails.root.join("bun.config.js").exist?
114
- "bun"
115
- else
116
- "yarn"
117
- end
118
- elsif Rails.root.join("config/importmap.rb").exist?
119
- "importmap"
120
- end
82
+ def extension
83
+ FRAMEWORKS[framework][:extension]
121
84
  end
122
85
 
123
86
  def framework
124
- @framework ||= ask("What framework do you want to use with Turbo Mount?", limited_to: FRAMEWORKS.keys, default: "react")
87
+ @framework ||= options[:framework] || ask("What framework do you want to use with Turbo Mount?", :green, limited_to: FRAMEWORKS.keys, default: "react")
125
88
  end
126
89
  end
127
90
  end
@@ -0,0 +1,57 @@
1
+ module TurboMount
2
+ module Generators
3
+ class JSPackageManager
4
+ def self.package_managers
5
+ %w[npm yarn bun pnpm importmap]
6
+ end
7
+
8
+ def initialize(generator)
9
+ @generator = generator
10
+ @package_manager = generator.options[:package_manager] || detect_package_manager
11
+ end
12
+
13
+ def validate!
14
+ return if @package_manager.present?
15
+
16
+ @generator.say "Could not find a package.json or config/importmap.rb file to add the turbo-mount dependency to, please add it manually.", :red
17
+ exit(1)
18
+ end
19
+
20
+ def importmap?
21
+ @package_manager == "importmap"
22
+ end
23
+
24
+ def add_dependencies(*dependencies)
25
+ cmd =
26
+ if importmap?
27
+ "bin/importmap pin #{dependencies.join(" ")}"
28
+ else
29
+ "#{@package_manager} add #{dependencies.join(" ")}#{@generator.options[:verbose] ? "" : " --silent"}"
30
+ end
31
+ @generator.in_root { @generator.run cmd }
32
+ end
33
+
34
+ private
35
+
36
+ def detect_package_manager
37
+ if file?("package.json")
38
+ if file?("package-lock.json")
39
+ "npm"
40
+ elsif file?("pnpm-lock.yaml")
41
+ "pnpm"
42
+ elsif file?("bun.lockb")
43
+ "bun"
44
+ else
45
+ "yarn"
46
+ end
47
+ elsif file?("config/importmap.rb")
48
+ "importmap"
49
+ end
50
+ end
51
+
52
+ def file?(*relative_path)
53
+ @generator.file?(*relative_path)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Turbo
4
4
  module Mount
5
- VERSION = "0.3.3"
5
+ VERSION = "0.4.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.3.3
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Svyatoslav Kryukov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-09-24 00:00:00.000000000 Z
11
+ date: 2024-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -44,11 +44,17 @@ files:
44
44
  - app/assets/javascripts/turbo-mount/svelte.js
45
45
  - app/assets/javascripts/turbo-mount/svelte.min.js
46
46
  - app/assets/javascripts/turbo-mount/svelte.min.js.map
47
+ - app/assets/javascripts/turbo-mount/svelte4.js
48
+ - app/assets/javascripts/turbo-mount/svelte4.min.js
49
+ - app/assets/javascripts/turbo-mount/svelte4.min.js.map
47
50
  - app/assets/javascripts/turbo-mount/vue.js
48
51
  - app/assets/javascripts/turbo-mount/vue.min.js
49
52
  - app/assets/javascripts/turbo-mount/vue.min.js.map
53
+ - lib/generators/turbo_mount/frameworks.yml
54
+ - lib/generators/turbo_mount/helpers.rb
50
55
  - lib/generators/turbo_mount/install/turbo-mount.js.tt
51
56
  - lib/generators/turbo_mount/install_generator.rb
57
+ - lib/generators/turbo_mount/js_package_manager.rb
52
58
  - lib/turbo/mount.rb
53
59
  - lib/turbo/mount/engine.rb
54
60
  - lib/turbo/mount/helpers.rb