@sdcorejs/angular 20.1.3 → 20.1.5

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 +19 -14
  2. package/components/audit-diff/index.d.ts +74 -0
  3. package/components/badge/index.d.ts +1 -1
  4. package/components/breadcrumb/index.d.ts +53 -0
  5. package/components/data-state/index.d.ts +44 -0
  6. package/components/document-builder/index.d.ts +5 -5
  7. package/components/form-generic/index.d.ts +67 -104
  8. package/components/import-excel/index.d.ts +4 -14
  9. package/components/index.d.ts +4 -0
  10. package/components/job-progress/index.d.ts +38 -0
  11. package/components/mini-editor/index.d.ts +9 -9
  12. package/components/modal/index.d.ts +7 -1
  13. package/components/preview/index.d.ts +181 -28
  14. package/components/side-drawer/index.d.ts +7 -1
  15. package/components/tab/index.d.ts +9 -2
  16. package/components/table/index.d.ts +8 -7
  17. package/components/tree/index.d.ts +24 -2
  18. package/components/upload-file/index.d.ts +5 -4
  19. package/directives/index.d.ts +10 -8
  20. package/fesm2022/sdcorejs-angular-components-anchor.mjs +14 -14
  21. package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
  22. package/fesm2022/sdcorejs-angular-components-audit-diff.mjs +332 -0
  23. package/fesm2022/sdcorejs-angular-components-audit-diff.mjs.map +1 -0
  24. package/fesm2022/sdcorejs-angular-components-badge.mjs +1 -0
  25. package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
  26. package/fesm2022/sdcorejs-angular-components-breadcrumb.mjs +121 -0
  27. package/fesm2022/sdcorejs-angular-components-breadcrumb.mjs.map +1 -0
  28. package/fesm2022/sdcorejs-angular-components-data-state.mjs +68 -0
  29. package/fesm2022/sdcorejs-angular-components-data-state.mjs.map +1 -0
  30. package/fesm2022/sdcorejs-angular-components-document-builder.mjs +18 -23
  31. package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +1 -1
  32. package/fesm2022/sdcorejs-angular-components-editor.mjs +6 -4
  33. package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
  34. package/fesm2022/sdcorejs-angular-components-form-generic.mjs +495 -838
  35. package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
  36. package/fesm2022/sdcorejs-angular-components-import-excel.mjs +24 -34
  37. package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
  38. package/fesm2022/sdcorejs-angular-components-job-progress.mjs +97 -0
  39. package/fesm2022/sdcorejs-angular-components-job-progress.mjs.map +1 -0
  40. package/fesm2022/sdcorejs-angular-components-mini-editor.mjs +10 -20
  41. package/fesm2022/sdcorejs-angular-components-mini-editor.mjs.map +1 -1
  42. package/fesm2022/sdcorejs-angular-components-modal.mjs +61 -4
  43. package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
  44. package/fesm2022/sdcorejs-angular-components-preview.mjs +1438 -218
  45. package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
  46. package/fesm2022/sdcorejs-angular-components-query-bar.mjs +4 -4
  47. package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
  48. package/fesm2022/sdcorejs-angular-components-query-builder.mjs +1 -1
  49. package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
  50. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +39 -2
  51. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
  52. package/fesm2022/sdcorejs-angular-components-tab-router.mjs +16 -22
  53. package/fesm2022/sdcorejs-angular-components-tab-router.mjs.map +1 -1
  54. package/fesm2022/sdcorejs-angular-components-tab.mjs +48 -4
  55. package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
  56. package/fesm2022/sdcorejs-angular-components-table.mjs +96 -63
  57. package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
  58. package/fesm2022/sdcorejs-angular-components-tree.mjs +186 -17
  59. package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
  60. package/fesm2022/sdcorejs-angular-components-upload-file.mjs +15 -22
  61. package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
  62. package/fesm2022/sdcorejs-angular-components.mjs +4 -0
  63. package/fesm2022/sdcorejs-angular-components.mjs.map +1 -1
  64. package/fesm2022/sdcorejs-angular-directives.mjs +29 -41
  65. package/fesm2022/sdcorejs-angular-directives.mjs.map +1 -1
  66. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +7 -7
  67. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
  68. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs +20 -83
  69. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
  70. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +6 -3
  71. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
  72. package/fesm2022/sdcorejs-angular-forms-chip.mjs +6 -3
  73. package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
  74. package/fesm2022/sdcorejs-angular-forms-date-range.mjs +16 -13
  75. package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
  76. package/fesm2022/sdcorejs-angular-forms-date.mjs +42 -65
  77. package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
  78. package/fesm2022/sdcorejs-angular-forms-datetime.mjs +51 -66
  79. package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
  80. package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs +384 -0
  81. package/fesm2022/sdcorejs-angular-forms-entity-picker.mjs.map +1 -0
  82. package/fesm2022/sdcorejs-angular-forms-input-color.mjs +1 -1
  83. package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
  84. package/fesm2022/sdcorejs-angular-forms-input-number.mjs +7 -5
  85. package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
  86. package/fesm2022/sdcorejs-angular-forms-input.mjs +290 -56
  87. package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
  88. package/fesm2022/sdcorejs-angular-forms-models.mjs +155 -2
  89. package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
  90. package/fesm2022/sdcorejs-angular-forms-radio.mjs +44 -78
  91. package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
  92. package/fesm2022/sdcorejs-angular-forms-select.mjs +6 -20
  93. package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
  94. package/fesm2022/sdcorejs-angular-forms-switch.mjs +19 -76
  95. package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
  96. package/fesm2022/sdcorejs-angular-forms-textarea.mjs +6 -5
  97. package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
  98. package/fesm2022/sdcorejs-angular-forms-time-range.mjs +280 -0
  99. package/fesm2022/sdcorejs-angular-forms-time-range.mjs.map +1 -0
  100. package/fesm2022/sdcorejs-angular-forms-time.mjs +310 -0
  101. package/fesm2022/sdcorejs-angular-forms-time.mjs.map +1 -0
  102. package/fesm2022/sdcorejs-angular-forms-tree-select.mjs +210 -0
  103. package/fesm2022/sdcorejs-angular-forms-tree-select.mjs.map +1 -0
  104. package/fesm2022/sdcorejs-angular-forms.mjs +73 -4
  105. package/fesm2022/sdcorejs-angular-forms.mjs.map +1 -1
  106. package/fesm2022/sdcorejs-angular-i18n.mjs +375 -10
  107. package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
  108. package/fesm2022/sdcorejs-angular-interceptors.mjs +12 -17
  109. package/fesm2022/sdcorejs-angular-interceptors.mjs.map +1 -1
  110. package/fesm2022/sdcorejs-angular-modules-layout.mjs +994 -168
  111. package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
  112. package/fesm2022/sdcorejs-angular-pipes.mjs +5 -7
  113. package/fesm2022/sdcorejs-angular-pipes.mjs.map +1 -1
  114. package/fesm2022/sdcorejs-angular-services-api.mjs +407 -137
  115. package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
  116. package/fesm2022/sdcorejs-angular-services-cache.mjs +515 -105
  117. package/fesm2022/sdcorejs-angular-services-cache.mjs.map +1 -1
  118. package/fesm2022/sdcorejs-angular-services-confirm.mjs +5 -7
  119. package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
  120. package/fesm2022/sdcorejs-angular-services-docx.mjs +7 -10
  121. package/fesm2022/sdcorejs-angular-services-docx.mjs.map +1 -1
  122. package/fesm2022/sdcorejs-angular-services-loading.mjs +339 -74
  123. package/fesm2022/sdcorejs-angular-services-loading.mjs.map +1 -1
  124. package/fesm2022/sdcorejs-angular-services-notify.mjs +7 -10
  125. package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
  126. package/fesm2022/sdcorejs-angular-services-persistence.mjs +899 -0
  127. package/fesm2022/sdcorejs-angular-services-persistence.mjs.map +1 -0
  128. package/fesm2022/sdcorejs-angular-services-storage.mjs +324 -122
  129. package/fesm2022/sdcorejs-angular-services-storage.mjs.map +1 -1
  130. package/fesm2022/sdcorejs-angular-services-task.mjs +389 -0
  131. package/fesm2022/sdcorejs-angular-services-task.mjs.map +1 -0
  132. package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs +255 -0
  133. package/fesm2022/sdcorejs-angular-services-unsaved-changes.mjs.map +1 -0
  134. package/fesm2022/sdcorejs-angular-services-viewport.mjs +76 -0
  135. package/fesm2022/sdcorejs-angular-services-viewport.mjs.map +1 -0
  136. package/fesm2022/sdcorejs-angular-services.mjs +4 -0
  137. package/fesm2022/sdcorejs-angular-services.mjs.map +1 -1
  138. package/forms/autocomplete/index.d.ts +3 -4
  139. package/forms/checkbox/index.d.ts +4 -8
  140. package/forms/date/index.d.ts +2 -2
  141. package/forms/date-range/index.d.ts +6 -7
  142. package/forms/datetime/index.d.ts +2 -2
  143. package/forms/entity-picker/index.d.ts +128 -0
  144. package/forms/index.d.ts +15 -5
  145. package/forms/input/index.d.ts +74 -11
  146. package/forms/input-number/index.d.ts +1 -1
  147. package/forms/models/index.d.ts +86 -3
  148. package/forms/radio/index.d.ts +12 -8
  149. package/forms/switch/index.d.ts +4 -7
  150. package/forms/time/index.d.ts +117 -0
  151. package/forms/time-range/index.d.ts +98 -0
  152. package/forms/tree-select/index.d.ts +76 -0
  153. package/i18n/index.d.ts +72 -2
  154. package/interceptors/index.d.ts +5 -6
  155. package/modules/layout/index.d.ts +354 -17
  156. package/package.json +49 -1
  157. package/pipes/index.d.ts +2 -2
  158. package/services/api/index.d.ts +68 -24
  159. package/services/cache/index.d.ts +52 -16
  160. package/services/confirm/index.d.ts +3 -2
  161. package/services/docx/index.d.ts +2 -3
  162. package/services/index.d.ts +4 -0
  163. package/services/loading/index.d.ts +16 -8
  164. package/services/persistence/index.d.ts +159 -0
  165. package/services/storage/index.d.ts +26 -8
  166. package/services/task/index.d.ts +104 -0
  167. package/services/unsaved-changes/index.d.ts +83 -0
  168. package/services/viewport/index.d.ts +42 -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" }] : []));
30
+ #pendingPrompts = new Map();
31
+ registrations = computed(() => this.#entries().map(entry => entry.ref), ...(ngDevMode ? [{ debugName: "registrations" }] : []));
32
+ dirty = computed(() => this.#dirtyEntries().length > 0, ...(ngDevMode ? [{ debugName: "dirty" }] : []));
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" }] : []));
64
+ const destroyed = signal(false, ...(ngDevMode ? [{ debugName: "destroyed" }] : []));
65
+ const dirty = computed(() => !destroyed() && (dirtyOverride() ?? readDirty(watcher.isDirty)), ...(ngDevMode ? [{ debugName: "dirty" }] : []));
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: "20.3.25", ngImport: i0, type: SdUnsavedChangesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
170
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdUnsavedChangesService, providedIn: 'root' });
171
+ }
172
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", 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" }] : []));
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" }] : []));
38
+ #height = signal(this.#viewport?.innerHeight ?? Number.MAX_SAFE_INTEGER, ...(ngDevMode ? [{ debugName: "#height" }] : []));
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" }] : []));
50
+ isMobile = computed(() => this.currentBreakpoint() === 'mobile', ...(ngDevMode ? [{ debugName: "isMobile" }] : []));
51
+ isTablet = computed(() => this.currentBreakpoint() === 'tablet', ...(ngDevMode ? [{ debugName: "isTablet" }] : []));
52
+ isDesktop = computed(() => this.currentBreakpoint() === 'desktop', ...(ngDevMode ? [{ debugName: "isDesktop" }] : []));
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: "20.3.25", ngImport: i0, type: SdViewportService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
64
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.25", ngImport: i0, type: SdViewportService, providedIn: 'root' });
65
+ }
66
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.25", 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"}
@@ -1,5 +1,5 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { OnInit, OnDestroy, AfterViewInit, ElementRef, TemplateRef, EventEmitter } from '@angular/core';
2
+ import { OnInit, OnDestroy, ElementRef, TemplateRef, EventEmitter } from '@angular/core';
3
3
  import { FormGroup, FormControl, FormGroupDirective, NgForm } from '@angular/forms';
4
4
  import { MatAutocompleteTrigger } from '@angular/material/autocomplete';
5
5
  import { ErrorStateMatcher } from '@angular/material/core';
@@ -13,7 +13,7 @@ declare class SdAutocompleteErrotStateMatcher implements ErrorStateMatcher {
13
13
  constructor(formControl: FormControl);
14
14
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean;
15
15
  }
16
- declare class SdAutocomplete<T = any> implements OnInit, OnDestroy, AfterViewInit {
16
+ declare class SdAutocomplete<T = any> implements OnInit, OnDestroy {
17
17
  #private;
18
18
  id: string;
19
19
  inputRef: _angular_core.Signal<ElementRef<HTMLInputElement> | undefined>;
@@ -73,7 +73,7 @@ declare class SdAutocomplete<T = any> implements OnInit, OnDestroy, AfterViewIni
73
73
  valueModel: _angular_core.ModelSignal<string | number | null | undefined>;
74
74
  sdChange: _angular_core.OutputEmitterRef<string | number | null>;
75
75
  sdSelection: _angular_core.OutputEmitterRef<SdSelectionData<any>>;
76
- sdAdd: EventEmitter<void>;
76
+ readonly sdAdd: EventEmitter<void>;
77
77
  loading: _angular_core.WritableSignal<boolean>;
78
78
  isFocused: boolean;
79
79
  isTyping: _angular_core.WritableSignal<boolean>;
@@ -88,7 +88,6 @@ declare class SdAutocomplete<T = any> implements OnInit, OnDestroy, AfterViewIni
88
88
  getNestedValue: (obj: any, path: string | undefined) => any;
89
89
  constructor();
90
90
  ngOnInit(): void;
91
- ngAfterViewInit(): void;
92
91
  ngOnDestroy(): void;
93
92
  onSelect: (item: T) => void;
94
93
  onFocus: () => void;
@@ -1,16 +1,15 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { OnDestroy, AfterViewInit } from '@angular/core';
3
- import { FormControl, FormGroup } from '@angular/forms';
4
- import { SdViewed, SdViewedInput } from '@sdcorejs/angular/forms/models';
2
+ import { FormControl } from '@angular/forms';
3
+ import { ɵSdFormControlParent as _SdFormControlParent, SdViewed, SdViewedInput } from '@sdcorejs/angular/forms/models';
5
4
  import { Color } from '@sdcorejs/utils/models';
6
5
 
7
- declare class SdCheckbox implements OnDestroy, AfterViewInit {
6
+ declare class SdCheckbox {
8
7
  #private;
9
8
  id: string;
10
9
  formControl: FormControl<any>;
11
10
  readonly autoIdInput: _angular_core.InputSignalWithTransform<string | undefined, string | null | undefined>;
12
11
  readonly name: _angular_core.InputSignalWithTransform<string | undefined, string | null | undefined>;
13
- readonly form: _angular_core.InputSignalWithTransform<FormGroup<any> | undefined, any>;
12
+ readonly form: _angular_core.InputSignal<_SdFormControlParent>;
14
13
  readonly label: _angular_core.InputSignalWithTransform<string | undefined, string | null | undefined>;
15
14
  readonly color: _angular_core.InputSignalWithTransform<Color, Color | null | undefined>;
16
15
  readonly disabled: _angular_core.InputSignalWithTransform<boolean, unknown>;
@@ -25,9 +24,6 @@ declare class SdCheckbox implements OnDestroy, AfterViewInit {
25
24
  readonly dataDisabled: _angular_core.Signal<"true" | "false">;
26
25
  readonly dataEmpty: _angular_core.Signal<"true" | "false">;
27
26
  readonly dataValue: _angular_core.Signal<string>;
28
- constructor();
29
- ngAfterViewInit(): void;
30
- ngOnDestroy(): void;
31
27
  static ɵfac: _angular_core.ɵɵFactoryDeclaration<SdCheckbox, never>;
32
28
  static ɵcmp: _angular_core.ɵɵComponentDeclaration<SdCheckbox, "sd-checkbox", never, { "autoIdInput": { "alias": "autoId"; "required": false; "isSignal": true; }; "name": { "alias": "name"; "required": false; "isSignal": true; }; "form": { "alias": "form"; "required": false; "isSignal": true; }; "label": { "alias": "label"; "required": false; "isSignal": true; }; "color": { "alias": "color"; "required": false; "isSignal": true; }; "disabled": { "alias": "disabled"; "required": false; "isSignal": true; }; "viewed": { "alias": "viewed"; "required": false; "isSignal": true; }; "inlineError": { "alias": "inlineError"; "required": false; "isSignal": true; }; "model": { "alias": "model"; "required": false; "isSignal": true; }; }, { "model": "modelChange"; "sdChange": "sdChange"; }, never, never, true, never>;
33
29
  }
@@ -7,6 +7,7 @@ import { SdViewDefDirective, SdLabelDefDirective } from '@sdcorejs/angular/forms
7
7
  import { SdViewed, SdViewedInput, SdFormControl } from '@sdcorejs/angular/forms/models';
8
8
  import { Size } from '@sdcorejs/utils/models';
9
9
 
10
+ type SdDateModelValue = string | number | Date | undefined | null;
10
11
  declare class SdDate implements OnDestroy, OnInit {
11
12
  #private;
12
13
  id: string;
@@ -62,14 +63,13 @@ declare class SdDate implements OnDestroy, OnInit {
62
63
  maxInput: _angular_core.InputSignal<any>;
63
64
  maxDateInput: _angular_core.InputSignal<any>;
64
65
  resolvedMax: _angular_core.Signal<Date | null>;
65
- valueModel: _angular_core.ModelSignal<string | number | Date | null | undefined>;
66
+ valueModel: _angular_core.ModelSignal<SdDateModelValue>;
66
67
  sdChange: _angular_core.OutputEmitterRef<any>;
67
68
  sdFocus: _angular_core.OutputEmitterRef<void>;
68
69
  isMobileOrTablet: boolean;
69
70
  formControl: SdFormControl;
70
71
  isFocused: boolean;
71
72
  isValid?: boolean;
72
- constructor();
73
73
  ngOnInit(): void;
74
74
  ngOnDestroy(): void;
75
75
  onFocus: () => void;
@@ -1,5 +1,5 @@
1
1
  import * as _angular_core from '@angular/core';
2
- import { OnDestroy, OnInit, TemplateRef } from '@angular/core';
2
+ import { TemplateRef } from '@angular/core';
3
3
  import { FormGroup, FormControl } from '@angular/forms';
4
4
  import { MatDateRangePicker, MatDatepickerInputEvent } from '@angular/material/datepicker';
5
5
  import { MatFormFieldAppearance, FloatLabelType } from '@angular/material/form-field';
@@ -7,11 +7,11 @@ import { SdLabelDefDirective } from '@sdcorejs/angular/forms/directives';
7
7
  import { SdViewed, SdViewedInput } from '@sdcorejs/angular/forms/models';
8
8
  import { Size } from '@sdcorejs/utils/models';
9
9
 
10
- interface Daterange {
10
+ interface SdDateRangeValue {
11
11
  from?: string | null;
12
12
  to?: string | null;
13
13
  }
14
- declare class SdDateRange implements OnDestroy, OnInit {
14
+ declare class SdDateRange {
15
15
  #private;
16
16
  id1: string;
17
17
  id2: string;
@@ -68,15 +68,13 @@ declare class SdDateRange implements OnDestroy, OnInit {
68
68
  resolvedMin: _angular_core.Signal<Date | null>;
69
69
  maxInput: _angular_core.InputSignal<any>;
70
70
  resolvedMax: _angular_core.Signal<Date | null>;
71
- valueModel: _angular_core.ModelSignal<Daterange | null | undefined>;
72
- sdChange: _angular_core.OutputEmitterRef<Daterange | null | undefined>;
71
+ valueModel: _angular_core.ModelSignal<SdDateRangeValue | null | undefined>;
72
+ sdChange: _angular_core.OutputEmitterRef<SdDateRangeValue | null | undefined>;
73
73
  isMobileOrTablet: boolean;
74
74
  formControl: FormControl<any>;
75
75
  control1: FormControl<any>;
76
76
  control2: FormControl<any>;
77
77
  constructor();
78
- ngOnInit(): void;
79
- ngOnDestroy(): void;
80
78
  onStartChange: (event: MatDatepickerInputEvent<Date>) => void;
81
79
  onEndChange: (event: MatDatepickerInputEvent<Date>) => void;
82
80
  clear: () => void;
@@ -90,3 +88,4 @@ declare class SdDateRange implements OnDestroy, OnInit {
90
88
  }
91
89
 
92
90
  export { SdDateRange };
91
+ export type { SdDateRangeValue };
@@ -6,6 +6,7 @@ import { SdViewDefDirective } from '@sdcorejs/angular/forms/directives';
6
6
  import { SdViewed, SdViewedInput, SdFormControl } from '@sdcorejs/angular/forms/models';
7
7
  import { Size } from '@sdcorejs/utils/models';
8
8
 
9
+ type SdDatetimeModelValue = string | number | Date | undefined | null;
9
10
  declare class SdDatetime implements OnDestroy, OnInit {
10
11
  #private;
11
12
  id: string;
@@ -63,7 +64,7 @@ declare class SdDatetime implements OnDestroy, OnInit {
63
64
  maxInput: _angular_core.InputSignal<any>;
64
65
  maxDateInput: _angular_core.InputSignal<any>;
65
66
  resolvedMax: _angular_core.Signal<Date | null>;
66
- valueModel: _angular_core.ModelSignal<string | number | Date | null | undefined>;
67
+ valueModel: _angular_core.ModelSignal<SdDatetimeModelValue>;
67
68
  viewedDate: _angular_core.Signal<Date | null>;
68
69
  sdChange: _angular_core.OutputEmitterRef<any>;
69
70
  sdFocus: _angular_core.OutputEmitterRef<void>;
@@ -73,7 +74,6 @@ declare class SdDatetime implements OnDestroy, OnInit {
73
74
  isValid?: boolean;
74
75
  /** State popup — true khi picker package đang mở. */
75
76
  pickerOpened: _angular_core.Signal<boolean>;
76
- constructor();
77
77
  ngOnInit(): void;
78
78
  ngOnDestroy(): void;
79
79
  /** Mở popup chọn datetime, neo vào input. */