@verbb/plugin-kit-forms 2.0.0
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/CHANGELOG.md +13 -0
- package/LICENSE.md +21 -0
- package/README.md +62 -0
- package/dist/index.d.ts +170 -0
- package/dist/index.js +2174 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 2.0.0 - 2026-07-19
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Initial public release of `@verbb/plugin-kit-forms` — headless schema form engine for Craft CP settings screens.
|
|
9
|
+
- Builtin lazy `$field` keys: `text`, `textarea`, `number`, `select`, `radioGroup`, `lightswitch`, `color`, `checkboxSelect`, `combobox`, `group`, `date`, `codeEditor`.
|
|
10
|
+
- Framework UI bindings consume this package from `@verbb/plugin-kit-react/forms` and `@verbb/plugin-kit-vue/forms`.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Package versions lockstep with `@verbb/plugin-kit-web`, React/Vue adapters, and related `@verbb/plugin-kit-*` packages at `2.0.0`.
|
package/LICENSE.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) Verbb
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# `@verbb/plugin-kit-forms`
|
|
2
|
+
|
|
3
|
+
Framework-agnostic **SchemaForm engine** — state, validation, schema traversal, and conditions — with **no UI framework dependencies**.
|
|
4
|
+
|
|
5
|
+
React and Vue layer their own renderer, field components, and registry on top:
|
|
6
|
+
|
|
7
|
+
- `@verbb/plugin-kit-react/forms`
|
|
8
|
+
- `@verbb/plugin-kit-vue/forms`
|
|
9
|
+
|
|
10
|
+
Most apps import those surfaces, not this package directly.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install @verbb/plugin-kit-forms
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## What lives here
|
|
19
|
+
|
|
20
|
+
| Area | Contents |
|
|
21
|
+
|------|----------|
|
|
22
|
+
| State | `FormStateStore` (`subscribe` / path get-set, errors, touched, dirty) |
|
|
23
|
+
| Validation | `createValidationEngine`, built-in rule handlers (`required`, `min`, `max`, `email`, …) |
|
|
24
|
+
| Schema | `normalizeSchema`, types, traversal, Jexl `evaluateCondition` |
|
|
25
|
+
| i18n | `translate` / `setTranslateFunction` (falls back to `Craft.t`, then the raw message) |
|
|
26
|
+
|
|
27
|
+
## What stays out
|
|
28
|
+
|
|
29
|
+
`SchemaFormEngine`, `Field`, registries, and field components are framework-specific. Product-owned fields (handles, element selects, variable pickers, …) are registered by the host app — they are not kit builtins. Styling and controls live in `@verbb/plugin-kit-web`.
|
|
30
|
+
|
|
31
|
+
Builtin `$field` keys (documented under Forms): `text`, `textarea`, `number`, `select`, `radioGroup`, `lightswitch`, `color`, `checkboxSelect`, `combobox`, `group`, `date`, `codeEditor`.
|
|
32
|
+
|
|
33
|
+
## Engine usage
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import {
|
|
37
|
+
FormStateStore,
|
|
38
|
+
createValidationEngine,
|
|
39
|
+
normalizeSchema,
|
|
40
|
+
setTranslateFunction,
|
|
41
|
+
type SchemaIndex,
|
|
42
|
+
} from '@verbb/plugin-kit-forms';
|
|
43
|
+
|
|
44
|
+
setTranslateFunction((category, message, params) => Craft.t(category, message, params));
|
|
45
|
+
|
|
46
|
+
const store = new FormStateStore(defaultValues);
|
|
47
|
+
const index: SchemaIndex = {
|
|
48
|
+
schema: normalizeSchema(compiled.schema),
|
|
49
|
+
fieldEntries: compiled.fieldEntries,
|
|
50
|
+
};
|
|
51
|
+
const { validate } = createValidationEngine(index);
|
|
52
|
+
|
|
53
|
+
const result = validate(store.state.values);
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Dependencies
|
|
57
|
+
|
|
58
|
+
Runtime: [`jexl`](https://github.com/TomFrost/jexl), [`lodash-es`](https://lodash.com/). Kept external so consumers dedupe a single copy.
|
|
59
|
+
|
|
60
|
+
## Docs
|
|
61
|
+
|
|
62
|
+
[docs.verbb.io/plugin-kit/forms](https://docs.verbb.io/plugin-kit/forms/)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
export declare const buildGroupedMessage: (message: string, parentLabel?: string, childLabel?: string) => string;
|
|
2
|
+
|
|
3
|
+
export declare type ConditionDataResolver = (values: FormValues, field?: SchemaNode) => Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
export declare const createSchemaFieldIndex: (node: unknown) => {
|
|
6
|
+
fieldNames: string[];
|
|
7
|
+
hasField: (fieldName: string) => boolean;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export declare const createValidationEngine: (index: SchemaIndex, options?: {
|
|
11
|
+
conditionDataResolver?: ConditionDataResolver;
|
|
12
|
+
}) => {
|
|
13
|
+
validate: (values: FormValues) => ValidationResult;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export declare const emailOrVariableRule: (value: unknown, label: string) => string | null;
|
|
17
|
+
|
|
18
|
+
export declare const emailRule: (value: unknown, label: string) => string | null;
|
|
19
|
+
|
|
20
|
+
export declare const evaluateCondition: (condition: string | undefined, data: Record<string, unknown>) => any;
|
|
21
|
+
|
|
22
|
+
export declare const extractFieldNames: (content: TraversalValue) => string[];
|
|
23
|
+
|
|
24
|
+
export declare const extractFields: (nodes: TraversalValue) => TraversalNode[];
|
|
25
|
+
|
|
26
|
+
export declare type FieldEntry = {
|
|
27
|
+
path: string;
|
|
28
|
+
field: SchemaNode;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export declare type FormState = {
|
|
32
|
+
values: Record<string, unknown>;
|
|
33
|
+
errors: Record<string, string[]>;
|
|
34
|
+
touched: Set<string>;
|
|
35
|
+
dirty: Set<string>;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export declare class FormStateStore {
|
|
39
|
+
state: FormState;
|
|
40
|
+
private listeners;
|
|
41
|
+
private initialValues;
|
|
42
|
+
constructor(initialValues?: Record<string, unknown>);
|
|
43
|
+
subscribe(listener: FormSubscriber): () => void;
|
|
44
|
+
notify(): void;
|
|
45
|
+
getValue(path: string): unknown;
|
|
46
|
+
setValue(path: string, value: unknown): void;
|
|
47
|
+
setValues(values: Record<string, unknown>): void;
|
|
48
|
+
setErrors(errors: Record<string, string[]>): void;
|
|
49
|
+
clearErrors(): void;
|
|
50
|
+
setTouched(path: string, touched?: boolean): void;
|
|
51
|
+
reset(values?: Record<string, unknown>): void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
declare type FormSubscriber = () => void;
|
|
55
|
+
|
|
56
|
+
export declare type FormValues = Record<string, unknown>;
|
|
57
|
+
|
|
58
|
+
export declare const getSchemaFieldNames: (node: unknown) => string[];
|
|
59
|
+
|
|
60
|
+
export declare const handleRule: (value: unknown) => string | null;
|
|
61
|
+
|
|
62
|
+
export declare const hasSchemaErrors: (errors: Record<string, unknown> | undefined, node: unknown) => boolean;
|
|
63
|
+
|
|
64
|
+
export declare const isRequiredRuleName: (ruleName: string) => boolean;
|
|
65
|
+
|
|
66
|
+
export declare const maxRule: (value: unknown, label: string, args: string[]) => string | null;
|
|
67
|
+
|
|
68
|
+
export declare const minRule: (value: unknown, label: string, args: string[]) => string | null;
|
|
69
|
+
|
|
70
|
+
export declare type NestedFormApi = {
|
|
71
|
+
path: string;
|
|
72
|
+
validate: () => {
|
|
73
|
+
fields: Record<string, string[]>;
|
|
74
|
+
} | undefined;
|
|
75
|
+
getValues: () => FormValues;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export declare const normalizeSchema: (items: SchemaRenderable, path?: string) => SchemaRenderable;
|
|
79
|
+
|
|
80
|
+
export declare const REQUIRED_RULE_NAMES: Set<string>;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Required check for TipTap/ProseMirror JSON fields (`validation: 'requiredRichText'`).
|
|
84
|
+
* Empty docs are JSON (`[]` / bare paragraphs), not `''` — handled via `isEmptyValue`.
|
|
85
|
+
*/
|
|
86
|
+
export declare const requiredRichTextRule: (value: unknown, label: string) => string | null;
|
|
87
|
+
|
|
88
|
+
export declare const requiredRule: (value: unknown, label: string) => string | null;
|
|
89
|
+
|
|
90
|
+
declare type RuleContext = {
|
|
91
|
+
path: string;
|
|
92
|
+
values: Record<string, unknown>;
|
|
93
|
+
field: SchemaNode;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
export declare type RuleHandler = (value: unknown, label: string, args: string[], context?: RuleHandlerContext) => string | null;
|
|
97
|
+
|
|
98
|
+
export declare type RuleHandlerContext = {
|
|
99
|
+
path: string;
|
|
100
|
+
values: Record<string, unknown>;
|
|
101
|
+
field: SchemaNode;
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export declare const ruleHandlers: Record<string, RuleHandler>;
|
|
105
|
+
|
|
106
|
+
export declare type SchemaFieldApi = {
|
|
107
|
+
state: {
|
|
108
|
+
value: unknown;
|
|
109
|
+
};
|
|
110
|
+
handleChange: (valueOrEvent: unknown) => void;
|
|
111
|
+
handleBlur: () => void;
|
|
112
|
+
errors: string[];
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export declare type SchemaIndex = {
|
|
116
|
+
schema: SchemaRenderable;
|
|
117
|
+
fieldEntries: FieldEntry[];
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
export declare type SchemaNode = {
|
|
121
|
+
_id?: string;
|
|
122
|
+
_data?: Record<string, unknown>;
|
|
123
|
+
_scopePath?: string;
|
|
124
|
+
name?: string;
|
|
125
|
+
label?: string;
|
|
126
|
+
required?: boolean;
|
|
127
|
+
validation?: string;
|
|
128
|
+
columns?: unknown[];
|
|
129
|
+
children?: SchemaRenderable;
|
|
130
|
+
schema?: SchemaRenderable;
|
|
131
|
+
schemaChildPrefix?: string;
|
|
132
|
+
$field?: string;
|
|
133
|
+
$cmp?: string;
|
|
134
|
+
$el?: string;
|
|
135
|
+
type?: string;
|
|
136
|
+
if?: string;
|
|
137
|
+
hideOnIf?: boolean;
|
|
138
|
+
attrs?: Record<string, unknown>;
|
|
139
|
+
props?: Record<string, unknown>;
|
|
140
|
+
[key: string]: unknown;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
export declare type SchemaRenderable = SchemaNode | string | SchemaRenderable[];
|
|
144
|
+
|
|
145
|
+
export declare const setTranslateFunction: (fn?: (category: string, message: string, params?: TranslateParams) => string) => void;
|
|
146
|
+
|
|
147
|
+
export declare const setTranslationCategory: (category: string) => void;
|
|
148
|
+
|
|
149
|
+
export declare const translate: (message: string, params?: TranslateParams) => string;
|
|
150
|
+
|
|
151
|
+
export declare type TranslateParams = Record<string, string>;
|
|
152
|
+
|
|
153
|
+
declare type TraversalNode = Record<string, unknown> & {
|
|
154
|
+
$field?: string;
|
|
155
|
+
name?: string;
|
|
156
|
+
children?: TraversalValue;
|
|
157
|
+
schema?: TraversalValue;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
declare type TraversalValue = TraversalNode | TraversalNode[] | null | undefined;
|
|
161
|
+
|
|
162
|
+
export declare const traverseSchema: (node: TraversalValue, visitor: (node: TraversalNode) => void) => void;
|
|
163
|
+
|
|
164
|
+
export declare const uniqueHandleRule: (value: unknown, label: string, args: string[], context?: RuleContext) => string | null;
|
|
165
|
+
|
|
166
|
+
export declare type ValidationResult = {
|
|
167
|
+
fields: Record<string, string[]>;
|
|
168
|
+
} | undefined;
|
|
169
|
+
|
|
170
|
+
export { }
|