praxis-kit 7.4.0 → 7.8.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/lit/index.js CHANGED
@@ -33,6 +33,9 @@ function dynamic(resolve) {
33
33
  }
34
34
 
35
35
  // ../../lib/primitive/src/guards/foundational/is-defined.ts
36
+ function isDefined(value) {
37
+ return value !== void 0;
38
+ }
36
39
  function isUndefined(value) {
37
40
  return value === void 0;
38
41
  }
@@ -667,21 +670,65 @@ function getImplicitRole(tag, props) {
667
670
  return void 0;
668
671
  }
669
672
 
670
- // ../../lib/contract/src/diagnostics/aria.ts
673
+ // ../../lib/contract/src/diagnostics/anchor-accessibility.ts
671
674
  import { DiagnosticCategory, DiagnosticCode } from "../_shared/diagnostics.js";
675
+ function createDiagnostic(input) {
676
+ return { category: DiagnosticCategory.Accessibility, ...input };
677
+ }
678
+ var AnchorAccessibilityDiagnostics = {
679
+ roleButtonWithHref() {
680
+ return createDiagnostic({
681
+ code: DiagnosticCode.A11yAnchorRoleButtonWithHref,
682
+ severity: "warning",
683
+ message: 'role="button" on an <a> with an href overrides its navigation semantics for assistive technology; use a real <button> element, or remove href, if this element should not navigate.',
684
+ rationale: 'Assistive technology announces role="button" as a button, not a link \u2014 but the element still follows the link when activated by mouse or keyboard, which is confusing and inconsistent with how a button is expected to behave.',
685
+ suggestions: [
686
+ {
687
+ title: "Use a real <button>",
688
+ description: "If this element should not navigate, render a <button> instead of an <a>."
689
+ },
690
+ {
691
+ title: 'Remove role="button"',
692
+ description: "If this element should navigate, keep the default link role."
693
+ }
694
+ ]
695
+ });
696
+ },
697
+ ariaDisabledInert() {
698
+ return createDiagnostic({
699
+ code: DiagnosticCode.A11yAnchorAriaDisabledInert,
700
+ severity: "warning",
701
+ message: "aria-disabled does not prevent an <a> from being focused, clicked, or navigated via keyboard; remove href, or prevent navigation yourself, if this link should be inert.",
702
+ rationale: "Unlike a native form control, an <a> has no disabled state the browser enforces \u2014 aria-disabled only changes what assistive technology announces, not what actually happens when the link is activated by mouse or keyboard.",
703
+ suggestions: [
704
+ {
705
+ title: "Remove href",
706
+ description: "If this element is temporarily unavailable, remove href until it can be activated."
707
+ },
708
+ {
709
+ title: "Prevent navigation when disabled",
710
+ description: "Prevent the click and keyboard activation when the link is disabled so it behaves consistently for all users."
711
+ }
712
+ ]
713
+ });
714
+ }
715
+ };
716
+
717
+ // ../../lib/contract/src/diagnostics/aria.ts
718
+ import { DiagnosticCategory as DiagnosticCategory2, DiagnosticCode as DiagnosticCode2 } from "../_shared/diagnostics.js";
672
719
  var AriaDiagnostics = {
673
720
  /** Generic bridge for violations produced by external AriaRule functions. */
674
721
  fromViolation(v) {
675
722
  return {
676
- code: DiagnosticCode.AriaViolation,
677
- category: DiagnosticCategory.ARIA,
723
+ code: DiagnosticCode2.AriaViolation,
724
+ category: DiagnosticCategory2.ARIA,
678
725
  message: v.message
679
726
  };
680
727
  },
681
728
  attributeInvalid(key, role) {
682
729
  return {
683
- code: DiagnosticCode.AriaAttributeInvalid,
684
- category: DiagnosticCategory.ARIA,
730
+ code: DiagnosticCode2.AriaAttributeInvalid,
731
+ category: DiagnosticCategory2.ARIA,
685
732
  message: `"${key}" is not valid on role="${role}". It will be removed.`,
686
733
  rationale: "Invalid ARIA attributes are ignored by assistive technology and may trigger accessibility-tree warnings in browser devtools.",
687
734
  suggestions: [
@@ -694,8 +741,8 @@ var AriaDiagnostics = {
694
741
  },
695
742
  missingLiveRegion(role, impliedLive) {
696
743
  return {
697
- code: DiagnosticCode.AriaMissingLiveRegion,
698
- category: DiagnosticCategory.ARIA,
744
+ code: DiagnosticCode2.AriaMissingLiveRegion,
745
+ category: DiagnosticCategory2.ARIA,
699
746
  message: `role="${role}" implies aria-live="${impliedLive}" but it is missing. It has been injected.`,
700
747
  rationale: "Live-region roles announce dynamic content changes to screen readers. Without aria-live the politeness level is unspecified and announcements may be silent.",
701
748
  suggestions: [
@@ -709,8 +756,8 @@ var AriaDiagnostics = {
709
756
  },
710
757
  missingAtomic(role) {
711
758
  return {
712
- code: DiagnosticCode.AriaMissingAtomic,
713
- category: DiagnosticCategory.ARIA,
759
+ code: DiagnosticCode2.AriaMissingAtomic,
760
+ category: DiagnosticCategory2.ARIA,
714
761
  message: `role="${role}" is a live region. Consider setting aria-atomic="true" if the full region should be announced as a unit, or aria-atomic="false" if only changed nodes should be read.`,
715
762
  rationale: "aria-atomic controls whether assistive technology announces the entire live region or only the changed nodes. Omitting it leaves the behaviour browser-defined."
716
763
  };
@@ -718,8 +765,8 @@ var AriaDiagnostics = {
718
765
  relevantInvalidTokens(invalid) {
719
766
  const quoted = invalid.map((t) => `"${t}"`).join(", ");
720
767
  return {
721
- code: DiagnosticCode.AriaRelevantInvalidToken,
722
- category: DiagnosticCategory.ARIA,
768
+ code: DiagnosticCode2.AriaRelevantInvalidToken,
769
+ category: DiagnosticCategory2.ARIA,
723
770
  message: `aria-relevant contains invalid token(s): ${quoted}. Valid tokens are: additions, removals, text, all.`,
724
771
  rationale: "aria-relevant accepts a space-separated list of change types. Unrecognised tokens are silently ignored by assistive technology, making the attribute ineffective.",
725
772
  suggestions: [
@@ -732,8 +779,8 @@ var AriaDiagnostics = {
732
779
  },
733
780
  relevantSuperseded() {
734
781
  return {
735
- code: DiagnosticCode.AriaRelevantSuperseded,
736
- category: DiagnosticCategory.ARIA,
782
+ code: DiagnosticCode2.AriaRelevantSuperseded,
783
+ category: DiagnosticCategory2.ARIA,
737
784
  message: 'aria-relevant includes "all" alongside other tokens. "all" supersedes additions, removals, and text \u2014 use aria-relevant="all" alone.',
738
785
  rationale: '"all" is equivalent to "additions removals text". Combining it with other tokens is redundant and may confuse readers of the markup.',
739
786
  suggestions: [
@@ -746,8 +793,8 @@ var AriaDiagnostics = {
746
793
  },
747
794
  missingAccessibleName(tag) {
748
795
  return {
749
- code: DiagnosticCode.AriaMissingAccessibleName,
750
- category: DiagnosticCategory.ARIA,
796
+ code: DiagnosticCode2.AriaMissingAccessibleName,
797
+ category: DiagnosticCategory2.ARIA,
751
798
  message: `<${tag}> has no accessible name. Add aria-label or aria-labelledby.`,
752
799
  rationale: "Elements with a landmark or interactive role must have an accessible name so that assistive technology can identify them when presenting the page outline.",
753
800
  suggestions: [
@@ -764,8 +811,8 @@ var AriaDiagnostics = {
764
811
  },
765
812
  attributeOnPresentational(attr, tag) {
766
813
  return {
767
- code: DiagnosticCode.AriaAttributeOnPresentational,
768
- category: DiagnosticCategory.ARIA,
814
+ code: DiagnosticCode2.AriaAttributeOnPresentational,
815
+ category: DiagnosticCategory2.ARIA,
769
816
  message: `"${attr}" is not allowed on a presentational <${tag}>. Presentational elements are invisible to assistive technology.`,
770
817
  rationale: 'role="none" and role="presentation" (including <img alt="">) remove an element from the accessibility tree. ARIA attributes on such elements are ignored by assistive technology.',
771
818
  suggestions: [
@@ -778,8 +825,8 @@ var AriaDiagnostics = {
778
825
  },
779
826
  ariaHiddenOnFocusable(tag) {
780
827
  return {
781
- code: DiagnosticCode.AriaHiddenOnFocusable,
782
- category: DiagnosticCategory.ARIA,
828
+ code: DiagnosticCode2.AriaHiddenOnFocusable,
829
+ category: DiagnosticCategory2.ARIA,
783
830
  message: `aria-hidden="true" must not be used on focusable <${tag}> elements. Screen reader users who navigate by keyboard will encounter the element but receive no information about it.`,
784
831
  rationale: 'aria-hidden removes an element from the accessibility tree while leaving it keyboard-reachable. This creates a "ghost" \u2014 a focusable element assistive technology cannot describe.',
785
832
  suggestions: [
@@ -797,8 +844,8 @@ var AriaDiagnostics = {
797
844
  invalidAttributeValue(attr, value, expected) {
798
845
  const got = value === null ? "null" : value === void 0 ? "undefined" : typeof value === "string" ? `"${value}"` : String(value);
799
846
  return {
800
- code: DiagnosticCode.AriaInvalidAttributeValue,
801
- category: DiagnosticCategory.ARIA,
847
+ code: DiagnosticCode2.AriaInvalidAttributeValue,
848
+ category: DiagnosticCategory2.ARIA,
802
849
  message: `"${attr}" has an invalid value (${got}). Expected: ${expected}.`,
803
850
  rationale: "ARIA attributes with invalid values are silently ignored by assistive technology, making the markup semantically inert.",
804
851
  suggestions: [
@@ -811,8 +858,8 @@ var AriaDiagnostics = {
811
858
  },
812
859
  redundantAriaLevel(tag, level) {
813
860
  return {
814
- code: DiagnosticCode.AriaRedundantLevelAttribute,
815
- category: DiagnosticCategory.ARIA,
861
+ code: DiagnosticCode2.AriaRedundantLevelAttribute,
862
+ category: DiagnosticCategory2.ARIA,
816
863
  message: `aria-level="${level}" is redundant on <${tag}>: the element already has an implicit heading level of ${level}. Remove the attribute.`,
817
864
  rationale: 'Restating the implicit aria-level adds noise without semantic value. Use aria-level only to override the native heading level (e.g. aria-level="3" on <h2>).',
818
865
  suggestions: [
@@ -825,8 +872,8 @@ var AriaDiagnostics = {
825
872
  },
826
873
  requiredProperty(attr, role) {
827
874
  return {
828
- code: DiagnosticCode.AriaRequiredProperty,
829
- category: DiagnosticCategory.ARIA,
875
+ code: DiagnosticCode2.AriaRequiredProperty,
876
+ category: DiagnosticCategory2.ARIA,
830
877
  message: `"${attr}" is required for role="${role}" but is missing.`,
831
878
  rationale: `WAI-ARIA 1.2 specifies required states and properties for certain roles. Without "${attr}", assistive technology cannot correctly communicate the element's state to users.`,
832
879
  suggestions: [
@@ -839,8 +886,8 @@ var AriaDiagnostics = {
839
886
  },
840
887
  invalidRole(role, tag) {
841
888
  return {
842
- code: DiagnosticCode.AriaInvalidRole,
843
- category: DiagnosticCategory.ARIA,
889
+ code: DiagnosticCode2.AriaInvalidRole,
890
+ category: DiagnosticCategory2.ARIA,
844
891
  message: `Invalid role "${role ?? ""}" on <${tag}>.`,
845
892
  rationale: "An unrecognised or misapplied ARIA role is ignored by assistive technology and may degrade the accessibility of the element."
846
893
  };
@@ -848,12 +895,12 @@ var AriaDiagnostics = {
848
895
  };
849
896
 
850
897
  // ../../lib/contract/src/diagnostics/contract.ts
851
- import { DiagnosticCategory as DiagnosticCategory2, DiagnosticCode as DiagnosticCode2 } from "../_shared/diagnostics.js";
898
+ import { DiagnosticCategory as DiagnosticCategory3, DiagnosticCode as DiagnosticCode3 } from "../_shared/diagnostics.js";
852
899
  var ContractDiagnostics = {
853
900
  unexpectedChild(typeName, index, context) {
854
901
  return {
855
- code: DiagnosticCode2.UnexpectedChild,
856
- category: DiagnosticCategory2.Contract,
902
+ code: DiagnosticCode3.UnexpectedChild,
903
+ category: DiagnosticCategory3.Contract,
857
904
  component: context,
858
905
  message: `${context}: unexpected child "${typeName}" at index ${index}.`
859
906
  };
@@ -861,69 +908,69 @@ var ContractDiagnostics = {
861
908
  ambiguousChild(typeName, index, ruleNames, context) {
862
909
  const quoted = ruleNames.map((n) => `"${n}"`).join(" and ");
863
910
  return {
864
- code: DiagnosticCode2.AmbiguousChild,
865
- category: DiagnosticCategory2.Contract,
911
+ code: DiagnosticCode3.AmbiguousChild,
912
+ category: DiagnosticCategory3.Contract,
866
913
  component: context,
867
914
  message: `${context}: child "${typeName}" at index ${index} matches multiple child rules: ${quoted}.`
868
915
  };
869
916
  },
870
917
  cardinalityMin(ruleName, min, context) {
871
918
  return {
872
- code: DiagnosticCode2.CardinalityMin,
873
- category: DiagnosticCategory2.Contract,
919
+ code: DiagnosticCode3.CardinalityMin,
920
+ category: DiagnosticCategory3.Contract,
874
921
  component: context,
875
922
  message: `${context}: "${ruleName}" requires at least ${min}.`
876
923
  };
877
924
  },
878
925
  cardinalityMax(ruleName, max, context) {
879
926
  return {
880
- code: DiagnosticCode2.CardinalityMax,
881
- category: DiagnosticCategory2.Contract,
927
+ code: DiagnosticCode3.CardinalityMax,
928
+ category: DiagnosticCategory3.Contract,
882
929
  component: context,
883
930
  message: `${context}: "${ruleName}" allows at most ${max}.`
884
931
  };
885
932
  },
886
933
  positionViolation(ruleName, position, index, context) {
887
934
  return {
888
- code: DiagnosticCode2.PositionViolation,
889
- category: DiagnosticCategory2.Contract,
935
+ code: DiagnosticCode3.PositionViolation,
936
+ category: DiagnosticCategory3.Contract,
890
937
  component: context,
891
938
  message: `${context}: "${ruleName}" must be ${position}, got index ${index}`
892
939
  };
893
940
  },
894
941
  unknownVariantDim(component, label2, dim) {
895
942
  return {
896
- code: DiagnosticCode2.ContractUnknownVariantDim,
897
- category: DiagnosticCategory2.Contract,
943
+ code: DiagnosticCode3.ContractUnknownVariantDim,
944
+ category: DiagnosticCategory3.Contract,
898
945
  message: `${component}: ${label2} references unknown variant "${dim}".`
899
946
  };
900
947
  },
901
948
  unknownVariantValue(component, label2, dim, value, valid) {
902
949
  return {
903
- code: DiagnosticCode2.ContractUnknownVariantValue,
904
- category: DiagnosticCategory2.Contract,
950
+ code: DiagnosticCode3.ContractUnknownVariantValue,
951
+ category: DiagnosticCategory3.Contract,
905
952
  message: `${component}: ${label2} sets "${dim}" to unknown value "${value}" (valid: ${valid.join(", ")}).`
906
953
  };
907
954
  },
908
955
  unknownRecipeKey(component, key) {
909
956
  return {
910
- code: DiagnosticCode2.ContractUnknownRecipeKey,
911
- category: DiagnosticCategory2.Contract,
957
+ code: DiagnosticCode3.ContractUnknownRecipeKey,
958
+ category: DiagnosticCategory3.Contract,
912
959
  message: `${component} Unknown recipeKey "${key}" \u2014 no preset with that name exists.`
913
960
  };
914
961
  },
915
962
  invalidVariantValue(component, key, value) {
916
963
  return {
917
- code: DiagnosticCode2.ContractInvalidVariantValue,
918
- category: DiagnosticCategory2.Contract,
964
+ code: DiagnosticCode3.ContractInvalidVariantValue,
965
+ category: DiagnosticCategory3.Contract,
919
966
  message: `${component} Variant "${key}=${value}" is not a defined value for the "${key}" dimension.`
920
967
  };
921
968
  },
922
969
  allowedAsViolation(tag, allowedAs, component) {
923
970
  const allowed = allowedAs.map((t) => `"${String(t)}"`).join(", ");
924
971
  return {
925
- code: DiagnosticCode2.AllowedAsViolation,
926
- category: DiagnosticCategory2.Contract,
972
+ code: DiagnosticCode3.AllowedAsViolation,
973
+ category: DiagnosticCategory3.Contract,
927
974
  component,
928
975
  message: `<${component}>: "as" prop received "${tag}" but only [${allowed}] are allowed.`
929
976
  };
@@ -931,68 +978,68 @@ var ContractDiagnostics = {
931
978
  };
932
979
 
933
980
  // ../../lib/contract/src/diagnostics/html.ts
934
- import { DiagnosticCategory as DiagnosticCategory3, DiagnosticCode as DiagnosticCode3 } from "../_shared/diagnostics.js";
981
+ import { DiagnosticCategory as DiagnosticCategory4, DiagnosticCode as DiagnosticCode4 } from "../_shared/diagnostics.js";
935
982
  var ATTRIBUTE_IGNORED_CODES = {
936
- checked: DiagnosticCode3.HtmlInputCheckedIgnoredForType,
937
- multiple: DiagnosticCode3.HtmlInputMultipleIgnoredForType,
938
- maxLength: DiagnosticCode3.HtmlInputMaxLengthIgnoredForType,
939
- minLength: DiagnosticCode3.HtmlInputMinLengthIgnoredForType,
940
- pattern: DiagnosticCode3.HtmlInputPatternIgnoredForType,
941
- min: DiagnosticCode3.HtmlInputMinIgnoredForType,
942
- max: DiagnosticCode3.HtmlInputMaxIgnoredForType,
943
- step: DiagnosticCode3.HtmlInputStepIgnoredForType,
944
- accept: DiagnosticCode3.HtmlInputAcceptIgnoredForType,
945
- capture: DiagnosticCode3.HtmlInputCaptureIgnoredForType,
946
- size: DiagnosticCode3.HtmlInputSizeIgnoredForType,
947
- alt: DiagnosticCode3.HtmlInputAltIgnoredForType,
948
- height: DiagnosticCode3.HtmlInputHeightIgnoredForType,
949
- width: DiagnosticCode3.HtmlInputWidthIgnoredForType
983
+ checked: DiagnosticCode4.HtmlInputCheckedIgnoredForType,
984
+ multiple: DiagnosticCode4.HtmlInputMultipleIgnoredForType,
985
+ maxLength: DiagnosticCode4.HtmlInputMaxLengthIgnoredForType,
986
+ minLength: DiagnosticCode4.HtmlInputMinLengthIgnoredForType,
987
+ pattern: DiagnosticCode4.HtmlInputPatternIgnoredForType,
988
+ min: DiagnosticCode4.HtmlInputMinIgnoredForType,
989
+ max: DiagnosticCode4.HtmlInputMaxIgnoredForType,
990
+ step: DiagnosticCode4.HtmlInputStepIgnoredForType,
991
+ accept: DiagnosticCode4.HtmlInputAcceptIgnoredForType,
992
+ capture: DiagnosticCode4.HtmlInputCaptureIgnoredForType,
993
+ size: DiagnosticCode4.HtmlInputSizeIgnoredForType,
994
+ alt: DiagnosticCode4.HtmlInputAltIgnoredForType,
995
+ height: DiagnosticCode4.HtmlInputHeightIgnoredForType,
996
+ width: DiagnosticCode4.HtmlInputWidthIgnoredForType
950
997
  };
951
998
  var HtmlDiagnostics = {
952
999
  emptyRole(tag) {
953
1000
  return {
954
- code: DiagnosticCode3.HtmlEmptyRole,
955
- category: DiagnosticCategory3.HTML,
1001
+ code: DiagnosticCode4.HtmlEmptyRole,
1002
+ category: DiagnosticCategory4.HTML,
956
1003
  severity: "warning",
957
1004
  message: `<${tag}> has an explicit empty role="". Omit the attribute instead.`
958
1005
  };
959
1006
  },
960
1007
  implicitRoleRedundant(tag, implicitRole) {
961
1008
  return {
962
- code: DiagnosticCode3.HtmlImplicitRoleRedundant,
963
- category: DiagnosticCategory3.HTML,
1009
+ code: DiagnosticCode4.HtmlImplicitRoleRedundant,
1010
+ category: DiagnosticCategory4.HTML,
964
1011
  severity: "warning",
965
1012
  message: `<${tag}> already has implicit role="${implicitRole}". Avoid redundant role assignment.`
966
1013
  };
967
1014
  },
968
1015
  implicitRoleOverride(tag, implicitRole, role) {
969
1016
  return {
970
- code: DiagnosticCode3.HtmlImplicitRoleOverride,
971
- category: DiagnosticCategory3.HTML,
1017
+ code: DiagnosticCode4.HtmlImplicitRoleOverride,
1018
+ category: DiagnosticCategory4.HTML,
972
1019
  severity: "error",
973
1020
  message: `<${tag}> should not override its implicit role="${implicitRole}" with role="${role}".`
974
1021
  };
975
1022
  },
976
1023
  standaloneRegionOverride(tag, implicitRole) {
977
1024
  return {
978
- code: DiagnosticCode3.HtmlStandaloneRegionOverride,
979
- category: DiagnosticCategory3.HTML,
1025
+ code: DiagnosticCode4.HtmlStandaloneRegionOverride,
1026
+ category: DiagnosticCategory4.HTML,
980
1027
  severity: "error",
981
1028
  message: `<${tag}> is a self-contained element with implicit role="${implicitRole}". Assigning role="region" has been removed.`
982
1029
  };
983
1030
  },
984
1031
  landmarkRoleOverride(tag, implicitRole, role) {
985
1032
  return {
986
- code: DiagnosticCode3.HtmlLandmarkRoleOverride,
987
- category: DiagnosticCategory3.HTML,
1033
+ code: DiagnosticCode4.HtmlLandmarkRoleOverride,
1034
+ category: DiagnosticCategory4.HTML,
988
1035
  severity: "error",
989
1036
  message: `<${tag}> has a fixed landmark role="${implicitRole}". role="${role}" overrides it and confuses assistive technology. The override has been removed.`
990
1037
  };
991
1038
  },
992
1039
  invalidChild(child, parent, allowed) {
993
1040
  return {
994
- code: DiagnosticCode3.HtmlInvalidChild,
995
- category: DiagnosticCategory3.HTML,
1041
+ code: DiagnosticCode4.HtmlInvalidChild,
1042
+ category: DiagnosticCategory4.HTML,
996
1043
  severity: "error",
997
1044
  message: `<${child}> is not a valid direct child of <${parent}>. Allowed: ${allowed}.`
998
1045
  };
@@ -1000,8 +1047,8 @@ var HtmlDiagnostics = {
1000
1047
  roleNotPermitted(tag, role, allowedRoles) {
1001
1048
  const allowed = allowedRoles.length > 0 ? allowedRoles.map((r) => `"${r}"`).join(", ") : "none \u2014 no explicit role is permitted on this element";
1002
1049
  return {
1003
- code: DiagnosticCode3.HtmlRoleNotPermitted,
1004
- category: DiagnosticCategory3.HTML,
1050
+ code: DiagnosticCode4.HtmlRoleNotPermitted,
1051
+ category: DiagnosticCategory4.HTML,
1005
1052
  severity: "error",
1006
1053
  message: `role="${role}" is not permitted on <${tag}>. Allowed alternate role(s): ${allowed}.`,
1007
1054
  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.'
@@ -1012,8 +1059,8 @@ var HtmlDiagnostics = {
1012
1059
  input: {
1013
1060
  unsupportedType(type) {
1014
1061
  return {
1015
- code: DiagnosticCode3.HtmlInputUnsupportedType,
1016
- category: DiagnosticCategory3.HTML,
1062
+ code: DiagnosticCode4.HtmlInputUnsupportedType,
1063
+ category: DiagnosticCategory4.HTML,
1017
1064
  severity: "warning",
1018
1065
  message: `type="${type}" is not a value defined by the HTML specification. Browsers silently fall back to type="text".`,
1019
1066
  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).',
@@ -1033,7 +1080,7 @@ var HtmlDiagnostics = {
1033
1080
  if (!code) throw new Error(`No DiagnosticCode registered for input attribute "${attribute}"`);
1034
1081
  return {
1035
1082
  code,
1036
- category: DiagnosticCategory3.HTML,
1083
+ category: DiagnosticCategory4.HTML,
1037
1084
  severity: "warning",
1038
1085
  message: `"${attribute}" is ignored on <input type="${type}">.`,
1039
1086
  rationale: `"${attribute}" only has an effect when type is one of: ${allowed}. Browsers silently ignore it on other input types.`,
@@ -1045,18 +1092,30 @@ var HtmlDiagnostics = {
1045
1092
  ]
1046
1093
  };
1047
1094
  }
1095
+ },
1096
+ // Reserved for <a>-specific facts (HTML3201–3299, see codes.ts).
1097
+ anchor: {
1098
+ dangerousHref(href) {
1099
+ return {
1100
+ code: DiagnosticCode4.HtmlAnchorDangerousHref,
1101
+ category: DiagnosticCategory4.HTML,
1102
+ severity: "warning",
1103
+ message: `href="${href}" uses a scheme that executes attacker-controlled content when navigated to. It has been removed.`,
1104
+ rationale: '"javascript:", "data:", and "vbscript:" URLs are a common XSS vector when href comes from untrusted input \u2014 none of the three are needed for legitimate navigation.'
1105
+ };
1106
+ }
1048
1107
  }
1049
1108
  };
1050
1109
 
1051
1110
  // ../../lib/contract/src/diagnostics/input-accessibility.ts
1052
- import { DiagnosticCategory as DiagnosticCategory4, DiagnosticCode as DiagnosticCode4 } from "../_shared/diagnostics.js";
1111
+ import { DiagnosticCategory as DiagnosticCategory5, DiagnosticCode as DiagnosticCode5 } from "../_shared/diagnostics.js";
1053
1112
  function accessibilityFact(input) {
1054
- return { category: DiagnosticCategory4.Accessibility, ...input };
1113
+ return { category: DiagnosticCategory5.Accessibility, ...input };
1055
1114
  }
1056
1115
  var InputAccessibilityDiagnostics = {
1057
1116
  missingAccessibleName() {
1058
1117
  return accessibilityFact({
1059
- code: DiagnosticCode4.A11yInputMissingAccessibleName,
1118
+ code: DiagnosticCode5.A11yInputMissingAccessibleName,
1060
1119
  severity: "warning",
1061
1120
  message: "This input has no accessible name. Add an associated <label>, aria-label, or aria-labelledby.",
1062
1121
  rationale: "Assistive technology announces a form field by its accessible name; without one, users of screen readers cannot tell what the field is for.",
@@ -1071,7 +1130,7 @@ var InputAccessibilityDiagnostics = {
1071
1130
  },
1072
1131
  placeholderIsNotLabel() {
1073
1132
  return accessibilityFact({
1074
- code: DiagnosticCode4.A11yInputPlaceholderNotLabel,
1133
+ code: DiagnosticCode5.A11yInputPlaceholderNotLabel,
1075
1134
  severity: "warning",
1076
1135
  message: "Placeholder text does not provide an accessible name. Add an associated <label>, aria-label, or aria-labelledby.",
1077
1136
  rationale: "Placeholder text disappears as users interact with the field and is not treated as the control's accessible name by many assistive technologies.",
@@ -1086,7 +1145,7 @@ var InputAccessibilityDiagnostics = {
1086
1145
  },
1087
1146
  passwordMissingAutocomplete() {
1088
1147
  return accessibilityFact({
1089
- code: DiagnosticCode4.A11yInputPasswordAutocomplete,
1148
+ code: DiagnosticCode5.A11yInputPasswordAutocomplete,
1090
1149
  severity: "warning",
1091
1150
  message: "Password inputs should specify an autoComplete value.",
1092
1151
  rationale: "Without an explicit autocomplete hint, password managers and browsers cannot reliably tell a sign-in field apart from a password-creation field.",
@@ -1104,7 +1163,7 @@ var InputAccessibilityDiagnostics = {
1104
1163
  },
1105
1164
  requiredReadOnlyConflict() {
1106
1165
  return accessibilityFact({
1107
- code: DiagnosticCode4.A11yInputRequiredReadOnlyConflict,
1166
+ code: DiagnosticCode5.A11yInputRequiredReadOnlyConflict,
1108
1167
  severity: "warning",
1109
1168
  message: "The required and readOnly attributes are both present. A read-only field cannot satisfy required validation through user interaction.",
1110
1169
  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."
@@ -1851,6 +1910,27 @@ var AriaPolicyEngine = class _AriaPolicyEngine extends InvariantBase {
1851
1910
  };
1852
1911
 
1853
1912
  // ../../lib/contract/src/aria/factories.ts
1913
+ function invalidWithoutFix(input) {
1914
+ return {
1915
+ valid: false,
1916
+ fixable: false,
1917
+ severity: input.severity,
1918
+ ...isDefined(input.attribute) && { attribute: input.attribute },
1919
+ ...isDefined(input.message) && { message: input.message },
1920
+ ...isDefined(input.diagnostic) && { diagnostic: input.diagnostic }
1921
+ };
1922
+ }
1923
+ function invalidWithFix(input) {
1924
+ return {
1925
+ valid: false,
1926
+ fixable: true,
1927
+ severity: input.severity,
1928
+ ...isDefined(input.attribute) && { attribute: input.attribute },
1929
+ ...isDefined(input.message) && { message: input.message },
1930
+ ...isDefined(input.diagnostic) && { diagnostic: input.diagnostic },
1931
+ fix: input.fix
1932
+ };
1933
+ }
1854
1934
  function removeProp(props, key) {
1855
1935
  const next = { ...props };
1856
1936
  delete next[key];
@@ -1866,6 +1946,34 @@ function removeAttributeFix(attribute) {
1866
1946
  }
1867
1947
  });
1868
1948
  }
1949
+ function defineRuleMetadata(rule, metadata2) {
1950
+ const descriptors = {};
1951
+ for (const key of Object.keys(metadata2)) {
1952
+ descriptors[key] = { value: metadata2[key], enumerable: false };
1953
+ }
1954
+ Object.defineProperties(rule, descriptors);
1955
+ return rule;
1956
+ }
1957
+ function createRemoveAttributeRule(attribute, options) {
1958
+ const { when, severity = "warning", message, diagnostic, readsProps, tags } = options;
1959
+ const fix = removeAttributeFix(attribute);
1960
+ const rule = (context) => {
1961
+ if (!when(context)) return [];
1962
+ return [
1963
+ invalidWithFix({
1964
+ severity,
1965
+ attribute,
1966
+ ...isDefined(message) && { message },
1967
+ ...isDefined(diagnostic) && { diagnostic: diagnostic(context) },
1968
+ fix
1969
+ })
1970
+ ];
1971
+ };
1972
+ return defineRuleMetadata(rule, {
1973
+ ...isDefined(readsProps) && { readsProps },
1974
+ ...isDefined(tags) && { tags }
1975
+ });
1976
+ }
1869
1977
 
1870
1978
  // ../../lib/contract/src/children/get-type-name.ts
1871
1979
  function getTypeName(value) {
@@ -2254,6 +2362,52 @@ var P_BLOCKED_TAGS = [
2254
2362
  "ul"
2255
2363
  ];
2256
2364
 
2365
+ // ../core/src/html/anchor-rules.ts
2366
+ var DANGEROUS_URL_SCHEMES = ["javascript:", "data:", "vbscript:"];
2367
+ function normalizeForSchemeCheck(href) {
2368
+ return href.replace(/[\x00-\x20]/g, "").toLowerCase();
2369
+ }
2370
+ function isDangerousUrl(href) {
2371
+ if (!isString(href)) return false;
2372
+ const normalized = normalizeForSchemeCheck(href);
2373
+ return DANGEROUS_URL_SCHEMES.some((scheme) => normalized.startsWith(scheme));
2374
+ }
2375
+ var dangerousHrefRule = createRemoveAttributeRule("href", {
2376
+ when: ({ props }) => isDangerousUrl(props.href),
2377
+ severity: "warning",
2378
+ diagnostic: ({ props }) => HtmlDiagnostics.anchor.dangerousHref(props.href),
2379
+ readsProps: ["href"],
2380
+ tags: ["a"]
2381
+ });
2382
+ var roleButtonWithHrefRule = Object.assign(
2383
+ ({ props }) => {
2384
+ if (props.role !== "button" || !isString(props.href) || props.href.length === 0) {
2385
+ return [];
2386
+ }
2387
+ const diagnostic = AnchorAccessibilityDiagnostics.roleButtonWithHref();
2388
+ return [invalidWithoutFix({ severity: diagnostic.severity, attribute: "role", diagnostic })];
2389
+ },
2390
+ { readsProps: ["role", "href"], tags: ["a"] }
2391
+ );
2392
+ var ariaDisabledInertRule = Object.assign(
2393
+ ({ props }) => {
2394
+ const ariaDisabled = props["aria-disabled"];
2395
+ const isDisabled = ariaDisabled === true || ariaDisabled === "true";
2396
+ const hasHref = isString(props.href) && props.href.length > 0;
2397
+ if (!isDisabled || !hasHref) return [];
2398
+ const diagnostic = AnchorAccessibilityDiagnostics.ariaDisabledInert();
2399
+ return [
2400
+ invalidWithoutFix({ severity: diagnostic.severity, attribute: "aria-disabled", diagnostic })
2401
+ ];
2402
+ },
2403
+ { readsProps: ["aria-disabled", "href"], tags: ["a"] }
2404
+ );
2405
+ var ANCHOR_RULES = [
2406
+ dangerousHrefRule,
2407
+ roleButtonWithHrefRule,
2408
+ ariaDisabledInertRule
2409
+ ];
2410
+
2257
2411
  // ../core/src/html/spec/vocabulary/input.ts
2258
2412
  var TEXT_INPUT_TYPES = ["text", "search", "url", "tel", "email", "password"];
2259
2413
  var NUMERIC_INPUT_TYPES = [
@@ -2333,12 +2487,12 @@ function createInputAttributeTypeRule({
2333
2487
  // ../core/src/html/spec/validators/mutually-exclusive-validator.ts
2334
2488
  function createMutuallyExclusiveRule({
2335
2489
  props: conflictingProps,
2336
- diagnostic: createDiagnostic
2490
+ diagnostic: createDiagnostic2
2337
2491
  }) {
2338
2492
  const [first, second] = conflictingProps;
2339
2493
  const rule = ({ tag, props }) => {
2340
2494
  if (tag !== "input" || !props[first] || !props[second]) return [];
2341
- const diagnostic = createDiagnostic();
2495
+ const diagnostic = createDiagnostic2();
2342
2496
  return [{ valid: false, fixable: false, severity: diagnostic.severity, diagnostic }];
2343
2497
  };
2344
2498
  return Object.assign(rule, { readsProps: conflictingProps, tags: ["input"] });
@@ -2718,7 +2872,8 @@ var HTML_ARIA_RULES = [
2718
2872
  landmarkRoleRule,
2719
2873
  landmarkAccessibleNameRule,
2720
2874
  roleNotPermittedRule,
2721
- ...INPUT_RULES
2875
+ ...INPUT_RULES,
2876
+ ...ANCHOR_RULES
2722
2877
  ];
2723
2878
 
2724
2879
  // ../core/src/html/contracts/helpers.ts
@@ -3128,19 +3283,6 @@ function createClassPipeline(resolved) {
3128
3283
  };
3129
3284
  }
3130
3285
 
3131
- // ../../lib/pipeline-kit/src/factory/define-pipeline.ts
3132
- function definePipeline(factory) {
3133
- const cache = /* @__PURE__ */ new WeakMap();
3134
- return (resolved) => {
3135
- let pipeline = cache.get(resolved);
3136
- if (!pipeline) {
3137
- pipeline = factory(resolved);
3138
- cache.set(resolved, pipeline);
3139
- }
3140
- return pipeline;
3141
- };
3142
- }
3143
-
3144
3286
  // ../core/src/options/resolve-factory-options.ts
3145
3287
  import { resolveDiagnostics, silentDiagnostics } from "../_shared/diagnostics.js";
3146
3288
  var EMPTY_VARIANT_KEYS = /* @__PURE__ */ new Set();
@@ -3244,25 +3386,66 @@ function validateRenderProps(diagnostics, options, props, recipeKey) {
3244
3386
  }
3245
3387
  }
3246
3388
 
3389
+ // ../../lib/pipeline-kit/src/factory/define-pipeline.ts
3390
+ function definePipeline(factory) {
3391
+ const cache = /* @__PURE__ */ new WeakMap();
3392
+ return (resolved) => {
3393
+ let pipeline = cache.get(resolved);
3394
+ if (!pipeline) {
3395
+ pipeline = factory(resolved);
3396
+ cache.set(resolved, pipeline);
3397
+ }
3398
+ return pipeline;
3399
+ };
3400
+ }
3401
+
3402
+ // ../core/src/factory/pipelines/generic.ts
3403
+ var createTagPipeline = (resolved) => makeResolveTag(resolved.defaultTag);
3404
+ var createPropsPipeline = (resolved) => (props) => mergeProps(resolved.defaultProps, props);
3405
+ var createHtmlPropNormalizersPipeline = () => getHtmlPropNormalizers;
3406
+ var createHtmlChildrenEvaluatorPipeline = () => getHtmlChildrenEvaluator;
3407
+ var createStylingClassPipeline = (resolved) => createClassPipeline(resolved);
3408
+ var memoizedTagPipeline = definePipeline(createTagPipeline);
3409
+ var memoizedPropsPipeline = definePipeline(createPropsPipeline);
3410
+ var memoizedHtmlPropNormalizersPipeline = definePipeline(createHtmlPropNormalizersPipeline);
3411
+ var memoizedHtmlChildrenEvaluatorPipeline = definePipeline(
3412
+ createHtmlChildrenEvaluatorPipeline
3413
+ );
3414
+ var memoizedClassPipeline = definePipeline(createStylingClassPipeline);
3415
+
3416
+ // ../core/src/factory/pipelines/aria.ts
3417
+ function resolveAriaRules(resolved) {
3418
+ return [.../* @__PURE__ */ new Set([...HTML_ARIA_RULES, ...resolved.ariaRules ?? []])];
3419
+ }
3420
+ var createAriaPipeline = (resolved) => {
3421
+ const rules = resolveAriaRules(resolved);
3422
+ const engine = new AriaPolicyEngine(resolved.diagnostics, rules.length ? { rules } : void 0);
3423
+ return (tag, props) => engine.validate(tag, props);
3424
+ };
3425
+ var memoizedAriaPipeline = definePipeline(createAriaPipeline);
3426
+ function resolveAriaPassthrough(_tag, props) {
3427
+ return { props };
3428
+ }
3429
+
3247
3430
  // ../core/src/factory/plugin-invariants.ts
3248
3431
  import { throwDiagnostics } from "../_shared/diagnostics.js";
3249
3432
 
3250
3433
  // ../core/src/factory/plugin-diagnostics.ts
3251
- import { DiagnosticCategory as DiagnosticCategory5, DiagnosticCode as DiagnosticCode5 } from "../_shared/diagnostics.js";
3434
+ import { DiagnosticCategory as DiagnosticCategory6, DiagnosticCode as DiagnosticCode6 } from "../_shared/diagnostics.js";
3252
3435
  var PluginDiagnostics = {
3253
3436
  invalidShape(received) {
3254
- const got = received === null ? "null" : typeof received;
3437
+ const got = isNull(received) ? "null" : typeof received;
3255
3438
  return {
3256
- code: DiagnosticCode5.PluginInvalidShape,
3257
- category: DiagnosticCategory5.Internal,
3439
+ code: DiagnosticCode6.PluginInvalidShape,
3440
+ category: DiagnosticCategory6.Internal,
3258
3441
  message: `[praxis-kit] Plugin factory must return an object with a 'pipeline' function. Got: ${got}.`
3259
3442
  };
3260
3443
  },
3261
3444
  pipelineReturnType(received) {
3262
- const got = received === null ? "null" : Array.isArray(received) ? "array" : typeof received;
3445
+ const got = isNull(received) ? "null" : Array.isArray(received) ? "array" : typeof received;
3263
3446
  return {
3264
- code: DiagnosticCode5.PluginPipelineReturnType,
3265
- category: DiagnosticCategory5.Internal,
3447
+ code: DiagnosticCode6.PluginPipelineReturnType,
3448
+ category: DiagnosticCategory6.Internal,
3266
3449
  message: `[praxis-kit] Plugin pipeline must return a string. Got: ${got}.`
3267
3450
  };
3268
3451
  }
@@ -3270,60 +3453,41 @@ var PluginDiagnostics = {
3270
3453
 
3271
3454
  // ../core/src/factory/plugin-invariants.ts
3272
3455
  function assertPluginShape(result) {
3273
- if (result === null || typeof result !== "object")
3456
+ if (isNull(result) || !isObject(result))
3274
3457
  throwDiagnostics.error(PluginDiagnostics.invalidShape(result));
3275
3458
  const plugin = result;
3276
- if (typeof plugin.pipeline !== "function")
3277
- throwDiagnostics.error(PluginDiagnostics.invalidShape(result));
3459
+ if (!isFunction(plugin.pipeline)) throwDiagnostics.error(PluginDiagnostics.invalidShape(result));
3278
3460
  }
3279
3461
  function guardPipeline(pipeline) {
3280
3462
  if (process.env.NODE_ENV === "production") return pipeline;
3281
3463
  return function guardedPipeline(tag, props, className, recipe) {
3282
3464
  const result = pipeline(tag, props, className, recipe);
3283
- if (typeof result !== "string")
3284
- throwDiagnostics.error(PluginDiagnostics.pipelineReturnType(result));
3465
+ if (!isString(result)) throwDiagnostics.error(PluginDiagnostics.pipelineReturnType(result));
3285
3466
  return result;
3286
3467
  };
3287
3468
  }
3288
3469
 
3289
- // ../core/src/factory/create-polymorphic2.ts
3290
- var createTagPipeline = (resolved) => makeResolveTag(resolved.defaultTag);
3291
- var createPropsPipeline = (resolved) => (props) => mergeProps(resolved.defaultProps, props);
3292
- var createHtmlPropNormalizersPipeline = () => getHtmlPropNormalizers;
3293
- var createHtmlChildrenEvaluatorPipeline = () => getHtmlChildrenEvaluator;
3294
- var createStylingClassPipeline = (resolved) => createClassPipeline(resolved);
3295
- function resolveAriaRules(resolved) {
3296
- return [.../* @__PURE__ */ new Set([...HTML_ARIA_RULES, ...resolved.ariaRules ?? []])];
3297
- }
3298
- var createAriaPipeline = (resolved) => {
3299
- const rules = resolveAriaRules(resolved);
3300
- const engine = new AriaPolicyEngine(resolved.diagnostics, rules.length ? { rules } : void 0);
3301
- return (tag, props) => engine.validate(tag, props);
3302
- };
3303
- var memoizedTagPipeline = definePipeline(createTagPipeline);
3304
- var memoizedPropsPipeline = definePipeline(createPropsPipeline);
3305
- var memoizedHtmlPropNormalizersPipeline = definePipeline(createHtmlPropNormalizersPipeline);
3306
- var memoizedHtmlChildrenEvaluatorPipeline = definePipeline(createHtmlChildrenEvaluatorPipeline);
3307
- var memoizedClassPipeline = definePipeline(createStylingClassPipeline);
3308
- var memoizedAriaPipeline = definePipeline(createAriaPipeline);
3309
- function resolveAriaPassthrough(_tag, props) {
3310
- return { props };
3311
- }
3470
+ // ../core/src/factory/pipelines/styling.ts
3312
3471
  function resolveClassPlugin(factory, resolved, diagnostics) {
3313
3472
  if (!factory) return { pluginResult: void 0, classPipeline: memoizedClassPipeline(resolved) };
3314
3473
  const pluginResult = factory(resolved, diagnostics);
3315
3474
  assertPluginShape(pluginResult);
3316
3475
  return { pluginResult, classPipeline: guardPipeline(pluginResult.pipeline) };
3317
3476
  }
3318
- function createPolymorphic2(options = {}) {
3477
+
3478
+ // ../core/src/factory/create-polymorphic.ts
3479
+ function eraseResolvedShape(resolved) {
3480
+ return resolved;
3481
+ }
3482
+ function createPolymorphic(options = {}) {
3319
3483
  const baseResolved = resolveFactoryOptions(options);
3320
- const anyBaseResolved = baseResolved;
3484
+ const anyBaseResolved = eraseResolvedShape(baseResolved);
3321
3485
  const resolved = Object.freeze({
3322
3486
  ...baseResolved,
3323
3487
  htmlPropNormalizersFn: memoizedHtmlPropNormalizersPipeline(anyBaseResolved),
3324
3488
  htmlChildrenEvaluatorFn: memoizedHtmlChildrenEvaluatorPipeline(anyBaseResolved)
3325
3489
  });
3326
- const anyResolved = resolved;
3490
+ const anyResolved = eraseResolvedShape(resolved);
3327
3491
  if (process.env.NODE_ENV !== "production") {
3328
3492
  validateFactoryOptions(resolved, resolved.diagnostics);
3329
3493
  }
@@ -3364,7 +3528,7 @@ function enforceAllowedAs(tag, allowedAs, diagnostics, displayName) {
3364
3528
  // ../../lib/adapter-utils/src/runtime/build-core-runtime.ts
3365
3529
  var EMPTY_SET = /* @__PURE__ */ new Set();
3366
3530
  function buildCoreRuntime(normalized) {
3367
- const runtime = createPolymorphic2(normalized);
3531
+ const runtime = createPolymorphic(normalized);
3368
3532
  const ownedKeys = "classPlugin" in runtime ? runtime.classPlugin.ownedKeys ?? EMPTY_SET : EMPTY_SET;
3369
3533
  return { runtime, ownedKeys };
3370
3534
  }