@marianmeres/stuic 3.85.0 → 3.87.0
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.
- package/dist/actions/validate.svelte.d.ts +24 -4
- package/dist/actions/validate.svelte.js +18 -5
- package/dist/components/EmailVerifyForm/EmailVerifyForm.svelte +21 -0
- package/dist/components/EmailVerifyForm/EmailVerifyForm.svelte.d.ts +4 -1
- package/dist/components/Input/FieldAssets.svelte +48 -3
- package/dist/components/Input/FieldAssets.svelte.d.ts +8 -2
- package/dist/components/Input/FieldCheckbox.svelte +34 -3
- package/dist/components/Input/FieldCheckbox.svelte.d.ts +8 -1
- package/dist/components/Input/FieldCountry.svelte +68 -8
- package/dist/components/Input/FieldCountry.svelte.d.ts +8 -1
- package/dist/components/Input/FieldFile.svelte +34 -3
- package/dist/components/Input/FieldFile.svelte.d.ts +8 -1
- package/dist/components/Input/FieldInput.svelte +43 -3
- package/dist/components/Input/FieldInput.svelte.d.ts +8 -1
- package/dist/components/Input/FieldInputLocalized.svelte +41 -2
- package/dist/components/Input/FieldInputLocalized.svelte.d.ts +8 -2
- package/dist/components/Input/FieldKeyValues.svelte +37 -2
- package/dist/components/Input/FieldKeyValues.svelte.d.ts +8 -2
- package/dist/components/Input/FieldLikeButton.svelte +41 -4
- package/dist/components/Input/FieldLikeButton.svelte.d.ts +8 -1
- package/dist/components/Input/FieldObject.svelte +64 -6
- package/dist/components/Input/FieldObject.svelte.d.ts +8 -2
- package/dist/components/Input/FieldOptions.svelte +36 -3
- package/dist/components/Input/FieldOptions.svelte.d.ts +8 -2
- package/dist/components/Input/FieldPhoneNumber.svelte +51 -6
- package/dist/components/Input/FieldPhoneNumber.svelte.d.ts +8 -1
- package/dist/components/Input/FieldRadios.svelte +36 -2
- package/dist/components/Input/FieldRadios.svelte.d.ts +8 -1
- package/dist/components/Input/FieldSelect.svelte +34 -3
- package/dist/components/Input/FieldSelect.svelte.d.ts +8 -1
- package/dist/components/Input/FieldSwitch.svelte +41 -2
- package/dist/components/Input/FieldSwitch.svelte.d.ts +8 -1
- package/dist/components/Input/FieldTextarea.svelte +34 -3
- package/dist/components/Input/FieldTextarea.svelte.d.ts +8 -1
- package/dist/components/Input/_internal/FieldRadioInternal.svelte +34 -3
- package/dist/components/Input/_internal/FieldRadioInternal.svelte.d.ts +7 -1
- package/dist/components/Input/_internal/countries.js +3 -0
- package/dist/components/Input/index.css +21 -0
- package/dist/components/LoginForm/LoginForm.svelte +35 -0
- package/dist/components/LoginForm/LoginForm.svelte.d.ts +5 -1
- package/dist/components/LoginOrRegisterForm/LoginOrRegisterForm.svelte +40 -0
- package/dist/components/LoginOrRegisterForm/LoginOrRegisterForm.svelte.d.ts +5 -1
- package/dist/components/RegisterForm/RegisterForm.svelte +46 -2
- package/dist/components/RegisterForm/RegisterForm.svelte.d.ts +5 -1
- package/dist/components/Switch/Switch.svelte +42 -4
- package/dist/components/Switch/Switch.svelte.d.ts +7 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/validate-fields.d.ts +72 -0
- package/dist/utils/validate-fields.js +73 -0
- package/docs/domains/actions.md +74 -0
- package/docs/domains/components.md +131 -0
- package/docs/domains/utils.md +38 -0
- package/package.json +1 -1
|
@@ -92,7 +92,8 @@
|
|
|
92
92
|
renderSize = "md",
|
|
93
93
|
required = false,
|
|
94
94
|
disabled = false,
|
|
95
|
-
validate
|
|
95
|
+
// Renamed local binding to avoid collision with `export function validate()` below.
|
|
96
|
+
validate: validateProp,
|
|
96
97
|
labelAfter,
|
|
97
98
|
below,
|
|
98
99
|
labelLeft,
|
|
@@ -226,6 +227,41 @@
|
|
|
226
227
|
let validation: ValidationResult | undefined = $state();
|
|
227
228
|
const setValidationResult = (res: ValidationResult) => (validation = res);
|
|
228
229
|
|
|
230
|
+
let _doValidate: (() => void) | undefined = $state();
|
|
231
|
+
// Default-language visible input — focus target for the imperative API.
|
|
232
|
+
let defaultInputEl: HTMLInputElement | HTMLTextAreaElement | undefined = $state();
|
|
233
|
+
|
|
234
|
+
/** Trigger validation now. Renders the inline message if invalid. */
|
|
235
|
+
export function validate(): ValidationResult | undefined {
|
|
236
|
+
_doValidate?.();
|
|
237
|
+
return validation;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Clear the inline validation message and reset `setCustomValidity`. */
|
|
241
|
+
export function clearValidation(): void {
|
|
242
|
+
validation = undefined;
|
|
243
|
+
hiddenInputEl?.setCustomValidity?.("");
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Current validation state, or undefined if validator has never run. */
|
|
247
|
+
export function getValidation(): ValidationResult | undefined {
|
|
248
|
+
return validation;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Focus the default-language input/textarea. */
|
|
252
|
+
export function focus(): void {
|
|
253
|
+
defaultInputEl?.focus?.();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Scroll the field into view. Defaults to smooth + center. */
|
|
257
|
+
export function scrollIntoView(opts?: ScrollIntoViewOptions): void {
|
|
258
|
+
defaultInputEl?.scrollIntoView?.({
|
|
259
|
+
behavior: "smooth",
|
|
260
|
+
block: "center",
|
|
261
|
+
...opts,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
229
265
|
let wrappedValidate: Omit<ValidateOptions, "setValidationResult"> = $derived({
|
|
230
266
|
enabled: true,
|
|
231
267
|
customValidator(val: any, context: Record<string, any> | undefined, el: any) {
|
|
@@ -236,9 +272,10 @@
|
|
|
236
272
|
}
|
|
237
273
|
}
|
|
238
274
|
// Delegate to provided validator
|
|
239
|
-
return (
|
|
275
|
+
return (validateProp as any)?.customValidator?.(val, context, el) || "";
|
|
240
276
|
},
|
|
241
277
|
setValidationResult,
|
|
278
|
+
setDoValidate: (fn: () => void) => (_doValidate = fn),
|
|
242
279
|
});
|
|
243
280
|
|
|
244
281
|
const INPUT_CLS = "w-full";
|
|
@@ -281,6 +318,7 @@
|
|
|
281
318
|
{#if !expanded}
|
|
282
319
|
{#if multiline}
|
|
283
320
|
<textarea
|
|
321
|
+
bind:this={defaultInputEl}
|
|
284
322
|
value={entries.find((e) => e.language === _defaultLanguage)?.value ?? ""}
|
|
285
323
|
oninput={(e) => updateEntry(_defaultLanguage, e.currentTarget.value)}
|
|
286
324
|
class={twMerge(INPUT_CLS, "min-h-16", classLanguageInput)}
|
|
@@ -294,6 +332,7 @@
|
|
|
294
332
|
></textarea>
|
|
295
333
|
{:else}
|
|
296
334
|
<input
|
|
335
|
+
bind:this={defaultInputEl}
|
|
297
336
|
type="text"
|
|
298
337
|
value={entries.find((e) => e.language === _defaultLanguage)?.value ?? ""}
|
|
299
338
|
oninput={(e) => updateEntry(_defaultLanguage, e.currentTarget.value)}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
|
-
import type { ValidateOptions } from "../../actions/validate.svelte.js";
|
|
2
|
+
import type { ValidateOptions, ValidationResult } from "../../actions/validate.svelte.js";
|
|
3
3
|
import type { TranslateFn } from "../../types.js";
|
|
4
4
|
import type { THC } from "../Thc/Thc.svelte";
|
|
5
5
|
import type { InputWrapClassProps } from "./types.js";
|
|
@@ -38,6 +38,12 @@ export interface Props extends InputWrapClassProps, Record<string, any> {
|
|
|
38
38
|
t?: TranslateFn;
|
|
39
39
|
forceLocalizedOutput?: boolean;
|
|
40
40
|
}
|
|
41
|
-
declare const FieldInputLocalized: import("svelte").Component<Props, {
|
|
41
|
+
declare const FieldInputLocalized: import("svelte").Component<Props, {
|
|
42
|
+
validate: () => ValidationResult | undefined;
|
|
43
|
+
clearValidation: () => void;
|
|
44
|
+
getValidation: () => ValidationResult | undefined;
|
|
45
|
+
focus: () => void;
|
|
46
|
+
scrollIntoView: (opts?: ScrollIntoViewOptions) => void;
|
|
47
|
+
}, "value" | "expanded">;
|
|
42
48
|
type FieldInputLocalized = ReturnType<typeof FieldInputLocalized>;
|
|
43
49
|
export default FieldInputLocalized;
|
|
@@ -93,7 +93,8 @@
|
|
|
93
93
|
renderSize = "sm",
|
|
94
94
|
required = false,
|
|
95
95
|
disabled = false,
|
|
96
|
-
validate
|
|
96
|
+
// Renamed local binding to avoid collision with `export function validate()` below.
|
|
97
|
+
validate: validateProp,
|
|
97
98
|
labelAfter,
|
|
98
99
|
below,
|
|
99
100
|
labelLeft,
|
|
@@ -235,6 +236,39 @@
|
|
|
235
236
|
let validation: ValidationResult | undefined = $state();
|
|
236
237
|
const setValidationResult = (res: ValidationResult) => (validation = res);
|
|
237
238
|
|
|
239
|
+
let _doValidate: (() => void) | undefined = $state();
|
|
240
|
+
|
|
241
|
+
/** Trigger validation now. Renders the inline message if invalid. */
|
|
242
|
+
export function validate(): ValidationResult | undefined {
|
|
243
|
+
_doValidate?.();
|
|
244
|
+
return validation;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Clear the inline validation message and reset `setCustomValidity`. */
|
|
248
|
+
export function clearValidation(): void {
|
|
249
|
+
validation = undefined;
|
|
250
|
+
hiddenInputEl?.setCustomValidity?.("");
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/** Current validation state, or undefined if validator has never run. */
|
|
254
|
+
export function getValidation(): ValidationResult | undefined {
|
|
255
|
+
return validation;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/** Focus the first key input. */
|
|
259
|
+
export function focus(): void {
|
|
260
|
+
keyInputRefs[0]?.focus?.();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** Scroll the field into view. Defaults to smooth + center. */
|
|
264
|
+
export function scrollIntoView(opts?: ScrollIntoViewOptions): void {
|
|
265
|
+
keyInputRefs[0]?.scrollIntoView?.({
|
|
266
|
+
behavior: "smooth",
|
|
267
|
+
block: "center",
|
|
268
|
+
...opts,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
238
272
|
let wrappedValidate: Omit<ValidateOptions, "setValidationResult"> = $derived({
|
|
239
273
|
enabled: true,
|
|
240
274
|
customValidator(val: any, context: Record<string, any> | undefined, el: any) {
|
|
@@ -264,9 +298,10 @@
|
|
|
264
298
|
}
|
|
265
299
|
|
|
266
300
|
// Continue with provided validator
|
|
267
|
-
return (
|
|
301
|
+
return (validateProp as any)?.customValidator?.(val, context, el) || "";
|
|
268
302
|
},
|
|
269
303
|
setValidationResult,
|
|
304
|
+
setDoValidate: (fn: () => void) => (_doValidate = fn),
|
|
270
305
|
});
|
|
271
306
|
|
|
272
307
|
const BTN_CLS = [
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
|
-
import { type ValidateOptions } from "../../actions/validate.svelte.js";
|
|
2
|
+
import { type ValidateOptions, type ValidationResult } from "../../actions/validate.svelte.js";
|
|
3
3
|
import type { TranslateFn } from "../../types.js";
|
|
4
4
|
import type { THC } from "../Thc/Thc.svelte";
|
|
5
5
|
import type { InputWrapClassProps } from "./types.js";
|
|
@@ -40,6 +40,12 @@ export interface Props extends InputWrapClassProps, Record<string, any> {
|
|
|
40
40
|
strictJsonValidation?: boolean;
|
|
41
41
|
t?: TranslateFn;
|
|
42
42
|
}
|
|
43
|
-
declare const FieldKeyValues: import("svelte").Component<Props, {
|
|
43
|
+
declare const FieldKeyValues: import("svelte").Component<Props, {
|
|
44
|
+
validate: () => ValidationResult | undefined;
|
|
45
|
+
clearValidation: () => void;
|
|
46
|
+
getValidation: () => ValidationResult | undefined;
|
|
47
|
+
focus: () => void;
|
|
48
|
+
scrollIntoView: (opts?: ScrollIntoViewOptions) => void;
|
|
49
|
+
}, "value">;
|
|
44
50
|
type FieldKeyValues = ReturnType<typeof FieldKeyValues>;
|
|
45
51
|
export default FieldKeyValues;
|
|
@@ -61,7 +61,8 @@
|
|
|
61
61
|
required = false,
|
|
62
62
|
disabled = false,
|
|
63
63
|
//
|
|
64
|
-
validate
|
|
64
|
+
// Renamed local binding to avoid collision with `export function validate()` below.
|
|
65
|
+
validate: validateProp,
|
|
65
66
|
//
|
|
66
67
|
labelAfter,
|
|
67
68
|
inputBefore,
|
|
@@ -124,6 +125,40 @@
|
|
|
124
125
|
let validation: ValidationResult | undefined = $state();
|
|
125
126
|
const setValidationResult = (res: ValidationResult) => (validation = res);
|
|
126
127
|
|
|
128
|
+
let _doValidate: (() => void) | undefined = $state();
|
|
129
|
+
let buttonEl: HTMLElement | undefined = $state();
|
|
130
|
+
|
|
131
|
+
/** Trigger validation now. Renders the inline message if invalid. */
|
|
132
|
+
export function validate(): ValidationResult | undefined {
|
|
133
|
+
_doValidate?.();
|
|
134
|
+
return validation;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Clear the inline validation message and reset `setCustomValidity`. */
|
|
138
|
+
export function clearValidation(): void {
|
|
139
|
+
validation = undefined;
|
|
140
|
+
input?.setCustomValidity?.("");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Current validation state, or undefined if validator has never run. */
|
|
144
|
+
export function getValidation(): ValidationResult | undefined {
|
|
145
|
+
return validation;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Focus the visible button (hidden input cannot be focused). */
|
|
149
|
+
export function focus(): void {
|
|
150
|
+
buttonEl?.focus?.();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/** Scroll the field into view. Defaults to smooth + center. */
|
|
154
|
+
export function scrollIntoView(opts?: ScrollIntoViewOptions): void {
|
|
155
|
+
buttonEl?.scrollIntoView?.({
|
|
156
|
+
behavior: "smooth",
|
|
157
|
+
block: "center",
|
|
158
|
+
...opts,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
127
162
|
// $inspect("validation", validation);
|
|
128
163
|
</script>
|
|
129
164
|
|
|
@@ -155,6 +190,7 @@
|
|
|
155
190
|
{style}
|
|
156
191
|
>
|
|
157
192
|
<Button
|
|
193
|
+
bind:el={buttonEl}
|
|
158
194
|
type="button"
|
|
159
195
|
class={twMerge(
|
|
160
196
|
"w-full inline-block text-left py-2.5 px-3 border-0 bg-transparent",
|
|
@@ -183,8 +219,8 @@
|
|
|
183
219
|
{id}
|
|
184
220
|
{name}
|
|
185
221
|
use:validateAction={() => ({
|
|
186
|
-
enabled:
|
|
187
|
-
...(typeof
|
|
222
|
+
enabled: validateProp !== false,
|
|
223
|
+
...(typeof validateProp === "boolean"
|
|
188
224
|
? {
|
|
189
225
|
// Return actual messages (not reason names) because hidden inputs
|
|
190
226
|
// don't support el.validationMessage - the validate action preserves
|
|
@@ -202,8 +238,9 @@
|
|
|
202
238
|
}
|
|
203
239
|
},
|
|
204
240
|
}
|
|
205
|
-
:
|
|
241
|
+
: validateProp),
|
|
206
242
|
setValidationResult,
|
|
243
|
+
setDoValidate: (fn) => (_doValidate = fn),
|
|
207
244
|
})}
|
|
208
245
|
{required}
|
|
209
246
|
{disabled}
|
|
@@ -32,6 +32,13 @@ export interface Props extends InputWrapClassProps, Record<string, any> {
|
|
|
32
32
|
style?: string;
|
|
33
33
|
renderValue?: (rawValue: any) => string;
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
import { type ValidationResult } from "../../actions/validate.svelte.js";
|
|
36
|
+
declare const FieldLikeButton: import("svelte").Component<Props, {
|
|
37
|
+
validate: () => ValidationResult | undefined;
|
|
38
|
+
clearValidation: () => void;
|
|
39
|
+
getValidation: () => ValidationResult | undefined;
|
|
40
|
+
focus: () => void;
|
|
41
|
+
scrollIntoView: (opts?: ScrollIntoViewOptions) => void;
|
|
42
|
+
}, "value" | "input">;
|
|
36
43
|
type FieldLikeButton = ReturnType<typeof FieldLikeButton>;
|
|
37
44
|
export default FieldLikeButton;
|
|
@@ -51,7 +51,8 @@
|
|
|
51
51
|
renderSize = "sm",
|
|
52
52
|
required = false,
|
|
53
53
|
disabled = false,
|
|
54
|
-
validate
|
|
54
|
+
// Renamed local binding to avoid collision with `export function validate()` below.
|
|
55
|
+
validate: validateProp,
|
|
55
56
|
labelAfter,
|
|
56
57
|
below,
|
|
57
58
|
labelLeft,
|
|
@@ -72,6 +73,9 @@
|
|
|
72
73
|
|
|
73
74
|
let editMode = $state(false);
|
|
74
75
|
let hiddenInputEl: HTMLInputElement | undefined = $state();
|
|
76
|
+
// Visible interactive elements used by the imperative focus/scroll API.
|
|
77
|
+
let toggleBtnEl: HTMLButtonElement | undefined = $state();
|
|
78
|
+
let textareaEl: HTMLTextAreaElement | undefined = $state();
|
|
75
79
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- initialized by bind:clientWidth
|
|
76
80
|
let contentWidth: number = $state()!;
|
|
77
81
|
const isNarrow = $derived(contentWidth < 360);
|
|
@@ -124,6 +128,44 @@
|
|
|
124
128
|
let validation: ValidationResult | undefined = $state();
|
|
125
129
|
const setValidationResult = (res: ValidationResult) => (validation = res);
|
|
126
130
|
|
|
131
|
+
let _doValidate: (() => void) | undefined = $state();
|
|
132
|
+
|
|
133
|
+
/** Trigger validation now. Renders the inline message if invalid. */
|
|
134
|
+
export function validate(): ValidationResult | undefined {
|
|
135
|
+
_doValidate?.();
|
|
136
|
+
return validation;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Clear the inline validation message and reset `setCustomValidity`. */
|
|
140
|
+
export function clearValidation(): void {
|
|
141
|
+
validation = undefined;
|
|
142
|
+
hiddenInputEl?.setCustomValidity?.("");
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Current validation state, or undefined if validator has never run. */
|
|
146
|
+
export function getValidation(): ValidationResult | undefined {
|
|
147
|
+
return validation;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Focus the visible interactive element — the textarea in edit mode,
|
|
152
|
+
* otherwise the edit-toggle button. The hidden validation `<input>`
|
|
153
|
+
* itself cannot be focused.
|
|
154
|
+
*/
|
|
155
|
+
export function focus(): void {
|
|
156
|
+
if (editMode) textareaEl?.focus?.();
|
|
157
|
+
else toggleBtnEl?.focus?.();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/** Scroll the field into view. Defaults to smooth + center. */
|
|
161
|
+
export function scrollIntoView(opts?: ScrollIntoViewOptions): void {
|
|
162
|
+
(toggleBtnEl ?? textareaEl)?.scrollIntoView?.({
|
|
163
|
+
behavior: "smooth",
|
|
164
|
+
block: "center",
|
|
165
|
+
...opts,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
127
169
|
const TEXTAREA_CLS =
|
|
128
170
|
"w-full min-h-16 p-2 font-mono text-sm focus:outline-none focus:ring-0";
|
|
129
171
|
|
|
@@ -263,6 +305,7 @@
|
|
|
263
305
|
{#if editMode}
|
|
264
306
|
<textarea
|
|
265
307
|
bind:value
|
|
308
|
+
bind:this={textareaEl}
|
|
266
309
|
{id}
|
|
267
310
|
class={TEXTAREA_CLS}
|
|
268
311
|
{tabindex}
|
|
@@ -287,6 +330,7 @@
|
|
|
287
330
|
<button
|
|
288
331
|
type="button"
|
|
289
332
|
class={BTN_CLS}
|
|
333
|
+
bind:this={toggleBtnEl}
|
|
290
334
|
onclick={toggleMode}
|
|
291
335
|
{disabled}
|
|
292
336
|
use:tooltip={() => ({
|
|
@@ -309,9 +353,23 @@
|
|
|
309
353
|
{name}
|
|
310
354
|
{value}
|
|
311
355
|
bind:this={hiddenInputEl}
|
|
312
|
-
use:validateAction={() =>
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
356
|
+
use:validateAction={() => {
|
|
357
|
+
const customOpts = typeof validateProp === "object" && validateProp ? validateProp : {};
|
|
358
|
+
const userValidator = customOpts.customValidator;
|
|
359
|
+
return {
|
|
360
|
+
enabled: validateProp !== false,
|
|
361
|
+
...customOpts,
|
|
362
|
+
// Hidden inputs are barred from native constraint validation, so
|
|
363
|
+
// `required` on the element itself is a no-op. We enforce it here
|
|
364
|
+
// before delegating to the consumer's customValidator.
|
|
365
|
+
customValidator(val, ctx, el) {
|
|
366
|
+
if (required && (val == null || val === "")) {
|
|
367
|
+
return "This field requires attention. Please review and try again.";
|
|
368
|
+
}
|
|
369
|
+
return userValidator?.(val, ctx, el) || "";
|
|
370
|
+
},
|
|
371
|
+
setValidationResult,
|
|
372
|
+
setDoValidate: (fn) => (_doValidate = fn),
|
|
373
|
+
};
|
|
374
|
+
}}
|
|
317
375
|
/>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Snippet } from "svelte";
|
|
2
|
-
import type { ValidateOptions } from "../../actions/validate.svelte.js";
|
|
2
|
+
import type { ValidateOptions, ValidationResult } from "../../actions/validate.svelte.js";
|
|
3
3
|
import type { THC } from "../Thc/Thc.svelte";
|
|
4
4
|
import type { InputWrapClassProps } from "./types.js";
|
|
5
5
|
type SnippetWithId = Snippet<[{
|
|
@@ -25,6 +25,12 @@ export interface Props extends InputWrapClassProps, Record<string, any> {
|
|
|
25
25
|
style?: string;
|
|
26
26
|
onChange?: (value: string) => void;
|
|
27
27
|
}
|
|
28
|
-
declare const FieldObject: import("svelte").Component<Props, {
|
|
28
|
+
declare const FieldObject: import("svelte").Component<Props, {
|
|
29
|
+
validate: () => ValidationResult | undefined;
|
|
30
|
+
clearValidation: () => void;
|
|
31
|
+
getValidation: () => ValidationResult | undefined;
|
|
32
|
+
focus: () => void;
|
|
33
|
+
scrollIntoView: (opts?: ScrollIntoViewOptions) => void;
|
|
34
|
+
}, "value">;
|
|
29
35
|
type FieldObject = ReturnType<typeof FieldObject>;
|
|
30
36
|
export default FieldObject;
|
|
@@ -5,7 +5,10 @@
|
|
|
5
5
|
import { Debounced, watch } from "runed";
|
|
6
6
|
import { onDestroy, tick, type Snippet } from "svelte";
|
|
7
7
|
import { tooltip } from "../../actions/index.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
type ValidateOptions,
|
|
10
|
+
type ValidationResult,
|
|
11
|
+
} from "../../actions/validate.svelte.js";
|
|
9
12
|
import type { TranslateFn } from "../../types.js";
|
|
10
13
|
import { getId } from "../../utils/get-id.js";
|
|
11
14
|
import { isPlainObject } from "../../utils/is-plain-object.js";
|
|
@@ -136,7 +139,8 @@
|
|
|
136
139
|
required = false,
|
|
137
140
|
disabled = false,
|
|
138
141
|
//
|
|
139
|
-
validate
|
|
142
|
+
// Renamed local binding to avoid collision with `export function validate()` below.
|
|
143
|
+
validate: validateProp,
|
|
140
144
|
//
|
|
141
145
|
labelAfter,
|
|
142
146
|
below,
|
|
@@ -187,6 +191,34 @@
|
|
|
187
191
|
$effect(() => {
|
|
188
192
|
modal = modalDialog;
|
|
189
193
|
});
|
|
194
|
+
|
|
195
|
+
// Imperative API delegates to the inner FieldLikeButton trigger.
|
|
196
|
+
let triggerRef: FieldLikeButton | undefined = $state();
|
|
197
|
+
|
|
198
|
+
/** Trigger validation now. Renders the inline message if invalid. */
|
|
199
|
+
export function validate(): ValidationResult | undefined {
|
|
200
|
+
return triggerRef?.validate();
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** Clear the inline validation message. */
|
|
204
|
+
export function clearValidation(): void {
|
|
205
|
+
triggerRef?.clearValidation?.();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Current validation state. */
|
|
209
|
+
export function getValidation(): ValidationResult | undefined {
|
|
210
|
+
return triggerRef?.getValidation?.();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Focus the visible trigger button. */
|
|
214
|
+
export function focus(): void {
|
|
215
|
+
triggerRef?.focus?.();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** Scroll the field into view. */
|
|
219
|
+
export function scrollIntoView(opts?: ScrollIntoViewOptions): void {
|
|
220
|
+
triggerRef?.scrollIntoView?.(opts);
|
|
221
|
+
}
|
|
190
222
|
let innerValue = $state("");
|
|
191
223
|
let isFetching = $state(false);
|
|
192
224
|
let isUnmounted = false;
|
|
@@ -214,7 +246,7 @@
|
|
|
214
246
|
if (selected.length > cardinality) return "rangeOverflow";
|
|
215
247
|
|
|
216
248
|
// continue with provided validator
|
|
217
|
-
return (
|
|
249
|
+
return (validateProp as any)?.customValidator?.(value, context, el) || "";
|
|
218
250
|
},
|
|
219
251
|
t(reason: keyof ValidityStateFlags, value: any, fallback: string) {
|
|
220
252
|
// Unfortunately, for hidden, everything is a `customError` reason. So, we must generalize...
|
|
@@ -508,6 +540,7 @@
|
|
|
508
540
|
{@render trigger({ value, modal: modalDialog })}
|
|
509
541
|
{:else}
|
|
510
542
|
<FieldLikeButton
|
|
543
|
+
bind:this={triggerRef}
|
|
511
544
|
bind:value
|
|
512
545
|
bind:input={parentHiddenInputEl}
|
|
513
546
|
{name}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Item } from "@marianmeres/item-collection";
|
|
2
2
|
import { type Snippet } from "svelte";
|
|
3
|
-
import { type ValidateOptions } from "../../actions/validate.svelte.js";
|
|
3
|
+
import { type ValidateOptions, type ValidationResult } from "../../actions/validate.svelte.js";
|
|
4
4
|
import type { TranslateFn } from "../../types.js";
|
|
5
5
|
import { ModalDialog } from "../ModalDialog/index.js";
|
|
6
6
|
import { NotificationsStack } from "../Notifications/index.js";
|
|
@@ -62,6 +62,12 @@ export interface Props extends InputWrapClassProps, Record<string, any> {
|
|
|
62
62
|
itemIdPropName?: string;
|
|
63
63
|
onChange?: (value: string) => void;
|
|
64
64
|
}
|
|
65
|
-
declare const FieldOptions: import("svelte").Component<Props, {
|
|
65
|
+
declare const FieldOptions: import("svelte").Component<Props, {
|
|
66
|
+
validate: () => ValidationResult | undefined;
|
|
67
|
+
clearValidation: () => void;
|
|
68
|
+
getValidation: () => ValidationResult | undefined;
|
|
69
|
+
focus: () => void;
|
|
70
|
+
scrollIntoView: (opts?: ScrollIntoViewOptions) => void;
|
|
71
|
+
}, "value" | "input" | "modal">;
|
|
66
72
|
type FieldOptions = ReturnType<typeof FieldOptions>;
|
|
67
73
|
export default FieldOptions;
|
|
@@ -98,7 +98,8 @@
|
|
|
98
98
|
renderSize = "md",
|
|
99
99
|
required = false,
|
|
100
100
|
disabled = false,
|
|
101
|
-
validate
|
|
101
|
+
// Renamed local binding to avoid collision with `export function validate()` below.
|
|
102
|
+
validate: validateProp,
|
|
102
103
|
//
|
|
103
104
|
labelAfter,
|
|
104
105
|
inputAfter,
|
|
@@ -134,6 +135,35 @@
|
|
|
134
135
|
let validation: ValidationResult | undefined = $state();
|
|
135
136
|
const setValidationResult = (res: ValidationResult) => (validation = res);
|
|
136
137
|
|
|
138
|
+
let _doValidate: (() => void) | undefined = $state();
|
|
139
|
+
|
|
140
|
+
/** Trigger validation now. Renders the inline message if invalid. */
|
|
141
|
+
export function validate(): ValidationResult | undefined {
|
|
142
|
+
_doValidate?.();
|
|
143
|
+
return validation;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Clear the inline validation message and reset `setCustomValidity`. */
|
|
147
|
+
export function clearValidation(): void {
|
|
148
|
+
validation = undefined;
|
|
149
|
+
hiddenInputEl?.setCustomValidity?.("");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Current validation state, or undefined if validator has never run. */
|
|
153
|
+
export function getValidation(): ValidationResult | undefined {
|
|
154
|
+
return validation;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Focus the visible tel input. */
|
|
158
|
+
export function focus(): void {
|
|
159
|
+
input?.focus?.();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Scroll the field into view. Defaults to smooth + center. */
|
|
163
|
+
export function scrollIntoView(opts?: ScrollIntoViewOptions): void {
|
|
164
|
+
input?.scrollIntoView?.({ behavior: "smooth", block: "center", ...opts });
|
|
165
|
+
}
|
|
166
|
+
|
|
137
167
|
// Filtered country list
|
|
138
168
|
let countryList = $derived.by(() => {
|
|
139
169
|
if (!allowedCountries) return COUNTRIES;
|
|
@@ -316,20 +346,35 @@
|
|
|
316
346
|
/>
|
|
317
347
|
</InputWrap>
|
|
318
348
|
|
|
319
|
-
<!-- Hidden input for form submission and validation
|
|
320
|
-
|
|
349
|
+
<!-- Hidden input for form submission and validation.
|
|
350
|
+
Rendered whenever validation is enabled (default) OR `name` is set, so
|
|
351
|
+
imperative `validate()` works even when the field is used outside a
|
|
352
|
+
<form> / without a name. A hidden input without `name` is skipped by
|
|
353
|
+
FormData per the HTML spec, so this is invisible to form submission. -->
|
|
354
|
+
{#if name || validateProp !== false}
|
|
321
355
|
<input
|
|
322
356
|
type="hidden"
|
|
323
357
|
{name}
|
|
324
358
|
value={value ?? ""}
|
|
325
359
|
bind:this={hiddenInputEl}
|
|
326
360
|
use:validateAction={() => {
|
|
327
|
-
const customOpts =
|
|
361
|
+
const customOpts =
|
|
362
|
+
typeof validateProp === "object" && validateProp ? validateProp : {};
|
|
363
|
+
const innerValidator = customOpts.customValidator ?? validatePhoneNumber;
|
|
328
364
|
return {
|
|
329
|
-
enabled:
|
|
365
|
+
enabled: validateProp !== false,
|
|
330
366
|
...customOpts,
|
|
331
|
-
|
|
367
|
+
// Hidden inputs are barred from native constraint validation, so
|
|
368
|
+
// `required` on the element itself is a no-op. We enforce it here
|
|
369
|
+
// before delegating to the consumer's (or default) validator.
|
|
370
|
+
customValidator(val, ctx, el) {
|
|
371
|
+
if (required && (val == null || val === "")) {
|
|
372
|
+
return "This field requires attention. Please review and try again.";
|
|
373
|
+
}
|
|
374
|
+
return innerValidator(val, ctx, el) || "";
|
|
375
|
+
},
|
|
332
376
|
setValidationResult,
|
|
377
|
+
setDoValidate: (fn) => (_doValidate = fn),
|
|
333
378
|
};
|
|
334
379
|
}}
|
|
335
380
|
/>
|
|
@@ -52,6 +52,13 @@ export interface Props extends InputWrapClassProps, Record<string, any> {
|
|
|
52
52
|
t?: TranslateFn;
|
|
53
53
|
onChange?: (value: string) => void;
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
import { type ValidationResult } from "../../actions/validate.svelte.js";
|
|
56
|
+
declare const FieldPhoneNumber: import("svelte").Component<Props, {
|
|
57
|
+
validate: () => ValidationResult | undefined;
|
|
58
|
+
clearValidation: () => void;
|
|
59
|
+
getValidation: () => ValidationResult | undefined;
|
|
60
|
+
focus: () => void;
|
|
61
|
+
scrollIntoView: (opts?: ScrollIntoViewOptions) => void;
|
|
62
|
+
}, "value" | "input" | "country" | "dialCode" | "localNumber">;
|
|
56
63
|
type FieldPhoneNumber = ReturnType<typeof FieldPhoneNumber>;
|
|
57
64
|
export default FieldPhoneNumber;
|
|
@@ -38,7 +38,8 @@
|
|
|
38
38
|
required,
|
|
39
39
|
disabled,
|
|
40
40
|
renderSize = "md",
|
|
41
|
-
validate
|
|
41
|
+
// Renamed local binding to avoid collision with `export function validate()` below.
|
|
42
|
+
validate: validateProp,
|
|
42
43
|
//
|
|
43
44
|
class: classProp,
|
|
44
45
|
classRadioBox,
|
|
@@ -63,6 +64,38 @@
|
|
|
63
64
|
let validation = $state<ValidationResult | undefined>();
|
|
64
65
|
let invalid = $derived(validation && !validation?.valid);
|
|
65
66
|
|
|
67
|
+
// Refs to each rendered radio — used for the imperative validate() / focus().
|
|
68
|
+
// All radios in the group share validity (browser-level), so delegating to
|
|
69
|
+
// the first one is sufficient to run the validator.
|
|
70
|
+
let radioRefs: (FieldRadioInternal | undefined)[] = $state([]);
|
|
71
|
+
|
|
72
|
+
/** Trigger validation now. Renders the inline message if invalid. */
|
|
73
|
+
export function validate(): ValidationResult | undefined {
|
|
74
|
+
radioRefs[0]?.validate();
|
|
75
|
+
return validation;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Clear the inline validation message. */
|
|
79
|
+
export function clearValidation(): void {
|
|
80
|
+
for (const r of radioRefs) r?.clearValidation?.();
|
|
81
|
+
validation = undefined;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Current validation state. */
|
|
85
|
+
export function getValidation(): ValidationResult | undefined {
|
|
86
|
+
return validation;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Focus the first radio. */
|
|
90
|
+
export function focus(): void {
|
|
91
|
+
radioRefs[0]?.focus?.();
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Scroll the field into view. */
|
|
95
|
+
export function scrollIntoView(opts?: ScrollIntoViewOptions): void {
|
|
96
|
+
radioRefs[0]?.scrollIntoView?.(opts);
|
|
97
|
+
}
|
|
98
|
+
|
|
66
99
|
// $inspect(value);
|
|
67
100
|
</script>
|
|
68
101
|
|
|
@@ -75,6 +108,7 @@
|
|
|
75
108
|
<div class={twMerge("radios-box", "gap-y-2 grid p-2 mb-8", classProp)}>
|
|
76
109
|
{#each _options as o, i}
|
|
77
110
|
<FieldRadioInternal
|
|
111
|
+
bind:this={radioRefs[i]}
|
|
78
112
|
{name}
|
|
79
113
|
bind:group={value}
|
|
80
114
|
label={o.label}
|
|
@@ -84,7 +118,7 @@
|
|
|
84
118
|
{disabled}
|
|
85
119
|
{tabindex}
|
|
86
120
|
{required}
|
|
87
|
-
{
|
|
121
|
+
validate={validateProp}
|
|
88
122
|
{classRadioBox}
|
|
89
123
|
{classInputBox}
|
|
90
124
|
{classInput}
|