Skip to content

feat: add reactive attributes for action lifecycle events#6

Merged
adnaan merged 1 commit into
mainfrom
feature/reactive-attributes
Dec 1, 2025
Merged

feat: add reactive attributes for action lifecycle events#6
adnaan merged 1 commit into
mainfrom
feature/reactive-attributes

Conversation

@adnaan
Copy link
Copy Markdown
Contributor

@adnaan adnaan commented Dec 1, 2025

Summary

  • Adds reactive attributes module for declarative DOM actions
  • Responds to action lifecycle events (pending, success, error, done)
  • No custom JavaScript required for common UX patterns

Features

Pattern: lvt-{action}-on:{event}="param"

8 Supported Actions:

Action Example Effect
reset lvt-reset-on:success Form reset
disable lvt-disable-on:pending Disable element
enable lvt-enable-on:done Enable element
addClass lvt-addClass-on:pending="loading" Add CSS classes
removeClass lvt-removeClass-on:done="loading" Remove CSS classes
toggleClass lvt-toggleClass-on:pending="active" Toggle CSS classes
setAttr lvt-setAttr-on:pending="aria-busy:true" Set attribute
toggleAttr lvt-toggleAttr-on:pending="readonly" Toggle boolean attribute

Event Scope:

  • Global: lvt-reset-on:success (any action)
  • Action-specific: lvt-reset-on:create-todo:success

Files 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

  • 42 unit tests pass covering:
    • Attribute parsing (valid/invalid)
    • All 8 action types
    • Event matching (global/action-specific)
    • Multiple bindings per element
  • All 180 client tests pass
  • TypeScript compiles without errors
  • Build produces valid browser bundle

🤖 Generated with Claude Code

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>
Copilot AI review requested due to automatic review settings December 1, 2025 18:02
@adnaan adnaan merged commit 46e2065 into main Dec 1, 2025
7 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +213 to +214
// This is a broad selector but avoids escaping issues with attribute names containing colons
const allElements = document.querySelectorAll("*");
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. 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
  2. Or use document.querySelectorAll('[class*="lvt-"][class*="-on:"]') to target elements with these attributes
  3. 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.

Suggested change
// 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]'
);

Copilot uses AI. Check for mistakes.
Comment thread livetemplate-client.ts
Comment on lines +354 to +355
// Set up reactive attribute listeners for lvt-{action}-on:{event} attributes
setupReactiveAttributeListeners();
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling setupReactiveAttributeListeners() on every connect() will create duplicate event listeners, causing reactive attributes to execute multiple times per event. The setup should either:

  1. Be called only once during client initialization (move to constructor)
  2. Track if listeners are already set up and skip if so
  3. 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.

Copilot uses AI. Check for mistakes.
Comment on lines +48 to +57
const REACTIVE_ACTIONS: ReactiveAction[] = [
"reset",
"disable",
"enable",
"addClass",
"removeClass",
"toggleClass",
"setAttr",
"toggleAttr",
];
Copy link

Copilot AI Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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.

Suggested change
const REACTIVE_ACTIONS: ReactiveAction[] = [
"reset",
"disable",
"enable",
"addClass",
"removeClass",
"toggleClass",
"setAttr",
"toggleAttr",
];

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants