@dynamic-field-kit/angular 1.4.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +190 -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 +58 -24
- 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
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
import { NgIf } from '@angular/common';
|
|
2
|
-
import { Component, EventEmitter, Input, Output } from '@angular/core';
|
|
3
|
-
import { FieldRegistry } from '@dynamic-field-kit/core';
|
|
4
|
-
|
|
5
|
-
// The field types the specs register. Without this augmentation `keyof
|
|
6
|
-
// FieldTypeMap` is `never`, so `registry.register('text', …)` fails to compile
|
|
7
|
-
// once vitest type-checking is enabled. Every spec imports this helper, so the
|
|
8
|
-
// augmentation applies across the whole test compilation.
|
|
9
|
-
declare module '@dynamic-field-kit/core' {
|
|
10
|
-
interface FieldTypeMap {
|
|
11
|
-
text: string;
|
|
12
|
-
number: number;
|
|
13
|
-
defaults: string;
|
|
14
|
-
}
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
@Component({
|
|
18
|
-
selector: 'dfk-test-text',
|
|
19
|
-
standalone: true,
|
|
20
|
-
imports: [NgIf],
|
|
21
|
-
template: `
|
|
22
|
-
<input
|
|
23
|
-
class="txt"
|
|
24
|
-
[value]="value ?? ''"
|
|
25
|
-
[disabled]="!!disabled"
|
|
26
|
-
[readOnly]="!!readOnly"
|
|
27
|
-
[placeholder]="placeholder ?? ''"
|
|
28
|
-
(input)="valueChange.emit($any($event.target).value)"
|
|
29
|
-
/>
|
|
30
|
-
<span class="err" *ngIf="error">{{ errorText }}</span>
|
|
31
|
-
<span class="hint" *ngIf="hint">{{ hint }}</span>
|
|
32
|
-
`,
|
|
33
|
-
})
|
|
34
|
-
export class TextRendererComponent {
|
|
35
|
-
@Input() value?: unknown;
|
|
36
|
-
@Input() label?: string;
|
|
37
|
-
@Input() placeholder?: string;
|
|
38
|
-
@Input() required?: boolean;
|
|
39
|
-
@Input() disabled?: boolean;
|
|
40
|
-
@Input() readOnly?: boolean;
|
|
41
|
-
@Input() error?: string | string[];
|
|
42
|
-
@Input() options?: unknown[];
|
|
43
|
-
@Input() className?: string;
|
|
44
|
-
@Input() description?: string;
|
|
45
|
-
// Not a FieldRendererProps key: proves extraProps reach the instance.
|
|
46
|
-
@Input() hint?: string;
|
|
47
|
-
|
|
48
|
-
@Output() valueChange = new EventEmitter<unknown>();
|
|
49
|
-
|
|
50
|
-
get errorText(): string {
|
|
51
|
-
return ([] as string[]).concat(this.error ?? []).join(', ');
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
@Component({
|
|
56
|
-
selector: 'dfk-test-legacy',
|
|
57
|
-
standalone: true,
|
|
58
|
-
template: `<button class="legacy-btn" (click)="onValueChange.emit('legacy')">
|
|
59
|
-
go
|
|
60
|
-
</button>`,
|
|
61
|
-
})
|
|
62
|
-
export class LegacyOutputRendererComponent {
|
|
63
|
-
@Input() value?: unknown;
|
|
64
|
-
// Deliberately the legacy output name, to cover DynamicInput.bindOutputs.
|
|
65
|
-
@Output() onValueChange = new EventEmitter<unknown>();
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
@Component({
|
|
69
|
-
selector: 'dfk-test-defaults',
|
|
70
|
-
standalone: true,
|
|
71
|
-
template: `<span class="label">{{ label }}</span>`,
|
|
72
|
-
})
|
|
73
|
-
export class DefaultsRendererComponent {
|
|
74
|
-
// Initialized input: proves DynamicInput does not overwrite a renderer's
|
|
75
|
-
// own default with undefined for a prop it was never given.
|
|
76
|
-
@Input() label = 'None';
|
|
77
|
-
@Input() value?: unknown;
|
|
78
|
-
@Output() valueChange = new EventEmitter<unknown>();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export function fallbackRenderer(props: Record<string, unknown>): string {
|
|
82
|
-
return `<span class="fallback">${String(props['label'] ?? '')}:${String(
|
|
83
|
-
props['value'] ?? ''
|
|
84
|
-
)}</span>`;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export function makeRegistry(): FieldRegistry {
|
|
88
|
-
return new FieldRegistry();
|
|
89
|
-
}
|
package/test/layout.spec.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import { Component, TemplateRef, ViewChild } from '@angular/core';
|
|
2
|
-
import { TestBed } from '@angular/core/testing';
|
|
3
|
-
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
-
import {
|
|
5
|
-
ColumnLayout,
|
|
6
|
-
GridLayout,
|
|
7
|
-
RowLayout,
|
|
8
|
-
} from '../src/layout/defaultLayouts';
|
|
9
|
-
import { LayoutRegistry, layoutRegistry } from '../src/layout/layoutRegistry';
|
|
10
|
-
|
|
11
|
-
@Component({
|
|
12
|
-
standalone: true,
|
|
13
|
-
imports: [ColumnLayout, RowLayout, GridLayout],
|
|
14
|
-
template: `
|
|
15
|
-
<ng-template #tpl><span class="child">x</span></ng-template>
|
|
16
|
-
<dfk-column-layout
|
|
17
|
-
[template]="tpl"
|
|
18
|
-
[config]="{ gap: 20 }"
|
|
19
|
-
></dfk-column-layout>
|
|
20
|
-
<dfk-row-layout [template]="tpl"></dfk-row-layout>
|
|
21
|
-
<dfk-grid-layout
|
|
22
|
-
[template]="tpl"
|
|
23
|
-
[config]="{ columns: 3 }"
|
|
24
|
-
></dfk-grid-layout>
|
|
25
|
-
`,
|
|
26
|
-
})
|
|
27
|
-
class LayoutHost {
|
|
28
|
-
@ViewChild('tpl', { static: true }) tpl!: TemplateRef<unknown>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
@Component({
|
|
32
|
-
standalone: true,
|
|
33
|
-
imports: [GridLayout],
|
|
34
|
-
template: `
|
|
35
|
-
<ng-template #tpl><span class="child">x</span></ng-template>
|
|
36
|
-
<dfk-grid-layout [template]="tpl"></dfk-grid-layout>
|
|
37
|
-
`,
|
|
38
|
-
})
|
|
39
|
-
class GridDefaultsHost {
|
|
40
|
-
@ViewChild('tpl', { static: true }) tpl!: TemplateRef<unknown>;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
describe('LayoutRegistry', () => {
|
|
44
|
-
afterEach(() => {
|
|
45
|
-
vi.restoreAllMocks();
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
it('registers and retrieves a layout', () => {
|
|
49
|
-
const registry = new LayoutRegistry();
|
|
50
|
-
registry.register('custom', ColumnLayout);
|
|
51
|
-
|
|
52
|
-
expect(registry.get('custom')).toBe(ColumnLayout);
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('returns undefined for an unknown layout', () => {
|
|
56
|
-
expect(new LayoutRegistry().get('nope')).toBeUndefined();
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('warns when a layout type is registered twice', () => {
|
|
60
|
-
const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
|
|
61
|
-
const registry = new LayoutRegistry();
|
|
62
|
-
registry.register('custom', ColumnLayout);
|
|
63
|
-
registry.register('custom', RowLayout);
|
|
64
|
-
|
|
65
|
-
expect(warn).toHaveBeenCalledTimes(1);
|
|
66
|
-
expect(registry.get('custom')).toBe(RowLayout);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('exports a shared registry instance', () => {
|
|
70
|
-
expect(layoutRegistry).toBeInstanceOf(LayoutRegistry);
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
describe('default layouts', () => {
|
|
75
|
-
it('renders the projected template with the configured gap', () => {
|
|
76
|
-
const fixture = TestBed.createComponent(LayoutHost);
|
|
77
|
-
fixture.detectChanges();
|
|
78
|
-
|
|
79
|
-
const column: HTMLElement = fixture.nativeElement.querySelector(
|
|
80
|
-
'dfk-column-layout > div'
|
|
81
|
-
);
|
|
82
|
-
expect(column.style.flexDirection).toBe('column');
|
|
83
|
-
expect(column.style.gap).toBe('20px');
|
|
84
|
-
expect(column.querySelector('.child')).not.toBeNull();
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
it('defaults the gap to 12px', () => {
|
|
88
|
-
const fixture = TestBed.createComponent(LayoutHost);
|
|
89
|
-
fixture.detectChanges();
|
|
90
|
-
|
|
91
|
-
const row: HTMLElement = fixture.nativeElement.querySelector(
|
|
92
|
-
'dfk-row-layout > div'
|
|
93
|
-
);
|
|
94
|
-
expect(row.style.flexDirection).toBe('row');
|
|
95
|
-
expect(row.style.gap).toBe('12px');
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('renders the grid layout with the configured column count', () => {
|
|
99
|
-
const fixture = TestBed.createComponent(LayoutHost);
|
|
100
|
-
fixture.detectChanges();
|
|
101
|
-
|
|
102
|
-
const grid: HTMLElement = fixture.nativeElement.querySelector(
|
|
103
|
-
'dfk-grid-layout > div'
|
|
104
|
-
);
|
|
105
|
-
expect(grid.style.display).toBe('grid');
|
|
106
|
-
expect(grid.style.gridTemplateColumns).toBe('repeat(3, 1fr)');
|
|
107
|
-
expect(grid.style.gap).toBe('12px');
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
it('defaults the grid columns to 2', () => {
|
|
111
|
-
const fixture = TestBed.createComponent(GridDefaultsHost);
|
|
112
|
-
fixture.detectChanges();
|
|
113
|
-
|
|
114
|
-
const grid: HTMLElement = fixture.nativeElement.querySelector(
|
|
115
|
-
'dfk-grid-layout > div'
|
|
116
|
-
);
|
|
117
|
-
expect(grid.style.gridTemplateColumns).toBe('repeat(2, 1fr)');
|
|
118
|
-
});
|
|
119
|
-
});
|
package/test/publicApi.spec.ts
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { Component } from '@angular/core';
|
|
2
|
-
import { TestBed } from '@angular/core/testing';
|
|
3
|
-
import { FieldRegistry, fieldRegistry } from '@dynamic-field-kit/core';
|
|
4
|
-
import { describe, expect, it } from 'vitest';
|
|
5
|
-
import * as publicApi from '../src/public-api';
|
|
6
|
-
import { FIELD_REGISTRY } from '../src/fieldRegistryToken';
|
|
7
|
-
import { DynamicFieldKitModule } from '../src/lib/dynamic-field-kit.module';
|
|
8
|
-
import { makeRegistry, TextRendererComponent } from './helpers/renderers';
|
|
9
|
-
|
|
10
|
-
describe('public API', () => {
|
|
11
|
-
it('exports the components, registry and validation helpers', () => {
|
|
12
|
-
expect(publicApi.DynamicInput).toBeDefined();
|
|
13
|
-
expect(publicApi.FieldInput).toBeDefined();
|
|
14
|
-
expect(publicApi.MultiFieldInput).toBeDefined();
|
|
15
|
-
expect(publicApi.FIELD_REGISTRY).toBe(FIELD_REGISTRY);
|
|
16
|
-
expect(publicApi.FieldRegistry).toBe(FieldRegistry);
|
|
17
|
-
expect(typeof publicApi.validateField).toBe('function');
|
|
18
|
-
expect(typeof publicApi.validateFields).toBe('function');
|
|
19
|
-
expect(typeof publicApi.resolveDisabled).toBe('function');
|
|
20
|
-
expect(typeof publicApi.resolveReadOnly).toBe('function');
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
describe('FIELD_REGISTRY token', () => {
|
|
25
|
-
it('defaults to the process-wide singleton', () => {
|
|
26
|
-
TestBed.configureTestingModule({});
|
|
27
|
-
|
|
28
|
-
expect(TestBed.inject(FIELD_REGISTRY)).toBe(fieldRegistry);
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it('can be overridden with a scoped registry', () => {
|
|
32
|
-
const scoped = makeRegistry();
|
|
33
|
-
TestBed.configureTestingModule({
|
|
34
|
-
providers: [{ provide: FIELD_REGISTRY, useValue: scoped }],
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
expect(TestBed.inject(FIELD_REGISTRY)).toBe(scoped);
|
|
38
|
-
expect(TestBed.inject(FIELD_REGISTRY)).not.toBe(fieldRegistry);
|
|
39
|
-
});
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
describe('DynamicFieldKitModule', () => {
|
|
43
|
-
@Component({
|
|
44
|
-
standalone: true,
|
|
45
|
-
imports: [DynamicFieldKitModule],
|
|
46
|
-
template: `<dfk-field-input
|
|
47
|
-
[fieldDescription]="{ name: 'a', type: 'text' }"
|
|
48
|
-
></dfk-field-input>`,
|
|
49
|
-
})
|
|
50
|
-
class ModuleHost {}
|
|
51
|
-
|
|
52
|
-
it('exports the components for template use', () => {
|
|
53
|
-
const scoped = makeRegistry();
|
|
54
|
-
scoped.register('text', TextRendererComponent as never);
|
|
55
|
-
TestBed.configureTestingModule({
|
|
56
|
-
providers: [{ provide: FIELD_REGISTRY, useValue: scoped }],
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
const fixture = TestBed.createComponent(ModuleHost);
|
|
60
|
-
fixture.detectChanges();
|
|
61
|
-
|
|
62
|
-
expect(fixture.nativeElement.querySelector('input.txt')).not.toBeNull();
|
|
63
|
-
});
|
|
64
|
-
});
|
package/test/setup.ts
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import '@analogjs/vitest-angular/setup-zone';
|
|
2
|
-
|
|
3
|
-
import { getTestBed } from '@angular/core/testing';
|
|
4
|
-
import {
|
|
5
|
-
BrowserDynamicTestingModule,
|
|
6
|
-
platformBrowserDynamicTesting,
|
|
7
|
-
} from '@angular/platform-browser-dynamic/testing';
|
|
8
|
-
|
|
9
|
-
getTestBed().initTestEnvironment(
|
|
10
|
-
BrowserDynamicTestingModule,
|
|
11
|
-
platformBrowserDynamicTesting()
|
|
12
|
-
);
|
package/test/smoke.spec.ts
DELETED
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import { Component } from '@angular/core';
|
|
2
|
-
import { TestBed } from '@angular/core/testing';
|
|
3
|
-
import { fieldRegistry } from '@dynamic-field-kit/core';
|
|
4
|
-
import { describe, expect, it } from 'vitest';
|
|
5
|
-
|
|
6
|
-
@Component({
|
|
7
|
-
selector: 'dfk-smoke',
|
|
8
|
-
standalone: true,
|
|
9
|
-
template: `<span class="smoke">{{ label }}</span>`,
|
|
10
|
-
})
|
|
11
|
-
class SmokeComponent {
|
|
12
|
-
label = 'mounted';
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
describe('vitest + Angular infrastructure', () => {
|
|
16
|
-
it('imports @dynamic-field-kit/core', () => {
|
|
17
|
-
expect(typeof fieldRegistry.register).toBe('function');
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
it('mounts a component through TestBed', () => {
|
|
21
|
-
const fixture = TestBed.createComponent(SmokeComponent);
|
|
22
|
-
fixture.detectChanges();
|
|
23
|
-
|
|
24
|
-
const el: HTMLElement = fixture.nativeElement.querySelector('.smoke');
|
|
25
|
-
expect(el.textContent).toBe('mounted');
|
|
26
|
-
});
|
|
27
|
-
});
|
package/tsconfig.json
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"extends": "../../tsconfig.base.json",
|
|
3
|
-
"compilerOptions": {
|
|
4
|
-
"composite": false,
|
|
5
|
-
"declaration": true,
|
|
6
|
-
"outDir": "dist",
|
|
7
|
-
"target": "ES2019",
|
|
8
|
-
"module": "ES2020",
|
|
9
|
-
"experimentalDecorators": true,
|
|
10
|
-
"emitDecoratorMetadata": true
|
|
11
|
-
},
|
|
12
|
-
"include": ["src/**/*"]
|
|
13
|
-
}
|
package/tsconfig.spec.json
DELETED
package/vitest.config.ts
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
/// <reference types="vitest" />
|
|
2
|
-
import angular from '@analogjs/vite-plugin-angular';
|
|
3
|
-
import { defineConfig } from 'vite';
|
|
4
|
-
|
|
5
|
-
export default defineConfig({
|
|
6
|
-
// disableTypeChecking:false turns Angular semantic diagnostics (NG2007 etc.)
|
|
7
|
-
// back on for specs — otherwise only the ng-packagr build typechecks src, and
|
|
8
|
-
// spec files get no type checking at all.
|
|
9
|
-
plugins: [angular({ disableTypeChecking: false })],
|
|
10
|
-
test: {
|
|
11
|
-
globals: true,
|
|
12
|
-
environment: 'jsdom',
|
|
13
|
-
setupFiles: ['test/setup.ts'],
|
|
14
|
-
include: ['test/**/*.spec.ts'],
|
|
15
|
-
pool: 'forks',
|
|
16
|
-
coverage: {
|
|
17
|
-
provider: 'istanbul',
|
|
18
|
-
reportsDirectory: 'coverage',
|
|
19
|
-
reporter: ['lcov', 'text-summary'],
|
|
20
|
-
include: ['src/**/*.ts'],
|
|
21
|
-
// Coverage floor — fails the run when coverage drops below these numbers.
|
|
22
|
-
thresholds: {
|
|
23
|
-
statements: 85,
|
|
24
|
-
branches: 75,
|
|
25
|
-
functions: 85,
|
|
26
|
-
lines: 85,
|
|
27
|
-
},
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
});
|