@reticular/speakable 1.1.1 → 1.3.0

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/dist/index.js CHANGED
@@ -329,10 +329,10 @@ function describeChange(change) {
329
329
  case "removed":
330
330
  return `Removed ${change.node?.role} "${change.node?.name}" from ${change.path}`;
331
331
  case "changed": {
332
- const descriptions = change.changes?.map((pc) => {
333
- const oldVal = formatValue(pc.oldValue);
334
- const newVal = formatValue(pc.newValue);
335
- return `${pc.property}: ${oldVal} \u2192 ${newVal}`;
332
+ const descriptions = change.changes?.map((pc2) => {
333
+ const oldVal = formatValue(pc2.oldValue);
334
+ const newVal = formatValue(pc2.newValue);
335
+ return `${pc2.property}: ${oldVal} \u2192 ${newVal}`;
336
336
  }) || [];
337
337
  return `Changed at ${change.path}: ${descriptions.join(", ")}`;
338
338
  }
@@ -438,7 +438,7 @@ function formatDiffForCI(diff) {
438
438
  break;
439
439
  case "changed":
440
440
  const props = change.changes?.map(
441
- (pc) => `${pc.property}:${formatValue2(pc.oldValue)}->${formatValue2(pc.newValue)}`
441
+ (pc2) => `${pc2.property}:${formatValue2(pc2.oldValue)}->${formatValue2(pc2.newValue)}`
442
442
  ).join(" | ") || "";
443
443
  lines.push(`CHANGED: ${change.path} | ${props}`);
444
444
  break;
@@ -489,21 +489,2210 @@ function hasAccessibilityRegression(diff) {
489
489
  }
490
490
  return false;
491
491
  }
492
+
493
+ // src/cli/colors.ts
494
+ import pc from "picocolors";
495
+ var identity = (s) => s;
496
+ function createColors(enabled) {
497
+ if (!enabled) {
498
+ return {
499
+ error: identity,
500
+ warning: identity,
501
+ info: identity,
502
+ success: identity,
503
+ heading: identity,
504
+ title: identity,
505
+ dim: identity,
506
+ roleName: identity,
507
+ stateName: identity,
508
+ elementName: identity,
509
+ sectionHeader: identity,
510
+ description: identity,
511
+ bold: identity,
512
+ enabled: false
513
+ };
514
+ }
515
+ return {
516
+ error: pc.red,
517
+ warning: pc.yellow,
518
+ info: pc.blue,
519
+ success: pc.green,
520
+ heading: (s) => pc.bold(pc.cyan(s)),
521
+ title: (s) => pc.bold(pc.cyan(s)),
522
+ dim: pc.dim,
523
+ roleName: pc.cyan,
524
+ stateName: pc.yellow,
525
+ elementName: (s) => pc.bold(pc.white(s)),
526
+ sectionHeader: (s) => pc.bold(pc.white(s)),
527
+ description: pc.dim,
528
+ bold: pc.bold,
529
+ enabled: true
530
+ };
531
+ }
532
+
533
+ // src/renderer/nvda-renderer.ts
534
+ function renderNVDA(model, colorize) {
535
+ const c = createColors(colorize ?? false);
536
+ const announcements = [];
537
+ renderNodeNVDA(model.root, announcements, c);
538
+ return announcements.join("\n");
539
+ }
540
+ function renderNodeNVDA(node, announcements, c) {
541
+ const announcement = formatNodeNVDA(node, c);
542
+ if (announcement) {
543
+ announcements.push(announcement);
544
+ }
545
+ for (const child of node.children) {
546
+ if (child.name && child.name === node.name && node.children.length === 1) {
547
+ for (const grandchild of child.children) {
548
+ renderNodeNVDA(grandchild, announcements, c);
549
+ }
550
+ continue;
551
+ }
552
+ renderNodeNVDA(child, announcements, c);
553
+ }
554
+ }
555
+ function formatNodeNVDA(node, c) {
556
+ const parts = [];
557
+ if (node.name) {
558
+ parts.push(c.elementName(node.name));
559
+ }
560
+ const roleText = formatRoleNVDA(node);
561
+ if (roleText) {
562
+ parts.push(c.roleName(roleText));
563
+ }
564
+ const stateText = formatStatesNVDA(node);
565
+ if (stateText) {
566
+ parts.push(c.stateName(stateText));
567
+ }
568
+ if (node.value) {
569
+ const valueText = node.value.text || String(node.value.current);
570
+ if (valueText) {
571
+ parts.push(valueText);
572
+ }
573
+ }
574
+ if (node.description) {
575
+ parts.push(c.description(node.description));
576
+ }
577
+ return parts.join(", ");
578
+ }
579
+ function formatRoleNVDA(node) {
580
+ const role = node.role;
581
+ switch (role) {
582
+ case "button":
583
+ return "button";
584
+ case "link":
585
+ return "link";
586
+ case "heading":
587
+ if (node.state.level) {
588
+ return `heading level ${node.state.level}`;
589
+ }
590
+ return "heading";
591
+ case "textbox":
592
+ return "edit";
593
+ case "checkbox":
594
+ return "checkbox";
595
+ case "radio":
596
+ return "radio button";
597
+ case "combobox":
598
+ return "combo box";
599
+ case "listbox":
600
+ return "list box";
601
+ case "option":
602
+ return "option";
603
+ case "list":
604
+ return "list";
605
+ case "listitem":
606
+ return "list item";
607
+ case "navigation":
608
+ return "navigation landmark";
609
+ case "main":
610
+ return "main landmark";
611
+ case "banner":
612
+ return "banner landmark";
613
+ case "contentinfo":
614
+ return "content information landmark";
615
+ case "region":
616
+ return "region landmark";
617
+ case "complementary":
618
+ return "complementary landmark";
619
+ case "form":
620
+ return "form landmark";
621
+ case "search":
622
+ return "search landmark";
623
+ case "img":
624
+ return "graphic";
625
+ case "article":
626
+ return "article";
627
+ case "generic":
628
+ return "";
629
+ case "staticText":
630
+ case "paragraph":
631
+ case "cell":
632
+ case "term":
633
+ case "definition":
634
+ case "caption":
635
+ return "";
636
+ case "blockquote":
637
+ return "block quote";
638
+ case "code":
639
+ return "code";
640
+ case "table":
641
+ return "table";
642
+ case "row":
643
+ return "row";
644
+ case "columnheader":
645
+ return "column header";
646
+ case "rowheader":
647
+ return "row header";
648
+ case "figure":
649
+ return "figure";
650
+ case "dialog":
651
+ return "dialog";
652
+ case "meter":
653
+ return "meter";
654
+ case "progressbar":
655
+ return "progress bar";
656
+ case "status":
657
+ return "status";
658
+ case "group":
659
+ return node.name ? "grouping" : "";
660
+ case "document":
661
+ return "document";
662
+ case "application":
663
+ return "embedded object";
664
+ case "separator":
665
+ return "separator";
666
+ default:
667
+ return role;
668
+ }
669
+ }
670
+ function formatStatesNVDA(node) {
671
+ const states = [];
672
+ if (node.state.expanded !== void 0) {
673
+ states.push(node.state.expanded ? "expanded" : "collapsed");
674
+ }
675
+ if (node.state.checked !== void 0) {
676
+ if (node.state.checked === "mixed") {
677
+ states.push("half checked");
678
+ } else {
679
+ states.push(node.state.checked ? "checked" : "not checked");
680
+ }
681
+ }
682
+ if (node.state.pressed !== void 0) {
683
+ if (node.state.pressed === "mixed") {
684
+ states.push("half pressed");
685
+ } else {
686
+ states.push(node.state.pressed ? "pressed" : "not pressed");
687
+ }
688
+ }
689
+ if (node.state.selected !== void 0) {
690
+ states.push(node.state.selected ? "selected" : "not selected");
691
+ }
692
+ if (node.state.disabled) {
693
+ states.push("unavailable");
694
+ }
695
+ if (node.state.invalid) {
696
+ states.push("invalid entry");
697
+ }
698
+ if (node.state.required) {
699
+ states.push("required");
700
+ }
701
+ if (node.state.readonly) {
702
+ states.push("read only");
703
+ }
704
+ if (node.state.busy) {
705
+ states.push("busy");
706
+ }
707
+ if (node.state.current) {
708
+ if (node.state.current === "page") {
709
+ states.push("current page");
710
+ } else if (node.state.current === "step") {
711
+ states.push("current step");
712
+ } else if (node.state.current === "location") {
713
+ states.push("current location");
714
+ } else if (node.state.current === "date") {
715
+ states.push("current date");
716
+ } else if (node.state.current === "time") {
717
+ states.push("current time");
718
+ } else if (node.state.current === "true") {
719
+ states.push("current");
720
+ }
721
+ }
722
+ if (node.state.grabbed !== void 0) {
723
+ states.push(node.state.grabbed ? "grabbed" : "not grabbed");
724
+ }
725
+ return states.join(", ");
726
+ }
727
+
728
+ // src/renderer/jaws-renderer.ts
729
+ function renderJAWS(model, colorize) {
730
+ const c = createColors(colorize ?? false);
731
+ const announcements = [];
732
+ renderNodeJAWS(model.root, announcements, c);
733
+ return announcements.join("\n");
734
+ }
735
+ function renderNodeJAWS(node, announcements, c) {
736
+ const announcement = formatNodeJAWS(node, c);
737
+ if (announcement) {
738
+ announcements.push(announcement);
739
+ }
740
+ for (const child of node.children) {
741
+ if (child.name && child.name === node.name && node.children.length === 1) {
742
+ for (const grandchild of child.children) {
743
+ renderNodeJAWS(grandchild, announcements, c);
744
+ }
745
+ continue;
746
+ }
747
+ renderNodeJAWS(child, announcements, c);
748
+ }
749
+ }
750
+ function formatNodeJAWS(node, c) {
751
+ const parts = [];
752
+ if (node.name) {
753
+ parts.push(c.elementName(node.name));
754
+ }
755
+ const roleText = formatRoleJAWS(node);
756
+ if (roleText) {
757
+ parts.push(c.roleName(roleText));
758
+ }
759
+ const stateText = formatStatesJAWS(node);
760
+ if (stateText) {
761
+ parts.push(c.stateName(stateText));
762
+ }
763
+ if (node.value) {
764
+ const valueText = node.value.text || String(node.value.current);
765
+ if (valueText) {
766
+ parts.push(valueText);
767
+ }
768
+ }
769
+ if (node.description) {
770
+ parts.push(c.description(node.description));
771
+ }
772
+ return parts.join(", ");
773
+ }
774
+ function formatRoleJAWS(node) {
775
+ const role = node.role;
776
+ switch (role) {
777
+ case "button":
778
+ return "button";
779
+ case "link":
780
+ return "clickable";
781
+ case "heading":
782
+ if (node.state.level) {
783
+ return `heading ${node.state.level}`;
784
+ }
785
+ return "heading";
786
+ case "textbox":
787
+ return "edit";
788
+ case "checkbox":
789
+ return "check box";
790
+ case "radio":
791
+ return "radio button";
792
+ case "combobox":
793
+ return "combo box";
794
+ case "listbox":
795
+ return "list box";
796
+ case "option":
797
+ return "option";
798
+ case "list":
799
+ return "list";
800
+ case "listitem":
801
+ return "list item";
802
+ case "navigation":
803
+ return "navigation region";
804
+ case "main":
805
+ return "main region";
806
+ case "banner":
807
+ return "banner region";
808
+ case "contentinfo":
809
+ return "content information region";
810
+ case "region":
811
+ return "region";
812
+ case "img":
813
+ return "graphic";
814
+ case "article":
815
+ return "article";
816
+ case "complementary":
817
+ return "complementary region";
818
+ case "form":
819
+ return "form";
820
+ case "search":
821
+ return "search region";
822
+ case "generic":
823
+ return "";
824
+ case "staticText":
825
+ case "paragraph":
826
+ case "term":
827
+ case "definition":
828
+ case "caption":
829
+ return "";
830
+ case "blockquote":
831
+ return "block quote";
832
+ case "code":
833
+ return "code";
834
+ case "table": {
835
+ const rows = node.children.filter((c) => c.role === "row");
836
+ const rowCount = rows.length;
837
+ const colCount = rows.length > 0 ? rows[0].children.length : 0;
838
+ return `table with ${rowCount} rows and ${colCount} columns`;
839
+ }
840
+ case "row": {
841
+ const pos = node.state.posinset ?? 0;
842
+ return `row ${pos}`;
843
+ }
844
+ case "cell": {
845
+ const pos = node.state.posinset ?? 0;
846
+ return `column ${pos}`;
847
+ }
848
+ case "columnheader":
849
+ return "column header";
850
+ case "rowheader":
851
+ return "row header";
852
+ case "figure":
853
+ return "figure";
854
+ case "dialog":
855
+ return "dialog";
856
+ case "meter":
857
+ return "meter";
858
+ case "progressbar":
859
+ return "progress bar";
860
+ case "status":
861
+ return "status";
862
+ case "group":
863
+ return node.name ? "group" : "";
864
+ case "document":
865
+ return "frame";
866
+ case "application":
867
+ return "embedded object";
868
+ case "separator":
869
+ return "separator";
870
+ default:
871
+ return role;
872
+ }
873
+ }
874
+ function formatStatesJAWS(node) {
875
+ const states = [];
876
+ if (node.state.expanded !== void 0) {
877
+ states.push(node.state.expanded ? "expanded" : "collapsed");
878
+ }
879
+ if (node.state.checked !== void 0) {
880
+ if (node.state.checked === "mixed") {
881
+ states.push("partially checked");
882
+ } else if (node.state.checked) {
883
+ states.push("checked");
884
+ } else {
885
+ states.push("not checked");
886
+ }
887
+ }
888
+ if (node.state.pressed !== void 0) {
889
+ if (node.state.pressed === "mixed") {
890
+ states.push("partially pressed");
891
+ } else if (node.state.pressed) {
892
+ states.push("pressed");
893
+ } else {
894
+ states.push("not pressed");
895
+ }
896
+ }
897
+ if (node.state.selected !== void 0) {
898
+ states.push(node.state.selected ? "selected" : "not selected");
899
+ }
900
+ if (node.state.disabled) {
901
+ states.push("unavailable");
902
+ }
903
+ if (node.state.invalid) {
904
+ states.push("invalid entry");
905
+ }
906
+ if (node.state.required) {
907
+ states.push("required");
908
+ }
909
+ if (node.state.readonly) {
910
+ states.push("read only");
911
+ }
912
+ if (node.state.busy) {
913
+ states.push("busy");
914
+ }
915
+ if (node.state.current) {
916
+ switch (node.state.current) {
917
+ case "page":
918
+ states.push("current page");
919
+ break;
920
+ case "step":
921
+ states.push("current step");
922
+ break;
923
+ case "location":
924
+ states.push("current location");
925
+ break;
926
+ case "date":
927
+ states.push("current date");
928
+ break;
929
+ case "time":
930
+ states.push("current time");
931
+ break;
932
+ case "true":
933
+ states.push("current");
934
+ break;
935
+ }
936
+ }
937
+ if (node.state.grabbed !== void 0) {
938
+ states.push(node.state.grabbed ? "grabbed" : "not grabbed");
939
+ }
940
+ return states.join(", ");
941
+ }
942
+
943
+ // src/renderer/voiceover-renderer.ts
944
+ function renderVoiceOver(model, colorize) {
945
+ const c = createColors(colorize ?? false);
946
+ const announcements = [];
947
+ renderNodeVoiceOver(model.root, announcements, c);
948
+ return announcements.join("\n");
949
+ }
950
+ function renderNodeVoiceOver(node, announcements, c) {
951
+ const announcement = formatNodeVoiceOver(node, c);
952
+ if (announcement) {
953
+ announcements.push(announcement);
954
+ }
955
+ for (const child of node.children) {
956
+ if (child.name && child.name === node.name && node.children.length === 1) {
957
+ for (const grandchild of child.children) {
958
+ renderNodeVoiceOver(grandchild, announcements, c);
959
+ }
960
+ continue;
961
+ }
962
+ renderNodeVoiceOver(child, announcements, c);
963
+ }
964
+ }
965
+ function formatNodeVoiceOver(node, c) {
966
+ const parts = [];
967
+ const roleFirst = shouldAnnounceRoleFirst(node.role);
968
+ if (roleFirst) {
969
+ const roleText = formatRoleVoiceOver(node);
970
+ if (roleText) {
971
+ parts.push(c.roleName(roleText));
972
+ }
973
+ if (node.name) {
974
+ parts.push(c.elementName(node.name));
975
+ }
976
+ } else {
977
+ if (node.name) {
978
+ parts.push(c.elementName(node.name));
979
+ }
980
+ const roleText = formatRoleVoiceOver(node);
981
+ if (roleText) {
982
+ parts.push(c.roleName(roleText));
983
+ }
984
+ }
985
+ const stateText = formatStatesVoiceOver(node);
986
+ if (stateText) {
987
+ parts.push(c.stateName(stateText));
988
+ }
989
+ if (node.value) {
990
+ const valueText = node.value.text || String(node.value.current);
991
+ if (valueText) {
992
+ parts.push(valueText);
993
+ }
994
+ }
995
+ if (node.description) {
996
+ parts.push(c.description(node.description));
997
+ }
998
+ return parts.join(", ");
999
+ }
1000
+ function shouldAnnounceRoleFirst(role) {
1001
+ return role === "heading" || role === "navigation" || role === "main" || role === "banner" || role === "contentinfo" || role === "complementary" || role === "region" || role === "form" || role === "search" || role === "blockquote" || role === "figure" || role === "dialog" || role === "group" || role === "document";
1002
+ }
1003
+ function formatRoleVoiceOver(node) {
1004
+ const role = node.role;
1005
+ switch (role) {
1006
+ case "button":
1007
+ return "button";
1008
+ case "link":
1009
+ return "link";
1010
+ case "heading":
1011
+ if (node.state.level) {
1012
+ return `heading level ${node.state.level}`;
1013
+ }
1014
+ return "heading";
1015
+ case "textbox":
1016
+ return "edit text";
1017
+ case "checkbox":
1018
+ return "checkbox";
1019
+ case "radio":
1020
+ return "radio button";
1021
+ case "combobox":
1022
+ return "combo box";
1023
+ case "listbox":
1024
+ return "list box";
1025
+ case "option":
1026
+ return "option";
1027
+ case "list":
1028
+ return "list";
1029
+ case "listitem":
1030
+ return "item";
1031
+ case "navigation":
1032
+ return "navigation";
1033
+ case "main":
1034
+ return "main";
1035
+ case "banner":
1036
+ return "banner";
1037
+ case "contentinfo":
1038
+ return "content information";
1039
+ case "region":
1040
+ return "region";
1041
+ case "complementary":
1042
+ return "complementary";
1043
+ case "form":
1044
+ return "form";
1045
+ case "search":
1046
+ return "search";
1047
+ case "img":
1048
+ return "image";
1049
+ case "article":
1050
+ return "article";
1051
+ case "generic":
1052
+ return "";
1053
+ case "staticText":
1054
+ case "paragraph":
1055
+ case "cell":
1056
+ case "term":
1057
+ case "definition":
1058
+ case "caption":
1059
+ return "";
1060
+ case "blockquote":
1061
+ return "blockquote";
1062
+ case "code":
1063
+ return "code";
1064
+ case "table": {
1065
+ const rows = node.children.filter((c) => c.role === "row");
1066
+ const rowCount = rows.length;
1067
+ const colCount = rows.length > 0 ? rows[0].children.length : 0;
1068
+ return `table, ${rowCount} rows, ${colCount} columns`;
1069
+ }
1070
+ case "row":
1071
+ return "row";
1072
+ case "columnheader":
1073
+ return "column header";
1074
+ case "rowheader":
1075
+ return "row header";
1076
+ case "figure":
1077
+ return "figure";
1078
+ case "dialog":
1079
+ return "web dialog";
1080
+ case "meter":
1081
+ return "level indicator";
1082
+ case "progressbar":
1083
+ return "progress indicator";
1084
+ case "status":
1085
+ return "status";
1086
+ case "group":
1087
+ return node.name ? "group" : "";
1088
+ case "document":
1089
+ return "frame";
1090
+ case "application":
1091
+ return "embedded object";
1092
+ case "separator":
1093
+ return "separator";
1094
+ default:
1095
+ return role;
1096
+ }
1097
+ }
1098
+ function formatStatesVoiceOver(node) {
1099
+ const states = [];
1100
+ if (node.state.expanded !== void 0) {
1101
+ states.push(node.state.expanded ? "expanded" : "collapsed");
1102
+ }
1103
+ if (node.state.checked !== void 0) {
1104
+ if (node.state.checked === "mixed") {
1105
+ states.push("mixed");
1106
+ } else {
1107
+ states.push(node.state.checked ? "checked" : "unchecked");
1108
+ }
1109
+ }
1110
+ if (node.state.pressed !== void 0) {
1111
+ if (node.state.pressed === "mixed") {
1112
+ states.push("mixed");
1113
+ } else {
1114
+ states.push(node.state.pressed ? "pressed" : "not pressed");
1115
+ }
1116
+ }
1117
+ if (node.state.selected !== void 0) {
1118
+ states.push(node.state.selected ? "selected" : "unselected");
1119
+ }
1120
+ if (node.state.disabled) {
1121
+ states.push("dimmed");
1122
+ }
1123
+ if (node.state.invalid) {
1124
+ states.push("invalid data");
1125
+ }
1126
+ if (node.state.required) {
1127
+ states.push("required");
1128
+ }
1129
+ if (node.state.readonly) {
1130
+ states.push("read only");
1131
+ }
1132
+ if (node.state.busy) {
1133
+ states.push("busy");
1134
+ }
1135
+ if (node.state.current) {
1136
+ if (node.state.current === "page") {
1137
+ states.push("current page");
1138
+ } else if (node.state.current === "step") {
1139
+ states.push("current step");
1140
+ } else if (node.state.current === "location") {
1141
+ states.push("current location");
1142
+ } else if (node.state.current === "date") {
1143
+ states.push("current date");
1144
+ } else if (node.state.current === "time") {
1145
+ states.push("current time");
1146
+ } else if (node.state.current === "true") {
1147
+ states.push("current");
1148
+ }
1149
+ }
1150
+ if (node.state.grabbed !== void 0) {
1151
+ states.push(node.state.grabbed ? "grabbed" : "not grabbed");
1152
+ }
1153
+ return states.join(", ");
1154
+ }
1155
+
1156
+ // src/renderer/narrator-renderer.ts
1157
+ function renderNarrator(model, colorize) {
1158
+ const c = createColors(colorize ?? false);
1159
+ const announcements = [];
1160
+ renderNodeNarrator(model.root, announcements, c);
1161
+ return announcements.join("\n");
1162
+ }
1163
+ function renderNodeNarrator(node, announcements, c) {
1164
+ const announcement = formatNodeNarrator(node, c);
1165
+ if (announcement) {
1166
+ announcements.push(announcement);
1167
+ }
1168
+ for (const child of node.children) {
1169
+ if (child.name && child.name === node.name && node.children.length === 1) {
1170
+ for (const grandchild of child.children) {
1171
+ renderNodeNarrator(grandchild, announcements, c);
1172
+ }
1173
+ continue;
1174
+ }
1175
+ renderNodeNarrator(child, announcements, c);
1176
+ }
1177
+ }
1178
+ function formatNodeNarrator(node, c) {
1179
+ const parts = [];
1180
+ const roleFirst = shouldAnnounceRoleFirst2(node.role);
1181
+ if (roleFirst) {
1182
+ const roleText = formatRoleNarrator(node);
1183
+ if (roleText) {
1184
+ parts.push(c.roleName(roleText));
1185
+ }
1186
+ if (node.name) {
1187
+ parts.push(c.elementName(node.name));
1188
+ }
1189
+ } else {
1190
+ if (node.name) {
1191
+ parts.push(c.elementName(node.name));
1192
+ }
1193
+ const roleText = formatRoleNarrator(node);
1194
+ if (roleText) {
1195
+ parts.push(c.roleName(roleText));
1196
+ }
1197
+ }
1198
+ const stateText = formatStatesNarrator(node);
1199
+ if (stateText) {
1200
+ parts.push(c.stateName(stateText));
1201
+ }
1202
+ if (node.value) {
1203
+ const valueText = node.value.text || String(node.value.current);
1204
+ if (valueText) {
1205
+ parts.push(valueText);
1206
+ }
1207
+ }
1208
+ if (node.description) {
1209
+ parts.push(c.description(node.description));
1210
+ }
1211
+ return parts.join(", ");
1212
+ }
1213
+ function shouldAnnounceRoleFirst2(role) {
1214
+ const roleFirstRoles = [
1215
+ "link",
1216
+ "heading",
1217
+ "navigation",
1218
+ "main",
1219
+ "banner",
1220
+ "contentinfo",
1221
+ "complementary",
1222
+ "region",
1223
+ "form",
1224
+ "search",
1225
+ "blockquote",
1226
+ "figure",
1227
+ "dialog",
1228
+ "group",
1229
+ "document"
1230
+ ];
1231
+ return roleFirstRoles.includes(role);
1232
+ }
1233
+ function formatRoleNarrator(node) {
1234
+ const role = node.role;
1235
+ switch (role) {
1236
+ case "button":
1237
+ return "button";
1238
+ case "link":
1239
+ return "link";
1240
+ case "heading":
1241
+ if (node.state.level) {
1242
+ return `Heading level ${node.state.level}`;
1243
+ }
1244
+ return "Heading";
1245
+ case "textbox":
1246
+ return "edit";
1247
+ case "checkbox":
1248
+ return "check box";
1249
+ case "radio":
1250
+ return "radio button";
1251
+ case "combobox":
1252
+ return "combo box";
1253
+ case "listbox":
1254
+ return "list box";
1255
+ case "option":
1256
+ return "option";
1257
+ case "list":
1258
+ return "list";
1259
+ case "listitem":
1260
+ return "list item";
1261
+ case "navigation":
1262
+ return "navigation";
1263
+ case "main":
1264
+ return "main";
1265
+ case "banner":
1266
+ return "banner";
1267
+ case "contentinfo":
1268
+ return "content info";
1269
+ case "complementary":
1270
+ return "complementary";
1271
+ case "form":
1272
+ return "form";
1273
+ case "search":
1274
+ return "search";
1275
+ case "region":
1276
+ return "region";
1277
+ case "img":
1278
+ return "image";
1279
+ case "article":
1280
+ return "article";
1281
+ case "generic":
1282
+ return "";
1283
+ case "staticText":
1284
+ case "paragraph":
1285
+ case "cell":
1286
+ case "term":
1287
+ case "definition":
1288
+ case "caption":
1289
+ return "";
1290
+ case "blockquote":
1291
+ return "block quote";
1292
+ case "code":
1293
+ return "code";
1294
+ case "table": {
1295
+ const rows = node.children.filter((c) => c.role === "row");
1296
+ const rowCount = rows.length;
1297
+ const colCount = rows.length > 0 ? rows[0].children.length : 0;
1298
+ return `table, ${rowCount} rows, ${colCount} columns`;
1299
+ }
1300
+ case "row":
1301
+ return "row";
1302
+ case "columnheader":
1303
+ return "column header";
1304
+ case "rowheader":
1305
+ return "row header";
1306
+ case "figure":
1307
+ return "figure";
1308
+ case "dialog":
1309
+ return "dialog";
1310
+ case "meter":
1311
+ return "meter";
1312
+ case "progressbar":
1313
+ return "progress bar";
1314
+ case "status":
1315
+ return "status";
1316
+ case "group":
1317
+ return node.name ? "group" : "";
1318
+ case "document":
1319
+ return "document";
1320
+ case "application":
1321
+ return "application";
1322
+ case "separator":
1323
+ return "separator";
1324
+ default:
1325
+ return role;
1326
+ }
1327
+ }
1328
+ function formatStatesNarrator(node) {
1329
+ const states = [];
1330
+ if (node.state.expanded !== void 0) {
1331
+ states.push(node.state.expanded ? "expanded" : "collapsed");
1332
+ }
1333
+ if (node.state.checked !== void 0) {
1334
+ if (node.state.checked === "mixed") {
1335
+ states.push("partially selected");
1336
+ } else {
1337
+ states.push(node.state.checked ? "checked" : "unchecked");
1338
+ }
1339
+ }
1340
+ if (node.state.pressed !== void 0) {
1341
+ if (node.state.pressed === "mixed") {
1342
+ states.push("partially pressed");
1343
+ } else {
1344
+ states.push(node.state.pressed ? "pressed" : "not pressed");
1345
+ }
1346
+ }
1347
+ if (node.state.selected !== void 0) {
1348
+ states.push(node.state.selected ? "selected" : "not selected");
1349
+ }
1350
+ if (node.state.disabled) {
1351
+ states.push("disabled");
1352
+ }
1353
+ if (node.state.invalid) {
1354
+ states.push("invalid");
1355
+ }
1356
+ if (node.state.required) {
1357
+ states.push("required");
1358
+ }
1359
+ if (node.state.readonly) {
1360
+ states.push("read only");
1361
+ }
1362
+ if (node.state.busy) {
1363
+ states.push("busy");
1364
+ }
1365
+ if (node.state.current) {
1366
+ if (node.state.current === "page") {
1367
+ states.push("current page");
1368
+ } else if (node.state.current === "step") {
1369
+ states.push("current step");
1370
+ } else if (node.state.current === "location") {
1371
+ states.push("current location");
1372
+ } else if (node.state.current === "date") {
1373
+ states.push("current date");
1374
+ } else if (node.state.current === "time") {
1375
+ states.push("current time");
1376
+ } else if (node.state.current === "true") {
1377
+ states.push("current");
1378
+ }
1379
+ }
1380
+ if (node.state.grabbed !== void 0) {
1381
+ states.push(node.state.grabbed ? "grabbed" : "not grabbed");
1382
+ }
1383
+ return states.join(", ");
1384
+ }
1385
+
1386
+ // src/renderer/audit-renderer.ts
1387
+ function renderAuditReport(model, colorize) {
1388
+ const report = generateAuditReport(model);
1389
+ return formatAuditReport(report, model, colorize);
1390
+ }
1391
+ function generateAuditReport(model) {
1392
+ const statistics = {
1393
+ totalElements: 0,
1394
+ roleDistribution: {},
1395
+ landmarkCount: 0,
1396
+ headingCount: 0,
1397
+ interactiveCount: 0,
1398
+ focusableCount: 0,
1399
+ statesUsed: /* @__PURE__ */ new Set()
1400
+ };
1401
+ const landmarks = [];
1402
+ const headings = [];
1403
+ const interactiveElements = [];
1404
+ const issues = [];
1405
+ collectAuditData(model.root, statistics, landmarks, headings, interactiveElements);
1406
+ detectLandmarkIssues(landmarks, issues);
1407
+ detectHeadingIssues(headings, issues);
1408
+ detectInteractiveIssues(interactiveElements, issues);
1409
+ return {
1410
+ statistics,
1411
+ landmarks,
1412
+ headings,
1413
+ interactiveElements,
1414
+ issues
1415
+ };
1416
+ }
1417
+ function collectAuditData(node, statistics, landmarks, headings, interactiveElements) {
1418
+ if (node.role !== "generic") {
1419
+ statistics.totalElements++;
1420
+ statistics.roleDistribution[node.role] = (statistics.roleDistribution[node.role] || 0) + 1;
1421
+ if (isLandmark(node.role)) {
1422
+ statistics.landmarkCount++;
1423
+ landmarks.push(node);
1424
+ }
1425
+ if (node.role === "heading") {
1426
+ statistics.headingCount++;
1427
+ headings.push(node);
1428
+ }
1429
+ if (isInteractive(node.role)) {
1430
+ statistics.interactiveCount++;
1431
+ interactiveElements.push(node);
1432
+ }
1433
+ if (node.focus.focusable) {
1434
+ statistics.focusableCount++;
1435
+ }
1436
+ Object.keys(node.state).forEach((state) => {
1437
+ if (node.state[state] !== void 0) {
1438
+ statistics.statesUsed.add(state);
1439
+ }
1440
+ });
1441
+ }
1442
+ for (const child of node.children) {
1443
+ collectAuditData(child, statistics, landmarks, headings, interactiveElements);
1444
+ }
1445
+ }
1446
+ function isLandmark(role) {
1447
+ return [
1448
+ "navigation",
1449
+ "main",
1450
+ "banner",
1451
+ "contentinfo",
1452
+ "region",
1453
+ "complementary",
1454
+ "form",
1455
+ "search"
1456
+ ].includes(role);
1457
+ }
1458
+ function isInteractive(role) {
1459
+ return [
1460
+ "button",
1461
+ "link",
1462
+ "textbox",
1463
+ "checkbox",
1464
+ "radio",
1465
+ "combobox",
1466
+ "listbox",
1467
+ "option"
1468
+ ].includes(role);
1469
+ }
1470
+ function detectLandmarkIssues(landmarks, issues) {
1471
+ if (landmarks.length === 0) {
1472
+ issues.push({
1473
+ severity: "info",
1474
+ message: "No landmarks found",
1475
+ suggestion: "Consider adding semantic landmarks (main, nav, aside, etc.)"
1476
+ });
1477
+ }
1478
+ landmarks.forEach((landmark) => {
1479
+ if (!landmark.name && ["navigation", "region", "complementary", "form"].includes(landmark.role)) {
1480
+ issues.push({
1481
+ severity: "warning",
1482
+ message: `${landmark.role} landmark has no accessible name`,
1483
+ suggestion: `Add aria-label="${landmark.role === "navigation" ? "Main navigation" : "Descriptive name"}" to the ${landmark.role} element`,
1484
+ element: {
1485
+ role: landmark.role,
1486
+ name: landmark.name
1487
+ }
1488
+ });
1489
+ }
1490
+ });
1491
+ const landmarksByRole = /* @__PURE__ */ new Map();
1492
+ landmarks.forEach((landmark) => {
1493
+ if (!landmarksByRole.has(landmark.role)) {
1494
+ landmarksByRole.set(landmark.role, []);
1495
+ }
1496
+ landmarksByRole.get(landmark.role).push(landmark);
1497
+ });
1498
+ landmarksByRole.forEach((nodes, role) => {
1499
+ if (nodes.length > 1) {
1500
+ const allUnnamed = nodes.every((n) => !n.name);
1501
+ const allSameName = nodes.every((n) => n.name === nodes[0].name);
1502
+ if (allUnnamed || allSameName) {
1503
+ issues.push({
1504
+ severity: "warning",
1505
+ message: `Multiple ${role} landmarks without distinguishing names`,
1506
+ suggestion: `Add unique aria-label attributes to distinguish between ${role} landmarks`
1507
+ });
1508
+ }
1509
+ }
1510
+ });
1511
+ }
1512
+ function detectHeadingIssues(headings, issues) {
1513
+ if (headings.length === 0) {
1514
+ return;
1515
+ }
1516
+ const firstHeading = headings[0];
1517
+ if (firstHeading.state.level && firstHeading.state.level !== 1) {
1518
+ issues.push({
1519
+ severity: "error",
1520
+ message: `First heading is h${firstHeading.state.level} (should be h1)`,
1521
+ suggestion: `Change <h${firstHeading.state.level}> to <h1> or add h1 before it`,
1522
+ element: {
1523
+ role: firstHeading.role,
1524
+ name: firstHeading.name
1525
+ }
1526
+ });
1527
+ }
1528
+ for (let i = 1; i < headings.length; i++) {
1529
+ const prevLevel = headings[i - 1].state.level || 1;
1530
+ const currLevel = headings[i].state.level || 1;
1531
+ if (currLevel > prevLevel + 1) {
1532
+ issues.push({
1533
+ severity: "error",
1534
+ message: `Heading hierarchy violation: h${prevLevel} followed by h${currLevel} (skipped h${prevLevel + 1})`,
1535
+ suggestion: `Change h${currLevel} to h${prevLevel + 1} or add intermediate heading levels`,
1536
+ element: {
1537
+ role: headings[i].role,
1538
+ name: headings[i].name
1539
+ }
1540
+ });
1541
+ }
1542
+ }
1543
+ }
1544
+ function detectInteractiveIssues(elements, issues) {
1545
+ elements.forEach((element) => {
1546
+ if (!element.name) {
1547
+ issues.push({
1548
+ severity: "error",
1549
+ message: `${element.role} has no accessible name`,
1550
+ suggestion: element.role === "button" ? "Add text content or aria-label to button" : element.role === "link" ? "Add text content or aria-label to link" : `Add aria-label to ${element.role}`,
1551
+ element: {
1552
+ role: element.role,
1553
+ name: element.name
1554
+ }
1555
+ });
1556
+ }
1557
+ if (element.role === "textbox" && !element.name) {
1558
+ issues.push({
1559
+ severity: "error",
1560
+ message: "Input has no associated label",
1561
+ suggestion: "Add <label> element or aria-label attribute",
1562
+ element: {
1563
+ role: element.role,
1564
+ name: element.name
1565
+ }
1566
+ });
1567
+ }
1568
+ });
1569
+ }
1570
+ function formatAuditReport(report, model, colorize) {
1571
+ const c = createColors(colorize ?? false);
1572
+ const lines = [];
1573
+ lines.push(c.title("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
1574
+ lines.push(c.title("\u2551 ACCESSIBILITY AUDIT REPORT \u2551"));
1575
+ lines.push(c.title("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
1576
+ lines.push("");
1577
+ lines.push(c.dim(`Analyzed: ${model.metadata.extractedAt}`));
1578
+ lines.push("");
1579
+ lines.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
1580
+ lines.push(c.heading("\u{1F4CD} LANDMARK STRUCTURE"));
1581
+ lines.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
1582
+ lines.push("");
1583
+ if (report.landmarks.length === 0) {
1584
+ lines.push(c.error("\u2717 No landmarks found"));
1585
+ } else {
1586
+ lines.push(c.success(`\u2713 ${report.landmarks.length} landmark(s) found`));
1587
+ lines.push("");
1588
+ report.landmarks.forEach((landmark) => {
1589
+ const name = landmark.name ? `"${landmark.name}"` : "(unnamed)";
1590
+ lines.push(`${landmark.role} ${name}`);
1591
+ });
1592
+ }
1593
+ lines.push("");
1594
+ lines.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
1595
+ lines.push(c.heading("\u{1F4D1} HEADING HIERARCHY"));
1596
+ lines.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
1597
+ lines.push("");
1598
+ if (report.headings.length === 0) {
1599
+ lines.push(c.info("\u2139 No headings found"));
1600
+ } else {
1601
+ const hasHierarchyIssues = report.issues.some(
1602
+ (i) => i.message.includes("heading") || i.message.includes("h1") || i.message.includes("h2")
1603
+ );
1604
+ lines.push(`${hasHierarchyIssues ? c.error("\u2717") : c.success("\u2713")} ${report.headings.length} heading(s) found ${hasHierarchyIssues ? "(HIERARCHY VIOLATION)" : "(proper hierarchy)"}`);
1605
+ lines.push("");
1606
+ report.headings.forEach((heading) => {
1607
+ const level = heading.state.level || 1;
1608
+ const indent = " ".repeat(level - 1);
1609
+ lines.push(`${indent}h${level} "${heading.name}"`);
1610
+ });
1611
+ }
1612
+ lines.push("");
1613
+ lines.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
1614
+ lines.push(c.heading("\u{1F3AF} INTERACTIVE ELEMENTS"));
1615
+ lines.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
1616
+ lines.push("");
1617
+ if (report.interactiveElements.length === 0) {
1618
+ lines.push(c.info("\u2139 No interactive elements found"));
1619
+ } else {
1620
+ lines.push(c.success(`\u2713 ${report.interactiveElements.length} interactive element(s) found`));
1621
+ lines.push("");
1622
+ const byRole = /* @__PURE__ */ new Map();
1623
+ report.interactiveElements.forEach((el) => {
1624
+ if (!byRole.has(el.role)) {
1625
+ byRole.set(el.role, []);
1626
+ }
1627
+ byRole.get(el.role).push(el);
1628
+ });
1629
+ byRole.forEach((elements, role) => {
1630
+ lines.push(`${role}s (${elements.length}):`);
1631
+ elements.forEach((el) => {
1632
+ const name = el.name ? `"${el.name}"` : "(unnamed)";
1633
+ const states = Object.keys(el.state).filter((k) => el.state[k] !== void 0);
1634
+ const stateStr = states.length > 0 ? ` [${states.join(", ")}]` : "";
1635
+ lines.push(` \u2022 ${name}${stateStr}`);
1636
+ });
1637
+ });
1638
+ }
1639
+ lines.push("");
1640
+ lines.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
1641
+ lines.push(c.heading("\u{1F4CA} SUMMARY STATISTICS"));
1642
+ lines.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
1643
+ lines.push("");
1644
+ lines.push(`Total accessible elements: ${report.statistics.totalElements}`);
1645
+ lines.push("");
1646
+ lines.push("Role distribution:");
1647
+ Object.entries(report.statistics.roleDistribution).sort((a, b) => b[1] - a[1]).forEach(([role, count]) => {
1648
+ lines.push(c.dim(` \u2022 ${role}: ${count}`));
1649
+ });
1650
+ lines.push("");
1651
+ lines.push(c.dim(`Focusable elements: ${report.statistics.focusableCount}`));
1652
+ lines.push("");
1653
+ if (report.issues.length > 0) {
1654
+ lines.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
1655
+ lines.push(c.heading("\u26A0\uFE0F ACCESSIBILITY ISSUES"));
1656
+ lines.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
1657
+ lines.push("");
1658
+ const errors = report.issues.filter((i) => i.severity === "error");
1659
+ const warnings = report.issues.filter((i) => i.severity === "warning");
1660
+ const infos = report.issues.filter((i) => i.severity === "info");
1661
+ if (errors.length > 0) {
1662
+ lines.push(c.error(`\u2717 Error (${errors.length}):`));
1663
+ errors.forEach((issue) => {
1664
+ lines.push(c.error(` \u2022 ${issue.message}`));
1665
+ if (issue.suggestion) {
1666
+ lines.push(` \u2192 ${issue.suggestion}`);
1667
+ }
1668
+ });
1669
+ lines.push("");
1670
+ }
1671
+ if (warnings.length > 0) {
1672
+ lines.push(c.warning(`\u26A0 Warning (${warnings.length}):`));
1673
+ warnings.forEach((issue) => {
1674
+ lines.push(c.warning(` \u2022 ${issue.message}`));
1675
+ if (issue.suggestion) {
1676
+ lines.push(` \u2192 ${issue.suggestion}`);
1677
+ }
1678
+ });
1679
+ lines.push("");
1680
+ }
1681
+ if (infos.length > 0) {
1682
+ lines.push(c.info(`\u2139 Info (${infos.length}):`));
1683
+ infos.forEach((issue) => {
1684
+ lines.push(c.info(` \u2022 ${issue.message}`));
1685
+ if (issue.suggestion) {
1686
+ lines.push(` \u2192 ${issue.suggestion}`);
1687
+ }
1688
+ });
1689
+ lines.push("");
1690
+ }
1691
+ }
1692
+ lines.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
1693
+ lines.push(c.success("\u2705 OVERALL ASSESSMENT"));
1694
+ lines.push("\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501");
1695
+ lines.push("");
1696
+ const errorCount = report.issues.filter((i) => i.severity === "error").length;
1697
+ const warningCount = report.issues.filter((i) => i.severity === "warning").length;
1698
+ if (errorCount === 0 && warningCount === 0) {
1699
+ lines.push(c.success("Excellent! No critical issues found."));
1700
+ } else if (errorCount > 0) {
1701
+ lines.push(`Critical accessibility issues found:`);
1702
+ lines.push(c.error(` \u2717 ${errorCount} error(s)`));
1703
+ if (warningCount > 0) {
1704
+ lines.push(c.warning(` \u26A0 ${warningCount} warning(s)`));
1705
+ }
1706
+ lines.push("");
1707
+ lines.push("Recommendation: Address errors before deployment");
1708
+ } else {
1709
+ lines.push(`Good structure with minor improvements needed:`);
1710
+ lines.push(c.warning(` \u26A0 ${warningCount} warning(s)`));
1711
+ }
1712
+ return lines.join("\n");
1713
+ }
1714
+
1715
+ // src/parser/html-parser.ts
1716
+ import { JSDOM } from "jsdom";
1717
+ var ParsingError = class extends Error {
1718
+ constructor(message, cause) {
1719
+ super(message);
1720
+ this.cause = cause;
1721
+ this.name = "ParsingError";
1722
+ }
1723
+ };
1724
+ var MAX_INPUT_SIZE = 10 * 1024 * 1024;
1725
+ function parseHTML(html) {
1726
+ const warnings = [];
1727
+ const inputSize = Buffer.byteLength(html, "utf-8");
1728
+ if (inputSize > MAX_INPUT_SIZE) {
1729
+ throw new ParsingError(
1730
+ `Input exceeds maximum size of ${MAX_INPUT_SIZE / (1024 * 1024)} MB (received ${(inputSize / (1024 * 1024)).toFixed(1)} MB). Use CSS selectors to analyze specific sections of large documents.`
1731
+ );
1732
+ }
1733
+ try {
1734
+ const dom = new JSDOM(html, {
1735
+ contentType: "text/html",
1736
+ // Include useful defaults for accessibility tree extraction
1737
+ includeNodeLocations: false,
1738
+ storageQuota: 0
1739
+ });
1740
+ const document = dom.window.document;
1741
+ if (!document.documentElement) {
1742
+ warnings.push({
1743
+ message: "Document has no root element. HTML may be severely malformed."
1744
+ });
1745
+ }
1746
+ if (!document.body) {
1747
+ warnings.push({
1748
+ message: "Document has no body element. HTML may be malformed."
1749
+ });
1750
+ }
1751
+ return {
1752
+ document,
1753
+ warnings
1754
+ };
1755
+ } catch (error) {
1756
+ throw new ParsingError(
1757
+ `Failed to parse HTML: ${error instanceof Error ? error.message : String(error)}`,
1758
+ error instanceof Error ? error : void 0
1759
+ );
1760
+ }
1761
+ }
1762
+ function parseHTMLStrict(html) {
1763
+ const result = parseHTML(html);
1764
+ if (result.warnings.length > 0) {
1765
+ const warningMessages = result.warnings.map((w) => w.message).join("; ");
1766
+ throw new ParsingError(`HTML parsing warnings: ${warningMessages}`);
1767
+ }
1768
+ return result.document;
1769
+ }
1770
+ function validateHTMLNotEmpty(html) {
1771
+ if (!html || html.trim().length === 0) {
1772
+ throw new ParsingError("HTML input is empty or contains only whitespace");
1773
+ }
1774
+ }
1775
+
1776
+ // src/extractor/aria-name.ts
1777
+ function computeAccessibleName(element, visited = /* @__PURE__ */ new Set()) {
1778
+ const warnings = [];
1779
+ if (visited.has(element)) {
1780
+ warnings.push({
1781
+ message: "Circular reference detected in aria-labelledby chain",
1782
+ element
1783
+ });
1784
+ return { name: "", warnings };
1785
+ }
1786
+ visited.add(element);
1787
+ const labelledBy = element.getAttribute("aria-labelledby");
1788
+ if (labelledBy) {
1789
+ const name = computeNameFromLabelledBy(element, labelledBy, visited, warnings);
1790
+ return { name, warnings };
1791
+ }
1792
+ const ariaLabel = element.getAttribute("aria-label");
1793
+ if (ariaLabel && ariaLabel.trim()) {
1794
+ return { name: ariaLabel.trim(), warnings };
1795
+ }
1796
+ if (isFormControl(element)) {
1797
+ const labelName = computeNameFromLabel(element);
1798
+ if (labelName) {
1799
+ return { name: labelName, warnings };
1800
+ }
1801
+ }
1802
+ if (element.tagName.toLowerCase() === "img") {
1803
+ const alt = element.getAttribute("alt");
1804
+ if (alt !== null) {
1805
+ return { name: alt.trim(), warnings };
1806
+ }
1807
+ }
1808
+ if (supportsNameFromContent(element)) {
1809
+ const textName = computeNameFromContent(element, visited);
1810
+ if (textName) {
1811
+ return { name: textName, warnings };
1812
+ }
1813
+ }
1814
+ const title = element.getAttribute("title");
1815
+ if (title && title.trim()) {
1816
+ return { name: title.trim(), warnings };
1817
+ }
1818
+ return { name: "", warnings };
1819
+ }
1820
+ function computeNameFromLabelledBy(element, labelledBy, visited, warnings) {
1821
+ const document = element.ownerDocument;
1822
+ if (!document) {
1823
+ return "";
1824
+ }
1825
+ const ids = labelledBy.trim().split(/\s+/);
1826
+ const names = [];
1827
+ for (const id of ids) {
1828
+ if (!id) continue;
1829
+ const referencedElement = document.getElementById(id);
1830
+ if (!referencedElement) {
1831
+ warnings.push({
1832
+ message: `aria-labelledby references non-existent ID: ${id}`,
1833
+ element
1834
+ });
1835
+ continue;
1836
+ }
1837
+ const result = computeAccessibleName(referencedElement, new Set(visited));
1838
+ warnings.push(...result.warnings);
1839
+ const name = result.name || getTextContent(referencedElement);
1840
+ if (name) {
1841
+ names.push(name);
1842
+ }
1843
+ }
1844
+ return names.join(" ").trim();
1845
+ }
1846
+ function computeNameFromLabel(element) {
1847
+ const document = element.ownerDocument;
1848
+ if (!document) {
1849
+ return "";
1850
+ }
1851
+ const id = element.getAttribute("id");
1852
+ if (id) {
1853
+ const label = document.querySelector(`label[for="${id}"]`);
1854
+ if (label) {
1855
+ return getTextContent(label);
1856
+ }
1857
+ }
1858
+ const parentLabel = element.closest("label");
1859
+ if (parentLabel) {
1860
+ return getTextContent(parentLabel);
1861
+ }
1862
+ return "";
1863
+ }
1864
+ function computeNameFromContent(element, _visited) {
1865
+ return getTextContent(element);
1866
+ }
1867
+ function getTextContent(element) {
1868
+ if (element.getAttribute("aria-hidden") === "true") {
1869
+ return "";
1870
+ }
1871
+ let text = "";
1872
+ for (const node of Array.from(element.childNodes)) {
1873
+ if (node.nodeType === 3) {
1874
+ text += node.textContent || "";
1875
+ } else if (node.nodeType === 1) {
1876
+ const childElement = node;
1877
+ if (childElement.getAttribute("aria-hidden") === "true") {
1878
+ continue;
1879
+ }
1880
+ text += getTextContent(childElement);
1881
+ }
1882
+ }
1883
+ return text.trim();
1884
+ }
1885
+ function isFormControl(element) {
1886
+ const tagName = element.tagName.toLowerCase();
1887
+ return tagName === "input" || tagName === "textarea" || tagName === "select" || tagName === "button";
1888
+ }
1889
+ function supportsNameFromContent(element) {
1890
+ const tagName = element.tagName.toLowerCase();
1891
+ const role = element.getAttribute("role");
1892
+ const contentElements = [
1893
+ "button",
1894
+ "a",
1895
+ "h1",
1896
+ "h2",
1897
+ "h3",
1898
+ "h4",
1899
+ "h5",
1900
+ "h6",
1901
+ "summary",
1902
+ "figcaption",
1903
+ "legend",
1904
+ "caption",
1905
+ "th",
1906
+ "td",
1907
+ "li",
1908
+ "dt",
1909
+ "dd"
1910
+ ];
1911
+ if (contentElements.includes(tagName)) {
1912
+ return true;
1913
+ }
1914
+ const contentRoles = [
1915
+ "button",
1916
+ "link",
1917
+ "heading",
1918
+ "tab",
1919
+ "treeitem",
1920
+ "option",
1921
+ "row",
1922
+ "cell",
1923
+ "columnheader",
1924
+ "rowheader",
1925
+ "tooltip",
1926
+ "menuitem",
1927
+ "menuitemcheckbox",
1928
+ "menuitemradio"
1929
+ ];
1930
+ if (role && contentRoles.includes(role)) {
1931
+ return true;
1932
+ }
1933
+ return false;
1934
+ }
1935
+ function computeAccessibleDescription(element) {
1936
+ const warnings = [];
1937
+ const describedBy = element.getAttribute("aria-describedby");
1938
+ if (describedBy) {
1939
+ const description = computeDescriptionFromDescribedBy(
1940
+ element,
1941
+ describedBy,
1942
+ warnings
1943
+ );
1944
+ if (description) {
1945
+ return { description, warnings };
1946
+ }
1947
+ }
1948
+ const title = element.getAttribute("title");
1949
+ if (title && title.trim()) {
1950
+ return { description: title.trim(), warnings };
1951
+ }
1952
+ return { description: "", warnings };
1953
+ }
1954
+ function computeDescriptionFromDescribedBy(element, describedBy, warnings) {
1955
+ const document = element.ownerDocument;
1956
+ if (!document) {
1957
+ return "";
1958
+ }
1959
+ const ids = describedBy.trim().split(/\s+/);
1960
+ const descriptions = [];
1961
+ for (const id of ids) {
1962
+ if (!id) continue;
1963
+ const referencedElement = document.getElementById(id);
1964
+ if (!referencedElement) {
1965
+ warnings.push({
1966
+ message: `aria-describedby references non-existent ID: ${id}`,
1967
+ element
1968
+ });
1969
+ continue;
1970
+ }
1971
+ const text = getTextContent(referencedElement);
1972
+ if (text) {
1973
+ descriptions.push(text);
1974
+ }
1975
+ }
1976
+ return descriptions.join(" ").trim();
1977
+ }
1978
+
1979
+ // src/extractor/role-mapper.ts
1980
+ var IMPLICIT_ROLE_MAP = {
1981
+ // Interactive elements
1982
+ "button": "button",
1983
+ "a": "link",
1984
+ // Only if href attribute present
1985
+ // Form controls
1986
+ "input": "textbox",
1987
+ // Default, varies by type
1988
+ "textarea": "textbox",
1989
+ "select": "listbox",
1990
+ // Headings
1991
+ "h1": "heading",
1992
+ "h2": "heading",
1993
+ "h3": "heading",
1994
+ "h4": "heading",
1995
+ "h5": "heading",
1996
+ "h6": "heading",
1997
+ // Landmarks
1998
+ "nav": "navigation",
1999
+ "main": "main",
2000
+ "header": "banner",
2001
+ // Only if not nested in article/section
2002
+ "footer": "contentinfo",
2003
+ // Only if not nested in article/section
2004
+ "aside": "complementary",
2005
+ "form": "form",
2006
+ "section": "region",
2007
+ // Only if has accessible name
2008
+ // Lists
2009
+ "ul": "list",
2010
+ "ol": "list",
2011
+ "li": "listitem",
2012
+ // Images
2013
+ "img": "img",
2014
+ // Articles
2015
+ "article": "article",
2016
+ // Static content (new)
2017
+ "p": "paragraph",
2018
+ "blockquote": "blockquote",
2019
+ "code": "code",
2020
+ "pre": "code",
2021
+ // Tables (new)
2022
+ "table": "table",
2023
+ "tr": "row",
2024
+ "td": "cell",
2025
+ "th": "columnheader",
2026
+ // Default; scope="row" handled in computeImplicitRole
2027
+ // Definition lists (new)
2028
+ "dl": "list",
2029
+ "dt": "term",
2030
+ "dd": "definition",
2031
+ // Figures (new)
2032
+ "figure": "figure",
2033
+ "figcaption": "caption",
2034
+ // Disclosure (new)
2035
+ "details": "group",
2036
+ "summary": "button",
2037
+ // Dialogs and widgets (new)
2038
+ "dialog": "dialog",
2039
+ "meter": "meter",
2040
+ "progress": "progressbar",
2041
+ "output": "status",
2042
+ // Forms (new)
2043
+ "fieldset": "group",
2044
+ "legend": "caption",
2045
+ // Embedded content (new)
2046
+ "iframe": "document",
2047
+ "video": "application",
2048
+ "audio": "application",
2049
+ // Separators (new)
2050
+ "hr": "separator",
2051
+ // Table caption (new)
2052
+ "caption": "caption"
2053
+ };
2054
+ var INPUT_TYPE_ROLE_MAP = {
2055
+ "button": "button",
2056
+ "submit": "button",
2057
+ "reset": "button",
2058
+ "checkbox": "checkbox",
2059
+ "radio": "radio",
2060
+ "text": "textbox",
2061
+ "email": "textbox",
2062
+ "password": "textbox",
2063
+ "search": "textbox",
2064
+ "tel": "textbox",
2065
+ "url": "textbox",
2066
+ "number": "textbox"
2067
+ };
2068
+ function computeRole(element) {
2069
+ const warnings = [];
2070
+ const explicitRole = element.getAttribute("role");
2071
+ if (explicitRole) {
2072
+ const role = validateAndNormalizeRole(explicitRole, element, warnings);
2073
+ if (role) {
2074
+ return { role, warnings };
2075
+ }
2076
+ }
2077
+ const implicitRole = computeImplicitRole(element, warnings);
2078
+ return { role: implicitRole, warnings };
2079
+ }
2080
+ function validateAndNormalizeRole(roleAttr, element, warnings) {
2081
+ const role = roleAttr.trim().toLowerCase();
2082
+ if (role === "presentation" || role === "none") {
2083
+ return null;
2084
+ }
2085
+ if (SUPPORTED_ROLES.includes(role)) {
2086
+ return role;
2087
+ }
2088
+ warnings.push({
2089
+ message: `Invalid or unsupported role: "${roleAttr}". Falling back to implicit role.`,
2090
+ element
2091
+ });
2092
+ return null;
2093
+ }
2094
+ function computeImplicitRole(element, _warnings) {
2095
+ const tagName = element.tagName.toLowerCase();
2096
+ if (tagName === "input") {
2097
+ return computeInputRole(element);
2098
+ }
2099
+ if (tagName === "a") {
2100
+ return element.hasAttribute("href") ? "link" : null;
2101
+ }
2102
+ if (tagName === "header") {
2103
+ return isTopLevelLandmark(element) ? "banner" : null;
2104
+ }
2105
+ if (tagName === "footer") {
2106
+ return isTopLevelLandmark(element) ? "contentinfo" : null;
2107
+ }
2108
+ if (tagName === "th") {
2109
+ const scope = element.getAttribute("scope");
2110
+ return scope === "row" ? "rowheader" : "columnheader";
2111
+ }
2112
+ if (tagName === "section") {
2113
+ return element.hasAttribute("aria-label") || element.hasAttribute("aria-labelledby") ? "region" : null;
2114
+ }
2115
+ const implicitRole = IMPLICIT_ROLE_MAP[tagName];
2116
+ return implicitRole || null;
2117
+ }
2118
+ function computeInputRole(input) {
2119
+ const type = (input.getAttribute("type") || "text").toLowerCase();
2120
+ return INPUT_TYPE_ROLE_MAP[type] || "textbox";
2121
+ }
2122
+ function isTopLevelLandmark(element) {
2123
+ let parent = element.parentElement;
2124
+ while (parent) {
2125
+ const tagName = parent.tagName.toLowerCase();
2126
+ if (tagName === "article" || tagName === "section") {
2127
+ return false;
2128
+ }
2129
+ parent = parent.parentElement;
2130
+ }
2131
+ return true;
2132
+ }
2133
+ function isAccessible(element) {
2134
+ if (element.getAttribute("aria-hidden") === "true") {
2135
+ return false;
2136
+ }
2137
+ const role = element.getAttribute("role");
2138
+ if (role === "presentation" || role === "none") {
2139
+ return false;
2140
+ }
2141
+ const roleResult = computeRole(element);
2142
+ return roleResult.role !== null;
2143
+ }
2144
+
2145
+ // src/extractor/state-extractor.ts
2146
+ function extractState(element) {
2147
+ const warnings = [];
2148
+ const state = {};
2149
+ const expanded = extractBooleanAttribute(element, "aria-expanded", warnings);
2150
+ if (expanded !== void 0) {
2151
+ state.expanded = expanded;
2152
+ }
2153
+ const checked = extractTriStateAttribute(element, "aria-checked", warnings);
2154
+ if (checked !== void 0) {
2155
+ state.checked = checked;
2156
+ }
2157
+ const pressed = extractTriStateAttribute(element, "aria-pressed", warnings);
2158
+ if (pressed !== void 0) {
2159
+ state.pressed = pressed;
2160
+ }
2161
+ const selected = extractBooleanAttribute(element, "aria-selected", warnings);
2162
+ if (selected !== void 0) {
2163
+ state.selected = selected;
2164
+ }
2165
+ const disabled = extractBooleanAttribute(element, "aria-disabled", warnings);
2166
+ if (disabled !== void 0) {
2167
+ state.disabled = disabled;
2168
+ }
2169
+ const invalid = extractBooleanAttribute(element, "aria-invalid", warnings);
2170
+ if (invalid !== void 0) {
2171
+ state.invalid = invalid;
2172
+ }
2173
+ const required = extractBooleanAttribute(element, "aria-required", warnings);
2174
+ if (required !== void 0) {
2175
+ state.required = required;
2176
+ }
2177
+ const readonly = extractBooleanAttribute(element, "aria-readonly", warnings);
2178
+ if (readonly !== void 0) {
2179
+ state.readonly = readonly;
2180
+ }
2181
+ const busy = extractBooleanAttribute(element, "aria-busy", warnings);
2182
+ if (busy !== void 0) {
2183
+ state.busy = busy;
2184
+ }
2185
+ const current = extractCurrentAttribute(element, warnings);
2186
+ if (current !== void 0) {
2187
+ state.current = current;
2188
+ }
2189
+ const grabbed = extractBooleanAttribute(element, "aria-grabbed", warnings);
2190
+ if (grabbed !== void 0) {
2191
+ state.grabbed = grabbed;
2192
+ }
2193
+ const hidden = extractBooleanAttribute(element, "aria-hidden", warnings);
2194
+ if (hidden !== void 0) {
2195
+ state.hidden = hidden;
2196
+ }
2197
+ const level = extractLevelAttribute(element, warnings);
2198
+ if (level !== void 0) {
2199
+ state.level = level;
2200
+ }
2201
+ const posinset = extractNumberAttribute(element, "aria-posinset", warnings);
2202
+ if (posinset !== void 0) {
2203
+ state.posinset = posinset;
2204
+ }
2205
+ const setsize = extractNumberAttribute(element, "aria-setsize", warnings);
2206
+ if (setsize !== void 0) {
2207
+ state.setsize = setsize;
2208
+ }
2209
+ extractNativeStates(element, state, warnings);
2210
+ return { state, warnings };
2211
+ }
2212
+ function extractValue(element) {
2213
+ const warnings = [];
2214
+ const tagName = element.tagName.toLowerCase();
2215
+ if (tagName === "input") {
2216
+ return extractInputValue(element, warnings);
2217
+ }
2218
+ if (tagName === "textarea") {
2219
+ return extractTextareaValue(element, warnings);
2220
+ }
2221
+ if (tagName === "select") {
2222
+ return extractSelectValue(element, warnings);
2223
+ }
2224
+ const valueNow = element.getAttribute("aria-valuenow");
2225
+ if (valueNow !== null) {
2226
+ const current = parseFloat(valueNow);
2227
+ if (!isNaN(current)) {
2228
+ const valueMin = element.getAttribute("aria-valuemin");
2229
+ const valueMax = element.getAttribute("aria-valuemax");
2230
+ const valueText = element.getAttribute("aria-valuetext");
2231
+ return {
2232
+ value: {
2233
+ current,
2234
+ min: valueMin !== null ? parseFloat(valueMin) : void 0,
2235
+ max: valueMax !== null ? parseFloat(valueMax) : void 0,
2236
+ text: valueText || void 0
2237
+ },
2238
+ warnings
2239
+ };
2240
+ }
2241
+ }
2242
+ return { value: void 0, warnings };
2243
+ }
2244
+ function extractBooleanAttribute(element, attrName, warnings) {
2245
+ const value = element.getAttribute(attrName);
2246
+ if (value === null) {
2247
+ return void 0;
2248
+ }
2249
+ const normalized = value.toLowerCase().trim();
2250
+ if (normalized === "true") {
2251
+ return true;
2252
+ }
2253
+ if (normalized === "false") {
2254
+ return false;
2255
+ }
2256
+ warnings.push({
2257
+ message: `Invalid ${attrName} value: "${value}". Expected "true" or "false".`,
2258
+ element
2259
+ });
2260
+ return void 0;
2261
+ }
2262
+ function extractTriStateAttribute(element, attrName, warnings) {
2263
+ const value = element.getAttribute(attrName);
2264
+ if (value === null) {
2265
+ return void 0;
2266
+ }
2267
+ const normalized = value.toLowerCase().trim();
2268
+ if (normalized === "true") {
2269
+ return true;
2270
+ }
2271
+ if (normalized === "false") {
2272
+ return false;
2273
+ }
2274
+ if (normalized === "mixed") {
2275
+ return "mixed";
2276
+ }
2277
+ warnings.push({
2278
+ message: `Invalid ${attrName} value: "${value}". Expected "true", "false", or "mixed".`,
2279
+ element
2280
+ });
2281
+ return void 0;
2282
+ }
2283
+ function extractCurrentAttribute(element, warnings) {
2284
+ const value = element.getAttribute("aria-current");
2285
+ if (value === null) {
2286
+ return void 0;
2287
+ }
2288
+ const normalized = value.toLowerCase().trim();
2289
+ const validValues = ["page", "step", "location", "date", "time", "true", "false"];
2290
+ if (validValues.includes(normalized)) {
2291
+ return normalized === "false" ? false : normalized;
2292
+ }
2293
+ warnings.push({
2294
+ message: `Invalid aria-current value: "${value}". Expected one of: ${validValues.join(", ")}.`,
2295
+ element
2296
+ });
2297
+ return void 0;
2298
+ }
2299
+ function extractLevelAttribute(element, warnings) {
2300
+ const ariaLevel = element.getAttribute("aria-level");
2301
+ if (ariaLevel !== null) {
2302
+ const level = parseInt(ariaLevel, 10);
2303
+ if (!isNaN(level) && level >= 1 && level <= 6) {
2304
+ return level;
2305
+ }
2306
+ warnings.push({
2307
+ message: `Invalid aria-level value: "${ariaLevel}". Expected integer 1-6.`,
2308
+ element
2309
+ });
2310
+ return void 0;
2311
+ }
2312
+ const tagName = element.tagName.toLowerCase();
2313
+ const match = tagName.match(/^h([1-6])$/);
2314
+ if (match) {
2315
+ return parseInt(match[1], 10);
2316
+ }
2317
+ return void 0;
2318
+ }
2319
+ function extractNumberAttribute(element, attrName, warnings) {
2320
+ const value = element.getAttribute(attrName);
2321
+ if (value === null) {
2322
+ return void 0;
2323
+ }
2324
+ const num = parseInt(value, 10);
2325
+ if (isNaN(num) || num < 1) {
2326
+ warnings.push({
2327
+ message: `Invalid ${attrName} value: "${value}". Expected positive integer.`,
2328
+ element
2329
+ });
2330
+ return void 0;
2331
+ }
2332
+ return num;
2333
+ }
2334
+ function extractNativeStates(element, state, _warnings) {
2335
+ const tagName = element.tagName.toLowerCase();
2336
+ if (tagName === "input") {
2337
+ const input = element;
2338
+ const type = input.type.toLowerCase();
2339
+ if (type === "checkbox" || type === "radio") {
2340
+ if (state.checked === void 0) {
2341
+ state.checked = input.checked;
2342
+ }
2343
+ }
2344
+ }
2345
+ if ("disabled" in element) {
2346
+ const htmlElement = element;
2347
+ if (state.disabled === void 0 && htmlElement.disabled) {
2348
+ state.disabled = true;
2349
+ }
2350
+ }
2351
+ if ("required" in element) {
2352
+ const htmlElement = element;
2353
+ if (state.required === void 0 && htmlElement.required) {
2354
+ state.required = true;
2355
+ }
2356
+ }
2357
+ if ("readOnly" in element) {
2358
+ const htmlElement = element;
2359
+ if (state.readonly === void 0 && htmlElement.readOnly) {
2360
+ state.readonly = true;
2361
+ }
2362
+ }
2363
+ }
2364
+ function extractInputValue(input, warnings) {
2365
+ const type = input.type.toLowerCase();
2366
+ if (type === "checkbox" || type === "radio") {
2367
+ return { value: void 0, warnings };
2368
+ }
2369
+ if (type === "button" || type === "submit" || type === "reset") {
2370
+ return { value: void 0, warnings };
2371
+ }
2372
+ const value = input.value;
2373
+ if (value) {
2374
+ return {
2375
+ value: {
2376
+ current: value,
2377
+ text: value
2378
+ },
2379
+ warnings
2380
+ };
2381
+ }
2382
+ return { value: void 0, warnings };
2383
+ }
2384
+ function extractTextareaValue(textarea, warnings) {
2385
+ const value = textarea.value;
2386
+ if (value) {
2387
+ return {
2388
+ value: {
2389
+ current: value,
2390
+ text: value
2391
+ },
2392
+ warnings
2393
+ };
2394
+ }
2395
+ return { value: void 0, warnings };
2396
+ }
2397
+ function extractSelectValue(select, warnings) {
2398
+ const value = select.value;
2399
+ const selectedOption = select.options[select.selectedIndex];
2400
+ const text = selectedOption?.textContent || value;
2401
+ if (value) {
2402
+ return {
2403
+ value: {
2404
+ current: value,
2405
+ text
2406
+ },
2407
+ warnings
2408
+ };
2409
+ }
2410
+ return { value: void 0, warnings };
2411
+ }
2412
+
2413
+ // src/extractor/focus-extractor.ts
2414
+ var NATIVELY_FOCUSABLE_ELEMENTS = [
2415
+ "a",
2416
+ "button",
2417
+ "input",
2418
+ "select",
2419
+ "textarea",
2420
+ "area",
2421
+ "iframe",
2422
+ "object",
2423
+ "embed",
2424
+ "audio",
2425
+ "video"
2426
+ ];
2427
+ function extractFocusInfo(element) {
2428
+ const tabindexAttr = element.getAttribute("tabindex");
2429
+ const tabindex = tabindexAttr !== null ? parseInt(tabindexAttr, 10) : void 0;
2430
+ const focusable = isFocusable(element, tabindex);
2431
+ return {
2432
+ focusable,
2433
+ ...tabindexAttr !== null && !isNaN(tabindex) && { tabindex }
2434
+ };
2435
+ }
2436
+ function isFocusable(element, tabindex) {
2437
+ const tagName = element.tagName.toLowerCase();
2438
+ if (isDisabled(element)) {
2439
+ return false;
2440
+ }
2441
+ if (tabindex !== void 0 && !isNaN(tabindex)) {
2442
+ return true;
2443
+ }
2444
+ if (isNativelyFocusable(element, tagName)) {
2445
+ return true;
2446
+ }
2447
+ return false;
2448
+ }
2449
+ function isNativelyFocusable(element, tagName) {
2450
+ if (tagName === "a" || tagName === "area") {
2451
+ return element.hasAttribute("href");
2452
+ }
2453
+ if (NATIVELY_FOCUSABLE_ELEMENTS.includes(tagName)) {
2454
+ return true;
2455
+ }
2456
+ return false;
2457
+ }
2458
+ function isDisabled(element) {
2459
+ if ("disabled" in element) {
2460
+ const htmlElement = element;
2461
+ if (htmlElement.disabled) {
2462
+ return true;
2463
+ }
2464
+ }
2465
+ const ariaDisabled = element.getAttribute("aria-disabled");
2466
+ if (ariaDisabled === "true") {
2467
+ return true;
2468
+ }
2469
+ return false;
2470
+ }
2471
+
2472
+ // src/extractor/tree-builder.ts
2473
+ var SelectorError = class extends Error {
2474
+ constructor(message) {
2475
+ super(message);
2476
+ this.name = "SelectorError";
2477
+ }
2478
+ };
2479
+ function isHidden(element) {
2480
+ if (element.getAttribute("aria-hidden") === "true") return true;
2481
+ if (typeof getComputedStyle === "function") {
2482
+ try {
2483
+ const style = getComputedStyle(element);
2484
+ if (style.display === "none") return true;
2485
+ if (style.visibility === "hidden") return true;
2486
+ } catch {
2487
+ }
2488
+ }
2489
+ return false;
2490
+ }
2491
+ function processChildNode(node, children, warnings) {
2492
+ if (node.nodeType === 3) {
2493
+ const text = (node.textContent || "").trim();
2494
+ if (text.length > 0) {
2495
+ children.push({
2496
+ role: "staticText",
2497
+ name: text,
2498
+ state: {},
2499
+ focus: { focusable: false },
2500
+ children: []
2501
+ });
2502
+ }
2503
+ return;
2504
+ }
2505
+ if (node.nodeType === 1) {
2506
+ const element = node;
2507
+ if (isHidden(element)) return;
2508
+ if (!isAccessible(element)) {
2509
+ const grandchildren = collectChildrenFromRolelessElement(element, warnings);
2510
+ children.push(...grandchildren);
2511
+ return;
2512
+ }
2513
+ const childNode = buildNodeRecursive(element, warnings);
2514
+ if (childNode) children.push(childNode);
2515
+ }
2516
+ }
2517
+ function collectChildrenFromRolelessElement(element, warnings) {
2518
+ const children = [];
2519
+ if (element.shadowRoot) {
2520
+ for (const child of Array.from(element.shadowRoot.childNodes)) {
2521
+ processChildNode(child, children, warnings);
2522
+ }
2523
+ }
2524
+ for (const child of Array.from(element.childNodes)) {
2525
+ processChildNode(child, children, warnings);
2526
+ }
2527
+ return children;
2528
+ }
2529
+ function buildAccessibilityTree(rootElement, sourceHash) {
2530
+ const warnings = [];
2531
+ if (isHidden(rootElement)) {
2532
+ const model2 = {
2533
+ version: CURRENT_MODEL_VERSION,
2534
+ root: {
2535
+ role: "generic",
2536
+ name: "",
2537
+ state: {},
2538
+ focus: { focusable: false },
2539
+ children: []
2540
+ },
2541
+ metadata: {
2542
+ extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
2543
+ ...sourceHash && { sourceHash }
2544
+ }
2545
+ };
2546
+ return { model: model2, warnings };
2547
+ }
2548
+ const rootNode = buildNodeRecursive(rootElement, warnings);
2549
+ const accessibleRoot = rootNode || createGenericContainer(rootElement, warnings);
2550
+ const model = {
2551
+ version: CURRENT_MODEL_VERSION,
2552
+ root: accessibleRoot,
2553
+ metadata: {
2554
+ extractedAt: (/* @__PURE__ */ new Date()).toISOString(),
2555
+ ...sourceHash && { sourceHash }
2556
+ }
2557
+ };
2558
+ return { model, warnings };
2559
+ }
2560
+ function buildAccessibilityTreeWithSelector(rootElement, selector, sourceHash) {
2561
+ const document = rootElement.ownerDocument;
2562
+ if (!document) {
2563
+ throw new SelectorError("Element has no owner document");
2564
+ }
2565
+ let matchingElements;
2566
+ try {
2567
+ if (rootElement.matches(selector)) {
2568
+ matchingElements = [rootElement];
2569
+ } else {
2570
+ matchingElements = Array.from(rootElement.querySelectorAll(selector));
2571
+ }
2572
+ } catch (error) {
2573
+ throw new SelectorError(`Invalid CSS selector: "${selector}". ${error instanceof Error ? error.message : String(error)}`);
2574
+ }
2575
+ if (matchingElements.length === 0) {
2576
+ throw new SelectorError(`No elements match selector: "${selector}"`);
2577
+ }
2578
+ const results = [];
2579
+ for (const element of matchingElements) {
2580
+ const result = buildAccessibilityTree(element, sourceHash);
2581
+ results.push(result);
2582
+ }
2583
+ return results;
2584
+ }
2585
+ function buildNodeRecursive(element, warnings) {
2586
+ if (isHidden(element)) {
2587
+ return null;
2588
+ }
2589
+ if (!isAccessible(element)) {
2590
+ return null;
2591
+ }
2592
+ const roleResult = computeRole(element);
2593
+ warnings.push(...roleResult.warnings.map((w) => ({
2594
+ message: w.message,
2595
+ element: w.element
2596
+ })));
2597
+ if (!roleResult.role) {
2598
+ return null;
2599
+ }
2600
+ const nameResult = computeAccessibleName(element);
2601
+ warnings.push(...nameResult.warnings.map((w) => ({
2602
+ message: w.message,
2603
+ element: w.element
2604
+ })));
2605
+ const descResult = computeAccessibleDescription(element);
2606
+ warnings.push(...descResult.warnings.map((w) => ({
2607
+ message: w.message,
2608
+ element: w.element
2609
+ })));
2610
+ const stateResult = extractState(element);
2611
+ warnings.push(...stateResult.warnings.map((w) => ({
2612
+ message: w.message,
2613
+ element: w.element
2614
+ })));
2615
+ const valueResult = extractValue(element);
2616
+ warnings.push(...valueResult.warnings.map((w) => ({
2617
+ message: w.message,
2618
+ element: w.element
2619
+ })));
2620
+ const focusInfo = extractFocusInfo(element);
2621
+ const children = [];
2622
+ if (element.shadowRoot) {
2623
+ for (const child of Array.from(element.shadowRoot.childNodes)) {
2624
+ processChildNode(child, children, warnings);
2625
+ }
2626
+ }
2627
+ for (const child of Array.from(element.childNodes)) {
2628
+ processChildNode(child, children, warnings);
2629
+ }
2630
+ const node = {
2631
+ role: roleResult.role,
2632
+ name: nameResult.name,
2633
+ ...descResult.description && { description: descResult.description },
2634
+ ...valueResult.value && { value: valueResult.value },
2635
+ state: stateResult.state,
2636
+ focus: focusInfo,
2637
+ children
2638
+ };
2639
+ return node;
2640
+ }
2641
+ function createGenericContainer(rootElement, warnings) {
2642
+ const children = [];
2643
+ if (rootElement.shadowRoot) {
2644
+ for (const child of Array.from(rootElement.shadowRoot.childNodes)) {
2645
+ processChildNode(child, children, warnings);
2646
+ }
2647
+ }
2648
+ for (const child of Array.from(rootElement.childNodes)) {
2649
+ processChildNode(child, children, warnings);
2650
+ }
2651
+ return {
2652
+ role: "generic",
2653
+ name: "",
2654
+ state: {},
2655
+ focus: {
2656
+ focusable: false
2657
+ },
2658
+ children
2659
+ };
2660
+ }
492
2661
  export {
493
2662
  CURRENT_MODEL_VERSION,
2663
+ ParsingError,
494
2664
  SUPPORTED_ROLES,
2665
+ SelectorError,
495
2666
  ValidationError,
2667
+ buildAccessibilityTree,
2668
+ buildAccessibilityTreeWithSelector,
496
2669
  cloneModel,
2670
+ computeAccessibleDescription,
2671
+ computeAccessibleName,
2672
+ computeRole,
497
2673
  createModel,
498
2674
  describeChange,
499
2675
  deserializeModel,
500
2676
  diffAccessibilityTrees,
2677
+ extractFocusInfo,
2678
+ extractState,
2679
+ extractValue,
501
2680
  formatDiffAsJSON,
502
2681
  formatDiffAsText,
503
2682
  formatDiffForCI,
2683
+ generateAuditReport,
504
2684
  hasAccessibilityRegression,
2685
+ isAccessible,
505
2686
  modelsEqual,
2687
+ parseHTML,
2688
+ parseHTMLStrict,
2689
+ renderAuditReport,
2690
+ renderJAWS,
2691
+ renderNVDA,
2692
+ renderNarrator,
2693
+ renderVoiceOver,
506
2694
  serializeModel,
2695
+ validateHTMLNotEmpty,
507
2696
  validateModel,
508
2697
  validateRole,
509
2698
  validateState,