ngx-dynamic-entity-builder 1.8.1 → 1.9.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 { signal, computed, Injectable, inject, ChangeDetectionStrategy, Component, EventEmitter, Output, Input, effect, HostListener } from '@angular/core';
|
|
2
|
+
import { signal, computed, Injectable, InjectionToken, inject, ChangeDetectionStrategy, Component, EventEmitter, Output, Input, effect, HostListener } from '@angular/core';
|
|
3
3
|
import * as i1 from '@angular/forms';
|
|
4
4
|
import { FormsModule } from '@angular/forms';
|
|
5
5
|
import * as i2 from '@angular/material/button';
|
|
@@ -26,7 +26,7 @@ import * as i4$2 from '@angular/material/tooltip';
|
|
|
26
26
|
import { MatTooltipModule } from '@angular/material/tooltip';
|
|
27
27
|
import { collectFieldScopes, assignFieldRefs, normalizeConfigOptions, getFieldTypeMeta, createFieldConfig, labelToId, createFieldSnapshot, computeFieldDrift, toRefToken, humanizeId, findTab, fieldRefFor, parseFieldRef, refOf, resolveLabel, resolveOptionLabel, FIELD_TYPE_CATALOG, COMMON_MODULES } from '@dynamic-entity/core';
|
|
28
28
|
export { FIELD_TYPE_CATALOG, createFieldConfig, getFieldTypeMeta, humanizeId } from '@dynamic-entity/core';
|
|
29
|
-
import { ConfigSourceService, SYSTEM_DEFAULT_CAN_EDIT } from 'ngx-dynamic-entity';
|
|
29
|
+
import { resolveUiText, ConfigSourceService, SYSTEM_DEFAULT_CAN_EDIT } from 'ngx-dynamic-entity';
|
|
30
30
|
export { SYSTEM_DEFAULT_CAN_EDIT } from 'ngx-dynamic-entity';
|
|
31
31
|
import * as i3 from '@angular/material/divider';
|
|
32
32
|
import { MatDividerModule } from '@angular/material/divider';
|
|
@@ -1553,6 +1553,221 @@ function withExistingOptions(options, current) {
|
|
|
1553
1553
|
return [...options, ...extra];
|
|
1554
1554
|
}
|
|
1555
1555
|
|
|
1556
|
+
/**
|
|
1557
|
+
* Every word the builder puts on screen that is not part of the config being authored.
|
|
1558
|
+
*
|
|
1559
|
+
* The renderer has its own list (`DEFAULT_UI_TEXT`) and the two are deliberately separate:
|
|
1560
|
+
* an application that ships only the renderer should not see a hundred and fifty builder
|
|
1561
|
+
* keys in completion, and the two vocabularies share no strings worth deduplicating. What
|
|
1562
|
+
* they do share is the resolution rule, which comes from `resolveUiText`.
|
|
1563
|
+
*
|
|
1564
|
+
* Keys are grouped by panel, because a translator works screen by screen.
|
|
1565
|
+
*/
|
|
1566
|
+
const DEFAULT_BUILDER_TEXT = {
|
|
1567
|
+
// Toolbar and page chrome
|
|
1568
|
+
builderTitle: 'Entity Builder',
|
|
1569
|
+
undo: 'Undo',
|
|
1570
|
+
undoTooltip: 'Undo (Ctrl+Z)',
|
|
1571
|
+
redo: 'Redo',
|
|
1572
|
+
redoTooltip: 'Redo (Ctrl+Shift+Z)',
|
|
1573
|
+
issuesTooltip: '{count} issue(s) — hover items in the list',
|
|
1574
|
+
copyJson: 'Copy JSON',
|
|
1575
|
+
save: 'Save',
|
|
1576
|
+
// Entity settings panel
|
|
1577
|
+
entityName: 'Entity name',
|
|
1578
|
+
entityNamePlaceholder: 'e.g. clients',
|
|
1579
|
+
editingLanguage: 'Editing language',
|
|
1580
|
+
maskEntity: 'Mask this entity (RBAC)',
|
|
1581
|
+
permissions: 'Permissions',
|
|
1582
|
+
actionRoles: '{action} roles',
|
|
1583
|
+
actionRolesPlaceholder: '{action} roles (comma-separated)',
|
|
1584
|
+
permissionsHint: 'Empty = no restriction for that action.',
|
|
1585
|
+
addField: 'Add field',
|
|
1586
|
+
fieldProperties: 'Field properties',
|
|
1587
|
+
configJson: 'Config JSON',
|
|
1588
|
+
// Canvas
|
|
1589
|
+
fieldsHeading: 'Fields ({count})',
|
|
1590
|
+
canvasEmpty: 'No fields yet. Pick a type from Add field to get started.',
|
|
1591
|
+
// Field rows in the tree
|
|
1592
|
+
selectField: 'Select field {field}',
|
|
1593
|
+
dragToReorder: 'Drag to reorder (or use the move buttons)',
|
|
1594
|
+
referencedFieldTooltip: 'Referenced field (linked to {entity})',
|
|
1595
|
+
driftDetected: 'Drift detected: source field has evolved',
|
|
1596
|
+
required: 'Required',
|
|
1597
|
+
moveUp: 'Move up',
|
|
1598
|
+
moveDown: 'Move down',
|
|
1599
|
+
duplicate: 'Duplicate',
|
|
1600
|
+
delete: 'Delete',
|
|
1601
|
+
moveFieldUp: 'Move {field} up',
|
|
1602
|
+
moveFieldDown: 'Move {field} down',
|
|
1603
|
+
duplicateField: 'Duplicate {field}',
|
|
1604
|
+
// Field inspector — identity
|
|
1605
|
+
inspectorEmpty: 'Select a field to edit its properties.',
|
|
1606
|
+
fieldId: 'Field id',
|
|
1607
|
+
fieldIdFixedHint: 'The stored data key for this saved field — fixed.',
|
|
1608
|
+
fieldIdDerivedHint: 'Derived from the label. Used as the data key.',
|
|
1609
|
+
tab: 'Tab',
|
|
1610
|
+
tabMoveHint: 'Moving the field rewrites its path and repoints the rules that named it.',
|
|
1611
|
+
labelInLanguage: 'Label ({language})',
|
|
1612
|
+
placeholderInLanguage: 'Placeholder ({language})',
|
|
1613
|
+
defaultValue: 'Default value',
|
|
1614
|
+
validation: 'Validation',
|
|
1615
|
+
// Field inspector — options
|
|
1616
|
+
dataSource: 'Data source',
|
|
1617
|
+
optionsComeFrom: 'Options come from',
|
|
1618
|
+
optionsSourceNone: 'Nothing yet',
|
|
1619
|
+
optionsSourceInline: 'Options authored here',
|
|
1620
|
+
optionsSourceList: 'A named list',
|
|
1621
|
+
listName: 'List name',
|
|
1622
|
+
listNameHint: 'The consuming app resolves this name through its lookup registry. Options are not authored here.',
|
|
1623
|
+
options: 'Options',
|
|
1624
|
+
option: 'Option',
|
|
1625
|
+
optionInLanguage: 'Option ({language})',
|
|
1626
|
+
removeOption: 'Remove option',
|
|
1627
|
+
noOptionsYet: 'No options yet — add at least one.',
|
|
1628
|
+
// Field inspector — display flags
|
|
1629
|
+
display: 'Display',
|
|
1630
|
+
visible: 'Visible',
|
|
1631
|
+
tableColumn: 'Table column',
|
|
1632
|
+
readOnly: 'Read-only',
|
|
1633
|
+
disabled: 'Disabled',
|
|
1634
|
+
maskData: 'Mask data (RBAC)',
|
|
1635
|
+
criticalField: 'Critical field',
|
|
1636
|
+
criticalFieldTooltip: 'Renders locked; editing it announces a change against the session baseline.',
|
|
1637
|
+
showInSummary: 'Show in summary',
|
|
1638
|
+
showInSummaryTooltip: "Shown in the record editor's summary panel.",
|
|
1639
|
+
// Field inspector — conditional visibility
|
|
1640
|
+
showWhen: 'Show when',
|
|
1641
|
+
condition: 'Condition',
|
|
1642
|
+
watchedField: 'Watched field',
|
|
1643
|
+
equals: 'Equals',
|
|
1644
|
+
removeCondition: 'Remove condition',
|
|
1645
|
+
alwaysVisibleHint: 'Always visible. Add a condition to show this field only for certain values.',
|
|
1646
|
+
// Field inspector — copy on true
|
|
1647
|
+
copyOnTrue: 'Copy on true',
|
|
1648
|
+
mapping: 'Mapping',
|
|
1649
|
+
fromField: 'From field',
|
|
1650
|
+
toField: 'To field',
|
|
1651
|
+
removeMapping: 'Remove mapping',
|
|
1652
|
+
copyOnTrueHint: "When this flips to true, copy one field's value into another.",
|
|
1653
|
+
// Rules list
|
|
1654
|
+
rules: 'Rules',
|
|
1655
|
+
rule: 'Rule',
|
|
1656
|
+
noRulesOnField: 'No rules on this field.',
|
|
1657
|
+
rulePriority: 'priority {priority} · {targets} target(s)',
|
|
1658
|
+
toggleRule: 'Enable / disable',
|
|
1659
|
+
moveRuleUp: 'Move rule up',
|
|
1660
|
+
moveRuleDown: 'Move rule down',
|
|
1661
|
+
editRule: 'Edit rule',
|
|
1662
|
+
deleteRule: 'Delete rule',
|
|
1663
|
+
// Rule form
|
|
1664
|
+
newRuleTitle: 'New Form Rule',
|
|
1665
|
+
editRuleTitle: 'Edit Rule',
|
|
1666
|
+
triggerField: 'Trigger Field',
|
|
1667
|
+
applyToFields: 'Apply to fields',
|
|
1668
|
+
conditions: 'Conditions',
|
|
1669
|
+
operator: 'Operator',
|
|
1670
|
+
value: 'Value',
|
|
1671
|
+
conditionValuePlaceholder: 'Condition value',
|
|
1672
|
+
addCondition: 'Add Condition',
|
|
1673
|
+
action: 'Action',
|
|
1674
|
+
actionType: 'Action Type',
|
|
1675
|
+
actionVisibility: 'Visibility (Show/Hide)',
|
|
1676
|
+
actionValidationMessage: 'Validation Message',
|
|
1677
|
+
actionInfoBanner: 'Info Banner',
|
|
1678
|
+
valueOrMessage: 'Value / Message',
|
|
1679
|
+
valueOrMessagePlaceholder: 'false to hide, or message text',
|
|
1680
|
+
cancel: 'Cancel',
|
|
1681
|
+
saveRule: 'Save Rule',
|
|
1682
|
+
// Tab manager
|
|
1683
|
+
tabsManager: 'Tabs Manager',
|
|
1684
|
+
systemTag: '(System)',
|
|
1685
|
+
addTab: 'Add Tab',
|
|
1686
|
+
noTabs: 'No tabs — all fields render in a single section.',
|
|
1687
|
+
flatData: 'Flat Data',
|
|
1688
|
+
primaryTab: 'Primary Tab',
|
|
1689
|
+
maskTabData: 'Mask Tab Data',
|
|
1690
|
+
systemDefault: 'System Default',
|
|
1691
|
+
addSubTab: '+ Sub-tab',
|
|
1692
|
+
consumerModuleName: 'Consumer Module Name (Optional)',
|
|
1693
|
+
consumerModulePlaceholder: 'e.g. documents-view',
|
|
1694
|
+
subTabs: 'Sub-tabs:',
|
|
1695
|
+
moveTabUp: 'Move tab up',
|
|
1696
|
+
moveTabDown: 'Move tab down',
|
|
1697
|
+
removeTab: 'Remove tab',
|
|
1698
|
+
removeSubTab: 'Remove sub-tab',
|
|
1699
|
+
// Entity reference panel
|
|
1700
|
+
entityReference: 'Entity reference',
|
|
1701
|
+
registryKey: 'Registry key',
|
|
1702
|
+
registryKeyHint: 'Key the consumer registered a loader under. Defaults to the field id.',
|
|
1703
|
+
displayFields: 'Display fields',
|
|
1704
|
+
displayFieldsHint: 'Comma-separated record paths used to build each option label.',
|
|
1705
|
+
staticFilters: 'Static filters (JSON)',
|
|
1706
|
+
staticFiltersHint: 'Passed to the loader, e.g. {"isEmployee": false}.',
|
|
1707
|
+
cascade: 'Cascade',
|
|
1708
|
+
parentField: 'Parent field',
|
|
1709
|
+
parentFieldNone: 'None — load all options',
|
|
1710
|
+
cascadeHint: "This field's options reload whenever the parent changes.",
|
|
1711
|
+
lookupFilterPath: 'Lookup filter path',
|
|
1712
|
+
lookupFilterPathHint: 'Keep options whose record matches the parent value at this path.',
|
|
1713
|
+
lookupPath: 'Lookup path (nested options)',
|
|
1714
|
+
lookupPathHint: "Take options from this array on the selected parent's record instead.",
|
|
1715
|
+
autoPatchOnSelect: 'Auto-patch on select',
|
|
1716
|
+
targetTab: 'Target tab',
|
|
1717
|
+
sourceLinkedRecord: 'Source (linked record)',
|
|
1718
|
+
targetField: 'Target field',
|
|
1719
|
+
noAutoPatch: 'No auto-patch configured — selecting a reference will not copy any values.',
|
|
1720
|
+
// Referenced-field panel
|
|
1721
|
+
referencedField: 'Referenced Field',
|
|
1722
|
+
link: 'Link',
|
|
1723
|
+
sourceEntityKey: 'Source Entity Key',
|
|
1724
|
+
sourceEntityKeyPlaceholder: 'e.g. individuals',
|
|
1725
|
+
sourceFieldId: 'Source Field ID',
|
|
1726
|
+
sourceFieldIdPlaceholder: 'e.g. firstName',
|
|
1727
|
+
driftHeading: 'Source field definition has drifted!',
|
|
1728
|
+
driftBody: 'The upstream field configuration in "{entity}" has evolved.',
|
|
1729
|
+
syncWithSource: 'Sync with Source',
|
|
1730
|
+
};
|
|
1731
|
+
/**
|
|
1732
|
+
* Overrides for the builder's own chrome.
|
|
1733
|
+
*
|
|
1734
|
+
* Accepts what `UI_TEXT` accepts: a partial map whose values are strings or `LocalizedText`
|
|
1735
|
+
* (`{ en, de }`), or a resolver `(key, defaultText, language) => string` for a host with an
|
|
1736
|
+
* existing i18n layer. `DEFAULT_BUILDER_TEXT` is the full key list with its English source
|
|
1737
|
+
* strings; anything left out keeps its default.
|
|
1738
|
+
*
|
|
1739
|
+
* @example
|
|
1740
|
+
* { provide: BUILDER_TEXT, useValue: { save: { en: 'Save', de: 'Speichern' } } }
|
|
1741
|
+
*/
|
|
1742
|
+
const BUILDER_TEXT = new InjectionToken('BUILDER_TEXT');
|
|
1743
|
+
/**
|
|
1744
|
+
* Resolves the builder's own UI text.
|
|
1745
|
+
*
|
|
1746
|
+
* The language lives on the service rather than travelling through every template call,
|
|
1747
|
+
* because the builder already has a *different* language on screen: `activeLanguage()`
|
|
1748
|
+
* selects the `LocalizedText` entry being **authored**. Passing that one to the chrome
|
|
1749
|
+
* would flip the builder's own interface every time an author switched the label language
|
|
1750
|
+
* they were editing, which is the opposite of what it means.
|
|
1751
|
+
*
|
|
1752
|
+
* Root-provided and therefore shared: two builders mounted at once show one chrome
|
|
1753
|
+
* language. `EntityBuilderComponent`'s `uiLanguage` input is what sets it.
|
|
1754
|
+
*/
|
|
1755
|
+
class BuilderTextService {
|
|
1756
|
+
overrides = inject(BUILDER_TEXT, { optional: true });
|
|
1757
|
+
/** The chrome language. Not the authoring language — see the class comment. */
|
|
1758
|
+
language = signal('en', ...(ngDevMode ? [{ debugName: "language" }] : /* istanbul ignore next */ []));
|
|
1759
|
+
/** One label by key. `params` fill the `{placeholder}` slots. */
|
|
1760
|
+
text(key, params) {
|
|
1761
|
+
return resolveUiText(this.overrides, DEFAULT_BUILDER_TEXT, key, this.language(), params);
|
|
1762
|
+
}
|
|
1763
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: BuilderTextService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
1764
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: BuilderTextService, providedIn: 'root' });
|
|
1765
|
+
}
|
|
1766
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: BuilderTextService, decorators: [{
|
|
1767
|
+
type: Injectable,
|
|
1768
|
+
args: [{ providedIn: 'root' }]
|
|
1769
|
+
}] });
|
|
1770
|
+
|
|
1556
1771
|
/**
|
|
1557
1772
|
* EntityReferenceConfigComponent — authors the `entityReference` block of an entity-ref field:
|
|
1558
1773
|
* registry key, display fields, static filters, and the parent→child cascade.
|
|
@@ -1561,6 +1776,8 @@ function withExistingOptions(options, current) {
|
|
|
1561
1776
|
* selected reference returns.
|
|
1562
1777
|
*/
|
|
1563
1778
|
class EntityReferenceConfigComponent {
|
|
1779
|
+
/** Builder chrome, overridable via BUILDER_TEXT. */
|
|
1780
|
+
ui = inject(BuilderTextService);
|
|
1564
1781
|
store = inject(BuilderStore);
|
|
1565
1782
|
field = this.store.selectedField;
|
|
1566
1783
|
/** Sibling fields that can drive a cascade — anything but this field itself. */
|
|
@@ -1640,32 +1857,32 @@ class EntityReferenceConfigComponent {
|
|
|
1640
1857
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: EntityReferenceConfigComponent, isStandalone: true, selector: "ngx-entity-reference-config", ngImport: i0, template: `
|
|
1641
1858
|
@if (field(); as f) {
|
|
1642
1859
|
<div class="deb-entity-ref">
|
|
1643
|
-
<span class="deb-section-title">
|
|
1860
|
+
<span class="deb-section-title">{{ ui.text('entityReference') }}</span>
|
|
1644
1861
|
|
|
1645
1862
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1646
|
-
<mat-label>
|
|
1863
|
+
<mat-label>{{ ui.text('registryKey') }}</mat-label>
|
|
1647
1864
|
<input
|
|
1648
1865
|
matInput
|
|
1649
1866
|
data-testid="entity-ref-key"
|
|
1650
1867
|
[ngModel]="ref(f).linkedEntityKey ?? ''"
|
|
1651
1868
|
(ngModelChange)="patchRef(f, { linkedEntityKey: $event || undefined })"
|
|
1652
1869
|
/>
|
|
1653
|
-
<mat-hint>
|
|
1870
|
+
<mat-hint>{{ ui.text('registryKeyHint') }}</mat-hint>
|
|
1654
1871
|
</mat-form-field>
|
|
1655
1872
|
|
|
1656
1873
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1657
|
-
<mat-label>
|
|
1874
|
+
<mat-label>{{ ui.text('displayFields') }}</mat-label>
|
|
1658
1875
|
<input
|
|
1659
1876
|
matInput
|
|
1660
1877
|
data-testid="entity-ref-display-fields"
|
|
1661
1878
|
[ngModel]="displayFieldsText(f)"
|
|
1662
1879
|
(ngModelChange)="setDisplayFields(f, $event)"
|
|
1663
1880
|
/>
|
|
1664
|
-
<mat-hint>
|
|
1881
|
+
<mat-hint>{{ ui.text('displayFieldsHint') }}</mat-hint>
|
|
1665
1882
|
</mat-form-field>
|
|
1666
1883
|
|
|
1667
1884
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1668
|
-
<mat-label>
|
|
1885
|
+
<mat-label>{{ ui.text('staticFilters') }}</mat-label>
|
|
1669
1886
|
<input
|
|
1670
1887
|
matInput
|
|
1671
1888
|
data-testid="entity-ref-filters"
|
|
@@ -1675,52 +1892,52 @@ class EntityReferenceConfigComponent {
|
|
|
1675
1892
|
@if (filtersError()) {
|
|
1676
1893
|
<mat-hint class="deb-error-hint">{{ filtersError() }}</mat-hint>
|
|
1677
1894
|
} @else {
|
|
1678
|
-
<mat-hint>
|
|
1895
|
+
<mat-hint>{{ ui.text('staticFiltersHint') }}</mat-hint>
|
|
1679
1896
|
}
|
|
1680
1897
|
</mat-form-field>
|
|
1681
1898
|
|
|
1682
1899
|
<mat-divider></mat-divider>
|
|
1683
1900
|
|
|
1684
1901
|
<!-- Cascade -->
|
|
1685
|
-
<span class="deb-section-title">
|
|
1902
|
+
<span class="deb-section-title">{{ ui.text('cascade') }}</span>
|
|
1686
1903
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1687
|
-
<mat-label>
|
|
1904
|
+
<mat-label>{{ ui.text('parentField') }}</mat-label>
|
|
1688
1905
|
<mat-select
|
|
1689
1906
|
data-testid="entity-ref-parent"
|
|
1690
1907
|
[ngModel]="ref(f).parentField ?? ''"
|
|
1691
1908
|
(ngModelChange)="patchRef(f, { parentField: $event || undefined })"
|
|
1692
1909
|
>
|
|
1693
|
-
<mat-option value="">
|
|
1910
|
+
<mat-option value="">{{ ui.text('parentFieldNone') }}</mat-option>
|
|
1694
1911
|
@for (option of parentOptions(); track option.value) {
|
|
1695
1912
|
<mat-option [value]="option.value">
|
|
1696
1913
|
{{ option.label }} <span class="deb-path-hint">{{ option.path }}</span>
|
|
1697
1914
|
</mat-option>
|
|
1698
1915
|
}
|
|
1699
1916
|
</mat-select>
|
|
1700
|
-
<mat-hint>
|
|
1917
|
+
<mat-hint>{{ ui.text('cascadeHint') }}</mat-hint>
|
|
1701
1918
|
</mat-form-field>
|
|
1702
1919
|
|
|
1703
1920
|
@if (ref(f).parentField) {
|
|
1704
1921
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1705
|
-
<mat-label>
|
|
1922
|
+
<mat-label>{{ ui.text('lookupFilterPath') }}</mat-label>
|
|
1706
1923
|
<input
|
|
1707
1924
|
matInput
|
|
1708
1925
|
data-testid="entity-ref-lookup-filter"
|
|
1709
1926
|
[ngModel]="ref(f).lookupFilter ?? ''"
|
|
1710
1927
|
(ngModelChange)="patchRef(f, { lookupFilter: $event || undefined })"
|
|
1711
1928
|
/>
|
|
1712
|
-
<mat-hint>
|
|
1929
|
+
<mat-hint>{{ ui.text('lookupFilterPathHint') }}</mat-hint>
|
|
1713
1930
|
</mat-form-field>
|
|
1714
1931
|
|
|
1715
1932
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1716
|
-
<mat-label>
|
|
1933
|
+
<mat-label>{{ ui.text('lookupPath') }}</mat-label>
|
|
1717
1934
|
<input
|
|
1718
1935
|
matInput
|
|
1719
1936
|
data-testid="entity-ref-lookup-path"
|
|
1720
1937
|
[ngModel]="ref(f).lookupPath ?? ''"
|
|
1721
1938
|
(ngModelChange)="patchRef(f, { lookupPath: $event || undefined })"
|
|
1722
1939
|
/>
|
|
1723
|
-
<mat-hint>
|
|
1940
|
+
<mat-hint>{{ ui.text('lookupPathHint') }}</mat-hint>
|
|
1724
1941
|
</mat-form-field>
|
|
1725
1942
|
}
|
|
1726
1943
|
|
|
@@ -1728,20 +1945,20 @@ class EntityReferenceConfigComponent {
|
|
|
1728
1945
|
|
|
1729
1946
|
<!-- autoPatch -->
|
|
1730
1947
|
<div class="deb-row deb-row--split">
|
|
1731
|
-
<span class="deb-section-title">
|
|
1948
|
+
<span class="deb-section-title">{{ ui.text('autoPatchOnSelect') }}</span>
|
|
1732
1949
|
<button
|
|
1733
1950
|
mat-stroked-button
|
|
1734
1951
|
type="button"
|
|
1735
1952
|
data-testid="add-auto-patch"
|
|
1736
1953
|
(click)="store.addAutoPatchMapping(store.keyOf(f))"
|
|
1737
1954
|
>
|
|
1738
|
-
<mat-icon>add</mat-icon>
|
|
1955
|
+
<mat-icon>add</mat-icon> {{ ui.text('mapping') }}
|
|
1739
1956
|
</button>
|
|
1740
1957
|
</div>
|
|
1741
1958
|
|
|
1742
1959
|
@if (f.autoPatch) {
|
|
1743
1960
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1744
|
-
<mat-label>
|
|
1961
|
+
<mat-label>{{ ui.text('targetTab') }}</mat-label>
|
|
1745
1962
|
<mat-select
|
|
1746
1963
|
data-testid="auto-patch-tab"
|
|
1747
1964
|
[ngModel]="f.autoPatch.targetTab"
|
|
@@ -1756,7 +1973,7 @@ class EntityReferenceConfigComponent {
|
|
|
1756
1973
|
@for (mapping of f.autoPatch.mappings; track $index) {
|
|
1757
1974
|
<div class="deb-option-row">
|
|
1758
1975
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
1759
|
-
<mat-label>
|
|
1976
|
+
<mat-label>{{ ui.text('sourceLinkedRecord') }}</mat-label>
|
|
1760
1977
|
<input
|
|
1761
1978
|
matInput
|
|
1762
1979
|
[ngModel]="mapping.source"
|
|
@@ -1764,7 +1981,7 @@ class EntityReferenceConfigComponent {
|
|
|
1764
1981
|
/>
|
|
1765
1982
|
</mat-form-field>
|
|
1766
1983
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
1767
|
-
<mat-label>
|
|
1984
|
+
<mat-label>{{ ui.text('targetField') }}</mat-label>
|
|
1768
1985
|
<mat-select
|
|
1769
1986
|
data-testid="auto-patch-target"
|
|
1770
1987
|
[ngModel]="mapping.target"
|
|
@@ -1781,7 +1998,7 @@ class EntityReferenceConfigComponent {
|
|
|
1781
1998
|
mat-icon-button
|
|
1782
1999
|
type="button"
|
|
1783
2000
|
color="warn"
|
|
1784
|
-
matTooltip="
|
|
2001
|
+
[matTooltip]="ui.text('removeMapping')"
|
|
1785
2002
|
(click)="store.removeAutoPatchMapping(store.keyOf(f), $index)"
|
|
1786
2003
|
>
|
|
1787
2004
|
<mat-icon>delete</mat-icon>
|
|
@@ -1790,7 +2007,7 @@ class EntityReferenceConfigComponent {
|
|
|
1790
2007
|
}
|
|
1791
2008
|
} @else {
|
|
1792
2009
|
<p class="deb-hint">
|
|
1793
|
-
|
|
2010
|
+
{{ ui.text('noAutoPatch') }}
|
|
1794
2011
|
</p>
|
|
1795
2012
|
}
|
|
1796
2013
|
</div>
|
|
@@ -1812,32 +2029,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
1812
2029
|
], template: `
|
|
1813
2030
|
@if (field(); as f) {
|
|
1814
2031
|
<div class="deb-entity-ref">
|
|
1815
|
-
<span class="deb-section-title">
|
|
2032
|
+
<span class="deb-section-title">{{ ui.text('entityReference') }}</span>
|
|
1816
2033
|
|
|
1817
2034
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1818
|
-
<mat-label>
|
|
2035
|
+
<mat-label>{{ ui.text('registryKey') }}</mat-label>
|
|
1819
2036
|
<input
|
|
1820
2037
|
matInput
|
|
1821
2038
|
data-testid="entity-ref-key"
|
|
1822
2039
|
[ngModel]="ref(f).linkedEntityKey ?? ''"
|
|
1823
2040
|
(ngModelChange)="patchRef(f, { linkedEntityKey: $event || undefined })"
|
|
1824
2041
|
/>
|
|
1825
|
-
<mat-hint>
|
|
2042
|
+
<mat-hint>{{ ui.text('registryKeyHint') }}</mat-hint>
|
|
1826
2043
|
</mat-form-field>
|
|
1827
2044
|
|
|
1828
2045
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1829
|
-
<mat-label>
|
|
2046
|
+
<mat-label>{{ ui.text('displayFields') }}</mat-label>
|
|
1830
2047
|
<input
|
|
1831
2048
|
matInput
|
|
1832
2049
|
data-testid="entity-ref-display-fields"
|
|
1833
2050
|
[ngModel]="displayFieldsText(f)"
|
|
1834
2051
|
(ngModelChange)="setDisplayFields(f, $event)"
|
|
1835
2052
|
/>
|
|
1836
|
-
<mat-hint>
|
|
2053
|
+
<mat-hint>{{ ui.text('displayFieldsHint') }}</mat-hint>
|
|
1837
2054
|
</mat-form-field>
|
|
1838
2055
|
|
|
1839
2056
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1840
|
-
<mat-label>
|
|
2057
|
+
<mat-label>{{ ui.text('staticFilters') }}</mat-label>
|
|
1841
2058
|
<input
|
|
1842
2059
|
matInput
|
|
1843
2060
|
data-testid="entity-ref-filters"
|
|
@@ -1847,52 +2064,52 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
1847
2064
|
@if (filtersError()) {
|
|
1848
2065
|
<mat-hint class="deb-error-hint">{{ filtersError() }}</mat-hint>
|
|
1849
2066
|
} @else {
|
|
1850
|
-
<mat-hint>
|
|
2067
|
+
<mat-hint>{{ ui.text('staticFiltersHint') }}</mat-hint>
|
|
1851
2068
|
}
|
|
1852
2069
|
</mat-form-field>
|
|
1853
2070
|
|
|
1854
2071
|
<mat-divider></mat-divider>
|
|
1855
2072
|
|
|
1856
2073
|
<!-- Cascade -->
|
|
1857
|
-
<span class="deb-section-title">
|
|
2074
|
+
<span class="deb-section-title">{{ ui.text('cascade') }}</span>
|
|
1858
2075
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1859
|
-
<mat-label>
|
|
2076
|
+
<mat-label>{{ ui.text('parentField') }}</mat-label>
|
|
1860
2077
|
<mat-select
|
|
1861
2078
|
data-testid="entity-ref-parent"
|
|
1862
2079
|
[ngModel]="ref(f).parentField ?? ''"
|
|
1863
2080
|
(ngModelChange)="patchRef(f, { parentField: $event || undefined })"
|
|
1864
2081
|
>
|
|
1865
|
-
<mat-option value="">
|
|
2082
|
+
<mat-option value="">{{ ui.text('parentFieldNone') }}</mat-option>
|
|
1866
2083
|
@for (option of parentOptions(); track option.value) {
|
|
1867
2084
|
<mat-option [value]="option.value">
|
|
1868
2085
|
{{ option.label }} <span class="deb-path-hint">{{ option.path }}</span>
|
|
1869
2086
|
</mat-option>
|
|
1870
2087
|
}
|
|
1871
2088
|
</mat-select>
|
|
1872
|
-
<mat-hint>
|
|
2089
|
+
<mat-hint>{{ ui.text('cascadeHint') }}</mat-hint>
|
|
1873
2090
|
</mat-form-field>
|
|
1874
2091
|
|
|
1875
2092
|
@if (ref(f).parentField) {
|
|
1876
2093
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1877
|
-
<mat-label>
|
|
2094
|
+
<mat-label>{{ ui.text('lookupFilterPath') }}</mat-label>
|
|
1878
2095
|
<input
|
|
1879
2096
|
matInput
|
|
1880
2097
|
data-testid="entity-ref-lookup-filter"
|
|
1881
2098
|
[ngModel]="ref(f).lookupFilter ?? ''"
|
|
1882
2099
|
(ngModelChange)="patchRef(f, { lookupFilter: $event || undefined })"
|
|
1883
2100
|
/>
|
|
1884
|
-
<mat-hint>
|
|
2101
|
+
<mat-hint>{{ ui.text('lookupFilterPathHint') }}</mat-hint>
|
|
1885
2102
|
</mat-form-field>
|
|
1886
2103
|
|
|
1887
2104
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1888
|
-
<mat-label>
|
|
2105
|
+
<mat-label>{{ ui.text('lookupPath') }}</mat-label>
|
|
1889
2106
|
<input
|
|
1890
2107
|
matInput
|
|
1891
2108
|
data-testid="entity-ref-lookup-path"
|
|
1892
2109
|
[ngModel]="ref(f).lookupPath ?? ''"
|
|
1893
2110
|
(ngModelChange)="patchRef(f, { lookupPath: $event || undefined })"
|
|
1894
2111
|
/>
|
|
1895
|
-
<mat-hint>
|
|
2112
|
+
<mat-hint>{{ ui.text('lookupPathHint') }}</mat-hint>
|
|
1896
2113
|
</mat-form-field>
|
|
1897
2114
|
}
|
|
1898
2115
|
|
|
@@ -1900,20 +2117,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
1900
2117
|
|
|
1901
2118
|
<!-- autoPatch -->
|
|
1902
2119
|
<div class="deb-row deb-row--split">
|
|
1903
|
-
<span class="deb-section-title">
|
|
2120
|
+
<span class="deb-section-title">{{ ui.text('autoPatchOnSelect') }}</span>
|
|
1904
2121
|
<button
|
|
1905
2122
|
mat-stroked-button
|
|
1906
2123
|
type="button"
|
|
1907
2124
|
data-testid="add-auto-patch"
|
|
1908
2125
|
(click)="store.addAutoPatchMapping(store.keyOf(f))"
|
|
1909
2126
|
>
|
|
1910
|
-
<mat-icon>add</mat-icon>
|
|
2127
|
+
<mat-icon>add</mat-icon> {{ ui.text('mapping') }}
|
|
1911
2128
|
</button>
|
|
1912
2129
|
</div>
|
|
1913
2130
|
|
|
1914
2131
|
@if (f.autoPatch) {
|
|
1915
2132
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1916
|
-
<mat-label>
|
|
2133
|
+
<mat-label>{{ ui.text('targetTab') }}</mat-label>
|
|
1917
2134
|
<mat-select
|
|
1918
2135
|
data-testid="auto-patch-tab"
|
|
1919
2136
|
[ngModel]="f.autoPatch.targetTab"
|
|
@@ -1928,7 +2145,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
1928
2145
|
@for (mapping of f.autoPatch.mappings; track $index) {
|
|
1929
2146
|
<div class="deb-option-row">
|
|
1930
2147
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
1931
|
-
<mat-label>
|
|
2148
|
+
<mat-label>{{ ui.text('sourceLinkedRecord') }}</mat-label>
|
|
1932
2149
|
<input
|
|
1933
2150
|
matInput
|
|
1934
2151
|
[ngModel]="mapping.source"
|
|
@@ -1936,7 +2153,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
1936
2153
|
/>
|
|
1937
2154
|
</mat-form-field>
|
|
1938
2155
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
1939
|
-
<mat-label>
|
|
2156
|
+
<mat-label>{{ ui.text('targetField') }}</mat-label>
|
|
1940
2157
|
<mat-select
|
|
1941
2158
|
data-testid="auto-patch-target"
|
|
1942
2159
|
[ngModel]="mapping.target"
|
|
@@ -1953,7 +2170,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
1953
2170
|
mat-icon-button
|
|
1954
2171
|
type="button"
|
|
1955
2172
|
color="warn"
|
|
1956
|
-
matTooltip="
|
|
2173
|
+
[matTooltip]="ui.text('removeMapping')"
|
|
1957
2174
|
(click)="store.removeAutoPatchMapping(store.keyOf(f), $index)"
|
|
1958
2175
|
>
|
|
1959
2176
|
<mat-icon>delete</mat-icon>
|
|
@@ -1962,7 +2179,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
1962
2179
|
}
|
|
1963
2180
|
} @else {
|
|
1964
2181
|
<p class="deb-hint">
|
|
1965
|
-
|
|
2182
|
+
{{ ui.text('noAutoPatch') }}
|
|
1966
2183
|
</p>
|
|
1967
2184
|
}
|
|
1968
2185
|
</div>
|
|
@@ -1974,6 +2191,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
1974
2191
|
* RuleFormComponent — dialog/panel form for creating or editing a FormRule.
|
|
1975
2192
|
*/
|
|
1976
2193
|
class RuleFormComponent {
|
|
2194
|
+
/** Builder chrome, overridable via BUILDER_TEXT. */
|
|
2195
|
+
ui = inject(BuilderTextService);
|
|
1977
2196
|
store = inject(BuilderStore);
|
|
1978
2197
|
rule = {
|
|
1979
2198
|
formConfigId: 'form-1',
|
|
@@ -2029,8 +2248,7 @@ class RuleFormComponent {
|
|
|
2029
2248
|
*/
|
|
2030
2249
|
targetValues() {
|
|
2031
2250
|
const ids = this.rule.targets.filter(t => t.type === 'field').map(t => t.id);
|
|
2032
|
-
const unchanged = ids.length === this.cachedTargets.length &&
|
|
2033
|
-
ids.every((id, i) => id === this.cachedTargets[i]);
|
|
2251
|
+
const unchanged = ids.length === this.cachedTargets.length && ids.every((id, i) => id === this.cachedTargets[i]);
|
|
2034
2252
|
if (!unchanged)
|
|
2035
2253
|
this.cachedTargets = ids;
|
|
2036
2254
|
return this.cachedTargets;
|
|
@@ -2054,11 +2272,11 @@ class RuleFormComponent {
|
|
|
2054
2272
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RuleFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2055
2273
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: RuleFormComponent, isStandalone: true, selector: "ngx-rule-form", inputs: { rule: "rule" }, outputs: { save: "save", cancel: "cancel" }, ngImport: i0, template: `
|
|
2056
2274
|
<div class="deb-rule-form">
|
|
2057
|
-
<h3 class="deb-rule-form__title">{{ rule.id ? '
|
|
2275
|
+
<h3 class="deb-rule-form__title">{{ ui.text(rule.id ? 'editRuleTitle' : 'newRuleTitle') }}</h3>
|
|
2058
2276
|
|
|
2059
2277
|
<!-- Trigger Field -->
|
|
2060
2278
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
2061
|
-
<mat-label>
|
|
2279
|
+
<mat-label>{{ ui.text('triggerField') }}</mat-label>
|
|
2062
2280
|
<mat-select [(ngModel)]="rule.fieldId" data-testid="rule-trigger">
|
|
2063
2281
|
@for (option of triggerOptions(); track option.value) {
|
|
2064
2282
|
<mat-option [value]="option.value">
|
|
@@ -2070,13 +2288,8 @@ class RuleFormComponent {
|
|
|
2070
2288
|
|
|
2071
2289
|
<!-- Targets: the fields this rule acts on -->
|
|
2072
2290
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
2073
|
-
<mat-label>
|
|
2074
|
-
<mat-select
|
|
2075
|
-
multiple
|
|
2076
|
-
data-testid="rule-targets"
|
|
2077
|
-
[ngModel]="targetValues()"
|
|
2078
|
-
(ngModelChange)="setTargets($event)"
|
|
2079
|
-
>
|
|
2291
|
+
<mat-label>{{ ui.text('applyToFields') }}</mat-label>
|
|
2292
|
+
<mat-select multiple data-testid="rule-targets" [ngModel]="targetValues()" (ngModelChange)="setTargets($event)">
|
|
2080
2293
|
@for (option of targetOptions(); track option.value) {
|
|
2081
2294
|
<mat-option [value]="option.value">
|
|
2082
2295
|
{{ option.label }} <span class="deb-rule-path">{{ option.path }}</span>
|
|
@@ -2087,11 +2300,11 @@ class RuleFormComponent {
|
|
|
2087
2300
|
|
|
2088
2301
|
<!-- Conditions -->
|
|
2089
2302
|
<div class="deb-rule-section">
|
|
2090
|
-
<h4>
|
|
2303
|
+
<h4>{{ ui.text('conditions') }}</h4>
|
|
2091
2304
|
@for (cond of rule.conditions; track $index) {
|
|
2092
2305
|
<div class="deb-rule-row">
|
|
2093
2306
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
2094
|
-
<mat-label>
|
|
2307
|
+
<mat-label>{{ ui.text('operator') }}</mat-label>
|
|
2095
2308
|
<mat-select [(ngModel)]="cond.operator">
|
|
2096
2309
|
@for (op of operators; track op) {
|
|
2097
2310
|
<mat-option [value]="op">{{ op }}</mat-option>
|
|
@@ -2099,8 +2312,8 @@ class RuleFormComponent {
|
|
|
2099
2312
|
</mat-select>
|
|
2100
2313
|
</mat-form-field>
|
|
2101
2314
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
2102
|
-
<mat-label>
|
|
2103
|
-
<input matInput [(ngModel)]="cond.value" placeholder="
|
|
2315
|
+
<mat-label>{{ ui.text('value') }}</mat-label>
|
|
2316
|
+
<input matInput [(ngModel)]="cond.value" [placeholder]="ui.text('conditionValuePlaceholder')" />
|
|
2104
2317
|
</mat-form-field>
|
|
2105
2318
|
<button mat-icon-button color="warn" type="button" (click)="removeCondition($index)">
|
|
2106
2319
|
<mat-icon>delete</mat-icon>
|
|
@@ -2108,53 +2321,45 @@ class RuleFormComponent {
|
|
|
2108
2321
|
</div>
|
|
2109
2322
|
}
|
|
2110
2323
|
<button mat-stroked-button type="button" (click)="addCondition()">
|
|
2111
|
-
<mat-icon>add</mat-icon>
|
|
2324
|
+
<mat-icon>add</mat-icon> {{ ui.text('addCondition') }}
|
|
2112
2325
|
</button>
|
|
2113
2326
|
</div>
|
|
2114
2327
|
|
|
2115
2328
|
<!-- Action -->
|
|
2116
2329
|
<div class="deb-rule-section">
|
|
2117
|
-
<h4>
|
|
2330
|
+
<h4>{{ ui.text('action') }}</h4>
|
|
2118
2331
|
<div class="deb-rule-row">
|
|
2119
2332
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
2120
|
-
<mat-label>
|
|
2333
|
+
<mat-label>{{ ui.text('actionType') }}</mat-label>
|
|
2121
2334
|
<mat-select [(ngModel)]="rule.action.type">
|
|
2122
|
-
<mat-option value="visibility">
|
|
2123
|
-
<mat-option value="validation">
|
|
2124
|
-
<mat-option value="info">
|
|
2335
|
+
<mat-option value="visibility">{{ ui.text('actionVisibility') }}</mat-option>
|
|
2336
|
+
<mat-option value="validation">{{ ui.text('actionValidationMessage') }}</mat-option>
|
|
2337
|
+
<mat-option value="info">{{ ui.text('actionInfoBanner') }}</mat-option>
|
|
2125
2338
|
</mat-select>
|
|
2126
2339
|
</mat-form-field>
|
|
2127
2340
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
2128
|
-
<mat-label>
|
|
2129
|
-
<input matInput [(ngModel)]="rule.action.value" placeholder="
|
|
2341
|
+
<mat-label>{{ ui.text('valueOrMessage') }}</mat-label>
|
|
2342
|
+
<input matInput [(ngModel)]="rule.action.value" [placeholder]="ui.text('valueOrMessagePlaceholder')" />
|
|
2130
2343
|
</mat-form-field>
|
|
2131
2344
|
</div>
|
|
2132
2345
|
</div>
|
|
2133
2346
|
|
|
2134
2347
|
<div class="deb-rule-form__actions">
|
|
2135
|
-
<button mat-button type="button" (click)="cancel.emit()">
|
|
2136
|
-
<button mat-raised-button color="primary" type="button" (click)="save.emit(rule)">
|
|
2348
|
+
<button mat-button type="button" (click)="cancel.emit()">{{ ui.text('cancel') }}</button>
|
|
2349
|
+
<button mat-raised-button color="primary" type="button" (click)="save.emit(rule)">{{ ui.text('saveRule') }}</button>
|
|
2137
2350
|
</div>
|
|
2138
2351
|
</div>
|
|
2139
2352
|
`, isInline: true, styles: [".deb-rule-form{display:flex;flex-direction:column;gap:12px;background:#fafafa;border:1px solid #e0e0e0;border-radius:8px;padding:16px}.deb-rule-form__title{margin:0 0 8px;font-size:16px;font-weight:600}.deb-full{width:100%}.deb-rule-path{color:#888;font-size:11px;margin-left:6px}.deb-rule-section{display:flex;flex-direction:column;gap:8px}.deb-rule-section h4{margin:0;font-size:13px;font-weight:600;color:#555}.deb-rule-row{display:flex;gap:8px;align-items:center}.deb-rule-row>*{flex:1}.deb-rule-form__actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
2140
2353
|
}
|
|
2141
2354
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RuleFormComponent, decorators: [{
|
|
2142
2355
|
type: Component,
|
|
2143
|
-
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-rule-form', standalone: true, imports: [
|
|
2144
|
-
CommonModule,
|
|
2145
|
-
FormsModule,
|
|
2146
|
-
MatButtonModule,
|
|
2147
|
-
MatFormFieldModule,
|
|
2148
|
-
MatIconModule,
|
|
2149
|
-
MatInputModule,
|
|
2150
|
-
MatSelectModule,
|
|
2151
|
-
], template: `
|
|
2356
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-rule-form', standalone: true, imports: [CommonModule, FormsModule, MatButtonModule, MatFormFieldModule, MatIconModule, MatInputModule, MatSelectModule], template: `
|
|
2152
2357
|
<div class="deb-rule-form">
|
|
2153
|
-
<h3 class="deb-rule-form__title">{{ rule.id ? '
|
|
2358
|
+
<h3 class="deb-rule-form__title">{{ ui.text(rule.id ? 'editRuleTitle' : 'newRuleTitle') }}</h3>
|
|
2154
2359
|
|
|
2155
2360
|
<!-- Trigger Field -->
|
|
2156
2361
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
2157
|
-
<mat-label>
|
|
2362
|
+
<mat-label>{{ ui.text('triggerField') }}</mat-label>
|
|
2158
2363
|
<mat-select [(ngModel)]="rule.fieldId" data-testid="rule-trigger">
|
|
2159
2364
|
@for (option of triggerOptions(); track option.value) {
|
|
2160
2365
|
<mat-option [value]="option.value">
|
|
@@ -2166,13 +2371,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2166
2371
|
|
|
2167
2372
|
<!-- Targets: the fields this rule acts on -->
|
|
2168
2373
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
2169
|
-
<mat-label>
|
|
2170
|
-
<mat-select
|
|
2171
|
-
multiple
|
|
2172
|
-
data-testid="rule-targets"
|
|
2173
|
-
[ngModel]="targetValues()"
|
|
2174
|
-
(ngModelChange)="setTargets($event)"
|
|
2175
|
-
>
|
|
2374
|
+
<mat-label>{{ ui.text('applyToFields') }}</mat-label>
|
|
2375
|
+
<mat-select multiple data-testid="rule-targets" [ngModel]="targetValues()" (ngModelChange)="setTargets($event)">
|
|
2176
2376
|
@for (option of targetOptions(); track option.value) {
|
|
2177
2377
|
<mat-option [value]="option.value">
|
|
2178
2378
|
{{ option.label }} <span class="deb-rule-path">{{ option.path }}</span>
|
|
@@ -2183,11 +2383,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2183
2383
|
|
|
2184
2384
|
<!-- Conditions -->
|
|
2185
2385
|
<div class="deb-rule-section">
|
|
2186
|
-
<h4>
|
|
2386
|
+
<h4>{{ ui.text('conditions') }}</h4>
|
|
2187
2387
|
@for (cond of rule.conditions; track $index) {
|
|
2188
2388
|
<div class="deb-rule-row">
|
|
2189
2389
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
2190
|
-
<mat-label>
|
|
2390
|
+
<mat-label>{{ ui.text('operator') }}</mat-label>
|
|
2191
2391
|
<mat-select [(ngModel)]="cond.operator">
|
|
2192
2392
|
@for (op of operators; track op) {
|
|
2193
2393
|
<mat-option [value]="op">{{ op }}</mat-option>
|
|
@@ -2195,8 +2395,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2195
2395
|
</mat-select>
|
|
2196
2396
|
</mat-form-field>
|
|
2197
2397
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
2198
|
-
<mat-label>
|
|
2199
|
-
<input matInput [(ngModel)]="cond.value" placeholder="
|
|
2398
|
+
<mat-label>{{ ui.text('value') }}</mat-label>
|
|
2399
|
+
<input matInput [(ngModel)]="cond.value" [placeholder]="ui.text('conditionValuePlaceholder')" />
|
|
2200
2400
|
</mat-form-field>
|
|
2201
2401
|
<button mat-icon-button color="warn" type="button" (click)="removeCondition($index)">
|
|
2202
2402
|
<mat-icon>delete</mat-icon>
|
|
@@ -2204,32 +2404,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2204
2404
|
</div>
|
|
2205
2405
|
}
|
|
2206
2406
|
<button mat-stroked-button type="button" (click)="addCondition()">
|
|
2207
|
-
<mat-icon>add</mat-icon>
|
|
2407
|
+
<mat-icon>add</mat-icon> {{ ui.text('addCondition') }}
|
|
2208
2408
|
</button>
|
|
2209
2409
|
</div>
|
|
2210
2410
|
|
|
2211
2411
|
<!-- Action -->
|
|
2212
2412
|
<div class="deb-rule-section">
|
|
2213
|
-
<h4>
|
|
2413
|
+
<h4>{{ ui.text('action') }}</h4>
|
|
2214
2414
|
<div class="deb-rule-row">
|
|
2215
2415
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
2216
|
-
<mat-label>
|
|
2416
|
+
<mat-label>{{ ui.text('actionType') }}</mat-label>
|
|
2217
2417
|
<mat-select [(ngModel)]="rule.action.type">
|
|
2218
|
-
<mat-option value="visibility">
|
|
2219
|
-
<mat-option value="validation">
|
|
2220
|
-
<mat-option value="info">
|
|
2418
|
+
<mat-option value="visibility">{{ ui.text('actionVisibility') }}</mat-option>
|
|
2419
|
+
<mat-option value="validation">{{ ui.text('actionValidationMessage') }}</mat-option>
|
|
2420
|
+
<mat-option value="info">{{ ui.text('actionInfoBanner') }}</mat-option>
|
|
2221
2421
|
</mat-select>
|
|
2222
2422
|
</mat-form-field>
|
|
2223
2423
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
2224
|
-
<mat-label>
|
|
2225
|
-
<input matInput [(ngModel)]="rule.action.value" placeholder="
|
|
2424
|
+
<mat-label>{{ ui.text('valueOrMessage') }}</mat-label>
|
|
2425
|
+
<input matInput [(ngModel)]="rule.action.value" [placeholder]="ui.text('valueOrMessagePlaceholder')" />
|
|
2226
2426
|
</mat-form-field>
|
|
2227
2427
|
</div>
|
|
2228
2428
|
</div>
|
|
2229
2429
|
|
|
2230
2430
|
<div class="deb-rule-form__actions">
|
|
2231
|
-
<button mat-button type="button" (click)="cancel.emit()">
|
|
2232
|
-
<button mat-raised-button color="primary" type="button" (click)="save.emit(rule)">
|
|
2431
|
+
<button mat-button type="button" (click)="cancel.emit()">{{ ui.text('cancel') }}</button>
|
|
2432
|
+
<button mat-raised-button color="primary" type="button" (click)="save.emit(rule)">{{ ui.text('saveRule') }}</button>
|
|
2233
2433
|
</div>
|
|
2234
2434
|
</div>
|
|
2235
2435
|
`, styles: [".deb-rule-form{display:flex;flex-direction:column;gap:12px;background:#fafafa;border:1px solid #e0e0e0;border-radius:8px;padding:16px}.deb-rule-form__title{margin:0 0 8px;font-size:16px;font-weight:600}.deb-full{width:100%}.deb-rule-path{color:#888;font-size:11px;margin-left:6px}.deb-rule-section{display:flex;flex-direction:column;gap:8px}.deb-rule-section h4{margin:0;font-size:13px;font-weight:600;color:#555}.deb-rule-row{display:flex;gap:8px;align-items:center}.deb-rule-row>*{flex:1}.deb-rule-form__actions{display:flex;justify-content:flex-end;gap:8px;margin-top:8px}\n"] }]
|
|
@@ -2246,6 +2446,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2246
2446
|
* enable/disable, edit, delete. `RuleFormComponent` authors one rule; this owns the set.
|
|
2247
2447
|
*/
|
|
2248
2448
|
class FieldRulesListComponent {
|
|
2449
|
+
/** Builder chrome, overridable via BUILDER_TEXT. */
|
|
2450
|
+
ui = inject(BuilderTextService);
|
|
2249
2451
|
store = inject(BuilderStore);
|
|
2250
2452
|
/** The rule currently open in the editor — a new draft or a copy of an existing rule. */
|
|
2251
2453
|
editing = signal(null, ...(ngDevMode ? [{ debugName: "editing" }] : /* istanbul ignore next */ []));
|
|
@@ -2297,7 +2499,7 @@ class FieldRulesListComponent {
|
|
|
2297
2499
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: FieldRulesListComponent, isStandalone: true, selector: "ngx-field-rules-list", ngImport: i0, template: `
|
|
2298
2500
|
<div class="deb-rules">
|
|
2299
2501
|
<div class="deb-rules__head">
|
|
2300
|
-
<span class="deb-section-title">
|
|
2502
|
+
<span class="deb-section-title">{{ ui.text('rules') }}</span>
|
|
2301
2503
|
<button
|
|
2302
2504
|
mat-stroked-button
|
|
2303
2505
|
type="button"
|
|
@@ -2305,7 +2507,7 @@ class FieldRulesListComponent {
|
|
|
2305
2507
|
[disabled]="!store.selectedFieldId()"
|
|
2306
2508
|
(click)="startCreate()"
|
|
2307
2509
|
>
|
|
2308
|
-
<mat-icon>add</mat-icon>
|
|
2510
|
+
<mat-icon>add</mat-icon> {{ ui.text('rule') }}
|
|
2309
2511
|
</button>
|
|
2310
2512
|
</div>
|
|
2311
2513
|
|
|
@@ -2314,7 +2516,7 @@ class FieldRulesListComponent {
|
|
|
2314
2516
|
}
|
|
2315
2517
|
|
|
2316
2518
|
@if (store.rulesForSelectedField().length === 0 && !editing()) {
|
|
2317
|
-
<p class="deb-hint" data-testid="rules-empty">
|
|
2519
|
+
<p class="deb-hint" data-testid="rules-empty">{{ ui.text('noRulesOnField') }}</p>
|
|
2318
2520
|
}
|
|
2319
2521
|
|
|
2320
2522
|
@for (rule of store.rulesForSelectedField(); track rule.id) {
|
|
@@ -2322,19 +2524,20 @@ class FieldRulesListComponent {
|
|
|
2322
2524
|
<div class="deb-rule-item__body">
|
|
2323
2525
|
<span class="deb-rule-item__summary">{{ summarize(rule) }}</span>
|
|
2324
2526
|
<span class="deb-rule-item__meta">
|
|
2325
|
-
|
|
2527
|
+
{{ ui.text('rulePriority', { priority: rule.priority, targets: rule.targets.length }) }}
|
|
2326
2528
|
</span>
|
|
2327
2529
|
</div>
|
|
2328
2530
|
<div class="deb-rule-item__actions">
|
|
2329
2531
|
<mat-slide-toggle
|
|
2330
2532
|
[checked]="rule.enabled"
|
|
2331
|
-
matTooltip="
|
|
2533
|
+
[matTooltip]="ui.text('toggleRule')"
|
|
2332
2534
|
(change)="store.toggleRule(rule.id!, $event.checked)"
|
|
2333
2535
|
/>
|
|
2334
2536
|
<button
|
|
2335
2537
|
mat-icon-button
|
|
2336
2538
|
type="button"
|
|
2337
|
-
matTooltip="
|
|
2539
|
+
[matTooltip]="ui.text('moveUp')"
|
|
2540
|
+
[attr.aria-label]="ui.text('moveRuleUp')"
|
|
2338
2541
|
[attr.data-testid]="'rule-up-' + rule.id"
|
|
2339
2542
|
(click)="store.moveRule(rule.id!, -1)"
|
|
2340
2543
|
>
|
|
@@ -2343,7 +2546,8 @@ class FieldRulesListComponent {
|
|
|
2343
2546
|
<button
|
|
2344
2547
|
mat-icon-button
|
|
2345
2548
|
type="button"
|
|
2346
|
-
matTooltip="
|
|
2549
|
+
[matTooltip]="ui.text('moveDown')"
|
|
2550
|
+
[attr.aria-label]="ui.text('moveRuleDown')"
|
|
2347
2551
|
[attr.data-testid]="'rule-down-' + rule.id"
|
|
2348
2552
|
(click)="store.moveRule(rule.id!, 1)"
|
|
2349
2553
|
>
|
|
@@ -2352,7 +2556,7 @@ class FieldRulesListComponent {
|
|
|
2352
2556
|
<button
|
|
2353
2557
|
mat-icon-button
|
|
2354
2558
|
type="button"
|
|
2355
|
-
matTooltip="
|
|
2559
|
+
[matTooltip]="ui.text('editRule')"
|
|
2356
2560
|
[attr.data-testid]="'rule-edit-' + rule.id"
|
|
2357
2561
|
(click)="startEdit(rule)"
|
|
2358
2562
|
>
|
|
@@ -2362,7 +2566,7 @@ class FieldRulesListComponent {
|
|
|
2362
2566
|
mat-icon-button
|
|
2363
2567
|
type="button"
|
|
2364
2568
|
color="warn"
|
|
2365
|
-
matTooltip="
|
|
2569
|
+
[matTooltip]="ui.text('deleteRule')"
|
|
2366
2570
|
[attr.data-testid]="'rule-delete-' + rule.id"
|
|
2367
2571
|
(click)="store.removeRule(rule.id!)"
|
|
2368
2572
|
>
|
|
@@ -2379,7 +2583,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2379
2583
|
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-field-rules-list', standalone: true, imports: [MatButtonModule, MatIconModule, MatSlideToggleModule, MatTooltipModule, RuleFormComponent], template: `
|
|
2380
2584
|
<div class="deb-rules">
|
|
2381
2585
|
<div class="deb-rules__head">
|
|
2382
|
-
<span class="deb-section-title">
|
|
2586
|
+
<span class="deb-section-title">{{ ui.text('rules') }}</span>
|
|
2383
2587
|
<button
|
|
2384
2588
|
mat-stroked-button
|
|
2385
2589
|
type="button"
|
|
@@ -2387,7 +2591,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2387
2591
|
[disabled]="!store.selectedFieldId()"
|
|
2388
2592
|
(click)="startCreate()"
|
|
2389
2593
|
>
|
|
2390
|
-
<mat-icon>add</mat-icon>
|
|
2594
|
+
<mat-icon>add</mat-icon> {{ ui.text('rule') }}
|
|
2391
2595
|
</button>
|
|
2392
2596
|
</div>
|
|
2393
2597
|
|
|
@@ -2396,7 +2600,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2396
2600
|
}
|
|
2397
2601
|
|
|
2398
2602
|
@if (store.rulesForSelectedField().length === 0 && !editing()) {
|
|
2399
|
-
<p class="deb-hint" data-testid="rules-empty">
|
|
2603
|
+
<p class="deb-hint" data-testid="rules-empty">{{ ui.text('noRulesOnField') }}</p>
|
|
2400
2604
|
}
|
|
2401
2605
|
|
|
2402
2606
|
@for (rule of store.rulesForSelectedField(); track rule.id) {
|
|
@@ -2404,19 +2608,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2404
2608
|
<div class="deb-rule-item__body">
|
|
2405
2609
|
<span class="deb-rule-item__summary">{{ summarize(rule) }}</span>
|
|
2406
2610
|
<span class="deb-rule-item__meta">
|
|
2407
|
-
|
|
2611
|
+
{{ ui.text('rulePriority', { priority: rule.priority, targets: rule.targets.length }) }}
|
|
2408
2612
|
</span>
|
|
2409
2613
|
</div>
|
|
2410
2614
|
<div class="deb-rule-item__actions">
|
|
2411
2615
|
<mat-slide-toggle
|
|
2412
2616
|
[checked]="rule.enabled"
|
|
2413
|
-
matTooltip="
|
|
2617
|
+
[matTooltip]="ui.text('toggleRule')"
|
|
2414
2618
|
(change)="store.toggleRule(rule.id!, $event.checked)"
|
|
2415
2619
|
/>
|
|
2416
2620
|
<button
|
|
2417
2621
|
mat-icon-button
|
|
2418
2622
|
type="button"
|
|
2419
|
-
matTooltip="
|
|
2623
|
+
[matTooltip]="ui.text('moveUp')"
|
|
2624
|
+
[attr.aria-label]="ui.text('moveRuleUp')"
|
|
2420
2625
|
[attr.data-testid]="'rule-up-' + rule.id"
|
|
2421
2626
|
(click)="store.moveRule(rule.id!, -1)"
|
|
2422
2627
|
>
|
|
@@ -2425,7 +2630,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2425
2630
|
<button
|
|
2426
2631
|
mat-icon-button
|
|
2427
2632
|
type="button"
|
|
2428
|
-
matTooltip="
|
|
2633
|
+
[matTooltip]="ui.text('moveDown')"
|
|
2634
|
+
[attr.aria-label]="ui.text('moveRuleDown')"
|
|
2429
2635
|
[attr.data-testid]="'rule-down-' + rule.id"
|
|
2430
2636
|
(click)="store.moveRule(rule.id!, 1)"
|
|
2431
2637
|
>
|
|
@@ -2434,7 +2640,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2434
2640
|
<button
|
|
2435
2641
|
mat-icon-button
|
|
2436
2642
|
type="button"
|
|
2437
|
-
matTooltip="
|
|
2643
|
+
[matTooltip]="ui.text('editRule')"
|
|
2438
2644
|
[attr.data-testid]="'rule-edit-' + rule.id"
|
|
2439
2645
|
(click)="startEdit(rule)"
|
|
2440
2646
|
>
|
|
@@ -2444,7 +2650,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2444
2650
|
mat-icon-button
|
|
2445
2651
|
type="button"
|
|
2446
2652
|
color="warn"
|
|
2447
|
-
matTooltip="
|
|
2653
|
+
[matTooltip]="ui.text('deleteRule')"
|
|
2448
2654
|
[attr.data-testid]="'rule-delete-' + rule.id"
|
|
2449
2655
|
(click)="store.removeRule(rule.id!)"
|
|
2450
2656
|
>
|
|
@@ -2458,6 +2664,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2458
2664
|
}] });
|
|
2459
2665
|
|
|
2460
2666
|
class ReferencedFieldConfigComponent {
|
|
2667
|
+
/** Builder chrome, overridable via BUILDER_TEXT. */
|
|
2668
|
+
ui = inject(BuilderTextService);
|
|
2461
2669
|
store = inject(BuilderStore);
|
|
2462
2670
|
configSource = inject(ConfigSourceService, { optional: true });
|
|
2463
2671
|
field = computed(() => this.store.selectedField(), ...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
|
|
@@ -2536,56 +2744,54 @@ class ReferencedFieldConfigComponent {
|
|
|
2536
2744
|
@if (field(); as f) {
|
|
2537
2745
|
<div class="deb-referenced-field" style="margin-top: 16px;">
|
|
2538
2746
|
<div class="deb-row" style="justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
2539
|
-
<span class="deb-section-title" style="margin:0;">
|
|
2747
|
+
<span class="deb-section-title" style="margin:0;">{{ ui.text('referencedField') }}</span>
|
|
2540
2748
|
<mat-slide-toggle
|
|
2541
2749
|
data-testid="toggle-referenced"
|
|
2542
2750
|
[ngModel]="f.isReferenced ?? false"
|
|
2543
2751
|
(ngModelChange)="toggleReferenced(f, $event)"
|
|
2544
2752
|
>
|
|
2545
|
-
|
|
2753
|
+
{{ ui.text('link') }}
|
|
2546
2754
|
</mat-slide-toggle>
|
|
2547
2755
|
</div>
|
|
2548
2756
|
|
|
2549
2757
|
@if (f.isReferenced) {
|
|
2550
2758
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full" style="margin-bottom: 8px;">
|
|
2551
|
-
<mat-label>
|
|
2759
|
+
<mat-label>{{ ui.text('sourceEntityKey') }}</mat-label>
|
|
2552
2760
|
<input
|
|
2553
2761
|
matInput
|
|
2554
2762
|
data-testid="referenced-entity-key"
|
|
2555
2763
|
[ngModel]="f.referencedEntityKey ?? ''"
|
|
2556
2764
|
(ngModelChange)="updateEntityKey(f, $event)"
|
|
2557
|
-
placeholder="
|
|
2765
|
+
[placeholder]="ui.text('sourceEntityKeyPlaceholder')"
|
|
2558
2766
|
/>
|
|
2559
2767
|
</mat-form-field>
|
|
2560
2768
|
|
|
2561
2769
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full" style="margin-bottom: 8px;">
|
|
2562
|
-
<mat-label>
|
|
2770
|
+
<mat-label>{{ ui.text('sourceFieldId') }}</mat-label>
|
|
2563
2771
|
<input
|
|
2564
2772
|
matInput
|
|
2565
2773
|
data-testid="referenced-field-id"
|
|
2566
2774
|
[ngModel]="f.referencedFieldId ?? ''"
|
|
2567
2775
|
(ngModelChange)="updateFieldId(f, $event)"
|
|
2568
|
-
placeholder="
|
|
2776
|
+
[placeholder]="ui.text('sourceFieldIdPlaceholder')"
|
|
2569
2777
|
/>
|
|
2570
2778
|
</mat-form-field>
|
|
2571
2779
|
|
|
2572
2780
|
@if (f.hasDrift) {
|
|
2573
|
-
<div
|
|
2781
|
+
<div
|
|
2782
|
+
class="deb-drift-banner"
|
|
2783
|
+
data-testid="drift-banner"
|
|
2784
|
+
style="background: #fff3e0; border: 1px solid #ffe0b2; padding: 8px 12px; border-radius: 4px; margin-top: 8px;"
|
|
2785
|
+
>
|
|
2574
2786
|
<div class="deb-row" style="gap: 8px; align-items: center; color: #e65100; font-weight: 500;">
|
|
2575
2787
|
<mat-icon style="font-size: 18px; width:18px; height:18px;">warning</mat-icon>
|
|
2576
|
-
<span>
|
|
2788
|
+
<span>{{ ui.text('driftHeading') }}</span>
|
|
2577
2789
|
</div>
|
|
2578
2790
|
<p style="margin: 4px 0 8px 0; font-size: 12px; color: #666;">
|
|
2579
|
-
|
|
2791
|
+
{{ ui.text('driftBody', { entity: f.referencedEntityKey ?? '' }) }}
|
|
2580
2792
|
</p>
|
|
2581
|
-
<button
|
|
2582
|
-
|
|
2583
|
-
color="warn"
|
|
2584
|
-
type="button"
|
|
2585
|
-
data-testid="sync-source-btn"
|
|
2586
|
-
(click)="syncWithSource(f)"
|
|
2587
|
-
>
|
|
2588
|
-
Sync with Source
|
|
2793
|
+
<button mat-flat-button color="warn" type="button" data-testid="sync-source-btn" (click)="syncWithSource(f)">
|
|
2794
|
+
{{ ui.text('syncWithSource') }}
|
|
2589
2795
|
</button>
|
|
2590
2796
|
</div>
|
|
2591
2797
|
}
|
|
@@ -2614,56 +2820,54 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2614
2820
|
@if (field(); as f) {
|
|
2615
2821
|
<div class="deb-referenced-field" style="margin-top: 16px;">
|
|
2616
2822
|
<div class="deb-row" style="justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
2617
|
-
<span class="deb-section-title" style="margin:0;">
|
|
2823
|
+
<span class="deb-section-title" style="margin:0;">{{ ui.text('referencedField') }}</span>
|
|
2618
2824
|
<mat-slide-toggle
|
|
2619
2825
|
data-testid="toggle-referenced"
|
|
2620
2826
|
[ngModel]="f.isReferenced ?? false"
|
|
2621
2827
|
(ngModelChange)="toggleReferenced(f, $event)"
|
|
2622
2828
|
>
|
|
2623
|
-
|
|
2829
|
+
{{ ui.text('link') }}
|
|
2624
2830
|
</mat-slide-toggle>
|
|
2625
2831
|
</div>
|
|
2626
2832
|
|
|
2627
2833
|
@if (f.isReferenced) {
|
|
2628
2834
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full" style="margin-bottom: 8px;">
|
|
2629
|
-
<mat-label>
|
|
2835
|
+
<mat-label>{{ ui.text('sourceEntityKey') }}</mat-label>
|
|
2630
2836
|
<input
|
|
2631
2837
|
matInput
|
|
2632
2838
|
data-testid="referenced-entity-key"
|
|
2633
2839
|
[ngModel]="f.referencedEntityKey ?? ''"
|
|
2634
2840
|
(ngModelChange)="updateEntityKey(f, $event)"
|
|
2635
|
-
placeholder="
|
|
2841
|
+
[placeholder]="ui.text('sourceEntityKeyPlaceholder')"
|
|
2636
2842
|
/>
|
|
2637
2843
|
</mat-form-field>
|
|
2638
2844
|
|
|
2639
2845
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full" style="margin-bottom: 8px;">
|
|
2640
|
-
<mat-label>
|
|
2846
|
+
<mat-label>{{ ui.text('sourceFieldId') }}</mat-label>
|
|
2641
2847
|
<input
|
|
2642
2848
|
matInput
|
|
2643
2849
|
data-testid="referenced-field-id"
|
|
2644
2850
|
[ngModel]="f.referencedFieldId ?? ''"
|
|
2645
2851
|
(ngModelChange)="updateFieldId(f, $event)"
|
|
2646
|
-
placeholder="
|
|
2852
|
+
[placeholder]="ui.text('sourceFieldIdPlaceholder')"
|
|
2647
2853
|
/>
|
|
2648
2854
|
</mat-form-field>
|
|
2649
2855
|
|
|
2650
2856
|
@if (f.hasDrift) {
|
|
2651
|
-
<div
|
|
2857
|
+
<div
|
|
2858
|
+
class="deb-drift-banner"
|
|
2859
|
+
data-testid="drift-banner"
|
|
2860
|
+
style="background: #fff3e0; border: 1px solid #ffe0b2; padding: 8px 12px; border-radius: 4px; margin-top: 8px;"
|
|
2861
|
+
>
|
|
2652
2862
|
<div class="deb-row" style="gap: 8px; align-items: center; color: #e65100; font-weight: 500;">
|
|
2653
2863
|
<mat-icon style="font-size: 18px; width:18px; height:18px;">warning</mat-icon>
|
|
2654
|
-
<span>
|
|
2864
|
+
<span>{{ ui.text('driftHeading') }}</span>
|
|
2655
2865
|
</div>
|
|
2656
2866
|
<p style="margin: 4px 0 8px 0; font-size: 12px; color: #666;">
|
|
2657
|
-
|
|
2867
|
+
{{ ui.text('driftBody', { entity: f.referencedEntityKey ?? '' }) }}
|
|
2658
2868
|
</p>
|
|
2659
|
-
<button
|
|
2660
|
-
|
|
2661
|
-
color="warn"
|
|
2662
|
-
type="button"
|
|
2663
|
-
data-testid="sync-source-btn"
|
|
2664
|
-
(click)="syncWithSource(f)"
|
|
2665
|
-
>
|
|
2666
|
-
Sync with Source
|
|
2869
|
+
<button mat-flat-button color="warn" type="button" data-testid="sync-source-btn" (click)="syncWithSource(f)">
|
|
2870
|
+
{{ ui.text('syncWithSource') }}
|
|
2667
2871
|
</button>
|
|
2668
2872
|
</div>
|
|
2669
2873
|
}
|
|
@@ -2678,6 +2882,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2678
2882
|
* FieldInspectorComponent — edits every property of the currently selected field.
|
|
2679
2883
|
*/
|
|
2680
2884
|
class FieldInspectorComponent {
|
|
2885
|
+
/** Builder chrome, overridable via BUILDER_TEXT. */
|
|
2886
|
+
ui = inject(BuilderTextService);
|
|
2681
2887
|
store = inject(BuilderStore);
|
|
2682
2888
|
field = this.store.selectedField;
|
|
2683
2889
|
meta = computed(() => {
|
|
@@ -2814,7 +3020,7 @@ class FieldInspectorComponent {
|
|
|
2814
3020
|
return String(value);
|
|
2815
3021
|
}
|
|
2816
3022
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FieldInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2817
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: FieldInspectorComponent, isStandalone: true, selector: "ngx-field-inspector", ngImport: i0, template: "@if (field(); as f) {\n <div class=\"deb-inspector\">\n <!-- Identity -->\n <div class=\"deb-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>Field id</mat-label>\n <input matInput data-testid=\"field-id\" [value]=\"f.id\" disabled />\n @if (store.hasManualId(f.id)) {\n <mat-hint>The stored data key for this saved field \u2014 fixed.</mat-hint>\n } @else {\n <mat-hint>Derived from the label. Used as the data key.</mat-hint>\n }\n </mat-form-field>\n <span class=\"deb-chip\">{{ meta()?.label || f.type }}</span>\n </div>\n\n @if (tabOptions().length > 1) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Tab</mat-label>\n <mat-select data-testid=\"field-tab\"\n [ngModel]=\"currentTabId(f)\" (ngModelChange)=\"moveToTab(f, $event)\">\n @for (tab of tabOptions(); track tab.id) {\n <mat-option [value]=\"tab.id\">{{ tab.label }}</mat-option>\n }\n </mat-select>\n <mat-hint>Moving the field rewrites its path and repoints the rules that named it.</mat-hint>\n </mat-form-field>\n }\n\n\n <!-- Localised text (active language) -->\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Label ({{ lang() }})</mat-label>\n <input\n matInput\n data-testid=\"field-label\"\n [ngModel]=\"labelValue(f)\"\n (ngModelChange)=\"store.setFieldLabel(store.keyOf(f), lang(), $event)\"\n />\n </mat-form-field>\n\n @if (meta()?.supportsPlaceholder) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Placeholder ({{ lang() }})</mat-label>\n <input\n matInput\n [ngModel]=\"placeholderValue(f)\"\n (ngModelChange)=\"store.setFieldPlaceholder(store.keyOf(f), lang(), $event)\"\n />\n </mat-form-field>\n }\n\n @if (meta()?.supportsDefaultValue) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Default value</mat-label>\n <input\n matInput\n [ngModel]=\"f.defaultValue ?? ''\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { defaultValue: $event })\"\n />\n </mat-form-field>\n }\n\n <!-- Entity reference, cascade, and autoPatch -->\n @if (meta()?.isEntityRef) {\n <mat-divider></mat-divider>\n <ngx-entity-reference-config />\n }\n\n <mat-divider></mat-divider>\n\n <!-- Validators -->\n @if (meta(); as m) {\n @if (m.flagValidators.length || m.paramValidators.length) {\n <span class=\"deb-section-title\">Validation</span>\n <div class=\"deb-toggles\">\n @for (v of m.flagValidators; track v) {\n <mat-checkbox\n [ngModel]=\"store.hasFlagValidator(f, v)\"\n (ngModelChange)=\"store.toggleFlagValidator(store.keyOf(f), v, $event)\"\n >{{ v }}</mat-checkbox>\n }\n </div>\n @for (v of m.paramValidators; track v) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ v }}</mat-label>\n <input\n matInput\n type=\"number\"\n [ngModel]=\"store.getParamValidator(f, v)\"\n (ngModelChange)=\"store.setParamValidator(store.keyOf(f), v, toNum($event))\"\n />\n </mat-form-field>\n }\n }\n }\n\n <!-- Options (dropdown / radio / multiSelect) -->\n @if (meta()?.hasOptions) {\n <mat-divider></mat-divider>\n <span class=\"deb-section-title\">Data source</span>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Options come from</mat-label>\n <mat-select\n data-testid=\"data-source\"\n [ngModel]=\"store.fieldDataSource(f)\"\n (ngModelChange)=\"store.setFieldDataSource(store.keyOf(f), $event)\"\n >\n <mat-option value=\"none\">Nothing yet</mat-option>\n <mat-option value=\"manual\">Options authored here</mat-option>\n <mat-option value=\"lookup\">A named list</mat-option>\n </mat-select>\n </mat-form-field>\n }\n\n <!-- Named lookup list -->\n @if (meta()?.hasOptions && store.fieldDataSource(f) === 'lookup') {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>List name</mat-label>\n <input\n matInput\n data-testid=\"list-name\"\n [ngModel]=\"f.listName ?? ''\"\n (ngModelChange)=\"store.setListName(store.keyOf(f), $event)\"\n />\n </mat-form-field>\n <p class=\"deb-hint\">\n The consuming app resolves this name through its lookup registry. Options are not\n authored here.\n </p>\n }\n\n <!-- Inline options -->\n @if (meta()?.hasOptions && store.fieldDataSource(f) !== 'lookup') {\n <div class=\"deb-row\" style=\"justify-content: space-between;\">\n <span class=\"deb-section-title\">Options</span>\n <button mat-stroked-button type=\"button\" (click)=\"store.addOption(store.keyOf(f))\">\n <mat-icon>add</mat-icon> Option\n </button>\n </div>\n @for (opt of f.options ?? []; track $index) {\n <div class=\"deb-option-row\" data-testid=\"option-row\">\n <!-- One input: the displayed text is the stored value. -->\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Option ({{ lang() }})</mat-label>\n <input\n matInput\n [attr.data-testid]=\"'option-' + $index\"\n [ngModel]=\"optionLabel(opt)\"\n (ngModelChange)=\"store.setOptionLabel(store.keyOf(f), $index, lang(), $event)\"\n />\n </mat-form-field>\n <button mat-icon-button type=\"button\" color=\"warn\"\n (click)=\"store.removeOption(store.keyOf(f), $index)\" matTooltip=\"Remove option\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n @if (!(f.options ?? []).length) {\n <p class=\"deb-hint\">No options yet \u2014 add at least one.</p>\n }\n }\n\n <mat-divider></mat-divider>\n\n <!-- Flags -->\n <span class=\"deb-section-title\">Display</span>\n <div class=\"deb-toggles\">\n <mat-slide-toggle\n [ngModel]=\"f.visibility ?? true\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { visibility: $event })\"\n >Visible</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.table?.visible ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { table: { visible: $event } })\"\n >Table column</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.readonly ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { readonly: $event })\"\n >Read-only</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.disabled ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { disabled: $event })\"\n >Disabled</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.maskData ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { maskData: $event })\"\n >Mask data (RBAC)</mat-slide-toggle>\n <mat-slide-toggle\n data-testid=\"toggle-critical\"\n [ngModel]=\"f.criticalField ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { criticalField: $event })\"\n matTooltip=\"Renders locked; editing it announces a change against the session baseline.\"\n >Critical field</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.showOnMinimize ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { showOnMinimize: $event })\"\n matTooltip=\"Shown in the record editor's summary panel.\"\n >Show in summary</mat-slide-toggle>\n </div>\n\n <mat-divider></mat-divider>\n\n <!-- Conditional visibility (static showWhen) -->\n <div class=\"deb-row deb-row--split\">\n <span class=\"deb-section-title\">Show when</span>\n <button mat-stroked-button type=\"button\" data-testid=\"add-show-when\" (click)=\"addShowWhen(f)\">\n <mat-icon>add</mat-icon> Condition\n </button>\n </div>\n @for (entry of showWhenEntries(f); track entry.key) {\n <div class=\"deb-option-row\" data-testid=\"option-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>Watched field</mat-label>\n <mat-select data-testid=\"show-when-field\"\n [ngModel]=\"entry.key\" (ngModelChange)=\"renameShowWhen(f, entry.key, $event)\">\n @for (option of optionsWith(entry.key); track option.value) {\n <mat-option [value]=\"option.value\">\n {{ option.label }} <span class=\"deb-path-hint\">{{ option.path }}</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>Equals</mat-label>\n <input matInput [ngModel]=\"entry.display\" (ngModelChange)=\"setShowWhenValue(f, entry.key, $event)\" />\n </mat-form-field>\n <button mat-icon-button type=\"button\" color=\"warn\" matTooltip=\"Remove condition\"\n (click)=\"removeShowWhen(f, entry.key)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n @if (!showWhenEntries(f).length) {\n <p class=\"deb-hint\">Always visible. Add a condition to show this field only for certain values.</p>\n }\n\n <!-- patchOnTrue (boolean/checkbox only) -->\n @if (f.type === 'boolean' || f.type === 'checkbox') {\n <mat-divider></mat-divider>\n <div class=\"deb-row deb-row--split\">\n <span class=\"deb-section-title\">Copy on true</span>\n <button mat-stroked-button type=\"button\" data-testid=\"add-patch-on-true\"\n (click)=\"store.addPatchOnTrueMapping(store.keyOf(f))\">\n <mat-icon>add</mat-icon> Mapping\n </button>\n </div>\n @for (mapping of f.patchOnTrue ?? []; track $index) {\n <div class=\"deb-option-row\" data-testid=\"option-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>From field</mat-label>\n <mat-select data-testid=\"patch-on-true-from\"\n [ngModel]=\"mapping.from\"\n (ngModelChange)=\"store.updatePatchOnTrueMapping(store.keyOf(f), $index, { from: $event })\">\n @for (option of optionsWith(mapping.from); track option.value) {\n <mat-option [value]=\"option.value\">\n {{ option.label }} <span class=\"deb-path-hint\">{{ option.path }}</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>To field</mat-label>\n <mat-select data-testid=\"patch-on-true-to\"\n [ngModel]=\"mapping.to\"\n (ngModelChange)=\"store.updatePatchOnTrueMapping(store.keyOf(f), $index, { to: $event })\">\n @for (option of optionsWith(mapping.to); track option.value) {\n <mat-option [value]=\"option.value\">\n {{ option.label }} <span class=\"deb-path-hint\">{{ option.path }}</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <button mat-icon-button type=\"button\" color=\"warn\" matTooltip=\"Remove mapping\"\n (click)=\"store.removePatchOnTrueMapping(store.keyOf(f), $index)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n @if (!(f.patchOnTrue ?? []).length) {\n <p class=\"deb-hint\">When this flips to true, copy one field's value into another.</p>\n }\n }\n\n <mat-divider></mat-divider>\n\n <!-- Rules attached to this field -->\n <ngx-field-rules-list />\n\n <mat-divider></mat-divider>\n\n <!-- Cross-entity field reference & drift management (Phase 8) -->\n <ngx-referenced-field-config />\n </div>\n} @else {\n <div class=\"deb-inspector__empty\">\n <mat-icon>touch_app</mat-icon>\n <p>Select a field to edit its properties.</p>\n </div>\n}\n", styles: [".deb-inspector{display:flex;flex-direction:column;gap:12px}.deb-inspector__empty{color:var(--deb-muted, #6b7280);text-align:center;padding:24px 8px}.deb-full{width:100%}.deb-row{display:flex;gap:8px;align-items:center}.deb-row>*{flex:1}.deb-row--split{justify-content:space-between}.deb-row--split>*{flex:0 0 auto}.deb-toggles{display:grid;grid-template-columns:1fr 1fr;gap:8px 12px}.deb-option-row{display:flex;gap:6px;align-items:center}.deb-chip{display:inline-block;padding:2px 8px;border-radius:12px;background:var(--deb-accent-soft, #eef2ff);color:var(--deb-accent, #6366f1);font-size:12px;font-weight:600}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i3$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i3.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i9.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: EntityReferenceConfigComponent, selector: "ngx-entity-reference-config" }, { kind: "component", type: FieldRulesListComponent, selector: "ngx-field-rules-list" }, { kind: "component", type: ReferencedFieldConfigComponent, selector: "ngx-referenced-field-config" }] });
|
|
3023
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: FieldInspectorComponent, isStandalone: true, selector: "ngx-field-inspector", ngImport: i0, template: "@if (field(); as f) {\n <div class=\"deb-inspector\">\n <!-- Identity -->\n <div class=\"deb-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ ui.text('fieldId') }}</mat-label>\n <input matInput data-testid=\"field-id\" [value]=\"f.id\" disabled />\n @if (store.hasManualId(f.id)) {\n <mat-hint>{{ ui.text('fieldIdFixedHint') }}</mat-hint>\n } @else {\n <mat-hint>{{ ui.text('fieldIdDerivedHint') }}</mat-hint>\n }\n </mat-form-field>\n <span class=\"deb-chip\">{{ meta()?.label || f.type }}</span>\n </div>\n\n @if (tabOptions().length > 1) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('tab') }}</mat-label>\n <mat-select data-testid=\"field-tab\" [ngModel]=\"currentTabId(f)\" (ngModelChange)=\"moveToTab(f, $event)\">\n @for (tab of tabOptions(); track tab.id) {\n <mat-option [value]=\"tab.id\">{{ tab.label }}</mat-option>\n }\n </mat-select>\n <mat-hint>{{ ui.text('tabMoveHint') }}</mat-hint>\n </mat-form-field>\n }\n\n <!-- Localised text (active language) -->\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('labelInLanguage', { language: lang() }) }}</mat-label>\n <input\n matInput\n data-testid=\"field-label\"\n [ngModel]=\"labelValue(f)\"\n (ngModelChange)=\"store.setFieldLabel(store.keyOf(f), lang(), $event)\"\n />\n </mat-form-field>\n\n @if (meta()?.supportsPlaceholder) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('placeholderInLanguage', { language: lang() }) }}</mat-label>\n <input\n matInput\n [ngModel]=\"placeholderValue(f)\"\n (ngModelChange)=\"store.setFieldPlaceholder(store.keyOf(f), lang(), $event)\"\n />\n </mat-form-field>\n }\n\n @if (meta()?.supportsDefaultValue) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('defaultValue') }}</mat-label>\n <input\n matInput\n [ngModel]=\"f.defaultValue ?? ''\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { defaultValue: $event })\"\n />\n </mat-form-field>\n }\n\n <!-- Entity reference, cascade, and autoPatch -->\n @if (meta()?.isEntityRef) {\n <mat-divider></mat-divider>\n <ngx-entity-reference-config />\n }\n\n <mat-divider></mat-divider>\n\n <!-- Validators -->\n @if (meta(); as m) {\n @if (m.flagValidators.length || m.paramValidators.length) {\n <span class=\"deb-section-title\">{{ ui.text('validation') }}</span>\n <div class=\"deb-toggles\">\n @for (v of m.flagValidators; track v) {\n <mat-checkbox\n [ngModel]=\"store.hasFlagValidator(f, v)\"\n (ngModelChange)=\"store.toggleFlagValidator(store.keyOf(f), v, $event)\"\n >{{ v }}</mat-checkbox\n >\n }\n </div>\n @for (v of m.paramValidators; track v) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ v }}</mat-label>\n <input\n matInput\n type=\"number\"\n [ngModel]=\"store.getParamValidator(f, v)\"\n (ngModelChange)=\"store.setParamValidator(store.keyOf(f), v, toNum($event))\"\n />\n </mat-form-field>\n }\n }\n }\n\n <!-- Options (dropdown / radio / multiSelect) -->\n @if (meta()?.hasOptions) {\n <mat-divider></mat-divider>\n <span class=\"deb-section-title\">{{ ui.text('dataSource') }}</span>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('optionsComeFrom') }}</mat-label>\n <mat-select\n data-testid=\"data-source\"\n [ngModel]=\"store.fieldDataSource(f)\"\n (ngModelChange)=\"store.setFieldDataSource(store.keyOf(f), $event)\"\n >\n <mat-option value=\"none\">{{ ui.text('optionsSourceNone') }}</mat-option>\n <mat-option value=\"manual\">{{ ui.text('optionsSourceInline') }}</mat-option>\n <mat-option value=\"lookup\">{{ ui.text('optionsSourceList') }}</mat-option>\n </mat-select>\n </mat-form-field>\n }\n\n <!-- Named lookup list -->\n @if (meta()?.hasOptions && store.fieldDataSource(f) === 'lookup') {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('listName') }}</mat-label>\n <input\n matInput\n data-testid=\"list-name\"\n [ngModel]=\"f.listName ?? ''\"\n (ngModelChange)=\"store.setListName(store.keyOf(f), $event)\"\n />\n </mat-form-field>\n <p class=\"deb-hint\">\n {{ ui.text('listNameHint') }}\n </p>\n }\n\n <!-- Inline options -->\n @if (meta()?.hasOptions && store.fieldDataSource(f) !== 'lookup') {\n <div class=\"deb-row\" style=\"justify-content: space-between\">\n <span class=\"deb-section-title\">{{ ui.text('options') }}</span>\n <button mat-stroked-button type=\"button\" (click)=\"store.addOption(store.keyOf(f))\">\n <mat-icon>add</mat-icon> {{ ui.text('option') }}\n </button>\n </div>\n @for (opt of f.options ?? []; track $index) {\n <div class=\"deb-option-row\" data-testid=\"option-row\">\n <!-- One input: the displayed text is the stored value. -->\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('optionInLanguage', { language: lang() }) }}</mat-label>\n <input\n matInput\n [attr.data-testid]=\"'option-' + $index\"\n [ngModel]=\"optionLabel(opt)\"\n (ngModelChange)=\"store.setOptionLabel(store.keyOf(f), $index, lang(), $event)\"\n />\n </mat-form-field>\n <button\n mat-icon-button\n type=\"button\"\n color=\"warn\"\n (click)=\"store.removeOption(store.keyOf(f), $index)\"\n [matTooltip]=\"ui.text('removeOption')\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n @if (!(f.options ?? []).length) {\n <p class=\"deb-hint\">{{ ui.text('noOptionsYet') }}</p>\n }\n }\n\n <mat-divider></mat-divider>\n\n <!-- Flags -->\n <span class=\"deb-section-title\">{{ ui.text('display') }}</span>\n <div class=\"deb-toggles\">\n <mat-slide-toggle\n [ngModel]=\"f.visibility ?? true\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { visibility: $event })\"\n >{{ ui.text('visible') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [ngModel]=\"f.table?.visible ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { table: { visible: $event } })\"\n >{{ ui.text('tableColumn') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [ngModel]=\"f.readonly ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { readonly: $event })\"\n >{{ ui.text('readOnly') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [ngModel]=\"f.disabled ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { disabled: $event })\"\n >{{ ui.text('disabled') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [ngModel]=\"f.maskData ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { maskData: $event })\"\n >{{ ui.text('maskData') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n data-testid=\"toggle-critical\"\n [ngModel]=\"f.criticalField ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { criticalField: $event })\"\n [matTooltip]=\"ui.text('criticalFieldTooltip')\"\n >{{ ui.text('criticalField') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [ngModel]=\"f.showOnMinimize ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { showOnMinimize: $event })\"\n [matTooltip]=\"ui.text('showInSummaryTooltip')\"\n >{{ ui.text('showInSummary') }}</mat-slide-toggle\n >\n </div>\n\n <mat-divider></mat-divider>\n\n <!-- Conditional visibility (static showWhen) -->\n <div class=\"deb-row deb-row--split\">\n <span class=\"deb-section-title\">{{ ui.text('showWhen') }}</span>\n <button mat-stroked-button type=\"button\" data-testid=\"add-show-when\" (click)=\"addShowWhen(f)\">\n <mat-icon>add</mat-icon> {{ ui.text('condition') }}\n </button>\n </div>\n @for (entry of showWhenEntries(f); track entry.key) {\n <div class=\"deb-option-row\" data-testid=\"option-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ ui.text('watchedField') }}</mat-label>\n <mat-select\n data-testid=\"show-when-field\"\n [ngModel]=\"entry.key\"\n (ngModelChange)=\"renameShowWhen(f, entry.key, $event)\"\n >\n @for (option of optionsWith(entry.key); track option.value) {\n <mat-option [value]=\"option.value\">\n {{ option.label }} <span class=\"deb-path-hint\">{{ option.path }}</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ ui.text('equals') }}</mat-label>\n <input matInput [ngModel]=\"entry.display\" (ngModelChange)=\"setShowWhenValue(f, entry.key, $event)\" />\n </mat-form-field>\n <button\n mat-icon-button\n type=\"button\"\n color=\"warn\"\n [matTooltip]=\"ui.text('removeCondition')\"\n (click)=\"removeShowWhen(f, entry.key)\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n @if (!showWhenEntries(f).length) {\n <p class=\"deb-hint\">{{ ui.text('alwaysVisibleHint') }}</p>\n }\n\n <!-- patchOnTrue (boolean/checkbox only) -->\n @if (f.type === 'boolean' || f.type === 'checkbox') {\n <mat-divider></mat-divider>\n <div class=\"deb-row deb-row--split\">\n <span class=\"deb-section-title\">{{ ui.text('copyOnTrue') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n data-testid=\"add-patch-on-true\"\n (click)=\"store.addPatchOnTrueMapping(store.keyOf(f))\"\n >\n <mat-icon>add</mat-icon> {{ ui.text('mapping') }}\n </button>\n </div>\n @for (mapping of f.patchOnTrue ?? []; track $index) {\n <div class=\"deb-option-row\" data-testid=\"option-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ ui.text('fromField') }}</mat-label>\n <mat-select\n data-testid=\"patch-on-true-from\"\n [ngModel]=\"mapping.from\"\n (ngModelChange)=\"store.updatePatchOnTrueMapping(store.keyOf(f), $index, { from: $event })\"\n >\n @for (option of optionsWith(mapping.from); track option.value) {\n <mat-option [value]=\"option.value\">\n {{ option.label }} <span class=\"deb-path-hint\">{{ option.path }}</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ ui.text('toField') }}</mat-label>\n <mat-select\n data-testid=\"patch-on-true-to\"\n [ngModel]=\"mapping.to\"\n (ngModelChange)=\"store.updatePatchOnTrueMapping(store.keyOf(f), $index, { to: $event })\"\n >\n @for (option of optionsWith(mapping.to); track option.value) {\n <mat-option [value]=\"option.value\">\n {{ option.label }} <span class=\"deb-path-hint\">{{ option.path }}</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <button\n mat-icon-button\n type=\"button\"\n color=\"warn\"\n [matTooltip]=\"ui.text('removeMapping')\"\n (click)=\"store.removePatchOnTrueMapping(store.keyOf(f), $index)\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n @if (!(f.patchOnTrue ?? []).length) {\n <p class=\"deb-hint\">{{ ui.text('copyOnTrueHint') }}</p>\n }\n }\n\n <mat-divider></mat-divider>\n\n <!-- Rules attached to this field -->\n <ngx-field-rules-list />\n\n <mat-divider></mat-divider>\n\n <!-- Cross-entity field reference & drift management (Phase 8) -->\n <ngx-referenced-field-config />\n </div>\n} @else {\n <div class=\"deb-inspector__empty\">\n <mat-icon>touch_app</mat-icon>\n <p>{{ ui.text('inspectorEmpty') }}</p>\n </div>\n}\n", styles: [".deb-inspector{display:flex;flex-direction:column;gap:12px}.deb-inspector__empty{color:var(--deb-muted, #6b7280);text-align:center;padding:24px 8px}.deb-full{width:100%}.deb-row{display:flex;gap:8px;align-items:center}.deb-row>*{flex:1}.deb-row--split{justify-content:space-between}.deb-row--split>*{flex:0 0 auto}.deb-toggles{display:grid;grid-template-columns:1fr 1fr;gap:8px 12px}.deb-option-row{display:flex;gap:6px;align-items:center}.deb-chip{display:inline-block;padding:2px 8px;border-radius:12px;background:var(--deb-accent-soft, #eef2ff);color:var(--deb-accent, #6366f1);font-size:12px;font-weight:600}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: i3$1.MatCheckbox, selector: "mat-checkbox", inputs: ["aria-label", "aria-labelledby", "aria-describedby", "aria-expanded", "aria-controls", "aria-owns", "id", "required", "labelPosition", "name", "value", "disableRipple", "tabIndex", "color", "disabledInteractive", "checked", "disabled", "indeterminate"], outputs: ["change", "indeterminateChange"], exportAs: ["matCheckbox"] }, { kind: "ngmodule", type: MatDividerModule }, { kind: "component", type: i3.MatDivider, selector: "mat-divider", inputs: ["vertical", "inset"] }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "directive", type: i4.MatHint, selector: "mat-hint", inputs: ["align", "id"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i9.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: EntityReferenceConfigComponent, selector: "ngx-entity-reference-config" }, { kind: "component", type: FieldRulesListComponent, selector: "ngx-field-rules-list" }, { kind: "component", type: ReferencedFieldConfigComponent, selector: "ngx-referenced-field-config" }] });
|
|
2818
3024
|
}
|
|
2819
3025
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FieldInspectorComponent, decorators: [{
|
|
2820
3026
|
type: Component,
|
|
@@ -2832,7 +3038,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2832
3038
|
EntityReferenceConfigComponent,
|
|
2833
3039
|
FieldRulesListComponent,
|
|
2834
3040
|
ReferencedFieldConfigComponent,
|
|
2835
|
-
], template: "@if (field(); as f) {\n <div class=\"deb-inspector\">\n <!-- Identity -->\n <div class=\"deb-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>Field id</mat-label>\n <input matInput data-testid=\"field-id\" [value]=\"f.id\" disabled />\n @if (store.hasManualId(f.id)) {\n <mat-hint>The stored data key for this saved field \u2014 fixed.</mat-hint>\n } @else {\n <mat-hint>Derived from the label. Used as the data key.</mat-hint>\n }\n </mat-form-field>\n <span class=\"deb-chip\">{{ meta()?.label || f.type }}</span>\n </div>\n\n @if (tabOptions().length > 1) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Tab</mat-label>\n <mat-select data-testid=\"field-tab\"\n [ngModel]=\"currentTabId(f)\" (ngModelChange)=\"moveToTab(f, $event)\">\n @for (tab of tabOptions(); track tab.id) {\n <mat-option [value]=\"tab.id\">{{ tab.label }}</mat-option>\n }\n </mat-select>\n <mat-hint>Moving the field rewrites its path and repoints the rules that named it.</mat-hint>\n </mat-form-field>\n }\n\n\n <!-- Localised text (active language) -->\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Label ({{ lang() }})</mat-label>\n <input\n matInput\n data-testid=\"field-label\"\n [ngModel]=\"labelValue(f)\"\n (ngModelChange)=\"store.setFieldLabel(store.keyOf(f), lang(), $event)\"\n />\n </mat-form-field>\n\n @if (meta()?.supportsPlaceholder) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Placeholder ({{ lang() }})</mat-label>\n <input\n matInput\n [ngModel]=\"placeholderValue(f)\"\n (ngModelChange)=\"store.setFieldPlaceholder(store.keyOf(f), lang(), $event)\"\n />\n </mat-form-field>\n }\n\n @if (meta()?.supportsDefaultValue) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Default value</mat-label>\n <input\n matInput\n [ngModel]=\"f.defaultValue ?? ''\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { defaultValue: $event })\"\n />\n </mat-form-field>\n }\n\n <!-- Entity reference, cascade, and autoPatch -->\n @if (meta()?.isEntityRef) {\n <mat-divider></mat-divider>\n <ngx-entity-reference-config />\n }\n\n <mat-divider></mat-divider>\n\n <!-- Validators -->\n @if (meta(); as m) {\n @if (m.flagValidators.length || m.paramValidators.length) {\n <span class=\"deb-section-title\">Validation</span>\n <div class=\"deb-toggles\">\n @for (v of m.flagValidators; track v) {\n <mat-checkbox\n [ngModel]=\"store.hasFlagValidator(f, v)\"\n (ngModelChange)=\"store.toggleFlagValidator(store.keyOf(f), v, $event)\"\n >{{ v }}</mat-checkbox>\n }\n </div>\n @for (v of m.paramValidators; track v) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ v }}</mat-label>\n <input\n matInput\n type=\"number\"\n [ngModel]=\"store.getParamValidator(f, v)\"\n (ngModelChange)=\"store.setParamValidator(store.keyOf(f), v, toNum($event))\"\n />\n </mat-form-field>\n }\n }\n }\n\n <!-- Options (dropdown / radio / multiSelect) -->\n @if (meta()?.hasOptions) {\n <mat-divider></mat-divider>\n <span class=\"deb-section-title\">Data source</span>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Options come from</mat-label>\n <mat-select\n data-testid=\"data-source\"\n [ngModel]=\"store.fieldDataSource(f)\"\n (ngModelChange)=\"store.setFieldDataSource(store.keyOf(f), $event)\"\n >\n <mat-option value=\"none\">Nothing yet</mat-option>\n <mat-option value=\"manual\">Options authored here</mat-option>\n <mat-option value=\"lookup\">A named list</mat-option>\n </mat-select>\n </mat-form-field>\n }\n\n <!-- Named lookup list -->\n @if (meta()?.hasOptions && store.fieldDataSource(f) === 'lookup') {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>List name</mat-label>\n <input\n matInput\n data-testid=\"list-name\"\n [ngModel]=\"f.listName ?? ''\"\n (ngModelChange)=\"store.setListName(store.keyOf(f), $event)\"\n />\n </mat-form-field>\n <p class=\"deb-hint\">\n The consuming app resolves this name through its lookup registry. Options are not\n authored here.\n </p>\n }\n\n <!-- Inline options -->\n @if (meta()?.hasOptions && store.fieldDataSource(f) !== 'lookup') {\n <div class=\"deb-row\" style=\"justify-content: space-between;\">\n <span class=\"deb-section-title\">Options</span>\n <button mat-stroked-button type=\"button\" (click)=\"store.addOption(store.keyOf(f))\">\n <mat-icon>add</mat-icon> Option\n </button>\n </div>\n @for (opt of f.options ?? []; track $index) {\n <div class=\"deb-option-row\" data-testid=\"option-row\">\n <!-- One input: the displayed text is the stored value. -->\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Option ({{ lang() }})</mat-label>\n <input\n matInput\n [attr.data-testid]=\"'option-' + $index\"\n [ngModel]=\"optionLabel(opt)\"\n (ngModelChange)=\"store.setOptionLabel(store.keyOf(f), $index, lang(), $event)\"\n />\n </mat-form-field>\n <button mat-icon-button type=\"button\" color=\"warn\"\n (click)=\"store.removeOption(store.keyOf(f), $index)\" matTooltip=\"Remove option\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n @if (!(f.options ?? []).length) {\n <p class=\"deb-hint\">No options yet \u2014 add at least one.</p>\n }\n }\n\n <mat-divider></mat-divider>\n\n <!-- Flags -->\n <span class=\"deb-section-title\">Display</span>\n <div class=\"deb-toggles\">\n <mat-slide-toggle\n [ngModel]=\"f.visibility ?? true\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { visibility: $event })\"\n >Visible</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.table?.visible ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { table: { visible: $event } })\"\n >Table column</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.readonly ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { readonly: $event })\"\n >Read-only</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.disabled ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { disabled: $event })\"\n >Disabled</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.maskData ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { maskData: $event })\"\n >Mask data (RBAC)</mat-slide-toggle>\n <mat-slide-toggle\n data-testid=\"toggle-critical\"\n [ngModel]=\"f.criticalField ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { criticalField: $event })\"\n matTooltip=\"Renders locked; editing it announces a change against the session baseline.\"\n >Critical field</mat-slide-toggle>\n <mat-slide-toggle\n [ngModel]=\"f.showOnMinimize ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { showOnMinimize: $event })\"\n matTooltip=\"Shown in the record editor's summary panel.\"\n >Show in summary</mat-slide-toggle>\n </div>\n\n <mat-divider></mat-divider>\n\n <!-- Conditional visibility (static showWhen) -->\n <div class=\"deb-row deb-row--split\">\n <span class=\"deb-section-title\">Show when</span>\n <button mat-stroked-button type=\"button\" data-testid=\"add-show-when\" (click)=\"addShowWhen(f)\">\n <mat-icon>add</mat-icon> Condition\n </button>\n </div>\n @for (entry of showWhenEntries(f); track entry.key) {\n <div class=\"deb-option-row\" data-testid=\"option-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>Watched field</mat-label>\n <mat-select data-testid=\"show-when-field\"\n [ngModel]=\"entry.key\" (ngModelChange)=\"renameShowWhen(f, entry.key, $event)\">\n @for (option of optionsWith(entry.key); track option.value) {\n <mat-option [value]=\"option.value\">\n {{ option.label }} <span class=\"deb-path-hint\">{{ option.path }}</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>Equals</mat-label>\n <input matInput [ngModel]=\"entry.display\" (ngModelChange)=\"setShowWhenValue(f, entry.key, $event)\" />\n </mat-form-field>\n <button mat-icon-button type=\"button\" color=\"warn\" matTooltip=\"Remove condition\"\n (click)=\"removeShowWhen(f, entry.key)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n @if (!showWhenEntries(f).length) {\n <p class=\"deb-hint\">Always visible. Add a condition to show this field only for certain values.</p>\n }\n\n <!-- patchOnTrue (boolean/checkbox only) -->\n @if (f.type === 'boolean' || f.type === 'checkbox') {\n <mat-divider></mat-divider>\n <div class=\"deb-row deb-row--split\">\n <span class=\"deb-section-title\">Copy on true</span>\n <button mat-stroked-button type=\"button\" data-testid=\"add-patch-on-true\"\n (click)=\"store.addPatchOnTrueMapping(store.keyOf(f))\">\n <mat-icon>add</mat-icon> Mapping\n </button>\n </div>\n @for (mapping of f.patchOnTrue ?? []; track $index) {\n <div class=\"deb-option-row\" data-testid=\"option-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>From field</mat-label>\n <mat-select data-testid=\"patch-on-true-from\"\n [ngModel]=\"mapping.from\"\n (ngModelChange)=\"store.updatePatchOnTrueMapping(store.keyOf(f), $index, { from: $event })\">\n @for (option of optionsWith(mapping.from); track option.value) {\n <mat-option [value]=\"option.value\">\n {{ option.label }} <span class=\"deb-path-hint\">{{ option.path }}</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>To field</mat-label>\n <mat-select data-testid=\"patch-on-true-to\"\n [ngModel]=\"mapping.to\"\n (ngModelChange)=\"store.updatePatchOnTrueMapping(store.keyOf(f), $index, { to: $event })\">\n @for (option of optionsWith(mapping.to); track option.value) {\n <mat-option [value]=\"option.value\">\n {{ option.label }} <span class=\"deb-path-hint\">{{ option.path }}</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <button mat-icon-button type=\"button\" color=\"warn\" matTooltip=\"Remove mapping\"\n (click)=\"store.removePatchOnTrueMapping(store.keyOf(f), $index)\">\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n @if (!(f.patchOnTrue ?? []).length) {\n <p class=\"deb-hint\">When this flips to true, copy one field's value into another.</p>\n }\n }\n\n <mat-divider></mat-divider>\n\n <!-- Rules attached to this field -->\n <ngx-field-rules-list />\n\n <mat-divider></mat-divider>\n\n <!-- Cross-entity field reference & drift management (Phase 8) -->\n <ngx-referenced-field-config />\n </div>\n} @else {\n <div class=\"deb-inspector__empty\">\n <mat-icon>touch_app</mat-icon>\n <p>Select a field to edit its properties.</p>\n </div>\n}\n", styles: [".deb-inspector{display:flex;flex-direction:column;gap:12px}.deb-inspector__empty{color:var(--deb-muted, #6b7280);text-align:center;padding:24px 8px}.deb-full{width:100%}.deb-row{display:flex;gap:8px;align-items:center}.deb-row>*{flex:1}.deb-row--split{justify-content:space-between}.deb-row--split>*{flex:0 0 auto}.deb-toggles{display:grid;grid-template-columns:1fr 1fr;gap:8px 12px}.deb-option-row{display:flex;gap:6px;align-items:center}.deb-chip{display:inline-block;padding:2px 8px;border-radius:12px;background:var(--deb-accent-soft, #eef2ff);color:var(--deb-accent, #6366f1);font-size:12px;font-weight:600}\n"] }]
|
|
3041
|
+
], template: "@if (field(); as f) {\n <div class=\"deb-inspector\">\n <!-- Identity -->\n <div class=\"deb-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ ui.text('fieldId') }}</mat-label>\n <input matInput data-testid=\"field-id\" [value]=\"f.id\" disabled />\n @if (store.hasManualId(f.id)) {\n <mat-hint>{{ ui.text('fieldIdFixedHint') }}</mat-hint>\n } @else {\n <mat-hint>{{ ui.text('fieldIdDerivedHint') }}</mat-hint>\n }\n </mat-form-field>\n <span class=\"deb-chip\">{{ meta()?.label || f.type }}</span>\n </div>\n\n @if (tabOptions().length > 1) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('tab') }}</mat-label>\n <mat-select data-testid=\"field-tab\" [ngModel]=\"currentTabId(f)\" (ngModelChange)=\"moveToTab(f, $event)\">\n @for (tab of tabOptions(); track tab.id) {\n <mat-option [value]=\"tab.id\">{{ tab.label }}</mat-option>\n }\n </mat-select>\n <mat-hint>{{ ui.text('tabMoveHint') }}</mat-hint>\n </mat-form-field>\n }\n\n <!-- Localised text (active language) -->\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('labelInLanguage', { language: lang() }) }}</mat-label>\n <input\n matInput\n data-testid=\"field-label\"\n [ngModel]=\"labelValue(f)\"\n (ngModelChange)=\"store.setFieldLabel(store.keyOf(f), lang(), $event)\"\n />\n </mat-form-field>\n\n @if (meta()?.supportsPlaceholder) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('placeholderInLanguage', { language: lang() }) }}</mat-label>\n <input\n matInput\n [ngModel]=\"placeholderValue(f)\"\n (ngModelChange)=\"store.setFieldPlaceholder(store.keyOf(f), lang(), $event)\"\n />\n </mat-form-field>\n }\n\n @if (meta()?.supportsDefaultValue) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('defaultValue') }}</mat-label>\n <input\n matInput\n [ngModel]=\"f.defaultValue ?? ''\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { defaultValue: $event })\"\n />\n </mat-form-field>\n }\n\n <!-- Entity reference, cascade, and autoPatch -->\n @if (meta()?.isEntityRef) {\n <mat-divider></mat-divider>\n <ngx-entity-reference-config />\n }\n\n <mat-divider></mat-divider>\n\n <!-- Validators -->\n @if (meta(); as m) {\n @if (m.flagValidators.length || m.paramValidators.length) {\n <span class=\"deb-section-title\">{{ ui.text('validation') }}</span>\n <div class=\"deb-toggles\">\n @for (v of m.flagValidators; track v) {\n <mat-checkbox\n [ngModel]=\"store.hasFlagValidator(f, v)\"\n (ngModelChange)=\"store.toggleFlagValidator(store.keyOf(f), v, $event)\"\n >{{ v }}</mat-checkbox\n >\n }\n </div>\n @for (v of m.paramValidators; track v) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ v }}</mat-label>\n <input\n matInput\n type=\"number\"\n [ngModel]=\"store.getParamValidator(f, v)\"\n (ngModelChange)=\"store.setParamValidator(store.keyOf(f), v, toNum($event))\"\n />\n </mat-form-field>\n }\n }\n }\n\n <!-- Options (dropdown / radio / multiSelect) -->\n @if (meta()?.hasOptions) {\n <mat-divider></mat-divider>\n <span class=\"deb-section-title\">{{ ui.text('dataSource') }}</span>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('optionsComeFrom') }}</mat-label>\n <mat-select\n data-testid=\"data-source\"\n [ngModel]=\"store.fieldDataSource(f)\"\n (ngModelChange)=\"store.setFieldDataSource(store.keyOf(f), $event)\"\n >\n <mat-option value=\"none\">{{ ui.text('optionsSourceNone') }}</mat-option>\n <mat-option value=\"manual\">{{ ui.text('optionsSourceInline') }}</mat-option>\n <mat-option value=\"lookup\">{{ ui.text('optionsSourceList') }}</mat-option>\n </mat-select>\n </mat-form-field>\n }\n\n <!-- Named lookup list -->\n @if (meta()?.hasOptions && store.fieldDataSource(f) === 'lookup') {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('listName') }}</mat-label>\n <input\n matInput\n data-testid=\"list-name\"\n [ngModel]=\"f.listName ?? ''\"\n (ngModelChange)=\"store.setListName(store.keyOf(f), $event)\"\n />\n </mat-form-field>\n <p class=\"deb-hint\">\n {{ ui.text('listNameHint') }}\n </p>\n }\n\n <!-- Inline options -->\n @if (meta()?.hasOptions && store.fieldDataSource(f) !== 'lookup') {\n <div class=\"deb-row\" style=\"justify-content: space-between\">\n <span class=\"deb-section-title\">{{ ui.text('options') }}</span>\n <button mat-stroked-button type=\"button\" (click)=\"store.addOption(store.keyOf(f))\">\n <mat-icon>add</mat-icon> {{ ui.text('option') }}\n </button>\n </div>\n @for (opt of f.options ?? []; track $index) {\n <div class=\"deb-option-row\" data-testid=\"option-row\">\n <!-- One input: the displayed text is the stored value. -->\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('optionInLanguage', { language: lang() }) }}</mat-label>\n <input\n matInput\n [attr.data-testid]=\"'option-' + $index\"\n [ngModel]=\"optionLabel(opt)\"\n (ngModelChange)=\"store.setOptionLabel(store.keyOf(f), $index, lang(), $event)\"\n />\n </mat-form-field>\n <button\n mat-icon-button\n type=\"button\"\n color=\"warn\"\n (click)=\"store.removeOption(store.keyOf(f), $index)\"\n [matTooltip]=\"ui.text('removeOption')\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n @if (!(f.options ?? []).length) {\n <p class=\"deb-hint\">{{ ui.text('noOptionsYet') }}</p>\n }\n }\n\n <mat-divider></mat-divider>\n\n <!-- Flags -->\n <span class=\"deb-section-title\">{{ ui.text('display') }}</span>\n <div class=\"deb-toggles\">\n <mat-slide-toggle\n [ngModel]=\"f.visibility ?? true\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { visibility: $event })\"\n >{{ ui.text('visible') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [ngModel]=\"f.table?.visible ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { table: { visible: $event } })\"\n >{{ ui.text('tableColumn') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [ngModel]=\"f.readonly ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { readonly: $event })\"\n >{{ ui.text('readOnly') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [ngModel]=\"f.disabled ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { disabled: $event })\"\n >{{ ui.text('disabled') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [ngModel]=\"f.maskData ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { maskData: $event })\"\n >{{ ui.text('maskData') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n data-testid=\"toggle-critical\"\n [ngModel]=\"f.criticalField ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { criticalField: $event })\"\n [matTooltip]=\"ui.text('criticalFieldTooltip')\"\n >{{ ui.text('criticalField') }}</mat-slide-toggle\n >\n <mat-slide-toggle\n [ngModel]=\"f.showOnMinimize ?? false\"\n (ngModelChange)=\"store.updateField(store.keyOf(f), { showOnMinimize: $event })\"\n [matTooltip]=\"ui.text('showInSummaryTooltip')\"\n >{{ ui.text('showInSummary') }}</mat-slide-toggle\n >\n </div>\n\n <mat-divider></mat-divider>\n\n <!-- Conditional visibility (static showWhen) -->\n <div class=\"deb-row deb-row--split\">\n <span class=\"deb-section-title\">{{ ui.text('showWhen') }}</span>\n <button mat-stroked-button type=\"button\" data-testid=\"add-show-when\" (click)=\"addShowWhen(f)\">\n <mat-icon>add</mat-icon> {{ ui.text('condition') }}\n </button>\n </div>\n @for (entry of showWhenEntries(f); track entry.key) {\n <div class=\"deb-option-row\" data-testid=\"option-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ ui.text('watchedField') }}</mat-label>\n <mat-select\n data-testid=\"show-when-field\"\n [ngModel]=\"entry.key\"\n (ngModelChange)=\"renameShowWhen(f, entry.key, $event)\"\n >\n @for (option of optionsWith(entry.key); track option.value) {\n <mat-option [value]=\"option.value\">\n {{ option.label }} <span class=\"deb-path-hint\">{{ option.path }}</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ ui.text('equals') }}</mat-label>\n <input matInput [ngModel]=\"entry.display\" (ngModelChange)=\"setShowWhenValue(f, entry.key, $event)\" />\n </mat-form-field>\n <button\n mat-icon-button\n type=\"button\"\n color=\"warn\"\n [matTooltip]=\"ui.text('removeCondition')\"\n (click)=\"removeShowWhen(f, entry.key)\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n @if (!showWhenEntries(f).length) {\n <p class=\"deb-hint\">{{ ui.text('alwaysVisibleHint') }}</p>\n }\n\n <!-- patchOnTrue (boolean/checkbox only) -->\n @if (f.type === 'boolean' || f.type === 'checkbox') {\n <mat-divider></mat-divider>\n <div class=\"deb-row deb-row--split\">\n <span class=\"deb-section-title\">{{ ui.text('copyOnTrue') }}</span>\n <button\n mat-stroked-button\n type=\"button\"\n data-testid=\"add-patch-on-true\"\n (click)=\"store.addPatchOnTrueMapping(store.keyOf(f))\"\n >\n <mat-icon>add</mat-icon> {{ ui.text('mapping') }}\n </button>\n </div>\n @for (mapping of f.patchOnTrue ?? []; track $index) {\n <div class=\"deb-option-row\" data-testid=\"option-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ ui.text('fromField') }}</mat-label>\n <mat-select\n data-testid=\"patch-on-true-from\"\n [ngModel]=\"mapping.from\"\n (ngModelChange)=\"store.updatePatchOnTrueMapping(store.keyOf(f), $index, { from: $event })\"\n >\n @for (option of optionsWith(mapping.from); track option.value) {\n <mat-option [value]=\"option.value\">\n {{ option.label }} <span class=\"deb-path-hint\">{{ option.path }}</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\">\n <mat-label>{{ ui.text('toField') }}</mat-label>\n <mat-select\n data-testid=\"patch-on-true-to\"\n [ngModel]=\"mapping.to\"\n (ngModelChange)=\"store.updatePatchOnTrueMapping(store.keyOf(f), $index, { to: $event })\"\n >\n @for (option of optionsWith(mapping.to); track option.value) {\n <mat-option [value]=\"option.value\">\n {{ option.label }} <span class=\"deb-path-hint\">{{ option.path }}</span>\n </mat-option>\n }\n </mat-select>\n </mat-form-field>\n <button\n mat-icon-button\n type=\"button\"\n color=\"warn\"\n [matTooltip]=\"ui.text('removeMapping')\"\n (click)=\"store.removePatchOnTrueMapping(store.keyOf(f), $index)\"\n >\n <mat-icon>delete</mat-icon>\n </button>\n </div>\n }\n @if (!(f.patchOnTrue ?? []).length) {\n <p class=\"deb-hint\">{{ ui.text('copyOnTrueHint') }}</p>\n }\n }\n\n <mat-divider></mat-divider>\n\n <!-- Rules attached to this field -->\n <ngx-field-rules-list />\n\n <mat-divider></mat-divider>\n\n <!-- Cross-entity field reference & drift management (Phase 8) -->\n <ngx-referenced-field-config />\n </div>\n} @else {\n <div class=\"deb-inspector__empty\">\n <mat-icon>touch_app</mat-icon>\n <p>{{ ui.text('inspectorEmpty') }}</p>\n </div>\n}\n", styles: [".deb-inspector{display:flex;flex-direction:column;gap:12px}.deb-inspector__empty{color:var(--deb-muted, #6b7280);text-align:center;padding:24px 8px}.deb-full{width:100%}.deb-row{display:flex;gap:8px;align-items:center}.deb-row>*{flex:1}.deb-row--split{justify-content:space-between}.deb-row--split>*{flex:0 0 auto}.deb-toggles{display:grid;grid-template-columns:1fr 1fr;gap:8px 12px}.deb-option-row{display:flex;gap:6px;align-items:center}.deb-chip{display:inline-block;padding:2px 8px;border-radius:12px;background:var(--deb-accent-soft, #eef2ff);color:var(--deb-accent, #6366f1);font-size:12px;font-weight:600}\n"] }]
|
|
2836
3042
|
}] });
|
|
2837
3043
|
|
|
2838
3044
|
/**
|
|
@@ -2887,8 +3093,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2887
3093
|
* TabManagerComponent — add / rename / reorder / remove tabs & sub-tabs with systemDefault protection.
|
|
2888
3094
|
*/
|
|
2889
3095
|
class TabManagerComponent {
|
|
3096
|
+
/** Builder chrome, overridable via BUILDER_TEXT. */
|
|
3097
|
+
ui = inject(BuilderTextService);
|
|
2890
3098
|
store = inject(BuilderStore);
|
|
2891
|
-
canEditSystemDefaultsFn = inject(SYSTEM_DEFAULT_CAN_EDIT, {
|
|
3099
|
+
canEditSystemDefaultsFn = inject(SYSTEM_DEFAULT_CAN_EDIT, {
|
|
3100
|
+
optional: true,
|
|
3101
|
+
});
|
|
2892
3102
|
lang() {
|
|
2893
3103
|
return this.store.activeLanguage();
|
|
2894
3104
|
}
|
|
@@ -2910,21 +3120,21 @@ class TabManagerComponent {
|
|
|
2910
3120
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: TabManagerComponent, isStandalone: true, selector: "ngx-tab-manager", ngImport: i0, template: `
|
|
2911
3121
|
<div class="deb-tabs">
|
|
2912
3122
|
<div class="deb-tabs__head">
|
|
2913
|
-
<span class="deb-section-title">
|
|
3123
|
+
<span class="deb-section-title">{{ ui.text('tabsManager') }}</span>
|
|
2914
3124
|
<button mat-stroked-button type="button" (click)="store.addTab()">
|
|
2915
|
-
<mat-icon>add</mat-icon>
|
|
3125
|
+
<mat-icon>add</mat-icon> {{ ui.text('addTab') }}
|
|
2916
3126
|
</button>
|
|
2917
3127
|
</div>
|
|
2918
3128
|
|
|
2919
3129
|
@if (store.tabs().length === 0) {
|
|
2920
|
-
<p class="deb-hint">
|
|
3130
|
+
<p class="deb-hint">{{ ui.text('noTabs') }}</p>
|
|
2921
3131
|
}
|
|
2922
3132
|
|
|
2923
3133
|
@for (tab of store.tabs(); track tab.id; let i = $index, count = $count) {
|
|
2924
3134
|
<div class="deb-tab-card" [class.deb-tab-card--system]="tab.systemDefault">
|
|
2925
3135
|
<div class="deb-tabs__row" [attr.data-testid]="'tab-row-' + tab.id">
|
|
2926
3136
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-grow">
|
|
2927
|
-
<mat-label>{{ tab.id }} {{ tab.systemDefault ? '
|
|
3137
|
+
<mat-label>{{ tab.id }} {{ tab.systemDefault ? ui.text('systemTag') : '' }}</mat-label>
|
|
2928
3138
|
<input
|
|
2929
3139
|
matInput
|
|
2930
3140
|
[disabled]="!canEditTab(tab)"
|
|
@@ -2937,7 +3147,8 @@ class TabManagerComponent {
|
|
|
2937
3147
|
type="button"
|
|
2938
3148
|
[disabled]="i === 0 || !canEditTab(tab)"
|
|
2939
3149
|
(click)="store.moveTab(tab.id, -1)"
|
|
2940
|
-
matTooltip="
|
|
3150
|
+
[matTooltip]="ui.text('moveUp')"
|
|
3151
|
+
[attr.aria-label]="ui.text('moveTabUp')"
|
|
2941
3152
|
>
|
|
2942
3153
|
<mat-icon>arrow_upward</mat-icon>
|
|
2943
3154
|
</button>
|
|
@@ -2946,7 +3157,8 @@ class TabManagerComponent {
|
|
|
2946
3157
|
type="button"
|
|
2947
3158
|
[disabled]="i === count - 1 || !canEditTab(tab)"
|
|
2948
3159
|
(click)="store.moveTab(tab.id, 1)"
|
|
2949
|
-
matTooltip="
|
|
3160
|
+
[matTooltip]="ui.text('moveDown')"
|
|
3161
|
+
[attr.aria-label]="ui.text('moveTabDown')"
|
|
2950
3162
|
>
|
|
2951
3163
|
<mat-icon>arrow_downward</mat-icon>
|
|
2952
3164
|
</button>
|
|
@@ -2956,7 +3168,8 @@ class TabManagerComponent {
|
|
|
2956
3168
|
color="warn"
|
|
2957
3169
|
[disabled]="!canEditTab(tab)"
|
|
2958
3170
|
(click)="store.removeTab(tab.id)"
|
|
2959
|
-
matTooltip="
|
|
3171
|
+
[matTooltip]="ui.text('removeTab')"
|
|
3172
|
+
[attr.aria-label]="ui.text('removeTab')"
|
|
2960
3173
|
>
|
|
2961
3174
|
<mat-icon>delete</mat-icon>
|
|
2962
3175
|
</button>
|
|
@@ -2969,47 +3182,41 @@ class TabManagerComponent {
|
|
|
2969
3182
|
[ngModel]="tab.flatData"
|
|
2970
3183
|
(ngModelChange)="store.updateTab(tab.id, { flatData: $event })"
|
|
2971
3184
|
>
|
|
2972
|
-
|
|
3185
|
+
{{ ui.text('flatData') }}
|
|
2973
3186
|
</mat-checkbox>
|
|
2974
3187
|
<mat-checkbox
|
|
2975
3188
|
[disabled]="!canEditTab(tab)"
|
|
2976
3189
|
[ngModel]="tab.isPrimaryTab"
|
|
2977
3190
|
(ngModelChange)="$event ? store.setPrimaryTab(tab.id) : store.updateTab(tab.id, { isPrimaryTab: false })"
|
|
2978
3191
|
>
|
|
2979
|
-
|
|
3192
|
+
{{ ui.text('primaryTab') }}
|
|
2980
3193
|
</mat-checkbox>
|
|
2981
3194
|
<mat-checkbox
|
|
2982
3195
|
[disabled]="!canEditTab(tab)"
|
|
2983
3196
|
[ngModel]="tab.maskData"
|
|
2984
3197
|
(ngModelChange)="store.updateTab(tab.id, { maskData: $event })"
|
|
2985
3198
|
>
|
|
2986
|
-
|
|
3199
|
+
{{ ui.text('maskTabData') }}
|
|
2987
3200
|
</mat-checkbox>
|
|
2988
3201
|
<mat-checkbox
|
|
2989
3202
|
[disabled]="!canEditTab(tab)"
|
|
2990
3203
|
[ngModel]="tab.systemDefault"
|
|
2991
3204
|
(ngModelChange)="store.updateTab(tab.id, { systemDefault: $event })"
|
|
2992
3205
|
>
|
|
2993
|
-
|
|
3206
|
+
{{ ui.text('systemDefault') }}
|
|
2994
3207
|
</mat-checkbox>
|
|
2995
|
-
<button
|
|
2996
|
-
|
|
2997
|
-
type="button"
|
|
2998
|
-
color="primary"
|
|
2999
|
-
[disabled]="!canEditTab(tab)"
|
|
3000
|
-
(click)="store.addSubTab(tab.id)"
|
|
3001
|
-
>
|
|
3002
|
-
+ Sub-tab
|
|
3208
|
+
<button mat-button type="button" color="primary" [disabled]="!canEditTab(tab)" (click)="store.addSubTab(tab.id)">
|
|
3209
|
+
{{ ui.text('addSubTab') }}
|
|
3003
3210
|
</button>
|
|
3004
3211
|
</div>
|
|
3005
3212
|
|
|
3006
3213
|
<!-- Module tab configuration -->
|
|
3007
3214
|
<div class="deb-tab-module">
|
|
3008
3215
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-grow">
|
|
3009
|
-
<mat-label>
|
|
3216
|
+
<mat-label>{{ ui.text('consumerModuleName') }}</mat-label>
|
|
3010
3217
|
<input
|
|
3011
3218
|
matInput
|
|
3012
|
-
placeholder="
|
|
3219
|
+
[placeholder]="ui.text('consumerModulePlaceholder')"
|
|
3013
3220
|
[disabled]="!canEditTab(tab)"
|
|
3014
3221
|
[ngModel]="tab.moduleName"
|
|
3015
3222
|
(ngModelChange)="store.updateTab(tab.id, { moduleName: $event || undefined })"
|
|
@@ -3020,7 +3227,7 @@ class TabManagerComponent {
|
|
|
3020
3227
|
<!-- Sub-tabs recursive render (Level 2) -->
|
|
3021
3228
|
@if (tab.children && tab.children.length > 0) {
|
|
3022
3229
|
<div class="deb-subtabs">
|
|
3023
|
-
<span class="deb-subtabs__title">
|
|
3230
|
+
<span class="deb-subtabs__title">{{ ui.text('subTabs') }}</span>
|
|
3024
3231
|
@for (sub of tab.children; track sub.id) {
|
|
3025
3232
|
<div class="deb-tabs__row deb-subtab-row" [attr.data-testid]="'subtab-row-' + sub.id">
|
|
3026
3233
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-grow">
|
|
@@ -3038,7 +3245,8 @@ class TabManagerComponent {
|
|
|
3038
3245
|
color="warn"
|
|
3039
3246
|
[disabled]="!canEditTab(sub)"
|
|
3040
3247
|
(click)="store.removeTab(sub.id)"
|
|
3041
|
-
matTooltip="
|
|
3248
|
+
[matTooltip]="ui.text('removeSubTab')"
|
|
3249
|
+
[attr.aria-label]="ui.text('removeSubTab')"
|
|
3042
3250
|
>
|
|
3043
3251
|
<mat-icon>delete</mat-icon>
|
|
3044
3252
|
</button>
|
|
@@ -3065,21 +3273,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3065
3273
|
], template: `
|
|
3066
3274
|
<div class="deb-tabs">
|
|
3067
3275
|
<div class="deb-tabs__head">
|
|
3068
|
-
<span class="deb-section-title">
|
|
3276
|
+
<span class="deb-section-title">{{ ui.text('tabsManager') }}</span>
|
|
3069
3277
|
<button mat-stroked-button type="button" (click)="store.addTab()">
|
|
3070
|
-
<mat-icon>add</mat-icon>
|
|
3278
|
+
<mat-icon>add</mat-icon> {{ ui.text('addTab') }}
|
|
3071
3279
|
</button>
|
|
3072
3280
|
</div>
|
|
3073
3281
|
|
|
3074
3282
|
@if (store.tabs().length === 0) {
|
|
3075
|
-
<p class="deb-hint">
|
|
3283
|
+
<p class="deb-hint">{{ ui.text('noTabs') }}</p>
|
|
3076
3284
|
}
|
|
3077
3285
|
|
|
3078
3286
|
@for (tab of store.tabs(); track tab.id; let i = $index, count = $count) {
|
|
3079
3287
|
<div class="deb-tab-card" [class.deb-tab-card--system]="tab.systemDefault">
|
|
3080
3288
|
<div class="deb-tabs__row" [attr.data-testid]="'tab-row-' + tab.id">
|
|
3081
3289
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-grow">
|
|
3082
|
-
<mat-label>{{ tab.id }} {{ tab.systemDefault ? '
|
|
3290
|
+
<mat-label>{{ tab.id }} {{ tab.systemDefault ? ui.text('systemTag') : '' }}</mat-label>
|
|
3083
3291
|
<input
|
|
3084
3292
|
matInput
|
|
3085
3293
|
[disabled]="!canEditTab(tab)"
|
|
@@ -3092,7 +3300,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3092
3300
|
type="button"
|
|
3093
3301
|
[disabled]="i === 0 || !canEditTab(tab)"
|
|
3094
3302
|
(click)="store.moveTab(tab.id, -1)"
|
|
3095
|
-
matTooltip="
|
|
3303
|
+
[matTooltip]="ui.text('moveUp')"
|
|
3304
|
+
[attr.aria-label]="ui.text('moveTabUp')"
|
|
3096
3305
|
>
|
|
3097
3306
|
<mat-icon>arrow_upward</mat-icon>
|
|
3098
3307
|
</button>
|
|
@@ -3101,7 +3310,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3101
3310
|
type="button"
|
|
3102
3311
|
[disabled]="i === count - 1 || !canEditTab(tab)"
|
|
3103
3312
|
(click)="store.moveTab(tab.id, 1)"
|
|
3104
|
-
matTooltip="
|
|
3313
|
+
[matTooltip]="ui.text('moveDown')"
|
|
3314
|
+
[attr.aria-label]="ui.text('moveTabDown')"
|
|
3105
3315
|
>
|
|
3106
3316
|
<mat-icon>arrow_downward</mat-icon>
|
|
3107
3317
|
</button>
|
|
@@ -3111,7 +3321,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3111
3321
|
color="warn"
|
|
3112
3322
|
[disabled]="!canEditTab(tab)"
|
|
3113
3323
|
(click)="store.removeTab(tab.id)"
|
|
3114
|
-
matTooltip="
|
|
3324
|
+
[matTooltip]="ui.text('removeTab')"
|
|
3325
|
+
[attr.aria-label]="ui.text('removeTab')"
|
|
3115
3326
|
>
|
|
3116
3327
|
<mat-icon>delete</mat-icon>
|
|
3117
3328
|
</button>
|
|
@@ -3124,47 +3335,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3124
3335
|
[ngModel]="tab.flatData"
|
|
3125
3336
|
(ngModelChange)="store.updateTab(tab.id, { flatData: $event })"
|
|
3126
3337
|
>
|
|
3127
|
-
|
|
3338
|
+
{{ ui.text('flatData') }}
|
|
3128
3339
|
</mat-checkbox>
|
|
3129
3340
|
<mat-checkbox
|
|
3130
3341
|
[disabled]="!canEditTab(tab)"
|
|
3131
3342
|
[ngModel]="tab.isPrimaryTab"
|
|
3132
3343
|
(ngModelChange)="$event ? store.setPrimaryTab(tab.id) : store.updateTab(tab.id, { isPrimaryTab: false })"
|
|
3133
3344
|
>
|
|
3134
|
-
|
|
3345
|
+
{{ ui.text('primaryTab') }}
|
|
3135
3346
|
</mat-checkbox>
|
|
3136
3347
|
<mat-checkbox
|
|
3137
3348
|
[disabled]="!canEditTab(tab)"
|
|
3138
3349
|
[ngModel]="tab.maskData"
|
|
3139
3350
|
(ngModelChange)="store.updateTab(tab.id, { maskData: $event })"
|
|
3140
3351
|
>
|
|
3141
|
-
|
|
3352
|
+
{{ ui.text('maskTabData') }}
|
|
3142
3353
|
</mat-checkbox>
|
|
3143
3354
|
<mat-checkbox
|
|
3144
3355
|
[disabled]="!canEditTab(tab)"
|
|
3145
3356
|
[ngModel]="tab.systemDefault"
|
|
3146
3357
|
(ngModelChange)="store.updateTab(tab.id, { systemDefault: $event })"
|
|
3147
3358
|
>
|
|
3148
|
-
|
|
3359
|
+
{{ ui.text('systemDefault') }}
|
|
3149
3360
|
</mat-checkbox>
|
|
3150
|
-
<button
|
|
3151
|
-
|
|
3152
|
-
type="button"
|
|
3153
|
-
color="primary"
|
|
3154
|
-
[disabled]="!canEditTab(tab)"
|
|
3155
|
-
(click)="store.addSubTab(tab.id)"
|
|
3156
|
-
>
|
|
3157
|
-
+ Sub-tab
|
|
3361
|
+
<button mat-button type="button" color="primary" [disabled]="!canEditTab(tab)" (click)="store.addSubTab(tab.id)">
|
|
3362
|
+
{{ ui.text('addSubTab') }}
|
|
3158
3363
|
</button>
|
|
3159
3364
|
</div>
|
|
3160
3365
|
|
|
3161
3366
|
<!-- Module tab configuration -->
|
|
3162
3367
|
<div class="deb-tab-module">
|
|
3163
3368
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-grow">
|
|
3164
|
-
<mat-label>
|
|
3369
|
+
<mat-label>{{ ui.text('consumerModuleName') }}</mat-label>
|
|
3165
3370
|
<input
|
|
3166
3371
|
matInput
|
|
3167
|
-
placeholder="
|
|
3372
|
+
[placeholder]="ui.text('consumerModulePlaceholder')"
|
|
3168
3373
|
[disabled]="!canEditTab(tab)"
|
|
3169
3374
|
[ngModel]="tab.moduleName"
|
|
3170
3375
|
(ngModelChange)="store.updateTab(tab.id, { moduleName: $event || undefined })"
|
|
@@ -3175,7 +3380,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3175
3380
|
<!-- Sub-tabs recursive render (Level 2) -->
|
|
3176
3381
|
@if (tab.children && tab.children.length > 0) {
|
|
3177
3382
|
<div class="deb-subtabs">
|
|
3178
|
-
<span class="deb-subtabs__title">
|
|
3383
|
+
<span class="deb-subtabs__title">{{ ui.text('subTabs') }}</span>
|
|
3179
3384
|
@for (sub of tab.children; track sub.id) {
|
|
3180
3385
|
<div class="deb-tabs__row deb-subtab-row" [attr.data-testid]="'subtab-row-' + sub.id">
|
|
3181
3386
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-grow">
|
|
@@ -3193,7 +3398,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3193
3398
|
color="warn"
|
|
3194
3399
|
[disabled]="!canEditTab(sub)"
|
|
3195
3400
|
(click)="store.removeTab(sub.id)"
|
|
3196
|
-
matTooltip="
|
|
3401
|
+
[matTooltip]="ui.text('removeSubTab')"
|
|
3402
|
+
[attr.aria-label]="ui.text('removeSubTab')"
|
|
3197
3403
|
>
|
|
3198
3404
|
<mat-icon>delete</mat-icon>
|
|
3199
3405
|
</button>
|
|
@@ -3208,6 +3414,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3208
3414
|
}] });
|
|
3209
3415
|
|
|
3210
3416
|
class EntityBuilderTreeNodeComponent {
|
|
3417
|
+
/** Builder chrome, overridable via BUILDER_TEXT. */
|
|
3418
|
+
ui = inject(BuilderTextService);
|
|
3211
3419
|
store = inject(BuilderStore);
|
|
3212
3420
|
field;
|
|
3213
3421
|
index;
|
|
@@ -3235,58 +3443,89 @@ class EntityBuilderTreeNodeComponent {
|
|
|
3235
3443
|
role="button"
|
|
3236
3444
|
tabindex="0"
|
|
3237
3445
|
[attr.aria-pressed]="store.isSelected(field)"
|
|
3238
|
-
[attr.aria-label]="'
|
|
3446
|
+
[attr.aria-label]="ui.text('selectField', { field: fieldLabel(field) })"
|
|
3239
3447
|
>
|
|
3240
|
-
<mat-icon class="deb-drag-handle" cdkDragHandle aria-hidden="true" title="
|
|
3448
|
+
<mat-icon class="deb-drag-handle" cdkDragHandle aria-hidden="true" [title]="ui.text('dragToReorder')"
|
|
3449
|
+
>drag_indicator</mat-icon
|
|
3450
|
+
>
|
|
3241
3451
|
<mat-icon class="deb-field-type" [matTooltip]="fieldTypeLabel(field.type)">{{ fieldTypeIcon(field.type) }}</mat-icon>
|
|
3242
3452
|
<div class="deb-field-meta">
|
|
3243
3453
|
<div class="deb-row" style="gap:6px">
|
|
3244
3454
|
<span class="deb-field-label" [attr.data-testid]="'row-label-' + field.id">{{ fieldLabel(field) }}</span>
|
|
3245
3455
|
<span class="deb-type-badge">{{ field.type }}</span>
|
|
3246
3456
|
@if (field.isReferenced) {
|
|
3247
|
-
<mat-icon
|
|
3457
|
+
<mat-icon
|
|
3458
|
+
style="font-size: 16px; width: 16px; height: 16px; color: #1976d2;"
|
|
3459
|
+
[matTooltip]="ui.text('referencedFieldTooltip', { entity: field.referencedEntityKey ?? '' })"
|
|
3460
|
+
>link</mat-icon
|
|
3461
|
+
>
|
|
3248
3462
|
}
|
|
3249
3463
|
@if (field.hasDrift) {
|
|
3250
|
-
<mat-icon
|
|
3464
|
+
<mat-icon
|
|
3465
|
+
style="font-size: 16px; width: 16px; height: 16px; color: #d32f2f;"
|
|
3466
|
+
data-testid="drift-warning-icon"
|
|
3467
|
+
[matTooltip]="ui.text('driftDetected')"
|
|
3468
|
+
>sync_problem</mat-icon
|
|
3469
|
+
>
|
|
3251
3470
|
}
|
|
3252
3471
|
</div>
|
|
3253
3472
|
<span class="deb-field-id" [attr.data-testid]="'row-id-' + field.id">{{ field.id }}</span>
|
|
3254
3473
|
</div>
|
|
3255
|
-
@if (field.validators?.required) {
|
|
3474
|
+
@if (field.validators?.required) {
|
|
3475
|
+
<span class="deb-req" [title]="ui.text('required')">*</span>
|
|
3476
|
+
}
|
|
3256
3477
|
|
|
3257
|
-
<button
|
|
3478
|
+
<button
|
|
3479
|
+
mat-icon-button
|
|
3480
|
+
type="button"
|
|
3481
|
+
[disabled]="index === 0"
|
|
3258
3482
|
[attr.data-testid]="'row-up-' + field.id"
|
|
3259
|
-
[attr.aria-label]="'
|
|
3260
|
-
(click)="store.moveField(store.keyOf(field), -1); $event.stopPropagation()"
|
|
3483
|
+
[attr.aria-label]="ui.text('moveFieldUp', { field: fieldLabel(field) })"
|
|
3484
|
+
(click)="store.moveField(store.keyOf(field), -1); $event.stopPropagation()"
|
|
3485
|
+
[matTooltip]="ui.text('moveUp')"
|
|
3486
|
+
>
|
|
3261
3487
|
<mat-icon>arrow_upward</mat-icon>
|
|
3262
3488
|
</button>
|
|
3263
|
-
<button
|
|
3489
|
+
<button
|
|
3490
|
+
mat-icon-button
|
|
3491
|
+
type="button"
|
|
3492
|
+
[disabled]="index === totalCount - 1"
|
|
3264
3493
|
[attr.data-testid]="'row-down-' + field.id"
|
|
3265
|
-
[attr.aria-label]="'
|
|
3266
|
-
(click)="store.moveField(store.keyOf(field), 1); $event.stopPropagation()"
|
|
3494
|
+
[attr.aria-label]="ui.text('moveFieldDown', { field: fieldLabel(field) })"
|
|
3495
|
+
(click)="store.moveField(store.keyOf(field), 1); $event.stopPropagation()"
|
|
3496
|
+
[matTooltip]="ui.text('moveDown')"
|
|
3497
|
+
>
|
|
3267
3498
|
<mat-icon>arrow_downward</mat-icon>
|
|
3268
3499
|
</button>
|
|
3269
|
-
<button
|
|
3500
|
+
<button
|
|
3501
|
+
mat-icon-button
|
|
3502
|
+
type="button"
|
|
3270
3503
|
[attr.data-testid]="'row-duplicate-' + field.id"
|
|
3271
|
-
[attr.aria-label]="'
|
|
3272
|
-
(click)="store.duplicateField(store.keyOf(field)); $event.stopPropagation()"
|
|
3504
|
+
[attr.aria-label]="ui.text('duplicateField', { field: fieldLabel(field) })"
|
|
3505
|
+
(click)="store.duplicateField(store.keyOf(field)); $event.stopPropagation()"
|
|
3506
|
+
[matTooltip]="ui.text('duplicate')"
|
|
3507
|
+
>
|
|
3273
3508
|
<mat-icon>content_copy</mat-icon>
|
|
3274
3509
|
</button>
|
|
3275
|
-
<button
|
|
3510
|
+
<button
|
|
3511
|
+
mat-icon-button
|
|
3512
|
+
type="button"
|
|
3513
|
+
color="warn"
|
|
3276
3514
|
[attr.data-testid]="'row-delete-' + field.id"
|
|
3277
|
-
(click)="store.removeField(store.keyOf(field)); $event.stopPropagation()"
|
|
3515
|
+
(click)="store.removeField(store.keyOf(field)); $event.stopPropagation()"
|
|
3516
|
+
[matTooltip]="ui.text('delete')"
|
|
3517
|
+
>
|
|
3278
3518
|
<mat-icon>delete</mat-icon>
|
|
3279
3519
|
</button>
|
|
3280
3520
|
</div>
|
|
3281
3521
|
|
|
3282
3522
|
@if (field.children && field.children.length > 0) {
|
|
3283
|
-
<div
|
|
3523
|
+
<div
|
|
3524
|
+
class="deb-tree-children"
|
|
3525
|
+
style="padding-left: 20px; border-left: 2px solid rgba(0,0,0,0.08); margin-left: 12px; margin-top: 4px;"
|
|
3526
|
+
>
|
|
3284
3527
|
@for (child of field.children; track child.id; let ci = $index, cCount = $count) {
|
|
3285
|
-
<ngx-entity-builder-tree-node
|
|
3286
|
-
[field]="child"
|
|
3287
|
-
[index]="ci"
|
|
3288
|
-
[totalCount]="cCount"
|
|
3289
|
-
/>
|
|
3528
|
+
<ngx-entity-builder-tree-node [field]="child" [index]="ci" [totalCount]="cCount" />
|
|
3290
3529
|
}
|
|
3291
3530
|
</div>
|
|
3292
3531
|
}
|
|
@@ -3298,13 +3537,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3298
3537
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3299
3538
|
selector: 'ngx-entity-builder-tree-node',
|
|
3300
3539
|
standalone: true,
|
|
3301
|
-
imports: [
|
|
3302
|
-
CommonModule,
|
|
3303
|
-
DragDropModule,
|
|
3304
|
-
MatButtonModule,
|
|
3305
|
-
MatIconModule,
|
|
3306
|
-
MatTooltipModule,
|
|
3307
|
-
],
|
|
3540
|
+
imports: [CommonModule, DragDropModule, MatButtonModule, MatIconModule, MatTooltipModule],
|
|
3308
3541
|
template: `
|
|
3309
3542
|
<div
|
|
3310
3543
|
cdkDrag
|
|
@@ -3317,58 +3550,89 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3317
3550
|
role="button"
|
|
3318
3551
|
tabindex="0"
|
|
3319
3552
|
[attr.aria-pressed]="store.isSelected(field)"
|
|
3320
|
-
[attr.aria-label]="'
|
|
3553
|
+
[attr.aria-label]="ui.text('selectField', { field: fieldLabel(field) })"
|
|
3321
3554
|
>
|
|
3322
|
-
<mat-icon class="deb-drag-handle" cdkDragHandle aria-hidden="true" title="
|
|
3555
|
+
<mat-icon class="deb-drag-handle" cdkDragHandle aria-hidden="true" [title]="ui.text('dragToReorder')"
|
|
3556
|
+
>drag_indicator</mat-icon
|
|
3557
|
+
>
|
|
3323
3558
|
<mat-icon class="deb-field-type" [matTooltip]="fieldTypeLabel(field.type)">{{ fieldTypeIcon(field.type) }}</mat-icon>
|
|
3324
3559
|
<div class="deb-field-meta">
|
|
3325
3560
|
<div class="deb-row" style="gap:6px">
|
|
3326
3561
|
<span class="deb-field-label" [attr.data-testid]="'row-label-' + field.id">{{ fieldLabel(field) }}</span>
|
|
3327
3562
|
<span class="deb-type-badge">{{ field.type }}</span>
|
|
3328
3563
|
@if (field.isReferenced) {
|
|
3329
|
-
<mat-icon
|
|
3564
|
+
<mat-icon
|
|
3565
|
+
style="font-size: 16px; width: 16px; height: 16px; color: #1976d2;"
|
|
3566
|
+
[matTooltip]="ui.text('referencedFieldTooltip', { entity: field.referencedEntityKey ?? '' })"
|
|
3567
|
+
>link</mat-icon
|
|
3568
|
+
>
|
|
3330
3569
|
}
|
|
3331
3570
|
@if (field.hasDrift) {
|
|
3332
|
-
<mat-icon
|
|
3571
|
+
<mat-icon
|
|
3572
|
+
style="font-size: 16px; width: 16px; height: 16px; color: #d32f2f;"
|
|
3573
|
+
data-testid="drift-warning-icon"
|
|
3574
|
+
[matTooltip]="ui.text('driftDetected')"
|
|
3575
|
+
>sync_problem</mat-icon
|
|
3576
|
+
>
|
|
3333
3577
|
}
|
|
3334
3578
|
</div>
|
|
3335
3579
|
<span class="deb-field-id" [attr.data-testid]="'row-id-' + field.id">{{ field.id }}</span>
|
|
3336
3580
|
</div>
|
|
3337
|
-
@if (field.validators?.required) {
|
|
3581
|
+
@if (field.validators?.required) {
|
|
3582
|
+
<span class="deb-req" [title]="ui.text('required')">*</span>
|
|
3583
|
+
}
|
|
3338
3584
|
|
|
3339
|
-
<button
|
|
3585
|
+
<button
|
|
3586
|
+
mat-icon-button
|
|
3587
|
+
type="button"
|
|
3588
|
+
[disabled]="index === 0"
|
|
3340
3589
|
[attr.data-testid]="'row-up-' + field.id"
|
|
3341
|
-
[attr.aria-label]="'
|
|
3342
|
-
(click)="store.moveField(store.keyOf(field), -1); $event.stopPropagation()"
|
|
3590
|
+
[attr.aria-label]="ui.text('moveFieldUp', { field: fieldLabel(field) })"
|
|
3591
|
+
(click)="store.moveField(store.keyOf(field), -1); $event.stopPropagation()"
|
|
3592
|
+
[matTooltip]="ui.text('moveUp')"
|
|
3593
|
+
>
|
|
3343
3594
|
<mat-icon>arrow_upward</mat-icon>
|
|
3344
3595
|
</button>
|
|
3345
|
-
<button
|
|
3596
|
+
<button
|
|
3597
|
+
mat-icon-button
|
|
3598
|
+
type="button"
|
|
3599
|
+
[disabled]="index === totalCount - 1"
|
|
3346
3600
|
[attr.data-testid]="'row-down-' + field.id"
|
|
3347
|
-
[attr.aria-label]="'
|
|
3348
|
-
(click)="store.moveField(store.keyOf(field), 1); $event.stopPropagation()"
|
|
3601
|
+
[attr.aria-label]="ui.text('moveFieldDown', { field: fieldLabel(field) })"
|
|
3602
|
+
(click)="store.moveField(store.keyOf(field), 1); $event.stopPropagation()"
|
|
3603
|
+
[matTooltip]="ui.text('moveDown')"
|
|
3604
|
+
>
|
|
3349
3605
|
<mat-icon>arrow_downward</mat-icon>
|
|
3350
3606
|
</button>
|
|
3351
|
-
<button
|
|
3607
|
+
<button
|
|
3608
|
+
mat-icon-button
|
|
3609
|
+
type="button"
|
|
3352
3610
|
[attr.data-testid]="'row-duplicate-' + field.id"
|
|
3353
|
-
[attr.aria-label]="'
|
|
3354
|
-
(click)="store.duplicateField(store.keyOf(field)); $event.stopPropagation()"
|
|
3611
|
+
[attr.aria-label]="ui.text('duplicateField', { field: fieldLabel(field) })"
|
|
3612
|
+
(click)="store.duplicateField(store.keyOf(field)); $event.stopPropagation()"
|
|
3613
|
+
[matTooltip]="ui.text('duplicate')"
|
|
3614
|
+
>
|
|
3355
3615
|
<mat-icon>content_copy</mat-icon>
|
|
3356
3616
|
</button>
|
|
3357
|
-
<button
|
|
3617
|
+
<button
|
|
3618
|
+
mat-icon-button
|
|
3619
|
+
type="button"
|
|
3620
|
+
color="warn"
|
|
3358
3621
|
[attr.data-testid]="'row-delete-' + field.id"
|
|
3359
|
-
(click)="store.removeField(store.keyOf(field)); $event.stopPropagation()"
|
|
3622
|
+
(click)="store.removeField(store.keyOf(field)); $event.stopPropagation()"
|
|
3623
|
+
[matTooltip]="ui.text('delete')"
|
|
3624
|
+
>
|
|
3360
3625
|
<mat-icon>delete</mat-icon>
|
|
3361
3626
|
</button>
|
|
3362
3627
|
</div>
|
|
3363
3628
|
|
|
3364
3629
|
@if (field.children && field.children.length > 0) {
|
|
3365
|
-
<div
|
|
3630
|
+
<div
|
|
3631
|
+
class="deb-tree-children"
|
|
3632
|
+
style="padding-left: 20px; border-left: 2px solid rgba(0,0,0,0.08); margin-left: 12px; margin-top: 4px;"
|
|
3633
|
+
>
|
|
3366
3634
|
@for (child of field.children; track child.id; let ci = $index, cCount = $count) {
|
|
3367
|
-
<ngx-entity-builder-tree-node
|
|
3368
|
-
[field]="child"
|
|
3369
|
-
[index]="ci"
|
|
3370
|
-
[totalCount]="cCount"
|
|
3371
|
-
/>
|
|
3635
|
+
<ngx-entity-builder-tree-node [field]="child" [index]="ci" [totalCount]="cCount" />
|
|
3372
3636
|
}
|
|
3373
3637
|
</div>
|
|
3374
3638
|
}
|
|
@@ -3386,6 +3650,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3386
3650
|
}] } });
|
|
3387
3651
|
|
|
3388
3652
|
class EntityBuilderCanvasComponent {
|
|
3653
|
+
/** Builder chrome, overridable via BUILDER_TEXT. */
|
|
3654
|
+
ui = inject(BuilderTextService);
|
|
3389
3655
|
store = inject(BuilderStore);
|
|
3390
3656
|
/**
|
|
3391
3657
|
* Headings only earn their space once the fields are actually split across tabs. A
|
|
@@ -3404,13 +3670,13 @@ class EntityBuilderCanvasComponent {
|
|
|
3404
3670
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: EntityBuilderCanvasComponent, isStandalone: true, selector: "ngx-entity-builder-canvas", ngImport: i0, template: `
|
|
3405
3671
|
<mat-card class="deb-canvas">
|
|
3406
3672
|
<mat-card-header>
|
|
3407
|
-
<mat-card-title>
|
|
3673
|
+
<mat-card-title>{{ ui.text('fieldsHeading', { count: store.fields().length }) }}</mat-card-title>
|
|
3408
3674
|
</mat-card-header>
|
|
3409
3675
|
<mat-card-content>
|
|
3410
3676
|
@if (store.fields().length === 0) {
|
|
3411
3677
|
<div class="deb-empty">
|
|
3412
3678
|
<mat-icon>widgets</mat-icon>
|
|
3413
|
-
<p>
|
|
3679
|
+
<p>{{ ui.text('canvasEmpty') }}</p>
|
|
3414
3680
|
</div>
|
|
3415
3681
|
}
|
|
3416
3682
|
|
|
@@ -3432,11 +3698,7 @@ class EntityBuilderCanvasComponent {
|
|
|
3432
3698
|
(cdkDropListDropped)="onDrop(group.tabId, $event)"
|
|
3433
3699
|
>
|
|
3434
3700
|
@for (f of group.fields; track f.id; let i = $index, count = $count) {
|
|
3435
|
-
<ngx-entity-builder-tree-node
|
|
3436
|
-
[field]="f"
|
|
3437
|
-
[index]="i"
|
|
3438
|
-
[totalCount]="count"
|
|
3439
|
-
/>
|
|
3701
|
+
<ngx-entity-builder-tree-node [field]="f" [index]="i" [totalCount]="count" />
|
|
3440
3702
|
}
|
|
3441
3703
|
</div>
|
|
3442
3704
|
}
|
|
@@ -3450,23 +3712,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3450
3712
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3451
3713
|
selector: 'ngx-entity-builder-canvas',
|
|
3452
3714
|
standalone: true,
|
|
3453
|
-
imports: [
|
|
3454
|
-
CommonModule,
|
|
3455
|
-
DragDropModule,
|
|
3456
|
-
MatCardModule,
|
|
3457
|
-
MatIconModule,
|
|
3458
|
-
EntityBuilderTreeNodeComponent,
|
|
3459
|
-
],
|
|
3715
|
+
imports: [CommonModule, DragDropModule, MatCardModule, MatIconModule, EntityBuilderTreeNodeComponent],
|
|
3460
3716
|
template: `
|
|
3461
3717
|
<mat-card class="deb-canvas">
|
|
3462
3718
|
<mat-card-header>
|
|
3463
|
-
<mat-card-title>
|
|
3719
|
+
<mat-card-title>{{ ui.text('fieldsHeading', { count: store.fields().length }) }}</mat-card-title>
|
|
3464
3720
|
</mat-card-header>
|
|
3465
3721
|
<mat-card-content>
|
|
3466
3722
|
@if (store.fields().length === 0) {
|
|
3467
3723
|
<div class="deb-empty">
|
|
3468
3724
|
<mat-icon>widgets</mat-icon>
|
|
3469
|
-
<p>
|
|
3725
|
+
<p>{{ ui.text('canvasEmpty') }}</p>
|
|
3470
3726
|
</div>
|
|
3471
3727
|
}
|
|
3472
3728
|
|
|
@@ -3488,11 +3744,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3488
3744
|
(cdkDropListDropped)="onDrop(group.tabId, $event)"
|
|
3489
3745
|
>
|
|
3490
3746
|
@for (f of group.fields; track f.id; let i = $index, count = $count) {
|
|
3491
|
-
<ngx-entity-builder-tree-node
|
|
3492
|
-
[field]="f"
|
|
3493
|
-
[index]="i"
|
|
3494
|
-
[totalCount]="count"
|
|
3495
|
-
/>
|
|
3747
|
+
<ngx-entity-builder-tree-node [field]="f" [index]="i" [totalCount]="count" />
|
|
3496
3748
|
}
|
|
3497
3749
|
</div>
|
|
3498
3750
|
}
|
|
@@ -3510,6 +3762,8 @@ const EMPTY_ROLES = Object.freeze([]);
|
|
|
3510
3762
|
* EntityBuilderComponent — the top-level visual builder for an EntityFormConfig.
|
|
3511
3763
|
*/
|
|
3512
3764
|
class EntityBuilderComponent {
|
|
3765
|
+
/** Builder chrome, overridable via BUILDER_TEXT. */
|
|
3766
|
+
ui = inject(BuilderTextService);
|
|
3513
3767
|
store = inject(BuilderStore);
|
|
3514
3768
|
/**
|
|
3515
3769
|
* Optional: only apps that registered a CONFIG_SOURCE have one. Used to drop the cached
|
|
@@ -3520,6 +3774,16 @@ class EntityBuilderComponent {
|
|
|
3520
3774
|
config;
|
|
3521
3775
|
/** Languages available for label/placeholder editing. First entry is the default. */
|
|
3522
3776
|
languages = ['en'];
|
|
3777
|
+
/**
|
|
3778
|
+
* Language for the builder's **own** chrome, resolved through `BUILDER_TEXT`.
|
|
3779
|
+
*
|
|
3780
|
+
* Deliberately separate from `languages` and `store.activeLanguage()`, which choose the
|
|
3781
|
+
* `LocalizedText` entry being authored: switching the label language you are editing
|
|
3782
|
+
* should not translate the interface around it.
|
|
3783
|
+
*/
|
|
3784
|
+
set uiLanguage(value) {
|
|
3785
|
+
this.ui.language.set(value || 'en');
|
|
3786
|
+
}
|
|
3523
3787
|
/** Optional role list — enables multi-select role pickers for RBAC instead of free text. */
|
|
3524
3788
|
availableRoles = [];
|
|
3525
3789
|
/**
|
|
@@ -3609,7 +3873,7 @@ class EntityBuilderComponent {
|
|
|
3609
3873
|
this.store.setPermission(action, roles);
|
|
3610
3874
|
}
|
|
3611
3875
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3612
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: EntityBuilderComponent, isStandalone: true, selector: "ngx-entity-builder", inputs: { config: "config", languages: "languages", availableRoles: "availableRoles", userRoles: "userRoles", commonModules: "commonModules" }, outputs: { configChange: "configChange", save: "save" }, host: { listeners: { "document:keydown": "onKeydown($event)" } }, providers: [BuilderStore], usesOnChanges: true, ngImport: i0, template: "<mat-toolbar class=\"deb-toolbar\" color=\"primary\">\n <mat-icon>dashboard_customize</mat-icon>\n <span class=\"deb-toolbar__title\">Entity Builder</span>\n <span class=\"deb-spacer\"></span>\n\n @if (store.problems().length) {\n <button\n mat-button\n type=\"button\"\n class=\"deb-problems\"\n [class.deb-problems--error]=\"!store.isValid()\"\n [matTooltip]=\"store.problems().length + ' issue(s) \u2014 hover items in the list'\"\n >\n <mat-icon>{{ store.isValid() ? 'warning' : 'error' }}</mat-icon>\n {{ store.errors().length }} / {{ store.problems().length }}\n </button>\n }\n\n <button\n mat-icon-button\n type=\"button\"\n data-testid=\"builder-undo\"\n [disabled]=\"!store.canUndo()\"\n (click)=\"store.undo()\"\n matTooltip=\"Undo (Ctrl+Z)\"\n aria-label=\"Undo\"\n >\n <mat-icon>undo</mat-icon>\n </button>\n <button\n mat-icon-button\n type=\"button\"\n data-testid=\"builder-redo\"\n [disabled]=\"!store.canRedo()\"\n (click)=\"store.redo()\"\n matTooltip=\"Redo (Ctrl+Shift+Z)\"\n aria-label=\"Redo\"\n >\n <mat-icon>redo</mat-icon>\n </button>\n\n <button mat-button type=\"button\" (click)=\"copyJson()\">\n <mat-icon>content_copy</mat-icon> Copy JSON\n </button>\n <!-- primary, not accent: white on Material's default pink accent is 3.33:1, below AA. -->\n <button mat-flat-button color=\"primary\" type=\"button\" [disabled]=\"!store.isValid()\" (click)=\"doSave()\">\n <mat-icon>save</mat-icon> Save\n </button>\n</mat-toolbar>\n\n<div class=\"deb-body\">\n <!-- \u2500\u2500\u2500 Left: settings, palette, tabs \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--left\">\n <mat-card>\n <mat-card-content>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>Entity name</mat-label>\n <input\n matInput\n [ngModel]=\"store.config().entity\"\n (ngModelChange)=\"store.setEntityName($event)\"\n placeholder=\"e.g. clients\"\n />\n </mat-form-field>\n\n <div class=\"deb-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-grow\">\n <mat-label>Editing language</mat-label>\n <mat-select\n [ngModel]=\"store.activeLanguage()\"\n (ngModelChange)=\"store.setActiveLanguage($event)\"\n >\n @for (l of languages; track l) {\n <mat-option [value]=\"l\">{{ l }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <mat-slide-toggle\n [ngModel]=\"store.config().maskData ?? false\"\n (ngModelChange)=\"store.setMaskData($event)\"\n >Mask this entity (RBAC)</mat-slide-toggle>\n\n <mat-expansion-panel class=\"deb-rbac\">\n <mat-expansion-panel-header>\n <mat-panel-title>Permissions</mat-panel-title>\n </mat-expansion-panel-header>\n @for (action of rbacActions; track action) {\n @if (availableRoles.length) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ action }} roles</mat-label>\n <mat-select multiple [ngModel]=\"rolesFor(action)\" (ngModelChange)=\"setRoles(action, $event)\">\n @for (role of availableRoles; track role) {\n <mat-option [value]=\"role\">{{ role }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ action }} roles (comma-separated)</mat-label>\n <input\n matInput\n [ngModel]=\"rolesFor(action).join(', ')\"\n (ngModelChange)=\"setRolesFromText(action, $event)\"\n />\n </mat-form-field>\n }\n }\n <p class=\"deb-hint\">Empty = no restriction for that action.</p>\n </mat-expansion-panel>\n </mat-card-content>\n </mat-card>\n\n <mat-card>\n <mat-card-header><mat-card-title>Add field</mat-card-title></mat-card-header>\n <mat-card-content>\n <ngx-field-palette (pick)=\"store.addField($event)\"></ngx-field-palette>\n </mat-card-content>\n </mat-card>\n\n <mat-card>\n <mat-card-content>\n <ngx-tab-manager></ngx-tab-manager>\n </mat-card-content>\n </mat-card>\n </div>\n\n <!-- \u2500\u2500\u2500 Center: field canvas \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--center\">\n <ngx-entity-builder-canvas />\n </div>\n\n <!-- \u2500\u2500\u2500 Right: inspector \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--right\">\n <mat-card>\n <mat-card-header><mat-card-title>Field properties</mat-card-title></mat-card-header>\n <mat-card-content>\n <ngx-field-inspector></ngx-field-inspector>\n </mat-card-content>\n </mat-card>\n </div>\n</div>\n\n<!-- \u2500\u2500\u2500 Footer: JSON + optional live preview \u2500\u2500\u2500 -->\n<div class=\"deb-footer\">\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title><mat-icon>data_object</mat-icon> Config JSON</mat-panel-title>\n </mat-expansion-panel-header>\n <pre class=\"deb-json\">{{ json }}</pre>\n </mat-expansion-panel>\n\n <div class=\"deb-preview-slot\">\n <ng-content select=\"[ngxBuilderPreview]\"></ng-content>\n </div>\n</div>\n", styles: [":host{--deb-accent: #6366f1;--deb-accent-hover: #4f46e5;--deb-accent-soft: #eeef2;--deb-border: #e2e8f0;--deb-muted: #64748b;--deb-surface: #ffffff;display:block}.deb-toolbar__title{margin-left:8px;font-family:var(--font-heading, sans-serif);font-size:16px;font-weight:700;color:#fff}.deb-spacer{flex:1 1 auto}.deb-problems--error{color:#ef4444;font-weight:600}.deb-full{width:100%}.deb-grow{flex:1}.deb-row{display:flex;gap:8px;align-items:center}.deb-section-title{font-family:var(--font-heading, sans-serif);font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--deb-muted)}.deb-hint{margin:4px 0 0;font-size:12px;color:var(--deb-muted)}.deb-chip{display:inline-block;padding:3px 10px;border-radius:9999px;background:var(--deb-accent-soft);color:var(--deb-accent-hover);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.03em}.deb-chip--muted{background:#f1f5f9;color:var(--deb-muted)}.deb-body{display:grid;grid-template-columns:320px minmax(0,1fr) 380px;gap:20px;padding:20px;align-items:start}.deb-col{display:flex;flex-direction:column;gap:16px;min-width:0}.deb-col--right{position:sticky;top:20px;max-height:calc(100vh - 40px);overflow:auto}@media(max-width:1100px){.deb-body{grid-template-columns:1fr}.deb-col--right{position:static}}.deb-field-list{display:flex;flex-direction:column;gap:10px}.deb-field-row{display:flex;align-items:center;gap:10px;padding:10px 14px;border:1px solid var(--deb-border);border-radius:10px;background:var(--deb-surface);cursor:pointer;box-shadow:0 1px 3px #00000008;transition:all .18s cubic-bezier(.16,1,.3,1)}.deb-field-row:hover{border-color:#a5b4fc;background:#fcfaff;transform:translateY(-1px);box-shadow:0 4px 12px #6366f114}.deb-field-row--active{border-color:var(--deb-accent);border-left:4px solid var(--deb-accent);background:#f5f3ff;box-shadow:0 4px 16px #6366f12e}.deb-type-badge{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;padding:2px 7px;border-radius:6px;background:#e0e7ff;color:#4338ca}.deb-drag-handle{cursor:grab;color:#94a3b8;transition:color .15s}.deb-drag-handle:hover,.deb-field-type{color:var(--deb-accent)}.deb-field-meta{display:flex;flex-direction:column;min-width:0;flex:1}.deb-field-label{font-family:var(--font-heading, sans-serif);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#0f172a;font-size:14px}.deb-field-id{font-size:11px;color:var(--deb-muted);font-family:ui-monospace,SFMono-Regular,monospace}.deb-req{color:#ef4444;font-weight:700}.deb-empty,.deb-inspector__empty{text-align:center;color:var(--deb-muted);padding:40px 16px}.deb-empty mat-icon{transform:scale(1.8);margin-bottom:16px;color:#cbd5e1}.cdk-drag-preview{box-shadow:0 12px 28px #4f46e538;border-radius:10px;border:1px solid var(--deb-accent);background:#fff}.cdk-drag-placeholder{opacity:.35}.cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.deb-field-list.cdk-drop-list-dragging .deb-field-row:not(.cdk-drag-placeholder){transition:transform .2s cubic-bezier(0,0,.2,1)}.deb-footer{padding:0 20px 20px;display:flex;flex-direction:column;gap:16px}.deb-json{margin:0;max-height:320px;overflow:auto;font-family:ui-monospace,SFMono-Regular,monospace;font-size:12px;line-height:1.5;padding:14px;background:#0f172a;color:#e2e8f0;border-radius:10px}.deb-path-hint{color:#0000008c;font-size:11px;margin-left:6px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { 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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i2$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i2$1.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i2$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i2$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i4$3.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i4$3.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i4$3.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i9.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatToolbarModule }, { kind: "component", type: i10.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: FieldPaletteComponent, selector: "ngx-field-palette", outputs: ["pick"] }, { kind: "component", type: TabManagerComponent, selector: "ngx-tab-manager" }, { kind: "component", type: FieldInspectorComponent, selector: "ngx-field-inspector" }, { kind: "component", type: EntityBuilderCanvasComponent, selector: "ngx-entity-builder-canvas" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3876
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: EntityBuilderComponent, isStandalone: true, selector: "ngx-entity-builder", inputs: { config: "config", languages: "languages", uiLanguage: "uiLanguage", availableRoles: "availableRoles", userRoles: "userRoles", commonModules: "commonModules" }, outputs: { configChange: "configChange", save: "save" }, host: { listeners: { "document:keydown": "onKeydown($event)" } }, providers: [BuilderStore], usesOnChanges: true, ngImport: i0, template: "<mat-toolbar class=\"deb-toolbar\" color=\"primary\">\n <mat-icon>dashboard_customize</mat-icon>\n <span class=\"deb-toolbar__title\">{{ ui.text('builderTitle') }}</span>\n <span class=\"deb-spacer\"></span>\n\n @if (store.problems().length) {\n <button\n mat-button\n type=\"button\"\n class=\"deb-problems\"\n [class.deb-problems--error]=\"!store.isValid()\"\n [matTooltip]=\"ui.text('issuesTooltip', { count: store.problems().length })\"\n >\n <mat-icon>{{ store.isValid() ? 'warning' : 'error' }}</mat-icon>\n {{ store.errors().length }} / {{ store.problems().length }}\n </button>\n }\n\n <button\n mat-icon-button\n type=\"button\"\n data-testid=\"builder-undo\"\n [disabled]=\"!store.canUndo()\"\n (click)=\"store.undo()\"\n [matTooltip]=\"ui.text('undoTooltip')\"\n [attr.aria-label]=\"ui.text('undo')\"\n >\n <mat-icon>undo</mat-icon>\n </button>\n <button\n mat-icon-button\n type=\"button\"\n data-testid=\"builder-redo\"\n [disabled]=\"!store.canRedo()\"\n (click)=\"store.redo()\"\n [matTooltip]=\"ui.text('redoTooltip')\"\n [attr.aria-label]=\"ui.text('redo')\"\n >\n <mat-icon>redo</mat-icon>\n </button>\n\n <button mat-button type=\"button\" (click)=\"copyJson()\"><mat-icon>content_copy</mat-icon> {{ ui.text('copyJson') }}</button>\n <!-- primary, not accent: white on Material's default pink accent is 3.33:1, below AA. -->\n <button mat-flat-button color=\"primary\" type=\"button\" [disabled]=\"!store.isValid()\" (click)=\"doSave()\">\n <mat-icon>save</mat-icon> {{ ui.text('save') }}\n </button>\n</mat-toolbar>\n\n<div class=\"deb-body\">\n <!-- \u2500\u2500\u2500 Left: settings, palette, tabs \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--left\">\n <mat-card>\n <mat-card-content>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('entityName') }}</mat-label>\n <input\n matInput\n [ngModel]=\"store.config().entity\"\n (ngModelChange)=\"store.setEntityName($event)\"\n [placeholder]=\"ui.text('entityNamePlaceholder')\"\n />\n </mat-form-field>\n\n <div class=\"deb-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-grow\">\n <mat-label>{{ ui.text('editingLanguage') }}</mat-label>\n <mat-select\n data-testid=\"builder-editing-language\"\n [ngModel]=\"store.activeLanguage()\"\n (ngModelChange)=\"store.setActiveLanguage($event)\"\n >\n @for (l of languages; track l) {\n <mat-option [value]=\"l\">{{ l }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <mat-slide-toggle [ngModel]=\"store.config().maskData ?? false\" (ngModelChange)=\"store.setMaskData($event)\">{{\n ui.text('maskEntity')\n }}</mat-slide-toggle>\n\n <mat-expansion-panel class=\"deb-rbac\">\n <mat-expansion-panel-header>\n <mat-panel-title>{{ ui.text('permissions') }}</mat-panel-title>\n </mat-expansion-panel-header>\n @for (action of rbacActions; track action) {\n @if (availableRoles.length) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('actionRoles', { action }) }}</mat-label>\n <mat-select multiple [ngModel]=\"rolesFor(action)\" (ngModelChange)=\"setRoles(action, $event)\">\n @for (role of availableRoles; track role) {\n <mat-option [value]=\"role\">{{ role }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('actionRolesPlaceholder', { action }) }}</mat-label>\n <input matInput [ngModel]=\"rolesFor(action).join(', ')\" (ngModelChange)=\"setRolesFromText(action, $event)\" />\n </mat-form-field>\n }\n }\n <p class=\"deb-hint\">{{ ui.text('permissionsHint') }}</p>\n </mat-expansion-panel>\n </mat-card-content>\n </mat-card>\n\n <mat-card>\n <mat-card-header\n ><mat-card-title>{{ ui.text('addField') }}</mat-card-title></mat-card-header\n >\n <mat-card-content>\n <ngx-field-palette (pick)=\"store.addField($event)\"></ngx-field-palette>\n </mat-card-content>\n </mat-card>\n\n <mat-card>\n <mat-card-content>\n <ngx-tab-manager></ngx-tab-manager>\n </mat-card-content>\n </mat-card>\n </div>\n\n <!-- \u2500\u2500\u2500 Center: field canvas \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--center\">\n <ngx-entity-builder-canvas />\n </div>\n\n <!-- \u2500\u2500\u2500 Right: inspector \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--right\">\n <mat-card>\n <mat-card-header\n ><mat-card-title>{{ ui.text('fieldProperties') }}</mat-card-title></mat-card-header\n >\n <mat-card-content>\n <ngx-field-inspector></ngx-field-inspector>\n </mat-card-content>\n </mat-card>\n </div>\n</div>\n\n<!-- \u2500\u2500\u2500 Footer: JSON + optional live preview \u2500\u2500\u2500 -->\n<div class=\"deb-footer\">\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title><mat-icon>data_object</mat-icon> {{ ui.text('configJson') }}</mat-panel-title>\n </mat-expansion-panel-header>\n <pre class=\"deb-json\">{{ json }}</pre>\n </mat-expansion-panel>\n\n <div class=\"deb-preview-slot\">\n <ng-content select=\"[ngxBuilderPreview]\"></ng-content>\n </div>\n</div>\n", styles: [":host{--deb-accent: #6366f1;--deb-accent-hover: #4f46e5;--deb-accent-soft: #eeef2;--deb-border: #e2e8f0;--deb-muted: #64748b;--deb-surface: #ffffff;display:block}.deb-toolbar__title{margin-left:8px;font-family:var(--font-heading, sans-serif);font-size:16px;font-weight:700;color:#fff}.deb-spacer{flex:1 1 auto}.deb-problems--error{color:#ef4444;font-weight:600}.deb-full{width:100%}.deb-grow{flex:1}.deb-row{display:flex;gap:8px;align-items:center}.deb-section-title{font-family:var(--font-heading, sans-serif);font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--deb-muted)}.deb-hint{margin:4px 0 0;font-size:12px;color:var(--deb-muted)}.deb-chip{display:inline-block;padding:3px 10px;border-radius:9999px;background:var(--deb-accent-soft);color:var(--deb-accent-hover);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.03em}.deb-chip--muted{background:#f1f5f9;color:var(--deb-muted)}.deb-body{display:grid;grid-template-columns:320px minmax(0,1fr) 380px;gap:20px;padding:20px;align-items:start}.deb-col{display:flex;flex-direction:column;gap:16px;min-width:0}.deb-col--right{position:sticky;top:20px;max-height:calc(100vh - 40px);overflow:auto}@media(max-width:1100px){.deb-body{grid-template-columns:1fr}.deb-col--right{position:static}}.deb-field-list{display:flex;flex-direction:column;gap:10px}.deb-field-row{display:flex;align-items:center;gap:10px;padding:10px 14px;border:1px solid var(--deb-border);border-radius:10px;background:var(--deb-surface);cursor:pointer;box-shadow:0 1px 3px #00000008;transition:all .18s cubic-bezier(.16,1,.3,1)}.deb-field-row:hover{border-color:#a5b4fc;background:#fcfaff;transform:translateY(-1px);box-shadow:0 4px 12px #6366f114}.deb-field-row--active{border-color:var(--deb-accent);border-left:4px solid var(--deb-accent);background:#f5f3ff;box-shadow:0 4px 16px #6366f12e}.deb-type-badge{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;padding:2px 7px;border-radius:6px;background:#e0e7ff;color:#4338ca}.deb-drag-handle{cursor:grab;color:#94a3b8;transition:color .15s}.deb-drag-handle:hover,.deb-field-type{color:var(--deb-accent)}.deb-field-meta{display:flex;flex-direction:column;min-width:0;flex:1}.deb-field-label{font-family:var(--font-heading, sans-serif);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#0f172a;font-size:14px}.deb-field-id{font-size:11px;color:var(--deb-muted);font-family:ui-monospace,SFMono-Regular,monospace}.deb-req{color:#ef4444;font-weight:700}.deb-empty,.deb-inspector__empty{text-align:center;color:var(--deb-muted);padding:40px 16px}.deb-empty mat-icon{transform:scale(1.8);margin-bottom:16px;color:#cbd5e1}.cdk-drag-preview{box-shadow:0 12px 28px #4f46e538;border-radius:10px;border:1px solid var(--deb-accent);background:#fff}.cdk-drag-placeholder{opacity:.35}.cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.deb-field-list.cdk-drop-list-dragging .deb-field-row:not(.cdk-drag-placeholder){transition:transform .2s cubic-bezier(0,0,.2,1)}.deb-footer{padding:0 20px 20px;display:flex;flex-direction:column;gap:16px}.deb-json{margin:0;max-height:320px;overflow:auto;font-family:ui-monospace,SFMono-Regular,monospace;font-size:12px;line-height:1.5;padding:14px;background:#0f172a;color:#e2e8f0;border-radius:10px}.deb-path-hint{color:#0000008c;font-size:11px;margin-left:6px}\n"], dependencies: [{ kind: "ngmodule", type: FormsModule }, { 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.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatButtonModule }, { kind: "component", type: i2.MatButton, selector: " button[matButton], a[matButton], button[mat-button], button[mat-raised-button], button[mat-flat-button], button[mat-stroked-button], a[mat-button], a[mat-raised-button], a[mat-flat-button], a[mat-stroked-button] ", inputs: ["matButton"], exportAs: ["matButton", "matAnchor"] }, { kind: "component", type: i2.MatIconButton, selector: "button[mat-icon-button], a[mat-icon-button], button[matIconButton], a[matIconButton]", exportAs: ["matButton", "matAnchor"] }, { kind: "ngmodule", type: MatCardModule }, { kind: "component", type: i2$1.MatCard, selector: "mat-card", inputs: ["appearance"], exportAs: ["matCard"] }, { kind: "directive", type: i2$1.MatCardContent, selector: "mat-card-content" }, { kind: "component", type: i2$1.MatCardHeader, selector: "mat-card-header" }, { kind: "directive", type: i2$1.MatCardTitle, selector: "mat-card-title, [mat-card-title], [matCardTitle]" }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "ngmodule", type: MatExpansionModule }, { kind: "component", type: i4$3.MatExpansionPanel, selector: "mat-expansion-panel", inputs: ["hideToggle", "togglePosition"], outputs: ["afterExpand", "afterCollapse"], exportAs: ["matExpansionPanel"] }, { kind: "component", type: i4$3.MatExpansionPanelHeader, selector: "mat-expansion-panel-header", inputs: ["expandedHeight", "collapsedHeight", "tabIndex"] }, { kind: "directive", type: i4$3.MatExpansionPanelTitle, selector: "mat-panel-title" }, { kind: "ngmodule", type: MatFormFieldModule }, { kind: "component", type: i4.MatFormField, selector: "mat-form-field", inputs: ["hideRequiredMarker", "color", "floatLabel", "appearance", "subscriptSizing", "hintLabel"], exportAs: ["matFormField"] }, { kind: "directive", type: i4.MatLabel, selector: "mat-label" }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i4$1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatInputModule }, { kind: "directive", type: i5.MatInput, selector: "input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]", inputs: ["disabled", "id", "placeholder", "name", "required", "type", "errorStateMatcher", "aria-describedby", "value", "readonly", "disabledInteractive"], exportAs: ["matInput"] }, { kind: "ngmodule", type: MatSelectModule }, { kind: "component", type: i8.MatSelect, selector: "mat-select", inputs: ["aria-describedby", "panelClass", "disabled", "disableRipple", "tabIndex", "hideSingleSelectionIndicator", "placeholder", "required", "multiple", "disableOptionCentering", "compareWith", "value", "aria-label", "aria-labelledby", "errorStateMatcher", "typeaheadDebounceInterval", "sortComparator", "id", "panelWidth", "canSelectNullableOptions"], outputs: ["openedChange", "opened", "closed", "selectionChange", "valueChange"], exportAs: ["matSelect"] }, { kind: "component", type: i8.MatOption, selector: "mat-option", inputs: ["value", "id", "disabled"], outputs: ["onSelectionChange"], exportAs: ["matOption"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "component", type: i9.MatSlideToggle, selector: "mat-slide-toggle", inputs: ["name", "id", "labelPosition", "aria-label", "aria-labelledby", "aria-describedby", "required", "color", "disabled", "disableRipple", "tabIndex", "checked", "hideIcon", "disabledInteractive"], outputs: ["change", "toggleChange"], exportAs: ["matSlideToggle"] }, { kind: "ngmodule", type: MatToolbarModule }, { kind: "component", type: i10.MatToolbar, selector: "mat-toolbar", inputs: ["color"], exportAs: ["matToolbar"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4$2.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }, { kind: "component", type: FieldPaletteComponent, selector: "ngx-field-palette", outputs: ["pick"] }, { kind: "component", type: TabManagerComponent, selector: "ngx-tab-manager" }, { kind: "component", type: FieldInspectorComponent, selector: "ngx-field-inspector" }, { kind: "component", type: EntityBuilderCanvasComponent, selector: "ngx-entity-builder-canvas" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
3613
3877
|
}
|
|
3614
3878
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityBuilderComponent, decorators: [{
|
|
3615
3879
|
type: Component,
|
|
@@ -3630,11 +3894,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3630
3894
|
TabManagerComponent,
|
|
3631
3895
|
FieldInspectorComponent,
|
|
3632
3896
|
EntityBuilderCanvasComponent,
|
|
3633
|
-
], template: "<mat-toolbar class=\"deb-toolbar\" color=\"primary\">\n <mat-icon>dashboard_customize</mat-icon>\n <span class=\"deb-toolbar__title\">
|
|
3897
|
+
], template: "<mat-toolbar class=\"deb-toolbar\" color=\"primary\">\n <mat-icon>dashboard_customize</mat-icon>\n <span class=\"deb-toolbar__title\">{{ ui.text('builderTitle') }}</span>\n <span class=\"deb-spacer\"></span>\n\n @if (store.problems().length) {\n <button\n mat-button\n type=\"button\"\n class=\"deb-problems\"\n [class.deb-problems--error]=\"!store.isValid()\"\n [matTooltip]=\"ui.text('issuesTooltip', { count: store.problems().length })\"\n >\n <mat-icon>{{ store.isValid() ? 'warning' : 'error' }}</mat-icon>\n {{ store.errors().length }} / {{ store.problems().length }}\n </button>\n }\n\n <button\n mat-icon-button\n type=\"button\"\n data-testid=\"builder-undo\"\n [disabled]=\"!store.canUndo()\"\n (click)=\"store.undo()\"\n [matTooltip]=\"ui.text('undoTooltip')\"\n [attr.aria-label]=\"ui.text('undo')\"\n >\n <mat-icon>undo</mat-icon>\n </button>\n <button\n mat-icon-button\n type=\"button\"\n data-testid=\"builder-redo\"\n [disabled]=\"!store.canRedo()\"\n (click)=\"store.redo()\"\n [matTooltip]=\"ui.text('redoTooltip')\"\n [attr.aria-label]=\"ui.text('redo')\"\n >\n <mat-icon>redo</mat-icon>\n </button>\n\n <button mat-button type=\"button\" (click)=\"copyJson()\"><mat-icon>content_copy</mat-icon> {{ ui.text('copyJson') }}</button>\n <!-- primary, not accent: white on Material's default pink accent is 3.33:1, below AA. -->\n <button mat-flat-button color=\"primary\" type=\"button\" [disabled]=\"!store.isValid()\" (click)=\"doSave()\">\n <mat-icon>save</mat-icon> {{ ui.text('save') }}\n </button>\n</mat-toolbar>\n\n<div class=\"deb-body\">\n <!-- \u2500\u2500\u2500 Left: settings, palette, tabs \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--left\">\n <mat-card>\n <mat-card-content>\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('entityName') }}</mat-label>\n <input\n matInput\n [ngModel]=\"store.config().entity\"\n (ngModelChange)=\"store.setEntityName($event)\"\n [placeholder]=\"ui.text('entityNamePlaceholder')\"\n />\n </mat-form-field>\n\n <div class=\"deb-row\">\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-grow\">\n <mat-label>{{ ui.text('editingLanguage') }}</mat-label>\n <mat-select\n data-testid=\"builder-editing-language\"\n [ngModel]=\"store.activeLanguage()\"\n (ngModelChange)=\"store.setActiveLanguage($event)\"\n >\n @for (l of languages; track l) {\n <mat-option [value]=\"l\">{{ l }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n </div>\n\n <mat-slide-toggle [ngModel]=\"store.config().maskData ?? false\" (ngModelChange)=\"store.setMaskData($event)\">{{\n ui.text('maskEntity')\n }}</mat-slide-toggle>\n\n <mat-expansion-panel class=\"deb-rbac\">\n <mat-expansion-panel-header>\n <mat-panel-title>{{ ui.text('permissions') }}</mat-panel-title>\n </mat-expansion-panel-header>\n @for (action of rbacActions; track action) {\n @if (availableRoles.length) {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('actionRoles', { action }) }}</mat-label>\n <mat-select multiple [ngModel]=\"rolesFor(action)\" (ngModelChange)=\"setRoles(action, $event)\">\n @for (role of availableRoles; track role) {\n <mat-option [value]=\"role\">{{ role }}</mat-option>\n }\n </mat-select>\n </mat-form-field>\n } @else {\n <mat-form-field appearance=\"outline\" subscriptSizing=\"dynamic\" class=\"deb-full\">\n <mat-label>{{ ui.text('actionRolesPlaceholder', { action }) }}</mat-label>\n <input matInput [ngModel]=\"rolesFor(action).join(', ')\" (ngModelChange)=\"setRolesFromText(action, $event)\" />\n </mat-form-field>\n }\n }\n <p class=\"deb-hint\">{{ ui.text('permissionsHint') }}</p>\n </mat-expansion-panel>\n </mat-card-content>\n </mat-card>\n\n <mat-card>\n <mat-card-header\n ><mat-card-title>{{ ui.text('addField') }}</mat-card-title></mat-card-header\n >\n <mat-card-content>\n <ngx-field-palette (pick)=\"store.addField($event)\"></ngx-field-palette>\n </mat-card-content>\n </mat-card>\n\n <mat-card>\n <mat-card-content>\n <ngx-tab-manager></ngx-tab-manager>\n </mat-card-content>\n </mat-card>\n </div>\n\n <!-- \u2500\u2500\u2500 Center: field canvas \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--center\">\n <ngx-entity-builder-canvas />\n </div>\n\n <!-- \u2500\u2500\u2500 Right: inspector \u2500\u2500\u2500 -->\n <div class=\"deb-col deb-col--right\">\n <mat-card>\n <mat-card-header\n ><mat-card-title>{{ ui.text('fieldProperties') }}</mat-card-title></mat-card-header\n >\n <mat-card-content>\n <ngx-field-inspector></ngx-field-inspector>\n </mat-card-content>\n </mat-card>\n </div>\n</div>\n\n<!-- \u2500\u2500\u2500 Footer: JSON + optional live preview \u2500\u2500\u2500 -->\n<div class=\"deb-footer\">\n <mat-expansion-panel>\n <mat-expansion-panel-header>\n <mat-panel-title><mat-icon>data_object</mat-icon> {{ ui.text('configJson') }}</mat-panel-title>\n </mat-expansion-panel-header>\n <pre class=\"deb-json\">{{ json }}</pre>\n </mat-expansion-panel>\n\n <div class=\"deb-preview-slot\">\n <ng-content select=\"[ngxBuilderPreview]\"></ng-content>\n </div>\n</div>\n", styles: [":host{--deb-accent: #6366f1;--deb-accent-hover: #4f46e5;--deb-accent-soft: #eeef2;--deb-border: #e2e8f0;--deb-muted: #64748b;--deb-surface: #ffffff;display:block}.deb-toolbar__title{margin-left:8px;font-family:var(--font-heading, sans-serif);font-size:16px;font-weight:700;color:#fff}.deb-spacer{flex:1 1 auto}.deb-problems--error{color:#ef4444;font-weight:600}.deb-full{width:100%}.deb-grow{flex:1}.deb-row{display:flex;gap:8px;align-items:center}.deb-section-title{font-family:var(--font-heading, sans-serif);font-size:12px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--deb-muted)}.deb-hint{margin:4px 0 0;font-size:12px;color:var(--deb-muted)}.deb-chip{display:inline-block;padding:3px 10px;border-radius:9999px;background:var(--deb-accent-soft);color:var(--deb-accent-hover);font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.03em}.deb-chip--muted{background:#f1f5f9;color:var(--deb-muted)}.deb-body{display:grid;grid-template-columns:320px minmax(0,1fr) 380px;gap:20px;padding:20px;align-items:start}.deb-col{display:flex;flex-direction:column;gap:16px;min-width:0}.deb-col--right{position:sticky;top:20px;max-height:calc(100vh - 40px);overflow:auto}@media(max-width:1100px){.deb-body{grid-template-columns:1fr}.deb-col--right{position:static}}.deb-field-list{display:flex;flex-direction:column;gap:10px}.deb-field-row{display:flex;align-items:center;gap:10px;padding:10px 14px;border:1px solid var(--deb-border);border-radius:10px;background:var(--deb-surface);cursor:pointer;box-shadow:0 1px 3px #00000008;transition:all .18s cubic-bezier(.16,1,.3,1)}.deb-field-row:hover{border-color:#a5b4fc;background:#fcfaff;transform:translateY(-1px);box-shadow:0 4px 12px #6366f114}.deb-field-row--active{border-color:var(--deb-accent);border-left:4px solid var(--deb-accent);background:#f5f3ff;box-shadow:0 4px 16px #6366f12e}.deb-type-badge{font-size:10px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;padding:2px 7px;border-radius:6px;background:#e0e7ff;color:#4338ca}.deb-drag-handle{cursor:grab;color:#94a3b8;transition:color .15s}.deb-drag-handle:hover,.deb-field-type{color:var(--deb-accent)}.deb-field-meta{display:flex;flex-direction:column;min-width:0;flex:1}.deb-field-label{font-family:var(--font-heading, sans-serif);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;color:#0f172a;font-size:14px}.deb-field-id{font-size:11px;color:var(--deb-muted);font-family:ui-monospace,SFMono-Regular,monospace}.deb-req{color:#ef4444;font-weight:700}.deb-empty,.deb-inspector__empty{text-align:center;color:var(--deb-muted);padding:40px 16px}.deb-empty mat-icon{transform:scale(1.8);margin-bottom:16px;color:#cbd5e1}.cdk-drag-preview{box-shadow:0 12px 28px #4f46e538;border-radius:10px;border:1px solid var(--deb-accent);background:#fff}.cdk-drag-placeholder{opacity:.35}.cdk-drag-animating{transition:transform .2s cubic-bezier(0,0,.2,1)}.deb-field-list.cdk-drop-list-dragging .deb-field-row:not(.cdk-drag-placeholder){transition:transform .2s cubic-bezier(0,0,.2,1)}.deb-footer{padding:0 20px 20px;display:flex;flex-direction:column;gap:16px}.deb-json{margin:0;max-height:320px;overflow:auto;font-family:ui-monospace,SFMono-Regular,monospace;font-size:12px;line-height:1.5;padding:14px;background:#0f172a;color:#e2e8f0;border-radius:10px}.deb-path-hint{color:#0000008c;font-size:11px;margin-left:6px}\n"] }]
|
|
3634
3898
|
}], ctorParameters: () => [], propDecorators: { config: [{
|
|
3635
3899
|
type: Input
|
|
3636
3900
|
}], languages: [{
|
|
3637
3901
|
type: Input
|
|
3902
|
+
}], uiLanguage: [{
|
|
3903
|
+
type: Input
|
|
3638
3904
|
}], availableRoles: [{
|
|
3639
3905
|
type: Input
|
|
3640
3906
|
}], userRoles: [{
|
|
@@ -3656,5 +3922,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3656
3922
|
* Generated bundle index. Do not edit.
|
|
3657
3923
|
*/
|
|
3658
3924
|
|
|
3659
|
-
export { BuilderStore, EntityBuilderCanvasComponent, EntityBuilderComponent, EntityBuilderTreeNodeComponent, EntityReferenceConfigComponent, FieldInspectorComponent, FieldPaletteComponent, FieldRulesListComponent, RuleFormComponent, TabManagerComponent, fieldPathOptions, withExistingOptions };
|
|
3925
|
+
export { BUILDER_TEXT, BuilderStore, BuilderTextService, DEFAULT_BUILDER_TEXT, EntityBuilderCanvasComponent, EntityBuilderComponent, EntityBuilderTreeNodeComponent, EntityReferenceConfigComponent, FieldInspectorComponent, FieldPaletteComponent, FieldRulesListComponent, RuleFormComponent, TabManagerComponent, fieldPathOptions, withExistingOptions };
|
|
3660
3926
|
//# sourceMappingURL=ngx-dynamic-entity-builder.mjs.map
|