@sdcorejs/angular 21.1.2 → 21.1.4

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 (177) hide show
  1. package/README.md +127 -807
  2. package/fesm2022/sdcorejs-angular-components-anchor.mjs +14 -14
  3. package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
  4. package/fesm2022/sdcorejs-angular-components-audit-diff.mjs +332 -0
  5. package/fesm2022/sdcorejs-angular-components-audit-diff.mjs.map +1 -0
  6. package/fesm2022/sdcorejs-angular-components-badge.mjs +1 -0
  7. package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
  8. package/fesm2022/sdcorejs-angular-components-breadcrumb.mjs +121 -0
  9. package/fesm2022/sdcorejs-angular-components-breadcrumb.mjs.map +1 -0
  10. package/fesm2022/sdcorejs-angular-components-data-state.mjs +68 -0
  11. package/fesm2022/sdcorejs-angular-components-data-state.mjs.map +1 -0
  12. package/fesm2022/sdcorejs-angular-components-document-builder.mjs +18 -23
  13. package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +1 -1
  14. package/fesm2022/sdcorejs-angular-components-editor.mjs +6 -4
  15. package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
  16. package/fesm2022/sdcorejs-angular-components-form-generic.mjs +633 -886
  17. package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
  18. package/fesm2022/sdcorejs-angular-components-import-excel.mjs +26 -41
  19. package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
  20. package/fesm2022/sdcorejs-angular-components-job-progress.mjs +97 -0
  21. package/fesm2022/sdcorejs-angular-components-job-progress.mjs.map +1 -0
  22. package/fesm2022/sdcorejs-angular-components-mini-editor.mjs +10 -20
  23. package/fesm2022/sdcorejs-angular-components-mini-editor.mjs.map +1 -1
  24. package/fesm2022/sdcorejs-angular-components-modal.mjs +61 -4
  25. package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
  26. package/fesm2022/sdcorejs-angular-components-preview.mjs +1438 -218
  27. package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
  28. package/fesm2022/sdcorejs-angular-components-query-bar.mjs +9 -17
  29. package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
  30. package/fesm2022/sdcorejs-angular-components-query-builder.mjs +1 -1
  31. package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
  32. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +39 -2
  33. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
  34. package/fesm2022/sdcorejs-angular-components-tab-router.mjs +76 -52
  35. package/fesm2022/sdcorejs-angular-components-tab-router.mjs.map +1 -1
  36. package/fesm2022/sdcorejs-angular-components-tab.mjs +48 -4
  37. package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
  38. package/fesm2022/sdcorejs-angular-components-table.mjs +123 -89
  39. package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
  40. package/fesm2022/sdcorejs-angular-components-tree.mjs +188 -18
  41. package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
  42. package/fesm2022/sdcorejs-angular-components-upload-file.mjs +17 -23
  43. package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
  44. package/fesm2022/sdcorejs-angular-components.mjs +4 -0
  45. package/fesm2022/sdcorejs-angular-components.mjs.map +1 -1
  46. package/fesm2022/sdcorejs-angular-directives.mjs +29 -41
  47. package/fesm2022/sdcorejs-angular-directives.mjs.map +1 -1
  48. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +9 -8
  49. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
  50. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs +20 -71
  51. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
  52. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +8 -4
  53. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
  54. package/fesm2022/sdcorejs-angular-forms-chip.mjs +8 -4
  55. package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
  56. package/fesm2022/sdcorejs-angular-forms-date-range.mjs +18 -14
  57. package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
  58. package/fesm2022/sdcorejs-angular-forms-date.mjs +48 -70
  59. package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
  60. package/fesm2022/sdcorejs-angular-forms-datetime.mjs +62 -528
  61. package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
  62. package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs +384 -0
  63. package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs.map +1 -0
  64. package/fesm2022/sdcorejs-angular-forms-input-color.mjs +5 -3
  65. package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
  66. package/fesm2022/sdcorejs-angular-forms-input-number.mjs +16 -11
  67. package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
  68. package/fesm2022/sdcorejs-angular-forms-input.mjs +297 -59
  69. package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
  70. package/fesm2022/sdcorejs-angular-forms-models.mjs +155 -2
  71. package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
  72. package/fesm2022/sdcorejs-angular-forms-radio.mjs +44 -66
  73. package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
  74. package/fesm2022/sdcorejs-angular-forms-select.mjs +8 -21
  75. package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
  76. package/fesm2022/sdcorejs-angular-forms-switch.mjs +19 -64
  77. package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
  78. package/fesm2022/sdcorejs-angular-forms-textarea.mjs +8 -6
  79. package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
  80. package/fesm2022/sdcorejs-angular-forms-time-range.mjs +277 -0
  81. package/fesm2022/sdcorejs-angular-forms-time-range.mjs.map +1 -0
  82. package/fesm2022/sdcorejs-angular-forms-time.mjs +307 -0
  83. package/fesm2022/sdcorejs-angular-forms-time.mjs.map +1 -0
  84. package/fesm2022/sdcorejs-angular-forms-tree-select.mjs +210 -0
  85. package/fesm2022/sdcorejs-angular-forms-tree-select.mjs.map +1 -0
  86. package/fesm2022/sdcorejs-angular-forms.mjs +73 -4
  87. package/fesm2022/sdcorejs-angular-forms.mjs.map +1 -1
  88. package/fesm2022/sdcorejs-angular-i18n.mjs +355 -10
  89. package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
  90. package/fesm2022/sdcorejs-angular-interceptors.mjs +12 -17
  91. package/fesm2022/sdcorejs-angular-interceptors.mjs.map +1 -1
  92. package/fesm2022/sdcorejs-angular-modules-icon.mjs.map +1 -1
  93. package/fesm2022/sdcorejs-angular-modules-layout.mjs +861 -80
  94. package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
  95. package/fesm2022/sdcorejs-angular-modules.mjs +0 -1
  96. package/fesm2022/sdcorejs-angular-modules.mjs.map +1 -1
  97. package/fesm2022/sdcorejs-angular-pipes.mjs +5 -7
  98. package/fesm2022/sdcorejs-angular-pipes.mjs.map +1 -1
  99. package/fesm2022/sdcorejs-angular-services-api.mjs +407 -137
  100. package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
  101. package/fesm2022/sdcorejs-angular-services-cache.mjs +515 -105
  102. package/fesm2022/sdcorejs-angular-services-cache.mjs.map +1 -1
  103. package/fesm2022/sdcorejs-angular-services-confirm.mjs +5 -7
  104. package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
  105. package/fesm2022/sdcorejs-angular-services-docx.mjs +7 -10
  106. package/fesm2022/sdcorejs-angular-services-docx.mjs.map +1 -1
  107. package/fesm2022/sdcorejs-angular-services-loading.mjs +340 -66
  108. package/fesm2022/sdcorejs-angular-services-loading.mjs.map +1 -1
  109. package/fesm2022/sdcorejs-angular-services-notify.mjs +7 -10
  110. package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
  111. package/fesm2022/sdcorejs-angular-services-persistence.mjs +899 -0
  112. package/fesm2022/sdcorejs-angular-services-persistence.mjs.map +1 -0
  113. package/fesm2022/sdcorejs-angular-services-storage.mjs +324 -122
  114. package/fesm2022/sdcorejs-angular-services-storage.mjs.map +1 -1
  115. package/fesm2022/sdcorejs-angular-services-task.mjs +389 -0
  116. package/fesm2022/sdcorejs-angular-services-task.mjs.map +1 -0
  117. package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs +255 -0
  118. package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs.map +1 -0
  119. package/fesm2022/sdcorejs-angular-services-viewport.mjs +76 -0
  120. package/fesm2022/sdcorejs-angular-services-viewport.mjs.map +1 -0
  121. package/fesm2022/sdcorejs-angular-services.mjs +4 -0
  122. package/fesm2022/sdcorejs-angular-services.mjs.map +1 -1
  123. package/package.json +50 -5
  124. package/types/sdcorejs-angular-components-audit-diff.d.ts +74 -0
  125. package/types/sdcorejs-angular-components-badge.d.ts +1 -1
  126. package/types/sdcorejs-angular-components-breadcrumb.d.ts +53 -0
  127. package/types/sdcorejs-angular-components-data-state.d.ts +44 -0
  128. package/types/sdcorejs-angular-components-document-builder.d.ts +5 -5
  129. package/types/sdcorejs-angular-components-form-generic.d.ts +89 -103
  130. package/types/sdcorejs-angular-components-import-excel.d.ts +4 -14
  131. package/types/sdcorejs-angular-components-job-progress.d.ts +38 -0
  132. package/types/sdcorejs-angular-components-mini-editor.d.ts +9 -9
  133. package/types/sdcorejs-angular-components-modal.d.ts +7 -1
  134. package/types/sdcorejs-angular-components-preview.d.ts +181 -28
  135. package/types/sdcorejs-angular-components-side-drawer.d.ts +7 -1
  136. package/types/sdcorejs-angular-components-tab.d.ts +9 -2
  137. package/types/sdcorejs-angular-components-table.d.ts +8 -7
  138. package/types/sdcorejs-angular-components-tree.d.ts +24 -2
  139. package/types/sdcorejs-angular-components-upload-file.d.ts +5 -4
  140. package/types/sdcorejs-angular-components.d.ts +4 -0
  141. package/types/sdcorejs-angular-directives.d.ts +10 -8
  142. package/types/sdcorejs-angular-forms-autocomplete.d.ts +3 -4
  143. package/types/sdcorejs-angular-forms-checkbox.d.ts +4 -8
  144. package/types/sdcorejs-angular-forms-date-range.d.ts +6 -7
  145. package/types/sdcorejs-angular-forms-date.d.ts +3 -3
  146. package/types/sdcorejs-angular-forms-datetime.d.ts +3 -3
  147. package/types/sdcorejs-angular-forms-entity-picker.d.ts +128 -0
  148. package/types/sdcorejs-angular-forms-input-color.d.ts +3 -1
  149. package/types/sdcorejs-angular-forms-input-number.d.ts +4 -2
  150. package/types/sdcorejs-angular-forms-input.d.ts +76 -11
  151. package/types/sdcorejs-angular-forms-models.d.ts +86 -3
  152. package/types/sdcorejs-angular-forms-radio.d.ts +12 -8
  153. package/types/sdcorejs-angular-forms-switch.d.ts +4 -7
  154. package/types/sdcorejs-angular-forms-time-range.d.ts +98 -0
  155. package/types/sdcorejs-angular-forms-time.d.ts +117 -0
  156. package/types/sdcorejs-angular-forms-tree-select.d.ts +76 -0
  157. package/types/sdcorejs-angular-forms.d.ts +15 -5
  158. package/types/sdcorejs-angular-i18n.d.ts +68 -2
  159. package/types/sdcorejs-angular-interceptors.d.ts +5 -6
  160. package/types/sdcorejs-angular-modules-icon.d.ts +2 -3
  161. package/types/sdcorejs-angular-modules-layout.d.ts +288 -15
  162. package/types/sdcorejs-angular-modules.d.ts +0 -1
  163. package/types/sdcorejs-angular-pipes.d.ts +2 -2
  164. package/types/sdcorejs-angular-services-api.d.ts +68 -24
  165. package/types/sdcorejs-angular-services-cache.d.ts +52 -16
  166. package/types/sdcorejs-angular-services-confirm.d.ts +3 -2
  167. package/types/sdcorejs-angular-services-docx.d.ts +2 -3
  168. package/types/sdcorejs-angular-services-loading.d.ts +16 -8
  169. package/types/sdcorejs-angular-services-persistence.d.ts +159 -0
  170. package/types/sdcorejs-angular-services-storage.d.ts +26 -8
  171. package/types/sdcorejs-angular-services-task.d.ts +104 -0
  172. package/types/sdcorejs-angular-services-unsaved-changes.d.ts +83 -0
  173. package/types/sdcorejs-angular-services-viewport.d.ts +42 -0
  174. package/types/sdcorejs-angular-services.d.ts +4 -0
  175. package/fesm2022/sdcorejs-angular-modules-authom.mjs +0 -360
  176. package/fesm2022/sdcorejs-angular-modules-authom.mjs.map +0 -1
  177. package/types/sdcorejs-angular-modules-authom.d.ts +0 -70
@@ -0,0 +1,255 @@
1
+ import { DOCUMENT } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { InjectionToken, inject, DestroyRef, signal, computed, effect, Injectable, isSignal } from '@angular/core';
4
+ import { I18nService } from '@sdcorejs/angular/i18n';
5
+
6
+ const SD_UNSAVED_CHANGES_WINDOW = new InjectionToken('SD_UNSAVED_CHANGES_WINDOW', {
7
+ factory: () => inject(DOCUMENT).defaultView,
8
+ });
9
+ const SD_UNSAVED_CHANGES_CONFIRMATION_ADAPTER = new InjectionToken('SD_UNSAVED_CHANGES_CONFIRMATION_ADAPTER', {
10
+ factory: () => {
11
+ const windowRef = inject(SD_UNSAVED_CHANGES_WINDOW);
12
+ const i18n = inject(I18nService);
13
+ return {
14
+ confirm: (context) => {
15
+ if (!windowRef?.confirm)
16
+ return 'cancel';
17
+ const message = context.message || i18n.t('core.service.unsaved-changes.message');
18
+ return windowRef.confirm(message) ? 'discard' : 'cancel';
19
+ },
20
+ };
21
+ },
22
+ });
23
+
24
+ class SdUnsavedChangesService {
25
+ #adapter = inject(SD_UNSAVED_CHANGES_CONFIRMATION_ADAPTER);
26
+ #window = inject(SD_UNSAVED_CHANGES_WINDOW);
27
+ #i18n = inject(I18nService);
28
+ #destroyRef = inject(DestroyRef);
29
+ #entries = signal([], ...(ngDevMode ? [{ debugName: "#entries" }] : /* istanbul ignore next */ []));
30
+ #pendingPrompts = new Map();
31
+ registrations = computed(() => this.#entries().map(entry => entry.ref), ...(ngDevMode ? [{ debugName: "registrations" }] : /* istanbul ignore next */ []));
32
+ dirty = computed(() => this.#dirtyEntries().length > 0, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
33
+ constructor() {
34
+ effect(onCleanup => {
35
+ if (!this.#window || !this.dirty())
36
+ return;
37
+ const listener = (event) => {
38
+ const message = this.#dirtyEntries()[0]?.watcher.message ?? this.#i18n.t('core.service.unsaved-changes.message');
39
+ event.preventDefault();
40
+ event.returnValue = message;
41
+ };
42
+ this.#window.addEventListener('beforeunload', listener);
43
+ onCleanup(() => this.#window?.removeEventListener('beforeunload', listener));
44
+ });
45
+ this.#destroyRef.onDestroy(() => {
46
+ for (const entry of this.#entries()) {
47
+ if (entry.destroyed())
48
+ continue;
49
+ entry.destroyed.set(true);
50
+ this.#runCleanup(entry.watcher);
51
+ }
52
+ this.#entries.set([]);
53
+ this.#pendingPrompts.clear();
54
+ });
55
+ }
56
+ register(watcher) {
57
+ const existing = this.#entries().find(entry => entry.watcher.id === watcher.id && Object.is(entry.watcher.scope, watcher.scope));
58
+ if (existing) {
59
+ if (existing.watcher !== watcher)
60
+ this.#runCleanup(watcher);
61
+ return existing.ref;
62
+ }
63
+ const dirtyOverride = signal(undefined, ...(ngDevMode ? [{ debugName: "dirtyOverride" }] : /* istanbul ignore next */ []));
64
+ const destroyed = signal(false, ...(ngDevMode ? [{ debugName: "destroyed" }] : /* istanbul ignore next */ []));
65
+ const dirty = computed(() => !destroyed() && (dirtyOverride() ?? readDirty(watcher.isDirty)), ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
66
+ const entry = {};
67
+ const ref = {
68
+ id: watcher.id,
69
+ scope: watcher.scope,
70
+ message: watcher.message,
71
+ destroyed: destroyed.asReadonly(),
72
+ dirty,
73
+ markDirty: () => dirtyOverride.set(true),
74
+ markPristine: () => dirtyOverride.set(false),
75
+ save: () => this.#runEntryAction(entry, 'save'),
76
+ reset: () => this.#runEntryAction(entry, 'reset'),
77
+ discard: () => this.#runEntryAction(entry, 'discard'),
78
+ destroy: () => this.#destroyEntry(entry),
79
+ };
80
+ Object.assign(entry, { watcher, dirtyOverride, destroyed, dirty, ref });
81
+ this.#entries.update(entries => [...entries, entry]);
82
+ return ref;
83
+ }
84
+ hasChanges(scope) {
85
+ return this.#dirtyEntries(scope).length > 0;
86
+ }
87
+ confirmLeave(options = {}) {
88
+ if (!this.hasChanges(options.scope))
89
+ return Promise.resolve(true);
90
+ const promptKey = options.scope;
91
+ const existingPrompt = this.#pendingPrompts.get(promptKey);
92
+ if (existingPrompt)
93
+ return existingPrompt;
94
+ const watchers = this.#dirtyEntries(options.scope).map(entry => entry.ref);
95
+ const message = watchers.find(watcher => watcher.message)?.message ?? this.#i18n.t('core.service.unsaved-changes.message');
96
+ const run = async () => {
97
+ try {
98
+ const rawDecision = await Promise.resolve(this.#adapter.confirm({ reason: options.reason ?? 'manual', scope: options.scope, message, watchers }));
99
+ const decision = typeof rawDecision === 'boolean' ? (rawDecision ? 'discard' : 'cancel') : rawDecision;
100
+ if (decision === 'save')
101
+ return this.saveAll(options.scope);
102
+ if (decision === 'discard')
103
+ return this.discardAll(options.scope);
104
+ return false;
105
+ }
106
+ catch {
107
+ return false;
108
+ }
109
+ };
110
+ const pending = run().finally(() => {
111
+ if (this.#pendingPrompts.get(promptKey) === pending)
112
+ this.#pendingPrompts.delete(promptKey);
113
+ });
114
+ this.#pendingPrompts.set(promptKey, pending);
115
+ return pending;
116
+ }
117
+ saveAll(scope) {
118
+ return this.#runAll('save', scope);
119
+ }
120
+ resetAll(scope) {
121
+ return this.#runAll('reset', scope);
122
+ }
123
+ discardAll(scope) {
124
+ return this.#runAll('discard', scope);
125
+ }
126
+ async #runAll(action, scope) {
127
+ for (const entry of this.#dirtyEntries(scope)) {
128
+ if (!(await this.#runEntryAction(entry, action)))
129
+ return false;
130
+ }
131
+ return true;
132
+ }
133
+ async #runEntryAction(entry, action) {
134
+ if (entry.destroyed())
135
+ return true;
136
+ try {
137
+ const callback = entry.watcher[action];
138
+ const result = callback ? await Promise.resolve(callback()) : undefined;
139
+ if (result === false)
140
+ return false;
141
+ // A callback owns its underlying dirty source (for example FormGroup
142
+ // marks itself pristine). Only synthesize pristine state when there is
143
+ // no callback capable of updating that source.
144
+ entry.dirtyOverride.set(callback ? undefined : false);
145
+ return true;
146
+ }
147
+ catch {
148
+ return false;
149
+ }
150
+ }
151
+ #dirtyEntries(scope) {
152
+ return this.#entries().filter(entry => (scope === undefined || Object.is(entry.watcher.scope, scope)) && entry.dirty());
153
+ }
154
+ #destroyEntry(entry) {
155
+ if (entry.destroyed())
156
+ return;
157
+ entry.destroyed.set(true);
158
+ this.#runCleanup(entry.watcher);
159
+ this.#entries.update(entries => entries.filter(candidate => candidate !== entry));
160
+ }
161
+ #runCleanup(watcher) {
162
+ try {
163
+ watcher.cleanup?.();
164
+ }
165
+ catch {
166
+ // Cleanup must never prevent registry teardown.
167
+ }
168
+ }
169
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdUnsavedChangesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
170
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdUnsavedChangesService, providedIn: 'root' });
171
+ }
172
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdUnsavedChangesService, decorators: [{
173
+ type: Injectable,
174
+ args: [{ providedIn: 'root' }]
175
+ }], ctorParameters: () => [] });
176
+ function readDirty(source) {
177
+ if (typeof source === 'boolean')
178
+ return source;
179
+ if (isSignal(source))
180
+ return !!source();
181
+ return !!source();
182
+ }
183
+
184
+ function registerSdUnsavedChangesForm(service, form, options) {
185
+ let snapshot = cloneValue(form.getRawValue());
186
+ const dirty = signal(form.dirty, ...(ngDevMode ? [{ debugName: "dirty" }] : /* istanbul ignore next */ []));
187
+ const subscription = form.events.subscribe(() => dirty.set(form.dirty));
188
+ const resetToSnapshot = () => {
189
+ form.reset(cloneValue(snapshot));
190
+ form.markAsPristine();
191
+ dirty.set(false);
192
+ };
193
+ const registration = service.register({
194
+ id: options.id,
195
+ scope: options.scope,
196
+ message: options.message,
197
+ isDirty: dirty,
198
+ save: async () => {
199
+ const result = options.save ? await Promise.resolve(options.save(form.getRawValue())) : undefined;
200
+ if (result === false)
201
+ return false;
202
+ snapshot = cloneValue(form.getRawValue());
203
+ form.markAsPristine();
204
+ dirty.set(false);
205
+ return true;
206
+ },
207
+ reset: async () => {
208
+ const result = options.reset ? await Promise.resolve(options.reset(form)) : undefined;
209
+ if (result === false)
210
+ return false;
211
+ if (!options.reset)
212
+ resetToSnapshot();
213
+ else {
214
+ form.markAsPristine();
215
+ dirty.set(false);
216
+ }
217
+ return true;
218
+ },
219
+ discard: async () => {
220
+ const result = options.discard ? await Promise.resolve(options.discard(form)) : undefined;
221
+ if (result === false)
222
+ return false;
223
+ if (!options.discard)
224
+ resetToSnapshot();
225
+ else {
226
+ form.markAsPristine();
227
+ dirty.set(false);
228
+ }
229
+ return true;
230
+ },
231
+ cleanup: () => subscription.unsubscribe(),
232
+ });
233
+ return registration;
234
+ }
235
+ function createSdUnsavedChangesCloseGuard(service, options = {}) {
236
+ return () => service.confirmLeave({ ...options, reason: 'close' });
237
+ }
238
+ const sdUnsavedChangesGuard = () => inject(SdUnsavedChangesService).confirmLeave({ reason: 'navigation' });
239
+ function cloneValue(value) {
240
+ if (value instanceof Date)
241
+ return new Date(value.getTime());
242
+ if (Array.isArray(value))
243
+ return value.map(item => cloneValue(item));
244
+ if (value && typeof value === 'object') {
245
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, cloneValue(item)]));
246
+ }
247
+ return value;
248
+ }
249
+
250
+ /**
251
+ * Generated bundle index. Do not edit.
252
+ */
253
+
254
+ export { SD_UNSAVED_CHANGES_CONFIRMATION_ADAPTER, SD_UNSAVED_CHANGES_WINDOW, SdUnsavedChangesService, createSdUnsavedChangesCloseGuard, registerSdUnsavedChangesForm, sdUnsavedChangesGuard };
255
+ //# sourceMappingURL=sdcorejs-angular-services-unsaved-changes.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdcorejs-angular-services-unsaved-changes.mjs","sources":["../../../projects/sdcorejs-angular/services/unsaved-changes/src/unsaved-changes.tokens.ts","../../../projects/sdcorejs-angular/services/unsaved-changes/src/unsaved-changes.service.ts","../../../projects/sdcorejs-angular/services/unsaved-changes/src/unsaved-changes.adapters.ts","../../../projects/sdcorejs-angular/services/unsaved-changes/sdcorejs-angular-services-unsaved-changes.ts"],"sourcesContent":["import { DOCUMENT } from '@angular/common';\nimport { InjectionToken, inject } from '@angular/core';\nimport { I18nService } from '@sdcorejs/angular/i18n';\nimport { SdUnsavedChangesConfirmationAdapter, SdUnsavedChangesDecision, SdUnsavedChangesPromptContext } from './unsaved-changes.model';\n\nexport interface SdUnsavedChangesWindow {\n addEventListener(type: 'beforeunload', listener: (event: BeforeUnloadEvent) => void): void;\n removeEventListener(type: 'beforeunload', listener: (event: BeforeUnloadEvent) => void): void;\n confirm?(message?: string): boolean;\n}\n\nexport const SD_UNSAVED_CHANGES_WINDOW = new InjectionToken<SdUnsavedChangesWindow | null>('SD_UNSAVED_CHANGES_WINDOW', {\n factory: () => inject(DOCUMENT).defaultView as SdUnsavedChangesWindow | null,\n});\n\nexport const SD_UNSAVED_CHANGES_CONFIRMATION_ADAPTER = new InjectionToken<SdUnsavedChangesConfirmationAdapter>(\n 'SD_UNSAVED_CHANGES_CONFIRMATION_ADAPTER',\n {\n factory: () => {\n const windowRef = inject(SD_UNSAVED_CHANGES_WINDOW);\n const i18n = inject(I18nService);\n return {\n confirm: (context: SdUnsavedChangesPromptContext): SdUnsavedChangesDecision => {\n if (!windowRef?.confirm) return 'cancel';\n const message = context.message || i18n.t('core.service.unsaved-changes.message');\n return windowRef.confirm(message) ? 'discard' : 'cancel';\n },\n };\n },\n }\n);\n","import { DestroyRef, Injectable, computed, effect, inject, isSignal, signal } from '@angular/core';\nimport { I18nService } from '@sdcorejs/angular/i18n';\nimport {\n SdUnsavedChangesConfirmOptions,\n SdUnsavedChangesDecision,\n SdUnsavedChangesRegistration,\n SdUnsavedChangesWatcher,\n} from './unsaved-changes.model';\nimport { SD_UNSAVED_CHANGES_CONFIRMATION_ADAPTER, SD_UNSAVED_CHANGES_WINDOW } from './unsaved-changes.tokens';\n\ninterface RegistryEntry {\n readonly watcher: SdUnsavedChangesWatcher;\n readonly dirtyOverride: ReturnType<typeof signal<boolean | undefined>>;\n readonly destroyed: ReturnType<typeof signal<boolean>>;\n readonly dirty: ReturnType<typeof computed<boolean>>;\n readonly ref: SdUnsavedChangesRegistration;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class SdUnsavedChangesService {\n readonly #adapter = inject(SD_UNSAVED_CHANGES_CONFIRMATION_ADAPTER);\n readonly #window = inject(SD_UNSAVED_CHANGES_WINDOW);\n readonly #i18n = inject(I18nService);\n readonly #destroyRef = inject(DestroyRef);\n readonly #entries = signal<readonly RegistryEntry[]>([]);\n readonly #pendingPrompts = new Map<unknown, Promise<boolean>>();\n\n readonly registrations = computed(() => this.#entries().map(entry => entry.ref));\n readonly dirty = computed(() => this.#dirtyEntries().length > 0);\n\n constructor() {\n effect(onCleanup => {\n if (!this.#window || !this.dirty()) return;\n const listener = (event: BeforeUnloadEvent): void => {\n const message = this.#dirtyEntries()[0]?.watcher.message ?? this.#i18n.t('core.service.unsaved-changes.message');\n event.preventDefault();\n event.returnValue = message;\n };\n this.#window.addEventListener('beforeunload', listener);\n onCleanup(() => this.#window?.removeEventListener('beforeunload', listener));\n });\n\n this.#destroyRef.onDestroy(() => {\n for (const entry of this.#entries()) {\n if (entry.destroyed()) continue;\n entry.destroyed.set(true);\n this.#runCleanup(entry.watcher);\n }\n this.#entries.set([]);\n this.#pendingPrompts.clear();\n });\n }\n\n register(watcher: SdUnsavedChangesWatcher): SdUnsavedChangesRegistration {\n const existing = this.#entries().find(entry => entry.watcher.id === watcher.id && Object.is(entry.watcher.scope, watcher.scope));\n if (existing) {\n if (existing.watcher !== watcher) this.#runCleanup(watcher);\n return existing.ref;\n }\n\n const dirtyOverride = signal<boolean | undefined>(undefined);\n const destroyed = signal(false);\n const dirty = computed(() => !destroyed() && (dirtyOverride() ?? readDirty(watcher.isDirty)));\n const entry = {} as RegistryEntry;\n const ref: SdUnsavedChangesRegistration = {\n id: watcher.id,\n scope: watcher.scope,\n message: watcher.message,\n destroyed: destroyed.asReadonly(),\n dirty,\n markDirty: () => dirtyOverride.set(true),\n markPristine: () => dirtyOverride.set(false),\n save: () => this.#runEntryAction(entry, 'save'),\n reset: () => this.#runEntryAction(entry, 'reset'),\n discard: () => this.#runEntryAction(entry, 'discard'),\n destroy: () => this.#destroyEntry(entry),\n };\n Object.assign(entry, { watcher, dirtyOverride, destroyed, dirty, ref });\n this.#entries.update(entries => [...entries, entry]);\n return ref;\n }\n\n hasChanges(scope?: unknown): boolean {\n return this.#dirtyEntries(scope).length > 0;\n }\n\n confirmLeave(options: SdUnsavedChangesConfirmOptions = {}): Promise<boolean> {\n if (!this.hasChanges(options.scope)) return Promise.resolve(true);\n const promptKey = options.scope;\n const existingPrompt = this.#pendingPrompts.get(promptKey);\n if (existingPrompt) return existingPrompt;\n\n const watchers = this.#dirtyEntries(options.scope).map(entry => entry.ref);\n const message = watchers.find(watcher => watcher.message)?.message ?? this.#i18n.t('core.service.unsaved-changes.message');\n const run = async (): Promise<boolean> => {\n try {\n const rawDecision = await Promise.resolve(\n this.#adapter.confirm({ reason: options.reason ?? 'manual', scope: options.scope, message, watchers })\n );\n const decision: SdUnsavedChangesDecision = typeof rawDecision === 'boolean' ? (rawDecision ? 'discard' : 'cancel') : rawDecision;\n if (decision === 'save') return this.saveAll(options.scope);\n if (decision === 'discard') return this.discardAll(options.scope);\n return false;\n } catch {\n return false;\n }\n };\n\n const pending = run().finally(() => {\n if (this.#pendingPrompts.get(promptKey) === pending) this.#pendingPrompts.delete(promptKey);\n });\n this.#pendingPrompts.set(promptKey, pending);\n return pending;\n }\n\n saveAll(scope?: unknown): Promise<boolean> {\n return this.#runAll('save', scope);\n }\n\n resetAll(scope?: unknown): Promise<boolean> {\n return this.#runAll('reset', scope);\n }\n\n discardAll(scope?: unknown): Promise<boolean> {\n return this.#runAll('discard', scope);\n }\n\n async #runAll(action: 'save' | 'reset' | 'discard', scope?: unknown): Promise<boolean> {\n for (const entry of this.#dirtyEntries(scope)) {\n if (!(await this.#runEntryAction(entry, action))) return false;\n }\n return true;\n }\n\n async #runEntryAction(entry: RegistryEntry, action: 'save' | 'reset' | 'discard'): Promise<boolean> {\n if (entry.destroyed()) return true;\n try {\n const callback = entry.watcher[action];\n const result = callback ? await Promise.resolve(callback()) : undefined;\n if (result === false) return false;\n // A callback owns its underlying dirty source (for example FormGroup\n // marks itself pristine). Only synthesize pristine state when there is\n // no callback capable of updating that source.\n entry.dirtyOverride.set(callback ? undefined : false);\n return true;\n } catch {\n return false;\n }\n }\n\n #dirtyEntries(scope?: unknown): RegistryEntry[] {\n return this.#entries().filter(entry => (scope === undefined || Object.is(entry.watcher.scope, scope)) && entry.dirty());\n }\n\n #destroyEntry(entry: RegistryEntry): void {\n if (entry.destroyed()) return;\n entry.destroyed.set(true);\n this.#runCleanup(entry.watcher);\n this.#entries.update(entries => entries.filter(candidate => candidate !== entry));\n }\n\n #runCleanup(watcher: SdUnsavedChangesWatcher): void {\n try {\n watcher.cleanup?.();\n } catch {\n // Cleanup must never prevent registry teardown.\n }\n }\n}\n\nfunction readDirty(source: SdUnsavedChangesWatcher['isDirty']): boolean {\n if (typeof source === 'boolean') return source;\n if (isSignal(source)) return !!source();\n return !!source();\n}\n","import { signal } from '@angular/core';\nimport { FormGroup } from '@angular/forms';\nimport { CanDeactivateFn } from '@angular/router';\nimport { SdUnsavedChangesConfirmOptions, SdUnsavedChangesMaybeAsync, SdUnsavedChangesRegistration } from './unsaved-changes.model';\nimport { SdUnsavedChangesService } from './unsaved-changes.service';\nimport { inject } from '@angular/core';\n\nexport interface SdUnsavedChangesFormOptions<TValue> {\n readonly id: string;\n readonly scope?: unknown;\n readonly message?: string;\n readonly save?: (value: TValue) => SdUnsavedChangesMaybeAsync<void | boolean>;\n readonly reset?: (form: FormGroup) => SdUnsavedChangesMaybeAsync<void | boolean>;\n readonly discard?: (form: FormGroup) => SdUnsavedChangesMaybeAsync<void | boolean>;\n}\n\nexport function registerSdUnsavedChangesForm<TValue extends Record<string, unknown>>(\n service: SdUnsavedChangesService,\n form: FormGroup,\n options: SdUnsavedChangesFormOptions<TValue>\n): SdUnsavedChangesRegistration {\n let snapshot = cloneValue(form.getRawValue()) as TValue;\n const dirty = signal(form.dirty);\n const subscription = form.events.subscribe(() => dirty.set(form.dirty));\n const resetToSnapshot = (): void => {\n form.reset(cloneValue(snapshot));\n form.markAsPristine();\n dirty.set(false);\n };\n const registration = service.register({\n id: options.id,\n scope: options.scope,\n message: options.message,\n isDirty: dirty,\n save: async () => {\n const result = options.save ? await Promise.resolve(options.save(form.getRawValue() as TValue)) : undefined;\n if (result === false) return false;\n snapshot = cloneValue(form.getRawValue()) as TValue;\n form.markAsPristine();\n dirty.set(false);\n return true;\n },\n reset: async () => {\n const result = options.reset ? await Promise.resolve(options.reset(form)) : undefined;\n if (result === false) return false;\n if (!options.reset) resetToSnapshot();\n else {\n form.markAsPristine();\n dirty.set(false);\n }\n return true;\n },\n discard: async () => {\n const result = options.discard ? await Promise.resolve(options.discard(form)) : undefined;\n if (result === false) return false;\n if (!options.discard) resetToSnapshot();\n else {\n form.markAsPristine();\n dirty.set(false);\n }\n return true;\n },\n cleanup: () => subscription.unsubscribe(),\n });\n return registration;\n}\n\nexport function createSdUnsavedChangesCloseGuard(\n service: SdUnsavedChangesService,\n options: Omit<SdUnsavedChangesConfirmOptions, 'reason'> = {}\n): () => Promise<boolean> {\n return () => service.confirmLeave({ ...options, reason: 'close' });\n}\n\nexport const sdUnsavedChangesGuard: CanDeactivateFn<unknown> = () => inject(SdUnsavedChangesService).confirmLeave({ reason: 'navigation' });\n\nfunction cloneValue<T>(value: T): T {\n if (value instanceof Date) return new Date(value.getTime()) as T;\n if (Array.isArray(value)) return value.map(item => cloneValue(item)) as T;\n if (value && typeof value === 'object') {\n return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, cloneValue(item)])) as T;\n }\n return value;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,76 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, inject, DestroyRef, signal, computed, Injectable } from '@angular/core';
3
+
4
+ const SD_VIEWPORT_DEFAULT_BREAKPOINTS = Object.freeze({
5
+ mobile: 0,
6
+ tablet: 768,
7
+ desktop: 1024,
8
+ });
9
+ const SD_VIEWPORT = new InjectionToken('sd.viewport', {
10
+ providedIn: 'root',
11
+ factory: () => (typeof window === 'undefined' ? null : window),
12
+ });
13
+ const SD_VIEWPORT_BREAKPOINTS = new InjectionToken('sd.viewport.breakpoints', {
14
+ providedIn: 'root',
15
+ factory: () => SD_VIEWPORT_DEFAULT_BREAKPOINTS,
16
+ });
17
+ function sdNormalizeViewportBreakpoints(value) {
18
+ const mobile = normalizeBreakpoint(value.mobile);
19
+ const tablet = normalizeBreakpoint(value.tablet);
20
+ const desktop = normalizeBreakpoint(value.desktop);
21
+ if (mobile === undefined || tablet === undefined || desktop === undefined || mobile >= tablet || tablet >= desktop) {
22
+ return SD_VIEWPORT_DEFAULT_BREAKPOINTS;
23
+ }
24
+ return Object.freeze({ mobile, tablet, desktop });
25
+ }
26
+ function normalizeBreakpoint(value) {
27
+ return Number.isFinite(value) && value >= 0 ? Math.floor(value) : undefined;
28
+ }
29
+
30
+ /**
31
+ * Exposes one SSR-safe, signal-based viewport state for the application.
32
+ * Breakpoints use min-width semantics and can be configured through `SD_VIEWPORT_BREAKPOINTS`.
33
+ */
34
+ class SdViewportService {
35
+ #viewport = inject(SD_VIEWPORT);
36
+ #destroyRef = inject(DestroyRef);
37
+ #width = signal(this.#viewport?.innerWidth ?? Number.MAX_SAFE_INTEGER, ...(ngDevMode ? [{ debugName: "#width" }] : /* istanbul ignore next */ []));
38
+ #height = signal(this.#viewport?.innerHeight ?? Number.MAX_SAFE_INTEGER, ...(ngDevMode ? [{ debugName: "#height" }] : /* istanbul ignore next */ []));
39
+ breakpoints = sdNormalizeViewportBreakpoints(inject(SD_VIEWPORT_BREAKPOINTS));
40
+ width = this.#width.asReadonly();
41
+ height = this.#height.asReadonly();
42
+ currentBreakpoint = computed(() => {
43
+ const width = this.width();
44
+ if (width >= this.breakpoints.desktop)
45
+ return 'desktop';
46
+ if (width >= this.breakpoints.tablet)
47
+ return 'tablet';
48
+ return 'mobile';
49
+ }, ...(ngDevMode ? [{ debugName: "currentBreakpoint" }] : /* istanbul ignore next */ []));
50
+ isMobile = computed(() => this.currentBreakpoint() === 'mobile', ...(ngDevMode ? [{ debugName: "isMobile" }] : /* istanbul ignore next */ []));
51
+ isTablet = computed(() => this.currentBreakpoint() === 'tablet', ...(ngDevMode ? [{ debugName: "isTablet" }] : /* istanbul ignore next */ []));
52
+ isDesktop = computed(() => this.currentBreakpoint() === 'desktop', ...(ngDevMode ? [{ debugName: "isDesktop" }] : /* istanbul ignore next */ []));
53
+ constructor() {
54
+ if (!this.#viewport)
55
+ return;
56
+ const onResize = () => {
57
+ this.#width.set(this.#viewport?.innerWidth ?? Number.MAX_SAFE_INTEGER);
58
+ this.#height.set(this.#viewport?.innerHeight ?? Number.MAX_SAFE_INTEGER);
59
+ };
60
+ this.#viewport.addEventListener('resize', onResize);
61
+ this.#destroyRef.onDestroy(() => this.#viewport?.removeEventListener('resize', onResize));
62
+ }
63
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdViewportService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
64
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdViewportService, providedIn: 'root' });
65
+ }
66
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdViewportService, decorators: [{
67
+ type: Injectable,
68
+ args: [{ providedIn: 'root' }]
69
+ }], ctorParameters: () => [] });
70
+
71
+ /**
72
+ * Generated bundle index. Do not edit.
73
+ */
74
+
75
+ export { SD_VIEWPORT, SD_VIEWPORT_BREAKPOINTS, SD_VIEWPORT_DEFAULT_BREAKPOINTS, SdViewportService, sdNormalizeViewportBreakpoints };
76
+ //# sourceMappingURL=sdcorejs-angular-services-viewport.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdcorejs-angular-services-viewport.mjs","sources":["../../../projects/sdcorejs-angular/services/viewport/src/viewport.tokens.ts","../../../projects/sdcorejs-angular/services/viewport/src/viewport.service.ts","../../../projects/sdcorejs-angular/services/viewport/sdcorejs-angular-services-viewport.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport { SdViewport, SdViewportBreakpoints } from './viewport.model';\n\nexport const SD_VIEWPORT_DEFAULT_BREAKPOINTS: Readonly<SdViewportBreakpoints> = Object.freeze({\n mobile: 0,\n tablet: 768,\n desktop: 1024,\n});\n\nexport const SD_VIEWPORT = new InjectionToken<SdViewport | null>('sd.viewport', {\n providedIn: 'root',\n factory: () => (typeof window === 'undefined' ? null : window),\n});\n\nexport const SD_VIEWPORT_BREAKPOINTS = new InjectionToken<Readonly<SdViewportBreakpoints>>('sd.viewport.breakpoints', {\n providedIn: 'root',\n factory: () => SD_VIEWPORT_DEFAULT_BREAKPOINTS,\n});\n\nexport function sdNormalizeViewportBreakpoints(value: Readonly<SdViewportBreakpoints>): Readonly<SdViewportBreakpoints> {\n const mobile = normalizeBreakpoint(value.mobile);\n const tablet = normalizeBreakpoint(value.tablet);\n const desktop = normalizeBreakpoint(value.desktop);\n\n if (mobile === undefined || tablet === undefined || desktop === undefined || mobile >= tablet || tablet >= desktop) {\n return SD_VIEWPORT_DEFAULT_BREAKPOINTS;\n }\n\n return Object.freeze({ mobile, tablet, desktop });\n}\n\nfunction normalizeBreakpoint(value: number): number | undefined {\n return Number.isFinite(value) && value >= 0 ? Math.floor(value) : undefined;\n}\n","import { DestroyRef, Injectable, computed, inject, signal } from '@angular/core';\nimport { SdViewportBreakpoint } from './viewport.model';\nimport { SD_VIEWPORT, SD_VIEWPORT_BREAKPOINTS, sdNormalizeViewportBreakpoints } from './viewport.tokens';\n\n/**\n * Exposes one SSR-safe, signal-based viewport state for the application.\n * Breakpoints use min-width semantics and can be configured through `SD_VIEWPORT_BREAKPOINTS`.\n */\n@Injectable({ providedIn: 'root' })\nexport class SdViewportService {\n readonly #viewport = inject(SD_VIEWPORT);\n readonly #destroyRef = inject(DestroyRef);\n readonly #width = signal(this.#viewport?.innerWidth ?? Number.MAX_SAFE_INTEGER);\n readonly #height = signal(this.#viewport?.innerHeight ?? Number.MAX_SAFE_INTEGER);\n\n readonly breakpoints = sdNormalizeViewportBreakpoints(inject(SD_VIEWPORT_BREAKPOINTS));\n readonly width = this.#width.asReadonly();\n readonly height = this.#height.asReadonly();\n readonly currentBreakpoint = computed<SdViewportBreakpoint>(() => {\n const width = this.width();\n if (width >= this.breakpoints.desktop) return 'desktop';\n if (width >= this.breakpoints.tablet) return 'tablet';\n return 'mobile';\n });\n readonly isMobile = computed(() => this.currentBreakpoint() === 'mobile');\n readonly isTablet = computed(() => this.currentBreakpoint() === 'tablet');\n readonly isDesktop = computed(() => this.currentBreakpoint() === 'desktop');\n\n constructor() {\n if (!this.#viewport) return;\n\n const onResize = (): void => {\n this.#width.set(this.#viewport?.innerWidth ?? Number.MAX_SAFE_INTEGER);\n this.#height.set(this.#viewport?.innerHeight ?? Number.MAX_SAFE_INTEGER);\n };\n this.#viewport.addEventListener('resize', onResize);\n this.#destroyRef.onDestroy(() => this.#viewport?.removeEventListener('resize', onResize));\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -1,11 +1,15 @@
1
1
  export * from '@sdcorejs/angular/services/cache';
2
2
  export * from '@sdcorejs/angular/services/storage';
3
+ export * from '@sdcorejs/angular/services/persistence';
3
4
  export * from '@sdcorejs/angular/services/notify';
4
5
  export * from '@sdcorejs/angular/services/confirm';
5
6
  export * from '@sdcorejs/angular/services/api';
6
7
  export * from '@sdcorejs/angular/services/loading';
7
8
  export * from '@sdcorejs/angular/services/excel';
8
9
  export * from '@sdcorejs/angular/services/docx';
10
+ export * from '@sdcorejs/angular/services/viewport';
11
+ export * from '@sdcorejs/angular/services/unsaved-changes';
12
+ export * from '@sdcorejs/angular/services/task';
9
13
 
10
14
  /**
11
15
  * Generated bundle index. Do not edit.
@@ -1 +1 @@
1
- {"version":3,"file":"sdcorejs-angular-services.mjs","sources":["../../../projects/sdcorejs-angular/services/sdcorejs-angular-services.ts"],"sourcesContent":["/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;;;AAAA;;AAEG"}
1
+ {"version":3,"file":"sdcorejs-angular-services.mjs","sources":["../../../projects/sdcorejs-angular/services/sdcorejs-angular-services.ts"],"sourcesContent":["/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;;;;;;;;;;;AAAA;;AAEG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdcorejs/angular",
3
- "version": "21.1.2",
3
+ "version": "21.1.4",
4
4
  "license": "MIT",
5
5
  "peerDependencies": {
6
6
  "@angular/common": "^19.0.0 || ^20.0.0 || ^21.0.0",
@@ -9,6 +9,7 @@
9
9
  "@angular/material-date-fns-adapter": "^19.0.0 || ^20.0.0 || ^21.0.0"
10
10
  },
11
11
  "dependencies": {
12
+ "@sdcorejs/angular-material-datetime": "1.0.3",
12
13
  "@sdcorejs/utils": "1.1.4",
13
14
  "@lucide/angular": "^1.22.0",
14
15
  "@ckeditor/ckeditor5-angular": "11.1.2",
@@ -42,6 +43,10 @@
42
43
  "types": "./types/sdcorejs-angular-components-anchor.d.ts",
43
44
  "default": "./fesm2022/sdcorejs-angular-components-anchor.mjs"
44
45
  },
46
+ "./components/audit-diff": {
47
+ "types": "./types/sdcorejs-angular-components-audit-diff.d.ts",
48
+ "default": "./fesm2022/sdcorejs-angular-components-audit-diff.mjs"
49
+ },
45
50
  "./components/autoid-inspector": {
46
51
  "types": "./types/sdcorejs-angular-components-autoid-inspector.d.ts",
47
52
  "default": "./fesm2022/sdcorejs-angular-components-autoid-inspector.mjs"
@@ -58,6 +63,10 @@
58
63
  "types": "./types/sdcorejs-angular-components-base.d.ts",
59
64
  "default": "./fesm2022/sdcorejs-angular-components-base.mjs"
60
65
  },
66
+ "./components/breadcrumb": {
67
+ "types": "./types/sdcorejs-angular-components-breadcrumb.d.ts",
68
+ "default": "./fesm2022/sdcorejs-angular-components-breadcrumb.mjs"
69
+ },
61
70
  "./components/button": {
62
71
  "types": "./types/sdcorejs-angular-components-button.d.ts",
63
72
  "default": "./fesm2022/sdcorejs-angular-components-button.mjs"
@@ -74,6 +83,10 @@
74
83
  "types": "./types/sdcorejs-angular-components-code-editor.d.ts",
75
84
  "default": "./fesm2022/sdcorejs-angular-components-code-editor.mjs"
76
85
  },
86
+ "./components/data-state": {
87
+ "types": "./types/sdcorejs-angular-components-data-state.d.ts",
88
+ "default": "./fesm2022/sdcorejs-angular-components-data-state.mjs"
89
+ },
77
90
  "./components/document-builder": {
78
91
  "types": "./types/sdcorejs-angular-components-document-builder.d.ts",
79
92
  "default": "./fesm2022/sdcorejs-angular-components-document-builder.mjs"
@@ -98,6 +111,10 @@
98
111
  "types": "./types/sdcorejs-angular-components-inform.d.ts",
99
112
  "default": "./fesm2022/sdcorejs-angular-components-inform.mjs"
100
113
  },
114
+ "./components/job-progress": {
115
+ "types": "./types/sdcorejs-angular-components-job-progress.d.ts",
116
+ "default": "./fesm2022/sdcorejs-angular-components-job-progress.mjs"
117
+ },
101
118
  "./components/mini-editor": {
102
119
  "types": "./types/sdcorejs-angular-components-mini-editor.d.ts",
103
120
  "default": "./fesm2022/sdcorejs-angular-components-mini-editor.mjs"
@@ -218,6 +235,10 @@
218
235
  "types": "./types/sdcorejs-angular-forms-directives.d.ts",
219
236
  "default": "./fesm2022/sdcorejs-angular-forms-directives.mjs"
220
237
  },
238
+ "./forms/entity-picker": {
239
+ "types": "./types/sdcorejs-angular-forms-entity-picker.d.ts",
240
+ "default": "./fesm2022/sdcorejs-angular-forms-entity-picker.mjs"
241
+ },
221
242
  "./forms/inline-text": {
222
243
  "types": "./types/sdcorejs-angular-forms-inline-text.d.ts",
223
244
  "default": "./fesm2022/sdcorejs-angular-forms-inline-text.mjs"
@@ -258,6 +279,18 @@
258
279
  "types": "./types/sdcorejs-angular-forms-textarea.d.ts",
259
280
  "default": "./fesm2022/sdcorejs-angular-forms-textarea.mjs"
260
281
  },
282
+ "./forms/time": {
283
+ "types": "./types/sdcorejs-angular-forms-time.d.ts",
284
+ "default": "./fesm2022/sdcorejs-angular-forms-time.mjs"
285
+ },
286
+ "./forms/time-range": {
287
+ "types": "./types/sdcorejs-angular-forms-time-range.d.ts",
288
+ "default": "./fesm2022/sdcorejs-angular-forms-time-range.mjs"
289
+ },
290
+ "./forms/tree-select": {
291
+ "types": "./types/sdcorejs-angular-forms-tree-select.d.ts",
292
+ "default": "./fesm2022/sdcorejs-angular-forms-tree-select.mjs"
293
+ },
261
294
  "./handlers": {
262
295
  "types": "./types/sdcorejs-angular-handlers.d.ts",
263
296
  "default": "./fesm2022/sdcorejs-angular-handlers.mjs"
@@ -282,10 +315,6 @@
282
315
  "types": "./types/sdcorejs-angular-modules-auth.d.ts",
283
316
  "default": "./fesm2022/sdcorejs-angular-modules-auth.mjs"
284
317
  },
285
- "./modules/authom": {
286
- "types": "./types/sdcorejs-angular-modules-authom.d.ts",
287
- "default": "./fesm2022/sdcorejs-angular-modules-authom.mjs"
288
- },
289
318
  "./modules/icon": {
290
319
  "types": "./types/sdcorejs-angular-modules-icon.d.ts",
291
320
  "default": "./fesm2022/sdcorejs-angular-modules-icon.mjs"
@@ -342,10 +371,26 @@
342
371
  "types": "./types/sdcorejs-angular-services-notify.d.ts",
343
372
  "default": "./fesm2022/sdcorejs-angular-services-notify.mjs"
344
373
  },
374
+ "./services/persistence": {
375
+ "types": "./types/sdcorejs-angular-services-persistence.d.ts",
376
+ "default": "./fesm2022/sdcorejs-angular-services-persistence.mjs"
377
+ },
345
378
  "./services/storage": {
346
379
  "types": "./types/sdcorejs-angular-services-storage.d.ts",
347
380
  "default": "./fesm2022/sdcorejs-angular-services-storage.mjs"
348
381
  },
382
+ "./services/task": {
383
+ "types": "./types/sdcorejs-angular-services-task.d.ts",
384
+ "default": "./fesm2022/sdcorejs-angular-services-task.mjs"
385
+ },
386
+ "./services/unsaved-changes": {
387
+ "types": "./types/sdcorejs-angular-services-unsaved-changes.d.ts",
388
+ "default": "./fesm2022/sdcorejs-angular-services-unsaved-changes.mjs"
389
+ },
390
+ "./services/viewport": {
391
+ "types": "./types/sdcorejs-angular-services-viewport.d.ts",
392
+ "default": "./fesm2022/sdcorejs-angular-services-viewport.mjs"
393
+ },
349
394
  "./utilities": {
350
395
  "types": "./types/sdcorejs-angular-utilities.d.ts",
351
396
  "default": "./fesm2022/sdcorejs-angular-utilities.mjs"
@@ -0,0 +1,74 @@
1
+ import * as _angular_core from '@angular/core';
2
+ import { TemplateRef } from '@angular/core';
3
+
4
+ type SdAuditDiffKind = 'added' | 'removed' | 'changed' | 'unchanged';
5
+ type SdAuditDiffSide = 'before' | 'after';
6
+ type SdAuditDiffArrayKey = string | ((item: unknown) => unknown);
7
+ interface SdAuditDiffFormatContext {
8
+ readonly path: string;
9
+ readonly configPath: string;
10
+ readonly side: SdAuditDiffSide;
11
+ readonly kind: SdAuditDiffKind;
12
+ readonly before: unknown;
13
+ readonly after: unknown;
14
+ }
15
+ interface SdAuditDiffField {
16
+ readonly path: string;
17
+ readonly label?: string;
18
+ readonly order?: number;
19
+ readonly hidden?: boolean;
20
+ readonly redacted?: boolean;
21
+ readonly arrayKey?: SdAuditDiffArrayKey;
22
+ readonly enumMap?: Readonly<Record<string, unknown>>;
23
+ readonly format?: (value: unknown, context: SdAuditDiffFormatContext) => unknown;
24
+ }
25
+ interface SdAuditDiffOptions {
26
+ readonly fields?: readonly SdAuditDiffField[];
27
+ readonly includeUnchanged?: boolean;
28
+ readonly redactedValue?: unknown;
29
+ readonly rootLabel?: string;
30
+ }
31
+ interface SdAuditDiffRow {
32
+ readonly id: string;
33
+ readonly path: string;
34
+ readonly configPath: string;
35
+ readonly label: string;
36
+ readonly kind: SdAuditDiffKind;
37
+ readonly before: unknown;
38
+ readonly after: unknown;
39
+ readonly beforePresent: boolean;
40
+ readonly afterPresent: boolean;
41
+ readonly redacted: boolean;
42
+ }
43
+ declare function sdBuildAuditDiff(before: unknown, after: unknown, options?: SdAuditDiffOptions): SdAuditDiffRow[];
44
+
45
+ type SdAuditDiffMode = 'table' | 'detail-list';
46
+ interface SdAuditDiffValueTemplateContext {
47
+ readonly $implicit: unknown;
48
+ readonly row: SdAuditDiffRow;
49
+ readonly side: SdAuditDiffSide;
50
+ }
51
+ declare class SdAuditDiffValueTemplateDirective {
52
+ readonly template: TemplateRef<SdAuditDiffValueTemplateContext>;
53
+ static ngTemplateContextGuard(_directive: SdAuditDiffValueTemplateDirective, _context: unknown): _context is SdAuditDiffValueTemplateContext;
54
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SdAuditDiffValueTemplateDirective, never>;
55
+ static ɵdir: _angular_core.ɵɵDirectiveDeclaration<SdAuditDiffValueTemplateDirective, "ng-template[sdAuditDiffValue]", never, {}, {}, never, never, true, never>;
56
+ }
57
+ declare class SdAuditDiff {
58
+ #private;
59
+ readonly before: _angular_core.InputSignal<unknown>;
60
+ readonly after: _angular_core.InputSignal<unknown>;
61
+ readonly options: _angular_core.InputSignal<SdAuditDiffOptions>;
62
+ readonly mode: _angular_core.InputSignal<SdAuditDiffMode>;
63
+ readonly ariaLabel: _angular_core.InputSignal<string | undefined>;
64
+ readonly rows: _angular_core.Signal<SdAuditDiffRow[]>;
65
+ protected readonly valueTemplate: _angular_core.Signal<SdAuditDiffValueTemplateDirective | undefined>;
66
+ protected text(key: string): string;
67
+ protected valueContext(row: SdAuditDiffRow, side: SdAuditDiffSide): SdAuditDiffValueTemplateContext;
68
+ protected displayValue(row: SdAuditDiffRow, side: SdAuditDiffSide): string;
69
+ static ɵfac: _angular_core.ɵɵFactoryDeclaration<SdAuditDiff, never>;
70
+ static ɵcmp: _angular_core.ɵɵComponentDeclaration<SdAuditDiff, "sd-audit-diff", never, { "before": { "alias": "before"; "required": false; "isSignal": true; }; "after": { "alias": "after"; "required": false; "isSignal": true; }; "options": { "alias": "options"; "required": false; "isSignal": true; }; "mode": { "alias": "mode"; "required": false; "isSignal": true; }; "ariaLabel": { "alias": "ariaLabel"; "required": false; "isSignal": true; }; }, {}, ["valueTemplate"], never, true, never>;
71
+ }
72
+
73
+ export { SdAuditDiff, SdAuditDiffValueTemplateDirective, sdBuildAuditDiff };
74
+ export type { SdAuditDiffArrayKey, SdAuditDiffField, SdAuditDiffFormatContext, SdAuditDiffKind, SdAuditDiffMode, SdAuditDiffOptions, SdAuditDiffRow, SdAuditDiffSide, SdAuditDiffValueTemplateContext };
@@ -20,7 +20,7 @@ declare class SdBadge {
20
20
  tooltip: _angular_core.InputSignal<string | null | undefined>;
21
21
  icon: _angular_core.InputSignal<string | null | undefined>;
22
22
  size: _angular_core.InputSignalWithTransform<Size, Size | null | undefined>;
23
- click: EventEmitter<Event>;
23
+ readonly click: EventEmitter<Event>;
24
24
  onClick: (event: Event) => void;
25
25
  effectiveColor: _angular_core.Signal<Color>;
26
26
  baseColorClasses: _angular_core.Signal<{