ngx-dynamic-entity 1.6.0 → 1.8.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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken, makeEnvironmentProviders, inject, Injectable, signal, Input, ChangeDetectionStrategy, Component, DestroyRef, Injector, isDevMode, ViewContainerRef, ViewChild, forwardRef,
|
|
2
|
+
import { InjectionToken, makeEnvironmentProviders, inject, Injectable, signal, Input, ChangeDetectionStrategy, Component, ChangeDetectorRef, DestroyRef, Injector, isDevMode, ViewContainerRef, ViewChild, forwardRef, Optional, Inject, EventEmitter, computed, HostListener, ViewChildren, Output, ElementRef, afterNextRender } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/forms';
|
|
4
4
|
import { ReactiveFormsModule, Validators, FormBuilder, FormArray, FormGroup } from '@angular/forms';
|
|
5
5
|
import { resolveLabel, formatDisplayValue, normalizeLookupValues, lookupValuesToOptions, valuesMatch, getOptionStoredValue, resolveOptionLabel, resolveOptionValue, buildReferenceCacheKey, normalizeReferenceOptions, entityKeyFromCacheKey, applyCascadeFilter, resolveEffectiveMask, shouldMaskField, findTab, fileRefName, evaluateFormRules, filterRulesForTab, evaluateFieldVisibility, normalizeConfigOptions, migrateRecord, getTabData, getValueByPath, setTabData, setValueByPath, normalizeArrayStructures, ambiguousFieldIds, parseFieldRef, toRefToken, collectFieldScopes, fieldRefFor, refOf, getTabPath, applyAutoPatch, applyPatchOnTrue } from '@dynamic-entity/core';
|
|
@@ -152,6 +152,25 @@ const SYSTEM_DEFAULT_CAN_EDIT = new InjectionToken('SYSTEM_DEFAULT_CAN_EDIT');
|
|
|
152
152
|
* Enables cross-entity field referencing (`isReferenced`, `referencedEntityKey`, `referencedFieldId`, `hasDrift`).
|
|
153
153
|
*/
|
|
154
154
|
const CONFIG_SOURCE = new InjectionToken('CONFIG_SOURCE');
|
|
155
|
+
/**
|
|
156
|
+
* Turns the markdown a `markdown` field stores into HTML for display.
|
|
157
|
+
*
|
|
158
|
+
* Optional, and deliberately not bundled: these packages declare no runtime dependencies
|
|
159
|
+
* beyond `tslib`, and a markdown parser is a large one to force on every consumer. Without
|
|
160
|
+
* a renderer the field still works — it stores and shows the source text, with line breaks
|
|
161
|
+
* preserved and nothing interpreted.
|
|
162
|
+
*
|
|
163
|
+
* The returned HTML is bound through `[innerHTML]`, so Angular's sanitizer strips scripts
|
|
164
|
+
* and event handlers before it reaches the DOM. That is a backstop, not a licence: a
|
|
165
|
+
* renderer should still be configured to escape raw HTML in its input, because sanitizing
|
|
166
|
+
* removes the dangerous parts silently rather than telling the author their content was
|
|
167
|
+
* altered.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* import { marked } from 'marked';
|
|
171
|
+
* { provide: MARKDOWN_RENDERER, useValue: (src: string) => marked.parse(src) as string }
|
|
172
|
+
*/
|
|
173
|
+
const MARKDOWN_RENDERER = new InjectionToken('MARKDOWN_RENDERER');
|
|
155
174
|
|
|
156
175
|
/**
|
|
157
176
|
* Provider function for ngx-dynamic-entity.
|
|
@@ -426,6 +445,233 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
426
445
|
type: Input
|
|
427
446
|
}] } });
|
|
428
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Markdown field: a long-form input that stores **markdown source**, never HTML.
|
|
450
|
+
*
|
|
451
|
+
* Storing the source rather than rendered HTML is the whole design. The record stays plain
|
|
452
|
+
* text — diffable, portable, safe to log, and impossible to turn into stored XSS by writing
|
|
453
|
+
* it into a database. Rendering happens at display time and only when a consumer asks for
|
|
454
|
+
* it, via `MARKDOWN_RENDERER`.
|
|
455
|
+
*
|
|
456
|
+
* Without a renderer this degrades to exactly what `textarea` does, plus preserved line
|
|
457
|
+
* breaks: the source is shown as text with nothing interpreted. That is the default because
|
|
458
|
+
* these packages declare no runtime dependencies beyond `tslib`, and a markdown parser is a
|
|
459
|
+
* large thing to force on a consumer who wanted a form library.
|
|
460
|
+
*
|
|
461
|
+
* With a renderer, its HTML is bound through `[innerHTML]`, which Angular sanitizes — see
|
|
462
|
+
* the token's own note on why that is a backstop rather than a licence.
|
|
463
|
+
*/
|
|
464
|
+
class MarkdownFieldComponent {
|
|
465
|
+
field;
|
|
466
|
+
control;
|
|
467
|
+
language = 'en';
|
|
468
|
+
readonly = false;
|
|
469
|
+
masked = false;
|
|
470
|
+
/** Optional — the field is fully usable without one. */
|
|
471
|
+
render = inject(MARKDOWN_RENDERER, { optional: true });
|
|
472
|
+
cdr = inject(ChangeDetectorRef);
|
|
473
|
+
destroyRef = inject(DestroyRef);
|
|
474
|
+
previewing = signal(false, ...(ngDevMode ? [{ debugName: "previewing" }] : /* istanbul ignore next */ []));
|
|
475
|
+
valueSub;
|
|
476
|
+
/**
|
|
477
|
+
* Re-render when the value changes from outside this component.
|
|
478
|
+
*
|
|
479
|
+
* `rendered()` reads `control.value`, which under OnPush is neither an input nor a
|
|
480
|
+
* template event: typing in the textarea updates through `formControl`, but a host
|
|
481
|
+
* calling `patchValue`, a rule, or an `autoPatch` mapping does not mark this component
|
|
482
|
+
* dirty. The preview and the read-only view would keep showing the previous document
|
|
483
|
+
* while the record already held a different one.
|
|
484
|
+
*/
|
|
485
|
+
ngOnChanges(changes) {
|
|
486
|
+
if (!changes['control'])
|
|
487
|
+
return;
|
|
488
|
+
this.valueSub?.unsubscribe();
|
|
489
|
+
this.valueSub = this.control?.valueChanges?.subscribe(() => this.cdr.markForCheck());
|
|
490
|
+
if (this.valueSub)
|
|
491
|
+
this.destroyRef.onDestroy(() => this.valueSub?.unsubscribe());
|
|
492
|
+
}
|
|
493
|
+
/** A Preview tab that can only ever show the source back is worse than no tab. */
|
|
494
|
+
get canPreview() {
|
|
495
|
+
return !!this.render;
|
|
496
|
+
}
|
|
497
|
+
get label() {
|
|
498
|
+
return resolveLabel(this.field?.label, this.language);
|
|
499
|
+
}
|
|
500
|
+
get placeholder() {
|
|
501
|
+
return resolveLabel(this.field?.placeholder, this.language);
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Rendered HTML, or null when there is no renderer or nothing to render.
|
|
505
|
+
*
|
|
506
|
+
* A renderer is consumer code and may throw on input it does not like. Letting that
|
|
507
|
+
* escape would take down the whole form over one malformed field, so it falls back to the
|
|
508
|
+
* source text — which is what the field would have shown with no renderer at all.
|
|
509
|
+
*/
|
|
510
|
+
rendered() {
|
|
511
|
+
const source = this.control?.value;
|
|
512
|
+
if (!this.render || typeof source !== 'string' || source === '')
|
|
513
|
+
return null;
|
|
514
|
+
try {
|
|
515
|
+
return this.render(source);
|
|
516
|
+
}
|
|
517
|
+
catch {
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MarkdownFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
522
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: MarkdownFieldComponent, isStandalone: true, selector: "ngx-markdown-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, usesOnChanges: true, ngImport: i0, template: `
|
|
523
|
+
<div
|
|
524
|
+
class="ngx-field ngx-field--markdown"
|
|
525
|
+
[attr.data-testid]="'field-' + field.id"
|
|
526
|
+
[attr.data-field-type]="field.type"
|
|
527
|
+
[class.ngx-field--readonly]="readonly"
|
|
528
|
+
[class.ngx-field--masked]="masked"
|
|
529
|
+
>
|
|
530
|
+
<label class="ngx-field__label" [attr.for]="field.id">{{ label }}</label>
|
|
531
|
+
|
|
532
|
+
@if (masked) {
|
|
533
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
534
|
+
} @else if (readonly) {
|
|
535
|
+
@if (rendered(); as html) {
|
|
536
|
+
<div class="ngx-field__value ngx-field__markdown" [attr.data-testid]="'field-' + field.id + '-value'" [innerHTML]="html"></div>
|
|
537
|
+
} @else {
|
|
538
|
+
<!-- No renderer: the source is the display. The stylesheet keeps its line
|
|
539
|
+
breaks with white-space: pre-wrap, and interpolation escapes it. -->
|
|
540
|
+
<span class="ngx-field__value ngx-field__markdown--source" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value }}</span>
|
|
541
|
+
}
|
|
542
|
+
} @else {
|
|
543
|
+
<div class="ngx-field__markdown-editor">
|
|
544
|
+
@if (canPreview) {
|
|
545
|
+
<div class="ngx-field__markdown-tabs" role="group" [attr.aria-label]="label + ' editor mode'">
|
|
546
|
+
<button
|
|
547
|
+
type="button"
|
|
548
|
+
class="ngx-field__markdown-tab"
|
|
549
|
+
[attr.data-testid]="'field-' + field.id + '-write'"
|
|
550
|
+
[attr.aria-pressed]="!previewing()"
|
|
551
|
+
(click)="previewing.set(false)"
|
|
552
|
+
>
|
|
553
|
+
Write
|
|
554
|
+
</button>
|
|
555
|
+
<button
|
|
556
|
+
type="button"
|
|
557
|
+
class="ngx-field__markdown-tab"
|
|
558
|
+
[attr.data-testid]="'field-' + field.id + '-preview'"
|
|
559
|
+
[attr.aria-pressed]="previewing()"
|
|
560
|
+
(click)="previewing.set(true)"
|
|
561
|
+
>
|
|
562
|
+
Preview
|
|
563
|
+
</button>
|
|
564
|
+
</div>
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
@if (previewing()) {
|
|
568
|
+
<div class="ngx-field__markdown ngx-field__markdown--preview" [attr.data-testid]="'field-' + field.id + '-preview-body'" [innerHTML]="rendered()"></div>
|
|
569
|
+
} @else {
|
|
570
|
+
<textarea
|
|
571
|
+
[id]="field.id"
|
|
572
|
+
class="ngx-field__input ngx-field__input--markdown"
|
|
573
|
+
[attr.data-testid]="'field-' + field.id + '-input'"
|
|
574
|
+
[formControl]="$any(control)"
|
|
575
|
+
[placeholder]="placeholder"
|
|
576
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
577
|
+
rows="6"
|
|
578
|
+
></textarea>
|
|
579
|
+
}
|
|
580
|
+
</div>
|
|
581
|
+
|
|
582
|
+
@if (control.invalid && control.touched) {
|
|
583
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
</div>
|
|
587
|
+
`, isInline: true, dependencies: [{ kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.FormControlDirective, selector: "[formControl]", inputs: ["formControl", "disabled", "ngModel"], outputs: ["ngModelChange"], exportAs: ["ngForm"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
588
|
+
}
|
|
589
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: MarkdownFieldComponent, decorators: [{
|
|
590
|
+
type: Component,
|
|
591
|
+
args: [{
|
|
592
|
+
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
593
|
+
selector: 'ngx-markdown-field',
|
|
594
|
+
standalone: true,
|
|
595
|
+
imports: [ReactiveFormsModule],
|
|
596
|
+
template: `
|
|
597
|
+
<div
|
|
598
|
+
class="ngx-field ngx-field--markdown"
|
|
599
|
+
[attr.data-testid]="'field-' + field.id"
|
|
600
|
+
[attr.data-field-type]="field.type"
|
|
601
|
+
[class.ngx-field--readonly]="readonly"
|
|
602
|
+
[class.ngx-field--masked]="masked"
|
|
603
|
+
>
|
|
604
|
+
<label class="ngx-field__label" [attr.for]="field.id">{{ label }}</label>
|
|
605
|
+
|
|
606
|
+
@if (masked) {
|
|
607
|
+
<span class="ngx-field__value ngx-field__value--masked" [attr.data-testid]="'field-' + field.id + '-masked'">XXXXXXXXX</span>
|
|
608
|
+
} @else if (readonly) {
|
|
609
|
+
@if (rendered(); as html) {
|
|
610
|
+
<div class="ngx-field__value ngx-field__markdown" [attr.data-testid]="'field-' + field.id + '-value'" [innerHTML]="html"></div>
|
|
611
|
+
} @else {
|
|
612
|
+
<!-- No renderer: the source is the display. The stylesheet keeps its line
|
|
613
|
+
breaks with white-space: pre-wrap, and interpolation escapes it. -->
|
|
614
|
+
<span class="ngx-field__value ngx-field__markdown--source" [attr.data-testid]="'field-' + field.id + '-value'">{{ control.value }}</span>
|
|
615
|
+
}
|
|
616
|
+
} @else {
|
|
617
|
+
<div class="ngx-field__markdown-editor">
|
|
618
|
+
@if (canPreview) {
|
|
619
|
+
<div class="ngx-field__markdown-tabs" role="group" [attr.aria-label]="label + ' editor mode'">
|
|
620
|
+
<button
|
|
621
|
+
type="button"
|
|
622
|
+
class="ngx-field__markdown-tab"
|
|
623
|
+
[attr.data-testid]="'field-' + field.id + '-write'"
|
|
624
|
+
[attr.aria-pressed]="!previewing()"
|
|
625
|
+
(click)="previewing.set(false)"
|
|
626
|
+
>
|
|
627
|
+
Write
|
|
628
|
+
</button>
|
|
629
|
+
<button
|
|
630
|
+
type="button"
|
|
631
|
+
class="ngx-field__markdown-tab"
|
|
632
|
+
[attr.data-testid]="'field-' + field.id + '-preview'"
|
|
633
|
+
[attr.aria-pressed]="previewing()"
|
|
634
|
+
(click)="previewing.set(true)"
|
|
635
|
+
>
|
|
636
|
+
Preview
|
|
637
|
+
</button>
|
|
638
|
+
</div>
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
@if (previewing()) {
|
|
642
|
+
<div class="ngx-field__markdown ngx-field__markdown--preview" [attr.data-testid]="'field-' + field.id + '-preview-body'" [innerHTML]="rendered()"></div>
|
|
643
|
+
} @else {
|
|
644
|
+
<textarea
|
|
645
|
+
[id]="field.id"
|
|
646
|
+
class="ngx-field__input ngx-field__input--markdown"
|
|
647
|
+
[attr.data-testid]="'field-' + field.id + '-input'"
|
|
648
|
+
[formControl]="$any(control)"
|
|
649
|
+
[placeholder]="placeholder"
|
|
650
|
+
[attr.disabled]="field.disabled ? true : null"
|
|
651
|
+
rows="6"
|
|
652
|
+
></textarea>
|
|
653
|
+
}
|
|
654
|
+
</div>
|
|
655
|
+
|
|
656
|
+
@if (control.invalid && control.touched) {
|
|
657
|
+
<span class="ngx-field__error" [attr.data-testid]="'field-' + field.id + '-error'">This field has an error</span>
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
</div>
|
|
661
|
+
`,
|
|
662
|
+
}]
|
|
663
|
+
}], propDecorators: { field: [{
|
|
664
|
+
type: Input
|
|
665
|
+
}], control: [{
|
|
666
|
+
type: Input
|
|
667
|
+
}], language: [{
|
|
668
|
+
type: Input
|
|
669
|
+
}], readonly: [{
|
|
670
|
+
type: Input
|
|
671
|
+
}], masked: [{
|
|
672
|
+
type: Input
|
|
673
|
+
}] } });
|
|
674
|
+
|
|
429
675
|
class NumberFieldComponent {
|
|
430
676
|
messages = inject(ValidationMessagesService);
|
|
431
677
|
field;
|
|
@@ -1017,12 +1263,12 @@ class DateFieldComponent {
|
|
|
1017
1263
|
formatDate(value) {
|
|
1018
1264
|
if (!value)
|
|
1019
1265
|
return '—';
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1266
|
+
// `new Date('nonsense')` does not throw and `toLocaleDateString()` returns the *string*
|
|
1267
|
+
// "Invalid Date", so the try/catch this replaced never fired and readers saw that text
|
|
1268
|
+
// instead of their data. Records outlive schemas — a field retyped from text to date can
|
|
1269
|
+
// hold anything — so an unparseable value is shown as stored.
|
|
1270
|
+
const parsed = new Date(value);
|
|
1271
|
+
return Number.isNaN(parsed.getTime()) ? value : parsed.toLocaleDateString();
|
|
1026
1272
|
}
|
|
1027
1273
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DateFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1028
1274
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: DateFieldComponent, isStandalone: true, selector: "ngx-date-field", inputs: { field: "field", control: "control", language: "language", readonly: "readonly", masked: "masked" }, ngImport: i0, template: `
|
|
@@ -1910,7 +2156,11 @@ class RadioFieldComponent {
|
|
|
1910
2156
|
* into an id attribute.
|
|
1911
2157
|
*/
|
|
1912
2158
|
getRadioId(option) {
|
|
1913
|
-
|
|
2159
|
+
// `??` cannot fire here: `resolveOptionValue` returns '' for an empty option, not null,
|
|
2160
|
+
// so every valueless option produced the same id — and duplicate ids break the `for`
|
|
2161
|
+
// that ties each label to its input.
|
|
2162
|
+
const raw = String(this.getOptVal(option) ?? '').trim();
|
|
2163
|
+
const text = raw || 'opt';
|
|
1914
2164
|
return `${this.field.id}-${text.trim().replace(/\s+/g, '_').toLowerCase()}`;
|
|
1915
2165
|
}
|
|
1916
2166
|
getOptVal(option) {
|
|
@@ -3694,6 +3944,7 @@ function builtInFieldTypes() {
|
|
|
3694
3944
|
return {
|
|
3695
3945
|
text: TextFieldComponent,
|
|
3696
3946
|
textarea: TextareaFieldComponent,
|
|
3947
|
+
markdown: MarkdownFieldComponent,
|
|
3697
3948
|
number: NumberFieldComponent,
|
|
3698
3949
|
currency: CurrencyFieldComponent,
|
|
3699
3950
|
email: EmailFieldComponent,
|
|
@@ -4086,11 +4337,50 @@ class DynamicFormComponent {
|
|
|
4086
4337
|
}
|
|
4087
4338
|
if (changes['config'] || changes['initialData']) {
|
|
4088
4339
|
this.buildForm();
|
|
4089
|
-
|
|
4090
|
-
|
|
4340
|
+
// Clear *before* the guard below, not after it.
|
|
4341
|
+
//
|
|
4342
|
+
// The reset used to sit behind `if (visibleTabs.length === 0) return`, so a swap that
|
|
4343
|
+
// arrived while no tab was visible — a config still being applied, or every tab hidden
|
|
4344
|
+
// by a rule at that instant — skipped it entirely and the previous record's tab
|
|
4345
|
+
// survived into the next one. Clearing is unconditional and cheap, and safe on its own
|
|
4346
|
+
// because `activeTabConfig` already falls back to the first visible tab when nothing
|
|
4347
|
+
// is selected. Selecting the tab is what needs tabs to exist; forgetting the old one
|
|
4348
|
+
// does not.
|
|
4349
|
+
if (changes['config'] || this.isRecordSwap(changes['initialData'])) {
|
|
4350
|
+
this.activeTab.set('');
|
|
4351
|
+
this.activeSubTab.set('');
|
|
4352
|
+
}
|
|
4353
|
+
if (this.visibleTabs.length === 0)
|
|
4354
|
+
return;
|
|
4355
|
+
// The first tab used to be chosen only when no tab was active yet, and nothing ever
|
|
4356
|
+
// cleared `activeTab` — so a form that stays mounted while `initialData` is swapped
|
|
4357
|
+
// opened the next record on the tab the previous one was left on. The demo escaped it
|
|
4358
|
+
// by destroying the form between records; a host that keeps it mounted did not.
|
|
4359
|
+
//
|
|
4360
|
+
// Resetting here is consistent with what already happens: `buildForm` assigns a brand
|
|
4361
|
+
// new FormGroup, so a changed `initialData` has already discarded every control and
|
|
4362
|
+
// all validation state. The tab was the one thing pretending nothing had changed.
|
|
4363
|
+
if (!this.activeTab()) {
|
|
4364
|
+
// No focus steal: the panel gains focus when a *user* picks a tab, not when a host
|
|
4365
|
+
// swaps the record underneath them.
|
|
4366
|
+
this.setActiveTab(this.visibleTabs[0].id, { focusPanel: false });
|
|
4091
4367
|
}
|
|
4092
4368
|
}
|
|
4093
4369
|
}
|
|
4370
|
+
/**
|
|
4371
|
+
* Whether an `initialData` change represents loading a different record.
|
|
4372
|
+
*
|
|
4373
|
+
* Not every change does. A template binding like `[initialData]="record() || {}"` yields a
|
|
4374
|
+
* fresh object literal on each evaluation, so `ngOnChanges` fires with two empty objects
|
|
4375
|
+
* and nothing has actually been loaded — resetting there would drag someone back to the
|
|
4376
|
+
* first tab while they were filling in a new record on the third.
|
|
4377
|
+
*/
|
|
4378
|
+
isRecordSwap(change) {
|
|
4379
|
+
if (!change || change.isFirstChange())
|
|
4380
|
+
return false;
|
|
4381
|
+
const empty = (v) => v === null || v === undefined || (typeof v === 'object' && Object.keys(v).length === 0);
|
|
4382
|
+
return !(empty(change.previousValue) && empty(change.currentValue));
|
|
4383
|
+
}
|
|
4094
4384
|
ngOnDestroy() {
|
|
4095
4385
|
this.valueSub?.unsubscribe();
|
|
4096
4386
|
this.statusSub?.unsubscribe();
|
|
@@ -5371,5 +5661,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
5371
5661
|
* Generated bundle index. Do not edit.
|
|
5372
5662
|
*/
|
|
5373
5663
|
|
|
5374
|
-
export { ASYNC_VALIDATOR_REGISTRY, ArrayFieldComponent, BooleanFieldComponent, COMMON_MODULES_REGISTRY, CONFIG_SOURCE, CascadeDataService, CheckboxFieldComponent, ConfigSourceService, CurrencyFieldComponent, DEFAULT_VALIDATION_MESSAGES, DateFieldComponent, DateTimeFieldComponent, DropdownFieldComponent, DynamicFieldComponent, DynamicFormComponent, DynamicRecordFormComponent, ENTITY_REF_CACHE_STORE, ENTITY_REF_REGISTRY, EmailFieldComponent, EntityRefFieldComponent, EntityRefRegistryService, EntityRefSelectionService, EntityReferenceService, FIELD_TYPE_REGISTRY, FIELD_TYPE_SETS, FieldRegistryService, FileFieldComponent, GroupFieldComponent, HOOK_REGISTRY, HookRegistryService, ImageFieldComponent, InMemoryEntityRefCacheStore, LOOKUP_REGISTRY, LookupRegistryService, MASKED_ROLES, MonthYearFieldComponent, MultiSelectFieldComponent, NumberFieldComponent, PasswordFieldComponent, RECORD_MIGRATIONS, RadioFieldComponent, RbacService, RulesEvaluationService, SYSTEM_DEFAULT_CAN_EDIT, TextFieldComponent, TextareaFieldComponent, TimeFieldComponent, UPLOAD_HANDLER, VALIDATION_MESSAGES, VALIDATOR_REGISTRY, ValidationMessagesService, ValidatorRegistryService, builtInFieldTypes, provideBuiltInFieldTypes, provideFieldTypes, provideNgxDynamicEntity, refreshChoiceOptions };
|
|
5664
|
+
export { ASYNC_VALIDATOR_REGISTRY, ArrayFieldComponent, BooleanFieldComponent, COMMON_MODULES_REGISTRY, CONFIG_SOURCE, CascadeDataService, CheckboxFieldComponent, ConfigSourceService, CurrencyFieldComponent, DEFAULT_VALIDATION_MESSAGES, DateFieldComponent, DateTimeFieldComponent, DropdownFieldComponent, DynamicFieldComponent, DynamicFormComponent, DynamicRecordFormComponent, ENTITY_REF_CACHE_STORE, ENTITY_REF_REGISTRY, EmailFieldComponent, EntityRefFieldComponent, EntityRefRegistryService, EntityRefSelectionService, EntityReferenceService, FIELD_TYPE_REGISTRY, FIELD_TYPE_SETS, FieldRegistryService, FileFieldComponent, GroupFieldComponent, HOOK_REGISTRY, HookRegistryService, ImageFieldComponent, InMemoryEntityRefCacheStore, LOOKUP_REGISTRY, LookupRegistryService, MARKDOWN_RENDERER, MASKED_ROLES, MarkdownFieldComponent, MonthYearFieldComponent, MultiSelectFieldComponent, NumberFieldComponent, PasswordFieldComponent, RECORD_MIGRATIONS, RadioFieldComponent, RbacService, RulesEvaluationService, SYSTEM_DEFAULT_CAN_EDIT, TextFieldComponent, TextareaFieldComponent, TimeFieldComponent, UPLOAD_HANDLER, VALIDATION_MESSAGES, VALIDATOR_REGISTRY, ValidationMessagesService, ValidatorRegistryService, builtInFieldTypes, provideBuiltInFieldTypes, provideFieldTypes, provideNgxDynamicEntity, refreshChoiceOptions };
|
|
5375
5665
|
//# sourceMappingURL=ngx-dynamic-entity.mjs.map
|