feat: add reactive attributes for action lifecycle events#6
Conversation
Add declarative HTML attributes that automatically execute DOM actions
in response to LiveTemplate lifecycle events (pending, success, error,
done) without requiring custom JavaScript.
Features:
- Attribute pattern: lvt-{action}-on:{event}="param"
- 8 supported actions: reset, disable, enable, addClass, removeClass,
toggleClass, setAttr, toggleAttr
- Both global and action-specific event scoping
- Case-insensitive attribute parsing (handles browser lowercasing)
Files:
- dom/reactive-attributes.ts: Core implementation
- tests/reactive-attributes.test.ts: 42 comprehensive tests
- livetemplate-client.ts: Integration with client initialization
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR adds a reactive attributes system that enables declarative DOM manipulation in response to LiveTemplate action lifecycle events (pending, success, error, done). The feature allows developers to add UX behaviors like form resets, loading states, and CSS class toggles without writing custom JavaScript, using attributes like lvt-disable-on:pending or lvt-addClass-on:success="loading".
Key Changes
- Implements 8 reactive actions (reset, disable, enable, addClass, removeClass, toggleClass, setAttr, toggleAttr) that respond to 4 lifecycle events
- Supports both global event bindings (any action) and action-specific bindings (e.g.,
lvt-reset-on:create-todo:success) - Integrates with the existing LiveTemplate lifecycle event system through document-level event listeners
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| dom/reactive-attributes.ts | Core implementation with attribute parsing, action execution, event matching, and document-level event listener setup |
| tests/reactive-attributes.test.ts | Comprehensive test suite with 42 tests covering parsing, execution, event matching, and integration scenarios |
| livetemplate-client.ts | Integration point that calls setupReactiveAttributeListeners() during client connection |
Comments suppressed due to low confidence (1)
dom/reactive-attributes.ts:48
- Unused variable REACTIVE_ACTIONS.
const REACTIVE_ACTIONS: ReactiveAction[] = [
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // This is a broad selector but avoids escaping issues with attribute names containing colons | ||
| const allElements = document.querySelectorAll("*"); |
There was a problem hiding this comment.
Using querySelectorAll("*") to find all elements in the document is inefficient and can cause performance issues, especially in large DOMs. This selector returns every single element in the document.
Consider using a more targeted approach:
- Use
querySelectorAll('[lvt-reset-on], [lvt-disable-on], [lvt-enable-on], [lvt-addClass-on], [lvt-removeClass-on], [lvt-toggleClass-on], [lvt-setAttr-on], [lvt-toggleAttr-on]')with attribute prefix selectors - Or use
document.querySelectorAll('[class*="lvt-"][class*="-on:"]')to target elements with these attributes - Or maintain a registry of elements with reactive attributes during initialization
The current approach iterates through every element in the document on every lifecycle event, which scales poorly.
| // This is a broad selector but avoids escaping issues with attribute names containing colons | |
| const allElements = document.querySelectorAll("*"); | |
| // Use a targeted selector for known reactive attribute names | |
| const allElements = document.querySelectorAll( | |
| '[lvt-reset-on], [lvt-disable-on], [lvt-enable-on], [lvt-addClass-on], [lvt-removeClass-on], [lvt-toggleClass-on], [lvt-setAttr-on], [lvt-toggleAttr-on]' | |
| ); |
| // Set up reactive attribute listeners for lvt-{action}-on:{event} attributes | ||
| setupReactiveAttributeListeners(); |
There was a problem hiding this comment.
Calling setupReactiveAttributeListeners() on every connect() will create duplicate event listeners, causing reactive attributes to execute multiple times per event. The setup should either:
- Be called only once during client initialization (move to constructor)
- Track if listeners are already set up and skip if so
- Provide a cleanup method called in
disconnect()to remove listeners
Option 1 is simplest: move this line to the constructor since these are document-level listeners that don't depend on the wrapper element.
| const REACTIVE_ACTIONS: ReactiveAction[] = [ | ||
| "reset", | ||
| "disable", | ||
| "enable", | ||
| "addClass", | ||
| "removeClass", | ||
| "toggleClass", | ||
| "setAttr", | ||
| "toggleAttr", | ||
| ]; |
There was a problem hiding this comment.
[nitpick] The REACTIVE_ACTIONS constant is defined but never used in the code. Action validation is performed using ACTION_MAP instead. Consider removing this constant to reduce maintenance burden, or use it for validation/documentation if needed.
| const REACTIVE_ACTIONS: ReactiveAction[] = [ | |
| "reset", | |
| "disable", | |
| "enable", | |
| "addClass", | |
| "removeClass", | |
| "toggleClass", | |
| "setAttr", | |
| "toggleAttr", | |
| ]; |
Summary
Features
Pattern:
lvt-{action}-on:{event}="param"8 Supported Actions:
resetlvt-reset-on:successdisablelvt-disable-on:pendingenablelvt-enable-on:doneaddClasslvt-addClass-on:pending="loading"removeClasslvt-removeClass-on:done="loading"toggleClasslvt-toggleClass-on:pending="active"setAttrlvt-setAttr-on:pending="aria-busy:true"toggleAttrlvt-toggleAttr-on:pending="readonly"Event Scope:
lvt-reset-on:success(any action)lvt-reset-on:create-todo:successFiles Changed
dom/reactive-attributes.ts- Core implementation (249 lines)tests/reactive-attributes.test.ts- 42 comprehensive tests (492 lines)livetemplate-client.ts- Integration (4 lines)Related PRs
Test plan
🤖 Generated with Claude Code