@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.
Files changed (51) hide show
  1. package/CHANGELOG.md +166 -0
  2. package/LICENSE +21 -0
  3. package/README.md +144 -5
  4. package/dist/components/DynamicFormDevTools.d.ts +16 -0
  5. package/dist/components/DynamicInput.d.ts +2 -1
  6. package/dist/components/FieldInput.d.ts +7 -1
  7. package/dist/components/MultiFieldInput.d.ts +13 -2
  8. package/dist/fesm2022/dynamic-field-kit-angular.mjs +578 -34
  9. package/dist/index.d.ts +1 -1
  10. package/dist/layout/index.d.ts +2 -2
  11. package/dist/lib/dynamic-field-kit.module.d.ts +4 -4
  12. package/dist/lib/dynamic-form.store.d.ts +23 -0
  13. package/dist/public-api.d.ts +10 -8
  14. package/package.json +40 -21
  15. package/dist/README.md +0 -235
  16. package/dist/esm2022/components/BaseInput.mjs +0 -59
  17. package/dist/esm2022/components/DynamicInput.mjs +0 -244
  18. package/dist/esm2022/components/FieldInput.mjs +0 -99
  19. package/dist/esm2022/components/MultiFieldInput.mjs +0 -338
  20. package/dist/esm2022/dynamic-field-kit-angular.mjs +0 -5
  21. package/dist/esm2022/fieldRegistryToken.mjs +0 -12
  22. package/dist/esm2022/layout/defaultLayouts.mjs +0 -114
  23. package/dist/esm2022/layout/index.mjs +0 -3
  24. package/dist/esm2022/layout/layoutRegistry.mjs +0 -14
  25. package/dist/esm2022/lib/dynamic-field-kit.module.mjs +0 -52
  26. package/dist/esm2022/public-api.mjs +0 -18
  27. package/dist/esm2022/types/layout.mjs +0 -2
  28. package/dist/fesm2022/dynamic-field-kit-angular.mjs.map +0 -1
  29. package/ng-package.json +0 -7
  30. package/src/components/BaseInput.ts +0 -57
  31. package/src/components/DynamicInput.ts +0 -280
  32. package/src/components/FieldInput.ts +0 -74
  33. package/src/components/MultiFieldInput.ts +0 -331
  34. package/src/fieldRegistryToken.ts +0 -15
  35. package/src/layout/defaultLayouts.ts +0 -70
  36. package/src/layout/index.ts +0 -2
  37. package/src/layout/layoutRegistry.ts +0 -25
  38. package/src/lib/dynamic-field-kit.module.ts +0 -29
  39. package/src/public-api.ts +0 -40
  40. package/src/types/layout.ts +0 -14
  41. package/test/DynamicInput.spec.ts +0 -230
  42. package/test/FieldInput.spec.ts +0 -146
  43. package/test/MultiFieldInput.spec.ts +0 -256
  44. package/test/helpers/renderers.ts +0 -89
  45. package/test/layout.spec.ts +0 -119
  46. package/test/publicApi.spec.ts +0 -64
  47. package/test/setup.ts +0 -12
  48. package/test/smoke.spec.ts +0 -27
  49. package/tsconfig.json +0 -13
  50. package/tsconfig.spec.json +0 -9
  51. 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
- Demo app: https://github.com/vannt-dev/dynamic-field-kit-demo
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.12 @dynamic-field-kit/angular@^1.2.3
22
+ npm install @dynamic-field-kit/core@^1.5.0 @dynamic-field-kit/angular@^1.5.0
21
23
  ```
22
24
 
23
- ## What it exports
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
- - `validateField` / `validateFields` / `resolveDisabled` / `resolveReadOnly` / `ValidationResult`
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 (Angular 14 and earlier with NgModule)
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
  }