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
package/dist/vue/index.js
CHANGED
|
@@ -965,11 +965,24 @@ var ContractDiagnostics = {
|
|
|
965
965
|
|
|
966
966
|
// ../../lib/contract/src/diagnostics/html.ts
|
|
967
967
|
import { DiagnosticCategory as DiagnosticCategory3, DiagnosticCode as DiagnosticCode3 } from "../_shared/diagnostics.js";
|
|
968
|
+
var ATTRIBUTE_IGNORED_CODES = {
|
|
969
|
+
checked: DiagnosticCode3.HtmlInputCheckedIgnoredForType,
|
|
970
|
+
multiple: DiagnosticCode3.HtmlInputMultipleIgnoredForType,
|
|
971
|
+
maxLength: DiagnosticCode3.HtmlInputMaxLengthIgnoredForType,
|
|
972
|
+
minLength: DiagnosticCode3.HtmlInputMinLengthIgnoredForType,
|
|
973
|
+
pattern: DiagnosticCode3.HtmlInputPatternIgnoredForType,
|
|
974
|
+
min: DiagnosticCode3.HtmlInputMinIgnoredForType,
|
|
975
|
+
max: DiagnosticCode3.HtmlInputMaxIgnoredForType,
|
|
976
|
+
step: DiagnosticCode3.HtmlInputStepIgnoredForType,
|
|
977
|
+
accept: DiagnosticCode3.HtmlInputAcceptIgnoredForType,
|
|
978
|
+
capture: DiagnosticCode3.HtmlInputCaptureIgnoredForType
|
|
979
|
+
};
|
|
968
980
|
var HtmlDiagnostics = {
|
|
969
981
|
emptyRole(tag) {
|
|
970
982
|
return {
|
|
971
983
|
code: DiagnosticCode3.HtmlEmptyRole,
|
|
972
984
|
category: DiagnosticCategory3.HTML,
|
|
985
|
+
severity: "warning",
|
|
973
986
|
message: `<${tag}> has an explicit empty role="". Omit the attribute instead.`
|
|
974
987
|
};
|
|
975
988
|
},
|
|
@@ -977,6 +990,7 @@ var HtmlDiagnostics = {
|
|
|
977
990
|
return {
|
|
978
991
|
code: DiagnosticCode3.HtmlImplicitRoleRedundant,
|
|
979
992
|
category: DiagnosticCategory3.HTML,
|
|
993
|
+
severity: "warning",
|
|
980
994
|
message: `<${tag}> already has implicit role="${implicitRole}". Avoid redundant role assignment.`
|
|
981
995
|
};
|
|
982
996
|
},
|
|
@@ -984,6 +998,7 @@ var HtmlDiagnostics = {
|
|
|
984
998
|
return {
|
|
985
999
|
code: DiagnosticCode3.HtmlImplicitRoleOverride,
|
|
986
1000
|
category: DiagnosticCategory3.HTML,
|
|
1001
|
+
severity: "error",
|
|
987
1002
|
message: `<${tag}> should not override its implicit role="${implicitRole}" with role="${role}".`
|
|
988
1003
|
};
|
|
989
1004
|
},
|
|
@@ -991,6 +1006,7 @@ var HtmlDiagnostics = {
|
|
|
991
1006
|
return {
|
|
992
1007
|
code: DiagnosticCode3.HtmlStandaloneRegionOverride,
|
|
993
1008
|
category: DiagnosticCategory3.HTML,
|
|
1009
|
+
severity: "error",
|
|
994
1010
|
message: `<${tag}> is a self-contained element with implicit role="${implicitRole}". Assigning role="region" has been removed.`
|
|
995
1011
|
};
|
|
996
1012
|
},
|
|
@@ -998,6 +1014,7 @@ var HtmlDiagnostics = {
|
|
|
998
1014
|
return {
|
|
999
1015
|
code: DiagnosticCode3.HtmlLandmarkRoleOverride,
|
|
1000
1016
|
category: DiagnosticCategory3.HTML,
|
|
1017
|
+
severity: "error",
|
|
1001
1018
|
message: `<${tag}> has a fixed landmark role="${implicitRole}". role="${role}" overrides it and confuses assistive technology. The override has been removed.`
|
|
1002
1019
|
};
|
|
1003
1020
|
},
|
|
@@ -1005,34 +1022,148 @@ var HtmlDiagnostics = {
|
|
|
1005
1022
|
return {
|
|
1006
1023
|
code: DiagnosticCode3.HtmlInvalidChild,
|
|
1007
1024
|
category: DiagnosticCategory3.HTML,
|
|
1025
|
+
severity: "error",
|
|
1008
1026
|
message: `<${child}> is not a valid direct child of <${parent}>. Allowed: ${allowed}.`
|
|
1009
1027
|
};
|
|
1028
|
+
},
|
|
1029
|
+
roleNotPermitted(tag, role, allowedRoles) {
|
|
1030
|
+
const allowed = allowedRoles.length > 0 ? allowedRoles.map((r) => `"${r}"`).join(", ") : "none \u2014 no explicit role is permitted on this element";
|
|
1031
|
+
return {
|
|
1032
|
+
code: DiagnosticCode3.HtmlRoleNotPermitted,
|
|
1033
|
+
category: DiagnosticCategory3.HTML,
|
|
1034
|
+
severity: "error",
|
|
1035
|
+
message: `role="${role}" is not permitted on <${tag}>. Allowed alternate role(s): ${allowed}.`,
|
|
1036
|
+
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.'
|
|
1037
|
+
};
|
|
1038
|
+
},
|
|
1039
|
+
// Reserved for <input>-specific facts (HTML3101–3199, see codes.ts) — later element families
|
|
1040
|
+
// (button, img, table, ...) get their own reserved block and their own namespace here.
|
|
1041
|
+
input: {
|
|
1042
|
+
unsupportedType(type) {
|
|
1043
|
+
return {
|
|
1044
|
+
code: DiagnosticCode3.HtmlInputUnsupportedType,
|
|
1045
|
+
category: DiagnosticCategory3.HTML,
|
|
1046
|
+
severity: "warning",
|
|
1047
|
+
message: `type="${type}" is not a value defined by the HTML specification. Browsers silently fall back to type="text".`,
|
|
1048
|
+
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).',
|
|
1049
|
+
suggestions: [
|
|
1050
|
+
{
|
|
1051
|
+
title: "Check for a typo in the type value",
|
|
1052
|
+
description: `"${type}" does not match any HTML5 input type.`
|
|
1053
|
+
}
|
|
1054
|
+
]
|
|
1055
|
+
};
|
|
1056
|
+
},
|
|
1057
|
+
// The user-visible problem is that the attribute is ignored — not that it "requires" a type;
|
|
1058
|
+
// that's the rule's internal framing, not what the browser actually does.
|
|
1059
|
+
attributeIgnoredForType(attribute, type, allowedTypes) {
|
|
1060
|
+
const allowed = allowedTypes.map((t) => `"${t}"`).join(", ");
|
|
1061
|
+
const code = ATTRIBUTE_IGNORED_CODES[attribute];
|
|
1062
|
+
if (!code) throw new Error(`No DiagnosticCode registered for input attribute "${attribute}"`);
|
|
1063
|
+
return {
|
|
1064
|
+
code,
|
|
1065
|
+
category: DiagnosticCategory3.HTML,
|
|
1066
|
+
severity: "warning",
|
|
1067
|
+
message: `"${attribute}" is ignored on <input type="${type}">.`,
|
|
1068
|
+
rationale: `"${attribute}" only has an effect when type is one of: ${allowed}. Browsers silently ignore it on other input types.`,
|
|
1069
|
+
suggestions: [
|
|
1070
|
+
{
|
|
1071
|
+
title: `Remove "${attribute}"`,
|
|
1072
|
+
description: `"${attribute}" only affects <input> when type is one of: ${allowed}.`
|
|
1073
|
+
}
|
|
1074
|
+
]
|
|
1075
|
+
};
|
|
1076
|
+
}
|
|
1010
1077
|
}
|
|
1011
1078
|
};
|
|
1012
1079
|
|
|
1013
|
-
// ../../lib/contract/src/diagnostics/
|
|
1080
|
+
// ../../lib/contract/src/diagnostics/input-accessibility.ts
|
|
1014
1081
|
import { DiagnosticCategory as DiagnosticCategory4, DiagnosticCode as DiagnosticCode4 } from "../_shared/diagnostics.js";
|
|
1082
|
+
function accessibilityFact(input) {
|
|
1083
|
+
return { category: DiagnosticCategory4.Accessibility, ...input };
|
|
1084
|
+
}
|
|
1085
|
+
var InputAccessibilityDiagnostics = {
|
|
1086
|
+
missingAccessibleName() {
|
|
1087
|
+
return accessibilityFact({
|
|
1088
|
+
code: DiagnosticCode4.A11yInputMissingAccessibleName,
|
|
1089
|
+
severity: "warning",
|
|
1090
|
+
message: "This input has no accessible name. Add an associated <label>, aria-label, or aria-labelledby.",
|
|
1091
|
+
rationale: "Assistive technology announces a form field by its accessible name; without one, users of screen readers cannot tell what the field is for.",
|
|
1092
|
+
suggestions: [
|
|
1093
|
+
{ title: "Add aria-label", description: 'Set aria-label="\u2026" directly on the input.' },
|
|
1094
|
+
{
|
|
1095
|
+
title: "Add an associated <label>",
|
|
1096
|
+
description: 'Wrap the input in a <label>, or point a <label for="\u2026"> at its id.'
|
|
1097
|
+
}
|
|
1098
|
+
]
|
|
1099
|
+
});
|
|
1100
|
+
},
|
|
1101
|
+
placeholderIsNotLabel() {
|
|
1102
|
+
return accessibilityFact({
|
|
1103
|
+
code: DiagnosticCode4.A11yInputPlaceholderNotLabel,
|
|
1104
|
+
severity: "warning",
|
|
1105
|
+
message: "Placeholder text does not provide an accessible name. Add an associated <label>, aria-label, or aria-labelledby.",
|
|
1106
|
+
rationale: "Placeholder text disappears as users interact with the field and is not treated as the control's accessible name by many assistive technologies.",
|
|
1107
|
+
suggestions: [
|
|
1108
|
+
{ title: "Add aria-label", description: 'Set aria-label="\u2026" directly on the input.' },
|
|
1109
|
+
{
|
|
1110
|
+
title: "Add an associated <label>",
|
|
1111
|
+
description: 'Wrap the input in a <label>, or point a <label for="\u2026"> at its id.'
|
|
1112
|
+
}
|
|
1113
|
+
]
|
|
1114
|
+
});
|
|
1115
|
+
},
|
|
1116
|
+
passwordMissingAutocomplete() {
|
|
1117
|
+
return accessibilityFact({
|
|
1118
|
+
code: DiagnosticCode4.A11yInputPasswordAutocomplete,
|
|
1119
|
+
severity: "warning",
|
|
1120
|
+
message: "Password inputs should specify an autoComplete value.",
|
|
1121
|
+
rationale: "Without an explicit autocomplete hint, password managers and browsers cannot reliably tell a sign-in field apart from a password-creation field.",
|
|
1122
|
+
suggestions: [
|
|
1123
|
+
{
|
|
1124
|
+
title: 'Set autoComplete="current-password"',
|
|
1125
|
+
description: "Use this for sign-in forms."
|
|
1126
|
+
},
|
|
1127
|
+
{
|
|
1128
|
+
title: 'Set autoComplete="new-password"',
|
|
1129
|
+
description: "Use this for sign-up / change-password forms."
|
|
1130
|
+
}
|
|
1131
|
+
]
|
|
1132
|
+
});
|
|
1133
|
+
},
|
|
1134
|
+
requiredReadOnlyConflict() {
|
|
1135
|
+
return accessibilityFact({
|
|
1136
|
+
code: DiagnosticCode4.A11yInputRequiredReadOnlyConflict,
|
|
1137
|
+
severity: "warning",
|
|
1138
|
+
message: "The required and readOnly attributes are both present. A read-only field cannot satisfy required validation through user interaction.",
|
|
1139
|
+
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."
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
};
|
|
1143
|
+
|
|
1144
|
+
// ../../lib/contract/src/diagnostics/slot.ts
|
|
1145
|
+
import { DiagnosticCategory as DiagnosticCategory5, DiagnosticCode as DiagnosticCode5 } from "../_shared/diagnostics.js";
|
|
1015
1146
|
var SlotDiagnostics = {
|
|
1016
1147
|
exclusive(name) {
|
|
1017
1148
|
return {
|
|
1018
|
-
code:
|
|
1019
|
-
category:
|
|
1149
|
+
code: DiagnosticCode5.SlotExclusive,
|
|
1150
|
+
category: DiagnosticCategory5.Contract,
|
|
1020
1151
|
component: name,
|
|
1021
1152
|
message: `${name}: "as" and "asChild" are mutually exclusive`
|
|
1022
1153
|
};
|
|
1023
1154
|
},
|
|
1024
1155
|
singleChildRequired(name, elementTerm) {
|
|
1025
1156
|
return {
|
|
1026
|
-
code:
|
|
1027
|
-
category:
|
|
1157
|
+
code: DiagnosticCode5.SlotSingleChild,
|
|
1158
|
+
category: DiagnosticCategory5.Contract,
|
|
1028
1159
|
component: name,
|
|
1029
1160
|
message: `${name}: asChild requires a ${elementTerm} child`
|
|
1030
1161
|
};
|
|
1031
1162
|
},
|
|
1032
1163
|
singleChildExceeded(name, elementTerm, count) {
|
|
1033
1164
|
return {
|
|
1034
|
-
code:
|
|
1035
|
-
category:
|
|
1165
|
+
code: DiagnosticCode5.SlotSingleChild,
|
|
1166
|
+
category: DiagnosticCategory5.Contract,
|
|
1036
1167
|
component: name,
|
|
1037
1168
|
message: `${name}: asChild requires exactly one ${elementTerm} child, got ${count}`
|
|
1038
1169
|
};
|
|
@@ -1040,16 +1171,16 @@ var SlotDiagnostics = {
|
|
|
1040
1171
|
discardedChildren(name, elementTerm, count) {
|
|
1041
1172
|
const suffix = count === 1 ? "" : "ren";
|
|
1042
1173
|
return {
|
|
1043
|
-
code:
|
|
1044
|
-
category:
|
|
1174
|
+
code: DiagnosticCode5.SlotDiscardedChildren,
|
|
1175
|
+
category: DiagnosticCategory5.Contract,
|
|
1045
1176
|
component: name,
|
|
1046
1177
|
message: `${name}: asChild discarded ${count} non-element child${suffix} \u2014 only ${elementTerm}s are valid asChild children.`
|
|
1047
1178
|
};
|
|
1048
1179
|
},
|
|
1049
1180
|
renderFnRequired(name, received) {
|
|
1050
1181
|
return {
|
|
1051
|
-
code:
|
|
1052
|
-
category:
|
|
1182
|
+
code: DiagnosticCode5.SlotRenderFn,
|
|
1183
|
+
category: DiagnosticCategory5.Contract,
|
|
1053
1184
|
component: name,
|
|
1054
1185
|
message: `${name}: asChild requires a render function as children, got ${received}`
|
|
1055
1186
|
};
|
|
@@ -1111,7 +1242,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1111
1242
|
tag,
|
|
1112
1243
|
role: "",
|
|
1113
1244
|
attribute: void 0,
|
|
1114
|
-
severity:
|
|
1245
|
+
severity: d.severity,
|
|
1115
1246
|
phase: "evaluate"
|
|
1116
1247
|
}
|
|
1117
1248
|
]
|
|
@@ -1370,13 +1501,14 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1370
1501
|
const role = props.role;
|
|
1371
1502
|
if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS;
|
|
1372
1503
|
if (isStrongImplicitRole(tag) && role === "region") {
|
|
1504
|
+
const diagnostic = HtmlDiagnostics.implicitRoleOverride(tag, implicitRole, role);
|
|
1373
1505
|
return [
|
|
1374
1506
|
{
|
|
1375
1507
|
valid: false,
|
|
1376
1508
|
fixable: true,
|
|
1377
|
-
severity:
|
|
1509
|
+
severity: diagnostic.severity,
|
|
1378
1510
|
fix: _AriaPolicyEngine.#removeRole,
|
|
1379
|
-
diagnostic
|
|
1511
|
+
diagnostic
|
|
1380
1512
|
}
|
|
1381
1513
|
];
|
|
1382
1514
|
}
|
|
@@ -1385,13 +1517,14 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1385
1517
|
static #checkRedundantRole({ tag, props, implicitRole }) {
|
|
1386
1518
|
const role = props.role;
|
|
1387
1519
|
if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS;
|
|
1520
|
+
const diagnostic = HtmlDiagnostics.implicitRoleRedundant(tag, implicitRole);
|
|
1388
1521
|
return [
|
|
1389
1522
|
{
|
|
1390
1523
|
valid: false,
|
|
1391
1524
|
fixable: true,
|
|
1392
|
-
severity:
|
|
1525
|
+
severity: diagnostic.severity,
|
|
1393
1526
|
fix: _AriaPolicyEngine.#removeRole,
|
|
1394
|
-
diagnostic
|
|
1527
|
+
diagnostic
|
|
1395
1528
|
}
|
|
1396
1529
|
];
|
|
1397
1530
|
}
|
|
@@ -1399,13 +1532,14 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
|
|
|
1399
1532
|
const role = props.role;
|
|
1400
1533
|
if (role !== "region") return NO_VIOLATIONS;
|
|
1401
1534
|
if (!isStandaloneTag(tag)) return NO_VIOLATIONS;
|
|
1535
|
+
const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
|
|
1402
1536
|
return [
|
|
1403
1537
|
{
|
|
1404
1538
|
valid: false,
|
|
1405
1539
|
fixable: true,
|
|
1406
|
-
severity:
|
|
1540
|
+
severity: diagnostic.severity,
|
|
1407
1541
|
fix: _AriaPolicyEngine.#removeRole,
|
|
1408
|
-
diagnostic
|
|
1542
|
+
diagnostic
|
|
1409
1543
|
}
|
|
1410
1544
|
];
|
|
1411
1545
|
}
|
|
@@ -2094,6 +2228,331 @@ var readonlyProps = ({
|
|
|
2094
2228
|
// ../core/src/html/evaluators.ts
|
|
2095
2229
|
import { warnDiagnostics as warnDiagnostics2 } from "../_shared/diagnostics.js";
|
|
2096
2230
|
|
|
2231
|
+
// ../core/src/html/input-rules.ts
|
|
2232
|
+
var DEFAULT_INPUT_TYPE = "text";
|
|
2233
|
+
function omit(props, key) {
|
|
2234
|
+
const next = { ...props };
|
|
2235
|
+
delete next[key];
|
|
2236
|
+
return next;
|
|
2237
|
+
}
|
|
2238
|
+
function removeAttributeFix(attribute) {
|
|
2239
|
+
return {
|
|
2240
|
+
kind: `removeAttribute:${attribute}`,
|
|
2241
|
+
apply: ({ props }) => {
|
|
2242
|
+
if (!(attribute in props)) return { applied: false, next: props };
|
|
2243
|
+
return { applied: true, next: omit(props, attribute), previous: props };
|
|
2244
|
+
}
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
2247
|
+
function inputAttributeRequiresType(attribute, allowedTypes) {
|
|
2248
|
+
const rule = ({ tag, props }) => {
|
|
2249
|
+
if (tag !== "input" || !(attribute in props)) return [];
|
|
2250
|
+
const type = typeof props.type === "string" ? props.type : DEFAULT_INPUT_TYPE;
|
|
2251
|
+
if (allowedTypes.includes(type)) return [];
|
|
2252
|
+
const diagnostic = HtmlDiagnostics.input.attributeIgnoredForType(attribute, type, allowedTypes);
|
|
2253
|
+
return [
|
|
2254
|
+
{
|
|
2255
|
+
valid: false,
|
|
2256
|
+
fixable: true,
|
|
2257
|
+
severity: diagnostic.severity,
|
|
2258
|
+
fix: removeAttributeFix(attribute),
|
|
2259
|
+
diagnostic
|
|
2260
|
+
}
|
|
2261
|
+
];
|
|
2262
|
+
};
|
|
2263
|
+
return Object.assign(rule, { readsProps: ["type", attribute] });
|
|
2264
|
+
}
|
|
2265
|
+
var TEXT_INPUT_TYPES = ["text", "search", "url", "tel", "email", "password"];
|
|
2266
|
+
var NUMERIC_INPUT_TYPES = [
|
|
2267
|
+
"number",
|
|
2268
|
+
"range",
|
|
2269
|
+
"date",
|
|
2270
|
+
"month",
|
|
2271
|
+
"week",
|
|
2272
|
+
"time",
|
|
2273
|
+
"datetime-local"
|
|
2274
|
+
];
|
|
2275
|
+
var HTML_INPUT_TYPES = /* @__PURE__ */ new Set([
|
|
2276
|
+
...TEXT_INPUT_TYPES,
|
|
2277
|
+
...NUMERIC_INPUT_TYPES,
|
|
2278
|
+
"checkbox",
|
|
2279
|
+
"radio",
|
|
2280
|
+
"file",
|
|
2281
|
+
"color",
|
|
2282
|
+
"hidden",
|
|
2283
|
+
"button",
|
|
2284
|
+
"submit",
|
|
2285
|
+
"reset",
|
|
2286
|
+
"image"
|
|
2287
|
+
]);
|
|
2288
|
+
var supportedInputTypeRule = Object.assign(
|
|
2289
|
+
({ tag, props }) => {
|
|
2290
|
+
if (tag !== "input" || typeof props.type !== "string") return [];
|
|
2291
|
+
const type = props.type;
|
|
2292
|
+
if (HTML_INPUT_TYPES.has(type)) return [];
|
|
2293
|
+
const diagnostic = HtmlDiagnostics.input.unsupportedType(type);
|
|
2294
|
+
return [
|
|
2295
|
+
{
|
|
2296
|
+
valid: false,
|
|
2297
|
+
fixable: false,
|
|
2298
|
+
severity: diagnostic.severity,
|
|
2299
|
+
diagnostic
|
|
2300
|
+
}
|
|
2301
|
+
];
|
|
2302
|
+
},
|
|
2303
|
+
{ readsProps: ["type"] }
|
|
2304
|
+
);
|
|
2305
|
+
var checkedRequiresCheckableTypeRule = inputAttributeRequiresType("checked", [
|
|
2306
|
+
"checkbox",
|
|
2307
|
+
"radio"
|
|
2308
|
+
]);
|
|
2309
|
+
var multipleRequiresSupportedTypeRule = inputAttributeRequiresType("multiple", [
|
|
2310
|
+
"email",
|
|
2311
|
+
"file"
|
|
2312
|
+
]);
|
|
2313
|
+
var maxLengthRequiresTextTypeRule = inputAttributeRequiresType(
|
|
2314
|
+
"maxLength",
|
|
2315
|
+
TEXT_INPUT_TYPES
|
|
2316
|
+
);
|
|
2317
|
+
var minLengthRequiresTextTypeRule = inputAttributeRequiresType(
|
|
2318
|
+
"minLength",
|
|
2319
|
+
TEXT_INPUT_TYPES
|
|
2320
|
+
);
|
|
2321
|
+
var patternRequiresTextTypeRule = inputAttributeRequiresType("pattern", TEXT_INPUT_TYPES);
|
|
2322
|
+
var minRequiresNumericTypeRule = inputAttributeRequiresType("min", NUMERIC_INPUT_TYPES);
|
|
2323
|
+
var maxRequiresNumericTypeRule = inputAttributeRequiresType("max", NUMERIC_INPUT_TYPES);
|
|
2324
|
+
var stepRequiresNumericTypeRule = inputAttributeRequiresType("step", NUMERIC_INPUT_TYPES);
|
|
2325
|
+
var acceptRequiresFileTypeRule = inputAttributeRequiresType("accept", ["file"]);
|
|
2326
|
+
var captureRequiresFileTypeRule = inputAttributeRequiresType("capture", ["file"]);
|
|
2327
|
+
var inputAccessibleNameRule = Object.assign(
|
|
2328
|
+
({ tag, props }) => {
|
|
2329
|
+
if (tag !== "input" || props.type === "hidden") return [];
|
|
2330
|
+
if ("aria-label" in props || "aria-labelledby" in props) return [];
|
|
2331
|
+
const hasPlaceholder = typeof props.placeholder === "string" && props.placeholder.length > 0;
|
|
2332
|
+
const diagnostic = hasPlaceholder ? InputAccessibilityDiagnostics.placeholderIsNotLabel() : InputAccessibilityDiagnostics.missingAccessibleName();
|
|
2333
|
+
return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
|
|
2334
|
+
},
|
|
2335
|
+
{ readsProps: ["type", "aria-label", "aria-labelledby", "placeholder"] }
|
|
2336
|
+
);
|
|
2337
|
+
var PASSWORD_AUTOCOMPLETE_VALUES = ["current-password", "new-password"];
|
|
2338
|
+
var passwordAutocompleteRule = Object.assign(
|
|
2339
|
+
({ tag, props }) => {
|
|
2340
|
+
if (tag !== "input" || props.type !== "password") return [];
|
|
2341
|
+
const autoComplete = props.autoComplete;
|
|
2342
|
+
const tokens = typeof autoComplete === "string" ? autoComplete.split(" ") : [];
|
|
2343
|
+
if (PASSWORD_AUTOCOMPLETE_VALUES.some((value) => tokens.includes(value))) return [];
|
|
2344
|
+
const diagnostic = InputAccessibilityDiagnostics.passwordMissingAutocomplete();
|
|
2345
|
+
return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
|
|
2346
|
+
},
|
|
2347
|
+
{ readsProps: ["type", "autoComplete"] }
|
|
2348
|
+
);
|
|
2349
|
+
var requiredReadOnlyConflictRule = Object.assign(
|
|
2350
|
+
({ tag, props }) => {
|
|
2351
|
+
if (tag !== "input" || !props.required || !props.readOnly) return [];
|
|
2352
|
+
const diagnostic = InputAccessibilityDiagnostics.requiredReadOnlyConflict();
|
|
2353
|
+
return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
|
|
2354
|
+
},
|
|
2355
|
+
{ readsProps: ["required", "readOnly"] }
|
|
2356
|
+
);
|
|
2357
|
+
var INPUT_RULES = [
|
|
2358
|
+
supportedInputTypeRule,
|
|
2359
|
+
checkedRequiresCheckableTypeRule,
|
|
2360
|
+
multipleRequiresSupportedTypeRule,
|
|
2361
|
+
maxLengthRequiresTextTypeRule,
|
|
2362
|
+
minLengthRequiresTextTypeRule,
|
|
2363
|
+
patternRequiresTextTypeRule,
|
|
2364
|
+
minRequiresNumericTypeRule,
|
|
2365
|
+
maxRequiresNumericTypeRule,
|
|
2366
|
+
stepRequiresNumericTypeRule,
|
|
2367
|
+
acceptRequiresFileTypeRule,
|
|
2368
|
+
captureRequiresFileTypeRule,
|
|
2369
|
+
inputAccessibleNameRule,
|
|
2370
|
+
passwordAutocompleteRule,
|
|
2371
|
+
requiredReadOnlyConflictRule
|
|
2372
|
+
];
|
|
2373
|
+
|
|
2374
|
+
// ../core/src/html/role-restrictions.ts
|
|
2375
|
+
var ALLOWED_ROLES = {
|
|
2376
|
+
article: ["application", "document", "feed", "main", "none", "presentation", "region"],
|
|
2377
|
+
aside: ["feed", "none", "presentation", "region", "search"],
|
|
2378
|
+
footer: ["group", "none", "presentation"],
|
|
2379
|
+
header: ["group", "none", "presentation"],
|
|
2380
|
+
main: [],
|
|
2381
|
+
nav: [],
|
|
2382
|
+
a: [
|
|
2383
|
+
"button",
|
|
2384
|
+
"checkbox",
|
|
2385
|
+
"menuitem",
|
|
2386
|
+
"menuitemcheckbox",
|
|
2387
|
+
"menuitemradio",
|
|
2388
|
+
"option",
|
|
2389
|
+
"radio",
|
|
2390
|
+
"switch",
|
|
2391
|
+
"tab",
|
|
2392
|
+
"treeitem"
|
|
2393
|
+
],
|
|
2394
|
+
button: [
|
|
2395
|
+
"checkbox",
|
|
2396
|
+
"link",
|
|
2397
|
+
"menuitem",
|
|
2398
|
+
"menuitemcheckbox",
|
|
2399
|
+
"menuitemradio",
|
|
2400
|
+
"option",
|
|
2401
|
+
"radio",
|
|
2402
|
+
"switch",
|
|
2403
|
+
"tab"
|
|
2404
|
+
],
|
|
2405
|
+
select: ["menu"],
|
|
2406
|
+
h1: ["tab", "presentation", "none"],
|
|
2407
|
+
h2: ["tab", "presentation", "none"],
|
|
2408
|
+
h3: ["tab", "presentation", "none"],
|
|
2409
|
+
h4: ["tab", "presentation", "none"],
|
|
2410
|
+
h5: ["tab", "presentation", "none"],
|
|
2411
|
+
h6: ["tab", "presentation", "none"],
|
|
2412
|
+
ul: [
|
|
2413
|
+
"directory",
|
|
2414
|
+
"group",
|
|
2415
|
+
"listbox",
|
|
2416
|
+
"menu",
|
|
2417
|
+
"menubar",
|
|
2418
|
+
"radiogroup",
|
|
2419
|
+
"tablist",
|
|
2420
|
+
"toolbar",
|
|
2421
|
+
"tree"
|
|
2422
|
+
],
|
|
2423
|
+
ol: [
|
|
2424
|
+
"directory",
|
|
2425
|
+
"group",
|
|
2426
|
+
"listbox",
|
|
2427
|
+
"menu",
|
|
2428
|
+
"menubar",
|
|
2429
|
+
"radiogroup",
|
|
2430
|
+
"tablist",
|
|
2431
|
+
"toolbar",
|
|
2432
|
+
"tree"
|
|
2433
|
+
],
|
|
2434
|
+
li: [
|
|
2435
|
+
"menuitem",
|
|
2436
|
+
"menuitemcheckbox",
|
|
2437
|
+
"menuitemradio",
|
|
2438
|
+
"option",
|
|
2439
|
+
"none",
|
|
2440
|
+
"presentation",
|
|
2441
|
+
"radio",
|
|
2442
|
+
"separator",
|
|
2443
|
+
"tab",
|
|
2444
|
+
"treeitem"
|
|
2445
|
+
],
|
|
2446
|
+
table: ["grid", "treegrid"],
|
|
2447
|
+
dialog: ["alertdialog"],
|
|
2448
|
+
fieldset: ["none", "presentation", "radiogroup"]
|
|
2449
|
+
};
|
|
2450
|
+
var IMG_NAMED_ROLES = [
|
|
2451
|
+
"button",
|
|
2452
|
+
"checkbox",
|
|
2453
|
+
"link",
|
|
2454
|
+
"menuitem",
|
|
2455
|
+
"menuitemcheckbox",
|
|
2456
|
+
"menuitemradio",
|
|
2457
|
+
"option",
|
|
2458
|
+
"progressbar",
|
|
2459
|
+
"scrollbar",
|
|
2460
|
+
"separator",
|
|
2461
|
+
"slider",
|
|
2462
|
+
"switch",
|
|
2463
|
+
"tab",
|
|
2464
|
+
"treeitem"
|
|
2465
|
+
];
|
|
2466
|
+
var ALLOWED_INPUT_ROLES = {
|
|
2467
|
+
checkbox: ["menuitemcheckbox", "option", "switch", "button"],
|
|
2468
|
+
radio: ["menuitemradio"],
|
|
2469
|
+
range: [],
|
|
2470
|
+
number: [],
|
|
2471
|
+
search: ["combobox"],
|
|
2472
|
+
text: ["combobox", "searchbox", "spinbutton"],
|
|
2473
|
+
email: ["combobox"],
|
|
2474
|
+
tel: ["combobox"],
|
|
2475
|
+
url: ["combobox"],
|
|
2476
|
+
button: [
|
|
2477
|
+
"link",
|
|
2478
|
+
"menuitem",
|
|
2479
|
+
"menuitemcheckbox",
|
|
2480
|
+
"menuitemradio",
|
|
2481
|
+
"option",
|
|
2482
|
+
"radio",
|
|
2483
|
+
"switch",
|
|
2484
|
+
"tab"
|
|
2485
|
+
],
|
|
2486
|
+
submit: [
|
|
2487
|
+
"link",
|
|
2488
|
+
"menuitem",
|
|
2489
|
+
"menuitemcheckbox",
|
|
2490
|
+
"menuitemradio",
|
|
2491
|
+
"option",
|
|
2492
|
+
"radio",
|
|
2493
|
+
"switch",
|
|
2494
|
+
"tab"
|
|
2495
|
+
],
|
|
2496
|
+
reset: [
|
|
2497
|
+
"link",
|
|
2498
|
+
"menuitem",
|
|
2499
|
+
"menuitemcheckbox",
|
|
2500
|
+
"menuitemradio",
|
|
2501
|
+
"option",
|
|
2502
|
+
"radio",
|
|
2503
|
+
"switch",
|
|
2504
|
+
"tab"
|
|
2505
|
+
],
|
|
2506
|
+
image: [
|
|
2507
|
+
"link",
|
|
2508
|
+
"menuitem",
|
|
2509
|
+
"menuitemcheckbox",
|
|
2510
|
+
"menuitemradio",
|
|
2511
|
+
"option",
|
|
2512
|
+
"radio",
|
|
2513
|
+
"switch",
|
|
2514
|
+
"tab"
|
|
2515
|
+
],
|
|
2516
|
+
hidden: []
|
|
2517
|
+
};
|
|
2518
|
+
function getAllowedRoles(tag, props) {
|
|
2519
|
+
if (tag === "input") {
|
|
2520
|
+
const type = typeof props.type === "string" ? props.type : "text";
|
|
2521
|
+
return ALLOWED_INPUT_ROLES[type];
|
|
2522
|
+
}
|
|
2523
|
+
if (tag === "img") {
|
|
2524
|
+
return props.alt === "" ? [] : IMG_NAMED_ROLES;
|
|
2525
|
+
}
|
|
2526
|
+
return ALLOWED_ROLES[tag];
|
|
2527
|
+
}
|
|
2528
|
+
var removeRoleFix = {
|
|
2529
|
+
kind: "removeRole",
|
|
2530
|
+
apply: ({ props }) => {
|
|
2531
|
+
if (!("role" in props)) return { applied: false, next: props };
|
|
2532
|
+
const { role: _role, ...rest } = props;
|
|
2533
|
+
return { applied: true, next: rest, previous: props };
|
|
2534
|
+
}
|
|
2535
|
+
};
|
|
2536
|
+
var roleNotPermittedRule = Object.assign(
|
|
2537
|
+
({ tag, props, implicitRole }) => {
|
|
2538
|
+
const role = props.role;
|
|
2539
|
+
if (typeof role !== "string" || role.length === 0 || role === implicitRole) return [];
|
|
2540
|
+
const allowed = getAllowedRoles(tag, props);
|
|
2541
|
+
if (allowed === void 0 || allowed.includes(role)) return [];
|
|
2542
|
+
const diagnostic = HtmlDiagnostics.roleNotPermitted(tag, role, allowed);
|
|
2543
|
+
return [
|
|
2544
|
+
{
|
|
2545
|
+
valid: false,
|
|
2546
|
+
fixable: true,
|
|
2547
|
+
severity: diagnostic.severity,
|
|
2548
|
+
fix: removeRoleFix,
|
|
2549
|
+
diagnostic
|
|
2550
|
+
}
|
|
2551
|
+
];
|
|
2552
|
+
},
|
|
2553
|
+
{ readsProps: ["role", "type", "alt"] }
|
|
2554
|
+
);
|
|
2555
|
+
|
|
2097
2556
|
// ../core/src/html/aria-rules.ts
|
|
2098
2557
|
var LANDMARK_TAG_SET = /* @__PURE__ */ new Set(["article", "aside", "footer", "header", "main", "nav"]);
|
|
2099
2558
|
var removeLandmarkRoleOverride = {
|
|
@@ -2108,13 +2567,14 @@ function landmarkRoleRule({ tag, props, implicitRole }) {
|
|
|
2108
2567
|
if (!LANDMARK_TAG_SET.has(tag) || !implicitRole) return [];
|
|
2109
2568
|
const role = props.role;
|
|
2110
2569
|
if (!role || role === implicitRole) return [];
|
|
2570
|
+
const diagnostic = HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role);
|
|
2111
2571
|
return [
|
|
2112
2572
|
{
|
|
2113
2573
|
valid: false,
|
|
2114
2574
|
fixable: true,
|
|
2115
|
-
severity:
|
|
2575
|
+
severity: diagnostic.severity,
|
|
2116
2576
|
fix: removeLandmarkRoleOverride,
|
|
2117
|
-
diagnostic
|
|
2577
|
+
diagnostic
|
|
2118
2578
|
}
|
|
2119
2579
|
];
|
|
2120
2580
|
}
|
|
@@ -2134,7 +2594,12 @@ function landmarkNameAdvisory(ctx) {
|
|
|
2134
2594
|
if (!ctx.implicitRole || !NAMED_LANDMARK_TAGS.has(ctx.tag)) return [];
|
|
2135
2595
|
return requireAccessibleName(ctx);
|
|
2136
2596
|
}
|
|
2137
|
-
var HTML_ARIA_RULES = [
|
|
2597
|
+
var HTML_ARIA_RULES = [
|
|
2598
|
+
landmarkRoleRule,
|
|
2599
|
+
landmarkNameAdvisory,
|
|
2600
|
+
roleNotPermittedRule,
|
|
2601
|
+
...INPUT_RULES
|
|
2602
|
+
];
|
|
2138
2603
|
|
|
2139
2604
|
// ../core/src/html/contracts.ts
|
|
2140
2605
|
import { warnDiagnostics } from "../_shared/diagnostics.js";
|
|
@@ -2595,21 +3060,21 @@ function validateRenderProps(diagnostics, options, props, recipeKey) {
|
|
|
2595
3060
|
import { throwDiagnostics } from "../_shared/diagnostics.js";
|
|
2596
3061
|
|
|
2597
3062
|
// ../core/src/factory/plugin-diagnostics.ts
|
|
2598
|
-
import { DiagnosticCategory as
|
|
3063
|
+
import { DiagnosticCategory as DiagnosticCategory6, DiagnosticCode as DiagnosticCode6 } from "../_shared/diagnostics.js";
|
|
2599
3064
|
var PluginDiagnostics = {
|
|
2600
3065
|
invalidShape(received) {
|
|
2601
3066
|
const got = received === null ? "null" : typeof received;
|
|
2602
3067
|
return {
|
|
2603
|
-
code:
|
|
2604
|
-
category:
|
|
3068
|
+
code: DiagnosticCode6.PluginInvalidShape,
|
|
3069
|
+
category: DiagnosticCategory6.Internal,
|
|
2605
3070
|
message: `[praxis-kit] Plugin factory must return an object with a 'pipeline' function. Got: ${got}.`
|
|
2606
3071
|
};
|
|
2607
3072
|
},
|
|
2608
3073
|
pipelineReturnType(received) {
|
|
2609
3074
|
const got = received === null ? "null" : Array.isArray(received) ? "array" : typeof received;
|
|
2610
3075
|
return {
|
|
2611
|
-
code:
|
|
2612
|
-
category:
|
|
3076
|
+
code: DiagnosticCode6.PluginPipelineReturnType,
|
|
3077
|
+
category: DiagnosticCategory6.Internal,
|
|
2613
3078
|
message: `[praxis-kit] Plugin pipeline must return a string. Got: ${got}.`
|
|
2614
3079
|
};
|
|
2615
3080
|
}
|