kayforms 0.1.1
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/LICENSE +21 -0
- package/README.md +337 -0
- package/examples/react-demo/README.md +337 -0
- package/examples/react-demo/eslint.config.js +22 -0
- package/examples/react-demo/index.html +13 -0
- package/examples/react-demo/package.json +33 -0
- package/examples/react-demo/public/apple-touch-icon.png +0 -0
- package/examples/react-demo/public/favicon-96x96.png +0 -0
- package/examples/react-demo/public/favicon.ico +0 -0
- package/examples/react-demo/public/favicon.svg +17 -0
- package/examples/react-demo/public/icons.svg +24 -0
- package/examples/react-demo/public/site.webmanifest +21 -0
- package/examples/react-demo/public/web-app-manifest-192x192.png +0 -0
- package/examples/react-demo/public/web-app-manifest-512x512.png +0 -0
- package/examples/react-demo/src/App.css +184 -0
- package/examples/react-demo/src/App.tsx +825 -0
- package/examples/react-demo/src/assets/hero.png +0 -0
- package/examples/react-demo/src/assets/react.svg +1 -0
- package/examples/react-demo/src/assets/vite.svg +1 -0
- package/examples/react-demo/src/index.css +627 -0
- package/examples/react-demo/src/main.tsx +10 -0
- package/examples/react-demo/tsconfig.app.json +25 -0
- package/examples/react-demo/tsconfig.json +7 -0
- package/examples/react-demo/tsconfig.node.json +24 -0
- package/examples/react-demo/vite.config.ts +7 -0
- package/kayforms.jpg +0 -0
- package/package.json +26 -0
- package/packages/angular/package.json +43 -0
- package/packages/angular/src/index.ts +198 -0
- package/packages/angular/tsconfig.json +8 -0
- package/packages/angular/tsup.config.ts +17 -0
- package/packages/core/README.md +337 -0
- package/packages/core/package.json +37 -0
- package/packages/core/src/batch.ts +106 -0
- package/packages/core/src/devtools.ts +329 -0
- package/packages/core/src/field.ts +167 -0
- package/packages/core/src/form.ts +448 -0
- package/packages/core/src/index.ts +71 -0
- package/packages/core/src/registry.ts +126 -0
- package/packages/core/src/signal.ts +399 -0
- package/packages/core/src/time-travel.ts +275 -0
- package/packages/core/src/validation.ts +243 -0
- package/packages/core/tsconfig.json +8 -0
- package/packages/core/tsup.config.ts +16 -0
- package/packages/devtools/extension/background.js +35 -0
- package/packages/devtools/extension/content-script.js +10 -0
- package/packages/devtools/extension/devtools.html +9 -0
- package/packages/devtools/extension/devtools.js +8 -0
- package/packages/devtools/extension/manifest.json +19 -0
- package/packages/devtools/extension/panel.css +505 -0
- package/packages/devtools/extension/panel.html +108 -0
- package/packages/devtools/extension/panel.js +354 -0
- package/packages/devtools/package.json +38 -0
- package/packages/devtools/src/index.ts +95 -0
- package/packages/devtools/src/panel.ts +226 -0
- package/packages/devtools/src/styles.ts +422 -0
- package/packages/devtools/src/timeline.ts +283 -0
- package/packages/devtools/tsconfig.json +8 -0
- package/packages/devtools/tsup.config.ts +17 -0
- package/packages/react/package.json +46 -0
- package/packages/react/src/index.ts +279 -0
- package/packages/react/tsconfig.json +8 -0
- package/packages/react/tsup.config.ts +17 -0
- package/packages/solid/package.json +42 -0
- package/packages/solid/src/index.ts +206 -0
- package/packages/solid/tsconfig.json +8 -0
- package/packages/solid/tsup.config.ts +17 -0
- package/packages/svelte/package.json +42 -0
- package/packages/svelte/src/index.ts +199 -0
- package/packages/svelte/tsconfig.json +8 -0
- package/packages/svelte/tsup.config.ts +17 -0
- package/packages/vanilla/package.json +38 -0
- package/packages/vanilla/src/index.ts +254 -0
- package/packages/vanilla/tsconfig.json +8 -0
- package/packages/vanilla/tsup.config.ts +17 -0
- package/packages/vue/package.json +42 -0
- package/packages/vue/src/index.ts +217 -0
- package/packages/vue/tsconfig.json +8 -0
- package/packages/vue/tsup.config.ts +17 -0
- package/pnpm-workspace.yaml +3 -0
- package/tsconfig.base.json +21 -0
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// @kayforms/core — Validation Pipeline
|
|
3
|
+
// ============================================================================
|
|
4
|
+
// Provides sync + async validation with smart batching, built-in validators,
|
|
5
|
+
// and schema adapter hooks for Zod/Yup/Valibot integration.
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Types
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
|
|
12
|
+
/** Result of a form-level validation */
|
|
13
|
+
export type ValidationResult = Record<string, string | undefined>;
|
|
14
|
+
|
|
15
|
+
/** A form-level validator function */
|
|
16
|
+
export type ValidatorFn<T> = (
|
|
17
|
+
values: T
|
|
18
|
+
) => ValidationResult | Promise<ValidationResult>;
|
|
19
|
+
|
|
20
|
+
/** A single-field validator function */
|
|
21
|
+
export type FieldValidator<V = unknown> = (
|
|
22
|
+
value: V
|
|
23
|
+
) => string | undefined | Promise<string | undefined>;
|
|
24
|
+
|
|
25
|
+
/** Configuration for field-level validation */
|
|
26
|
+
export interface FieldValidationConfig<V = unknown> {
|
|
27
|
+
/** Array of validator functions for this field */
|
|
28
|
+
validators?: FieldValidator<V>[];
|
|
29
|
+
/** When to trigger validation (default: inherits from form) */
|
|
30
|
+
validateOn?: "change" | "blur" | "submit";
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// ---------------------------------------------------------------------------
|
|
34
|
+
// Built-in Validators
|
|
35
|
+
// ---------------------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
export const validators = {
|
|
38
|
+
/**
|
|
39
|
+
* Validates that a value is not empty.
|
|
40
|
+
* Works with strings, arrays, and nullish values.
|
|
41
|
+
*/
|
|
42
|
+
required(msg?: string): FieldValidator {
|
|
43
|
+
return (value: unknown) => {
|
|
44
|
+
if (value === null || value === undefined || value === "") {
|
|
45
|
+
return msg ?? "This field is required";
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
48
|
+
return msg ?? "This field is required";
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
/** Validates minimum string length */
|
|
55
|
+
minLength(n: number, msg?: string): FieldValidator<string> {
|
|
56
|
+
return (value: string) => {
|
|
57
|
+
if (typeof value === "string" && value.length < n) {
|
|
58
|
+
return msg ?? `Must be at least ${n} characters`;
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
/** Validates maximum string length */
|
|
65
|
+
maxLength(n: number, msg?: string): FieldValidator<string> {
|
|
66
|
+
return (value: string) => {
|
|
67
|
+
if (typeof value === "string" && value.length > n) {
|
|
68
|
+
return msg ?? `Must be at most ${n} characters`;
|
|
69
|
+
}
|
|
70
|
+
return undefined;
|
|
71
|
+
};
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
/** Validates against a regular expression pattern */
|
|
75
|
+
pattern(re: RegExp, msg?: string): FieldValidator<string> {
|
|
76
|
+
return (value: string) => {
|
|
77
|
+
if (typeof value === "string" && value.length > 0 && !re.test(value)) {
|
|
78
|
+
return msg ?? "Invalid format";
|
|
79
|
+
}
|
|
80
|
+
return undefined;
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
|
|
84
|
+
/** Validates email format */
|
|
85
|
+
email(msg?: string): FieldValidator<string> {
|
|
86
|
+
const emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
87
|
+
return (value: string) => {
|
|
88
|
+
if (typeof value === "string" && value.length > 0 && !emailRe.test(value)) {
|
|
89
|
+
return msg ?? "Invalid email address";
|
|
90
|
+
}
|
|
91
|
+
return undefined;
|
|
92
|
+
};
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
/** Validates minimum numeric value */
|
|
96
|
+
min(n: number, msg?: string): FieldValidator<number> {
|
|
97
|
+
return (value: number) => {
|
|
98
|
+
if (typeof value === "number" && value < n) {
|
|
99
|
+
return msg ?? `Must be at least ${n}`;
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
};
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
/** Validates maximum numeric value */
|
|
106
|
+
max(n: number, msg?: string): FieldValidator<number> {
|
|
107
|
+
return (value: number) => {
|
|
108
|
+
if (typeof value === "number" && value > n) {
|
|
109
|
+
return msg ?? `Must be at most ${n}`;
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
};
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
/** Custom validator with user-defined logic */
|
|
116
|
+
custom<V = unknown>(fn: (value: V) => string | undefined): FieldValidator<V> {
|
|
117
|
+
return fn;
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// ---------------------------------------------------------------------------
|
|
122
|
+
// Schema Adapter — Zod / Yup / Valibot interop
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
/** Minimal schema interface — works with Zod, Yup, Valibot, or any lib with parse/safeParse */
|
|
126
|
+
interface SchemaLike<T> {
|
|
127
|
+
parse?: (value: T) => T;
|
|
128
|
+
safeParse?: (value: T) => { success: boolean; error?: { issues?: Array<{ path?: Array<string | number>; message: string }> }; errors?: Array<{ path?: Array<string | number>; message: string }> };
|
|
129
|
+
validate?: (value: T) => Promise<T>;
|
|
130
|
+
validateSync?: (value: T) => T;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Create a form-level validator from a schema object (Zod, Yup, Valibot, etc).
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* import { z } from 'zod';
|
|
139
|
+
* const schema = z.object({ email: z.string().email(), name: z.string().min(2) });
|
|
140
|
+
* const form = createForm({ initialValues: { email: '', name: '' }, validate: withSchema(schema) });
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
export function withSchema<T extends Record<string, unknown>>(
|
|
144
|
+
schema: SchemaLike<T>
|
|
145
|
+
): ValidatorFn<T> {
|
|
146
|
+
return (values: T): ValidationResult => {
|
|
147
|
+
const errors: ValidationResult = {};
|
|
148
|
+
|
|
149
|
+
// Zod-style safeParse
|
|
150
|
+
if (schema.safeParse) {
|
|
151
|
+
const result = schema.safeParse(values);
|
|
152
|
+
if (!result.success) {
|
|
153
|
+
const issues = result.error?.issues ?? result.errors ?? [];
|
|
154
|
+
for (const issue of issues) {
|
|
155
|
+
const path = issue.path?.join(".") ?? "";
|
|
156
|
+
if (path && !errors[path]) {
|
|
157
|
+
errors[path] = issue.message;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return errors;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Yup-style validateSync
|
|
165
|
+
if (schema.validateSync) {
|
|
166
|
+
try {
|
|
167
|
+
schema.validateSync(values);
|
|
168
|
+
} catch (err: unknown) {
|
|
169
|
+
if (err && typeof err === "object" && "inner" in err) {
|
|
170
|
+
const yupError = err as {
|
|
171
|
+
inner: Array<{ path?: string; message: string }>;
|
|
172
|
+
};
|
|
173
|
+
for (const e of yupError.inner) {
|
|
174
|
+
if (e.path && !errors[e.path]) {
|
|
175
|
+
errors[e.path] = e.message;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return errors;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Fallback: try parse and catch
|
|
184
|
+
if (schema.parse) {
|
|
185
|
+
try {
|
|
186
|
+
schema.parse(values);
|
|
187
|
+
} catch (err: unknown) {
|
|
188
|
+
if (err && typeof err === "object" && "issues" in err) {
|
|
189
|
+
const zodError = err as {
|
|
190
|
+
issues: Array<{ path: Array<string | number>; message: string }>;
|
|
191
|
+
};
|
|
192
|
+
for (const issue of zodError.issues) {
|
|
193
|
+
const path = issue.path.join(".");
|
|
194
|
+
if (!errors[path]) {
|
|
195
|
+
errors[path] = issue.message;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return errors;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
return errors;
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
// Validation Runner
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Run an array of field validators against a value.
|
|
213
|
+
* Returns the first error found (short-circuit), or undefined if valid.
|
|
214
|
+
*/
|
|
215
|
+
export async function runFieldValidators<V>(
|
|
216
|
+
value: V,
|
|
217
|
+
fieldValidators: FieldValidator<V>[]
|
|
218
|
+
): Promise<string | undefined> {
|
|
219
|
+
for (const validator of fieldValidators) {
|
|
220
|
+
const result = validator(value);
|
|
221
|
+
// Handle both sync and async validators
|
|
222
|
+
const error = result instanceof Promise ? await result : result;
|
|
223
|
+
if (error) return error;
|
|
224
|
+
}
|
|
225
|
+
return undefined;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Run field validators synchronously (skip async validators).
|
|
230
|
+
* Used for immediate feedback on keystroke.
|
|
231
|
+
*/
|
|
232
|
+
export function runFieldValidatorsSync<V>(
|
|
233
|
+
value: V,
|
|
234
|
+
fieldValidators: FieldValidator<V>[]
|
|
235
|
+
): string | undefined {
|
|
236
|
+
for (const validator of fieldValidators) {
|
|
237
|
+
const result = validator(value);
|
|
238
|
+
// Only process sync results
|
|
239
|
+
if (result instanceof Promise) continue;
|
|
240
|
+
if (result) return result;
|
|
241
|
+
}
|
|
242
|
+
return undefined;
|
|
243
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineConfig } from "tsup";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: ["src/index.ts"],
|
|
5
|
+
format: ["esm", "cjs"],
|
|
6
|
+
dts: true,
|
|
7
|
+
clean: true,
|
|
8
|
+
sourcemap: true,
|
|
9
|
+
minify: false,
|
|
10
|
+
treeshake: true,
|
|
11
|
+
outExtension({ format }) {
|
|
12
|
+
return {
|
|
13
|
+
js: format === "cjs" ? ".cjs" : ".js",
|
|
14
|
+
};
|
|
15
|
+
},
|
|
16
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// background.js
|
|
2
|
+
const ports = {};
|
|
3
|
+
|
|
4
|
+
chrome.runtime.onConnect.addListener((port) => {
|
|
5
|
+
if (port.name !== "kayforms-devtools") return;
|
|
6
|
+
|
|
7
|
+
let devtoolsTabId;
|
|
8
|
+
const listener = (message) => {
|
|
9
|
+
if (message.type === "init") {
|
|
10
|
+
devtoolsTabId = message.tabId;
|
|
11
|
+
ports[devtoolsTabId] = port;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
port.onMessage.addListener(listener);
|
|
16
|
+
port.onDisconnect.addListener(() => {
|
|
17
|
+
port.onMessage.removeListener(listener);
|
|
18
|
+
if (devtoolsTabId) {
|
|
19
|
+
delete ports[devtoolsTabId];
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
chrome.runtime.onMessage.addListener((message, sender) => {
|
|
25
|
+
if (message.source === "kayforms-page" && sender.tab) {
|
|
26
|
+
const tabId = sender.tab.id;
|
|
27
|
+
if (ports[tabId]) {
|
|
28
|
+
ports[tabId].postMessage({
|
|
29
|
+
type: "history-change",
|
|
30
|
+
detail: message.detail
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// content-script.js
|
|
2
|
+
window.addEventListener("kayforms:history-change", (event) => {
|
|
3
|
+
// Check if detail is present (CustomEvents from the page share details)
|
|
4
|
+
if (event.detail) {
|
|
5
|
+
chrome.runtime.sendMessage({
|
|
6
|
+
source: "kayforms-page",
|
|
7
|
+
detail: event.detail
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"manifest_version": 3,
|
|
3
|
+
"name": "KayForms Time-Travel DevTools",
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "DevTools panel for time-travel debugging in KayForms",
|
|
6
|
+
"permissions": ["activeTab", "scripting"],
|
|
7
|
+
"background": {
|
|
8
|
+
"service_worker": "background.js"
|
|
9
|
+
},
|
|
10
|
+
"content_scripts": [
|
|
11
|
+
{
|
|
12
|
+
"matches": ["<all_urls>"],
|
|
13
|
+
"js": ["content-script.js"],
|
|
14
|
+
"run_at": "document_start",
|
|
15
|
+
"all_frames": true
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"devtools_page": "devtools.html"
|
|
19
|
+
}
|