@csedl/hotwire-svelte-helpers
v4.2.1
Published
Hotwire + Svelte helpers for Rails: Stimulus floating dropdowns/toolips + Svelte global panels/modals + RTurbo-friendly utilities. Build together with the rubygem svelte-on-rails and its npm-package.
Readme
Hotwire Svelte Helpers
Helpers for building overlays — such as dropdowns, tooltips, and modals — with Floating UI, Stimulus, and Svelte in a Hotwire/Turbo environment.
Includes flexible Svelte form components that work with the svelte-on-rails gem and can be imported directly from the package or copied into your project.
A small toolkit for building more interactive Rails apps with Hotwire and Svelte.
Links:
Setup
import { HotwireSvelteHelpers } from "@csedl/hotwire-svelte-helpers/setup"
HotwireSvelteHelpers.debug = true
HotwireSvelteHelpers.initializeOverlays()cleanMount()
Interaction with (Svelte) components can lead to orphaned instances. This can happen when the unmount event does not happen, for example when the underlaying DOM element disappears which can happen in a Hotwire Environemnt.
For this here is a double security. CleanMount() adds the instance to a global store and then executes mount().
import {cleanMount} from "@csedl/hotwire-svelte-helpers";
cleanMount(AnySvelteComponent, { target: cleanMountDemoTag, ...});Next, you must add something like
import { unmountAllDetached } from '@csedl/hotwire-svelte-helpers'
document.addEventListener('turbo:render', () => {
unmountAllDetached()
})to your application.js. This will check for detached instances and unmount them.
Dropdown Example
<div data-controller="csedl-dropdown" data-panel-id="dropdown-panel-3h5k7l4">
Button
</div>
<div id="dropdown-panel-3h5k7l4" class="dropdownSvelte-panel-example-class" style="display: none;">
... any content
</div>body#overlays-box Tag
Svelte dropdown panels are mounted into #overlays-box, a shared overlay container appended directly to document.body.
If #overlays-box does not exist, it is created automatically.
This keeps overlays at the root level to avoid most z-index and clipping issues,
while keeping the DOM cleaner than mounting panels directly into body.
This aligns to the behaviour of the stimulus side
Svelte Dropdowns
There are some default components included, which can be used to build Svelte Dropdowns. Check the online example app for more details.
What it does
- When the button is clicked, it toggles the
displaystyle on the panel and places the panel using floating-ui. - When the panel is open, the
has-open-panelclass is added to the button, otherwise it is removed. - Adds functionality to close all panels when clicking outside a panel.
- When a
data-srcattribute is given to the panel, on opening the panel, it fires a xhr request and replaces the content (fetched by selector:.contentwithin the panel-element) by the response. - This all works with stacked panels too (panel in panel).
Close on click outside
When a dropdownSvelte is open, it closes when clicking outside a panel.
This behaviour can be stopped by:
- The clicked element or its parent elements has the
data-dropdownSvelte-persist(not:data-dropdownSvelte-persist="false") attribute. - the event has the attribute
event.detail.dataDropdownPersistset to true.
Notes
Important: When creating or initializing the dropdown,
always call the initialize function before attaching a listener to the panel's close event.
The initialize function adds a close event listener to the panel that executes the onPanelClose function.
If your custom close function destroys the panel, this has to be done after the onPanelClose is fired by the close event.
Options
- If there is an element with ID
arrowinside the dropdownSvelte panel, it is treated as described on floating-ui. - The
data-placementattribute on the panel can be used to control positioning, see floating-ui/placements.
Events
Events on the button element:
place-panelplaces the panel, and, if present, the arrow element, byfloating-ui.
Events the panel element:
closecloses the panel.place-melike place-panel on the button.
Event Triggers on the button element:
before-open-panelafter-close-panel
Event Triggers on the panel element:
before-open
Helpers
If the panels are rendered to a different location than the button (see z-index on rails-app), within a scrollable (e.g.) container, the button would scroll away from the panel. For such cases, add this both data-attributes to the scrollable element:
<div data-controller="csedl-place-dropdownSvelte-panels" data-on="scroll" data-run-after="500" style="overflow: scroll;">
...
</div>Now, on scrolling, it searches for all dropdownSvelte-buttons (by class-name has-open-panel) and triggers the place-panel event there.
Options
data-on Attribute:
scrolltriggered byscrollEvent of the given element.resize-observertriggered by ResizeObserver on the given element.
data-run-after Attribute:
- Milliseconds as number.
This is only relevant if you have things like css transition enabled, so that after the above resize events are fired, subsequent events are needed. It will fire the place-panel after the last resize/scroll event within the given time.
Tip Turn console-debug-log on (see configs) and check how events are working.
Explanation
What these helpers mainly do is to find all the dropdowns by the has-open-panel class and fire the place-panel event. But within the helper, things like performance optimisation are done: it searches once and places the panels multiple times.
Tooltip
<span data-controller="csedl-tooltip" data-panel-id="tooltip-123" data-delay="0.2">
Text-with-tooltip
</span>
<div id="tooltip-123" class="tooltip-panel" style="display: none;">
<div id="arrow"></div>
... any content
</div>makes a tooltip.
It adds the class tooltip-is-visible to the tooltip label while the tooltip is visible.
data-src attribute is working similar to dropdownSvelte
Stimulus Usage
This package uses Stimulus unconventionally to initialize and toggle external dropdownSvelte or tooltip panels (via data-panel-id) rather than managing child elements within the controller’s scope, as is typical in Stimulus documentation.
The same result could be achieved using MutationObserver and plain JavaScript.
Stimulus was chosen because this package is intended for use with Hotwire/Turbo where Stimulus already is installed. It has a modest footprint of just 10 KB. It also holds many configs for MutationObserver, especially for our exact purpose.
License
MIT
