praxis-kit 6.2.1 → 6.5.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/_shared/diagnostics.d.ts +20 -0
- package/dist/_shared/diagnostics.js +20 -0
- package/dist/{chunk-4YVNOMUS.js → chunk-AM5J6PY3.js} +839 -198
- package/dist/contract/index.d.ts +1 -0
- package/dist/lit/index.d.ts +1 -0
- package/dist/lit/index.js +828 -187
- package/dist/preact/index.d.ts +1 -0
- package/dist/preact/index.js +839 -198
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +1 -1
- package/dist/react/legacy.d.ts +2 -2
- package/dist/react/legacy.js +1 -1
- package/dist/{react-options-DQTX4YS_.d.ts → react-options-DoFNqNdY.d.ts} +1 -0
- package/dist/solid/index.d.ts +1 -0
- package/dist/solid/index.js +839 -198
- package/dist/svelte/index.d.ts +1 -0
- package/dist/svelte/index.js +839 -198
- package/dist/utils/index.d.ts +15 -0
- package/dist/utils/index.js +15 -0
- package/dist/vite-plugin/index.d.ts +20 -0
- package/dist/vue/index.d.ts +1 -0
- package/dist/vue/index.js +839 -198
- package/dist/web/index.d.ts +1 -0
- package/dist/web/index.js +828 -187
- package/package.json +13 -6
package/dist/svelte/index.js
CHANGED
|
@@ -858,11 +858,28 @@ var ContractDiagnostics = {
|
|
|
858
858
|
|
|
859
859
|
// ../../lib/contract/src/diagnostics/html.ts
|
|
860
860
|
import { DiagnosticCategory as DiagnosticCategory3, DiagnosticCode as DiagnosticCode3 } from "../_shared/diagnostics.js";
|
|
861
|
+
var ATTRIBUTE_IGNORED_CODES = {
|
|
862
|
+
checked: DiagnosticCode3.HtmlInputCheckedIgnoredForType,
|
|
863
|
+
multiple: DiagnosticCode3.HtmlInputMultipleIgnoredForType,
|
|
864
|
+
maxLength: DiagnosticCode3.HtmlInputMaxLengthIgnoredForType,
|
|
865
|
+
minLength: DiagnosticCode3.HtmlInputMinLengthIgnoredForType,
|
|
866
|
+
pattern: DiagnosticCode3.HtmlInputPatternIgnoredForType,
|
|
867
|
+
min: DiagnosticCode3.HtmlInputMinIgnoredForType,
|
|
868
|
+
max: DiagnosticCode3.HtmlInputMaxIgnoredForType,
|
|
869
|
+
step: DiagnosticCode3.HtmlInputStepIgnoredForType,
|
|
870
|
+
accept: DiagnosticCode3.HtmlInputAcceptIgnoredForType,
|
|
871
|
+
capture: DiagnosticCode3.HtmlInputCaptureIgnoredForType,
|
|
872
|
+
size: DiagnosticCode3.HtmlInputSizeIgnoredForType,
|
|
873
|
+
alt: DiagnosticCode3.HtmlInputAltIgnoredForType,
|
|
874
|
+
height: DiagnosticCode3.HtmlInputHeightIgnoredForType,
|
|
875
|
+
width: DiagnosticCode3.HtmlInputWidthIgnoredForType
|
|
876
|
+
};
|
|
861
877
|
var HtmlDiagnostics = {
|
|
862
878
|
emptyRole(tag) {
|
|
863
879
|
return {
|
|
864
880
|
code: DiagnosticCode3.HtmlEmptyRole,
|
|
865
881
|
category: DiagnosticCategory3.HTML,
|
|
882
|
+
severity: "warning",
|
|
866
883
|
message: `<${tag}> has an explicit empty role="". Omit the attribute instead.`
|
|
867
884
|
};
|
|
868
885
|
},
|
|
@@ -870,6 +887,7 @@ var HtmlDiagnostics = {
|
|
|
870
887
|
return {
|
|
871
888
|
code: DiagnosticCode3.HtmlImplicitRoleRedundant,
|
|
872
889
|
category: DiagnosticCategory3.HTML,
|
|
890
|
+
severity: "warning",
|
|
873
891
|
message: `<${tag}> already has implicit role="${implicitRole}". Avoid redundant role assignment.`
|
|
874
892
|
};
|
|
875
893
|
},
|
|
@@ -877,6 +895,7 @@ var HtmlDiagnostics = {
|
|
|
877
895
|
return {
|
|
878
896
|
code: DiagnosticCode3.HtmlImplicitRoleOverride,
|
|
879
897
|
category: DiagnosticCategory3.HTML,
|
|
898
|
+
severity: "error",
|
|
880
899
|
message: `<${tag}> should not override its implicit role="${implicitRole}" with role="${role}".`
|
|
881
900
|
};
|
|
882
901
|
},
|
|
@@ -884,6 +903,7 @@ var HtmlDiagnostics = {
|
|
|
884
903
|
return {
|
|
885
904
|
code: DiagnosticCode3.HtmlStandaloneRegionOverride,
|
|
886
905
|
category: DiagnosticCategory3.HTML,
|
|
906
|
+
severity: "error",
|
|
887
907
|
message: `<${tag}> is a self-contained element with implicit role="${implicitRole}". Assigning role="region" has been removed.`
|
|
888
908
|
};
|
|
889
909
|
},
|
|
@@ -891,6 +911,7 @@ var HtmlDiagnostics = {
|
|
|
891
911
|
return {
|
|
892
912
|
code: DiagnosticCode3.HtmlLandmarkRoleOverride,
|
|
893
913
|
category: DiagnosticCategory3.HTML,
|
|
914
|
+
severity: "error",
|
|
894
915
|
message: `<${tag}> has a fixed landmark role="${implicitRole}". role="${role}" overrides it and confuses assistive technology. The override has been removed.`
|
|
895
916
|
};
|
|
896
917
|
},
|
|
@@ -898,34 +919,148 @@ var HtmlDiagnostics = {
|
|
|
898
919
|
return {
|
|
899
920
|
code: DiagnosticCode3.HtmlInvalidChild,
|
|
900
921
|
category: DiagnosticCategory3.HTML,
|
|
922
|
+
severity: "error",
|
|
901
923
|
message: `<${child}> is not a valid direct child of <${parent}>. Allowed: ${allowed}.`
|
|
902
924
|
};
|
|
925
|
+
},
|
|
926
|
+
roleNotPermitted(tag, role, allowedRoles) {
|
|
927
|
+
const allowed = allowedRoles.length > 0 ? allowedRoles.map((r) => `"${r}"`).join(", ") : "none \u2014 no explicit role is permitted on this element";
|
|
928
|
+
return {
|
|
929
|
+
code: DiagnosticCode3.HtmlRoleNotPermitted,
|
|
930
|
+
category: DiagnosticCategory3.HTML,
|
|
931
|
+
severity: "error",
|
|
932
|
+
message: `role="${role}" is not permitted on <${tag}>. Allowed alternate role(s): ${allowed}.`,
|
|
933
|
+
rationale: 'The WAI-ARIA "ARIA in HTML" specification restricts which explicit roles a native element may take. A role outside that set is ignored or produces undefined behavior in assistive technology.'
|
|
934
|
+
};
|
|
935
|
+
},
|
|
936
|
+
// Reserved for <input>-specific facts (HTML3101–3199, see codes.ts) — later element families
|
|
937
|
+
// (button, img, table, ...) get their own reserved block and their own namespace here.
|
|
938
|
+
input: {
|
|
939
|
+
unsupportedType(type) {
|
|
940
|
+
return {
|
|
941
|
+
code: DiagnosticCode3.HtmlInputUnsupportedType,
|
|
942
|
+
category: DiagnosticCategory3.HTML,
|
|
943
|
+
severity: "warning",
|
|
944
|
+
message: `type="${type}" is not a value defined by the HTML specification. Browsers silently fall back to type="text".`,
|
|
945
|
+
rationale: 'An unrecognized input type is not invalid markup \u2014 the spec requires the "text" fallback \u2014 but it usually means a typo, since the input keeps working while silently losing the intended type-specific behavior (validation, virtual keyboard, picker UI).',
|
|
946
|
+
suggestions: [
|
|
947
|
+
{
|
|
948
|
+
title: "Check for a typo in the type value",
|
|
949
|
+
description: `"${type}" does not match any HTML5 input type.`
|
|
950
|
+
}
|
|
951
|
+
]
|
|
952
|
+
};
|
|
953
|
+
},
|
|
954
|
+
// The user-visible problem is that the attribute is ignored — not that it "requires" a type;
|
|
955
|
+
// that's the rule's internal framing, not what the browser actually does.
|
|
956
|
+
attributeIgnoredForType(attribute, type, allowedTypes) {
|
|
957
|
+
const allowed = allowedTypes.map((t) => `"${t}"`).join(", ");
|
|
958
|
+
const code = ATTRIBUTE_IGNORED_CODES[attribute];
|
|
959
|
+
if (!code) throw new Error(`No DiagnosticCode registered for input attribute "${attribute}"`);
|
|
960
|
+
return {
|
|
961
|
+
code,
|
|
962
|
+
category: DiagnosticCategory3.HTML,
|
|
963
|
+
severity: "warning",
|
|
964
|
+
message: `"${attribute}" is ignored on <input type="${type}">.`,
|
|
965
|
+
rationale: `"${attribute}" only has an effect when type is one of: ${allowed}. Browsers silently ignore it on other input types.`,
|
|
966
|
+
suggestions: [
|
|
967
|
+
{
|
|
968
|
+
title: `Remove "${attribute}"`,
|
|
969
|
+
description: `"${attribute}" only affects <input> when type is one of: ${allowed}.`
|
|
970
|
+
}
|
|
971
|
+
]
|
|
972
|
+
};
|
|
973
|
+
}
|
|
903
974
|
}
|
|
904
975
|
};
|
|
905
976
|
|
|
906
|
-
// ../../lib/contract/src/diagnostics/
|
|
977
|
+
// ../../lib/contract/src/diagnostics/input-accessibility.ts
|
|
907
978
|
import { DiagnosticCategory as DiagnosticCategory4, DiagnosticCode as DiagnosticCode4 } from "../_shared/diagnostics.js";
|
|
979
|
+
function accessibilityFact(input) {
|
|
980
|
+
return { category: DiagnosticCategory4.Accessibility, ...input };
|
|
981
|
+
}
|
|
982
|
+
var InputAccessibilityDiagnostics = {
|
|
983
|
+
missingAccessibleName() {
|
|
984
|
+
return accessibilityFact({
|
|
985
|
+
code: DiagnosticCode4.A11yInputMissingAccessibleName,
|
|
986
|
+
severity: "warning",
|
|
987
|
+
message: "This input has no accessible name. Add an associated <label>, aria-label, or aria-labelledby.",
|
|
988
|
+
rationale: "Assistive technology announces a form field by its accessible name; without one, users of screen readers cannot tell what the field is for.",
|
|
989
|
+
suggestions: [
|
|
990
|
+
{ title: "Add aria-label", description: 'Set aria-label="\u2026" directly on the input.' },
|
|
991
|
+
{
|
|
992
|
+
title: "Add an associated <label>",
|
|
993
|
+
description: 'Wrap the input in a <label>, or point a <label for="\u2026"> at its id.'
|
|
994
|
+
}
|
|
995
|
+
]
|
|
996
|
+
});
|
|
997
|
+
},
|
|
998
|
+
placeholderIsNotLabel() {
|
|
999
|
+
return accessibilityFact({
|
|
1000
|
+
code: DiagnosticCode4.A11yInputPlaceholderNotLabel,
|
|
1001
|
+
severity: "warning",
|
|
1002
|
+
message: "Placeholder text does not provide an accessible name. Add an associated <label>, aria-label, or aria-labelledby.",
|
|
1003
|
+
rationale: "Placeholder text disappears as users interact with the field and is not treated as the control's accessible name by many assistive technologies.",
|
|
1004
|
+
suggestions: [
|
|
1005
|
+
{ title: "Add aria-label", description: 'Set aria-label="\u2026" directly on the input.' },
|
|
1006
|
+
{
|
|
1007
|
+
title: "Add an associated <label>",
|
|
1008
|
+
description: 'Wrap the input in a <label>, or point a <label for="\u2026"> at its id.'
|
|
1009
|
+
}
|
|
1010
|
+
]
|
|
1011
|
+
});
|
|
1012
|
+
},
|
|
1013
|
+
passwordMissingAutocomplete() {
|
|
1014
|
+
return accessibilityFact({
|
|
1015
|
+
code: DiagnosticCode4.A11yInputPasswordAutocomplete,
|
|
1016
|
+
severity: "warning",
|
|
1017
|
+
message: "Password inputs should specify an autoComplete value.",
|
|
1018
|
+
rationale: "Without an explicit autocomplete hint, password managers and browsers cannot reliably tell a sign-in field apart from a password-creation field.",
|
|
1019
|
+
suggestions: [
|
|
1020
|
+
{
|
|
1021
|
+
title: 'Set autoComplete="current-password"',
|
|
1022
|
+
description: "Use this for sign-in forms."
|
|
1023
|
+
},
|
|
1024
|
+
{
|
|
1025
|
+
title: 'Set autoComplete="new-password"',
|
|
1026
|
+
description: "Use this for sign-up / change-password forms."
|
|
1027
|
+
}
|
|
1028
|
+
]
|
|
1029
|
+
});
|
|
1030
|
+
},
|
|
1031
|
+
requiredReadOnlyConflict() {
|
|
1032
|
+
return accessibilityFact({
|
|
1033
|
+
code: DiagnosticCode4.A11yInputRequiredReadOnlyConflict,
|
|
1034
|
+
severity: "warning",
|
|
1035
|
+
message: "The required and readOnly attributes are both present. A read-only field cannot satisfy required validation through user interaction.",
|
|
1036
|
+
rationale: "This combination is valid HTML but usually indicates an unintended state. Consider using disabled instead of readOnly, or only applying required when the field is editable."
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
|
|
1041
|
+
// ../../lib/contract/src/diagnostics/slot.ts
|
|
1042
|
+
import { DiagnosticCategory as DiagnosticCategory5, DiagnosticCode as DiagnosticCode5 } from "../_shared/diagnostics.js";
|
|
908
1043
|
var SlotDiagnostics = {
|
|
909
1044
|
exclusive(name) {
|
|
910
1045
|
return {
|
|
911
|
-
code:
|
|
912
|
-
category:
|
|
1046
|
+
code: DiagnosticCode5.SlotExclusive,
|
|
1047
|
+
category: DiagnosticCategory5.Contract,
|
|
913
1048
|
component: name,
|
|
914
1049
|
message: `${name}: "as" and "asChild" are mutually exclusive`
|
|
915
1050
|
};
|
|
916
1051
|
},
|
|
917
1052
|
singleChildRequired(name, elementTerm) {
|
|
918
1053
|
return {
|
|
919
|
-
code:
|
|
920
|
-
category:
|
|
1054
|
+
code: DiagnosticCode5.SlotSingleChild,
|
|
1055
|
+
category: DiagnosticCategory5.Contract,
|
|
921
1056
|
component: name,
|
|
922
1057
|
message: `${name}: asChild requires a ${elementTerm} child`
|
|
923
1058
|
};
|
|
924
1059
|
},
|
|
925
1060
|
singleChildExceeded(name, elementTerm, count) {
|
|
926
1061
|
return {
|
|
927
|
-
code:
|
|
928
|
-
category:
|
|
1062
|
+
code: DiagnosticCode5.SlotSingleChild,
|
|
1063
|
+
category: DiagnosticCategory5.Contract,
|
|
929
1064
|
component: name,
|
|
930
1065
|
message: `${name}: asChild requires exactly one ${elementTerm} child, got ${count}`
|
|
931
1066
|
};
|
|
@@ -933,16 +1068,16 @@ var SlotDiagnostics = {
|
|
|
933
1068
|
discardedChildren(name, elementTerm, count) {
|
|
934
1069
|
const suffix = count === 1 ? "" : "ren";
|
|
935
1070
|
return {
|
|
936
|
-
code:
|
|
937
|
-
category:
|
|
1071
|
+
code: DiagnosticCode5.SlotDiscardedChildren,
|
|
1072
|
+
category: DiagnosticCategory5.Contract,
|
|
938
1073
|
component: name,
|
|
939
1074
|
message: `${name}: asChild discarded ${count} non-element child${suffix} \u2014 only ${elementTerm}s are valid asChild children.`
|
|
940
1075
|
};
|
|
941
1076
|
},
|
|
942
1077
|
renderFnRequired(name, received) {
|
|
943
1078
|
return {
|
|
944
|
-
code:
|
|
945
|
-
category:
|
|
1079
|
+
code: DiagnosticCode5.SlotRenderFn,
|
|
1080
|
+
category: DiagnosticCategory5.Contract,
|
|
946
1081
|
component: name,
|
|
947
1082
|
message: `${name}: asChild requires a render function as children, got ${received}`
|
|
948
1083
|
};
|
|
@@ -971,8 +1106,132 @@ var InvariantBase = class {
|
|
|
971
1106
|
}
|
|
972
1107
|
};
|
|
973
1108
|
|
|
974
|
-
// ../../lib/contract/src/aria/
|
|
1109
|
+
// ../../lib/contract/src/aria/spec/roles/required-properties.ts
|
|
1110
|
+
var REQUIRED_ARIA_PROPERTIES = {
|
|
1111
|
+
combobox: ["aria-expanded"],
|
|
1112
|
+
option: ["aria-selected"],
|
|
1113
|
+
slider: ["aria-valuenow"],
|
|
1114
|
+
scrollbar: ["aria-controls", "aria-valuenow"],
|
|
1115
|
+
spinbutton: ["aria-valuenow"]
|
|
1116
|
+
};
|
|
1117
|
+
|
|
1118
|
+
// ../../lib/contract/src/aria/spec/roles/name-required.ts
|
|
1119
|
+
var NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
|
|
1120
|
+
|
|
1121
|
+
// ../../lib/contract/src/aria/spec/validators/required-properties-validator.ts
|
|
975
1122
|
var NO_VIOLATIONS = [{ valid: true }];
|
|
1123
|
+
function requiredAttributeByRole(roles, attribute) {
|
|
1124
|
+
return Object.fromEntries([...roles].map((role) => [role, [attribute]]));
|
|
1125
|
+
}
|
|
1126
|
+
function checkRequiredAttributes(requirement, { props, effectiveRole }) {
|
|
1127
|
+
if (!effectiveRole) return NO_VIOLATIONS;
|
|
1128
|
+
const requiredAttributes = requirement.attributesByRole[effectiveRole];
|
|
1129
|
+
if (!requiredAttributes) return NO_VIOLATIONS;
|
|
1130
|
+
const results = [];
|
|
1131
|
+
for (const attribute of requiredAttributes) {
|
|
1132
|
+
if (attribute in props) continue;
|
|
1133
|
+
results.push({
|
|
1134
|
+
valid: false,
|
|
1135
|
+
fixable: false,
|
|
1136
|
+
severity: "warning",
|
|
1137
|
+
attribute,
|
|
1138
|
+
diagnostic: requirement.diagnosticFor(attribute, effectiveRole)
|
|
1139
|
+
});
|
|
1140
|
+
}
|
|
1141
|
+
return results;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// ../../lib/contract/src/aria/spec/roles/live-region.ts
|
|
1145
|
+
var LIVE_REGION_ROLES = /* @__PURE__ */ new Map([
|
|
1146
|
+
["alert", "assertive"],
|
|
1147
|
+
["status", "polite"],
|
|
1148
|
+
["log", "polite"],
|
|
1149
|
+
["timer", "off"]
|
|
1150
|
+
]);
|
|
1151
|
+
var ATOMIC_REQUIREMENTS = requiredAttributeByRole(LIVE_REGION_ROLES.keys(), "aria-atomic");
|
|
1152
|
+
|
|
1153
|
+
// ../../lib/contract/src/aria/spec/attributes/aria-value-types.ts
|
|
1154
|
+
var ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
|
|
1155
|
+
// Boolean (true | false)
|
|
1156
|
+
["aria-atomic", { kind: "boolean" }],
|
|
1157
|
+
["aria-busy", { kind: "boolean" }],
|
|
1158
|
+
["aria-disabled", { kind: "boolean" }],
|
|
1159
|
+
["aria-expanded", { kind: "boolean" }],
|
|
1160
|
+
["aria-hidden", { kind: "boolean" }],
|
|
1161
|
+
["aria-modal", { kind: "boolean" }],
|
|
1162
|
+
["aria-multiline", { kind: "boolean" }],
|
|
1163
|
+
["aria-multiselectable", { kind: "boolean" }],
|
|
1164
|
+
["aria-readonly", { kind: "boolean" }],
|
|
1165
|
+
["aria-required", { kind: "boolean" }],
|
|
1166
|
+
["aria-selected", { kind: "boolean" }],
|
|
1167
|
+
// Tristate (true | false | mixed)
|
|
1168
|
+
["aria-checked", { kind: "tristate" }],
|
|
1169
|
+
["aria-pressed", { kind: "tristate" }],
|
|
1170
|
+
// Numeric (any finite number)
|
|
1171
|
+
["aria-valuenow", { kind: "number" }],
|
|
1172
|
+
["aria-valuemin", { kind: "number" }],
|
|
1173
|
+
["aria-valuemax", { kind: "number" }],
|
|
1174
|
+
// Integer with optional range
|
|
1175
|
+
["aria-level", { kind: "integer", min: 1, max: 6 }],
|
|
1176
|
+
["aria-posinset", { kind: "integer", min: 1 }],
|
|
1177
|
+
["aria-setsize", { kind: "integer", min: -1 }],
|
|
1178
|
+
["aria-rowcount", { kind: "integer", min: -1 }],
|
|
1179
|
+
["aria-colcount", { kind: "integer", min: -1 }],
|
|
1180
|
+
["aria-rowindex", { kind: "integer", min: 1 }],
|
|
1181
|
+
["aria-colindex", { kind: "integer", min: 1 }],
|
|
1182
|
+
["aria-rowspan", { kind: "integer", min: 0 }],
|
|
1183
|
+
["aria-colspan", { kind: "integer", min: 0 }],
|
|
1184
|
+
// Enum (specific allowed tokens)
|
|
1185
|
+
["aria-autocomplete", { kind: "enum", values: /* @__PURE__ */ new Set(["inline", "list", "both", "none"]) }],
|
|
1186
|
+
[
|
|
1187
|
+
"aria-current",
|
|
1188
|
+
{
|
|
1189
|
+
kind: "enum",
|
|
1190
|
+
values: /* @__PURE__ */ new Set(["page", "step", "location", "date", "time", "true", "false"])
|
|
1191
|
+
}
|
|
1192
|
+
],
|
|
1193
|
+
[
|
|
1194
|
+
"aria-haspopup",
|
|
1195
|
+
{
|
|
1196
|
+
kind: "enum",
|
|
1197
|
+
values: /* @__PURE__ */ new Set(["false", "true", "menu", "listbox", "tree", "grid", "dialog"])
|
|
1198
|
+
}
|
|
1199
|
+
],
|
|
1200
|
+
["aria-invalid", { kind: "enum", values: /* @__PURE__ */ new Set(["grammar", "false", "spelling", "true"]) }],
|
|
1201
|
+
["aria-live", { kind: "enum", values: /* @__PURE__ */ new Set(["assertive", "off", "polite"]) }],
|
|
1202
|
+
["aria-orientation", { kind: "enum", values: /* @__PURE__ */ new Set(["horizontal", "vertical", "undefined"]) }],
|
|
1203
|
+
["aria-sort", { kind: "enum", values: /* @__PURE__ */ new Set(["ascending", "descending", "none", "other"]) }]
|
|
1204
|
+
]);
|
|
1205
|
+
|
|
1206
|
+
// ../../lib/contract/src/aria/spec/attributes/aria-relevant-tokens.ts
|
|
1207
|
+
var VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set([
|
|
1208
|
+
"additions",
|
|
1209
|
+
"removals",
|
|
1210
|
+
"text",
|
|
1211
|
+
"all"
|
|
1212
|
+
]);
|
|
1213
|
+
|
|
1214
|
+
// ../../lib/contract/src/aria/spec/elements/heading-implicit-levels.ts
|
|
1215
|
+
var HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
|
|
1216
|
+
["h1", 1],
|
|
1217
|
+
["h2", 2],
|
|
1218
|
+
["h3", 3],
|
|
1219
|
+
["h4", 4],
|
|
1220
|
+
["h5", 5],
|
|
1221
|
+
["h6", 6]
|
|
1222
|
+
]);
|
|
1223
|
+
|
|
1224
|
+
// ../../lib/contract/src/aria/spec/elements/interactive-tags.ts
|
|
1225
|
+
var INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
|
|
1226
|
+
"a",
|
|
1227
|
+
"button",
|
|
1228
|
+
"input",
|
|
1229
|
+
"select",
|
|
1230
|
+
"textarea"
|
|
1231
|
+
]);
|
|
1232
|
+
|
|
1233
|
+
// ../../lib/contract/src/aria/polymorphic-validator.ts
|
|
1234
|
+
var NO_VIOLATIONS2 = [{ valid: true }];
|
|
976
1235
|
function isIntrinsicTag(tag) {
|
|
977
1236
|
return isString(tag);
|
|
978
1237
|
}
|
|
@@ -1004,7 +1263,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1004
1263
|
tag,
|
|
1005
1264
|
role: "",
|
|
1006
1265
|
attribute: void 0,
|
|
1007
|
-
severity:
|
|
1266
|
+
severity: d.severity,
|
|
1008
1267
|
phase: "evaluate"
|
|
1009
1268
|
}
|
|
1010
1269
|
]
|
|
@@ -1034,6 +1293,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1034
1293
|
const violations = [];
|
|
1035
1294
|
const fixes = [];
|
|
1036
1295
|
iterate.forEach(rules, (rule) => {
|
|
1296
|
+
if (isNonNull(rule.tags) && !rule.tags.includes(context.tag)) return;
|
|
1037
1297
|
iterate.forEach(rule(context), (result) => {
|
|
1038
1298
|
if (result.valid) return;
|
|
1039
1299
|
const {
|
|
@@ -1059,7 +1319,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1059
1319
|
return { violations, fixes };
|
|
1060
1320
|
}
|
|
1061
1321
|
static #getRules(context) {
|
|
1062
|
-
if (_AriaPolicyEngine.#hasRole(context.props) || isNonNull(context.effectiveRole) &&
|
|
1322
|
+
if (_AriaPolicyEngine.#hasRole(context.props) || isNonNull(context.effectiveRole) && LIVE_REGION_ROLES.has(context.effectiveRole)) {
|
|
1063
1323
|
return _AriaPolicyEngine.#pipeline;
|
|
1064
1324
|
}
|
|
1065
1325
|
return _AriaPolicyEngine.#implicitOnlyRules;
|
|
@@ -1261,44 +1521,47 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1261
1521
|
implicitRole
|
|
1262
1522
|
}) {
|
|
1263
1523
|
const role = props.role;
|
|
1264
|
-
if (!implicitRole || !role || role === implicitRole) return
|
|
1524
|
+
if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS2;
|
|
1265
1525
|
if (isStrongImplicitRole(tag) && role === "region") {
|
|
1526
|
+
const diagnostic = HtmlDiagnostics.implicitRoleOverride(tag, implicitRole, role);
|
|
1266
1527
|
return [
|
|
1267
1528
|
{
|
|
1268
1529
|
valid: false,
|
|
1269
1530
|
fixable: true,
|
|
1270
|
-
severity:
|
|
1531
|
+
severity: diagnostic.severity,
|
|
1271
1532
|
fix: _AriaPolicyEngine.#removeRole,
|
|
1272
|
-
diagnostic
|
|
1533
|
+
diagnostic
|
|
1273
1534
|
}
|
|
1274
1535
|
];
|
|
1275
1536
|
}
|
|
1276
|
-
return
|
|
1537
|
+
return NO_VIOLATIONS2;
|
|
1277
1538
|
}
|
|
1278
1539
|
static #checkRedundantRole({ tag, props, implicitRole }) {
|
|
1279
1540
|
const role = props.role;
|
|
1280
|
-
if (!implicitRole || !role || role !== implicitRole) return
|
|
1541
|
+
if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS2;
|
|
1542
|
+
const diagnostic = HtmlDiagnostics.implicitRoleRedundant(tag, implicitRole);
|
|
1281
1543
|
return [
|
|
1282
1544
|
{
|
|
1283
1545
|
valid: false,
|
|
1284
1546
|
fixable: true,
|
|
1285
|
-
severity:
|
|
1547
|
+
severity: diagnostic.severity,
|
|
1286
1548
|
fix: _AriaPolicyEngine.#removeRole,
|
|
1287
|
-
diagnostic
|
|
1549
|
+
diagnostic
|
|
1288
1550
|
}
|
|
1289
1551
|
];
|
|
1290
1552
|
}
|
|
1291
1553
|
static #checkStandaloneRegion({ tag, props, implicitRole }) {
|
|
1292
1554
|
const role = props.role;
|
|
1293
|
-
if (role !== "region") return
|
|
1294
|
-
if (!isStandaloneTag(tag)) return
|
|
1555
|
+
if (role !== "region") return NO_VIOLATIONS2;
|
|
1556
|
+
if (!isStandaloneTag(tag)) return NO_VIOLATIONS2;
|
|
1557
|
+
const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
|
|
1295
1558
|
return [
|
|
1296
1559
|
{
|
|
1297
1560
|
valid: false,
|
|
1298
1561
|
fixable: true,
|
|
1299
|
-
severity:
|
|
1562
|
+
severity: diagnostic.severity,
|
|
1300
1563
|
fix: _AriaPolicyEngine.#removeRole,
|
|
1301
|
-
diagnostic
|
|
1564
|
+
diagnostic
|
|
1302
1565
|
}
|
|
1303
1566
|
];
|
|
1304
1567
|
}
|
|
@@ -1307,7 +1570,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1307
1570
|
props,
|
|
1308
1571
|
effectiveRole
|
|
1309
1572
|
}) {
|
|
1310
|
-
if (effectiveRole === "none" || effectiveRole === "presentation") return
|
|
1573
|
+
if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS2;
|
|
1311
1574
|
const results = [];
|
|
1312
1575
|
iterate.forEachEntry(props, (key) => {
|
|
1313
1576
|
if (!key.startsWith("aria-")) return;
|
|
@@ -1325,62 +1588,6 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1325
1588
|
return results;
|
|
1326
1589
|
}
|
|
1327
1590
|
// ─── ARIA attribute value validation ──────────────────────────────────────
|
|
1328
|
-
// Accepted value shapes for typed ARIA attributes.
|
|
1329
|
-
// Attributes not in this map are unconstrained (arbitrary string values permitted).
|
|
1330
|
-
static #ARIA_VALUE_TYPES = /* @__PURE__ */ new Map([
|
|
1331
|
-
// Boolean (true | false)
|
|
1332
|
-
["aria-atomic", { kind: "boolean" }],
|
|
1333
|
-
["aria-busy", { kind: "boolean" }],
|
|
1334
|
-
["aria-disabled", { kind: "boolean" }],
|
|
1335
|
-
["aria-expanded", { kind: "boolean" }],
|
|
1336
|
-
["aria-hidden", { kind: "boolean" }],
|
|
1337
|
-
["aria-modal", { kind: "boolean" }],
|
|
1338
|
-
["aria-multiline", { kind: "boolean" }],
|
|
1339
|
-
["aria-multiselectable", { kind: "boolean" }],
|
|
1340
|
-
["aria-readonly", { kind: "boolean" }],
|
|
1341
|
-
["aria-required", { kind: "boolean" }],
|
|
1342
|
-
["aria-selected", { kind: "boolean" }],
|
|
1343
|
-
// Tristate (true | false | mixed)
|
|
1344
|
-
["aria-checked", { kind: "tristate" }],
|
|
1345
|
-
["aria-pressed", { kind: "tristate" }],
|
|
1346
|
-
// Numeric (any finite number)
|
|
1347
|
-
["aria-valuenow", { kind: "number" }],
|
|
1348
|
-
["aria-valuemin", { kind: "number" }],
|
|
1349
|
-
["aria-valuemax", { kind: "number" }],
|
|
1350
|
-
// Integer with optional range
|
|
1351
|
-
["aria-level", { kind: "integer", min: 1, max: 6 }],
|
|
1352
|
-
["aria-posinset", { kind: "integer", min: 1 }],
|
|
1353
|
-
["aria-setsize", { kind: "integer", min: -1 }],
|
|
1354
|
-
["aria-rowcount", { kind: "integer", min: -1 }],
|
|
1355
|
-
["aria-colcount", { kind: "integer", min: -1 }],
|
|
1356
|
-
["aria-rowindex", { kind: "integer", min: 1 }],
|
|
1357
|
-
["aria-colindex", { kind: "integer", min: 1 }],
|
|
1358
|
-
["aria-rowspan", { kind: "integer", min: 0 }],
|
|
1359
|
-
["aria-colspan", { kind: "integer", min: 0 }],
|
|
1360
|
-
// Enum (specific allowed tokens)
|
|
1361
|
-
["aria-autocomplete", { kind: "enum", values: /* @__PURE__ */ new Set(["inline", "list", "both", "none"]) }],
|
|
1362
|
-
[
|
|
1363
|
-
"aria-current",
|
|
1364
|
-
{
|
|
1365
|
-
kind: "enum",
|
|
1366
|
-
values: /* @__PURE__ */ new Set(["page", "step", "location", "date", "time", "true", "false"])
|
|
1367
|
-
}
|
|
1368
|
-
],
|
|
1369
|
-
[
|
|
1370
|
-
"aria-haspopup",
|
|
1371
|
-
{
|
|
1372
|
-
kind: "enum",
|
|
1373
|
-
values: /* @__PURE__ */ new Set(["false", "true", "menu", "listbox", "tree", "grid", "dialog"])
|
|
1374
|
-
}
|
|
1375
|
-
],
|
|
1376
|
-
["aria-invalid", { kind: "enum", values: /* @__PURE__ */ new Set(["grammar", "false", "spelling", "true"]) }],
|
|
1377
|
-
["aria-live", { kind: "enum", values: /* @__PURE__ */ new Set(["assertive", "off", "polite"]) }],
|
|
1378
|
-
[
|
|
1379
|
-
"aria-orientation",
|
|
1380
|
-
{ kind: "enum", values: /* @__PURE__ */ new Set(["horizontal", "vertical", "undefined"]) }
|
|
1381
|
-
],
|
|
1382
|
-
["aria-sort", { kind: "enum", values: /* @__PURE__ */ new Set(["ascending", "descending", "none", "other"]) }]
|
|
1383
|
-
]);
|
|
1384
1591
|
static #isValidAriaValue(value, type) {
|
|
1385
1592
|
switch (type.kind) {
|
|
1386
1593
|
case "boolean":
|
|
@@ -1425,11 +1632,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1425
1632
|
}
|
|
1426
1633
|
}
|
|
1427
1634
|
static #checkAriaAttributeValues({ props, effectiveRole }) {
|
|
1428
|
-
if (effectiveRole === "none" || effectiveRole === "presentation") return
|
|
1635
|
+
if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS2;
|
|
1429
1636
|
const results = [];
|
|
1430
1637
|
iterate.forEachEntry(props, (key, value) => {
|
|
1431
1638
|
if (!key.startsWith("aria-")) return;
|
|
1432
|
-
const type =
|
|
1639
|
+
const type = ARIA_VALUE_TYPES.get(key);
|
|
1433
1640
|
if (!isNonNull(type)) return;
|
|
1434
1641
|
if (_AriaPolicyEngine.#isValidAriaValue(value, type)) return;
|
|
1435
1642
|
results.push({
|
|
@@ -1448,26 +1655,18 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1448
1655
|
return results;
|
|
1449
1656
|
}
|
|
1450
1657
|
// ─── Heading implicit level ────────────────────────────────────────────────
|
|
1451
|
-
static #HEADING_IMPLICIT_LEVELS = /* @__PURE__ */ new Map([
|
|
1452
|
-
["h1", 1],
|
|
1453
|
-
["h2", 2],
|
|
1454
|
-
["h3", 3],
|
|
1455
|
-
["h4", 4],
|
|
1456
|
-
["h5", 5],
|
|
1457
|
-
["h6", 6]
|
|
1458
|
-
]);
|
|
1459
1658
|
static #checkRedundantAriaLevel({
|
|
1460
1659
|
tag,
|
|
1461
1660
|
props,
|
|
1462
1661
|
effectiveRole
|
|
1463
1662
|
}) {
|
|
1464
|
-
if (effectiveRole === "none" || effectiveRole === "presentation") return
|
|
1465
|
-
const implicitLevel =
|
|
1466
|
-
if (!isNonNull(implicitLevel)) return
|
|
1663
|
+
if (effectiveRole === "none" || effectiveRole === "presentation") return NO_VIOLATIONS2;
|
|
1664
|
+
const implicitLevel = HEADING_IMPLICIT_LEVELS.get(tag);
|
|
1665
|
+
if (!isNonNull(implicitLevel)) return NO_VIOLATIONS2;
|
|
1467
1666
|
const raw = props["aria-level"];
|
|
1468
|
-
if (!isNonNull(raw)) return
|
|
1667
|
+
if (!isNonNull(raw)) return NO_VIOLATIONS2;
|
|
1469
1668
|
const n = typeof raw === "number" ? raw : typeof raw === "string" ? parseInt(raw, 10) : NaN;
|
|
1470
|
-
if (!Number.isFinite(n) || n !== implicitLevel) return
|
|
1669
|
+
if (!Number.isFinite(n) || n !== implicitLevel) return NO_VIOLATIONS2;
|
|
1471
1670
|
return [
|
|
1472
1671
|
{
|
|
1473
1672
|
valid: false,
|
|
@@ -1480,20 +1679,14 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1480
1679
|
];
|
|
1481
1680
|
}
|
|
1482
1681
|
// ─── Name-required roles ───────────────────────────────────────────────────
|
|
1483
|
-
// Roles that always require an accessible name per WAI-ARIA APG.
|
|
1484
|
-
// Dialog and landmark names are enforced via contracts (ariaContract) rather than
|
|
1485
|
-
// the built-in pipeline so consumers can opt in; img is built in because role=img
|
|
1486
|
-
// on any element (including bare <img>) is definitionally useless without a name.
|
|
1487
|
-
static #NAME_REQUIRED_ROLES = /* @__PURE__ */ new Set(["img"]);
|
|
1488
1682
|
static #checkNameRequiredRoles({
|
|
1489
1683
|
tag,
|
|
1490
1684
|
props,
|
|
1491
1685
|
effectiveRole
|
|
1492
1686
|
}) {
|
|
1493
|
-
if (!effectiveRole || !
|
|
1494
|
-
|
|
1495
|
-
if ("
|
|
1496
|
-
if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return NO_VIOLATIONS;
|
|
1687
|
+
if (!effectiveRole || !NAME_REQUIRED_ROLES.has(effectiveRole)) return NO_VIOLATIONS2;
|
|
1688
|
+
if ("aria-label" in props || "aria-labelledby" in props) return NO_VIOLATIONS2;
|
|
1689
|
+
if (tag === "img" && typeof props.alt === "string" && props.alt.length > 0) return NO_VIOLATIONS2;
|
|
1497
1690
|
return [
|
|
1498
1691
|
{
|
|
1499
1692
|
valid: false,
|
|
@@ -1503,51 +1696,21 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1503
1696
|
}
|
|
1504
1697
|
];
|
|
1505
1698
|
}
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
["scrollbar", ["aria-controls", "aria-valuenow"]],
|
|
1513
|
-
["spinbutton", ["aria-valuenow"]]
|
|
1514
|
-
]);
|
|
1515
|
-
static #checkRequiredAriaProperties({
|
|
1516
|
-
props,
|
|
1517
|
-
effectiveRole
|
|
1518
|
-
}) {
|
|
1519
|
-
if (!effectiveRole) return NO_VIOLATIONS;
|
|
1520
|
-
const required = _AriaPolicyEngine.#REQUIRED_PROPERTIES.get(effectiveRole);
|
|
1521
|
-
if (!isNonNull(required)) return NO_VIOLATIONS;
|
|
1522
|
-
const results = [];
|
|
1523
|
-
iterate.forEach(required, (attr) => {
|
|
1524
|
-
if (attr in props) return;
|
|
1525
|
-
results.push({
|
|
1526
|
-
valid: false,
|
|
1527
|
-
fixable: false,
|
|
1528
|
-
severity: "warning",
|
|
1529
|
-
attribute: attr,
|
|
1530
|
-
diagnostic: AriaDiagnostics.requiredProperty(attr, effectiveRole)
|
|
1531
|
-
});
|
|
1532
|
-
});
|
|
1533
|
-
return results;
|
|
1699
|
+
static #requiredAriaPropertiesRule = {
|
|
1700
|
+
attributesByRole: REQUIRED_ARIA_PROPERTIES,
|
|
1701
|
+
diagnosticFor: (attribute, role) => AriaDiagnostics.requiredProperty(attribute, role)
|
|
1702
|
+
};
|
|
1703
|
+
static #checkRequiredAriaProperties(context) {
|
|
1704
|
+
return checkRequiredAttributes(_AriaPolicyEngine.#requiredAriaPropertiesRule, context);
|
|
1534
1705
|
}
|
|
1535
|
-
// Natively interactive HTML elements — always keyboard-reachable unless explicitly disabled.
|
|
1536
|
-
static #INTERACTIVE_TAGS = /* @__PURE__ */ new Set([
|
|
1537
|
-
"a",
|
|
1538
|
-
"button",
|
|
1539
|
-
"input",
|
|
1540
|
-
"select",
|
|
1541
|
-
"textarea"
|
|
1542
|
-
]);
|
|
1543
1706
|
// WAI-ARIA 1.2 §6.6: aria-hidden="true" must not be placed on focusable elements.
|
|
1544
1707
|
static #checkAriaHiddenOnFocusable({ tag, props }) {
|
|
1545
|
-
if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return
|
|
1546
|
-
const isInteractive =
|
|
1708
|
+
if (props["aria-hidden"] !== "true" && props["aria-hidden"] !== true) return NO_VIOLATIONS2;
|
|
1709
|
+
const isInteractive = INTERACTIVE_TAGS.has(tag);
|
|
1547
1710
|
if (!isInteractive) {
|
|
1548
1711
|
const tabindex = props.tabindex;
|
|
1549
1712
|
const n = typeof tabindex === "number" ? tabindex : typeof tabindex === "string" ? parseInt(tabindex, 10) : NaN;
|
|
1550
|
-
if (!Number.isFinite(n) || n < 0) return
|
|
1713
|
+
if (!Number.isFinite(n) || n < 0) return NO_VIOLATIONS2;
|
|
1551
1714
|
}
|
|
1552
1715
|
return [
|
|
1553
1716
|
{
|
|
@@ -1566,7 +1729,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1566
1729
|
props,
|
|
1567
1730
|
effectiveRole
|
|
1568
1731
|
}) {
|
|
1569
|
-
if (effectiveRole !== "none" && effectiveRole !== "presentation") return
|
|
1732
|
+
if (effectiveRole !== "none" && effectiveRole !== "presentation") return NO_VIOLATIONS2;
|
|
1570
1733
|
const results = [];
|
|
1571
1734
|
iterate.forEachEntry(props, (key) => {
|
|
1572
1735
|
if (!key.startsWith("aria-")) return;
|
|
@@ -1582,18 +1745,11 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1582
1745
|
});
|
|
1583
1746
|
return results;
|
|
1584
1747
|
}
|
|
1585
|
-
// WAI-ARIA live region roles and their implied aria-live politeness values.
|
|
1586
|
-
static #LIVE_REGION_ROLES = /* @__PURE__ */ new Map([
|
|
1587
|
-
["alert", "assertive"],
|
|
1588
|
-
["status", "polite"],
|
|
1589
|
-
["log", "polite"],
|
|
1590
|
-
["timer", "off"]
|
|
1591
|
-
]);
|
|
1592
1748
|
static #checkMissingLiveRegion({ effectiveRole, props }) {
|
|
1593
|
-
if (!effectiveRole) return
|
|
1594
|
-
const impliedLive =
|
|
1595
|
-
if (!impliedLive) return
|
|
1596
|
-
if ("aria-live" in props) return
|
|
1749
|
+
if (!effectiveRole) return NO_VIOLATIONS2;
|
|
1750
|
+
const impliedLive = LIVE_REGION_ROLES.get(effectiveRole);
|
|
1751
|
+
if (!impliedLive) return NO_VIOLATIONS2;
|
|
1752
|
+
if ("aria-live" in props) return NO_VIOLATIONS2;
|
|
1597
1753
|
const injectLive = {
|
|
1598
1754
|
kind: `injectLive:${effectiveRole}`,
|
|
1599
1755
|
apply: (ctx) => ({
|
|
@@ -1612,20 +1768,13 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1612
1768
|
}
|
|
1613
1769
|
];
|
|
1614
1770
|
}
|
|
1615
|
-
static #
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
valid: false,
|
|
1622
|
-
fixable: false,
|
|
1623
|
-
severity: "warning",
|
|
1624
|
-
diagnostic: AriaDiagnostics.missingAtomic(effectiveRole)
|
|
1625
|
-
}
|
|
1626
|
-
];
|
|
1771
|
+
static #missingAtomicRule = {
|
|
1772
|
+
attributesByRole: ATOMIC_REQUIREMENTS,
|
|
1773
|
+
diagnosticFor: (_attribute, role) => AriaDiagnostics.missingAtomic(role)
|
|
1774
|
+
};
|
|
1775
|
+
static #checkMissingAtomic(context) {
|
|
1776
|
+
return checkRequiredAttributes(_AriaPolicyEngine.#missingAtomicRule, context);
|
|
1627
1777
|
}
|
|
1628
|
-
static #VALID_RELEVANT_TOKENS = /* @__PURE__ */ new Set(["additions", "removals", "text", "all"]);
|
|
1629
1778
|
// Custom fix rules passed via `options.rules` must be pure functions of (tag, props) — the cache
|
|
1630
1779
|
// replays stored fixes against new prop objects, so fixes that close over external state will
|
|
1631
1780
|
// produce inconsistent results on cache hits.
|
|
@@ -1639,10 +1788,10 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1639
1788
|
};
|
|
1640
1789
|
static #checkInvalidAriaRelevant({ props }) {
|
|
1641
1790
|
const relevant = props["aria-relevant"];
|
|
1642
|
-
if (relevant === void 0) return
|
|
1643
|
-
if (typeof relevant !== "string") return
|
|
1791
|
+
if (relevant === void 0) return NO_VIOLATIONS2;
|
|
1792
|
+
if (typeof relevant !== "string") return NO_VIOLATIONS2;
|
|
1644
1793
|
const tokens = relevant.trim().split(/\s+/);
|
|
1645
|
-
const invalid = tokens.filter((t) => !
|
|
1794
|
+
const invalid = tokens.filter((t) => !VALID_RELEVANT_TOKENS.has(t));
|
|
1646
1795
|
if (invalid.length > 0) {
|
|
1647
1796
|
return [
|
|
1648
1797
|
{
|
|
@@ -1667,7 +1816,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1667
1816
|
}
|
|
1668
1817
|
];
|
|
1669
1818
|
}
|
|
1670
|
-
return
|
|
1819
|
+
return NO_VIOLATIONS2;
|
|
1671
1820
|
}
|
|
1672
1821
|
};
|
|
1673
1822
|
|
|
@@ -1987,6 +2136,425 @@ var readonlyProps = ({
|
|
|
1987
2136
|
// ../core/src/html/evaluators.ts
|
|
1988
2137
|
import { warnDiagnostics as warnDiagnostics2 } from "../_shared/diagnostics.js";
|
|
1989
2138
|
|
|
2139
|
+
// ../core/src/html/spec/vocabulary/input.ts
|
|
2140
|
+
var TEXT_INPUT_TYPES = ["text", "search", "url", "tel", "email", "password"];
|
|
2141
|
+
var NUMERIC_INPUT_TYPES = [
|
|
2142
|
+
"number",
|
|
2143
|
+
"range",
|
|
2144
|
+
"date",
|
|
2145
|
+
"month",
|
|
2146
|
+
"week",
|
|
2147
|
+
"time",
|
|
2148
|
+
"datetime-local"
|
|
2149
|
+
];
|
|
2150
|
+
var HTML_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
2151
|
+
...TEXT_INPUT_TYPES,
|
|
2152
|
+
...NUMERIC_INPUT_TYPES,
|
|
2153
|
+
"checkbox",
|
|
2154
|
+
"radio",
|
|
2155
|
+
"file",
|
|
2156
|
+
"color",
|
|
2157
|
+
"hidden",
|
|
2158
|
+
"button",
|
|
2159
|
+
"submit",
|
|
2160
|
+
"reset",
|
|
2161
|
+
"image"
|
|
2162
|
+
]);
|
|
2163
|
+
|
|
2164
|
+
// ../core/src/html/spec/attributes/input.ts
|
|
2165
|
+
var INPUT_ATTRIBUTE_TYPE_POLICIES = [
|
|
2166
|
+
{ attribute: "checked", allowedTypes: ["checkbox", "radio"] },
|
|
2167
|
+
{ attribute: "multiple", allowedTypes: ["email", "file"] },
|
|
2168
|
+
{ attribute: "maxLength", allowedTypes: TEXT_INPUT_TYPES },
|
|
2169
|
+
{ attribute: "minLength", allowedTypes: TEXT_INPUT_TYPES },
|
|
2170
|
+
{ attribute: "pattern", allowedTypes: TEXT_INPUT_TYPES },
|
|
2171
|
+
{ attribute: "min", allowedTypes: NUMERIC_INPUT_TYPES },
|
|
2172
|
+
{ attribute: "max", allowedTypes: NUMERIC_INPUT_TYPES },
|
|
2173
|
+
{ attribute: "step", allowedTypes: NUMERIC_INPUT_TYPES },
|
|
2174
|
+
{ attribute: "accept", allowedTypes: ["file"] },
|
|
2175
|
+
{ attribute: "capture", allowedTypes: ["file"] },
|
|
2176
|
+
{ attribute: "size", allowedTypes: TEXT_INPUT_TYPES },
|
|
2177
|
+
{ attribute: "alt", allowedTypes: ["image"] },
|
|
2178
|
+
{ attribute: "height", allowedTypes: ["image"] },
|
|
2179
|
+
{ attribute: "width", allowedTypes: ["image"] }
|
|
2180
|
+
];
|
|
2181
|
+
|
|
2182
|
+
// ../core/src/html/spec/constraints/input.ts
|
|
2183
|
+
var REQUIRED_READONLY_CONFLICT = {
|
|
2184
|
+
props: ["required", "readOnly"],
|
|
2185
|
+
diagnostic: () => InputAccessibilityDiagnostics.requiredReadOnlyConflict()
|
|
2186
|
+
};
|
|
2187
|
+
var INPUT_MUTUALLY_EXCLUSIVE_POLICIES = [
|
|
2188
|
+
REQUIRED_READONLY_CONFLICT
|
|
2189
|
+
];
|
|
2190
|
+
|
|
2191
|
+
// ../core/src/html/spec/validators/attribute-type-validator.ts
|
|
2192
|
+
var DEFAULT_INPUT_TYPE = "text";
|
|
2193
|
+
function omit(props, key) {
|
|
2194
|
+
const next = { ...props };
|
|
2195
|
+
delete next[key];
|
|
2196
|
+
return next;
|
|
2197
|
+
}
|
|
2198
|
+
function removeAttributeFix(attribute) {
|
|
2199
|
+
return {
|
|
2200
|
+
kind: `removeAttribute:${attribute}`,
|
|
2201
|
+
apply: ({ props }) => {
|
|
2202
|
+
if (!(attribute in props)) return { applied: false, next: props };
|
|
2203
|
+
return { applied: true, next: omit(props, attribute), previous: props };
|
|
2204
|
+
}
|
|
2205
|
+
};
|
|
2206
|
+
}
|
|
2207
|
+
function createInputAttributeTypeRule({
|
|
2208
|
+
attribute,
|
|
2209
|
+
allowedTypes
|
|
2210
|
+
}) {
|
|
2211
|
+
const rule = ({ tag, props }) => {
|
|
2212
|
+
if (tag !== "input" || !(attribute in props)) return [];
|
|
2213
|
+
const type = typeof props.type === "string" ? props.type : DEFAULT_INPUT_TYPE;
|
|
2214
|
+
if (allowedTypes.includes(type)) return [];
|
|
2215
|
+
const diagnostic = HtmlDiagnostics.input.attributeIgnoredForType(attribute, type, allowedTypes);
|
|
2216
|
+
return [
|
|
2217
|
+
{
|
|
2218
|
+
valid: false,
|
|
2219
|
+
fixable: true,
|
|
2220
|
+
severity: diagnostic.severity,
|
|
2221
|
+
fix: removeAttributeFix(attribute),
|
|
2222
|
+
diagnostic
|
|
2223
|
+
}
|
|
2224
|
+
];
|
|
2225
|
+
};
|
|
2226
|
+
return Object.assign(rule, { readsProps: ["type", attribute], tags: ["input"] });
|
|
2227
|
+
}
|
|
2228
|
+
|
|
2229
|
+
// ../core/src/html/spec/validators/mutually-exclusive-validator.ts
|
|
2230
|
+
function createMutuallyExclusiveRule({
|
|
2231
|
+
props: conflictingProps,
|
|
2232
|
+
diagnostic: createDiagnostic
|
|
2233
|
+
}) {
|
|
2234
|
+
const [first, second] = conflictingProps;
|
|
2235
|
+
const rule = ({ tag, props }) => {
|
|
2236
|
+
if (tag !== "input" || !props[first] || !props[second]) return [];
|
|
2237
|
+
const diagnostic = createDiagnostic();
|
|
2238
|
+
return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
|
|
2239
|
+
};
|
|
2240
|
+
return Object.assign(rule, { readsProps: conflictingProps, tags: ["input"] });
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2243
|
+
// ../core/src/html/input-rules.ts
|
|
2244
|
+
var policyByAttribute = Object.fromEntries(
|
|
2245
|
+
INPUT_ATTRIBUTE_TYPE_POLICIES.map((policy) => [policy.attribute, policy])
|
|
2246
|
+
);
|
|
2247
|
+
function policyFor(attribute) {
|
|
2248
|
+
return policyByAttribute[attribute];
|
|
2249
|
+
}
|
|
2250
|
+
var supportedInputTypeRule = Object.assign(
|
|
2251
|
+
({ tag, props }) => {
|
|
2252
|
+
if (tag !== "input" || typeof props.type !== "string") return [];
|
|
2253
|
+
const type = props.type;
|
|
2254
|
+
if (HTML_INPUT_TYPES.has(type)) return [];
|
|
2255
|
+
const diagnostic = HtmlDiagnostics.input.unsupportedType(type);
|
|
2256
|
+
return [
|
|
2257
|
+
{
|
|
2258
|
+
valid: false,
|
|
2259
|
+
fixable: false,
|
|
2260
|
+
severity: diagnostic.severity,
|
|
2261
|
+
diagnostic
|
|
2262
|
+
}
|
|
2263
|
+
];
|
|
2264
|
+
},
|
|
2265
|
+
{ readsProps: ["type"], tags: ["input"] }
|
|
2266
|
+
);
|
|
2267
|
+
var checkedRequiresCheckableTypeRule = createInputAttributeTypeRule(policyFor("checked"));
|
|
2268
|
+
var multipleRequiresSupportedTypeRule = createInputAttributeTypeRule(policyFor("multiple"));
|
|
2269
|
+
var maxLengthRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("maxLength"));
|
|
2270
|
+
var minLengthRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("minLength"));
|
|
2271
|
+
var patternRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("pattern"));
|
|
2272
|
+
var minRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("min"));
|
|
2273
|
+
var maxRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("max"));
|
|
2274
|
+
var stepRequiresNumericTypeRule = createInputAttributeTypeRule(policyFor("step"));
|
|
2275
|
+
var acceptRequiresFileTypeRule = createInputAttributeTypeRule(policyFor("accept"));
|
|
2276
|
+
var captureRequiresFileTypeRule = createInputAttributeTypeRule(policyFor("capture"));
|
|
2277
|
+
var sizeRequiresTextTypeRule = createInputAttributeTypeRule(policyFor("size"));
|
|
2278
|
+
var altRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("alt"));
|
|
2279
|
+
var heightRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("height"));
|
|
2280
|
+
var widthRequiresImageTypeRule = createInputAttributeTypeRule(policyFor("width"));
|
|
2281
|
+
var inputAccessibleNameRule = Object.assign(
|
|
2282
|
+
({ tag, props }) => {
|
|
2283
|
+
if (tag !== "input" || props.type === "hidden") return [];
|
|
2284
|
+
if ("aria-label" in props || "aria-labelledby" in props) return [];
|
|
2285
|
+
const hasPlaceholder = typeof props.placeholder === "string" && props.placeholder.length > 0;
|
|
2286
|
+
const diagnostic = hasPlaceholder ? InputAccessibilityDiagnostics.placeholderIsNotLabel() : InputAccessibilityDiagnostics.missingAccessibleName();
|
|
2287
|
+
return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
|
|
2288
|
+
},
|
|
2289
|
+
{
|
|
2290
|
+
readsProps: ["type", "aria-label", "aria-labelledby", "placeholder"],
|
|
2291
|
+
tags: ["input"]
|
|
2292
|
+
}
|
|
2293
|
+
);
|
|
2294
|
+
var PASSWORD_AUTOCOMPLETE_VALUES = ["current-password", "new-password"];
|
|
2295
|
+
var passwordAutocompleteRule = Object.assign(
|
|
2296
|
+
({ tag, props }) => {
|
|
2297
|
+
if (tag !== "input" || props.type !== "password") return [];
|
|
2298
|
+
const autoComplete = props.autoComplete;
|
|
2299
|
+
const tokens = typeof autoComplete === "string" ? autoComplete.split(" ") : [];
|
|
2300
|
+
if (PASSWORD_AUTOCOMPLETE_VALUES.some((value) => tokens.includes(value))) return [];
|
|
2301
|
+
const diagnostic = InputAccessibilityDiagnostics.passwordMissingAutocomplete();
|
|
2302
|
+
return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
|
|
2303
|
+
},
|
|
2304
|
+
{ readsProps: ["type", "autoComplete"], tags: ["input"] }
|
|
2305
|
+
);
|
|
2306
|
+
var requiredReadOnlyConflictRule = createMutuallyExclusiveRule(REQUIRED_READONLY_CONFLICT);
|
|
2307
|
+
var INPUT_RULES = [
|
|
2308
|
+
supportedInputTypeRule,
|
|
2309
|
+
checkedRequiresCheckableTypeRule,
|
|
2310
|
+
multipleRequiresSupportedTypeRule,
|
|
2311
|
+
maxLengthRequiresTextTypeRule,
|
|
2312
|
+
minLengthRequiresTextTypeRule,
|
|
2313
|
+
patternRequiresTextTypeRule,
|
|
2314
|
+
minRequiresNumericTypeRule,
|
|
2315
|
+
maxRequiresNumericTypeRule,
|
|
2316
|
+
stepRequiresNumericTypeRule,
|
|
2317
|
+
acceptRequiresFileTypeRule,
|
|
2318
|
+
captureRequiresFileTypeRule,
|
|
2319
|
+
sizeRequiresTextTypeRule,
|
|
2320
|
+
altRequiresImageTypeRule,
|
|
2321
|
+
heightRequiresImageTypeRule,
|
|
2322
|
+
widthRequiresImageTypeRule,
|
|
2323
|
+
inputAccessibleNameRule,
|
|
2324
|
+
passwordAutocompleteRule,
|
|
2325
|
+
requiredReadOnlyConflictRule
|
|
2326
|
+
];
|
|
2327
|
+
|
|
2328
|
+
// ../core/src/html/spec/types.ts
|
|
2329
|
+
function definePropRolePolicy(prop, map2, fallback) {
|
|
2330
|
+
return { kind: "byProp", prop, map: map2, fallback };
|
|
2331
|
+
}
|
|
2332
|
+
function resolveAllowedRoles(spec, props) {
|
|
2333
|
+
const policy = spec.allowedRoles;
|
|
2334
|
+
if (!policy) return void 0;
|
|
2335
|
+
switch (policy.kind) {
|
|
2336
|
+
case "fixed":
|
|
2337
|
+
return policy.roles;
|
|
2338
|
+
case "byProp": {
|
|
2339
|
+
const value = typeof props[policy.prop] === "string" ? props[policy.prop] : policy.fallback;
|
|
2340
|
+
return policy.map[value];
|
|
2341
|
+
}
|
|
2342
|
+
case "dynamic":
|
|
2343
|
+
return policy.resolve({ props });
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
|
|
2347
|
+
// ../core/src/html/spec/roles/input.ts
|
|
2348
|
+
var ALLOWED_INPUT_ROLES = {
|
|
2349
|
+
checkbox: ["menuitemcheckbox", "option", "switch", "button"],
|
|
2350
|
+
radio: ["menuitemradio"],
|
|
2351
|
+
range: [],
|
|
2352
|
+
number: [],
|
|
2353
|
+
search: ["combobox"],
|
|
2354
|
+
text: ["combobox", "searchbox", "spinbutton"],
|
|
2355
|
+
email: ["combobox"],
|
|
2356
|
+
tel: ["combobox"],
|
|
2357
|
+
url: ["combobox"],
|
|
2358
|
+
button: [
|
|
2359
|
+
"link",
|
|
2360
|
+
"menuitem",
|
|
2361
|
+
"menuitemcheckbox",
|
|
2362
|
+
"menuitemradio",
|
|
2363
|
+
"option",
|
|
2364
|
+
"radio",
|
|
2365
|
+
"switch",
|
|
2366
|
+
"tab"
|
|
2367
|
+
],
|
|
2368
|
+
submit: [
|
|
2369
|
+
"link",
|
|
2370
|
+
"menuitem",
|
|
2371
|
+
"menuitemcheckbox",
|
|
2372
|
+
"menuitemradio",
|
|
2373
|
+
"option",
|
|
2374
|
+
"radio",
|
|
2375
|
+
"switch",
|
|
2376
|
+
"tab"
|
|
2377
|
+
],
|
|
2378
|
+
reset: [
|
|
2379
|
+
"link",
|
|
2380
|
+
"menuitem",
|
|
2381
|
+
"menuitemcheckbox",
|
|
2382
|
+
"menuitemradio",
|
|
2383
|
+
"option",
|
|
2384
|
+
"radio",
|
|
2385
|
+
"switch",
|
|
2386
|
+
"tab"
|
|
2387
|
+
],
|
|
2388
|
+
image: [
|
|
2389
|
+
"link",
|
|
2390
|
+
"menuitem",
|
|
2391
|
+
"menuitemcheckbox",
|
|
2392
|
+
"menuitemradio",
|
|
2393
|
+
"option",
|
|
2394
|
+
"radio",
|
|
2395
|
+
"switch",
|
|
2396
|
+
"tab"
|
|
2397
|
+
],
|
|
2398
|
+
hidden: []
|
|
2399
|
+
};
|
|
2400
|
+
|
|
2401
|
+
// ../core/src/html/spec/elements/input.ts
|
|
2402
|
+
var inputElementSpec = {
|
|
2403
|
+
tag: "input",
|
|
2404
|
+
allowedRoles: definePropRolePolicy("type", ALLOWED_INPUT_ROLES, "text"),
|
|
2405
|
+
attributes: INPUT_ATTRIBUTE_TYPE_POLICIES,
|
|
2406
|
+
mutuallyExclusive: INPUT_MUTUALLY_EXCLUSIVE_POLICIES
|
|
2407
|
+
};
|
|
2408
|
+
|
|
2409
|
+
// ../core/src/html/spec/roles/img.ts
|
|
2410
|
+
var IMG_NAMED_ROLES = [
|
|
2411
|
+
"button",
|
|
2412
|
+
"checkbox",
|
|
2413
|
+
"link",
|
|
2414
|
+
"menuitem",
|
|
2415
|
+
"menuitemcheckbox",
|
|
2416
|
+
"menuitemradio",
|
|
2417
|
+
"option",
|
|
2418
|
+
"progressbar",
|
|
2419
|
+
"scrollbar",
|
|
2420
|
+
"separator",
|
|
2421
|
+
"slider",
|
|
2422
|
+
"switch",
|
|
2423
|
+
"tab",
|
|
2424
|
+
"treeitem"
|
|
2425
|
+
];
|
|
2426
|
+
|
|
2427
|
+
// ../core/src/html/spec/elements/img.ts
|
|
2428
|
+
var imgElementSpec = {
|
|
2429
|
+
tag: "img",
|
|
2430
|
+
allowedRoles: {
|
|
2431
|
+
kind: "dynamic",
|
|
2432
|
+
resolve: ({ props }) => props.alt === "" ? [] : IMG_NAMED_ROLES
|
|
2433
|
+
}
|
|
2434
|
+
};
|
|
2435
|
+
|
|
2436
|
+
// ../core/src/html/spec/roles/table.ts
|
|
2437
|
+
var ALLOWED_TABLE_ROLES = ["grid", "treegrid"];
|
|
2438
|
+
|
|
2439
|
+
// ../core/src/html/spec/elements/table.ts
|
|
2440
|
+
var tableElementSpec = {
|
|
2441
|
+
tag: "table",
|
|
2442
|
+
allowedRoles: { kind: "fixed", roles: ALLOWED_TABLE_ROLES }
|
|
2443
|
+
};
|
|
2444
|
+
|
|
2445
|
+
// ../core/src/html/role-restrictions.ts
|
|
2446
|
+
var ALLOWED_ROLES = {
|
|
2447
|
+
article: ["application", "document", "feed", "main", "none", "presentation", "region"],
|
|
2448
|
+
aside: ["feed", "none", "presentation", "region", "search"],
|
|
2449
|
+
footer: ["group", "none", "presentation"],
|
|
2450
|
+
header: ["group", "none", "presentation"],
|
|
2451
|
+
main: [],
|
|
2452
|
+
nav: [],
|
|
2453
|
+
a: [
|
|
2454
|
+
"button",
|
|
2455
|
+
"checkbox",
|
|
2456
|
+
"menuitem",
|
|
2457
|
+
"menuitemcheckbox",
|
|
2458
|
+
"menuitemradio",
|
|
2459
|
+
"option",
|
|
2460
|
+
"radio",
|
|
2461
|
+
"switch",
|
|
2462
|
+
"tab",
|
|
2463
|
+
"treeitem"
|
|
2464
|
+
],
|
|
2465
|
+
button: [
|
|
2466
|
+
"checkbox",
|
|
2467
|
+
"link",
|
|
2468
|
+
"menuitem",
|
|
2469
|
+
"menuitemcheckbox",
|
|
2470
|
+
"menuitemradio",
|
|
2471
|
+
"option",
|
|
2472
|
+
"radio",
|
|
2473
|
+
"switch",
|
|
2474
|
+
"tab"
|
|
2475
|
+
],
|
|
2476
|
+
select: ["menu"],
|
|
2477
|
+
h1: ["tab", "presentation", "none"],
|
|
2478
|
+
h2: ["tab", "presentation", "none"],
|
|
2479
|
+
h3: ["tab", "presentation", "none"],
|
|
2480
|
+
h4: ["tab", "presentation", "none"],
|
|
2481
|
+
h5: ["tab", "presentation", "none"],
|
|
2482
|
+
h6: ["tab", "presentation", "none"],
|
|
2483
|
+
ul: [
|
|
2484
|
+
"directory",
|
|
2485
|
+
"group",
|
|
2486
|
+
"listbox",
|
|
2487
|
+
"menu",
|
|
2488
|
+
"menubar",
|
|
2489
|
+
"radiogroup",
|
|
2490
|
+
"tablist",
|
|
2491
|
+
"toolbar",
|
|
2492
|
+
"tree"
|
|
2493
|
+
],
|
|
2494
|
+
ol: [
|
|
2495
|
+
"directory",
|
|
2496
|
+
"group",
|
|
2497
|
+
"listbox",
|
|
2498
|
+
"menu",
|
|
2499
|
+
"menubar",
|
|
2500
|
+
"radiogroup",
|
|
2501
|
+
"tablist",
|
|
2502
|
+
"toolbar",
|
|
2503
|
+
"tree"
|
|
2504
|
+
],
|
|
2505
|
+
li: [
|
|
2506
|
+
"menuitem",
|
|
2507
|
+
"menuitemcheckbox",
|
|
2508
|
+
"menuitemradio",
|
|
2509
|
+
"option",
|
|
2510
|
+
"none",
|
|
2511
|
+
"presentation",
|
|
2512
|
+
"radio",
|
|
2513
|
+
"separator",
|
|
2514
|
+
"tab",
|
|
2515
|
+
"treeitem"
|
|
2516
|
+
],
|
|
2517
|
+
dialog: ["alertdialog"],
|
|
2518
|
+
fieldset: ["none", "presentation", "radiogroup"]
|
|
2519
|
+
};
|
|
2520
|
+
var ELEMENT_SPECS = {
|
|
2521
|
+
input: inputElementSpec,
|
|
2522
|
+
img: imgElementSpec,
|
|
2523
|
+
table: tableElementSpec
|
|
2524
|
+
};
|
|
2525
|
+
function getAllowedRoles(tag, props) {
|
|
2526
|
+
const spec = ELEMENT_SPECS[tag];
|
|
2527
|
+
if (spec) return resolveAllowedRoles(spec, props);
|
|
2528
|
+
return ALLOWED_ROLES[tag];
|
|
2529
|
+
}
|
|
2530
|
+
var removeRoleFix = {
|
|
2531
|
+
kind: "removeRole",
|
|
2532
|
+
apply: ({ props }) => {
|
|
2533
|
+
if (!("role" in props)) return { applied: false, next: props };
|
|
2534
|
+
const { role: _role, ...rest } = props;
|
|
2535
|
+
return { applied: true, next: rest, previous: props };
|
|
2536
|
+
}
|
|
2537
|
+
};
|
|
2538
|
+
var roleNotPermittedRule = Object.assign(
|
|
2539
|
+
({ tag, props, implicitRole }) => {
|
|
2540
|
+
const role = props.role;
|
|
2541
|
+
if (typeof role !== "string" || role.length === 0 || role === implicitRole) return [];
|
|
2542
|
+
const allowed = getAllowedRoles(tag, props);
|
|
2543
|
+
if (allowed === void 0 || allowed.includes(role)) return [];
|
|
2544
|
+
const diagnostic = HtmlDiagnostics.roleNotPermitted(tag, role, allowed);
|
|
2545
|
+
return [
|
|
2546
|
+
{
|
|
2547
|
+
valid: false,
|
|
2548
|
+
fixable: true,
|
|
2549
|
+
severity: diagnostic.severity,
|
|
2550
|
+
fix: removeRoleFix,
|
|
2551
|
+
diagnostic
|
|
2552
|
+
}
|
|
2553
|
+
];
|
|
2554
|
+
},
|
|
2555
|
+
{ readsProps: ["role", "type", "alt"] }
|
|
2556
|
+
);
|
|
2557
|
+
|
|
1990
2558
|
// ../core/src/html/aria-rules.ts
|
|
1991
2559
|
var LANDMARK_TAG_SET = /* @__PURE__ */ new Set(["article", "aside", "footer", "header", "main", "nav"]);
|
|
1992
2560
|
var removeLandmarkRoleOverride = {
|
|
@@ -1997,20 +2565,24 @@ var removeLandmarkRoleOverride = {
|
|
|
1997
2565
|
return { applied: true, next: rest, previous: props };
|
|
1998
2566
|
}
|
|
1999
2567
|
};
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
}
|
|
2568
|
+
var landmarkRoleRule = Object.assign(
|
|
2569
|
+
({ tag, props, implicitRole }) => {
|
|
2570
|
+
if (!LANDMARK_TAG_SET.has(tag) || !implicitRole) return [];
|
|
2571
|
+
const role = props.role;
|
|
2572
|
+
if (!role || role === implicitRole) return [];
|
|
2573
|
+
const diagnostic = HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role);
|
|
2574
|
+
return [
|
|
2575
|
+
{
|
|
2576
|
+
valid: false,
|
|
2577
|
+
fixable: true,
|
|
2578
|
+
severity: diagnostic.severity,
|
|
2579
|
+
fix: removeLandmarkRoleOverride,
|
|
2580
|
+
diagnostic
|
|
2581
|
+
}
|
|
2582
|
+
];
|
|
2583
|
+
},
|
|
2584
|
+
{ tags: [...LANDMARK_TAG_SET] }
|
|
2585
|
+
);
|
|
2014
2586
|
function requireAccessibleName({ tag, props }) {
|
|
2015
2587
|
if ("aria-label" in props || "aria-labelledby" in props) return [];
|
|
2016
2588
|
return [
|
|
@@ -2023,11 +2595,19 @@ function requireAccessibleName({ tag, props }) {
|
|
|
2023
2595
|
];
|
|
2024
2596
|
}
|
|
2025
2597
|
var NAMED_LANDMARK_TAGS = /* @__PURE__ */ new Set(["nav", "aside"]);
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2598
|
+
var landmarkNameAdvisory = Object.assign(
|
|
2599
|
+
(ctx) => {
|
|
2600
|
+
if (!ctx.implicitRole || !NAMED_LANDMARK_TAGS.has(ctx.tag)) return [];
|
|
2601
|
+
return requireAccessibleName(ctx);
|
|
2602
|
+
},
|
|
2603
|
+
{ tags: [...NAMED_LANDMARK_TAGS] }
|
|
2604
|
+
);
|
|
2605
|
+
var HTML_ARIA_RULES = [
|
|
2606
|
+
landmarkRoleRule,
|
|
2607
|
+
landmarkNameAdvisory,
|
|
2608
|
+
roleNotPermittedRule,
|
|
2609
|
+
...INPUT_RULES
|
|
2610
|
+
];
|
|
2031
2611
|
|
|
2032
2612
|
// ../core/src/html/contracts.ts
|
|
2033
2613
|
import { warnDiagnostics } from "../_shared/diagnostics.js";
|
|
@@ -2122,6 +2702,62 @@ var figureContract = contract([
|
|
|
2122
2702
|
]);
|
|
2123
2703
|
var detailsContract = firstChildContract("summary", "summary");
|
|
2124
2704
|
var fieldsetContract = firstChildContract("legend", "legend");
|
|
2705
|
+
var objectContract = contract([
|
|
2706
|
+
{ name: "param", match: isTag("param") },
|
|
2707
|
+
{ name: "content", match: isOpenContent("param") }
|
|
2708
|
+
]);
|
|
2709
|
+
var INTERACTIVE_CONTENT_TAGS = ["a", "button", "input", "select", "textarea", "label"];
|
|
2710
|
+
var buttonContract = closedContract([
|
|
2711
|
+
{ name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
|
|
2712
|
+
]);
|
|
2713
|
+
var anchorContract = closedContract([
|
|
2714
|
+
{ name: "content", match: isOpenContent(...INTERACTIVE_CONTENT_TAGS) }
|
|
2715
|
+
]);
|
|
2716
|
+
var LABELABLE_TAGS = [
|
|
2717
|
+
"button",
|
|
2718
|
+
"input",
|
|
2719
|
+
"meter",
|
|
2720
|
+
"output",
|
|
2721
|
+
"progress",
|
|
2722
|
+
"select",
|
|
2723
|
+
"textarea"
|
|
2724
|
+
];
|
|
2725
|
+
var labelContract = contract([
|
|
2726
|
+
{ name: "control", match: isTag(...LABELABLE_TAGS), cardinality: { max: 1 } }
|
|
2727
|
+
]);
|
|
2728
|
+
var P_BLOCKED_TAGS = [
|
|
2729
|
+
"address",
|
|
2730
|
+
"article",
|
|
2731
|
+
"aside",
|
|
2732
|
+
"blockquote",
|
|
2733
|
+
"details",
|
|
2734
|
+
"dialog",
|
|
2735
|
+
"div",
|
|
2736
|
+
"dl",
|
|
2737
|
+
"fieldset",
|
|
2738
|
+
"figure",
|
|
2739
|
+
"footer",
|
|
2740
|
+
"form",
|
|
2741
|
+
"h1",
|
|
2742
|
+
"h2",
|
|
2743
|
+
"h3",
|
|
2744
|
+
"h4",
|
|
2745
|
+
"h5",
|
|
2746
|
+
"h6",
|
|
2747
|
+
"header",
|
|
2748
|
+
"hr",
|
|
2749
|
+
"main",
|
|
2750
|
+
"nav",
|
|
2751
|
+
"ol",
|
|
2752
|
+
"p",
|
|
2753
|
+
"pre",
|
|
2754
|
+
"section",
|
|
2755
|
+
"table",
|
|
2756
|
+
"ul"
|
|
2757
|
+
];
|
|
2758
|
+
var pContract = closedContract([
|
|
2759
|
+
{ name: "content", match: isOpenContent(...P_BLOCKED_TAGS) }
|
|
2760
|
+
]);
|
|
2125
2761
|
var mediaContract = contract([
|
|
2126
2762
|
{ name: "source", match: isTag("source") },
|
|
2127
2763
|
{ name: "track", match: isTag("track") },
|
|
@@ -2176,6 +2812,11 @@ var htmlContracts = {
|
|
|
2176
2812
|
details: detailsContract,
|
|
2177
2813
|
fieldset: fieldsetContract,
|
|
2178
2814
|
dialog: dialogContract,
|
|
2815
|
+
object: objectContract,
|
|
2816
|
+
button: buttonContract,
|
|
2817
|
+
a: anchorContract,
|
|
2818
|
+
label: labelContract,
|
|
2819
|
+
p: pContract,
|
|
2179
2820
|
head: headContract,
|
|
2180
2821
|
html: htmlContract
|
|
2181
2822
|
};
|
|
@@ -2488,21 +3129,21 @@ function validateRenderProps(diagnostics, options, props, recipeKey) {
|
|
|
2488
3129
|
import { throwDiagnostics } from "../_shared/diagnostics.js";
|
|
2489
3130
|
|
|
2490
3131
|
// ../core/src/factory/plugin-diagnostics.ts
|
|
2491
|
-
import { DiagnosticCategory as
|
|
3132
|
+
import { DiagnosticCategory as DiagnosticCategory6, DiagnosticCode as DiagnosticCode6 } from "../_shared/diagnostics.js";
|
|
2492
3133
|
var PluginDiagnostics = {
|
|
2493
3134
|
invalidShape(received) {
|
|
2494
3135
|
const got = received === null ? "null" : typeof received;
|
|
2495
3136
|
return {
|
|
2496
|
-
code:
|
|
2497
|
-
category:
|
|
3137
|
+
code: DiagnosticCode6.PluginInvalidShape,
|
|
3138
|
+
category: DiagnosticCategory6.Internal,
|
|
2498
3139
|
message: `[praxis-kit] Plugin factory must return an object with a 'pipeline' function. Got: ${got}.`
|
|
2499
3140
|
};
|
|
2500
3141
|
},
|
|
2501
3142
|
pipelineReturnType(received) {
|
|
2502
3143
|
const got = received === null ? "null" : Array.isArray(received) ? "array" : typeof received;
|
|
2503
3144
|
return {
|
|
2504
|
-
code:
|
|
2505
|
-
category:
|
|
3145
|
+
code: DiagnosticCode6.PluginPipelineReturnType,
|
|
3146
|
+
category: DiagnosticCategory6.Internal,
|
|
2506
3147
|
message: `[praxis-kit] Plugin pipeline must return a string. Got: ${got}.`
|
|
2507
3148
|
};
|
|
2508
3149
|
}
|