@a13y/react 0.1.2 → 0.1.4

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.
@@ -1,5 +1,5 @@
1
1
  export { A as AccessibleButtonProps, P as PressEvent, U as UseAccessibleButtonProps, a as UseAccessibleButtonReturn, u as useAccessibleButton } from '../use-accessible-button-B0syf-Az.js';
2
- import { AriaRole } from 'react';
2
+ import { AriaRole, RefObject } from 'react';
3
3
 
4
4
  /**
5
5
  * @a13y/react - useAccessibleDialog
@@ -722,4 +722,146 @@ type RequireLabel<T> = T & {
722
722
  label: string;
723
723
  };
724
724
 
725
- export { type DialogBackdropProps, type DialogContainerProps, type DialogDescriptionProps, type DialogTitleProps, type FieldConfig, type FieldProps, type FieldValidator, type FormConfig, type FormState, type FormValidator, type NavigableItemProps, type RequireLabel, type UseAccessibleDialogProps, type UseAccessibleDialogReturn, type UseAccessibleFormReturn, type UseFocusTrapProps, type UseFocusTrapReturn, type UseFormFieldProps, type UseFormFieldReturn, type UseKeyboardNavigationProps, type UseKeyboardNavigationReturn, useAccessibleDialog, useAccessibleForm, useFocusTrap, useFormField, useKeyboardNavigation };
725
+ /**
726
+ * useId Hook
727
+ * Generates unique IDs for accessibility attributes
728
+ * Provides backward compatibility for React < 18
729
+ */
730
+ /**
731
+ * Generates a unique ID for accessibility attributes
732
+ * @param prefix - Optional prefix for the ID
733
+ * @returns A unique ID string
734
+ *
735
+ * @example
736
+ * ```tsx
737
+ * const tooltipId = useId('tooltip');
738
+ * // Returns: "tooltip-1"
739
+ * ```
740
+ */
741
+ declare function useId(prefix?: string): string;
742
+
743
+ /**
744
+ * useReducedMotion Hook
745
+ * Detects user's preference for reduced motion
746
+ */
747
+ /**
748
+ * Detects if the user prefers reduced motion
749
+ * @returns true if the user prefers reduced motion
750
+ *
751
+ * @example
752
+ * ```tsx
753
+ * const prefersReducedMotion = useReducedMotion();
754
+ *
755
+ * <div style={{
756
+ * transition: prefersReducedMotion ? 'none' : 'all 0.3s ease'
757
+ * }}>
758
+ * Content
759
+ * </div>
760
+ * ```
761
+ */
762
+ declare function useReducedMotion(): boolean;
763
+
764
+ /**
765
+ * useClickOutside Hook
766
+ * Detects clicks outside of a referenced element
767
+ */
768
+
769
+ /**
770
+ * Calls a handler when user clicks outside of the referenced element
771
+ * @param ref - React ref to the element
772
+ * @param handler - Function to call when clicking outside
773
+ * @param enabled - Whether the hook is enabled (default: true)
774
+ *
775
+ * @example
776
+ * ```tsx
777
+ * const menuRef = useRef<HTMLDivElement>(null);
778
+ * useClickOutside(menuRef, () => setIsOpen(false));
779
+ *
780
+ * <div ref={menuRef}>
781
+ * Menu content
782
+ * </div>
783
+ * ```
784
+ */
785
+ declare function useClickOutside(ref: RefObject<HTMLElement | null>, handler: (event: MouseEvent | TouchEvent) => void, enabled?: boolean): void;
786
+
787
+ /**
788
+ * useMediaQuery Hook
789
+ * Tracks the state of a CSS media query
790
+ */
791
+ /**
792
+ * Tracks whether a media query matches
793
+ * @param query - CSS media query string
794
+ * @returns true if the media query matches
795
+ *
796
+ * @example
797
+ * ```tsx
798
+ * const isMobile = useMediaQuery('(max-width: 768px)');
799
+ * const prefersDark = useMediaQuery('(prefers-color-scheme: dark)');
800
+ *
801
+ * {isMobile ? <MobileView /> : <DesktopView />}
802
+ * ```
803
+ */
804
+ declare function useMediaQuery(query: string): boolean;
805
+
806
+ /**
807
+ * useAriaLive Hook
808
+ * Manages ARIA live regions for dynamic announcements
809
+ */
810
+ type AriaLivePoliteness = 'polite' | 'assertive' | 'off';
811
+ interface UseAriaLiveReturn {
812
+ /** Sets a message to be announced */
813
+ setMessage: (message: string) => void;
814
+ /** Clears the current message */
815
+ clearMessage: () => void;
816
+ /** Current message */
817
+ message: string;
818
+ /** Props to spread on the live region element */
819
+ liveRegionProps: {
820
+ role: 'status' | 'alert';
821
+ 'aria-live': AriaLivePoliteness;
822
+ 'aria-atomic': 'true';
823
+ 'aria-relevant': 'additions text';
824
+ };
825
+ }
826
+ /**
827
+ * Creates a managed ARIA live region
828
+ * @param politeness - How urgently the message should be announced
829
+ * @returns Object with message management functions and props
830
+ *
831
+ * @example
832
+ * ```tsx
833
+ * const { setMessage, liveRegionProps } = useAriaLive('polite');
834
+ *
835
+ * <button onClick={() => setMessage('Item added to cart')}>
836
+ * Add to cart
837
+ * </button>
838
+ *
839
+ * <div {...liveRegionProps} className="sr-only">
840
+ * {message}
841
+ * </div>
842
+ * ```
843
+ */
844
+ declare function useAriaLive(politeness?: AriaLivePoliteness): UseAriaLiveReturn;
845
+
846
+ /**
847
+ * useAnnounce Hook
848
+ * Provides a simple way to make screen reader announcements
849
+ */
850
+
851
+ /**
852
+ * Creates a function to announce messages to screen readers
853
+ * @returns Function to announce messages
854
+ *
855
+ * @example
856
+ * ```tsx
857
+ * const announce = useAnnounce();
858
+ *
859
+ * const handleAddToCart = () => {
860
+ * addItem(item);
861
+ * announce('Item added to cart', 'polite');
862
+ * };
863
+ * ```
864
+ */
865
+ declare function useAnnounce(): (message: string, politeness?: AriaLivePoliteness) => void;
866
+
867
+ export { type AriaLivePoliteness, type DialogBackdropProps, type DialogContainerProps, type DialogDescriptionProps, type DialogTitleProps, type FieldConfig, type FieldProps, type FieldValidator, type FormConfig, type FormState, type FormValidator, type NavigableItemProps, type RequireLabel, type UseAccessibleDialogProps, type UseAccessibleDialogReturn, type UseAccessibleFormReturn, type UseAriaLiveReturn, type UseFocusTrapProps, type UseFocusTrapReturn, type UseFormFieldProps, type UseFormFieldReturn, type UseKeyboardNavigationProps, type UseKeyboardNavigationReturn, useAccessibleDialog, useAccessibleForm, useAnnounce, useAriaLive, useClickOutside, useFocusTrap, useFormField, useId, useKeyboardNavigation, useMediaQuery, useReducedMotion };
@@ -642,7 +642,155 @@ var useFormField = (props) => {
642
642
  fieldRef
643
643
  };
644
644
  };
645
+ var idCounter = 0;
646
+ function useId3(prefix = "a13y") {
647
+ const idRef = useRef(void 0);
648
+ if (!idRef.current) {
649
+ idRef.current = `${prefix}-${++idCounter}`;
650
+ }
651
+ return idRef.current;
652
+ }
653
+ function useReducedMotion() {
654
+ const [prefersReducedMotion, setPrefersReducedMotion] = useState(() => {
655
+ if (typeof window === "undefined") return false;
656
+ const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
657
+ return mediaQuery.matches;
658
+ });
659
+ useEffect(() => {
660
+ if (typeof window === "undefined") return;
661
+ const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)");
662
+ const handleChange = (event) => {
663
+ setPrefersReducedMotion(event.matches);
664
+ };
665
+ if (mediaQuery.addEventListener) {
666
+ mediaQuery.addEventListener("change", handleChange);
667
+ return () => mediaQuery.removeEventListener("change", handleChange);
668
+ }
669
+ mediaQuery.addListener(handleChange);
670
+ return () => mediaQuery.removeListener(handleChange);
671
+ }, []);
672
+ return prefersReducedMotion;
673
+ }
674
+ function useClickOutside(ref, handler, enabled = true) {
675
+ useEffect(() => {
676
+ if (!enabled) return;
677
+ const handleClickOutside = (event) => {
678
+ if (!ref.current || ref.current.contains(event.target)) {
679
+ return;
680
+ }
681
+ handler(event);
682
+ };
683
+ document.addEventListener("mousedown", handleClickOutside);
684
+ document.addEventListener("touchstart", handleClickOutside);
685
+ return () => {
686
+ document.removeEventListener("mousedown", handleClickOutside);
687
+ document.removeEventListener("touchstart", handleClickOutside);
688
+ };
689
+ }, [ref, handler, enabled]);
690
+ }
691
+ function useMediaQuery(query) {
692
+ const [matches, setMatches] = useState(() => {
693
+ if (typeof window === "undefined") return false;
694
+ const mediaQuery = window.matchMedia(query);
695
+ return mediaQuery.matches;
696
+ });
697
+ useEffect(() => {
698
+ if (typeof window === "undefined") return;
699
+ const mediaQuery = window.matchMedia(query);
700
+ setMatches(mediaQuery.matches);
701
+ const handleChange = (event) => {
702
+ setMatches(event.matches);
703
+ };
704
+ if (mediaQuery.addEventListener) {
705
+ mediaQuery.addEventListener("change", handleChange);
706
+ return () => mediaQuery.removeEventListener("change", handleChange);
707
+ }
708
+ mediaQuery.addListener(handleChange);
709
+ return () => mediaQuery.removeListener(handleChange);
710
+ }, [query]);
711
+ return matches;
712
+ }
713
+ function useAriaLive(politeness = "polite") {
714
+ const [message, setMessage] = useState("");
715
+ const timeoutRef = useRef(void 0);
716
+ const clearMessage = () => {
717
+ setMessage("");
718
+ if (timeoutRef.current) {
719
+ clearTimeout(timeoutRef.current);
720
+ }
721
+ };
722
+ const setMessageWithClear = (newMessage) => {
723
+ setMessage(newMessage);
724
+ if (timeoutRef.current) {
725
+ clearTimeout(timeoutRef.current);
726
+ }
727
+ timeoutRef.current = setTimeout(() => {
728
+ setMessage("");
729
+ }, 5e3);
730
+ };
731
+ useEffect(() => {
732
+ return () => {
733
+ if (timeoutRef.current) {
734
+ clearTimeout(timeoutRef.current);
735
+ }
736
+ };
737
+ }, []);
738
+ return {
739
+ setMessage: setMessageWithClear,
740
+ clearMessage,
741
+ message,
742
+ liveRegionProps: {
743
+ role: politeness === "assertive" ? "alert" : "status",
744
+ "aria-live": politeness,
745
+ "aria-atomic": "true",
746
+ "aria-relevant": "additions text"
747
+ }
748
+ };
749
+ }
750
+ function useAnnounce() {
751
+ const regionRef = useRef(null);
752
+ useEffect(() => {
753
+ if (!regionRef.current) {
754
+ const region = document.createElement("div");
755
+ region.setAttribute("role", "status");
756
+ region.setAttribute("aria-live", "polite");
757
+ region.setAttribute("aria-atomic", "true");
758
+ region.style.position = "absolute";
759
+ region.style.left = "-10000px";
760
+ region.style.width = "1px";
761
+ region.style.height = "1px";
762
+ region.style.overflow = "hidden";
763
+ document.body.appendChild(region);
764
+ regionRef.current = region;
765
+ }
766
+ return () => {
767
+ if (regionRef.current) {
768
+ document.body.removeChild(regionRef.current);
769
+ regionRef.current = null;
770
+ }
771
+ };
772
+ }, []);
773
+ const announce3 = useCallback(
774
+ (message, politeness = "polite") => {
775
+ if (!regionRef.current) return;
776
+ regionRef.current.setAttribute("aria-live", politeness);
777
+ regionRef.current.textContent = "";
778
+ setTimeout(() => {
779
+ if (regionRef.current) {
780
+ regionRef.current.textContent = message;
781
+ }
782
+ }, 100);
783
+ setTimeout(() => {
784
+ if (regionRef.current) {
785
+ regionRef.current.textContent = "";
786
+ }
787
+ }, 5100);
788
+ },
789
+ []
790
+ );
791
+ return announce3;
792
+ }
645
793
 
646
- export { useAccessibleButton, useAccessibleDialog, useAccessibleForm, useFocusTrap, useFormField, useKeyboardNavigation };
794
+ export { useAccessibleButton, useAccessibleDialog, useAccessibleForm, useAnnounce, useAriaLive, useClickOutside, useFocusTrap, useFormField, useId3 as useId, useKeyboardNavigation, useMediaQuery, useReducedMotion };
647
795
  //# sourceMappingURL=index.js.map
648
796
  //# sourceMappingURL=index.js.map