ngx-dynamic-entity-builder 1.6.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ngx-dynamic-entity-builder",
3
- "version": "1.6.0",
3
+ "version": "1.8.0",
4
4
  "description": "Angular standalone visual builder for authoring dynamic-entity EntityConfig (fields, tabs, validators, RBAC).",
5
5
  "//typings": "Local workspace resolution only: ng-packagr 21 writes types/ngx-dynamic-entity-builder.d.ts into dist/package.json. Do NOT add a 'main' field here — ng-packagr does not generate one, so it would be copied verbatim and resolve wrongly in the published package.",
6
6
  "typings": "types/ngx-dynamic-entity-builder.d.ts",
@@ -10,8 +10,8 @@
10
10
  "@angular/core": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
11
11
  "@angular/forms": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
12
12
  "@angular/material": "^17.0.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0 || ^22.0.0",
13
- "@dynamic-entity/core": "^1.6.0",
14
- "ngx-dynamic-entity": "^1.6.0",
13
+ "@dynamic-entity/core": "^1.8.0",
14
+ "ngx-dynamic-entity": "^1.8.0",
15
15
  "rxjs": "^7.0.0"
16
16
  },
17
17
  "keywords": [
@@ -46,6 +46,39 @@ declare class BuilderStore {
46
46
  * made any role-checking predicate answer false for everyone.
47
47
  */
48
48
  private readonly _userRoles;
49
+ private readonly history;
50
+ private readonly cursor;
51
+ private readonly historyLength;
52
+ private lastEditAt;
53
+ /** Consecutive edits closer together than this fold into one undo step. */
54
+ private static readonly COALESCE_MS;
55
+ readonly canUndo: _angular_core.Signal<boolean>;
56
+ readonly canRedo: _angular_core.Signal<boolean>;
57
+ /**
58
+ * Fold the current state into history.
59
+ *
60
+ * Called explicitly by the write paths rather than from an `effect`. An effect would have
61
+ * been fewer call sites, but it requires an injection context: `BuilderStore` had no
62
+ * constructor, so `new BuilderStore()` was legal, and adding one broke every caller that
63
+ * did it with NG0203. Explicit calls also make the timing deterministic — a test can
64
+ * assert straight after an edit instead of flushing effects first.
65
+ *
66
+ * Calling it twice for one operation is harmless: the second call sees the same signal
67
+ * references already at the cursor and returns.
68
+ *
69
+ * `setFieldLabel` is bound to a keystroke, so recording every emission would make undo
70
+ * walk back one character at a time. Two consecutive edits merge when they land inside
71
+ * `COALESCE_MS` *and* the structure is unchanged — a rename coalesces, while adding,
72
+ * removing or moving anything always earns its own step however fast it is clicked.
73
+ */
74
+ private record;
75
+ /** Counts that distinguish a structural edit from a value edit. */
76
+ private shapeOf;
77
+ /** Start history again from the state just loaded. Nothing before it is undoable. */
78
+ private resetHistory;
79
+ undo(): void;
80
+ redo(): void;
81
+ private applyHistory;
49
82
  readonly config: _angular_core.Signal<EntityFormConfig>;
50
83
  readonly selectedFieldId: _angular_core.Signal<string>;
51
84
  readonly activeLanguage: _angular_core.Signal<string>;
@@ -109,7 +142,36 @@ declare class BuilderStore {
109
142
  * therefore see deeper than `fields()`, which stops at a tab's own fields.
110
143
  */
111
144
  private allFieldIds;
145
+ /**
146
+ * Resolve a field by key, where a key is either its `refererField` path or a bare id.
147
+ *
148
+ * Every resolution in this store funnels through here, which is why a bare id was enough
149
+ * to edit the wrong field: with `personal.address` and `work.address` both present, the
150
+ * first match won, so selecting or editing the second one silently addressed the first.
151
+ *
152
+ * The path is tried across the whole tree before any bare id is, so an exact key never
153
+ * loses to a nearer-but-wrong match. Bare ids still resolve — every existing caller and
154
+ * spec passes one, and for an unambiguous id it is the same field either way.
155
+ */
112
156
  private findFieldInTabs;
157
+ private findFieldBy;
158
+ /**
159
+ * The key a caller should hand back to address this exact field. Templates use it so a
160
+ * click on the second of two same-named fields selects that one.
161
+ */
162
+ keyOf(field: {
163
+ id: string;
164
+ refererField?: string;
165
+ }): string;
166
+ /**
167
+ * Whether this exact field is the selected one.
168
+ *
169
+ * Not `selectedFieldId() === field.id`: selection may hold a bare id or a path depending
170
+ * on who set it, and with two fields sharing an id a bare comparison lights up both rows
171
+ * — or neither, once the click starts storing a path. Resolving both sides answers for
172
+ * the field itself.
173
+ */
174
+ isSelected(field: NestedFieldConfig): boolean;
113
175
  addField(type: RichFieldType, targetTabId?: string): string;
114
176
  updateField(id: string, patch: Partial<NestedFieldConfig>): void;
115
177
  /**
@@ -129,10 +191,16 @@ declare class BuilderStore {
129
191
  * That was survivable while renaming was a rare manual act; label-derived ids make it
130
192
  * routine, so the references move with it.
131
193
  */
194
+ /**
195
+ * `key` addresses the field to rename; `oldId` is the bare id every *reference* to it
196
+ * carries. They were one parameter, which worked only while an id was unique across the
197
+ * config: resolving needs the path, while `showWhen`, `parentField`, `autoPatch` targets
198
+ * and rules are all keyed by bare id and never match a path.
199
+ */
132
200
  private applyRename;
133
- removeField(id: string): void;
201
+ removeField(key: string): void;
134
202
  duplicateField(id: string): string | null;
135
- moveField(id: string, direction: -1 | 1): void;
203
+ moveField(key: string, direction: -1 | 1): void;
136
204
  /**
137
205
  * Moves a field to another tab, keeping its id.
138
206
  *
@@ -160,7 +228,7 @@ declare class BuilderStore {
160
228
  * Only the config's default language drives the id; translating a label must not rename
161
229
  * anything.
162
230
  */
163
- setFieldLabel(id: string, language: string, value: string): void;
231
+ setFieldLabel(key: string, language: string, value: string): void;
164
232
  /** `derived`, or `derived_2`, `derived_3`… if another field already holds it. */
165
233
  private availableId;
166
234
  setFieldPlaceholder(id: string, language: string, value: string): void;
@@ -324,6 +392,13 @@ declare class EntityBuilderComponent implements OnChanges {
324
392
  save: EventEmitter<EntityFormConfig>;
325
393
  protected readonly rbacActions: (keyof EntityPermissions)[];
326
394
  constructor();
395
+ /**
396
+ * Ctrl/Cmd+Z and Ctrl/Cmd+Shift+Z, but only when the user is not typing.
397
+ *
398
+ * A textarea or input has its own undo stack, and hijacking it would make Ctrl+Z revert a
399
+ * whole structural edit when the author only wanted to take back a character.
400
+ */
401
+ protected onKeydown(event: KeyboardEvent): void;
327
402
  ngOnChanges(changes: SimpleChanges): void;
328
403
  protected doSave(): void;
329
404
  protected get json(): string;