@marianmeres/stuic 3.76.1 → 3.76.3

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.
@@ -47,6 +47,12 @@ export function onSubmitValidityCheck(node) {
47
47
  e.preventDefault();
48
48
  // this will disable all other onsubmit listeners...
49
49
  e.stopImmediatePropagation();
50
+ // // [debug] kept commented for the next time Issue A regresses
51
+ // // eslint-disable-next-line no-console
52
+ // console.log(
53
+ // "[onSubmitValidityCheck] submit intercepted. element count:",
54
+ // node.elements?.length
55
+ // );
50
56
  const invalid = [];
51
57
  for (let i = 0; i < node.elements?.length; i++) {
52
58
  const el = node.elements[i];
@@ -55,11 +61,38 @@ export function onSubmitValidityCheck(node) {
55
61
  // input (last radio input), which is not desired
56
62
  if (el.type === "radio")
57
63
  continue;
64
+ // // [debug] kept commented for the next time Issue A regresses
65
+ // // eslint-disable-next-line no-console
66
+ // console.log(`[onSubmitValidityCheck] el#${i} ${el.name || el.type} BEFORE`, {
67
+ // value: el.value,
68
+ // valid: el.validity.valid,
69
+ // customError: el.validity.customError,
70
+ // valueMissing: el.validity.valueMissing,
71
+ // validationMessage: el.validationMessage,
72
+ // });
73
+ // Clear any stale `customError` flag from a prior submit attempt before
74
+ // re-dispatching the validate listeners. Without this, if the field's
75
+ // per-field validate $effect was torn down/re-mounted in a way that
76
+ // skipped re-running its `customValidator` (e.g., the parent re-rendered
77
+ // for an unrelated reason between submits), the previous submit's
78
+ // customValidity message lingers on the DOM element. Then `el.validity.valid`
79
+ // would return false here even though the customValidator now reports no
80
+ // error, silently routing the form to `submit_invalid` and never calling
81
+ // the consumer's onSubmit. The dispatched change event below re-runs the
82
+ // per-field validator which re-applies a real customValidity if needed.
83
+ if (typeof el.setCustomValidity === "function")
84
+ el.setCustomValidity("");
58
85
  el.dispatchEvent(new Event("input", { bubbles: true }));
59
86
  el.dispatchEvent(new Event("change", { bubbles: true }));
60
- // typeof el.checkValidity === "function" && !el.checkValidity();
61
- // NOTE: el.checkValidity() returns true for hidden inputs event if they are invalid!
62
- // if (!el.checkValidity()) {
87
+ // // [debug] kept commented for the next time Issue A regresses
88
+ // // eslint-disable-next-line no-console
89
+ // console.log(`[onSubmitValidityCheck] el#${i} ${el.name || el.type} AFTER `, {
90
+ // value: el.value,
91
+ // valid: el.validity.valid,
92
+ // customError: el.validity.customError,
93
+ // valueMissing: el.validity.valueMissing,
94
+ // validationMessage: el.validationMessage,
95
+ // });
63
96
  if (!el.validity.valid) {
64
97
  invalid.push(el);
65
98
  }
@@ -67,12 +100,28 @@ export function onSubmitValidityCheck(node) {
67
100
  }
68
101
  // none invalid
69
102
  if (!invalid.length) {
103
+ // // [debug] kept commented for the next time Issue A regresses
104
+ // // eslint-disable-next-line no-console
105
+ // console.log("[onSubmitValidityCheck] → dispatching submit_valid");
70
106
  node.dispatchEvent(new CustomEvent(SUBMIT_VALID_EVENT_NAME, {
71
107
  bubbles: true,
72
108
  detail: { formData: new FormData(node) },
73
109
  }));
74
110
  }
75
111
  else {
112
+ // // [debug] kept commented for the next time Issue A regresses
113
+ // // eslint-disable-next-line no-console
114
+ // console.warn(
115
+ // "[onSubmitValidityCheck] → dispatching submit_invalid; invalid =",
116
+ // invalid.map((el) => ({
117
+ // name: el.name,
118
+ // type: el.type,
119
+ // value: el.value,
120
+ // customError: el.validity.customError,
121
+ // valueMissing: el.validity.valueMissing,
122
+ // validationMessage: el.validationMessage,
123
+ // }))
124
+ // );
76
125
  node.dispatchEvent(new CustomEvent(SUBMIT_INVALID_EVENT_NAME, {
77
126
  bubbles: true,
78
127
  detail: { invalid },
@@ -154,6 +154,14 @@ export function validate(el, fn) {
154
154
  }
155
155
  return fallback;
156
156
  };
157
+ // // [debug] kept commented for the next time Issue A regresses
158
+ // // eslint-disable-next-line no-console
159
+ // console.log(
160
+ // `[validate $effect] (re)mount listener on <${el.tagName.toLowerCase()} name="${
161
+ // (el as HTMLInputElement).name || ""
162
+ // }">`,
163
+ // { enabled, on, hasCustomValidator: typeof customValidator === "function" }
164
+ // );
157
165
  const _doValidate = () => {
158
166
  if (!enabled)
159
167
  return;
@@ -165,8 +173,26 @@ export function validate(el, fn) {
165
173
  let customValidatorMessage = "";
166
174
  if (typeof customValidator === "function") {
167
175
  customValidatorMessage = customValidator(el.value, context, el) || "";
168
- el.setCustomValidity(customValidatorMessage);
169
176
  }
177
+ // Always write — including the no-validator case (which writes "" and
178
+ // thus clears any stale `customError` flag the element may be carrying
179
+ // from a prior listener generation). Without this, a `customValidator`
180
+ // being removed across re-renders would leave the previous message
181
+ // stuck on the element forever.
182
+ el.setCustomValidity(customValidatorMessage);
183
+ // // [debug] kept commented for the next time Issue A regresses
184
+ // // eslint-disable-next-line no-console
185
+ // console.log(
186
+ // `[validate _doValidate] ran on <${el.tagName.toLowerCase()} name="${
187
+ // (el as HTMLInputElement).name || ""
188
+ // }">`,
189
+ // {
190
+ // value: el.value,
191
+ // customValidatorMessage,
192
+ // customError: el.validity.customError,
193
+ // valid: el.validity.valid,
194
+ // }
195
+ // );
170
196
  // this triggers the bubble, which is not what we want
171
197
  // el.reportValidity();
172
198
  const validityState = el.validity;
@@ -2,11 +2,11 @@
2
2
  import type { Snippet } from "svelte";
3
3
  import type { HTMLAttributes } from "svelte/elements";
4
4
  import type { TranslateFn } from "../../types.js";
5
+ import type { NotificationsStack } from "../Notifications/notifications-stack.svelte.js";
5
6
  import type {
6
7
  LoginFormData,
7
8
  LoginFormValidationError,
8
9
  } from "./_internal/login-form-types.js";
9
- import type { NotificationsStack } from "../Notifications/notifications-stack.svelte.js";
10
10
 
11
11
  export interface Props extends Omit<HTMLAttributes<HTMLFormElement>, "children"> {
12
12
  /** Bindable login data. Default: createEmptyLoginFormData() */
@@ -78,18 +78,18 @@
78
78
 
79
79
  <script lang="ts">
80
80
  import { untrack } from "svelte";
81
+ import { onSubmitValidityCheck } from "../../actions/on-submit-validity-check.svelte.js";
82
+ import { tooltip } from "../../actions/tooltip/tooltip.svelte.js";
81
83
  import { twMerge } from "../../utils/tw-merge.js";
84
+ import Button from "../Button/Button.svelte";
85
+ import DismissibleMessage from "../DismissibleMessage/DismissibleMessage.svelte";
86
+ import FieldCheckbox from "../Input/FieldCheckbox.svelte";
87
+ import FieldInput from "../Input/FieldInput.svelte";
82
88
  import { t_default } from "./_internal/login-form-i18n-defaults.js";
83
89
  import {
84
90
  createEmptyLoginFormData,
85
91
  validateLoginForm,
86
92
  } from "./_internal/login-form-utils.js";
87
- import { tooltip } from "../../actions/tooltip/tooltip.svelte.js";
88
- import Button from "../Button/Button.svelte";
89
- import DismissibleMessage from "../DismissibleMessage/DismissibleMessage.svelte";
90
- import FieldCheckbox from "../Input/FieldCheckbox.svelte";
91
- import FieldInput from "../Input/FieldInput.svelte";
92
- import { onSubmitValidityCheck } from "../../actions/on-submit-validity-check.svelte.js";
93
93
 
94
94
  let {
95
95
  formData = $bindable(createEmptyLoginFormData()),
@@ -131,13 +131,20 @@
131
131
  }
132
132
 
133
133
  function handleSubmitValid() {
134
+ // // [debug] kept commented for the next time Issue A regresses
135
+ // // eslint-disable-next-line no-console
136
+ // console.log("[LoginForm handleSubmitValid] entered", {
137
+ // formData: { ...formData },
138
+ // internalErrors: $state.snapshot(internalErrors),
139
+ // externalErrors: [...externalErrors],
140
+ // error,
141
+ // });
142
+
134
143
  // Defensively clear any stale customValidity left on form fields by a prior
135
- // validation pass. The `validate` action already clears it when the field's
136
- // customValidator returns an empty string, but a field can skip that path
137
- // (e.g., it was disabled, or its $effect torn down between submits) and the
138
- // stale flag would then make `el.validity.valid` return false on the next
139
- // submit, silently routing the form to `submit_invalid` and never calling
140
- // `onSubmit`. Resetting here gives every retry a clean slate.
144
+ // validation pass. The canonical fix lives in `onSubmitValidityCheck` (which
145
+ // pre-clears before the per-field re-dispatch), but doing it here too is
146
+ // cheap insurance against any future regression that lets a stale flag slip
147
+ // past the action.
141
148
  if (el) {
142
149
  for (const node of Array.from(el.elements) as HTMLInputElement[]) {
143
150
  if (typeof node.setCustomValidity === "function") node.setCustomValidity("");
@@ -147,6 +154,15 @@
147
154
  const validationErrors = validateLoginForm(formData, t);
148
155
  internalErrors = validationErrors;
149
156
 
157
+ // // [debug] kept commented for the next time Issue A regresses
158
+ // // eslint-disable-next-line no-console
159
+ // console.log("[LoginForm handleSubmitValid] post-validate", {
160
+ // validationErrors,
161
+ // externalErrorsLen: externalErrors.length,
162
+ // willCallOnSubmit:
163
+ // validationErrors.length === 0 && externalErrors.length === 0,
164
+ // });
165
+
150
166
  if (validationErrors.length === 0 && externalErrors.length === 0) {
151
167
  onSubmit(formData);
152
168
  }
@@ -188,10 +204,10 @@
188
204
  <DismissibleMessage message={error} intent="destructive" />
189
205
 
190
206
  <!--
191
- svelte-ignore binding_property_non_reactive:
192
- formData is a $bindable prop — deep reactivity depends on the consumer
193
- passing a $state() object. The bindings work correctly regardless.
194
- -->
207
+ svelte-ignore binding_property_non_reactive:
208
+ formData is a $bindable prop — deep reactivity depends on the consumer
209
+ passing a $state() object. The bindings work correctly regardless.
210
+ -->
195
211
  <!-- Email -->
196
212
  <!-- svelte-ignore binding_property_non_reactive -->
197
213
  <FieldInput
@@ -1,8 +1,8 @@
1
1
  import type { Snippet } from "svelte";
2
2
  import type { HTMLAttributes } from "svelte/elements";
3
3
  import type { TranslateFn } from "../../types.js";
4
- import type { LoginFormData, LoginFormValidationError } from "./_internal/login-form-types.js";
5
4
  import type { NotificationsStack } from "../Notifications/notifications-stack.svelte.js";
5
+ import type { LoginFormData, LoginFormValidationError } from "./_internal/login-form-types.js";
6
6
  export interface Props extends Omit<HTMLAttributes<HTMLFormElement>, "children"> {
7
7
  /** Bindable login data. Default: createEmptyLoginFormData() */
8
8
  formData?: LoginFormData;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@marianmeres/stuic",
3
- "version": "3.76.1",
3
+ "version": "3.76.3",
4
4
  "files": [
5
5
  "dist",
6
6
  "!dist/**/*.test.*",