| name | Custom event triggers | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| overview | Allow any DOM event name to be used as a `trigger` in Interact, routing unknown trigger names through the existing `eventTrigger` handler. This requires opening up the `TriggerType` union, adding a handler resolution function, and updating add/remove/types accordingly. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| todos |
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| isProject | false |
Currently, triggers flow through a static handler map:
flowchart LR
Config["InteractConfig\n(trigger: TriggerType)"] --> AddTS["add.ts\naddInteraction()"]
AddTS --> HandlerMap["TRIGGER_TO_HANDLER_MODULE_MAP\n(static object)"]
HandlerMap --> ViewEnter["viewEnter handler"]
HandlerMap --> EventTrigger["eventTrigger handler"]
HandlerMap --> ViewProgress["viewProgress handler"]
HandlerMap --> PointerMove["pointerMove handler"]
HandlerMap --> AnimEnd["animationEnd handler"]
EventTrigger --> Presets["EVENT_TRIGGER_PRESETS\nhover/click/activate/interest"]
The key insight: hover, click, activate, and interest are all thin wrappers around eventTrigger.ts. They just inject a preset event config (e.g., hover -> { enter: ['mouseenter'], leave: ['mouseleave'] }). For custom triggers, we do the same -- the trigger name itself becomes the event config.
After the change, unknown trigger names will be automatically routed through eventTrigger using the trigger name as the DOM event:
flowchart LR
Config["trigger: 'dblclick'"] --> AddTS["add.ts\naddInteraction()"]
AddTS --> GetHandler["getHandlerForTrigger()"]
GetHandler -->|known| HandlerMap["Static map\n(viewEnter, hover, etc.)"]
GetHandler -->|unknown| DynHandler["Dynamic handler\neventConfig = trigger name"]
DynHandler --> EventTrigger["eventTrigger.add()"]
Rename across the entire codebase:
StateParams->StateTriggerParamsPointerTriggerParams->AnimationTriggerParams
Files affected (source):
src/types.ts-- type definitions,EventTriggerParams,TriggerParams,InteractionParamsTypes,IInteractElement.toggleEffectsrc/handlers/index.ts-- import andwithEventTriggerConfigsignaturesrc/handlers/eventTrigger.ts-- import and cast usages inaddEventTriggerHandlersrc/handlers/effectHandlers.ts-- import and function signaturessrc/web/InteractElement.ts-- import fortoggleEffectmethodsrc/core/InteractionController.ts-- import fortoggleEffectmethod
Files affected (rules/docs):
rules/full-lean.md-- references in params documentationrules/click.md-- references toStateParams.methoddocs/api/types.md-- type documentationdocs/api/interaction-controller.md--toggleEffectparam typedocs/api/README.md-- type references
Use replace_all for each rename. This is a straightforward find-and-replace with no logic changes.
File: src/types.ts
- Open up
TriggerTypeto accept any string while preserving IDE autocomplete for known types:
export type TriggerType =
| 'hover'
| 'click'
| 'viewEnter'
| 'pageVisible'
| 'animationEnd'
| 'viewProgress'
| 'pointerMove'
| 'activate'
| 'interest'
| (string & {});TriggerParamsstays the same -- noEventTriggerParamsadded.eventConfigremains internal-only and is never exposed in the public config API. Custom event triggers useStateTriggerParams | AnimationTriggerParams(same asclick/hover).- Add a string index signature to
InteractionParamsTypes(line 252) so that custom trigger keys resolve properly in generics:
export type InteractionParamsTypes = {
hover: StateTriggerParams | AnimationTriggerParams;
click: StateTriggerParams | AnimationTriggerParams;
// ... existing entries ...
[key: string]:
| StateTriggerParams
| AnimationTriggerParams
| ViewEnterParams
| PointerMoveParams
| AnimationEndParams;
};File: src/handlers/index.ts
Export a getHandlerForTrigger(trigger) function that:
- Returns the existing handler module for known triggers (hover, click, viewEnter, etc.)
- For unknown triggers, returns a dynamically created handler that internally injects the trigger name as
eventConfig(the internal property) intoeventTrigger.add(). The user never sees or provideseventConfig-- it is derived from the trigger name. - All dynamic handlers share
eventTrigger.removefor cleanup
const KNOWN_HANDLERS = {
/* existing map */
};
export function getHandlerForTrigger(trigger: string) {
if (trigger in KNOWN_HANDLERS) {
return KNOWN_HANDLERS[trigger as TriggerType];
}
return {
add: (source, target, effect, options, interactOptions) => {
// eventConfig is internal-only -- derived from the trigger name, never from user params
eventTrigger.add(
source,
target,
effect,
{ ...options, eventConfig: trigger },
interactOptions ?? {},
);
},
remove: eventTrigger.remove,
};
}
export default KNOWN_HANDLERS;File: src/core/add.ts
- Import
getHandlerForTriggerfrom../handlers - In
addInteraction()(line 715), replace:TRIGGER_TO_HANDLER_MODULE_MAP[trigger]?.add(...)withgetHandlerForTrigger(trigger).add(...) - In
_processSequences()(line 446) and_processSequencesForTarget()(line 536), apply the same replacement for the sequence handler dispatch
The removeListItems function iterates Object.values(TRIGGER_TO_HANDLER_MODULE_MAP) and calls module.remove(element). Since eventTrigger.remove is already among these values (referenced by hover, click, activate, interest), and since eventTrigger uses a single WeakMap<HTMLElement, Set<HandlerObject>> for all event-based handlers, calling eventTrigger.remove once cleans up ALL event handlers for that element -- including any custom triggers. No changes needed.
File: test/eventTrigger.spec.ts (new)
Add tests covering:
- Custom trigger name (e.g.,
trigger: 'dblclick') routes througheventTriggerand attaches the correct DOM listener - Custom trigger with
togglemode (StateTriggerParamsmethod: 'toggle') - Custom trigger with
oncemode (AnimationTriggerParamstype: 'once') - Cleanup of custom trigger handlers via
remove() - Existing built-in triggers still work as before (regression)
Use the same mock patterns from existing tests (mock @wix/motion, create DOM elements, test event dispatch).
File: apps/demo/src/react/components/CustomEventDemo.tsx (new)
Create a demo component showcasing custom event triggers:
- A text input that responds to
inputevent with a pulse animation - A card that responds to
dblclickwith a flip animation - A card that responds to
contextmenu(right-click) with a shake animation - Controls to switch between different custom event types
- Follow the same pattern as
Playground.tsx:useMemofor config,useInteractInstance(config),<Interaction>components
File: apps/demo/src/react/App.tsx
- Import and add
<CustomEventDemo />to the page layout
File: rules/custom-event.md (new)
Add a succinct rule file for custom event triggers:
- Explain that any DOM event name can be used as a trigger
- Show the basic pattern:
trigger: 'dblclick'withStateTriggerParamsorAnimationTriggerParams(same params as click/hover) - 2-3 concise examples
File: rules/full-lean.md
- Update the
trigger: TriggerTypesection to mention that any DOM event name is accepted as a custom trigger - Note that custom triggers use the same params as click/hover (
StateTriggerParams | AnimationTriggerParams)
File: docs/guides/understanding-triggers.md
- Add a new section "Custom Event Triggers" explaining arbitrary DOM events as triggers
- Update the trigger overview table to include custom event triggers
- Add examples for
dblclick,input,contextmenuusingStateTriggerParams/AnimationTriggerParams
File: docs/api/types.md
- Update
TriggerTypedocumentation to reflect it now accepts any string - Note that custom triggers accept
StateTriggerParams | AnimationTriggerParams(same as click/hover)