@dynamic-field-kit/angular 1.4.0 → 1.5.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 +166 -0
- package/LICENSE +21 -0
- package/README.md +144 -5
- package/dist/components/DynamicFormDevTools.d.ts +16 -0
- package/dist/components/DynamicInput.d.ts +2 -1
- package/dist/components/FieldInput.d.ts +7 -1
- package/dist/components/MultiFieldInput.d.ts +13 -2
- package/dist/fesm2022/dynamic-field-kit-angular.mjs +578 -34
- package/dist/index.d.ts +1 -1
- package/dist/layout/index.d.ts +2 -2
- package/dist/lib/dynamic-field-kit.module.d.ts +4 -4
- package/dist/lib/dynamic-form.store.d.ts +23 -0
- package/dist/public-api.d.ts +10 -8
- package/package.json +40 -21
- package/dist/README.md +0 -235
- package/dist/esm2022/components/BaseInput.mjs +0 -59
- package/dist/esm2022/components/DynamicInput.mjs +0 -244
- package/dist/esm2022/components/FieldInput.mjs +0 -99
- package/dist/esm2022/components/MultiFieldInput.mjs +0 -338
- package/dist/esm2022/dynamic-field-kit-angular.mjs +0 -5
- package/dist/esm2022/fieldRegistryToken.mjs +0 -12
- package/dist/esm2022/layout/defaultLayouts.mjs +0 -114
- package/dist/esm2022/layout/index.mjs +0 -3
- package/dist/esm2022/layout/layoutRegistry.mjs +0 -14
- package/dist/esm2022/lib/dynamic-field-kit.module.mjs +0 -52
- package/dist/esm2022/public-api.mjs +0 -18
- package/dist/esm2022/types/layout.mjs +0 -2
- package/dist/fesm2022/dynamic-field-kit-angular.mjs.map +0 -1
- package/ng-package.json +0 -7
- package/src/components/BaseInput.ts +0 -57
- package/src/components/DynamicInput.ts +0 -280
- package/src/components/FieldInput.ts +0 -74
- package/src/components/MultiFieldInput.ts +0 -331
- package/src/fieldRegistryToken.ts +0 -15
- package/src/layout/defaultLayouts.ts +0 -70
- package/src/layout/index.ts +0 -2
- package/src/layout/layoutRegistry.ts +0 -25
- package/src/lib/dynamic-field-kit.module.ts +0 -29
- package/src/public-api.ts +0 -40
- package/src/types/layout.ts +0 -14
- package/test/DynamicInput.spec.ts +0 -230
- package/test/FieldInput.spec.ts +0 -146
- package/test/MultiFieldInput.spec.ts +0 -256
- package/test/helpers/renderers.ts +0 -89
- package/test/layout.spec.ts +0 -119
- package/test/publicApi.spec.ts +0 -64
- package/test/setup.ts +0 -12
- package/test/smoke.spec.ts +0 -27
- package/tsconfig.json +0 -13
- package/tsconfig.spec.json +0 -9
- package/vitest.config.ts +0 -30
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,171 @@
|
|
|
1
1
|
# @dynamic-field-kit/angular
|
|
2
2
|
|
|
3
|
+
## 1.5.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- Form state hooks, schema adapters, wizard engine, DevTools and extended renderers across all four packages, with type declarations that now resolve correctly under node16 and nodenext.
|
|
8
|
+
- d91c737: Add form state hooks, schema adapters, wizard engine, DevTools and extended renderers.
|
|
9
|
+
|
|
10
|
+
**Core**
|
|
11
|
+
|
|
12
|
+
- `zodValidator`, `yupValidator`, `valibotValidator` / `standardSchemaValidator`. Adapters parse **synchronously** so their result is usable by the synchronous `validateFields`; schemas with async refinements or async `.test()` rules return a Promise and must be validated through `validateFieldsAsync`.
|
|
13
|
+
- Adapters take an explicit `{ target: 'form' | 'field' }` option. `'form'` (the default) parses the form data object; `'field'` parses a single scalar value. The field-name shorthand — `zodValidator(schema, 'email')` — is unchanged.
|
|
14
|
+
- Wizard engine: `createWizardState`, `validateStep`, `canGoNext`, `canGoPrev`, plus the navigation the engine needs to be usable - `goNext`, `goPrev`, `goToStep`, `markStepCompleted`, `isStepCompleted`. `goNext` records the step it leaves, so `completedSteps` is actually maintained.
|
|
15
|
+
- Group array helpers: `moveGroupItem`, `swapGroupItems`, `insertGroupItem`, `focusFirstInvalidField`.
|
|
16
|
+
- `switch` is a first-class field type: it had a shipped renderer in react and vue but no `FieldTypeMap` entry, so `type: 'switch'` did not typecheck.
|
|
17
|
+
|
|
18
|
+
**React / Vue / Angular**
|
|
19
|
+
|
|
20
|
+
- `useDynamicForm` (React, Vue) and `createDynamicFormStore` (Angular Signals) now expose the same surface, including `isSubmitting` and `isSubmitted`.
|
|
21
|
+
- `handleSubmit(onValid, onInvalid)` returns a submit handler in every framework and calls `preventDefault` on the event it receives.
|
|
22
|
+
- Default HTML5 renderers for `radio`, `range`, `file`, `date`, `time`, `datetime-local` and `switch`.
|
|
23
|
+
- `DynamicFormDevTools` overlay for inspecting form data, errors, metadata and field descriptions, with an error-count badge in all three frameworks.
|
|
24
|
+
- `MultiFieldInput` reports blur through `onBlurField` (an `@Output` in Angular), so a form store's `handleBlur` / `touched` / `validateOnBlur` can be wired to it. Vue and Angular previously had no blur plumbing at all.
|
|
25
|
+
|
|
26
|
+
### Patch Changes
|
|
27
|
+
|
|
28
|
+
- c3faa51: Document the v1.5 APIs in each package README: form state, schema adapters, the wizard engine, default renderers, DevTools and blur wiring. README ships in the npm tarball, so this reaches package pages only through a release.
|
|
29
|
+
- 75447a6: Close the remaining documentation gaps in each package README, so every public
|
|
30
|
+
export is described somewhere. README ships in the npm tarball, so this reaches
|
|
31
|
+
the package pages only through a release.
|
|
32
|
+
|
|
33
|
+
- **Sync vs async validation** is now spelled out in core, with the consequence
|
|
34
|
+
that was previously implicit: `validateField` / `validateFields` cannot await,
|
|
35
|
+
so a `validate` hook returning a Promise is treated as valid on the sync path.
|
|
36
|
+
`useDynamicForm` and `createDynamicFormStore` validate synchronously
|
|
37
|
+
(including on submit), so async rules have to run through
|
|
38
|
+
`validateFieldsAsync` explicitly. Each adapter README repeats the caveat and
|
|
39
|
+
links to the core section.
|
|
40
|
+
- Document `validateFieldAsync`, `validateFieldsAsync`, `resolveOptions` and
|
|
41
|
+
`validators` in the react, vue and angular export lists, separated from each
|
|
42
|
+
adapter's own exports so it is clear they are core re-exports.
|
|
43
|
+
- Document the core types that appear in every signature but had no definition
|
|
44
|
+
in the README: `Properties`, `ValidatorFn`, `FieldValidatorResult`,
|
|
45
|
+
`FieldValidatorFunction`, and `FormStep` alongside `WizardState`.
|
|
46
|
+
- Angular: document `layoutRegistry` / `LayoutRegistry`, the `ColumnLayout` /
|
|
47
|
+
`RowLayout` / `GridLayout` components and `BaseInputComponent`, with a custom
|
|
48
|
+
layout example. Its layout registry holds standalone components rather than
|
|
49
|
+
render functions, which is the one place the three adapters genuinely differ,
|
|
50
|
+
and it was the only adapter not documenting its registry at all.
|
|
51
|
+
- Rename angular's `## What it exports` to `## Exports` to match react and vue.
|
|
52
|
+
- Link the demo sub-routes: react's enterprise-features and wizard pages, and
|
|
53
|
+
from core the per-framework demos plus the wizard it documents.
|
|
54
|
+
|
|
55
|
+
- e4f8dbd: Stop publishing the fesm2022 sourcemap, which is 70.7 KB of a 165.9 KB install.
|
|
56
|
+
|
|
57
|
+
When core, react and vue dropped their sourcemaps, angular was recorded as
|
|
58
|
+
unaffected on the grounds that "ng-packagr's published output does not carry
|
|
59
|
+
them". That was wrong. ng-packagr emits
|
|
60
|
+
`fesm2022/dynamic-field-kit-angular.mjs.map` with `sourcesContent` — 12 embedded
|
|
61
|
+
TypeScript files — and `files: ["dist"]` has been publishing it ever since.
|
|
62
|
+
|
|
63
|
+
| | Before | After |
|
|
64
|
+
| -------- | -------- | -------------- |
|
|
65
|
+
| Unpacked | 165.9 KB | 97.1 KB (−41%) |
|
|
66
|
+
| Tarball | 33.7 KB | 18.2 KB (−46%) |
|
|
67
|
+
| Files | 19 | 18 |
|
|
68
|
+
|
|
69
|
+
This is the same deliberate trade the other three packages made, not a free win:
|
|
70
|
+
the map worked, and dropping it costs the ability to step into this library's
|
|
71
|
+
TypeScript source while debugging a consuming app.
|
|
72
|
+
|
|
73
|
+
ng-packagr has no switch for it, so a `postbuild` step
|
|
74
|
+
(`scripts/strip-sourcemaps.js`) deletes the map and the
|
|
75
|
+
`//# sourceMappingURL=` comment that pointed at it. The comment has to go too —
|
|
76
|
+
excluding the map at publish time alone would leave every consumer's devtools
|
|
77
|
+
fetching a URL that 404s.
|
|
78
|
+
|
|
79
|
+
Nothing that reaches an application bundle changes: the fesm2022 bundle is
|
|
80
|
+
identical bar that one trailing comment line.
|
|
81
|
+
|
|
82
|
+
- e4f8dbd: Correct the Angular peer range's lower bound from `>=13` to `>=14`.
|
|
83
|
+
|
|
84
|
+
The published `fesm2022` bundle is Angular partial-compilation output, and each
|
|
85
|
+
declaration in it carries the minimum Angular version able to link it. The
|
|
86
|
+
highest across this package is `minVersion: "14.0.0"`, so an Angular 13
|
|
87
|
+
application could never have consumed it — npm would install cleanly and the
|
|
88
|
+
linker would then fail. The upper bound `<22` is unchanged.
|
|
89
|
+
|
|
90
|
+
No runtime change: the bundle is byte-identical, and only the range in
|
|
91
|
+
`package.json` moves.
|
|
92
|
+
|
|
93
|
+
- e10044b: Fix the type declarations consumers get under `moduleResolution: node16` /
|
|
94
|
+
`nodenext`, and fill in the npm metadata the package pages never had.
|
|
95
|
+
|
|
96
|
+
Every package declared a single `types` target for both module formats while
|
|
97
|
+
shipping two sets of declarations. `arethetypeswrong` on the built tarballs
|
|
98
|
+
reported core and react "masquerading as CJS" and vue "masquerading as ESM":
|
|
99
|
+
an ESM import resolved to the CommonJS declaration file, and vice versa. The
|
|
100
|
+
`exports` maps now declare `types` per condition, so each format resolves to
|
|
101
|
+
the declarations that describe it.
|
|
102
|
+
|
|
103
|
+
Angular was worse than a mismatch - it did not resolve at all. Its entry is an
|
|
104
|
+
`.mjs` bundle, so TypeScript reads its declarations in ESM mode, where the
|
|
105
|
+
extensionless `export * from './public-api'` that ng-packagr generates is error
|
|
106
|
+
TS2834. The failure is in `dist/index.d.ts`, the first file a consumer reaches,
|
|
107
|
+
so the package was unusable on node16 resolution. A `postbuild` step now adds
|
|
108
|
+
the explicit `.js` extension to relative specifiers in the emitted `.d.ts`
|
|
109
|
+
files, resolving each against the build output so a directory import becomes
|
|
110
|
+
`./layout/index.js` rather than a broken `./layout.js`. Angular also gains an
|
|
111
|
+
`exports` map and `"type": "module"`, which it needs to describe itself
|
|
112
|
+
honestly. All four packages are clean on all four resolution modes now, except
|
|
113
|
+
angular's `require()`, which is ESM-only by nature - as `@angular/core` is.
|
|
114
|
+
|
|
115
|
+
Also in the tarballs: `CHANGELOG.md` now ships, so the npm page has release
|
|
116
|
+
history, and angular no longer ships a second copy of its README and LICENSE
|
|
117
|
+
that ng-packagr had copied into `dist`. Every package gains `homepage`, `bugs`
|
|
118
|
+
and `repository.directory`, so npm links to the right README and issue tracker
|
|
119
|
+
instead of nothing, and `publishConfig.provenance`, so each published tarball
|
|
120
|
+
is signed and linked back to the workflow run that built it.
|
|
121
|
+
|
|
122
|
+
- 244c3d4: Ship the MIT license text, and fix what the angular package tells npm.
|
|
123
|
+
|
|
124
|
+
Every package declared `"license": "MIT"` with no LICENSE file anywhere in the
|
|
125
|
+
repo, so the tarballs carried the claim and not the terms. npm includes a
|
|
126
|
+
LICENSE at the package root regardless of `files`, so all four now ship one.
|
|
127
|
+
|
|
128
|
+
The angular package's README was wrong in three places, all of them visible on
|
|
129
|
+
its npm page: the "pin versions explicitly" example named `core@^1.0.12` and
|
|
130
|
+
`angular@^1.2.3`, nine public exports (`FieldInputProps`, `DynamicFormOptions`,
|
|
131
|
+
`FieldTypeKey`, `LayoutConfig`, `ColumnLayoutConfig`, `RowLayoutConfig`,
|
|
132
|
+
`GridLayoutConfig`, `BaseLayoutConfig`, `ResponsiveLayoutConfig`) appeared
|
|
133
|
+
nowhere in it, and a section titled "Angular 14 and earlier" named versions the
|
|
134
|
+
package cannot run on — its peer floor is `>=14`.
|
|
135
|
+
|
|
136
|
+
Its `sideEffects: false` was also untrue: `defaultLayouts.ts` registers the
|
|
137
|
+
three layout components with a module-scope call, which is exactly the side
|
|
138
|
+
effect react and vue list their own layout modules for. The flag is now
|
|
139
|
+
`["**/fesm2022/*.mjs"]` — a glob because ng-packagr copies the field into
|
|
140
|
+
`dist/package.json`, where paths resolve one directory lower, and a bundler
|
|
141
|
+
reads whichever manifest is nearest the module. Nothing observable changes
|
|
142
|
+
today: bundling the published 1.4.0 with esbuild while importing only
|
|
143
|
+
`DynamicInput` already kept all three `register()` calls, because the fesm2022
|
|
144
|
+
bundle is a single module the app is using. The flag was a claim waiting to
|
|
145
|
+
break.
|
|
146
|
+
|
|
147
|
+
- 5577177: Build with ng-packagr 19 instead of 17, which is what the package's Angular
|
|
148
|
+
version has needed all along — ng-packagr 17 declared peers of
|
|
149
|
+
`@angular/compiler-cli ^17` and `typescript >=5.2 <5.5` against an installed
|
|
150
|
+
19.2.25 and 5.6.3, so installing needed `--legacy-peer-deps`.
|
|
151
|
+
|
|
152
|
+
The bundle consumers actually load is unchanged: `fesm2022` is byte-identical,
|
|
153
|
+
as are `index.d.ts` and `public-api.d.ts`. What changes is the rest of the
|
|
154
|
+
tarball. ng-packagr stopped emitting the per-file `esm2022/` output in 18,
|
|
155
|
+
because the fesm2022 bundle is what the Angular linker consumes, and its
|
|
156
|
+
`esm2022` and `esm` export conditions go with it. The `.` export keeps `types`
|
|
157
|
+
and `default`, matching what every Angular 19+ library ships.
|
|
158
|
+
|
|
159
|
+
| | Before | After |
|
|
160
|
+
| -------- | -------- | --------------- |
|
|
161
|
+
| Unpacked | 317.1 KB | 165.9 KB (−48%) |
|
|
162
|
+
| Tarball | 75.8 KB | 33.7 KB (−56%) |
|
|
163
|
+
| Files | 33 | 19 |
|
|
164
|
+
|
|
165
|
+
If you were resolving this package through the `esm` or `esm2022` condition
|
|
166
|
+
explicitly, resolution now falls through to `default`, which points at the same
|
|
167
|
+
fesm2022 file those conditions already resolved to.
|
|
168
|
+
|
|
3
169
|
## 1.4.0
|
|
4
170
|
|
|
5
171
|
### Minor Changes
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 vannt-dev
|
|
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
CHANGED
|
@@ -4,7 +4,9 @@ Angular adapter for `@dynamic-field-kit/core`.
|
|
|
4
4
|
|
|
5
5
|
This package provides Angular components and a convenience module that render field schemas defined with `@dynamic-field-kit/core`.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
Live demo: https://vannt-dev.github.io/dynamic-field-kit/angular/ — tabs for the
|
|
8
|
+
basic schema, the enterprise features (`createDynamicFormStore`, HTML5 renderers,
|
|
9
|
+
blur wiring, DevTools) and the multi-step wizard.
|
|
8
10
|
|
|
9
11
|
## Install
|
|
10
12
|
|
|
@@ -17,10 +19,10 @@ Note: `@dynamic-field-kit/core`, `@angular/core`, and `@angular/common` are **pe
|
|
|
17
19
|
If you need to pin versions explicitly:
|
|
18
20
|
|
|
19
21
|
```bash
|
|
20
|
-
npm install @dynamic-field-kit/core@^1.0
|
|
22
|
+
npm install @dynamic-field-kit/core@^1.5.0 @dynamic-field-kit/angular@^1.5.0
|
|
21
23
|
```
|
|
22
24
|
|
|
23
|
-
##
|
|
25
|
+
## Exports
|
|
24
26
|
|
|
25
27
|
- `DynamicInput`
|
|
26
28
|
- `FieldInput`
|
|
@@ -29,7 +31,38 @@ npm install @dynamic-field-kit/core@^1.0.12 @dynamic-field-kit/angular@^1.2.3
|
|
|
29
31
|
- `fieldRegistry`
|
|
30
32
|
- `FieldRegistry` (class, for scoped registries)
|
|
31
33
|
- `FIELD_REGISTRY` (injection token)
|
|
32
|
-
- `
|
|
34
|
+
- `createDynamicFormStore` (signal-based form state)
|
|
35
|
+
- `DynamicFormDevToolsComponent`
|
|
36
|
+
- `layoutRegistry` / `LayoutRegistry` (class, for a scoped layout registry)
|
|
37
|
+
- `ColumnLayout` / `RowLayout` / `GridLayout` (the standalone layout components, registered for you)
|
|
38
|
+
- `BaseInputComponent` — the abstract base your custom renderers extend
|
|
39
|
+
- `FieldInputProps` — the inputs `BaseInputComponent` declares; the Angular
|
|
40
|
+
mirror of core's `FieldRendererProps`
|
|
41
|
+
- `DynamicFormOptions` — what `createDynamicFormStore` takes: `fields`,
|
|
42
|
+
`initialValues`, `validateOnBlur`, `validateOnChange`
|
|
43
|
+
- `LayoutConfig` / `ColumnLayoutConfig` / `RowLayoutConfig` /
|
|
44
|
+
`GridLayoutConfig` — the layout config types, re-exported from core
|
|
45
|
+
- `BaseLayoutConfig` / `ResponsiveLayoutConfig` — this adapter's historical
|
|
46
|
+
aliases for core's `BaseLayout` / `ResponsiveLayout`, kept so existing
|
|
47
|
+
imports keep resolving
|
|
48
|
+
|
|
49
|
+
Re-exported from `@dynamic-field-kit/core` so a consumer app rarely has to import
|
|
50
|
+
both packages:
|
|
51
|
+
|
|
52
|
+
- `validateField` / `validateFieldAsync` — one field, returns `string[]`
|
|
53
|
+
- `validateFields` / `validateFieldsAsync` — a whole schema, returns `ValidationResult`
|
|
54
|
+
- `resolveDisabled` / `resolveReadOnly` / `resolveOptions` — resolve a field's dynamic conditions and options
|
|
55
|
+
- `validators` — the built-in validator helpers (`required`, `email`, `minLength`, `compose`, …)
|
|
56
|
+
- `FieldDescription` / `FieldTypeKey` / `FieldRendererProps` — the schema and
|
|
57
|
+
renderer contracts every adapter shares
|
|
58
|
+
- `ValidationResult`
|
|
59
|
+
|
|
60
|
+
`createDynamicFormStore` validates **synchronously** via `validateFields`,
|
|
61
|
+
including on submit. Fields whose `validate` hook returns a Promise are treated
|
|
62
|
+
as valid on that path, so run async rules through `validateFieldsAsync`
|
|
63
|
+
yourself. See the
|
|
64
|
+
[core README](https://github.com/vannt-dev/dynamic-field-kit/tree/develop/packages/core#sync-vs-async-validation)
|
|
65
|
+
for the full rules.
|
|
33
66
|
|
|
34
67
|
## Basic setup (Angular 19+)
|
|
35
68
|
|
|
@@ -89,6 +122,89 @@ export class AppComponent {
|
|
|
89
122
|
></dfk-multi-field-input>
|
|
90
123
|
```
|
|
91
124
|
|
|
125
|
+
## Form state (`createDynamicFormStore`)
|
|
126
|
+
|
|
127
|
+
A signal-based store — the Angular counterpart of React and Vue's
|
|
128
|
+
`useDynamicForm`, with the same members. Read them as signals.
|
|
129
|
+
|
|
130
|
+
```ts
|
|
131
|
+
import {
|
|
132
|
+
createDynamicFormStore,
|
|
133
|
+
MultiFieldInput,
|
|
134
|
+
} from '@dynamic-field-kit/angular';
|
|
135
|
+
|
|
136
|
+
@Component({
|
|
137
|
+
standalone: true,
|
|
138
|
+
imports: [MultiFieldInput],
|
|
139
|
+
template: `
|
|
140
|
+
<form (ngSubmit)="onSubmit($event)">
|
|
141
|
+
<dfk-multi-field-input
|
|
142
|
+
[fieldDescriptions]="fields"
|
|
143
|
+
[properties]="store.data()"
|
|
144
|
+
(onChange)="store.handleChange($event)"
|
|
145
|
+
(onBlurField)="store.handleBlur($event)"
|
|
146
|
+
></dfk-multi-field-input>
|
|
147
|
+
<button [disabled]="store.isSubmitting()">Save</button>
|
|
148
|
+
</form>
|
|
149
|
+
`,
|
|
150
|
+
})
|
|
151
|
+
export class MyForm {
|
|
152
|
+
fields = fields;
|
|
153
|
+
store = createDynamicFormStore({
|
|
154
|
+
fields,
|
|
155
|
+
initialValues: { country: 'VN' },
|
|
156
|
+
validateOnBlur: true, // default
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// handleSubmit returns a handler, exactly like React and Vue.
|
|
160
|
+
onSubmit = this.store.handleSubmit((data) => this.save(data));
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
| Member | Description |
|
|
165
|
+
| ----------------------------------- | --------------------------------------------------------------------------------- |
|
|
166
|
+
| `data()` | Current form data, with `computeValue` fields applied |
|
|
167
|
+
| `errors()` | `Record<string, string[]>`, keyed like `validateFields` |
|
|
168
|
+
| `isValid()` / `isDirty()` | No errors recorded / any value has changed |
|
|
169
|
+
| `isSubmitting()` / `isSubmitted()` | In-flight submit / at least one submit attempted |
|
|
170
|
+
| `touched()` | Fields that have been blurred |
|
|
171
|
+
| `handleChange(data)` | Replace the whole form data — bind to `(onChange)` |
|
|
172
|
+
| `setFieldValue(name, value)` | Change one field |
|
|
173
|
+
| `handleBlur(name)` | Mark touched, and validate when `validateOnBlur` |
|
|
174
|
+
| `setFieldTouched(name, value?)` | Set touched explicitly |
|
|
175
|
+
| `validate()` | Validate now, returns a boolean |
|
|
176
|
+
| `reset(values?)` | Back to `initialValues` (or the values given), clearing errors/touched/submission |
|
|
177
|
+
| `handleSubmit(onValid, onInvalid?)` | Returns an async handler; calls `preventDefault`, validates, then dispatches |
|
|
178
|
+
|
|
179
|
+
`MultiFieldInput` emits `(onBlurField)` with the field's name, driven by a
|
|
180
|
+
`focusout` listener — so it works with any renderer, without the renderer
|
|
181
|
+
needing a blur output of its own.
|
|
182
|
+
|
|
183
|
+
## Default renderers
|
|
184
|
+
|
|
185
|
+
`text` · `number` · `password` · `email` · `textarea` · `checkbox` · `select` ·
|
|
186
|
+
`radio` · `range` · `file` · `date` · `time` · `datetime-local` · `switch`
|
|
187
|
+
|
|
188
|
+
Any type you have not registered falls back to one of these. `file` emits a
|
|
189
|
+
`File` (or `File[]` when `multiple` is set), `range` and `number` emit numbers,
|
|
190
|
+
`checkbox` / `switch` emit booleans; everything else emits strings.
|
|
191
|
+
|
|
192
|
+
## DevTools
|
|
193
|
+
|
|
194
|
+
```html
|
|
195
|
+
<dfk-dev-tools
|
|
196
|
+
[data]="store.data()"
|
|
197
|
+
[errors]="store.errors()"
|
|
198
|
+
[touched]="store.touched()"
|
|
199
|
+
[isDirty]="store.isDirty()"
|
|
200
|
+
[fields]="fields"
|
|
201
|
+
></dfk-dev-tools>
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Import `DynamicFormDevToolsComponent`. A floating overlay with data / errors /
|
|
205
|
+
meta / fields tabs; the collapsed button carries a red badge with the number of
|
|
206
|
+
fields in error.
|
|
207
|
+
|
|
92
208
|
## Layouts
|
|
93
209
|
|
|
94
210
|
`MultiFieldInput` supports `column`, `row`, `grid`, and `responsive` (mobile/desktop with a custom breakpoint), matching the React and Vue adapters:
|
|
@@ -109,6 +225,29 @@ export class AppComponent {
|
|
|
109
225
|
></dfk-multi-field-input>
|
|
110
226
|
```
|
|
111
227
|
|
|
228
|
+
Those four names resolve through `layoutRegistry`, which holds standalone
|
|
229
|
+
components rather than render functions — the Angular equivalent of the React
|
|
230
|
+
and Vue layout registries. `ColumnLayout`, `RowLayout` and `GridLayout` are
|
|
231
|
+
registered for you when you import the package root; register your own the same
|
|
232
|
+
way:
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
import { Component } from '@angular/core';
|
|
236
|
+
import { layoutRegistry } from '@dynamic-field-kit/angular';
|
|
237
|
+
|
|
238
|
+
@Component({
|
|
239
|
+
standalone: true,
|
|
240
|
+
selector: 'app-stack-tight',
|
|
241
|
+
template: `<div style="display: grid; gap: 8px"><ng-content /></div>`,
|
|
242
|
+
})
|
|
243
|
+
export class StackTightLayout {}
|
|
244
|
+
|
|
245
|
+
layoutRegistry.register('stack-tight', StackTightLayout);
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
`LayoutRegistry` is the class behind that singleton, for when you want an
|
|
249
|
+
isolated set of layouts instead of the shared one.
|
|
250
|
+
|
|
112
251
|
## Derived fields with `computeValue`
|
|
113
252
|
|
|
114
253
|
Give a field a `computeValue` to derive its value from the rest of the form data whenever any field changes:
|
|
@@ -196,7 +335,7 @@ The generic adapter forwards only the shared `FieldRendererProps`. For inputs sp
|
|
|
196
335
|
{ name: 'avatar', type: 'file', props: { acceptFile: 'image/*' } }
|
|
197
336
|
```
|
|
198
337
|
|
|
199
|
-
## Legacy setup (
|
|
338
|
+
## Legacy setup (NgModule apps)
|
|
200
339
|
|
|
201
340
|
```ts
|
|
202
341
|
import { BrowserModule } from '@angular/platform-browser';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { FieldDescription, Properties } from '@dynamic-field-kit/core';
|
|
2
|
+
import * as i0 from "@angular/core";
|
|
3
|
+
export declare class DynamicFormDevToolsComponent {
|
|
4
|
+
data: Properties;
|
|
5
|
+
errors: Record<string, string[]>;
|
|
6
|
+
touched: Record<string, boolean>;
|
|
7
|
+
isDirty: boolean;
|
|
8
|
+
fields: FieldDescription[];
|
|
9
|
+
/** Number of fields carrying errors, mirroring the react and vue overlays. */
|
|
10
|
+
errorCount(): number;
|
|
11
|
+
isOpen: import("@angular/core").WritableSignal<boolean>;
|
|
12
|
+
activeTab: import("@angular/core").WritableSignal<"data" | "meta" | "errors" | "fields">;
|
|
13
|
+
tabs: Array<'data' | 'errors' | 'meta' | 'fields'>;
|
|
14
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<DynamicFormDevToolsComponent, never>;
|
|
15
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<DynamicFormDevToolsComponent, "dfk-dev-tools", never, { "data": { "alias": "data"; "required": false; }; "errors": { "alias": "errors"; "required": false; }; "touched": { "alias": "touched"; "required": false; }; "isDirty": { "alias": "isDirty"; "required": false; }; "fields": { "alias": "fields"; "required": false; }; }, {}, never, never, true, never>;
|
|
16
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AfterViewInit, EventEmitter, OnChanges, OnDestroy, SimpleChanges, ViewContainerRef } from '@angular/core';
|
|
2
2
|
import { FieldTypeKey, Properties } from '@dynamic-field-kit/core';
|
|
3
|
-
import { BaseInputComponent } from './BaseInput';
|
|
3
|
+
import { BaseInputComponent } from './BaseInput.js';
|
|
4
4
|
import * as i0 from "@angular/core";
|
|
5
5
|
export declare class DynamicInput extends BaseInputComponent implements OnChanges, AfterViewInit, OnDestroy {
|
|
6
6
|
type: FieldTypeKey;
|
|
@@ -29,6 +29,7 @@ export declare class DynamicInput extends BaseInputComponent implements OnChange
|
|
|
29
29
|
private applyExtraProps;
|
|
30
30
|
private bindOutputs;
|
|
31
31
|
private emitValue;
|
|
32
|
+
private renderDefaultFallbackHTML5;
|
|
32
33
|
private renderError;
|
|
33
34
|
static ɵfac: i0.ɵɵFactoryDeclaration<DynamicInput, never>;
|
|
34
35
|
static ɵcmp: i0.ɵɵComponentDeclaration<DynamicInput, "dfk-dynamic-input", never, { "type": { "alias": "type"; "required": false; }; "extraProps": { "alias": "extraProps"; "required": false; }; }, { "valueChange": "valueChange"; "onChange": "onChange"; }, never, never, true, never>;
|
|
@@ -13,10 +13,16 @@ export declare class FieldInput implements OnChanges {
|
|
|
13
13
|
value: unknown;
|
|
14
14
|
key: string;
|
|
15
15
|
}>;
|
|
16
|
+
/**
|
|
17
|
+
* Emits this field's name when focus leaves it. Driven by `focusout`, which
|
|
18
|
+
* bubbles, so it works for any renderer without the renderer having to
|
|
19
|
+
* declare a blur output of its own.
|
|
20
|
+
*/
|
|
21
|
+
onBlurField: EventEmitter<string>;
|
|
16
22
|
shouldRender: boolean;
|
|
17
23
|
get resolvedOptions(): Record<string, unknown>[] | undefined;
|
|
18
24
|
constructor(cdr: ChangeDetectorRef);
|
|
19
25
|
ngOnChanges(_changes: SimpleChanges): void;
|
|
20
26
|
static ɵfac: i0.ɵɵFactoryDeclaration<FieldInput, never>;
|
|
21
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<FieldInput, "dfk-field-input", never, { "fieldDescription": { "alias": "fieldDescription"; "required": false; }; "value": { "alias": "value"; "required": false; }; "options": { "alias": "options"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "readOnly": { "alias": "readOnly"; "required": false; }; "error": { "alias": "error"; "required": false; }; }, { "onValueChangeField": "onValueChangeField"; }, never, never, true, never>;
|
|
27
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<FieldInput, "dfk-field-input", never, { "fieldDescription": { "alias": "fieldDescription"; "required": false; }; "value": { "alias": "value"; "required": false; }; "options": { "alias": "options"; "required": false; }; "disabled": { "alias": "disabled"; "required": false; }; "readOnly": { "alias": "readOnly"; "required": false; }; "error": { "alias": "error"; "required": false; }; }, { "onValueChangeField": "onValueChangeField"; "onBlurField": "onBlurField"; }, never, never, true, never>;
|
|
22
28
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { ChangeDetectorRef, EventEmitter, OnChanges, OnInit, SimpleChanges } from '@angular/core';
|
|
2
2
|
import { FieldDescription, Properties } from '@dynamic-field-kit/core';
|
|
3
3
|
import type { ValidationResult } from '@dynamic-field-kit/core';
|
|
4
|
-
import { BaseLayoutConfig, LayoutConfig } from '../types/layout';
|
|
4
|
+
import { BaseLayoutConfig, LayoutConfig } from '../types/layout.js';
|
|
5
5
|
import * as i0 from "@angular/core";
|
|
6
6
|
export declare class MultiFieldInput implements OnInit, OnChanges {
|
|
7
7
|
private cdr;
|
|
@@ -9,6 +9,17 @@ export declare class MultiFieldInput implements OnInit, OnChanges {
|
|
|
9
9
|
properties?: Properties;
|
|
10
10
|
onChange: EventEmitter<Properties>;
|
|
11
11
|
validityChange: EventEmitter<ValidationResult>;
|
|
12
|
+
/**
|
|
13
|
+
* Emits a field's name when it loses focus. Touched state is tracked
|
|
14
|
+
* internally either way; this is the hook for driving an external form store
|
|
15
|
+
* - pass `createDynamicFormStore`'s `handleBlur` to get its `touched` map and
|
|
16
|
+
* `validateOnBlur` behaviour.
|
|
17
|
+
*/
|
|
18
|
+
onBlurField: EventEmitter<string>;
|
|
19
|
+
private touchedFields;
|
|
20
|
+
handleBlurField(fieldName: string): void;
|
|
21
|
+
/** Whether this field has been blurred at least once. */
|
|
22
|
+
isTouched(fieldName: string): boolean;
|
|
12
23
|
layout: LayoutConfig;
|
|
13
24
|
rootData?: Properties;
|
|
14
25
|
data: Properties;
|
|
@@ -45,5 +56,5 @@ export declare class MultiFieldInput implements OnInit, OnChanges {
|
|
|
45
56
|
onGroupItemChange(field: FieldDescription, index: number, next: Properties): void;
|
|
46
57
|
private commitData;
|
|
47
58
|
static ɵfac: i0.ɵɵFactoryDeclaration<MultiFieldInput, never>;
|
|
48
|
-
static ɵcmp: i0.ɵɵComponentDeclaration<MultiFieldInput, "dfk-multi-field-input", never, { "fieldDescriptions": { "alias": "fieldDescriptions"; "required": false; }; "properties": { "alias": "properties"; "required": false; }; "layout": { "alias": "layout"; "required": false; }; "rootData": { "alias": "rootData"; "required": false; }; }, { "onChange": "onChange"; "validityChange": "validityChange"; }, never, never, true, never>;
|
|
59
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<MultiFieldInput, "dfk-multi-field-input", never, { "fieldDescriptions": { "alias": "fieldDescriptions"; "required": false; }; "properties": { "alias": "properties"; "required": false; }; "layout": { "alias": "layout"; "required": false; }; "rootData": { "alias": "rootData"; "required": false; }; }, { "onChange": "onChange"; "validityChange": "validityChange"; "onBlurField": "onBlurField"; }, never, never, true, never>;
|
|
49
60
|
}
|