Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
313 changes: 313 additions & 0 deletions docs/autofill-detection-comparison.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
# Autofill Detection Techniques Comparison

This document compares different techniques for detecting browser autofill in web forms, specifically within the context of the @lambdacurry/forms library.

## Overview

Browser autofill is a convenient feature that helps users fill out forms quickly, but it can be challenging for developers to detect when it happens and style the autofilled fields appropriately. This document explores several techniques for detecting autofill and compares their effectiveness across different browsers.

## Techniques Compared

1. **Value Comparison with useRef**
- Monitors input values and detects when they change without user interaction
- Uses useRef to track user interaction state

2. **CSS Animation Detection**
- Uses CSS animations that trigger only when inputs are autofilled
- Listens for the animationstart event to detect when these animations run

3. **Form State Monitoring**
- Subscribes to form state changes using React Hook Form's watch API
- Detects when field values change without user interaction

4. **Controlled Inputs with useController**
- Uses controlled inputs with React Hook Form's useController
- Tracks user interactions and detects when values change without user interaction

## Comparison Matrix

| Technique | Chrome | Firefox | Safari | Edge | Implementation Complexity | Performance Impact | Reliability |
|-----------|--------|---------|--------|------|--------------------------|-------------------|------------|
| Value Comparison | ✅ Good | ✅ Good | ✅ Good | ✅ Good | Medium | Low | Medium |
| CSS Animation | ✅ Excellent | ⚠️ Limited | ⚠️ Limited | ✅ Good | Low | Very Low | High in Chrome |
| Form State Monitoring | ✅ Good | ✅ Good | ✅ Good | ✅ Good | Medium | Low | Medium |
| Controlled Inputs | ✅ Excellent | ✅ Good | ✅ Good | ✅ Good | High | Medium | High |

## Detailed Analysis

### 1. Value Comparison with useRef

**Implementation:**
```tsx
const useAutofillDetection = (inputRef, value) => {
const [isAutofilled, setIsAutofilled] = useState(false);
const previousValueRef = useRef(value);
const userInteractionRef = useRef(false);

// Track user interactions
useEffect(() => {
const element = inputRef.current;
if (!element) return;

const handleUserInteraction = () => {
userInteractionRef.current = true;
setTimeout(() => {
userInteractionRef.current = false;
}, 50);
};

element.addEventListener('keydown', handleUserInteraction);
element.addEventListener('input', handleUserInteraction);
// ... more event listeners

return () => {
// ... cleanup
};
}, [inputRef]);

// Detect value changes that weren't triggered by user interaction
useEffect(() => {
if (previousValueRef.current === value) return;

if (value && value !== previousValueRef.current && !userInteractionRef.current) {
setIsAutofilled(true);
}

previousValueRef.current = value;
}, [value]);

return { isAutofilled, resetAutofilled: () => setIsAutofilled(false) };
};
```

**Pros:**
- Works across all major browsers
- Relatively simple implementation
- Low performance impact

**Cons:**
- May have false positives if other code changes the input value
- Requires tracking user interaction with multiple event listeners
- Timing can be tricky (the 50ms timeout is a heuristic)

### 2. CSS Animation Detection

**Implementation:**
```tsx
const useAutofillAnimationDetection = (inputRef) => {
const [isAutofilled, setIsAutofilled] = useState(false);

useEffect(() => {
const element = inputRef.current;
if (!element) return;

const handleAnimation = (event) => {
if (event.animationName === 'onAutoFillStart') {
setIsAutofilled(true);
} else if (event.animationName === 'onAutoFillCancel') {
setIsAutofilled(false);
}
};

element.addEventListener('animationstart', handleAnimation);

// Add CSS for animation detection
const style = document.createElement('style');
style.textContent = `
@keyframes onAutoFillStart { from {} to {} }
@keyframes onAutoFillCancel { from {} to {} }

input:-webkit-autofill {
animation-name: onAutoFillStart;
animation-fill-mode: both;
}

input:not(:-webkit-autofill) {
animation-name: onAutoFillCancel;
animation-fill-mode: both;
}
`;
document.head.appendChild(style);

return () => {
element.removeEventListener('animationstart', handleAnimation);
document.head.removeChild(style);
};
}, [inputRef]);

return { isAutofilled, resetAutofilled: () => setIsAutofilled(false) };
};
```

**Pros:**
- Excellent detection in Chrome and other WebKit browsers
- Very low performance impact
- Immediate detection when autofill happens
- Can detect both when autofill is applied and when it's removed

**Cons:**
- Limited support in Firefox and Safari
- Relies on browser-specific pseudo-classes
- Requires injecting CSS into the document

### 3. Form State Monitoring

**Implementation:**
```tsx
const useAutofillFormState = (form, name) => {
const [isAutofilled, setIsAutofilled] = useState(false);
const userInteractionRef = useRef(false);
const previousValueRef = useRef(form.getValues(name));
const touchedRef = useRef(false);

// Subscribe to form state changes
useEffect(() => {
const subscription = form.watch((values, { name: changedField, type }) => {
if (changedField !== name) return;

const currentValue = values[name];

if (currentValue === previousValueRef.current) return;

if (
!userInteractionRef.current &&
currentValue &&
type !== 'change' &&
type !== 'blur' &&
!touchedRef.current
) {
setIsAutofilled(true);
}

previousValueRef.current = currentValue;
});

return () => subscription.unsubscribe();
}, [form, name]);

// Track user interactions
useEffect(() => {
const handleUserInteraction = () => {
userInteractionRef.current = true;
touchedRef.current = true;

setTimeout(() => {
userInteractionRef.current = false;
}, 50);
};

document.addEventListener('keydown', handleUserInteraction);
// ... more event listeners

return () => {
// ... cleanup
};
}, []);

return { isAutofilled, resetAutofilled: () => setIsAutofilled(false) };
};
```

**Pros:**
- Works with React Hook Form's form state
- Can detect autofill across multiple fields at once
- Works in all major browsers

**Cons:**
- Relies on React Hook Form's watch API, which can impact performance
- More complex implementation
- May have timing issues with the user interaction detection

### 4. Controlled Inputs with useController

**Implementation:**
```tsx
const useAutofillControlledInput = (controller) => {
const [isAutofilled, setIsAutofilled] = useState(false);
const [userInteracted, setUserInteracted] = useState(false);

// Monitor value changes from the controller
useEffect(() => {
const { field } = controller;

if (field.value && !userInteracted) {
setIsAutofilled(true);
}
}, [controller.field.value, userInteracted]);

// Create handlers to track user interaction
const handleUserInteraction = () => {
setUserInteracted(true);

if (isAutofilled) {
setIsAutofilled(false);
}
};

// Enhanced onChange handler
const onChange = (e) => {
handleUserInteraction();
controller.field.onChange(e);
};

// ... more handlers

return {
isAutofilled,
resetAutofilled: () => {
setIsAutofilled(false);
setUserInteracted(true);
},
fieldProps: {
...controller.field,
onChange,
onBlur: /* ... */,
onFocus: handleUserInteraction,
// ... more props
},
};
};
```

**Pros:**
- Most reliable detection across all browsers
- Integrates well with React Hook Form's validation
- Provides enhanced field props for easy integration

**Cons:**
- Highest implementation complexity
- Requires using controlled inputs, which may impact performance
- More code to maintain

## Recommendations

Based on the comparison, here are our recommendations:

### Best Overall Solution

**Controlled Inputs with useController** provides the most reliable detection across all browsers and integrates well with React Hook Form. This approach is recommended for forms where accurate autofill detection is critical.

### Best Performance Solution

**CSS Animation Detection** has the lowest performance impact and works excellently in Chrome, which is the most common browser. This approach is recommended for high-performance applications where Chrome support is prioritized.

### Best Compatibility Solution

**Value Comparison with useRef** offers a good balance of compatibility, performance, and implementation complexity. This approach is recommended for applications that need to support a wide range of browsers.

### Implementation Strategy

We recommend implementing a combination of techniques:

1. Use **CSS Animation Detection** as the primary method for Chrome and Edge
2. Fall back to **Value Comparison with useRef** for Firefox and Safari
3. Provide **Controlled Inputs with useController** as an opt-in option for forms that require the highest reliability

This strategy provides the best balance of performance, compatibility, and reliability.

## Conclusion

Detecting browser autofill is challenging due to differences in browser implementations. Each technique has its strengths and weaknesses, and the best approach depends on the specific requirements of your application.

For the @lambdacurry/forms library, we recommend implementing a flexible solution that allows developers to choose the technique that best fits their needs, with sensible defaults that work well across all major browsers.

58 changes: 58 additions & 0 deletions docs/autofill-detection-techniques.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Autofill Detection Techniques Comparison

This document provides a comparison of different techniques for detecting browser autofill events in web forms.

## Overview

Browser autofill is a convenient feature that automatically fills in form fields with previously saved information. However, it presents challenges for developers:

1. **Inconsistent behavior** across browsers (Chrome, Firefox, Safari)
2. **Limited events** - browsers don't provide standard events for autofill
3. **Styling challenges** - autofilled inputs have browser-specific styling
4. **Validation integration** - autofilled values may bypass validation

## Detection Techniques

We'll explore the following techniques for detecting when a browser has autofilled form fields:

1. **Value comparison with useRef**
- Track initial and current values to detect changes not triggered by user input
- Implementation in `feature/autofill-value-comparison` branch

2. **CSS Animation Detection**
- Use CSS animations and `animationstart` event to detect autofill-specific animations
- Implementation in `feature/autofill-css-animation` branch

3. **Form State Monitoring**
- Monitor form state changes to detect unexpected value changes
- Implementation in `feature/autofill-form-state` branch

4. **Controlled Inputs with useController**
- Use controlled inputs with React Hook Form's useController to detect value changes
- Implementation in `feature/autofill-controlled-inputs` branch

## Comparison Criteria

Each technique will be evaluated based on:

1. **Browser Compatibility** - How well it works across Chrome, Firefox, Safari
2. **Reliability** - Consistency of detection
3. **Performance Impact** - Any noticeable performance issues
4. **Implementation Complexity** - How difficult it is to implement
5. **Maintenance Overhead** - Long-term maintenance considerations

## Implementation Notes

Each technique will be implemented in a separate branch with:
- A Storybook demo showing the technique in action
- Documentation of browser-specific behavior
- Integration with form validation
- Styling hooks for autofilled inputs

## Recommendations

Based on the comparison, we'll provide recommendations for:
- Best overall technique for most use cases
- Browser-specific considerations
- Fallback strategies for browsers with limited support