@sdcorejs/angular 21.1.3 → 21.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/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 +375 -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 +994 -168
  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 +72 -2
  155. package/types/sdcorejs-angular-interceptors.d.ts +5 -6
  156. package/types/sdcorejs-angular-modules-layout.d.ts +354 -17
  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
package/README.md CHANGED
@@ -79,10 +79,10 @@ Replace the timer with your typed service call and reset `saving` in a `finally`
79
79
 
80
80
  | Area | Representative APIs |
81
81
  | ----------------- | ------------------------------------------------------------------------------------------------------------------------------- |
82
- | UI components | Buttons, badges, avatars, sections, tabs, steppers, modals, drawers, charts, editors, previews, and navigation primitives |
83
- | Data and workflow | Local or server tables, tree data, query bars, query builders, generic forms, document building, file upload, and Excel import |
84
- | Form controls | Text, number, date, date range, datetime, select, autocomplete, checkbox, radio, switch, chip, color, inline text, and textarea |
85
- | Services | Notifications, confirmation dialogs, loading state, API access, storage, cache, Excel, and DOCX helpers |
82
+ | UI components | Navigation/data state, PDF preview, job progress, audit diff, modals, drawers, tabs, charts, editors, and document tooling |
83
+ | Data and workflow | Local/server tables, entity/tree pickers, query builders, unsaved-change guards, background tasks, upload, and Excel import |
84
+ | Form controls | Text/mask, number, time/time range, date/date range, datetime, select, autocomplete, checkbox, radio, switch, chip, and color |
85
+ | Services | Typed API/retry/cancel, ref-counted loading, graph-safe persistence/cache/storage, viewport signals, notifications, and exports |
86
86
  | Portal modules | Auth, Keycloak, permission, layout, and icon modules |
87
87
  | Localization | Built-in `vi`, `en`, `ja`, `ko`, and `zh` catalogs, plus a synchronous custom-catalog provider |
88
88
 
@@ -165,22 +165,27 @@ export const appConfig: ApplicationConfig = {
165
165
 
166
166
  ## Documentation and examples
167
167
 
168
- | Resource | Purpose |
169
- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
170
- | [Live showcase](https://sdcorejs.github.io/sdcorejs-angular/) | Interactive component, form, and service demos |
171
- | [Button example source](https://github.com/sdcorejs/sdcorejs-angular/blob/main/versions/v19/projects/showcase/src/app/pages/components/button/button-demo.component.ts) | Action variants, icons, disabled state, and loading state |
172
- | [Input example source](https://github.com/sdcorejs/sdcorejs-angular/blob/main/versions/v19/projects/showcase/src/app/pages/forms/input/input-demo.component.ts) | Model binding, validation, and viewed states |
173
- | [Table example source](https://github.com/sdcorejs/sdcorejs-angular/blob/main/versions/v19/projects/showcase/src/app/pages/components/table/table-demo.component.ts) | Data, selection, filters, grouping, paging, and tree workflows |
174
- | [Latest API manifest](https://sdcorejs.github.io/sdcorejs-angular/docs/latest/index.json) | Discover all Markdown docs for the latest release |
175
- | [Versions registry](https://sdcorejs.github.io/sdcorejs-angular/docs/versions.json) | Select docs matching an installed package version |
176
- | [Machine-readable catalog](https://sdcorejs.github.io/sdcorejs-angular/docs/catalog.json) | Discover documentation across maintained package lines |
177
- | [E2E attributes](https://github.com/sdcorejs/sdcorejs-angular/blob/main/versions/v19/projects/sdcorejs-angular/docs/E2E-ATTRIBUTES.md) | Stable runtime selectors and state attributes |
168
+ | Resource | Purpose |
169
+ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------- |
170
+ | [Live showcase](https://sdcorejs.github.io/sdcorejs-angular/) | Interactive component, form, and service demos |
171
+ | [Button example source](https://github.com/sdcorejs/sdcorejs-angular/blob/main/versions/v19/projects/showcase/src/app/pages/components/button/button-demo.component.ts) | Action variants, icons, disabled state, and loading state |
172
+ | [Input example source](https://github.com/sdcorejs/sdcorejs-angular/blob/main/versions/v19/projects/showcase/src/app/pages/forms/input/input-demo.component.ts) | Model binding, validation, and viewed states |
173
+ | [Table example source](https://github.com/sdcorejs/sdcorejs-angular/blob/main/versions/v19/projects/showcase/src/app/pages/components/table/table-demo.component.ts) | Data, selection, filters, grouping, paging, and tree workflows |
174
+ | [Persistence example source](https://github.com/sdcorejs/sdcorejs-angular/blob/main/versions/v19/projects/showcase/src/app/pages/services/persistence/persistence-demo.component.ts) | Graph round-trip, identity, envelopes, and error containment |
175
+ | [Latest API manifest](https://sdcorejs.github.io/sdcorejs-angular/docs/latest/index.json) | Discover all Markdown docs for the latest release |
176
+ | [Versions registry](https://sdcorejs.github.io/sdcorejs-angular/docs/versions.json) | Select docs matching an installed package version |
177
+ | [Machine-readable catalog](https://sdcorejs.github.io/sdcorejs-angular/docs/catalog.json) | Discover documentation across maintained package lines |
178
+ | [E2E attributes](https://github.com/sdcorejs/sdcorejs-angular/blob/main/versions/v19/projects/sdcorejs-angular/docs/E2E-ATTRIBUTES.md) | Stable runtime selectors and state attributes |
179
+ | [1.5 migration guide](https://github.com/sdcorejs/sdcorejs-angular/blob/main/docs/migrations/1.5.md) | Layout account actions, V2/V3 navigation, containment, and versioned docs |
180
+ | [1.4 migration guide](https://github.com/sdcorejs/sdcorejs-angular/blob/main/docs/migrations/1.4.md) | Dedupe, loading, persistence, connector, responsive, and signal migrations |
178
181
 
179
182
  ## Versioning
180
183
 
181
184
  - Use `@sdcorejs/angular@^19`, `@^20`, or `@^21` to match the application's Angular major.
182
185
  - Maintained package lines are released from the same feature surface with required Angular-major adaptations.
183
186
  - Consumer-breaking changes and migration notes are recorded in the [changelog](https://github.com/sdcorejs/sdcorejs-angular/blob/main/CHANGELOG.md).
187
+ - Before adopting the `1.5` suffix, follow the [1.5 migration guide](https://github.com/sdcorejs/sdcorejs-angular/blob/main/docs/migrations/1.5.md).
188
+ - Before adopting the `1.4` suffix, follow the [1.4 migration guide](https://github.com/sdcorejs/sdcorejs-angular/blob/main/docs/migrations/1.4.md).
184
189
  - Version-pinned reference docs remain available under `https://sdcorejs.github.io/sdcorejs-angular/docs/<package-version>/`.
185
190
 
186
191
  ## Contributing
@@ -73,10 +73,10 @@ class SdAnchor {
73
73
  sidebarWidth = input('200px', ...(ngDevMode ? [{ debugName: "sidebarWidth" }] : /* istanbul ignore next */ []));
74
74
  ellipsis = input(false, { ...(ngDevMode ? { debugName: "ellipsis" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
75
75
  overScroll = input(false, { ...(ngDevMode ? { debugName: "overScroll" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
76
- // Mobile: default ?n nav (sidebar TOC chi?m ch? tr�n m�n h�nh h?p).
77
- // Consumer truy?n `[hideNav]="false"` d? force hi?n tr�n mobile.
76
+ // Mobile: mặc định ẩn nav (sidebar TOC chiếm chỗ trên màn hình hẹp).
77
+ // Consumer truyền `[hideNav]="false"` để buộc hiển thị trên mobile.
78
78
  hideNav = input(BrowserUtilities.isMobile(), { ...(ngDevMode ? { debugName: "hideNav" } : /* istanbul ignore next */ {}), transform: booleanAttribute });
79
- // M�u highlight active nav (text + icon + vertical bar). Default 'primary'.
79
+ // Màu highlight active nav (text + icon + vertical bar). Default 'primary'.
80
80
  color = input('primary', ...(ngDevMode ? [{ debugName: "color" }] : /* istanbul ignore next */ []));
81
81
  activeSectionId = signal('', ...(ngDevMode ? [{ debugName: "activeSectionId" }] : /* istanbul ignore next */ []));
82
82
  #initialized = false;
@@ -93,7 +93,7 @@ class SdAnchor {
93
93
  this.#registerIntersectionObserver();
94
94
  }
95
95
  });
96
- // L?ng nghe thay d?i sections d? dang k� l?i observe section
96
+ // Lắng nghe thay đổi sections để đăng lại observer cho từng section.
97
97
  effect(() => {
98
98
  this.sections();
99
99
  if (!this.hideNav() && this.#initialized) {
@@ -104,24 +104,24 @@ class SdAnchor {
104
104
  #registerIntersectionObserver() {
105
105
  this.#cleanIntersectionObserver();
106
106
  const wrapperEl = this.wrapper().nativeElement;
107
- // Brower API IntersectionObserver s? h? tr? khi n�o 1 section du?c hi?n ra trong wrapperEl
107
+ // Browser API IntersectionObserver hỗ trợ xác định section đang hiển thị trong wrapperEl.
108
108
  this.#intersectionObserver = new IntersectionObserver(entries => {
109
109
  for (const entry of entries) {
110
110
  if (entry.isIntersecting) {
111
- // N?u section m?i di v�o v�ng nh�n th?y -> add v�o set
111
+ // Nếu section mới đi vào vùng nhìn thấy thì thêm vào set.
112
112
  this.#visibleSections.add(entry.target);
113
113
  }
114
114
  else {
115
- // N?u section d� di ra kh?i v�ng nh�n th?y -> x�a kh?i Set
115
+ // Nếu section đã đi ra khỏi vùng nhìn thấy thì xóa khỏi set.
116
116
  this.#visibleSections.delete(entry.target);
117
117
  }
118
118
  }
119
- // N?u dang scroll b?i click section th� return lu�n, k ph?i qu�t qua t�m v�ng section du?c active
119
+ // Nếu đang scroll do click section thì không cần quét section đang active.
120
120
  if (this.#isClickScrolling) {
121
121
  return;
122
122
  }
123
123
  for (const section of this.sections()) {
124
- // Ki?m tra xem DOM c?a section n�o dang n?m trong danh s�ch dang nh�n th?y d? active ch�nh x�c
124
+ // Kiểm tra DOM của section nào đang nằm trong danh sách nhìn thấy để active chính xác.
125
125
  if (this.#visibleSections.has(section.elementRef.nativeElement)) {
126
126
  if (this.activeSectionId() !== section.id) {
127
127
  this.activeSectionId.set(section.id);
@@ -130,7 +130,7 @@ class SdAnchor {
130
130
  }
131
131
  }
132
132
  }, { root: wrapperEl, threshold: 0 });
133
- // �ang k� theo d�i t?ng section c� trong anchor
133
+ // Đăng theo dõi từng section trong anchor.
134
134
  for (const section of this.sections()) {
135
135
  this.#intersectionObserver.observe(section.elementRef.nativeElement);
136
136
  }
@@ -145,15 +145,15 @@ class SdAnchor {
145
145
  this.#isClickScrolling = true;
146
146
  const wrapperEl = this.wrapper().nativeElement;
147
147
  const targetElement = targetSection.nativeElement;
148
- const prevScrollTop = wrapperEl.scrollTop; // Luu l?i v? tr� cu?n hi?n t?i c?a container
149
- const scrollTop = prevScrollTop + targetElement.getBoundingClientRect().top - wrapperEl.getBoundingClientRect().top; // V? tr� section c?n scroll t?i
150
- // �ang k� s? ki?n n?u d� du?c scroll xong
148
+ const prevScrollTop = wrapperEl.scrollTop; // Lưu lại vị trí cuộn hiện tại của container.
149
+ const scrollTop = prevScrollTop + targetElement.getBoundingClientRect().top - wrapperEl.getBoundingClientRect().top; // Vị trí section cần scroll tới.
150
+ // Đăng sự kiện khi scroll hoàn tất.
151
151
  this.#clickScrollSubscription = fromEvent(wrapperEl, 'scrollend')
152
152
  .pipe(take(1))
153
153
  .subscribe(() => {
154
154
  this.#isClickScrolling = false;
155
155
  });
156
- // N?u sectionTarget d� ? d�ng v? tr� (click nhung kh�ng scroll) th� sau #timeoutScrollFallback k�ch ho?t l?i IntersectionObserver
156
+ // Nếu sectionTarget đã đúng vị trí (click nhưng không scroll) thì kích hoạt lại IntersectionObserver sau timeout.
157
157
  this.#timeoutId = setTimeout(() => {
158
158
  if (wrapperEl.scrollTop === prevScrollTop) {
159
159
  this.#isClickScrolling = false;
@@ -1 +1 @@
1
- {"version":3,"file":"sdcorejs-angular-components-anchor.mjs","sources":["../../../projects/sdcorejs-angular/components/anchor/src/components/anchor-item/anchor-item.component.ts","../../../projects/sdcorejs-angular/components/anchor/src/components/anchor-item/anchor-item.component.html","../../../projects/sdcorejs-angular/components/anchor/src/components/anchor-nav/anchor-nav.component.ts","../../../projects/sdcorejs-angular/components/anchor/src/components/anchor-nav/anchor-nav.component.html","../../../projects/sdcorejs-angular/components/anchor/src/components/anchor/anchor.component.ts","../../../projects/sdcorejs-angular/components/anchor/src/components/anchor/anchor.component.html","../../../projects/sdcorejs-angular/components/anchor/sdcorejs-angular-components-anchor.ts"],"sourcesContent":["import { CommonModule } from '@angular/common';\nimport { Component, ElementRef, effect, inject, input } from '@angular/core';\nimport { IAnchorItem } from '../../models';\nimport { Utilities } from '@sdcorejs/utils/fns';\n\n@Component({\n selector: 'sd-anchor-item',\n templateUrl: './anchor-item.component.html',\n styleUrl: './anchor-item.component.scss',\n imports: [CommonModule],\n standalone: true,\n})\nexport class SdAnchorItem implements IAnchorItem {\n title = input.required<string>();\n icon = input<string | undefined>();\n // Stable key dùng cho data-autoId. Nếu không truyền thì fallback về uuid (không stable giữa các lần render).\n key = input<string | undefined>(undefined);\n id: string = Utilities.generateUuid();\n elementRef = inject(ElementRef);\n\n constructor() {\n effect(() => {\n if (this.title()) {\n this.elementRef.nativeElement.removeAttribute('title');\n }\n });\n }\n}\n","<section>\n <ng-content></ng-content>\n</section>\n","import { ChangeDetectionStrategy, Component, OnDestroy, computed, input, output } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { Color } from '@sdcorejs/utils/models';\nimport { Subject, Subscription, debounceTime } from 'rxjs';\n\nimport { SdAnchorItem } from '../anchor-item/anchor-item.component';\nimport { SdIcon } from '@sdcorejs/angular/modules/icon';\n\n@Component({\n selector: 'anchor-nav',\n templateUrl: './anchor-nav.component.html',\n styleUrl: './anchor-nav.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [SdIcon, CommonModule],\n standalone: true,\n})\nexport class AnchorNav implements OnDestroy {\n sections = input.required<readonly SdAnchorItem[]>();\n activeSectionId = input<string>('');\n ellipsis = input<boolean>(false);\n sidebarWidth = input<string>('');\n // autoId được forward từ <sd-anchor>. Derive per-item autoId theo `key` của item (nếu có).\n parentAutoId = input<string | undefined | null>(undefined);\n color = input<Color>('primary');\n\n // CSS var binding cho active state — map SdColor → global token `--sd-{color}`.\n // SCSS đọc qua `var(--anchor-active-color)` cho border + text + icon.\n cssActiveVar = computed(() => `var(--sd-${this.color()})`);\n\n itemAutoId(section: SdAnchorItem): string | undefined {\n const parent = this.parentAutoId();\n const key = section.key();\n return parent && key ? `${parent}-${key}` : undefined;\n }\n\n clickSection = output<string>();\n\n #delay = 200;\n #clickSectionSubject = new Subject<string>();\n #subscription = new Subscription();\n\n constructor() {\n this.#subscription.add(this.#clickSectionSubject.pipe(debounceTime(this.#delay)).subscribe((id: string) => this.clickSection.emit(id)));\n }\n\n onClickSection = (id: string): void => {\n this.#clickSectionSubject.next(id);\n };\n\n ngOnDestroy(): void {\n this.#subscription.unsubscribe();\n }\n}\n","<div\n class=\"c-anchor-list\"\n [style.width]=\"sidebarWidth()\"\n [style.--anchor-active-color]=\"cssActiveVar()\">\n @for (section of sections(); track section.id) {\n <div\n class=\"c-anchor-list-item\"\n [class.active]=\"section.id === activeSectionId()\"\n (click)=\"onClickSection(section.id)\"\n (keydown.enter)=\"onClickSection(section.id)\"\n [attr.data-autoId]=\"itemAutoId(section)\"\n tabindex=\"0\">\n @if (section.icon()) {\n <sd-icon class=\"c-anchor-list-item-icon\" [name]=\"section.icon()\"></sd-icon>\n }\n <div class=\"c-anchor-list-item-text\" [class.ellipsis]=\"ellipsis()\">\n {{ section.title() }}\n </div>\n </div>\n }\n</div>\n","import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n OnDestroy,\n afterNextRender,\n booleanAttribute,\n computed,\n contentChildren,\n effect,\n input,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { BrowserUtilities } from '@sdcorejs/utils/fns';\nimport { Color } from '@sdcorejs/utils/models';\nimport { Subscription, fromEvent, take } from 'rxjs';\nimport { AnchorNav } from '../anchor-nav/anchor-nav.component';\nimport { SdAnchorItem } from '../anchor-item/anchor-item.component';\n\n@Component({\n selector: 'sd-anchor',\n templateUrl: './anchor.component.html',\n styleUrl: './anchor.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [CommonModule, AnchorNav],\n standalone: true,\n})\nexport class SdAnchor implements OnDestroy {\n wrapper = viewChild.required<ElementRef>('wrapper');\n sections = contentChildren(SdAnchorItem);\n\n readonly autoIdInput = input<string | undefined | null>(undefined, { alias: 'autoId' });\n readonly autoId = computed(() => (this.autoIdInput() ? `components-anchor-${this.autoIdInput()}` : undefined));\n\n sidebarWidth = input<string>('200px');\n ellipsis = input(false, { transform: booleanAttribute });\n overScroll = input(false, { transform: booleanAttribute });\n // Mobile: default ?n nav (sidebar TOC chi?m ch? tr�n m�n h�nh h?p).\n // Consumer truy?n `[hideNav]=\"false\"` d? force hi?n tr�n mobile.\n hideNav = input(BrowserUtilities.isMobile(), { transform: booleanAttribute });\n // M�u highlight active nav (text + icon + vertical bar). Default 'primary'.\n color = input<Color>('primary');\n\n activeSectionId = signal<string>('');\n\n #initialized = false;\n #isClickScrolling = false;\n #clickScrollSubscription: Subscription | null = null;\n #intersectionObserver: IntersectionObserver | null = null;\n #visibleSections = new Set<Element>();\n #timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n #timeoutScrollFallback = 200;\n\n constructor() {\n afterNextRender(() => {\n this.#initialized = true;\n if (!this.hideNav()) {\n this.#registerIntersectionObserver();\n }\n });\n\n // L?ng nghe thay d?i sections d? dang k� l?i observe section\n effect(() => {\n this.sections();\n if (!this.hideNav() && this.#initialized) {\n untracked(() => this.#registerIntersectionObserver());\n }\n });\n }\n\n #registerIntersectionObserver(): void {\n this.#cleanIntersectionObserver();\n const wrapperEl = this.wrapper().nativeElement;\n\n // Brower API IntersectionObserver s? h? tr? khi n�o 1 section du?c hi?n ra trong wrapperEl\n this.#intersectionObserver = new IntersectionObserver(\n entries => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n // N?u section m?i di v�o v�ng nh�n th?y -> add v�o set\n this.#visibleSections.add(entry.target);\n } else {\n // N?u section d� di ra kh?i v�ng nh�n th?y -> x�a kh?i Set\n this.#visibleSections.delete(entry.target);\n }\n }\n\n // N?u dang scroll b?i click section th� return lu�n, k ph?i qu�t qua t�m v�ng section du?c active\n if (this.#isClickScrolling) {\n return;\n }\n\n for (const section of this.sections()) {\n // Ki?m tra xem DOM c?a section n�o dang n?m trong danh s�ch dang nh�n th?y d? active ch�nh x�c\n if (this.#visibleSections.has(section.elementRef.nativeElement)) {\n if (this.activeSectionId() !== section.id) {\n this.activeSectionId.set(section.id);\n }\n break;\n }\n }\n },\n { root: wrapperEl, threshold: 0 }\n );\n\n // �ang k� theo d�i t?ng section c� trong anchor\n for (const section of this.sections()) {\n this.#intersectionObserver.observe(section.elementRef.nativeElement);\n }\n }\n\n scrollSectionByClick(idSectionTarget: string): void {\n this.activeSectionId.set(idSectionTarget);\n const targetSection = this.sections().find(s => s.id === idSectionTarget)?.elementRef;\n if (!targetSection) {\n return;\n }\n this.#cleanScrollSectionByClickObserver();\n this.#isClickScrolling = true;\n\n const wrapperEl = this.wrapper().nativeElement;\n const targetElement = targetSection.nativeElement;\n\n const prevScrollTop = wrapperEl.scrollTop; // Luu l?i v? tr� cu?n hi?n t?i c?a container\n const scrollTop = prevScrollTop + targetElement.getBoundingClientRect().top - wrapperEl.getBoundingClientRect().top; // V? tr� section c?n scroll t?i\n\n // �ang k� s? ki?n n?u d� du?c scroll xong\n this.#clickScrollSubscription = fromEvent(wrapperEl, 'scrollend')\n .pipe(take(1))\n .subscribe(() => {\n this.#isClickScrolling = false;\n });\n\n // N?u sectionTarget d� ? d�ng v? tr� (click nhung kh�ng scroll) th� sau #timeoutScrollFallback k�ch ho?t l?i IntersectionObserver\n this.#timeoutId = setTimeout(() => {\n if (wrapperEl.scrollTop === prevScrollTop) {\n this.#isClickScrolling = false;\n this.#clickScrollSubscription?.unsubscribe();\n }\n }, this.#timeoutScrollFallback);\n\n wrapperEl.scrollTo({ top: scrollTop, behavior: 'smooth' });\n }\n\n #cleanScrollSectionByClickObserver = (): void => {\n if (this.#timeoutId) {\n clearTimeout(this.#timeoutId);\n }\n this.#clickScrollSubscription?.unsubscribe();\n };\n\n #cleanIntersectionObserver = (): void => {\n this.#intersectionObserver?.disconnect();\n this.#visibleSections.clear();\n };\n\n ngOnDestroy(): void {\n this.#cleanScrollSectionByClickObserver();\n this.#cleanIntersectionObserver();\n }\n}\n","<div class=\"c-anchor-vertical\" [ngClass]=\"{ 'c-stop-scroll-propagation': !overScroll() }\" #wrapper>\n <div class=\"c-anchor-content\">\n <ng-content></ng-content>\n </div>\n\n @if (!hideNav()) {\n <div class=\"c-anchor-list\" [style.flex-basis]=\"sidebarWidth()\">\n <anchor-nav\n [sections]=\"sections()\"\n [ellipsis]=\"ellipsis()\"\n [sidebarWidth]=\"sidebarWidth()\"\n [activeSectionId]=\"activeSectionId()\"\n [parentAutoId]=\"autoId()\"\n [color]=\"color()\"\n (clickSection)=\"scrollSectionByClick($event)\">\n </anchor-nav>\n </div>\n }\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sdcorejs-angular-components-anchor.mjs","sources":["../../../projects/sdcorejs-angular/components/anchor/src/components/anchor-item/anchor-item.component.ts","../../../projects/sdcorejs-angular/components/anchor/src/components/anchor-item/anchor-item.component.html","../../../projects/sdcorejs-angular/components/anchor/src/components/anchor-nav/anchor-nav.component.ts","../../../projects/sdcorejs-angular/components/anchor/src/components/anchor-nav/anchor-nav.component.html","../../../projects/sdcorejs-angular/components/anchor/src/components/anchor/anchor.component.ts","../../../projects/sdcorejs-angular/components/anchor/src/components/anchor/anchor.component.html","../../../projects/sdcorejs-angular/components/anchor/sdcorejs-angular-components-anchor.ts"],"sourcesContent":["import { CommonModule } from '@angular/common';\nimport { Component, ElementRef, effect, inject, input } from '@angular/core';\nimport { IAnchorItem } from '../../models';\nimport { Utilities } from '@sdcorejs/utils/fns';\n\n@Component({\n selector: 'sd-anchor-item',\n templateUrl: './anchor-item.component.html',\n styleUrl: './anchor-item.component.scss',\n imports: [CommonModule],\n standalone: true,\n})\nexport class SdAnchorItem implements IAnchorItem {\n title = input.required<string>();\n icon = input<string | undefined>();\n // Stable key dùng cho data-autoId. Nếu không truyền thì fallback về uuid (không stable giữa các lần render).\n key = input<string | undefined>(undefined);\n id: string = Utilities.generateUuid();\n elementRef = inject(ElementRef);\n\n constructor() {\n effect(() => {\n if (this.title()) {\n this.elementRef.nativeElement.removeAttribute('title');\n }\n });\n }\n}\n","<section>\n <ng-content></ng-content>\n</section>\n","import { ChangeDetectionStrategy, Component, OnDestroy, computed, input, output } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { Color } from '@sdcorejs/utils/models';\nimport { Subject, Subscription, debounceTime } from 'rxjs';\n\nimport { SdAnchorItem } from '../anchor-item/anchor-item.component';\nimport { SdIcon } from '@sdcorejs/angular/modules/icon';\n\n@Component({\n selector: 'anchor-nav',\n templateUrl: './anchor-nav.component.html',\n styleUrl: './anchor-nav.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [SdIcon, CommonModule],\n standalone: true,\n})\nexport class AnchorNav implements OnDestroy {\n sections = input.required<readonly SdAnchorItem[]>();\n activeSectionId = input<string>('');\n ellipsis = input<boolean>(false);\n sidebarWidth = input<string>('');\n // autoId được forward từ <sd-anchor>. Derive per-item autoId theo `key` của item (nếu có).\n parentAutoId = input<string | undefined | null>(undefined);\n color = input<Color>('primary');\n\n // CSS var binding cho active state — map SdColor → global token `--sd-{color}`.\n // SCSS đọc qua `var(--anchor-active-color)` cho border + text + icon.\n cssActiveVar = computed(() => `var(--sd-${this.color()})`);\n\n itemAutoId(section: SdAnchorItem): string | undefined {\n const parent = this.parentAutoId();\n const key = section.key();\n return parent && key ? `${parent}-${key}` : undefined;\n }\n\n clickSection = output<string>();\n\n #delay = 200;\n #clickSectionSubject = new Subject<string>();\n #subscription = new Subscription();\n\n constructor() {\n this.#subscription.add(this.#clickSectionSubject.pipe(debounceTime(this.#delay)).subscribe((id: string) => this.clickSection.emit(id)));\n }\n\n onClickSection = (id: string): void => {\n this.#clickSectionSubject.next(id);\n };\n\n ngOnDestroy(): void {\n this.#subscription.unsubscribe();\n }\n}\n","<div\n class=\"c-anchor-list\"\n [style.width]=\"sidebarWidth()\"\n [style.--anchor-active-color]=\"cssActiveVar()\">\n @for (section of sections(); track section.id) {\n <div\n class=\"c-anchor-list-item\"\n [class.active]=\"section.id === activeSectionId()\"\n (click)=\"onClickSection(section.id)\"\n (keydown.enter)=\"onClickSection(section.id)\"\n [attr.data-autoId]=\"itemAutoId(section)\"\n tabindex=\"0\">\n @if (section.icon()) {\n <sd-icon class=\"c-anchor-list-item-icon\" [name]=\"section.icon()\"></sd-icon>\n }\n <div class=\"c-anchor-list-item-text\" [class.ellipsis]=\"ellipsis()\">\n {{ section.title() }}\n </div>\n </div>\n }\n</div>\n","import {\n ChangeDetectionStrategy,\n Component,\n ElementRef,\n OnDestroy,\n afterNextRender,\n booleanAttribute,\n computed,\n contentChildren,\n effect,\n input,\n signal,\n untracked,\n viewChild,\n} from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { BrowserUtilities } from '@sdcorejs/utils/fns';\nimport { Color } from '@sdcorejs/utils/models';\nimport { Subscription, fromEvent, take } from 'rxjs';\nimport { AnchorNav } from '../anchor-nav/anchor-nav.component';\nimport { SdAnchorItem } from '../anchor-item/anchor-item.component';\n\n@Component({\n selector: 'sd-anchor',\n templateUrl: './anchor.component.html',\n styleUrl: './anchor.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n imports: [CommonModule, AnchorNav],\n standalone: true,\n})\nexport class SdAnchor implements OnDestroy {\n wrapper = viewChild.required<ElementRef>('wrapper');\n sections = contentChildren(SdAnchorItem);\n\n readonly autoIdInput = input<string | undefined | null>(undefined, { alias: 'autoId' });\n readonly autoId = computed(() => (this.autoIdInput() ? `components-anchor-${this.autoIdInput()}` : undefined));\n\n sidebarWidth = input<string>('200px');\n ellipsis = input(false, { transform: booleanAttribute });\n overScroll = input(false, { transform: booleanAttribute });\n // Mobile: mặc định ẩn nav (sidebar TOC chiếm chỗ trên màn hình hẹp).\n // Consumer truyền `[hideNav]=\"false\"` để buộc hiển thị trên mobile.\n hideNav = input(BrowserUtilities.isMobile(), { transform: booleanAttribute });\n // Màu highlight active nav (text + icon + vertical bar). Default 'primary'.\n color = input<Color>('primary');\n\n activeSectionId = signal<string>('');\n\n #initialized = false;\n #isClickScrolling = false;\n #clickScrollSubscription: Subscription | null = null;\n #intersectionObserver: IntersectionObserver | null = null;\n #visibleSections = new Set<Element>();\n #timeoutId: ReturnType<typeof setTimeout> | null = null;\n\n #timeoutScrollFallback = 200;\n\n constructor() {\n afterNextRender(() => {\n this.#initialized = true;\n if (!this.hideNav()) {\n this.#registerIntersectionObserver();\n }\n });\n\n // Lắng nghe thay đổi sections để đăng ký lại observer cho từng section.\n effect(() => {\n this.sections();\n if (!this.hideNav() && this.#initialized) {\n untracked(() => this.#registerIntersectionObserver());\n }\n });\n }\n\n #registerIntersectionObserver(): void {\n this.#cleanIntersectionObserver();\n const wrapperEl = this.wrapper().nativeElement;\n\n // Browser API IntersectionObserver hỗ trợ xác định section đang hiển thị trong wrapperEl.\n this.#intersectionObserver = new IntersectionObserver(\n entries => {\n for (const entry of entries) {\n if (entry.isIntersecting) {\n // Nếu section mới đi vào vùng nhìn thấy thì thêm vào set.\n this.#visibleSections.add(entry.target);\n } else {\n // Nếu section đã đi ra khỏi vùng nhìn thấy thì xóa khỏi set.\n this.#visibleSections.delete(entry.target);\n }\n }\n\n // Nếu đang scroll do click section thì không cần quét section đang active.\n if (this.#isClickScrolling) {\n return;\n }\n\n for (const section of this.sections()) {\n // Kiểm tra DOM của section nào đang nằm trong danh sách nhìn thấy để active chính xác.\n if (this.#visibleSections.has(section.elementRef.nativeElement)) {\n if (this.activeSectionId() !== section.id) {\n this.activeSectionId.set(section.id);\n }\n break;\n }\n }\n },\n { root: wrapperEl, threshold: 0 }\n );\n\n // Đăng ký theo dõi từng section có trong anchor.\n for (const section of this.sections()) {\n this.#intersectionObserver.observe(section.elementRef.nativeElement);\n }\n }\n\n scrollSectionByClick(idSectionTarget: string): void {\n this.activeSectionId.set(idSectionTarget);\n const targetSection = this.sections().find(s => s.id === idSectionTarget)?.elementRef;\n if (!targetSection) {\n return;\n }\n this.#cleanScrollSectionByClickObserver();\n this.#isClickScrolling = true;\n\n const wrapperEl = this.wrapper().nativeElement;\n const targetElement = targetSection.nativeElement;\n\n const prevScrollTop = wrapperEl.scrollTop; // Lưu lại vị trí cuộn hiện tại của container.\n const scrollTop = prevScrollTop + targetElement.getBoundingClientRect().top - wrapperEl.getBoundingClientRect().top; // Vị trí section cần scroll tới.\n\n // Đăng ký sự kiện khi scroll hoàn tất.\n this.#clickScrollSubscription = fromEvent(wrapperEl, 'scrollend')\n .pipe(take(1))\n .subscribe(() => {\n this.#isClickScrolling = false;\n });\n\n // Nếu sectionTarget đã ở đúng vị trí (click nhưng không scroll) thì kích hoạt lại IntersectionObserver sau timeout.\n this.#timeoutId = setTimeout(() => {\n if (wrapperEl.scrollTop === prevScrollTop) {\n this.#isClickScrolling = false;\n this.#clickScrollSubscription?.unsubscribe();\n }\n }, this.#timeoutScrollFallback);\n\n wrapperEl.scrollTo({ top: scrollTop, behavior: 'smooth' });\n }\n\n #cleanScrollSectionByClickObserver = (): void => {\n if (this.#timeoutId) {\n clearTimeout(this.#timeoutId);\n }\n this.#clickScrollSubscription?.unsubscribe();\n };\n\n #cleanIntersectionObserver = (): void => {\n this.#intersectionObserver?.disconnect();\n this.#visibleSections.clear();\n };\n\n ngOnDestroy(): void {\n this.#cleanScrollSectionByClickObserver();\n this.#cleanIntersectionObserver();\n }\n}\n","<div class=\"c-anchor-vertical\" [ngClass]=\"{ 'c-stop-scroll-propagation': !overScroll() }\" #wrapper>\n <div class=\"c-anchor-content\">\n <ng-content></ng-content>\n </div>\n\n @if (!hideNav()) {\n <div class=\"c-anchor-list\" [style.flex-basis]=\"sidebarWidth()\">\n <anchor-nav\n [sections]=\"sections()\"\n [ellipsis]=\"ellipsis()\"\n [sidebarWidth]=\"sidebarWidth()\"\n [activeSectionId]=\"activeSectionId()\"\n [parentAutoId]=\"autoId()\"\n [color]=\"color()\"\n (clickSection)=\"scrollSectionByClick($event)\">\n </anchor-nav>\n </div>\n }\n</div>\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
@@ -0,0 +1,332 @@
1
+ import { NgTemplateOutlet } from '@angular/common';
2
+ import * as i0 from '@angular/core';
3
+ import { inject, TemplateRef, Directive, input, computed, contentChild, ChangeDetectionStrategy, Component } from '@angular/core';
4
+ import { I18nService } from '@sdcorejs/angular/i18n';
5
+
6
+ const DEFAULT_REDACTED_VALUE = '••••••';
7
+ function sdBuildAuditDiff(before, after, options = {}) {
8
+ const context = {
9
+ options,
10
+ fields: options.fields ?? [],
11
+ rows: [],
12
+ activeBefore: new WeakSet(),
13
+ activeAfter: new WeakSet(),
14
+ };
15
+ walkValue(context, before, after, true, true, '$', '$');
16
+ return context.rows.sort((left, right) => compareRows(left, right, context.fields));
17
+ }
18
+ function walkValue(context, before, after, beforePresent, afterPresent, path, configPath) {
19
+ if (isConfigured(context.fields, configPath, 'hidden'))
20
+ return;
21
+ if (isConfigured(context.fields, configPath, 'redacted')) {
22
+ addAtomicRow(context, before, after, beforePresent, afterPresent, path, configPath, true);
23
+ return;
24
+ }
25
+ const field = findExactField(context.fields, configPath);
26
+ if (field?.format || field?.enumMap) {
27
+ addAtomicRow(context, before, after, beforePresent, afterPresent, path, configPath, false);
28
+ return;
29
+ }
30
+ const beforeArray = Array.isArray(before);
31
+ const afterArray = Array.isArray(after);
32
+ if (beforeArray || afterArray) {
33
+ if ((!beforePresent && afterArray) || (beforeArray && !afterPresent) || (beforeArray && afterArray)) {
34
+ const arrayKey = field?.arrayKey;
35
+ if (arrayKey && walkStableArray(context, beforeArray ? before : [], afterArray ? after : [], path, configPath, arrayKey))
36
+ return;
37
+ }
38
+ addAtomicRow(context, before, after, beforePresent, afterPresent, path, configPath, false);
39
+ return;
40
+ }
41
+ const beforeObject = isPlainObject(before);
42
+ const afterObject = isPlainObject(after);
43
+ const canRecurse = (beforeObject && afterObject) || (beforeObject && !afterPresent) || (afterObject && !beforePresent);
44
+ if (canRecurse) {
45
+ if ((beforeObject && context.activeBefore.has(before)) || (afterObject && context.activeAfter.has(after)))
46
+ return;
47
+ if (beforeObject)
48
+ context.activeBefore.add(before);
49
+ if (afterObject)
50
+ context.activeAfter.add(after);
51
+ const beforeRecord = beforeObject ? before : {};
52
+ const afterRecord = afterObject ? after : {};
53
+ const keys = [...new Set([...Object.keys(beforeRecord), ...Object.keys(afterRecord)])].sort(compareText);
54
+ if (keys.length === 0) {
55
+ addAtomicRow(context, before, after, beforePresent, afterPresent, path, configPath, false);
56
+ if (beforeObject)
57
+ context.activeBefore.delete(before);
58
+ if (afterObject)
59
+ context.activeAfter.delete(after);
60
+ return;
61
+ }
62
+ for (const key of keys) {
63
+ const childBeforePresent = beforeObject && hasOwn(beforeRecord, key);
64
+ const childAfterPresent = afterObject && hasOwn(afterRecord, key);
65
+ walkValue(context, childBeforePresent ? beforeRecord[key] : undefined, childAfterPresent ? afterRecord[key] : undefined, childBeforePresent, childAfterPresent, path === '$' ? key : `${path}.${key}`, configPath === '$' ? key : `${configPath}.${key}`);
66
+ }
67
+ if (beforeObject)
68
+ context.activeBefore.delete(before);
69
+ if (afterObject)
70
+ context.activeAfter.delete(after);
71
+ return;
72
+ }
73
+ addAtomicRow(context, before, after, beforePresent, afterPresent, path, configPath, false);
74
+ }
75
+ function walkStableArray(context, before, after, path, configPath, keySelector) {
76
+ const beforeItems = indexStableArray(before, keySelector);
77
+ const afterItems = indexStableArray(after, keySelector);
78
+ if (!beforeItems || !afterItems)
79
+ return false;
80
+ const keys = [...new Set([...beforeItems.keys(), ...afterItems.keys()])].sort(compareText);
81
+ const keyName = typeof keySelector === 'string' ? keySelector : 'key';
82
+ for (const key of keys) {
83
+ const beforeItem = beforeItems.get(key);
84
+ const afterItem = afterItems.get(key);
85
+ const itemPath = `${path}[${keyName}=${encodeURIComponent(key)}]`;
86
+ const itemConfigPath = `${configPath}[]`;
87
+ if (!beforeItem || !afterItem) {
88
+ walkValue(context, beforeItem?.value, afterItem?.value, Boolean(beforeItem), Boolean(afterItem), itemPath, itemConfigPath);
89
+ continue;
90
+ }
91
+ walkValue(context, beforeItem.value, afterItem.value, true, true, itemPath, itemConfigPath);
92
+ }
93
+ return true;
94
+ }
95
+ function indexStableArray(items, keySelector) {
96
+ const result = new Map();
97
+ for (const item of items) {
98
+ let key;
99
+ try {
100
+ key = typeof keySelector === 'function' ? keySelector(item) : isObject(item) ? item[keySelector] : undefined;
101
+ }
102
+ catch {
103
+ return null;
104
+ }
105
+ const serialized = serializeStableKey(key);
106
+ if (serialized === null || result.has(serialized))
107
+ return null;
108
+ result.set(serialized, { key, value: item });
109
+ }
110
+ return result;
111
+ }
112
+ function serializeStableKey(key) {
113
+ if (key === null)
114
+ return 'null:null';
115
+ switch (typeof key) {
116
+ case 'string':
117
+ case 'number':
118
+ case 'bigint':
119
+ case 'boolean':
120
+ return `${typeof key}:${String(key)}`;
121
+ default:
122
+ return null;
123
+ }
124
+ }
125
+ function addAtomicRow(context, before, after, beforePresent, afterPresent, path, configPath, redacted) {
126
+ const kind = classifyChange(before, after, beforePresent, afterPresent);
127
+ if (kind === 'unchanged' && !context.options.includeUnchanged)
128
+ return;
129
+ const field = findExactField(context.fields, configPath);
130
+ const redactedValue = context.options.redactedValue ?? DEFAULT_REDACTED_VALUE;
131
+ const normalizedBefore = redacted
132
+ ? beforePresent
133
+ ? redactedValue
134
+ : undefined
135
+ : normalizeValue(before, beforePresent, 'before', kind, path, configPath, field, after);
136
+ const normalizedAfter = redacted
137
+ ? afterPresent
138
+ ? redactedValue
139
+ : undefined
140
+ : normalizeValue(after, afterPresent, 'after', kind, path, configPath, field, before);
141
+ context.rows.push({
142
+ id: path,
143
+ path,
144
+ configPath,
145
+ label: field?.label ?? (path === '$' ? (context.options.rootLabel ?? 'Value') : path),
146
+ kind,
147
+ before: normalizedBefore,
148
+ after: normalizedAfter,
149
+ beforePresent,
150
+ afterPresent,
151
+ redacted,
152
+ });
153
+ }
154
+ function normalizeValue(value, present, side, kind, path, configPath, field, otherValue) {
155
+ if (!present)
156
+ return undefined;
157
+ const mapped = field?.enumMap && hasOwn(field.enumMap, String(value)) ? field.enumMap[String(value)] : value;
158
+ if (!field?.format)
159
+ return mapped;
160
+ try {
161
+ return field.format(mapped, {
162
+ path,
163
+ configPath,
164
+ side,
165
+ kind,
166
+ before: side === 'before' ? value : otherValue,
167
+ after: side === 'after' ? value : otherValue,
168
+ });
169
+ }
170
+ catch {
171
+ return mapped;
172
+ }
173
+ }
174
+ function classifyChange(before, after, beforePresent, afterPresent) {
175
+ if (!beforePresent && afterPresent)
176
+ return 'added';
177
+ if (beforePresent && !afterPresent)
178
+ return 'removed';
179
+ return deepEqual(before, after) ? 'unchanged' : 'changed';
180
+ }
181
+ function deepEqual(left, right, seen = new WeakMap()) {
182
+ if (Object.is(left, right))
183
+ return true;
184
+ if (left instanceof Date && right instanceof Date)
185
+ return left.getTime() === right.getTime();
186
+ if (!isObject(left) || !isObject(right))
187
+ return false;
188
+ let rightSet = seen.get(left);
189
+ if (rightSet?.has(right))
190
+ return true;
191
+ if (!rightSet) {
192
+ rightSet = new WeakSet();
193
+ seen.set(left, rightSet);
194
+ }
195
+ rightSet.add(right);
196
+ if (Array.isArray(left) && Array.isArray(right)) {
197
+ if (left.length !== right.length)
198
+ return false;
199
+ return left.every((value, index) => deepEqual(value, right[index], seen));
200
+ }
201
+ if (!isPlainObject(left) || !isPlainObject(right))
202
+ return false;
203
+ const leftKeys = Object.keys(left).sort(compareText);
204
+ const rightKeys = Object.keys(right).sort(compareText);
205
+ if (leftKeys.length !== rightKeys.length || leftKeys.some((key, index) => key !== rightKeys[index]))
206
+ return false;
207
+ return leftKeys.every(key => deepEqual(left[key], right[key], seen));
208
+ }
209
+ function compareRows(left, right, fields) {
210
+ const leftOrder = explicitOrder(left.configPath, fields);
211
+ const rightOrder = explicitOrder(right.configPath, fields);
212
+ if (leftOrder !== rightOrder)
213
+ return leftOrder - rightOrder;
214
+ return compareText(left.path, right.path);
215
+ }
216
+ function explicitOrder(path, fields) {
217
+ const field = fields
218
+ .filter(candidate => Number.isFinite(candidate.order) && isPathOrDescendant(path, candidate.path))
219
+ .sort((left, right) => right.path.length - left.path.length)[0];
220
+ return field?.order ?? Number.POSITIVE_INFINITY;
221
+ }
222
+ function findExactField(fields, path) {
223
+ return fields.find(field => field.path === path);
224
+ }
225
+ function isConfigured(fields, path, property) {
226
+ return fields.some(field => Boolean(field[property]) && isPathOrDescendant(path, field.path));
227
+ }
228
+ function isPathOrDescendant(path, ancestor) {
229
+ return path === ancestor || path.startsWith(`${ancestor}.`) || path.startsWith(`${ancestor}[]`);
230
+ }
231
+ function isObject(value) {
232
+ return typeof value === 'object' && value !== null;
233
+ }
234
+ function isPlainObject(value) {
235
+ if (!isObject(value))
236
+ return false;
237
+ const prototype = Object.getPrototypeOf(value);
238
+ return prototype === Object.prototype || prototype === null;
239
+ }
240
+ function hasOwn(object, key) {
241
+ return Object.prototype.hasOwnProperty.call(object, key);
242
+ }
243
+ function compareText(left, right) {
244
+ return left < right ? -1 : left > right ? 1 : 0;
245
+ }
246
+
247
+ class SdAuditDiffValueTemplateDirective {
248
+ template = inject(TemplateRef);
249
+ static ngTemplateContextGuard(_directive, _context) {
250
+ return true;
251
+ }
252
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdAuditDiffValueTemplateDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
253
+ static ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "21.2.17", type: SdAuditDiffValueTemplateDirective, isStandalone: true, selector: "ng-template[sdAuditDiffValue]", ngImport: i0 });
254
+ }
255
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdAuditDiffValueTemplateDirective, decorators: [{
256
+ type: Directive,
257
+ args: [{
258
+ selector: 'ng-template[sdAuditDiffValue]',
259
+ standalone: true,
260
+ }]
261
+ }] });
262
+ class SdAuditDiff {
263
+ before = input(undefined, ...(ngDevMode ? [{ debugName: "before" }] : /* istanbul ignore next */ []));
264
+ after = input(undefined, ...(ngDevMode ? [{ debugName: "after" }] : /* istanbul ignore next */ []));
265
+ options = input({}, ...(ngDevMode ? [{ debugName: "options" }] : /* istanbul ignore next */ []));
266
+ mode = input('table', ...(ngDevMode ? [{ debugName: "mode" }] : /* istanbul ignore next */ []));
267
+ ariaLabel = input(undefined, ...(ngDevMode ? [{ debugName: "ariaLabel" }] : /* istanbul ignore next */ []));
268
+ #i18n = inject(I18nService);
269
+ rows = computed(() => {
270
+ const options = this.options();
271
+ return sdBuildAuditDiff(this.before(), this.after(), {
272
+ ...options,
273
+ rootLabel: options.rootLabel ?? this.text('value.label'),
274
+ });
275
+ }, ...(ngDevMode ? [{ debugName: "rows" }] : /* istanbul ignore next */ []));
276
+ valueTemplate = contentChild(SdAuditDiffValueTemplateDirective, ...(ngDevMode ? [{ debugName: "valueTemplate" }] : /* istanbul ignore next */ []));
277
+ text(key) {
278
+ return this.#i18n.t(`core.component.audit-diff.${key}`);
279
+ }
280
+ valueContext(row, side) {
281
+ return { $implicit: row[side], row, side };
282
+ }
283
+ displayValue(row, side) {
284
+ const present = side === 'before' ? row.beforePresent : row.afterPresent;
285
+ if (!present)
286
+ return this.text('value.not-set');
287
+ const value = row[side];
288
+ if (value === undefined)
289
+ return this.text('value.undefined');
290
+ if (value === null)
291
+ return this.text('value.null');
292
+ if (value instanceof Date)
293
+ return value.toISOString();
294
+ if (typeof value === 'boolean')
295
+ return this.text(`value.${value ? 'true' : 'false'}`);
296
+ if (typeof value === 'string')
297
+ return value;
298
+ if (typeof value === 'number' || typeof value === 'bigint')
299
+ return String(value);
300
+ return safeStringify(value);
301
+ }
302
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdAuditDiff, deps: [], target: i0.ɵɵFactoryTarget.Component });
303
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: SdAuditDiff, isStandalone: true, selector: "sd-audit-diff", inputs: { before: { classPropertyName: "before", publicName: "before", isSignal: true, isRequired: false, transformFunction: null }, after: { classPropertyName: "after", publicName: "after", isSignal: true, isRequired: false, transformFunction: null }, options: { classPropertyName: "options", publicName: "options", isSignal: true, isRequired: false, transformFunction: null }, mode: { classPropertyName: "mode", publicName: "mode", isSignal: true, isRequired: false, transformFunction: null }, ariaLabel: { classPropertyName: "ariaLabel", publicName: "ariaLabel", isSignal: true, isRequired: false, transformFunction: null } }, queries: [{ propertyName: "valueTemplate", first: true, predicate: SdAuditDiffValueTemplateDirective, descendants: true, isSignal: true }], ngImport: i0, template: "@let currentRows = rows();\n@let customValue = valueTemplate();\n\n@if (currentRows.length === 0) {\n <p class=\"sd-audit-diff__empty\" data-audit-empty role=\"status\">{{ text('empty') }}</p>\n} @else if (mode() === 'table') {\n <div class=\"sd-audit-diff__table-scroll\">\n <table class=\"sd-audit-diff__table\" [attr.aria-label]=\"ariaLabel() || text('label')\">\n <thead>\n <tr>\n <th scope=\"col\">{{ text('field') }}</th>\n <th scope=\"col\">{{ text('before') }}</th>\n <th scope=\"col\">{{ text('after') }}</th>\n </tr>\n </thead>\n <tbody>\n @for (row of currentRows; track row.id) {\n <tr data-audit-row [attr.data-change-kind]=\"row.kind\">\n <th scope=\"row\">\n <span class=\"sd-audit-diff__field\">{{ row.label }}</span>\n <span class=\"sd-audit-diff__kind\">{{ text('kind.' + row.kind) }}</span>\n </th>\n <td data-audit-before>\n @if (customValue) {\n <ng-container\n [ngTemplateOutlet]=\"customValue.template\"\n [ngTemplateOutletContext]=\"valueContext(row, 'before')\"></ng-container>\n } @else {\n {{ displayValue(row, 'before') }}\n }\n </td>\n <td data-audit-after>\n @if (customValue) {\n <ng-container\n [ngTemplateOutlet]=\"customValue.template\"\n [ngTemplateOutletContext]=\"valueContext(row, 'after')\"></ng-container>\n } @else {\n {{ displayValue(row, 'after') }}\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n} @else {\n <dl class=\"sd-audit-diff__details\" [attr.aria-label]=\"ariaLabel() || text('label')\">\n @for (row of currentRows; track row.id) {\n <div class=\"sd-audit-diff__detail\" data-audit-row [attr.data-change-kind]=\"row.kind\">\n <dt>\n <span class=\"sd-audit-diff__field\">{{ row.label }}</span>\n <span class=\"sd-audit-diff__kind\">{{ text('kind.' + row.kind) }}</span>\n </dt>\n <dd data-audit-before>\n <span class=\"sd-audit-diff__side-label\">{{ text('before') }}</span>\n @if (customValue) {\n <ng-container [ngTemplateOutlet]=\"customValue.template\" [ngTemplateOutletContext]=\"valueContext(row, 'before')\"></ng-container>\n } @else {\n <span>{{ displayValue(row, 'before') }}</span>\n }\n </dd>\n <dd data-audit-after>\n <span class=\"sd-audit-diff__side-label\">{{ text('after') }}</span>\n @if (customValue) {\n <ng-container [ngTemplateOutlet]=\"customValue.template\" [ngTemplateOutletContext]=\"valueContext(row, 'after')\"></ng-container>\n } @else {\n <span>{{ displayValue(row, 'after') }}</span>\n }\n </dd>\n </div>\n }\n </dl>\n}\n", styles: [":host{display:block}.sd-audit-diff__table-scroll{overflow-x:auto;border:1px solid var(--sd-border-color, #dfe3e8);border-radius:8px}.sd-audit-diff__table{width:100%;border-collapse:collapse;background:var(--sd-surface, #fff)}.sd-audit-diff__table th,.sd-audit-diff__table td{min-width:160px;padding:10px 12px;border-bottom:1px solid var(--sd-border-color, #dfe3e8);text-align:left;vertical-align:top;overflow-wrap:anywhere}.sd-audit-diff__table th:first-child{min-width:180px}.sd-audit-diff__table tbody tr:last-child>*{border-bottom:0}.sd-audit-diff__field,.sd-audit-diff__kind,.sd-audit-diff__side-label{display:block}.sd-audit-diff__kind,.sd-audit-diff__side-label{margin-top:2px;color:var(--sd-text-muted, #5f6772);font-size:.75rem;font-weight:400}.sd-audit-diff__details{display:grid;gap:12px;margin:0}.sd-audit-diff__detail{display:grid;grid-template-columns:minmax(140px,.8fr) repeat(2,minmax(0,1fr));gap:12px;padding:12px;border:1px solid var(--sd-border-color, #dfe3e8);border-radius:8px;background:var(--sd-surface, #fff)}.sd-audit-diff__detail dt,.sd-audit-diff__detail dd{min-width:0;margin:0;overflow-wrap:anywhere}.sd-audit-diff__empty{margin:0;padding:16px;border:1px dashed var(--sd-border-color, #dfe3e8);border-radius:8px;color:var(--sd-text-muted, #5f6772);text-align:center}@media(max-width:640px){.sd-audit-diff__detail{grid-template-columns:1fr}}\n"], dependencies: [{ kind: "directive", type: NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
304
+ }
305
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: SdAuditDiff, decorators: [{
306
+ type: Component,
307
+ args: [{ selector: 'sd-audit-diff', standalone: true, imports: [NgTemplateOutlet], changeDetection: ChangeDetectionStrategy.OnPush, template: "@let currentRows = rows();\n@let customValue = valueTemplate();\n\n@if (currentRows.length === 0) {\n <p class=\"sd-audit-diff__empty\" data-audit-empty role=\"status\">{{ text('empty') }}</p>\n} @else if (mode() === 'table') {\n <div class=\"sd-audit-diff__table-scroll\">\n <table class=\"sd-audit-diff__table\" [attr.aria-label]=\"ariaLabel() || text('label')\">\n <thead>\n <tr>\n <th scope=\"col\">{{ text('field') }}</th>\n <th scope=\"col\">{{ text('before') }}</th>\n <th scope=\"col\">{{ text('after') }}</th>\n </tr>\n </thead>\n <tbody>\n @for (row of currentRows; track row.id) {\n <tr data-audit-row [attr.data-change-kind]=\"row.kind\">\n <th scope=\"row\">\n <span class=\"sd-audit-diff__field\">{{ row.label }}</span>\n <span class=\"sd-audit-diff__kind\">{{ text('kind.' + row.kind) }}</span>\n </th>\n <td data-audit-before>\n @if (customValue) {\n <ng-container\n [ngTemplateOutlet]=\"customValue.template\"\n [ngTemplateOutletContext]=\"valueContext(row, 'before')\"></ng-container>\n } @else {\n {{ displayValue(row, 'before') }}\n }\n </td>\n <td data-audit-after>\n @if (customValue) {\n <ng-container\n [ngTemplateOutlet]=\"customValue.template\"\n [ngTemplateOutletContext]=\"valueContext(row, 'after')\"></ng-container>\n } @else {\n {{ displayValue(row, 'after') }}\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n} @else {\n <dl class=\"sd-audit-diff__details\" [attr.aria-label]=\"ariaLabel() || text('label')\">\n @for (row of currentRows; track row.id) {\n <div class=\"sd-audit-diff__detail\" data-audit-row [attr.data-change-kind]=\"row.kind\">\n <dt>\n <span class=\"sd-audit-diff__field\">{{ row.label }}</span>\n <span class=\"sd-audit-diff__kind\">{{ text('kind.' + row.kind) }}</span>\n </dt>\n <dd data-audit-before>\n <span class=\"sd-audit-diff__side-label\">{{ text('before') }}</span>\n @if (customValue) {\n <ng-container [ngTemplateOutlet]=\"customValue.template\" [ngTemplateOutletContext]=\"valueContext(row, 'before')\"></ng-container>\n } @else {\n <span>{{ displayValue(row, 'before') }}</span>\n }\n </dd>\n <dd data-audit-after>\n <span class=\"sd-audit-diff__side-label\">{{ text('after') }}</span>\n @if (customValue) {\n <ng-container [ngTemplateOutlet]=\"customValue.template\" [ngTemplateOutletContext]=\"valueContext(row, 'after')\"></ng-container>\n } @else {\n <span>{{ displayValue(row, 'after') }}</span>\n }\n </dd>\n </div>\n }\n </dl>\n}\n", styles: [":host{display:block}.sd-audit-diff__table-scroll{overflow-x:auto;border:1px solid var(--sd-border-color, #dfe3e8);border-radius:8px}.sd-audit-diff__table{width:100%;border-collapse:collapse;background:var(--sd-surface, #fff)}.sd-audit-diff__table th,.sd-audit-diff__table td{min-width:160px;padding:10px 12px;border-bottom:1px solid var(--sd-border-color, #dfe3e8);text-align:left;vertical-align:top;overflow-wrap:anywhere}.sd-audit-diff__table th:first-child{min-width:180px}.sd-audit-diff__table tbody tr:last-child>*{border-bottom:0}.sd-audit-diff__field,.sd-audit-diff__kind,.sd-audit-diff__side-label{display:block}.sd-audit-diff__kind,.sd-audit-diff__side-label{margin-top:2px;color:var(--sd-text-muted, #5f6772);font-size:.75rem;font-weight:400}.sd-audit-diff__details{display:grid;gap:12px;margin:0}.sd-audit-diff__detail{display:grid;grid-template-columns:minmax(140px,.8fr) repeat(2,minmax(0,1fr));gap:12px;padding:12px;border:1px solid var(--sd-border-color, #dfe3e8);border-radius:8px;background:var(--sd-surface, #fff)}.sd-audit-diff__detail dt,.sd-audit-diff__detail dd{min-width:0;margin:0;overflow-wrap:anywhere}.sd-audit-diff__empty{margin:0;padding:16px;border:1px dashed var(--sd-border-color, #dfe3e8);border-radius:8px;color:var(--sd-text-muted, #5f6772);text-align:center}@media(max-width:640px){.sd-audit-diff__detail{grid-template-columns:1fr}}\n"] }]
308
+ }], propDecorators: { before: [{ type: i0.Input, args: [{ isSignal: true, alias: "before", required: false }] }], after: [{ type: i0.Input, args: [{ isSignal: true, alias: "after", required: false }] }], options: [{ type: i0.Input, args: [{ isSignal: true, alias: "options", required: false }] }], mode: [{ type: i0.Input, args: [{ isSignal: true, alias: "mode", required: false }] }], ariaLabel: [{ type: i0.Input, args: [{ isSignal: true, alias: "ariaLabel", required: false }] }], valueTemplate: [{ type: i0.ContentChild, args: [i0.forwardRef(() => SdAuditDiffValueTemplateDirective), { isSignal: true }] }] } });
309
+ function safeStringify(value) {
310
+ const seen = new WeakSet();
311
+ try {
312
+ const result = JSON.stringify(value, (_key, current) => {
313
+ if (typeof current !== 'object' || current === null)
314
+ return current;
315
+ if (seen.has(current))
316
+ return '[Circular]';
317
+ seen.add(current);
318
+ return current;
319
+ });
320
+ return result ?? String(value);
321
+ }
322
+ catch {
323
+ return String(value);
324
+ }
325
+ }
326
+
327
+ /**
328
+ * Generated bundle index. Do not edit.
329
+ */
330
+
331
+ export { SdAuditDiff, SdAuditDiffValueTemplateDirective, sdBuildAuditDiff };
332
+ //# sourceMappingURL=sdcorejs-angular-components-audit-diff.mjs.map