Skip to main content
Glama

Cloudflare Remix Vite MCP

by kentcdodds
remix-packages-documentation.md18.8 kB
# @remix-run/events and @remix-run/dom Documentation NOTE: this is unofficial and was generated by Cursor from within the Remix Jam demo using the following prompt: > Please use the example code in @public/ and the types definitions to write documentation for @remix-run/events and @remix-run/dom and stick it in a markdown file in the root of this repo. This documentation covers two core Remix 3 packages: `@remix-run/events` for declarative event handling and `@remix-run/dom` for creating reactive UI components. ## Table of Contents - [@remix-run/events](#remix-runevents) - [Core API](#core-api) - [Target Helpers](#target-helpers) - [Custom Event Types](#custom-event-types) - [Interactions](#interactions) - [Built-in Interactions](#built-in-interactions) - [@remix-run/dom](#remix-rundom) - [Component System](#component-system) - [Root and Rendering](#root-and-rendering) - [Element References](#element-references) --- ## @remix-run/events A declarative event handling library that provides a clean, composable API for managing event listeners across any `EventTarget`. ### Core API #### `events(target, descriptors)` Adds events to a target and returns a cleanup function. This is the primary way to attach event listeners. ```ts import { events, dom } from "@remix-run/events"; let cleanup = events(target, [ dom.click(event => { console.log(event.target); }) ]); // Later: cleanup all event listeners cleanup(); ``` **With EventTarget objects:** ```ts import { events } from "@remix-run/events"; let drummer = new Drummer(80); events(drummer, [ Drummer.change(() => this.update()), Drummer.kick(() => { console.log('kick!'); }) ]); ``` **On document:** ```ts import { events } from "@remix-run/events"; import { space, arrowUp, arrowDown } from "@remix-run/events/key"; events(document, [ space(() => { drummer.toggle(); }), arrowUp(() => { drummer.setTempo(drummer.bpm + 1); }), arrowDown(() => { drummer.setTempo(drummer.bpm - 1); }), ]); ``` #### `events(target)` Creates an event container that allows dynamic event management. ```ts let container = events(target); container.on([ dom.click(event => { console.log("first handler"); }) ]); // Change events dynamically container.on([ dom.mouseover(event => { console.log("new handler"); }) ]); // Clean up all events container.cleanup(); ``` #### `bind(type, handler, options?)` Attaches a raw string event to a target. Particularly useful for custom elements and web components. ```ts import { events, bind } from "@remix-run/events"; events(target, [ bind("custom-event", event => { console.log(event.target); }) ]); ``` **Type Signature:** ```ts function bind<E extends Event = Event, ECurrentTarget = any, ETarget = any>( type: string, handler: EventHandler<E, ECurrentTarget, ETarget>, options?: AddEventListenerOptions ): EventDescriptor<ECurrentTarget> ``` ### Target Helpers Pre-configured target proxies that provide type-safe access to native DOM events. #### `dom` - HTMLElement Events ```ts import { events, dom } from "@remix-run/events"; events(element, [ dom.click(event => { /* ... */ }), dom.mouseover(event => { /* ... */ }), dom.pointermove(event => { /* ... */ }), // All HTMLElementEventMap events available ]); ``` #### `win` - Window Events ```ts import { events, win } from "@remix-run/events"; events(window, [ win.resize(event => { /* ... */ }), win.scroll(event => { /* ... */ }), ]); ``` #### `doc` - Document Events ```ts import { events, doc } from "@remix-run/events"; events(document, [ doc.DOMContentLoaded(event => { /* ... */ }), doc.visibilitychange(event => { /* ... */ }), ]); ``` #### `xhr` - XMLHttpRequest Events ```ts import { events, xhr } from "@remix-run/events"; let request = new XMLHttpRequest(); events(request, [ xhr.load(event => { /* ... */ }), xhr.error(event => { /* ... */ }), ]); ``` #### `ws` - WebSocket Events ```ts import { events, ws } from "@remix-run/events"; let socket = new WebSocket('ws://localhost:8080'); events(socket, [ ws.message(event => { /* ... */ }), ws.error(event => { /* ... */ }), ]); ``` ### Custom Event Types #### `createEventType(eventName)` Creates a pair of functions: an event binder and an event creator. This is perfect for creating type-safe custom events on `EventTarget` subclasses. ```ts import { createEventType } from '@remix-run/events'; // Create event types let [kick, createKick] = createEventType('drum:kick'); let [snare, createSnare] = createEventType('drum:snare'); let [tempoChange, createTempoChange] = createEventType<number>('drum:tempo-change'); export class Drummer extends EventTarget { // Export as static methods for easy access static kick = kick; static snare = snare; static tempoChange = tempoChange; playKick() { // Dispatch the event this.dispatchEvent(createKick()); } setTempo(bpm: number) { // Dispatch with detail this.dispatchEvent(createTempoChange({ detail: bpm })); } } // Usage let drummer = new Drummer(); events(drummer, [ Drummer.kick(() => { console.log('kick!'); }), Drummer.tempoChange((event) => { console.log('tempo changed to', event.detail); }) ]); ``` **Return Value:** ```ts [ // Event binder function <ECurrentTarget extends EventTarget = EventTarget>( handler: EventHandler<CustomEvent<Detail>, ECurrentTarget>, options?: AddEventListenerOptions ) => EventDescriptor<ECurrentTarget>, // Event creator function (...args: [init?: CustomEventInit<Detail>]) => CustomEvent<Detail> ] ``` ### Interactions Interactions are higher-level event patterns that combine multiple low-level events into a single semantic event. #### `createInteraction(eventName, factory)` Creates a custom interaction that encapsulates complex event patterns. ```ts import { createInteraction, events } from "@remix-run/events"; import { press } from "@remix-run/events/press"; let tempoTap = createInteraction<HTMLElement, number>( "tempo-tap", ({ target, dispatch }) => { let taps: number[] = []; let minTaps = 4; let maxInterval = 2000; let resetTimer: number; let handleTap = () => { let now = Date.now(); clearTimeout(resetTimer); taps.push(now); taps = taps.filter(tap => now - tap < maxInterval); if (taps.length >= minTaps) { let intervals = []; for (let i = 1; i < taps.length; i++) { intervals.push(taps[i] - taps[i - 1]); } let bpms = intervals.map(interval => 60000 / interval); let avgBpm = Math.round( bpms.reduce((sum, value) => sum + value, 0) / bpms.length, ); dispatch({ detail: avgBpm }); } resetTimer = window.setTimeout(() => { taps = []; }, maxInterval); }; // Return cleanup function(s) return events(target, [press(handleTap)]); }, ); // Usage <Button on={[ tempoTap(event => { drummer.play(event.detail); }), ]} > SET TEMPO </Button> ``` **Factory Context:** ```ts { dispatch: (options?: CustomEventInit<Detail>, originalEvent?: Event) => void; target: Target; } ``` **Factory Return:** `Cleanup | Cleanup[] | void` ### Built-in Interactions #### Press Interactions High-level pointer and keyboard interactions for button-like elements. ##### `press(handler, options?)` A complete press interaction that handles both pointer and keyboard activation (Space/Enter keys). ```ts import { press } from "@remix-run/events/press"; <Button on={[ press(event => { console.log('Button pressed!'); console.log('Input type:', event.detail.inputType); // 'pointer' | 'keyboard' console.log('Original event:', event.detail.originalEvent); }) ]} > PLAY </Button> ``` **Options:** ```ts interface PressOptions { hit?: number; // Hit detection threshold (default varies) release?: number; // Release threshold (default varies) delay?: number; // Long press delay (default varies) } ``` **Event Detail:** ```ts type PressEventDetail = { originalEvent: PointerEvent; target: Element; inputType: 'pointer'; } | { originalEvent: KeyboardEvent; target: Element; inputType: 'keyboard'; } ``` ##### `pressDown(handler, options?)` Fires when the press starts (pointer down or key down). ```ts <Button on={[pressDown(event => { /* ... */ })]}> Press Me </Button> ``` - Sets `rmx-active="true"` attribute on the target during press ##### `pressUp(handler, options?)` Fires when the press ends (pointer up or key up). ```ts <Button on={[pressUp(event => { /* ... */ })]}> Press Me </Button> ``` - Removes `rmx-active` attribute from the target ##### `longPress(handler, options?)` Fires after holding the press for a specified duration. ```ts <Button on={[longPress(event => { /* ... */ }, { delay: 500 })]}> Hold Me </Button> ``` ##### `outerPress(handler)` / `outerPressDown(handler)` / `outerPressUp(handler)` Detects presses outside the target element. Useful for closing modals, dropdowns, etc. ```ts import { outerPress } from "@remix-run/events/press"; <Modal on={[outerPress(() => closeModal())]}> {/* Modal content */} </Modal> ``` **Outer Press Event Detail:** ```ts interface OuterPressEventDetail { originalEvent: PointerEvent; } ``` #### Key Interactions Keyboard interactions that automatically prevent default browser behavior and follow WAI-ARIA practices. All key interactions come from `@remix-run/events/key`: ```ts import { space, enter, escape, arrowUp, arrowDown, arrowLeft, arrowRight, home, end, pageUp, pageDown, tab, backspace, del } from "@remix-run/events/key"; ``` **Example Usage:** ```ts events(document, [ space(() => { drummer.toggle(); }), arrowUp(() => { drummer.setTempo(drummer.bpm + 1); }), arrowDown(() => { drummer.setTempo(drummer.bpm - 1); }), ]); ``` **Available Key Interactions:** - `space` - Space key (useful for triggering actions) - `enter` - Enter key (useful for submitting forms, selecting items) - `escape` - Escape key (useful for closing modals/menus) - `arrowUp` / `arrowDown` / `arrowLeft` / `arrowRight` - Arrow keys - `home` / `end` - Move to first/last item - `pageUp` / `pageDown` - Page navigation - `tab` - Tab key - `backspace` / `del` - Deletion keys **Event Detail:** ```ts type KeyInteractionEvent = CustomEvent<{ originalEvent: KeyboardEvent; }> ``` ##### `createKeyInteraction(key)` Create a custom key interaction for any key: ```ts import { createKeyInteraction } from "@remix-run/events/key"; let ctrlS = createKeyInteraction('s'); events(document, [ ctrlS(event => { if (event.detail.originalEvent.ctrlKey) { // Save document } }) ]); ``` --- ## @remix-run/dom A reactive component system that provides efficient DOM rendering and updates. ### Component System Components in Remix 3 are defined as functions with `this: Remix.Handle`. ```ts import type { Remix } from "@remix-run/dom"; function MyComponent(this: Remix.Handle) { // Setup code runs once let drummer = this.context.get(DrumMachine); events(drummer, [ Drummer.change(() => this.update()) ]); // Return render function return () => ( <div> <h1>BPM: {drummer.bpm}</h1> </div> ); } ``` **Key Features:** - Setup code runs once when component mounts - Return a render function that runs on each render - Access to lifecycle methods via `this.Handle` #### Component Types ```ts function DrumMachine(this: Remix.Handle<Drummer>) { // Generic type provides context type let drummer = new Drummer(80); this.context.set(drummer); return () => <Layout><Equalizer /></Layout>; } function Equalizer(this: Remix.Handle) { // Access parent context let drummer = this.context.get(DrumMachine); return () => <div>{drummer.bpm}</div>; } ``` ### Remix.Handle API #### `this.update()` Trigger an update of the component. ```ts events(drummer, [ Drummer.change(() => this.update()) ]); ``` #### `this.context.set(value)` Set a context value that child components can access. ```ts function Parent(this: Remix.Handle<MyType>) { let value = new MyType(); this.context.set(value); return () => <Child />; } ``` #### `this.context.get(Component)` Get a context value from a parent component. ```ts function Child(this: Remix.Handle) { let value = this.context.get(Parent); return () => <div>{value.data}</div>; } ``` #### `this.queueTask(callback)` Queue a task to run after the next update. ```ts this.queueTask(() => { // This runs after DOM updates element.focus(); }); ``` ### Root and Rendering #### `createRoot(element)` Creates a root for rendering your application. ```ts import { createRoot } from "@remix-run/dom"; createRoot(document.body).render(<DrumMachine />); ``` #### `createRangeRoot(range)` Creates a root from a DOM Range object for more precise insertion points. ```ts import { createRangeRoot } from "@remix-run/dom"; let range = document.createRange(); createRangeRoot(range).update(<App />); ``` ### Element References #### `connect(callback)` Get a reference to an element when it connects to the DOM. ```ts import { connect } from "@remix-run/dom"; function DrumControls(this: Remix.Handle) { let stopButton: HTMLButtonElement; let playButton: HTMLButtonElement; return () => ( <> <Button on={[ connect(event => (playButton = event.currentTarget)), press(() => { drummer.play(); this.queueTask(() => { stopButton.focus(); }); }), ]} > PLAY </Button> <Button on={[ connect(event => (stopButton = event.currentTarget)), press(() => { drummer.stop(); this.queueTask(() => { playButton.focus(); }); }), ]} > STOP </Button> </> ); } ``` #### `disconnect(callback)` Execute cleanup when an element is removed from the DOM. ```ts import { disconnect } from "@remix-run/dom"; <div on={[disconnect(() => { // Cleanup code })]}> Content </div> ``` ### Props Types #### `Remix.Props<K>` Get properly typed props for any HTML element or component. ```ts import type { Remix } from "@remix-run/dom"; export function Button({ children, ...rest }: Remix.Props<"button">) { return ( <button {...rest}> {children} </button> ); } interface TempoButtonProps extends Remix.Props<"button"> { orientation: "up" | "down"; } export function TempoButton({ orientation, css, ...rest }: TempoButtonProps) { return ( <button {...rest} css={{ ...css }}> <Triangle orientation={orientation} /> </button> ); } ``` #### `Remix.RemixNode` Type for Remix JSX children. ```ts export function Layout({ children }: { children: Remix.RemixNode }) { return ( <div> {children} </div> ); } ``` ### Special Props #### `on` Prop The `on` prop accepts an array of event descriptors and applies them to the element. ```ts <Button on={[ press(() => console.log('pressed')), dom.mouseover(() => console.log('hover')), ]} > Click Me </Button> ``` #### `css` Prop Inline styles with TypeScript support. ```ts <div css={{ display: "flex", background: "black", borderRadius: "24px", padding: "24px", "&:hover": { background: "#333", } }} > Content </div> ``` ### Complete Example Here's a complete example combining both packages: ```ts import { connect, createRoot, type Remix } from "@remix-run/dom"; import { events } from "@remix-run/events"; import { press } from "@remix-run/events/press"; import { space, arrowUp, arrowDown } from "@remix-run/events/key"; import { Drummer } from "./drummer"; function DrumMachine(this: Remix.Handle<Drummer>) { let drummer = new Drummer(80); // Listen to drummer events events(drummer, [ Drummer.change(() => this.update()) ]); // Add keyboard shortcuts events(document, [ space(() => drummer.toggle()), arrowUp(() => drummer.setTempo(drummer.bpm + 1)), arrowDown(() => drummer.setTempo(drummer.bpm - 1)), ]); this.context.set(drummer); return () => ( <div> <h1>BPM: {drummer.bpm}</h1> <Controls /> </div> ); } function Controls(this: Remix.Handle) { let drummer = this.context.get(DrumMachine); let playButton: HTMLButtonElement; return () => ( <> <button disabled={drummer.isPlaying} on={[ connect(event => (playButton = event.currentTarget)), press(() => drummer.play()) ]} > PLAY </button> <button disabled={!drummer.isPlaying} on={[press(() => drummer.stop())]} > STOP </button> </> ); } createRoot(document.body).update(<DrumMachine />); ``` --- ## Type Definitions ### Event Handler ```ts type EventHandler<E = Event, ECurrentTarget = any, ETarget = any> = ( event: EventWithTargets<E, ECurrentTarget, ETarget>, signal: AbortSignal ) => any | Promise<any>; ``` ### Event Descriptor ```ts interface EventDescriptor<ECurrentTarget = any> { type: string; handler: EventHandler<any, ECurrentTarget>; isCustom?: boolean; options?: AddEventListenerOptions; } ``` ### Event Container ```ts interface EventContainer { on: (events: EventDescriptor | EventDescriptor[] | undefined) => void; cleanup: () => void; } ``` ### Cleanup ```ts type Cleanup = () => void; ``` --- ## Best Practices 1. **Use interactions over raw DOM events** - Interactions like `press` handle both pointer and keyboard events automatically. 2. **Clean up event listeners** - Always store and call the cleanup function returned by `events()` when appropriate, though Remix components handle this automatically. 3. **Use context for shared state** - Pass data between components using `this.context.set()` and `this.context.get()`. 4. **Batch renders with queueTask** - Use `this.queueTask()` to run code after the next render, useful for focusing elements. 5. **Type your components** - Use `Remix.Handle<T>` to type your component's context. 6. **Prefer built-in key interactions** - Use pre-built key interactions like `space`, `enter`, etc. instead of raw keyboard event handlers for better accessibility. 7. **Create custom interactions for complex patterns** - When you have a complex event pattern that you reuse, create a custom interaction with `createInteraction`. 8. **Use createEventType for custom events** - When creating `EventTarget` subclasses, use `createEventType` to get type-safe event binding and dispatching.

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/kentcdodds/cloudflare-remix-vite-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server