@sd-angular/core 19.0.0-beta.97 → 19.0.0-beta.99

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 (178) hide show
  1. package/README.md +722 -716
  2. package/components/anchor/src/components/anchor/anchor.component.d.ts +5 -5
  3. package/components/anchor/src/components/anchor-nav/anchor-nav.component.d.ts +21 -0
  4. package/components/autoid-inspector/index.d.ts +8 -0
  5. package/components/autoid-inspector/src/autoid-inspector.component.d.ts +37 -0
  6. package/components/autoid-inspector/src/models/autoid-element.model.d.ts +22 -0
  7. package/components/autoid-inspector/src/models/autoid-export-format.model.d.ts +6 -0
  8. package/components/autoid-inspector/src/models/autoid-inspector-config.model.d.ts +8 -0
  9. package/components/autoid-inspector/src/services/autoid-audit.service.d.ts +19 -0
  10. package/components/autoid-inspector/src/services/autoid-export.service.d.ts +19 -0
  11. package/components/autoid-inspector/src/services/autoid-highlight.service.d.ts +12 -0
  12. package/components/autoid-inspector/src/services/autoid-scanner.service.d.ts +13 -0
  13. package/components/editor/src/editor.component.d.ts +2 -0
  14. package/components/index.d.ts +1 -0
  15. package/components/modal/src/modal.component.d.ts +1 -0
  16. package/components/side-drawer/src/side-drawer.component.d.ts +7 -3
  17. package/components/table/index.d.ts.map +1 -0
  18. package/components/table/sd-angular-core-components-table.d.ts.map +1 -0
  19. package/components/table/src/components/column-title/column-title.component.d.ts.map +1 -0
  20. package/components/table/src/components/command/desktop-command.component.d.ts +14 -0
  21. package/components/table/src/components/command/desktop-command.component.d.ts.map +1 -0
  22. package/components/table/src/components/command/pipes/command.pipe.d.ts +30 -0
  23. package/components/table/src/components/command/pipes/command.pipe.d.ts.map +1 -0
  24. package/components/table/src/components/command/pipes/filter.pipe.d.ts +35 -0
  25. package/components/table/src/components/command/pipes/filter.pipe.d.ts.map +1 -0
  26. package/components/table/src/components/config/config.component.d.ts +7 -14
  27. package/components/table/src/components/config/config.component.d.ts.map +1 -0
  28. package/components/table/src/components/desktop-cell/desktop-cell.component.d.ts +2 -2
  29. package/components/table/src/components/desktop-cell/desktop-cell.component.d.ts.map +1 -0
  30. package/components/table/src/components/desktop-cell/view/view.component.d.ts.map +1 -0
  31. package/components/table/src/components/filter/column-filter/column-filter.component.d.ts +36 -0
  32. package/components/table/src/components/filter/column-filter/column-filter.component.d.ts.map +1 -0
  33. package/components/table/src/components/filter/external-filter/external-filter.component.d.ts +27 -0
  34. package/components/table/src/components/filter/external-filter/external-filter.component.d.ts.map +1 -0
  35. package/components/table/src/components/filter/index.d.ts +3 -0
  36. package/components/table/src/components/filter/index.d.ts.map +1 -0
  37. package/components/table/src/components/filter/mobile-filter/mobile-filter.component.d.ts +42 -0
  38. package/components/table/src/components/filter/mobile-filter/mobile-filter.component.d.ts.map +1 -0
  39. package/components/table/src/components/index.d.ts +1 -2
  40. package/components/table/src/components/index.d.ts.map +1 -0
  41. package/components/table/src/components/popup-export/popup-export.component.d.ts.map +1 -0
  42. package/components/table/src/components/selector-action/action-filter.pipe.d.ts.map +1 -0
  43. package/components/table/src/components/selector-action/selector-action.component.d.ts +2 -4
  44. package/components/table/src/components/selector-action/selector-action.component.d.ts.map +1 -0
  45. package/components/table/src/configurations/index.d.ts.map +1 -0
  46. package/components/table/src/configurations/table.configuration.d.ts.map +1 -0
  47. package/components/table/src/directives/index.d.ts.map +1 -0
  48. package/components/table/src/directives/sd-column-resize.directive.d.ts.map +1 -0
  49. package/components/table/src/directives/sd-table-cell-def.directive.d.ts +4 -4
  50. package/components/table/src/directives/sd-table-cell-def.directive.d.ts.map +1 -0
  51. package/components/table/src/directives/sd-table-column-filter-def.directive.d.ts +2 -2
  52. package/components/table/src/directives/sd-table-column-filter-def.directive.d.ts.map +1 -0
  53. package/components/table/src/directives/sd-table-expand-def.directive.d.ts +3 -3
  54. package/components/table/src/directives/sd-table-expand-def.directive.d.ts.map +1 -0
  55. package/components/table/src/directives/sd-table-filter-def.directive.d.ts +2 -2
  56. package/components/table/src/directives/sd-table-filter-def.directive.d.ts.map +1 -0
  57. package/components/table/src/directives/sd-table-footer-def.directive.d.ts +2 -2
  58. package/components/table/src/directives/sd-table-footer-def.directive.d.ts.map +1 -0
  59. package/components/table/src/directives/sd-table-title-def.directive.d.ts +2 -2
  60. package/components/table/src/directives/sd-table-title-def.directive.d.ts.map +1 -0
  61. package/components/table/src/directives/sticky-shadow.directive.d.ts.map +1 -0
  62. package/components/table/src/models/index.d.ts.map +1 -0
  63. package/components/table/src/models/table-column.model.d.ts.map +1 -0
  64. package/components/table/src/models/table-command.model.d.ts.map +1 -0
  65. package/components/table/src/models/table-item.model.d.ts.map +1 -0
  66. package/components/table/src/models/table-option-config.model.d.ts.map +1 -0
  67. package/components/table/src/models/table-option-expand.model.d.ts.map +1 -0
  68. package/components/table/src/models/table-option-export.model.d.ts.map +1 -0
  69. package/components/table/src/models/table-option-group.model.d.ts.map +1 -0
  70. package/components/table/src/models/table-option-paginate.model.d.ts.map +1 -0
  71. package/components/table/src/models/table-option-reload.model.d.ts.map +1 -0
  72. package/components/table/src/models/table-option-selector.model.d.ts.map +1 -0
  73. package/components/table/src/models/table-option-sort.model.d.ts.map +1 -0
  74. package/components/table/src/models/table-option-style.model.d.ts.map +1 -0
  75. package/components/table/src/models/table-option-tree.model.d.ts.map +1 -0
  76. package/components/table/src/models/table-option.model.d.ts +13 -0
  77. package/components/table/src/models/table-option.model.d.ts.map +1 -0
  78. package/components/table/src/pipes/filter-values.pipe.d.ts.map +1 -0
  79. package/components/table/src/pipes/index.d.ts.map +1 -0
  80. package/components/table/src/pipes/sd-group.pipe.d.ts.map +1 -0
  81. package/components/table/src/pipes/sd-tree.pipe.d.ts.map +1 -0
  82. package/components/table/src/pipes/selection-disabled.pipe.d.ts.map +1 -0
  83. package/components/table/src/pipes/selection-visible-select-all.pipe.d.ts.map +1 -0
  84. package/components/table/src/pipes/selection-visible.pipe.d.ts.map +1 -0
  85. package/components/table/src/services/column-width.util.d.ts.map +1 -0
  86. package/components/table/src/services/config.service.d.ts.map +1 -0
  87. package/components/table/src/services/index.d.ts.map +1 -0
  88. package/components/table/src/services/table-export/table-export.service.d.ts.map +1 -0
  89. package/components/table/src/services/table-filter/table-filter.model.d.ts.map +1 -0
  90. package/components/table/src/services/table-filter/table-filter.service.d.ts.map +1 -0
  91. package/components/table/src/services/table-format/table-format.service.d.ts.map +1 -0
  92. package/components/table/src/services/tree/tree.util.d.ts.map +1 -0
  93. package/components/table/src/table.component.d.ts +10 -7
  94. package/components/table/src/table.component.d.ts.map +1 -0
  95. package/components/upload-file/src/upload-file.component.d.ts +3 -0
  96. package/fesm2022/sd-angular-core-components-anchor.mjs +23 -16
  97. package/fesm2022/sd-angular-core-components-anchor.mjs.map +1 -1
  98. package/fesm2022/sd-angular-core-components-autoid-inspector.mjs +585 -0
  99. package/fesm2022/sd-angular-core-components-autoid-inspector.mjs.map +1 -0
  100. package/fesm2022/sd-angular-core-components-button.mjs +2 -2
  101. package/fesm2022/sd-angular-core-components-button.mjs.map +1 -1
  102. package/fesm2022/sd-angular-core-components-editor.mjs +5 -2
  103. package/fesm2022/sd-angular-core-components-editor.mjs.map +1 -1
  104. package/fesm2022/sd-angular-core-components-form-generic.mjs +3 -3
  105. package/fesm2022/sd-angular-core-components-form-generic.mjs.map +1 -1
  106. package/fesm2022/sd-angular-core-components-modal.mjs +3 -2
  107. package/fesm2022/sd-angular-core-components-modal.mjs.map +1 -1
  108. package/fesm2022/sd-angular-core-components-side-drawer.mjs +17 -11
  109. package/fesm2022/sd-angular-core-components-side-drawer.mjs.map +1 -1
  110. package/fesm2022/sd-angular-core-components-table.mjs +631 -572
  111. package/fesm2022/sd-angular-core-components-table.mjs.map +1 -1
  112. package/fesm2022/sd-angular-core-components-upload-file.mjs +7 -2
  113. package/fesm2022/sd-angular-core-components-upload-file.mjs.map +1 -1
  114. package/fesm2022/sd-angular-core-components.mjs +1 -0
  115. package/fesm2022/sd-angular-core-components.mjs.map +1 -1
  116. package/fesm2022/sd-angular-core-forms-autocomplete.mjs +11 -3
  117. package/fesm2022/sd-angular-core-forms-autocomplete.mjs.map +1 -1
  118. package/fesm2022/sd-angular-core-forms-checkbox.mjs +8 -2
  119. package/fesm2022/sd-angular-core-forms-checkbox.mjs.map +1 -1
  120. package/fesm2022/sd-angular-core-forms-chip-calendar.mjs +12 -3
  121. package/fesm2022/sd-angular-core-forms-chip-calendar.mjs.map +1 -1
  122. package/fesm2022/sd-angular-core-forms-chip.mjs +12 -3
  123. package/fesm2022/sd-angular-core-forms-chip.mjs.map +1 -1
  124. package/fesm2022/sd-angular-core-forms-date-range.mjs +13 -3
  125. package/fesm2022/sd-angular-core-forms-date-range.mjs.map +1 -1
  126. package/fesm2022/sd-angular-core-forms-date.mjs +9 -3
  127. package/fesm2022/sd-angular-core-forms-date.mjs.map +1 -1
  128. package/fesm2022/sd-angular-core-forms-datetime.mjs +9 -3
  129. package/fesm2022/sd-angular-core-forms-datetime.mjs.map +1 -1
  130. package/fesm2022/sd-angular-core-forms-input-number.mjs +9 -3
  131. package/fesm2022/sd-angular-core-forms-input-number.mjs.map +1 -1
  132. package/fesm2022/sd-angular-core-forms-input.mjs +13 -3
  133. package/fesm2022/sd-angular-core-forms-input.mjs.map +1 -1
  134. package/fesm2022/sd-angular-core-forms-models.mjs +65 -2
  135. package/fesm2022/sd-angular-core-forms-models.mjs.map +1 -1
  136. package/fesm2022/sd-angular-core-forms-radio.mjs +8 -3
  137. package/fesm2022/sd-angular-core-forms-radio.mjs.map +1 -1
  138. package/fesm2022/sd-angular-core-forms-select.mjs +11 -3
  139. package/fesm2022/sd-angular-core-forms-select.mjs.map +1 -1
  140. package/fesm2022/sd-angular-core-forms-switch.mjs +8 -3
  141. package/fesm2022/sd-angular-core-forms-switch.mjs.map +1 -1
  142. package/fesm2022/sd-angular-core-forms-textarea.mjs +9 -3
  143. package/fesm2022/sd-angular-core-forms-textarea.mjs.map +1 -1
  144. package/fesm2022/sd-angular-core-i18n.mjs +205 -20
  145. package/fesm2022/sd-angular-core-i18n.mjs.map +1 -1
  146. package/fesm2022/sd-angular-core-utilities-data-state.mjs +38 -0
  147. package/fesm2022/sd-angular-core-utilities-data-state.mjs.map +1 -0
  148. package/fesm2022/sd-angular-core-utilities.mjs +1 -0
  149. package/fesm2022/sd-angular-core-utilities.mjs.map +1 -1
  150. package/forms/autocomplete/src/autocomplete.component.d.ts +5 -0
  151. package/forms/checkbox/src/checkbox.component.d.ts +3 -0
  152. package/forms/chip/src/chip.component.d.ts +4 -0
  153. package/forms/chip-calendar/src/chip-calendar.component.d.ts +4 -0
  154. package/forms/date/src/date.component.d.ts +4 -0
  155. package/forms/date-range/src/date-range.component.d.ts +4 -0
  156. package/forms/datetime/src/datetime.component.d.ts +4 -0
  157. package/forms/input/src/input.component.d.ts +4 -0
  158. package/forms/input-number/src/input-number.component.d.ts +6 -2
  159. package/forms/models/index.d.ts +1 -0
  160. package/forms/models/src/form-control-state.d.ts +23 -0
  161. package/forms/radio/src/radio.component.d.ts +3 -0
  162. package/forms/select/src/select.component.d.ts +5 -0
  163. package/forms/switch/src/switch.component.d.ts +3 -0
  164. package/forms/textarea/src/textarea.component.d.ts +4 -0
  165. package/i18n/src/en.d.ts +39 -3
  166. package/package.json +49 -41
  167. package/sd-angular-core-19.0.0-beta.99.tgz +0 -0
  168. package/utilities/data-state/index.d.ts +1 -0
  169. package/utilities/data-state/src/data-state.d.ts +11 -0
  170. package/utilities/index.d.ts +1 -0
  171. package/components/anchor/src/components/anchor-vertical/anchor-vertical-list.component.d.ts +0 -18
  172. package/components/table/src/components/column-filter/column-filter.component.d.ts +0 -40
  173. package/components/table/src/components/desktop-command/desktop-command.component.d.ts +0 -15
  174. package/components/table/src/components/external-filter/external-filter.component.d.ts +0 -36
  175. package/components/table/src/pipes/command-disable.pipe.d.ts +0 -9
  176. package/components/table/src/pipes/command-filter.pipe.d.ts +0 -25
  177. package/components/table/src/pipes/command-title.pipe.d.ts +0 -9
  178. package/sd-angular-core-19.0.0-beta.97.tgz +0 -0
@@ -0,0 +1,585 @@
1
+ import * as i1 from '@angular/common';
2
+ import { CommonModule } from '@angular/common';
3
+ import * as i0 from '@angular/core';
4
+ import { Injectable, input, booleanAttribute, signal, computed, inject, HostListener, ChangeDetectionStrategy, Component } from '@angular/core';
5
+ import * as i3 from '@angular/material/icon';
6
+ import { MatIconModule } from '@angular/material/icon';
7
+ import * as i4 from '@angular/material/tooltip';
8
+ import { MatTooltipModule } from '@angular/material/tooltip';
9
+ import * as i2 from '@angular/forms';
10
+ import { FormsModule } from '@angular/forms';
11
+
12
+ /** Selector mặc định coi là phải có data-autoid. */
13
+ const SD_AUTOID_DEFAULT_REQUIRE_SELECTORS = [
14
+ 'sd-input',
15
+ 'sd-input-number',
16
+ 'sd-textarea',
17
+ 'sd-select',
18
+ 'sd-autocomplete',
19
+ 'sd-checkbox',
20
+ 'sd-radio',
21
+ 'sd-switch',
22
+ 'sd-date',
23
+ 'sd-datetime',
24
+ 'sd-date-range',
25
+ 'sd-chip',
26
+ 'sd-chip-calendar',
27
+ 'sd-button',
28
+ ];
29
+
30
+ class SdAutoidScannerService {
31
+ /** Quét toàn bộ element có [data-autoid] trong root, gắn thông tin name/tag/value/xpath. */
32
+ scan(root = document.body) {
33
+ const nodes = root.querySelectorAll('[data-autoid]');
34
+ const dupCount = {};
35
+ nodes.forEach(n => {
36
+ const id = n.getAttribute('data-autoid');
37
+ if (!id)
38
+ return;
39
+ dupCount[id] = (dupCount[id] ?? 0) + 1;
40
+ });
41
+ const result = [];
42
+ let stt = 1;
43
+ nodes.forEach(node => {
44
+ const autoid = node.getAttribute('data-autoid');
45
+ if (!autoid)
46
+ return;
47
+ result.push({
48
+ stt: stt++,
49
+ name: this.resolveName(node),
50
+ autoid,
51
+ tag: this.resolveSdTag(node),
52
+ value: this.resolveValue(node),
53
+ xpath: `//*[@data-autoid="${autoid}"]`,
54
+ duplicate: dupCount[autoid] > 1,
55
+ });
56
+ });
57
+ return result;
58
+ }
59
+ /** Group element theo autoid để consumer lấy duplicate map. */
60
+ groupByAutoid(elements) {
61
+ const map = {};
62
+ for (const el of elements) {
63
+ (map[el.autoid] ??= []).push(el);
64
+ }
65
+ return map;
66
+ }
67
+ resolveName(node) {
68
+ if (node.id) {
69
+ // CSS.escape phòng id chứa ký tự đặc biệt làm vỡ selector.
70
+ const sel = typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(node.id) : node.id;
71
+ const label = document.querySelector(`label[for="${sel}"]`);
72
+ const text = label?.textContent?.trim();
73
+ if (text)
74
+ return text;
75
+ }
76
+ return node.getAttribute('aria-label')?.trim()
77
+ ?? node.getAttribute('placeholder')?.trim()
78
+ ?? node.getAttribute('title')?.trim()
79
+ ?? '';
80
+ }
81
+ resolveSdTag(node) {
82
+ let cur = node;
83
+ while (cur) {
84
+ const tag = cur.tagName.toLowerCase();
85
+ if (tag.startsWith('sd-'))
86
+ return tag;
87
+ cur = cur.parentElement;
88
+ }
89
+ return node.tagName.toLowerCase();
90
+ }
91
+ resolveValue(node) {
92
+ const input = node;
93
+ if (typeof input.value === 'string' && input.value)
94
+ return input.value;
95
+ return (node.textContent ?? '').trim().slice(0, 80);
96
+ }
97
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdAutoidScannerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
98
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdAutoidScannerService, providedIn: 'root' });
99
+ }
100
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdAutoidScannerService, decorators: [{
101
+ type: Injectable,
102
+ args: [{ providedIn: 'root' }]
103
+ }] });
104
+
105
+ class SdAutoidAuditService {
106
+ /**
107
+ * Audit kết quả scan + DOM:
108
+ * - duplicates: map autoid → count khi count > 1
109
+ * - missing: element khớp selector yêu cầu nhưng KHÔNG có [data-autoid]
110
+ */
111
+ audit(elements, options = {}) {
112
+ const requireSelectors = options.requireSelectors ?? SD_AUTOID_DEFAULT_REQUIRE_SELECTORS;
113
+ const root = options.root ?? document.body;
114
+ const duplicates = {};
115
+ for (const el of elements) {
116
+ if (el.duplicate) {
117
+ duplicates[el.autoid] = (duplicates[el.autoid] ?? 0) + 1;
118
+ }
119
+ }
120
+ const missing = this.findMissing(root, requireSelectors);
121
+ return {
122
+ total: elements.length,
123
+ duplicates,
124
+ duplicateCount: Object.keys(duplicates).length,
125
+ missing,
126
+ missingCount: missing.length,
127
+ };
128
+ }
129
+ findMissing(root, selectors) {
130
+ if (!selectors.length)
131
+ return [];
132
+ const joined = selectors.join(',');
133
+ const nodes = root.querySelectorAll(joined);
134
+ const missing = [];
135
+ nodes.forEach(node => {
136
+ // Coi là missing khi element + mọi descendant đều không có data-autoid.
137
+ // Lý do: sd-button render thành <button data-autoid="..."> bên trong host
138
+ // <sd-button>. Nếu chỉ check chính host sẽ false-positive cho mọi sd-button.
139
+ if (node.hasAttribute('data-autoid'))
140
+ return;
141
+ if (node.querySelector('[data-autoid]'))
142
+ return;
143
+ missing.push({
144
+ tag: node.tagName.toLowerCase(),
145
+ selector: this.buildSelectorPath(node),
146
+ outerHtmlPreview: this.shortHtml(node),
147
+ nameHint: this.resolveNameHint(node),
148
+ });
149
+ });
150
+ return missing;
151
+ }
152
+ buildSelectorPath(node) {
153
+ const parts = [];
154
+ let cur = node;
155
+ let depth = 0;
156
+ while (cur && depth < 4) {
157
+ let part = cur.tagName.toLowerCase();
158
+ if (cur.id) {
159
+ part += `#${cur.id}`;
160
+ parts.unshift(part);
161
+ break;
162
+ }
163
+ const cls = (cur.getAttribute('class') ?? '').trim().split(/\s+/).filter(Boolean).slice(0, 2);
164
+ if (cls.length)
165
+ part += `.${cls.join('.')}`;
166
+ parts.unshift(part);
167
+ cur = cur.parentElement;
168
+ depth++;
169
+ }
170
+ return parts.join(' > ');
171
+ }
172
+ shortHtml(node) {
173
+ const html = node.outerHTML.replace(/\s+/g, ' ');
174
+ return html.length > 160 ? html.slice(0, 157) + '...' : html;
175
+ }
176
+ resolveNameHint(node) {
177
+ return node.getAttribute('label')
178
+ ?? node.getAttribute('placeholder')
179
+ ?? node.getAttribute('aria-label')
180
+ ?? node.getAttribute('title')
181
+ ?? '';
182
+ }
183
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdAutoidAuditService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
184
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdAutoidAuditService, providedIn: 'root' });
185
+ }
186
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdAutoidAuditService, decorators: [{
187
+ type: Injectable,
188
+ args: [{ providedIn: 'root' }]
189
+ }] });
190
+
191
+ const ATTR = 'data-sd-autoid-highlight';
192
+ const SAVED_OUTLINE = 'data-sd-autoid-prev-outline';
193
+ const SAVED_OUTLINE_OFFSET = 'data-sd-autoid-prev-outline-offset';
194
+ const SAVED_BG = 'data-sd-autoid-prev-bg';
195
+ const SAVED_BORDER_RADIUS = 'data-sd-autoid-prev-radius';
196
+ // Token màu lấy từ @sd-angular/core theme (--sd-*). Fallback hex áp dụng khi
197
+ // consumer chưa import sd-core.scss. Dùng cùng bộ var với scss component để
198
+ // nhất quán visual giữa highlight ok / duplicate / missing.
199
+ const COLOR_OK = 'var(--sd-success, #4CAF50)';
200
+ const COLOR_OK_BG = 'var(--sd-success-light, rgba(76, 175, 80, 0.12))';
201
+ const COLOR_DUP = 'var(--sd-error, #F82C13)';
202
+ const COLOR_DUP_BG = 'var(--sd-error-light, rgba(248, 44, 19, 0.12))';
203
+ const COLOR_MISSING = 'var(--sd-warning, #FF9600)';
204
+ const COLOR_MISSING_BG = 'var(--sd-warning-light, rgba(255, 150, 0, 0.12))';
205
+ class SdAutoidHighlightService {
206
+ /** Apply outline cho mọi [data-autoid]: xanh nếu unique, đỏ nếu duplicate. Idempotent. */
207
+ apply(root = document.body) {
208
+ this.clear(root);
209
+ const nodes = root.querySelectorAll('[data-autoid]');
210
+ const count = {};
211
+ nodes.forEach(n => {
212
+ const id = n.getAttribute('data-autoid');
213
+ if (!id)
214
+ return;
215
+ count[id] = (count[id] ?? 0) + 1;
216
+ });
217
+ // Để tránh apply 2 lần lên cùng 1 target khi nhiều [data-autoid] cùng
218
+ // chia sẻ 1 sd-* host (vd 2 input lồng trong 1 sd-date-range).
219
+ const visited = new Set();
220
+ nodes.forEach(node => {
221
+ const id = node.getAttribute('data-autoid');
222
+ if (!id)
223
+ return;
224
+ // Highlight HOST sd-* (giống applyMissing) — outline ôm trọn card,
225
+ // không hiển thị lệch trên input con / label fragment. Fallback về
226
+ // chính node khi không tìm thấy sd-* ancestor (vd native button).
227
+ const target = this.#resolveTarget(node);
228
+ if (visited.has(target))
229
+ return;
230
+ visited.add(target);
231
+ const isDup = count[id] > 1;
232
+ this.#paint(target, isDup ? COLOR_DUP : COLOR_OK, isDup ? COLOR_DUP_BG : COLOR_OK_BG, 'solid', isDup ? 'duplicate' : 'ok');
233
+ });
234
+ }
235
+ /** Highlight tập element không có autoid (missing) bằng outline cam dashed. */
236
+ applyMissing(nodes) {
237
+ nodes.forEach(node => {
238
+ if (node.hasAttribute(ATTR))
239
+ return;
240
+ this.#paint(node, COLOR_MISSING, COLOR_MISSING_BG, 'dashed', 'missing');
241
+ });
242
+ }
243
+ /** Restore inline style như trước khi apply. */
244
+ clear(root = document.body) {
245
+ const nodes = root.querySelectorAll(`[${ATTR}]`);
246
+ nodes.forEach(node => {
247
+ node.style.outline = node.getAttribute(SAVED_OUTLINE) ?? '';
248
+ node.style.outlineOffset = node.getAttribute(SAVED_OUTLINE_OFFSET) ?? '';
249
+ node.style.backgroundColor = node.getAttribute(SAVED_BG) ?? '';
250
+ node.style.borderRadius = node.getAttribute(SAVED_BORDER_RADIUS) ?? '';
251
+ node.removeAttribute(SAVED_OUTLINE);
252
+ node.removeAttribute(SAVED_OUTLINE_OFFSET);
253
+ node.removeAttribute(SAVED_BG);
254
+ node.removeAttribute(SAVED_BORDER_RADIUS);
255
+ node.removeAttribute(ATTR);
256
+ });
257
+ }
258
+ // ==========================================
259
+ // HELPERS
260
+ // ==========================================
261
+ #resolveTarget(node) {
262
+ let cur = node;
263
+ while (cur) {
264
+ if (cur.tagName.toLowerCase().startsWith('sd-'))
265
+ return cur;
266
+ cur = cur.parentElement;
267
+ }
268
+ return node;
269
+ }
270
+ #paint(node, color, bg, style, marker) {
271
+ // Backup full style cần restore để clear() đưa về nguyên trạng.
272
+ node.setAttribute(SAVED_OUTLINE, node.style.outline ?? '');
273
+ node.setAttribute(SAVED_OUTLINE_OFFSET, node.style.outlineOffset ?? '');
274
+ node.setAttribute(SAVED_BG, node.style.backgroundColor ?? '');
275
+ node.setAttribute(SAVED_BORDER_RADIUS, node.style.borderRadius ?? '');
276
+ node.style.outline = `2px ${style} ${color}`;
277
+ // outline-offset 2px tránh đè lên border của host (mat-form-field, button)
278
+ // → highlight nằm gọn ngoài card thay vì cắt vào nội dung.
279
+ node.style.outlineOffset = '2px';
280
+ node.style.backgroundColor = bg;
281
+ // Bo nhẹ để outline trông sạch trên host bo góc sẵn.
282
+ if (!node.style.borderRadius) {
283
+ node.style.borderRadius = '6px';
284
+ }
285
+ node.setAttribute(ATTR, marker);
286
+ }
287
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdAutoidHighlightService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
288
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdAutoidHighlightService, providedIn: 'root' });
289
+ }
290
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdAutoidHighlightService, decorators: [{
291
+ type: Injectable,
292
+ args: [{ providedIn: 'root' }]
293
+ }] });
294
+
295
+ class SdAutoidExportService {
296
+ toJson(elements) {
297
+ return JSON.stringify(elements, null, 2);
298
+ }
299
+ toCsv(elements) {
300
+ const header = 'STT,name,autoid,tag,value,xpath';
301
+ const rows = elements.map(el => [el.stt, this.q(el.name), this.q(el.autoid), this.q(el.tag), this.q(el.value), this.q(el.xpath)].join(','));
302
+ return [header, ...rows].join('\n') + '\n';
303
+ }
304
+ /** POM TypeScript skeleton — copy ngay vào file Playwright/Cypress. */
305
+ toMarkdownPom(elements, meta) {
306
+ const className = this.toPascal(meta.pageTitle || 'AutoGenerated') + 'Page';
307
+ const lines = [];
308
+ lines.push(`# ${className}`);
309
+ lines.push('');
310
+ lines.push(`- Page: \`${meta.pageUrl}\``);
311
+ lines.push(`- Generated: \`${meta.timestamp}\``);
312
+ lines.push('');
313
+ lines.push('```ts');
314
+ lines.push(`export class ${className} {`);
315
+ for (const el of elements) {
316
+ const propName = this.toCamel(el.autoid);
317
+ const nameHint = el.name ? ` ${el.name} —` : '';
318
+ lines.push(` /** ${el.tag}${nameHint} */`);
319
+ lines.push(` readonly ${propName} = '${el.xpath}';`);
320
+ }
321
+ lines.push('}');
322
+ lines.push('```');
323
+ lines.push('');
324
+ return lines.join('\n');
325
+ }
326
+ /** Markdown reference table, group theo tag sd-*. */
327
+ toMarkdownTable(elements, meta) {
328
+ const lines = [];
329
+ lines.push('# AutoId Reference');
330
+ lines.push('');
331
+ lines.push(`- Page: \`${meta.pageUrl}\``);
332
+ lines.push(`- Generated: \`${meta.timestamp}\``);
333
+ lines.push('');
334
+ const groups = new Map();
335
+ for (const el of elements) {
336
+ const key = el.tag || '(no-tag)';
337
+ if (!groups.has(key))
338
+ groups.set(key, []);
339
+ groups.get(key).push(el);
340
+ }
341
+ const sortedTags = [...groups.keys()].sort();
342
+ for (const tag of sortedTags) {
343
+ lines.push(`## ${tag}`);
344
+ lines.push('');
345
+ lines.push('| name | autoid | xpath | duplicate |');
346
+ lines.push('|------|--------|-------|-----------|');
347
+ for (const el of groups.get(tag)) {
348
+ lines.push(`| ${this.mdCell(el.name)} | \`${el.autoid}\` | \`${el.xpath}\` | ${el.duplicate ? '⚠️' : ''} |`);
349
+ }
350
+ lines.push('');
351
+ }
352
+ return lines.join('\n');
353
+ }
354
+ download(content, filename, mime) {
355
+ const blob = new Blob([content], { type: mime });
356
+ const url = URL.createObjectURL(blob);
357
+ const link = document.createElement('a');
358
+ link.href = url;
359
+ link.download = filename;
360
+ document.body.appendChild(link);
361
+ link.click();
362
+ document.body.removeChild(link);
363
+ URL.revokeObjectURL(url);
364
+ }
365
+ async copyToClipboard(content) {
366
+ if (navigator.clipboard?.writeText) {
367
+ await navigator.clipboard.writeText(content);
368
+ return;
369
+ }
370
+ // Fallback cho browser cũ / context không có clipboard API.
371
+ const ta = document.createElement('textarea');
372
+ ta.value = content;
373
+ ta.style.position = 'fixed';
374
+ ta.style.opacity = '0';
375
+ document.body.appendChild(ta);
376
+ ta.select();
377
+ document.execCommand('copy');
378
+ document.body.removeChild(ta);
379
+ }
380
+ q(v) {
381
+ const s = String(v ?? '').replace(/"/g, '""');
382
+ return `"${s}"`;
383
+ }
384
+ mdCell(s) {
385
+ return (s || '').replace(/\|/g, '\\|').replace(/\n/g, ' ');
386
+ }
387
+ toCamel(autoid) {
388
+ const parts = autoid.split(/[-_]/).filter(Boolean);
389
+ return parts.map((p, i) => (i === 0 ? p : p[0].toUpperCase() + p.slice(1))).join('');
390
+ }
391
+ toPascal(title) {
392
+ return title
393
+ .split(/[^a-zA-Z0-9]+/)
394
+ .filter(Boolean)
395
+ .map(p => p[0].toUpperCase() + p.slice(1))
396
+ .join('');
397
+ }
398
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdAutoidExportService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
399
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdAutoidExportService, providedIn: 'root' });
400
+ }
401
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdAutoidExportService, decorators: [{
402
+ type: Injectable,
403
+ args: [{ providedIn: 'root' }]
404
+ }] });
405
+
406
+ class SdAutoidInspector {
407
+ // ==========================================
408
+ // INPUTS
409
+ // ==========================================
410
+ enabled = input(true, { transform: booleanAttribute });
411
+ config = input(undefined);
412
+ // ==========================================
413
+ // STATE
414
+ // ==========================================
415
+ open = signal(false);
416
+ segment = signal('audit');
417
+ filter = signal('');
418
+ elements = signal([]);
419
+ audit = signal(null);
420
+ copyOk = signal(false);
421
+ // Default true để mở panel lần đầu là có highlight ngay. Đóng panel KHÔNG
422
+ // tự tắt — user toggle qua nút trong tab Audit khi muốn nhìn DOM gốc.
423
+ highlightOn = signal(true);
424
+ // Ẩn tạm FAB — chỉ in-memory, reload trang sẽ hiện lại (theo thiết kế).
425
+ dismissed = signal(false);
426
+ filteredElements = computed(() => {
427
+ const q = this.filter().trim().toLowerCase();
428
+ if (!q)
429
+ return this.elements();
430
+ return this.elements().filter(el => el.autoid.toLowerCase().includes(q) ||
431
+ el.name.toLowerCase().includes(q) ||
432
+ el.tag.toLowerCase().includes(q));
433
+ });
434
+ // ==========================================
435
+ // SERVICES
436
+ // ==========================================
437
+ #scanner = inject(SdAutoidScannerService);
438
+ #auditSvc = inject(SdAutoidAuditService);
439
+ #highlight = inject(SdAutoidHighlightService);
440
+ #export = inject(SdAutoidExportService);
441
+ // ==========================================
442
+ // LIFECYCLE
443
+ // ==========================================
444
+ ngOnDestroy() {
445
+ this.#highlight.clear(this.#root());
446
+ }
447
+ // ==========================================
448
+ // ACTIONS
449
+ // ==========================================
450
+ togglePanel = () => {
451
+ if (this.open()) {
452
+ this.closePanel();
453
+ }
454
+ else {
455
+ this.openPanel();
456
+ }
457
+ };
458
+ openPanel = () => {
459
+ if (!this.enabled())
460
+ return;
461
+ this.refresh();
462
+ this.open.set(true);
463
+ };
464
+ closePanel = () => {
465
+ this.open.set(false);
466
+ // Highlight được giữ lại để user vẫn thấy outline khi panel ẩn —
467
+ // toggleHighlight trong tab Audit là cách duy nhất để clear.
468
+ };
469
+ refresh = () => {
470
+ const root = this.#root();
471
+ const elements = this.#scanner.scan(root);
472
+ const result = this.#auditSvc.audit(elements, {
473
+ root,
474
+ requireSelectors: this.#requireSelectors(),
475
+ });
476
+ this.elements.set(elements);
477
+ this.audit.set(result);
478
+ this.#applyHighlightIfOn(result);
479
+ };
480
+ dismissFab = (event) => {
481
+ event.stopPropagation();
482
+ this.dismissed.set(true);
483
+ if (this.open())
484
+ this.closePanel();
485
+ };
486
+ toggleHighlight = () => {
487
+ const next = !this.highlightOn();
488
+ this.highlightOn.set(next);
489
+ if (next) {
490
+ this.#applyHighlightIfOn(this.audit());
491
+ }
492
+ else {
493
+ this.#highlight.clear(this.#root());
494
+ }
495
+ };
496
+ setSegment = (seg) => this.segment.set(seg);
497
+ copyJson = async () => {
498
+ await this.#export.copyToClipboard(this.#export.toJson(this.elements()));
499
+ this.#flashCopyOk();
500
+ };
501
+ copyXpath = async (autoid) => {
502
+ await this.#export.copyToClipboard(`//*[@data-autoid="${autoid}"]`);
503
+ this.#flashCopyOk();
504
+ };
505
+ exportCsv = () => {
506
+ this.#export.download(this.#export.toCsv(this.elements()), this.#filename('csv'), 'text/csv;charset=utf-8');
507
+ };
508
+ exportJson = () => {
509
+ this.#export.download(this.#export.toJson(this.elements()), this.#filename('json'), 'application/json');
510
+ };
511
+ exportMdPom = () => {
512
+ this.#export.download(this.#export.toMarkdownPom(this.elements(), this.#meta()), this.#filename('pom.md'), 'text/markdown');
513
+ };
514
+ exportMdTable = () => {
515
+ this.#export.download(this.#export.toMarkdownTable(this.elements(), this.#meta()), this.#filename('reference.md'), 'text/markdown');
516
+ };
517
+ // ==========================================
518
+ // KEYBOARD: Esc đóng panel
519
+ // ==========================================
520
+ onEscape() {
521
+ if (this.open())
522
+ this.closePanel();
523
+ }
524
+ // ==========================================
525
+ // HELPERS
526
+ // ==========================================
527
+ #root() {
528
+ return this.config()?.root ?? document.body;
529
+ }
530
+ #requireSelectors() {
531
+ const extra = this.config()?.extraRequireSelectors ?? [];
532
+ return [...SD_AUTOID_DEFAULT_REQUIRE_SELECTORS, ...extra];
533
+ }
534
+ #highlightMissing(result) {
535
+ if (!result.missing.length)
536
+ return;
537
+ const root = this.#root();
538
+ const selector = this.#requireSelectors().join(',');
539
+ const all = Array.from(root.querySelectorAll(selector));
540
+ const missingNodes = all.filter(n => !n.hasAttribute('data-autoid') && !n.querySelector('[data-autoid]'));
541
+ this.#highlight.applyMissing(missingNodes);
542
+ }
543
+ #applyHighlightIfOn(result) {
544
+ if (!this.highlightOn())
545
+ return;
546
+ const root = this.#root();
547
+ this.#highlight.apply(root);
548
+ if (result)
549
+ this.#highlightMissing(result);
550
+ }
551
+ #meta() {
552
+ return {
553
+ pageUrl: window.location.pathname + window.location.search,
554
+ pageTitle: document.title || 'Page',
555
+ timestamp: new Date().toISOString(),
556
+ };
557
+ }
558
+ #filename(ext) {
559
+ const slug = (document.title || 'autoid')
560
+ .toLowerCase()
561
+ .replace(/[^a-z0-9]+/g, '-')
562
+ .replace(/^-|-$/g, '');
563
+ return `${slug || 'autoid'}-${Date.now()}.${ext}`;
564
+ }
565
+ #flashCopyOk() {
566
+ this.copyOk.set(true);
567
+ setTimeout(() => this.copyOk.set(false), 1200);
568
+ }
569
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdAutoidInspector, deps: [], target: i0.ɵɵFactoryTarget.Component });
570
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdAutoidInspector, isStandalone: true, selector: "sd-autoid-inspector", inputs: { enabled: { classPropertyName: "enabled", publicName: "enabled", isSignal: true, isRequired: false, transformFunction: null }, config: { classPropertyName: "config", publicName: "config", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:keydown.escape": "onEscape()" } }, ngImport: i0, template: "@if (enabled() && !dismissed()) {\r\n <div class=\"sd-autoid-inspector__fab-wrap\">\r\n <button\r\n type=\"button\"\r\n class=\"sd-autoid-inspector__fab\"\r\n data-autoid=\"sd-autoid-inspector-fab\"\r\n [matTooltip]=\"open() ? '\u0110\u00F3ng AutoId Inspector' : 'M\u1EDF AutoId Inspector'\"\r\n matTooltipPosition=\"left\"\r\n (click)=\"togglePanel()\">\r\n <mat-icon>{{ open() ? 'close' : 'tag' }}</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"sd-autoid-inspector__fab-dismiss\"\r\n matTooltip=\"\u1EA8n t\u1EA1m \u2014 reload trang \u0111\u1EC3 hi\u1EC7n l\u1EA1i\"\r\n matTooltipPosition=\"left\"\r\n aria-label=\"\u1EA8n t\u1EA1m FAB\"\r\n (click)=\"dismissFab($event)\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n\r\n}\r\n\r\n@if (enabled() && open()) {\r\n <div class=\"sd-autoid-inspector__backdrop\" (click)=\"closePanel()\"></div>\r\n <section class=\"sd-autoid-inspector__panel\" role=\"dialog\" aria-label=\"AutoId Inspector\">\r\n <header class=\"sd-autoid-inspector__header\">\r\n <div class=\"sd-autoid-inspector__segments\">\r\n <button\r\n type=\"button\"\r\n [class.is-active]=\"segment() === 'audit'\"\r\n (click)=\"setSegment('audit')\">\r\n Audit\r\n @if (audit(); as a) {\r\n <span class=\"sd-autoid-inspector__badge\" [class.is-warn]=\"a.missingCount + a.duplicateCount > 0\">\r\n {{ a.missingCount + a.duplicateCount }}\r\n </span>\r\n }\r\n </button>\r\n <button\r\n type=\"button\"\r\n [class.is-active]=\"segment() === 'elements'\"\r\n (click)=\"setSegment('elements')\">\r\n Elements\r\n <span class=\"sd-autoid-inspector__badge\">{{ elements().length }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n [class.is-active]=\"segment() === 'export'\"\r\n (click)=\"setSegment('export')\">\r\n Export\r\n </button>\r\n </div>\r\n <div class=\"sd-autoid-inspector__actions\">\r\n <button\r\n type=\"button\"\r\n class=\"sd-autoid-inspector__icon-btn\"\r\n matTooltip=\"Qu\u00E9t l\u1EA1i\"\r\n (click)=\"refresh()\">\r\n <mat-icon>refresh</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"sd-autoid-inspector__close-btn\"\r\n matTooltip=\"\u0110\u00F3ng\"\r\n aria-label=\"\u0110\u00F3ng panel\"\r\n (click)=\"closePanel()\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (copyOk()) {\r\n <div class=\"sd-autoid-inspector__toast\">\u0110\u00E3 copy \u2713</div>\r\n }\r\n\r\n <div class=\"sd-autoid-inspector__body\">\r\n @switch (segment()) {\r\n @case ('audit') {\r\n <ng-container *ngTemplateOutlet=\"auditTpl\"></ng-container>\r\n }\r\n @case ('elements') {\r\n <ng-container *ngTemplateOutlet=\"elementsTpl\"></ng-container>\r\n }\r\n @case ('export') {\r\n <ng-container *ngTemplateOutlet=\"exportTpl\"></ng-container>\r\n }\r\n }\r\n </div>\r\n </section>\r\n\r\n <ng-template #auditTpl>\r\n @if (audit(); as a) {\r\n <div class=\"sd-autoid-inspector__audit-toolbar\">\r\n <button\r\n type=\"button\"\r\n class=\"sd-autoid-inspector__toggle\"\r\n [class.is-on]=\"highlightOn()\"\r\n (click)=\"toggleHighlight()\">\r\n <mat-icon>{{ highlightOn() ? 'visibility' : 'visibility_off' }}</mat-icon>\r\n <span>Highlight DOM</span>\r\n <span class=\"sd-autoid-inspector__toggle-state\">{{ highlightOn() ? 'ON' : 'OFF' }}</span>\r\n </button>\r\n </div>\r\n <div class=\"sd-autoid-inspector__summary\">\r\n <div class=\"sd-autoid-inspector__stat is-info\">\r\n <div class=\"sd-autoid-inspector__stat-value\">{{ a.total }}</div>\r\n <div class=\"sd-autoid-inspector__stat-label\">T\u1ED5ng autoid</div>\r\n </div>\r\n <div class=\"sd-autoid-inspector__stat\" [class.is-error]=\"a.duplicateCount > 0\" [class.is-success]=\"a.duplicateCount === 0\">\r\n <div class=\"sd-autoid-inspector__stat-value\">{{ a.duplicateCount }}</div>\r\n <div class=\"sd-autoid-inspector__stat-label\">Tr\u00F9ng</div>\r\n </div>\r\n <div class=\"sd-autoid-inspector__stat\" [class.is-warning]=\"a.missingCount > 0\" [class.is-success]=\"a.missingCount === 0\">\r\n <div class=\"sd-autoid-inspector__stat-value\">{{ a.missingCount }}</div>\r\n <div class=\"sd-autoid-inspector__stat-label\">Thi\u1EBFu</div>\r\n </div>\r\n </div>\r\n\r\n @if (a.duplicateCount > 0) {\r\n <h4 class=\"sd-autoid-inspector__section-title\">\r\n <mat-icon class=\"is-error\">error_outline</mat-icon>\r\n Autoid b\u1ECB tr\u00F9ng\r\n </h4>\r\n <div class=\"sd-autoid-inspector__table-wrap\">\r\n <table class=\"sd-autoid-inspector__table\">\r\n <thead>\r\n <tr><th>Autoid</th><th>S\u1ED1 l\u1EA7n</th></tr>\r\n </thead>\r\n <tbody>\r\n @for (entry of a.duplicates | keyvalue; track entry.key) {\r\n <tr class=\"is-error\">\r\n <td><code>{{ entry.key }}</code></td>\r\n <td>{{ entry.value }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n }\r\n\r\n @if (a.missingCount > 0) {\r\n <h4 class=\"sd-autoid-inspector__section-title\">\r\n <mat-icon class=\"is-warning\">warning_amber</mat-icon>\r\n Element thi\u1EBFu autoid\r\n </h4>\r\n <div class=\"sd-autoid-inspector__table-wrap\">\r\n <table class=\"sd-autoid-inspector__table\">\r\n <thead>\r\n <tr><th>Tag</th><th>Name hint</th><th>Selector</th></tr>\r\n </thead>\r\n <tbody>\r\n @for (m of a.missing; track $index) {\r\n <tr class=\"is-warning\">\r\n <td><code>{{ m.tag }}</code></td>\r\n <td>{{ m.nameHint }}</td>\r\n <td><code>{{ m.selector }}</code></td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n }\r\n\r\n @if (a.duplicateCount === 0 && a.missingCount === 0) {\r\n <div class=\"sd-autoid-inspector__success-card\">\r\n <mat-icon>check_circle</mat-icon>\r\n <div>\r\n <strong>Kh\u00F4ng c\u00F3 element thi\u1EBFu ho\u1EB7c tr\u00F9ng autoid.</strong>\r\n <small>Trang \u0111\u00E3 \u0111\u1EA1t chu\u1EA9n convention autoid.</small>\r\n </div>\r\n </div>\r\n }\r\n }\r\n </ng-template>\r\n\r\n <ng-template #elementsTpl>\r\n <div class=\"sd-autoid-inspector__filter\">\r\n <mat-icon>search</mat-icon>\r\n <input\r\n type=\"text\"\r\n placeholder=\"L\u1ECDc theo autoid / name / tag\"\r\n [ngModel]=\"filter()\"\r\n (ngModelChange)=\"filter.set($event)\"/>\r\n @if (filter()) {\r\n <button\r\n type=\"button\"\r\n class=\"sd-autoid-inspector__icon-btn\"\r\n matTooltip=\"Xo\u00E1 filter\"\r\n (click)=\"filter.set('')\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n }\r\n </div>\r\n <div class=\"sd-autoid-inspector__table-wrap\">\r\n <table class=\"sd-autoid-inspector__table\">\r\n <thead>\r\n <tr>\r\n <th class=\"is-num\">STT</th>\r\n <th>Name</th>\r\n <th>Autoid</th>\r\n <th>Tag</th>\r\n <th>Value</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (el of filteredElements(); track el.autoid + $index) {\r\n <tr [class.is-error]=\"el.duplicate\">\r\n <td class=\"is-num\">{{ el.stt }}</td>\r\n <td>{{ el.name || '\u2014' }}</td>\r\n <td>\r\n <div class=\"sd-autoid-inspector__autoid-cell\">\r\n <code>{{ el.autoid }}</code>\r\n <button\r\n type=\"button\"\r\n class=\"sd-autoid-inspector__icon-btn is-primary\"\r\n matTooltip=\"Copy xpath\"\r\n (click)=\"copyXpath(el.autoid)\">\r\n <mat-icon>content_copy</mat-icon>\r\n </button>\r\n </div>\r\n </td>\r\n <td><span class=\"sd-autoid-inspector__tag-chip\">{{ el.tag }}</span></td>\r\n <td class=\"sd-autoid-inspector__value\" [matTooltip]=\"el.value || ''\">\r\n {{ el.value || '\u2014' }}\r\n </td>\r\n </tr>\r\n } @empty {\r\n <tr><td colspan=\"5\" class=\"sd-autoid-inspector__empty\">Kh\u00F4ng c\u00F3 k\u1EBFt qu\u1EA3.</td></tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template #exportTpl>\r\n <div class=\"sd-autoid-inspector__export-grid\">\r\n <button type=\"button\" matTooltip=\"Copy JSON v\u00E0o clipboard\" (click)=\"copyJson()\">\r\n <mat-icon>content_copy</mat-icon>\r\n <span>Copy JSON</span>\r\n <small>clipboard</small>\r\n </button>\r\n <button type=\"button\" matTooltip=\"T\u1EA3i file JSON\" (click)=\"exportJson()\">\r\n <mat-icon>download</mat-icon>\r\n <span>JSON</span>\r\n <small>.json</small>\r\n </button>\r\n <button type=\"button\" matTooltip=\"T\u1EA3i file CSV\" (click)=\"exportCsv()\">\r\n <mat-icon>table_view</mat-icon>\r\n <span>CSV</span>\r\n <small>.csv</small>\r\n </button>\r\n <button type=\"button\" matTooltip=\"T\u1EA3i MD POM skeleton \u2014 cho AI vi\u1EBFt auto-test\" (click)=\"exportMdPom()\">\r\n <mat-icon>code</mat-icon>\r\n <span>MD POM</span>\r\n <small>AI test</small>\r\n </button>\r\n <button type=\"button\" matTooltip=\"T\u1EA3i MD reference table \u2014 group theo tag sd-*\" (click)=\"exportMdTable()\">\r\n <mat-icon>view_list</mat-icon>\r\n <span>MD Table</span>\r\n <small>reference</small>\r\n </button>\r\n </div>\r\n </ng-template>\r\n}\r\n", styles: [":host{position:fixed;inset:auto 0 0 auto;z-index:100000;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;font-size:13px;color:var(--sd-black500, #212121)}.sd-autoid-inspector__fab-wrap{position:fixed;right:16px;bottom:16px;z-index:100000}.sd-autoid-inspector__fab-wrap:hover .sd-autoid-inspector__fab-dismiss{opacity:1;transform:scale(1);pointer-events:auto}.sd-autoid-inspector__fab{width:36px;height:36px;border-radius:50%;border:none;background:var(--sd-primary, #2A66F4);color:#fff;cursor:pointer;box-shadow:0 3px 10px #00000038;display:inline-flex;align-items:center;justify-content:center;transition:transform .15s ease,background .15s ease,box-shadow .15s ease}.sd-autoid-inspector__fab:hover{background:var(--sd-primary-dark, #1C4AD9);transform:scale(1.08);box-shadow:0 5px 14px #00000047}.sd-autoid-inspector__fab mat-icon{font-size:18px;width:18px;height:18px}.sd-autoid-inspector__fab-dismiss{position:absolute;top:-6px;right:-6px;width:18px;height:18px;border-radius:50%;border:1px solid var(--sd-black200, #E6E6E6);background:#fff;color:var(--sd-black400, #757575);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0;opacity:0;transform:scale(.7);pointer-events:none;transition:opacity .12s ease,transform .12s ease,color .12s ease,background .12s ease;box-shadow:0 1px 4px #00000026}.sd-autoid-inspector__fab-dismiss:hover{background:var(--sd-error-light, #FED5D0);color:var(--sd-error-dark, #BA200E);border-color:var(--sd-error, #F82C13)}.sd-autoid-inspector__fab-dismiss mat-icon{font-size:12px;width:12px;height:12px;line-height:12px}.sd-autoid-inspector__backdrop{position:fixed;inset:0;background:#00000052;z-index:100001;backdrop-filter:blur(2px)}.sd-autoid-inspector__panel{position:fixed;right:20px;bottom:80px;width:min(860px,100vw - 40px);max-height:calc(100vh - 110px);background:#fff;border-radius:12px;box-shadow:0 16px 48px #00000038;display:flex;flex-direction:column;z-index:100002;overflow:hidden;border:1px solid var(--sd-black200, #E6E6E6)}.sd-autoid-inspector__header{display:flex;align-items:center;gap:8px;padding:8px 10px;border-bottom:1px solid var(--sd-black200, #E6E6E6);background:var(--sd-black100, #F2F2F2);flex-shrink:0}.sd-autoid-inspector__segments{display:inline-flex;border:1px solid var(--sd-black200, #E6E6E6);border-radius:8px;background:#fff;overflow:hidden}.sd-autoid-inspector__segments button{border:none;background:transparent;padding:5px 12px;cursor:pointer;font-size:12px;font-weight:500;color:var(--sd-black400, #757575);display:inline-flex;align-items:center;gap:6px;border-right:1px solid var(--sd-black200, #E6E6E6);transition:background .12s ease,color .12s ease}.sd-autoid-inspector__segments button:last-child{border-right:none}.sd-autoid-inspector__segments button:hover:not(.is-active){background:var(--sd-black100, #F2F2F2);color:var(--sd-black500, #212121)}.sd-autoid-inspector__segments button.is-active{background:var(--sd-primary, #2A66F4);color:#fff}.sd-autoid-inspector__badge{background:var(--sd-black200, #E6E6E6);color:var(--sd-black500, #212121);border-radius:10px;padding:0 7px;font-size:11px;font-weight:600;line-height:16px}.is-active .sd-autoid-inspector__badge{background:#ffffff40;color:#fff}.sd-autoid-inspector__badge.is-warn{background:var(--sd-error, #F82C13);color:#fff}.sd-autoid-inspector__actions{display:inline-flex;gap:4px;margin-left:auto}.sd-autoid-inspector__icon-btn{border:none;background:transparent;cursor:pointer;padding:4px;border-radius:6px;color:var(--sd-black400, #757575);display:inline-flex;align-items:center;justify-content:center;transition:background .12s ease,color .12s ease}.sd-autoid-inspector__icon-btn:hover{background:var(--sd-black100, #F2F2F2);color:var(--sd-black500, #212121)}.sd-autoid-inspector__icon-btn.is-primary{color:var(--sd-primary, #2A66F4)}.sd-autoid-inspector__icon-btn.is-primary:hover{background:var(--sd-primary-light, #EAF1FF);color:var(--sd-primary-dark, #1C4AD9)}.sd-autoid-inspector__icon-btn mat-icon{font-size:18px;width:18px;height:18px}.sd-autoid-inspector__close-btn{border:none;cursor:pointer;padding:4px;border-radius:50%;width:28px;height:28px;background:transparent;color:var(--sd-black400, #757575);display:inline-flex;align-items:center;justify-content:center;transition:background .12s ease,color .12s ease}.sd-autoid-inspector__close-btn:hover{background:var(--sd-error-light, #FED5D0);color:var(--sd-error-dark, #BA200E)}.sd-autoid-inspector__close-btn mat-icon{font-size:18px;width:18px;height:18px}.sd-autoid-inspector__body{padding:14px;overflow:auto;flex:1;background:#fff}.sd-autoid-inspector__toast{position:absolute;top:56px;right:14px;background:var(--sd-success, #4CAF50);color:#fff;padding:6px 12px;border-radius:6px;font-size:12px;z-index:2;box-shadow:0 4px 12px #0000002e}.sd-autoid-inspector__audit-toolbar{display:flex;justify-content:flex-end;margin-bottom:12px}.sd-autoid-inspector__toggle{display:inline-flex;align-items:center;gap:8px;border:1px solid var(--sd-black200, #E6E6E6);background:#fff;border-radius:999px;padding:5px 12px 5px 10px;cursor:pointer;font-size:12px;font-weight:500;color:var(--sd-black400, #757575);transition:all .15s ease}.sd-autoid-inspector__toggle:hover{border-color:var(--sd-primary, #2A66F4);color:var(--sd-primary, #2A66F4)}.sd-autoid-inspector__toggle.is-on{border-color:var(--sd-primary, #2A66F4);color:var(--sd-primary, #2A66F4);background:var(--sd-primary-light, #EAF1FF)}.sd-autoid-inspector__toggle mat-icon{font-size:16px;width:16px;height:16px}.sd-autoid-inspector__toggle-state{font-size:10px;font-weight:700;padding:1px 7px;border-radius:999px;background:var(--sd-black200, #E6E6E6);color:var(--sd-black400, #757575)}.is-on .sd-autoid-inspector__toggle-state{background:var(--sd-primary, #2A66F4);color:#fff}.sd-autoid-inspector__summary{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:16px}.sd-autoid-inspector__stat{background:var(--sd-black100, #F2F2F2);border-radius:10px;padding:12px;text-align:center;border:1px solid transparent}.sd-autoid-inspector__stat.is-info{background:var(--sd-info-light, #E7E9FF);border-color:var(--sd-info, #2962FF)}.sd-autoid-inspector__stat.is-info .sd-autoid-inspector__stat-value{color:var(--sd-info, #2962FF)}.sd-autoid-inspector__stat.is-success{background:var(--sd-success-light, #DBEFDC);border-color:var(--sd-success, #4CAF50)}.sd-autoid-inspector__stat.is-success .sd-autoid-inspector__stat-value{color:var(--sd-success-dark, #39833C)}.sd-autoid-inspector__stat.is-warning{background:var(--sd-warning-light, #FFEACC);border-color:var(--sd-warning, #FF9600)}.sd-autoid-inspector__stat.is-warning .sd-autoid-inspector__stat-value{color:var(--sd-warning-dark, #BF7000)}.sd-autoid-inspector__stat.is-error{background:var(--sd-error-light, #FED5D0);border-color:var(--sd-error, #F82C13)}.sd-autoid-inspector__stat.is-error .sd-autoid-inspector__stat-value{color:var(--sd-error-dark, #BA200E)}.sd-autoid-inspector__stat-value{font-size:22px;font-weight:700;line-height:1;margin-bottom:4px}.sd-autoid-inspector__stat-label{font-size:11px;color:var(--sd-black400, #757575);text-transform:uppercase;letter-spacing:.4px;font-weight:600}.sd-autoid-inspector__section-title{display:flex;align-items:center;gap:6px;margin:14px 0 8px;font-size:13px;font-weight:600;color:var(--sd-black500, #212121)}.sd-autoid-inspector__section-title mat-icon{font-size:18px;width:18px;height:18px}.sd-autoid-inspector__section-title mat-icon.is-error{color:var(--sd-error, #F82C13)}.sd-autoid-inspector__section-title mat-icon.is-warning{color:var(--sd-warning, #FF9600)}.sd-autoid-inspector__success-card{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--sd-success-light, #DBEFDC);border:1px solid var(--sd-success, #4CAF50);border-radius:10px;color:var(--sd-success-dark, #39833C)}.sd-autoid-inspector__success-card mat-icon{font-size:28px;width:28px;height:28px;color:var(--sd-success, #4CAF50)}.sd-autoid-inspector__success-card strong{display:block;font-size:13px;margin-bottom:2px}.sd-autoid-inspector__success-card small{font-size:11px;opacity:.85}.sd-autoid-inspector__table-wrap{border:1px solid var(--sd-black200, #E6E6E6);border-radius:8px;overflow:auto;max-height:60vh;background:#fff}.sd-autoid-inspector__table{width:100%;border-collapse:separate;border-spacing:0;font-size:12px}.sd-autoid-inspector__table th,.sd-autoid-inspector__table td{padding:8px 10px;text-align:left;vertical-align:middle;border-bottom:1px solid var(--sd-black200, #E6E6E6)}.sd-autoid-inspector__table thead th{position:sticky;top:0;z-index:1;background:var(--sd-black100, #F2F2F2);font-weight:600;color:var(--sd-black500, #212121);text-transform:none;font-size:11px;letter-spacing:.3px;border-bottom:none;box-shadow:inset 0 -1px 0 var(--sd-black200, #E6E6E6),0 2px 4px #0000000a;white-space:nowrap}.sd-autoid-inspector__table thead th.is-num{text-align:right;width:50px}.sd-autoid-inspector__table tbody tr{transition:background .1s ease}.sd-autoid-inspector__table tbody tr:hover{background:var(--sd-primary-light, #EAF1FF)}.sd-autoid-inspector__table tbody tr:last-child td{border-bottom:none}.sd-autoid-inspector__table tbody tr.is-error{background:var(--sd-error-light, #FED5D0)}.sd-autoid-inspector__table tbody tr.is-error:hover{background:var(--sd-error-light, #FED5D0);filter:brightness(.98)}.sd-autoid-inspector__table tbody tr.is-warning{background:var(--sd-warning-light, #FFEACC)}.sd-autoid-inspector__table tbody tr.is-warning:hover{background:var(--sd-warning-light, #FFEACC);filter:brightness(.98)}.sd-autoid-inspector__table td.is-num{text-align:right;color:var(--sd-black400, #757575);font-variant-numeric:tabular-nums}.sd-autoid-inspector__table code{font-family:JetBrains Mono,Fira Code,Consolas,monospace;font-size:11px;background:var(--sd-black100, #F2F2F2);padding:2px 6px;border-radius:4px;color:var(--sd-primary-dark, #1C4AD9);border:1px solid var(--sd-black200, #E6E6E6)}.sd-autoid-inspector__autoid-cell{display:inline-flex;align-items:center;gap:6px}.sd-autoid-inspector__tag-chip{display:inline-block;font-family:JetBrains Mono,Fira Code,Consolas,monospace;font-size:11px;background:var(--sd-info-light, #E7E9FF);color:var(--sd-info, #2962FF);border:1px solid var(--sd-info, #2962FF);padding:1px 8px;border-radius:999px;font-weight:500}.sd-autoid-inspector__value{max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--sd-black400, #757575);font-style:italic}.sd-autoid-inspector__empty{text-align:center;color:var(--sd-black400, #757575);padding:24px;font-style:italic}.sd-autoid-inspector__filter{display:flex;align-items:center;gap:6px;background:var(--sd-black100, #F2F2F2);border:1px solid var(--sd-black200, #E6E6E6);border-radius:8px;padding:6px 10px;margin-bottom:12px;transition:border-color .15s ease}.sd-autoid-inspector__filter:focus-within{border-color:var(--sd-primary, #2A66F4);background:#fff}.sd-autoid-inspector__filter input{flex:1;border:none;background:transparent;outline:none;font-size:13px;color:var(--sd-black500, #212121)}.sd-autoid-inspector__filter input::placeholder{color:var(--sd-black300, #BFBFBF)}.sd-autoid-inspector__filter>mat-icon{font-size:18px;width:18px;height:18px;color:var(--sd-black400, #757575)}.sd-autoid-inspector__export-grid{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:8px}.sd-autoid-inspector__export-grid button{border:1px solid var(--sd-black200, #E6E6E6);background:#fff;border-radius:10px;padding:12px 10px;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;min-width:0;transition:border-color .15s ease,background .15s ease,transform .12s ease;text-align:center}.sd-autoid-inspector__export-grid button:hover{border-color:var(--sd-primary, #2A66F4);background:var(--sd-primary-light, #EAF1FF);transform:translateY(-1px)}.sd-autoid-inspector__export-grid button span{font-size:12px;font-weight:600;color:var(--sd-black500, #212121);line-height:1.25}.sd-autoid-inspector__export-grid button small{font-size:10px;color:var(--sd-black400, #757575);line-height:1.2}.sd-autoid-inspector__export-grid button mat-icon{font-size:22px;width:22px;height:22px;color:var(--sd-primary, #2A66F4)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i1.KeyValuePipe, name: "keyvalue" }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "directive", type: i4.MatTooltip, selector: "[matTooltip]", inputs: ["matTooltipPosition", "matTooltipPositionAtOrigin", "matTooltipDisabled", "matTooltipShowDelay", "matTooltipHideDelay", "matTooltipTouchGestures", "matTooltip", "matTooltipClass"], exportAs: ["matTooltip"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
571
+ }
572
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdAutoidInspector, decorators: [{
573
+ type: Component,
574
+ args: [{ selector: 'sd-autoid-inspector', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, FormsModule, MatIconModule, MatTooltipModule], template: "@if (enabled() && !dismissed()) {\r\n <div class=\"sd-autoid-inspector__fab-wrap\">\r\n <button\r\n type=\"button\"\r\n class=\"sd-autoid-inspector__fab\"\r\n data-autoid=\"sd-autoid-inspector-fab\"\r\n [matTooltip]=\"open() ? '\u0110\u00F3ng AutoId Inspector' : 'M\u1EDF AutoId Inspector'\"\r\n matTooltipPosition=\"left\"\r\n (click)=\"togglePanel()\">\r\n <mat-icon>{{ open() ? 'close' : 'tag' }}</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"sd-autoid-inspector__fab-dismiss\"\r\n matTooltip=\"\u1EA8n t\u1EA1m \u2014 reload trang \u0111\u1EC3 hi\u1EC7n l\u1EA1i\"\r\n matTooltipPosition=\"left\"\r\n aria-label=\"\u1EA8n t\u1EA1m FAB\"\r\n (click)=\"dismissFab($event)\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n\r\n}\r\n\r\n@if (enabled() && open()) {\r\n <div class=\"sd-autoid-inspector__backdrop\" (click)=\"closePanel()\"></div>\r\n <section class=\"sd-autoid-inspector__panel\" role=\"dialog\" aria-label=\"AutoId Inspector\">\r\n <header class=\"sd-autoid-inspector__header\">\r\n <div class=\"sd-autoid-inspector__segments\">\r\n <button\r\n type=\"button\"\r\n [class.is-active]=\"segment() === 'audit'\"\r\n (click)=\"setSegment('audit')\">\r\n Audit\r\n @if (audit(); as a) {\r\n <span class=\"sd-autoid-inspector__badge\" [class.is-warn]=\"a.missingCount + a.duplicateCount > 0\">\r\n {{ a.missingCount + a.duplicateCount }}\r\n </span>\r\n }\r\n </button>\r\n <button\r\n type=\"button\"\r\n [class.is-active]=\"segment() === 'elements'\"\r\n (click)=\"setSegment('elements')\">\r\n Elements\r\n <span class=\"sd-autoid-inspector__badge\">{{ elements().length }}</span>\r\n </button>\r\n <button\r\n type=\"button\"\r\n [class.is-active]=\"segment() === 'export'\"\r\n (click)=\"setSegment('export')\">\r\n Export\r\n </button>\r\n </div>\r\n <div class=\"sd-autoid-inspector__actions\">\r\n <button\r\n type=\"button\"\r\n class=\"sd-autoid-inspector__icon-btn\"\r\n matTooltip=\"Qu\u00E9t l\u1EA1i\"\r\n (click)=\"refresh()\">\r\n <mat-icon>refresh</mat-icon>\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"sd-autoid-inspector__close-btn\"\r\n matTooltip=\"\u0110\u00F3ng\"\r\n aria-label=\"\u0110\u00F3ng panel\"\r\n (click)=\"closePanel()\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n </div>\r\n </header>\r\n\r\n @if (copyOk()) {\r\n <div class=\"sd-autoid-inspector__toast\">\u0110\u00E3 copy \u2713</div>\r\n }\r\n\r\n <div class=\"sd-autoid-inspector__body\">\r\n @switch (segment()) {\r\n @case ('audit') {\r\n <ng-container *ngTemplateOutlet=\"auditTpl\"></ng-container>\r\n }\r\n @case ('elements') {\r\n <ng-container *ngTemplateOutlet=\"elementsTpl\"></ng-container>\r\n }\r\n @case ('export') {\r\n <ng-container *ngTemplateOutlet=\"exportTpl\"></ng-container>\r\n }\r\n }\r\n </div>\r\n </section>\r\n\r\n <ng-template #auditTpl>\r\n @if (audit(); as a) {\r\n <div class=\"sd-autoid-inspector__audit-toolbar\">\r\n <button\r\n type=\"button\"\r\n class=\"sd-autoid-inspector__toggle\"\r\n [class.is-on]=\"highlightOn()\"\r\n (click)=\"toggleHighlight()\">\r\n <mat-icon>{{ highlightOn() ? 'visibility' : 'visibility_off' }}</mat-icon>\r\n <span>Highlight DOM</span>\r\n <span class=\"sd-autoid-inspector__toggle-state\">{{ highlightOn() ? 'ON' : 'OFF' }}</span>\r\n </button>\r\n </div>\r\n <div class=\"sd-autoid-inspector__summary\">\r\n <div class=\"sd-autoid-inspector__stat is-info\">\r\n <div class=\"sd-autoid-inspector__stat-value\">{{ a.total }}</div>\r\n <div class=\"sd-autoid-inspector__stat-label\">T\u1ED5ng autoid</div>\r\n </div>\r\n <div class=\"sd-autoid-inspector__stat\" [class.is-error]=\"a.duplicateCount > 0\" [class.is-success]=\"a.duplicateCount === 0\">\r\n <div class=\"sd-autoid-inspector__stat-value\">{{ a.duplicateCount }}</div>\r\n <div class=\"sd-autoid-inspector__stat-label\">Tr\u00F9ng</div>\r\n </div>\r\n <div class=\"sd-autoid-inspector__stat\" [class.is-warning]=\"a.missingCount > 0\" [class.is-success]=\"a.missingCount === 0\">\r\n <div class=\"sd-autoid-inspector__stat-value\">{{ a.missingCount }}</div>\r\n <div class=\"sd-autoid-inspector__stat-label\">Thi\u1EBFu</div>\r\n </div>\r\n </div>\r\n\r\n @if (a.duplicateCount > 0) {\r\n <h4 class=\"sd-autoid-inspector__section-title\">\r\n <mat-icon class=\"is-error\">error_outline</mat-icon>\r\n Autoid b\u1ECB tr\u00F9ng\r\n </h4>\r\n <div class=\"sd-autoid-inspector__table-wrap\">\r\n <table class=\"sd-autoid-inspector__table\">\r\n <thead>\r\n <tr><th>Autoid</th><th>S\u1ED1 l\u1EA7n</th></tr>\r\n </thead>\r\n <tbody>\r\n @for (entry of a.duplicates | keyvalue; track entry.key) {\r\n <tr class=\"is-error\">\r\n <td><code>{{ entry.key }}</code></td>\r\n <td>{{ entry.value }}</td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n }\r\n\r\n @if (a.missingCount > 0) {\r\n <h4 class=\"sd-autoid-inspector__section-title\">\r\n <mat-icon class=\"is-warning\">warning_amber</mat-icon>\r\n Element thi\u1EBFu autoid\r\n </h4>\r\n <div class=\"sd-autoid-inspector__table-wrap\">\r\n <table class=\"sd-autoid-inspector__table\">\r\n <thead>\r\n <tr><th>Tag</th><th>Name hint</th><th>Selector</th></tr>\r\n </thead>\r\n <tbody>\r\n @for (m of a.missing; track $index) {\r\n <tr class=\"is-warning\">\r\n <td><code>{{ m.tag }}</code></td>\r\n <td>{{ m.nameHint }}</td>\r\n <td><code>{{ m.selector }}</code></td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n }\r\n\r\n @if (a.duplicateCount === 0 && a.missingCount === 0) {\r\n <div class=\"sd-autoid-inspector__success-card\">\r\n <mat-icon>check_circle</mat-icon>\r\n <div>\r\n <strong>Kh\u00F4ng c\u00F3 element thi\u1EBFu ho\u1EB7c tr\u00F9ng autoid.</strong>\r\n <small>Trang \u0111\u00E3 \u0111\u1EA1t chu\u1EA9n convention autoid.</small>\r\n </div>\r\n </div>\r\n }\r\n }\r\n </ng-template>\r\n\r\n <ng-template #elementsTpl>\r\n <div class=\"sd-autoid-inspector__filter\">\r\n <mat-icon>search</mat-icon>\r\n <input\r\n type=\"text\"\r\n placeholder=\"L\u1ECDc theo autoid / name / tag\"\r\n [ngModel]=\"filter()\"\r\n (ngModelChange)=\"filter.set($event)\"/>\r\n @if (filter()) {\r\n <button\r\n type=\"button\"\r\n class=\"sd-autoid-inspector__icon-btn\"\r\n matTooltip=\"Xo\u00E1 filter\"\r\n (click)=\"filter.set('')\">\r\n <mat-icon>close</mat-icon>\r\n </button>\r\n }\r\n </div>\r\n <div class=\"sd-autoid-inspector__table-wrap\">\r\n <table class=\"sd-autoid-inspector__table\">\r\n <thead>\r\n <tr>\r\n <th class=\"is-num\">STT</th>\r\n <th>Name</th>\r\n <th>Autoid</th>\r\n <th>Tag</th>\r\n <th>Value</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (el of filteredElements(); track el.autoid + $index) {\r\n <tr [class.is-error]=\"el.duplicate\">\r\n <td class=\"is-num\">{{ el.stt }}</td>\r\n <td>{{ el.name || '\u2014' }}</td>\r\n <td>\r\n <div class=\"sd-autoid-inspector__autoid-cell\">\r\n <code>{{ el.autoid }}</code>\r\n <button\r\n type=\"button\"\r\n class=\"sd-autoid-inspector__icon-btn is-primary\"\r\n matTooltip=\"Copy xpath\"\r\n (click)=\"copyXpath(el.autoid)\">\r\n <mat-icon>content_copy</mat-icon>\r\n </button>\r\n </div>\r\n </td>\r\n <td><span class=\"sd-autoid-inspector__tag-chip\">{{ el.tag }}</span></td>\r\n <td class=\"sd-autoid-inspector__value\" [matTooltip]=\"el.value || ''\">\r\n {{ el.value || '\u2014' }}\r\n </td>\r\n </tr>\r\n } @empty {\r\n <tr><td colspan=\"5\" class=\"sd-autoid-inspector__empty\">Kh\u00F4ng c\u00F3 k\u1EBFt qu\u1EA3.</td></tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n </ng-template>\r\n\r\n <ng-template #exportTpl>\r\n <div class=\"sd-autoid-inspector__export-grid\">\r\n <button type=\"button\" matTooltip=\"Copy JSON v\u00E0o clipboard\" (click)=\"copyJson()\">\r\n <mat-icon>content_copy</mat-icon>\r\n <span>Copy JSON</span>\r\n <small>clipboard</small>\r\n </button>\r\n <button type=\"button\" matTooltip=\"T\u1EA3i file JSON\" (click)=\"exportJson()\">\r\n <mat-icon>download</mat-icon>\r\n <span>JSON</span>\r\n <small>.json</small>\r\n </button>\r\n <button type=\"button\" matTooltip=\"T\u1EA3i file CSV\" (click)=\"exportCsv()\">\r\n <mat-icon>table_view</mat-icon>\r\n <span>CSV</span>\r\n <small>.csv</small>\r\n </button>\r\n <button type=\"button\" matTooltip=\"T\u1EA3i MD POM skeleton \u2014 cho AI vi\u1EBFt auto-test\" (click)=\"exportMdPom()\">\r\n <mat-icon>code</mat-icon>\r\n <span>MD POM</span>\r\n <small>AI test</small>\r\n </button>\r\n <button type=\"button\" matTooltip=\"T\u1EA3i MD reference table \u2014 group theo tag sd-*\" (click)=\"exportMdTable()\">\r\n <mat-icon>view_list</mat-icon>\r\n <span>MD Table</span>\r\n <small>reference</small>\r\n </button>\r\n </div>\r\n </ng-template>\r\n}\r\n", styles: [":host{position:fixed;inset:auto 0 0 auto;z-index:100000;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;font-size:13px;color:var(--sd-black500, #212121)}.sd-autoid-inspector__fab-wrap{position:fixed;right:16px;bottom:16px;z-index:100000}.sd-autoid-inspector__fab-wrap:hover .sd-autoid-inspector__fab-dismiss{opacity:1;transform:scale(1);pointer-events:auto}.sd-autoid-inspector__fab{width:36px;height:36px;border-radius:50%;border:none;background:var(--sd-primary, #2A66F4);color:#fff;cursor:pointer;box-shadow:0 3px 10px #00000038;display:inline-flex;align-items:center;justify-content:center;transition:transform .15s ease,background .15s ease,box-shadow .15s ease}.sd-autoid-inspector__fab:hover{background:var(--sd-primary-dark, #1C4AD9);transform:scale(1.08);box-shadow:0 5px 14px #00000047}.sd-autoid-inspector__fab mat-icon{font-size:18px;width:18px;height:18px}.sd-autoid-inspector__fab-dismiss{position:absolute;top:-6px;right:-6px;width:18px;height:18px;border-radius:50%;border:1px solid var(--sd-black200, #E6E6E6);background:#fff;color:var(--sd-black400, #757575);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;padding:0;opacity:0;transform:scale(.7);pointer-events:none;transition:opacity .12s ease,transform .12s ease,color .12s ease,background .12s ease;box-shadow:0 1px 4px #00000026}.sd-autoid-inspector__fab-dismiss:hover{background:var(--sd-error-light, #FED5D0);color:var(--sd-error-dark, #BA200E);border-color:var(--sd-error, #F82C13)}.sd-autoid-inspector__fab-dismiss mat-icon{font-size:12px;width:12px;height:12px;line-height:12px}.sd-autoid-inspector__backdrop{position:fixed;inset:0;background:#00000052;z-index:100001;backdrop-filter:blur(2px)}.sd-autoid-inspector__panel{position:fixed;right:20px;bottom:80px;width:min(860px,100vw - 40px);max-height:calc(100vh - 110px);background:#fff;border-radius:12px;box-shadow:0 16px 48px #00000038;display:flex;flex-direction:column;z-index:100002;overflow:hidden;border:1px solid var(--sd-black200, #E6E6E6)}.sd-autoid-inspector__header{display:flex;align-items:center;gap:8px;padding:8px 10px;border-bottom:1px solid var(--sd-black200, #E6E6E6);background:var(--sd-black100, #F2F2F2);flex-shrink:0}.sd-autoid-inspector__segments{display:inline-flex;border:1px solid var(--sd-black200, #E6E6E6);border-radius:8px;background:#fff;overflow:hidden}.sd-autoid-inspector__segments button{border:none;background:transparent;padding:5px 12px;cursor:pointer;font-size:12px;font-weight:500;color:var(--sd-black400, #757575);display:inline-flex;align-items:center;gap:6px;border-right:1px solid var(--sd-black200, #E6E6E6);transition:background .12s ease,color .12s ease}.sd-autoid-inspector__segments button:last-child{border-right:none}.sd-autoid-inspector__segments button:hover:not(.is-active){background:var(--sd-black100, #F2F2F2);color:var(--sd-black500, #212121)}.sd-autoid-inspector__segments button.is-active{background:var(--sd-primary, #2A66F4);color:#fff}.sd-autoid-inspector__badge{background:var(--sd-black200, #E6E6E6);color:var(--sd-black500, #212121);border-radius:10px;padding:0 7px;font-size:11px;font-weight:600;line-height:16px}.is-active .sd-autoid-inspector__badge{background:#ffffff40;color:#fff}.sd-autoid-inspector__badge.is-warn{background:var(--sd-error, #F82C13);color:#fff}.sd-autoid-inspector__actions{display:inline-flex;gap:4px;margin-left:auto}.sd-autoid-inspector__icon-btn{border:none;background:transparent;cursor:pointer;padding:4px;border-radius:6px;color:var(--sd-black400, #757575);display:inline-flex;align-items:center;justify-content:center;transition:background .12s ease,color .12s ease}.sd-autoid-inspector__icon-btn:hover{background:var(--sd-black100, #F2F2F2);color:var(--sd-black500, #212121)}.sd-autoid-inspector__icon-btn.is-primary{color:var(--sd-primary, #2A66F4)}.sd-autoid-inspector__icon-btn.is-primary:hover{background:var(--sd-primary-light, #EAF1FF);color:var(--sd-primary-dark, #1C4AD9)}.sd-autoid-inspector__icon-btn mat-icon{font-size:18px;width:18px;height:18px}.sd-autoid-inspector__close-btn{border:none;cursor:pointer;padding:4px;border-radius:50%;width:28px;height:28px;background:transparent;color:var(--sd-black400, #757575);display:inline-flex;align-items:center;justify-content:center;transition:background .12s ease,color .12s ease}.sd-autoid-inspector__close-btn:hover{background:var(--sd-error-light, #FED5D0);color:var(--sd-error-dark, #BA200E)}.sd-autoid-inspector__close-btn mat-icon{font-size:18px;width:18px;height:18px}.sd-autoid-inspector__body{padding:14px;overflow:auto;flex:1;background:#fff}.sd-autoid-inspector__toast{position:absolute;top:56px;right:14px;background:var(--sd-success, #4CAF50);color:#fff;padding:6px 12px;border-radius:6px;font-size:12px;z-index:2;box-shadow:0 4px 12px #0000002e}.sd-autoid-inspector__audit-toolbar{display:flex;justify-content:flex-end;margin-bottom:12px}.sd-autoid-inspector__toggle{display:inline-flex;align-items:center;gap:8px;border:1px solid var(--sd-black200, #E6E6E6);background:#fff;border-radius:999px;padding:5px 12px 5px 10px;cursor:pointer;font-size:12px;font-weight:500;color:var(--sd-black400, #757575);transition:all .15s ease}.sd-autoid-inspector__toggle:hover{border-color:var(--sd-primary, #2A66F4);color:var(--sd-primary, #2A66F4)}.sd-autoid-inspector__toggle.is-on{border-color:var(--sd-primary, #2A66F4);color:var(--sd-primary, #2A66F4);background:var(--sd-primary-light, #EAF1FF)}.sd-autoid-inspector__toggle mat-icon{font-size:16px;width:16px;height:16px}.sd-autoid-inspector__toggle-state{font-size:10px;font-weight:700;padding:1px 7px;border-radius:999px;background:var(--sd-black200, #E6E6E6);color:var(--sd-black400, #757575)}.is-on .sd-autoid-inspector__toggle-state{background:var(--sd-primary, #2A66F4);color:#fff}.sd-autoid-inspector__summary{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-bottom:16px}.sd-autoid-inspector__stat{background:var(--sd-black100, #F2F2F2);border-radius:10px;padding:12px;text-align:center;border:1px solid transparent}.sd-autoid-inspector__stat.is-info{background:var(--sd-info-light, #E7E9FF);border-color:var(--sd-info, #2962FF)}.sd-autoid-inspector__stat.is-info .sd-autoid-inspector__stat-value{color:var(--sd-info, #2962FF)}.sd-autoid-inspector__stat.is-success{background:var(--sd-success-light, #DBEFDC);border-color:var(--sd-success, #4CAF50)}.sd-autoid-inspector__stat.is-success .sd-autoid-inspector__stat-value{color:var(--sd-success-dark, #39833C)}.sd-autoid-inspector__stat.is-warning{background:var(--sd-warning-light, #FFEACC);border-color:var(--sd-warning, #FF9600)}.sd-autoid-inspector__stat.is-warning .sd-autoid-inspector__stat-value{color:var(--sd-warning-dark, #BF7000)}.sd-autoid-inspector__stat.is-error{background:var(--sd-error-light, #FED5D0);border-color:var(--sd-error, #F82C13)}.sd-autoid-inspector__stat.is-error .sd-autoid-inspector__stat-value{color:var(--sd-error-dark, #BA200E)}.sd-autoid-inspector__stat-value{font-size:22px;font-weight:700;line-height:1;margin-bottom:4px}.sd-autoid-inspector__stat-label{font-size:11px;color:var(--sd-black400, #757575);text-transform:uppercase;letter-spacing:.4px;font-weight:600}.sd-autoid-inspector__section-title{display:flex;align-items:center;gap:6px;margin:14px 0 8px;font-size:13px;font-weight:600;color:var(--sd-black500, #212121)}.sd-autoid-inspector__section-title mat-icon{font-size:18px;width:18px;height:18px}.sd-autoid-inspector__section-title mat-icon.is-error{color:var(--sd-error, #F82C13)}.sd-autoid-inspector__section-title mat-icon.is-warning{color:var(--sd-warning, #FF9600)}.sd-autoid-inspector__success-card{display:flex;align-items:center;gap:12px;padding:14px 16px;background:var(--sd-success-light, #DBEFDC);border:1px solid var(--sd-success, #4CAF50);border-radius:10px;color:var(--sd-success-dark, #39833C)}.sd-autoid-inspector__success-card mat-icon{font-size:28px;width:28px;height:28px;color:var(--sd-success, #4CAF50)}.sd-autoid-inspector__success-card strong{display:block;font-size:13px;margin-bottom:2px}.sd-autoid-inspector__success-card small{font-size:11px;opacity:.85}.sd-autoid-inspector__table-wrap{border:1px solid var(--sd-black200, #E6E6E6);border-radius:8px;overflow:auto;max-height:60vh;background:#fff}.sd-autoid-inspector__table{width:100%;border-collapse:separate;border-spacing:0;font-size:12px}.sd-autoid-inspector__table th,.sd-autoid-inspector__table td{padding:8px 10px;text-align:left;vertical-align:middle;border-bottom:1px solid var(--sd-black200, #E6E6E6)}.sd-autoid-inspector__table thead th{position:sticky;top:0;z-index:1;background:var(--sd-black100, #F2F2F2);font-weight:600;color:var(--sd-black500, #212121);text-transform:none;font-size:11px;letter-spacing:.3px;border-bottom:none;box-shadow:inset 0 -1px 0 var(--sd-black200, #E6E6E6),0 2px 4px #0000000a;white-space:nowrap}.sd-autoid-inspector__table thead th.is-num{text-align:right;width:50px}.sd-autoid-inspector__table tbody tr{transition:background .1s ease}.sd-autoid-inspector__table tbody tr:hover{background:var(--sd-primary-light, #EAF1FF)}.sd-autoid-inspector__table tbody tr:last-child td{border-bottom:none}.sd-autoid-inspector__table tbody tr.is-error{background:var(--sd-error-light, #FED5D0)}.sd-autoid-inspector__table tbody tr.is-error:hover{background:var(--sd-error-light, #FED5D0);filter:brightness(.98)}.sd-autoid-inspector__table tbody tr.is-warning{background:var(--sd-warning-light, #FFEACC)}.sd-autoid-inspector__table tbody tr.is-warning:hover{background:var(--sd-warning-light, #FFEACC);filter:brightness(.98)}.sd-autoid-inspector__table td.is-num{text-align:right;color:var(--sd-black400, #757575);font-variant-numeric:tabular-nums}.sd-autoid-inspector__table code{font-family:JetBrains Mono,Fira Code,Consolas,monospace;font-size:11px;background:var(--sd-black100, #F2F2F2);padding:2px 6px;border-radius:4px;color:var(--sd-primary-dark, #1C4AD9);border:1px solid var(--sd-black200, #E6E6E6)}.sd-autoid-inspector__autoid-cell{display:inline-flex;align-items:center;gap:6px}.sd-autoid-inspector__tag-chip{display:inline-block;font-family:JetBrains Mono,Fira Code,Consolas,monospace;font-size:11px;background:var(--sd-info-light, #E7E9FF);color:var(--sd-info, #2962FF);border:1px solid var(--sd-info, #2962FF);padding:1px 8px;border-radius:999px;font-weight:500}.sd-autoid-inspector__value{max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--sd-black400, #757575);font-style:italic}.sd-autoid-inspector__empty{text-align:center;color:var(--sd-black400, #757575);padding:24px;font-style:italic}.sd-autoid-inspector__filter{display:flex;align-items:center;gap:6px;background:var(--sd-black100, #F2F2F2);border:1px solid var(--sd-black200, #E6E6E6);border-radius:8px;padding:6px 10px;margin-bottom:12px;transition:border-color .15s ease}.sd-autoid-inspector__filter:focus-within{border-color:var(--sd-primary, #2A66F4);background:#fff}.sd-autoid-inspector__filter input{flex:1;border:none;background:transparent;outline:none;font-size:13px;color:var(--sd-black500, #212121)}.sd-autoid-inspector__filter input::placeholder{color:var(--sd-black300, #BFBFBF)}.sd-autoid-inspector__filter>mat-icon{font-size:18px;width:18px;height:18px;color:var(--sd-black400, #757575)}.sd-autoid-inspector__export-grid{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:8px}.sd-autoid-inspector__export-grid button{border:1px solid var(--sd-black200, #E6E6E6);background:#fff;border-radius:10px;padding:12px 10px;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;min-width:0;transition:border-color .15s ease,background .15s ease,transform .12s ease;text-align:center}.sd-autoid-inspector__export-grid button:hover{border-color:var(--sd-primary, #2A66F4);background:var(--sd-primary-light, #EAF1FF);transform:translateY(-1px)}.sd-autoid-inspector__export-grid button span{font-size:12px;font-weight:600;color:var(--sd-black500, #212121);line-height:1.25}.sd-autoid-inspector__export-grid button small{font-size:10px;color:var(--sd-black400, #757575);line-height:1.2}.sd-autoid-inspector__export-grid button mat-icon{font-size:22px;width:22px;height:22px;color:var(--sd-primary, #2A66F4)}\n"] }]
575
+ }], propDecorators: { onEscape: [{
576
+ type: HostListener,
577
+ args: ['document:keydown.escape']
578
+ }] } });
579
+
580
+ /**
581
+ * Generated bundle index. Do not edit.
582
+ */
583
+
584
+ export { SD_AUTOID_DEFAULT_REQUIRE_SELECTORS, SdAutoidAuditService, SdAutoidExportService, SdAutoidHighlightService, SdAutoidInspector, SdAutoidScannerService };
585
+ //# sourceMappingURL=sd-angular-core-components-autoid-inspector.mjs.map