@sdcorejs/angular 21.1.3 → 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 (168) hide show
  1. package/README.md +17 -14
  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 +495 -838
  17. package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
  18. package/fesm2022/sdcorejs-angular-components-import-excel.mjs +24 -34
  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 +4 -4
  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 +16 -22
  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 +96 -63
  39. package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
  40. package/fesm2022/sdcorejs-angular-components-tree.mjs +186 -17
  41. package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
  42. package/fesm2022/sdcorejs-angular-components-upload-file.mjs +15 -22
  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 +7 -7
  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 +6 -3
  53. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
  54. package/fesm2022/sdcorejs-angular-forms-chip.mjs +6 -3
  55. package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
  56. package/fesm2022/sdcorejs-angular-forms-date-range.mjs +16 -13
  57. package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
  58. package/fesm2022/sdcorejs-angular-forms-date.mjs +42 -65
  59. package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
  60. package/fesm2022/sdcorejs-angular-forms-datetime.mjs +51 -66
  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 +1 -1
  65. package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
  66. package/fesm2022/sdcorejs-angular-forms-input-number.mjs +7 -5
  67. package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
  68. package/fesm2022/sdcorejs-angular-forms-input.mjs +290 -56
  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 +6 -20
  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 +6 -5
  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-layout.mjs +861 -80
  93. package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
  94. package/fesm2022/sdcorejs-angular-pipes.mjs +5 -7
  95. package/fesm2022/sdcorejs-angular-pipes.mjs.map +1 -1
  96. package/fesm2022/sdcorejs-angular-services-api.mjs +407 -137
  97. package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
  98. package/fesm2022/sdcorejs-angular-services-cache.mjs +515 -105
  99. package/fesm2022/sdcorejs-angular-services-cache.mjs.map +1 -1
  100. package/fesm2022/sdcorejs-angular-services-confirm.mjs +5 -7
  101. package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
  102. package/fesm2022/sdcorejs-angular-services-docx.mjs +7 -10
  103. package/fesm2022/sdcorejs-angular-services-docx.mjs.map +1 -1
  104. package/fesm2022/sdcorejs-angular-services-loading.mjs +339 -74
  105. package/fesm2022/sdcorejs-angular-services-loading.mjs.map +1 -1
  106. package/fesm2022/sdcorejs-angular-services-notify.mjs +7 -10
  107. package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
  108. package/fesm2022/sdcorejs-angular-services-persistence.mjs +899 -0
  109. package/fesm2022/sdcorejs-angular-services-persistence.mjs.map +1 -0
  110. package/fesm2022/sdcorejs-angular-services-storage.mjs +324 -122
  111. package/fesm2022/sdcorejs-angular-services-storage.mjs.map +1 -1
  112. package/fesm2022/sdcorejs-angular-services-task.mjs +389 -0
  113. package/fesm2022/sdcorejs-angular-services-task.mjs.map +1 -0
  114. package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs +255 -0
  115. package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs.map +1 -0
  116. package/fesm2022/sdcorejs-angular-services-viewport.mjs +76 -0
  117. package/fesm2022/sdcorejs-angular-services-viewport.mjs.map +1 -0
  118. package/fesm2022/sdcorejs-angular-services.mjs +4 -0
  119. package/fesm2022/sdcorejs-angular-services.mjs.map +1 -1
  120. package/package.json +49 -1
  121. package/types/sdcorejs-angular-components-audit-diff.d.ts +74 -0
  122. package/types/sdcorejs-angular-components-badge.d.ts +1 -1
  123. package/types/sdcorejs-angular-components-breadcrumb.d.ts +53 -0
  124. package/types/sdcorejs-angular-components-data-state.d.ts +44 -0
  125. package/types/sdcorejs-angular-components-document-builder.d.ts +5 -5
  126. package/types/sdcorejs-angular-components-form-generic.d.ts +67 -104
  127. package/types/sdcorejs-angular-components-import-excel.d.ts +4 -14
  128. package/types/sdcorejs-angular-components-job-progress.d.ts +38 -0
  129. package/types/sdcorejs-angular-components-mini-editor.d.ts +9 -9
  130. package/types/sdcorejs-angular-components-modal.d.ts +7 -1
  131. package/types/sdcorejs-angular-components-preview.d.ts +181 -28
  132. package/types/sdcorejs-angular-components-side-drawer.d.ts +7 -1
  133. package/types/sdcorejs-angular-components-tab.d.ts +9 -2
  134. package/types/sdcorejs-angular-components-table.d.ts +8 -7
  135. package/types/sdcorejs-angular-components-tree.d.ts +24 -2
  136. package/types/sdcorejs-angular-components-upload-file.d.ts +5 -4
  137. package/types/sdcorejs-angular-components.d.ts +4 -0
  138. package/types/sdcorejs-angular-directives.d.ts +10 -8
  139. package/types/sdcorejs-angular-forms-autocomplete.d.ts +3 -4
  140. package/types/sdcorejs-angular-forms-checkbox.d.ts +4 -8
  141. package/types/sdcorejs-angular-forms-date-range.d.ts +6 -7
  142. package/types/sdcorejs-angular-forms-date.d.ts +2 -2
  143. package/types/sdcorejs-angular-forms-datetime.d.ts +2 -2
  144. package/types/sdcorejs-angular-forms-entity-picker.d.ts +128 -0
  145. package/types/sdcorejs-angular-forms-input-number.d.ts +1 -1
  146. package/types/sdcorejs-angular-forms-input.d.ts +74 -11
  147. package/types/sdcorejs-angular-forms-models.d.ts +86 -3
  148. package/types/sdcorejs-angular-forms-radio.d.ts +12 -8
  149. package/types/sdcorejs-angular-forms-switch.d.ts +4 -7
  150. package/types/sdcorejs-angular-forms-time-range.d.ts +98 -0
  151. package/types/sdcorejs-angular-forms-time.d.ts +117 -0
  152. package/types/sdcorejs-angular-forms-tree-select.d.ts +76 -0
  153. package/types/sdcorejs-angular-forms.d.ts +15 -5
  154. package/types/sdcorejs-angular-i18n.d.ts +68 -2
  155. package/types/sdcorejs-angular-interceptors.d.ts +5 -6
  156. package/types/sdcorejs-angular-modules-layout.d.ts +288 -15
  157. package/types/sdcorejs-angular-pipes.d.ts +2 -2
  158. package/types/sdcorejs-angular-services-api.d.ts +68 -24
  159. package/types/sdcorejs-angular-services-cache.d.ts +52 -16
  160. package/types/sdcorejs-angular-services-confirm.d.ts +3 -2
  161. package/types/sdcorejs-angular-services-docx.d.ts +2 -3
  162. package/types/sdcorejs-angular-services-loading.d.ts +16 -8
  163. package/types/sdcorejs-angular-services-persistence.d.ts +159 -0
  164. package/types/sdcorejs-angular-services-storage.d.ts +26 -8
  165. package/types/sdcorejs-angular-services-task.d.ts +104 -0
  166. package/types/sdcorejs-angular-services-unsaved-changes.d.ts +83 -0
  167. package/types/sdcorejs-angular-services-viewport.d.ts +42 -0
  168. package/types/sdcorejs-angular-services.d.ts +4 -0
@@ -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.3",
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",
@@ -43,6 +43,10 @@
43
43
  "types": "./types/sdcorejs-angular-components-anchor.d.ts",
44
44
  "default": "./fesm2022/sdcorejs-angular-components-anchor.mjs"
45
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
+ },
46
50
  "./components/autoid-inspector": {
47
51
  "types": "./types/sdcorejs-angular-components-autoid-inspector.d.ts",
48
52
  "default": "./fesm2022/sdcorejs-angular-components-autoid-inspector.mjs"
@@ -59,6 +63,10 @@
59
63
  "types": "./types/sdcorejs-angular-components-base.d.ts",
60
64
  "default": "./fesm2022/sdcorejs-angular-components-base.mjs"
61
65
  },
66
+ "./components/breadcrumb": {
67
+ "types": "./types/sdcorejs-angular-components-breadcrumb.d.ts",
68
+ "default": "./fesm2022/sdcorejs-angular-components-breadcrumb.mjs"
69
+ },
62
70
  "./components/button": {
63
71
  "types": "./types/sdcorejs-angular-components-button.d.ts",
64
72
  "default": "./fesm2022/sdcorejs-angular-components-button.mjs"
@@ -75,6 +83,10 @@
75
83
  "types": "./types/sdcorejs-angular-components-code-editor.d.ts",
76
84
  "default": "./fesm2022/sdcorejs-angular-components-code-editor.mjs"
77
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
+ },
78
90
  "./components/document-builder": {
79
91
  "types": "./types/sdcorejs-angular-components-document-builder.d.ts",
80
92
  "default": "./fesm2022/sdcorejs-angular-components-document-builder.mjs"
@@ -99,6 +111,10 @@
99
111
  "types": "./types/sdcorejs-angular-components-inform.d.ts",
100
112
  "default": "./fesm2022/sdcorejs-angular-components-inform.mjs"
101
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
+ },
102
118
  "./components/mini-editor": {
103
119
  "types": "./types/sdcorejs-angular-components-mini-editor.d.ts",
104
120
  "default": "./fesm2022/sdcorejs-angular-components-mini-editor.mjs"
@@ -219,6 +235,10 @@
219
235
  "types": "./types/sdcorejs-angular-forms-directives.d.ts",
220
236
  "default": "./fesm2022/sdcorejs-angular-forms-directives.mjs"
221
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
+ },
222
242
  "./forms/inline-text": {
223
243
  "types": "./types/sdcorejs-angular-forms-inline-text.d.ts",
224
244
  "default": "./fesm2022/sdcorejs-angular-forms-inline-text.mjs"
@@ -259,6 +279,18 @@
259
279
  "types": "./types/sdcorejs-angular-forms-textarea.d.ts",
260
280
  "default": "./fesm2022/sdcorejs-angular-forms-textarea.mjs"
261
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
+ },
262
294
  "./handlers": {
263
295
  "types": "./types/sdcorejs-angular-handlers.d.ts",
264
296
  "default": "./fesm2022/sdcorejs-angular-handlers.mjs"
@@ -339,10 +371,26 @@
339
371
  "types": "./types/sdcorejs-angular-services-notify.d.ts",
340
372
  "default": "./fesm2022/sdcorejs-angular-services-notify.mjs"
341
373
  },
374
+ "./services/persistence": {
375
+ "types": "./types/sdcorejs-angular-services-persistence.d.ts",
376
+ "default": "./fesm2022/sdcorejs-angular-services-persistence.mjs"
377
+ },
342
378
  "./services/storage": {
343
379
  "types": "./types/sdcorejs-angular-services-storage.d.ts",
344
380
  "default": "./fesm2022/sdcorejs-angular-services-storage.mjs"
345
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
+ },
346
394
  "./utilities": {
347
395
  "types": "./types/sdcorejs-angular-utilities.d.ts",
348
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<{