Skip to content

Commit

Permalink
Introduce new API
Browse files Browse the repository at this point in the history
  • Loading branch information
skryukov committed May 29, 2024
1 parent 368b856 commit 79c4166
Show file tree
Hide file tree
Showing 32 changed files with 265 additions and 321 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning].

## [Unreleased]

### Added

- [BREAKING] New API without controller inheritance. ([@skryukov])
To migrate to the new API:
- Replace `new TurboMountReact()` (or any other framework specific constructor) with `new TurboMount()`
- Replace `turboMount.register(...)` with `registerComponent(turboMount, ...)`

## [0.2.3] - 2024-05-12

### Added
Expand Down
77 changes: 20 additions & 57 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,69 +88,35 @@ Note: Importmap-only mode is quite limited in terms of JavaScript dependencies.

### Initialization

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.

#### Standard Initialization

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:

```js
import { Application } from "@hotwired/stimulus";
import { TurboMount } from "turbo-mount";
import plugin from "turbo-mount/react";
import { SketchPicker } from 'react-color';

const application = Application.start();
const turboMount = new TurboMount({ application, plugin });

turboMount.register('SketchPicker', SketchPicker);
```

#### Simplified Initialization

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:
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:

```js
import { TurboMount } from "turbo-mount";
import plugin from "turbo-mount/react";
import { SketchPicker } from 'react-color';
import { registerComponent } from "turbo-mount/react";
import { HexColorPicker } from 'react-colorful';

const turboMount = new TurboMount({ plugin });
const turboMount = new TurboMount(); // or new TurboMount({ application })

turboMount.register('SketchPicker', SketchPicker);
registerComponent(turboMount, "HexColorPicker", HexColorPicker);
```

#### Plugin-Specific Initialization

For a more streamlined setup, you can directly import a specialized version of `TurboMount`:

```js
import { TurboMountReact } from "turbo-mount/react";
import { SketchPicker } from 'react-color';

const turboMount = new TurboMountReact();

turboMount.register('SketchPicker', SketchPicker);
```
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.

### View Helpers

Use the following helpers to mount components in your views:

```erb
<%= turbo_mount_component("SketchPicker", framework: "react", props: {color: "#034"}) %>
<%# or using alias %>
<%= turbo_mount_react_component("SketchPicker", props: {color: "#430"}) %>
<%= turbo_mount("HexColorPicker", props: {color: "#034"}, class: "mb-5") %>
```

This will generate the following HTML:

```html
<div data-controller="turbo-mount-react-sketch-picker"
data-turbo-mount-react-sketch-picker-component-value="SketchPicker"
data-turbo-mount-react-sketch-picker-props-value="{&quot;color&quot;:&quot;#034&quot;}">
<div data-controller="turbo-mount-hex-color-picker"
data-turbo-mount-hex-color-picker-component-value="HexColorPicker"
data-turbo-mount-hex-color-picker-props-value="{&quot;color&quot;:&quot;#034&quot;}"
class="mb-5">
</div>
```

Expand All @@ -162,16 +128,16 @@ This will generate the following HTML:
- Vue: `"turbo-mount/vue"`
- Svelte: `"turbo-mount/svelte"`

To add support for other frameworks, create a custom controller class extending `TurboMountController` and provide a plugin. See included plugins for examples.
To add support for other frameworks, create a custom plugin. See included plugins for examples.

### Custom Controllers

To customize component behavior or pass functions as props, create a custom controller:

```js
import { TurboMountReactController } from "turbo-mount";
import { TurboMountController } from "turbo-mount";

export default class extends TurboMountReactController {
export default class extends TurboMountController {
get componentProps() {
return {
...this.propsValue,
Expand All @@ -180,17 +146,17 @@ export default class extends TurboMountReactController {
}

onChange = (color) => {
this.propsValue = { ...this.propsValue, color: color.hex };
this.propsValue = { ...this.propsValue, color: color };
};
}
```

Then pass this controller to the register method:
Then pass this controller to the `registerComponent` method:

```js
import SketchController from "controllers/turbo_mount/sketch_picker_controller";
import HexColorPickerController from "controllers/turbo_mount/hex_color_picker_controller";

turboMount.register('SketchPicker', SketchPicker, SketchController);
registerComponent(turboMount, "HexColorPicker", HexColorPicker, HexColorPickerController);
```

### Vite Integration
Expand All @@ -199,7 +165,7 @@ turboMount.register('SketchPicker', SketchPicker, SketchController);

```js
import { TurboMount } from "turbo-mount/react";
import { registerComponents } from "turbo-mount/vite";
import { registerComponents } from "turbo-mount/registerComponents/react";

const controllers = import.meta.glob("./**/*_controller.js", { eager: true });
const components = import.meta.glob("/components/**/*.jsx", { eager: true });
Expand All @@ -209,9 +175,6 @@ registerComponents({ turboMount, components, controllers });
```

The `registerComponents` helper searches for controllers in the following paths:
- `controllers/turbo-mount/${framework}/${controllerName}`
- `controllers/turbo-mount/${framework}-${controllerName}`
- `controllers/turbo-mount-${framework}-${controllerName}`
- `controllers/turbo-mount/${controllerName}`
- `controllers/turbo-mount-${controllerName}`

Expand All @@ -220,7 +183,7 @@ The `registerComponents` helper searches for controllers in the following paths:
To specify a non-root mount target, use the `data-<%= controller_name %>-target="mount"` attribute:

```erb
<%= turbo_mount_react_component("SketchPicker", props: {color: "#430"}) do |controller_name| %>
<%= turbo_mount("HexColorPicker", props: {color: "#430"}) do |controller_name| %>
<h3>Color picker</h3>
<div data-<%= controller_name %>-target="mount"></div>
<% end %>
Expand Down
57 changes: 31 additions & 26 deletions app/assets/javascripts/turbo-mount.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,21 @@ class TurboMountController extends Controller {
return this.hasMountTarget ? this.mountTarget : this.element;
}
get resolvedComponent() {
return this.resolveComponent(this.componentValue);
return this.resolveMounted(this.componentValue).component;
}
get resolvedPlugin() {
return this.resolveMounted(this.componentValue).plugin;
}
umountComponent() {
this._umountComponentCallback && this._umountComponentCallback();
this._umountComponentCallback = undefined;
}
resolveComponent(component) {
mountComponent(el, Component, props) {
return this.resolvedPlugin.mountComponent(el, Component, props);
}
resolveMounted(component) {
const app = this.application;
return app.turboMount[this.framework].resolve(component);
return app.turboMount.resolve(component);
}
}
TurboMountController.values = {
Expand All @@ -40,34 +46,20 @@ const camelToKebabCase = (str) => {
};

class TurboMount {
constructor({ application, plugin }) {
var _a;
constructor(props = {}) {
this.components = new Map();
this.application = this.findOrStartApplication(application);
this.framework = plugin.framework;
this.baseController = plugin.controller;
(_a = this.application).turboMount || (_a.turboMount = {});
this.application.turboMount[this.framework] = this;
if (this.baseController) {
this.application.register(`turbo-mount-${this.framework}`, this.baseController);
}
}
findOrStartApplication(hydratedApp) {
let application = hydratedApp || window.Stimulus;
if (!application) {
application = Application.start();
window.Stimulus = application;
}
return application;
this.application = this.findOrStartApplication(props.application);
this.application.turboMount = this;
this.application.register("turbo-mount", TurboMountController);
}
register(name, component, controller) {
controller || (controller = this.baseController);
register(plugin, name, component, controller) {
controller || (controller = TurboMountController);
if (this.components.has(name)) {
throw new Error(`Component '${name}' is already registered.`);
}
this.components.set(name, component);
this.components.set(name, { component, plugin });
if (controller) {
const controllerName = `turbo-mount-${this.framework}-${camelToKebabCase(name)}`;
const controllerName = `turbo-mount-${camelToKebabCase(name)}`;
this.application.register(controllerName, controller);
}
}
Expand All @@ -78,6 +70,19 @@ class TurboMount {
}
return component;
}
findOrStartApplication(hydratedApp) {
let application = hydratedApp || window.Stimulus;
if (!application) {
application = Application.start();
window.Stimulus = application;
}
return application;
}
}
function buildRegisterFunction(plugin) {
return (turboMount, name, component, controller) => {
turboMount.register(plugin, name, component, controller);
};
}

export { TurboMount, TurboMountController };
export { TurboMount, TurboMountController, buildRegisterFunction };
2 changes: 1 addition & 1 deletion app/assets/javascripts/turbo-mount.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 79c4166

Please sign in to comment.