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.
Files changed (81) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +337 -0
  3. package/examples/react-demo/README.md +337 -0
  4. package/examples/react-demo/eslint.config.js +22 -0
  5. package/examples/react-demo/index.html +13 -0
  6. package/examples/react-demo/package.json +33 -0
  7. package/examples/react-demo/public/apple-touch-icon.png +0 -0
  8. package/examples/react-demo/public/favicon-96x96.png +0 -0
  9. package/examples/react-demo/public/favicon.ico +0 -0
  10. package/examples/react-demo/public/favicon.svg +17 -0
  11. package/examples/react-demo/public/icons.svg +24 -0
  12. package/examples/react-demo/public/site.webmanifest +21 -0
  13. package/examples/react-demo/public/web-app-manifest-192x192.png +0 -0
  14. package/examples/react-demo/public/web-app-manifest-512x512.png +0 -0
  15. package/examples/react-demo/src/App.css +184 -0
  16. package/examples/react-demo/src/App.tsx +825 -0
  17. package/examples/react-demo/src/assets/hero.png +0 -0
  18. package/examples/react-demo/src/assets/react.svg +1 -0
  19. package/examples/react-demo/src/assets/vite.svg +1 -0
  20. package/examples/react-demo/src/index.css +627 -0
  21. package/examples/react-demo/src/main.tsx +10 -0
  22. package/examples/react-demo/tsconfig.app.json +25 -0
  23. package/examples/react-demo/tsconfig.json +7 -0
  24. package/examples/react-demo/tsconfig.node.json +24 -0
  25. package/examples/react-demo/vite.config.ts +7 -0
  26. package/kayforms.jpg +0 -0
  27. package/package.json +26 -0
  28. package/packages/angular/package.json +43 -0
  29. package/packages/angular/src/index.ts +198 -0
  30. package/packages/angular/tsconfig.json +8 -0
  31. package/packages/angular/tsup.config.ts +17 -0
  32. package/packages/core/README.md +337 -0
  33. package/packages/core/package.json +37 -0
  34. package/packages/core/src/batch.ts +106 -0
  35. package/packages/core/src/devtools.ts +329 -0
  36. package/packages/core/src/field.ts +167 -0
  37. package/packages/core/src/form.ts +448 -0
  38. package/packages/core/src/index.ts +71 -0
  39. package/packages/core/src/registry.ts +126 -0
  40. package/packages/core/src/signal.ts +399 -0
  41. package/packages/core/src/time-travel.ts +275 -0
  42. package/packages/core/src/validation.ts +243 -0
  43. package/packages/core/tsconfig.json +8 -0
  44. package/packages/core/tsup.config.ts +16 -0
  45. package/packages/devtools/extension/background.js +35 -0
  46. package/packages/devtools/extension/content-script.js +10 -0
  47. package/packages/devtools/extension/devtools.html +9 -0
  48. package/packages/devtools/extension/devtools.js +8 -0
  49. package/packages/devtools/extension/manifest.json +19 -0
  50. package/packages/devtools/extension/panel.css +505 -0
  51. package/packages/devtools/extension/panel.html +108 -0
  52. package/packages/devtools/extension/panel.js +354 -0
  53. package/packages/devtools/package.json +38 -0
  54. package/packages/devtools/src/index.ts +95 -0
  55. package/packages/devtools/src/panel.ts +226 -0
  56. package/packages/devtools/src/styles.ts +422 -0
  57. package/packages/devtools/src/timeline.ts +283 -0
  58. package/packages/devtools/tsconfig.json +8 -0
  59. package/packages/devtools/tsup.config.ts +17 -0
  60. package/packages/react/package.json +46 -0
  61. package/packages/react/src/index.ts +279 -0
  62. package/packages/react/tsconfig.json +8 -0
  63. package/packages/react/tsup.config.ts +17 -0
  64. package/packages/solid/package.json +42 -0
  65. package/packages/solid/src/index.ts +206 -0
  66. package/packages/solid/tsconfig.json +8 -0
  67. package/packages/solid/tsup.config.ts +17 -0
  68. package/packages/svelte/package.json +42 -0
  69. package/packages/svelte/src/index.ts +199 -0
  70. package/packages/svelte/tsconfig.json +8 -0
  71. package/packages/svelte/tsup.config.ts +17 -0
  72. package/packages/vanilla/package.json +38 -0
  73. package/packages/vanilla/src/index.ts +254 -0
  74. package/packages/vanilla/tsconfig.json +8 -0
  75. package/packages/vanilla/tsup.config.ts +17 -0
  76. package/packages/vue/package.json +42 -0
  77. package/packages/vue/src/index.ts +217 -0
  78. package/packages/vue/tsconfig.json +8 -0
  79. package/packages/vue/tsup.config.ts +17 -0
  80. package/pnpm-workspace.yaml +3 -0
  81. package/tsconfig.base.json +21 -0
@@ -0,0 +1,206 @@
1
+ // ============================================================================
2
+ // @kayforms/solid — Solid JS Adapter
3
+ // ============================================================================
4
+ // Bridges Kayforms reactive signals to Solid's reactive Signal/Context systems.
5
+ // ============================================================================
6
+
7
+ import {
8
+ createSignal,
9
+ onCleanup,
10
+ createContext,
11
+ useContext,
12
+ type JSX,
13
+ type Accessor,
14
+ } from "solid-js";
15
+ import {
16
+ createForm,
17
+ type FormConfig,
18
+ type FormStore,
19
+ type FormErrors,
20
+ type FieldNode,
21
+ type Signal,
22
+ type Computed,
23
+ } from "@kayforms/core";
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Signal → Solid Accessor bridge
27
+ // ---------------------------------------------------------------------------
28
+
29
+ /**
30
+ * Hook to convert a Kayforms Signal or Computed into a Solid read-only Accessor.
31
+ * Automatically handles subscriptions and cleanups on component teardown.
32
+ */
33
+ export function useSignalValue<T>(signal: Signal<T> | Computed<T>): Accessor<T> {
34
+ const [val, setVal] = createSignal<T>(signal.peek());
35
+ const unsubscribe = signal.subscribe((next) => {
36
+ setVal(() => next);
37
+ });
38
+ onCleanup(unsubscribe);
39
+ return val;
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // Context System
44
+ // ---------------------------------------------------------------------------
45
+
46
+ const FormContext = createContext<FormStore<any>>();
47
+
48
+ export interface FormProviderProps {
49
+ form: FormStore<any>;
50
+ children: JSX.Element;
51
+ }
52
+
53
+ /** Provides form context to child Solid components */
54
+ export function FormProvider(props: FormProviderProps) {
55
+ return FormContext.Provider({
56
+ value: props.form,
57
+ get children() {
58
+ return props.children;
59
+ },
60
+ });
61
+ }
62
+
63
+ /** Injects form context into child Solid components */
64
+ export function useFormContext(): FormStore<any> {
65
+ const store = useContext(FormContext);
66
+ if (!store) {
67
+ throw new Error(
68
+ "[kayforms/solid] useField must be used inside a <FormProvider> component."
69
+ );
70
+ }
71
+ return store;
72
+ }
73
+
74
+ // ---------------------------------------------------------------------------
75
+ // useForm
76
+ // ---------------------------------------------------------------------------
77
+
78
+ export interface UseFormReturn<T extends Record<string, unknown>> {
79
+ /** The reactive form store */
80
+ store: FormStore<T>;
81
+ /** Solid Accessor for values */
82
+ values: Accessor<T>;
83
+ /** Solid Accessor for errors */
84
+ errors: Accessor<FormErrors<T>>;
85
+ /** Solid Accessor for dirty state */
86
+ dirty: Accessor<boolean>;
87
+ /** Solid Accessor for validity */
88
+ valid: Accessor<boolean>;
89
+ /** Solid Accessor for submitting state */
90
+ submitting: Accessor<boolean>;
91
+ /** Solid Accessor for validating state */
92
+ validating: Accessor<boolean>;
93
+ /** Submission handler */
94
+ handleSubmit: (e?: Event) => void;
95
+ /** Reset form values */
96
+ reset: (values?: Partial<T>) => void;
97
+ /** Get a field node by path */
98
+ getField: <V = unknown>(path: string) => FieldNode<V>;
99
+ }
100
+
101
+ /**
102
+ * Create and manage a Kayforms form in a Solid JS component.
103
+ */
104
+ export function useForm<T extends Record<string, unknown>>(
105
+ config: FormConfig<T>
106
+ ): UseFormReturn<T> {
107
+ const store = createForm(config);
108
+
109
+ onCleanup(() => {
110
+ store.dispose();
111
+ });
112
+
113
+ const values = useSignalValue(store.values);
114
+ const errors = useSignalValue(store.errors);
115
+ const dirty = useSignalValue(store.dirty);
116
+ const valid = useSignalValue(store.valid);
117
+ const submitting = useSignalValue(store.submitting);
118
+ const validating = useSignalValue(store.validating);
119
+
120
+ const handleSubmit = (e?: Event) => {
121
+ e?.preventDefault();
122
+ store.submit();
123
+ };
124
+
125
+ const reset = (newValues?: Partial<T>) => {
126
+ store.reset(newValues);
127
+ };
128
+
129
+ return {
130
+ store,
131
+ values,
132
+ errors,
133
+ dirty,
134
+ valid,
135
+ submitting,
136
+ validating,
137
+ handleSubmit,
138
+ reset,
139
+ getField: store.getField,
140
+ };
141
+ }
142
+
143
+ // ---------------------------------------------------------------------------
144
+ // useField
145
+ // ---------------------------------------------------------------------------
146
+
147
+ export interface UseFieldReturn<V = unknown> {
148
+ /** Solid Accessor for field value */
149
+ value: Accessor<V>;
150
+ /** Solid Accessor for field error */
151
+ error: Accessor<string | undefined>;
152
+ /** Solid Accessor for touched state */
153
+ touched: Accessor<boolean>;
154
+ /** Solid Accessor for dirty state */
155
+ dirty: Accessor<boolean>;
156
+ /** Solid Accessor for validating state */
157
+ validating: Accessor<boolean>;
158
+ /** Triggered value update */
159
+ onChange: (value: V) => void;
160
+ /** Blur handler */
161
+ onBlur: () => void;
162
+ /** Props for binding to plain DOM inputs */
163
+ inputProps: {
164
+ value: V;
165
+ onInput: (e: any) => void;
166
+ onBlur: () => void;
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Bind a single field's reactive signals in a Solid JS component.
172
+ */
173
+ export function useField<V = unknown>(path: string): UseFieldReturn<V> {
174
+ const store = useFormContext();
175
+ const field = store.getField<V>(path);
176
+
177
+ const value = useSignalValue(field.value);
178
+ const error = useSignalValue(field.error);
179
+ const touched = useSignalValue(field.touched);
180
+ const dirty = useSignalValue(field.dirty);
181
+ const validating = useSignalValue(field.validating);
182
+
183
+ const onChange = (v: V) => field.onChange(v);
184
+ const onBlur = () => field.onBlur();
185
+
186
+ const handleInput = (e: any) => {
187
+ field.onChange(e.target.value as V);
188
+ };
189
+
190
+ return {
191
+ value,
192
+ error,
193
+ touched,
194
+ dirty,
195
+ validating,
196
+ onChange,
197
+ onBlur,
198
+ get inputProps() {
199
+ return {
200
+ value: value(),
201
+ onInput: handleInput,
202
+ onBlur,
203
+ };
204
+ },
205
+ };
206
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src"]
8
+ }
@@ -0,0 +1,17 @@
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
+ external: ["solid-js", "@kayforms/core"],
12
+ outExtension({ format }) {
13
+ return {
14
+ js: format === "cjs" ? ".cjs" : ".js",
15
+ };
16
+ },
17
+ });
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "@kayforms/svelte",
3
+ "version": "0.1.0",
4
+ "description": "Svelte adapter for Kayforms reactive form library",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": ["dist"],
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "dev": "tsup --watch",
25
+ "clean": "rimraf dist",
26
+ "typecheck": "tsc --noEmit"
27
+ },
28
+ "dependencies": {
29
+ "@kayforms/core": "workspace:*"
30
+ },
31
+ "peerDependencies": {
32
+ "svelte": "^3.0.0 || ^4.0.0 || ^5.0.0"
33
+ },
34
+ "devDependencies": {
35
+ "svelte": "^4.0.0",
36
+ "tsup": "^8.0.0",
37
+ "typescript": "^5.5.0",
38
+ "rimraf": "^5.0.0"
39
+ },
40
+ "sideEffects": false,
41
+ "license": "MIT"
42
+ }
@@ -0,0 +1,199 @@
1
+ // ============================================================================
2
+ // @kayforms/svelte — Svelte Adapter
3
+ // ============================================================================
4
+ // Bridges Kayforms signals to Svelte's reactive stores.
5
+ // Enables using the Svelte `$store` template syntax directly.
6
+ // ============================================================================
7
+
8
+ import { onDestroy, setContext, getContext } from "svelte";
9
+ import {
10
+ createForm,
11
+ type FormConfig,
12
+ type FormStore,
13
+ type FormErrors,
14
+ type FieldNode,
15
+ type Signal,
16
+ type Computed,
17
+ } from "@kayforms/core";
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Svelte Store Wrapper
21
+ // ---------------------------------------------------------------------------
22
+
23
+ export interface ReadableStore<T> {
24
+ subscribe(run: (value: T) => void): () => void;
25
+ }
26
+
27
+ /**
28
+ * Wrap a Kayforms Signal or Computed in a Svelte readable store.
29
+ * Synchronously fires the current value to comply with Svelte store specifications.
30
+ */
31
+ export function toStore<T>(signal: Signal<T> | Computed<T>): ReadableStore<T> {
32
+ return {
33
+ subscribe(run: (value: T) => void): () => void {
34
+ // Svelte stores must call the callback immediately upon subscription
35
+ run(signal.peek());
36
+ return signal.subscribe(run);
37
+ },
38
+ };
39
+ }
40
+
41
+ // ---------------------------------------------------------------------------
42
+ // Context System
43
+ // ---------------------------------------------------------------------------
44
+
45
+ const FORM_KEY = "kayforms-form-context";
46
+
47
+ /** Provide form context inside a parent Svelte component */
48
+ export function setFormContext(store: FormStore<any>): void {
49
+ setContext(FORM_KEY, store);
50
+ }
51
+
52
+ /** Retrieve form context inside a child Svelte component */
53
+ export function getFormContext(): FormStore<any> {
54
+ const store = getContext<FormStore<any>>(FORM_KEY);
55
+ if (!store) {
56
+ throw new Error(
57
+ "[kayforms/svelte] getFormContext must be used inside a component " +
58
+ "where setFormContext(form.store) was called."
59
+ );
60
+ }
61
+ return store;
62
+ }
63
+
64
+ // ---------------------------------------------------------------------------
65
+ // useForm (createFormStore)
66
+ // ---------------------------------------------------------------------------
67
+
68
+ export interface UseFormReturn<T extends Record<string, unknown>> {
69
+ /** The reactive form store */
70
+ store: FormStore<T>;
71
+ /** Svelte store for values */
72
+ values: ReadableStore<T>;
73
+ /** Svelte store for errors */
74
+ errors: ReadableStore<FormErrors<T>>;
75
+ /** Svelte store for dirty state */
76
+ dirty: ReadableStore<boolean>;
77
+ /** Svelte store for validity */
78
+ valid: ReadableStore<boolean>;
79
+ /** Svelte store for submitting state */
80
+ submitting: ReadableStore<boolean>;
81
+ /** Svelte store for validating state */
82
+ validating: ReadableStore<boolean>;
83
+ /** Submit handler */
84
+ handleSubmit: (e?: Event) => void;
85
+ /** Reset form values */
86
+ reset: (values?: Partial<T>) => void;
87
+ /** Get a field node by path */
88
+ getField: <V = unknown>(path: string) => FieldNode<V>;
89
+ }
90
+
91
+ /**
92
+ * Create and manage a Kayforms form in a Svelte component.
93
+ */
94
+ export function useForm<T extends Record<string, unknown>>(
95
+ config: FormConfig<T>
96
+ ): UseFormReturn<T> {
97
+ const store = createForm(config);
98
+
99
+ onDestroy(() => {
100
+ store.dispose();
101
+ });
102
+
103
+ const values = toStore(store.values);
104
+ const errors = toStore(store.errors);
105
+ const dirty = toStore(store.dirty);
106
+ const valid = toStore(store.valid);
107
+ const submitting = toStore(store.submitting);
108
+ const validating = toStore(store.validating);
109
+
110
+ const handleSubmit = (e?: Event) => {
111
+ e?.preventDefault();
112
+ store.submit();
113
+ };
114
+
115
+ const reset = (newValues?: Partial<T>) => {
116
+ store.reset(newValues);
117
+ };
118
+
119
+ return {
120
+ store,
121
+ values,
122
+ errors,
123
+ dirty,
124
+ valid,
125
+ submitting,
126
+ validating,
127
+ handleSubmit,
128
+ reset,
129
+ getField: store.getField,
130
+ };
131
+ }
132
+
133
+ // ---------------------------------------------------------------------------
134
+ // useField (getFieldStore)
135
+ // ---------------------------------------------------------------------------
136
+
137
+ export interface UseFieldReturn<V = unknown> {
138
+ /** Underlying field node */
139
+ field: FieldNode<V>;
140
+ /** Svelte store for field value */
141
+ value: ReadableStore<V>;
142
+ /** Svelte store for field error */
143
+ error: ReadableStore<string | undefined>;
144
+ /** Svelte store for touched state */
145
+ touched: ReadableStore<boolean>;
146
+ /** Svelte store for dirty state */
147
+ dirty: ReadableStore<boolean>;
148
+ /** Svelte store for validating state */
149
+ validating: ReadableStore<boolean>;
150
+ /** Triggered value update */
151
+ onChange: (value: V) => void;
152
+ /** Blur handler */
153
+ onBlur: () => void;
154
+ /** Props for binding to plain DOM inputs */
155
+ inputProps: {
156
+ value: V;
157
+ onInput: (e: any) => void;
158
+ onBlur: () => void;
159
+ };
160
+ }
161
+
162
+ /**
163
+ * Bind a single field's reactive signals in a Svelte component.
164
+ */
165
+ export function useField<V = unknown>(path: string): UseFieldReturn<V> {
166
+ const store = getFormContext();
167
+ const field = store.getField<V>(path);
168
+
169
+ const value = toStore(field.value);
170
+ const error = toStore(field.error);
171
+ const touched = toStore(field.touched);
172
+ const dirty = toStore(field.dirty);
173
+ const validating = toStore(field.validating);
174
+
175
+ const onChange = (v: V) => field.onChange(v);
176
+ const onBlur = () => field.onBlur();
177
+
178
+ const handleInput = (e: any) => {
179
+ field.onChange(e.target.value as V);
180
+ };
181
+
182
+ return {
183
+ field,
184
+ value,
185
+ error,
186
+ touched,
187
+ dirty,
188
+ validating,
189
+ onChange,
190
+ onBlur,
191
+ get inputProps() {
192
+ return {
193
+ value: field.value.peek(),
194
+ onInput: handleInput,
195
+ onBlur,
196
+ };
197
+ },
198
+ };
199
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src"]
8
+ }
@@ -0,0 +1,17 @@
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
+ external: ["svelte", "@kayforms/core"],
12
+ outExtension({ format }) {
13
+ return {
14
+ js: format === "cjs" ? ".cjs" : ".js",
15
+ };
16
+ },
17
+ });
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@kayforms/vanilla",
3
+ "version": "0.1.0",
4
+ "description": "Vanilla JS adapter for Kayforms reactive form library",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "import": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ },
15
+ "require": {
16
+ "types": "./dist/index.d.cts",
17
+ "default": "./dist/index.cjs"
18
+ }
19
+ }
20
+ },
21
+ "files": ["dist"],
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "dev": "tsup --watch",
25
+ "clean": "rimraf dist",
26
+ "typecheck": "tsc --noEmit"
27
+ },
28
+ "dependencies": {
29
+ "@kayforms/core": "workspace:*"
30
+ },
31
+ "devDependencies": {
32
+ "tsup": "^8.0.0",
33
+ "typescript": "^5.5.0",
34
+ "rimraf": "^5.0.0"
35
+ },
36
+ "sideEffects": false,
37
+ "license": "MIT"
38
+ }