@fagon/ngx-intellitoolx 16.0.4 → 16.0.6

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.
Files changed (24) hide show
  1. package/README.md +582 -13
  2. package/esm2022/lib/form-changes/can-component-deactivate.interface.mjs +2 -0
  3. package/esm2022/lib/form-changes/confirm-handler.type.mjs +2 -0
  4. package/esm2022/lib/form-changes/form-changes-tracker.service.mjs +58 -0
  5. package/esm2022/lib/form-changes/form-update-message-config.interface.mjs +2 -0
  6. package/esm2022/lib/form-changes/form-update-message.component.mjs +53 -0
  7. package/esm2022/lib/form-changes/unsaved-changes.guard.mjs +29 -0
  8. package/esm2022/lib/helpers/intellitoolx.helper.mjs +2 -3
  9. package/esm2022/public-api.mjs +15 -14
  10. package/fesm2022/fagon-ngx-intellitoolx.mjs +197 -134
  11. package/fesm2022/fagon-ngx-intellitoolx.mjs.map +1 -1
  12. package/lib/form-changes/can-component-deactivate.interface.d.ts +5 -0
  13. package/lib/form-changes/form-changes-tracker.service.d.ts +15 -0
  14. package/lib/{form-update → form-changes}/unsaved-changes.guard.d.ts +5 -5
  15. package/lib/helpers/intellitoolx.helper.d.ts +1 -1
  16. package/package.json +1 -1
  17. package/public-api.d.ts +11 -9
  18. package/esm2022/lib/form-update/confirm-handler.type.mjs +0 -2
  19. package/esm2022/lib/form-update/form-update-message-config.interface.mjs +0 -2
  20. package/esm2022/lib/form-update/form-update-message.component.mjs +0 -53
  21. package/esm2022/lib/form-update/unsaved-changes.guard.mjs +0 -19
  22. /package/lib/{form-update → form-changes}/confirm-handler.type.d.ts +0 -0
  23. /package/lib/{form-update → form-changes}/form-update-message-config.interface.d.ts +0 -0
  24. /package/lib/{form-update → form-changes}/form-update-message.component.d.ts +0 -0
package/README.md CHANGED
@@ -1,10 +1,15 @@
1
1
  # IntelliToolx
2
2
 
3
+ ![npm version](https://img.shields.io/npm/v/@fagon/ngx-intellittolx.svg)
4
+ ![npm downloads](https://img.shields.io/npm/dm/@fagon/ngx-intellittolx.svg)
5
+ ![bundle size](https://img.shields.io/bundlephobia/minzip/@fagon/ngx-intellittolx)
3
6
  ![Angular](https://img.shields.io/badge/Angular-14%2B-red)
4
- ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue)
5
- ![License](https://img.shields.io/badge/License-MIT-green)
7
+ ![TypeScript](https://img.shields.io/badge/TypeScript-ready-blue)
8
+ ![License](https://img.shields.io/npm/l/@fagon/ngx-chronox)
6
9
  ![Reactive Forms](https://img.shields.io/badge/Reactive%20Forms-Supported-orange)
7
10
  ![Standalone Components](https://img.shields.io/badge/Standalone-Compatible-purple)
11
+ ![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)
12
+ ![Made with Love](https://img.shields.io/badge/made%20with-❤-red)
8
13
 
9
14
  ## Overview
10
15
 
@@ -17,7 +22,7 @@ Built with scalability, accessibility, and maintainability in mind, IntelliToolx
17
22
  ### Installation
18
23
 
19
24
  ```bash
20
- npm install intellitoolx
25
+ npm intall @fagon/ngx-intellitoolx
21
26
  ```
22
27
 
23
28
  # IntelliToolxHelper
@@ -562,7 +567,7 @@ The component includes built-in messages for common validators:
562
567
  | maxMonthYear | Date is later than allowed |
563
568
  | minMonthYear | Date is earlier than allowed |
564
569
  | exceededAllowedDateDifference | Date difference can only be one month |
565
- | exceededLeastDateAllowed | Start date cannot be greater than end date |
570
+ | startDateAfterEndDate | Start date cannot be greater than end date |
566
571
 
567
572
  ### Adding Control Labels (User-Friendly Messages)
568
573
 
@@ -675,12 +680,6 @@ The component intelligently handles different validator outputs:
675
680
  - String { customError: 'Invalid value provided' } → displays string directly
676
681
  - Object { minlength: { requiredLength: 5 } } → dynamic message rendering
677
682
 
678
- Built-in Smart Messages
679
-
680
- - Pattern Errors Display: `Please provide a valid {controlLabel}`
681
- - Min / Max Validators Display: `Age cannot be less than 18` / `Price cannot be greater than 100`
682
- - Ngb Date Errors Display: `Invalid date format provided`
683
-
684
683
  Best Practices
685
684
 
686
685
  - Always provide controlLabel for better UX
@@ -1124,7 +1123,7 @@ These validators return error keys compatible with the error component:
1124
1123
  | passwordMismatchValidator | passwordMismatch |
1125
1124
  | uniqueEmailsValidator | duplicateEmail |
1126
1125
 
1127
- Best Practices
1126
+ ** Best Practices **
1128
1127
 
1129
1128
  - Use with Reactive Forms only
1130
1129
  - Combine with required validators when necessary
@@ -1141,5 +1140,575 @@ this.form = new FormGroup({
1141
1140
  });
1142
1141
  ```
1143
1142
 
1144
- License
1145
- MIT
1143
+ # Unsaved Changes Protection
1144
+
1145
+ Protect Angular routes and modals from accidental navigation or closing when there are unsaved changes.
1146
+ Supports:
1147
+
1148
+ 1. Angular route navigation
1149
+ 2. Browser back button
1150
+ 3. Browser refresh / tab close
1151
+ 4. Modal close (NG Bootstrap / Material / custom)
1152
+ 5. Parent + child component setups
1153
+ 6. Nested forms
1154
+
1155
+ ### The system is built around three core pieces:
1156
+
1157
+ 1. FormChangesTrackerService
1158
+ 2. UnsavedChangesGuard
1159
+ 3. CanComponentDeactivate interface (for route protection)
1160
+
1161
+ Architecture:
1162
+
1163
+ ```
1164
+ Form → TrackerService → Guard → Confirmation → Continue / Block
1165
+ ```
1166
+
1167
+ The library does NOT depend on your UI layer.
1168
+ Your app controls the confirmation modal.
1169
+ Core Exports
1170
+
1171
+ ```ts
1172
+ import { FormChangesTrackerService, UnsavedChangesGuard, CanComponentDeactivate } from "ngx-intellitoolx";
1173
+ ```
1174
+
1175
+ ## FormChangesTrackerService API
1176
+
1177
+ The FormChangesTrackerService tracks changes in Angular forms and provides methods for managing form state across your application.
1178
+
1179
+ 1. Single Form Operations
1180
+
1181
+ ### register(form)
1182
+
1183
+ Registers a single form for change tracking.
1184
+
1185
+ ```ts
1186
+ register(form: AbstractControl): void
1187
+ ```
1188
+
1189
+ Behavior:
1190
+
1191
+ - Captures the current form value as the initial state
1192
+ - Marks the form as pristine
1193
+ - Stores the form reference for change detection
1194
+
1195
+ Example:
1196
+
1197
+ ```ts
1198
+ ngOnInit() {
1199
+ this.form = this.fb.group({
1200
+ name: [''],
1201
+ email: ['']
1202
+ });
1203
+
1204
+ this.tracker.register(this.form);
1205
+ }
1206
+ ```
1207
+
1208
+ ### reset(form)
1209
+
1210
+ Resets the tracked initial value to the current form value.
1211
+
1212
+ ```ts
1213
+ reset(form: AbstractControl): void
1214
+ ```
1215
+
1216
+ Use Case: After patching form data from an API, reset the tracker to prevent false positives.
1217
+ Example:
1218
+
1219
+ ```ts
1220
+ loadData() {
1221
+ this.api.getData().subscribe(data => {
1222
+ this.form.patchValue(data);
1223
+ // Reset tracker after patching to track only future changes
1224
+ this.tracker.reset(this.form);
1225
+ });
1226
+ }
1227
+ ```
1228
+
1229
+ ### unregister(form)
1230
+
1231
+ Removes a form from the tracker.
1232
+
1233
+ ```ts
1234
+ unregister(form: AbstractControl): void
1235
+ ```
1236
+
1237
+ Example:
1238
+
1239
+ ```ts
1240
+ ngOnDestroy() {
1241
+ this.tracker.unregister(this.form);
1242
+ }
1243
+ ```
1244
+
1245
+ ### hasChanges()
1246
+
1247
+ Checks if any tracked form has unsaved changes.
1248
+
1249
+ ```ts
1250
+ hasChanges(): boolean
1251
+ ```
1252
+
1253
+ Returns: true if any tracked form has changes, false otherwise.
1254
+ Example:
1255
+
1256
+ ```ts
1257
+ if (this.tracker.hasChanges()) {
1258
+ // Show confirmation modal
1259
+ }
1260
+ ```
1261
+
1262
+ ### clearAll()
1263
+
1264
+ Removes all forms from the tracker.
1265
+
1266
+ ```ts
1267
+ clearAll(): void
1268
+ ```
1269
+
1270
+ Example:
1271
+
1272
+ ```ts
1273
+ ngOnDestroy() {
1274
+ this.tracker.clearAll();
1275
+ }
1276
+ ```
1277
+
1278
+ 2. Batch Operations
1279
+ For applications with multiple forms that need to be managed together, the service provides batch methods.
1280
+
1281
+ ### registerForms(forms)
1282
+
1283
+ Registers multiple forms at once for change tracking.
1284
+
1285
+ ```ts
1286
+ registerForms(forms: AbstractControl[]): void
1287
+ ```
1288
+
1289
+ Use Case:
1290
+
1291
+ - Multi-step forms
1292
+ - Forms in tabs
1293
+ - Parent component managing multiple child forms
1294
+
1295
+ Example:
1296
+
1297
+ ```ts
1298
+ ngOnInit() {
1299
+ this.personalInfoForm = this.fb.group({
1300
+ firstName: [''],
1301
+ lastName: ['']
1302
+ });
1303
+
1304
+ this.contactForm = this.fb.group({
1305
+ email: [''],
1306
+ phone: ['']
1307
+ });
1308
+
1309
+ this.addressForm = this.fb.group({
1310
+ street: [''],
1311
+ city: ['']
1312
+ });
1313
+
1314
+ // Register all forms at once
1315
+ this.tracker.registerForms([
1316
+ this.personalInfoForm,
1317
+ this.contactForm,
1318
+ this.addressForm
1319
+ ]);
1320
+ }
1321
+ ```
1322
+
1323
+ ### resetForms(forms)
1324
+
1325
+ Resets multiple forms to their current values, useful after batch updates or API calls.
1326
+
1327
+ ```ts
1328
+ resetForms(forms: AbstractControl[]): void
1329
+ ```
1330
+
1331
+ Use Case:
1332
+
1333
+ - After loading data into multiple forms
1334
+ - After bulk form updates
1335
+ - Resetting multiple wizard steps
1336
+
1337
+ Example:
1338
+
1339
+ ```ts
1340
+ loadAllData() {
1341
+ this.api.getUserData().subscribe(data => {
1342
+ this.personalInfoForm.patchValue(data.personal);
1343
+ this.contactForm.patchValue(data.contact);
1344
+ this.addressForm.patchValue(data.address);
1345
+
1346
+ // Reset all forms after patching
1347
+ this.tracker.resetForms([
1348
+ this.personalInfoForm,
1349
+ this.contactForm,
1350
+ this.addressForm
1351
+ ]);
1352
+ });
1353
+ }
1354
+ ```
1355
+
1356
+ ### unregisterForms(forms)
1357
+
1358
+ Removes multiple forms from the tracker at once.
1359
+
1360
+ ```ts
1361
+ unregisterForms(forms: AbstractControl[]): void
1362
+ ```
1363
+
1364
+ Use Case:
1365
+
1366
+ - Component cleanup with multiple forms
1367
+ - Dynamically removing form sections
1368
+ - Batch cleanup in parent components
1369
+
1370
+ Example:
1371
+
1372
+ ```ts
1373
+ ngOnDestroy() {
1374
+ // Unregister all forms at once
1375
+ this.tracker.unregisterForms([
1376
+ this.personalInfoForm,
1377
+ this.contactForm,
1378
+ this.addressForm
1379
+ ]);
1380
+ }
1381
+ ```
1382
+
1383
+ ### Complete Multi-Form Example
1384
+
1385
+ ```ts
1386
+ import { Component, OnInit, OnDestroy } from "@angular/core";
1387
+ import { FormBuilder, FormGroup } from "@angular/forms";
1388
+ import { FormChangesTrackerService } from "ngx-intellitoolx";
1389
+
1390
+ @Component({
1391
+ selector: "app-user-profile",
1392
+ templateUrl: "./user-profile.component.html",
1393
+ })
1394
+ export class UserProfileComponent implements OnInit, OnDestroy {
1395
+ personalForm!: FormGroup;
1396
+ contactForm!: FormGroup;
1397
+ preferencesForm!: FormGroup;
1398
+
1399
+ constructor(
1400
+ private fb: FormBuilder,
1401
+ private tracker: FormChangesTrackerService,
1402
+ private api: UserApiService,
1403
+ ) {}
1404
+
1405
+ ngOnInit() {
1406
+ // Create multiple forms
1407
+ this.personalForm = this.fb.group({
1408
+ firstName: [""],
1409
+ lastName: [""],
1410
+ dateOfBirth: [""],
1411
+ });
1412
+
1413
+ this.contactForm = this.fb.group({
1414
+ email: [""],
1415
+ phone: [""],
1416
+ address: [""],
1417
+ });
1418
+
1419
+ this.preferencesForm = this.fb.group({
1420
+ newsletter: [false],
1421
+ notifications: [false],
1422
+ theme: ["light"],
1423
+ });
1424
+
1425
+ // Register all forms at once
1426
+ this.tracker.registerForms([this.personalForm, this.contactForm, this.preferencesForm]);
1427
+
1428
+ // Load data
1429
+ this.loadUserData();
1430
+ }
1431
+
1432
+ loadUserData() {
1433
+ this.api.getUserProfile().subscribe((data) => {
1434
+ this.personalForm.patchValue(data.personal);
1435
+ this.contactForm.patchValue(data.contact);
1436
+ this.preferencesForm.patchValue(data.preferences);
1437
+
1438
+ // Reset all trackers after loading
1439
+ this.tracker.resetForms([this.personalForm, this.contactForm, this.preferencesForm]);
1440
+ });
1441
+ }
1442
+
1443
+ saveAll() {
1444
+ if (!this.tracker.hasChanges()) {
1445
+ console.log("No changes to save");
1446
+ return;
1447
+ }
1448
+
1449
+ const allData = {
1450
+ personal: this.personalForm.value,
1451
+ contact: this.contactForm.value,
1452
+ preferences: this.preferencesForm.value,
1453
+ };
1454
+
1455
+ this.api.updateProfile(allData).subscribe(() => {
1456
+ // Reset all forms after successful save
1457
+ this.tracker.resetForms([this.personalForm, this.contactForm, this.preferencesForm]);
1458
+ });
1459
+ }
1460
+
1461
+ ngOnDestroy() {
1462
+ // Clean up all forms at once
1463
+ this.tracker.unregisterForms([this.personalForm, this.contactForm, this.preferencesForm]);
1464
+ }
1465
+ }
1466
+ ```
1467
+
1468
+ ### Best Practices
1469
+
1470
+ 1. Always unregister forms in ngOnDestroy() to prevent memory leaks
1471
+ 2. Use clearAll() when component manages all tracked forms
1472
+ 3. Reset after API loads to establish the correct baseline for change detection
1473
+ 4. Batch operations improve code readability when managing multiple forms
1474
+ 5. Call markAsPristine() after successful saves (handled automatically by `reset()` and `resetForms()`)
1475
+
1476
+ ## Protecting Angular Routes
1477
+
1478
+ ### Add Guard to Route
1479
+
1480
+ ```ts
1481
+ {
1482
+ path: 'add-item',
1483
+ component: AddItemPageComponent,
1484
+ canDeactivate: [UnsavedChangesGuard]
1485
+ }
1486
+ ```
1487
+
1488
+ ### Implement Route Component
1489
+
1490
+ Your routed component must implement CanComponentDeactivate. This is not related to the library, as the modal trigger logic exists entirely outside the library's scope.
1491
+
1492
+ ```ts
1493
+ import { FormChangesTrackerService } from "ngx-intellitoolx";
1494
+ import { UtilityService } from "../services/utility.service";
1495
+
1496
+ @Component({
1497
+ selector: "app-add-item-page",
1498
+ template: `<app-item-form></app-item-form>`,
1499
+ })
1500
+ export class AddItemPageComponent implements CanComponentDeactivate {
1501
+ constructor(private utils: UtilityService) {}
1502
+
1503
+ confirmUnsavedChanges(): Promise<boolean> {
1504
+ return this.utils.confirmUnsavedChanges();
1505
+ }
1506
+ }
1507
+ ```
1508
+
1509
+ ### Track Form Changes (Child Component)
1510
+
1511
+ ```ts
1512
+ @Component({
1513
+ selector: "app-item-form",
1514
+ templateUrl: "./item-form.component.html",
1515
+ })
1516
+ export class ItemFormComponent implements OnInit {
1517
+ form!: FormGroup;
1518
+
1519
+ constructor(
1520
+ private fb: FormBuilder,
1521
+ private tracker: FormChangesTrackerService,
1522
+ ) {}
1523
+
1524
+ ngOnInit(): void {
1525
+ this.form = this.fb.group({
1526
+ name: [""],
1527
+ description: [""],
1528
+ });
1529
+
1530
+ this.tracker.register(this.form);
1531
+ }
1532
+
1533
+ patchForm() {
1534
+ this.form.patchValue({
1535
+ name: this.data.name,
1536
+ description: this.data.description,
1537
+ });
1538
+ // reset form after patching to track changes afterwards.
1539
+ // Very useful during form update
1540
+ this.tracker.reset(this.form);
1541
+ }
1542
+
1543
+ save(): void {
1544
+ // API call
1545
+ this.form.markAsPristine();
1546
+ }
1547
+
1548
+ ngOnDestroy(): void {
1549
+ // This is very important to not keep track of forms from other component
1550
+ this.tracker.clearAll();
1551
+ }
1552
+ }
1553
+ ```
1554
+
1555
+ ## Protecting Browser Refresh / Tab Close
1556
+
1557
+ ### Add this inside the routed component:
1558
+
1559
+ ```ts
1560
+ // Declare cleanup callback
1561
+ private unregister?: () => void;
1562
+
1563
+ constructor(
1564
+ private tracker: FormChangesTrackerService,
1565
+ public utilservice: UtilityService
1566
+ ) {
1567
+ super();
1568
+ }
1569
+
1570
+ ngOnInit() {
1571
+ // Register a beforeunload handler to prevent or warn about navigation when there are unsaved changes.
1572
+ this.unregister = IntelliToolxHelper.registerBeforeUnload(() =>
1573
+ this.tracker.hasChanges(),
1574
+ );
1575
+ }
1576
+
1577
+ confirmUnsavedChanges(): Promise<boolean> {
1578
+ return this.utilservice.confirmUnsavedChanges();
1579
+ }
1580
+
1581
+ ngOnDestroy() {
1582
+ // Safely invokes the teardown/unsubscribe callback if it exists.
1583
+ this.unregister?.();
1584
+ }
1585
+ ```
1586
+
1587
+ ## Protecting Modals (NG Bootstrap Example)
1588
+
1589
+ Route guards do NOT protect modals.
1590
+ You must intercept modal close manually.
1591
+
1592
+ Modal Form Component
1593
+
1594
+ ```ts
1595
+ import { NgbActiveModal } from "@ng-bootstrap/ng-bootstrap";
1596
+ import { FormChangesTrackerService } from "ngx-intellitoolx";
1597
+ import { UtilityService } from "../utility.service";
1598
+
1599
+ @Component({
1600
+ selector: "app-item-modal",
1601
+ templateUrl: "./item-modal.component.html",
1602
+ })
1603
+ export class ItemModalComponent implements OnInit {
1604
+ form!: FormGroup;
1605
+
1606
+ constructor(
1607
+ private fb: FormBuilder,
1608
+ private tracker: FormChangesTrackerService,
1609
+ private utils: UtilityService,
1610
+ public activeModal: NgbActiveModal,
1611
+ ) {}
1612
+
1613
+ ngOnInit(): void {
1614
+ this.form = this.fb.group({
1615
+ name: [""],
1616
+ description: [""],
1617
+ });
1618
+
1619
+ this.tracker.register(this.form);
1620
+ }
1621
+
1622
+ async closeModal() {
1623
+ if (this.tracker.hasChanges()) {
1624
+ const allowClose = await IntelliToolxHelper.confirmIfChanged(this.tracker.hasChanges(), () => this.utilsService.confirmUnsavedChanges());
1625
+ if (!allowClose) return;
1626
+ }
1627
+
1628
+ this.activeModal.dismiss();
1629
+ }
1630
+
1631
+ save(): void {
1632
+ this.form.markAsPristine();
1633
+ this.activeModal.close("saved");
1634
+ }
1635
+
1636
+ ngOnDestroy(): void {
1637
+ // This is very important to not keep track of forms from other component
1638
+ this.tracker.clearAll();
1639
+ }
1640
+ }
1641
+ ```
1642
+
1643
+ Modal Template
1644
+
1645
+ ```html
1646
+ <div class="modal-header">
1647
+ <h5 class="modal-title">Add Item</h5>
1648
+ <button type="button" class="btn-close" (click)="closeModal()"></button>
1649
+ </div>
1650
+
1651
+ <div class="modal-body">
1652
+ <form [formGroup]="form">
1653
+ <input formControlName="name" />
1654
+ <textarea formControlName="description"></textarea>
1655
+ </form>
1656
+ </div>
1657
+
1658
+ <div class="modal-footer">
1659
+ <button class="btn btn-secondary" (click)="closeModal()">Cancel</button>
1660
+
1661
+ <button class="btn btn-primary" (click)="save()">Save</button>
1662
+ </div>
1663
+ ```
1664
+
1665
+ ## How It Works
1666
+
1667
+ | Action | What Happens |
1668
+ | -------------------- | --------------------- |
1669
+ | User edits form | Tracker marks dirty |
1670
+ | User navigates route | Guard checks tracker |
1671
+ | User refreshes page | beforeunload triggers |
1672
+ | User closes modal | Manual interception |
1673
+ | User saves form | Tracker resets |
1674
+
1675
+ ### Parent + Child Structure
1676
+
1677
+ If your form is nested:
1678
+
1679
+ ```
1680
+ RouteComponent
1681
+ └── Modal
1682
+ └── Form
1683
+
1684
+ Child form → marks tracker dirty
1685
+
1686
+ Route component → implements guard interface
1687
+
1688
+ Modal → manually checks tracker before close
1689
+ ```
1690
+
1691
+ ### Confirmation Modal Example (App Layer)
1692
+
1693
+ ```ts
1694
+ @Injectable({ providedIn: "root" })
1695
+ export class UtilityService {
1696
+ constructor(private modal: NgbModal) {}
1697
+
1698
+ confirmUnsavedChanges(): Promise<boolean> {
1699
+ const modalRef = this.modal.open(UnsavedConfirmComponent, {
1700
+ backdrop: "static",
1701
+ });
1702
+
1703
+ return modalRef.result.then(() => true).catch(() => false);
1704
+ }
1705
+ }
1706
+ ```
1707
+
1708
+ 📄 License
1709
+
1710
+ ```
1711
+ MIT © Fagon Technologies
1712
+ ```
1713
+
1714
+ [REPO]: https://github.com/Nana-Darko-Ampem/ngx-intellitoolx
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2FuLWNvbXBvbmVudC1kZWFjdGl2YXRlLmludGVyZmFjZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvZm9ybS1jaGFuZ2VzL2Nhbi1jb21wb25lbnQtZGVhY3RpdmF0ZS5pbnRlcmZhY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IiIsInNvdXJjZXNDb250ZW50IjpbImV4cG9ydCBpbnRlcmZhY2UgQ2FuQ29tcG9uZW50RGVhY3RpdmF0ZSB7XG4gIGhhc1Vuc2F2ZWRDaGFuZ2VzOiAoKSA9PiBib29sZWFuO1xuICBjb25maXJtVW5zYXZlZENoYW5nZXM/OiAoKSA9PiBQcm9taXNlPGJvb2xlYW4+IHwgYm9vbGVhbjtcbiAgZ2V0VW5zYXZlZE1lc3NhZ2U/OiAoKSA9PiBzdHJpbmc7XG59XG4iXX0=
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29uZmlybS1oYW5kbGVyLnR5cGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9zcmMvbGliL2Zvcm0tY2hhbmdlcy9jb25maXJtLWhhbmRsZXIudHlwZS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiIiwic291cmNlc0NvbnRlbnQiOlsiZXhwb3J0IHR5cGUgQ29uZmlybUhhbmRsZXIgPSAoKSA9PiBib29sZWFuIHwgUHJvbWlzZTxib29sZWFuPjtcbiJdfQ==
@@ -0,0 +1,58 @@
1
+ import { Injectable } from '@angular/core';
2
+ import { IntelliToolxHelper } from '../helpers/intellitoolx.helper';
3
+ import * as i0 from "@angular/core";
4
+ export class FormChangesTrackerService {
5
+ constructor() {
6
+ this.trackedForms = new Map();
7
+ }
8
+ register(form) {
9
+ const currentRawValue = form.getRawValue?.() ?? form.value;
10
+ this.trackedForms.set(form, {
11
+ initialValue: IntelliToolxHelper.clone(currentRawValue),
12
+ });
13
+ form.markAsPristine();
14
+ }
15
+ registerForms(forms) {
16
+ forms.forEach((form) => this.register(form));
17
+ }
18
+ hasChanges() {
19
+ // We iterate over entries [form, metadata]
20
+ for (const [form, { initialValue }] of this.trackedForms.entries()) {
21
+ if (IntelliToolxHelper.formHasChanges(initialValue, form)) {
22
+ return true;
23
+ }
24
+ }
25
+ return false;
26
+ }
27
+ unregister(form) {
28
+ this.trackedForms.delete(form);
29
+ }
30
+ unregisterForms(forms) {
31
+ forms.forEach((form) => this.unregister(form));
32
+ }
33
+ reset(form) {
34
+ const entry = this.trackedForms.get(form);
35
+ if (!entry) {
36
+ this.register(form);
37
+ return;
38
+ }
39
+ const currentRawValue = form.getRawValue?.() ?? form.value;
40
+ entry.initialValue = IntelliToolxHelper.clone(currentRawValue);
41
+ form.markAsPristine();
42
+ }
43
+ resetForms(forms) {
44
+ forms.forEach((form) => this.reset(form));
45
+ }
46
+ clearAll() {
47
+ this.trackedForms.clear();
48
+ }
49
+ static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormChangesTrackerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
50
+ static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormChangesTrackerService, providedIn: 'root' }); }
51
+ }
52
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FormChangesTrackerService, decorators: [{
53
+ type: Injectable,
54
+ args: [{
55
+ providedIn: 'root',
56
+ }]
57
+ }] });
58
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9ybS1jaGFuZ2VzLXRyYWNrZXIuc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9saWIvZm9ybS1jaGFuZ2VzL2Zvcm0tY2hhbmdlcy10cmFja2VyLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUMzQyxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQzs7QUFNcEUsTUFBTSxPQUFPLHlCQUF5QjtJQUh0QztRQUlVLGlCQUFZLEdBQUcsSUFBSSxHQUFHLEVBSzNCLENBQUM7S0FzREw7SUFwREMsUUFBUSxDQUFDLElBQXFCO1FBQzVCLE1BQU0sZUFBZSxHQUFJLElBQVksQ0FBQyxXQUFXLEVBQUUsRUFBRSxJQUFJLElBQUksQ0FBQyxLQUFLLENBQUM7UUFFcEUsSUFBSSxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsSUFBSSxFQUFFO1lBQzFCLFlBQVksRUFBRSxrQkFBa0IsQ0FBQyxLQUFLLENBQUMsZUFBZSxDQUFDO1NBQ3hELENBQUMsQ0FBQztRQUVILElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztJQUN4QixDQUFDO0lBRUQsYUFBYSxDQUFDLEtBQXdCO1FBQ3BDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUMvQyxDQUFDO0lBRUQsVUFBVTtRQUNSLDJDQUEyQztRQUMzQyxLQUFLLE1BQU0sQ0FBQyxJQUFJLEVBQUUsRUFBRSxZQUFZLEVBQUUsQ0FBQyxJQUFJLElBQUksQ0FBQyxZQUFZLENBQUMsT0FBTyxFQUFFLEVBQUU7WUFDbEUsSUFBSSxrQkFBa0IsQ0FBQyxjQUFjLENBQUMsWUFBWSxFQUFFLElBQUksQ0FBQyxFQUFFO2dCQUN6RCxPQUFPLElBQUksQ0FBQzthQUNiO1NBQ0Y7UUFDRCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFFRCxVQUFVLENBQUMsSUFBcUI7UUFDOUIsSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDakMsQ0FBQztJQUVELGVBQWUsQ0FBQyxLQUF3QjtRQUN0QyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsSUFBSSxFQUFFLEVBQUUsQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7SUFDakQsQ0FBQztJQUVELEtBQUssQ0FBQyxJQUFxQjtRQUN6QixNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUMxQyxJQUFJLENBQUMsS0FBSyxFQUFFO1lBQ1YsSUFBSSxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUNwQixPQUFPO1NBQ1I7UUFFRCxNQUFNLGVBQWUsR0FBSSxJQUFZLENBQUMsV0FBVyxFQUFFLEVBQUUsSUFBSSxJQUFJLENBQUMsS0FBSyxDQUFDO1FBQ3BFLEtBQUssQ0FBQyxZQUFZLEdBQUcsa0JBQWtCLENBQUMsS0FBSyxDQUFDLGVBQWUsQ0FBQyxDQUFDO1FBRS9ELElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztJQUN4QixDQUFDO0lBRUQsVUFBVSxDQUFDLEtBQXdCO1FBQ2pDLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRSxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUM1QyxDQUFDO0lBRUQsUUFBUTtRQUNOLElBQUksQ0FBQyxZQUFZLENBQUMsS0FBSyxFQUFFLENBQUM7SUFDNUIsQ0FBQzsrR0EzRFUseUJBQXlCO21IQUF6Qix5QkFBeUIsY0FGeEIsTUFBTTs7NEZBRVAseUJBQXlCO2tCQUhyQyxVQUFVO21CQUFDO29CQUNWLFVBQVUsRUFBRSxNQUFNO2lCQUNuQiIsInNvdXJjZXNDb250ZW50IjpbImltcG9ydCB7IEluamVjdGFibGUgfSBmcm9tICdAYW5ndWxhci9jb3JlJztcbmltcG9ydCB7IEludGVsbGlUb29seEhlbHBlciB9IGZyb20gJy4uL2hlbHBlcnMvaW50ZWxsaXRvb2x4LmhlbHBlcic7XG5pbXBvcnQgeyBBYnN0cmFjdENvbnRyb2wgfSBmcm9tICdAYW5ndWxhci9mb3Jtcyc7XG5cbkBJbmplY3RhYmxlKHtcbiAgcHJvdmlkZWRJbjogJ3Jvb3QnLFxufSlcbmV4cG9ydCBjbGFzcyBGb3JtQ2hhbmdlc1RyYWNrZXJTZXJ2aWNlIHtcbiAgcHJpdmF0ZSB0cmFja2VkRm9ybXMgPSBuZXcgTWFwPFxuICAgIEFic3RyYWN0Q29udHJvbCxcbiAgICB7XG4gICAgICBpbml0aWFsVmFsdWU6IGFueTtcbiAgICB9XG4gID4oKTtcblxuICByZWdpc3Rlcihmb3JtOiBBYnN0cmFjdENvbnRyb2wpIHtcbiAgICBjb25zdCBjdXJyZW50UmF3VmFsdWUgPSAoZm9ybSBhcyBhbnkpLmdldFJhd1ZhbHVlPy4oKSA/PyBmb3JtLnZhbHVlO1xuXG4gICAgdGhpcy50cmFja2VkRm9ybXMuc2V0KGZvcm0sIHtcbiAgICAgIGluaXRpYWxWYWx1ZTogSW50ZWxsaVRvb2x4SGVscGVyLmNsb25lKGN1cnJlbnRSYXdWYWx1ZSksXG4gICAgfSk7XG5cbiAgICBmb3JtLm1hcmtBc1ByaXN0aW5lKCk7XG4gIH1cblxuICByZWdpc3RlckZvcm1zKGZvcm1zOiBBYnN0cmFjdENvbnRyb2xbXSkge1xuICAgIGZvcm1zLmZvckVhY2goKGZvcm0pID0+IHRoaXMucmVnaXN0ZXIoZm9ybSkpO1xuICB9XG5cbiAgaGFzQ2hhbmdlcygpOiBib29sZWFuIHtcbiAgICAvLyBXZSBpdGVyYXRlIG92ZXIgZW50cmllcyBbZm9ybSwgbWV0YWRhdGFdXG4gICAgZm9yIChjb25zdCBbZm9ybSwgeyBpbml0aWFsVmFsdWUgfV0gb2YgdGhpcy50cmFja2VkRm9ybXMuZW50cmllcygpKSB7XG4gICAgICBpZiAoSW50ZWxsaVRvb2x4SGVscGVyLmZvcm1IYXNDaGFuZ2VzKGluaXRpYWxWYWx1ZSwgZm9ybSkpIHtcbiAgICAgICAgcmV0dXJuIHRydWU7XG4gICAgICB9XG4gICAgfVxuICAgIHJldHVybiBmYWxzZTtcbiAgfVxuXG4gIHVucmVnaXN0ZXIoZm9ybTogQWJzdHJhY3RDb250cm9sKSB7XG4gICAgdGhpcy50cmFja2VkRm9ybXMuZGVsZXRlKGZvcm0pO1xuICB9XG5cbiAgdW5yZWdpc3RlckZvcm1zKGZvcm1zOiBBYnN0cmFjdENvbnRyb2xbXSkge1xuICAgIGZvcm1zLmZvckVhY2goKGZvcm0pID0+IHRoaXMudW5yZWdpc3Rlcihmb3JtKSk7XG4gIH1cblxuICByZXNldChmb3JtOiBBYnN0cmFjdENvbnRyb2wpIHtcbiAgICBjb25zdCBlbnRyeSA9IHRoaXMudHJhY2tlZEZvcm1zLmdldChmb3JtKTtcbiAgICBpZiAoIWVudHJ5KSB7XG4gICAgICB0aGlzLnJlZ2lzdGVyKGZvcm0pO1xuICAgICAgcmV0dXJuO1xuICAgIH1cblxuICAgIGNvbnN0IGN1cnJlbnRSYXdWYWx1ZSA9IChmb3JtIGFzIGFueSkuZ2V0UmF3VmFsdWU/LigpID8/IGZvcm0udmFsdWU7XG4gICAgZW50cnkuaW5pdGlhbFZhbHVlID0gSW50ZWxsaVRvb2x4SGVscGVyLmNsb25lKGN1cnJlbnRSYXdWYWx1ZSk7XG5cbiAgICBmb3JtLm1hcmtBc1ByaXN0aW5lKCk7XG4gIH1cblxuICByZXNldEZvcm1zKGZvcm1zOiBBYnN0cmFjdENvbnRyb2xbXSkge1xuICAgIGZvcm1zLmZvckVhY2goKGZvcm0pID0+IHRoaXMucmVzZXQoZm9ybSkpO1xuICB9XG5cbiAgY2xlYXJBbGwoKSB7XG4gICAgdGhpcy50cmFja2VkRm9ybXMuY2xlYXIoKTtcbiAgfVxufVxuIl19
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZm9ybS11cGRhdGUtbWVzc2FnZS1jb25maWcuaW50ZXJmYWNlLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL2xpYi9mb3JtLWNoYW5nZXMvZm9ybS11cGRhdGUtbWVzc2FnZS1jb25maWcuaW50ZXJmYWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiIiLCJzb3VyY2VzQ29udGVudCI6WyJleHBvcnQgaW50ZXJmYWNlIEludGVsbGl0b29seEZvcm1VcGRhdGVNZXNzYWdlQ29uZmlnIHtcbiAgbWVzc2FnZT86IHN0cmluZztcbiAgYmFja2dyb3VuZENvbG9yPzogc3RyaW5nO1xuICB0ZXh0Q29sb3I/OiBzdHJpbmc7XG4gIGJvcmRlckNvbG9yPzogc3RyaW5nO1xuICBmb250V2VpZ2h0PzogbnVtYmVyO1xuICBib3JkZXJSYWRpdXM/OiBzdHJpbmc7XG4gIHBhZGRpbmc/OiBzdHJpbmc7XG4gIGljb25BbmRNZXNzYWdlR2FwPzogc3RyaW5nO1xuICBpY29uU2l6ZT86IHN0cmluZztcbn1cbiJdfQ==