ngx-dynamic-entity 1.7.0 → 1.8.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.
|
@@ -1263,12 +1263,12 @@ class DateFieldComponent {
|
|
|
1263
1263
|
formatDate(value) {
|
|
1264
1264
|
if (!value)
|
|
1265
1265
|
return '—';
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
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();
|
|
1272
1272
|
}
|
|
1273
1273
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: DateFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1274
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: `
|
|
@@ -2156,7 +2156,11 @@ class RadioFieldComponent {
|
|
|
2156
2156
|
* into an id attribute.
|
|
2157
2157
|
*/
|
|
2158
2158
|
getRadioId(option) {
|
|
2159
|
-
|
|
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';
|
|
2160
2164
|
return `${this.field.id}-${text.trim().replace(/\s+/g, '_').toLowerCase()}`;
|
|
2161
2165
|
}
|
|
2162
2166
|
getOptVal(option) {
|
|
@@ -4333,11 +4337,50 @@ class DynamicFormComponent {
|
|
|
4333
4337
|
}
|
|
4334
4338
|
if (changes['config'] || changes['initialData']) {
|
|
4335
4339
|
this.buildForm();
|
|
4336
|
-
|
|
4337
|
-
|
|
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 });
|
|
4338
4367
|
}
|
|
4339
4368
|
}
|
|
4340
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
|
+
}
|
|
4341
4384
|
ngOnDestroy() {
|
|
4342
4385
|
this.valueSub?.unsubscribe();
|
|
4343
4386
|
this.statusSub?.unsubscribe();
|
|
@@ -4950,7 +4993,12 @@ class DynamicFormComponent {
|
|
|
4950
4993
|
focusActivePanel() {
|
|
4951
4994
|
if (typeof requestAnimationFrame !== 'function')
|
|
4952
4995
|
return;
|
|
4953
|
-
requestAnimationFrame(() =>
|
|
4996
|
+
requestAnimationFrame(() =>
|
|
4997
|
+
// `preventScroll` because this focus exists to tell a screen reader the panel changed,
|
|
4998
|
+
// not to move the page. The default scrolls the panel into view, which on a short
|
|
4999
|
+
// viewport jumps the layout — enough that a Save button below it moves out from under
|
|
5000
|
+
// a tap. The panel sits directly beneath the tab strip and is already on screen.
|
|
5001
|
+
this.formPanel?.nativeElement?.focus?.({ preventScroll: true }));
|
|
4954
5002
|
}
|
|
4955
5003
|
/** Accessible name for the field panel — it is the tabpanel for the active tab. */
|
|
4956
5004
|
get activeTabLabel() {
|