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/lit/index.js CHANGED
@@ -858,11 +858,24 @@ 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
+ };
861
873
  var HtmlDiagnostics = {
862
874
  emptyRole(tag) {
863
875
  return {
864
876
  code: DiagnosticCode3.HtmlEmptyRole,
865
877
  category: DiagnosticCategory3.HTML,
878
+ severity: "warning",
866
879
  message: `<${tag}> has an explicit empty role="". Omit the attribute instead.`
867
880
  };
868
881
  },
@@ -870,6 +883,7 @@ var HtmlDiagnostics = {
870
883
  return {
871
884
  code: DiagnosticCode3.HtmlImplicitRoleRedundant,
872
885
  category: DiagnosticCategory3.HTML,
886
+ severity: "warning",
873
887
  message: `<${tag}> already has implicit role="${implicitRole}". Avoid redundant role assignment.`
874
888
  };
875
889
  },
@@ -877,6 +891,7 @@ var HtmlDiagnostics = {
877
891
  return {
878
892
  code: DiagnosticCode3.HtmlImplicitRoleOverride,
879
893
  category: DiagnosticCategory3.HTML,
894
+ severity: "error",
880
895
  message: `<${tag}> should not override its implicit role="${implicitRole}" with role="${role}".`
881
896
  };
882
897
  },
@@ -884,6 +899,7 @@ var HtmlDiagnostics = {
884
899
  return {
885
900
  code: DiagnosticCode3.HtmlStandaloneRegionOverride,
886
901
  category: DiagnosticCategory3.HTML,
902
+ severity: "error",
887
903
  message: `<${tag}> is a self-contained element with implicit role="${implicitRole}". Assigning role="region" has been removed.`
888
904
  };
889
905
  },
@@ -891,6 +907,7 @@ var HtmlDiagnostics = {
891
907
  return {
892
908
  code: DiagnosticCode3.HtmlLandmarkRoleOverride,
893
909
  category: DiagnosticCategory3.HTML,
910
+ severity: "error",
894
911
  message: `<${tag}> has a fixed landmark role="${implicitRole}". role="${role}" overrides it and confuses assistive technology. The override has been removed.`
895
912
  };
896
913
  },
@@ -898,8 +915,122 @@ var HtmlDiagnostics = {
898
915
  return {
899
916
  code: DiagnosticCode3.HtmlInvalidChild,
900
917
  category: DiagnosticCategory3.HTML,
918
+ severity: "error",
901
919
  message: `<${child}> is not a valid direct child of <${parent}>. Allowed: ${allowed}.`
902
920
  };
921
+ },
922
+ roleNotPermitted(tag, role, allowedRoles) {
923
+ const allowed = allowedRoles.length > 0 ? allowedRoles.map((r) => `"${r}"`).join(", ") : "none \u2014 no explicit role is permitted on this element";
924
+ return {
925
+ code: DiagnosticCode3.HtmlRoleNotPermitted,
926
+ category: DiagnosticCategory3.HTML,
927
+ severity: "error",
928
+ message: `role="${role}" is not permitted on <${tag}>. Allowed alternate role(s): ${allowed}.`,
929
+ 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.'
930
+ };
931
+ },
932
+ // Reserved for <input>-specific facts (HTML3101–3199, see codes.ts) — later element families
933
+ // (button, img, table, ...) get their own reserved block and their own namespace here.
934
+ input: {
935
+ unsupportedType(type) {
936
+ return {
937
+ code: DiagnosticCode3.HtmlInputUnsupportedType,
938
+ category: DiagnosticCategory3.HTML,
939
+ severity: "warning",
940
+ message: `type="${type}" is not a value defined by the HTML specification. Browsers silently fall back to type="text".`,
941
+ 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).',
942
+ suggestions: [
943
+ {
944
+ title: "Check for a typo in the type value",
945
+ description: `"${type}" does not match any HTML5 input type.`
946
+ }
947
+ ]
948
+ };
949
+ },
950
+ // The user-visible problem is that the attribute is ignored — not that it "requires" a type;
951
+ // that's the rule's internal framing, not what the browser actually does.
952
+ attributeIgnoredForType(attribute, type, allowedTypes) {
953
+ const allowed = allowedTypes.map((t) => `"${t}"`).join(", ");
954
+ const code = ATTRIBUTE_IGNORED_CODES[attribute];
955
+ if (!code) throw new Error(`No DiagnosticCode registered for input attribute "${attribute}"`);
956
+ return {
957
+ code,
958
+ category: DiagnosticCategory3.HTML,
959
+ severity: "warning",
960
+ message: `"${attribute}" is ignored on <input type="${type}">.`,
961
+ rationale: `"${attribute}" only has an effect when type is one of: ${allowed}. Browsers silently ignore it on other input types.`,
962
+ suggestions: [
963
+ {
964
+ title: `Remove "${attribute}"`,
965
+ description: `"${attribute}" only affects <input> when type is one of: ${allowed}.`
966
+ }
967
+ ]
968
+ };
969
+ }
970
+ }
971
+ };
972
+
973
+ // ../../lib/contract/src/diagnostics/input-accessibility.ts
974
+ import { DiagnosticCategory as DiagnosticCategory4, DiagnosticCode as DiagnosticCode4 } from "../_shared/diagnostics.js";
975
+ function accessibilityFact(input) {
976
+ return { category: DiagnosticCategory4.Accessibility, ...input };
977
+ }
978
+ var InputAccessibilityDiagnostics = {
979
+ missingAccessibleName() {
980
+ return accessibilityFact({
981
+ code: DiagnosticCode4.A11yInputMissingAccessibleName,
982
+ severity: "warning",
983
+ message: "This input has no accessible name. Add an associated <label>, aria-label, or aria-labelledby.",
984
+ rationale: "Assistive technology announces a form field by its accessible name; without one, users of screen readers cannot tell what the field is for.",
985
+ suggestions: [
986
+ { title: "Add aria-label", description: 'Set aria-label="\u2026" directly on the input.' },
987
+ {
988
+ title: "Add an associated <label>",
989
+ description: 'Wrap the input in a <label>, or point a <label for="\u2026"> at its id.'
990
+ }
991
+ ]
992
+ });
993
+ },
994
+ placeholderIsNotLabel() {
995
+ return accessibilityFact({
996
+ code: DiagnosticCode4.A11yInputPlaceholderNotLabel,
997
+ severity: "warning",
998
+ message: "Placeholder text does not provide an accessible name. Add an associated <label>, aria-label, or aria-labelledby.",
999
+ rationale: "Placeholder text disappears as users interact with the field and is not treated as the control's accessible name by many assistive technologies.",
1000
+ suggestions: [
1001
+ { title: "Add aria-label", description: 'Set aria-label="\u2026" directly on the input.' },
1002
+ {
1003
+ title: "Add an associated <label>",
1004
+ description: 'Wrap the input in a <label>, or point a <label for="\u2026"> at its id.'
1005
+ }
1006
+ ]
1007
+ });
1008
+ },
1009
+ passwordMissingAutocomplete() {
1010
+ return accessibilityFact({
1011
+ code: DiagnosticCode4.A11yInputPasswordAutocomplete,
1012
+ severity: "warning",
1013
+ message: "Password inputs should specify an autoComplete value.",
1014
+ rationale: "Without an explicit autocomplete hint, password managers and browsers cannot reliably tell a sign-in field apart from a password-creation field.",
1015
+ suggestions: [
1016
+ {
1017
+ title: 'Set autoComplete="current-password"',
1018
+ description: "Use this for sign-in forms."
1019
+ },
1020
+ {
1021
+ title: 'Set autoComplete="new-password"',
1022
+ description: "Use this for sign-up / change-password forms."
1023
+ }
1024
+ ]
1025
+ });
1026
+ },
1027
+ requiredReadOnlyConflict() {
1028
+ return accessibilityFact({
1029
+ code: DiagnosticCode4.A11yInputRequiredReadOnlyConflict,
1030
+ severity: "warning",
1031
+ message: "The required and readOnly attributes are both present. A read-only field cannot satisfy required validation through user interaction.",
1032
+ 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."
1033
+ });
903
1034
  }
904
1035
  };
905
1036
 
@@ -958,7 +1089,7 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
958
1089
  tag,
959
1090
  role: "",
960
1091
  attribute: void 0,
961
- severity: "warning",
1092
+ severity: d.severity,
962
1093
  phase: "evaluate"
963
1094
  }
964
1095
  ]
@@ -1217,13 +1348,14 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1217
1348
  const role = props.role;
1218
1349
  if (!implicitRole || !role || role === implicitRole) return NO_VIOLATIONS;
1219
1350
  if (isStrongImplicitRole(tag) && role === "region") {
1351
+ const diagnostic = HtmlDiagnostics.implicitRoleOverride(tag, implicitRole, role);
1220
1352
  return [
1221
1353
  {
1222
1354
  valid: false,
1223
1355
  fixable: true,
1224
- severity: "error",
1356
+ severity: diagnostic.severity,
1225
1357
  fix: _AriaPolicyEngine.#removeRole,
1226
- diagnostic: HtmlDiagnostics.implicitRoleOverride(tag, implicitRole, role)
1358
+ diagnostic
1227
1359
  }
1228
1360
  ];
1229
1361
  }
@@ -1232,13 +1364,14 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1232
1364
  static #checkRedundantRole({ tag, props, implicitRole }) {
1233
1365
  const role = props.role;
1234
1366
  if (!implicitRole || !role || role !== implicitRole) return NO_VIOLATIONS;
1367
+ const diagnostic = HtmlDiagnostics.implicitRoleRedundant(tag, implicitRole);
1235
1368
  return [
1236
1369
  {
1237
1370
  valid: false,
1238
1371
  fixable: true,
1239
- severity: "warning",
1372
+ severity: diagnostic.severity,
1240
1373
  fix: _AriaPolicyEngine.#removeRole,
1241
- diagnostic: HtmlDiagnostics.implicitRoleRedundant(tag, implicitRole)
1374
+ diagnostic
1242
1375
  }
1243
1376
  ];
1244
1377
  }
@@ -1246,13 +1379,14 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1246
1379
  const role = props.role;
1247
1380
  if (role !== "region") return NO_VIOLATIONS;
1248
1381
  if (!isStandaloneTag(tag)) return NO_VIOLATIONS;
1382
+ const diagnostic = HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag);
1249
1383
  return [
1250
1384
  {
1251
1385
  valid: false,
1252
1386
  fixable: true,
1253
- severity: "error",
1387
+ severity: diagnostic.severity,
1254
1388
  fix: _AriaPolicyEngine.#removeRole,
1255
- diagnostic: HtmlDiagnostics.standaloneRegionOverride(tag, implicitRole ?? tag)
1389
+ diagnostic
1256
1390
  }
1257
1391
  ];
1258
1392
  }
@@ -1941,6 +2075,331 @@ var readonlyProps = ({
1941
2075
  // ../core/src/html/evaluators.ts
1942
2076
  import { warnDiagnostics as warnDiagnostics2 } from "../_shared/diagnostics.js";
1943
2077
 
2078
+ // ../core/src/html/input-rules.ts
2079
+ var DEFAULT_INPUT_TYPE = "text";
2080
+ function omit(props, key) {
2081
+ const next = { ...props };
2082
+ delete next[key];
2083
+ return next;
2084
+ }
2085
+ function removeAttributeFix(attribute) {
2086
+ return {
2087
+ kind: `removeAttribute:${attribute}`,
2088
+ apply: ({ props }) => {
2089
+ if (!(attribute in props)) return { applied: false, next: props };
2090
+ return { applied: true, next: omit(props, attribute), previous: props };
2091
+ }
2092
+ };
2093
+ }
2094
+ function inputAttributeRequiresType(attribute, allowedTypes) {
2095
+ const rule = ({ tag, props }) => {
2096
+ if (tag !== "input" || !(attribute in props)) return [];
2097
+ const type = typeof props.type === "string" ? props.type : DEFAULT_INPUT_TYPE;
2098
+ if (allowedTypes.includes(type)) return [];
2099
+ const diagnostic = HtmlDiagnostics.input.attributeIgnoredForType(attribute, type, allowedTypes);
2100
+ return [
2101
+ {
2102
+ valid: false,
2103
+ fixable: true,
2104
+ severity: diagnostic.severity,
2105
+ fix: removeAttributeFix(attribute),
2106
+ diagnostic
2107
+ }
2108
+ ];
2109
+ };
2110
+ return Object.assign(rule, { readsProps: ["type", attribute] });
2111
+ }
2112
+ var TEXT_INPUT_TYPES = ["text", "search", "url", "tel", "email", "password"];
2113
+ var NUMERIC_INPUT_TYPES = [
2114
+ "number",
2115
+ "range",
2116
+ "date",
2117
+ "month",
2118
+ "week",
2119
+ "time",
2120
+ "datetime-local"
2121
+ ];
2122
+ var HTML_INPUT_TYPES = /* @__PURE__ */ new Set([
2123
+ ...TEXT_INPUT_TYPES,
2124
+ ...NUMERIC_INPUT_TYPES,
2125
+ "checkbox",
2126
+ "radio",
2127
+ "file",
2128
+ "color",
2129
+ "hidden",
2130
+ "button",
2131
+ "submit",
2132
+ "reset",
2133
+ "image"
2134
+ ]);
2135
+ var supportedInputTypeRule = Object.assign(
2136
+ ({ tag, props }) => {
2137
+ if (tag !== "input" || typeof props.type !== "string") return [];
2138
+ const type = props.type;
2139
+ if (HTML_INPUT_TYPES.has(type)) return [];
2140
+ const diagnostic = HtmlDiagnostics.input.unsupportedType(type);
2141
+ return [
2142
+ {
2143
+ valid: false,
2144
+ fixable: false,
2145
+ severity: diagnostic.severity,
2146
+ diagnostic
2147
+ }
2148
+ ];
2149
+ },
2150
+ { readsProps: ["type"] }
2151
+ );
2152
+ var checkedRequiresCheckableTypeRule = inputAttributeRequiresType("checked", [
2153
+ "checkbox",
2154
+ "radio"
2155
+ ]);
2156
+ var multipleRequiresSupportedTypeRule = inputAttributeRequiresType("multiple", [
2157
+ "email",
2158
+ "file"
2159
+ ]);
2160
+ var maxLengthRequiresTextTypeRule = inputAttributeRequiresType(
2161
+ "maxLength",
2162
+ TEXT_INPUT_TYPES
2163
+ );
2164
+ var minLengthRequiresTextTypeRule = inputAttributeRequiresType(
2165
+ "minLength",
2166
+ TEXT_INPUT_TYPES
2167
+ );
2168
+ var patternRequiresTextTypeRule = inputAttributeRequiresType("pattern", TEXT_INPUT_TYPES);
2169
+ var minRequiresNumericTypeRule = inputAttributeRequiresType("min", NUMERIC_INPUT_TYPES);
2170
+ var maxRequiresNumericTypeRule = inputAttributeRequiresType("max", NUMERIC_INPUT_TYPES);
2171
+ var stepRequiresNumericTypeRule = inputAttributeRequiresType("step", NUMERIC_INPUT_TYPES);
2172
+ var acceptRequiresFileTypeRule = inputAttributeRequiresType("accept", ["file"]);
2173
+ var captureRequiresFileTypeRule = inputAttributeRequiresType("capture", ["file"]);
2174
+ var inputAccessibleNameRule = Object.assign(
2175
+ ({ tag, props }) => {
2176
+ if (tag !== "input" || props.type === "hidden") return [];
2177
+ if ("aria-label" in props || "aria-labelledby" in props) return [];
2178
+ const hasPlaceholder = typeof props.placeholder === "string" && props.placeholder.length > 0;
2179
+ const diagnostic = hasPlaceholder ? InputAccessibilityDiagnostics.placeholderIsNotLabel() : InputAccessibilityDiagnostics.missingAccessibleName();
2180
+ return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2181
+ },
2182
+ { readsProps: ["type", "aria-label", "aria-labelledby", "placeholder"] }
2183
+ );
2184
+ var PASSWORD_AUTOCOMPLETE_VALUES = ["current-password", "new-password"];
2185
+ var passwordAutocompleteRule = Object.assign(
2186
+ ({ tag, props }) => {
2187
+ if (tag !== "input" || props.type !== "password") return [];
2188
+ const autoComplete = props.autoComplete;
2189
+ const tokens = typeof autoComplete === "string" ? autoComplete.split(" ") : [];
2190
+ if (PASSWORD_AUTOCOMPLETE_VALUES.some((value) => tokens.includes(value))) return [];
2191
+ const diagnostic = InputAccessibilityDiagnostics.passwordMissingAutocomplete();
2192
+ return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2193
+ },
2194
+ { readsProps: ["type", "autoComplete"] }
2195
+ );
2196
+ var requiredReadOnlyConflictRule = Object.assign(
2197
+ ({ tag, props }) => {
2198
+ if (tag !== "input" || !props.required || !props.readOnly) return [];
2199
+ const diagnostic = InputAccessibilityDiagnostics.requiredReadOnlyConflict();
2200
+ return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2201
+ },
2202
+ { readsProps: ["required", "readOnly"] }
2203
+ );
2204
+ var INPUT_RULES = [
2205
+ supportedInputTypeRule,
2206
+ checkedRequiresCheckableTypeRule,
2207
+ multipleRequiresSupportedTypeRule,
2208
+ maxLengthRequiresTextTypeRule,
2209
+ minLengthRequiresTextTypeRule,
2210
+ patternRequiresTextTypeRule,
2211
+ minRequiresNumericTypeRule,
2212
+ maxRequiresNumericTypeRule,
2213
+ stepRequiresNumericTypeRule,
2214
+ acceptRequiresFileTypeRule,
2215
+ captureRequiresFileTypeRule,
2216
+ inputAccessibleNameRule,
2217
+ passwordAutocompleteRule,
2218
+ requiredReadOnlyConflictRule
2219
+ ];
2220
+
2221
+ // ../core/src/html/role-restrictions.ts
2222
+ var ALLOWED_ROLES = {
2223
+ article: ["application", "document", "feed", "main", "none", "presentation", "region"],
2224
+ aside: ["feed", "none", "presentation", "region", "search"],
2225
+ footer: ["group", "none", "presentation"],
2226
+ header: ["group", "none", "presentation"],
2227
+ main: [],
2228
+ nav: [],
2229
+ a: [
2230
+ "button",
2231
+ "checkbox",
2232
+ "menuitem",
2233
+ "menuitemcheckbox",
2234
+ "menuitemradio",
2235
+ "option",
2236
+ "radio",
2237
+ "switch",
2238
+ "tab",
2239
+ "treeitem"
2240
+ ],
2241
+ button: [
2242
+ "checkbox",
2243
+ "link",
2244
+ "menuitem",
2245
+ "menuitemcheckbox",
2246
+ "menuitemradio",
2247
+ "option",
2248
+ "radio",
2249
+ "switch",
2250
+ "tab"
2251
+ ],
2252
+ select: ["menu"],
2253
+ h1: ["tab", "presentation", "none"],
2254
+ h2: ["tab", "presentation", "none"],
2255
+ h3: ["tab", "presentation", "none"],
2256
+ h4: ["tab", "presentation", "none"],
2257
+ h5: ["tab", "presentation", "none"],
2258
+ h6: ["tab", "presentation", "none"],
2259
+ ul: [
2260
+ "directory",
2261
+ "group",
2262
+ "listbox",
2263
+ "menu",
2264
+ "menubar",
2265
+ "radiogroup",
2266
+ "tablist",
2267
+ "toolbar",
2268
+ "tree"
2269
+ ],
2270
+ ol: [
2271
+ "directory",
2272
+ "group",
2273
+ "listbox",
2274
+ "menu",
2275
+ "menubar",
2276
+ "radiogroup",
2277
+ "tablist",
2278
+ "toolbar",
2279
+ "tree"
2280
+ ],
2281
+ li: [
2282
+ "menuitem",
2283
+ "menuitemcheckbox",
2284
+ "menuitemradio",
2285
+ "option",
2286
+ "none",
2287
+ "presentation",
2288
+ "radio",
2289
+ "separator",
2290
+ "tab",
2291
+ "treeitem"
2292
+ ],
2293
+ table: ["grid", "treegrid"],
2294
+ dialog: ["alertdialog"],
2295
+ fieldset: ["none", "presentation", "radiogroup"]
2296
+ };
2297
+ var IMG_NAMED_ROLES = [
2298
+ "button",
2299
+ "checkbox",
2300
+ "link",
2301
+ "menuitem",
2302
+ "menuitemcheckbox",
2303
+ "menuitemradio",
2304
+ "option",
2305
+ "progressbar",
2306
+ "scrollbar",
2307
+ "separator",
2308
+ "slider",
2309
+ "switch",
2310
+ "tab",
2311
+ "treeitem"
2312
+ ];
2313
+ var ALLOWED_INPUT_ROLES = {
2314
+ checkbox: ["menuitemcheckbox", "option", "switch", "button"],
2315
+ radio: ["menuitemradio"],
2316
+ range: [],
2317
+ number: [],
2318
+ search: ["combobox"],
2319
+ text: ["combobox", "searchbox", "spinbutton"],
2320
+ email: ["combobox"],
2321
+ tel: ["combobox"],
2322
+ url: ["combobox"],
2323
+ button: [
2324
+ "link",
2325
+ "menuitem",
2326
+ "menuitemcheckbox",
2327
+ "menuitemradio",
2328
+ "option",
2329
+ "radio",
2330
+ "switch",
2331
+ "tab"
2332
+ ],
2333
+ submit: [
2334
+ "link",
2335
+ "menuitem",
2336
+ "menuitemcheckbox",
2337
+ "menuitemradio",
2338
+ "option",
2339
+ "radio",
2340
+ "switch",
2341
+ "tab"
2342
+ ],
2343
+ reset: [
2344
+ "link",
2345
+ "menuitem",
2346
+ "menuitemcheckbox",
2347
+ "menuitemradio",
2348
+ "option",
2349
+ "radio",
2350
+ "switch",
2351
+ "tab"
2352
+ ],
2353
+ image: [
2354
+ "link",
2355
+ "menuitem",
2356
+ "menuitemcheckbox",
2357
+ "menuitemradio",
2358
+ "option",
2359
+ "radio",
2360
+ "switch",
2361
+ "tab"
2362
+ ],
2363
+ hidden: []
2364
+ };
2365
+ function getAllowedRoles(tag, props) {
2366
+ if (tag === "input") {
2367
+ const type = typeof props.type === "string" ? props.type : "text";
2368
+ return ALLOWED_INPUT_ROLES[type];
2369
+ }
2370
+ if (tag === "img") {
2371
+ return props.alt === "" ? [] : IMG_NAMED_ROLES;
2372
+ }
2373
+ return ALLOWED_ROLES[tag];
2374
+ }
2375
+ var removeRoleFix = {
2376
+ kind: "removeRole",
2377
+ apply: ({ props }) => {
2378
+ if (!("role" in props)) return { applied: false, next: props };
2379
+ const { role: _role, ...rest } = props;
2380
+ return { applied: true, next: rest, previous: props };
2381
+ }
2382
+ };
2383
+ var roleNotPermittedRule = Object.assign(
2384
+ ({ tag, props, implicitRole }) => {
2385
+ const role = props.role;
2386
+ if (typeof role !== "string" || role.length === 0 || role === implicitRole) return [];
2387
+ const allowed = getAllowedRoles(tag, props);
2388
+ if (allowed === void 0 || allowed.includes(role)) return [];
2389
+ const diagnostic = HtmlDiagnostics.roleNotPermitted(tag, role, allowed);
2390
+ return [
2391
+ {
2392
+ valid: false,
2393
+ fixable: true,
2394
+ severity: diagnostic.severity,
2395
+ fix: removeRoleFix,
2396
+ diagnostic
2397
+ }
2398
+ ];
2399
+ },
2400
+ { readsProps: ["role", "type", "alt"] }
2401
+ );
2402
+
1944
2403
  // ../core/src/html/aria-rules.ts
1945
2404
  var LANDMARK_TAG_SET = /* @__PURE__ */ new Set(["article", "aside", "footer", "header", "main", "nav"]);
1946
2405
  var removeLandmarkRoleOverride = {
@@ -1955,13 +2414,14 @@ function landmarkRoleRule({ tag, props, implicitRole }) {
1955
2414
  if (!LANDMARK_TAG_SET.has(tag) || !implicitRole) return [];
1956
2415
  const role = props.role;
1957
2416
  if (!role || role === implicitRole) return [];
2417
+ const diagnostic = HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role);
1958
2418
  return [
1959
2419
  {
1960
2420
  valid: false,
1961
2421
  fixable: true,
1962
- severity: "error",
2422
+ severity: diagnostic.severity,
1963
2423
  fix: removeLandmarkRoleOverride,
1964
- diagnostic: HtmlDiagnostics.landmarkRoleOverride(tag, implicitRole, role)
2424
+ diagnostic
1965
2425
  }
1966
2426
  ];
1967
2427
  }
@@ -1981,7 +2441,12 @@ function landmarkNameAdvisory(ctx) {
1981
2441
  if (!ctx.implicitRole || !NAMED_LANDMARK_TAGS.has(ctx.tag)) return [];
1982
2442
  return requireAccessibleName(ctx);
1983
2443
  }
1984
- var HTML_ARIA_RULES = [landmarkRoleRule, landmarkNameAdvisory];
2444
+ var HTML_ARIA_RULES = [
2445
+ landmarkRoleRule,
2446
+ landmarkNameAdvisory,
2447
+ roleNotPermittedRule,
2448
+ ...INPUT_RULES
2449
+ ];
1985
2450
 
1986
2451
  // ../core/src/html/contracts.ts
1987
2452
  import { warnDiagnostics } from "../_shared/diagnostics.js";
@@ -2442,21 +2907,21 @@ function validateRenderProps(diagnostics, options, props, recipeKey) {
2442
2907
  import { throwDiagnostics } from "../_shared/diagnostics.js";
2443
2908
 
2444
2909
  // ../core/src/factory/plugin-diagnostics.ts
2445
- import { DiagnosticCategory as DiagnosticCategory4, DiagnosticCode as DiagnosticCode4 } from "../_shared/diagnostics.js";
2910
+ import { DiagnosticCategory as DiagnosticCategory5, DiagnosticCode as DiagnosticCode5 } from "../_shared/diagnostics.js";
2446
2911
  var PluginDiagnostics = {
2447
2912
  invalidShape(received) {
2448
2913
  const got = received === null ? "null" : typeof received;
2449
2914
  return {
2450
- code: DiagnosticCode4.PluginInvalidShape,
2451
- category: DiagnosticCategory4.Internal,
2915
+ code: DiagnosticCode5.PluginInvalidShape,
2916
+ category: DiagnosticCategory5.Internal,
2452
2917
  message: `[praxis-kit] Plugin factory must return an object with a 'pipeline' function. Got: ${got}.`
2453
2918
  };
2454
2919
  },
2455
2920
  pipelineReturnType(received) {
2456
2921
  const got = received === null ? "null" : Array.isArray(received) ? "array" : typeof received;
2457
2922
  return {
2458
- code: DiagnosticCode4.PluginPipelineReturnType,
2459
- category: DiagnosticCategory4.Internal,
2923
+ code: DiagnosticCode5.PluginPipelineReturnType,
2924
+ category: DiagnosticCategory5.Internal,
2460
2925
  message: `[praxis-kit] Plugin pipeline must return a string. Got: ${got}.`
2461
2926
  };
2462
2927
  }