@happyvertical/smrt-svelte 0.43.4 → 0.43.6
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/AGENTS.md +23 -0
- package/README.md +23 -1
- package/dist/components/forms/AddressInput.svelte +155 -42
- package/dist/components/forms/AddressInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/CheckboxInput.svelte +40 -13
- package/dist/components/forms/CheckboxInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/DateRangeInput.svelte +132 -16
- package/dist/components/forms/DateRangeInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/DateTimeInput.svelte +48 -14
- package/dist/components/forms/DateTimeInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/Form.svelte +599 -70
- package/dist/components/forms/Form.svelte.d.ts +6 -2
- package/dist/components/forms/Form.svelte.d.ts.map +1 -1
- package/dist/components/forms/MeasurementInput.svelte +137 -31
- package/dist/components/forms/MeasurementInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/MoneyInput.svelte +42 -23
- package/dist/components/forms/MoneyInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/NumberInput.svelte +59 -27
- package/dist/components/forms/NumberInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/PhoneInput.svelte +51 -22
- package/dist/components/forms/PhoneInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/SelectInput.svelte +38 -22
- package/dist/components/forms/SelectInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/TextInput.svelte +32 -18
- package/dist/components/forms/TextInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/TextareaInput.svelte +25 -18
- package/dist/components/forms/TextareaInput.svelte.d.ts.map +1 -1
- package/dist/components/forms/__tests__/AddressInput.behavior.test.js +94 -0
- package/dist/components/forms/__tests__/Form.behavior.test.js +1 -1
- package/dist/components/forms/__tests__/Form.extraction.test.js +68 -0
- package/dist/components/forms/__tests__/Form.test.js +28 -0
- package/dist/components/forms/__tests__/Form.webmcp.test.js +2123 -114
- package/dist/components/forms/__tests__/FormMicButton.test.js +0 -1
- package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte +30 -0
- package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte.d.ts +7 -0
- package/dist/components/forms/__tests__/async-invalid-field.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/async-validation-form.fixture.svelte +19 -0
- package/dist/components/forms/__tests__/async-validation-form.fixture.svelte.d.ts +9 -0
- package/dist/components/forms/__tests__/async-validation-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte +21 -0
- package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte.d.ts +8 -0
- package/dist/components/forms/__tests__/async-validation-snapshot-form.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte +27 -0
- package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte.d.ts +9 -0
- package/dist/components/forms/__tests__/compound-registration-wrapper.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte +48 -0
- package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte.d.ts +10 -0
- package/dist/components/forms/__tests__/custom-registration-field.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte +26 -0
- package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte.d.ts +13 -0
- package/dist/components/forms/__tests__/form-extraction-values.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte +52 -0
- package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte.d.ts +16 -0
- package/dist/components/forms/__tests__/form-registration-lifecycle.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-with-fields.fixture.svelte +108 -5
- package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts +34 -0
- package/dist/components/forms/__tests__/form-with-fields.fixture.svelte.d.ts.map +1 -1
- package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte +24 -0
- package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte.d.ts +11 -0
- package/dist/components/forms/__tests__/form-with-policy-field.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte +69 -4
- package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte.d.ts +32 -0
- package/dist/components/forms/__tests__/form-with-structured-fields.fixture.svelte.d.ts.map +1 -1
- package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte +44 -0
- package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte.d.ts +9 -0
- package/dist/components/forms/__tests__/legacy-form-context.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/__tests__/policy-field.fixture.svelte +35 -0
- package/dist/components/forms/__tests__/policy-field.fixture.svelte.d.ts +8 -0
- package/dist/components/forms/__tests__/policy-field.fixture.svelte.d.ts.map +1 -0
- package/dist/components/forms/prepare-field-value.d.ts +5 -0
- package/dist/components/forms/prepare-field-value.d.ts.map +1 -0
- package/dist/components/forms/prepare-field-value.js +15 -0
- package/dist/state/form-context.d.ts +28 -6
- package/dist/state/form-context.d.ts.map +1 -1
- package/dist/state/form-context.js +75 -7
- package/dist/web/__tests__/webmcp-composed.child.fixture.svelte +62 -0
- package/dist/web/__tests__/webmcp-composed.child.fixture.svelte.d.ts +8 -0
- package/dist/web/__tests__/webmcp-composed.child.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/webmcp-composed.fixture.svelte +52 -0
- package/dist/web/__tests__/webmcp-composed.fixture.svelte.d.ts +12 -0
- package/dist/web/__tests__/webmcp-composed.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/webmcp-composed.integration.svelte.test.js +247 -0
- package/dist/web/__tests__/webmcp-ssr.fixture.svelte +13 -0
- package/dist/web/__tests__/webmcp-ssr.fixture.svelte.d.ts +19 -0
- package/dist/web/__tests__/webmcp-ssr.fixture.svelte.d.ts.map +1 -0
- package/dist/web/__tests__/webmcp-ui.test.js +209 -33
- package/dist/web/__tests__/webmcp.test.js +20 -0
- package/dist/web/webmcp-ui.d.ts.map +1 -1
- package/dist/web/webmcp-ui.js +50 -31
- package/dist/web/webmcp.svelte.d.ts.map +1 -1
- package/dist/web/webmcp.svelte.js +6 -1
- package/package.json +8 -6
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ControlInteractionEvent, type ControlInteractionRegistry } from '@happyvertical/smrt-ui/forms';
|
|
1
|
+
import { type ControlInteractionEvent, type ControlInteractionRegistry, type ControlSubject } from '@happyvertical/smrt-ui/forms';
|
|
2
2
|
import type { Snippet } from 'svelte';
|
|
3
3
|
import type { LLMModelId, STTAdapterType } from './types.js';
|
|
4
4
|
export interface Props {
|
|
@@ -16,7 +16,7 @@ export interface Props {
|
|
|
16
16
|
llmModel?: LLMModelId;
|
|
17
17
|
/** Called when form is submitted (if provided, prevents native submission) */
|
|
18
18
|
onsubmit?: (data: Record<string, unknown>) => void | Promise<void>;
|
|
19
|
-
/**
|
|
19
|
+
/** Let WebMCP propose field changes for local human review. */
|
|
20
20
|
webmcp?: boolean | {
|
|
21
21
|
name?: string;
|
|
22
22
|
description?: string;
|
|
@@ -29,8 +29,12 @@ export interface Props {
|
|
|
29
29
|
action?: string;
|
|
30
30
|
/** Stable identity used by control/agent interaction adapters. */
|
|
31
31
|
formId?: string;
|
|
32
|
+
/** Default record-qualified identity for registered fields. */
|
|
33
|
+
subject?: ControlSubject;
|
|
32
34
|
interactionRegistry?: ControlInteractionRegistry;
|
|
33
35
|
oninteraction?: (event: ControlInteractionEvent) => void;
|
|
36
|
+
/** Render the built-in human review surface for staged changes. */
|
|
37
|
+
stagedReview?: boolean;
|
|
34
38
|
id?: string;
|
|
35
39
|
name?: string;
|
|
36
40
|
class?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Form.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Form.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,
|
|
1
|
+
{"version":3,"file":"Form.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/Form.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,uBAAuB,EAC5B,KAAK,0BAA0B,EAG/B,KAAK,cAAc,EAIpB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAatC,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG7D,MAAM,WAAW,KAAK;IACpB,oBAAoB;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,8BAA8B;IAC9B,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,oCAAoC;IACpC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,iDAAiD;IACjD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,uBAAuB;IACvB,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,UAAU,CAAC;IACtB,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnE,+DAA+D;IAC/D,MAAM,CAAC,EAAE,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3D,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,CAAC;IACxB,iDAAiD;IACjD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kEAAkE;IAClE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,OAAO,CAAC,EAAE,cAAc,CAAC;IACzB,mBAAmB,CAAC,EAAE,0BAA0B,CAAC;IACjD,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,uBAAuB,KAAK,IAAI,CAAC;IACzD,mEAAmE;IACnE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AA04CD,QAAA,MAAM,IAAI;uBAjFe,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;kCAQZ,0BAA0B;MAyEZ,CAAC;AACnD,KAAK,IAAI,GAAG,UAAU,CAAC,OAAO,IAAI,CAAC,CAAC;AACpC,eAAe,IAAI,CAAC"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
3
|
-
import { onDestroy, onMount } from 'svelte';
|
|
4
3
|
import { useAppState } from '../../hooks/useAppState.svelte.js';
|
|
5
4
|
import { M } from '../../i18n/strings.forms.js';
|
|
6
5
|
import {
|
|
7
6
|
type FieldDefinition,
|
|
8
7
|
tryGetFormContext,
|
|
9
8
|
} from '../../state/form-context.js';
|
|
9
|
+
import { invalidStagedValue } from './prepare-field-value.js';
|
|
10
10
|
import type { MeasurementUnit, MeasurementValue } from './types.js';
|
|
11
11
|
|
|
12
12
|
const { t } = useI18n();
|
|
@@ -89,16 +89,24 @@ let {
|
|
|
89
89
|
|
|
90
90
|
const app = useAppState();
|
|
91
91
|
const formContext = tryGetFormContext();
|
|
92
|
+
const fieldOwnerId = $props.id();
|
|
93
|
+
const fieldOwnerToken = `smrt-rich-field-${fieldOwnerId}`;
|
|
92
94
|
|
|
93
95
|
const isSmrt = $derived(app.state.mode === 'smrt');
|
|
94
96
|
|
|
95
97
|
// Validation
|
|
96
|
-
|
|
97
|
-
if (
|
|
98
|
-
if (min !== undefined &&
|
|
99
|
-
if (max !== undefined &&
|
|
98
|
+
function measurementIsInRange(candidate: number | null): boolean {
|
|
99
|
+
if (candidate === null) return true;
|
|
100
|
+
if (min !== undefined && candidate < min) return false;
|
|
101
|
+
if (max !== undefined && candidate > max) return false;
|
|
100
102
|
return true;
|
|
101
|
-
}
|
|
103
|
+
}
|
|
104
|
+
function measurementMatchesStep(candidate: number): boolean {
|
|
105
|
+
if (!Number.isFinite(step) || step <= 0) return true;
|
|
106
|
+
const offset = (candidate - (min ?? 0)) / step;
|
|
107
|
+
return Math.abs(offset - Math.round(offset)) <= 1e-9;
|
|
108
|
+
}
|
|
109
|
+
const isInRange = $derived(measurementIsInRange(value));
|
|
102
110
|
const showInvalid = $derived((value !== null && !isInRange) || !!error);
|
|
103
111
|
|
|
104
112
|
function updateValue(newValue: number | null, newUnit?: MeasurementUnit) {
|
|
@@ -262,25 +270,31 @@ function parseSpokenMeasurement(text: string): MeasurementValue | null {
|
|
|
262
270
|
}
|
|
263
271
|
|
|
264
272
|
// Register with form context
|
|
265
|
-
|
|
273
|
+
$effect(() => {
|
|
266
274
|
if (formContext) {
|
|
267
|
-
|
|
268
|
-
if (min !== undefined && max !== undefined) {
|
|
269
|
-
rangeDesc = ` (between ${min} and ${max})`;
|
|
270
|
-
} else if (min !== undefined) {
|
|
271
|
-
rangeDesc = ` (minimum ${min})`;
|
|
272
|
-
} else if (max !== undefined) {
|
|
273
|
-
rangeDesc = ` (maximum ${max})`;
|
|
274
|
-
}
|
|
275
|
-
|
|
275
|
+
const registeredName = name;
|
|
276
276
|
const fieldDef: FieldDefinition = {
|
|
277
|
-
name,
|
|
277
|
+
name: registeredName,
|
|
278
|
+
ownerToken: fieldOwnerToken,
|
|
278
279
|
type: 'measurement',
|
|
279
|
-
label
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
280
|
+
get label() {
|
|
281
|
+
return label;
|
|
282
|
+
},
|
|
283
|
+
get description() {
|
|
284
|
+
let rangeDescription = '';
|
|
285
|
+
if (min !== undefined && max !== undefined) {
|
|
286
|
+
rangeDescription = ` (between ${min} and ${max})`;
|
|
287
|
+
} else if (min !== undefined) {
|
|
288
|
+
rangeDescription = ` (minimum ${min})`;
|
|
289
|
+
} else if (max !== undefined) {
|
|
290
|
+
rangeDescription = ` (maximum ${max})`;
|
|
291
|
+
}
|
|
292
|
+
return (
|
|
293
|
+
(description ||
|
|
294
|
+
'A measurement with value and unit (e.g., "12 feet 6 inches")') +
|
|
295
|
+
rangeDescription
|
|
296
|
+
);
|
|
297
|
+
},
|
|
284
298
|
setValue: (v: unknown) => {
|
|
285
299
|
if (v === null || v === undefined || v === '') {
|
|
286
300
|
updateValue(null);
|
|
@@ -302,17 +316,109 @@ onMount(() => {
|
|
|
302
316
|
}
|
|
303
317
|
},
|
|
304
318
|
getValue: () => (value !== null ? { value, unit } : null),
|
|
305
|
-
|
|
319
|
+
prepareValue: (candidate) => {
|
|
320
|
+
if (candidate === null || candidate === undefined || candidate === '') {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
if (typeof candidate === 'number') {
|
|
324
|
+
return Number.isFinite(candidate)
|
|
325
|
+
? { value: candidate, unit }
|
|
326
|
+
: invalidStagedValue();
|
|
327
|
+
}
|
|
328
|
+
if (typeof candidate === 'string') {
|
|
329
|
+
return parseSpokenMeasurement(candidate) ?? invalidStagedValue();
|
|
330
|
+
}
|
|
331
|
+
if (
|
|
332
|
+
typeof candidate !== 'object' ||
|
|
333
|
+
Array.isArray(candidate) ||
|
|
334
|
+
![Object.prototype, null].includes(Object.getPrototypeOf(candidate))
|
|
335
|
+
) {
|
|
336
|
+
return invalidStagedValue();
|
|
337
|
+
}
|
|
338
|
+
const measurement = candidate as Record<string, unknown>;
|
|
339
|
+
return {
|
|
340
|
+
value: measurement.value,
|
|
341
|
+
unit: measurement.unit,
|
|
342
|
+
...Object.fromEntries(
|
|
343
|
+
Object.entries(measurement).filter(
|
|
344
|
+
([key]) => key !== 'value' && key !== 'unit',
|
|
345
|
+
),
|
|
346
|
+
),
|
|
347
|
+
};
|
|
348
|
+
},
|
|
349
|
+
clear: () => {
|
|
350
|
+
updateValue(null);
|
|
351
|
+
return true;
|
|
352
|
+
},
|
|
353
|
+
get unit() {
|
|
354
|
+
return unit;
|
|
355
|
+
},
|
|
356
|
+
getState: () => ({ disabled }),
|
|
357
|
+
get constraints() {
|
|
358
|
+
return { required, min, max, step };
|
|
359
|
+
},
|
|
360
|
+
get webMcpSchema() {
|
|
361
|
+
return {
|
|
362
|
+
type: 'object',
|
|
363
|
+
additionalProperties: false,
|
|
364
|
+
properties: {
|
|
365
|
+
value: {
|
|
366
|
+
type: 'number',
|
|
367
|
+
...(min !== undefined ? { minimum: min } : {}),
|
|
368
|
+
...(max !== undefined ? { maximum: max } : {}),
|
|
369
|
+
...(Number.isFinite(step) &&
|
|
370
|
+
step > 0 &&
|
|
371
|
+
(min === undefined || measurementMatchesStep(0))
|
|
372
|
+
? { multipleOf: step }
|
|
373
|
+
: {}),
|
|
374
|
+
},
|
|
375
|
+
unit: { type: 'string', enum: [...units] },
|
|
376
|
+
},
|
|
377
|
+
...(required ? { required: ['value', 'unit'] } : {}),
|
|
378
|
+
};
|
|
379
|
+
},
|
|
380
|
+
validateValue: (candidate) => {
|
|
381
|
+
if (candidate === null || candidate === undefined || candidate === '') {
|
|
382
|
+
return !required;
|
|
383
|
+
}
|
|
384
|
+
let proposed: MeasurementValue | null = null;
|
|
385
|
+
if (
|
|
386
|
+
typeof candidate === 'object' &&
|
|
387
|
+
candidate !== null &&
|
|
388
|
+
!Array.isArray(candidate)
|
|
389
|
+
) {
|
|
390
|
+
const measurement = candidate as Record<string, unknown>;
|
|
391
|
+
if (
|
|
392
|
+
[Object.prototype, null].includes(
|
|
393
|
+
Object.getPrototypeOf(measurement),
|
|
394
|
+
) &&
|
|
395
|
+
Object.keys(measurement).every((key) =>
|
|
396
|
+
['value', 'unit'].includes(key),
|
|
397
|
+
) &&
|
|
398
|
+
Object.hasOwn(measurement, 'value') &&
|
|
399
|
+
Object.hasOwn(measurement, 'unit') &&
|
|
400
|
+
typeof measurement.value === 'number' &&
|
|
401
|
+
typeof measurement.unit === 'string'
|
|
402
|
+
) {
|
|
403
|
+
proposed = measurement as unknown as MeasurementValue;
|
|
404
|
+
}
|
|
405
|
+
} else if (typeof candidate === 'number') {
|
|
406
|
+
proposed = { value: candidate, unit };
|
|
407
|
+
} else {
|
|
408
|
+
proposed = parseSpokenMeasurement(String(candidate));
|
|
409
|
+
}
|
|
410
|
+
return (
|
|
411
|
+
proposed !== null &&
|
|
412
|
+
Number.isFinite(proposed.value) &&
|
|
413
|
+
units.includes(proposed.unit) &&
|
|
414
|
+
measurementIsInRange(proposed.value) &&
|
|
415
|
+
measurementMatchesStep(proposed.value)
|
|
416
|
+
);
|
|
417
|
+
},
|
|
306
418
|
validate: () =>
|
|
307
419
|
value === null ? !required : Number.isFinite(value) && isInRange,
|
|
308
420
|
};
|
|
309
|
-
formContext.registerField(fieldDef);
|
|
310
|
-
}
|
|
311
|
-
});
|
|
312
|
-
|
|
313
|
-
onDestroy(() => {
|
|
314
|
-
if (formContext) {
|
|
315
|
-
formContext.unregisterField(name);
|
|
421
|
+
return formContext.registerField(fieldDef);
|
|
316
422
|
}
|
|
317
423
|
});
|
|
318
424
|
|
|
@@ -334,7 +440,7 @@ function handleUnitChange(e: Event) {
|
|
|
334
440
|
}
|
|
335
441
|
</script>
|
|
336
442
|
|
|
337
|
-
<div class="smrt-measurement">
|
|
443
|
+
<div class="smrt-measurement" data-smrt-field-owner={fieldOwnerToken}>
|
|
338
444
|
{#if label}
|
|
339
445
|
<label for={name} class="smrt-label">
|
|
340
446
|
{label}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MeasurementInput.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/MeasurementInput.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGpE,MAAM,WAAW,KAAK;IACpB,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,8BAA8B;IAC9B,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,qCAAqC;IACrC,KAAK,CAAC,EAAE,eAAe,EAAE,CAAC;IAC1B,oBAAoB;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,gBAAgB,GAAG,IAAI,KAAK,IAAI,CAAC;CAC3D;
|
|
1
|
+
{"version":3,"file":"MeasurementInput.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/MeasurementInput.svelte.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAGpE,MAAM,WAAW,KAAK;IACpB,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,8BAA8B;IAC9B,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,qCAAqC;IACrC,KAAK,CAAC,EAAE,eAAe,EAAE,CAAC;IAC1B,oBAAoB;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,gBAAgB,GAAG,IAAI,KAAK,IAAI,CAAC;CAC3D;AAgcD,QAAA,MAAM,gBAAgB,yDAAwC,CAAC;AAC/D,KAAK,gBAAgB,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAC5D,eAAe,gBAAgB,CAAC"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
3
|
-
import { onDestroy, onMount } from 'svelte';
|
|
4
3
|
import { useAppState } from '../../hooks/useAppState.svelte.js';
|
|
5
4
|
import { M } from '../../i18n/strings.forms.js';
|
|
6
5
|
import {
|
|
7
6
|
type FieldDefinition,
|
|
8
7
|
tryGetFormContext,
|
|
9
8
|
} from '../../state/form-context.js';
|
|
9
|
+
import { invalidStagedValue } from './prepare-field-value.js';
|
|
10
10
|
|
|
11
11
|
const { t } = useI18n();
|
|
12
12
|
|
|
@@ -216,22 +216,28 @@ function parseSpokenMoney(text: string): number | null {
|
|
|
216
216
|
}
|
|
217
217
|
|
|
218
218
|
// Register with form context
|
|
219
|
-
|
|
219
|
+
$effect(() => {
|
|
220
220
|
if (formContext) {
|
|
221
|
-
|
|
222
|
-
if (min !== undefined && max !== undefined) {
|
|
223
|
-
rangeDesc = ` (between $${(min / 100).toFixed(2)} and $${(max / 100).toFixed(2)})`;
|
|
224
|
-
} else if (min !== undefined) {
|
|
225
|
-
rangeDesc = ` (minimum $${(min / 100).toFixed(2)})`;
|
|
226
|
-
} else if (max !== undefined) {
|
|
227
|
-
rangeDesc = ` (maximum $${(max / 100).toFixed(2)})`;
|
|
228
|
-
}
|
|
229
|
-
|
|
221
|
+
const registeredName = name;
|
|
230
222
|
const fieldDef: FieldDefinition = {
|
|
231
|
-
name,
|
|
223
|
+
name: registeredName,
|
|
232
224
|
type: 'money',
|
|
233
|
-
label
|
|
234
|
-
|
|
225
|
+
get label() {
|
|
226
|
+
return label;
|
|
227
|
+
},
|
|
228
|
+
get description() {
|
|
229
|
+
let rangeDescription = '';
|
|
230
|
+
if (min !== undefined && max !== undefined) {
|
|
231
|
+
rangeDescription = ` (between $${(min / 100).toFixed(2)} and $${(max / 100).toFixed(2)})`;
|
|
232
|
+
} else if (min !== undefined) {
|
|
233
|
+
rangeDescription = ` (minimum $${(min / 100).toFixed(2)})`;
|
|
234
|
+
} else if (max !== undefined) {
|
|
235
|
+
rangeDescription = ` (maximum $${(max / 100).toFixed(2)})`;
|
|
236
|
+
}
|
|
237
|
+
return (
|
|
238
|
+
(description || 'A monetary amount in dollars') + rangeDescription
|
|
239
|
+
);
|
|
240
|
+
},
|
|
235
241
|
setValue: (v: unknown) => {
|
|
236
242
|
if (v === null || v === undefined || v === '') {
|
|
237
243
|
updateValue(null);
|
|
@@ -252,17 +258,30 @@ onMount(() => {
|
|
|
252
258
|
}
|
|
253
259
|
},
|
|
254
260
|
getValue: () => value,
|
|
255
|
-
|
|
261
|
+
prepareValue: (candidate) => {
|
|
262
|
+
if (candidate === null || candidate === undefined || candidate === '') {
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
if (typeof candidate === 'number') {
|
|
266
|
+
return candidate;
|
|
267
|
+
}
|
|
268
|
+
if (typeof candidate !== 'string') return invalidStagedValue();
|
|
269
|
+
return parseSpokenMoney(candidate) ?? invalidStagedValue();
|
|
270
|
+
},
|
|
271
|
+
getState: () => ({ disabled }),
|
|
272
|
+
get constraints() {
|
|
273
|
+
return { required, min, max };
|
|
274
|
+
},
|
|
275
|
+
validateValue: (candidate) =>
|
|
276
|
+
(candidate === null && !required) ||
|
|
277
|
+
(typeof candidate === 'number' &&
|
|
278
|
+
Number.isSafeInteger(candidate) &&
|
|
279
|
+
(min === undefined || candidate >= min) &&
|
|
280
|
+
(max === undefined || candidate <= max)),
|
|
256
281
|
validate: () =>
|
|
257
|
-
value === null ? !required : Number.
|
|
282
|
+
value === null ? !required : Number.isSafeInteger(value) && isInRange,
|
|
258
283
|
};
|
|
259
|
-
formContext.registerField(fieldDef);
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
onDestroy(() => {
|
|
264
|
-
if (formContext) {
|
|
265
|
-
formContext.unregisterField(name);
|
|
284
|
+
return formContext.registerField(fieldDef);
|
|
266
285
|
}
|
|
267
286
|
});
|
|
268
287
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MoneyInput.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/MoneyInput.svelte.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,KAAK;IACpB,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACzB,6BAA6B;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CAC3C;
|
|
1
|
+
{"version":3,"file":"MoneyInput.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/MoneyInput.svelte.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,KAAK;IACpB,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;IACzB,6BAA6B;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CAC3C;AAuTD,QAAA,MAAM,UAAU,gDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
3
|
-
import { onDestroy, onMount } from 'svelte';
|
|
4
3
|
import { useAppState } from '../../hooks/useAppState.svelte.js';
|
|
5
4
|
import { M } from '../../i18n/strings.forms.js';
|
|
6
5
|
import {
|
|
7
6
|
type FieldDefinition,
|
|
8
7
|
tryGetFormContext,
|
|
9
8
|
} from '../../state/form-context.js';
|
|
9
|
+
import { invalidStagedValue } from './prepare-field-value.js';
|
|
10
10
|
|
|
11
11
|
const { t } = useI18n();
|
|
12
12
|
|
|
@@ -55,12 +55,18 @@ const formContext = tryGetFormContext();
|
|
|
55
55
|
const isSmrt = $derived(app.state.mode === 'smrt');
|
|
56
56
|
|
|
57
57
|
// Validation
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
if (min !== undefined &&
|
|
61
|
-
if (max !== undefined &&
|
|
58
|
+
function numberIsInRange(candidate: number | null): boolean {
|
|
59
|
+
if (candidate === null) return true;
|
|
60
|
+
if (min !== undefined && candidate < min) return false;
|
|
61
|
+
if (max !== undefined && candidate > max) return false;
|
|
62
62
|
return true;
|
|
63
|
-
}
|
|
63
|
+
}
|
|
64
|
+
function numberMatchesStep(candidate: number): boolean {
|
|
65
|
+
if (!Number.isFinite(step) || step <= 0) return true;
|
|
66
|
+
const offset = (candidate - (min ?? 0)) / step;
|
|
67
|
+
return Math.abs(offset - Math.round(offset)) <= 1e-9;
|
|
68
|
+
}
|
|
69
|
+
const isInRange = $derived(numberIsInRange(value));
|
|
64
70
|
const showInvalid = $derived(value !== null && !isInRange);
|
|
65
71
|
|
|
66
72
|
function updateValue(newValue: number | null) {
|
|
@@ -143,22 +149,26 @@ function parseSpokenNumber(text: string): number | null {
|
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
// Register with form context
|
|
146
|
-
|
|
152
|
+
$effect(() => {
|
|
147
153
|
if (formContext) {
|
|
148
|
-
|
|
149
|
-
if (min !== undefined && max !== undefined) {
|
|
150
|
-
rangeDesc = ` (between ${min} and ${max})`;
|
|
151
|
-
} else if (min !== undefined) {
|
|
152
|
-
rangeDesc = ` (minimum ${min})`;
|
|
153
|
-
} else if (max !== undefined) {
|
|
154
|
-
rangeDesc = ` (maximum ${max})`;
|
|
155
|
-
}
|
|
156
|
-
|
|
154
|
+
const registeredName = name;
|
|
157
155
|
const fieldDef: FieldDefinition = {
|
|
158
|
-
name,
|
|
156
|
+
name: registeredName,
|
|
159
157
|
type: 'number',
|
|
160
|
-
label
|
|
161
|
-
|
|
158
|
+
get label() {
|
|
159
|
+
return label;
|
|
160
|
+
},
|
|
161
|
+
get description() {
|
|
162
|
+
let rangeDescription = '';
|
|
163
|
+
if (min !== undefined && max !== undefined) {
|
|
164
|
+
rangeDescription = ` (between ${min} and ${max})`;
|
|
165
|
+
} else if (min !== undefined) {
|
|
166
|
+
rangeDescription = ` (minimum ${min})`;
|
|
167
|
+
} else if (max !== undefined) {
|
|
168
|
+
rangeDescription = ` (maximum ${max})`;
|
|
169
|
+
}
|
|
170
|
+
return (description || 'A number') + rangeDescription;
|
|
171
|
+
},
|
|
162
172
|
setValue: (v: unknown) => {
|
|
163
173
|
if (v === null || v === undefined || v === '') {
|
|
164
174
|
updateValue(null);
|
|
@@ -175,17 +185,39 @@ onMount(() => {
|
|
|
175
185
|
}
|
|
176
186
|
},
|
|
177
187
|
getValue: () => value,
|
|
178
|
-
|
|
188
|
+
prepareValue: (candidate) => {
|
|
189
|
+
if (candidate === null || candidate === undefined || candidate === '') {
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
if (typeof candidate === 'number') {
|
|
193
|
+
return candidate;
|
|
194
|
+
}
|
|
195
|
+
if (typeof candidate !== 'string') return invalidStagedValue();
|
|
196
|
+
return parseSpokenNumber(candidate) ?? invalidStagedValue();
|
|
197
|
+
},
|
|
198
|
+
getState: () => ({ disabled }),
|
|
199
|
+
get constraints() {
|
|
200
|
+
return { required, min, max, step };
|
|
201
|
+
},
|
|
202
|
+
validateValue: (candidate) => {
|
|
203
|
+
if (candidate === null || candidate === undefined || candidate === '') {
|
|
204
|
+
return !required;
|
|
205
|
+
}
|
|
206
|
+
const proposed =
|
|
207
|
+
typeof candidate === 'number'
|
|
208
|
+
? candidate
|
|
209
|
+
: parseSpokenNumber(String(candidate));
|
|
210
|
+
return (
|
|
211
|
+
proposed !== null &&
|
|
212
|
+
Number.isFinite(proposed) &&
|
|
213
|
+
numberIsInRange(proposed) &&
|
|
214
|
+
numberMatchesStep(proposed)
|
|
215
|
+
);
|
|
216
|
+
},
|
|
179
217
|
validate: () =>
|
|
180
218
|
value === null ? !required : Number.isFinite(value) && isInRange,
|
|
181
219
|
};
|
|
182
|
-
formContext.registerField(fieldDef);
|
|
183
|
-
}
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
onDestroy(() => {
|
|
187
|
-
if (formContext) {
|
|
188
|
-
formContext.unregisterField(name);
|
|
220
|
+
return formContext.registerField(fieldDef);
|
|
189
221
|
}
|
|
190
222
|
});
|
|
191
223
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NumberInput.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/NumberInput.svelte.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,KAAK;IACpB,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,oBAAoB;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CAC3C;
|
|
1
|
+
{"version":3,"file":"NumberInput.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/NumberInput.svelte.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,KAAK;IACpB,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,oBAAoB;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,oBAAoB;IACpB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;CAC3C;AAuOD,QAAA,MAAM,WAAW,gDAAwC,CAAC;AAC1D,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC;AAClD,eAAe,WAAW,CAAC"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { useI18n } from '@happyvertical/smrt-ui/i18n';
|
|
3
|
-
import { onDestroy, onMount } from 'svelte';
|
|
4
3
|
import { useAppState } from '../../hooks/useAppState.svelte.js';
|
|
5
4
|
import { useSTT } from '../../hooks/useSTT.svelte.js';
|
|
6
5
|
import { M } from '../../i18n/strings.forms.js';
|
|
@@ -9,6 +8,10 @@ import {
|
|
|
9
8
|
type FieldDefinition,
|
|
10
9
|
tryGetFormContext,
|
|
11
10
|
} from '../../state/form-context.js';
|
|
11
|
+
import {
|
|
12
|
+
invalidStagedValue,
|
|
13
|
+
prepareTextFieldValue,
|
|
14
|
+
} from './prepare-field-value.js';
|
|
12
15
|
|
|
13
16
|
const { t } = useI18n();
|
|
14
17
|
|
|
@@ -60,11 +63,13 @@ const isInitializing = $derived(stt.isInitializing);
|
|
|
60
63
|
const downloadProgress = $derived(stt.downloadProgress);
|
|
61
64
|
|
|
62
65
|
// Phone validation (basic North American format)
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
function isValidPhoneValue(candidate: unknown): boolean {
|
|
67
|
+
if (typeof candidate !== 'string') return false;
|
|
68
|
+
if (!candidate) return !required;
|
|
69
|
+
const digits = candidate.replace(/\D/g, '');
|
|
70
|
+
return digits.length === 10 || (digits.length === 11 && digits[0] === '1');
|
|
71
|
+
}
|
|
72
|
+
const isValidPhone = $derived.by(() => isValidPhoneValue(value));
|
|
68
73
|
const showInvalid = $derived(value && !isValidPhone);
|
|
69
74
|
|
|
70
75
|
function updateValue(newValue: string) {
|
|
@@ -91,7 +96,7 @@ function formatPhoneNumber(input: string): string {
|
|
|
91
96
|
}
|
|
92
97
|
|
|
93
98
|
// Parse spoken phone number
|
|
94
|
-
function
|
|
99
|
+
function extractSpokenPhoneDigits(text: string): string {
|
|
95
100
|
const wordNumbers: Record<string, string> = {
|
|
96
101
|
zero: '0',
|
|
97
102
|
oh: '0',
|
|
@@ -130,35 +135,59 @@ function parseSpokenPhone(text: string): string {
|
|
|
130
135
|
result = result.replace(/\btriple\s*(\d)/gi, '$1$1$1');
|
|
131
136
|
|
|
132
137
|
// Extract just digits
|
|
133
|
-
|
|
138
|
+
return result.replace(/\D/g, '');
|
|
139
|
+
}
|
|
134
140
|
|
|
135
|
-
|
|
141
|
+
function parseSpokenPhone(text: string): string {
|
|
142
|
+
return formatPhoneNumber(extractSpokenPhoneDigits(text));
|
|
136
143
|
}
|
|
137
144
|
|
|
138
145
|
// Register with form context
|
|
139
|
-
|
|
146
|
+
$effect(() => {
|
|
140
147
|
if (formContext) {
|
|
148
|
+
const registeredName = name;
|
|
141
149
|
const fieldDef: FieldDefinition = {
|
|
142
|
-
name,
|
|
150
|
+
name: registeredName,
|
|
143
151
|
type: 'phone',
|
|
144
|
-
label
|
|
145
|
-
|
|
152
|
+
get label() {
|
|
153
|
+
return label;
|
|
154
|
+
},
|
|
155
|
+
get description() {
|
|
156
|
+
return description || 'Phone number';
|
|
157
|
+
},
|
|
146
158
|
setValue: (v: unknown) => {
|
|
147
159
|
const strVal = String(v ?? '');
|
|
148
160
|
const formatted = parseSpokenPhone(strVal);
|
|
149
161
|
updateValue(formatted);
|
|
150
162
|
},
|
|
151
163
|
getValue: () => value,
|
|
152
|
-
|
|
153
|
-
|
|
164
|
+
prepareValue: (candidate) => {
|
|
165
|
+
const prepared = prepareTextFieldValue(candidate);
|
|
166
|
+
if (prepared === '') return '';
|
|
167
|
+
const digits = extractSpokenPhoneDigits(prepared);
|
|
168
|
+
if (
|
|
169
|
+
digits.length !== 10 &&
|
|
170
|
+
!(digits.length === 11 && digits[0] === '1')
|
|
171
|
+
) {
|
|
172
|
+
return prepared;
|
|
173
|
+
}
|
|
174
|
+
return formatPhoneNumber(digits) || invalidStagedValue();
|
|
175
|
+
},
|
|
176
|
+
getState: () => ({ disabled }),
|
|
177
|
+
get constraints() {
|
|
178
|
+
return { required };
|
|
179
|
+
},
|
|
180
|
+
validateValue: (candidate) => {
|
|
181
|
+
if (typeof candidate !== 'string') return false;
|
|
182
|
+
if (candidate === '') return !required;
|
|
183
|
+
const digits = extractSpokenPhoneDigits(candidate);
|
|
184
|
+
return (
|
|
185
|
+
digits.length === 10 || (digits.length === 11 && digits[0] === '1')
|
|
186
|
+
);
|
|
187
|
+
},
|
|
188
|
+
validate: () => isValidPhoneValue(value),
|
|
154
189
|
};
|
|
155
|
-
formContext.registerField(fieldDef);
|
|
156
|
-
}
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
onDestroy(() => {
|
|
160
|
-
if (formContext) {
|
|
161
|
-
formContext.unregisterField(name);
|
|
190
|
+
return formContext.registerField(fieldDef);
|
|
162
191
|
}
|
|
163
192
|
});
|
|
164
193
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PhoneInput.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/PhoneInput.svelte.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"PhoneInput.svelte.d.ts","sourceRoot":"","sources":["../../../src/components/forms/PhoneInput.svelte.ts"],"names":[],"mappings":"AAkBA,MAAM,WAAW,KAAK;IACpB,iBAAiB;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,kBAAkB;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,+BAA+B;IAC/B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qBAAqB;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,gCAAgC;IAChC,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpC;AAwUD,QAAA,MAAM,UAAU,gDAAwC,CAAC;AACzD,KAAK,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAChD,eAAe,UAAU,CAAC"}
|