@sdcorejs/angular 19.0.7 → 19.0.9
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.
- package/README.md +8 -2
- package/assets/STYLE-GUIDE.md +13 -0
- package/components/autoid-inspector/index.d.ts +1 -7
- package/components/autoid-inspector/src/autoid-inspector.component.d.ts +22 -8
- package/components/autoid-inspector/src/models/autoid-element.model.d.ts +7 -7
- package/components/autoid-inspector/src/models/autoid-export-format.model.d.ts +17 -8
- package/components/autoid-inspector/src/models/autoid-inspector-config.model.d.ts +10 -5
- package/components/autoid-inspector/src/services/autoid-audit.service.d.ts +4 -4
- package/components/autoid-inspector/src/services/autoid-export.service.d.ts +7 -6
- package/components/autoid-inspector/src/services/autoid-scanner.service.d.ts +3 -3
- package/components/table/src/components/filter/column-filter/column-filter.component.d.ts +0 -1
- package/components/table/src/services/table-filter/table-filter.service.d.ts +1 -0
- package/components/table/src/services/table-local/table-local.util.d.ts +19 -0
- package/components/table/src/services/table-reorder/table-reorder.util.d.ts +29 -0
- package/components/table/src/services/table-selection/table-selection.util.d.ts +8 -0
- package/components/table/src/services/tree/tree.util.d.ts +10 -1
- package/components/table/src/table.component.d.ts +6 -10
- package/fesm2022/sdcorejs-angular-components-anchor.mjs +2 -3
- package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs +195 -25
- package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-badge.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-button.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-button.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-document-builder.mjs +5 -6
- package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-editor.mjs +4 -4
- package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs +4 -4
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-inform.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-inform.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-modal.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-operator.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-operator.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-org-chart.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-org-chart.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-preview.mjs +3 -3
- package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs +8 -8
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-section.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-section.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +6 -4
- package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-table.mjs +772 -810
- package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tree.mjs +2 -2
- package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-upload-file.mjs +6 -7
- package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +4 -5
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-checkbox.mjs +3 -3
- package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +4 -4
- package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-chip.mjs +4 -4
- package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs +16 -9
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date.mjs +5 -6
- package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-datetime.mjs +5 -6
- package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-inline-text.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-inline-text.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input-number.mjs +5 -6
- package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input.mjs +3 -3
- package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-radio.mjs +3 -4
- package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-select.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-switch.mjs +3 -4
- package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-textarea.mjs +5 -7
- package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-layout.mjs +2 -3
- package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-api.mjs +1 -2
- package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-confirm.mjs +2 -2
- package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
- package/forms/date-range/src/date-range.component.d.ts +2 -0
- package/forms/input-number/src/input-number.component.d.ts +0 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i1 from '@angular/common';
|
|
2
2
|
import { CommonModule } from '@angular/common';
|
|
3
3
|
import * as i0 from '@angular/core';
|
|
4
|
-
import { Injectable, input, booleanAttribute, signal, computed,
|
|
4
|
+
import { InjectionToken, Injectable, inject, input, booleanAttribute, signal, computed, HostListener, ChangeDetectionStrategy, Component } from '@angular/core';
|
|
5
5
|
import * as i3 from '@angular/material/icon';
|
|
6
6
|
import { MatIconModule } from '@angular/material/icon';
|
|
7
7
|
import * as i4 from '@angular/material/tooltip';
|
|
@@ -10,8 +10,10 @@ import * as i2 from '@angular/forms';
|
|
|
10
10
|
import { FormsModule } from '@angular/forms';
|
|
11
11
|
import { Router } from '@angular/router';
|
|
12
12
|
|
|
13
|
-
/**
|
|
14
|
-
const
|
|
13
|
+
/** Cau hinh global cho AutoId Inspector. `[config]` tren component co the override theo instance. */
|
|
14
|
+
const SD_AUTOID_INSPECTOR_CONFIGURATION = new InjectionToken('sd.autoid-inspector.configuration');
|
|
15
|
+
/** Selector mac dinh coi la phai co data-autoid. */
|
|
16
|
+
const AUTOID_DEFAULT_REQUIRE_SELECTORS = [
|
|
15
17
|
'sd-input',
|
|
16
18
|
'sd-input-number',
|
|
17
19
|
'sd-textarea',
|
|
@@ -163,10 +165,7 @@ class SdAutoidScannerService {
|
|
|
163
165
|
if (text)
|
|
164
166
|
return text;
|
|
165
167
|
}
|
|
166
|
-
return node.getAttribute('aria-label')?.trim()
|
|
167
|
-
?? node.getAttribute('placeholder')?.trim()
|
|
168
|
-
?? node.getAttribute('title')?.trim()
|
|
169
|
-
?? '';
|
|
168
|
+
return node.getAttribute('aria-label')?.trim() ?? node.getAttribute('placeholder')?.trim() ?? node.getAttribute('title')?.trim() ?? '';
|
|
170
169
|
}
|
|
171
170
|
resolveSdTag(node) {
|
|
172
171
|
let cur = node;
|
|
@@ -199,7 +198,7 @@ class SdAutoidAuditService {
|
|
|
199
198
|
* - missing: element khớp selector yêu cầu nhưng KHÔNG có [data-autoid]
|
|
200
199
|
*/
|
|
201
200
|
audit(elements, options = {}) {
|
|
202
|
-
const requireSelectors = options.requireSelectors ??
|
|
201
|
+
const requireSelectors = options.requireSelectors ?? AUTOID_DEFAULT_REQUIRE_SELECTORS;
|
|
203
202
|
const root = options.root ?? document.body;
|
|
204
203
|
const duplicates = {};
|
|
205
204
|
for (const el of elements) {
|
|
@@ -264,11 +263,7 @@ class SdAutoidAuditService {
|
|
|
264
263
|
return html.length > 160 ? html.slice(0, 157) + '...' : html;
|
|
265
264
|
}
|
|
266
265
|
resolveNameHint(node) {
|
|
267
|
-
return node.getAttribute('label')
|
|
268
|
-
?? node.getAttribute('placeholder')
|
|
269
|
-
?? node.getAttribute('aria-label')
|
|
270
|
-
?? node.getAttribute('title')
|
|
271
|
-
?? '';
|
|
266
|
+
return (node.getAttribute('label') ?? node.getAttribute('placeholder') ?? node.getAttribute('aria-label') ?? node.getAttribute('title') ?? '');
|
|
272
267
|
}
|
|
273
268
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdAutoidAuditService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
274
269
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdAutoidAuditService, providedIn: 'root' });
|
|
@@ -505,6 +500,9 @@ class SdAutoidExportService {
|
|
|
505
500
|
}
|
|
506
501
|
download(content, filename, mime) {
|
|
507
502
|
const blob = new Blob([content], { type: mime });
|
|
503
|
+
this.downloadBlob(blob, filename);
|
|
504
|
+
}
|
|
505
|
+
downloadBlob(blob, filename) {
|
|
508
506
|
const url = URL.createObjectURL(blob);
|
|
509
507
|
const link = document.createElement('a');
|
|
510
508
|
link.href = url;
|
|
@@ -575,6 +573,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
|
|
|
575
573
|
}] });
|
|
576
574
|
|
|
577
575
|
class SdAutoidInspector {
|
|
576
|
+
#injectedConfig = inject(SD_AUTOID_INSPECTOR_CONFIGURATION, { optional: true });
|
|
578
577
|
// ==========================================
|
|
579
578
|
// INPUTS
|
|
580
579
|
// ==========================================
|
|
@@ -589,18 +588,23 @@ class SdAutoidInspector {
|
|
|
589
588
|
elements = signal([]);
|
|
590
589
|
audit = signal(null);
|
|
591
590
|
copyOk = signal(false);
|
|
591
|
+
e2eExporting = signal(null);
|
|
592
|
+
e2eExportOk = signal(null);
|
|
593
|
+
e2eExportError = signal(null);
|
|
594
|
+
robotContextOpen = signal(false);
|
|
595
|
+
robotExportContext = signal({});
|
|
592
596
|
// Default true để mở panel lần đầu là có highlight ngay. Đóng panel KHÔNG
|
|
593
597
|
// tự tắt — user toggle qua nút trong tab Audit khi muốn nhìn DOM gốc.
|
|
594
598
|
highlightOn = signal(true);
|
|
595
599
|
// Ẩn tạm FAB — chỉ in-memory, reload trang sẽ hiện lại (theo thiết kế).
|
|
596
600
|
dismissed = signal(false);
|
|
601
|
+
e2eGeneratorHost = computed(() => this.#normalizeHost(this.#effectiveConfig().host));
|
|
602
|
+
hasRobotExportContext = computed(() => Object.values(this.robotExportContext()).some(value => Boolean(value?.trim())));
|
|
597
603
|
filteredElements = computed(() => {
|
|
598
604
|
const q = this.filter().trim().toLowerCase();
|
|
599
605
|
if (!q)
|
|
600
606
|
return this.elements();
|
|
601
|
-
return this.elements().filter(el => el.autoid.toLowerCase().includes(q) ||
|
|
602
|
-
el.name.toLowerCase().includes(q) ||
|
|
603
|
-
el.tag.toLowerCase().includes(q));
|
|
607
|
+
return this.elements().filter(el => el.autoid.toLowerCase().includes(q) || el.name.toLowerCase().includes(q) || el.tag.toLowerCase().includes(q));
|
|
604
608
|
});
|
|
605
609
|
/**
|
|
606
610
|
* Elements not inside any sd-table — rendered in the main "Top-level" section.
|
|
@@ -620,9 +624,7 @@ class SdAutoidInspector {
|
|
|
620
624
|
groups.get(el.tableScope).push(el);
|
|
621
625
|
}
|
|
622
626
|
// Return a sorted array for stable rendering.
|
|
623
|
-
return [...groups.entries()]
|
|
624
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
625
|
-
.map(([scope, items]) => ({ scope, items }));
|
|
627
|
+
return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([scope, items]) => ({ scope, items }));
|
|
626
628
|
});
|
|
627
629
|
// ==========================================
|
|
628
630
|
// DATA-* ATTRIBUTE COMPUTEDS (for E2E selectors)
|
|
@@ -718,6 +720,62 @@ class SdAutoidInspector {
|
|
|
718
720
|
exportMdTable = () => {
|
|
719
721
|
this.#export.download(this.#export.toMarkdownTable(this.elements(), this.#meta()), this.#filename('reference.md'), 'text/markdown');
|
|
720
722
|
};
|
|
723
|
+
openRobotExportContext = () => {
|
|
724
|
+
if (this.e2eExporting())
|
|
725
|
+
return;
|
|
726
|
+
this.e2eExportOk.set(null);
|
|
727
|
+
this.e2eExportError.set(null);
|
|
728
|
+
this.robotContextOpen.set(true);
|
|
729
|
+
};
|
|
730
|
+
closeRobotExportContext = () => {
|
|
731
|
+
if (this.e2eExporting())
|
|
732
|
+
return;
|
|
733
|
+
this.robotContextOpen.set(false);
|
|
734
|
+
};
|
|
735
|
+
updateRobotExportContext = (field, value) => {
|
|
736
|
+
this.robotExportContext.update(context => ({
|
|
737
|
+
...context,
|
|
738
|
+
[field]: value,
|
|
739
|
+
}));
|
|
740
|
+
};
|
|
741
|
+
confirmRobotExport = async () => {
|
|
742
|
+
const context = this.#cleanRobotExportContext();
|
|
743
|
+
if (!context)
|
|
744
|
+
return;
|
|
745
|
+
const exported = await this.exportE2eTest('robot', context);
|
|
746
|
+
if (exported)
|
|
747
|
+
this.robotContextOpen.set(false);
|
|
748
|
+
};
|
|
749
|
+
exportE2eTest = async (target, context) => {
|
|
750
|
+
const host = this.e2eGeneratorHost();
|
|
751
|
+
if (!host || this.e2eExporting())
|
|
752
|
+
return false;
|
|
753
|
+
this.e2eExporting.set(target);
|
|
754
|
+
this.e2eExportOk.set(null);
|
|
755
|
+
this.e2eExportError.set(null);
|
|
756
|
+
try {
|
|
757
|
+
this.refresh();
|
|
758
|
+
const response = await fetch(this.#e2eEndpoint(host, target), {
|
|
759
|
+
method: 'POST',
|
|
760
|
+
headers: { 'Content-Type': 'application/json' },
|
|
761
|
+
body: JSON.stringify(this.#testGenerationRequest(target, context)),
|
|
762
|
+
});
|
|
763
|
+
if (!response.ok) {
|
|
764
|
+
throw new Error(await this.#responseErrorMessage(response));
|
|
765
|
+
}
|
|
766
|
+
const blob = await response.blob();
|
|
767
|
+
this.#export.downloadBlob(blob, this.#zipFilename(response, target));
|
|
768
|
+
this.#flashE2eExportOk(this.#targetLabel(target));
|
|
769
|
+
return true;
|
|
770
|
+
}
|
|
771
|
+
catch (err) {
|
|
772
|
+
this.e2eExportError.set(this.#toErrorMessage(err));
|
|
773
|
+
return false;
|
|
774
|
+
}
|
|
775
|
+
finally {
|
|
776
|
+
this.e2eExporting.set(null);
|
|
777
|
+
}
|
|
778
|
+
};
|
|
721
779
|
// ==========================================
|
|
722
780
|
// KEYBOARD: Esc đóng panel
|
|
723
781
|
// ==========================================
|
|
@@ -729,11 +787,20 @@ class SdAutoidInspector {
|
|
|
729
787
|
// HELPERS
|
|
730
788
|
// ==========================================
|
|
731
789
|
#root() {
|
|
732
|
-
return this
|
|
790
|
+
return this.#effectiveConfig().root ?? document.body;
|
|
733
791
|
}
|
|
734
792
|
#requireSelectors() {
|
|
735
|
-
const extra = this
|
|
736
|
-
return [...
|
|
793
|
+
const extra = this.#effectiveConfig().extraRequireSelectors ?? [];
|
|
794
|
+
return [...new Set([...AUTOID_DEFAULT_REQUIRE_SELECTORS, ...extra])];
|
|
795
|
+
}
|
|
796
|
+
#effectiveConfig() {
|
|
797
|
+
const injected = this.#injectedConfig ?? {};
|
|
798
|
+
const local = this.config() ?? {};
|
|
799
|
+
return {
|
|
800
|
+
...injected,
|
|
801
|
+
...local,
|
|
802
|
+
extraRequireSelectors: [...(injected.extraRequireSelectors ?? []), ...(local.extraRequireSelectors ?? [])],
|
|
803
|
+
};
|
|
737
804
|
}
|
|
738
805
|
#highlightMissing(result) {
|
|
739
806
|
if (!result.missing.length)
|
|
@@ -787,16 +854,119 @@ class SdAutoidInspector {
|
|
|
787
854
|
.replace(/^-|-$/g, '');
|
|
788
855
|
return `${slug || 'autoid'}-${Date.now()}.${ext}`;
|
|
789
856
|
}
|
|
857
|
+
#normalizeHost(host) {
|
|
858
|
+
const trimmed = host?.trim();
|
|
859
|
+
if (!trimmed)
|
|
860
|
+
return undefined;
|
|
861
|
+
return trimmed.replace(/\/+$/g, '');
|
|
862
|
+
}
|
|
863
|
+
#e2eEndpoint(host, target) {
|
|
864
|
+
const endpoint = target === 'playwright' ? 'playwright' : 'robot';
|
|
865
|
+
return `${host}/e2e/test-generator/${endpoint}`;
|
|
866
|
+
}
|
|
867
|
+
#testGenerationRequest(target, context) {
|
|
868
|
+
const baseUrl = this.#baseUrl();
|
|
869
|
+
const options = {
|
|
870
|
+
language: 'vi',
|
|
871
|
+
mode: 'happy-path',
|
|
872
|
+
outputName: this.#outputBaseName(target),
|
|
873
|
+
testName: `${document.title || 'AutoId'} ${this.#targetLabel(target)}`,
|
|
874
|
+
...(baseUrl ? { baseUrl } : {}),
|
|
875
|
+
};
|
|
876
|
+
return {
|
|
877
|
+
payload: {
|
|
878
|
+
meta: this.#meta(),
|
|
879
|
+
elements: this.elements(),
|
|
880
|
+
},
|
|
881
|
+
options,
|
|
882
|
+
...(context
|
|
883
|
+
? {
|
|
884
|
+
prompt: 'Sinh Robot Framework E2E test bám theo ngữ cảnh QC, testcase, test data và requirement được cung cấp.',
|
|
885
|
+
context,
|
|
886
|
+
}
|
|
887
|
+
: {}),
|
|
888
|
+
};
|
|
889
|
+
}
|
|
890
|
+
#cleanRobotExportContext() {
|
|
891
|
+
const context = this.robotExportContext();
|
|
892
|
+
const cleaned = {};
|
|
893
|
+
for (const [key, value] of Object.entries(context)) {
|
|
894
|
+
const trimmed = value?.trim();
|
|
895
|
+
if (trimmed)
|
|
896
|
+
cleaned[key] = trimmed;
|
|
897
|
+
}
|
|
898
|
+
return Object.keys(cleaned).length ? cleaned : undefined;
|
|
899
|
+
}
|
|
900
|
+
#baseUrl() {
|
|
901
|
+
const origin = window.location.origin;
|
|
902
|
+
return /^https?:\/\//.test(origin) ? origin : undefined;
|
|
903
|
+
}
|
|
904
|
+
#outputBaseName(target) {
|
|
905
|
+
const slug = (document.title || 'autoid')
|
|
906
|
+
.toLowerCase()
|
|
907
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
908
|
+
.replace(/^-|-$/g, '');
|
|
909
|
+
return `${slug || 'autoid'}-${target === 'playwright' ? 'playwright' : 'robot-framework'}`;
|
|
910
|
+
}
|
|
911
|
+
#targetLabel(target) {
|
|
912
|
+
return target === 'playwright' ? 'Playwright' : 'Robot Framework';
|
|
913
|
+
}
|
|
914
|
+
#zipFilename(response, target) {
|
|
915
|
+
const header = response.headers.get('Content-Disposition') ?? '';
|
|
916
|
+
const filenameStar = /filename\*=UTF-8''([^;]+)/i.exec(header)?.[1];
|
|
917
|
+
if (filenameStar)
|
|
918
|
+
return this.#decodeFilename(filenameStar.replace(/^"|"$/g, ''));
|
|
919
|
+
const filename = /filename="?([^";]+)"?/i.exec(header)?.[1];
|
|
920
|
+
return filename?.trim() || this.#filename(`e2e-${target}.zip`);
|
|
921
|
+
}
|
|
922
|
+
#decodeFilename(value) {
|
|
923
|
+
try {
|
|
924
|
+
return decodeURIComponent(value);
|
|
925
|
+
}
|
|
926
|
+
catch {
|
|
927
|
+
return value;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
async #responseErrorMessage(response) {
|
|
931
|
+
const fallback = `Không sinh được E2E test (HTTP ${response.status}).`;
|
|
932
|
+
const text = await response.text().catch(() => '');
|
|
933
|
+
if (!text)
|
|
934
|
+
return fallback;
|
|
935
|
+
try {
|
|
936
|
+
const parsed = JSON.parse(text);
|
|
937
|
+
const message = Array.isArray(parsed.message) ? parsed.message.join(', ') : parsed.message;
|
|
938
|
+
if (typeof message === 'string' && message.trim())
|
|
939
|
+
return message;
|
|
940
|
+
if (typeof parsed.error === 'string' && parsed.error.trim())
|
|
941
|
+
return parsed.error;
|
|
942
|
+
}
|
|
943
|
+
catch {
|
|
944
|
+
return text.slice(0, 180);
|
|
945
|
+
}
|
|
946
|
+
return fallback;
|
|
947
|
+
}
|
|
948
|
+
#toErrorMessage(err) {
|
|
949
|
+
if (err instanceof Error && err.message)
|
|
950
|
+
return err.message;
|
|
951
|
+
return 'Không sinh được E2E test.';
|
|
952
|
+
}
|
|
790
953
|
#flashCopyOk() {
|
|
791
954
|
this.copyOk.set(true);
|
|
792
955
|
setTimeout(() => this.copyOk.set(false), 1200);
|
|
793
956
|
}
|
|
957
|
+
#flashE2eExportOk(label) {
|
|
958
|
+
this.e2eExportOk.set(label);
|
|
959
|
+
setTimeout(() => {
|
|
960
|
+
if (this.e2eExportOk() === label)
|
|
961
|
+
this.e2eExportOk.set(null);
|
|
962
|
+
}, 1800);
|
|
963
|
+
}
|
|
794
964
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdAutoidInspector, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
795
|
-
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: "@let _enabled = enabled();\n@let _open = open();\n@if (_enabled && !dismissed()) {\n <div class=\"sd-autoid-inspector__fab-wrap\">\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__fab\"\n data-autoid=\"sd-autoid-inspector-fab\"\n [attr.data-opened]=\"dataOpened()\"\n [matTooltip]=\"_open ? '\u0110\u00F3ng AutoId Inspector' : 'M\u1EDF AutoId Inspector'\"\n matTooltipPosition=\"left\"\n (click)=\"togglePanel()\">\n <mat-icon>{{ _open ? 'close' : 'tag' }}</mat-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__fab-dismiss\"\n matTooltip=\"\u1EA8n t\u1EA1m \u2014 reload trang \u0111\u1EC3 hi\u1EC7n l\u1EA1i\"\n matTooltipPosition=\"left\"\n aria-label=\"\u1EA8n t\u1EA1m FAB\"\n (click)=\"dismissFab($event)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n}\n\n@if (_enabled && _open) {\n @let _segment = segment();\n <div class=\"sd-autoid-inspector__backdrop\" (click)=\"closePanel()\"></div>\n <section class=\"sd-autoid-inspector__panel\"\n role=\"dialog\"\n aria-label=\"AutoId Inspector\"\n data-autoid=\"sd-autoid-inspector-panel\"\n [attr.data-segment]=\"dataSegment()\"\n [attr.data-highlight-on]=\"dataHighlightOn()\"\n [attr.data-element-count]=\"dataElementCount()\"\n [attr.data-missing-count]=\"dataMissingCount()\"\n [attr.data-duplicate-count]=\"dataDuplicateCount()\">\n <header class=\"sd-autoid-inspector__header\">\n <div class=\"sd-autoid-inspector__segments\">\n <button\n type=\"button\"\n [class.is-active]=\"_segment === 'audit'\"\n (click)=\"setSegment('audit')\">\n Audit\n @if (audit(); as a) {\n <span class=\"sd-autoid-inspector__badge\" [class.is-warn]=\"a.missingCount + a.duplicateCount > 0\">\n {{ a.missingCount + a.duplicateCount }}\n </span>\n }\n </button>\n <button\n type=\"button\"\n [class.is-active]=\"_segment === 'elements'\"\n (click)=\"setSegment('elements')\">\n Elements\n <span class=\"sd-autoid-inspector__badge\">{{ elements().length }}</span>\n </button>\n <button\n type=\"button\"\n [class.is-active]=\"_segment === 'export'\"\n (click)=\"setSegment('export')\">\n Export\n </button>\n </div>\n <div class=\"sd-autoid-inspector__actions\">\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__icon-btn\"\n matTooltip=\"Qu\u00E9t l\u1EA1i\"\n (click)=\"refresh()\">\n <mat-icon>refresh</mat-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__close-btn\"\n matTooltip=\"\u0110\u00F3ng\"\n aria-label=\"\u0110\u00F3ng panel\"\n (click)=\"closePanel()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </header>\n\n @if (copyOk()) {\n <div class=\"sd-autoid-inspector__toast\">\u0110\u00E3 copy \u2713</div>\n }\n\n <div class=\"sd-autoid-inspector__body\">\n @switch (_segment) {\n @case ('audit') {\n <ng-container *ngTemplateOutlet=\"auditTpl\"></ng-container>\n }\n @case ('elements') {\n <ng-container *ngTemplateOutlet=\"elementsTpl\"></ng-container>\n }\n @case ('export') {\n <ng-container *ngTemplateOutlet=\"exportTpl\"></ng-container>\n }\n }\n </div>\n </section>\n\n <ng-template #auditTpl>\n @if (audit(); as a) {\n @let _highlightOn = highlightOn();\n <div class=\"sd-autoid-inspector__audit-toolbar\">\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__toggle\"\n [class.is-on]=\"_highlightOn\"\n (click)=\"toggleHighlight()\">\n <mat-icon>{{ _highlightOn ? 'visibility' : 'visibility_off' }}</mat-icon>\n <span>Highlight DOM</span>\n <span class=\"sd-autoid-inspector__toggle-state\">{{ _highlightOn ? 'ON' : 'OFF' }}</span>\n </button>\n </div>\n <div class=\"sd-autoid-inspector__summary\">\n <div class=\"sd-autoid-inspector__stat is-info\">\n <div class=\"sd-autoid-inspector__stat-value\">{{ a.total }}</div>\n <div class=\"sd-autoid-inspector__stat-label\">T\u1ED5ng autoid</div>\n </div>\n <div class=\"sd-autoid-inspector__stat\" [class.is-error]=\"a.duplicateCount > 0\" [class.is-success]=\"a.duplicateCount === 0\">\n <div class=\"sd-autoid-inspector__stat-value\">{{ a.duplicateCount }}</div>\n <div class=\"sd-autoid-inspector__stat-label\">Tr\u00F9ng</div>\n </div>\n <div class=\"sd-autoid-inspector__stat\" [class.is-warning]=\"a.missingCount > 0\" [class.is-success]=\"a.missingCount === 0\">\n <div class=\"sd-autoid-inspector__stat-value\">{{ a.missingCount }}</div>\n <div class=\"sd-autoid-inspector__stat-label\">Thi\u1EBFu</div>\n </div>\n </div>\n\n @if (a.duplicateCount > 0) {\n <h4 class=\"sd-autoid-inspector__section-title\">\n <mat-icon class=\"is-error\">error_outline</mat-icon>\n Autoid b\u1ECB tr\u00F9ng\n </h4>\n <div class=\"sd-autoid-inspector__table-wrap\">\n <table class=\"sd-autoid-inspector__table\">\n <thead>\n <tr><th>Autoid</th><th>S\u1ED1 l\u1EA7n</th></tr>\n </thead>\n <tbody>\n @for (entry of a.duplicates | keyvalue; track entry.key) {\n <tr class=\"is-error\">\n <td><code>{{ entry.key }}</code></td>\n <td>{{ entry.value }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n @if (a.missingCount > 0) {\n <h4 class=\"sd-autoid-inspector__section-title\">\n <mat-icon class=\"is-warning\">warning_amber</mat-icon>\n Element thi\u1EBFu autoid\n </h4>\n <div class=\"sd-autoid-inspector__table-wrap\">\n <table class=\"sd-autoid-inspector__table\">\n <thead>\n <tr><th>Tag</th><th>Name hint</th><th>Selector</th></tr>\n </thead>\n <tbody>\n @for (m of a.missing; track $index) {\n <tr class=\"is-warning\">\n <td><code>{{ m.tag }}</code></td>\n <td>{{ m.nameHint }}</td>\n <td><code>{{ m.selector }}</code></td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n @if (a.duplicateCount === 0 && a.missingCount === 0) {\n <div class=\"sd-autoid-inspector__success-card\">\n <mat-icon>check_circle</mat-icon>\n <div>\n <strong>Kh\u00F4ng c\u00F3 element thi\u1EBFu ho\u1EB7c tr\u00F9ng autoid.</strong>\n <small>Trang \u0111\u00E3 \u0111\u1EA1t chu\u1EA9n convention autoid.</small>\n </div>\n </div>\n }\n }\n </ng-template>\n\n <ng-template #elementsTpl>\n @let _filter = filter();\n <div class=\"sd-autoid-inspector__filter\">\n <mat-icon>search</mat-icon>\n <input\n type=\"text\"\n placeholder=\"L\u1ECDc theo autoid / name / tag\"\n [ngModel]=\"_filter\"\n (ngModelChange)=\"filter.set($event)\"/>\n @if (_filter) {\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__icon-btn\"\n matTooltip=\"Xo\u00E1 filter\"\n (click)=\"filter.set('')\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n\n <!-- Top-level elements (not inside any sd-table) -->\n <h4 class=\"sd-autoid-inspector__section-title\">\n Top-level elements\n <span class=\"sd-autoid-inspector__badge\">{{ topLevelElements().length }}</span>\n </h4>\n <div class=\"sd-autoid-inspector__table-wrap\">\n <table class=\"sd-autoid-inspector__table\">\n <thead>\n <tr>\n <th class=\"is-num\">STT</th>\n <th>Name</th>\n <th>Autoid</th>\n <th>Tag</th>\n <th>State</th>\n <th>Value</th>\n </tr>\n </thead>\n <tbody>\n @for (el of topLevelElements(); track el.autoid + $index) {\n <tr [class.is-error]=\"el.duplicate\">\n <td class=\"is-num\">{{ el.stt }}</td>\n <td>{{ el.name || '\u2014' }}</td>\n <td>\n <div class=\"sd-autoid-inspector__autoid-cell\">\n <code>{{ el.autoid }}</code>\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__icon-btn is-primary\"\n matTooltip=\"Copy xpath\"\n (click)=\"copyXpath(el.autoid)\">\n <mat-icon>content_copy</mat-icon>\n </button>\n </div>\n </td>\n <td><span class=\"sd-autoid-inspector__tag-chip\">{{ el.tag }}</span></td>\n <td class=\"sd-autoid-inspector__state-cell\">\n @if (el.state.disabled !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-error]=\"el.state.disabled === 'true'\">\n disabled={{ el.state.disabled }}\n </span>\n }\n @if (el.state.loading !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-warning]=\"el.state.loading === 'true'\">\n loading={{ el.state.loading }}\n </span>\n }\n @if (el.state.empty !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-info]=\"el.state.empty === 'true'\">\n empty={{ el.state.empty }}\n </span>\n }\n @if (el.state.invalid !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-error]=\"el.state.invalid === 'true'\">\n invalid={{ el.state.invalid }}\n </span>\n }\n @if (el.state.opened !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-info]=\"el.state.opened === 'true'\">\n opened={{ el.state.opened }}\n </span>\n }\n @if (el.state.count !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\">\n count={{ el.state.count }}\n </span>\n }\n @if (el.state.required !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-warning]=\"el.state.required === 'true'\">\n required={{ el.state.required }}\n </span>\n }\n @if (el.state.maxlength !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\">\n maxlength={{ el.state.maxlength }}\n </span>\n }\n @if (el.state.minlength !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\">\n minlength={{ el.state.minlength }}\n </span>\n }\n @if (el.state.pattern !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\">\n pattern={{ el.state.pattern }}\n </span>\n }\n @if (el.state.errorMessage !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-error\">\n error: {{ el.state.errorMessage }}\n </span>\n }\n </td>\n <td class=\"sd-autoid-inspector__value\" [matTooltip]=\"el.state.dataValue || el.text || ''\">\n {{ el.state.dataValue || el.text || '\u2014' }}\n </td>\n </tr>\n } @empty {\n <tr><td colspan=\"6\" class=\"sd-autoid-inspector__empty\">Kh\u00F4ng c\u00F3 k\u1EBFt qu\u1EA3.</td></tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- One section per sd-table group -->\n @for (group of tableGroups(); track group.scope) {\n <h4 class=\"sd-autoid-inspector__section-title\">\n <mat-icon class=\"is-info\">table_chart</mat-icon>\n Inside <code>{{ group.scope }}</code>\n <span class=\"sd-autoid-inspector__badge\">{{ group.items.length }}</span>\n </h4>\n <div class=\"sd-autoid-inspector__table-wrap\">\n <table class=\"sd-autoid-inspector__table\">\n <thead>\n <tr>\n <th class=\"is-num\">STT</th>\n <th>Name</th>\n <th>Autoid</th>\n <th>Tag</th>\n <th>State</th>\n <th>Value</th>\n </tr>\n </thead>\n <tbody>\n @for (el of group.items; track el.autoid + $index) {\n <tr [class.is-error]=\"el.duplicate\">\n <td class=\"is-num\">{{ el.stt }}</td>\n <td>{{ el.name || '\u2014' }}</td>\n <td>\n <div class=\"sd-autoid-inspector__autoid-cell\">\n <code>{{ el.autoid }}</code>\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__icon-btn is-primary\"\n matTooltip=\"Copy xpath\"\n (click)=\"copyXpath(el.autoid)\">\n <mat-icon>content_copy</mat-icon>\n </button>\n </div>\n </td>\n <td><span class=\"sd-autoid-inspector__tag-chip\">{{ el.tag }}</span></td>\n <td class=\"sd-autoid-inspector__state-cell\">\n @if (el.state.disabled !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-error]=\"el.state.disabled === 'true'\">\n disabled={{ el.state.disabled }}\n </span>\n }\n @if (el.state.loading !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-warning]=\"el.state.loading === 'true'\">\n loading={{ el.state.loading }}\n </span>\n }\n @if (el.state.empty !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-info]=\"el.state.empty === 'true'\">\n empty={{ el.state.empty }}\n </span>\n }\n @if (el.state.invalid !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-error]=\"el.state.invalid === 'true'\">\n invalid={{ el.state.invalid }}\n </span>\n }\n @if (el.state.opened !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-info]=\"el.state.opened === 'true'\">\n opened={{ el.state.opened }}\n </span>\n }\n @if (el.state.count !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\">\n count={{ el.state.count }}\n </span>\n }\n @if (el.state.required !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-warning]=\"el.state.required === 'true'\">\n required={{ el.state.required }}\n </span>\n }\n @if (el.state.maxlength !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\">\n maxlength={{ el.state.maxlength }}\n </span>\n }\n @if (el.state.minlength !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\">\n minlength={{ el.state.minlength }}\n </span>\n }\n @if (el.state.pattern !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\">\n pattern={{ el.state.pattern }}\n </span>\n }\n @if (el.state.errorMessage !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-error\">\n error: {{ el.state.errorMessage }}\n </span>\n }\n </td>\n <td class=\"sd-autoid-inspector__value\" [matTooltip]=\"el.state.dataValue || el.text || ''\">\n {{ el.state.dataValue || el.text || '\u2014' }}\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </ng-template>\n\n <ng-template #exportTpl>\n <div class=\"sd-autoid-inspector__export-grid\">\n <button type=\"button\" matTooltip=\"Copy JSON v\u00E0o clipboard\" (click)=\"copyJson()\">\n <mat-icon>content_copy</mat-icon>\n <span>Copy JSON</span>\n <small>clipboard</small>\n </button>\n <button type=\"button\" matTooltip=\"T\u1EA3i file JSON\" (click)=\"exportJson()\">\n <mat-icon>download</mat-icon>\n <span>JSON</span>\n <small>.json</small>\n </button>\n <button type=\"button\" matTooltip=\"T\u1EA3i file CSV\" (click)=\"exportCsv()\">\n <mat-icon>table_view</mat-icon>\n <span>CSV</span>\n <small>.csv</small>\n </button>\n <button type=\"button\" matTooltip=\"T\u1EA3i MD POM skeleton \u2014 cho AI vi\u1EBFt auto-test\" (click)=\"exportMdPom()\">\n <mat-icon>code</mat-icon>\n <span>MD POM</span>\n <small>AI test</small>\n </button>\n <button type=\"button\" matTooltip=\"T\u1EA3i MD reference table \u2014 group theo tag sd-*\" (click)=\"exportMdTable()\">\n <mat-icon>view_list</mat-icon>\n <span>MD Table</span>\n <small>reference</small>\n </button>\n </div>\n </ng-template>\n}\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__section-title mat-icon.is-info{color:var(--sd-info, #2962FF)}.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__state-cell{display:flex;flex-wrap:wrap;gap:4px;align-items:center;min-width:0}.sd-autoid-inspector__state-chip{display:inline-block;font-family:JetBrains Mono,Fira Code,Consolas,monospace;font-size:10px;background:var(--sd-black100, #F2F2F2);color:var(--sd-black400, #757575);border:1px solid var(--sd-black200, #E6E6E6);padding:1px 6px;border-radius:999px;font-weight:500;white-space:nowrap}.sd-autoid-inspector__state-chip.is-error{background:var(--sd-error-light, #FED5D0);color:var(--sd-error-dark, #BA200E);border-color:var(--sd-error, #F82C13)}.sd-autoid-inspector__state-chip.is-warning{background:var(--sd-warning-light, #FFEACC);color:var(--sd-warning-dark, #BF7000);border-color:var(--sd-warning, #FF9600)}.sd-autoid-inspector__state-chip.is-info{background:var(--sd-info-light, #E7E9FF);color:var(--sd-info, #2962FF);border-color:var(--sd-info, #2962FF)}.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 });
|
|
965
|
+
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: "@let _enabled = enabled();\n@let _open = open();\n@if (_enabled && !dismissed()) {\n <div class=\"sd-autoid-inspector__fab-wrap\">\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__fab\"\n data-autoid=\"sd-autoid-inspector-fab\"\n [attr.data-opened]=\"dataOpened()\"\n [matTooltip]=\"_open ? '\u0110\u00F3ng AutoId Inspector' : 'M\u1EDF AutoId Inspector'\"\n matTooltipPosition=\"left\"\n (click)=\"togglePanel()\">\n <mat-icon>{{ _open ? 'close' : 'tag' }}</mat-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__fab-dismiss\"\n matTooltip=\"\u1EA8n t\u1EA1m \u2014 reload trang \u0111\u1EC3 hi\u1EC7n l\u1EA1i\"\n matTooltipPosition=\"left\"\n aria-label=\"\u1EA8n t\u1EA1m FAB\"\n (click)=\"dismissFab($event)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n}\n\n@if (_enabled && _open) {\n @let _segment = segment();\n <button type=\"button\" class=\"sd-autoid-inspector__backdrop\" aria-label=\"\u0110\u00F3ng AutoId Inspector\" (click)=\"closePanel()\"></button>\n <section\n class=\"sd-autoid-inspector__panel\"\n role=\"dialog\"\n aria-label=\"AutoId Inspector\"\n data-autoid=\"sd-autoid-inspector-panel\"\n [attr.data-segment]=\"dataSegment()\"\n [attr.data-highlight-on]=\"dataHighlightOn()\"\n [attr.data-element-count]=\"dataElementCount()\"\n [attr.data-missing-count]=\"dataMissingCount()\"\n [attr.data-duplicate-count]=\"dataDuplicateCount()\">\n <header class=\"sd-autoid-inspector__header\">\n <div class=\"sd-autoid-inspector__segments\">\n <button type=\"button\" [class.is-active]=\"_segment === 'audit'\" (click)=\"setSegment('audit')\">\n Audit\n @if (audit(); as a) {\n <span class=\"sd-autoid-inspector__badge\" [class.is-warn]=\"a.missingCount + a.duplicateCount > 0\">\n {{ a.missingCount + a.duplicateCount }}\n </span>\n }\n </button>\n <button type=\"button\" [class.is-active]=\"_segment === 'elements'\" (click)=\"setSegment('elements')\">\n Elements\n <span class=\"sd-autoid-inspector__badge\">{{ elements().length }}</span>\n </button>\n <button type=\"button\" [class.is-active]=\"_segment === 'export'\" (click)=\"setSegment('export')\">Export</button>\n </div>\n <div class=\"sd-autoid-inspector__actions\">\n <button type=\"button\" class=\"sd-autoid-inspector__icon-btn\" matTooltip=\"Qu\u00E9t l\u1EA1i\" (click)=\"refresh()\">\n <mat-icon>refresh</mat-icon>\n </button>\n <button type=\"button\" class=\"sd-autoid-inspector__close-btn\" matTooltip=\"\u0110\u00F3ng\" aria-label=\"\u0110\u00F3ng panel\" (click)=\"closePanel()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </header>\n\n @if (copyOk()) {\n <div class=\"sd-autoid-inspector__toast\">\u0110\u00E3 copy \u2713</div>\n }\n\n <div class=\"sd-autoid-inspector__body\">\n @switch (_segment) {\n @case ('audit') {\n <ng-container *ngTemplateOutlet=\"auditTpl\"></ng-container>\n }\n @case ('elements') {\n <ng-container *ngTemplateOutlet=\"elementsTpl\"></ng-container>\n }\n @case ('export') {\n <ng-container *ngTemplateOutlet=\"exportTpl\"></ng-container>\n }\n }\n </div>\n </section>\n\n <ng-template #auditTpl>\n @if (audit(); as a) {\n @let _highlightOn = highlightOn();\n <div class=\"sd-autoid-inspector__audit-toolbar\">\n <button type=\"button\" class=\"sd-autoid-inspector__toggle\" [class.is-on]=\"_highlightOn\" (click)=\"toggleHighlight()\">\n <mat-icon>{{ _highlightOn ? 'visibility' : 'visibility_off' }}</mat-icon>\n <span>Highlight DOM</span>\n <span class=\"sd-autoid-inspector__toggle-state\">{{ _highlightOn ? 'ON' : 'OFF' }}</span>\n </button>\n </div>\n <div class=\"sd-autoid-inspector__summary\">\n <div class=\"sd-autoid-inspector__stat is-info\">\n <div class=\"sd-autoid-inspector__stat-value\">{{ a.total }}</div>\n <div class=\"sd-autoid-inspector__stat-label\">T\u1ED5ng autoid</div>\n </div>\n <div class=\"sd-autoid-inspector__stat\" [class.is-error]=\"a.duplicateCount > 0\" [class.is-success]=\"a.duplicateCount === 0\">\n <div class=\"sd-autoid-inspector__stat-value\">{{ a.duplicateCount }}</div>\n <div class=\"sd-autoid-inspector__stat-label\">Tr\u00F9ng</div>\n </div>\n <div class=\"sd-autoid-inspector__stat\" [class.is-warning]=\"a.missingCount > 0\" [class.is-success]=\"a.missingCount === 0\">\n <div class=\"sd-autoid-inspector__stat-value\">{{ a.missingCount }}</div>\n <div class=\"sd-autoid-inspector__stat-label\">Thi\u1EBFu</div>\n </div>\n </div>\n\n @if (a.duplicateCount > 0) {\n <h4 class=\"sd-autoid-inspector__section-title\">\n <mat-icon class=\"is-error\">error_outline</mat-icon>\n Autoid b\u1ECB tr\u00F9ng\n </h4>\n <div class=\"sd-autoid-inspector__table-wrap\">\n <table class=\"sd-autoid-inspector__table\">\n <thead>\n <tr>\n <th>Autoid</th>\n <th>S\u1ED1 l\u1EA7n</th>\n </tr>\n </thead>\n <tbody>\n @for (entry of a.duplicates | keyvalue; track entry.key) {\n <tr class=\"is-error\">\n <td>\n <code>{{ entry.key }}</code>\n </td>\n <td>{{ entry.value }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n @if (a.missingCount > 0) {\n <h4 class=\"sd-autoid-inspector__section-title\">\n <mat-icon class=\"is-warning\">warning_amber</mat-icon>\n Element thi\u1EBFu autoid\n </h4>\n <div class=\"sd-autoid-inspector__table-wrap\">\n <table class=\"sd-autoid-inspector__table\">\n <thead>\n <tr>\n <th>Tag</th>\n <th>Name hint</th>\n <th>Selector</th>\n </tr>\n </thead>\n <tbody>\n @for (m of a.missing; track $index) {\n <tr class=\"is-warning\">\n <td>\n <code>{{ m.tag }}</code>\n </td>\n <td>{{ m.nameHint }}</td>\n <td>\n <code>{{ m.selector }}</code>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n @if (a.duplicateCount === 0 && a.missingCount === 0) {\n <div class=\"sd-autoid-inspector__success-card\">\n <mat-icon>check_circle</mat-icon>\n <div>\n <strong>Kh\u00F4ng c\u00F3 element thi\u1EBFu ho\u1EB7c tr\u00F9ng autoid.</strong>\n <small>Trang \u0111\u00E3 \u0111\u1EA1t chu\u1EA9n convention autoid.</small>\n </div>\n </div>\n }\n }\n </ng-template>\n\n <ng-template #elementsTpl>\n @let _filter = filter();\n <div class=\"sd-autoid-inspector__filter\">\n <mat-icon>search</mat-icon>\n <input type=\"text\" placeholder=\"L\u1ECDc theo autoid / name / tag\" [ngModel]=\"_filter\" (ngModelChange)=\"filter.set($event)\" />\n @if (_filter) {\n <button type=\"button\" class=\"sd-autoid-inspector__icon-btn\" matTooltip=\"Xo\u00E1 filter\" (click)=\"filter.set('')\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n\n <!-- Top-level elements (not inside any sd-table) -->\n <h4 class=\"sd-autoid-inspector__section-title\">\n Top-level elements\n <span class=\"sd-autoid-inspector__badge\">{{ topLevelElements().length }}</span>\n </h4>\n <div class=\"sd-autoid-inspector__table-wrap\">\n <table class=\"sd-autoid-inspector__table\">\n <thead>\n <tr>\n <th class=\"is-num\">STT</th>\n <th>Name</th>\n <th>Autoid</th>\n <th>Tag</th>\n <th>State</th>\n <th>Value</th>\n </tr>\n </thead>\n <tbody>\n @for (el of topLevelElements(); track el.autoid + $index) {\n <tr [class.is-error]=\"el.duplicate\">\n <td class=\"is-num\">{{ el.stt }}</td>\n <td>{{ el.name || '\u2014' }}</td>\n <td>\n <div class=\"sd-autoid-inspector__autoid-cell\">\n <code>{{ el.autoid }}</code>\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__icon-btn is-primary\"\n matTooltip=\"Copy xpath\"\n (click)=\"copyXpath(el.autoid)\">\n <mat-icon>content_copy</mat-icon>\n </button>\n </div>\n </td>\n <td>\n <span class=\"sd-autoid-inspector__tag-chip\">{{ el.tag }}</span>\n </td>\n <td class=\"sd-autoid-inspector__state-cell\">\n @if (el.state.disabled !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-error]=\"el.state.disabled === 'true'\">\n disabled={{ el.state.disabled }}\n </span>\n }\n @if (el.state.loading !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-warning]=\"el.state.loading === 'true'\">\n loading={{ el.state.loading }}\n </span>\n }\n @if (el.state.empty !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-info]=\"el.state.empty === 'true'\">\n empty={{ el.state.empty }}\n </span>\n }\n @if (el.state.invalid !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-error]=\"el.state.invalid === 'true'\">\n invalid={{ el.state.invalid }}\n </span>\n }\n @if (el.state.opened !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-info]=\"el.state.opened === 'true'\">\n opened={{ el.state.opened }}\n </span>\n }\n @if (el.state.count !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\"> count={{ el.state.count }} </span>\n }\n @if (el.state.required !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-warning]=\"el.state.required === 'true'\">\n required={{ el.state.required }}\n </span>\n }\n @if (el.state.maxlength !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\"> maxlength={{ el.state.maxlength }} </span>\n }\n @if (el.state.minlength !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\"> minlength={{ el.state.minlength }} </span>\n }\n @if (el.state.pattern !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\"> pattern={{ el.state.pattern }} </span>\n }\n @if (el.state.errorMessage !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-error\"> error: {{ el.state.errorMessage }} </span>\n }\n </td>\n <td class=\"sd-autoid-inspector__value\" [matTooltip]=\"el.state.dataValue || el.text || ''\">\n {{ el.state.dataValue || el.text || '\u2014' }}\n </td>\n </tr>\n } @empty {\n <tr>\n <td colspan=\"6\" class=\"sd-autoid-inspector__empty\">Kh\u00F4ng c\u00F3 k\u1EBFt qu\u1EA3.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- One section per sd-table group -->\n @for (group of tableGroups(); track group.scope) {\n <h4 class=\"sd-autoid-inspector__section-title\">\n <mat-icon class=\"is-info\">table_chart</mat-icon>\n Inside <code>{{ group.scope }}</code>\n <span class=\"sd-autoid-inspector__badge\">{{ group.items.length }}</span>\n </h4>\n <div class=\"sd-autoid-inspector__table-wrap\">\n <table class=\"sd-autoid-inspector__table\">\n <thead>\n <tr>\n <th class=\"is-num\">STT</th>\n <th>Name</th>\n <th>Autoid</th>\n <th>Tag</th>\n <th>State</th>\n <th>Value</th>\n </tr>\n </thead>\n <tbody>\n @for (el of group.items; track el.autoid + $index) {\n <tr [class.is-error]=\"el.duplicate\">\n <td class=\"is-num\">{{ el.stt }}</td>\n <td>{{ el.name || '\u2014' }}</td>\n <td>\n <div class=\"sd-autoid-inspector__autoid-cell\">\n <code>{{ el.autoid }}</code>\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__icon-btn is-primary\"\n matTooltip=\"Copy xpath\"\n (click)=\"copyXpath(el.autoid)\">\n <mat-icon>content_copy</mat-icon>\n </button>\n </div>\n </td>\n <td>\n <span class=\"sd-autoid-inspector__tag-chip\">{{ el.tag }}</span>\n </td>\n <td class=\"sd-autoid-inspector__state-cell\">\n @if (el.state.disabled !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-error]=\"el.state.disabled === 'true'\">\n disabled={{ el.state.disabled }}\n </span>\n }\n @if (el.state.loading !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-warning]=\"el.state.loading === 'true'\">\n loading={{ el.state.loading }}\n </span>\n }\n @if (el.state.empty !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-info]=\"el.state.empty === 'true'\">\n empty={{ el.state.empty }}\n </span>\n }\n @if (el.state.invalid !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-error]=\"el.state.invalid === 'true'\">\n invalid={{ el.state.invalid }}\n </span>\n }\n @if (el.state.opened !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-info]=\"el.state.opened === 'true'\">\n opened={{ el.state.opened }}\n </span>\n }\n @if (el.state.count !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\"> count={{ el.state.count }} </span>\n }\n @if (el.state.required !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-warning]=\"el.state.required === 'true'\">\n required={{ el.state.required }}\n </span>\n }\n @if (el.state.maxlength !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\"> maxlength={{ el.state.maxlength }} </span>\n }\n @if (el.state.minlength !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\"> minlength={{ el.state.minlength }} </span>\n }\n @if (el.state.pattern !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\"> pattern={{ el.state.pattern }} </span>\n }\n @if (el.state.errorMessage !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-error\"> error: {{ el.state.errorMessage }} </span>\n }\n </td>\n <td class=\"sd-autoid-inspector__value\" [matTooltip]=\"el.state.dataValue || el.text || ''\">\n {{ el.state.dataValue || el.text || '\u2014' }}\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </ng-template>\n\n <ng-template #exportTpl>\n <div class=\"sd-autoid-inspector__export-grid\">\n <button type=\"button\" matTooltip=\"Copy JSON v\u00E0o clipboard\" (click)=\"copyJson()\">\n <mat-icon>content_copy</mat-icon>\n <span>Copy JSON</span>\n <small>clipboard</small>\n </button>\n <button type=\"button\" matTooltip=\"T\u1EA3i file JSON\" (click)=\"exportJson()\">\n <mat-icon>download</mat-icon>\n <span>JSON</span>\n <small>.json</small>\n </button>\n <button type=\"button\" matTooltip=\"T\u1EA3i file CSV\" (click)=\"exportCsv()\">\n <mat-icon>table_view</mat-icon>\n <span>CSV</span>\n <small>.csv</small>\n </button>\n <button type=\"button\" matTooltip=\"T\u1EA3i MD POM skeleton \u2014 cho AI vi\u1EBFt auto-test\" (click)=\"exportMdPom()\">\n <mat-icon>code</mat-icon>\n <span>MD POM</span>\n <small>AI test</small>\n </button>\n <button type=\"button\" matTooltip=\"T\u1EA3i MD reference table \u2014 group theo tag sd-*\" (click)=\"exportMdTable()\">\n <mat-icon>view_list</mat-icon>\n <span>MD Table</span>\n <small>reference</small>\n </button>\n </div>\n\n @if (e2eGeneratorHost()) {\n @let _e2eExporting = e2eExporting();\n <div class=\"sd-autoid-inspector__export-section\">\n <h4 class=\"sd-autoid-inspector__section-title\">\n <mat-icon class=\"is-info\">auto_awesome</mat-icon>\n T\u1EA1o E2E test\n </h4>\n <div class=\"sd-autoid-inspector__export-grid is-e2e\">\n <button\n type=\"button\"\n matTooltip=\"T\u1EA1o b\u1ED9 test Playwright (.zip)\"\n [disabled]=\"_e2eExporting !== null\"\n (click)=\"exportE2eTest('playwright')\">\n <mat-icon>{{ _e2eExporting === 'playwright' ? 'hourglass_empty' : 'play_arrow' }}</mat-icon>\n <span>Playwright</span>\n <small>.zip</small>\n </button>\n <button\n type=\"button\"\n matTooltip=\"T\u1EA1o b\u1ED9 test Robot Framework (.zip)\"\n [disabled]=\"_e2eExporting !== null\"\n (click)=\"openRobotExportContext()\">\n <mat-icon>{{ _e2eExporting === 'robot' ? 'hourglass_empty' : 'smart_toy' }}</mat-icon>\n <span>Robot Framework</span>\n <small>.zip</small>\n </button>\n </div>\n\n @if (robotContextOpen()) {\n <div class=\"sd-autoid-inspector__context-backdrop\" role=\"presentation\" (click)=\"closeRobotExportContext()\"></div>\n <section\n class=\"sd-autoid-inspector__context-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"sd-autoid-robot-context-title\">\n <header class=\"sd-autoid-inspector__context-header\">\n <div>\n <h4 id=\"sd-autoid-robot-context-title\">Ng\u1EEF c\u1EA3nh QC cho Robot Framework</h4>\n <small>Requirement, test case, test data</small>\n </div>\n <button type=\"button\" class=\"sd-autoid-inspector__icon-btn\" matTooltip=\"\u0110\u00F3ng\" (click)=\"closeRobotExportContext()\">\n <mat-icon>close</mat-icon>\n </button>\n </header>\n\n <div class=\"sd-autoid-inspector__context-form\">\n <label>\n <span>Component</span>\n <input\n type=\"text\"\n placeholder=\"SP-Wallet\"\n [ngModel]=\"robotExportContext().component || ''\"\n (ngModelChange)=\"updateRobotExportContext('component', $event)\" />\n </label>\n <label>\n <span>Story / Trace</span>\n <input\n type=\"text\"\n placeholder=\"US-001, AC-01\"\n [ngModel]=\"robotExportContext().storyLinkages || ''\"\n (ngModelChange)=\"updateRobotExportContext('storyLinkages', $event)\" />\n </label>\n <label class=\"is-full\">\n <span>Requirement / AC / BR</span>\n <textarea\n rows=\"4\"\n placeholder=\"D\u00E1n requirement ho\u1EB7c acceptance criteria...\"\n [ngModel]=\"robotExportContext().requirement || ''\"\n (ngModelChange)=\"updateRobotExportContext('requirement', $event)\"></textarea>\n </label>\n <label class=\"is-full\">\n <span>Test case QC \u0111\u00E3 \u0111\u1ECBnh ngh\u0129a</span>\n <textarea\n rows=\"4\"\n placeholder=\"TC-001: ...\"\n [ngModel]=\"robotExportContext().testCases || ''\"\n (ngModelChange)=\"updateRobotExportContext('testCases', $event)\"></textarea>\n </label>\n <label class=\"is-full\">\n <span>Test data</span>\n <textarea\n rows=\"3\"\n placeholder=\"wallet=V\u00ED A, amount=100000...\"\n [ngModel]=\"robotExportContext().testData || ''\"\n (ngModelChange)=\"updateRobotExportContext('testData', $event)\"></textarea>\n </label>\n <label class=\"is-full\">\n <span>Precondition</span>\n <textarea\n rows=\"2\"\n placeholder=\"User \u0111\u00E3 \u0111\u0103ng nh\u1EADp, d\u1EEF li\u1EC7u v\u00ED \u0111\u00E3 t\u1ED3n t\u1EA1i...\"\n [ngModel]=\"robotExportContext().precondition || ''\"\n (ngModelChange)=\"updateRobotExportContext('precondition', $event)\"></textarea>\n </label>\n <label class=\"is-full\">\n <span>Ghi ch\u00FA format QC</span>\n <textarea\n rows=\"3\"\n placeholder=\"Smoke=Y, Auto=Y, \u01B0u ti\u00EAn happy path...\"\n [ngModel]=\"robotExportContext().qcNotes || ''\"\n (ngModelChange)=\"updateRobotExportContext('qcNotes', $event)\"></textarea>\n </label>\n </div>\n\n <footer class=\"sd-autoid-inspector__context-actions\">\n <button type=\"button\" class=\"is-secondary\" [disabled]=\"_e2eExporting !== null\" (click)=\"closeRobotExportContext()\">\n Hu\u1EF7\n </button>\n <button\n type=\"button\"\n class=\"is-primary\"\n [disabled]=\"_e2eExporting !== null || !hasRobotExportContext()\"\n (click)=\"confirmRobotExport()\">\n <mat-icon>{{ _e2eExporting === 'robot' ? 'hourglass_empty' : 'download' }}</mat-icon>\n <span>\u0110\u1ED3ng \u00FD xu\u1EA5t</span>\n </button>\n </footer>\n </section>\n }\n\n @if (e2eExportOk(); as targetLabel) {\n <div class=\"sd-autoid-inspector__export-message is-success\">\u0110\u00E3 t\u1EA3i b\u1ED9 test {{ targetLabel }}.</div>\n }\n @if (e2eExportError(); as error) {\n <div class=\"sd-autoid-inspector__export-message is-error\">\n {{ error }}\n </div>\n }\n </div>\n }\n </ng-template>\n}\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;padding:0;border:none;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__section-title mat-icon.is-info{color:var(--sd-info, #2962ff)}.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__state-cell{display:flex;flex-wrap:wrap;gap:4px;align-items:center;min-width:0}.sd-autoid-inspector__state-chip{display:inline-block;font-family:JetBrains Mono,Fira Code,Consolas,monospace;font-size:10px;background:var(--sd-black100, #f2f2f2);color:var(--sd-black400, #757575);border:1px solid var(--sd-black200, #e6e6e6);padding:1px 6px;border-radius:999px;font-weight:500;white-space:nowrap}.sd-autoid-inspector__state-chip.is-error{background:var(--sd-error-light, #fed5d0);color:var(--sd-error-dark, #ba200e);border-color:var(--sd-error, #f82c13)}.sd-autoid-inspector__state-chip.is-warning{background:var(--sd-warning-light, #ffeacc);color:var(--sd-warning-dark, #bf7000);border-color:var(--sd-warning, #ff9600)}.sd-autoid-inspector__state-chip.is-info{background:var(--sd-info-light, #e7e9ff);color:var(--sd-info, #2962ff);border-color:var(--sd-info, #2962ff)}.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(auto-fit,minmax(112px,1fr));gap:8px}.sd-autoid-inspector__export-grid.is-e2e{grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}.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:disabled{cursor:wait;opacity:.68;transform:none}.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)}.sd-autoid-inspector__export-section{margin-top:14px;padding-top:12px;border-top:1px solid var(--sd-black200, #e6e6e6)}.sd-autoid-inspector__context-backdrop{position:fixed;inset:0;z-index:10001;background:#141c2652}.sd-autoid-inspector__context-dialog{position:fixed;z-index:10002;top:50%;left:50%;width:min(720px,100vw - 28px);max-height:min(720px,100vh - 28px);transform:translate(-50%,-50%);display:flex;flex-direction:column;border:1px solid var(--sd-black200, #e6e6e6);border-radius:8px;background:#fff;box-shadow:0 18px 48px #0f172a38;overflow:hidden}.sd-autoid-inspector__context-header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:14px 16px;border-bottom:1px solid var(--sd-black200, #e6e6e6)}.sd-autoid-inspector__context-header h4{margin:0;font-size:15px;font-weight:700;line-height:1.3;color:var(--sd-black500, #212121)}.sd-autoid-inspector__context-header small{display:block;margin-top:2px;font-size:11px;color:var(--sd-black400, #757575);line-height:1.3}.sd-autoid-inspector__context-form{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;padding:14px 16px;overflow:auto}.sd-autoid-inspector__context-form label{display:flex;flex-direction:column;gap:6px;min-width:0}.sd-autoid-inspector__context-form label.is-full{grid-column:1/-1}.sd-autoid-inspector__context-form span{font-size:12px;font-weight:600;color:var(--sd-black500, #212121);line-height:1.25}.sd-autoid-inspector__context-form input,.sd-autoid-inspector__context-form textarea{width:100%;min-width:0;border:1px solid var(--sd-black200, #e6e6e6);border-radius:6px;padding:8px 10px;color:var(--sd-black500, #212121);font:inherit;font-size:12px;line-height:1.4;outline:none;resize:vertical}.sd-autoid-inspector__context-form input:focus,.sd-autoid-inspector__context-form textarea:focus{border-color:var(--sd-primary, #2a66f4);box-shadow:0 0 0 2px #2a66f424}.sd-autoid-inspector__context-actions{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--sd-black200, #e6e6e6)}.sd-autoid-inspector__context-actions button{min-height:34px;border-radius:6px;padding:0 12px;border:1px solid var(--sd-black200, #e6e6e6);background:#fff;color:var(--sd-black500, #212121);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:6px;font-size:12px;font-weight:600}.sd-autoid-inspector__context-actions button.is-primary{border-color:var(--sd-primary, #2a66f4);background:var(--sd-primary, #2a66f4);color:#fff}.sd-autoid-inspector__context-actions button:disabled{cursor:not-allowed;opacity:.62}.sd-autoid-inspector__context-actions button mat-icon{font-size:17px;width:17px;height:17px}.sd-autoid-inspector__export-message{margin-top:10px;border-radius:8px;padding:8px 10px;font-size:12px;line-height:1.35}.sd-autoid-inspector__export-message.is-success{background:var(--sd-success-light, #dbefdc);color:var(--sd-success-dark, #39833c);border:1px solid var(--sd-success, #4caf50)}.sd-autoid-inspector__export-message.is-error{background:var(--sd-error-light, #fed5d0);color:var(--sd-error-dark, #ba200e);border:1px solid var(--sd-error, #f82c13)}@media(max-width:560px){.sd-autoid-inspector__context-form{grid-template-columns:1fr}.sd-autoid-inspector__context-actions{flex-direction:column-reverse}.sd-autoid-inspector__context-actions button{width:100%}}\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 });
|
|
796
966
|
}
|
|
797
967
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdAutoidInspector, decorators: [{
|
|
798
968
|
type: Component,
|
|
799
|
-
args: [{ selector: 'sd-autoid-inspector', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, FormsModule, MatIconModule, MatTooltipModule], template: "@let _enabled = enabled();\n@let _open = open();\n@if (_enabled && !dismissed()) {\n <div class=\"sd-autoid-inspector__fab-wrap\">\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__fab\"\n data-autoid=\"sd-autoid-inspector-fab\"\n [attr.data-opened]=\"dataOpened()\"\n [matTooltip]=\"_open ? '\u0110\u00F3ng AutoId Inspector' : 'M\u1EDF AutoId Inspector'\"\n matTooltipPosition=\"left\"\n (click)=\"togglePanel()\">\n <mat-icon>{{ _open ? 'close' : 'tag' }}</mat-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__fab-dismiss\"\n matTooltip=\"\u1EA8n t\u1EA1m \u2014 reload trang \u0111\u1EC3 hi\u1EC7n l\u1EA1i\"\n matTooltipPosition=\"left\"\n aria-label=\"\u1EA8n t\u1EA1m FAB\"\n (click)=\"dismissFab($event)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n\n}\n\n@if (_enabled && _open) {\n @let _segment = segment();\n <div class=\"sd-autoid-inspector__backdrop\" (click)=\"closePanel()\"></div>\n <section class=\"sd-autoid-inspector__panel\"\n role=\"dialog\"\n aria-label=\"AutoId Inspector\"\n data-autoid=\"sd-autoid-inspector-panel\"\n [attr.data-segment]=\"dataSegment()\"\n [attr.data-highlight-on]=\"dataHighlightOn()\"\n [attr.data-element-count]=\"dataElementCount()\"\n [attr.data-missing-count]=\"dataMissingCount()\"\n [attr.data-duplicate-count]=\"dataDuplicateCount()\">\n <header class=\"sd-autoid-inspector__header\">\n <div class=\"sd-autoid-inspector__segments\">\n <button\n type=\"button\"\n [class.is-active]=\"_segment === 'audit'\"\n (click)=\"setSegment('audit')\">\n Audit\n @if (audit(); as a) {\n <span class=\"sd-autoid-inspector__badge\" [class.is-warn]=\"a.missingCount + a.duplicateCount > 0\">\n {{ a.missingCount + a.duplicateCount }}\n </span>\n }\n </button>\n <button\n type=\"button\"\n [class.is-active]=\"_segment === 'elements'\"\n (click)=\"setSegment('elements')\">\n Elements\n <span class=\"sd-autoid-inspector__badge\">{{ elements().length }}</span>\n </button>\n <button\n type=\"button\"\n [class.is-active]=\"_segment === 'export'\"\n (click)=\"setSegment('export')\">\n Export\n </button>\n </div>\n <div class=\"sd-autoid-inspector__actions\">\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__icon-btn\"\n matTooltip=\"Qu\u00E9t l\u1EA1i\"\n (click)=\"refresh()\">\n <mat-icon>refresh</mat-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__close-btn\"\n matTooltip=\"\u0110\u00F3ng\"\n aria-label=\"\u0110\u00F3ng panel\"\n (click)=\"closePanel()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </header>\n\n @if (copyOk()) {\n <div class=\"sd-autoid-inspector__toast\">\u0110\u00E3 copy \u2713</div>\n }\n\n <div class=\"sd-autoid-inspector__body\">\n @switch (_segment) {\n @case ('audit') {\n <ng-container *ngTemplateOutlet=\"auditTpl\"></ng-container>\n }\n @case ('elements') {\n <ng-container *ngTemplateOutlet=\"elementsTpl\"></ng-container>\n }\n @case ('export') {\n <ng-container *ngTemplateOutlet=\"exportTpl\"></ng-container>\n }\n }\n </div>\n </section>\n\n <ng-template #auditTpl>\n @if (audit(); as a) {\n @let _highlightOn = highlightOn();\n <div class=\"sd-autoid-inspector__audit-toolbar\">\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__toggle\"\n [class.is-on]=\"_highlightOn\"\n (click)=\"toggleHighlight()\">\n <mat-icon>{{ _highlightOn ? 'visibility' : 'visibility_off' }}</mat-icon>\n <span>Highlight DOM</span>\n <span class=\"sd-autoid-inspector__toggle-state\">{{ _highlightOn ? 'ON' : 'OFF' }}</span>\n </button>\n </div>\n <div class=\"sd-autoid-inspector__summary\">\n <div class=\"sd-autoid-inspector__stat is-info\">\n <div class=\"sd-autoid-inspector__stat-value\">{{ a.total }}</div>\n <div class=\"sd-autoid-inspector__stat-label\">T\u1ED5ng autoid</div>\n </div>\n <div class=\"sd-autoid-inspector__stat\" [class.is-error]=\"a.duplicateCount > 0\" [class.is-success]=\"a.duplicateCount === 0\">\n <div class=\"sd-autoid-inspector__stat-value\">{{ a.duplicateCount }}</div>\n <div class=\"sd-autoid-inspector__stat-label\">Tr\u00F9ng</div>\n </div>\n <div class=\"sd-autoid-inspector__stat\" [class.is-warning]=\"a.missingCount > 0\" [class.is-success]=\"a.missingCount === 0\">\n <div class=\"sd-autoid-inspector__stat-value\">{{ a.missingCount }}</div>\n <div class=\"sd-autoid-inspector__stat-label\">Thi\u1EBFu</div>\n </div>\n </div>\n\n @if (a.duplicateCount > 0) {\n <h4 class=\"sd-autoid-inspector__section-title\">\n <mat-icon class=\"is-error\">error_outline</mat-icon>\n Autoid b\u1ECB tr\u00F9ng\n </h4>\n <div class=\"sd-autoid-inspector__table-wrap\">\n <table class=\"sd-autoid-inspector__table\">\n <thead>\n <tr><th>Autoid</th><th>S\u1ED1 l\u1EA7n</th></tr>\n </thead>\n <tbody>\n @for (entry of a.duplicates | keyvalue; track entry.key) {\n <tr class=\"is-error\">\n <td><code>{{ entry.key }}</code></td>\n <td>{{ entry.value }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n @if (a.missingCount > 0) {\n <h4 class=\"sd-autoid-inspector__section-title\">\n <mat-icon class=\"is-warning\">warning_amber</mat-icon>\n Element thi\u1EBFu autoid\n </h4>\n <div class=\"sd-autoid-inspector__table-wrap\">\n <table class=\"sd-autoid-inspector__table\">\n <thead>\n <tr><th>Tag</th><th>Name hint</th><th>Selector</th></tr>\n </thead>\n <tbody>\n @for (m of a.missing; track $index) {\n <tr class=\"is-warning\">\n <td><code>{{ m.tag }}</code></td>\n <td>{{ m.nameHint }}</td>\n <td><code>{{ m.selector }}</code></td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n @if (a.duplicateCount === 0 && a.missingCount === 0) {\n <div class=\"sd-autoid-inspector__success-card\">\n <mat-icon>check_circle</mat-icon>\n <div>\n <strong>Kh\u00F4ng c\u00F3 element thi\u1EBFu ho\u1EB7c tr\u00F9ng autoid.</strong>\n <small>Trang \u0111\u00E3 \u0111\u1EA1t chu\u1EA9n convention autoid.</small>\n </div>\n </div>\n }\n }\n </ng-template>\n\n <ng-template #elementsTpl>\n @let _filter = filter();\n <div class=\"sd-autoid-inspector__filter\">\n <mat-icon>search</mat-icon>\n <input\n type=\"text\"\n placeholder=\"L\u1ECDc theo autoid / name / tag\"\n [ngModel]=\"_filter\"\n (ngModelChange)=\"filter.set($event)\"/>\n @if (_filter) {\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__icon-btn\"\n matTooltip=\"Xo\u00E1 filter\"\n (click)=\"filter.set('')\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n\n <!-- Top-level elements (not inside any sd-table) -->\n <h4 class=\"sd-autoid-inspector__section-title\">\n Top-level elements\n <span class=\"sd-autoid-inspector__badge\">{{ topLevelElements().length }}</span>\n </h4>\n <div class=\"sd-autoid-inspector__table-wrap\">\n <table class=\"sd-autoid-inspector__table\">\n <thead>\n <tr>\n <th class=\"is-num\">STT</th>\n <th>Name</th>\n <th>Autoid</th>\n <th>Tag</th>\n <th>State</th>\n <th>Value</th>\n </tr>\n </thead>\n <tbody>\n @for (el of topLevelElements(); track el.autoid + $index) {\n <tr [class.is-error]=\"el.duplicate\">\n <td class=\"is-num\">{{ el.stt }}</td>\n <td>{{ el.name || '\u2014' }}</td>\n <td>\n <div class=\"sd-autoid-inspector__autoid-cell\">\n <code>{{ el.autoid }}</code>\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__icon-btn is-primary\"\n matTooltip=\"Copy xpath\"\n (click)=\"copyXpath(el.autoid)\">\n <mat-icon>content_copy</mat-icon>\n </button>\n </div>\n </td>\n <td><span class=\"sd-autoid-inspector__tag-chip\">{{ el.tag }}</span></td>\n <td class=\"sd-autoid-inspector__state-cell\">\n @if (el.state.disabled !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-error]=\"el.state.disabled === 'true'\">\n disabled={{ el.state.disabled }}\n </span>\n }\n @if (el.state.loading !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-warning]=\"el.state.loading === 'true'\">\n loading={{ el.state.loading }}\n </span>\n }\n @if (el.state.empty !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-info]=\"el.state.empty === 'true'\">\n empty={{ el.state.empty }}\n </span>\n }\n @if (el.state.invalid !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-error]=\"el.state.invalid === 'true'\">\n invalid={{ el.state.invalid }}\n </span>\n }\n @if (el.state.opened !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-info]=\"el.state.opened === 'true'\">\n opened={{ el.state.opened }}\n </span>\n }\n @if (el.state.count !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\">\n count={{ el.state.count }}\n </span>\n }\n @if (el.state.required !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-warning]=\"el.state.required === 'true'\">\n required={{ el.state.required }}\n </span>\n }\n @if (el.state.maxlength !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\">\n maxlength={{ el.state.maxlength }}\n </span>\n }\n @if (el.state.minlength !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\">\n minlength={{ el.state.minlength }}\n </span>\n }\n @if (el.state.pattern !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\">\n pattern={{ el.state.pattern }}\n </span>\n }\n @if (el.state.errorMessage !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-error\">\n error: {{ el.state.errorMessage }}\n </span>\n }\n </td>\n <td class=\"sd-autoid-inspector__value\" [matTooltip]=\"el.state.dataValue || el.text || ''\">\n {{ el.state.dataValue || el.text || '\u2014' }}\n </td>\n </tr>\n } @empty {\n <tr><td colspan=\"6\" class=\"sd-autoid-inspector__empty\">Kh\u00F4ng c\u00F3 k\u1EBFt qu\u1EA3.</td></tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- One section per sd-table group -->\n @for (group of tableGroups(); track group.scope) {\n <h4 class=\"sd-autoid-inspector__section-title\">\n <mat-icon class=\"is-info\">table_chart</mat-icon>\n Inside <code>{{ group.scope }}</code>\n <span class=\"sd-autoid-inspector__badge\">{{ group.items.length }}</span>\n </h4>\n <div class=\"sd-autoid-inspector__table-wrap\">\n <table class=\"sd-autoid-inspector__table\">\n <thead>\n <tr>\n <th class=\"is-num\">STT</th>\n <th>Name</th>\n <th>Autoid</th>\n <th>Tag</th>\n <th>State</th>\n <th>Value</th>\n </tr>\n </thead>\n <tbody>\n @for (el of group.items; track el.autoid + $index) {\n <tr [class.is-error]=\"el.duplicate\">\n <td class=\"is-num\">{{ el.stt }}</td>\n <td>{{ el.name || '\u2014' }}</td>\n <td>\n <div class=\"sd-autoid-inspector__autoid-cell\">\n <code>{{ el.autoid }}</code>\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__icon-btn is-primary\"\n matTooltip=\"Copy xpath\"\n (click)=\"copyXpath(el.autoid)\">\n <mat-icon>content_copy</mat-icon>\n </button>\n </div>\n </td>\n <td><span class=\"sd-autoid-inspector__tag-chip\">{{ el.tag }}</span></td>\n <td class=\"sd-autoid-inspector__state-cell\">\n @if (el.state.disabled !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-error]=\"el.state.disabled === 'true'\">\n disabled={{ el.state.disabled }}\n </span>\n }\n @if (el.state.loading !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-warning]=\"el.state.loading === 'true'\">\n loading={{ el.state.loading }}\n </span>\n }\n @if (el.state.empty !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-info]=\"el.state.empty === 'true'\">\n empty={{ el.state.empty }}\n </span>\n }\n @if (el.state.invalid !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-error]=\"el.state.invalid === 'true'\">\n invalid={{ el.state.invalid }}\n </span>\n }\n @if (el.state.opened !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-info]=\"el.state.opened === 'true'\">\n opened={{ el.state.opened }}\n </span>\n }\n @if (el.state.count !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\">\n count={{ el.state.count }}\n </span>\n }\n @if (el.state.required !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\"\n [class.is-warning]=\"el.state.required === 'true'\">\n required={{ el.state.required }}\n </span>\n }\n @if (el.state.maxlength !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\">\n maxlength={{ el.state.maxlength }}\n </span>\n }\n @if (el.state.minlength !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\">\n minlength={{ el.state.minlength }}\n </span>\n }\n @if (el.state.pattern !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\">\n pattern={{ el.state.pattern }}\n </span>\n }\n @if (el.state.errorMessage !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-error\">\n error: {{ el.state.errorMessage }}\n </span>\n }\n </td>\n <td class=\"sd-autoid-inspector__value\" [matTooltip]=\"el.state.dataValue || el.text || ''\">\n {{ el.state.dataValue || el.text || '\u2014' }}\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </ng-template>\n\n <ng-template #exportTpl>\n <div class=\"sd-autoid-inspector__export-grid\">\n <button type=\"button\" matTooltip=\"Copy JSON v\u00E0o clipboard\" (click)=\"copyJson()\">\n <mat-icon>content_copy</mat-icon>\n <span>Copy JSON</span>\n <small>clipboard</small>\n </button>\n <button type=\"button\" matTooltip=\"T\u1EA3i file JSON\" (click)=\"exportJson()\">\n <mat-icon>download</mat-icon>\n <span>JSON</span>\n <small>.json</small>\n </button>\n <button type=\"button\" matTooltip=\"T\u1EA3i file CSV\" (click)=\"exportCsv()\">\n <mat-icon>table_view</mat-icon>\n <span>CSV</span>\n <small>.csv</small>\n </button>\n <button type=\"button\" matTooltip=\"T\u1EA3i MD POM skeleton \u2014 cho AI vi\u1EBFt auto-test\" (click)=\"exportMdPom()\">\n <mat-icon>code</mat-icon>\n <span>MD POM</span>\n <small>AI test</small>\n </button>\n <button type=\"button\" matTooltip=\"T\u1EA3i MD reference table \u2014 group theo tag sd-*\" (click)=\"exportMdTable()\">\n <mat-icon>view_list</mat-icon>\n <span>MD Table</span>\n <small>reference</small>\n </button>\n </div>\n </ng-template>\n}\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__section-title mat-icon.is-info{color:var(--sd-info, #2962FF)}.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__state-cell{display:flex;flex-wrap:wrap;gap:4px;align-items:center;min-width:0}.sd-autoid-inspector__state-chip{display:inline-block;font-family:JetBrains Mono,Fira Code,Consolas,monospace;font-size:10px;background:var(--sd-black100, #F2F2F2);color:var(--sd-black400, #757575);border:1px solid var(--sd-black200, #E6E6E6);padding:1px 6px;border-radius:999px;font-weight:500;white-space:nowrap}.sd-autoid-inspector__state-chip.is-error{background:var(--sd-error-light, #FED5D0);color:var(--sd-error-dark, #BA200E);border-color:var(--sd-error, #F82C13)}.sd-autoid-inspector__state-chip.is-warning{background:var(--sd-warning-light, #FFEACC);color:var(--sd-warning-dark, #BF7000);border-color:var(--sd-warning, #FF9600)}.sd-autoid-inspector__state-chip.is-info{background:var(--sd-info-light, #E7E9FF);color:var(--sd-info, #2962FF);border-color:var(--sd-info, #2962FF)}.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"] }]
|
|
969
|
+
args: [{ selector: 'sd-autoid-inspector', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, FormsModule, MatIconModule, MatTooltipModule], template: "@let _enabled = enabled();\n@let _open = open();\n@if (_enabled && !dismissed()) {\n <div class=\"sd-autoid-inspector__fab-wrap\">\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__fab\"\n data-autoid=\"sd-autoid-inspector-fab\"\n [attr.data-opened]=\"dataOpened()\"\n [matTooltip]=\"_open ? '\u0110\u00F3ng AutoId Inspector' : 'M\u1EDF AutoId Inspector'\"\n matTooltipPosition=\"left\"\n (click)=\"togglePanel()\">\n <mat-icon>{{ _open ? 'close' : 'tag' }}</mat-icon>\n </button>\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__fab-dismiss\"\n matTooltip=\"\u1EA8n t\u1EA1m \u2014 reload trang \u0111\u1EC3 hi\u1EC7n l\u1EA1i\"\n matTooltipPosition=\"left\"\n aria-label=\"\u1EA8n t\u1EA1m FAB\"\n (click)=\"dismissFab($event)\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n}\n\n@if (_enabled && _open) {\n @let _segment = segment();\n <button type=\"button\" class=\"sd-autoid-inspector__backdrop\" aria-label=\"\u0110\u00F3ng AutoId Inspector\" (click)=\"closePanel()\"></button>\n <section\n class=\"sd-autoid-inspector__panel\"\n role=\"dialog\"\n aria-label=\"AutoId Inspector\"\n data-autoid=\"sd-autoid-inspector-panel\"\n [attr.data-segment]=\"dataSegment()\"\n [attr.data-highlight-on]=\"dataHighlightOn()\"\n [attr.data-element-count]=\"dataElementCount()\"\n [attr.data-missing-count]=\"dataMissingCount()\"\n [attr.data-duplicate-count]=\"dataDuplicateCount()\">\n <header class=\"sd-autoid-inspector__header\">\n <div class=\"sd-autoid-inspector__segments\">\n <button type=\"button\" [class.is-active]=\"_segment === 'audit'\" (click)=\"setSegment('audit')\">\n Audit\n @if (audit(); as a) {\n <span class=\"sd-autoid-inspector__badge\" [class.is-warn]=\"a.missingCount + a.duplicateCount > 0\">\n {{ a.missingCount + a.duplicateCount }}\n </span>\n }\n </button>\n <button type=\"button\" [class.is-active]=\"_segment === 'elements'\" (click)=\"setSegment('elements')\">\n Elements\n <span class=\"sd-autoid-inspector__badge\">{{ elements().length }}</span>\n </button>\n <button type=\"button\" [class.is-active]=\"_segment === 'export'\" (click)=\"setSegment('export')\">Export</button>\n </div>\n <div class=\"sd-autoid-inspector__actions\">\n <button type=\"button\" class=\"sd-autoid-inspector__icon-btn\" matTooltip=\"Qu\u00E9t l\u1EA1i\" (click)=\"refresh()\">\n <mat-icon>refresh</mat-icon>\n </button>\n <button type=\"button\" class=\"sd-autoid-inspector__close-btn\" matTooltip=\"\u0110\u00F3ng\" aria-label=\"\u0110\u00F3ng panel\" (click)=\"closePanel()\">\n <mat-icon>close</mat-icon>\n </button>\n </div>\n </header>\n\n @if (copyOk()) {\n <div class=\"sd-autoid-inspector__toast\">\u0110\u00E3 copy \u2713</div>\n }\n\n <div class=\"sd-autoid-inspector__body\">\n @switch (_segment) {\n @case ('audit') {\n <ng-container *ngTemplateOutlet=\"auditTpl\"></ng-container>\n }\n @case ('elements') {\n <ng-container *ngTemplateOutlet=\"elementsTpl\"></ng-container>\n }\n @case ('export') {\n <ng-container *ngTemplateOutlet=\"exportTpl\"></ng-container>\n }\n }\n </div>\n </section>\n\n <ng-template #auditTpl>\n @if (audit(); as a) {\n @let _highlightOn = highlightOn();\n <div class=\"sd-autoid-inspector__audit-toolbar\">\n <button type=\"button\" class=\"sd-autoid-inspector__toggle\" [class.is-on]=\"_highlightOn\" (click)=\"toggleHighlight()\">\n <mat-icon>{{ _highlightOn ? 'visibility' : 'visibility_off' }}</mat-icon>\n <span>Highlight DOM</span>\n <span class=\"sd-autoid-inspector__toggle-state\">{{ _highlightOn ? 'ON' : 'OFF' }}</span>\n </button>\n </div>\n <div class=\"sd-autoid-inspector__summary\">\n <div class=\"sd-autoid-inspector__stat is-info\">\n <div class=\"sd-autoid-inspector__stat-value\">{{ a.total }}</div>\n <div class=\"sd-autoid-inspector__stat-label\">T\u1ED5ng autoid</div>\n </div>\n <div class=\"sd-autoid-inspector__stat\" [class.is-error]=\"a.duplicateCount > 0\" [class.is-success]=\"a.duplicateCount === 0\">\n <div class=\"sd-autoid-inspector__stat-value\">{{ a.duplicateCount }}</div>\n <div class=\"sd-autoid-inspector__stat-label\">Tr\u00F9ng</div>\n </div>\n <div class=\"sd-autoid-inspector__stat\" [class.is-warning]=\"a.missingCount > 0\" [class.is-success]=\"a.missingCount === 0\">\n <div class=\"sd-autoid-inspector__stat-value\">{{ a.missingCount }}</div>\n <div class=\"sd-autoid-inspector__stat-label\">Thi\u1EBFu</div>\n </div>\n </div>\n\n @if (a.duplicateCount > 0) {\n <h4 class=\"sd-autoid-inspector__section-title\">\n <mat-icon class=\"is-error\">error_outline</mat-icon>\n Autoid b\u1ECB tr\u00F9ng\n </h4>\n <div class=\"sd-autoid-inspector__table-wrap\">\n <table class=\"sd-autoid-inspector__table\">\n <thead>\n <tr>\n <th>Autoid</th>\n <th>S\u1ED1 l\u1EA7n</th>\n </tr>\n </thead>\n <tbody>\n @for (entry of a.duplicates | keyvalue; track entry.key) {\n <tr class=\"is-error\">\n <td>\n <code>{{ entry.key }}</code>\n </td>\n <td>{{ entry.value }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n @if (a.missingCount > 0) {\n <h4 class=\"sd-autoid-inspector__section-title\">\n <mat-icon class=\"is-warning\">warning_amber</mat-icon>\n Element thi\u1EBFu autoid\n </h4>\n <div class=\"sd-autoid-inspector__table-wrap\">\n <table class=\"sd-autoid-inspector__table\">\n <thead>\n <tr>\n <th>Tag</th>\n <th>Name hint</th>\n <th>Selector</th>\n </tr>\n </thead>\n <tbody>\n @for (m of a.missing; track $index) {\n <tr class=\"is-warning\">\n <td>\n <code>{{ m.tag }}</code>\n </td>\n <td>{{ m.nameHint }}</td>\n <td>\n <code>{{ m.selector }}</code>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n @if (a.duplicateCount === 0 && a.missingCount === 0) {\n <div class=\"sd-autoid-inspector__success-card\">\n <mat-icon>check_circle</mat-icon>\n <div>\n <strong>Kh\u00F4ng c\u00F3 element thi\u1EBFu ho\u1EB7c tr\u00F9ng autoid.</strong>\n <small>Trang \u0111\u00E3 \u0111\u1EA1t chu\u1EA9n convention autoid.</small>\n </div>\n </div>\n }\n }\n </ng-template>\n\n <ng-template #elementsTpl>\n @let _filter = filter();\n <div class=\"sd-autoid-inspector__filter\">\n <mat-icon>search</mat-icon>\n <input type=\"text\" placeholder=\"L\u1ECDc theo autoid / name / tag\" [ngModel]=\"_filter\" (ngModelChange)=\"filter.set($event)\" />\n @if (_filter) {\n <button type=\"button\" class=\"sd-autoid-inspector__icon-btn\" matTooltip=\"Xo\u00E1 filter\" (click)=\"filter.set('')\">\n <mat-icon>close</mat-icon>\n </button>\n }\n </div>\n\n <!-- Top-level elements (not inside any sd-table) -->\n <h4 class=\"sd-autoid-inspector__section-title\">\n Top-level elements\n <span class=\"sd-autoid-inspector__badge\">{{ topLevelElements().length }}</span>\n </h4>\n <div class=\"sd-autoid-inspector__table-wrap\">\n <table class=\"sd-autoid-inspector__table\">\n <thead>\n <tr>\n <th class=\"is-num\">STT</th>\n <th>Name</th>\n <th>Autoid</th>\n <th>Tag</th>\n <th>State</th>\n <th>Value</th>\n </tr>\n </thead>\n <tbody>\n @for (el of topLevelElements(); track el.autoid + $index) {\n <tr [class.is-error]=\"el.duplicate\">\n <td class=\"is-num\">{{ el.stt }}</td>\n <td>{{ el.name || '\u2014' }}</td>\n <td>\n <div class=\"sd-autoid-inspector__autoid-cell\">\n <code>{{ el.autoid }}</code>\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__icon-btn is-primary\"\n matTooltip=\"Copy xpath\"\n (click)=\"copyXpath(el.autoid)\">\n <mat-icon>content_copy</mat-icon>\n </button>\n </div>\n </td>\n <td>\n <span class=\"sd-autoid-inspector__tag-chip\">{{ el.tag }}</span>\n </td>\n <td class=\"sd-autoid-inspector__state-cell\">\n @if (el.state.disabled !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-error]=\"el.state.disabled === 'true'\">\n disabled={{ el.state.disabled }}\n </span>\n }\n @if (el.state.loading !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-warning]=\"el.state.loading === 'true'\">\n loading={{ el.state.loading }}\n </span>\n }\n @if (el.state.empty !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-info]=\"el.state.empty === 'true'\">\n empty={{ el.state.empty }}\n </span>\n }\n @if (el.state.invalid !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-error]=\"el.state.invalid === 'true'\">\n invalid={{ el.state.invalid }}\n </span>\n }\n @if (el.state.opened !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-info]=\"el.state.opened === 'true'\">\n opened={{ el.state.opened }}\n </span>\n }\n @if (el.state.count !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\"> count={{ el.state.count }} </span>\n }\n @if (el.state.required !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-warning]=\"el.state.required === 'true'\">\n required={{ el.state.required }}\n </span>\n }\n @if (el.state.maxlength !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\"> maxlength={{ el.state.maxlength }} </span>\n }\n @if (el.state.minlength !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\"> minlength={{ el.state.minlength }} </span>\n }\n @if (el.state.pattern !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\"> pattern={{ el.state.pattern }} </span>\n }\n @if (el.state.errorMessage !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-error\"> error: {{ el.state.errorMessage }} </span>\n }\n </td>\n <td class=\"sd-autoid-inspector__value\" [matTooltip]=\"el.state.dataValue || el.text || ''\">\n {{ el.state.dataValue || el.text || '\u2014' }}\n </td>\n </tr>\n } @empty {\n <tr>\n <td colspan=\"6\" class=\"sd-autoid-inspector__empty\">Kh\u00F4ng c\u00F3 k\u1EBFt qu\u1EA3.</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- One section per sd-table group -->\n @for (group of tableGroups(); track group.scope) {\n <h4 class=\"sd-autoid-inspector__section-title\">\n <mat-icon class=\"is-info\">table_chart</mat-icon>\n Inside <code>{{ group.scope }}</code>\n <span class=\"sd-autoid-inspector__badge\">{{ group.items.length }}</span>\n </h4>\n <div class=\"sd-autoid-inspector__table-wrap\">\n <table class=\"sd-autoid-inspector__table\">\n <thead>\n <tr>\n <th class=\"is-num\">STT</th>\n <th>Name</th>\n <th>Autoid</th>\n <th>Tag</th>\n <th>State</th>\n <th>Value</th>\n </tr>\n </thead>\n <tbody>\n @for (el of group.items; track el.autoid + $index) {\n <tr [class.is-error]=\"el.duplicate\">\n <td class=\"is-num\">{{ el.stt }}</td>\n <td>{{ el.name || '\u2014' }}</td>\n <td>\n <div class=\"sd-autoid-inspector__autoid-cell\">\n <code>{{ el.autoid }}</code>\n <button\n type=\"button\"\n class=\"sd-autoid-inspector__icon-btn is-primary\"\n matTooltip=\"Copy xpath\"\n (click)=\"copyXpath(el.autoid)\">\n <mat-icon>content_copy</mat-icon>\n </button>\n </div>\n </td>\n <td>\n <span class=\"sd-autoid-inspector__tag-chip\">{{ el.tag }}</span>\n </td>\n <td class=\"sd-autoid-inspector__state-cell\">\n @if (el.state.disabled !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-error]=\"el.state.disabled === 'true'\">\n disabled={{ el.state.disabled }}\n </span>\n }\n @if (el.state.loading !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-warning]=\"el.state.loading === 'true'\">\n loading={{ el.state.loading }}\n </span>\n }\n @if (el.state.empty !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-info]=\"el.state.empty === 'true'\">\n empty={{ el.state.empty }}\n </span>\n }\n @if (el.state.invalid !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-error]=\"el.state.invalid === 'true'\">\n invalid={{ el.state.invalid }}\n </span>\n }\n @if (el.state.opened !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-info]=\"el.state.opened === 'true'\">\n opened={{ el.state.opened }}\n </span>\n }\n @if (el.state.count !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\"> count={{ el.state.count }} </span>\n }\n @if (el.state.required !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip\" [class.is-warning]=\"el.state.required === 'true'\">\n required={{ el.state.required }}\n </span>\n }\n @if (el.state.maxlength !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\"> maxlength={{ el.state.maxlength }} </span>\n }\n @if (el.state.minlength !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\"> minlength={{ el.state.minlength }} </span>\n }\n @if (el.state.pattern !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-info\"> pattern={{ el.state.pattern }} </span>\n }\n @if (el.state.errorMessage !== undefined) {\n <span class=\"sd-autoid-inspector__state-chip is-error\"> error: {{ el.state.errorMessage }} </span>\n }\n </td>\n <td class=\"sd-autoid-inspector__value\" [matTooltip]=\"el.state.dataValue || el.text || ''\">\n {{ el.state.dataValue || el.text || '\u2014' }}\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n </ng-template>\n\n <ng-template #exportTpl>\n <div class=\"sd-autoid-inspector__export-grid\">\n <button type=\"button\" matTooltip=\"Copy JSON v\u00E0o clipboard\" (click)=\"copyJson()\">\n <mat-icon>content_copy</mat-icon>\n <span>Copy JSON</span>\n <small>clipboard</small>\n </button>\n <button type=\"button\" matTooltip=\"T\u1EA3i file JSON\" (click)=\"exportJson()\">\n <mat-icon>download</mat-icon>\n <span>JSON</span>\n <small>.json</small>\n </button>\n <button type=\"button\" matTooltip=\"T\u1EA3i file CSV\" (click)=\"exportCsv()\">\n <mat-icon>table_view</mat-icon>\n <span>CSV</span>\n <small>.csv</small>\n </button>\n <button type=\"button\" matTooltip=\"T\u1EA3i MD POM skeleton \u2014 cho AI vi\u1EBFt auto-test\" (click)=\"exportMdPom()\">\n <mat-icon>code</mat-icon>\n <span>MD POM</span>\n <small>AI test</small>\n </button>\n <button type=\"button\" matTooltip=\"T\u1EA3i MD reference table \u2014 group theo tag sd-*\" (click)=\"exportMdTable()\">\n <mat-icon>view_list</mat-icon>\n <span>MD Table</span>\n <small>reference</small>\n </button>\n </div>\n\n @if (e2eGeneratorHost()) {\n @let _e2eExporting = e2eExporting();\n <div class=\"sd-autoid-inspector__export-section\">\n <h4 class=\"sd-autoid-inspector__section-title\">\n <mat-icon class=\"is-info\">auto_awesome</mat-icon>\n T\u1EA1o E2E test\n </h4>\n <div class=\"sd-autoid-inspector__export-grid is-e2e\">\n <button\n type=\"button\"\n matTooltip=\"T\u1EA1o b\u1ED9 test Playwright (.zip)\"\n [disabled]=\"_e2eExporting !== null\"\n (click)=\"exportE2eTest('playwright')\">\n <mat-icon>{{ _e2eExporting === 'playwright' ? 'hourglass_empty' : 'play_arrow' }}</mat-icon>\n <span>Playwright</span>\n <small>.zip</small>\n </button>\n <button\n type=\"button\"\n matTooltip=\"T\u1EA1o b\u1ED9 test Robot Framework (.zip)\"\n [disabled]=\"_e2eExporting !== null\"\n (click)=\"openRobotExportContext()\">\n <mat-icon>{{ _e2eExporting === 'robot' ? 'hourglass_empty' : 'smart_toy' }}</mat-icon>\n <span>Robot Framework</span>\n <small>.zip</small>\n </button>\n </div>\n\n @if (robotContextOpen()) {\n <div class=\"sd-autoid-inspector__context-backdrop\" role=\"presentation\" (click)=\"closeRobotExportContext()\"></div>\n <section\n class=\"sd-autoid-inspector__context-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"sd-autoid-robot-context-title\">\n <header class=\"sd-autoid-inspector__context-header\">\n <div>\n <h4 id=\"sd-autoid-robot-context-title\">Ng\u1EEF c\u1EA3nh QC cho Robot Framework</h4>\n <small>Requirement, test case, test data</small>\n </div>\n <button type=\"button\" class=\"sd-autoid-inspector__icon-btn\" matTooltip=\"\u0110\u00F3ng\" (click)=\"closeRobotExportContext()\">\n <mat-icon>close</mat-icon>\n </button>\n </header>\n\n <div class=\"sd-autoid-inspector__context-form\">\n <label>\n <span>Component</span>\n <input\n type=\"text\"\n placeholder=\"SP-Wallet\"\n [ngModel]=\"robotExportContext().component || ''\"\n (ngModelChange)=\"updateRobotExportContext('component', $event)\" />\n </label>\n <label>\n <span>Story / Trace</span>\n <input\n type=\"text\"\n placeholder=\"US-001, AC-01\"\n [ngModel]=\"robotExportContext().storyLinkages || ''\"\n (ngModelChange)=\"updateRobotExportContext('storyLinkages', $event)\" />\n </label>\n <label class=\"is-full\">\n <span>Requirement / AC / BR</span>\n <textarea\n rows=\"4\"\n placeholder=\"D\u00E1n requirement ho\u1EB7c acceptance criteria...\"\n [ngModel]=\"robotExportContext().requirement || ''\"\n (ngModelChange)=\"updateRobotExportContext('requirement', $event)\"></textarea>\n </label>\n <label class=\"is-full\">\n <span>Test case QC \u0111\u00E3 \u0111\u1ECBnh ngh\u0129a</span>\n <textarea\n rows=\"4\"\n placeholder=\"TC-001: ...\"\n [ngModel]=\"robotExportContext().testCases || ''\"\n (ngModelChange)=\"updateRobotExportContext('testCases', $event)\"></textarea>\n </label>\n <label class=\"is-full\">\n <span>Test data</span>\n <textarea\n rows=\"3\"\n placeholder=\"wallet=V\u00ED A, amount=100000...\"\n [ngModel]=\"robotExportContext().testData || ''\"\n (ngModelChange)=\"updateRobotExportContext('testData', $event)\"></textarea>\n </label>\n <label class=\"is-full\">\n <span>Precondition</span>\n <textarea\n rows=\"2\"\n placeholder=\"User \u0111\u00E3 \u0111\u0103ng nh\u1EADp, d\u1EEF li\u1EC7u v\u00ED \u0111\u00E3 t\u1ED3n t\u1EA1i...\"\n [ngModel]=\"robotExportContext().precondition || ''\"\n (ngModelChange)=\"updateRobotExportContext('precondition', $event)\"></textarea>\n </label>\n <label class=\"is-full\">\n <span>Ghi ch\u00FA format QC</span>\n <textarea\n rows=\"3\"\n placeholder=\"Smoke=Y, Auto=Y, \u01B0u ti\u00EAn happy path...\"\n [ngModel]=\"robotExportContext().qcNotes || ''\"\n (ngModelChange)=\"updateRobotExportContext('qcNotes', $event)\"></textarea>\n </label>\n </div>\n\n <footer class=\"sd-autoid-inspector__context-actions\">\n <button type=\"button\" class=\"is-secondary\" [disabled]=\"_e2eExporting !== null\" (click)=\"closeRobotExportContext()\">\n Hu\u1EF7\n </button>\n <button\n type=\"button\"\n class=\"is-primary\"\n [disabled]=\"_e2eExporting !== null || !hasRobotExportContext()\"\n (click)=\"confirmRobotExport()\">\n <mat-icon>{{ _e2eExporting === 'robot' ? 'hourglass_empty' : 'download' }}</mat-icon>\n <span>\u0110\u1ED3ng \u00FD xu\u1EA5t</span>\n </button>\n </footer>\n </section>\n }\n\n @if (e2eExportOk(); as targetLabel) {\n <div class=\"sd-autoid-inspector__export-message is-success\">\u0110\u00E3 t\u1EA3i b\u1ED9 test {{ targetLabel }}.</div>\n }\n @if (e2eExportError(); as error) {\n <div class=\"sd-autoid-inspector__export-message is-error\">\n {{ error }}\n </div>\n }\n </div>\n }\n </ng-template>\n}\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;padding:0;border:none;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__section-title mat-icon.is-info{color:var(--sd-info, #2962ff)}.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__state-cell{display:flex;flex-wrap:wrap;gap:4px;align-items:center;min-width:0}.sd-autoid-inspector__state-chip{display:inline-block;font-family:JetBrains Mono,Fira Code,Consolas,monospace;font-size:10px;background:var(--sd-black100, #f2f2f2);color:var(--sd-black400, #757575);border:1px solid var(--sd-black200, #e6e6e6);padding:1px 6px;border-radius:999px;font-weight:500;white-space:nowrap}.sd-autoid-inspector__state-chip.is-error{background:var(--sd-error-light, #fed5d0);color:var(--sd-error-dark, #ba200e);border-color:var(--sd-error, #f82c13)}.sd-autoid-inspector__state-chip.is-warning{background:var(--sd-warning-light, #ffeacc);color:var(--sd-warning-dark, #bf7000);border-color:var(--sd-warning, #ff9600)}.sd-autoid-inspector__state-chip.is-info{background:var(--sd-info-light, #e7e9ff);color:var(--sd-info, #2962ff);border-color:var(--sd-info, #2962ff)}.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(auto-fit,minmax(112px,1fr));gap:8px}.sd-autoid-inspector__export-grid.is-e2e{grid-template-columns:repeat(auto-fit,minmax(150px,1fr))}.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:disabled{cursor:wait;opacity:.68;transform:none}.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)}.sd-autoid-inspector__export-section{margin-top:14px;padding-top:12px;border-top:1px solid var(--sd-black200, #e6e6e6)}.sd-autoid-inspector__context-backdrop{position:fixed;inset:0;z-index:10001;background:#141c2652}.sd-autoid-inspector__context-dialog{position:fixed;z-index:10002;top:50%;left:50%;width:min(720px,100vw - 28px);max-height:min(720px,100vh - 28px);transform:translate(-50%,-50%);display:flex;flex-direction:column;border:1px solid var(--sd-black200, #e6e6e6);border-radius:8px;background:#fff;box-shadow:0 18px 48px #0f172a38;overflow:hidden}.sd-autoid-inspector__context-header{display:flex;align-items:flex-start;justify-content:space-between;gap:12px;padding:14px 16px;border-bottom:1px solid var(--sd-black200, #e6e6e6)}.sd-autoid-inspector__context-header h4{margin:0;font-size:15px;font-weight:700;line-height:1.3;color:var(--sd-black500, #212121)}.sd-autoid-inspector__context-header small{display:block;margin-top:2px;font-size:11px;color:var(--sd-black400, #757575);line-height:1.3}.sd-autoid-inspector__context-form{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;padding:14px 16px;overflow:auto}.sd-autoid-inspector__context-form label{display:flex;flex-direction:column;gap:6px;min-width:0}.sd-autoid-inspector__context-form label.is-full{grid-column:1/-1}.sd-autoid-inspector__context-form span{font-size:12px;font-weight:600;color:var(--sd-black500, #212121);line-height:1.25}.sd-autoid-inspector__context-form input,.sd-autoid-inspector__context-form textarea{width:100%;min-width:0;border:1px solid var(--sd-black200, #e6e6e6);border-radius:6px;padding:8px 10px;color:var(--sd-black500, #212121);font:inherit;font-size:12px;line-height:1.4;outline:none;resize:vertical}.sd-autoid-inspector__context-form input:focus,.sd-autoid-inspector__context-form textarea:focus{border-color:var(--sd-primary, #2a66f4);box-shadow:0 0 0 2px #2a66f424}.sd-autoid-inspector__context-actions{display:flex;justify-content:flex-end;gap:8px;padding:12px 16px;border-top:1px solid var(--sd-black200, #e6e6e6)}.sd-autoid-inspector__context-actions button{min-height:34px;border-radius:6px;padding:0 12px;border:1px solid var(--sd-black200, #e6e6e6);background:#fff;color:var(--sd-black500, #212121);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:6px;font-size:12px;font-weight:600}.sd-autoid-inspector__context-actions button.is-primary{border-color:var(--sd-primary, #2a66f4);background:var(--sd-primary, #2a66f4);color:#fff}.sd-autoid-inspector__context-actions button:disabled{cursor:not-allowed;opacity:.62}.sd-autoid-inspector__context-actions button mat-icon{font-size:17px;width:17px;height:17px}.sd-autoid-inspector__export-message{margin-top:10px;border-radius:8px;padding:8px 10px;font-size:12px;line-height:1.35}.sd-autoid-inspector__export-message.is-success{background:var(--sd-success-light, #dbefdc);color:var(--sd-success-dark, #39833c);border:1px solid var(--sd-success, #4caf50)}.sd-autoid-inspector__export-message.is-error{background:var(--sd-error-light, #fed5d0);color:var(--sd-error-dark, #ba200e);border:1px solid var(--sd-error, #f82c13)}@media(max-width:560px){.sd-autoid-inspector__context-form{grid-template-columns:1fr}.sd-autoid-inspector__context-actions{flex-direction:column-reverse}.sd-autoid-inspector__context-actions button{width:100%}}\n"] }]
|
|
800
970
|
}], propDecorators: { onEscape: [{
|
|
801
971
|
type: HostListener,
|
|
802
972
|
args: ['document:keydown.escape']
|
|
@@ -806,5 +976,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
|
|
|
806
976
|
* Generated bundle index. Do not edit.
|
|
807
977
|
*/
|
|
808
978
|
|
|
809
|
-
export {
|
|
979
|
+
export { SD_AUTOID_INSPECTOR_CONFIGURATION, SdAutoidInspector };
|
|
810
980
|
//# sourceMappingURL=sdcorejs-angular-components-autoid-inspector.mjs.map
|