Skip to content

Commit 5d84164

Browse files
committed
[form] Add additional property key validator
1 parent 16ce8af commit 5d84164

File tree

6 files changed

+94
-3
lines changed

6 files changed

+94
-3
lines changed

.changeset/old-llamas-cry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@sjsf/form": minor
3+
---
4+
5+
Add additional property key validator

packages/form/src/form/context/context.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import type { Errors } from "../errors.js";
1414
import type { FormValidator } from "../validator.js";
1515
import type { Icons } from "../icons.js";
1616
import type { FormMerger } from "../merger.js";
17+
import type { Config } from '../config.js';
1718

1819
export type IconOrTranslationData = {
1920
[L in Label]: [L, ...Labels[L]];
@@ -42,6 +43,7 @@ export interface FormContext {
4243
IconOrTranslation: Component<{ data: IconOrTranslationData }>;
4344
/** @deprecated use `IconOrTranslation` instead */
4445
iconOrTranslation: Snippet<[IconOrTranslationData]>;
46+
validateAdditionalPropertyKey(config: Config, key: string): boolean;
4547
}
4648

4749
const FORM_CONTEXT = Symbol("form-context");

packages/form/src/form/fields/object/object-key-input.svelte

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@
6161
return;
6262
}
6363
const newKey = generateNewKey(key.value, objCtx.newKeySeparator, obj);
64+
if (!ctx.validateAdditionalPropertyKey(config, newKey)) {
65+
return;
66+
}
6467
obj[newKey] = obj[property];
6568
delete obj[property];
6669
},

packages/form/src/form/form-base.svelte

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@
168168
get inputsValidationMode() {
169169
return inputsValidationMode
170170
},
171+
validateAdditionalPropertyKey() {
172+
return true
173+
},
171174
get isChanged() {
172175
return isChanged
173176
},

packages/form/src/form/use-form.svelte.ts

Lines changed: 69 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import { SvelteMap } from "svelte/reactivity";
55
import type { SchedulerYield } from "@/lib/scheduler.js";
66
import type { Schema, SchemaValue } from "@/core/schema.js";
77

8-
import { type FormValidator, type ValidationError } from "./validator.js";
8+
import {
9+
ADDITIONAL_PROPERTY_KEY_ERROR,
10+
type AdditionalPropertyKeyError,
11+
type AdditionalPropertyKeyValidator,
12+
type FormValidator,
13+
type ValidationError,
14+
} from "./validator.js";
915
import type { Components } from "./component.js";
1016
import type { Widgets } from "./widgets.js";
1117
import type { Translation } from "./translation.js";
@@ -30,7 +36,11 @@ import {
3036
pathToId,
3137
} from "./id-schema.js";
3238
import IconOrTranslation from "./icon-or-translation.svelte";
39+
import type { Config } from "./config.js";
3340

41+
/**
42+
* @deprecated use `UseFormOptions2`
43+
*/
3444
export interface UseFormOptions<T, E> {
3545
validator: FormValidator<E>;
3646
schema: Schema;
@@ -98,6 +108,10 @@ export interface UseFormOptions<T, E> {
98108
schedulerYield?: SchedulerYield;
99109
}
100110

111+
export interface UseFormOptions2<T, E> extends UseFormOptions<T, E> {
112+
additionalPropertyKeyValidator?: AdditionalPropertyKeyValidator;
113+
}
114+
101115
export interface FormState<T, E> {
102116
value: T | undefined;
103117
formValue: SchemaValue | undefined;
@@ -119,9 +133,31 @@ export interface FormAPI<T, E> extends FormState<T, E> {
119133

120134
type Value = SchemaValue | undefined;
121135

136+
/**
137+
* @deprecated use `createForm2`
138+
*/
122139
export function createForm<T, E>(
123140
options: UseFormOptions<T, E>
124141
): [FormContext, FormAPI<T, E>] {
142+
return createForm2(options);
143+
}
144+
145+
type FormValueFromOptions<O extends UseFormOptions2<any, any>> =
146+
O extends UseFormOptions2<infer T, any> ? T : never;
147+
148+
type ValidatorErrorFromOptions<O extends UseFormOptions2<any, any>> =
149+
O extends UseFormOptions2<any, infer E> ? E : never;
150+
151+
export function createForm2<
152+
const O extends UseFormOptions2<any, any>,
153+
T = FormValueFromOptions<O>,
154+
VE = ValidatorErrorFromOptions<O>,
155+
E = O extends {
156+
additionalPropertyKeyValidator: AdditionalPropertyKeyValidator;
157+
}
158+
? VE | AdditionalPropertyKeyError
159+
: VE,
160+
>(options: O): [FormContext, FormAPI<T, E>] {
125161
const merger = $derived(
126162
options.merger ?? new DefaultFormMerger(options.validator, options.schema)
127163
);
@@ -213,12 +249,34 @@ export function createForm<T, E>(
213249
}, 0);
214250
})
215251
);
252+
const additionalPropertyKeyValidator = $derived.by(() => {
253+
const validator = options.additionalPropertyKeyValidator;
254+
return validator
255+
? (config: Config, key: string) => {
256+
const instanceId = config.idSchema.$id;
257+
const messages = validator.validateAdditionalPropertyKey(key);
258+
errors.set(
259+
instanceId,
260+
messages.map((message) => ({
261+
instanceId,
262+
propertyTitle: config.title,
263+
message,
264+
error: ADDITIONAL_PROPERTY_KEY_ERROR as E,
265+
}))
266+
);
267+
return messages.length === 0;
268+
}
269+
: () => true;
270+
});
216271

217272
return [
218273
{
219274
get inputsValidationMode() {
220275
return inputsValidationMode;
221276
},
277+
get validateAdditionalPropertyKey() {
278+
return additionalPropertyKeyValidator;
279+
},
222280
get isSubmitted() {
223281
return isSubmitted;
224282
},
@@ -358,11 +416,19 @@ export function createForm<T, E>(
358416
];
359417
}
360418

419+
/**
420+
* Create a FormAPI and set form context
421+
* @deprecated use `useForm2`
422+
*/
423+
export function useForm<T, E>(options: UseFormOptions<T, E>): FormAPI<T, E> {
424+
return useForm2(options);
425+
}
426+
361427
/**
362428
* Create a FormAPI and set form context
363429
*/
364-
export function useForm<T, E>(options: UseFormOptions<T, E>) {
365-
const [ctx, api] = createForm(options);
430+
export function useForm2<O extends UseFormOptions2<any, any>>(options: O) {
431+
const [ctx, api] = createForm2(options);
366432
setFromContext(ctx);
367433
return api;
368434
}

packages/form/src/form/validator.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,15 @@ export interface FormValidator<E = unknown> extends Validator {
2929
fieldData: SchemaValue | undefined
3030
): ValidationError<E>[];
3131
}
32+
33+
export const ADDITIONAL_PROPERTY_KEY_ERROR = Symbol(
34+
"additional-property-key-error"
35+
);
36+
export type AdditionalPropertyKeyError = typeof ADDITIONAL_PROPERTY_KEY_ERROR;
37+
38+
export interface AdditionalPropertyKeyValidator {
39+
/**
40+
* Additional property key validation
41+
*/
42+
validateAdditionalPropertyKey: (key: string) => string[];
43+
}

0 commit comments

Comments
 (0)