praxis-kit 6.2.0 → 6.2.3
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 +16 -0
- package/dist/_shared/diagnostics.js +16 -0
- package/dist/{chunk-4YVNOMUS.js → chunk-H3D2JA3J.js} +491 -26
- package/dist/lit/index.js +480 -15
- package/dist/preact/index.js +491 -26
- package/dist/react/index.js +1 -1
- package/dist/react/legacy.js +1 -1
- package/dist/solid/index.js +491 -26
- package/dist/svelte/index.js +491 -26
- package/dist/tailwind/index.js +1 -1
- package/dist/utils/index.d.ts +15 -0
- package/dist/utils/index.js +15 -0
- package/dist/vite-plugin/index.d.ts +16 -0
- package/dist/vue/index.js +491 -26
- package/dist/web/index.js +480 -15
- package/package.json +9 -2
|
@@ -993,11 +993,24 @@ var ContractDiagnostics = {
|
|
|
993
993
|
|
|
994
994
|
// ../../lib/contract/src/diagnostics/html.ts
|
|
995
995
|
import { DiagnosticCategory as DiagnosticCategory3, DiagnosticCode as DiagnosticCode3 } from "./_shared/diagnostics.js";
|
|
996
|
+
var ATTRIBUTE_IGNORED_CODES = {
|
|
997
|
+
checked: DiagnosticCode3.HtmlInputCheckedIgnoredForType,
|
|
998
|
+
multiple: DiagnosticCode3.HtmlInputMultipleIgnoredForType,
|
|
999
|
+
maxLength: DiagnosticCode3.HtmlInputMaxLengthIgnoredForType,
|
|
1000
|
+
minLength: DiagnosticCode3.HtmlInputMinLengthIgnoredForType,
|
|
1001
|
+
pattern: DiagnosticCode3.HtmlInputPatternIgnoredForType,
|
|
1002
|
+
min: DiagnosticCode3.HtmlInputMinIgnoredForType,
|
|
1003
|
+
max: DiagnosticCode3.HtmlInputMaxIgnoredForType,
|
|
1004
|
+
step: DiagnosticCode3.HtmlInputStepIgnoredForType,
|
|
1005
|
+
accept: DiagnosticCode3.HtmlInputAcceptIgnoredForType,
|
|
1006
|
+
capture: DiagnosticCode3.HtmlInputCaptureIgnoredForType
|
|
1007
|
+
};
|
|
996
1008
|
var HtmlDiagnostics = {
|
|
997
1009
|
emptyRole(tag) {
|
|
998
1010
|
return {
|
|
999
1011
|
code: DiagnosticCode3.HtmlEmptyRole,
|
|
1000
1012
|
category: DiagnosticCategory3.HTML,
|
|
1013
|
+
severity: "warning",
|
|
1001
1014
|
message: `<${tag}> has an explicit empty role="". Omit the attribute instead.`
|
|
1002
1015
|
};
|
|
1003
1016
|
},
|
|
@@ -1005,6 +1018,7 @@ var HtmlDiagnostics = {
|
|
|
1005
1018
|
return {
|
|
1006
1019
|
code: DiagnosticCode3.HtmlImplicitRoleRedundant,
|
|
1007
1020
|
category: DiagnosticCategory3.HTML,
|
|
1021
|
+
severity: "warning",
|
|
1008
1022
|
message: `<${tag}> already has implicit role="${implicitRole}". Avoid redundant role assignment.`
|
|
1009
1023
|
};
|
|
1010
1024
|
},
|
|
@@ -1012,6 +1026,7 @@ var HtmlDiagnostics = {
|
|
|
1012
1026
|
return {
|
|
1013
1027
|
code: DiagnosticCode3.HtmlImplicitRoleOverride,
|
|
1014
1028
|
category: DiagnosticCategory3.HTML,
|
|
1029
|
+
severity: "error",
|
|
1015
1030
|
message: `<${tag}> should not override its implicit role="${implicitRole}" with role="${role}".`
|
|
1016
1031
|
};
|
|
1017
1032
|
},
|
|
@@ -1019,6 +1034,7 @@ var HtmlDiagnostics = {
|
|
|
1019
1034
|
return {
|
|
1020
1035
|
code: DiagnosticCode3.HtmlStandaloneRegionOverride,
|
|
1021
1036
|
category: DiagnosticCategory3.HTML,
|
|
1037
|
+
severity: "error",
|
|
1022
1038
|
message: `<${tag}> is a self-contained element with implicit role="${implicitRole}". Assigning role="region" has been removed.`
|
|
1023
1039
|
};
|
|
1024
1040
|
},
|
|
@@ -1026,6 +1042,7 @@ var HtmlDiagnostics = {
|
|
|
1026
1042
|
return {
|
|
1027
1043
|
code: DiagnosticCode3.HtmlLandmarkRoleOverride,
|
|
1028
1044
|
category: DiagnosticCategory3.HTML,
|
|
1045
|
+
severity: "error",
|
|
1029
1046
|
message: `<${tag}> has a fixed landmark role="${implicitRole}". role="${role}" overrides it and confuses assistive technology. The override has been removed.`
|
|
1030
1047
|
};
|
|
1031
1048
|
},
|
|
@@ -1033,34 +1050,148 @@ var HtmlDiagnostics = {
|
|
|
1033
1050
|
return {
|
|
1034
1051
|
code: DiagnosticCode3.HtmlInvalidChild,
|
|
1035
1052
|
category: DiagnosticCategory3.HTML,
|
|
1053
|
+
severity: "error",
|
|
1036
1054
|
message: `<${child}> is not a valid direct child of <${parent}>. Allowed: ${allowed}.`
|
|
1037
1055
|
};
|
|
1056
|
+
},
|
|
1057
|
+
roleNotPermitted(tag, role, allowedRoles) {
|
|
1058
|
+
const allowed = allowedRoles.length > 0 ? allowedRoles.map((r) => `"${r}"`).join(", ") : "none \u2014 no explicit role is permitted on this element";
|
|
1059
|
+
return {
|
|
1060
|
+
code: DiagnosticCode3.HtmlRoleNotPermitted,
|
|
1061
|
+
category: DiagnosticCategory3.HTML,
|
|
1062
|
+
severity: "error",
|
|
1063
|
+
message: `role="${role}" is not permitted on <${tag}>. Allowed alternate role(s): ${allowed}.`,
|
|
1064
|
+
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.'
|
|
1065
|
+
};
|
|
1066
|
+
},
|
|
1067
|
+
// Reserved for <input>-specific facts (HTML3101–3199, see codes.ts) — later element families
|
|
1068
|
+
// (button, img, table, ...) get their own reserved block and their own namespace here.
|
|
1069
|
+
input: {
|
|
1070
|
+
unsupportedType(type) {
|
|
1071
|
+
return {
|
|
1072
|
+
code: DiagnosticCode3.HtmlInputUnsupportedType,
|
|
1073
|
+
category: DiagnosticCategory3.HTML,
|
|
1074
|
+
severity: "warning",
|
|
1075
|
+
message: `type="${type}" is not a value defined by the HTML specification. Browsers silently fall back to type="text".`,
|
|
1076
|
+
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).',
|
|
1077
|
+
suggestions: [
|
|
1078
|
+
{
|
|
1079
|
+
title: "Check for a typo in the type value",
|
|
1080
|
+
description: `"${type}" does not match any HTML5 input type.`
|
|
1081
|
+
}
|
|
1082
|
+
]
|
|
1083
|
+
};
|
|
1084
|
+
},
|
|
1085
|
+
// The user-visible problem is that the attribute is ignored — not that it "requires" a type;
|
|
1086
|
+
// that's the rule's internal framing, not what the browser actually does.
|
|
1087
|
+
attributeIgnoredForType(attribute, type, allowedTypes) {
|
|
1088
|
+
const allowed = allowedTypes.map((t) => `"${t}"`).join(", ");
|
|
1089
|
+
const code = ATTRIBUTE_IGNORED_CODES[attribute];
|
|
1090
|
+
if (!code) throw new Error(`No DiagnosticCode registered for input attribute "${attribute}"`);
|
|
1091
|
+
return {
|
|
1092
|
+
code,
|
|
1093
|
+
category: DiagnosticCategory3.HTML,
|
|
1094
|
+
severity: "warning",
|
|
1095
|
+
message: `"${attribute}" is ignored on <input type="${type}">.`,
|
|
1096
|
+
rationale: `"${attribute}" only has an effect when type is one of: ${allowed}. Browsers silently ignore it on other input types.`,
|
|
1097
|
+
suggestions: [
|
|
1098
|
+
{
|
|
1099
|
+
title: `Remove "${attribute}"`,
|
|
1100
|
+
description: `"${attribute}" only affects <input> when type is one of: ${allowed}.`
|
|
1101
|
+
}
|
|
1102
|
+
]
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1038
1105
|
}
|
|
1039
1106
|
};
|
|
1040
1107
|
|
|
1041
|
-
// ../../lib/contract/src/diagnostics/
|
|
1108
|
+
// ../../lib/contract/src/diagnostics/input-accessibility.ts
|
|
1042
1109
|
import { DiagnosticCategory as DiagnosticCategory4, DiagnosticCode as DiagnosticCode4 } from "./_shared/diagnostics.js";
|
|
1110
|
+
function accessibilityFact(input) {
|
|
1111
|
+
return { category: DiagnosticCategory4.Accessibility, ...input };
|
|
1112
|
+
}
|
|
1113
|
+
var InputAccessibilityDiagnostics = {
|
|
1114
|
+
missingAccessibleName() {
|
|
1115
|
+
return accessibilityFact({
|
|
1116
|
+
code: DiagnosticCode4.A11yInputMissingAccessibleName,
|
|
1117
|
+
severity: "warning",
|
|
1118
|
+
message: "This input has no accessible name. Add an associated <label>, aria-label, or aria-labelledby.",
|
|
1119
|
+
rationale: "Assistive technology announces a form field by its accessible name; without one, users of screen readers cannot tell what the field is for.",
|
|
1120
|
+
suggestions: [
|
|
1121
|
+
{ title: "Add aria-label", description: 'Set aria-label="\u2026" directly on the input.' },
|
|
1122
|
+
{
|
|
1123
|
+
title: "Add an associated <label>",
|
|
1124
|
+
description: 'Wrap the input in a <label>, or point a <label for="\u2026"> at its id.'
|
|
1125
|
+
}
|
|
1126
|
+
]
|
|
1127
|
+
});
|
|
1128
|
+
},
|
|
1129
|
+
placeholderIsNotLabel() {
|
|
1130
|
+
return accessibilityFact({
|
|
1131
|
+
code: DiagnosticCode4.A11yInputPlaceholderNotLabel,
|
|
1132
|
+
severity: "warning",
|
|
1133
|
+
message: "Placeholder text does not provide an accessible name. Add an associated <label>, aria-label, or aria-labelledby.",
|
|
1134
|
+
rationale: "Placeholder text disappears as users interact with the field and is not treated as the control's accessible name by many assistive technologies.",
|
|
1135
|
+
suggestions: [
|
|
1136
|
+
{ title: "Add aria-label", description: 'Set aria-label="\u2026" directly on the input.' },
|
|
1137
|
+
{
|
|
1138
|
+
title: "Add an associated <label>",
|
|
1139
|
+
description: 'Wrap the input in a <label>, or point a <label for="\u2026"> at its id.'
|
|
1140
|
+
}
|
|
1141
|
+
]
|
|
1142
|
+
});
|
|
1143
|
+
},
|
|
1144
|
+
passwordMissingAutocomplete() {
|
|
1145
|
+
return accessibilityFact({
|
|
1146
|
+
code: DiagnosticCode4.A11yInputPasswordAutocomplete,
|
|
1147
|
+
severity: "warning",
|
|
1148
|
+
message: "Password inputs should specify an autoComplete value.",
|
|
1149
|
+
rationale: "Without an explicit autocomplete hint, password managers and browsers cannot reliably tell a sign-in field apart from a password-creation field.",
|
|
1150
|
+
suggestions: [
|
|
1151
|
+
{
|
|
1152
|
+
title: 'Set autoComplete="current-password"',
|
|
1153
|
+
description: "Use this for sign-in forms."
|
|
1154
|
+
},
|
|
1155
|
+
{
|
|
1156
|
+
title: 'Set autoComplete="new-password"',
|
|
1157
|
+
description: "Use this for sign-up / change-password forms."
|
|
1158
|
+
}
|
|
1159
|
+
]
|
|
1160
|
+
});
|
|
1161
|
+
},
|
|
1162
|
+
requiredReadOnlyConflict() {
|
|
1163
|
+
return accessibilityFact({
|
|
1164
|
+
code: DiagnosticCode4.A11yInputRequiredReadOnlyConflict,
|
|
1165
|
+
severity: "warning",
|
|
1166
|
+
message: "The required and readOnly attributes are both present. A read-only field cannot satisfy required validation through user interaction.",
|
|
1167
|
+
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."
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
|
|
1172
|
+
// ../../lib/contract/src/diagnostics/slot.ts
|
|
1173
|
+
import { DiagnosticCategory as DiagnosticCategory5, DiagnosticCode as DiagnosticCode5 } from "./_shared/diagnostics.js";
|
|
1043
1174
|
var SlotDiagnostics = {
|
|
1044
1175
|
exclusive(name) {
|
|
1045
1176
|
return {
|
|
1046
|
-
code:
|
|
1047
|
-
category:
|
|
1177
|
+
code: DiagnosticCode5.SlotExclusive,
|
|
1178
|
+
category: DiagnosticCategory5.Contract,
|
|
1048
1179
|
component: name,
|
|
1049
1180
|
message: `${name}: "as" and "asChild" are mutually exclusive`
|
|
1050
1181
|
};
|
|
1051
1182
|
},
|
|
1052
1183
|
singleChildRequired(name, elementTerm) {
|
|
1053
1184
|
return {
|
|
1054
|
-
code:
|
|
1055
|
-
category:
|
|
1185
|
+
code: DiagnosticCode5.SlotSingleChild,
|
|
1186
|
+
category: DiagnosticCategory5.Contract,
|
|
1056
1187
|
component: name,
|
|
1057
1188
|
message: `${name}: asChild requires a ${elementTerm} child`
|
|
1058
1189
|
};
|
|
1059
1190
|
},
|
|
1060
1191
|
singleChildExceeded(name, elementTerm, count) {
|
|
1061
1192
|
return {
|
|
1062
|
-
code:
|
|
1063
|
-
category:
|
|
1193
|
+
code: DiagnosticCode5.SlotSingleChild,
|
|
1194
|
+
category: DiagnosticCategory5.Contract,
|
|
1064
1195
|
component: name,
|
|
1065
1196
|
message: `${name}: asChild requires exactly one ${elementTerm} child, got ${count}`
|
|
1066
1197
|
};
|
|
@@ -1068,16 +1199,16 @@ var SlotDiagnostics = {
|
|
|
1068
1199
|
discardedChildren(name, elementTerm, count) {
|
|
1069
1200
|
const suffix = count === 1 ? "" : "ren";
|
|
1070
1201
|
return {
|
|
1071
|
-
code:
|
|
1072
|
-
category:
|
|
1202
|
+
code: DiagnosticCode5.SlotDiscardedChildren,
|
|
1203
|
+
category: DiagnosticCategory5.Contract,
|
|
1073
1204
|
component: name,
|
|
1074
1205
|
message: `${name}: asChild discarded ${count} non-element child${suffix} \u2014 only ${elementTerm}s are valid asChild children.`
|
|
1075
1206
|
};
|
|
1076
1207
|
},
|
|
1077
1208
|
renderFnRequired(name, received) {
|
|
1078
1209
|
return {
|
|
1079
|
-
code:
|
|
1080
|
-
category:
|
|
1210
|
+
code: DiagnosticCode5.SlotRenderFn,
|
|
1211
|
+
category: DiagnosticCategory5.Contract,
|
|
1081
1212
|
component: name,
|
|
1082
1213
|
message: `${name}: asChild requires a render function as children, got ${received}`
|
|
1083
1214
|
};
|
|
@@ -1139,7 +1270,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1139
1270
|
tag,
|
|
1140
1271
|
role: "",
|
|
1141
1272
|
attribute: void 0,
|
|
1142
|
-
severity:
|
|
1273
|
+
severity: d.severity,
|
|
1143
1274
|
phase: "evaluate"
|
|
1144
1275
|
}
|
|
1145
1276
|
]
|
|
@@ -1398,13 +1529,14 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1398
1529
|
const role = props.role;
|
|
1399
1530
|
if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS;
|
|
1400
1531
|
if (isStrongImplicitRole(tag) && role === "region") {
|
|
1532
|
+
const diagnostic = HtmlDiagnostics.implicitRoleOverride(tag, implicitRole, role);
|
|
1401
1533
|
return [
|
|
1402
1534
|
{
|
|
1403
1535
|
valid: false,
|
|
1404
1536
|
fixable: true,
|
|
1405
|
-
severity:
|
|
1537
|
+
severity: diagnostic.severity,
|
|
1406
1538
|
fix: _AriaPolicyEngine.#removeRole,
|
|
1407
|
-
diagnostic
|
|
1539
|
+
diagnostic
|
|
1408
1540
|
}
|
|
1409
1541
|
];
|
|
1410
1542
|
}
|
|
@@ -1413,13 +1545,14 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1413
1545
|
static #checkRedundantRole({ tag, props, implicitRole }) {
|
|
1414
1546
|
const role = props.role;
|
|
1415
1547
|
if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS;
|
|
1548
|
+
const diagnostic = HtmlDiagnostics.implicitRoleRedundant(tag, implicitRole);
|
|
1416
1549
|
return [
|
|
1417
1550
|
{
|
|
1418
1551
|
valid: false,
|
|
1419
1552
|
fixable: true,
|
|
1420
|
-
severity:
|
|
1553
|
+
severity: diagnostic.severity,
|
|
1421
1554
|
fix: _AriaPolicyEngine.#removeRole,
|
|
1422
|
-
diagnostic
|
|
1555
|
+
diagnostic
|
|
1423
1556
|
}
|
|
1424
1557
|
];
|
|
1425
1558
|
}
|
|
@@ -1427,13 +1560,14 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1427
1560
|
const role = props.role;
|
|
1428
1561
|
if (role !== "region") return NO_VIOLATIONS;
|
|
1429
1562
|
if (!isStandaloneTag(tag)) return NO_VIOLATIONS;
|
|
1563
|
+
const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
|
|
1430
1564
|
return [
|
|
1431
1565
|
{
|
|
1432
1566
|
valid: false,
|
|
1433
1567
|
fixable: true,
|
|
1434
|
-
severity:
|
|
1568
|
+
severity: diagnostic.severity,
|
|
1435
1569
|
fix: _AriaPolicyEngine.#removeRole,
|
|
1436
|
-
diagnostic
|
|
1570
|
+
diagnostic
|
|
1437
1571
|
}
|
|
1438
1572
|
];
|
|
1439
1573
|
}
|
|
@@ -2122,6 +2256,331 @@ var readonlyProps = ({
|
|
|
2122
2256
|
// ../core/src/html/evaluators.ts
|
|
2123
2257
|
import { warnDiagnostics as warnDiagnostics2 } from "./_shared/diagnostics.js";
|
|
2124
2258
|
|
|
2259
|
+
// ../core/src/html/input-rules.ts
|
|
2260
|
+
var DEFAULT_INPUT_TYPE = "text";
|
|
2261
|
+
function omit(props, key) {
|
|
2262
|
+
const next = { ...props };
|
|
2263
|
+
delete next[key];
|
|
2264
|
+
return next;
|
|
2265
|
+
}
|
|
2266
|
+
function removeAttributeFix(attribute) {
|
|
2267
|
+
return {
|
|
2268
|
+
kind: `removeAttribute:${attribute}`,
|
|
2269
|
+
apply: ({ props }) => {
|
|
2270
|
+
if (!(attribute in props)) return { applied: false, next: props };
|
|
2271
|
+
return { applied: true, next: omit(props, attribute), previous: props };
|
|
2272
|
+
}
|
|
2273
|
+
};
|
|
2274
|
+
}
|
|
2275
|
+
function inputAttributeRequiresType(attribute, allowedTypes) {
|
|
2276
|
+
const rule = ({ tag, props }) => {
|
|
2277
|
+
if (tag !== "input" || !(attribute in props)) return [];
|
|
2278
|
+
const type = typeof props.type === "string" ? props.type : DEFAULT_INPUT_TYPE;
|
|
2279
|
+
if (allowedTypes.includes(type)) return [];
|
|
2280
|
+
const diagnostic = HtmlDiagnostics.input.attributeIgnoredForType(attribute, type, allowedTypes);
|
|
2281
|
+
return [
|
|
2282
|
+
{
|
|
2283
|
+
valid: false,
|
|
2284
|
+
fixable: true,
|
|
2285
|
+
severity: diagnostic.severity,
|
|
2286
|
+
fix: removeAttributeFix(attribute),
|
|
2287
|
+
diagnostic
|
|
2288
|
+
}
|
|
2289
|
+
];
|
|
2290
|
+
};
|
|
2291
|
+
return Object.assign(rule, { readsProps: ["type", attribute] });
|
|
2292
|
+
}
|
|
2293
|
+
var TEXT_INPUT_TYPES = ["text", "search", "url", "tel", "email", "password"];
|
|
2294
|
+
var NUMERIC_INPUT_TYPES = [
|
|
2295
|
+
"number",
|
|
2296
|
+
"range",
|
|
2297
|
+
"date",
|
|
2298
|
+
"month",
|
|
2299
|
+
"week",
|
|
2300
|
+
"time",
|
|
2301
|
+
"datetime-local"
|
|
2302
|
+
];
|
|
2303
|
+
var HTML_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
2304
|
+
...TEXT_INPUT_TYPES,
|
|
2305
|
+
...NUMERIC_INPUT_TYPES,
|
|
2306
|
+
"checkbox",
|
|
2307
|
+
"radio",
|
|
2308
|
+
"file",
|
|
2309
|
+
"color",
|
|
2310
|
+
"hidden",
|
|
2311
|
+
"button",
|
|
2312
|
+
"submit",
|
|
2313
|
+
"reset",
|
|
2314
|
+
"image"
|
|
2315
|
+
]);
|
|
2316
|
+
var supportedInputTypeRule = Object.assign(
|
|
2317
|
+
({ tag, props }) => {
|
|
2318
|
+
if (tag !== "input" || typeof props.type !== "string") return [];
|
|
2319
|
+
const type = props.type;
|
|
2320
|
+
if (HTML_INPUT_TYPES.has(type)) return [];
|
|
2321
|
+
const diagnostic = HtmlDiagnostics.input.unsupportedType(type);
|
|
2322
|
+
return [
|
|
2323
|
+
{
|
|
2324
|
+
valid: false,
|
|
2325
|
+
fixable: false,
|
|
2326
|
+
severity: diagnostic.severity,
|
|
2327
|
+
diagnostic
|
|
2328
|
+
}
|
|
2329
|
+
];
|
|
2330
|
+
},
|
|
2331
|
+
{ readsProps: ["type"] }
|
|
2332
|
+
);
|
|
2333
|
+
var checkedRequiresCheckableTypeRule = inputAttributeRequiresType("checked", [
|
|
2334
|
+
"checkbox",
|
|
2335
|
+
"radio"
|
|
2336
|
+
]);
|
|
2337
|
+
var multipleRequiresSupportedTypeRule = inputAttributeRequiresType("multiple", [
|
|
2338
|
+
"email",
|
|
2339
|
+
"file"
|
|
2340
|
+
]);
|
|
2341
|
+
var maxLengthRequiresTextTypeRule = inputAttributeRequiresType(
|
|
2342
|
+
"maxLength",
|
|
2343
|
+
TEXT_INPUT_TYPES
|
|
2344
|
+
);
|
|
2345
|
+
var minLengthRequiresTextTypeRule = inputAttributeRequiresType(
|
|
2346
|
+
"minLength",
|
|
2347
|
+
TEXT_INPUT_TYPES
|
|
2348
|
+
);
|
|
2349
|
+
var patternRequiresTextTypeRule = inputAttributeRequiresType("pattern", TEXT_INPUT_TYPES);
|
|
2350
|
+
var minRequiresNumericTypeRule = inputAttributeRequiresType("min", NUMERIC_INPUT_TYPES);
|
|
2351
|
+
var maxRequiresNumericTypeRule = inputAttributeRequiresType("max", NUMERIC_INPUT_TYPES);
|
|
2352
|
+
var stepRequiresNumericTypeRule = inputAttributeRequiresType("step", NUMERIC_INPUT_TYPES);
|
|
2353
|
+
var acceptRequiresFileTypeRule = inputAttributeRequiresType("accept", ["file"]);
|
|
2354
|
+
var captureRequiresFileTypeRule = inputAttributeRequiresType("capture", ["file"]);
|
|
2355
|
+
var inputAccessibleNameRule = Object.assign(
|
|
2356
|
+
({ tag, props }) => {
|
|
2357
|
+
if (tag !== "input" || props.type === "hidden") return [];
|
|
2358
|
+
if ("aria-label" in props || "aria-labelledby" in props) return [];
|
|
2359
|
+
const hasPlaceholder = typeof props.placeholder === "string" && props.placeholder.length > 0;
|
|
2360
|
+
const diagnostic = hasPlaceholder ? InputAccessibilityDiagnostics.placeholderIsNotLabel() : InputAccessibilityDiagnostics.missingAccessibleName();
|
|
2361
|
+
return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
|
|
2362
|
+
},
|
|
2363
|
+
{ readsProps: ["type", "aria-label", "aria-labelledby", "placeholder"] }
|
|
2364
|
+
);
|
|
2365
|
+
var PASSWORD_AUTOCOMPLETE_VALUES = ["current-password", "new-password"];
|
|
2366
|
+
var passwordAutocompleteRule = Object.assign(
|
|
2367
|
+
({ tag, props }) => {
|
|
2368
|
+
if (tag !== "input" || props.type !== "password") return [];
|
|
2369
|
+
const autoComplete = props.autoComplete;
|
|
2370
|
+
const tokens = typeof autoComplete === "string" ? autoComplete.split(" ") : [];
|
|
2371
|
+
if (PASSWORD_AUTOCOMPLETE_VALUES.some((value) => tokens.includes(value))) return [];
|
|
2372
|
+
const diagnostic = InputAccessibilityDiagnostics.passwordMissingAutocomplete();
|
|
2373
|
+
return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
|
|
2374
|
+
},
|
|
2375
|
+
{ readsProps: ["type", "autoComplete"] }
|
|
2376
|
+
);
|
|
2377
|
+
var requiredReadOnlyConflictRule = Object.assign(
|
|
2378
|
+
({ tag, props }) => {
|
|
2379
|
+
if (tag !== "input" || !props.required || !props.readOnly) return [];
|
|
2380
|
+
const diagnostic = InputAccessibilityDiagnostics.requiredReadOnlyConflict();
|
|
2381
|
+
return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
|
|
2382
|
+
},
|
|
2383
|
+
{ readsProps: ["required", "readOnly"] }
|
|
2384
|
+
);
|
|
2385
|
+
var INPUT_RULES = [
|
|
2386
|
+
supportedInputTypeRule,
|
|
2387
|
+
checkedRequiresCheckableTypeRule,
|
|
2388
|
+
multipleRequiresSupportedTypeRule,
|
|
2389
|
+
maxLengthRequiresTextTypeRule,
|
|
2390
|
+
minLengthRequiresTextTypeRule,
|
|
2391
|
+
patternRequiresTextTypeRule,
|
|
2392
|
+
minRequiresNumericTypeRule,
|
|
2393
|
+
maxRequiresNumericTypeRule,
|
|
2394
|
+
stepRequiresNumericTypeRule,
|
|
2395
|
+
acceptRequiresFileTypeRule,
|
|
2396
|
+
captureRequiresFileTypeRule,
|
|
2397
|
+
inputAccessibleNameRule,
|
|
2398
|
+
passwordAutocompleteRule,
|
|
2399
|
+
requiredReadOnlyConflictRule
|
|
2400
|
+
];
|
|
2401
|
+
|
|
2402
|
+
// ../core/src/html/role-restrictions.ts
|
|
2403
|
+
var ALLOWED_ROLES = {
|
|
2404
|
+
article: ["application", "document", "feed", "main", "none", "presentation", "region"],
|
|
2405
|
+
aside: ["feed", "none", "presentation", "region", "search"],
|
|
2406
|
+
footer: ["group", "none", "presentation"],
|
|
2407
|
+
header: ["group", "none", "presentation"],
|
|
2408
|
+
main: [],
|
|
2409
|
+
nav: [],
|
|
2410
|
+
a: [
|
|
2411
|
+
"button",
|
|
2412
|
+
"checkbox",
|
|
2413
|
+
"menuitem",
|
|
2414
|
+
"menuitemcheckbox",
|
|
2415
|
+
"menuitemradio",
|
|
2416
|
+
"option",
|
|
2417
|
+
"radio",
|
|
2418
|
+
"switch",
|
|
2419
|
+
"tab",
|
|
2420
|
+
"treeitem"
|
|
2421
|
+
],
|
|
2422
|
+
button: [
|
|
2423
|
+
"checkbox",
|
|
2424
|
+
"link",
|
|
2425
|
+
"menuitem",
|
|
2426
|
+
"menuitemcheckbox",
|
|
2427
|
+
"menuitemradio",
|
|
2428
|
+
"option",
|
|
2429
|
+
"radio",
|
|
2430
|
+
"switch",
|
|
2431
|
+
"tab"
|
|
2432
|
+
],
|
|
2433
|
+
select: ["menu"],
|
|
2434
|
+
h1: ["tab", "presentation", "none"],
|
|
2435
|
+
h2: ["tab", "presentation", "none"],
|
|
2436
|
+
h3: ["tab", "presentation", "none"],
|
|
2437
|
+
h4: ["tab", "presentation", "none"],
|
|
2438
|
+
h5: ["tab", "presentation", "none"],
|
|
2439
|
+
h6: ["tab", "presentation", "none"],
|
|
2440
|
+
ul: [
|
|
2441
|
+
"directory",
|
|
2442
|
+
"group",
|
|
2443
|
+
"listbox",
|
|
2444
|
+
"menu",
|
|
2445
|
+
"menubar",
|
|
2446
|
+
"radiogroup",
|
|
2447
|
+
"tablist",
|
|
2448
|
+
"toolbar",
|
|
2449
|
+
"tree"
|
|
2450
|
+
],
|
|
2451
|
+
ol: [
|
|
2452
|
+
"directory",
|
|
2453
|
+
"group",
|
|
2454
|
+
"listbox",
|
|
2455
|
+
"menu",
|
|
2456
|
+
"menubar",
|
|
2457
|
+
"radiogroup",
|
|
2458
|
+
"tablist",
|
|
2459
|
+
"toolbar",
|
|
2460
|
+
"tree"
|
|
2461
|
+
],
|
|
2462
|
+
li: [
|
|
2463
|
+
"menuitem",
|
|
2464
|
+
"menuitemcheckbox",
|
|
2465
|
+
"menuitemradio",
|
|
2466
|
+
"option",
|
|
2467
|
+
"none",
|
|
2468
|
+
"presentation",
|
|
2469
|
+
"radio",
|
|
2470
|
+
"separator",
|
|
2471
|
+
"tab",
|
|
2472
|
+
"treeitem"
|
|
2473
|
+
],
|
|
2474
|
+
table: ["grid", "treegrid"],
|
|
2475
|
+
dialog: ["alertdialog"],
|
|
2476
|
+
fieldset: ["none", "presentation", "radiogroup"]
|
|
2477
|
+
};
|
|
2478
|
+
var IMG_NAMED_ROLES = [
|
|
2479
|
+
"button",
|
|
2480
|
+
"checkbox",
|
|
2481
|
+
"link",
|
|
2482
|
+
"menuitem",
|
|
2483
|
+
"menuitemcheckbox",
|
|
2484
|
+
"menuitemradio",
|
|
2485
|
+
"option",
|
|
2486
|
+
"progressbar",
|
|
2487
|
+
"scrollbar",
|
|
2488
|
+
"separator",
|
|
2489
|
+
"slider",
|
|
2490
|
+
"switch",
|
|
2491
|
+
"tab",
|
|
2492
|
+
"treeitem"
|
|
2493
|
+
];
|
|
2494
|
+
var ALLOWED_INPUT_ROLES = {
|
|
2495
|
+
checkbox: ["menuitemcheckbox", "option", "switch", "button"],
|
|
2496
|
+
radio: ["menuitemradio"],
|
|
2497
|
+
range: [],
|
|
2498
|
+
number: [],
|
|
2499
|
+
search: ["combobox"],
|
|
2500
|
+
text: ["combobox", "searchbox", "spinbutton"],
|
|
2501
|
+
email: ["combobox"],
|
|
2502
|
+
tel: ["combobox"],
|
|
2503
|
+
url: ["combobox"],
|
|
2504
|
+
button: [
|
|
2505
|
+
"link",
|
|
2506
|
+
"menuitem",
|
|
2507
|
+
"menuitemcheckbox",
|
|
2508
|
+
"menuitemradio",
|
|
2509
|
+
"option",
|
|
2510
|
+
"radio",
|
|
2511
|
+
"switch",
|
|
2512
|
+
"tab"
|
|
2513
|
+
],
|
|
2514
|
+
submit: [
|
|
2515
|
+
"link",
|
|
2516
|
+
"menuitem",
|
|
2517
|
+
"menuitemcheckbox",
|
|
2518
|
+
"menuitemradio",
|
|
2519
|
+
"option",
|
|
2520
|
+
"radio",
|
|
2521
|
+
"switch",
|
|
2522
|
+
"tab"
|
|
2523
|
+
],
|
|
2524
|
+
reset: [
|
|
2525
|
+
"link",
|
|
2526
|
+
"menuitem",
|
|
2527
|
+
"menuitemcheckbox",
|
|
2528
|
+
"menuitemradio",
|
|
2529
|
+
"option",
|
|
2530
|
+
"radio",
|
|
2531
|
+
"switch",
|
|
2532
|
+
"tab"
|
|
2533
|
+
],
|
|
2534
|
+
image: [
|
|
2535
|
+
"link",
|
|
2536
|
+
"menuitem",
|
|
2537
|
+
"menuitemcheckbox",
|
|
2538
|
+
"menuitemradio",
|
|
2539
|
+
"option",
|
|
2540
|
+
"radio",
|
|
2541
|
+
"switch",
|
|
2542
|
+
"tab"
|
|
2543
|
+
],
|
|
2544
|
+
hidden: []
|
|
2545
|
+
};
|
|
2546
|
+
function getAllowedRoles(tag, props) {
|
|
2547
|
+
if (tag === "input") {
|
|
2548
|
+
const type = typeof props.type === "string" ? props.type : "text";
|
|
2549
|
+
return ALLOWED_INPUT_ROLES[type];
|
|
2550
|
+
}
|
|
2551
|
+
if (tag === "img") {
|
|
2552
|
+
return props.alt === "" ? [] : IMG_NAMED_ROLES;
|
|
2553
|
+
}
|
|
2554
|
+
return ALLOWED_ROLES[tag];
|
|
2555
|
+
}
|
|
2556
|
+
var removeRoleFix = {
|
|
2557
|
+
kind: "removeRole",
|
|
2558
|
+
apply: ({ props }) => {
|
|
2559
|
+
if (!("role" in props)) return { applied: false, next: props };
|
|
2560
|
+
const { role: _role, ...rest } = props;
|
|
2561
|
+
return { applied: true, next: rest, previous: props };
|
|
2562
|
+
}
|
|
2563
|
+
};
|
|
2564
|
+
var roleNotPermittedRule = Object.assign(
|
|
2565
|
+
({ tag, props, implicitRole }) => {
|
|
2566
|
+
const role = props.role;
|
|
2567
|
+
if (typeof role !== "string" || role.length === 0 || role === implicitRole) return [];
|
|
2568
|
+
const allowed = getAllowedRoles(tag, props);
|
|
2569
|
+
if (allowed === void 0 || allowed.includes(role)) return [];
|
|
2570
|
+
const diagnostic = HtmlDiagnostics.roleNotPermitted(tag, role, allowed);
|
|
2571
|
+
return [
|
|
2572
|
+
{
|
|
2573
|
+
valid: false,
|
|
2574
|
+
fixable: true,
|
|
2575
|
+
severity: diagnostic.severity,
|
|
2576
|
+
fix: removeRoleFix,
|
|
2577
|
+
diagnostic
|
|
2578
|
+
}
|
|
2579
|
+
];
|
|
2580
|
+
},
|
|
2581
|
+
{ readsProps: ["role", "type", "alt"] }
|
|
2582
|
+
);
|
|
2583
|
+
|
|
2125
2584
|
// ../core/src/html/aria-rules.ts
|
|
2126
2585
|
var LANDMARK_TAG_SET = /* @__PURE__ */ new Set(["article", "aside", "footer", "header", "main", "nav"]);
|
|
2127
2586
|
var removeLandmarkRoleOverride = {
|
|
@@ -2136,13 +2595,14 @@ function landmarkRoleRule({ tag, props, implicitRole }) {
|
|
|
2136
2595
|
if (!LANDMARK_TAG_SET.has(tag) || !implicitRole) return [];
|
|
2137
2596
|
const role = props.role;
|
|
2138
2597
|
if (!role || role === implicitRole) return [];
|
|
2598
|
+
const diagnostic = HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role);
|
|
2139
2599
|
return [
|
|
2140
2600
|
{
|
|
2141
2601
|
valid: false,
|
|
2142
2602
|
fixable: true,
|
|
2143
|
-
severity:
|
|
2603
|
+
severity: diagnostic.severity,
|
|
2144
2604
|
fix: removeLandmarkRoleOverride,
|
|
2145
|
-
diagnostic
|
|
2605
|
+
diagnostic
|
|
2146
2606
|
}
|
|
2147
2607
|
];
|
|
2148
2608
|
}
|
|
@@ -2162,7 +2622,12 @@ function landmarkNameAdvisory(ctx) {
|
|
|
2162
2622
|
if (!ctx.implicitRole || !NAMED_LANDMARK_TAGS.has(ctx.tag)) return [];
|
|
2163
2623
|
return requireAccessibleName(ctx);
|
|
2164
2624
|
}
|
|
2165
|
-
var HTML_ARIA_RULES = [
|
|
2625
|
+
var HTML_ARIA_RULES = [
|
|
2626
|
+
landmarkRoleRule,
|
|
2627
|
+
landmarkNameAdvisory,
|
|
2628
|
+
roleNotPermittedRule,
|
|
2629
|
+
...INPUT_RULES
|
|
2630
|
+
];
|
|
2166
2631
|
|
|
2167
2632
|
// ../core/src/html/contracts.ts
|
|
2168
2633
|
import { warnDiagnostics } from "./_shared/diagnostics.js";
|
|
@@ -2623,21 +3088,21 @@ function validateRenderProps(diagnostics, options, props, recipeKey) {
|
|
|
2623
3088
|
import { throwDiagnostics } from "./_shared/diagnostics.js";
|
|
2624
3089
|
|
|
2625
3090
|
// ../core/src/factory/plugin-diagnostics.ts
|
|
2626
|
-
import { DiagnosticCategory as
|
|
3091
|
+
import { DiagnosticCategory as DiagnosticCategory6, DiagnosticCode as DiagnosticCode6 } from "./_shared/diagnostics.js";
|
|
2627
3092
|
var PluginDiagnostics = {
|
|
2628
3093
|
invalidShape(received) {
|
|
2629
3094
|
const got = received === null ? "null" : typeof received;
|
|
2630
3095
|
return {
|
|
2631
|
-
code:
|
|
2632
|
-
category:
|
|
3096
|
+
code: DiagnosticCode6.PluginInvalidShape,
|
|
3097
|
+
category: DiagnosticCategory6.Internal,
|
|
2633
3098
|
message: `[praxis-kit] Plugin factory must return an object with a 'pipeline' function. Got: ${got}.`
|
|
2634
3099
|
};
|
|
2635
3100
|
},
|
|
2636
3101
|
pipelineReturnType(received) {
|
|
2637
3102
|
const got = received === null ? "null" : Array.isArray(received) ? "array" : typeof received;
|
|
2638
3103
|
return {
|
|
2639
|
-
code:
|
|
2640
|
-
category:
|
|
3104
|
+
code: DiagnosticCode6.PluginPipelineReturnType,
|
|
3105
|
+
category: DiagnosticCategory6.Internal,
|
|
2641
3106
|
message: `[praxis-kit] Plugin pipeline must return a string. Got: ${got}.`
|
|
2642
3107
|
};
|
|
2643
3108
|
}
|