ngx-dynamic-entity-builder 1.8.0 → 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';
|
|
@@ -1415,6 +1415,14 @@ class BuilderStore {
|
|
|
1415
1415
|
}
|
|
1416
1416
|
validate(config) {
|
|
1417
1417
|
const problems = [];
|
|
1418
|
+
// Core calls a config with no tabs an error — nothing can render — and the builder used
|
|
1419
|
+
// to report only a warning and leave Save enabled. That let the builder save a config
|
|
1420
|
+
// `dynamic-entity validate` then rejected in CI: the mirror of the bug where it refused
|
|
1421
|
+
// one core accepted. The builder defers to core here, as it already does for the scope
|
|
1422
|
+
// rule, and the message is core's so the two read the same in both places.
|
|
1423
|
+
if (!config.tabs?.length) {
|
|
1424
|
+
problems.push({ level: 'error', message: 'At least one tab is required.' });
|
|
1425
|
+
}
|
|
1418
1426
|
if (!config.entity || !config.entity.trim()) {
|
|
1419
1427
|
problems.push({ level: 'error', message: 'Entity name is required.' });
|
|
1420
1428
|
}
|
|
@@ -1545,6 +1553,221 @@ function withExistingOptions(options, current) {
|
|
|
1545
1553
|
return [...options, ...extra];
|
|
1546
1554
|
}
|
|
1547
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
|
+
|
|
1548
1771
|
/**
|
|
1549
1772
|
* EntityReferenceConfigComponent — authors the `entityReference` block of an entity-ref field:
|
|
1550
1773
|
* registry key, display fields, static filters, and the parent→child cascade.
|
|
@@ -1553,6 +1776,8 @@ function withExistingOptions(options, current) {
|
|
|
1553
1776
|
* selected reference returns.
|
|
1554
1777
|
*/
|
|
1555
1778
|
class EntityReferenceConfigComponent {
|
|
1779
|
+
/** Builder chrome, overridable via BUILDER_TEXT. */
|
|
1780
|
+
ui = inject(BuilderTextService);
|
|
1556
1781
|
store = inject(BuilderStore);
|
|
1557
1782
|
field = this.store.selectedField;
|
|
1558
1783
|
/** Sibling fields that can drive a cascade — anything but this field itself. */
|
|
@@ -1632,32 +1857,32 @@ class EntityReferenceConfigComponent {
|
|
|
1632
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: `
|
|
1633
1858
|
@if (field(); as f) {
|
|
1634
1859
|
<div class="deb-entity-ref">
|
|
1635
|
-
<span class="deb-section-title">
|
|
1860
|
+
<span class="deb-section-title">{{ ui.text('entityReference') }}</span>
|
|
1636
1861
|
|
|
1637
1862
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1638
|
-
<mat-label>
|
|
1863
|
+
<mat-label>{{ ui.text('registryKey') }}</mat-label>
|
|
1639
1864
|
<input
|
|
1640
1865
|
matInput
|
|
1641
1866
|
data-testid="entity-ref-key"
|
|
1642
1867
|
[ngModel]="ref(f).linkedEntityKey ?? ''"
|
|
1643
1868
|
(ngModelChange)="patchRef(f, { linkedEntityKey: $event || undefined })"
|
|
1644
1869
|
/>
|
|
1645
|
-
<mat-hint>
|
|
1870
|
+
<mat-hint>{{ ui.text('registryKeyHint') }}</mat-hint>
|
|
1646
1871
|
</mat-form-field>
|
|
1647
1872
|
|
|
1648
1873
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1649
|
-
<mat-label>
|
|
1874
|
+
<mat-label>{{ ui.text('displayFields') }}</mat-label>
|
|
1650
1875
|
<input
|
|
1651
1876
|
matInput
|
|
1652
1877
|
data-testid="entity-ref-display-fields"
|
|
1653
1878
|
[ngModel]="displayFieldsText(f)"
|
|
1654
1879
|
(ngModelChange)="setDisplayFields(f, $event)"
|
|
1655
1880
|
/>
|
|
1656
|
-
<mat-hint>
|
|
1881
|
+
<mat-hint>{{ ui.text('displayFieldsHint') }}</mat-hint>
|
|
1657
1882
|
</mat-form-field>
|
|
1658
1883
|
|
|
1659
1884
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1660
|
-
<mat-label>
|
|
1885
|
+
<mat-label>{{ ui.text('staticFilters') }}</mat-label>
|
|
1661
1886
|
<input
|
|
1662
1887
|
matInput
|
|
1663
1888
|
data-testid="entity-ref-filters"
|
|
@@ -1667,52 +1892,52 @@ class EntityReferenceConfigComponent {
|
|
|
1667
1892
|
@if (filtersError()) {
|
|
1668
1893
|
<mat-hint class="deb-error-hint">{{ filtersError() }}</mat-hint>
|
|
1669
1894
|
} @else {
|
|
1670
|
-
<mat-hint>
|
|
1895
|
+
<mat-hint>{{ ui.text('staticFiltersHint') }}</mat-hint>
|
|
1671
1896
|
}
|
|
1672
1897
|
</mat-form-field>
|
|
1673
1898
|
|
|
1674
1899
|
<mat-divider></mat-divider>
|
|
1675
1900
|
|
|
1676
1901
|
<!-- Cascade -->
|
|
1677
|
-
<span class="deb-section-title">
|
|
1902
|
+
<span class="deb-section-title">{{ ui.text('cascade') }}</span>
|
|
1678
1903
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1679
|
-
<mat-label>
|
|
1904
|
+
<mat-label>{{ ui.text('parentField') }}</mat-label>
|
|
1680
1905
|
<mat-select
|
|
1681
1906
|
data-testid="entity-ref-parent"
|
|
1682
1907
|
[ngModel]="ref(f).parentField ?? ''"
|
|
1683
1908
|
(ngModelChange)="patchRef(f, { parentField: $event || undefined })"
|
|
1684
1909
|
>
|
|
1685
|
-
<mat-option value="">
|
|
1910
|
+
<mat-option value="">{{ ui.text('parentFieldNone') }}</mat-option>
|
|
1686
1911
|
@for (option of parentOptions(); track option.value) {
|
|
1687
1912
|
<mat-option [value]="option.value">
|
|
1688
1913
|
{{ option.label }} <span class="deb-path-hint">{{ option.path }}</span>
|
|
1689
1914
|
</mat-option>
|
|
1690
1915
|
}
|
|
1691
1916
|
</mat-select>
|
|
1692
|
-
<mat-hint>
|
|
1917
|
+
<mat-hint>{{ ui.text('cascadeHint') }}</mat-hint>
|
|
1693
1918
|
</mat-form-field>
|
|
1694
1919
|
|
|
1695
1920
|
@if (ref(f).parentField) {
|
|
1696
1921
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1697
|
-
<mat-label>
|
|
1922
|
+
<mat-label>{{ ui.text('lookupFilterPath') }}</mat-label>
|
|
1698
1923
|
<input
|
|
1699
1924
|
matInput
|
|
1700
1925
|
data-testid="entity-ref-lookup-filter"
|
|
1701
1926
|
[ngModel]="ref(f).lookupFilter ?? ''"
|
|
1702
1927
|
(ngModelChange)="patchRef(f, { lookupFilter: $event || undefined })"
|
|
1703
1928
|
/>
|
|
1704
|
-
<mat-hint>
|
|
1929
|
+
<mat-hint>{{ ui.text('lookupFilterPathHint') }}</mat-hint>
|
|
1705
1930
|
</mat-form-field>
|
|
1706
1931
|
|
|
1707
1932
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1708
|
-
<mat-label>
|
|
1933
|
+
<mat-label>{{ ui.text('lookupPath') }}</mat-label>
|
|
1709
1934
|
<input
|
|
1710
1935
|
matInput
|
|
1711
1936
|
data-testid="entity-ref-lookup-path"
|
|
1712
1937
|
[ngModel]="ref(f).lookupPath ?? ''"
|
|
1713
1938
|
(ngModelChange)="patchRef(f, { lookupPath: $event || undefined })"
|
|
1714
1939
|
/>
|
|
1715
|
-
<mat-hint>
|
|
1940
|
+
<mat-hint>{{ ui.text('lookupPathHint') }}</mat-hint>
|
|
1716
1941
|
</mat-form-field>
|
|
1717
1942
|
}
|
|
1718
1943
|
|
|
@@ -1720,20 +1945,20 @@ class EntityReferenceConfigComponent {
|
|
|
1720
1945
|
|
|
1721
1946
|
<!-- autoPatch -->
|
|
1722
1947
|
<div class="deb-row deb-row--split">
|
|
1723
|
-
<span class="deb-section-title">
|
|
1948
|
+
<span class="deb-section-title">{{ ui.text('autoPatchOnSelect') }}</span>
|
|
1724
1949
|
<button
|
|
1725
1950
|
mat-stroked-button
|
|
1726
1951
|
type="button"
|
|
1727
1952
|
data-testid="add-auto-patch"
|
|
1728
1953
|
(click)="store.addAutoPatchMapping(store.keyOf(f))"
|
|
1729
1954
|
>
|
|
1730
|
-
<mat-icon>add</mat-icon>
|
|
1955
|
+
<mat-icon>add</mat-icon> {{ ui.text('mapping') }}
|
|
1731
1956
|
</button>
|
|
1732
1957
|
</div>
|
|
1733
1958
|
|
|
1734
1959
|
@if (f.autoPatch) {
|
|
1735
1960
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1736
|
-
<mat-label>
|
|
1961
|
+
<mat-label>{{ ui.text('targetTab') }}</mat-label>
|
|
1737
1962
|
<mat-select
|
|
1738
1963
|
data-testid="auto-patch-tab"
|
|
1739
1964
|
[ngModel]="f.autoPatch.targetTab"
|
|
@@ -1748,7 +1973,7 @@ class EntityReferenceConfigComponent {
|
|
|
1748
1973
|
@for (mapping of f.autoPatch.mappings; track $index) {
|
|
1749
1974
|
<div class="deb-option-row">
|
|
1750
1975
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
1751
|
-
<mat-label>
|
|
1976
|
+
<mat-label>{{ ui.text('sourceLinkedRecord') }}</mat-label>
|
|
1752
1977
|
<input
|
|
1753
1978
|
matInput
|
|
1754
1979
|
[ngModel]="mapping.source"
|
|
@@ -1756,7 +1981,7 @@ class EntityReferenceConfigComponent {
|
|
|
1756
1981
|
/>
|
|
1757
1982
|
</mat-form-field>
|
|
1758
1983
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
1759
|
-
<mat-label>
|
|
1984
|
+
<mat-label>{{ ui.text('targetField') }}</mat-label>
|
|
1760
1985
|
<mat-select
|
|
1761
1986
|
data-testid="auto-patch-target"
|
|
1762
1987
|
[ngModel]="mapping.target"
|
|
@@ -1773,7 +1998,7 @@ class EntityReferenceConfigComponent {
|
|
|
1773
1998
|
mat-icon-button
|
|
1774
1999
|
type="button"
|
|
1775
2000
|
color="warn"
|
|
1776
|
-
matTooltip="
|
|
2001
|
+
[matTooltip]="ui.text('removeMapping')"
|
|
1777
2002
|
(click)="store.removeAutoPatchMapping(store.keyOf(f), $index)"
|
|
1778
2003
|
>
|
|
1779
2004
|
<mat-icon>delete</mat-icon>
|
|
@@ -1782,7 +2007,7 @@ class EntityReferenceConfigComponent {
|
|
|
1782
2007
|
}
|
|
1783
2008
|
} @else {
|
|
1784
2009
|
<p class="deb-hint">
|
|
1785
|
-
|
|
2010
|
+
{{ ui.text('noAutoPatch') }}
|
|
1786
2011
|
</p>
|
|
1787
2012
|
}
|
|
1788
2013
|
</div>
|
|
@@ -1804,32 +2029,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
1804
2029
|
], template: `
|
|
1805
2030
|
@if (field(); as f) {
|
|
1806
2031
|
<div class="deb-entity-ref">
|
|
1807
|
-
<span class="deb-section-title">
|
|
2032
|
+
<span class="deb-section-title">{{ ui.text('entityReference') }}</span>
|
|
1808
2033
|
|
|
1809
2034
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1810
|
-
<mat-label>
|
|
2035
|
+
<mat-label>{{ ui.text('registryKey') }}</mat-label>
|
|
1811
2036
|
<input
|
|
1812
2037
|
matInput
|
|
1813
2038
|
data-testid="entity-ref-key"
|
|
1814
2039
|
[ngModel]="ref(f).linkedEntityKey ?? ''"
|
|
1815
2040
|
(ngModelChange)="patchRef(f, { linkedEntityKey: $event || undefined })"
|
|
1816
2041
|
/>
|
|
1817
|
-
<mat-hint>
|
|
2042
|
+
<mat-hint>{{ ui.text('registryKeyHint') }}</mat-hint>
|
|
1818
2043
|
</mat-form-field>
|
|
1819
2044
|
|
|
1820
2045
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1821
|
-
<mat-label>
|
|
2046
|
+
<mat-label>{{ ui.text('displayFields') }}</mat-label>
|
|
1822
2047
|
<input
|
|
1823
2048
|
matInput
|
|
1824
2049
|
data-testid="entity-ref-display-fields"
|
|
1825
2050
|
[ngModel]="displayFieldsText(f)"
|
|
1826
2051
|
(ngModelChange)="setDisplayFields(f, $event)"
|
|
1827
2052
|
/>
|
|
1828
|
-
<mat-hint>
|
|
2053
|
+
<mat-hint>{{ ui.text('displayFieldsHint') }}</mat-hint>
|
|
1829
2054
|
</mat-form-field>
|
|
1830
2055
|
|
|
1831
2056
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1832
|
-
<mat-label>
|
|
2057
|
+
<mat-label>{{ ui.text('staticFilters') }}</mat-label>
|
|
1833
2058
|
<input
|
|
1834
2059
|
matInput
|
|
1835
2060
|
data-testid="entity-ref-filters"
|
|
@@ -1839,52 +2064,52 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
1839
2064
|
@if (filtersError()) {
|
|
1840
2065
|
<mat-hint class="deb-error-hint">{{ filtersError() }}</mat-hint>
|
|
1841
2066
|
} @else {
|
|
1842
|
-
<mat-hint>
|
|
2067
|
+
<mat-hint>{{ ui.text('staticFiltersHint') }}</mat-hint>
|
|
1843
2068
|
}
|
|
1844
2069
|
</mat-form-field>
|
|
1845
2070
|
|
|
1846
2071
|
<mat-divider></mat-divider>
|
|
1847
2072
|
|
|
1848
2073
|
<!-- Cascade -->
|
|
1849
|
-
<span class="deb-section-title">
|
|
2074
|
+
<span class="deb-section-title">{{ ui.text('cascade') }}</span>
|
|
1850
2075
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1851
|
-
<mat-label>
|
|
2076
|
+
<mat-label>{{ ui.text('parentField') }}</mat-label>
|
|
1852
2077
|
<mat-select
|
|
1853
2078
|
data-testid="entity-ref-parent"
|
|
1854
2079
|
[ngModel]="ref(f).parentField ?? ''"
|
|
1855
2080
|
(ngModelChange)="patchRef(f, { parentField: $event || undefined })"
|
|
1856
2081
|
>
|
|
1857
|
-
<mat-option value="">
|
|
2082
|
+
<mat-option value="">{{ ui.text('parentFieldNone') }}</mat-option>
|
|
1858
2083
|
@for (option of parentOptions(); track option.value) {
|
|
1859
2084
|
<mat-option [value]="option.value">
|
|
1860
2085
|
{{ option.label }} <span class="deb-path-hint">{{ option.path }}</span>
|
|
1861
2086
|
</mat-option>
|
|
1862
2087
|
}
|
|
1863
2088
|
</mat-select>
|
|
1864
|
-
<mat-hint>
|
|
2089
|
+
<mat-hint>{{ ui.text('cascadeHint') }}</mat-hint>
|
|
1865
2090
|
</mat-form-field>
|
|
1866
2091
|
|
|
1867
2092
|
@if (ref(f).parentField) {
|
|
1868
2093
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1869
|
-
<mat-label>
|
|
2094
|
+
<mat-label>{{ ui.text('lookupFilterPath') }}</mat-label>
|
|
1870
2095
|
<input
|
|
1871
2096
|
matInput
|
|
1872
2097
|
data-testid="entity-ref-lookup-filter"
|
|
1873
2098
|
[ngModel]="ref(f).lookupFilter ?? ''"
|
|
1874
2099
|
(ngModelChange)="patchRef(f, { lookupFilter: $event || undefined })"
|
|
1875
2100
|
/>
|
|
1876
|
-
<mat-hint>
|
|
2101
|
+
<mat-hint>{{ ui.text('lookupFilterPathHint') }}</mat-hint>
|
|
1877
2102
|
</mat-form-field>
|
|
1878
2103
|
|
|
1879
2104
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1880
|
-
<mat-label>
|
|
2105
|
+
<mat-label>{{ ui.text('lookupPath') }}</mat-label>
|
|
1881
2106
|
<input
|
|
1882
2107
|
matInput
|
|
1883
2108
|
data-testid="entity-ref-lookup-path"
|
|
1884
2109
|
[ngModel]="ref(f).lookupPath ?? ''"
|
|
1885
2110
|
(ngModelChange)="patchRef(f, { lookupPath: $event || undefined })"
|
|
1886
2111
|
/>
|
|
1887
|
-
<mat-hint>
|
|
2112
|
+
<mat-hint>{{ ui.text('lookupPathHint') }}</mat-hint>
|
|
1888
2113
|
</mat-form-field>
|
|
1889
2114
|
}
|
|
1890
2115
|
|
|
@@ -1892,20 +2117,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
1892
2117
|
|
|
1893
2118
|
<!-- autoPatch -->
|
|
1894
2119
|
<div class="deb-row deb-row--split">
|
|
1895
|
-
<span class="deb-section-title">
|
|
2120
|
+
<span class="deb-section-title">{{ ui.text('autoPatchOnSelect') }}</span>
|
|
1896
2121
|
<button
|
|
1897
2122
|
mat-stroked-button
|
|
1898
2123
|
type="button"
|
|
1899
2124
|
data-testid="add-auto-patch"
|
|
1900
2125
|
(click)="store.addAutoPatchMapping(store.keyOf(f))"
|
|
1901
2126
|
>
|
|
1902
|
-
<mat-icon>add</mat-icon>
|
|
2127
|
+
<mat-icon>add</mat-icon> {{ ui.text('mapping') }}
|
|
1903
2128
|
</button>
|
|
1904
2129
|
</div>
|
|
1905
2130
|
|
|
1906
2131
|
@if (f.autoPatch) {
|
|
1907
2132
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
1908
|
-
<mat-label>
|
|
2133
|
+
<mat-label>{{ ui.text('targetTab') }}</mat-label>
|
|
1909
2134
|
<mat-select
|
|
1910
2135
|
data-testid="auto-patch-tab"
|
|
1911
2136
|
[ngModel]="f.autoPatch.targetTab"
|
|
@@ -1920,7 +2145,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
1920
2145
|
@for (mapping of f.autoPatch.mappings; track $index) {
|
|
1921
2146
|
<div class="deb-option-row">
|
|
1922
2147
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
1923
|
-
<mat-label>
|
|
2148
|
+
<mat-label>{{ ui.text('sourceLinkedRecord') }}</mat-label>
|
|
1924
2149
|
<input
|
|
1925
2150
|
matInput
|
|
1926
2151
|
[ngModel]="mapping.source"
|
|
@@ -1928,7 +2153,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
1928
2153
|
/>
|
|
1929
2154
|
</mat-form-field>
|
|
1930
2155
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
1931
|
-
<mat-label>
|
|
2156
|
+
<mat-label>{{ ui.text('targetField') }}</mat-label>
|
|
1932
2157
|
<mat-select
|
|
1933
2158
|
data-testid="auto-patch-target"
|
|
1934
2159
|
[ngModel]="mapping.target"
|
|
@@ -1945,7 +2170,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
1945
2170
|
mat-icon-button
|
|
1946
2171
|
type="button"
|
|
1947
2172
|
color="warn"
|
|
1948
|
-
matTooltip="
|
|
2173
|
+
[matTooltip]="ui.text('removeMapping')"
|
|
1949
2174
|
(click)="store.removeAutoPatchMapping(store.keyOf(f), $index)"
|
|
1950
2175
|
>
|
|
1951
2176
|
<mat-icon>delete</mat-icon>
|
|
@@ -1954,7 +2179,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
1954
2179
|
}
|
|
1955
2180
|
} @else {
|
|
1956
2181
|
<p class="deb-hint">
|
|
1957
|
-
|
|
2182
|
+
{{ ui.text('noAutoPatch') }}
|
|
1958
2183
|
</p>
|
|
1959
2184
|
}
|
|
1960
2185
|
</div>
|
|
@@ -1966,6 +2191,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
1966
2191
|
* RuleFormComponent — dialog/panel form for creating or editing a FormRule.
|
|
1967
2192
|
*/
|
|
1968
2193
|
class RuleFormComponent {
|
|
2194
|
+
/** Builder chrome, overridable via BUILDER_TEXT. */
|
|
2195
|
+
ui = inject(BuilderTextService);
|
|
1969
2196
|
store = inject(BuilderStore);
|
|
1970
2197
|
rule = {
|
|
1971
2198
|
formConfigId: 'form-1',
|
|
@@ -2021,8 +2248,7 @@ class RuleFormComponent {
|
|
|
2021
2248
|
*/
|
|
2022
2249
|
targetValues() {
|
|
2023
2250
|
const ids = this.rule.targets.filter(t => t.type === 'field').map(t => t.id);
|
|
2024
|
-
const unchanged = ids.length === this.cachedTargets.length &&
|
|
2025
|
-
ids.every((id, i) => id === this.cachedTargets[i]);
|
|
2251
|
+
const unchanged = ids.length === this.cachedTargets.length && ids.every((id, i) => id === this.cachedTargets[i]);
|
|
2026
2252
|
if (!unchanged)
|
|
2027
2253
|
this.cachedTargets = ids;
|
|
2028
2254
|
return this.cachedTargets;
|
|
@@ -2046,11 +2272,11 @@ class RuleFormComponent {
|
|
|
2046
2272
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RuleFormComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2047
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: `
|
|
2048
2274
|
<div class="deb-rule-form">
|
|
2049
|
-
<h3 class="deb-rule-form__title">{{ rule.id ? '
|
|
2275
|
+
<h3 class="deb-rule-form__title">{{ ui.text(rule.id ? 'editRuleTitle' : 'newRuleTitle') }}</h3>
|
|
2050
2276
|
|
|
2051
2277
|
<!-- Trigger Field -->
|
|
2052
2278
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
2053
|
-
<mat-label>
|
|
2279
|
+
<mat-label>{{ ui.text('triggerField') }}</mat-label>
|
|
2054
2280
|
<mat-select [(ngModel)]="rule.fieldId" data-testid="rule-trigger">
|
|
2055
2281
|
@for (option of triggerOptions(); track option.value) {
|
|
2056
2282
|
<mat-option [value]="option.value">
|
|
@@ -2062,13 +2288,8 @@ class RuleFormComponent {
|
|
|
2062
2288
|
|
|
2063
2289
|
<!-- Targets: the fields this rule acts on -->
|
|
2064
2290
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
2065
|
-
<mat-label>
|
|
2066
|
-
<mat-select
|
|
2067
|
-
multiple
|
|
2068
|
-
data-testid="rule-targets"
|
|
2069
|
-
[ngModel]="targetValues()"
|
|
2070
|
-
(ngModelChange)="setTargets($event)"
|
|
2071
|
-
>
|
|
2291
|
+
<mat-label>{{ ui.text('applyToFields') }}</mat-label>
|
|
2292
|
+
<mat-select multiple data-testid="rule-targets" [ngModel]="targetValues()" (ngModelChange)="setTargets($event)">
|
|
2072
2293
|
@for (option of targetOptions(); track option.value) {
|
|
2073
2294
|
<mat-option [value]="option.value">
|
|
2074
2295
|
{{ option.label }} <span class="deb-rule-path">{{ option.path }}</span>
|
|
@@ -2079,11 +2300,11 @@ class RuleFormComponent {
|
|
|
2079
2300
|
|
|
2080
2301
|
<!-- Conditions -->
|
|
2081
2302
|
<div class="deb-rule-section">
|
|
2082
|
-
<h4>
|
|
2303
|
+
<h4>{{ ui.text('conditions') }}</h4>
|
|
2083
2304
|
@for (cond of rule.conditions; track $index) {
|
|
2084
2305
|
<div class="deb-rule-row">
|
|
2085
2306
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
2086
|
-
<mat-label>
|
|
2307
|
+
<mat-label>{{ ui.text('operator') }}</mat-label>
|
|
2087
2308
|
<mat-select [(ngModel)]="cond.operator">
|
|
2088
2309
|
@for (op of operators; track op) {
|
|
2089
2310
|
<mat-option [value]="op">{{ op }}</mat-option>
|
|
@@ -2091,8 +2312,8 @@ class RuleFormComponent {
|
|
|
2091
2312
|
</mat-select>
|
|
2092
2313
|
</mat-form-field>
|
|
2093
2314
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
2094
|
-
<mat-label>
|
|
2095
|
-
<input matInput [(ngModel)]="cond.value" placeholder="
|
|
2315
|
+
<mat-label>{{ ui.text('value') }}</mat-label>
|
|
2316
|
+
<input matInput [(ngModel)]="cond.value" [placeholder]="ui.text('conditionValuePlaceholder')" />
|
|
2096
2317
|
</mat-form-field>
|
|
2097
2318
|
<button mat-icon-button color="warn" type="button" (click)="removeCondition($index)">
|
|
2098
2319
|
<mat-icon>delete</mat-icon>
|
|
@@ -2100,53 +2321,45 @@ class RuleFormComponent {
|
|
|
2100
2321
|
</div>
|
|
2101
2322
|
}
|
|
2102
2323
|
<button mat-stroked-button type="button" (click)="addCondition()">
|
|
2103
|
-
<mat-icon>add</mat-icon>
|
|
2324
|
+
<mat-icon>add</mat-icon> {{ ui.text('addCondition') }}
|
|
2104
2325
|
</button>
|
|
2105
2326
|
</div>
|
|
2106
2327
|
|
|
2107
2328
|
<!-- Action -->
|
|
2108
2329
|
<div class="deb-rule-section">
|
|
2109
|
-
<h4>
|
|
2330
|
+
<h4>{{ ui.text('action') }}</h4>
|
|
2110
2331
|
<div class="deb-rule-row">
|
|
2111
2332
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
2112
|
-
<mat-label>
|
|
2333
|
+
<mat-label>{{ ui.text('actionType') }}</mat-label>
|
|
2113
2334
|
<mat-select [(ngModel)]="rule.action.type">
|
|
2114
|
-
<mat-option value="visibility">
|
|
2115
|
-
<mat-option value="validation">
|
|
2116
|
-
<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>
|
|
2117
2338
|
</mat-select>
|
|
2118
2339
|
</mat-form-field>
|
|
2119
2340
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
2120
|
-
<mat-label>
|
|
2121
|
-
<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')" />
|
|
2122
2343
|
</mat-form-field>
|
|
2123
2344
|
</div>
|
|
2124
2345
|
</div>
|
|
2125
2346
|
|
|
2126
2347
|
<div class="deb-rule-form__actions">
|
|
2127
|
-
<button mat-button type="button" (click)="cancel.emit()">
|
|
2128
|
-
<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>
|
|
2129
2350
|
</div>
|
|
2130
2351
|
</div>
|
|
2131
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 });
|
|
2132
2353
|
}
|
|
2133
2354
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: RuleFormComponent, decorators: [{
|
|
2134
2355
|
type: Component,
|
|
2135
|
-
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-rule-form', standalone: true, imports: [
|
|
2136
|
-
CommonModule,
|
|
2137
|
-
FormsModule,
|
|
2138
|
-
MatButtonModule,
|
|
2139
|
-
MatFormFieldModule,
|
|
2140
|
-
MatIconModule,
|
|
2141
|
-
MatInputModule,
|
|
2142
|
-
MatSelectModule,
|
|
2143
|
-
], template: `
|
|
2356
|
+
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-rule-form', standalone: true, imports: [CommonModule, FormsModule, MatButtonModule, MatFormFieldModule, MatIconModule, MatInputModule, MatSelectModule], template: `
|
|
2144
2357
|
<div class="deb-rule-form">
|
|
2145
|
-
<h3 class="deb-rule-form__title">{{ rule.id ? '
|
|
2358
|
+
<h3 class="deb-rule-form__title">{{ ui.text(rule.id ? 'editRuleTitle' : 'newRuleTitle') }}</h3>
|
|
2146
2359
|
|
|
2147
2360
|
<!-- Trigger Field -->
|
|
2148
2361
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
2149
|
-
<mat-label>
|
|
2362
|
+
<mat-label>{{ ui.text('triggerField') }}</mat-label>
|
|
2150
2363
|
<mat-select [(ngModel)]="rule.fieldId" data-testid="rule-trigger">
|
|
2151
2364
|
@for (option of triggerOptions(); track option.value) {
|
|
2152
2365
|
<mat-option [value]="option.value">
|
|
@@ -2158,13 +2371,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2158
2371
|
|
|
2159
2372
|
<!-- Targets: the fields this rule acts on -->
|
|
2160
2373
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full">
|
|
2161
|
-
<mat-label>
|
|
2162
|
-
<mat-select
|
|
2163
|
-
multiple
|
|
2164
|
-
data-testid="rule-targets"
|
|
2165
|
-
[ngModel]="targetValues()"
|
|
2166
|
-
(ngModelChange)="setTargets($event)"
|
|
2167
|
-
>
|
|
2374
|
+
<mat-label>{{ ui.text('applyToFields') }}</mat-label>
|
|
2375
|
+
<mat-select multiple data-testid="rule-targets" [ngModel]="targetValues()" (ngModelChange)="setTargets($event)">
|
|
2168
2376
|
@for (option of targetOptions(); track option.value) {
|
|
2169
2377
|
<mat-option [value]="option.value">
|
|
2170
2378
|
{{ option.label }} <span class="deb-rule-path">{{ option.path }}</span>
|
|
@@ -2175,11 +2383,11 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2175
2383
|
|
|
2176
2384
|
<!-- Conditions -->
|
|
2177
2385
|
<div class="deb-rule-section">
|
|
2178
|
-
<h4>
|
|
2386
|
+
<h4>{{ ui.text('conditions') }}</h4>
|
|
2179
2387
|
@for (cond of rule.conditions; track $index) {
|
|
2180
2388
|
<div class="deb-rule-row">
|
|
2181
2389
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
2182
|
-
<mat-label>
|
|
2390
|
+
<mat-label>{{ ui.text('operator') }}</mat-label>
|
|
2183
2391
|
<mat-select [(ngModel)]="cond.operator">
|
|
2184
2392
|
@for (op of operators; track op) {
|
|
2185
2393
|
<mat-option [value]="op">{{ op }}</mat-option>
|
|
@@ -2187,8 +2395,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2187
2395
|
</mat-select>
|
|
2188
2396
|
</mat-form-field>
|
|
2189
2397
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
2190
|
-
<mat-label>
|
|
2191
|
-
<input matInput [(ngModel)]="cond.value" placeholder="
|
|
2398
|
+
<mat-label>{{ ui.text('value') }}</mat-label>
|
|
2399
|
+
<input matInput [(ngModel)]="cond.value" [placeholder]="ui.text('conditionValuePlaceholder')" />
|
|
2192
2400
|
</mat-form-field>
|
|
2193
2401
|
<button mat-icon-button color="warn" type="button" (click)="removeCondition($index)">
|
|
2194
2402
|
<mat-icon>delete</mat-icon>
|
|
@@ -2196,32 +2404,32 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2196
2404
|
</div>
|
|
2197
2405
|
}
|
|
2198
2406
|
<button mat-stroked-button type="button" (click)="addCondition()">
|
|
2199
|
-
<mat-icon>add</mat-icon>
|
|
2407
|
+
<mat-icon>add</mat-icon> {{ ui.text('addCondition') }}
|
|
2200
2408
|
</button>
|
|
2201
2409
|
</div>
|
|
2202
2410
|
|
|
2203
2411
|
<!-- Action -->
|
|
2204
2412
|
<div class="deb-rule-section">
|
|
2205
|
-
<h4>
|
|
2413
|
+
<h4>{{ ui.text('action') }}</h4>
|
|
2206
2414
|
<div class="deb-rule-row">
|
|
2207
2415
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
2208
|
-
<mat-label>
|
|
2416
|
+
<mat-label>{{ ui.text('actionType') }}</mat-label>
|
|
2209
2417
|
<mat-select [(ngModel)]="rule.action.type">
|
|
2210
|
-
<mat-option value="visibility">
|
|
2211
|
-
<mat-option value="validation">
|
|
2212
|
-
<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>
|
|
2213
2421
|
</mat-select>
|
|
2214
2422
|
</mat-form-field>
|
|
2215
2423
|
<mat-form-field appearance="outline" subscriptSizing="dynamic">
|
|
2216
|
-
<mat-label>
|
|
2217
|
-
<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')" />
|
|
2218
2426
|
</mat-form-field>
|
|
2219
2427
|
</div>
|
|
2220
2428
|
</div>
|
|
2221
2429
|
|
|
2222
2430
|
<div class="deb-rule-form__actions">
|
|
2223
|
-
<button mat-button type="button" (click)="cancel.emit()">
|
|
2224
|
-
<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>
|
|
2225
2433
|
</div>
|
|
2226
2434
|
</div>
|
|
2227
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"] }]
|
|
@@ -2238,6 +2446,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2238
2446
|
* enable/disable, edit, delete. `RuleFormComponent` authors one rule; this owns the set.
|
|
2239
2447
|
*/
|
|
2240
2448
|
class FieldRulesListComponent {
|
|
2449
|
+
/** Builder chrome, overridable via BUILDER_TEXT. */
|
|
2450
|
+
ui = inject(BuilderTextService);
|
|
2241
2451
|
store = inject(BuilderStore);
|
|
2242
2452
|
/** The rule currently open in the editor — a new draft or a copy of an existing rule. */
|
|
2243
2453
|
editing = signal(null, ...(ngDevMode ? [{ debugName: "editing" }] : /* istanbul ignore next */ []));
|
|
@@ -2289,7 +2499,7 @@ class FieldRulesListComponent {
|
|
|
2289
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: `
|
|
2290
2500
|
<div class="deb-rules">
|
|
2291
2501
|
<div class="deb-rules__head">
|
|
2292
|
-
<span class="deb-section-title">
|
|
2502
|
+
<span class="deb-section-title">{{ ui.text('rules') }}</span>
|
|
2293
2503
|
<button
|
|
2294
2504
|
mat-stroked-button
|
|
2295
2505
|
type="button"
|
|
@@ -2297,7 +2507,7 @@ class FieldRulesListComponent {
|
|
|
2297
2507
|
[disabled]="!store.selectedFieldId()"
|
|
2298
2508
|
(click)="startCreate()"
|
|
2299
2509
|
>
|
|
2300
|
-
<mat-icon>add</mat-icon>
|
|
2510
|
+
<mat-icon>add</mat-icon> {{ ui.text('rule') }}
|
|
2301
2511
|
</button>
|
|
2302
2512
|
</div>
|
|
2303
2513
|
|
|
@@ -2306,7 +2516,7 @@ class FieldRulesListComponent {
|
|
|
2306
2516
|
}
|
|
2307
2517
|
|
|
2308
2518
|
@if (store.rulesForSelectedField().length === 0 && !editing()) {
|
|
2309
|
-
<p class="deb-hint" data-testid="rules-empty">
|
|
2519
|
+
<p class="deb-hint" data-testid="rules-empty">{{ ui.text('noRulesOnField') }}</p>
|
|
2310
2520
|
}
|
|
2311
2521
|
|
|
2312
2522
|
@for (rule of store.rulesForSelectedField(); track rule.id) {
|
|
@@ -2314,19 +2524,20 @@ class FieldRulesListComponent {
|
|
|
2314
2524
|
<div class="deb-rule-item__body">
|
|
2315
2525
|
<span class="deb-rule-item__summary">{{ summarize(rule) }}</span>
|
|
2316
2526
|
<span class="deb-rule-item__meta">
|
|
2317
|
-
|
|
2527
|
+
{{ ui.text('rulePriority', { priority: rule.priority, targets: rule.targets.length }) }}
|
|
2318
2528
|
</span>
|
|
2319
2529
|
</div>
|
|
2320
2530
|
<div class="deb-rule-item__actions">
|
|
2321
2531
|
<mat-slide-toggle
|
|
2322
2532
|
[checked]="rule.enabled"
|
|
2323
|
-
matTooltip="
|
|
2533
|
+
[matTooltip]="ui.text('toggleRule')"
|
|
2324
2534
|
(change)="store.toggleRule(rule.id!, $event.checked)"
|
|
2325
2535
|
/>
|
|
2326
2536
|
<button
|
|
2327
2537
|
mat-icon-button
|
|
2328
2538
|
type="button"
|
|
2329
|
-
matTooltip="
|
|
2539
|
+
[matTooltip]="ui.text('moveUp')"
|
|
2540
|
+
[attr.aria-label]="ui.text('moveRuleUp')"
|
|
2330
2541
|
[attr.data-testid]="'rule-up-' + rule.id"
|
|
2331
2542
|
(click)="store.moveRule(rule.id!, -1)"
|
|
2332
2543
|
>
|
|
@@ -2335,7 +2546,8 @@ class FieldRulesListComponent {
|
|
|
2335
2546
|
<button
|
|
2336
2547
|
mat-icon-button
|
|
2337
2548
|
type="button"
|
|
2338
|
-
matTooltip="
|
|
2549
|
+
[matTooltip]="ui.text('moveDown')"
|
|
2550
|
+
[attr.aria-label]="ui.text('moveRuleDown')"
|
|
2339
2551
|
[attr.data-testid]="'rule-down-' + rule.id"
|
|
2340
2552
|
(click)="store.moveRule(rule.id!, 1)"
|
|
2341
2553
|
>
|
|
@@ -2344,7 +2556,7 @@ class FieldRulesListComponent {
|
|
|
2344
2556
|
<button
|
|
2345
2557
|
mat-icon-button
|
|
2346
2558
|
type="button"
|
|
2347
|
-
matTooltip="
|
|
2559
|
+
[matTooltip]="ui.text('editRule')"
|
|
2348
2560
|
[attr.data-testid]="'rule-edit-' + rule.id"
|
|
2349
2561
|
(click)="startEdit(rule)"
|
|
2350
2562
|
>
|
|
@@ -2354,7 +2566,7 @@ class FieldRulesListComponent {
|
|
|
2354
2566
|
mat-icon-button
|
|
2355
2567
|
type="button"
|
|
2356
2568
|
color="warn"
|
|
2357
|
-
matTooltip="
|
|
2569
|
+
[matTooltip]="ui.text('deleteRule')"
|
|
2358
2570
|
[attr.data-testid]="'rule-delete-' + rule.id"
|
|
2359
2571
|
(click)="store.removeRule(rule.id!)"
|
|
2360
2572
|
>
|
|
@@ -2371,7 +2583,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2371
2583
|
args: [{ changeDetection: ChangeDetectionStrategy.OnPush, selector: 'ngx-field-rules-list', standalone: true, imports: [MatButtonModule, MatIconModule, MatSlideToggleModule, MatTooltipModule, RuleFormComponent], template: `
|
|
2372
2584
|
<div class="deb-rules">
|
|
2373
2585
|
<div class="deb-rules__head">
|
|
2374
|
-
<span class="deb-section-title">
|
|
2586
|
+
<span class="deb-section-title">{{ ui.text('rules') }}</span>
|
|
2375
2587
|
<button
|
|
2376
2588
|
mat-stroked-button
|
|
2377
2589
|
type="button"
|
|
@@ -2379,7 +2591,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2379
2591
|
[disabled]="!store.selectedFieldId()"
|
|
2380
2592
|
(click)="startCreate()"
|
|
2381
2593
|
>
|
|
2382
|
-
<mat-icon>add</mat-icon>
|
|
2594
|
+
<mat-icon>add</mat-icon> {{ ui.text('rule') }}
|
|
2383
2595
|
</button>
|
|
2384
2596
|
</div>
|
|
2385
2597
|
|
|
@@ -2388,7 +2600,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2388
2600
|
}
|
|
2389
2601
|
|
|
2390
2602
|
@if (store.rulesForSelectedField().length === 0 && !editing()) {
|
|
2391
|
-
<p class="deb-hint" data-testid="rules-empty">
|
|
2603
|
+
<p class="deb-hint" data-testid="rules-empty">{{ ui.text('noRulesOnField') }}</p>
|
|
2392
2604
|
}
|
|
2393
2605
|
|
|
2394
2606
|
@for (rule of store.rulesForSelectedField(); track rule.id) {
|
|
@@ -2396,19 +2608,20 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2396
2608
|
<div class="deb-rule-item__body">
|
|
2397
2609
|
<span class="deb-rule-item__summary">{{ summarize(rule) }}</span>
|
|
2398
2610
|
<span class="deb-rule-item__meta">
|
|
2399
|
-
|
|
2611
|
+
{{ ui.text('rulePriority', { priority: rule.priority, targets: rule.targets.length }) }}
|
|
2400
2612
|
</span>
|
|
2401
2613
|
</div>
|
|
2402
2614
|
<div class="deb-rule-item__actions">
|
|
2403
2615
|
<mat-slide-toggle
|
|
2404
2616
|
[checked]="rule.enabled"
|
|
2405
|
-
matTooltip="
|
|
2617
|
+
[matTooltip]="ui.text('toggleRule')"
|
|
2406
2618
|
(change)="store.toggleRule(rule.id!, $event.checked)"
|
|
2407
2619
|
/>
|
|
2408
2620
|
<button
|
|
2409
2621
|
mat-icon-button
|
|
2410
2622
|
type="button"
|
|
2411
|
-
matTooltip="
|
|
2623
|
+
[matTooltip]="ui.text('moveUp')"
|
|
2624
|
+
[attr.aria-label]="ui.text('moveRuleUp')"
|
|
2412
2625
|
[attr.data-testid]="'rule-up-' + rule.id"
|
|
2413
2626
|
(click)="store.moveRule(rule.id!, -1)"
|
|
2414
2627
|
>
|
|
@@ -2417,7 +2630,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2417
2630
|
<button
|
|
2418
2631
|
mat-icon-button
|
|
2419
2632
|
type="button"
|
|
2420
|
-
matTooltip="
|
|
2633
|
+
[matTooltip]="ui.text('moveDown')"
|
|
2634
|
+
[attr.aria-label]="ui.text('moveRuleDown')"
|
|
2421
2635
|
[attr.data-testid]="'rule-down-' + rule.id"
|
|
2422
2636
|
(click)="store.moveRule(rule.id!, 1)"
|
|
2423
2637
|
>
|
|
@@ -2426,7 +2640,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2426
2640
|
<button
|
|
2427
2641
|
mat-icon-button
|
|
2428
2642
|
type="button"
|
|
2429
|
-
matTooltip="
|
|
2643
|
+
[matTooltip]="ui.text('editRule')"
|
|
2430
2644
|
[attr.data-testid]="'rule-edit-' + rule.id"
|
|
2431
2645
|
(click)="startEdit(rule)"
|
|
2432
2646
|
>
|
|
@@ -2436,7 +2650,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2436
2650
|
mat-icon-button
|
|
2437
2651
|
type="button"
|
|
2438
2652
|
color="warn"
|
|
2439
|
-
matTooltip="
|
|
2653
|
+
[matTooltip]="ui.text('deleteRule')"
|
|
2440
2654
|
[attr.data-testid]="'rule-delete-' + rule.id"
|
|
2441
2655
|
(click)="store.removeRule(rule.id!)"
|
|
2442
2656
|
>
|
|
@@ -2450,6 +2664,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2450
2664
|
}] });
|
|
2451
2665
|
|
|
2452
2666
|
class ReferencedFieldConfigComponent {
|
|
2667
|
+
/** Builder chrome, overridable via BUILDER_TEXT. */
|
|
2668
|
+
ui = inject(BuilderTextService);
|
|
2453
2669
|
store = inject(BuilderStore);
|
|
2454
2670
|
configSource = inject(ConfigSourceService, { optional: true });
|
|
2455
2671
|
field = computed(() => this.store.selectedField(), ...(ngDevMode ? [{ debugName: "field" }] : /* istanbul ignore next */ []));
|
|
@@ -2528,56 +2744,54 @@ class ReferencedFieldConfigComponent {
|
|
|
2528
2744
|
@if (field(); as f) {
|
|
2529
2745
|
<div class="deb-referenced-field" style="margin-top: 16px;">
|
|
2530
2746
|
<div class="deb-row" style="justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
2531
|
-
<span class="deb-section-title" style="margin:0;">
|
|
2747
|
+
<span class="deb-section-title" style="margin:0;">{{ ui.text('referencedField') }}</span>
|
|
2532
2748
|
<mat-slide-toggle
|
|
2533
2749
|
data-testid="toggle-referenced"
|
|
2534
2750
|
[ngModel]="f.isReferenced ?? false"
|
|
2535
2751
|
(ngModelChange)="toggleReferenced(f, $event)"
|
|
2536
2752
|
>
|
|
2537
|
-
|
|
2753
|
+
{{ ui.text('link') }}
|
|
2538
2754
|
</mat-slide-toggle>
|
|
2539
2755
|
</div>
|
|
2540
2756
|
|
|
2541
2757
|
@if (f.isReferenced) {
|
|
2542
2758
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full" style="margin-bottom: 8px;">
|
|
2543
|
-
<mat-label>
|
|
2759
|
+
<mat-label>{{ ui.text('sourceEntityKey') }}</mat-label>
|
|
2544
2760
|
<input
|
|
2545
2761
|
matInput
|
|
2546
2762
|
data-testid="referenced-entity-key"
|
|
2547
2763
|
[ngModel]="f.referencedEntityKey ?? ''"
|
|
2548
2764
|
(ngModelChange)="updateEntityKey(f, $event)"
|
|
2549
|
-
placeholder="
|
|
2765
|
+
[placeholder]="ui.text('sourceEntityKeyPlaceholder')"
|
|
2550
2766
|
/>
|
|
2551
2767
|
</mat-form-field>
|
|
2552
2768
|
|
|
2553
2769
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full" style="margin-bottom: 8px;">
|
|
2554
|
-
<mat-label>
|
|
2770
|
+
<mat-label>{{ ui.text('sourceFieldId') }}</mat-label>
|
|
2555
2771
|
<input
|
|
2556
2772
|
matInput
|
|
2557
2773
|
data-testid="referenced-field-id"
|
|
2558
2774
|
[ngModel]="f.referencedFieldId ?? ''"
|
|
2559
2775
|
(ngModelChange)="updateFieldId(f, $event)"
|
|
2560
|
-
placeholder="
|
|
2776
|
+
[placeholder]="ui.text('sourceFieldIdPlaceholder')"
|
|
2561
2777
|
/>
|
|
2562
2778
|
</mat-form-field>
|
|
2563
2779
|
|
|
2564
2780
|
@if (f.hasDrift) {
|
|
2565
|
-
<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
|
+
>
|
|
2566
2786
|
<div class="deb-row" style="gap: 8px; align-items: center; color: #e65100; font-weight: 500;">
|
|
2567
2787
|
<mat-icon style="font-size: 18px; width:18px; height:18px;">warning</mat-icon>
|
|
2568
|
-
<span>
|
|
2788
|
+
<span>{{ ui.text('driftHeading') }}</span>
|
|
2569
2789
|
</div>
|
|
2570
2790
|
<p style="margin: 4px 0 8px 0; font-size: 12px; color: #666;">
|
|
2571
|
-
|
|
2791
|
+
{{ ui.text('driftBody', { entity: f.referencedEntityKey ?? '' }) }}
|
|
2572
2792
|
</p>
|
|
2573
|
-
<button
|
|
2574
|
-
|
|
2575
|
-
color="warn"
|
|
2576
|
-
type="button"
|
|
2577
|
-
data-testid="sync-source-btn"
|
|
2578
|
-
(click)="syncWithSource(f)"
|
|
2579
|
-
>
|
|
2580
|
-
Sync with Source
|
|
2793
|
+
<button mat-flat-button color="warn" type="button" data-testid="sync-source-btn" (click)="syncWithSource(f)">
|
|
2794
|
+
{{ ui.text('syncWithSource') }}
|
|
2581
2795
|
</button>
|
|
2582
2796
|
</div>
|
|
2583
2797
|
}
|
|
@@ -2606,56 +2820,54 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2606
2820
|
@if (field(); as f) {
|
|
2607
2821
|
<div class="deb-referenced-field" style="margin-top: 16px;">
|
|
2608
2822
|
<div class="deb-row" style="justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
|
2609
|
-
<span class="deb-section-title" style="margin:0;">
|
|
2823
|
+
<span class="deb-section-title" style="margin:0;">{{ ui.text('referencedField') }}</span>
|
|
2610
2824
|
<mat-slide-toggle
|
|
2611
2825
|
data-testid="toggle-referenced"
|
|
2612
2826
|
[ngModel]="f.isReferenced ?? false"
|
|
2613
2827
|
(ngModelChange)="toggleReferenced(f, $event)"
|
|
2614
2828
|
>
|
|
2615
|
-
|
|
2829
|
+
{{ ui.text('link') }}
|
|
2616
2830
|
</mat-slide-toggle>
|
|
2617
2831
|
</div>
|
|
2618
2832
|
|
|
2619
2833
|
@if (f.isReferenced) {
|
|
2620
2834
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full" style="margin-bottom: 8px;">
|
|
2621
|
-
<mat-label>
|
|
2835
|
+
<mat-label>{{ ui.text('sourceEntityKey') }}</mat-label>
|
|
2622
2836
|
<input
|
|
2623
2837
|
matInput
|
|
2624
2838
|
data-testid="referenced-entity-key"
|
|
2625
2839
|
[ngModel]="f.referencedEntityKey ?? ''"
|
|
2626
2840
|
(ngModelChange)="updateEntityKey(f, $event)"
|
|
2627
|
-
placeholder="
|
|
2841
|
+
[placeholder]="ui.text('sourceEntityKeyPlaceholder')"
|
|
2628
2842
|
/>
|
|
2629
2843
|
</mat-form-field>
|
|
2630
2844
|
|
|
2631
2845
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-full" style="margin-bottom: 8px;">
|
|
2632
|
-
<mat-label>
|
|
2846
|
+
<mat-label>{{ ui.text('sourceFieldId') }}</mat-label>
|
|
2633
2847
|
<input
|
|
2634
2848
|
matInput
|
|
2635
2849
|
data-testid="referenced-field-id"
|
|
2636
2850
|
[ngModel]="f.referencedFieldId ?? ''"
|
|
2637
2851
|
(ngModelChange)="updateFieldId(f, $event)"
|
|
2638
|
-
placeholder="
|
|
2852
|
+
[placeholder]="ui.text('sourceFieldIdPlaceholder')"
|
|
2639
2853
|
/>
|
|
2640
2854
|
</mat-form-field>
|
|
2641
2855
|
|
|
2642
2856
|
@if (f.hasDrift) {
|
|
2643
|
-
<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
|
+
>
|
|
2644
2862
|
<div class="deb-row" style="gap: 8px; align-items: center; color: #e65100; font-weight: 500;">
|
|
2645
2863
|
<mat-icon style="font-size: 18px; width:18px; height:18px;">warning</mat-icon>
|
|
2646
|
-
<span>
|
|
2864
|
+
<span>{{ ui.text('driftHeading') }}</span>
|
|
2647
2865
|
</div>
|
|
2648
2866
|
<p style="margin: 4px 0 8px 0; font-size: 12px; color: #666;">
|
|
2649
|
-
|
|
2867
|
+
{{ ui.text('driftBody', { entity: f.referencedEntityKey ?? '' }) }}
|
|
2650
2868
|
</p>
|
|
2651
|
-
<button
|
|
2652
|
-
|
|
2653
|
-
color="warn"
|
|
2654
|
-
type="button"
|
|
2655
|
-
data-testid="sync-source-btn"
|
|
2656
|
-
(click)="syncWithSource(f)"
|
|
2657
|
-
>
|
|
2658
|
-
Sync with Source
|
|
2869
|
+
<button mat-flat-button color="warn" type="button" data-testid="sync-source-btn" (click)="syncWithSource(f)">
|
|
2870
|
+
{{ ui.text('syncWithSource') }}
|
|
2659
2871
|
</button>
|
|
2660
2872
|
</div>
|
|
2661
2873
|
}
|
|
@@ -2670,6 +2882,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2670
2882
|
* FieldInspectorComponent — edits every property of the currently selected field.
|
|
2671
2883
|
*/
|
|
2672
2884
|
class FieldInspectorComponent {
|
|
2885
|
+
/** Builder chrome, overridable via BUILDER_TEXT. */
|
|
2886
|
+
ui = inject(BuilderTextService);
|
|
2673
2887
|
store = inject(BuilderStore);
|
|
2674
2888
|
field = this.store.selectedField;
|
|
2675
2889
|
meta = computed(() => {
|
|
@@ -2806,7 +3020,7 @@ class FieldInspectorComponent {
|
|
|
2806
3020
|
return String(value);
|
|
2807
3021
|
}
|
|
2808
3022
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FieldInspectorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
2809
|
-
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" }] });
|
|
2810
3024
|
}
|
|
2811
3025
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: FieldInspectorComponent, decorators: [{
|
|
2812
3026
|
type: Component,
|
|
@@ -2824,7 +3038,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2824
3038
|
EntityReferenceConfigComponent,
|
|
2825
3039
|
FieldRulesListComponent,
|
|
2826
3040
|
ReferencedFieldConfigComponent,
|
|
2827
|
-
], 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"] }]
|
|
2828
3042
|
}] });
|
|
2829
3043
|
|
|
2830
3044
|
/**
|
|
@@ -2879,8 +3093,12 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
2879
3093
|
* TabManagerComponent — add / rename / reorder / remove tabs & sub-tabs with systemDefault protection.
|
|
2880
3094
|
*/
|
|
2881
3095
|
class TabManagerComponent {
|
|
3096
|
+
/** Builder chrome, overridable via BUILDER_TEXT. */
|
|
3097
|
+
ui = inject(BuilderTextService);
|
|
2882
3098
|
store = inject(BuilderStore);
|
|
2883
|
-
canEditSystemDefaultsFn = inject(SYSTEM_DEFAULT_CAN_EDIT, {
|
|
3099
|
+
canEditSystemDefaultsFn = inject(SYSTEM_DEFAULT_CAN_EDIT, {
|
|
3100
|
+
optional: true,
|
|
3101
|
+
});
|
|
2884
3102
|
lang() {
|
|
2885
3103
|
return this.store.activeLanguage();
|
|
2886
3104
|
}
|
|
@@ -2902,21 +3120,21 @@ class TabManagerComponent {
|
|
|
2902
3120
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.22", type: TabManagerComponent, isStandalone: true, selector: "ngx-tab-manager", ngImport: i0, template: `
|
|
2903
3121
|
<div class="deb-tabs">
|
|
2904
3122
|
<div class="deb-tabs__head">
|
|
2905
|
-
<span class="deb-section-title">
|
|
3123
|
+
<span class="deb-section-title">{{ ui.text('tabsManager') }}</span>
|
|
2906
3124
|
<button mat-stroked-button type="button" (click)="store.addTab()">
|
|
2907
|
-
<mat-icon>add</mat-icon>
|
|
3125
|
+
<mat-icon>add</mat-icon> {{ ui.text('addTab') }}
|
|
2908
3126
|
</button>
|
|
2909
3127
|
</div>
|
|
2910
3128
|
|
|
2911
3129
|
@if (store.tabs().length === 0) {
|
|
2912
|
-
<p class="deb-hint">
|
|
3130
|
+
<p class="deb-hint">{{ ui.text('noTabs') }}</p>
|
|
2913
3131
|
}
|
|
2914
3132
|
|
|
2915
3133
|
@for (tab of store.tabs(); track tab.id; let i = $index, count = $count) {
|
|
2916
3134
|
<div class="deb-tab-card" [class.deb-tab-card--system]="tab.systemDefault">
|
|
2917
3135
|
<div class="deb-tabs__row" [attr.data-testid]="'tab-row-' + tab.id">
|
|
2918
3136
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-grow">
|
|
2919
|
-
<mat-label>{{ tab.id }} {{ tab.systemDefault ? '
|
|
3137
|
+
<mat-label>{{ tab.id }} {{ tab.systemDefault ? ui.text('systemTag') : '' }}</mat-label>
|
|
2920
3138
|
<input
|
|
2921
3139
|
matInput
|
|
2922
3140
|
[disabled]="!canEditTab(tab)"
|
|
@@ -2929,7 +3147,8 @@ class TabManagerComponent {
|
|
|
2929
3147
|
type="button"
|
|
2930
3148
|
[disabled]="i === 0 || !canEditTab(tab)"
|
|
2931
3149
|
(click)="store.moveTab(tab.id, -1)"
|
|
2932
|
-
matTooltip="
|
|
3150
|
+
[matTooltip]="ui.text('moveUp')"
|
|
3151
|
+
[attr.aria-label]="ui.text('moveTabUp')"
|
|
2933
3152
|
>
|
|
2934
3153
|
<mat-icon>arrow_upward</mat-icon>
|
|
2935
3154
|
</button>
|
|
@@ -2938,7 +3157,8 @@ class TabManagerComponent {
|
|
|
2938
3157
|
type="button"
|
|
2939
3158
|
[disabled]="i === count - 1 || !canEditTab(tab)"
|
|
2940
3159
|
(click)="store.moveTab(tab.id, 1)"
|
|
2941
|
-
matTooltip="
|
|
3160
|
+
[matTooltip]="ui.text('moveDown')"
|
|
3161
|
+
[attr.aria-label]="ui.text('moveTabDown')"
|
|
2942
3162
|
>
|
|
2943
3163
|
<mat-icon>arrow_downward</mat-icon>
|
|
2944
3164
|
</button>
|
|
@@ -2948,7 +3168,8 @@ class TabManagerComponent {
|
|
|
2948
3168
|
color="warn"
|
|
2949
3169
|
[disabled]="!canEditTab(tab)"
|
|
2950
3170
|
(click)="store.removeTab(tab.id)"
|
|
2951
|
-
matTooltip="
|
|
3171
|
+
[matTooltip]="ui.text('removeTab')"
|
|
3172
|
+
[attr.aria-label]="ui.text('removeTab')"
|
|
2952
3173
|
>
|
|
2953
3174
|
<mat-icon>delete</mat-icon>
|
|
2954
3175
|
</button>
|
|
@@ -2961,47 +3182,41 @@ class TabManagerComponent {
|
|
|
2961
3182
|
[ngModel]="tab.flatData"
|
|
2962
3183
|
(ngModelChange)="store.updateTab(tab.id, { flatData: $event })"
|
|
2963
3184
|
>
|
|
2964
|
-
|
|
3185
|
+
{{ ui.text('flatData') }}
|
|
2965
3186
|
</mat-checkbox>
|
|
2966
3187
|
<mat-checkbox
|
|
2967
3188
|
[disabled]="!canEditTab(tab)"
|
|
2968
3189
|
[ngModel]="tab.isPrimaryTab"
|
|
2969
3190
|
(ngModelChange)="$event ? store.setPrimaryTab(tab.id) : store.updateTab(tab.id, { isPrimaryTab: false })"
|
|
2970
3191
|
>
|
|
2971
|
-
|
|
3192
|
+
{{ ui.text('primaryTab') }}
|
|
2972
3193
|
</mat-checkbox>
|
|
2973
3194
|
<mat-checkbox
|
|
2974
3195
|
[disabled]="!canEditTab(tab)"
|
|
2975
3196
|
[ngModel]="tab.maskData"
|
|
2976
3197
|
(ngModelChange)="store.updateTab(tab.id, { maskData: $event })"
|
|
2977
3198
|
>
|
|
2978
|
-
|
|
3199
|
+
{{ ui.text('maskTabData') }}
|
|
2979
3200
|
</mat-checkbox>
|
|
2980
3201
|
<mat-checkbox
|
|
2981
3202
|
[disabled]="!canEditTab(tab)"
|
|
2982
3203
|
[ngModel]="tab.systemDefault"
|
|
2983
3204
|
(ngModelChange)="store.updateTab(tab.id, { systemDefault: $event })"
|
|
2984
3205
|
>
|
|
2985
|
-
|
|
3206
|
+
{{ ui.text('systemDefault') }}
|
|
2986
3207
|
</mat-checkbox>
|
|
2987
|
-
<button
|
|
2988
|
-
|
|
2989
|
-
type="button"
|
|
2990
|
-
color="primary"
|
|
2991
|
-
[disabled]="!canEditTab(tab)"
|
|
2992
|
-
(click)="store.addSubTab(tab.id)"
|
|
2993
|
-
>
|
|
2994
|
-
+ Sub-tab
|
|
3208
|
+
<button mat-button type="button" color="primary" [disabled]="!canEditTab(tab)" (click)="store.addSubTab(tab.id)">
|
|
3209
|
+
{{ ui.text('addSubTab') }}
|
|
2995
3210
|
</button>
|
|
2996
3211
|
</div>
|
|
2997
3212
|
|
|
2998
3213
|
<!-- Module tab configuration -->
|
|
2999
3214
|
<div class="deb-tab-module">
|
|
3000
3215
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-grow">
|
|
3001
|
-
<mat-label>
|
|
3216
|
+
<mat-label>{{ ui.text('consumerModuleName') }}</mat-label>
|
|
3002
3217
|
<input
|
|
3003
3218
|
matInput
|
|
3004
|
-
placeholder="
|
|
3219
|
+
[placeholder]="ui.text('consumerModulePlaceholder')"
|
|
3005
3220
|
[disabled]="!canEditTab(tab)"
|
|
3006
3221
|
[ngModel]="tab.moduleName"
|
|
3007
3222
|
(ngModelChange)="store.updateTab(tab.id, { moduleName: $event || undefined })"
|
|
@@ -3012,7 +3227,7 @@ class TabManagerComponent {
|
|
|
3012
3227
|
<!-- Sub-tabs recursive render (Level 2) -->
|
|
3013
3228
|
@if (tab.children && tab.children.length > 0) {
|
|
3014
3229
|
<div class="deb-subtabs">
|
|
3015
|
-
<span class="deb-subtabs__title">
|
|
3230
|
+
<span class="deb-subtabs__title">{{ ui.text('subTabs') }}</span>
|
|
3016
3231
|
@for (sub of tab.children; track sub.id) {
|
|
3017
3232
|
<div class="deb-tabs__row deb-subtab-row" [attr.data-testid]="'subtab-row-' + sub.id">
|
|
3018
3233
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-grow">
|
|
@@ -3030,7 +3245,8 @@ class TabManagerComponent {
|
|
|
3030
3245
|
color="warn"
|
|
3031
3246
|
[disabled]="!canEditTab(sub)"
|
|
3032
3247
|
(click)="store.removeTab(sub.id)"
|
|
3033
|
-
matTooltip="
|
|
3248
|
+
[matTooltip]="ui.text('removeSubTab')"
|
|
3249
|
+
[attr.aria-label]="ui.text('removeSubTab')"
|
|
3034
3250
|
>
|
|
3035
3251
|
<mat-icon>delete</mat-icon>
|
|
3036
3252
|
</button>
|
|
@@ -3057,21 +3273,21 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3057
3273
|
], template: `
|
|
3058
3274
|
<div class="deb-tabs">
|
|
3059
3275
|
<div class="deb-tabs__head">
|
|
3060
|
-
<span class="deb-section-title">
|
|
3276
|
+
<span class="deb-section-title">{{ ui.text('tabsManager') }}</span>
|
|
3061
3277
|
<button mat-stroked-button type="button" (click)="store.addTab()">
|
|
3062
|
-
<mat-icon>add</mat-icon>
|
|
3278
|
+
<mat-icon>add</mat-icon> {{ ui.text('addTab') }}
|
|
3063
3279
|
</button>
|
|
3064
3280
|
</div>
|
|
3065
3281
|
|
|
3066
3282
|
@if (store.tabs().length === 0) {
|
|
3067
|
-
<p class="deb-hint">
|
|
3283
|
+
<p class="deb-hint">{{ ui.text('noTabs') }}</p>
|
|
3068
3284
|
}
|
|
3069
3285
|
|
|
3070
3286
|
@for (tab of store.tabs(); track tab.id; let i = $index, count = $count) {
|
|
3071
3287
|
<div class="deb-tab-card" [class.deb-tab-card--system]="tab.systemDefault">
|
|
3072
3288
|
<div class="deb-tabs__row" [attr.data-testid]="'tab-row-' + tab.id">
|
|
3073
3289
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-grow">
|
|
3074
|
-
<mat-label>{{ tab.id }} {{ tab.systemDefault ? '
|
|
3290
|
+
<mat-label>{{ tab.id }} {{ tab.systemDefault ? ui.text('systemTag') : '' }}</mat-label>
|
|
3075
3291
|
<input
|
|
3076
3292
|
matInput
|
|
3077
3293
|
[disabled]="!canEditTab(tab)"
|
|
@@ -3084,7 +3300,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3084
3300
|
type="button"
|
|
3085
3301
|
[disabled]="i === 0 || !canEditTab(tab)"
|
|
3086
3302
|
(click)="store.moveTab(tab.id, -1)"
|
|
3087
|
-
matTooltip="
|
|
3303
|
+
[matTooltip]="ui.text('moveUp')"
|
|
3304
|
+
[attr.aria-label]="ui.text('moveTabUp')"
|
|
3088
3305
|
>
|
|
3089
3306
|
<mat-icon>arrow_upward</mat-icon>
|
|
3090
3307
|
</button>
|
|
@@ -3093,7 +3310,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3093
3310
|
type="button"
|
|
3094
3311
|
[disabled]="i === count - 1 || !canEditTab(tab)"
|
|
3095
3312
|
(click)="store.moveTab(tab.id, 1)"
|
|
3096
|
-
matTooltip="
|
|
3313
|
+
[matTooltip]="ui.text('moveDown')"
|
|
3314
|
+
[attr.aria-label]="ui.text('moveTabDown')"
|
|
3097
3315
|
>
|
|
3098
3316
|
<mat-icon>arrow_downward</mat-icon>
|
|
3099
3317
|
</button>
|
|
@@ -3103,7 +3321,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3103
3321
|
color="warn"
|
|
3104
3322
|
[disabled]="!canEditTab(tab)"
|
|
3105
3323
|
(click)="store.removeTab(tab.id)"
|
|
3106
|
-
matTooltip="
|
|
3324
|
+
[matTooltip]="ui.text('removeTab')"
|
|
3325
|
+
[attr.aria-label]="ui.text('removeTab')"
|
|
3107
3326
|
>
|
|
3108
3327
|
<mat-icon>delete</mat-icon>
|
|
3109
3328
|
</button>
|
|
@@ -3116,47 +3335,41 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3116
3335
|
[ngModel]="tab.flatData"
|
|
3117
3336
|
(ngModelChange)="store.updateTab(tab.id, { flatData: $event })"
|
|
3118
3337
|
>
|
|
3119
|
-
|
|
3338
|
+
{{ ui.text('flatData') }}
|
|
3120
3339
|
</mat-checkbox>
|
|
3121
3340
|
<mat-checkbox
|
|
3122
3341
|
[disabled]="!canEditTab(tab)"
|
|
3123
3342
|
[ngModel]="tab.isPrimaryTab"
|
|
3124
3343
|
(ngModelChange)="$event ? store.setPrimaryTab(tab.id) : store.updateTab(tab.id, { isPrimaryTab: false })"
|
|
3125
3344
|
>
|
|
3126
|
-
|
|
3345
|
+
{{ ui.text('primaryTab') }}
|
|
3127
3346
|
</mat-checkbox>
|
|
3128
3347
|
<mat-checkbox
|
|
3129
3348
|
[disabled]="!canEditTab(tab)"
|
|
3130
3349
|
[ngModel]="tab.maskData"
|
|
3131
3350
|
(ngModelChange)="store.updateTab(tab.id, { maskData: $event })"
|
|
3132
3351
|
>
|
|
3133
|
-
|
|
3352
|
+
{{ ui.text('maskTabData') }}
|
|
3134
3353
|
</mat-checkbox>
|
|
3135
3354
|
<mat-checkbox
|
|
3136
3355
|
[disabled]="!canEditTab(tab)"
|
|
3137
3356
|
[ngModel]="tab.systemDefault"
|
|
3138
3357
|
(ngModelChange)="store.updateTab(tab.id, { systemDefault: $event })"
|
|
3139
3358
|
>
|
|
3140
|
-
|
|
3359
|
+
{{ ui.text('systemDefault') }}
|
|
3141
3360
|
</mat-checkbox>
|
|
3142
|
-
<button
|
|
3143
|
-
|
|
3144
|
-
type="button"
|
|
3145
|
-
color="primary"
|
|
3146
|
-
[disabled]="!canEditTab(tab)"
|
|
3147
|
-
(click)="store.addSubTab(tab.id)"
|
|
3148
|
-
>
|
|
3149
|
-
+ Sub-tab
|
|
3361
|
+
<button mat-button type="button" color="primary" [disabled]="!canEditTab(tab)" (click)="store.addSubTab(tab.id)">
|
|
3362
|
+
{{ ui.text('addSubTab') }}
|
|
3150
3363
|
</button>
|
|
3151
3364
|
</div>
|
|
3152
3365
|
|
|
3153
3366
|
<!-- Module tab configuration -->
|
|
3154
3367
|
<div class="deb-tab-module">
|
|
3155
3368
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-grow">
|
|
3156
|
-
<mat-label>
|
|
3369
|
+
<mat-label>{{ ui.text('consumerModuleName') }}</mat-label>
|
|
3157
3370
|
<input
|
|
3158
3371
|
matInput
|
|
3159
|
-
placeholder="
|
|
3372
|
+
[placeholder]="ui.text('consumerModulePlaceholder')"
|
|
3160
3373
|
[disabled]="!canEditTab(tab)"
|
|
3161
3374
|
[ngModel]="tab.moduleName"
|
|
3162
3375
|
(ngModelChange)="store.updateTab(tab.id, { moduleName: $event || undefined })"
|
|
@@ -3167,7 +3380,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3167
3380
|
<!-- Sub-tabs recursive render (Level 2) -->
|
|
3168
3381
|
@if (tab.children && tab.children.length > 0) {
|
|
3169
3382
|
<div class="deb-subtabs">
|
|
3170
|
-
<span class="deb-subtabs__title">
|
|
3383
|
+
<span class="deb-subtabs__title">{{ ui.text('subTabs') }}</span>
|
|
3171
3384
|
@for (sub of tab.children; track sub.id) {
|
|
3172
3385
|
<div class="deb-tabs__row deb-subtab-row" [attr.data-testid]="'subtab-row-' + sub.id">
|
|
3173
3386
|
<mat-form-field appearance="outline" subscriptSizing="dynamic" class="deb-grow">
|
|
@@ -3185,7 +3398,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3185
3398
|
color="warn"
|
|
3186
3399
|
[disabled]="!canEditTab(sub)"
|
|
3187
3400
|
(click)="store.removeTab(sub.id)"
|
|
3188
|
-
matTooltip="
|
|
3401
|
+
[matTooltip]="ui.text('removeSubTab')"
|
|
3402
|
+
[attr.aria-label]="ui.text('removeSubTab')"
|
|
3189
3403
|
>
|
|
3190
3404
|
<mat-icon>delete</mat-icon>
|
|
3191
3405
|
</button>
|
|
@@ -3200,6 +3414,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3200
3414
|
}] });
|
|
3201
3415
|
|
|
3202
3416
|
class EntityBuilderTreeNodeComponent {
|
|
3417
|
+
/** Builder chrome, overridable via BUILDER_TEXT. */
|
|
3418
|
+
ui = inject(BuilderTextService);
|
|
3203
3419
|
store = inject(BuilderStore);
|
|
3204
3420
|
field;
|
|
3205
3421
|
index;
|
|
@@ -3227,58 +3443,89 @@ class EntityBuilderTreeNodeComponent {
|
|
|
3227
3443
|
role="button"
|
|
3228
3444
|
tabindex="0"
|
|
3229
3445
|
[attr.aria-pressed]="store.isSelected(field)"
|
|
3230
|
-
[attr.aria-label]="'
|
|
3446
|
+
[attr.aria-label]="ui.text('selectField', { field: fieldLabel(field) })"
|
|
3231
3447
|
>
|
|
3232
|
-
<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
|
+
>
|
|
3233
3451
|
<mat-icon class="deb-field-type" [matTooltip]="fieldTypeLabel(field.type)">{{ fieldTypeIcon(field.type) }}</mat-icon>
|
|
3234
3452
|
<div class="deb-field-meta">
|
|
3235
3453
|
<div class="deb-row" style="gap:6px">
|
|
3236
3454
|
<span class="deb-field-label" [attr.data-testid]="'row-label-' + field.id">{{ fieldLabel(field) }}</span>
|
|
3237
3455
|
<span class="deb-type-badge">{{ field.type }}</span>
|
|
3238
3456
|
@if (field.isReferenced) {
|
|
3239
|
-
<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
|
+
>
|
|
3240
3462
|
}
|
|
3241
3463
|
@if (field.hasDrift) {
|
|
3242
|
-
<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
|
+
>
|
|
3243
3470
|
}
|
|
3244
3471
|
</div>
|
|
3245
3472
|
<span class="deb-field-id" [attr.data-testid]="'row-id-' + field.id">{{ field.id }}</span>
|
|
3246
3473
|
</div>
|
|
3247
|
-
@if (field.validators?.required) {
|
|
3474
|
+
@if (field.validators?.required) {
|
|
3475
|
+
<span class="deb-req" [title]="ui.text('required')">*</span>
|
|
3476
|
+
}
|
|
3248
3477
|
|
|
3249
|
-
<button
|
|
3478
|
+
<button
|
|
3479
|
+
mat-icon-button
|
|
3480
|
+
type="button"
|
|
3481
|
+
[disabled]="index === 0"
|
|
3250
3482
|
[attr.data-testid]="'row-up-' + field.id"
|
|
3251
|
-
[attr.aria-label]="'
|
|
3252
|
-
(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
|
+
>
|
|
3253
3487
|
<mat-icon>arrow_upward</mat-icon>
|
|
3254
3488
|
</button>
|
|
3255
|
-
<button
|
|
3489
|
+
<button
|
|
3490
|
+
mat-icon-button
|
|
3491
|
+
type="button"
|
|
3492
|
+
[disabled]="index === totalCount - 1"
|
|
3256
3493
|
[attr.data-testid]="'row-down-' + field.id"
|
|
3257
|
-
[attr.aria-label]="'
|
|
3258
|
-
(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
|
+
>
|
|
3259
3498
|
<mat-icon>arrow_downward</mat-icon>
|
|
3260
3499
|
</button>
|
|
3261
|
-
<button
|
|
3500
|
+
<button
|
|
3501
|
+
mat-icon-button
|
|
3502
|
+
type="button"
|
|
3262
3503
|
[attr.data-testid]="'row-duplicate-' + field.id"
|
|
3263
|
-
[attr.aria-label]="'
|
|
3264
|
-
(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
|
+
>
|
|
3265
3508
|
<mat-icon>content_copy</mat-icon>
|
|
3266
3509
|
</button>
|
|
3267
|
-
<button
|
|
3510
|
+
<button
|
|
3511
|
+
mat-icon-button
|
|
3512
|
+
type="button"
|
|
3513
|
+
color="warn"
|
|
3268
3514
|
[attr.data-testid]="'row-delete-' + field.id"
|
|
3269
|
-
(click)="store.removeField(store.keyOf(field)); $event.stopPropagation()"
|
|
3515
|
+
(click)="store.removeField(store.keyOf(field)); $event.stopPropagation()"
|
|
3516
|
+
[matTooltip]="ui.text('delete')"
|
|
3517
|
+
>
|
|
3270
3518
|
<mat-icon>delete</mat-icon>
|
|
3271
3519
|
</button>
|
|
3272
3520
|
</div>
|
|
3273
3521
|
|
|
3274
3522
|
@if (field.children && field.children.length > 0) {
|
|
3275
|
-
<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
|
+
>
|
|
3276
3527
|
@for (child of field.children; track child.id; let ci = $index, cCount = $count) {
|
|
3277
|
-
<ngx-entity-builder-tree-node
|
|
3278
|
-
[field]="child"
|
|
3279
|
-
[index]="ci"
|
|
3280
|
-
[totalCount]="cCount"
|
|
3281
|
-
/>
|
|
3528
|
+
<ngx-entity-builder-tree-node [field]="child" [index]="ci" [totalCount]="cCount" />
|
|
3282
3529
|
}
|
|
3283
3530
|
</div>
|
|
3284
3531
|
}
|
|
@@ -3290,13 +3537,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3290
3537
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3291
3538
|
selector: 'ngx-entity-builder-tree-node',
|
|
3292
3539
|
standalone: true,
|
|
3293
|
-
imports: [
|
|
3294
|
-
CommonModule,
|
|
3295
|
-
DragDropModule,
|
|
3296
|
-
MatButtonModule,
|
|
3297
|
-
MatIconModule,
|
|
3298
|
-
MatTooltipModule,
|
|
3299
|
-
],
|
|
3540
|
+
imports: [CommonModule, DragDropModule, MatButtonModule, MatIconModule, MatTooltipModule],
|
|
3300
3541
|
template: `
|
|
3301
3542
|
<div
|
|
3302
3543
|
cdkDrag
|
|
@@ -3309,58 +3550,89 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3309
3550
|
role="button"
|
|
3310
3551
|
tabindex="0"
|
|
3311
3552
|
[attr.aria-pressed]="store.isSelected(field)"
|
|
3312
|
-
[attr.aria-label]="'
|
|
3553
|
+
[attr.aria-label]="ui.text('selectField', { field: fieldLabel(field) })"
|
|
3313
3554
|
>
|
|
3314
|
-
<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
|
+
>
|
|
3315
3558
|
<mat-icon class="deb-field-type" [matTooltip]="fieldTypeLabel(field.type)">{{ fieldTypeIcon(field.type) }}</mat-icon>
|
|
3316
3559
|
<div class="deb-field-meta">
|
|
3317
3560
|
<div class="deb-row" style="gap:6px">
|
|
3318
3561
|
<span class="deb-field-label" [attr.data-testid]="'row-label-' + field.id">{{ fieldLabel(field) }}</span>
|
|
3319
3562
|
<span class="deb-type-badge">{{ field.type }}</span>
|
|
3320
3563
|
@if (field.isReferenced) {
|
|
3321
|
-
<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
|
+
>
|
|
3322
3569
|
}
|
|
3323
3570
|
@if (field.hasDrift) {
|
|
3324
|
-
<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
|
+
>
|
|
3325
3577
|
}
|
|
3326
3578
|
</div>
|
|
3327
3579
|
<span class="deb-field-id" [attr.data-testid]="'row-id-' + field.id">{{ field.id }}</span>
|
|
3328
3580
|
</div>
|
|
3329
|
-
@if (field.validators?.required) {
|
|
3581
|
+
@if (field.validators?.required) {
|
|
3582
|
+
<span class="deb-req" [title]="ui.text('required')">*</span>
|
|
3583
|
+
}
|
|
3330
3584
|
|
|
3331
|
-
<button
|
|
3585
|
+
<button
|
|
3586
|
+
mat-icon-button
|
|
3587
|
+
type="button"
|
|
3588
|
+
[disabled]="index === 0"
|
|
3332
3589
|
[attr.data-testid]="'row-up-' + field.id"
|
|
3333
|
-
[attr.aria-label]="'
|
|
3334
|
-
(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
|
+
>
|
|
3335
3594
|
<mat-icon>arrow_upward</mat-icon>
|
|
3336
3595
|
</button>
|
|
3337
|
-
<button
|
|
3596
|
+
<button
|
|
3597
|
+
mat-icon-button
|
|
3598
|
+
type="button"
|
|
3599
|
+
[disabled]="index === totalCount - 1"
|
|
3338
3600
|
[attr.data-testid]="'row-down-' + field.id"
|
|
3339
|
-
[attr.aria-label]="'
|
|
3340
|
-
(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
|
+
>
|
|
3341
3605
|
<mat-icon>arrow_downward</mat-icon>
|
|
3342
3606
|
</button>
|
|
3343
|
-
<button
|
|
3607
|
+
<button
|
|
3608
|
+
mat-icon-button
|
|
3609
|
+
type="button"
|
|
3344
3610
|
[attr.data-testid]="'row-duplicate-' + field.id"
|
|
3345
|
-
[attr.aria-label]="'
|
|
3346
|
-
(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
|
+
>
|
|
3347
3615
|
<mat-icon>content_copy</mat-icon>
|
|
3348
3616
|
</button>
|
|
3349
|
-
<button
|
|
3617
|
+
<button
|
|
3618
|
+
mat-icon-button
|
|
3619
|
+
type="button"
|
|
3620
|
+
color="warn"
|
|
3350
3621
|
[attr.data-testid]="'row-delete-' + field.id"
|
|
3351
|
-
(click)="store.removeField(store.keyOf(field)); $event.stopPropagation()"
|
|
3622
|
+
(click)="store.removeField(store.keyOf(field)); $event.stopPropagation()"
|
|
3623
|
+
[matTooltip]="ui.text('delete')"
|
|
3624
|
+
>
|
|
3352
3625
|
<mat-icon>delete</mat-icon>
|
|
3353
3626
|
</button>
|
|
3354
3627
|
</div>
|
|
3355
3628
|
|
|
3356
3629
|
@if (field.children && field.children.length > 0) {
|
|
3357
|
-
<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
|
+
>
|
|
3358
3634
|
@for (child of field.children; track child.id; let ci = $index, cCount = $count) {
|
|
3359
|
-
<ngx-entity-builder-tree-node
|
|
3360
|
-
[field]="child"
|
|
3361
|
-
[index]="ci"
|
|
3362
|
-
[totalCount]="cCount"
|
|
3363
|
-
/>
|
|
3635
|
+
<ngx-entity-builder-tree-node [field]="child" [index]="ci" [totalCount]="cCount" />
|
|
3364
3636
|
}
|
|
3365
3637
|
</div>
|
|
3366
3638
|
}
|
|
@@ -3378,6 +3650,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3378
3650
|
}] } });
|
|
3379
3651
|
|
|
3380
3652
|
class EntityBuilderCanvasComponent {
|
|
3653
|
+
/** Builder chrome, overridable via BUILDER_TEXT. */
|
|
3654
|
+
ui = inject(BuilderTextService);
|
|
3381
3655
|
store = inject(BuilderStore);
|
|
3382
3656
|
/**
|
|
3383
3657
|
* Headings only earn their space once the fields are actually split across tabs. A
|
|
@@ -3396,13 +3670,13 @@ class EntityBuilderCanvasComponent {
|
|
|
3396
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: `
|
|
3397
3671
|
<mat-card class="deb-canvas">
|
|
3398
3672
|
<mat-card-header>
|
|
3399
|
-
<mat-card-title>
|
|
3673
|
+
<mat-card-title>{{ ui.text('fieldsHeading', { count: store.fields().length }) }}</mat-card-title>
|
|
3400
3674
|
</mat-card-header>
|
|
3401
3675
|
<mat-card-content>
|
|
3402
3676
|
@if (store.fields().length === 0) {
|
|
3403
3677
|
<div class="deb-empty">
|
|
3404
3678
|
<mat-icon>widgets</mat-icon>
|
|
3405
|
-
<p>
|
|
3679
|
+
<p>{{ ui.text('canvasEmpty') }}</p>
|
|
3406
3680
|
</div>
|
|
3407
3681
|
}
|
|
3408
3682
|
|
|
@@ -3424,11 +3698,7 @@ class EntityBuilderCanvasComponent {
|
|
|
3424
3698
|
(cdkDropListDropped)="onDrop(group.tabId, $event)"
|
|
3425
3699
|
>
|
|
3426
3700
|
@for (f of group.fields; track f.id; let i = $index, count = $count) {
|
|
3427
|
-
<ngx-entity-builder-tree-node
|
|
3428
|
-
[field]="f"
|
|
3429
|
-
[index]="i"
|
|
3430
|
-
[totalCount]="count"
|
|
3431
|
-
/>
|
|
3701
|
+
<ngx-entity-builder-tree-node [field]="f" [index]="i" [totalCount]="count" />
|
|
3432
3702
|
}
|
|
3433
3703
|
</div>
|
|
3434
3704
|
}
|
|
@@ -3442,23 +3712,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3442
3712
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
3443
3713
|
selector: 'ngx-entity-builder-canvas',
|
|
3444
3714
|
standalone: true,
|
|
3445
|
-
imports: [
|
|
3446
|
-
CommonModule,
|
|
3447
|
-
DragDropModule,
|
|
3448
|
-
MatCardModule,
|
|
3449
|
-
MatIconModule,
|
|
3450
|
-
EntityBuilderTreeNodeComponent,
|
|
3451
|
-
],
|
|
3715
|
+
imports: [CommonModule, DragDropModule, MatCardModule, MatIconModule, EntityBuilderTreeNodeComponent],
|
|
3452
3716
|
template: `
|
|
3453
3717
|
<mat-card class="deb-canvas">
|
|
3454
3718
|
<mat-card-header>
|
|
3455
|
-
<mat-card-title>
|
|
3719
|
+
<mat-card-title>{{ ui.text('fieldsHeading', { count: store.fields().length }) }}</mat-card-title>
|
|
3456
3720
|
</mat-card-header>
|
|
3457
3721
|
<mat-card-content>
|
|
3458
3722
|
@if (store.fields().length === 0) {
|
|
3459
3723
|
<div class="deb-empty">
|
|
3460
3724
|
<mat-icon>widgets</mat-icon>
|
|
3461
|
-
<p>
|
|
3725
|
+
<p>{{ ui.text('canvasEmpty') }}</p>
|
|
3462
3726
|
</div>
|
|
3463
3727
|
}
|
|
3464
3728
|
|
|
@@ -3480,11 +3744,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3480
3744
|
(cdkDropListDropped)="onDrop(group.tabId, $event)"
|
|
3481
3745
|
>
|
|
3482
3746
|
@for (f of group.fields; track f.id; let i = $index, count = $count) {
|
|
3483
|
-
<ngx-entity-builder-tree-node
|
|
3484
|
-
[field]="f"
|
|
3485
|
-
[index]="i"
|
|
3486
|
-
[totalCount]="count"
|
|
3487
|
-
/>
|
|
3747
|
+
<ngx-entity-builder-tree-node [field]="f" [index]="i" [totalCount]="count" />
|
|
3488
3748
|
}
|
|
3489
3749
|
</div>
|
|
3490
3750
|
}
|
|
@@ -3502,6 +3762,8 @@ const EMPTY_ROLES = Object.freeze([]);
|
|
|
3502
3762
|
* EntityBuilderComponent — the top-level visual builder for an EntityFormConfig.
|
|
3503
3763
|
*/
|
|
3504
3764
|
class EntityBuilderComponent {
|
|
3765
|
+
/** Builder chrome, overridable via BUILDER_TEXT. */
|
|
3766
|
+
ui = inject(BuilderTextService);
|
|
3505
3767
|
store = inject(BuilderStore);
|
|
3506
3768
|
/**
|
|
3507
3769
|
* Optional: only apps that registered a CONFIG_SOURCE have one. Used to drop the cached
|
|
@@ -3512,6 +3774,16 @@ class EntityBuilderComponent {
|
|
|
3512
3774
|
config;
|
|
3513
3775
|
/** Languages available for label/placeholder editing. First entry is the default. */
|
|
3514
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
|
+
}
|
|
3515
3787
|
/** Optional role list — enables multi-select role pickers for RBAC instead of free text. */
|
|
3516
3788
|
availableRoles = [];
|
|
3517
3789
|
/**
|
|
@@ -3601,7 +3873,7 @@ class EntityBuilderComponent {
|
|
|
3601
3873
|
this.store.setPermission(action, roles);
|
|
3602
3874
|
}
|
|
3603
3875
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityBuilderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
3604
|
-
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 });
|
|
3605
3877
|
}
|
|
3606
3878
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImport: i0, type: EntityBuilderComponent, decorators: [{
|
|
3607
3879
|
type: Component,
|
|
@@ -3622,11 +3894,13 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3622
3894
|
TabManagerComponent,
|
|
3623
3895
|
FieldInspectorComponent,
|
|
3624
3896
|
EntityBuilderCanvasComponent,
|
|
3625
|
-
], 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"] }]
|
|
3626
3898
|
}], ctorParameters: () => [], propDecorators: { config: [{
|
|
3627
3899
|
type: Input
|
|
3628
3900
|
}], languages: [{
|
|
3629
3901
|
type: Input
|
|
3902
|
+
}], uiLanguage: [{
|
|
3903
|
+
type: Input
|
|
3630
3904
|
}], availableRoles: [{
|
|
3631
3905
|
type: Input
|
|
3632
3906
|
}], userRoles: [{
|
|
@@ -3648,5 +3922,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.22", ngImpo
|
|
|
3648
3922
|
* Generated bundle index. Do not edit.
|
|
3649
3923
|
*/
|
|
3650
3924
|
|
|
3651
|
-
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 };
|
|
3652
3926
|
//# sourceMappingURL=ngx-dynamic-entity-builder.mjs.map
|