@marianmeres/stuic 3.76.3 → 3.76.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -89,6 +89,18 @@
89
89
 
90
90
  let t = $derived(tProp ?? t_default);
91
91
 
92
+ // Mirror the form ref into local $state so it survives prop re-application
93
+ // when the parent re-renders without binding `el`. See LoginForm for the full
94
+ // rationale — same Svelte 5 `$bindable` + `bind:this` gotcha applies here.
95
+ // EmailVerifyForm uses `onsubmit={handleFormSubmit}` (declarative attribute)
96
+ // so the listener wouldn't get torn down the way LoginForm/RegisterForm did,
97
+ // but the public `el` prop is still unsafe for consumers that bind it after
98
+ // any parent re-render — same fix for consistency.
99
+ let formEl = $state<HTMLFormElement | undefined>();
100
+ $effect(() => {
101
+ el = formEl;
102
+ });
103
+
92
104
  let code = $state("");
93
105
  let cooldownRemaining = $state(0);
94
106
  let resentFlash = $state(false);
@@ -182,7 +194,7 @@
182
194
  let submitDisabled = $derived(code.length !== codeLength || isSubmitting);
183
195
  </script>
184
196
 
185
- <form bind:this={el} class={_class} onsubmit={handleFormSubmit} {...rest}>
197
+ <form bind:this={formEl} class={_class} onsubmit={handleFormSubmit} {...rest}>
186
198
  <!-- Heading -->
187
199
  <H level={2} class={unstyled ? undefined : "stuic-email-verify-form-heading"}>
188
200
  {t("email_verify_form.heading")}
@@ -115,6 +115,19 @@
115
115
 
116
116
  let t = $derived(tProp ?? t_default);
117
117
 
118
+ // Mirror the form ref into local $state so it survives prop re-application
119
+ // when the parent re-renders without binding `el`. Otherwise the bindable
120
+ // prop reverts to its default (undefined) on the next parent re-render, the
121
+ // `$effect` tracking it cleans up the `submit_valid` listener, and `bind:this`
122
+ // does NOT re-fire (the form element wasn't unmounted) — leaving the form
123
+ // alive in the DOM with no submit handler. Next click silently goes nowhere.
124
+ // `el` stays the public API: consumers can still bind it; we mirror formEl
125
+ // into it so the binding sees a value.
126
+ let formEl = $state<HTMLFormElement | undefined>();
127
+ $effect(() => {
128
+ el = formEl;
129
+ });
130
+
118
131
  // Internal validation errors (set on submit)
119
132
  let internalErrors = $state<LoginFormValidationError[]>([]);
120
133
 
@@ -145,8 +158,8 @@
145
158
  // pre-clears before the per-field re-dispatch), but doing it here too is
146
159
  // cheap insurance against any future regression that lets a stale flag slip
147
160
  // past the action.
148
- if (el) {
149
- for (const node of Array.from(el.elements) as HTMLInputElement[]) {
161
+ if (formEl) {
162
+ for (const node of Array.from(formEl.elements) as HTMLInputElement[]) {
150
163
  if (typeof node.setCustomValidity === "function") node.setCustomValidity("");
151
164
  }
152
165
  }
@@ -188,9 +201,11 @@
188
201
 
189
202
  // The onSubmitValidityCheck action intercepts native submit (capture phase,
190
203
  // stopImmediatePropagation) and dispatches a custom "submit_valid" event.
191
- // Listen for it on the form element as a fallback.
204
+ // Listen for it on the form element as a fallback. Reads `formEl` (local state)
205
+ // — NOT the `el` prop, which can revert to undefined on parent re-render and
206
+ // would cause this $effect's cleanup to silently detach the listener.
192
207
  $effect(() => {
193
- const node = el;
208
+ const node = formEl;
194
209
  if (!node) return;
195
210
  node.addEventListener("submit_valid", handleSubmitValid);
196
211
  return () => node.removeEventListener("submit_valid", handleSubmitValid);
@@ -199,7 +214,7 @@
199
214
  let _class = $derived(unstyled ? classProp : twMerge("stuic-login-form", classProp));
200
215
  </script>
201
216
 
202
- <form bind:this={el} class={_class} use:onSubmitValidityCheck {...rest}>
217
+ <form bind:this={formEl} class={_class} use:onSubmitValidityCheck {...rest}>
203
218
  <!-- General error alert -->
204
219
  <DismissibleMessage message={error} intent="destructive" />
205
220
 
@@ -132,6 +132,14 @@
132
132
 
133
133
  let t = $derived(tProp ?? t_default);
134
134
 
135
+ // Mirror the form ref into local $state so it survives prop re-application
136
+ // when the parent re-renders without binding `el`. See LoginForm for the full
137
+ // rationale — same Svelte 5 `$bindable` + `bind:this` gotcha applies here.
138
+ let formEl = $state<HTMLFormElement | undefined>();
139
+ $effect(() => {
140
+ el = formEl;
141
+ });
142
+
135
143
  let topFields = $derived(extraFields.filter((f) => f.position === "top"));
136
144
  let bottomFields = $derived(extraFields.filter((f) => f.position !== "top"));
137
145
 
@@ -180,9 +188,11 @@
180
188
 
181
189
  // The onSubmitValidityCheck action intercepts native submit (capture phase,
182
190
  // stopImmediatePropagation) and dispatches a custom "submit_valid" event.
183
- // Listen for it on the form element as a fallback.
191
+ // Listen for it on the form element as a fallback. Reads `formEl` (local state)
192
+ // — NOT the `el` prop, which can revert to undefined on parent re-render and
193
+ // would cause this $effect's cleanup to silently detach the listener.
184
194
  $effect(() => {
185
- const node = el;
195
+ const node = formEl;
186
196
  if (!node) return;
187
197
  node.addEventListener("submit_valid", handleSubmitValid);
188
198
  return () => node.removeEventListener("submit_valid", handleSubmitValid);
@@ -191,7 +201,7 @@
191
201
  let _class = $derived(unstyled ? classProp : twMerge("stuic-register-form", classProp));
192
202
  </script>
193
203
 
194
- <form bind:this={el} class={_class} use:onSubmitValidityCheck {...rest}>
204
+ <form bind:this={formEl} class={_class} use:onSubmitValidityCheck {...rest}>
195
205
  <!-- General error alert -->
196
206
  <DismissibleMessage message={error} intent="destructive" />
197
207
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.76.3",
3
+ "version": "3.76.4",
4
4
  "files": [
5
5
  "dist",
6
6
  "!dist/**/*.test.*",