Skip to content

Commit 07740f8

Browse files
committed
add form submit data
1 parent 51167c0 commit 07740f8

File tree

3 files changed

+99
-13
lines changed

3 files changed

+99
-13
lines changed

packages/signals/signals/src/core/signal-generators/dom-gen.ts

Lines changed: 90 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,31 @@ import { SignalEmitter } from '../emitter'
55
import { SignalGenerator } from './types'
66

77
interface Label {
8-
textContent?: string
8+
textContent: string
9+
id: string
10+
attributes: Record<string, unknown>
911
}
12+
13+
const parseFormData = (data: FormData): Record<string, string> => {
14+
return [...data].reduce((acc, [key, value]) => {
15+
if (typeof value === 'string') {
16+
acc[key] = value
17+
}
18+
return acc
19+
}, {} as Record<string, string>)
20+
}
21+
1022
const parseLabels = (
1123
labels: NodeListOf<HTMLLabelElement> | null | undefined
1224
): Label[] => {
1325
if (!labels) return []
14-
return [...labels].map((label) => ({
15-
textContent: label.textContent ?? undefined,
16-
}))
26+
return [...labels]
27+
.map((label) => ({
28+
id: label.id,
29+
attributes: parseNodeMap(label.attributes),
30+
textContent: label.textContent ? cleanText(label.textContent) : undefined,
31+
}))
32+
.filter((el): el is Label => Boolean(el.textContent))
1733
}
1834

1935
const parseNodeMap = (nodeMap: NamedNodeMap): Record<string, unknown> => {
@@ -31,21 +47,70 @@ export const cleanText = (str: string): string => {
3147
.trim() // Trim leading and trailing spaces
3248
}
3349

34-
const parseElement = (el: HTMLElement) => {
50+
interface ParsedElementBase {
51+
attributes: Record<string, unknown>
52+
classList: string[]
53+
id: string
54+
labels?: Label[]
55+
label?: Label
56+
name: string
57+
nodeName: string
58+
tagName: string
59+
title: string
60+
type: string
61+
value: string
62+
textContent?: string
63+
innerText?: string
64+
}
65+
66+
interface ParsedSelectElement extends ParsedElementBase {
67+
selectedOptions: { value: string; text: string }[]
68+
selectedIndex: number
69+
}
70+
interface ParsedInputElement extends ParsedElementBase {
71+
checked: boolean
72+
}
73+
interface ParsedMediaElement extends ParsedElementBase {
74+
currentSrc?: string
75+
currentTime?: number
76+
duration: number
77+
ended: boolean
78+
muted: boolean
79+
paused: boolean
80+
playbackRate: number
81+
readyState?: number
82+
src?: string
83+
volume?: number
84+
}
85+
86+
interface ParsedHTMLFormElement extends ParsedElementBase {
87+
formData: Record<string, string>
88+
}
89+
90+
type AnyParsedElement =
91+
| ParsedHTMLFormElement
92+
| ParsedSelectElement
93+
| ParsedInputElement
94+
| ParsedMediaElement
95+
| ParsedElementBase
96+
97+
const parseElement = (el: HTMLElement): AnyParsedElement => {
98+
const labels = parseLabels((el as HTMLInputElement).labels)
3599
const base = {
36100
// adding a bunch of fields that are not on _all_ elements, but are on enough that it's useful to have them here.
37101
attributes: parseNodeMap(el.attributes),
38102
classList: [...el.classList],
39103
id: el.id,
40-
labels: parseLabels((el as HTMLInputElement).labels),
104+
labels,
105+
label: labels[0],
41106
name: (el as HTMLInputElement).name,
42107
nodeName: el.nodeName,
43108
tagName: el.tagName,
44109
title: el.title,
45110
type: (el as HTMLInputElement).type,
46111
value: (el as HTMLInputElement).value,
47-
textContent: el.textContent && cleanText(el.textContent),
48-
innerText: el.innerText && cleanText(el.innerText),
112+
textContent: (el.textContent && cleanText(el.textContent)) ?? undefined,
113+
innerText: (el.innerText && cleanText(el.innerText)) ?? undefined,
49114
}
50115

51116
if (el instanceof HTMLSelectElement) {
@@ -76,6 +141,11 @@ const parseElement = (el: HTMLElement) => {
76141
src: el.src,
77142
volume: el.volume,
78143
}
144+
} else if (el instanceof HTMLFormElement) {
145+
return {
146+
...base,
147+
formData: parseFormData(new FormData(el)),
148+
}
79149
}
80150
return base
81151
}
@@ -111,11 +181,18 @@ export class FormSubmitGenerator implements SignalGenerator {
111181
id = 'form-submit'
112182
register(emitter: SignalEmitter) {
113183
const handleSubmit = (ev: SubmitEvent) => {
114-
const target = ev.submitter!
184+
const target = ev.target as HTMLFormElement | null
185+
186+
if (!target) return
187+
188+
// reference to the form element that the submit event is being fired at
189+
const submitter = ev.submitter
190+
// If the form is submitted via JavaScript using form.submit(), the submitter property will be null because no specific button/input triggered the submission.
115191
emitter.emit(
116192
createInteractionSignal({
117193
eventType: 'submit',
118-
submitter: parseElement(target),
194+
target: parseElement(target),
195+
submitter: submitter ? parseElement(submitter) : undefined,
119196
})
120197
)
121198
}
@@ -128,8 +205,9 @@ export class OnChangeGenerator implements SignalGenerator {
128205
id = 'change'
129206
register(emitter: SignalEmitter) {
130207
const handleChange = (ev: Event) => {
131-
const target = ev.target as HTMLElement
132-
if (target instanceof HTMLInputElement) {
208+
const target = ev.target as HTMLElement | null
209+
if (!target) return
210+
if (target && target instanceof HTMLInputElement) {
133211
if (target.type === 'password') {
134212
logger.debug('Ignoring change event for input', target)
135213
return
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
/**
2+
* This type guard can be passed into a function such as native filter
3+
* in order to remove nullish values from a list in a type-safe way.
4+
*/
5+
export const exists = <T>(value: T): value is NonNullable<T> => {
6+
return value != null && value !== undefined
7+
}

packages/signals/signals/src/types/signals.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ type ClickData = {
3030

3131
type SubmitData = {
3232
eventType: 'submit'
33-
submitter: SerializedTarget
33+
target: SerializedTarget
34+
submitter?: SerializedTarget
3435
}
3536

3637
type ChangeData = {

0 commit comments

Comments
 (0)