Compare commits

...
Author SHA1 Message Date
Sonarly Claude Code 09fb8b72b7 fix(front-component-renderer): add target shim on serialized events for React compatibility
https://sonarly.com/issue/35773?type=bug

Form controls (inputs, textareas, checkboxes, selects) in Front Components don't respond to user input because the Remote DOM architecture serializes events differently than standard React events.

Fix: Added a `target` property to serialized events in Front Components to enable more React-compatible event handling patterns.

**What changed:**
1. Added `SerializedEventTarget` type in `SerializedEventData.ts` containing form-related properties (`value`, `checked`, `scrollTop`, etc.)
2. Added `target?: SerializedEventTarget` to `SerializedEventData` type
3. Modified `serializeEvent()` in `createHtmlHostWrapper.ts` to populate the `target` object with form-related properties when a DOM target exists

**Why this helps:**
Before this fix, developers had to use the awkward pattern:
```typescript
onChange={(event) => {
  const detail = (event as unknown as { detail: { value?: string } }).detail;
  setTextValue(detail?.value ?? '');
}}
```

After this fix, developers can use a pattern closer to standard React:
```typescript
onChange={(event) => {
  setTextValue((event.detail as any).target?.value ?? '');
}}
```

**Limitation:**
This is a partial fix. The ideal pattern `event.target.value` still won't work because `event.target` in Remote DOM refers to the remote element, not the serialized event data. A complete fix would require changes to how @remote-dom/react constructs events, which is an upstream dependency concern. Documentation should be updated to show the correct pattern for Front Component event handling.

**Backward compatibility:**
The existing `event.detail.value` pattern continues to work - the `target` property is additive.
2026-05-07 17:57:26 +00:00
2 changed files with 30 additions and 0 deletions
@@ -1,3 +1,17 @@
export type SerializedEventTarget = {
value?: string;
checked?: boolean;
scrollTop?: number;
scrollLeft?: number;
currentTime?: number;
duration?: number;
paused?: boolean;
ended?: boolean;
volume?: number;
muted?: boolean;
playbackRate?: number;
};
export type SerializedEventData = {
type: string;
altKey?: boolean;
@@ -34,4 +48,5 @@ export type SerializedEventData = {
volume?: number;
muted?: boolean;
playbackRate?: number;
target?: SerializedEventTarget;
};
@@ -135,6 +135,21 @@ const serializeEvent = (event: unknown): SerializedEventData => {
if ('playbackRate' in target && typeof target.playbackRate === 'number') {
serialized.playbackRate = target.playbackRate;
}
// Add target property for React-compatible event patterns (event.detail.target.value)
serialized.target = {
value: serialized.value,
checked: serialized.checked,
scrollTop: serialized.scrollTop,
scrollLeft: serialized.scrollLeft,
currentTime: serialized.currentTime,
duration: serialized.duration,
paused: serialized.paused,
ended: serialized.ended,
volume: serialized.volume,
muted: serialized.muted,
playbackRate: serialized.playbackRate,
};
}
return serialized;