Skip to content

Commit ef72450

Browse files
committed
[playground] Allow to customize validation events and modifiers
1 parent d05c384 commit ef72450

File tree

4 files changed

+151
-54
lines changed

4 files changed

+151
-54
lines changed

.changeset/twenty-mirrors-clap.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"playground": minor
3+
---
4+
5+
Allow to customize validation events and modifiers

apps/playground/src/app.svelte

Lines changed: 110 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,39 @@
11
<script lang="ts">
2-
import { SvelteMap } from 'svelte/reactivity';
2+
import { SvelteMap } from "svelte/reactivity";
33
import Ajv from "ajv";
4-
import { ON_BLUR, ON_CHANGE, ON_INPUT, AFTER_CHANGED, AFTER_SUBMITTED, AFTER_TOUCHED, useForm2, SimpleForm } from "@sjsf/form";
5-
import { translation, handleValidationProcessError } from "@sjsf/form/translations/en";
4+
import {
5+
ON_BLUR,
6+
ON_CHANGE,
7+
ON_INPUT,
8+
AFTER_CHANGED,
9+
AFTER_SUBMITTED,
10+
AFTER_TOUCHED,
11+
useForm2,
12+
SimpleForm,
13+
ON_ARRAY_CHANGE,
14+
ON_OBJECT_CHANGE,
15+
} from "@sjsf/form";
16+
import {
17+
translation,
18+
handleValidationProcessError,
19+
} from "@sjsf/form/translations/en";
620
import { addFormComponents, DEFAULT_AJV_CONFIG } from "@sjsf/ajv8-validator";
7-
import { focusOnFirstError } from '@sjsf/form/focus-on-first-error';
8-
import { setThemeContext } from '@sjsf/shadcn-theme'
9-
import { components } from '@sjsf/shadcn-theme/default'
21+
import { focusOnFirstError } from "@sjsf/form/focus-on-first-error";
22+
import { setThemeContext } from "@sjsf/shadcn-theme";
23+
import { components } from "@sjsf/shadcn-theme/default";
1024
11-
import { themes, themeStyles } from './themes'
12-
import { icons, iconsStyles } from './icons'
25+
import { themes, themeStyles } from "./themes";
26+
import { icons, iconsStyles } from "./icons";
1327
import { ShadowHost } from "./shadow";
1428
import Github from "./github.svelte";
1529
import OpenBook from "./open-book.svelte";
1630
import ThemePicker from "./theme-picker.svelte";
17-
import Editor from './editor.svelte';
18-
import Debug from './debug.svelte';
31+
import Editor from "./editor.svelte";
32+
import Debug from "./debug.svelte";
1933
2034
import { samples } from "./samples";
21-
import { validators } from './validators';
35+
import { validators } from "./validators";
36+
import Bits from "./bits.svelte";
2237
2338
function isSampleName(name: unknown): name is keyof typeof samples {
2439
return typeof name === "string" && name in samples;
@@ -88,14 +103,11 @@
88103
: selectValidator("ajv8", true);
89104
let validatorName = $state(initialValidatorName);
90105
const validator = $derived.by(() => {
91-
const Val = samples[sampleName].Validator
106+
const Val = samples[sampleName].Validator;
92107
if (Val) {
93-
return new Val(
94-
addFormComponents(new Ajv(DEFAULT_AJV_CONFIG)),
95-
uiSchema
96-
)
108+
return new Val(addFormComponents(new Ajv(DEFAULT_AJV_CONFIG)), uiSchema);
97109
}
98-
return validators[validatorName]()
110+
return validators[validatorName]();
99111
});
100112
101113
let disabled = $state(false);
@@ -105,7 +117,7 @@
105117
const form = useForm2({
106118
handleValidationProcessError: (state) => {
107119
console.error(state);
108-
return handleValidationProcessError(state)
120+
return handleValidationProcessError(state);
109121
},
110122
initialValue: samples[initialSampleName].formData,
111123
initialErrors: samples[initialSampleName].errors ?? new SvelteMap(),
@@ -129,40 +141,63 @@
129141
return disabled;
130142
},
131143
get inputsValidationMode() {
132-
return validationEvent | validationAfter
144+
return validationEvent | validationAfter | ON_ARRAY_CHANGE;
133145
},
134146
get icons() {
135147
return iconSet;
136148
},
137-
onSubmit (value) {
149+
onSubmit(value) {
138150
console.log("submit", value);
139151
},
140-
onSubmitError (errors, e) {
152+
onSubmitError(errors, e) {
141153
if (doFocusOnFirstError) {
142154
focusOnFirstError(errors, e);
143155
}
144156
console.log("errors", errors);
145157
},
146-
})
158+
});
147159
148160
let playgroundTheme = $state<"system" | "light" | "dark">(
149161
localStorage.theme ?? "system"
150162
);
151163
152-
const lightOrDark = $derived(playgroundTheme === "system" ? window.matchMedia("(prefers-color-scheme: dark)") ? "dark" : "light" : playgroundTheme)
153-
164+
const lightOrDark = $derived(
165+
playgroundTheme === "system"
166+
? window.matchMedia("(prefers-color-scheme: dark)")
167+
? "dark"
168+
: "light"
169+
: playgroundTheme
170+
);
154171
155-
function setValidation(name: "vevent" | "vafter", value: number, replace = false) {
172+
function setValidation(
173+
name: "vevent" | "vafter",
174+
value: number,
175+
replace = false
176+
) {
156177
url.searchParams.set(name, value.toString());
157178
history[replace ? "replaceState" : "pushState"](null, "", url);
158179
return value;
159180
}
160-
const urlValidationEvent = Number(url.searchParams.get("vevent") ?? 0)
161-
const initialValidationEvent = urlValidationEvent > 0 && urlValidationEvent < 8 ? urlValidationEvent : 0
162-
let validationEvent = $state(setValidation("vevent", initialValidationEvent, true));
163-
const urlValidationAfter = Number(url.searchParams.get("vafter") ?? 0)
164-
const initialValidationAfter = [0, AFTER_SUBMITTED, AFTER_TOUCHED, AFTER_CHANGED].find((v) => v === urlValidationAfter) ?? 0
165-
let validationAfter = $state(setValidation("vafter", initialValidationAfter, true));
181+
const urlValidationEvent = Number(url.searchParams.get("vevent") ?? 0);
182+
const initialValidationEvent =
183+
urlValidationEvent > 0 && urlValidationEvent < 8 ? urlValidationEvent : 0;
184+
let validationEvent = $state(
185+
setValidation("vevent", initialValidationEvent, true)
186+
);
187+
$effect(() => {
188+
setValidation("vevent", validationEvent)
189+
})
190+
const urlValidationAfter = Number(url.searchParams.get("vafter") ?? 0);
191+
const initialValidationAfter =
192+
[0, AFTER_SUBMITTED, AFTER_TOUCHED, AFTER_CHANGED].find(
193+
(v) => v === urlValidationAfter
194+
) ?? 0;
195+
let validationAfter = $state(
196+
setValidation("vafter", initialValidationAfter, true)
197+
);
198+
$effect(() => {
199+
setValidation("vafter", validationAfter);
200+
})
166201
167202
setThemeContext({ components });
168203
</script>
@@ -184,23 +219,30 @@
184219
<input type="checkbox" bind:checked={doFocusOnFirstError} />
185220
Focus on first error
186221
</label>
187-
<select bind:value={validationEvent} onchange={() => setValidation("vevent", validationEvent)}>
188-
<option value={0}>None</option>
189-
<option value={ON_INPUT}>On Input</option>
190-
<option value={ON_CHANGE}>On Change</option>
191-
<option value={ON_BLUR}>On Blur</option>
192-
<option value={ON_INPUT | ON_BLUR}>Input & Blur</option>
193-
<option value={ON_INPUT | ON_CHANGE}>Input & Change</option>
194-
<option value={ON_BLUR | ON_CHANGE}>Blur & Change</option>
195-
<option value={ON_INPUT | ON_BLUR | ON_CHANGE}>All</option>
196-
</select>
197-
<select bind:value={validationAfter} onchange={() => setValidation("vafter", validationAfter)}>
198-
<option value={0}>Always</option>
199-
<option value={AFTER_CHANGED}>After Changed</option>
200-
<option value={AFTER_TOUCHED}>After Touched</option>
201-
<option value={AFTER_SUBMITTED}>After Submitted</option>
202-
</select>
203-
<select bind:value={validatorName} onchange={() => selectValidator(validatorName)}>
222+
<Bits
223+
title="Validation Events"
224+
bind:value={validationEvent}
225+
flags={[
226+
[ON_INPUT, "On Input"],
227+
[ON_CHANGE, "On Change"],
228+
[ON_BLUR, "On Blur"],
229+
[ON_ARRAY_CHANGE, "Array Changed"],
230+
[ON_OBJECT_CHANGE, "Object Changed"],
231+
]}
232+
/>
233+
<Bits
234+
title="Validation Modifiers"
235+
bind:value={validationAfter}
236+
flags={[
237+
[AFTER_CHANGED, "After Changed"],
238+
[AFTER_TOUCHED, "After Touched"],
239+
[AFTER_SUBMITTED, "After Submitted"],
240+
]}
241+
/>
242+
<select
243+
bind:value={validatorName}
244+
onchange={() => selectValidator(validatorName)}
245+
>
204246
{#each Object.keys(validators) as name (name)}
205247
<option value={name}>{name}</option>
206248
{/each}
@@ -210,7 +252,10 @@
210252
<option value={name}>{name}</option>
211253
{/each}
212254
</select>
213-
<select bind:value={iconSetName} onchange={() => selectIconSet(iconSetName)}>
255+
<select
256+
bind:value={iconSetName}
257+
onchange={() => selectIconSet(iconSetName)}
258+
>
214259
{#each Object.keys(icons) as name (name)}
215260
<option value={name}>{name}</option>
216261
{/each}
@@ -249,13 +294,25 @@
249294
</div>
250295
<div class="flex gap-8">
251296
<div class="flex-[4] flex flex-col gap-2">
252-
<Editor class="font-mono h-[400px] border rounded p-2 data-[error=true]:border-red-500 data-[error=true]:outline-none bg-transparent" bind:value={schema} />
297+
<Editor
298+
class="font-mono h-[400px] border rounded p-2 data-[error=true]:border-red-500 data-[error=true]:outline-none bg-transparent"
299+
bind:value={schema}
300+
/>
253301
<div class="flex gap-2">
254-
<Editor class="font-mono h-[400px] grow border rounded p-2 data-[error=true]:border-red-500 data-[error=true]:outline-none bg-transparent" bind:value={uiSchema} />
255-
<Editor class="font-mono h-[400px] grow border rounded p-2 data-[error=true]:border-red-500 data-[error=true]:outline-none bg-transparent" bind:value={form.value} />
302+
<Editor
303+
class="font-mono h-[400px] grow border rounded p-2 data-[error=true]:border-red-500 data-[error=true]:outline-none bg-transparent"
304+
bind:value={uiSchema}
305+
/>
306+
<Editor
307+
class="font-mono h-[400px] grow border rounded p-2 data-[error=true]:border-red-500 data-[error=true]:outline-none bg-transparent"
308+
bind:value={form.value}
309+
/>
256310
</div>
257311
</div>
258-
<ShadowHost class="flex-[3] max-h-[808px] overflow-y-auto" style={`${themeStyle}\n${iconSetStyle}`}>
312+
<ShadowHost
313+
class="flex-[3] max-h-[808px] overflow-y-auto"
314+
style={`${themeStyle}\n${iconSetStyle}`}
315+
>
259316
<SimpleForm
260317
{form}
261318
data-theme={themeName === "skeleton" ? "cerberus" : lightOrDark}

apps/playground/src/bits.svelte

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<script lang="ts">
2+
let {
3+
value = $bindable(),
4+
flags,
5+
title,
6+
}: { value: number; flags: [number, string][]; title: string } = $props();
7+
8+
const count = $derived(
9+
flags.reduce((c, f) => c + Number(Boolean(value & f[0])), 0)
10+
);
11+
</script>
12+
13+
<div class="relative group">
14+
<button class="p-2">
15+
{title} ({count})
16+
</button>
17+
<div class="absolute z-10 hidden group-hover:block">
18+
<div
19+
class="p-2 bg-slate-300 dark:bg-slate-600 dark:text-white shadow-lg rounded flex flex-col gap-2 w-max"
20+
>
21+
{#each flags as [flag, label]}
22+
<label>
23+
<input
24+
type="checkbox"
25+
checked={Boolean(value & flag)}
26+
onchange={() => {
27+
console.log((value ^= flag));
28+
}}
29+
/>
30+
{label}
31+
</label>
32+
{/each}
33+
</div>
34+
</div>
35+
</div>

mkfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ docs/:
5353
pnpm run preview
5454
popd
5555

56-
pg/:
56+
pl/:
5757
pushd apps/playground
5858
d:
5959
pnpm run dev

0 commit comments

Comments
 (0)