ngx-dynamic-entity 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.
|
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
|
|
|
2
2
|
import { InjectionToken, makeEnvironmentProviders, inject, Injectable, signal, Input, ChangeDetectionStrategy, Component, DestroyRef, Injector, isDevMode, ViewContainerRef, ViewChild, forwardRef, ChangeDetectorRef, 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
|
-
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, toRefToken, collectFieldScopes, fieldRefFor, refOf, getTabPath, applyAutoPatch, applyPatchOnTrue } from '@dynamic-entity/core';
|
|
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';
|
|
6
6
|
export { formatDisplayValue, normalizeConfig, normalizeOption, resolveLabel, resolveOptionLabel, resolveOptionValue, valuesMatch } from '@dynamic-entity/core';
|
|
7
7
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
8
8
|
import { Subject, isObservable, firstValueFrom } from 'rxjs';
|
|
@@ -4096,12 +4096,20 @@ class DynamicFormComponent {
|
|
|
4096
4096
|
this.statusSub?.unsubscribe();
|
|
4097
4097
|
this.selectionSub?.unsubscribe();
|
|
4098
4098
|
}
|
|
4099
|
-
|
|
4099
|
+
/**
|
|
4100
|
+
* `focusPanel: false` is for a caller that is about to focus something more specific.
|
|
4101
|
+
*
|
|
4102
|
+
* Activating a tab moves focus into its panel, which is right for a keyboard user pressing
|
|
4103
|
+
* a tab. A quick-jump also switches tabs, but then focuses the field it was aiming at — and
|
|
4104
|
+
* the panel focus runs on `requestAnimationFrame`, after `afterNextRender`, so it landed
|
|
4105
|
+
* second and took the focus back every time. Saying so is better than out-timing it.
|
|
4106
|
+
*/
|
|
4107
|
+
setActiveTab(tabId, options) {
|
|
4100
4108
|
const changed = this.activeTab() !== tabId;
|
|
4101
4109
|
if (changed)
|
|
4102
4110
|
this.activeTabChange.emit(tabId);
|
|
4103
4111
|
this.activeTab.set(tabId);
|
|
4104
|
-
if (changed)
|
|
4112
|
+
if (changed && options?.focusPanel !== false)
|
|
4105
4113
|
this.focusActivePanel();
|
|
4106
4114
|
const parent = findTab(this.tabs, tabId);
|
|
4107
4115
|
if (parent?.children?.length) {
|
|
@@ -4111,10 +4119,10 @@ class DynamicFormComponent {
|
|
|
4111
4119
|
this.activeSubTab.set('');
|
|
4112
4120
|
}
|
|
4113
4121
|
}
|
|
4114
|
-
setActiveSubTab(subTabId) {
|
|
4122
|
+
setActiveSubTab(subTabId, options) {
|
|
4115
4123
|
const changed = this.activeSubTab() !== subTabId;
|
|
4116
4124
|
this.activeSubTab.set(subTabId);
|
|
4117
|
-
if (changed)
|
|
4125
|
+
if (changed && options?.focusPanel !== false)
|
|
4118
4126
|
this.focusActivePanel();
|
|
4119
4127
|
}
|
|
4120
4128
|
getFieldSpan(field) {
|
|
@@ -4411,9 +4419,8 @@ class DynamicFormComponent {
|
|
|
4411
4419
|
* `flattenFormValues` keys by bare id, so one of the two silently wins and the rule reads
|
|
4412
4420
|
* whichever landed there.
|
|
4413
4421
|
*
|
|
4414
|
-
* `validateConfig` catches this
|
|
4415
|
-
*
|
|
4416
|
-
* checked for them and this is the only place both are in hand.
|
|
4422
|
+
* `validateConfig({ rules })` catches this statically. The check here remains because
|
|
4423
|
+
* consumers that never call the validator still get a signal in development.
|
|
4417
4424
|
*/
|
|
4418
4425
|
warnAmbiguousRuleReferences() {
|
|
4419
4426
|
if (!isDevMode() || !this.rules?.length || !this.config)
|
|
@@ -4434,14 +4441,18 @@ class DynamicFormComponent {
|
|
|
4434
4441
|
referenced.add(target.id);
|
|
4435
4442
|
}
|
|
4436
4443
|
}
|
|
4437
|
-
for (const
|
|
4444
|
+
for (const raw of referenced) {
|
|
4445
|
+
const parsed = parseFieldRef(raw);
|
|
4446
|
+
if (parsed.kind === 'ref')
|
|
4447
|
+
continue;
|
|
4448
|
+
const id = parsed.value;
|
|
4438
4449
|
const scopes = ambiguous.get(id);
|
|
4439
4450
|
if (!scopes || DynamicFormComponent.warnedAmbiguousIds.has(id))
|
|
4440
4451
|
continue;
|
|
4441
4452
|
DynamicFormComponent.warnedAmbiguousIds.add(id);
|
|
4442
4453
|
console.warn(`[ngx-dynamic-entity] A rule references field "${id}", which is defined in ${scopes.join(' and ')}. ` +
|
|
4443
|
-
`
|
|
4444
|
-
`
|
|
4454
|
+
`A bare id cannot say which one is meant, so the rule will read whichever the form finds first. ` +
|
|
4455
|
+
`Name it by path instead, as [${scopes[0]}.${id}].`);
|
|
4445
4456
|
}
|
|
4446
4457
|
}
|
|
4447
4458
|
static warnedAmbiguousIds = new Set();
|
|
@@ -4643,6 +4654,13 @@ class DynamicFormComponent {
|
|
|
4643
4654
|
getControl(fieldId, currentTabId) {
|
|
4644
4655
|
if (!this.form)
|
|
4645
4656
|
return null;
|
|
4657
|
+
// A bracketed path names exactly one control, and the form nests by tab exactly as the
|
|
4658
|
+
// path does — so `form.get('work.address')` walks straight to it. Anything that names a
|
|
4659
|
+
// field can therefore use a path: `showWhen` keys, `patchOnTrue` mappings and `autoPatch`
|
|
4660
|
+
// targets, not only rules.
|
|
4661
|
+
const parsed = parseFieldRef(fieldId);
|
|
4662
|
+
if (parsed.kind === 'ref')
|
|
4663
|
+
return this.form.get(parsed.value);
|
|
4646
4664
|
if (currentTabId) {
|
|
4647
4665
|
const tabGrp = this.getTabGroup(currentTabId);
|
|
4648
4666
|
const ctrl = tabGrp?.get(fieldId);
|
|
@@ -5259,12 +5277,14 @@ class DynamicRecordFormComponent {
|
|
|
5259
5277
|
const location = this.locateField(fieldId);
|
|
5260
5278
|
if (!location)
|
|
5261
5279
|
return;
|
|
5262
|
-
this.
|
|
5280
|
+
// The panel must not take the focus back: this jump is going to focus the field itself.
|
|
5281
|
+
this.dynamicFormComp?.setActiveTab(location.tabId, { focusPanel: false });
|
|
5263
5282
|
// `setActiveTab` resets to a tab's first child, so the sub-tab has to be selected after
|
|
5264
5283
|
// it, not before. Without this the walk found sub-tab fields and then rendered the wrong
|
|
5265
5284
|
// panel, so the element the callback below looks for never existed.
|
|
5266
|
-
if (location.subTabId)
|
|
5267
|
-
this.dynamicFormComp?.setActiveSubTab(location.subTabId);
|
|
5285
|
+
if (location.subTabId) {
|
|
5286
|
+
this.dynamicFormComp?.setActiveSubTab(location.subTabId, { focusPanel: false });
|
|
5287
|
+
}
|
|
5268
5288
|
afterNextRender(() => {
|
|
5269
5289
|
// The field id comes from config, so it never goes into a selector string: no
|
|
5270
5290
|
// escaping to get wrong, and no need for `CSS.escape`, which jsdom and older
|